Make the screen share volume button accessible on mobile
In landscape orientation the button would be buried underneath the footer, which would block interaction with it. This commit changes the footer to not show in cases where a button has been pressed.
This commit is contained in:
@@ -65,6 +65,7 @@ Please see LICENSE in the repository root for full details.
|
|||||||
.footer.overlay.hidden {
|
.footer.overlay.hidden {
|
||||||
display: grid;
|
display: grid;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer.overlay:has(:focus-visible) {
|
.footer.overlay:has(:focus-visible) {
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import { IconButton, Text, Tooltip } from "@vector-im/compound-web";
|
|||||||
import { type MatrixClient, type Room as MatrixRoom } from "matrix-js-sdk";
|
import { type MatrixClient, type Room as MatrixRoom } from "matrix-js-sdk";
|
||||||
import {
|
import {
|
||||||
type FC,
|
type FC,
|
||||||
type PointerEvent,
|
type MouseEvent as ReactMouseEvent,
|
||||||
type TouchEvent,
|
type PointerEvent as ReactPointerEvent,
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
useMemo,
|
useMemo,
|
||||||
@@ -110,8 +110,6 @@ import { ObservableScope } from "../state/ObservableScope.ts";
|
|||||||
|
|
||||||
const logger = rootLogger.getChild("[InCallView]");
|
const logger = rootLogger.getChild("[InCallView]");
|
||||||
|
|
||||||
const maxTapDurationMs = 400;
|
|
||||||
|
|
||||||
export interface ActiveCallProps extends Omit<
|
export interface ActiveCallProps extends Omit<
|
||||||
InCallViewProps,
|
InCallViewProps,
|
||||||
"vm" | "livekitRoom" | "connState"
|
"vm" | "livekitRoom" | "connState"
|
||||||
@@ -334,40 +332,20 @@ export const InCallView: FC<InCallViewProps> = ({
|
|||||||
) : null;
|
) : null;
|
||||||
}, [ringOverlay]);
|
}, [ringOverlay]);
|
||||||
|
|
||||||
// Ideally we could detect taps by listening for click events and checking
|
const onViewClick = useCallback(
|
||||||
// that the pointerType of the event is "touch", but this isn't yet supported
|
(e: ReactMouseEvent) => {
|
||||||
// in Safari: https://developer.mozilla.org/en-US/docs/Web/API/Element/click_event#browser_compatibility
|
if (
|
||||||
// Instead we have to watch for sufficiently fast touch events.
|
(e.nativeEvent as PointerEvent).pointerType === "touch" &&
|
||||||
const touchStart = useRef<number | null>(null);
|
// If an interactive element was tapped, don't count this as a tap on the screen
|
||||||
const onTouchStart = useCallback(() => (touchStart.current = Date.now()), []);
|
(e.target as Element).closest?.("button, input") === null
|
||||||
const onTouchEnd = useCallback(() => {
|
)
|
||||||
const start = touchStart.current;
|
vm.tapScreen();
|
||||||
if (start !== null && Date.now() - start <= maxTapDurationMs)
|
|
||||||
vm.tapScreen();
|
|
||||||
touchStart.current = null;
|
|
||||||
}, [vm]);
|
|
||||||
const onTouchCancel = useCallback(() => (touchStart.current = null), []);
|
|
||||||
|
|
||||||
// We also need to tell the footer controls to prevent touch events from
|
|
||||||
// bubbling up, or else the footer will be dismissed before a click/change
|
|
||||||
// event can be registered on the control
|
|
||||||
const onControlsTouchEnd = useCallback(
|
|
||||||
(e: TouchEvent) => {
|
|
||||||
// Somehow applying pointer-events: none to the controls when the footer
|
|
||||||
// is hidden is not enough to stop clicks from happening as the footer
|
|
||||||
// becomes visible, so we check manually whether the footer is shown
|
|
||||||
if (showFooter) {
|
|
||||||
e.stopPropagation();
|
|
||||||
vm.tapControls();
|
|
||||||
} else {
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[vm, showFooter],
|
[vm],
|
||||||
);
|
);
|
||||||
|
|
||||||
const onPointerMove = useCallback(
|
const onPointerMove = useCallback(
|
||||||
(e: PointerEvent) => {
|
(e: ReactPointerEvent) => {
|
||||||
if (e.pointerType === "mouse") vm.hoverScreen();
|
if (e.pointerType === "mouse") vm.hoverScreen();
|
||||||
},
|
},
|
||||||
[vm],
|
[vm],
|
||||||
@@ -667,7 +645,6 @@ export const InCallView: FC<InCallViewProps> = ({
|
|||||||
key="audio"
|
key="audio"
|
||||||
muted={!audioEnabled}
|
muted={!audioEnabled}
|
||||||
onClick={toggleAudio ?? undefined}
|
onClick={toggleAudio ?? undefined}
|
||||||
onTouchEnd={onControlsTouchEnd}
|
|
||||||
disabled={toggleAudio === null}
|
disabled={toggleAudio === null}
|
||||||
data-testid="incall_mute"
|
data-testid="incall_mute"
|
||||||
/>,
|
/>,
|
||||||
@@ -675,7 +652,6 @@ export const InCallView: FC<InCallViewProps> = ({
|
|||||||
key="video"
|
key="video"
|
||||||
muted={!videoEnabled}
|
muted={!videoEnabled}
|
||||||
onClick={toggleVideo ?? undefined}
|
onClick={toggleVideo ?? undefined}
|
||||||
onTouchEnd={onControlsTouchEnd}
|
|
||||||
disabled={toggleVideo === null}
|
disabled={toggleVideo === null}
|
||||||
data-testid="incall_videomute"
|
data-testid="incall_videomute"
|
||||||
/>,
|
/>,
|
||||||
@@ -687,7 +663,6 @@ export const InCallView: FC<InCallViewProps> = ({
|
|||||||
className={styles.shareScreen}
|
className={styles.shareScreen}
|
||||||
enabled={sharingScreen}
|
enabled={sharingScreen}
|
||||||
onClick={vm.toggleScreenSharing}
|
onClick={vm.toggleScreenSharing}
|
||||||
onTouchEnd={onControlsTouchEnd}
|
|
||||||
data-testid="incall_screenshare"
|
data-testid="incall_screenshare"
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
@@ -699,18 +674,11 @@ export const InCallView: FC<InCallViewProps> = ({
|
|||||||
key="raise_hand"
|
key="raise_hand"
|
||||||
className={styles.raiseHand}
|
className={styles.raiseHand}
|
||||||
identifier={`${client.getUserId()}:${client.getDeviceId()}`}
|
identifier={`${client.getUserId()}:${client.getDeviceId()}`}
|
||||||
onTouchEnd={onControlsTouchEnd}
|
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (layout.type !== "pip")
|
if (layout.type !== "pip")
|
||||||
buttons.push(
|
buttons.push(<SettingsButton key="settings" onClick={openSettings} />);
|
||||||
<SettingsButton
|
|
||||||
key="settings"
|
|
||||||
onClick={openSettings}
|
|
||||||
onTouchEnd={onControlsTouchEnd}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
buttons.push(
|
buttons.push(
|
||||||
<EndCallButton
|
<EndCallButton
|
||||||
@@ -718,7 +686,6 @@ export const InCallView: FC<InCallViewProps> = ({
|
|||||||
onClick={function (): void {
|
onClick={function (): void {
|
||||||
vm.hangup();
|
vm.hangup();
|
||||||
}}
|
}}
|
||||||
onTouchEnd={onControlsTouchEnd}
|
|
||||||
data-testid="incall_leave"
|
data-testid="incall_leave"
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
@@ -751,7 +718,6 @@ export const InCallView: FC<InCallViewProps> = ({
|
|||||||
className={styles.layout}
|
className={styles.layout}
|
||||||
layout={gridMode}
|
layout={gridMode}
|
||||||
setLayout={setGridMode}
|
setLayout={setGridMode}
|
||||||
onTouchEnd={onControlsTouchEnd}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -760,12 +726,13 @@ export const InCallView: FC<InCallViewProps> = ({
|
|||||||
const allConnections = useBehavior(vm.allConnections$);
|
const allConnections = useBehavior(vm.allConnections$);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
// The onClick handler here exists to control the visibility of the footer,
|
||||||
|
// and the footer is also viewable by moving focus into it, so this is fine.
|
||||||
|
// eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events
|
||||||
<div
|
<div
|
||||||
className={styles.inRoom}
|
className={styles.inRoom}
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
onTouchStart={onTouchStart}
|
onClick={onViewClick}
|
||||||
onTouchEnd={onTouchEnd}
|
|
||||||
onTouchCancel={onTouchCancel}
|
|
||||||
onPointerMove={onPointerMove}
|
onPointerMove={onPointerMove}
|
||||||
onPointerOut={onPointerOut}
|
onPointerOut={onPointerOut}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { type ChangeEvent, type FC, type TouchEvent, useCallback } from "react";
|
import { type ChangeEvent, type FC, useCallback } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Tooltip } from "@vector-im/compound-web";
|
import { Tooltip } from "@vector-im/compound-web";
|
||||||
import {
|
import {
|
||||||
@@ -22,15 +22,9 @@ interface Props {
|
|||||||
layout: Layout;
|
layout: Layout;
|
||||||
setLayout: (layout: Layout) => void;
|
setLayout: (layout: Layout) => void;
|
||||||
className?: string;
|
className?: string;
|
||||||
onTouchEnd?: (e: TouchEvent) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LayoutToggle: FC<Props> = ({
|
export const LayoutToggle: FC<Props> = ({ layout, setLayout, className }) => {
|
||||||
layout,
|
|
||||||
setLayout,
|
|
||||||
className,
|
|
||||||
onTouchEnd,
|
|
||||||
}) => {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const onChange = useCallback(
|
const onChange = useCallback(
|
||||||
@@ -47,7 +41,6 @@ export const LayoutToggle: FC<Props> = ({
|
|||||||
value="spotlight"
|
value="spotlight"
|
||||||
checked={layout === "spotlight"}
|
checked={layout === "spotlight"}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
onTouchEnd={onTouchEnd}
|
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<SpotlightIcon aria-hidden width={24} height={24} />
|
<SpotlightIcon aria-hidden width={24} height={24} />
|
||||||
@@ -58,7 +51,6 @@ export const LayoutToggle: FC<Props> = ({
|
|||||||
value="grid"
|
value="grid"
|
||||||
checked={layout === "grid"}
|
checked={layout === "grid"}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
onTouchEnd={onTouchEnd}
|
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<GridIcon aria-hidden width={24} height={24} />
|
<GridIcon aria-hidden width={24} height={24} />
|
||||||
|
|||||||
@@ -84,7 +84,6 @@ Please see LICENSE in the repository root for full details.
|
|||||||
.expand {
|
.expand {
|
||||||
appearance: none;
|
appearance: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
opacity: 0;
|
|
||||||
padding: var(--cpd-space-2x);
|
padding: var(--cpd-space-2x);
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: var(--cpd-radius-pill-effect);
|
border-radius: var(--cpd-radius-pill-effect);
|
||||||
@@ -148,17 +147,21 @@ Please see LICENSE in the repository root for full details.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.expand:active {
|
.expand:active, .expand[data-state="open"] {
|
||||||
background: var(--cpd-color-gray-100);
|
background: var(--cpd-color-gray-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (hover) {
|
@media (hover) {
|
||||||
|
.tile > div > button {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
.tile:hover > div > button {
|
.tile:hover > div > button {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.tile:has(:focus-visible) > div > button {
|
.tile:has(:focus-visible) > div > button,
|
||||||
|
.tile > div:has([data-state="open"]) > button {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user