From b2bc8edcc1c131eb18035739b88e506df382550d Mon Sep 17 00:00:00 2001 From: Robin Date: Fri, 1 Dec 2023 17:43:09 -0500 Subject: [PATCH] Refactor/redesign video tiles --- package.json | 1 + public/locales/en-GB/app.json | 11 +- src/Overlay.module.css | 2 - src/Slider.module.css | 66 ++ src/Slider.tsx | 68 +++ src/UserMenuContainer.tsx | 11 +- src/button/Button.tsx | 45 -- src/button/VolumeIcon.tsx | 35 -- src/icons/AudioLow.svg | 4 - src/icons/AudioMuted.svg | 3 - src/icons/Fullscreen.svg | 3 - src/icons/FullscreenExit.svg | 3 - src/index.css | 3 + src/observable-utils.ts | 37 ++ src/room/InCallView.module.css | 4 +- src/room/InCallView.tsx | 26 +- src/room/LobbyView.tsx | 7 +- src/settings/SettingsModal.tsx | 47 +- src/state/CallViewModel.ts | 57 +- src/state/TileViewModel.ts | 219 ++++++- src/state/subscribe.tsx | 24 +- src/video-grid/VideoGrid.tsx | 2 +- src/video-grid/VideoTile.module.css | 272 +++++---- src/video-grid/VideoTile.tsx | 574 ++++++++++++------ .../VideoTileSettingsModal.module.css | 120 ---- src/video-grid/VideoTileSettingsModal.tsx | 95 --- vite.config.js | 5 + yarn.lock | 51 +- 28 files changed, 1086 insertions(+), 709 deletions(-) create mode 100644 src/Slider.module.css create mode 100644 src/Slider.tsx delete mode 100644 src/button/VolumeIcon.tsx delete mode 100644 src/icons/AudioLow.svg delete mode 100644 src/icons/AudioMuted.svg delete mode 100644 src/icons/Fullscreen.svg delete mode 100644 src/icons/FullscreenExit.svg create mode 100644 src/observable-utils.ts delete mode 100644 src/video-grid/VideoTileSettingsModal.module.css delete mode 100644 src/video-grid/VideoTileSettingsModal.tsx diff --git a/package.json b/package.json index 6c5a71a3..103b5711 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "@opentelemetry/instrumentation-user-interaction": "^0.34.0", "@opentelemetry/sdk-trace-web": "^1.9.1", "@radix-ui/react-dialog": "^1.0.4", + "@radix-ui/react-slider": "^1.1.2", "@radix-ui/react-visually-hidden": "^1.0.3", "@react-aria/button": "^3.3.4", "@react-aria/focus": "^3.5.0", diff --git a/public/locales/en-GB/app.json b/public/locales/en-GB/app.json index 981299ee..9dca0a19 100644 --- a/public/locales/en-GB/app.json +++ b/public/locales/en-GB/app.json @@ -49,6 +49,7 @@ "home": "Home", "loading": "Loading…", "microphone": "Microphone", + "options": "Options", "password": "Password", "profile": "Profile", "settings": "Settings", @@ -57,10 +58,8 @@ "video": "Video" }, "disconnected_banner": "Connectivity to the server has been lost.", - "exit_fullscreen_button_label": "Exit full screen", "full_screen_view_description": "<0>Submitting debug logs will help us track down the problem.", "full_screen_view_h1": "<0>Oops, something's gone wrong.", - "fullscreen_button_label": "Full screen", "group_call_loader_failed_heading": "Call not found", "group_call_loader_failed_text": "Calls are now end-to-end encrypted and need to be created from the home page. This helps make sure everyone's using the same encryption key.", "hangup_button_label": "End call", @@ -81,7 +80,6 @@ "join_button": "Join call", "leave_button": "Back to recents" }, - "local_volume_label": "Local volume", "log_in": "Log In", "logging_in": "Logging in…", "login_auth_links": "<0>Create an account Or <2>Access as a guest", @@ -145,8 +143,11 @@ "unmute_microphone_button_label": "Unmute microphone", "version": "Version: {{version}}", "video_tile": { - "presenter_label": "{{displayName}} is presenting", - "sfu_participant_local": "You" + "exit_full_screen": "Exit full screen", + "full_screen": "Full screen", + "mute_for_me": "Mute for me", + "sfu_participant_local": "You", + "volume": "Volume" }, "waiting_for_participants": "Waiting for other participants…" } diff --git a/src/Overlay.module.css b/src/Overlay.module.css index 37bcf95d..b211c430 100644 --- a/src/Overlay.module.css +++ b/src/Overlay.module.css @@ -16,7 +16,6 @@ limitations under the License. .bg { position: fixed; - z-index: 100; inset: 0; background: rgba(3, 12, 27, 0.528); } @@ -49,7 +48,6 @@ limitations under the License. .overlay { position: fixed; - z-index: 101; } .overlay.animate { diff --git a/src/Slider.module.css b/src/Slider.module.css new file mode 100644 index 00000000..e6568193 --- /dev/null +++ b/src/Slider.module.css @@ -0,0 +1,66 @@ +/* +Copyright 2023 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.slider { + display: flex; + align-items: center; + position: relative; +} + +.track { + flex-grow: 1; + border-radius: var(--cpd-radius-pill-effect); + background: var(--cpd-color-bg-subtle-primary); + height: var(--cpd-space-2x); + outline: var(--cpd-border-width-1) solid + var(--cpd-color-border-interactive-primary); + outline-offset: calc(-1 * var(--cpd-border-width-1)); + cursor: pointer; + transition: outline-color ease 0.15s; +} + +.track[data-disabled] { + cursor: initial; + outline-color: var(--cpd-color-border-disabled); +} + +.highlight { + background: var(--cpd-color-bg-action-primary-rest); + position: absolute; + block-size: 100%; + border-radius: var(--cpd-radius-pill-effect); + transition: background-color ease 0.15s; +} + +.highlight[data-disabled] { + background: var(--cpd-color-bg-action-primary-disabled); +} + +.handle { + display: block; + block-size: var(--cpd-space-4x); + inline-size: var(--cpd-space-4x); + border-radius: var(--cpd-radius-pill-effect); + background: var(--cpd-color-bg-action-primary-rest); + box-shadow: 0 0 0 2px var(--cpd-color-bg-canvas-default); + cursor: pointer; + transition: background-color ease 0.15s; +} + +.handle[data-disabled] { + cursor: initial; + background: var(--cpd-color-bg-action-primary-disabled); +} diff --git a/src/Slider.tsx b/src/Slider.tsx new file mode 100644 index 00000000..c3471756 --- /dev/null +++ b/src/Slider.tsx @@ -0,0 +1,68 @@ +/* +Copyright 2023 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { FC, useCallback } from "react"; +import { Root, Track, Range, Thumb } from "@radix-ui/react-slider"; +import classNames from "classnames"; + +import styles from "./Slider.module.css"; + +interface Props { + className?: string; + label: string; + value: number; + onValueChange: (value: number) => void; + min: number; + max: number; + step: number; + disabled?: boolean; +} + +/** + * A slider control allowing a value to be selected from a range. + */ +export const Slider: FC = ({ + className, + label, + value, + onValueChange: onValueChangeProp, + min, + max, + step, + disabled, +}) => { + const onValueChange = useCallback( + ([v]: number[]) => onValueChangeProp(v), + [onValueChangeProp], + ); + + return ( + + + + + + + ); +}; diff --git a/src/UserMenuContainer.tsx b/src/UserMenuContainer.tsx index c0155dcb..87149c22 100644 --- a/src/UserMenuContainer.tsx +++ b/src/UserMenuContainer.tsx @@ -19,7 +19,7 @@ import { useHistory, useLocation } from "react-router-dom"; import { useClientLegacy } from "./ClientContext"; import { useProfile } from "./profile/useProfile"; -import { SettingsModal } from "./settings/SettingsModal"; +import { defaultSettingsTab, SettingsModal } from "./settings/SettingsModal"; import { UserMenu } from "./UserMenu"; interface Props { @@ -37,17 +37,17 @@ export const UserMenuContainer: FC = ({ preventNavigation = false }) => { [setSettingsModalOpen], ); - const [defaultSettingsTab, setDefaultSettingsTab] = useState(); + const [settingsTab, setSettingsTab] = useState(defaultSettingsTab); const onAction = useCallback( async (value: string) => { switch (value) { case "user": - setDefaultSettingsTab("profile"); + setSettingsTab("profile"); setSettingsModalOpen(true); break; case "settings": - setDefaultSettingsTab("audio"); + setSettingsTab("audio"); setSettingsModalOpen(true); break; case "logout": @@ -76,9 +76,10 @@ export const UserMenuContainer: FC = ({ preventNavigation = false }) => { {client && ( )} diff --git a/src/button/Button.tsx b/src/button/Button.tsx index e4ad8eb2..5645b859 100644 --- a/src/button/Button.tsx +++ b/src/button/Button.tsx @@ -30,9 +30,6 @@ import SettingsSolidIcon from "@vector-im/compound-design-tokens/icons/settings- import ChevronDownIcon from "@vector-im/compound-design-tokens/icons/chevron-down.svg?react"; import styles from "./Button.module.css"; -import Fullscreen from "../icons/Fullscreen.svg?react"; -import FullscreenExit from "../icons/FullscreenExit.svg?react"; -import { VolumeIcon } from "./VolumeIcon"; export type ButtonVariant = | "default" @@ -230,45 +227,3 @@ export const SettingsButton: FC<{ ); }; - -interface AudioButtonProps extends Omit { - /** - * A number between 0 and 1 - */ - volume: number; -} - -export const AudioButton: FC = ({ volume, ...rest }) => { - const { t } = useTranslation(); - - return ( - - - - ); -}; - -interface FullscreenButtonProps extends Omit { - fullscreen?: boolean; -} - -export const FullscreenButton: FC = ({ - fullscreen, - ...rest -}) => { - const { t } = useTranslation(); - const Icon = fullscreen ? FullscreenExit : Fullscreen; - const label = fullscreen - ? t("exit_fullscreen_button_label") - : t("fullscreen_button_label"); - - return ( - - - - ); -}; diff --git a/src/button/VolumeIcon.tsx b/src/button/VolumeIcon.tsx deleted file mode 100644 index 626dcece..00000000 --- a/src/button/VolumeIcon.tsx +++ /dev/null @@ -1,35 +0,0 @@ -/* -Copyright 2022 New Vector Ltd - - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import { ComponentPropsWithoutRef, FC } from "react"; - -import AudioMuted from "../icons/AudioMuted.svg?react"; -import AudioLow from "../icons/AudioLow.svg?react"; -import Audio from "../icons/Audio.svg?react"; - -interface Props extends ComponentPropsWithoutRef<"svg"> { - /** - * Number between 0 and 1 - */ - volume: number; -} - -export const VolumeIcon: FC = ({ volume, ...rest }) => { - if (volume <= 0) return ; - if (volume <= 0.5) return ; - return