diff --git a/public/locales/en-GB/app.json b/public/locales/en-GB/app.json index c3899a65..f6a7ed65 100644 --- a/public/locales/en-GB/app.json +++ b/public/locales/en-GB/app.json @@ -116,6 +116,7 @@ "Walkie-talkie call": "Walkie-talkie call", "Walkie-talkie call name": "Walkie-talkie call name", "Yes, join call": "Yes, join call", + "You": "You", "You were disconnected from the call": "You were disconnected from the call", "Your feedback": "Your feedback", "Your recent calls": "Your recent calls" diff --git a/src/App.tsx b/src/App.tsx index 8e9c4bf1..1931a8c7 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -25,7 +25,6 @@ import { LoginPage } from "./auth/LoginPage"; import { RegisterPage } from "./auth/RegisterPage"; import { RoomPage } from "./room/RoomPage"; import { ClientProvider } from "./ClientContext"; -import { usePageFocusStyle } from "./usePageFocusStyle"; import { SequenceDiagramViewerPage } from "./SequenceDiagramViewerPage"; import { InspectorContextProvider } from "./room/GroupCallInspector"; import { CrashView, LoadingView } from "./FullScreenView"; @@ -48,8 +47,6 @@ export default function App({ history }: AppProps) { }); }); - usePageFocusStyle(); - const errorPage = ; return ( diff --git a/src/button/Button.module.css b/src/button/Button.module.css index b1b712e1..d5cc8b15 100644 --- a/src/button/Button.module.css +++ b/src/button/Button.module.css @@ -50,14 +50,14 @@ limitations under the License. background-color: var(--cpd-color-text-action-accent); } -.button:focus, -.toolbarButton:focus, -.toolbarButtonSecondary:focus, -.iconButton:focus, -.iconCopyButton:focus, -.secondary:focus, -.secondaryHangup:focus, -.copyButton:focus { +.button:focus-visible, +.toolbarButton:focus-visible, +.toolbarButtonSecondary:focus-visible, +.iconButton:focus-visible, +.iconCopyButton:focus-visible, +.secondary:focus-visible, +.secondaryHangup:focus-visible, +.copyButton:focus-visible { outline: auto; } @@ -74,7 +74,7 @@ limitations under the License. background-color: var(--cpd-color-bg-canvas-default); color: var(--cpd-color-icon-primary); border: 1px solid var(--cpd-color-gray-400); - box-shadow: 0px 1px 2px 0px rgba(16, 24, 40, 0.05); + box-shadow: var(--subtle-drop-shadow); } .toolbarButton.on, diff --git a/src/index.css b/src/index.css index a1112ff0..366aa596 100644 --- a/src/index.css +++ b/src/index.css @@ -39,19 +39,22 @@ limitations under the License. --font-size-title: calc(24px * var(--font-scale)); --font-size-headline: calc(32px * var(--font-scale)); - --cpd-color-border-accent: var(--cpd-color-green-1100); /* These colors are needed during the transitionary period between the old and new Compound design systems, but should be removed ASAP */ --stopgap-color-on-solid-accent: var(--cpd-color-bg-canvas-default); --stopgap-background-85: rgba(255, 255, 255, 0.85); --stopgap-bgColor3: #444; + --cpd-color-border-accent: var(--cpd-color-green-800); /* The distance to inset non-full-width content from the edge of the window along the inline axis */ --inline-content-inset: max(var(--cpd-space-4x), calc((100vw - 1180px) / 2)); + --small-drop-shadow: 0px 1.2px 2.4px 0px rgba(0, 0, 0, 0.15); + --subtle-drop-shadow: 0px 1px 2px 0px rgba(16, 24, 40, 0.05); } .cpd-theme-dark { + --cpd-color-border-accent: var(--cpd-color-green-1100); --stopgap-color-on-solid-accent: var(--cpd-color-text-primary); --stopgap-background-85: rgba(16, 19, 23, 0.85); } diff --git a/src/room/LayoutToggle.module.css b/src/room/LayoutToggle.module.css index 08df7b17..e54e9447 100644 --- a/src/room/LayoutToggle.module.css +++ b/src/room/LayoutToggle.module.css @@ -34,7 +34,7 @@ limitations under the License. border-radius: var(--cpd-radius-pill-effect); color: var(--cpd-color-icon-primary); background: var(--cpd-color-bg-action-secondary-rest); - box-shadow: 0px 1.2px 2.4px 0px rgba(0, 0, 0, 0.15); + box-shadow: var(--small-drop-shadow); } @media (hover: hover) { diff --git a/src/usePageFocusStyle.module.css b/src/usePageFocusStyle.module.css deleted file mode 100644 index 7e92738a..00000000 --- a/src/usePageFocusStyle.module.css +++ /dev/null @@ -1,19 +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. -*/ - -.hideFocus * { - outline: none !important; -} diff --git a/src/usePageFocusStyle.ts b/src/usePageFocusStyle.ts deleted file mode 100644 index f433d3db..00000000 --- a/src/usePageFocusStyle.ts +++ /dev/null @@ -1,39 +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 { useEffect } from "react"; -import { useFocusVisible } from "@react-aria/interactions"; - -import styles from "./usePageFocusStyle.module.css"; - -export function usePageFocusStyle(): void { - const { isFocusVisible } = useFocusVisible(); - - useEffect(() => { - const classList = document.body.classList; - const hasClass = classList.contains(styles.hideFocus); - - if (isFocusVisible && hasClass) { - classList.remove(styles.hideFocus); - } else if (!isFocusVisible && !hasClass) { - classList.add(styles.hideFocus); - } - - return () => { - classList.remove(styles.hideFocus); - }; - }, [isFocusVisible]); -} diff --git a/src/video-grid/BigGrid.module.css b/src/video-grid/BigGrid.module.css index cde593a5..2201295d 100644 --- a/src/video-grid/BigGrid.module.css +++ b/src/video-grid/BigGrid.module.css @@ -16,14 +16,13 @@ limitations under the License. .bigGrid { display: grid; - grid-auto-rows: 163px; - gap: 8px; + grid-auto-rows: 130px; + gap: var(--cpd-space-2x); } @media (min-width: 800px) { .bigGrid { - grid-auto-rows: 183px; - column-gap: 18px; - row-gap: 21px; + grid-auto-rows: 135px; + gap: var(--cpd-space-5x); } } diff --git a/src/video-grid/BigGrid.tsx b/src/video-grid/BigGrid.tsx index d8f7a859..b6a5b6dc 100644 --- a/src/video-grid/BigGrid.tsx +++ b/src/video-grid/BigGrid.tsx @@ -957,7 +957,7 @@ function updateTiles(g: Grid, tiles: TileDescriptor[]): Grid { } function updateBounds(g: Grid, bounds: RectReadOnly): Grid { - const columns = Math.max(2, Math.floor(bounds.width * 0.0045)); + const columns = Math.max(2, Math.floor(bounds.width * 0.0055)); return columns === g.columns ? g : resize(g, columns); } diff --git a/src/video-grid/NewVideoGrid.tsx b/src/video-grid/NewVideoGrid.tsx index 3363c573..26402507 100644 --- a/src/video-grid/NewVideoGrid.tsx +++ b/src/video-grid/NewVideoGrid.tsx @@ -178,7 +178,7 @@ export function NewVideoGrid({ from: ({ x, y, width, height }: Tile) => ({ opacity: 0, scale: 0, - shadow: 1, + shadow: 0, shadowSpread: 0, zIndex: 1, x, @@ -221,7 +221,7 @@ export function NewVideoGrid({ ? { scale: 1, zIndex: 1, - shadow: 1, + shadow: 0, x: tile.x, y: tile.y, width: tile.width, diff --git a/src/video-grid/VideoGrid.tsx b/src/video-grid/VideoGrid.tsx index a4dbe09f..8eb11a3d 100644 --- a/src/video-grid/VideoGrid.tsx +++ b/src/video-grid/VideoGrid.tsx @@ -1082,7 +1082,7 @@ export function VideoGrid({ y?: number; width?: number; height?: number; - } = { shadow: 1, scale: 0, opacity: 0 }; + } = { shadow: 0, scale: 0, opacity: 0 }; let reset = false; if (!tilePositionsWereValid) { @@ -1105,7 +1105,7 @@ export function VideoGrid({ scale: remove ? 0 : 1, opacity: remove ? 0 : 1, zIndex: tilePosition.zIndex, - shadow: 1, + shadow: oneOnOneLayout && tile.item.local ? 1 : 0, shadowSpread: oneOnOneLayout && tile.item.local ? 1 : 0, from, reset, diff --git a/src/video-grid/VideoTile.module.css b/src/video-grid/VideoTile.module.css index d83c91bb..ca0278c9 100644 --- a/src/video-grid/VideoTile.module.css +++ b/src/video-grid/VideoTile.module.css @@ -20,14 +20,11 @@ limitations under the License. top: 0; container-name: videoTile; container-type: size; - --tileRadius: 8px; - border-radius: var(--tileRadius); + border-radius: var(--cpd-space-4x); overflow: hidden; cursor: pointer; - - /* HACK: This has no visual effect due to the short duration, but allows the - JS to detect movement via the transform property for audio spatialization */ - transition: transform 0.000000001s; + outline: 2px solid rgba(0, 0, 0, 0); + transition: outline-radius ease 0.15s, outline-color ease 0.15s; } .videoTile * { @@ -45,21 +42,14 @@ limitations under the License. transform: scaleX(-1); } -.videoTile::after { - position: absolute; - top: -1px; - left: -1px; - right: -1px; - bottom: -1px; - content: ""; - border-radius: var(--tileRadius); - box-shadow: inset 0 0 0 4px var(--cpd-color-border-accent) !important; - opacity: 0; - transition: opacity ease 0.15s; +.videoTile.speaking { + outline: 4px solid var(--cpd-color-border-accent); } -.videoTile.speaking::after { - opacity: 1; +@media (hover: hover) { + .videoTile:hover { + outline: 2px solid var(--cpd-color-gray-1400); + } } .videoTile.maximised { @@ -73,30 +63,47 @@ limitations under the License. object-fit: contain; } -.infoBubble { +.nameTag { position: absolute; - height: 24px; - padding: 0 8px; + inset-inline-start: var(--cpd-space-1x); + inset-block-end: var(--cpd-space-1x); + padding: var(--cpd-space-1x); + padding-block: var(--cpd-space-1x); color: var(--cpd-color-text-primary); - background-color: var(--stopgap-background-85); + /* TODO: un-hardcode this color. It comes from the dark theme. */ + background-color: rgba(237, 244, 252, 0.79); display: flex; align-items: center; - justify-content: center; - border-radius: 4px; + border-radius: var(--cpd-radius-pill-effect); user-select: none; max-width: calc(100% - 48px); overflow: hidden; z-index: 1; + box-shadow: var(--small-drop-shadow); } -.infoBubble > svg { - height: 16px; - width: 16px; - margin-right: 4px; +:global(.cpd-theme-dark) .nameTag { + /* TODO: un-hardcode this color. It comes from the light theme. */ + background-color: rgba(2, 7, 13, 0.77); } -.infoBubble > svg * { - fill: var(--cpd-color-icon-primary); +.nameTag > svg { + flex-shrink: 0; +} + +.nameTag > svg[data-muted="true"] { + color: var(--cpd-color-icon-secondary); +} + +.nameTag > svg[data-muted="false"] { + color: var(--cpd-color-icon-primary); +} + +.nameTag span { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + padding-inline: var(--cpd-space-2x); } .toolbar { @@ -137,24 +144,6 @@ limitations under the License. height: 16px; } -.memberName { - left: 16px; - bottom: 16px; -} - -.memberName > :last-child { - margin-right: 0px; -} - -.memberName span { - font-size: var(--font-size-caption); - font-weight: 400; - line-height: var(--font-size-body); - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; -} - .videoMutedOverlay { width: 100%; height: 100%; @@ -192,12 +181,6 @@ limitations under the License. background-color: rgba(0, 0, 0, 0.5); } -@media (min-width: 800px) { - .videoTile { - --tileRadius: 20px; - } -} - /* CSS makes us put a condition here, even though all we want to do is unconditionally select the container so we can use cqmin units */ @container videoTile (width > 0) { diff --git a/src/video-grid/VideoTile.tsx b/src/video-grid/VideoTile.tsx index ceaf7e1e..04d1b297 100644 --- a/src/video-grid/VideoTile.tsx +++ b/src/video-grid/VideoTile.tsx @@ -28,11 +28,12 @@ import { RoomMember, RoomMemberEvent, } from "matrix-js-sdk/src/models/room-member"; +import { ReactComponent as MicOnSolidIcon } from "@vector-im/compound-design-tokens/icons/mic-on-solid.svg"; +import { ReactComponent as MicOffSolidIcon } from "@vector-im/compound-design-tokens/icons/mic-off-solid.svg"; +import { Text } from "@vector-im/compound-web"; import { Avatar } from "../Avatar"; import styles from "./VideoTile.module.css"; -import { ReactComponent as MicIcon } from "../icons/Mic.svg"; -import { ReactComponent as MicMutedIcon } from "../icons/MicMuted.svg"; import { useReactiveState } from "../useReactiveState"; import { AudioButton, FullscreenButton } from "../button/Button"; import { useModalTriggerState } from "../Modal"; @@ -102,12 +103,15 @@ export const VideoTile = forwardRef( } }, [member, setDisplayName]); - const { isMuted: microphoneMuted } = useMediaTrack( - content === TileContent.UserMedia - ? Track.Source.Microphone - : Track.Source.ScreenShareAudio, - sfuParticipant - ); + const muted = + useMediaTrack( + content === TileContent.UserMedia + ? Track.Source.Microphone + : Track.Source.ScreenShareAudio, + sfuParticipant + ).isMuted !== false; + + const MicIcon = muted ? MicOffSolidIcon : MicOnSolidIcon; const onFullscreen = useCallback(() => { onToggleFullscreen(data.id); @@ -153,7 +157,6 @@ export const VideoTile = forwardRef( sfuParticipant.isSpeaking && content === TileContent.UserMedia && showSpeakingIndicator, - [styles.muted]: microphoneMuted, [styles.screenshare]: content === TileContent.ScreenShare, [styles.maximised]: maximised, })} @@ -182,9 +185,16 @@ export const VideoTile = forwardRef( {t("{{displayName}} is presenting", { displayName })} ) : ( - - {microphoneMuted === false ? : } - {displayName} + + + + {sfuParticipant.isLocal ? t("You") : displayName} + {showConnectionStats && ( )}