From 40283ab7609700d0293db880210f8f436e29507a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 31 Aug 2023 15:24:25 +0200 Subject: [PATCH 1/9] Remove unused `Facepile` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/Facepile.module.css | 42 ---------------- src/Facepile.tsx | 97 ------------------------------------- src/home/CallList.tsx | 22 +-------- src/home/RegisteredView.tsx | 2 +- 4 files changed, 3 insertions(+), 160 deletions(-) delete mode 100644 src/Facepile.module.css delete mode 100644 src/Facepile.tsx diff --git a/src/Facepile.module.css b/src/Facepile.module.css deleted file mode 100644 index 0c911658..00000000 --- a/src/Facepile.module.css +++ /dev/null @@ -1,42 +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. -*/ - -.facepile { - width: 100%; - position: relative; -} - -.facepile.xs { - height: 24px; -} - -.facepile.sm { - height: 32px; -} - -.facepile.md { - height: 36px; -} - -.facepile .avatar { - position: absolute; - top: 0; - border: 1px solid var(--cpd-color-bg-canvas-default); -} - -.facepile.md .avatar { - border-width: 2px; -} diff --git a/src/Facepile.tsx b/src/Facepile.tsx deleted file mode 100644 index 0c9ec239..00000000 --- a/src/Facepile.tsx +++ /dev/null @@ -1,97 +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 { HTMLAttributes, useMemo } from "react"; -import classNames from "classnames"; -import { MatrixClient } from "matrix-js-sdk/src/client"; -import { RoomMember } from "matrix-js-sdk/src/models/room-member"; -import { useTranslation } from "react-i18next"; - -import styles from "./Facepile.module.css"; -import { Avatar, Size, sizes } from "./Avatar"; - -const overlapMap: Partial> = { - [Size.XS]: 2, - [Size.SM]: 4, - [Size.MD]: 8, -}; - -interface Props extends HTMLAttributes { - className: string; - client: MatrixClient; - members: RoomMember[]; - max?: number; - size?: Size; -} - -export function Facepile({ - className, - client, - members, - max = 3, - size = Size.XS, - ...rest -}: Props) { - const { t } = useTranslation(); - - const _size = sizes.get(size)!; - const _overlap = overlapMap[size]!; - - const title = useMemo(() => { - return members.reduce( - (prev, curr) => - prev === null - ? curr.name - : t("{{names}}, {{name}}", { names: prev, name: curr.name }), - null - ) as string; - }, [members, t]); - - return ( -
- {members.slice(0, max).map((member, i) => { - const avatarUrl = member.getMxcAvatarUrl(); - return ( - - ); - })} - {members.length > max && ( - - )} -
- ); -} diff --git a/src/home/CallList.tsx b/src/home/CallList.tsx index 1e36360e..01b84369 100644 --- a/src/home/CallList.tsx +++ b/src/home/CallList.tsx @@ -19,7 +19,6 @@ import { MatrixClient } from "matrix-js-sdk/src/client"; import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import { CopyButton } from "../button"; -import { Facepile } from "../Facepile"; import { Avatar, Size } from "../Avatar"; import styles from "./CallList.module.css"; import { getRoomUrl } from "../matrix-utils"; @@ -30,9 +29,8 @@ import { useRoomSharedKey } from "../e2ee/sharedKeyManagement"; interface CallListProps { rooms: GroupCallRoom[]; client: MatrixClient; - disableFacepile?: boolean; } -export function CallList({ rooms, client, disableFacepile }: CallListProps) { +export function CallList({ rooms, client }: CallListProps) { return ( <>
@@ -44,7 +42,6 @@ export function CallList({ rooms, client, disableFacepile }: CallListProps) { avatarUrl={avatarUrl} roomId={room.roomId} participants={participants} - disableFacepile={disableFacepile} /> ))} {rooms.length > 3 && ( @@ -63,16 +60,8 @@ interface CallTileProps { roomId: string; participants: RoomMember[]; client: MatrixClient; - disableFacepile?: boolean; } -function CallTile({ - name, - avatarUrl, - roomId, - participants, - client, - disableFacepile, -}: CallTileProps) { +function CallTile({ name, avatarUrl, roomId }: CallTileProps) { const roomSharedKey = useRoomSharedKey(roomId); return ( @@ -89,13 +78,6 @@ function CallTile({ {name} - {participants && !disableFacepile && ( - - )}
diff --git a/src/home/RegisteredView.tsx b/src/home/RegisteredView.tsx index 42aba9e8..0f8cc93c 100644 --- a/src/home/RegisteredView.tsx +++ b/src/home/RegisteredView.tsx @@ -170,7 +170,7 @@ export function RegisteredView({ client, isPasswordlessUser }: Props) { {t("Your recent calls")} - + )} From 1cb0ad2f65306430e73a5b6da4b8c97a5d6189f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 31 Aug 2023 15:46:09 +0200 Subject: [PATCH 2/9] Switch to `Avatar` from Compound MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/Avatar.module.css | 76 ------------------------ src/Avatar.tsx | 89 +++++++---------------------- src/UserMenu.module.css | 14 ----- src/UserMenu.tsx | 6 +- src/UserMenuContainer.tsx | 1 + src/home/CallList.tsx | 8 +-- src/input/AvatarInputField.tsx | 15 ++++- src/room/GroupCallView.tsx | 3 +- src/room/VideoPreview.tsx | 4 +- src/settings/ProfileSettingsTab.tsx | 6 +- src/video-grid/VideoTile.tsx | 5 +- 11 files changed, 52 insertions(+), 175 deletions(-) delete mode 100644 src/Avatar.module.css diff --git a/src/Avatar.module.css b/src/Avatar.module.css deleted file mode 100644 index accff6ae..00000000 --- a/src/Avatar.module.css +++ /dev/null @@ -1,76 +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. -*/ - -.avatar { - position: relative; - color: var(--stopgap-color-on-solid-accent); - display: flex; - align-items: center; - justify-content: center; - pointer-events: none; - font-weight: 600; - overflow: hidden; - flex-shrink: 0; -} - -.avatar img { - width: 100%; - height: 100%; - object-fit: cover; -} - -.avatar svg * { - fill: var(--cpd-color-text-primary); -} - -.avatar span { - padding-top: 1px; -} - -.xs { - width: 22px; - height: 22px; - border-radius: 22px; - font-size: 14px; -} - -.sm { - width: 32px; - height: 32px; - border-radius: 32px; - font-size: 15px; -} - -.md { - width: 36px; - height: 36px; - border-radius: 36px; - font-size: 20px; -} - -.lg { - width: 42px; - height: 42px; - border-radius: 42px; - font-size: 24px; -} - -.xl { - width: 90px; - height: 90px; - border-radius: 90px; - font-size: 48px; -} diff --git a/src/Avatar.tsx b/src/Avatar.tsx index c11d71d4..e23bd909 100644 --- a/src/Avatar.tsx +++ b/src/Avatar.tsx @@ -14,23 +14,11 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { useMemo, CSSProperties, HTMLAttributes, FC } from "react"; -import classNames from "classnames"; +import { useMemo, FC } from "react"; +import { Avatar as CompoundAvatar } from "@vector-im/compound-web"; import { getAvatarUrl } from "./matrix-utils"; import { useClient } from "./ClientContext"; -import styles from "./Avatar.module.css"; - -const backgroundColors = [ - "#5C56F5", - "#03B381", - "#368BD6", - "#AC3BA8", - "#E64F7A", - "#FF812D", - "#2DC2C5", - "#74D12C", -]; export enum Size { XS = "xs", @@ -48,50 +36,28 @@ export const sizes = new Map([ [Size.XL, 90], ]); -function hashStringToArrIndex(str: string, arrLength: number) { - let sum = 0; - - for (let i = 0; i < str.length; i++) { - sum += str.charCodeAt(i); - } - - return sum % arrLength; -} - -interface Props extends HTMLAttributes { - bgKey?: string; +interface Props { + id: string; + name: string; + className?: string; src?: string; size?: Size | number; - className?: string; - style?: CSSProperties; - fallback: string; } export const Avatar: FC = ({ - bgKey, - src, - fallback, - size = Size.MD, className, - style = {}, - ...rest + id, + name, + src, + size = Size.MD, }) => { const { client } = useClient(); - const [sizeClass, sizePx, sizeStyle] = useMemo( + const sizePx = useMemo( () => Object.values(Size).includes(size as Size) - ? [styles[size as string], sizes.get(size as Size), {}] - : [ - null, - size as number, - { - width: size, - height: size, - borderRadius: size, - fontSize: Math.round((size as number) / 2), - }, - ], + ? sizes.get(size as Size) + : (size as number), [size] ); @@ -100,28 +66,13 @@ export const Avatar: FC = ({ return src.startsWith("mxc://") ? getAvatarUrl(client, src, sizePx) : src; }, [client, src, sizePx]); - const backgroundColor = useMemo(() => { - const index = hashStringToArrIndex( - bgKey || fallback || src || "", - backgroundColors.length - ); - return backgroundColors[index]; - }, [bgKey, src, fallback]); - - /* eslint-disable jsx-a11y/alt-text */ return ( -
- {resolvedSrc ? ( - - ) : typeof fallback === "string" ? ( - {fallback} - ) : ( - fallback - )} -
+ ); }; diff --git a/src/UserMenu.module.css b/src/UserMenu.module.css index d1db1071..575b71b9 100644 --- a/src/UserMenu.module.css +++ b/src/UserMenu.module.css @@ -24,17 +24,3 @@ limitations under the License. .userButton svg * { fill: var(--cpd-color-icon-primary); } - -.avatar { - width: 24px; - height: 24px; - font-size: var(--font-size-caption); -} - -@media (min-width: 800px) { - .avatar { - width: 32px; - height: 32px; - font-size: var(--font-size-body); - } -} diff --git a/src/UserMenu.tsx b/src/UserMenu.tsx index 9df3309d..515e71f0 100644 --- a/src/UserMenu.tsx +++ b/src/UserMenu.tsx @@ -35,6 +35,7 @@ interface UserMenuProps { preventNavigation: boolean; isAuthenticated: boolean; isPasswordlessUser: boolean; + userId: string; displayName: string; avatarUrl?: string; onAction: (value: string) => void; @@ -44,6 +45,7 @@ export function UserMenu({ preventNavigation, isAuthenticated, isPasswordlessUser, + userId, displayName, avatarUrl, onAction, @@ -109,10 +111,10 @@ export function UserMenu({ > {isAuthenticated && (!isPasswordlessUser || avatarUrl) ? ( ) : ( diff --git a/src/UserMenuContainer.tsx b/src/UserMenuContainer.tsx index 6a83133e..a03e5b5a 100644 --- a/src/UserMenuContainer.tsx +++ b/src/UserMenuContainer.tsx @@ -67,6 +67,7 @@ export function UserMenuContainer({ preventNavigation = false }: Props) { isPasswordlessUser={passwordlessUser} avatarUrl={avatarUrl} onAction={onAction} + userId={client?.getUserId() ?? ""} displayName={displayName || (userName ? userName.replace("@", "") : "")} /> {modalState.isOpen && client && ( diff --git a/src/home/CallList.tsx b/src/home/CallList.tsx index 01b84369..a82e4c13 100644 --- a/src/home/CallList.tsx +++ b/src/home/CallList.tsx @@ -67,13 +67,7 @@ function CallTile({ name, avatarUrl, roomId }: CallTileProps) { return (
- +
{name} diff --git a/src/input/AvatarInputField.tsx b/src/input/AvatarInputField.tsx index 0218258e..8a069718 100644 --- a/src/input/AvatarInputField.tsx +++ b/src/input/AvatarInputField.tsx @@ -35,13 +35,23 @@ interface Props extends AllHTMLAttributes { id: string; label: string; avatarUrl: string | undefined; + userId: string; displayName: string; onRemoveAvatar: () => void; } export const AvatarInputField = forwardRef( ( - { id, label, className, avatarUrl, displayName, onRemoveAvatar, ...rest }, + { + id, + label, + className, + avatarUrl, + userId, + displayName, + onRemoveAvatar, + ...rest + }, ref ) => { const { t } = useTranslation(); @@ -80,9 +90,10 @@ export const AvatarInputField = forwardRef(
{ return { + userId: client.getUserId()!, displayName: displayName!, avatarUrl: avatarUrl!, roomId: groupCall.room.roomId, roomName: groupCall.room.name, roomAlias: groupCall.room.getCanonicalAlias(), }; - }, [displayName, avatarUrl, groupCall]); + }, [client, displayName, avatarUrl, groupCall]); const deviceContext = useMediaDevices(); const latestDevices = useRef(); diff --git a/src/room/VideoPreview.tsx b/src/room/VideoPreview.tsx index 1b5c07ce..807d402f 100644 --- a/src/room/VideoPreview.tsx +++ b/src/room/VideoPreview.tsx @@ -35,6 +35,7 @@ import { useMediaDevices } from "../livekit/MediaDevicesContext"; import { MuteStates } from "./MuteStates"; export type MatrixInfo = { + userId: string; displayName: string; avatarUrl: string; roomId: string; @@ -124,9 +125,10 @@ export const VideoPreview: FC = ({ matrixInfo, muteStates }) => { {!muteStates.video.enabled && (
)} diff --git a/src/settings/ProfileSettingsTab.tsx b/src/settings/ProfileSettingsTab.tsx index e6a59634..4286a960 100644 --- a/src/settings/ProfileSettingsTab.tsx +++ b/src/settings/ProfileSettingsTab.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { useCallback, useEffect, useRef } from "react"; +import { useCallback, useEffect, useMemo, useRef } from "react"; import { MatrixClient } from "matrix-js-sdk/src/client"; import { useTranslation } from "react-i18next"; @@ -29,6 +29,7 @@ interface Props { export function ProfileSettingsTab({ client }: Props) { const { t } = useTranslation(); const { error, displayName, avatarUrl, saveProfile } = useProfile(client); + const userId = useMemo(() => client.getUserId(), [client]); const formRef = useRef(null); @@ -77,12 +78,13 @@ export function ProfileSettingsTab({ client }: Props) { return (
- {displayName && ( + {userId && displayName && ( diff --git a/src/video-grid/VideoTile.tsx b/src/video-grid/VideoTile.tsx index f15052e0..3433f8aa 100644 --- a/src/video-grid/VideoTile.tsx +++ b/src/video-grid/VideoTile.tsx @@ -145,6 +145,8 @@ export const VideoTile = forwardRef( // Firefox doesn't respect the disablePictureInPicture attribute // https://bugzilla.mozilla.org/show_bug.cgi?id=1611831 + console.log(`LOG VideoTIle mxcSrc=${member?.getMxcAvatarUrl()}`); + return ( (
From 40e31607d21d5aab15556662fad0daaeb6cf9f45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 4 Sep 2023 20:04:24 +0200 Subject: [PATCH 3/9] i18n MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- public/locales/en-GB/app.json | 1 - 1 file changed, 1 deletion(-) diff --git a/public/locales/en-GB/app.json b/public/locales/en-GB/app.json index f2605959..9864bcf4 100644 --- a/public/locales/en-GB/app.json +++ b/public/locales/en-GB/app.json @@ -3,7 +3,6 @@ "{{count}} stars|other": "{{count}} stars", "{{displayName}} is presenting": "{{displayName}} is presenting", "{{displayName}}, your call has ended.": "{{displayName}}, your call has ended.", - "{{names}}, {{name}}": "{{names}}, {{name}}", "<0><1>You may withdraw consent by unchecking this box. If you are currently in a call, this setting will take effect at the end of the call.": "<0><1>You may withdraw consent by unchecking this box. If you are currently in a call, this setting will take effect at the end of the call.", "<0>Already have an account?<1><0>Log in Or <2>Access as a guest": "<0>Already have an account?<1><0>Log in Or <2>Access as a guest", "<0>Create an account Or <2>Access as a guest": "<0>Create an account Or <2>Access as a guest", From 1445c42cc32644a1b573a800418febb81912ed83 Mon Sep 17 00:00:00 2001 From: Robin Date: Fri, 8 Sep 2023 15:39:10 -0400 Subject: [PATCH 4/9] Implement new in-call header and footer designs --- public/locales/en-GB/app.json | 16 +-- src/E2EELock.tsx | 55 --------- src/Facepile.tsx | 66 +++++++++++ src/Header.module.css | 77 ++++++------- src/Header.stories.jsx | 105 ------------------ src/Header.tsx | 92 ++++++++------- src/button/ShareButton.tsx | 31 ++++++ src/button/VolumeIcon.tsx | 14 ++- src/icons/AddUser.svg | 4 - src/icons/AlertTriangleFilled.svg | 3 - src/icons/ArrowLeft.svg | 3 - src/icons/DisableVideo.svg | 6 - src/icons/Freedom.svg | 6 - src/icons/Hangup.svg | 4 - src/icons/LogoMark.svg | 11 ++ src/icons/LogoType.svg | 17 +++ src/icons/Screenshare.svg | 3 - src/icons/Spotlight.svg | 3 - src/icons/VideoMuted.svg | 6 - src/index.css | 4 + .../EncryptionLock.module.css} | 28 +++-- src/room/EncryptionLock.tsx | 46 ++++++++ src/room/GridLayoutMenu.module.css | 0 src/room/GridLayoutMenu.tsx | 82 -------------- src/room/GroupCallView.tsx | 84 ++++++++++---- src/room/InCallView.module.css | 50 +++++---- src/room/InCallView.tsx | 102 ++++++++--------- src/room/LayoutToggle.module.css | 77 +++++++++++++ src/room/LayoutToggle.tsx | 75 +++++++++++++ src/room/LobbyView.tsx | 20 +++- ...Modal.module.css => ShareModal.module.css} | 0 src/room/{InviteModal.tsx => ShareModal.tsx} | 6 +- src/room/VideoPreview.tsx | 2 + src/room/useGroupCall.ts | 17 +-- src/room/useRoomName.ts | 27 +++++ src/useMediaQuery.ts | 37 ++++++ src/usePrefersReducedMotion.ts | 27 +---- src/video-grid/NewVideoGrid.module.css | 10 +- src/video-grid/VideoGrid.tsx | 18 +-- vite.config.js | 8 +- yarn.lock | 6 +- 41 files changed, 700 insertions(+), 548 deletions(-) delete mode 100644 src/E2EELock.tsx create mode 100644 src/Facepile.tsx delete mode 100644 src/Header.stories.jsx create mode 100644 src/button/ShareButton.tsx delete mode 100644 src/icons/AddUser.svg delete mode 100644 src/icons/AlertTriangleFilled.svg delete mode 100644 src/icons/ArrowLeft.svg delete mode 100644 src/icons/DisableVideo.svg delete mode 100644 src/icons/Freedom.svg delete mode 100644 src/icons/Hangup.svg create mode 100644 src/icons/LogoMark.svg create mode 100644 src/icons/LogoType.svg delete mode 100644 src/icons/Screenshare.svg delete mode 100644 src/icons/Spotlight.svg delete mode 100644 src/icons/VideoMuted.svg rename src/{E2EELock.module.css => room/EncryptionLock.module.css} (62%) create mode 100644 src/room/EncryptionLock.tsx delete mode 100644 src/room/GridLayoutMenu.module.css delete mode 100644 src/room/GridLayoutMenu.tsx create mode 100644 src/room/LayoutToggle.module.css create mode 100644 src/room/LayoutToggle.tsx rename src/room/{InviteModal.module.css => ShareModal.module.css} (100%) rename src/room/{InviteModal.tsx => ShareModal.tsx} (90%) create mode 100644 src/room/useRoomName.ts create mode 100644 src/useMediaQuery.ts diff --git a/public/locales/en-GB/app.json b/public/locales/en-GB/app.json index 9864bcf4..69127826 100644 --- a/public/locales/en-GB/app.json +++ b/public/locales/en-GB/app.json @@ -1,8 +1,11 @@ { + "{{count, number}}|one": "{{count, number}}", + "{{count, number}}|other": "{{count, number}}", "{{count}} stars|one": "{{count}} star", "{{count}} stars|other": "{{count}} stars", "{{displayName}} is presenting": "{{displayName}} is presenting", "{{displayName}}, your call has ended.": "{{displayName}}, your call has ended.", + "{{names, list(style: short;)}}": "{{names, list(style: short;)}}", "<0><1>You may withdraw consent by unchecking this box. If you are currently in a call, this setting will take effect at the end of the call.": "<0><1>You may withdraw consent by unchecking this box. If you are currently in a call, this setting will take effect at the end of the call.", "<0>Already have an account?<1><0>Log in Or <2>Access as a guest": "<0>Already have an account?<1><0>Log in Or <2>Access as a guest", "<0>Create an account Or <2>Access as a guest": "<0>Create an account Or <2>Access as a guest", @@ -21,7 +24,6 @@ "Call link copied": "Call link copied", "Call type menu": "Call type menu", "Camera": "Camera", - "Change layout": "Change layout", "Close": "Close", "Confirm password": "Confirm password", "Connectivity to the server has been lost.": "Connectivity to the server has been lost.", @@ -31,7 +33,6 @@ "Create account": "Create account", "Debug log": "Debug log", "Debug log request": "Debug log request", - "Details": "Details", "Developer": "Developer", "Developer Settings": "Developer Settings", "Display name": "Display name", @@ -39,25 +40,22 @@ "Element Call Home": "Element Call Home", "Element Call is temporarily not end-to-end encrypted while we test scalability.": "Element Call is temporarily not end-to-end encrypted while we test scalability.", "Enable end-to-end encryption (password protected calls)": "Enable end-to-end encryption (password protected calls)", + "Encrypted": "Encrypted", "End call": "End call", "End-to-end encryption isn't supported on your browser.": "End-to-end encryption isn't supported on your browser.", "Exit full screen": "Exit full screen", "Expose developer settings in the settings window.": "Expose developer settings in the settings window.", "Feedback": "Feedback", "Fetching group call timed out.": "Fetching group call timed out.", - "Freedom": "Freedom", "Full screen": "Full screen", "Go": "Go", - "Grid layout menu": "Grid layout menu", + "Grid": "Grid", "Home": "Home", "How did it go?": "How did it go?", "If you are experiencing issues or simply would like to provide some feedback, please send us a short description below.": "If you are experiencing issues or simply would like to provide some feedback, please send us a short description below.", "Include debug logs": "Include debug logs", "Incompatible versions": "Incompatible versions", - "Incompatible versions!": "Incompatible versions!", "Inspector": "Inspector", - "Invite": "Invite", - "Invite people": "Invite people", "Join call": "Join call", "Join call now": "Join call now", "Join existing call?": "Join existing call?", @@ -71,6 +69,7 @@ "Microphone on": "Microphone on", "More": "More", "No": "No", + "Not encrypted": "Not encrypted", "Not now, return to home screen": "Not now, return to home screen", "Not registered yet? <2>Create an account": "Not registered yet? <2>Create an account", "Other users are trying to join this call from incompatible versions. These users should ensure that they have refreshed their browsers:<1>{userLis}": "Other users are trying to join this call from incompatible versions. These users should ensure that they have refreshed their browsers:<1>{userLis}", @@ -90,7 +89,9 @@ "Sending debug logs…": "Sending debug logs…", "Sending…": "Sending…", "Settings": "Settings", + "Share": "Share", "Share screen": "Share screen", + "Share this call": "Share this call", "Sharing screen": "Sharing screen", "Show call inspector": "Show call inspector", "Show connection stats": "Show connection stats", @@ -105,7 +106,6 @@ "Thanks, we received your feedback!": "Thanks, we received your feedback!", "Thanks!": "Thanks!", "This call already exists, would you like to join?": "This call already exists, would you like to join?", - "This call is not end-to-end encrypted.": "This call is not end-to-end encrypted.", "This site is protected by ReCAPTCHA and the Google <2>Privacy Policy and <6>Terms of Service apply.<9>By clicking \"Register\", you agree to our <12>End User Licensing Agreement (EULA)": "This site is protected by ReCAPTCHA and the Google <2>Privacy Policy and <6>Terms of Service apply.<9>By clicking \"Register\", you agree to our <12>End User Licensing Agreement (EULA)", "User menu": "User menu", "Username": "Username", diff --git a/src/E2EELock.tsx b/src/E2EELock.tsx deleted file mode 100644 index 9a9a55e9..00000000 --- a/src/E2EELock.tsx +++ /dev/null @@ -1,55 +0,0 @@ -/* -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 { useTranslation } from "react-i18next"; -import { useCallback } from "react"; -import { useObjectRef } from "@react-aria/utils"; -import { useButton } from "@react-aria/button"; - -import styles from "./E2EELock.module.css"; -import { ReactComponent as LockOffIcon } from "./icons/LockOff.svg"; -import { TooltipTrigger } from "./Tooltip"; - -export const E2EELock = () => { - const { t } = useTranslation(); - const tooltip = useCallback( - () => t("This call is not end-to-end encrypted."), - [t] - ); - - return ( - - - - ); -}; - -/** - * This component is a bit of hack - for some reason for the TooltipTrigger to - * work, it needs to contain a component which uses the useButton hook; please - * note that for some reason this also needs to be a separate component and we - * cannot just use the useButton hook inside the E2EELock. - */ -const Icon = () => { - const buttonRef = useObjectRef(); - const { buttonProps } = useButton({}, buttonRef); - - return ( -
- -
- ); -}; diff --git a/src/Facepile.tsx b/src/Facepile.tsx new file mode 100644 index 00000000..7ed995ce --- /dev/null +++ b/src/Facepile.tsx @@ -0,0 +1,66 @@ +/* +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 { HTMLAttributes } from "react"; +import { MatrixClient } from "matrix-js-sdk/src/client"; +import { RoomMember } from "matrix-js-sdk/src/models/room-member"; +import { useTranslation } from "react-i18next"; +import { AvatarStack } from "@vector-im/compound-web"; + +import { Avatar, Size } from "./Avatar"; + +interface Props extends HTMLAttributes { + className?: string; + client: MatrixClient; + members: RoomMember[]; + max?: number; + size?: Size | number; +} + +export function Facepile({ + className, + client, + members, + max = 3, + size = Size.XS, + ...rest +}: Props) { + const { t } = useTranslation(); + + const displayedMembers = members.slice(0, max); + + return ( + m.name), + })} + {...rest} + > + {displayedMembers.map((member, i) => { + const avatarUrl = member.getMxcAvatarUrl(); + return ( + + ); + })} + + ); +} diff --git a/src/Header.module.css b/src/Header.module.css index e54edff5..84b9df7b 100644 --- a/src/Header.module.css +++ b/src/Header.module.css @@ -28,8 +28,8 @@ limitations under the License. flex: 1; align-items: center; white-space: nowrap; - padding: 0 20px; - height: 64px; + padding-inline: var(--inline-content-inset); + height: 80px; } .headerLogo { @@ -66,51 +66,56 @@ limitations under the License. margin-right: 0; } +.roomHeaderInfo { + display: grid; + column-gap: var(--cpd-space-4x); + grid-template-columns: auto auto; + grid-template-rows: 1fr auto; +} + +.roomHeaderInfo[data-size="sm"] { + grid-template-areas: "avatar name" ". participants"; +} + +.roomHeaderInfo[data-size="lg"] { + grid-template-areas: "avatar name" "avatar participants"; +} + .roomAvatar { - position: relative; - display: none; - justify-content: center; + align-self: flex-start; + grid-area: avatar; +} + +.nameLine { + grid-area: name; + flex-grow: 1; + display: flex; align-items: center; - width: 36px; - height: 36px; - border-radius: 36px; - background-color: #5c56f5; + gap: var(--cpd-space-1x); } -.roomAvatar > * { - fill: white; - stroke: white; -} - -.userName { - font-weight: 600; - margin-right: 8px; - text-overflow: ellipsis; +.nameLine > h1 { + margin: 0; + /* XXX I can't actually get this ellipsis overflow to trigger, because + constraint propagation in a nested flexbox layout is a massive pain */ overflow: hidden; - flex-shrink: 1; + text-overflow: ellipsis; } -.versionMismatchWarning { - padding-left: 15px; +.nameLine > svg { + flex-shrink: 0; } -.versionMismatchWarning::before { - content: ""; - display: inline-block; - position: relative; - top: 1px; - width: 16px; - height: 16px; - mask-image: url("./icons/AlertTriangleFilled.svg"); - mask-repeat: no-repeat; - mask-size: contain; - background-color: var(--cpd-color-icon-critical-primary); - padding-right: 5px; +.participantsLine { + grid-area: participants; + display: flex; + align-items: center; + gap: var(--cpd-space-1-5x); + font: var(--cpd-font-body-sm-medium); } @media (min-width: 800px) { .headerLogo, - .roomAvatar, .leftNav.hideMobile, .rightNav.hideMobile { display: flex; @@ -119,8 +124,4 @@ limitations under the License. .leftNav h3 { font-size: var(--font-size-subtitle); } - - .nav { - height: 76px; - } } diff --git a/src/Header.stories.jsx b/src/Header.stories.jsx deleted file mode 100644 index e5c4473a..00000000 --- a/src/Header.stories.jsx +++ /dev/null @@ -1,105 +0,0 @@ -import { GridLayoutMenu } from "./room/GridLayoutMenu"; -import { - Header, - HeaderLogo, - LeftNav, - RightNav, - RoomHeaderInfo, -} from "./Header"; -import { UserMenu } from "./UserMenu"; - -export default { - title: "Header", - component: Header, - parameters: { - layout: "fullscreen", - }, -}; - -export const HomeAnonymous = () => ( -
- - - - - - -
-); - -export const HomeNamedGuest = () => ( -
- - - - - - -
-); - -export const HomeLoggedIn = () => ( -
- - - - - - -
-); - -export const LobbyNamedGuest = () => ( -
- - - - - - -
-); - -export const LobbyLoggedIn = () => ( -
- - - - - - -
-); - -export const InRoomNamedGuest = () => ( -
- - - - - - - -
-); - -export const InRoomLoggedIn = () => ( -
- - - - - - - -
-); - -export const CreateAccount = () => ( -
- - - - -
-); diff --git a/src/Header.tsx b/src/Header.tsx index 8d754b92..aea3da71 100644 --- a/src/Header.tsx +++ b/src/Header.tsx @@ -15,17 +15,18 @@ limitations under the License. */ import classNames from "classnames"; -import { HTMLAttributes, ReactNode, useCallback } from "react"; +import { FC, HTMLAttributes, ReactNode } from "react"; import { Link } from "react-router-dom"; -import { Room } from "matrix-js-sdk/src/models/room"; import { useTranslation } from "react-i18next"; +import { MatrixClient, RoomMember } from "matrix-js-sdk/src/matrix"; +import { Heading } from "@vector-im/compound-web"; import styles from "./Header.module.css"; -import { useModalTriggerState } from "./Modal"; -import { Button } from "./button"; import { ReactComponent as Logo } from "./icons/Logo.svg"; -import { Subtitle } from "./typography/Typography"; -import { IncompatibleVersionModal } from "./IncompatibleVersionModal"; +import { Avatar, Size } from "./Avatar"; +import { Facepile } from "./Facepile"; +import { EncryptionLock } from "./room/EncryptionLock"; +import { useMediaQuery } from "./useMediaQuery"; interface HeaderProps extends HTMLAttributes { children: ReactNode; @@ -112,47 +113,52 @@ export function HeaderLogo({ className }: HeaderLogoProps) { ); } -interface RoomHeaderInfo { - roomName: string; +interface RoomHeaderInfoProps { + id: string; + name: string; + avatarUrl: string | null; + encrypted: boolean; + participants: RoomMember[]; + client: MatrixClient; } -export function RoomHeaderInfo({ roomName }: RoomHeaderInfo) { - return ( - <> - - {roomName} - - - ); -} - -interface VersionMismatchWarningProps { - users: Set; - room: Room; -} - -export function VersionMismatchWarning({ - users, - room, -}: VersionMismatchWarningProps) { +export const RoomHeaderInfo: FC = ({ + id, + name, + avatarUrl, + encrypted, + participants, + client, +}) => { const { t } = useTranslation(); - const { modalState, modalProps } = useModalTriggerState(); - - const onDetailsClick = useCallback(() => { - modalState.open(); - }, [modalState]); - - if (users.size === 0) return null; + const size = useMediaQuery("(max-width: 550px)") ? "sm" : "lg"; return ( - - {t("Incompatible versions!")} - - {modalState.isOpen && ( - +
+ +
+ + {name} + + +
+ {participants.length > 0 && ( +
+ + {t("{{count, number}}", { count: participants.length })} +
)} - +
); -} +}; diff --git a/src/button/ShareButton.tsx b/src/button/ShareButton.tsx new file mode 100644 index 00000000..2f7f1334 --- /dev/null +++ b/src/button/ShareButton.tsx @@ -0,0 +1,31 @@ +/* +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 { ComponentPropsWithoutRef, FC } from "react"; +import { Button } from "@vector-im/compound-web"; +import { useTranslation } from "react-i18next"; +import { ReactComponent as UserAddSolidIcon } from "@vector-im/compound-design-tokens/icons/user-add-solid.svg"; + +export const ShareButton: FC< + Omit, "children"> +> = (props) => { + const { t } = useTranslation(); + return ( + + ); +}; diff --git a/src/button/VolumeIcon.tsx b/src/button/VolumeIcon.tsx index 163699f6..00aebb06 100644 --- a/src/button/VolumeIcon.tsx +++ b/src/button/VolumeIcon.tsx @@ -15,19 +15,21 @@ See the License for the specific language governing permissions and limitations under the License. */ +import { ComponentPropsWithoutRef, FC } from "react"; + import { ReactComponent as AudioMuted } from "../icons/AudioMuted.svg"; import { ReactComponent as AudioLow } from "../icons/AudioLow.svg"; import { ReactComponent as Audio } from "../icons/Audio.svg"; -interface Props { +interface Props extends ComponentPropsWithoutRef<"svg"> { /** * Number between 0 and 1 */ volume: number; } -export function VolumeIcon({ volume }: Props) { - if (volume <= 0) return ; - if (volume <= 0.5) return ; - return