diff --git a/packages/client/components/app/interface/settings/user/Native.tsx b/packages/client/components/app/interface/settings/user/Native.tsx index a1933696d..61624cf37 100644 --- a/packages/client/components/app/interface/settings/user/Native.tsx +++ b/packages/client/components/app/interface/settings/user/Native.tsx @@ -30,6 +30,17 @@ declare global { minimise(): void; maximise(): void; close(): void; + onceScreenPicker( + onScreenPick: ( + sources: { + idx: number; + name: string; + isFullScreen: boolean; + image?: string; + }[], + ) => void, + ): void; + screenPickerCallback(idx: number, audio: boolean): void; }; desktopConfig: { diff --git a/packages/client/components/modal/modals.tsx b/packages/client/components/modal/modals.tsx index a835c16ee..1be7ec0de 100644 --- a/packages/client/components/modal/modals.tsx +++ b/packages/client/components/modal/modals.tsx @@ -48,6 +48,7 @@ import { PolicyChangeModal } from "./modals/PolicyChange"; import { RenameSessionModal } from "./modals/RenameSession"; import { ReportContentModal } from "./modals/ReportContent"; import { ResetBotTokenModal } from "./modals/ResetBotToken"; +import { ScreenSharePickerModal } from "./modals/ScreenSharePicker"; import { ScreenShareSettingsModal } from "./modals/ScreenShareSettings"; import { ServerIdentityModal } from "./modals/ServerIdentity"; import { ServerInfoModal } from "./modals/ServerInfo"; @@ -186,6 +187,8 @@ export function RenderModal(props: ActiveModal & { onClose: () => void }) { return ; case "screen_share_settings": return ; + case "screen_share_picker": + return ; default: console.error( "Failed to create modal for", diff --git a/packages/client/components/modal/modals/ScreenSharePicker.tsx b/packages/client/components/modal/modals/ScreenSharePicker.tsx new file mode 100644 index 000000000..0f74c22e3 --- /dev/null +++ b/packages/client/components/modal/modals/ScreenSharePicker.tsx @@ -0,0 +1,116 @@ +import { Trans, useLingui } from "@lingui-solid/solid/macro"; +import { createFormControl, createFormGroup } from "solid-forms"; + +import { useState } from "@revolt/state"; +import { ScreenShareQualityName } from "@revolt/state/stores/Voice"; +import { Avatar, Column, Dialog, DialogProps, Form2, Ripple } from "@revolt/ui"; + +import { createMemo } from "solid-js"; +import { styled } from "styled-system/jsx"; +import { Modals } from "../types"; + +export function ScreenSharePickerModal( + props: DialogProps & Modals & { type: "screen_share_picker" }, +) { + const { voice } = useState(); + const { t } = useLingui(); + + const group = createFormGroup({ + qualityName: createFormControl( + voice.screenShareQuality || "low", + ), + idx: createFormControl([0], { required: true }), + }); + + async function onSubmit() { + props.callback( + group.controls.idx.value[0], + group.controls.qualityName.value, + ); + props.onClose(); + } + + const submit = Form2.useSubmitHandler(group, onSubmit); + + const sources = createMemo(() => + props.sources.map((source) => { + return { item: source, value: source.idx }; + }), + ); + + return ( + { + props.onCancel(); + props.onClose(); + }} + title={t`Pick a Screen to Share`} + actions={[ + { text: Cancel }, + { + text: Go, + onClick: () => { + onSubmit(); + return false; + }, + }, + ]} + > +
+ + + {(val, selected) => ( + + + + {val.name} + + )} + + { + return { + children: quality.fullName, + value: quality.name, + }; + })} + /> + +
+
+ ); +} + +const Item = styled("div", { + base: { + height: "60px", + display: "flex", + position: "relative", + alignItems: "center", + gap: "var(--gap-md)", + padding: "var(--gap-md)", + borderRadius: "var(--borderRadius-sm)", + }, + variants: { + selected: { + true: { + color: "var(--md-sys-color-on-primary)", + background: "var(--md-sys-color-primary)", + }, + }, + }, +}); diff --git a/packages/client/components/modal/types.ts b/packages/client/components/modal/types.ts index 0c8cebb52..de7c6a38e 100644 --- a/packages/client/components/modal/types.ts +++ b/packages/client/components/modal/types.ts @@ -323,4 +323,16 @@ export type Modals = qualities: { name: string; fullName: string }[]; callback: (qualityName: ScreenShareQualityName) => void; onCancel: () => void; + } + | { + type: "screen_share_picker"; + callback: (idx: number, qualityName: ScreenShareQualityName) => void; + qualities: { name: string; fullName: string }[]; + sources: { + idx: number; + name: string; + isFullScreen: boolean; + image?: string; + }[]; + onCancel: () => void; }; diff --git a/packages/client/components/rtc/state.tsx b/packages/client/components/rtc/state.tsx index 39b1b2165..8b5a3a600 100644 --- a/packages/client/components/rtc/state.tsx +++ b/packages/client/components/rtc/state.tsx @@ -318,12 +318,37 @@ class Voice { async toggleScreenshare() { const room = this.room(); if (!room) throw "invalid state"; + if (this.screenshare()) { await room.localParticipant.setScreenShareEnabled(false); this.#setScreenshare(room.localParticipant.isScreenShareEnabled); } else { const qualities = this.getEnabledScreenShareQualities(); + let screenPickerQualityName: ScreenShareQualityName | undefined; + + // Register the modal on screen picker handler if it exists + if (window.native && window.native.onceScreenPicker) { + window.native.onceScreenPicker((sources) => { + this.openModal({ + type: "screen_share_picker", + onCancel: () => { + window.native.screenPickerCallback(-1, false); + }, + callback: (idx: number, qualityName: ScreenShareQualityName) => { + // TODO: Change this to true when enabling screen share audio. + window.native.screenPickerCallback(idx, false); + screenPickerQualityName = qualityName; + }, + sources: sources, + qualities: Object.keys(qualities).map((k) => { + const v = qualities[k as ScreenShareQualityName]!; + return { name: k, fullName: v.fullName }; + }), + }); + }); + } + try { const localTrack = await room.localParticipant.setScreenShareEnabled( true, @@ -360,7 +385,9 @@ class Voice { } }; - if (this.#settings.screenShareQualityAsk) { + if (screenPickerQualityName) { + callback(screenPickerQualityName || "low"); + } else if (this.#settings.screenShareQualityAsk) { if (Object.keys(qualities).length > 1) { localTrack.pauseUpstream(); this.openModal({ diff --git a/packages/client/components/ui/components/utils/Form2.tsx b/packages/client/components/ui/components/utils/Form2.tsx index 0e34bb5b4..ff27492e2 100644 --- a/packages/client/components/ui/components/utils/Form2.tsx +++ b/packages/client/components/ui/components/utils/Form2.tsx @@ -282,6 +282,7 @@ function FormVirtualSelect(props: { children: (item: T, selected?: boolean) => JSX.Element; itemHeight?: number; selectHeight?: string; + isMaxHeight?: boolean; multiple?: boolean; }) { let ref; @@ -290,9 +291,15 @@ function FormVirtualSelect(props: {
(props: { onClick={() => { if (!props.multiple) { props.control.setValue( - props.control.value[0] === item.item.value + props.control.value[0] === item.item.value && + !props.control.isRequired ? [] : [item.item.value], );