clean up and optimise Screenshare UI & CSS

This commit is contained in:
Vendicated 2025-02-01 19:49:44 +01:00
parent 67a1847cea
commit 00fb658355
No known key found for this signature in database
GPG key ID: D66986BAF75ECF18
4 changed files with 136 additions and 140 deletions

View file

@ -23,11 +23,13 @@ import { Node } from "@vencord/venmic";
import type { Dispatch, SetStateAction } from "react"; import type { Dispatch, SetStateAction } from "react";
import { addPatch } from "renderer/patches/shared"; import { addPatch } from "renderer/patches/shared";
import { useSettings } from "renderer/settings"; import { useSettings } from "renderer/settings";
import { isLinux, isWindows } from "renderer/utils"; import { classNameFactory, isLinux, isWindows } from "renderer/utils";
const StreamResolutions = ["480", "720", "1080", "1440", "2160"] as const; const StreamResolutions = ["480", "720", "1080", "1440", "2160"] as const;
const StreamFps = ["15", "30", "60"] as const; const StreamFps = ["15", "30", "60"] as const;
const cl = classNameFactory("vcd-screen-picker-");
const MediaEngineStore = findStoreLazy("MediaEngineStore"); const MediaEngineStore = findStoreLazy("MediaEngineStore");
export type StreamResolution = (typeof StreamResolutions)[number]; export type StreamResolution = (typeof StreamResolutions)[number];
@ -161,13 +163,21 @@ export function openScreenSharePicker(screens: Source[], skipPicker: boolean) {
function ScreenPicker({ screens, chooseScreen }: { screens: Source[]; chooseScreen: (id: string) => void }) { function ScreenPicker({ screens, chooseScreen }: { screens: Source[]; chooseScreen: (id: string) => void }) {
return ( return (
<div className="vcd-screen-picker-grid"> <div className={cl("screen-grid")}>
{screens.map(({ id, name, url }) => ( {screens.map(({ id, name, url }) => (
<label key={id}> <label key={id} className={cl("screen-label")}>
<input type="radio" name="screen" value={id} onChange={() => chooseScreen(id)} /> <input
type="radio"
className={cl("screen-radio")}
name="screen"
value={id}
onChange={() => chooseScreen(id)}
/>
<img src={url} alt="" /> <img src={url} alt="" />
<Text variant="text-sm/normal">{name}</Text> <Text className={cl("screen-name")} variant="text-sm/normal">
{name}
</Text>
</label> </label>
))} ))}
</div> </div>
@ -187,11 +197,13 @@ function AudioSettingsModal({
return ( return (
<Modals.ModalRoot {...modalProps} size={ModalSize.MEDIUM}> <Modals.ModalRoot {...modalProps} size={ModalSize.MEDIUM}>
<Modals.ModalHeader className="vcd-screen-picker-header"> <Modals.ModalHeader className={cl("header")}>
<Forms.FormTitle tag="h2">Venmic Settings</Forms.FormTitle> <Forms.FormTitle tag="h2" className={cl("header-title")}>
Venmic Settings
</Forms.FormTitle>
<Modals.ModalCloseButton onClick={close} /> <Modals.ModalCloseButton onClick={close} />
</Modals.ModalHeader> </Modals.ModalHeader>
<Modals.ModalContent className="vcd-screen-picker-modal"> <Modals.ModalContent className={cl("modal")}>
<Switch <Switch
hideBorder hideBorder
onChange={v => (Settings.audio = { ...Settings.audio, workaround: v })} onChange={v => (Settings.audio = { ...Settings.audio, workaround: v })}
@ -295,7 +307,7 @@ function AudioSettingsModal({
Device Selection Device Selection
</Switch> </Switch>
</Modals.ModalContent> </Modals.ModalContent>
<Modals.ModalFooter className="vcd-screen-picker-footer"> <Modals.ModalFooter className={cl("footer")}>
<Button color={Button.Colors.TRANSPARENT} onClick={close}> <Button color={Button.Colors.TRANSPARENT} onClick={close}>
Back Back
</Button> </Button>
@ -304,6 +316,34 @@ function AudioSettingsModal({
); );
} }
function OptionRadio(props: {
options: Array<string> | ReadonlyArray<string>;
labels?: Array<string>;
settingsKey: string;
settings: StreamSettings;
setSettings: Dispatch<SetStateAction<StreamSettings>>;
}) {
const { options, setSettings, settings, settingsKey, labels } = props;
return (
<div className={cl("option-radios")}>
{(options as string[]).map((option, idx) => (
<label className={cl("option-radio")} data-checked={settings[settingsKey] === option} key={option}>
<Text variant="text-sm/bold">{labels?.[idx] ?? option}</Text>
<input
className={cl("option-input")}
type="radio"
name="fps"
value={option}
checked={settings[settingsKey] === option}
onChange={() => setSettings(s => ({ ...s, [settingsKey]: option }))}
/>
</label>
))}
</div>
);
}
function StreamSettings({ function StreamSettings({
source, source,
settings, settings,
@ -340,88 +380,47 @@ function StreamSettings({
return ( return (
<div> <div>
<Forms.FormTitle>What you're streaming</Forms.FormTitle> <Forms.FormTitle>What you're streaming</Forms.FormTitle>
<Card className="vcd-screen-picker-card vcd-screen-picker-preview"> <Card className={cl("card", "preview")}>
<img <img src={thumb} alt="" className={cl(isLinux ? "preview-img-linux" : "preview-img")} />
src={thumb}
alt=""
className={isLinux ? "vcd-screen-picker-preview-img-linux" : "vcd-screen-picker-preview-img"}
/>
<Text variant="text-sm/normal">{source.name}</Text> <Text variant="text-sm/normal">{source.name}</Text>
</Card> </Card>
<Forms.FormTitle>Stream Settings</Forms.FormTitle> <Forms.FormTitle>Stream Settings</Forms.FormTitle>
<Card className="vcd-screen-picker-card"> <Card className={cl("card")}>
<div className="vcd-screen-picker-quality"> <div className={cl("quality")}>
<section> <section className={cl("quality-section")}>
<Forms.FormTitle>Resolution</Forms.FormTitle> <Forms.FormTitle>Resolution</Forms.FormTitle>
<div className="vcd-screen-picker-radios"> <OptionRadio
{StreamResolutions.map(res => ( options={StreamResolutions}
<label className="vcd-screen-picker-radio" data-checked={settings.resolution === res}> settingsKey="resolution"
<Text variant="text-sm/bold">{res}</Text> settings={settings}
<input setSettings={setSettings}
type="radio" />
name="resolution"
value={res}
checked={settings.resolution === res}
onChange={() => setSettings(s => ({ ...s, resolution: res }))}
/>
</label>
))}
</div>
</section> </section>
<section> <section className={cl("quality-section")}>
<Forms.FormTitle>Frame Rate</Forms.FormTitle> <Forms.FormTitle>Frame Rate</Forms.FormTitle>
<div className="vcd-screen-picker-radios"> <OptionRadio
{StreamFps.map(fps => ( options={StreamFps}
<label className="vcd-screen-picker-radio" data-checked={settings.fps === fps}> settingsKey="fps"
<Text variant="text-sm/bold">{fps}</Text> settings={settings}
<input setSettings={setSettings}
type="radio" />
name="fps"
value={fps}
checked={settings.fps === fps}
onChange={() => setSettings(s => ({ ...s, fps }))}
/>
</label>
))}
</div>
</section> </section>
</div> </div>
<div className="vcd-screen-picker-quality"> <div className={cl("quality")}>
<section> <section className={cl("quality-section")}>
<Forms.FormTitle>Content Type</Forms.FormTitle> <Forms.FormTitle>Content Type</Forms.FormTitle>
<div> <div>
<div className="vcd-screen-picker-radios"> <OptionRadio
<label options={["motion", "detail"]}
className="vcd-screen-picker-radio" labels={["Prefer Smoothness", "Prefer Clarity"]}
data-checked={settings.contentHint === "motion"} settingsKey="contentHint"
> settings={settings}
<Text variant="text-sm/bold">Prefer Smoothness</Text> setSettings={setSettings}
<input />
type="radio" <div className={cl("hint-description")}>
name="contenthint"
value="motion"
checked={settings.contentHint === "motion"}
onChange={() => setSettings(s => ({ ...s, contentHint: "motion" }))}
/>
</label>
<label
className="vcd-screen-picker-radio"
data-checked={settings.contentHint === "detail"}
>
<Text variant="text-sm/bold">Prefer Clarity</Text>
<input
type="radio"
name="contenthint"
value="detail"
checked={settings.contentHint === "detail"}
onChange={() => setSettings(s => ({ ...s, contentHint: "detail" }))}
/>
</label>
</div>
<div className="vcd-screen-picker-hint-description">
<p> <p>
Choosing "Prefer Clarity" will result in a significantly lower framerate in exchange Choosing "Prefer Clarity" will result in a significantly lower framerate in exchange
for a much sharper and clearer image. for a much sharper and clearer image.
@ -433,7 +432,7 @@ function StreamSettings({
value={settings.audio} value={settings.audio}
onChange={checked => setSettings(s => ({ ...s, audio: checked }))} onChange={checked => setSettings(s => ({ ...s, audio: checked }))}
hideBorder hideBorder
className="vcd-screen-picker-audio" className={cl("audio")}
> >
Stream With Audio Stream With Audio
</Switch> </Switch>
@ -639,7 +638,7 @@ function AudioSourcePickerLinux({
return ( return (
<> <>
<div className={includeSources === "Entire System" ? "vcd-screen-picker-quality" : undefined}> <div className={cl({ quality: includeSources === "Entire System" })}>
<section> <section>
<Forms.FormTitle>{loading ? "Loading Sources..." : "Audio Sources"}</Forms.FormTitle> <Forms.FormTitle>{loading ? "Loading Sources..." : "Audio Sources"}</Forms.FormTitle>
<Select <Select
@ -675,11 +674,7 @@ function AudioSourcePickerLinux({
</section> </section>
)} )}
</div> </div>
<Button <Button color={Button.Colors.TRANSPARENT} onClick={openSettings} className={cl("settings-button")}>
color={Button.Colors.TRANSPARENT}
onClick={openSettings}
className="vcd-screen-picker-settings-button"
>
Open Audio Settings Open Audio Settings
</Button> </Button>
</> </>
@ -710,11 +705,11 @@ function ModalComponent({
return ( return (
<Modals.ModalRoot {...modalProps} size={ModalSize.MEDIUM}> <Modals.ModalRoot {...modalProps} size={ModalSize.MEDIUM}>
<Modals.ModalHeader className="vcd-screen-picker-header"> <Modals.ModalHeader className={cl("header")}>
<Forms.FormTitle tag="h2">ScreenShare</Forms.FormTitle> <Forms.FormTitle tag="h2">ScreenShare</Forms.FormTitle>
<Modals.ModalCloseButton onClick={close} /> <Modals.ModalCloseButton onClick={close} />
</Modals.ModalHeader> </Modals.ModalHeader>
<Modals.ModalContent className="vcd-screen-picker-modal"> <Modals.ModalContent className={cl("modal")}>
{!selected ? ( {!selected ? (
<ScreenPicker screens={screens} chooseScreen={setSelected} /> <ScreenPicker screens={screens} chooseScreen={setSelected} />
) : ( ) : (
@ -726,7 +721,7 @@ function ModalComponent({
/> />
)} )}
</Modals.ModalContent> </Modals.ModalContent>
<Modals.ModalFooter className="vcd-screen-picker-footer"> <Modals.ModalFooter className={cl("footer")}>
<Button <Button
disabled={!selected} disabled={!selected}
onClick={() => { onClick={() => {

View file

@ -2,7 +2,7 @@
padding: 1em; padding: 1em;
} }
.vcd-screen-picker-header h1 { .vcd-screen-picker-header-title {
margin: 0; margin: 0;
} }
@ -15,23 +15,20 @@
flex-grow: 1; flex-grow: 1;
} }
.vcd-screen-picker-grid {
/* Screen Grid */
.vcd-screen-picker-screen-grid {
display: grid; display: grid;
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
gap: 2em 1em; gap: 2em 1em;
} }
.vcd-screen-picker-grid input { .vcd-screen-picker-screen-radio {
appearance: none; appearance: none;
cursor: pointer; cursor: pointer;
} }
.vcd-screen-picker-selected img { .vcd-screen-picker-screen-label {
border: 2px solid var(--brand-500);
border-radius: 3px;
}
.vcd-screen-picker-grid label {
overflow: hidden; overflow: hidden;
padding: 8px; padding: 8px;
cursor: pointer; cursor: pointer;
@ -39,11 +36,11 @@
justify-items: center; justify-items: center;
} }
.vcd-screen-picker-grid label:hover { .vcd-screen-picker-screen-label:hover {
outline: 2px solid var(--brand-500); outline: 2px solid var(--brand-500);
} }
.vcd-screen-picker-grid div { .vcd-screen-picker-screen-name {
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
overflow: hidden; overflow: hidden;
@ -75,37 +72,48 @@
margin-bottom: 1em; margin-bottom: 1em;
} }
.vcd-screen-picker-radio input {
display: none; /* Option Radios */
.vcd-screen-picker-option-radios {
display: flex;
width: 100%;
border-radius: 3px;
} }
.vcd-screen-picker-radio { .vcd-screen-picker-option-radio {
flex: 1 1 auto;
text-align: center;
background-color: var(--background-secondary); background-color: var(--background-secondary);
border: 1px solid var(--primary-800); border: 1px solid var(--primary-800);
padding: 0.3em; padding: 0.3em;
cursor: pointer; cursor: pointer;
} }
.vcd-screen-picker-radio h2 { .vcd-screen-picker-option-radio:first-child {
margin: 0; border-radius: 3px 0 0 3px;
} }
.vcd-screen-picker-radio[data-checked="true"] { .vcd-screen-picker-option-radio:last-child {
border-radius: 0 3px 3px 0;
}
.vcd-screen-picker-option-input {
display: none;
}
.vcd-screen-picker-option-radio[data-checked="true"] {
background-color: var(--brand-500); background-color: var(--brand-500);
border-color: var(--brand-500); border-color: var(--brand-500);
} }
.vcd-screen-picker-radio[data-checked="true"] h2 {
color: var(--interactive-active);
}
.vcd-screen-picker-quality { .vcd-screen-picker-quality {
display: flex; display: flex;
gap: 1em; gap: 1em;
margin-bottom: 0.5em; margin-bottom: 0.5em;
} }
.vcd-screen-picker-quality section { .vcd-screen-picker-quality-section {
flex: 1 1 auto; flex: 1 1 auto;
} }
@ -114,24 +122,6 @@
margin-top: 0.3rem; margin-top: 0.3rem;
} }
.vcd-screen-picker-radios {
display: flex;
width: 100%;
border-radius: 3px;
}
.vcd-screen-picker-radios label {
flex: 1 1 auto;
text-align: center;
}
.vcd-screen-picker-radios label:first-child {
border-radius: 3px 0 0 3px;
}
.vcd-screen-picker-radios label:last-child {
border-radius: 0 3px 3px 0;
}
.vcd-screen-picker-audio { .vcd-screen-picker-audio {
margin-bottom: 0; margin-bottom: 0;

View file

@ -1,15 +1,3 @@
/* Download Desktop button in guilds list */
[class^=listItem_]:has([data-list-item-id=guildsnav___app-download-button]),
[class^=listItem_]:has(+ [class^=listItem_] [data-list-item-id=guildsnav___app-download-button]) {
display: none;
}
/* FIXME: remove this once Discord fixes their css to not explode scrollbars on chromium >=121 */
* {
scrollbar-width: unset !important;
scrollbar-color: unset !important;
}
/* Workaround for making things in the draggable area clickable again on macOS */ /* Workaround for making things in the draggable area clickable again on macOS */
.platform-osx [class*=topic_], .platform-osx [class*=notice_] button { .platform-osx [class*=topic_], .platform-osx [class*=notice_] button {
-webkit-app-region: no-drag; -webkit-app-region: no-drag;

View file

@ -18,3 +18,26 @@ const { platform } = navigator;
export const isWindows = platform.startsWith("Win"); export const isWindows = platform.startsWith("Win");
export const isMac = platform.startsWith("Mac"); export const isMac = platform.startsWith("Mac");
export const isLinux = platform.startsWith("Linux"); export const isLinux = platform.startsWith("Linux");
type ClassNameFactoryArg = string | string[] | Record<string, unknown> | false | null | undefined | 0 | "";
/**
* @param prefix The prefix to add to each class, defaults to `""`
* @returns A classname generator function
* @example
* const cl = classNameFactory("plugin-");
*
* cl("base", ["item", "editable"], { selected: null, disabled: true })
* // => "plugin-base plugin-item plugin-editable plugin-disabled"
*/
export const classNameFactory =
(prefix: string = "") =>
(...args: ClassNameFactoryArg[]) => {
const classNames = new Set<string>();
for (const arg of args) {
if (arg && typeof arg === "string") classNames.add(arg);
else if (Array.isArray(arg)) arg.forEach(name => classNames.add(name));
else if (arg && typeof arg === "object")
Object.entries(arg).forEach(([name, value]) => value && classNames.add(name));
}
return Array.from(classNames, name => prefix + name).join(" ");
};