diff --git a/public/locales/en-GB/app.json b/public/locales/en-GB/app.json index 84798e1e..f769c7e0 100644 --- a/public/locales/en-GB/app.json +++ b/public/locales/en-GB/app.json @@ -26,6 +26,7 @@ "Close": "Close", "Confirm password": "Confirm password", "Connectivity to the server has been lost.": "Connectivity to the server has been lost.", + "Continue in browser": "Continue in browser", "Copied!": "Copied!", "Copy": "Copy", "Copy and share this call link": "Copy and share this call link", @@ -65,14 +66,17 @@ "Microphone off": "Microphone off", "Microphone on": "Microphone on", "More": "More", + "Mute microphone": "Mute microphone", "Name of call": "Name of call", "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", + "Open in the app": "Open in the app", "Password": "Password", "Passwords must match": "Passwords must match", "Profile": "Profile", + "Ready to join?": "Ready to join?", "Recaptcha dismissed": "Recaptcha dismissed", "Recaptcha not loaded": "Recaptcha not loaded", "Reconnect": "Reconnect", @@ -82,6 +86,7 @@ "Retry sending logs": "Retry sending logs", "Return to home screen": "Return to home screen", "Select an option": "Select an option", + "Select app": "Select app", "Send debug logs": "Send debug logs", "Sending debug logs…": "Sending debug logs…", "Sending…": "Sending…", @@ -97,6 +102,8 @@ "Speaker": "Speaker", "Spotlight": "Spotlight", "Start new call": "Start new call", + "Start video": "Start video", + "Stop video": "Stop video", "Submit": "Submit", "Submit feedback": "Submit feedback", "Submitting…": "Submitting…", @@ -105,12 +112,11 @@ "Thanks!": "Thanks!", "This call already exists, would you like to join?": "This call already exists, would you like to join?", "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)", + "Unmute microphone": "Unmute microphone", "User menu": "User menu", "Username": "Username", "Version: {{version}}": "Version: {{version}}", "Video": "Video", - "Video off": "Video off", - "Video on": "Video on", "Waiting for other participants…": "Waiting for other participants…", "Yes, join call": "Yes, join call", "You": "You", diff --git a/src/UrlParams.ts b/src/UrlParams.ts index 6a2ea501..4aa6ace4 100644 --- a/src/UrlParams.ts +++ b/src/UrlParams.ts @@ -90,6 +90,22 @@ interface UrlParams { password: string | null; } +export function editFragmentQuery( + hash: string, + edit: (params: URLSearchParams) => URLSearchParams +): string { + const fragmentQueryStart = hash.indexOf("?"); + const fragmentParams = edit( + new URLSearchParams( + fragmentQueryStart === -1 ? "" : hash.substring(fragmentQueryStart) + ) + ); + return `${hash.substring( + 0, + fragmentQueryStart + )}?${fragmentParams.toString()}`; +} + /** * Gets the app parameters for the current URL. * @param ignoreRoomAlias If true, does not try to parse a room alias from the URL diff --git a/src/button/Button.tsx b/src/button/Button.tsx index 2b8049e5..3d269dc2 100644 --- a/src/button/Button.tsx +++ b/src/button/Button.tsx @@ -145,11 +145,11 @@ export function MicButton({ }) { const { t } = useTranslation(); const Icon = muted ? MicOffSolidIcon : MicOnSolidIcon; - const label = muted ? t("Microphone off") : t("Microphone on"); + const label = muted ? t("Unmute microphone") : t("Mute microphone"); return ( - @@ -166,11 +166,11 @@ export function VideoButton({ }) { const { t } = useTranslation(); const Icon = muted ? VideoCallOffIcon : VideoCallIcon; - const label = muted ? t("Video off") : t("Video on"); + const label = muted ? t("Start video") : t("Stop video"); return ( - diff --git a/src/room/AppSelectionModal.module.css b/src/room/AppSelectionModal.module.css new file mode 100644 index 00000000..773df4fd --- /dev/null +++ b/src/room/AppSelectionModal.module.css @@ -0,0 +1,33 @@ +/* +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. +*/ + +.modal p { + text-align: center; + margin-block-end: var(--cpd-space-8x); +} + +.modal button, +.modal a { + width: 100%; +} + +.modal button { + margin-block-end: var(--cpd-space-6x); +} + +.modal a { + box-sizing: border-box; +} diff --git a/src/room/AppSelectionModal.tsx b/src/room/AppSelectionModal.tsx new file mode 100644 index 00000000..8d4a05f8 --- /dev/null +++ b/src/room/AppSelectionModal.tsx @@ -0,0 +1,80 @@ +/* +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, MouseEvent, useCallback, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Button, Text } from "@vector-im/compound-web"; +import { ReactComponent as PopOutIcon } from "@vector-im/compound-design-tokens/icons/pop-out.svg"; + +import { Modal } from "../Modal"; +import { useRoomSharedKey } from "../e2ee/sharedKeyManagement"; +import { getRoomUrl } from "../matrix-utils"; +import styles from "./AppSelectionModal.module.css"; +import { editFragmentQuery } from "../UrlParams"; + +interface Props { + roomId: string | null; +} + +export const AppSelectionModal: FC = ({ roomId }) => { + const { t } = useTranslation(); + + const [open, setOpen] = useState(true); + const onBrowserClick = useCallback( + (e: MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + setOpen(false); + }, + [setOpen] + ); + + const roomSharedKey = useRoomSharedKey(roomId ?? ""); + const appUrl = useMemo(() => { + // If the room ID is not known, fall back to the URL of the current page + const url = new URL( + roomId === null + ? window.location.href + : getRoomUrl(roomId, roomSharedKey ?? undefined) + ); + // Edit the URL so that it opens in embedded mode. We do this for two + // reasons: It causes the mobile app to limit the user to only visiting the + // room in question, and it prevents this app selection prompt from being + // shown a second time. + url.hash = editFragmentQuery(url.hash, (params) => { + params.set("isEmbedded", ""); + return params; + }); + + const result = new URL("element://call"); + result.searchParams.set("url", url.toString()); + return result.toString(); + }, [roomId, roomSharedKey]); + + return ( + + + {t("Ready to join?")} + + + + + ); +}; diff --git a/src/room/CallEndedView.module.css b/src/room/CallEndedView.module.css index 9952a784..12409d4e 100644 --- a/src/room/CallEndedView.module.css +++ b/src/room/CallEndedView.module.css @@ -17,7 +17,8 @@ limitations under the License. .headline { text-align: center; margin-bottom: 60px; - white-space: pre; + white-space: pre-wrap; + overflow-wrap: break-word; } .callEndedContent { @@ -66,6 +67,7 @@ limitations under the License. flex: 1; flex-direction: column; align-items: center; + padding-inline: var(--inline-content-inset); } .logo { diff --git a/src/room/RoomPage.tsx b/src/room/RoomPage.tsx index c7eed871..35cdda27 100644 --- a/src/room/RoomPage.tsx +++ b/src/room/RoomPage.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { FC, useEffect, useState, useCallback } from "react"; +import { FC, useEffect, useState, useCallback, ReactNode } from "react"; import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession"; import { useClientLegacy } from "../ClientContext"; @@ -26,6 +26,8 @@ import { useUrlParams } from "../UrlParams"; import { useRegisterPasswordlessUser } from "../auth/useRegisterPasswordlessUser"; import { useOptInAnalytics } from "../settings/useSetting"; import { HomePage } from "../home/HomePage"; +import { platform } from "../Platform"; +import { AppSelectionModal } from "./AppSelectionModal"; export const RoomPage: FC = () => { const { @@ -85,29 +87,36 @@ export const RoomPage: FC = () => { [client, passwordlessUser, isEmbedded, preload, hideHeader] ); + let content: ReactNode; if (loading || isRegistering) { - return ; - } - - if (error) { - return ; - } - - if (!client) { - return ; - } - - if (!roomIdOrAlias) { - return ; + content = ; + } else if (error) { + content = ; + } else if (!client) { + content = ; + } else if (!roomIdOrAlias) { + // TODO: This doesn't belong here, the app routes need to be reworked + content = ; + } else { + content = ( + + {groupCallView} + + ); } return ( - - {groupCallView} - + <> + {content} + {/* On mobile, show a prompt to launch the mobile app. If in embedded mode, + that means we *are* in the mobile app and should show no further prompt. */} + {(platform === "android" || platform === "ios") && !isEmbedded && ( + + )} + ); }; diff --git a/yarn.lock b/yarn.lock index 6ab5f6cf..6ed7f185 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4564,11 +4564,16 @@ "@types/tough-cookie" "*" parse5 "^7.0.0" -"@types/json-schema@*", "@types/json-schema@^7.0.12", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8": +"@types/json-schema@*", "@types/json-schema@^7.0.12", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.8": version "7.0.12" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb" integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA== +"@types/json-schema@^7.0.5": + version "7.0.13" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.13.tgz#02c24f4363176d2d18fc8b70b9f3c54aba178a85" + integrity sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ== + "@types/json5@^0.0.29": version "0.0.29" resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" @@ -4615,9 +4620,9 @@ integrity sha512-Mnq3O9Xz52exs3mlxMcQuA7/9VFe/dXcrgAyfjLkABIqxXKOgBRjyazTxUbjsxDa4BP7hhPliyjVTP9RDP14xg== "@types/node@^18.13.0": - version "18.13.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.13.0.tgz#0400d1e6ce87e9d3032c19eb6c58205b0d3f7850" - integrity sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg== + version "18.17.17" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.17.17.tgz#53cc07ce582c9d7c5850702a3c2cb0af0d7b0ca1" + integrity sha512-cOxcXsQ2sxiwkykdJqvyFS+MLQPLvIdwh5l6gNg8qF6s+C7XSkEWOZjK+XhUZd+mYvHV/180g2cnCcIl4l06Pw== "@types/normalize-package-data@^2.4.0": version "2.4.1" @@ -4650,9 +4655,9 @@ integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== "@types/q@^1.5.1": - version "1.5.5" - resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.5.tgz#75a2a8e7d8ab4b230414505d92335d1dcb53a6df" - integrity sha512-L28j2FcJfSZOnL1WBjDYp2vUHCeIFlyYI/53EwD/rKUBQ7MtUUfbQWiyKJGpcnv4/WgrhWsFKrcPstcAt/J0tQ== + version "1.5.6" + resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.6.tgz#a6edffe8283910e46dc7a573621f928e6b47fa56" + integrity sha512-IKjZ8RjTSwD4/YG+2gtj7BPFRB/lNbWKTiSj3M7U/TD2B7HfYCxvp2Zz6xA2WIY7pAuL1QOUPw8gQRbUrrq4fQ== "@types/qs@^6.9.5": version "6.9.8" @@ -4660,9 +4665,9 @@ integrity sha512-u95svzDlTysU5xecFNTgfFG5RUWu1A9P0VzgpcIiGZA9iraHOdSzcxMxQ55DyeRaGCSxQi7LxXDI4rzq/MYfdg== "@types/react-dom@^18.0.0": - version "18.0.7" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.7.tgz#ee7cf8ec4e6977e3f0a7b1d38bd89c75aa2aec28" - integrity sha512-HaXc+BbqAZE1RdsK3tC8SbkFy6UL2xF76lT9rQs5JkPrJg3rWA3Ou/Lhw3YJQzEDkBpmJ79nBsfnd05WrBd2QQ== + version "18.2.7" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.7.tgz#67222a08c0a6ae0a0da33c3532348277c70abb63" + integrity sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA== dependencies: "@types/react" "*" @@ -4905,17 +4910,17 @@ "@typescript-eslint/types" "6.1.0" eslint-visitor-keys "^3.4.1" -"@use-gesture/core@10.2.27": - version "10.2.27" - resolved "https://registry.yarnpkg.com/@use-gesture/core/-/core-10.2.27.tgz#0f24b17c036cd828ba07e3451ff45e2df959c6f5" - integrity sha512-V4XV7hn9GAD2MYu8yBBVi5iuWBsAMfjPRMsEVzoTNGYH72tf0kFP+OKqGKc8YJFQIJx6yj+AOqxmEHOmx2/MEA== +"@use-gesture/core@10.3.0": + version "10.3.0" + resolved "https://registry.yarnpkg.com/@use-gesture/core/-/core-10.3.0.tgz#9afd3777a45b2a08990a5dcfcf8d9ddd55b00db9" + integrity sha512-rh+6MND31zfHcy9VU3dOZCqGY511lvGcfyJenN4cWZe0u1BH6brBpBddLVXhF2r4BMqWbvxfsbL7D287thJU2A== "@use-gesture/react@^10.2.11": - version "10.2.27" - resolved "https://registry.yarnpkg.com/@use-gesture/react/-/react-10.2.27.tgz#7fbd50d14449ec5bc49c9b6cfef8a2845f5e0608" - integrity sha512-7E5vnWCxeslWlxwZ8uKIcnUZVMTRMZ8cvSnLLKF1NkyNb3PnNiAzoXM4G1vTKJKRhgOTeI6wK1YsEpwo9ABV5w== + version "10.3.0" + resolved "https://registry.yarnpkg.com/@use-gesture/react/-/react-10.3.0.tgz#180534c821fd635c2853cbcfa813f92c94f27e3f" + integrity sha512-3zc+Ve99z4usVP6l9knYVbVnZgfqhKah7sIG+PS2w+vpig2v2OLct05vs+ZXMzwxdNCMka8B+8WlOo0z6Pn6DA== dependencies: - "@use-gesture/core" "10.2.27" + "@use-gesture/core" "10.3.0" "@vector-im/compound-design-tokens@^0.0.5": version "0.0.5" @@ -5789,7 +5794,7 @@ babel-jest@^29.7.0: graceful-fs "^4.2.9" slash "^3.0.0" -babel-loader@^8.0.0: +babel-loader@^8.0.0, babel-loader@^8.2.3: version "8.3.0" resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.3.0.tgz#124936e841ba4fe8176786d6ff28add1f134d6a8" integrity sha512-H8SvsMF+m9t15HNLMipppzkC+Y2Yq+v3SonZyU70RBL/h1gxPkH08Ot8pEE9Z4Kd+czyWJClmFS8qzIP9OZ04Q== @@ -5799,16 +5804,6 @@ babel-loader@^8.0.0: make-dir "^3.1.0" schema-utils "^2.6.5" -babel-loader@^8.2.3: - version "8.2.5" - resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.2.5.tgz#d45f585e654d5a5d90f5350a779d7647c5ed512e" - integrity sha512-OSiFfH89LrEMiWd4pLNqGz4CwJDtbs2ZVc+iGu2HrkRfPxId9F2anQj38IxWpmRfsUY0aBZYi1EFcd3mhtRMLQ== - dependencies: - find-cache-dir "^3.3.1" - loader-utils "^2.0.0" - make-dir "^3.1.0" - schema-utils "^2.6.5" - babel-plugin-add-react-displayname@^0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/babel-plugin-add-react-displayname/-/babel-plugin-add-react-displayname-0.0.5.tgz#339d4cddb7b65fd62d1df9db9fe04de134122bd5"