From b3ac0c71e107571ea5312d58ad53b8614d7eec2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 21 Jun 2021 12:17:51 +0200 Subject: [PATCH 01/80] Fix access token copy button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- .../tabs/user/_HelpUserSettingsTab.scss | 34 +++++++++++-------- .../tabs/user/HelpUserSettingsTab.tsx | 4 +-- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/res/css/views/settings/tabs/user/_HelpUserSettingsTab.scss b/res/css/views/settings/tabs/user/_HelpUserSettingsTab.scss index 0f879d209e..1498f6fbf0 100644 --- a/res/css/views/settings/tabs/user/_HelpUserSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_HelpUserSettingsTab.scss @@ -28,7 +28,7 @@ limitations under the License. user-select: all; } -.mx_HelpUserSettingsTab_accessToken { +.mx_HelpUserSettingsTab_copy { display: flex; justify-content: space-between; border-radius: 5px; @@ -36,20 +36,24 @@ limitations under the License. margin-bottom: 10px; margin-top: 10px; padding: 10px; -} -.mx_HelpUserSettingsTab_accessToken_copy { - flex-shrink: 0; - cursor: pointer; - margin-left: 20px; - display: inherit; -} + .mx_HelpUserSettingsTab_copyButton { + flex-shrink: 0; + width: 20px; + height: 20px; + cursor: pointer; + margin-left: 20px; + display: block; -.mx_HelpUserSettingsTab_accessToken_copy > div { - mask-image: url($copy-button-url); - background-color: $message-action-bar-fg-color; - margin-left: 5px; - width: 20px; - height: 20px; - background-repeat: no-repeat; + &::before { + content: ""; + + mask-image: url($copy-button-url); + background-color: $message-action-bar-fg-color; + width: 20px; + height: 20px; + display: block; + background-repeat: no-repeat; + } + } } diff --git a/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx b/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx index beff033001..5288dc8977 100644 --- a/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx @@ -300,12 +300,12 @@ export default class HelpUserSettingsTab extends React.Component {_t("Access Token")}
{_t("Your access token gives full access to your account." + " Do not share it with anyone." )} -
+
{MatrixClientPeg.get().getAccessToken()}

From 0b1fbf7e530cb9d1753f54175d0fbefdbd76c5bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 21 Jun 2021 12:31:58 +0200 Subject: [PATCH 02/80] Make version copiable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- .../tabs/user/HelpUserSettingsTab.tsx | 45 ++++++++++++++++--- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx b/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx index 5288dc8977..6147265a51 100644 --- a/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx @@ -68,6 +68,18 @@ export default class HelpUserSettingsTab extends React.Component if (this.closeCopiedTooltip) this.closeCopiedTooltip(); } + private getVersionInfo(): { appVersion: string, olmVersion: string } { + const brand = SdkConfig.get().brand; + const appVersion = this.state.appVersion || 'unknown'; + let olmVersion = MatrixClientPeg.get().olmVersion; + olmVersion = olmVersion ? `${olmVersion[0]}.${olmVersion[1]}.${olmVersion[2]}` : ''; + + return { + appVersion: `${_t("%(brand)s version:", { brand })} ${appVersion}`, + olmVersion: `${_t("Olm version:")} ${olmVersion}`, + }; + } + private onClearCacheAndReload = (e) => { if (!PlatformPeg.get()) return; @@ -179,6 +191,21 @@ export default class HelpUserSettingsTab extends React.Component this.closeCopiedTooltip = target.onmouseleave = close; } + private onCopyVersionClicked = async (e) => { + e.preventDefault(); + const target = e.target; // copy target before we go async and React throws it away + + const { appVersion, olmVersion } = this.getVersionInfo(); + const successful = await copyPlaintext(`${appVersion}\n${olmVersion}`); + const buttonRect = target.getBoundingClientRect(); + const GenericTextContextMenu = sdk.getComponent('context_menus.GenericTextContextMenu'); + const { close } = ContextMenu.createMenu(GenericTextContextMenu, { + ...toRightOf(buttonRect, 2), + message: successful ? _t('Copied!') : _t('Failed to copy'), + }); + this.closeCopiedTooltip = target.onmouseleave = close; + }; + render() { const brand = SdkConfig.get().brand; @@ -225,11 +252,6 @@ export default class HelpUserSettingsTab extends React.Component ); } - const appVersion = this.state.appVersion || 'unknown'; - - let olmVersion = MatrixClientPeg.get().olmVersion; - olmVersion = olmVersion ? `${olmVersion[0]}.${olmVersion[1]}.${olmVersion[2]}` : ''; - let updateButton = null; if (this.state.canUpdate) { updateButton = ; @@ -267,6 +289,8 @@ export default class HelpUserSettingsTab extends React.Component ); } + const { appVersion, olmVersion } = this.getVersionInfo(); + return (
{_t("Help & About")}
@@ -283,8 +307,15 @@ export default class HelpUserSettingsTab extends React.Component
{_t("Versions")}
- {_t("%(brand)s version:", { brand })} {appVersion}
- {_t("olm version:")} {olmVersion}
+
+ { appVersion }
+ { olmVersion }
+ +
{updateButton}
From d74d3aaf73866f2f1864ce2ccf613b903e6ca39e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 21 Jun 2021 12:40:31 +0200 Subject: [PATCH 03/80] Move copying into a separate method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- .../tabs/user/HelpUserSettingsTab.tsx | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx b/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx index 6147265a51..7a286617a1 100644 --- a/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx @@ -17,7 +17,7 @@ limitations under the License. import React from 'react'; import {_t, getCurrentLanguage} from "../../../../../languageHandler"; import {MatrixClientPeg} from "../../../../../MatrixClientPeg"; -import AccessibleButton from "../../../elements/AccessibleButton"; +import AccessibleButton, { ButtonEvent } from "../../../elements/AccessibleButton"; import AccessibleTooltipButton from '../../../elements/AccessibleTooltipButton'; import SdkConfig from "../../../../../SdkConfig"; import createRoom from "../../../../../createRoom"; @@ -177,11 +177,11 @@ export default class HelpUserSettingsTab extends React.Component ); } - onAccessTokenCopyClick = async (e) => { + private async copy(text: string, e: ButtonEvent) { e.preventDefault(); - const target = e.target; // copy target before we go async and React throws it away + const target = e.target as HTMLDivElement; // copy target before we go async and React throws it away - const successful = await copyPlaintext(MatrixClientPeg.get().getAccessToken()); + const successful = await copyPlaintext(text); const buttonRect = target.getBoundingClientRect(); const GenericTextContextMenu = sdk.getComponent('context_menus.GenericTextContextMenu'); const {close} = ContextMenu.createMenu(GenericTextContextMenu, { @@ -191,19 +191,13 @@ export default class HelpUserSettingsTab extends React.Component this.closeCopiedTooltip = target.onmouseleave = close; } - private onCopyVersionClicked = async (e) => { - e.preventDefault(); - const target = e.target; // copy target before we go async and React throws it away + private onAccessTokenCopyClick = (e: ButtonEvent) => { + this.copy(MatrixClientPeg.get().getAccessToken(), e); + }; + private onCopyVersionClicked = (e: ButtonEvent) => { const { appVersion, olmVersion } = this.getVersionInfo(); - const successful = await copyPlaintext(`${appVersion}\n${olmVersion}`); - const buttonRect = target.getBoundingClientRect(); - const GenericTextContextMenu = sdk.getComponent('context_menus.GenericTextContextMenu'); - const { close } = ContextMenu.createMenu(GenericTextContextMenu, { - ...toRightOf(buttonRect, 2), - message: successful ? _t('Copied!') : _t('Failed to copy'), - }); - this.closeCopiedTooltip = target.onmouseleave = close; + this.copy(`${appVersion}\n${olmVersion}`, e); }; render() { From 1bf9e592e951b0959a6f978fbe9f63773356689d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 21 Jun 2021 12:41:07 +0200 Subject: [PATCH 04/80] i18n MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/i18n/strings/en_EN.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index b88dc79da5..d9cfd2fea4 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1247,6 +1247,8 @@ "Deactivate account": "Deactivate account", "Discovery": "Discovery", "General": "General", + "%(brand)s version:": "%(brand)s version:", + "Olm version:": "Olm version:", "Legal": "Legal", "Credits": "Credits", "For help with using %(brand)s, click here.": "For help with using %(brand)s, click here.", @@ -1260,13 +1262,11 @@ "FAQ": "FAQ", "Keyboard Shortcuts": "Keyboard Shortcuts", "Versions": "Versions", - "%(brand)s version:": "%(brand)s version:", - "olm version:": "olm version:", + "Copy": "Copy", "Homeserver is": "Homeserver is", "Identity Server is": "Identity Server is", "Access Token": "Access Token", "Your access token gives full access to your account. Do not share it with anyone.": "Your access token gives full access to your account. Do not share it with anyone.", - "Copy": "Copy", "Clear cache and reload": "Clear cache and reload", "Labs": "Labs", "Feeling experimental? Labs are the best way to get things early, test out new features and help shape them before they actually launch. Learn more.": "Feeling experimental? Labs are the best way to get things early, test out new features and help shape them before they actually launch. Learn more.", From 7a329b7a018d9343c7514c3c5ef83edce97c345e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 11 Jul 2021 10:31:24 +0200 Subject: [PATCH 05/80] Add ReactUtils MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/utils/ReactUtils.tsx | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/utils/ReactUtils.tsx diff --git a/src/utils/ReactUtils.tsx b/src/utils/ReactUtils.tsx new file mode 100644 index 0000000000..ce92dd8a51 --- /dev/null +++ b/src/utils/ReactUtils.tsx @@ -0,0 +1,33 @@ +/* +Copyright 2021 Šimon Brandner + +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 React from "react"; + +/** + * Joins an array into one value with a joiner. E.g. join(["hello", "world"], " ") -> hello world + * @param array the array of element to join + * @param joiner the string/JSX.Element to join with + * @returns the joined array + */ +export function join(array: Array, joiner?: string | JSX.Element): JSX.Element { + const newArray = []; + array.forEach((element, index) => { + newArray.push(element, (index === array.length - 1) ? null : joiner); + }); + return ( + { newArray } + ); +} From 3e95cd1854017de2cf7cd3e6ecda9c4c09992bda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 11 Jul 2021 10:34:15 +0200 Subject: [PATCH 06/80] Handle JSX in MELS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/EventListSummary.tsx | 2 +- .../views/elements/MemberEventListSummary.tsx | 14 +++++++++++--- src/utils/FormattingUtils.ts | 7 ++++--- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/components/views/elements/EventListSummary.tsx b/src/components/views/elements/EventListSummary.tsx index 681817ca86..4f6df2aa0e 100644 --- a/src/components/views/elements/EventListSummary.tsx +++ b/src/components/views/elements/EventListSummary.tsx @@ -33,7 +33,7 @@ interface IProps { // The list of room members for which to show avatars next to the summary summaryMembers?: RoomMember[]; // The text to show as the summary of this event list - summaryText?: string; + summaryText?: string | JSX.Element; // An array of EventTiles to render when expanded children: ReactNode[]; // Called when the event list expansion is toggled diff --git a/src/components/views/elements/MemberEventListSummary.tsx b/src/components/views/elements/MemberEventListSummary.tsx index d52462f629..cef6195067 100644 --- a/src/components/views/elements/MemberEventListSummary.tsx +++ b/src/components/views/elements/MemberEventListSummary.tsx @@ -25,6 +25,7 @@ import { formatCommaSeparatedList } from '../../../utils/FormattingUtils'; import { isValid3pidInvite } from "../../../RoomInvite"; import EventListSummary from "./EventListSummary"; import { replaceableComponent } from "../../../utils/replaceableComponent"; +import { join } from '../../../utils/ReactUtils'; interface IProps extends Omit, "summaryText" | "summaryMembers"> { // The maximum number of names to show in either each summary e.g. 2 would result "A, B and 234 others left" @@ -89,7 +90,10 @@ export default class MemberEventListSummary extends React.Component { * `Object.keys(eventAggregates)`. * @returns {string} the textual summary of the aggregated events that occurred. */ - private generateSummary(eventAggregates: Record, orderedTransitionSequences: string[]) { + private generateSummary( + eventAggregates: Record, + orderedTransitionSequences: string[], + ): string | JSX.Element { const summaries = orderedTransitionSequences.map((transitions) => { const userNames = eventAggregates[transitions]; const nameList = this.renderNameList(userNames); @@ -118,7 +122,7 @@ export default class MemberEventListSummary extends React.Component { return null; } - return summaries.join(", "); + return join(summaries, ", "); } /** @@ -212,7 +216,11 @@ export default class MemberEventListSummary extends React.Component { * @param {number} repeats the number of times the transition was repeated in a row. * @returns {string} the written Human Readable equivalent of the transition. */ - private static getDescriptionForTransition(t: TransitionType, userCount: number, repeats: number) { + private static getDescriptionForTransition( + t: TransitionType, + userCount: number, + repeats: number, + ): string | JSX.Element { // The empty interpolations 'severalUsers' and 'oneUser' // are there only to show translators to non-English languages // that the verb is conjugated to plural or singular Subject. diff --git a/src/utils/FormattingUtils.ts b/src/utils/FormattingUtils.ts index 1fe3669f26..53a4adb238 100644 --- a/src/utils/FormattingUtils.ts +++ b/src/utils/FormattingUtils.ts @@ -16,6 +16,7 @@ limitations under the License. */ import { _t } from '../languageHandler'; +import { join } from './ReactUtils'; /** * formats numbers to fit into ~3 characters, suitable for badge counts @@ -103,7 +104,7 @@ export function getUserNameColorClass(userId: string): string { * @returns {string} a string constructed by joining `items` with a comma * between each item, but with the last item appended as " and [lastItem]". */ -export function formatCommaSeparatedList(items: string[], itemLimit?: number): string { +export function formatCommaSeparatedList(items: Array, itemLimit?: number): string | JSX.Element { const remaining = itemLimit === undefined ? 0 : Math.max( items.length - itemLimit, 0, ); @@ -113,9 +114,9 @@ export function formatCommaSeparatedList(items: string[], itemLimit?: number): s return items[0]; } else if (remaining > 0) { items = items.slice(0, itemLimit); - return _t("%(items)s and %(count)s others", { items: items.join(', '), count: remaining } ); + return _t("%(items)s and %(count)s others", { items: join(items, ', '), count: remaining } ); } else { const lastItem = items.pop(); - return _t("%(items)s and %(lastItem)s", { items: items.join(', '), lastItem: lastItem }); + return _t("%(items)s and %(lastItem)s", { items: join(items, ', '), lastItem: lastItem }); } } From f80f4620dfec779c6b47c3bc059e6a4d86f124d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 11 Jul 2021 10:35:20 +0200 Subject: [PATCH 07/80] Add pinned messages to MELS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/structures/MessagePanel.tsx | 7 +++- .../views/elements/MemberEventListSummary.tsx | 39 +++++++++++++++---- 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/src/components/structures/MessagePanel.tsx b/src/components/structures/MessagePanel.tsx index a0a1ac9b10..16b1c0064b 100644 --- a/src/components/structures/MessagePanel.tsx +++ b/src/components/structures/MessagePanel.tsx @@ -50,7 +50,12 @@ import EditorStateTransfer from "../../utils/EditorStateTransfer"; const CONTINUATION_MAX_INTERVAL = 5 * 60 * 1000; // 5 minutes const continuedTypes = [EventType.Sticker, EventType.RoomMessage]; -const membershipTypes = [EventType.RoomMember, EventType.RoomThirdPartyInvite, EventType.RoomServerAcl]; +const membershipTypes = [ + EventType.RoomMember, + EventType.RoomThirdPartyInvite, + EventType.RoomServerAcl, + EventType.RoomPinnedEvents, +]; // check if there is a previous event and it has the same sender as this event // and the types are the same/is in continuedTypes and the time between them is <= CONTINUATION_MAX_INTERVAL diff --git a/src/components/views/elements/MemberEventListSummary.tsx b/src/components/views/elements/MemberEventListSummary.tsx index cef6195067..80efb2bea8 100644 --- a/src/components/views/elements/MemberEventListSummary.tsx +++ b/src/components/views/elements/MemberEventListSummary.tsx @@ -25,7 +25,22 @@ import { formatCommaSeparatedList } from '../../../utils/FormattingUtils'; import { isValid3pidInvite } from "../../../RoomInvite"; import EventListSummary from "./EventListSummary"; import { replaceableComponent } from "../../../utils/replaceableComponent"; +import defaultDispatcher from '../../../dispatcher/dispatcher'; +import { RightPanelPhases } from '../../../stores/RightPanelStorePhases'; +import { Action } from '../../../dispatcher/actions'; +import { SetRightPanelPhasePayload } from '../../../dispatcher/payloads/SetRightPanelPhasePayload'; import { join } from '../../../utils/ReactUtils'; +import { EventType } from '../../../../../matrix-js-sdk/src/@types/event'; + +const onPinnedMessagesClick = (): void => { + defaultDispatcher.dispatch({ + action: Action.SetRightPanelPhase, + phase: RightPanelPhases.PinnedMessages, + allowClose: false, + }); +}; + +const SENDER_AS_DISPLAY_NAME_EVENTS = [EventType.RoomServerAcl, EventType.RoomPinnedEvents]; interface IProps extends Omit, "summaryText" | "summaryMembers"> { // The maximum number of names to show in either each summary e.g. 2 would result "A, B and 234 others left" @@ -58,6 +73,7 @@ enum TransitionType { ChangedAvatar = "changed_avatar", NoChange = "no_change", ServerAcl = "server_acl", + PinnedMessages = "pinned_messages" } const SEP = ","; @@ -303,6 +319,15 @@ export default class MemberEventListSummary extends React.Component { { severalUsers: "", count: repeats }) : _t("%(oneUser)schanged the server ACLs %(count)s times", { oneUser: "", count: repeats }); break; + case "pinned_messages": + res = (userCount > 1) + ? _t("%(severalUsers)schanged the pinned messages for the room %(count)s times.", + { severalUsers: "", count: repeats }, + { "a": (sub) => { sub } }) + : _t("%(oneUser)schanged the pinned messages for the room %(count)s times.", + { oneUser: "", count: repeats }, + { "a": (sub) => { sub } }); + break; } return res; @@ -321,16 +346,16 @@ export default class MemberEventListSummary extends React.Component { * if a transition is not recognised. */ private static getTransition(e: IUserEvents): TransitionType { - if (e.mxEvent.getType() === 'm.room.third_party_invite') { + if (e.mxEvent.getType() === EventType.RoomThirdPartyInvite) { // Handle 3pid invites the same as invites so they get bundled together if (!isValid3pidInvite(e.mxEvent)) { return TransitionType.InviteWithdrawal; } return TransitionType.Invited; - } - - if (e.mxEvent.getType() === 'm.room.server_acl') { + } else if (e.mxEvent.getType() === EventType.RoomServerAcl) { return TransitionType.ServerAcl; + } else if (e.mxEvent.getType() === EventType.RoomPinnedEvents) { + return TransitionType.PinnedMessages; } switch (e.mxEvent.getContent().membership) { @@ -425,16 +450,16 @@ export default class MemberEventListSummary extends React.Component { userEvents[userId] = []; } - if (e.getType() === 'm.room.server_acl') { + if (SENDER_AS_DISPLAY_NAME_EVENTS.includes(e.getType() as EventType)) { latestUserAvatarMember.set(userId, e.sender); } else if (e.target) { latestUserAvatarMember.set(userId, e.target); } let displayName = userId; - if (e.getType() === 'm.room.third_party_invite') { + if (e.getType() === EventType.RoomThirdPartyInvite) { displayName = e.getContent().display_name; - } else if (e.getType() === 'm.room.server_acl') { + } else if (SENDER_AS_DISPLAY_NAME_EVENTS.includes(e.getType() as EventType)) { displayName = e.sender.name; } else if (e.target) { displayName = e.target.name; From 1b993ef1bde87f797ce4af6dbb9b5708ca56c430 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 11 Jul 2021 10:36:56 +0200 Subject: [PATCH 08/80] i18n MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/i18n/strings/en_EN.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 7795bb2610..9c22efdb3e 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2058,6 +2058,8 @@ "%(severalUsers)schanged the server ACLs %(count)s times|one": "%(severalUsers)schanged the server ACLs", "%(oneUser)schanged the server ACLs %(count)s times|other": "%(oneUser)schanged the server ACLs %(count)s times", "%(oneUser)schanged the server ACLs %(count)s times|one": "%(oneUser)schanged the server ACLs", + "%(severalUsers)schanged the pinned messages for the room %(count)s times.|other": "%(severalUsers)schanged the pinned messages for the room %(count)s times.", + "%(oneUser)schanged the pinned messages for the room %(count)s times.|other": "%(oneUser)schanged the pinned messages for the room %(count)s times.", "Power level": "Power level", "Custom level": "Custom level", "QR Code": "QR Code", From db8ebd6df009f268ced03b2f1bacedec5b3c300f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 11 Jul 2021 10:45:16 +0200 Subject: [PATCH 09/80] Fix import MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/MemberEventListSummary.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/elements/MemberEventListSummary.tsx b/src/components/views/elements/MemberEventListSummary.tsx index 80efb2bea8..e3dbd0a906 100644 --- a/src/components/views/elements/MemberEventListSummary.tsx +++ b/src/components/views/elements/MemberEventListSummary.tsx @@ -30,7 +30,7 @@ import { RightPanelPhases } from '../../../stores/RightPanelStorePhases'; import { Action } from '../../../dispatcher/actions'; import { SetRightPanelPhasePayload } from '../../../dispatcher/payloads/SetRightPanelPhasePayload'; import { join } from '../../../utils/ReactUtils'; -import { EventType } from '../../../../../matrix-js-sdk/src/@types/event'; +import { EventType } from 'matrix-js-sdk/src/@types/event'; const onPinnedMessagesClick = (): void => { defaultDispatcher.dispatch({ From 29ef5905d60b14f3751698afb96cffe3c663b6b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 11 Jul 2021 10:45:30 +0200 Subject: [PATCH 10/80] Export type into a var MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- .../views/elements/MemberEventListSummary.tsx | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/components/views/elements/MemberEventListSummary.tsx b/src/components/views/elements/MemberEventListSummary.tsx index e3dbd0a906..4ae64b65a5 100644 --- a/src/components/views/elements/MemberEventListSummary.tsx +++ b/src/components/views/elements/MemberEventListSummary.tsx @@ -346,15 +346,17 @@ export default class MemberEventListSummary extends React.Component { * if a transition is not recognised. */ private static getTransition(e: IUserEvents): TransitionType { - if (e.mxEvent.getType() === EventType.RoomThirdPartyInvite) { + const type = e.mxEvent.getType(); + + if (type === EventType.RoomThirdPartyInvite) { // Handle 3pid invites the same as invites so they get bundled together if (!isValid3pidInvite(e.mxEvent)) { return TransitionType.InviteWithdrawal; } return TransitionType.Invited; - } else if (e.mxEvent.getType() === EventType.RoomServerAcl) { + } else if (type === EventType.RoomServerAcl) { return TransitionType.ServerAcl; - } else if (e.mxEvent.getType() === EventType.RoomPinnedEvents) { + } else if (type === EventType.RoomPinnedEvents) { return TransitionType.PinnedMessages; } @@ -444,22 +446,23 @@ export default class MemberEventListSummary extends React.Component { // Object mapping user IDs to an array of IUserEvents const userEvents: Record = {}; eventsToRender.forEach((e, index) => { - const userId = e.getType() === 'm.room.server_acl' ? e.getSender() : e.getStateKey(); + const type = e.getType(); + const userId = type === EventType.RoomServerAcl ? e.getSender() : e.getStateKey(); // Initialise a user's events if (!userEvents[userId]) { userEvents[userId] = []; } - if (SENDER_AS_DISPLAY_NAME_EVENTS.includes(e.getType() as EventType)) { + if (SENDER_AS_DISPLAY_NAME_EVENTS.includes(type as EventType)) { latestUserAvatarMember.set(userId, e.sender); } else if (e.target) { latestUserAvatarMember.set(userId, e.target); } let displayName = userId; - if (e.getType() === EventType.RoomThirdPartyInvite) { + if (type === EventType.RoomThirdPartyInvite) { displayName = e.getContent().display_name; - } else if (SENDER_AS_DISPLAY_NAME_EVENTS.includes(e.getType() as EventType)) { + } else if (SENDER_AS_DISPLAY_NAME_EVENTS.includes(type as EventType)) { displayName = e.sender.name; } else if (e.target) { displayName = e.target.name; From 19f14e4b2e6495cbbe9b7c70f9753b1005ed2e98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 11 Jul 2021 10:58:04 +0200 Subject: [PATCH 11/80] Fix tests? MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/utils/ReactUtils.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/ReactUtils.tsx b/src/utils/ReactUtils.tsx index ce92dd8a51..25669d2d9b 100644 --- a/src/utils/ReactUtils.tsx +++ b/src/utils/ReactUtils.tsx @@ -28,6 +28,6 @@ export function join(array: Array, joiner?: string | JSX.E newArray.push(element, (index === array.length - 1) ? null : joiner); }); return ( - { newArray } + { newArray } ); } From 226224b0394e9100fb56f1e7f80e9212c37b47fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 14 Jul 2021 15:12:35 +0200 Subject: [PATCH 12/80] Allow sending hidden RRs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/structures/TimelinePanel.tsx | 8 ++++++-- .../views/settings/tabs/user/LabsUserSettingsTab.js | 1 + src/settings/Settings.tsx | 7 +++++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/components/structures/TimelinePanel.tsx b/src/components/structures/TimelinePanel.tsx index 85a048e9b8..bd90b637d6 100644 --- a/src/components/structures/TimelinePanel.tsx +++ b/src/components/structures/TimelinePanel.tsx @@ -758,16 +758,20 @@ class TimelinePanel extends React.Component { } this.lastRMSentEventId = this.state.readMarkerEventId; + const roomId = this.props.timelineSet.room.roomId; + const hiddenRR = !SettingsStore.getValue("sendReadReceipts", roomId); + debuglog('TimelinePanel: Sending Read Markers for ', this.props.timelineSet.room.roomId, 'rm', this.state.readMarkerEventId, lastReadEvent ? 'rr ' + lastReadEvent.getId() : '', + ' hidden:' + hiddenRR, ); MatrixClientPeg.get().setRoomReadMarkers( - this.props.timelineSet.room.roomId, + roomId, this.state.readMarkerEventId, lastReadEvent, // Could be null, in which case no RR is sent - {}, + { hidden: hiddenRR }, ).catch((e) => { // /read_markers API is not implemented on this HS, fallback to just RR if (e.errcode === 'M_UNRECOGNIZED' && lastReadEvent) { diff --git a/src/components/views/settings/tabs/user/LabsUserSettingsTab.js b/src/components/views/settings/tabs/user/LabsUserSettingsTab.js index abf9709f50..e57ad80bbc 100644 --- a/src/components/views/settings/tabs/user/LabsUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/LabsUserSettingsTab.js @@ -74,6 +74,7 @@ export default class LabsUserSettingsTab extends React.Component { +
; } diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index 1751eddb2c..163bfad2b3 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -325,6 +325,13 @@ export const SETTINGS: {[setting: string]: ISetting} = { supportedLevels: LEVELS_ACCOUNT_SETTINGS, default: null, }, + "sendReadReceipts": { + supportedLevels: LEVELS_ROOM_SETTINGS, + displayName: _td( + "Send read receipts for messages (requires compatible homeserver to disable)", + ), + default: true, + }, "baseFontSize": { displayName: _td("Font size"), supportedLevels: LEVELS_ACCOUNT_SETTINGS, From 18343d839c9756785ecffdf5d294b0504c597525 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 15 Jul 2021 09:23:15 +0200 Subject: [PATCH 13/80] Show MSC2285 only if supported by server MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- .../settings/tabs/user/LabsUserSettingsTab.js | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/components/views/settings/tabs/user/LabsUserSettingsTab.js b/src/components/views/settings/tabs/user/LabsUserSettingsTab.js index e57ad80bbc..18e78ae7b0 100644 --- a/src/components/views/settings/tabs/user/LabsUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/LabsUserSettingsTab.js @@ -19,11 +19,12 @@ import { _t } from "../../../../../languageHandler"; import PropTypes from "prop-types"; import SettingsStore from "../../../../../settings/SettingsStore"; import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch"; -import * as sdk from "../../../../../index"; import { SettingLevel } from "../../../../../settings/SettingLevel"; import { replaceableComponent } from "../../../../../utils/replaceableComponent"; import SdkConfig from "../../../../../SdkConfig"; import BetaCard from "../../../beta/BetaCard"; +import SettingsFlag from '../../../elements/SettingsFlag'; +import { MatrixClientPeg } from '../../../../../MatrixClientPeg'; export class LabsSettingToggle extends React.Component { static propTypes = { @@ -47,6 +48,14 @@ export class LabsSettingToggle extends React.Component { export default class LabsUserSettingsTab extends React.Component { constructor() { super(); + + MatrixClientPeg.get().doesServerSupportUnstableFeature("org.matrix.msc2285").then((showHiddenReadReceipts) => { + this.setState({ showHiddenReadReceipts }); + }); + + this.state = { + showHiddenReadReceipts: false, + }; } render() { @@ -65,16 +74,22 @@ export default class LabsUserSettingsTab extends React.Component { let labsSection; if (SdkConfig.get()['showLabsSettings']) { - const SettingsFlag = sdk.getComponent("views.elements.SettingsFlag"); const flags = labs.map(f => ); + let hiddenReadReceipts; + if (this.state.showHiddenReadReceipts) { + hiddenReadReceipts = ( + + ); + } + labsSection =
- {flags} + { flags } - + { hiddenReadReceipts }
; } From d9b3c4d19ccf002968a1d242f7f88c462178b66f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 16 Jul 2021 10:22:02 +0200 Subject: [PATCH 14/80] i18n MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/i18n/strings/en_EN.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 5cc900a21b..cad6e438d8 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -823,6 +823,7 @@ "Offline encrypted messaging using dehydrated devices": "Offline encrypted messaging using dehydrated devices", "Enable advanced debugging for the room list": "Enable advanced debugging for the room list", "Show info about bridges in room settings": "Show info about bridges in room settings", + "Send read receipts for messages (requires compatible homeserver to disable)": "Send read receipts for messages (requires compatible homeserver to disable)", "Font size": "Font size", "Use custom size": "Use custom size", "Enable Emoji suggestions while typing": "Enable Emoji suggestions while typing", From e7c5711bb76c93a9cd7e8215a847a247aa54d764 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 22 Jul 2021 07:45:48 +0200 Subject: [PATCH 15/80] membershipTypes -> groupedEvents MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/structures/MessagePanel.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/structures/MessagePanel.tsx b/src/components/structures/MessagePanel.tsx index 16b1c0064b..1e113b0b7b 100644 --- a/src/components/structures/MessagePanel.tsx +++ b/src/components/structures/MessagePanel.tsx @@ -50,7 +50,7 @@ import EditorStateTransfer from "../../utils/EditorStateTransfer"; const CONTINUATION_MAX_INTERVAL = 5 * 60 * 1000; // 5 minutes const continuedTypes = [EventType.Sticker, EventType.RoomMessage]; -const membershipTypes = [ +const groupedEvents = [ EventType.RoomMember, EventType.RoomThirdPartyInvite, EventType.RoomServerAcl, @@ -1185,7 +1185,7 @@ class RedactionGrouper extends BaseGrouper { // Wrap consecutive member events in a ListSummary, ignore if redacted class MemberGrouper extends BaseGrouper { static canStartGroup = function(panel: MessagePanel, ev: MatrixEvent): boolean { - return panel.shouldShowEvent(ev) && membershipTypes.includes(ev.getType() as EventType); + return panel.shouldShowEvent(ev) && groupedEvents.includes(ev.getType() as EventType); }; constructor( @@ -1202,7 +1202,7 @@ class MemberGrouper extends BaseGrouper { if (this.panel.wantsDateSeparator(this.events[0], ev.getDate())) { return false; } - return membershipTypes.includes(ev.getType() as EventType); + return groupedEvents.includes(ev.getType() as EventType); } public add(ev: MatrixEvent): void { From 51c112fd821995c4435488a5bbf2974b39ea706d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 22 Jul 2021 07:46:41 +0200 Subject: [PATCH 16/80] PinnedMessages -> ChangedPins MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/MemberEventListSummary.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/elements/MemberEventListSummary.tsx b/src/components/views/elements/MemberEventListSummary.tsx index 4ae64b65a5..3b1557b9ad 100644 --- a/src/components/views/elements/MemberEventListSummary.tsx +++ b/src/components/views/elements/MemberEventListSummary.tsx @@ -73,7 +73,7 @@ enum TransitionType { ChangedAvatar = "changed_avatar", NoChange = "no_change", ServerAcl = "server_acl", - PinnedMessages = "pinned_messages" + ChangedPins = "pinned_messages" } const SEP = ","; @@ -357,7 +357,7 @@ export default class MemberEventListSummary extends React.Component { } else if (type === EventType.RoomServerAcl) { return TransitionType.ServerAcl; } else if (type === EventType.RoomPinnedEvents) { - return TransitionType.PinnedMessages; + return TransitionType.ChangedPins; } switch (e.mxEvent.getContent().membership) { From 9f227893b1a093de8b1b41dea724dc3be3058f63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 22 Jul 2021 07:47:04 +0200 Subject: [PATCH 17/80] join -> jsxJoin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/MemberEventListSummary.tsx | 4 ++-- src/utils/FormattingUtils.ts | 6 +++--- src/utils/ReactUtils.tsx | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/views/elements/MemberEventListSummary.tsx b/src/components/views/elements/MemberEventListSummary.tsx index 3b1557b9ad..46a27415ca 100644 --- a/src/components/views/elements/MemberEventListSummary.tsx +++ b/src/components/views/elements/MemberEventListSummary.tsx @@ -29,7 +29,7 @@ import defaultDispatcher from '../../../dispatcher/dispatcher'; import { RightPanelPhases } from '../../../stores/RightPanelStorePhases'; import { Action } from '../../../dispatcher/actions'; import { SetRightPanelPhasePayload } from '../../../dispatcher/payloads/SetRightPanelPhasePayload'; -import { join } from '../../../utils/ReactUtils'; +import { jsxJoin } from '../../../utils/ReactUtils'; import { EventType } from 'matrix-js-sdk/src/@types/event'; const onPinnedMessagesClick = (): void => { @@ -138,7 +138,7 @@ export default class MemberEventListSummary extends React.Component { return null; } - return join(summaries, ", "); + return jsxJoin(summaries, ", "); } /** diff --git a/src/utils/FormattingUtils.ts b/src/utils/FormattingUtils.ts index 53a4adb238..b527ee7ea2 100644 --- a/src/utils/FormattingUtils.ts +++ b/src/utils/FormattingUtils.ts @@ -16,7 +16,7 @@ limitations under the License. */ import { _t } from '../languageHandler'; -import { join } from './ReactUtils'; +import { jsxJoin } from './ReactUtils'; /** * formats numbers to fit into ~3 characters, suitable for badge counts @@ -114,9 +114,9 @@ export function formatCommaSeparatedList(items: Array, ite return items[0]; } else if (remaining > 0) { items = items.slice(0, itemLimit); - return _t("%(items)s and %(count)s others", { items: join(items, ', '), count: remaining } ); + return _t("%(items)s and %(count)s others", { items: jsxJoin(items, ', '), count: remaining } ); } else { const lastItem = items.pop(); - return _t("%(items)s and %(lastItem)s", { items: join(items, ', '), lastItem: lastItem }); + return _t("%(items)s and %(lastItem)s", { items: jsxJoin(items, ', '), lastItem: lastItem }); } } diff --git a/src/utils/ReactUtils.tsx b/src/utils/ReactUtils.tsx index 25669d2d9b..4cd2d750f3 100644 --- a/src/utils/ReactUtils.tsx +++ b/src/utils/ReactUtils.tsx @@ -22,7 +22,7 @@ import React from "react"; * @param joiner the string/JSX.Element to join with * @returns the joined array */ -export function join(array: Array, joiner?: string | JSX.Element): JSX.Element { +export function jsxJoin(array: Array, joiner?: string | JSX.Element): JSX.Element { const newArray = []; array.forEach((element, index) => { newArray.push(element, (index === array.length - 1) ? null : joiner); From 5f6a1e336e1be8e45144aaa4739d8bd518eaa208 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 27 Jul 2021 18:46:19 +0200 Subject: [PATCH 18/80] Remove unnecessary curly braces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/settings/tabs/user/LabsUserSettingsTab.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/settings/tabs/user/LabsUserSettingsTab.js b/src/components/views/settings/tabs/user/LabsUserSettingsTab.js index 20444c1ce7..5274d5191d 100644 --- a/src/components/views/settings/tabs/user/LabsUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/LabsUserSettingsTab.js @@ -79,7 +79,7 @@ export default class LabsUserSettingsTab extends React.Component { let hiddenReadReceipts; if (this.state.showHiddenReadReceipts) { hiddenReadReceipts = ( - + ); } From 507dcd91d9acd1527ad030f66daf5a948f9e7831 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 27 Jul 2021 18:53:57 +0200 Subject: [PATCH 19/80] Fix i18n? MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/i18n/strings/en_EN.json | 65 +++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 8e05630f05..89252c5f07 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -529,7 +529,9 @@ "%(senderName)s set the main address for this room to %(address)s.": "%(senderName)s set the main address for this room to %(address)s.", "%(senderName)s removed the main address for this room.": "%(senderName)s removed the main address for this room.", "%(senderName)s added the alternative addresses %(addresses)s for this room.|other": "%(senderName)s added the alternative addresses %(addresses)s for this room.", + "%(senderName)s added the alternative addresses %(addresses)s for this room.|one": "%(senderName)s added alternative address %(addresses)s for this room.", "%(senderName)s removed the alternative addresses %(addresses)s for this room.|other": "%(senderName)s removed the alternative addresses %(addresses)s for this room.", + "%(senderName)s removed the alternative addresses %(addresses)s for this room.|one": "%(senderName)s removed alternative address %(addresses)s for this room.", "%(senderName)s changed the alternative addresses for this room.": "%(senderName)s changed the alternative addresses for this room.", "%(senderName)s changed the main and alternative addresses for this room.": "%(senderName)s changed the main and alternative addresses for this room.", "%(senderName)s changed the addresses for this room.": "%(senderName)s changed the addresses for this room.", @@ -570,6 +572,7 @@ "Dark": "Dark", "%(displayName)s is typing …": "%(displayName)s is typing …", "%(names)s and %(count)s others are typing …|other": "%(names)s and %(count)s others are typing …", + "%(names)s and %(count)s others are typing …|one": "%(names)s and one other is typing …", "%(names)s and %(lastPerson)s are typing …": "%(names)s and %(lastPerson)s are typing …", "Remain on your screen when viewing another room, when running": "Remain on your screen when viewing another room, when running", "Remain on your screen while running": "Remain on your screen while running", @@ -650,6 +653,7 @@ "Unable to connect to Homeserver. Retrying...": "Unable to connect to Homeserver. Retrying...", "Attachment": "Attachment", "%(items)s and %(count)s others|other": "%(items)s and %(count)s others", + "%(items)s and %(count)s others|one": "%(items)s and one other", "%(items)s and %(lastItem)s": "%(items)s and %(lastItem)s", "a few seconds ago": "a few seconds ago", "about a minute ago": "about a minute ago", @@ -1102,11 +1106,15 @@ "Your homeserver does not support session management.": "Your homeserver does not support session management.", "Unable to load session list": "Unable to load session list", "Confirm deleting these sessions by using Single Sign On to prove your identity.|other": "Confirm deleting these sessions by using Single Sign On to prove your identity.", + "Confirm deleting these sessions by using Single Sign On to prove your identity.|one": "Confirm deleting this session by using Single Sign On to prove your identity.", "Confirm deleting these sessions": "Confirm deleting these sessions", "Click the button below to confirm deleting these sessions.|other": "Click the button below to confirm deleting these sessions.", + "Click the button below to confirm deleting these sessions.|one": "Click the button below to confirm deleting this session.", "Delete sessions|other": "Delete sessions", + "Delete sessions|one": "Delete session", "Authentication": "Authentication", "Delete %(count)s sessions|other": "Delete %(count)s sessions", + "Delete %(count)s sessions|one": "Delete %(count)s session", "ID": "ID", "Public Name": "Public Name", "Last seen": "Last seen", @@ -1114,6 +1122,7 @@ "Encryption": "Encryption", "Individually verify each session used by a user to mark it as trusted, not trusting cross-signed devices.": "Individually verify each session used by a user to mark it as trusted, not trusting cross-signed devices.", "Securely cache encrypted messages locally for them to appear in search results, using %(size)s to store messages from %(rooms)s rooms.|other": "Securely cache encrypted messages locally for them to appear in search results, using %(size)s to store messages from %(rooms)s rooms.", + "Securely cache encrypted messages locally for them to appear in search results, using %(size)s to store messages from %(rooms)s rooms.|one": "Securely cache encrypted messages locally for them to appear in search results, using %(size)s to store messages from %(rooms)s room.", "Manage": "Manage", "Securely cache encrypted messages locally for them to appear in search results.": "Securely cache encrypted messages locally for them to appear in search results.", "%(brand)s is missing some components required for securely caching encrypted messages locally. If you'd like to experiment with this feature, build a custom %(brand)s Desktop with search components added.": "%(brand)s is missing some components required for securely caching encrypted messages locally. If you'd like to experiment with this feature, build a custom %(brand)s Desktop with search components added.", @@ -1503,8 +1512,10 @@ "Failed to send": "Failed to send", "Scroll to most recent messages": "Scroll to most recent messages", "Show %(count)s other previews|other": "Show %(count)s other previews", + "Show %(count)s other previews|one": "Show %(count)s other preview", "Close preview": "Close preview", "and %(count)s others...|other": "and %(count)s others...", + "and %(count)s others...|one": "and one other...", "Invite to this room": "Invite to this room", "Invite to this community": "Invite to this community", "Invite to this space": "Invite to this space", @@ -1566,6 +1577,7 @@ "Screen sharing is here!": "Screen sharing is here!", "You can now share your screen by pressing the \"screen share\" button during a call. You can even do this in audio calls if both sides support it!": "You can now share your screen by pressing the \"screen share\" button during a call. You can even do this in audio calls if both sides support it!", "(~%(count)s results)|other": "(~%(count)s results)", + "(~%(count)s results)|one": "(~%(count)s result)", "Join Room": "Join Room", "Forget room": "Forget room", "Hide Widgets": "Hide Widgets", @@ -1595,7 +1607,9 @@ "Quick actions": "Quick actions", "Use the + to make a new room or explore existing ones below": "Use the + to make a new room or explore existing ones below", "%(count)s results in all spaces|other": "%(count)s results in all spaces", + "%(count)s results in all spaces|one": "%(count)s result in all spaces", "%(count)s results|other": "%(count)s results", + "%(count)s results|one": "%(count)s result", "This room": "This room", "Joining room …": "Joining room …", "Loading …": "Loading …", @@ -1644,6 +1658,7 @@ "Jump to first unread room.": "Jump to first unread room.", "Jump to first invite.": "Jump to first invite.", "Show %(count)s more|other": "Show %(count)s more", + "Show %(count)s more|one": "Show %(count)s more", "Show less": "Show less", "Use default": "Use default", "All messages": "All messages", @@ -1658,7 +1673,9 @@ "Leave Room": "Leave Room", "Room options": "Room options", "%(count)s unread messages including mentions.|other": "%(count)s unread messages including mentions.", + "%(count)s unread messages including mentions.|one": "1 unread mention.", "%(count)s unread messages.|other": "%(count)s unread messages.", + "%(count)s unread messages.|one": "1 unread message.", "Unread messages.": "Unread messages.", "Upgrading this room will shut down the current instance of the room and create an upgraded room with the same name.": "Upgrading this room will shut down the current instance of the room and create an upgraded room with the same name.", "This room has already been upgraded.": "This room has already been upgraded.", @@ -1760,14 +1777,17 @@ "Not encrypted": "Not encrypted", "About": "About", "%(count)s people|other": "%(count)s people", + "%(count)s people|one": "%(count)s person", "Show files": "Show files", "Share room": "Share room", "Room settings": "Room settings", "Trusted": "Trusted", "Not trusted": "Not trusted", "%(count)s verified sessions|other": "%(count)s verified sessions", + "%(count)s verified sessions|one": "1 verified session", "Hide verified sessions": "Hide verified sessions", "%(count)s sessions|other": "%(count)s sessions", + "%(count)s sessions|one": "%(count)s session", "Hide sessions": "Hide sessions", "Jump to read receipt": "Jump to read receipt", "Mention": "Mention", @@ -1787,8 +1807,10 @@ "Try scrolling up in the timeline to see if there are any earlier ones.": "Try scrolling up in the timeline to see if there are any earlier ones.", "Remove recent messages by %(user)s": "Remove recent messages by %(user)s", "You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?|other": "You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?", + "You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?|one": "You are about to remove 1 message by %(user)s. This cannot be undone. Do you wish to continue?", "For a large amount of messages, this might take some time. Please don't refresh your client in the meantime.": "For a large amount of messages, this might take some time. Please don't refresh your client in the meantime.", "Remove %(count)s messages|other": "Remove %(count)s messages", + "Remove %(count)s messages|one": "Remove 1 message", "Remove recent messages": "Remove recent messages", "Ban": "Ban", "Unban this user?": "Unban this user?", @@ -1991,9 +2013,12 @@ "collapse": "collapse", "expand": "expand", "View all %(count)s members|other": "View all %(count)s members", + "View all %(count)s members|one": "View 1 member", "Including %(commaSeparatedMembers)s": "Including %(commaSeparatedMembers)s", "%(count)s members including %(commaSeparatedMembers)s|other": "%(count)s members including %(commaSeparatedMembers)s", + "%(count)s members including %(commaSeparatedMembers)s|one": "%(commaSeparatedMembers)s", "%(count)s people you know have already joined|other": "%(count)s people you know have already joined", + "%(count)s people you know have already joined|one": "%(count)s person you know has already joined", "Zoom out": "Zoom out", "Zoom in": "Zoom in", "Rotate Left": "Rotate Left", @@ -2002,33 +2027,61 @@ "Language Dropdown": "Language Dropdown", "%(nameList)s %(transitionList)s": "%(nameList)s %(transitionList)s", "%(severalUsers)sjoined %(count)s times|other": "%(severalUsers)sjoined %(count)s times", + "%(severalUsers)sjoined %(count)s times|one": "%(severalUsers)sjoined", "%(oneUser)sjoined %(count)s times|other": "%(oneUser)sjoined %(count)s times", + "%(oneUser)sjoined %(count)s times|one": "%(oneUser)sjoined", "%(severalUsers)sleft %(count)s times|other": "%(severalUsers)sleft %(count)s times", + "%(severalUsers)sleft %(count)s times|one": "%(severalUsers)sleft", "%(oneUser)sleft %(count)s times|other": "%(oneUser)sleft %(count)s times", + "%(oneUser)sleft %(count)s times|one": "%(oneUser)sleft", "%(severalUsers)sjoined and left %(count)s times|other": "%(severalUsers)sjoined and left %(count)s times", + "%(severalUsers)sjoined and left %(count)s times|one": "%(severalUsers)sjoined and left", "%(oneUser)sjoined and left %(count)s times|other": "%(oneUser)sjoined and left %(count)s times", + "%(oneUser)sjoined and left %(count)s times|one": "%(oneUser)sjoined and left", "%(severalUsers)sleft and rejoined %(count)s times|other": "%(severalUsers)sleft and rejoined %(count)s times", + "%(severalUsers)sleft and rejoined %(count)s times|one": "%(severalUsers)sleft and rejoined", "%(oneUser)sleft and rejoined %(count)s times|other": "%(oneUser)sleft and rejoined %(count)s times", + "%(oneUser)sleft and rejoined %(count)s times|one": "%(oneUser)sleft and rejoined", "%(severalUsers)srejected their invitations %(count)s times|other": "%(severalUsers)srejected their invitations %(count)s times", + "%(severalUsers)srejected their invitations %(count)s times|one": "%(severalUsers)srejected their invitations", "%(oneUser)srejected their invitation %(count)s times|other": "%(oneUser)srejected their invitation %(count)s times", + "%(oneUser)srejected their invitation %(count)s times|one": "%(oneUser)srejected their invitation", "%(severalUsers)shad their invitations withdrawn %(count)s times|other": "%(severalUsers)shad their invitations withdrawn %(count)s times", + "%(severalUsers)shad their invitations withdrawn %(count)s times|one": "%(severalUsers)shad their invitations withdrawn", "%(oneUser)shad their invitation withdrawn %(count)s times|other": "%(oneUser)shad their invitation withdrawn %(count)s times", + "%(oneUser)shad their invitation withdrawn %(count)s times|one": "%(oneUser)shad their invitation withdrawn", "were invited %(count)s times|other": "were invited %(count)s times", + "were invited %(count)s times|one": "were invited", "was invited %(count)s times|other": "was invited %(count)s times", + "was invited %(count)s times|one": "was invited", "were banned %(count)s times|other": "were banned %(count)s times", + "were banned %(count)s times|one": "were banned", "was banned %(count)s times|other": "was banned %(count)s times", + "was banned %(count)s times|one": "was banned", "were unbanned %(count)s times|other": "were unbanned %(count)s times", + "were unbanned %(count)s times|one": "were unbanned", "was unbanned %(count)s times|other": "was unbanned %(count)s times", + "was unbanned %(count)s times|one": "was unbanned", "were kicked %(count)s times|other": "were kicked %(count)s times", + "were kicked %(count)s times|one": "were kicked", "was kicked %(count)s times|other": "was kicked %(count)s times", + "was kicked %(count)s times|one": "was kicked", "%(severalUsers)schanged their name %(count)s times|other": "%(severalUsers)schanged their name %(count)s times", + "%(severalUsers)schanged their name %(count)s times|one": "%(severalUsers)schanged their name", "%(oneUser)schanged their name %(count)s times|other": "%(oneUser)schanged their name %(count)s times", + "%(oneUser)schanged their name %(count)s times|one": "%(oneUser)schanged their name", "%(severalUsers)schanged their avatar %(count)s times|other": "%(severalUsers)schanged their avatar %(count)s times", + "%(severalUsers)schanged their avatar %(count)s times|one": "%(severalUsers)schanged their avatar", "%(oneUser)schanged their avatar %(count)s times|other": "%(oneUser)schanged their avatar %(count)s times", + "%(oneUser)schanged their avatar %(count)s times|one": "%(oneUser)schanged their avatar", "%(severalUsers)smade no changes %(count)s times|other": "%(severalUsers)smade no changes %(count)s times", + "%(severalUsers)smade no changes %(count)s times|one": "%(severalUsers)smade no changes", "%(oneUser)smade no changes %(count)s times|other": "%(oneUser)smade no changes %(count)s times", + "%(oneUser)smade no changes %(count)s times|one": "%(oneUser)smade no changes", "%(severalUsers)schanged the server ACLs %(count)s times|other": "%(severalUsers)schanged the server ACLs %(count)s times", + "%(severalUsers)schanged the server ACLs %(count)s times|one": "%(severalUsers)schanged the server ACLs", "%(oneUser)schanged the server ACLs %(count)s times|other": "%(oneUser)schanged the server ACLs %(count)s times", + "%(oneUser)schanged the server ACLs %(count)s times|one": "%(oneUser)schanged the server ACLs", "Power level": "Power level", "Custom level": "Custom level", "QR Code": "QR Code", @@ -2063,6 +2116,7 @@ "Matrix rooms": "Matrix rooms", "Not all selected were added": "Not all selected were added", "Adding rooms... (%(progress)s out of %(count)s)|other": "Adding rooms... (%(progress)s out of %(count)s)", + "Adding rooms... (%(progress)s out of %(count)s)|one": "Adding room...", "Filter your rooms and spaces": "Filter your rooms and spaces", "Feeling experimental?": "Feeling experimental?", "You can add existing spaces to a space.": "You can add existing spaces to a space.", @@ -2116,6 +2170,7 @@ "Hide": "Hide", "Show": "Show", "Send %(count)s invites|other": "Send %(count)s invites", + "Send %(count)s invites|one": "Send %(count)s invite", "Invite people to join %(communityName)s": "Invite people to join %(communityName)s", "You cannot delete this message. (%(code)s)": "You cannot delete this message. (%(code)s)", "Removing…": "Removing…", @@ -2315,7 +2370,9 @@ "You'll lose access to your encrypted messages": "You'll lose access to your encrypted messages", "Are you sure you want to sign out?": "Are you sure you want to sign out?", "%(count)s members|other": "%(count)s members", + "%(count)s members|one": "%(count)s member", "%(count)s rooms|other": "%(count)s rooms", + "%(count)s rooms|one": "%(count)s room", "You're removing all spaces. Access will default to invite only": "You're removing all spaces. Access will default to invite only", "Select spaces": "Select spaces", "Decide which spaces can access this room. If a space is selected, its members can find and join .": "Decide which spaces can access this room. If a space is selected, its members can find and join .", @@ -2447,6 +2504,7 @@ "These files are too large to upload. The file size limit is %(limit)s.": "These files are too large to upload. The file size limit is %(limit)s.", "Some files are too large to be uploaded. The file size limit is %(limit)s.": "Some files are too large to be uploaded. The file size limit is %(limit)s.", "Upload %(count)s other files|other": "Upload %(count)s other files", + "Upload %(count)s other files|one": "Upload %(count)s other file", "Cancel All": "Cancel All", "Upload Error": "Upload Error", "Verify other login": "Verify other login", @@ -2670,6 +2728,7 @@ "%(creator)s created this DM.": "%(creator)s created this DM.", "%(creator)s created and configured the room.": "%(creator)s created and configured the room.", "%(count)s messages deleted.|other": "%(count)s messages deleted.", + "%(count)s messages deleted.|one": "%(count)s message deleted.", "Your Communities": "Your Communities", "Did you know: you can use communities to filter your %(brand)s experience!": "Did you know: you can use communities to filter your %(brand)s experience!", "You can click on an avatar in the filter panel at any time to see only the rooms and people associated with that community.": "You can click on an avatar in the filter panel at any time to see only the rooms and people associated with that community.", @@ -2725,12 +2784,15 @@ "Failed to reject invite": "Failed to reject invite", "Drop file here to upload": "Drop file here to upload", "You have %(count)s unread notifications in a prior version of this room.|other": "You have %(count)s unread notifications in a prior version of this room.", + "You have %(count)s unread notifications in a prior version of this room.|one": "You have %(count)s unread notification in a prior version of this room.", "You don't have permission": "You don't have permission", "This room is suggested as a good one to join": "This room is suggested as a good one to join", "Suggested": "Suggested", "Your server does not support showing space hierarchies.": "Your server does not support showing space hierarchies.", "%(count)s rooms and %(numSpaces)s spaces|other": "%(count)s rooms and %(numSpaces)s spaces", + "%(count)s rooms and %(numSpaces)s spaces|one": "%(count)s room and %(numSpaces)s spaces", "%(count)s rooms and 1 space|other": "%(count)s rooms and 1 space", + "%(count)s rooms and 1 space|one": "%(count)s room and 1 space", "Select a room below first": "Select a room below first", "Failed to remove some rooms. Try again later": "Failed to remove some rooms. Try again later", "Removing...": "Removing...", @@ -2784,6 +2846,8 @@ "Tried to load a specific point in this room's timeline, but was unable to find it.": "Tried to load a specific point in this room's timeline, but was unable to find it.", "Failed to load timeline position": "Failed to load timeline position", "Uploading %(filename)s and %(count)s others|other": "Uploading %(filename)s and %(count)s others", + "Uploading %(filename)s and %(count)s others|zero": "Uploading %(filename)s", + "Uploading %(filename)s and %(count)s others|one": "Uploading %(filename)s and %(count)s other", "Failed to find the general chat for this community": "Failed to find the general chat for this community", "Got an account? Sign in": "Got an account? Sign in", "New here? Create an account": "New here? Create an account", @@ -2798,6 +2862,7 @@ "User menu": "User menu", "Community and user menu": "Community and user menu", "Currently joining %(count)s rooms|other": "Currently joining %(count)s rooms", + "Currently joining %(count)s rooms|one": "Currently joining %(count)s room", "Could not load user profile": "Could not load user profile", "Decrypted event source": "Decrypted event source", "Original event source": "Original event source", From 9ef70f027ea9264adf339dfd173e493ff6d9cdbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 2 Aug 2021 11:36:53 +0200 Subject: [PATCH 20/80] Make the version box smaller MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/settings/tabs/user/_HelpUserSettingsTab.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/views/settings/tabs/user/_HelpUserSettingsTab.scss b/res/css/views/settings/tabs/user/_HelpUserSettingsTab.scss index 1498f6fbf0..fbbe9909e7 100644 --- a/res/css/views/settings/tabs/user/_HelpUserSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_HelpUserSettingsTab.scss @@ -30,12 +30,12 @@ limitations under the License. .mx_HelpUserSettingsTab_copy { display: flex; - justify-content: space-between; border-radius: 5px; border: solid 1px $light-fg-color; margin-bottom: 10px; margin-top: 10px; padding: 10px; + width: max-content; .mx_HelpUserSettingsTab_copyButton { flex-shrink: 0; From ae411b94011ce1a74a771f458c55d8b064d279b1 Mon Sep 17 00:00:00 2001 From: Dariusz Niemczyk Date: Tue, 3 Aug 2021 12:40:37 +0200 Subject: [PATCH 21/80] Refactor Call components into smaller pieces --- src/components/views/voip/CallPreview.tsx | 178 +-------------- src/components/views/voip/CallView.tsx | 118 +--------- .../views/voip/CallView/CallViewHeader.tsx | 135 ++++++++++++ .../views/voip/PictureInPictureDragger.tsx | 208 ++++++++++++++++++ 4 files changed, 363 insertions(+), 276 deletions(-) create mode 100644 src/components/views/voip/CallView/CallViewHeader.tsx create mode 100644 src/components/views/voip/PictureInPictureDragger.tsx diff --git a/src/components/views/voip/CallPreview.tsx b/src/components/views/voip/CallPreview.tsx index 6261b9965f..565e4657b9 100644 --- a/src/components/views/voip/CallPreview.tsx +++ b/src/components/views/voip/CallPreview.tsx @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { createRef } from 'react'; +import React from 'react'; import CallView from "./CallView"; import RoomViewStore from '../../../stores/RoomViewStore'; @@ -27,23 +27,8 @@ import SettingsStore from "../../../settings/SettingsStore"; import { CallEvent, CallState, MatrixCall } from 'matrix-js-sdk/src/webrtc/call'; import { MatrixClientPeg } from '../../../MatrixClientPeg'; import { replaceableComponent } from "../../../utils/replaceableComponent"; -import UIStore from '../../../stores/UIStore'; -import { lerp } from '../../../utils/AnimationUtils'; -import { MarkedExecution } from '../../../utils/MarkedExecution'; import { EventSubscription } from 'fbemitter'; - -const PIP_VIEW_WIDTH = 336; -const PIP_VIEW_HEIGHT = 232; - -const MOVING_AMT = 0.2; -const SNAPPING_AMT = 0.05; - -const PADDING = { - top: 58, - bottom: 58, - left: 76, - right: 8, -}; +import { PictureInPictureDragger } from './PictureInPictureDragger'; const SHOW_CALL_IN_STATES = [ CallState.Connected, @@ -66,10 +51,6 @@ interface IState { // Any other call we're displaying: only if the user is on two calls and not viewing either of the rooms // they belong to secondaryCall: MatrixCall; - - // Position of the CallPreview - translationX: number; - translationY: number; } // Splits a list of calls into one 'primary' one and a list @@ -112,16 +93,6 @@ export default class CallPreview extends React.Component { private roomStoreToken: EventSubscription; private dispatcherRef: string; private settingsWatcherRef: string; - private callViewWrapper = createRef(); - private initX = 0; - private initY = 0; - private desiredTranslationX = UIStore.instance.windowWidth - PADDING.right - PIP_VIEW_WIDTH; - private desiredTranslationY = UIStore.instance.windowHeight - PADDING.bottom - PIP_VIEW_WIDTH; - private moving = false; - private scheduledUpdate = new MarkedExecution( - () => this.animationCallback(), - () => requestAnimationFrame(() => this.scheduledUpdate.trigger()), - ); constructor(props: IProps) { super(props); @@ -136,17 +107,12 @@ export default class CallPreview extends React.Component { roomId, primaryCall: primaryCall, secondaryCall: secondaryCalls[0], - translationX: UIStore.instance.windowWidth - PADDING.right - PIP_VIEW_WIDTH, - translationY: UIStore.instance.windowHeight - PADDING.bottom - PIP_VIEW_WIDTH, }; } public componentDidMount() { CallHandler.sharedInstance().addListener(CallHandlerEvent.CallChangeRoom, this.updateCalls); this.roomStoreToken = RoomViewStore.addListener(this.onRoomViewStoreUpdate); - document.addEventListener("mousemove", this.onMoving); - document.addEventListener("mouseup", this.onEndMoving); - window.addEventListener("resize", this.onResize); this.dispatcherRef = dis.register(this.onAction); MatrixClientPeg.get().on(CallEvent.RemoteHoldUnhold, this.onCallRemoteHold); } @@ -154,9 +120,6 @@ export default class CallPreview extends React.Component { public componentWillUnmount() { CallHandler.sharedInstance().removeListener(CallHandlerEvent.CallChangeRoom, this.updateCalls); MatrixClientPeg.get().removeListener(CallEvent.RemoteHoldUnhold, this.onCallRemoteHold); - document.removeEventListener("mousemove", this.onMoving); - document.removeEventListener("mouseup", this.onEndMoving); - window.removeEventListener("resize", this.onResize); if (this.roomStoreToken) { this.roomStoreToken.remove(); } @@ -164,94 +127,6 @@ export default class CallPreview extends React.Component { SettingsStore.unwatchSetting(this.settingsWatcherRef); } - private onResize = (): void => { - this.snap(false); - }; - - private animationCallback = () => { - // If the PiP isn't being dragged and there is only a tiny difference in - // the desiredTranslation and translation, quit the animationCallback - // loop. If that is the case, it means the PiP has snapped into its - // position and there is nothing to do. Not doing this would cause an - // infinite loop - if ( - !this.moving && - Math.abs(this.state.translationX - this.desiredTranslationX) <= 1 && - Math.abs(this.state.translationY - this.desiredTranslationY) <= 1 - ) return; - - const amt = this.moving ? MOVING_AMT : SNAPPING_AMT; - this.setState({ - translationX: lerp(this.state.translationX, this.desiredTranslationX, amt), - translationY: lerp(this.state.translationY, this.desiredTranslationY, amt), - }); - this.scheduledUpdate.mark(); - }; - - private setTranslation(inTranslationX: number, inTranslationY: number) { - const width = this.callViewWrapper.current?.clientWidth || PIP_VIEW_WIDTH; - const height = this.callViewWrapper.current?.clientHeight || PIP_VIEW_HEIGHT; - - // Avoid overflow on the x axis - if (inTranslationX + width >= UIStore.instance.windowWidth) { - this.desiredTranslationX = UIStore.instance.windowWidth - width; - } else if (inTranslationX <= 0) { - this.desiredTranslationX = 0; - } else { - this.desiredTranslationX = inTranslationX; - } - - // Avoid overflow on the y axis - if (inTranslationY + height >= UIStore.instance.windowHeight) { - this.desiredTranslationY = UIStore.instance.windowHeight - height; - } else if (inTranslationY <= 0) { - this.desiredTranslationY = 0; - } else { - this.desiredTranslationY = inTranslationY; - } - } - - private snap(animate?: boolean): void { - const translationX = this.desiredTranslationX; - const translationY = this.desiredTranslationY; - // We subtract the PiP size from the window size in order to calculate - // the position to snap to from the PiP center and not its top-left - // corner - const windowWidth = ( - UIStore.instance.windowWidth - - (this.callViewWrapper.current?.clientWidth || PIP_VIEW_WIDTH) - ); - const windowHeight = ( - UIStore.instance.windowHeight - - (this.callViewWrapper.current?.clientHeight || PIP_VIEW_HEIGHT) - ); - - if (translationX >= windowWidth / 2 && translationY >= windowHeight / 2) { - this.desiredTranslationX = windowWidth - PADDING.right; - this.desiredTranslationY = windowHeight - PADDING.bottom; - } else if (translationX >= windowWidth / 2 && translationY <= windowHeight / 2) { - this.desiredTranslationX = windowWidth - PADDING.right; - this.desiredTranslationY = PADDING.top; - } else if (translationX <= windowWidth / 2 && translationY >= windowHeight / 2) { - this.desiredTranslationX = PADDING.left; - this.desiredTranslationY = windowHeight - PADDING.bottom; - } else { - this.desiredTranslationX = PADDING.left; - this.desiredTranslationY = PADDING.top; - } - - if (animate) { - // We start animating here because we want the PiP to move when we're - // resizing the window - this.scheduledUpdate.mark(); - } else { - this.setState({ - translationX: this.desiredTranslationX, - translationY: this.desiredTranslationY, - }); - } - } - private onRoomViewStoreUpdate = () => { if (RoomViewStore.getRoomId() === this.state.roomId) return; @@ -300,53 +175,22 @@ export default class CallPreview extends React.Component { }); }; - private onStartMoving = (event: React.MouseEvent) => { - event.preventDefault(); - event.stopPropagation(); - - this.moving = true; - this.initX = event.pageX - this.desiredTranslationX; - this.initY = event.pageY - this.desiredTranslationY; - this.scheduledUpdate.mark(); - }; - - private onMoving = (event: React.MouseEvent | MouseEvent) => { - if (!this.moving) return; - - event.preventDefault(); - event.stopPropagation(); - - this.setTranslation(event.pageX - this.initX, event.pageY - this.initY); - }; - - private onEndMoving = () => { - this.moving = false; - this.snap(true); - }; - public render() { + const pipMode = true; if (this.state.primaryCall) { - const translatePixelsX = this.state.translationX + "px"; - const translatePixelsY = this.state.translationY + "px"; - const style = { - transform: `translateX(${translatePixelsX}) - translateY(${translatePixelsY})`, - }; - return ( -
- -
+ pipMode={pipMode} + /> } + + ); } diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index 356e642d65..096cd8a6a9 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -37,6 +37,7 @@ import DesktopCapturerSourcePicker from "../elements/DesktopCapturerSourcePicker import Modal from '../../../Modal'; import { SDPStreamMetadataPurpose } from 'matrix-js-sdk/src/webrtc/callEventTypes'; import CallViewSidebar from './CallViewSidebar'; +import { CallViewHeader } from './CallView/CallViewHeader'; interface IProps { // The call for us to display @@ -56,7 +57,7 @@ interface IProps { pipMode?: boolean; // Used for dragging the PiP CallView - onMouseDownOnHeader?: (event: React.MouseEvent) => void; + onMouseDownOnHeader?: (event: React.MouseEvent) => void; } interface IState { @@ -115,7 +116,6 @@ export default class CallView extends React.Component { private controlsHideTimer: number = null; private dialpadButton = createRef(); private contextMenuButton = createRef(); - private contextMenu = createRef(); constructor(props: IProps) { super(props); @@ -231,21 +231,6 @@ export default class CallView extends React.Component { }); }; - private onFullscreenClick = () => { - dis.dispatch({ - action: 'video_fullscreen', - fullscreen: true, - }); - }; - - private onExpandClick = () => { - const userFacingRoomId = CallHandler.sharedInstance().roomIdForCall(this.props.call); - dis.dispatch({ - action: 'view_room', - room_id: userFacingRoomId, - }); - }; - private onControlsHideTimer = () => { if (this.state.hoveringControls || this.state.showDialpad || this.state.showMoreMenu) return; this.controlsHideTimer = null; @@ -389,23 +374,6 @@ export default class CallView extends React.Component { this.setState({ hoveringControls: false }); }; - private onRoomAvatarClick = (): void => { - const userFacingRoomId = CallHandler.sharedInstance().roomIdForCall(this.props.call); - dis.dispatch({ - action: 'view_room', - room_id: userFacingRoomId, - }); - }; - - private onSecondaryRoomAvatarClick = (): void => { - const userFacingRoomId = CallHandler.sharedInstance().roomIdForCall(this.props.secondaryCall); - - dis.dispatch({ - action: 'view_room', - room_id: userFacingRoomId, - }); - }; - private onCallResumeClick = (): void => { const userFacingRoomId = CallHandler.sharedInstance().roomIdForCall(this.props.call); CallHandler.sharedInstance().setActiveCallRoomId(userFacingRoomId); @@ -814,83 +782,15 @@ export default class CallView extends React.Component { ); } - const callTypeText = isVideoCall ? _t("Video Call") : _t("Voice Call"); - let myClassName; - - let fullScreenButton; - if (!this.props.pipMode) { - fullScreenButton = ( -
- ); - } - - let expandButton; - if (this.props.pipMode) { - expandButton =
; - } - - const headerControls =
- { fullScreenButton } - { expandButton } -
; - - const callTypeIconClassName = classNames("mx_CallView_header_callTypeIcon", { - "mx_CallView_header_callTypeIcon_voice": !isVideoCall, - "mx_CallView_header_callTypeIcon_video": isVideoCall, - }); - - let header: React.ReactNode; - if (!this.props.pipMode) { - header =
-
- { callTypeText } - { headerControls } -
; - myClassName = 'mx_CallView_large'; - } else { - let secondaryCallInfo; - if (this.props.secondaryCall) { - secondaryCallInfo = - - - - { _t("%(name)s on hold", { name: secCallRoom.name }) } - - - ; - } - - header = ( -
- - - -
-
{ callRoom.name }
-
- { callTypeText } - { secondaryCallInfo } -
-
- { headerControls } -
- ); - myClassName = 'mx_CallView_pip'; - } + const myClassName = this.props.pipMode ? 'mx_CallView_pip' : 'mx_CallView_large'; return
- { header } + { contentView }
; } diff --git a/src/components/views/voip/CallView/CallViewHeader.tsx b/src/components/views/voip/CallView/CallViewHeader.tsx new file mode 100644 index 0000000000..5df45c90a0 --- /dev/null +++ b/src/components/views/voip/CallView/CallViewHeader.tsx @@ -0,0 +1,135 @@ +/* +Copyright 2021 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 { CallType } from 'matrix-js-sdk/src/webrtc/call'; +import { Room } from 'matrix-js-sdk/src/models/room'; +import React from 'react'; +import { isUndefined } from 'lodash'; +import { _t } from '../../../../languageHandler'; +import RoomAvatar from '../../avatars/RoomAvatar'; +import AccessibleButton from '../../elements/AccessibleButton'; +import dis from '../../../../dispatcher/dispatcher'; +import WidgetAvatar from '../../avatars/WidgetAvatar'; +import { IApp } from '../../../../stores/WidgetStore'; +import WidgetUtils from '../../../../utils/WidgetUtils'; + +const callTypeTranslationByType: Record string> = { + [CallType.Video]: () => _t("Video Call"), + [CallType.Voice]: () => _t("Audio Call"), + 'widget': (app: IApp) => WidgetUtils.getWidgetName(app), +}; + +interface CallViewHeaderProps { + pipMode: boolean; + type: CallType | 'widget'; + callRooms?: Room[]; + app?: IApp; + onPipMouseDown: (event: React.MouseEvent) => void; +} + +const onRoomAvatarClick = (roomId: string) => { + dis.dispatch({ + action: 'view_room', + room_id: roomId, + }); +}; + +const onFullscreenClick = () => { + dis.dispatch({ + action: 'video_fullscreen', + fullscreen: true, + }); +}; + +const onExpandClick = (roomId: string) => { + dis.dispatch({ + action: 'view_room', + room_id: roomId, + }); +}; + +type CallControlsProps = Pick & { + roomId: string; +}; +function CallControls({ pipMode = false, type, roomId }: CallControlsProps) { + return
+ { (pipMode && type === CallType.Video) && +
} + { pipMode &&
onExpandClick(roomId)} + title={_t("Return to call")} + /> } +
; +} +function SecondaryCallInfo({ callRoom }: {callRoom: Room}) { + return + onRoomAvatarClick(callRoom.roomId)}> + + + { _t("%(name)s on hold", { name: callRoom.name }) } + + + ; +} + +function getAvatarBasedOnRoomType(roomOrWidget: Room | IApp) { + if (roomOrWidget instanceof Room) { + return ; + } else if (!isUndefined(roomOrWidget)) { + return ; + } + return null; +} + +export function CallViewHeader({ + type, + pipMode = false, + callRooms = [], + app, + onPipMouseDown, +}: CallViewHeaderProps) { + const [callRoom, onHoldCallRoom] = callRooms; + const callTypeText = callTypeTranslationByType[type](app); + const avatar = getAvatarBasedOnRoomType(callRoom ?? app); + const callRoomName = type === 'widget' ? callTypeText : callRoom.name; + const roomId = app ? app.roomId : callRoom.roomId; + if (!pipMode) { + return
+
+ { callTypeText } + +
; + } + return (
+ onRoomAvatarClick(roomId)}> + { avatar } + +
+
{ callRoomName }
+
+ { callTypeText } + { onHoldCallRoom && } +
+
+ +
+ ); +} diff --git a/src/components/views/voip/PictureInPictureDragger.tsx b/src/components/views/voip/PictureInPictureDragger.tsx new file mode 100644 index 0000000000..14a2a88939 --- /dev/null +++ b/src/components/views/voip/PictureInPictureDragger.tsx @@ -0,0 +1,208 @@ +/* +Copyright 2021 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 React, { createRef } from 'react'; +import UIStore from '../../../stores/UIStore'; +import { IApp } from '../../../stores/WidgetStore'; +import { lerp } from '../../../utils/AnimationUtils'; +import { MarkedExecution } from '../../../utils/MarkedExecution'; +import { replaceableComponent } from '../../../utils/replaceableComponent'; + +const PIP_VIEW_WIDTH = 336; +const PIP_VIEW_HEIGHT = 232; + +const MOVING_AMT = 0.2; +const SNAPPING_AMT = 0.05; + +const PADDING = { + top: 58, + bottom: 58, + left: 76, + right: 8, +}; + +interface IProps { + className?: string; + children: (event: MouseEvent) => React.ReactNode; + draggable: boolean; + app?: IApp; +} + +interface IState { + // Position of the PictureInPictureDragger + translationX: number; + translationY: number; +} + +/** + * PictureInPictureDragger shows a small version of CallView hovering over the UI in 'picture-in-picture' + * (PiP mode). It displays the call(s) which is *not* in the room the user is currently viewing. + */ +@replaceableComponent("views.voip.PictureInPictureDragger") +export class PictureInPictureDragger extends React.Component { + private callViewWrapper = createRef(); + private initX = 0; + private initY = 0; + private desiredTranslationX = UIStore.instance.windowWidth - PADDING.right - PIP_VIEW_WIDTH; + private desiredTranslationY = UIStore.instance.windowHeight - PADDING.bottom - PIP_VIEW_WIDTH; + private moving = false; + private scheduledUpdate = new MarkedExecution( + () => this.animationCallback(), + () => requestAnimationFrame(() => this.scheduledUpdate.trigger()), + ); + + constructor(props: IProps) { + super(props); + + this.state = { + translationX: UIStore.instance.windowWidth - PADDING.right - PIP_VIEW_WIDTH, + translationY: UIStore.instance.windowHeight - PADDING.bottom - PIP_VIEW_WIDTH, + }; + } + + public componentDidMount() { + document.addEventListener("mousemove", this.onMoving); + document.addEventListener("mouseup", this.onEndMoving); + window.addEventListener("resize", this.snap); + } + + public componentWillUnmount() { + document.removeEventListener("mousemove", this.onMoving); + document.removeEventListener("mouseup", this.onEndMoving); + window.removeEventListener("resize", this.snap); + } + + private animationCallback = () => { + // If the PiP isn't being dragged and there is only a tiny difference in + // the desiredTranslation and translation, quit the animationCallback + // loop. If that is the case, it means the PiP has snapped into its + // position and there is nothing to do. Not doing this would cause an + // infinite loop + if ( + !this.moving && + Math.abs(this.state.translationX - this.desiredTranslationX) <= 1 && + Math.abs(this.state.translationY - this.desiredTranslationY) <= 1 + ) return; + + const amt = this.moving ? MOVING_AMT : SNAPPING_AMT; + this.setState({ + translationX: lerp(this.state.translationX, this.desiredTranslationX, amt), + translationY: lerp(this.state.translationY, this.desiredTranslationY, amt), + }); + this.scheduledUpdate.mark(); + }; + + private setTranslation(inTranslationX: number, inTranslationY: number) { + const width = this.callViewWrapper.current?.clientWidth || PIP_VIEW_WIDTH; + const height = this.callViewWrapper.current?.clientHeight || PIP_VIEW_HEIGHT; + + // Avoid overflow on the x axis + if (inTranslationX + width >= UIStore.instance.windowWidth) { + this.desiredTranslationX = UIStore.instance.windowWidth - width; + } else if (inTranslationX <= 0) { + this.desiredTranslationX = 0; + } else { + this.desiredTranslationX = inTranslationX; + } + + // Avoid overflow on the y axis + if (inTranslationY + height >= UIStore.instance.windowHeight) { + this.desiredTranslationY = UIStore.instance.windowHeight - height; + } else if (inTranslationY <= 0) { + this.desiredTranslationY = 0; + } else { + this.desiredTranslationY = inTranslationY; + } + } + + private snap = () => { + const translationX = this.desiredTranslationX; + const translationY = this.desiredTranslationY; + // We subtract the PiP size from the window size in order to calculate + // the position to snap to from the PiP center and not its top-left + // corner + const windowWidth = ( + UIStore.instance.windowWidth - + (this.callViewWrapper.current?.clientWidth || PIP_VIEW_WIDTH) + ); + const windowHeight = ( + UIStore.instance.windowHeight - + (this.callViewWrapper.current?.clientHeight || PIP_VIEW_HEIGHT) + ); + + if (translationX >= windowWidth / 2 && translationY >= windowHeight / 2) { + this.desiredTranslationX = windowWidth - PADDING.right; + this.desiredTranslationY = windowHeight - PADDING.bottom; + } else if (translationX >= windowWidth / 2 && translationY <= windowHeight / 2) { + this.desiredTranslationX = windowWidth - PADDING.right; + this.desiredTranslationY = PADDING.top; + } else if (translationX <= windowWidth / 2 && translationY >= windowHeight / 2) { + this.desiredTranslationX = PADDING.left; + this.desiredTranslationY = windowHeight - PADDING.bottom; + } else { + this.desiredTranslationX = PADDING.left; + this.desiredTranslationY = PADDING.top; + } + + // We start animating here because we want the PiP to move when we're + // resizing the window + this.scheduledUpdate.mark(); + }; + + private onStartMoving = (event: React.MouseEvent | MouseEvent) => { + event.preventDefault(); + event.stopPropagation(); + + this.moving = true; + this.initX = event.pageX - this.desiredTranslationX; + this.initY = event.pageY - this.desiredTranslationY; + this.scheduledUpdate.mark(); + }; + + private onMoving = (event: React.MouseEvent | MouseEvent) => { + if (!this.moving) return; + + event.preventDefault(); + event.stopPropagation(); + + this.setTranslation(event.pageX - this.initX, event.pageY - this.initY); + }; + + private onEndMoving = () => { + this.moving = false; + this.snap(); + }; + + public render() { + const translatePixelsX = this.state.translationX + "px"; + const translatePixelsY = this.state.translationY + "px"; + const style = { + transform: `translateX(${translatePixelsX}) + translateY(${translatePixelsY})`, + }; + return ( +
+ <> + { this.props.children(this.onStartMoving) } + +
+ ); + } +} + From 36efa448b2ef673d8c282ad87bc3da6bb4d01cf2 Mon Sep 17 00:00:00 2001 From: Dariusz Niemczyk Date: Tue, 3 Aug 2021 13:24:41 +0200 Subject: [PATCH 22/80] Fix TS types --- src/components/views/voip/CallView.tsx | 2 +- src/components/views/voip/PictureInPictureDragger.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index 096cd8a6a9..3fa8d602da 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -57,7 +57,7 @@ interface IProps { pipMode?: boolean; // Used for dragging the PiP CallView - onMouseDownOnHeader?: (event: React.MouseEvent) => void; + onMouseDownOnHeader?: (event: MouseEvent) => void; } interface IState { diff --git a/src/components/views/voip/PictureInPictureDragger.tsx b/src/components/views/voip/PictureInPictureDragger.tsx index 14a2a88939..1a3c3d8828 100644 --- a/src/components/views/voip/PictureInPictureDragger.tsx +++ b/src/components/views/voip/PictureInPictureDragger.tsx @@ -35,7 +35,7 @@ const PADDING = { interface IProps { className?: string; - children: (event: MouseEvent) => React.ReactNode; + children: (startMovingEventHandler: (event: MouseEvent) => void) => React.ReactNode; draggable: boolean; app?: IApp; } From 29e9db44a319bf6b7fb7438809e64d2df3e849a4 Mon Sep 17 00:00:00 2001 From: Dariusz Niemczyk Date: Tue, 3 Aug 2021 13:30:14 +0200 Subject: [PATCH 23/80] Another fix to the TS types --- src/components/views/voip/CallView.tsx | 2 +- src/components/views/voip/PictureInPictureDragger.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index 3fa8d602da..084efa66d1 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -57,7 +57,7 @@ interface IProps { pipMode?: boolean; // Used for dragging the PiP CallView - onMouseDownOnHeader?: (event: MouseEvent) => void; + onMouseDownOnHeader?: (event: React.MouseEvent) => void; } interface IState { diff --git a/src/components/views/voip/PictureInPictureDragger.tsx b/src/components/views/voip/PictureInPictureDragger.tsx index 1a3c3d8828..9445ee68b6 100644 --- a/src/components/views/voip/PictureInPictureDragger.tsx +++ b/src/components/views/voip/PictureInPictureDragger.tsx @@ -35,7 +35,7 @@ const PADDING = { interface IProps { className?: string; - children: (startMovingEventHandler: (event: MouseEvent) => void) => React.ReactNode; + children: (startMovingEventHandler: (event: React.MouseEvent) => void) => React.ReactNode; draggable: boolean; app?: IApp; } From 90c2eb0b92e1eb36fbc6bcbfced6cf62c5a20377 Mon Sep 17 00:00:00 2001 From: Dariusz Niemczyk <3636685+Palid@users.noreply.github.com> Date: Tue, 3 Aug 2021 13:50:00 +0200 Subject: [PATCH 24/80] Update src/components/views/voip/CallView/CallViewHeader.tsx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Šimon Brandner --- src/components/views/voip/CallView/CallViewHeader.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/voip/CallView/CallViewHeader.tsx b/src/components/views/voip/CallView/CallViewHeader.tsx index 5df45c90a0..954982b9b3 100644 --- a/src/components/views/voip/CallView/CallViewHeader.tsx +++ b/src/components/views/voip/CallView/CallViewHeader.tsx @@ -27,7 +27,7 @@ import WidgetUtils from '../../../../utils/WidgetUtils'; const callTypeTranslationByType: Record string> = { [CallType.Video]: () => _t("Video Call"), - [CallType.Voice]: () => _t("Audio Call"), + [CallType.Voice]: () => _t("Voice Call"), 'widget': (app: IApp) => WidgetUtils.getWidgetName(app), }; From 838d9105c9747b28eb32fd535664ec6f2007ce65 Mon Sep 17 00:00:00 2001 From: Dariusz Niemczyk <3636685+Palid@users.noreply.github.com> Date: Tue, 3 Aug 2021 13:53:49 +0200 Subject: [PATCH 25/80] Update src/components/views/voip/CallView/CallViewHeader.tsx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Šimon Brandner --- src/components/views/voip/CallView/CallViewHeader.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/voip/CallView/CallViewHeader.tsx b/src/components/views/voip/CallView/CallViewHeader.tsx index 954982b9b3..e5ee6bff96 100644 --- a/src/components/views/voip/CallView/CallViewHeader.tsx +++ b/src/components/views/voip/CallView/CallViewHeader.tsx @@ -96,13 +96,13 @@ function getAvatarBasedOnRoomType(roomOrWidget: Room | IApp) { return null; } -export function CallViewHeader({ +export const CallViewHeader: React.FC = ({ type, pipMode = false, callRooms = [], app, onPipMouseDown, -}: CallViewHeaderProps) { +}) { const [callRoom, onHoldCallRoom] = callRooms; const callTypeText = callTypeTranslationByType[type](app); const avatar = getAvatarBasedOnRoomType(callRoom ?? app); From a0b0a91d08ce9c14ce3e7fdf22a084847f3b48da Mon Sep 17 00:00:00 2001 From: Dariusz Niemczyk Date: Tue, 3 Aug 2021 14:03:35 +0200 Subject: [PATCH 26/80] Fix ident --- src/components/views/voip/CallPreview.tsx | 4 +- src/components/views/voip/CallView.tsx | 26 +- .../views/voip/CallView/CallViewHeader.tsx | 42 +-- .../views/voip/PictureInPictureDragger.tsx | 276 +++++++++--------- 4 files changed, 174 insertions(+), 174 deletions(-) diff --git a/src/components/views/voip/CallPreview.tsx b/src/components/views/voip/CallPreview.tsx index 565e4657b9..0607215e0c 100644 --- a/src/components/views/voip/CallPreview.tsx +++ b/src/components/views/voip/CallPreview.tsx @@ -144,8 +144,8 @@ export default class CallPreview extends React.Component { private onAction = (payload: ActionPayload) => { switch (payload.action) { - // listen for call state changes to prod the render method, which - // may hide the global CallView if the call it is tracking is dead + // listen for call state changes to prod the render method, which + // may hide the global CallView if the call it is tracking is dead case 'call_state': { this.updateCalls(); break; diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index 084efa66d1..c7297afb95 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -40,23 +40,23 @@ import CallViewSidebar from './CallViewSidebar'; import { CallViewHeader } from './CallView/CallViewHeader'; interface IProps { - // The call for us to display - call: MatrixCall; + // The call for us to display + call: MatrixCall; - // Another ongoing call to display information about - secondaryCall?: MatrixCall; + // Another ongoing call to display information about + secondaryCall?: MatrixCall; - // a callback which is called when the content in the CallView changes - // in a way that is likely to cause a resize. - onResize?: any; + // a callback which is called when the content in the CallView changes + // in a way that is likely to cause a resize. + onResize?: any; - // Whether this call view is for picture-in-picture mode - // otherwise, it's the larger call view when viewing the room the call is in. - // This is sort of a proxy for a number of things but we currently have no - // need to control those things separately, so this is simpler. - pipMode?: boolean; + // Whether this call view is for picture-in-picture mode + // otherwise, it's the larger call view when viewing the room the call is in. + // This is sort of a proxy for a number of things but we currently have no + // need to control those things separately, so this is simpler. + pipMode?: boolean; - // Used for dragging the PiP CallView + // Used for dragging the PiP CallView onMouseDownOnHeader?: (event: React.MouseEvent) => void; } diff --git a/src/components/views/voip/CallView/CallViewHeader.tsx b/src/components/views/voip/CallView/CallViewHeader.tsx index e5ee6bff96..4650e65853 100644 --- a/src/components/views/voip/CallView/CallViewHeader.tsx +++ b/src/components/views/voip/CallView/CallViewHeader.tsx @@ -26,37 +26,37 @@ import { IApp } from '../../../../stores/WidgetStore'; import WidgetUtils from '../../../../utils/WidgetUtils'; const callTypeTranslationByType: Record string> = { - [CallType.Video]: () => _t("Video Call"), - [CallType.Voice]: () => _t("Voice Call"), - 'widget': (app: IApp) => WidgetUtils.getWidgetName(app), + [CallType.Video]: () => _t("Video Call"), + [CallType.Voice]: () => _t("Voice Call"), + 'widget': (app: IApp) => WidgetUtils.getWidgetName(app), }; interface CallViewHeaderProps { - pipMode: boolean; - type: CallType | 'widget'; - callRooms?: Room[]; - app?: IApp; - onPipMouseDown: (event: React.MouseEvent) => void; + pipMode: boolean; + type: CallType | 'widget'; + callRooms?: Room[]; + app?: IApp; + onPipMouseDown: (event: React.MouseEvent) => void; } const onRoomAvatarClick = (roomId: string) => { dis.dispatch({ - action: 'view_room', - room_id: roomId, + action: 'view_room', + room_id: roomId, }); }; const onFullscreenClick = () => { dis.dispatch({ - action: 'video_fullscreen', - fullscreen: true, + action: 'video_fullscreen', + fullscreen: true, }); }; const onExpandClick = (roomId: string) => { dis.dispatch({ - action: 'view_room', - room_id: roomId, + action: 'view_room', + room_id: roomId, }); }; @@ -97,12 +97,12 @@ function getAvatarBasedOnRoomType(roomOrWidget: Room | IApp) { } export const CallViewHeader: React.FC = ({ - type, - pipMode = false, - callRooms = [], - app, - onPipMouseDown, -}) { + type, + pipMode = false, + callRooms = [], + app, + onPipMouseDown, +}) => { const [callRoom, onHoldCallRoom] = callRooms; const callTypeText = callTypeTranslationByType[type](app); const avatar = getAvatarBasedOnRoomType(callRoom ?? app); @@ -132,4 +132,4 @@ export const CallViewHeader: React.FC = ({
); -} +}; diff --git a/src/components/views/voip/PictureInPictureDragger.tsx b/src/components/views/voip/PictureInPictureDragger.tsx index 9445ee68b6..1ef7867992 100644 --- a/src/components/views/voip/PictureInPictureDragger.tsx +++ b/src/components/views/voip/PictureInPictureDragger.tsx @@ -27,23 +27,23 @@ const MOVING_AMT = 0.2; const SNAPPING_AMT = 0.05; const PADDING = { - top: 58, - bottom: 58, - left: 76, - right: 8, + top: 58, + bottom: 58, + left: 76, + right: 8, }; interface IProps { - className?: string; + className?: string; children: (startMovingEventHandler: (event: React.MouseEvent) => void) => React.ReactNode; - draggable: boolean; - app?: IApp; + draggable: boolean; + app?: IApp; } interface IState { - // Position of the PictureInPictureDragger - translationX: number; - translationY: number; + // Position of the PictureInPictureDragger + translationX: number; + translationY: number; } /** @@ -52,157 +52,157 @@ interface IState { */ @replaceableComponent("views.voip.PictureInPictureDragger") export class PictureInPictureDragger extends React.Component { - private callViewWrapper = createRef(); - private initX = 0; - private initY = 0; - private desiredTranslationX = UIStore.instance.windowWidth - PADDING.right - PIP_VIEW_WIDTH; - private desiredTranslationY = UIStore.instance.windowHeight - PADDING.bottom - PIP_VIEW_WIDTH; - private moving = false; - private scheduledUpdate = new MarkedExecution( - () => this.animationCallback(), - () => requestAnimationFrame(() => this.scheduledUpdate.trigger()), - ); + private callViewWrapper = createRef(); + private initX = 0; + private initY = 0; + private desiredTranslationX = UIStore.instance.windowWidth - PADDING.right - PIP_VIEW_WIDTH; + private desiredTranslationY = UIStore.instance.windowHeight - PADDING.bottom - PIP_VIEW_WIDTH; + private moving = false; + private scheduledUpdate = new MarkedExecution( + () => this.animationCallback(), + () => requestAnimationFrame(() => this.scheduledUpdate.trigger()), + ); - constructor(props: IProps) { - super(props); + constructor(props: IProps) { + super(props); - this.state = { - translationX: UIStore.instance.windowWidth - PADDING.right - PIP_VIEW_WIDTH, - translationY: UIStore.instance.windowHeight - PADDING.bottom - PIP_VIEW_WIDTH, - }; - } + this.state = { + translationX: UIStore.instance.windowWidth - PADDING.right - PIP_VIEW_WIDTH, + translationY: UIStore.instance.windowHeight - PADDING.bottom - PIP_VIEW_WIDTH, + }; + } - public componentDidMount() { - document.addEventListener("mousemove", this.onMoving); - document.addEventListener("mouseup", this.onEndMoving); - window.addEventListener("resize", this.snap); - } + public componentDidMount() { + document.addEventListener("mousemove", this.onMoving); + document.addEventListener("mouseup", this.onEndMoving); + window.addEventListener("resize", this.snap); + } - public componentWillUnmount() { - document.removeEventListener("mousemove", this.onMoving); - document.removeEventListener("mouseup", this.onEndMoving); - window.removeEventListener("resize", this.snap); - } + public componentWillUnmount() { + document.removeEventListener("mousemove", this.onMoving); + document.removeEventListener("mouseup", this.onEndMoving); + window.removeEventListener("resize", this.snap); + } - private animationCallback = () => { - // If the PiP isn't being dragged and there is only a tiny difference in - // the desiredTranslation and translation, quit the animationCallback - // loop. If that is the case, it means the PiP has snapped into its - // position and there is nothing to do. Not doing this would cause an - // infinite loop - if ( - !this.moving && + private animationCallback = () => { + // If the PiP isn't being dragged and there is only a tiny difference in + // the desiredTranslation and translation, quit the animationCallback + // loop. If that is the case, it means the PiP has snapped into its + // position and there is nothing to do. Not doing this would cause an + // infinite loop + if ( + !this.moving && Math.abs(this.state.translationX - this.desiredTranslationX) <= 1 && Math.abs(this.state.translationY - this.desiredTranslationY) <= 1 - ) return; + ) return; - const amt = this.moving ? MOVING_AMT : SNAPPING_AMT; - this.setState({ - translationX: lerp(this.state.translationX, this.desiredTranslationX, amt), - translationY: lerp(this.state.translationY, this.desiredTranslationY, amt), - }); - this.scheduledUpdate.mark(); - }; + const amt = this.moving ? MOVING_AMT : SNAPPING_AMT; + this.setState({ + translationX: lerp(this.state.translationX, this.desiredTranslationX, amt), + translationY: lerp(this.state.translationY, this.desiredTranslationY, amt), + }); + this.scheduledUpdate.mark(); + }; - private setTranslation(inTranslationX: number, inTranslationY: number) { - const width = this.callViewWrapper.current?.clientWidth || PIP_VIEW_WIDTH; - const height = this.callViewWrapper.current?.clientHeight || PIP_VIEW_HEIGHT; + private setTranslation(inTranslationX: number, inTranslationY: number) { + const width = this.callViewWrapper.current?.clientWidth || PIP_VIEW_WIDTH; + const height = this.callViewWrapper.current?.clientHeight || PIP_VIEW_HEIGHT; - // Avoid overflow on the x axis - if (inTranslationX + width >= UIStore.instance.windowWidth) { - this.desiredTranslationX = UIStore.instance.windowWidth - width; - } else if (inTranslationX <= 0) { - this.desiredTranslationX = 0; - } else { - this.desiredTranslationX = inTranslationX; - } + // Avoid overflow on the x axis + if (inTranslationX + width >= UIStore.instance.windowWidth) { + this.desiredTranslationX = UIStore.instance.windowWidth - width; + } else if (inTranslationX <= 0) { + this.desiredTranslationX = 0; + } else { + this.desiredTranslationX = inTranslationX; + } - // Avoid overflow on the y axis - if (inTranslationY + height >= UIStore.instance.windowHeight) { - this.desiredTranslationY = UIStore.instance.windowHeight - height; - } else if (inTranslationY <= 0) { - this.desiredTranslationY = 0; - } else { - this.desiredTranslationY = inTranslationY; - } - } + // Avoid overflow on the y axis + if (inTranslationY + height >= UIStore.instance.windowHeight) { + this.desiredTranslationY = UIStore.instance.windowHeight - height; + } else if (inTranslationY <= 0) { + this.desiredTranslationY = 0; + } else { + this.desiredTranslationY = inTranslationY; + } + } - private snap = () => { - const translationX = this.desiredTranslationX; - const translationY = this.desiredTranslationY; - // We subtract the PiP size from the window size in order to calculate - // the position to snap to from the PiP center and not its top-left - // corner - const windowWidth = ( - UIStore.instance.windowWidth - + private snap = () => { + const translationX = this.desiredTranslationX; + const translationY = this.desiredTranslationY; + // We subtract the PiP size from the window size in order to calculate + // the position to snap to from the PiP center and not its top-left + // corner + const windowWidth = ( + UIStore.instance.windowWidth - (this.callViewWrapper.current?.clientWidth || PIP_VIEW_WIDTH) - ); - const windowHeight = ( - UIStore.instance.windowHeight - + ); + const windowHeight = ( + UIStore.instance.windowHeight - (this.callViewWrapper.current?.clientHeight || PIP_VIEW_HEIGHT) - ); + ); - if (translationX >= windowWidth / 2 && translationY >= windowHeight / 2) { - this.desiredTranslationX = windowWidth - PADDING.right; - this.desiredTranslationY = windowHeight - PADDING.bottom; - } else if (translationX >= windowWidth / 2 && translationY <= windowHeight / 2) { - this.desiredTranslationX = windowWidth - PADDING.right; - this.desiredTranslationY = PADDING.top; - } else if (translationX <= windowWidth / 2 && translationY >= windowHeight / 2) { - this.desiredTranslationX = PADDING.left; - this.desiredTranslationY = windowHeight - PADDING.bottom; - } else { - this.desiredTranslationX = PADDING.left; - this.desiredTranslationY = PADDING.top; - } + if (translationX >= windowWidth / 2 && translationY >= windowHeight / 2) { + this.desiredTranslationX = windowWidth - PADDING.right; + this.desiredTranslationY = windowHeight - PADDING.bottom; + } else if (translationX >= windowWidth / 2 && translationY <= windowHeight / 2) { + this.desiredTranslationX = windowWidth - PADDING.right; + this.desiredTranslationY = PADDING.top; + } else if (translationX <= windowWidth / 2 && translationY >= windowHeight / 2) { + this.desiredTranslationX = PADDING.left; + this.desiredTranslationY = windowHeight - PADDING.bottom; + } else { + this.desiredTranslationX = PADDING.left; + this.desiredTranslationY = PADDING.top; + } - // We start animating here because we want the PiP to move when we're - // resizing the window - this.scheduledUpdate.mark(); - }; + // We start animating here because we want the PiP to move when we're + // resizing the window + this.scheduledUpdate.mark(); + }; - private onStartMoving = (event: React.MouseEvent | MouseEvent) => { - event.preventDefault(); - event.stopPropagation(); + private onStartMoving = (event: React.MouseEvent | MouseEvent) => { + event.preventDefault(); + event.stopPropagation(); - this.moving = true; - this.initX = event.pageX - this.desiredTranslationX; - this.initY = event.pageY - this.desiredTranslationY; - this.scheduledUpdate.mark(); - }; + this.moving = true; + this.initX = event.pageX - this.desiredTranslationX; + this.initY = event.pageY - this.desiredTranslationY; + this.scheduledUpdate.mark(); + }; - private onMoving = (event: React.MouseEvent | MouseEvent) => { - if (!this.moving) return; + private onMoving = (event: React.MouseEvent | MouseEvent) => { + if (!this.moving) return; - event.preventDefault(); - event.stopPropagation(); + event.preventDefault(); + event.stopPropagation(); - this.setTranslation(event.pageX - this.initX, event.pageY - this.initY); - }; + this.setTranslation(event.pageX - this.initX, event.pageY - this.initY); + }; - private onEndMoving = () => { - this.moving = false; - this.snap(); - }; + private onEndMoving = () => { + this.moving = false; + this.snap(); + }; - public render() { - const translatePixelsX = this.state.translationX + "px"; - const translatePixelsY = this.state.translationY + "px"; - const style = { - transform: `translateX(${translatePixelsX}) + public render() { + const translatePixelsX = this.state.translationX + "px"; + const translatePixelsY = this.state.translationY + "px"; + const style = { + transform: `translateX(${translatePixelsX}) translateY(${translatePixelsY})`, - }; - return ( -
- <> - { this.props.children(this.onStartMoving) } - -
- ); - } + }; + return ( +
+ <> + { this.props.children(this.onStartMoving) } + +
+ ); + } } From f592d37f3931625effd62280ec70bea0de3f9a2a Mon Sep 17 00:00:00 2001 From: Dariusz Niemczyk Date: Tue, 3 Aug 2021 14:15:14 +0200 Subject: [PATCH 27/80] Remove widget support for CallViewHeader --- .../views/voip/CallView/CallViewHeader.tsx | 69 +++++++++---------- .../views/voip/PictureInPictureDragger.tsx | 1 + 2 files changed, 32 insertions(+), 38 deletions(-) diff --git a/src/components/views/voip/CallView/CallViewHeader.tsx b/src/components/views/voip/CallView/CallViewHeader.tsx index 4650e65853..d721fe30e3 100644 --- a/src/components/views/voip/CallView/CallViewHeader.tsx +++ b/src/components/views/voip/CallView/CallViewHeader.tsx @@ -16,26 +16,20 @@ limitations under the License. import { CallType } from 'matrix-js-sdk/src/webrtc/call'; import { Room } from 'matrix-js-sdk/src/models/room'; import React from 'react'; -import { isUndefined } from 'lodash'; import { _t } from '../../../../languageHandler'; import RoomAvatar from '../../avatars/RoomAvatar'; import AccessibleButton from '../../elements/AccessibleButton'; import dis from '../../../../dispatcher/dispatcher'; -import WidgetAvatar from '../../avatars/WidgetAvatar'; -import { IApp } from '../../../../stores/WidgetStore'; -import WidgetUtils from '../../../../utils/WidgetUtils'; -const callTypeTranslationByType: Record string> = { +const callTypeTranslationByType: Record string> = { [CallType.Video]: () => _t("Video Call"), [CallType.Voice]: () => _t("Voice Call"), - 'widget': (app: IApp) => WidgetUtils.getWidgetName(app), }; interface CallViewHeaderProps { pipMode: boolean; - type: CallType | 'widget'; + type: CallType; callRooms?: Room[]; - app?: IApp; onPipMouseDown: (event: React.MouseEvent) => void; } @@ -63,7 +57,7 @@ const onExpandClick = (roomId: string) => { type CallControlsProps = Pick & { roomId: string; }; -function CallControls({ pipMode = false, type, roomId }: CallControlsProps) { +function CallViewHeaderControls({ pipMode = false, type, roomId }: CallControlsProps): JSX.Element { return
{ (pipMode && type === CallType.Video) &&
}
; } -function SecondaryCallInfo({ callRoom }: {callRoom: Room}) { +function SecondaryCallInfo({ callRoom }: {callRoom: Room}): JSX.Element { return onRoomAvatarClick(callRoom.roomId)}> @@ -87,49 +81,48 @@ function SecondaryCallInfo({ callRoom }: {callRoom: Room}) { ; } -function getAvatarBasedOnRoomType(roomOrWidget: Room | IApp) { - if (roomOrWidget instanceof Room) { - return ; - } else if (!isUndefined(roomOrWidget)) { - return ; - } - return null; +function CallTypeIcon({ type }: {type: CallType}) { + const classes = { + [CallType.Video]: 'mx_CallView_header_callTypeIcon_video', + [CallType.Voice]: 'mx_CallView_header_callTypeIcon_voice', + }; + const iconClass = classes[type] ?? ''; + return
; } export const CallViewHeader: React.FC = ({ type, pipMode = false, callRooms = [], - app, onPipMouseDown, }) => { const [callRoom, onHoldCallRoom] = callRooms; - const callTypeText = callTypeTranslationByType[type](app); - const avatar = getAvatarBasedOnRoomType(callRoom ?? app); - const callRoomName = type === 'widget' ? callTypeText : callRoom.name; - const roomId = app ? app.roomId : callRoom.roomId; + const callTypeText = callTypeTranslationByType[type]; + const callRoomName = callRoom.name; + const { roomId } = callRoom; if (!pipMode) { return
-
+ { callTypeText } - +
; } - return (
- onRoomAvatarClick(roomId)}> - { avatar } - -
-
{ callRoomName }
-
- { callTypeText } - { onHoldCallRoom && } + return ( +
+ onRoomAvatarClick(roomId)}> + ; + +
+
{ callRoomName }
+
+ { callTypeText } + { onHoldCallRoom && } +
+
- -
); }; diff --git a/src/components/views/voip/PictureInPictureDragger.tsx b/src/components/views/voip/PictureInPictureDragger.tsx index 1ef7867992..d55d431e8f 100644 --- a/src/components/views/voip/PictureInPictureDragger.tsx +++ b/src/components/views/voip/PictureInPictureDragger.tsx @@ -13,6 +13,7 @@ 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 React, { createRef } from 'react'; import UIStore from '../../../stores/UIStore'; import { IApp } from '../../../stores/WidgetStore'; From 466151a10c5e92d2543212e187a5763b3a09e96b Mon Sep 17 00:00:00 2001 From: Dariusz Niemczyk <3636685+Palid@users.noreply.github.com> Date: Tue, 3 Aug 2021 15:00:55 +0200 Subject: [PATCH 28/80] Update src/components/views/voip/CallPreview.tsx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Šimon Brandner --- src/components/views/voip/CallPreview.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/views/voip/CallPreview.tsx b/src/components/views/voip/CallPreview.tsx index 0607215e0c..e68e9fd148 100644 --- a/src/components/views/voip/CallPreview.tsx +++ b/src/components/views/voip/CallPreview.tsx @@ -144,9 +144,10 @@ export default class CallPreview extends React.Component { private onAction = (payload: ActionPayload) => { switch (payload.action) { - // listen for call state changes to prod the render method, which - // may hide the global CallView if the call it is tracking is dead case 'call_state': { + // listen for call state changes to prod the render method, which + // may hide the global CallView if the call it is tracking is dead + this.updateCalls(); break; } @@ -197,4 +198,3 @@ export default class CallPreview extends React.Component { return ; } } - From 389d0b2d4ae4022e4eb36bd491a2f9dedc6793b1 Mon Sep 17 00:00:00 2001 From: Dariusz Niemczyk Date: Tue, 3 Aug 2021 14:59:35 +0200 Subject: [PATCH 29/80] Another fix for indentation --- src/components/views/voip/CallView.tsx | 4 ++-- .../views/voip/CallView/CallViewHeader.tsx | 2 +- .../views/voip/PictureInPictureDragger.tsx | 18 +++++++++--------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index c7297afb95..5f755b74d0 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -660,7 +660,7 @@ export default class CallView extends React.Component { let onHoldBackground = null; const backgroundStyle: CSSProperties = {}; const backgroundAvatarUrl = avatarUrlForMember( - // is it worth getting the size of the div to pass here? + // is it worth getting the size of the div to pass here? this.props.call.getOpponentMember(), 1024, 1024, 'crop', ); backgroundStyle.backgroundImage = 'url(' + backgroundAvatarUrl + ')'; @@ -680,7 +680,7 @@ export default class CallView extends React.Component { mx_CallView_voice_hold: isOnHold, }); - contentView =( + contentView = (
{ }; type CallControlsProps = Pick & { - roomId: string; + roomId: string; }; function CallViewHeaderControls({ pipMode = false, type, roomId }: CallControlsProps): JSX.Element { return
diff --git a/src/components/views/voip/PictureInPictureDragger.tsx b/src/components/views/voip/PictureInPictureDragger.tsx index d55d431e8f..8fbe88f2ce 100644 --- a/src/components/views/voip/PictureInPictureDragger.tsx +++ b/src/components/views/voip/PictureInPictureDragger.tsx @@ -68,8 +68,8 @@ export class PictureInPictureDragger extends React.Component { super(props); this.state = { - translationX: UIStore.instance.windowWidth - PADDING.right - PIP_VIEW_WIDTH, - translationY: UIStore.instance.windowHeight - PADDING.bottom - PIP_VIEW_WIDTH, + translationX: UIStore.instance.windowWidth - PADDING.right - PIP_VIEW_WIDTH, + translationY: UIStore.instance.windowHeight - PADDING.bottom - PIP_VIEW_WIDTH, }; } @@ -93,14 +93,14 @@ export class PictureInPictureDragger extends React.Component { // infinite loop if ( !this.moving && - Math.abs(this.state.translationX - this.desiredTranslationX) <= 1 && - Math.abs(this.state.translationY - this.desiredTranslationY) <= 1 + Math.abs(this.state.translationX - this.desiredTranslationX) <= 1 && + Math.abs(this.state.translationY - this.desiredTranslationY) <= 1 ) return; const amt = this.moving ? MOVING_AMT : SNAPPING_AMT; this.setState({ - translationX: lerp(this.state.translationX, this.desiredTranslationX, amt), - translationY: lerp(this.state.translationY, this.desiredTranslationY, amt), + translationX: lerp(this.state.translationX, this.desiredTranslationX, amt), + translationY: lerp(this.state.translationY, this.desiredTranslationY, amt), }); this.scheduledUpdate.mark(); }; @@ -136,11 +136,11 @@ export class PictureInPictureDragger extends React.Component { // corner const windowWidth = ( UIStore.instance.windowWidth - - (this.callViewWrapper.current?.clientWidth || PIP_VIEW_WIDTH) + (this.callViewWrapper.current?.clientWidth || PIP_VIEW_WIDTH) ); const windowHeight = ( UIStore.instance.windowHeight - - (this.callViewWrapper.current?.clientHeight || PIP_VIEW_HEIGHT) + (this.callViewWrapper.current?.clientHeight || PIP_VIEW_HEIGHT) ); if (translationX >= windowWidth / 2 && translationY >= windowHeight / 2) { @@ -190,7 +190,7 @@ export class PictureInPictureDragger extends React.Component { const translatePixelsX = this.state.translationX + "px"; const translatePixelsY = this.state.translationY + "px"; const style = { - transform: `translateX(${translatePixelsX}) + transform: `translateX(${translatePixelsX}) translateY(${translatePixelsY})`, }; return ( From 3ab54bce6ea710342f3e12c023d0608077886996 Mon Sep 17 00:00:00 2001 From: Dariusz Niemczyk Date: Tue, 3 Aug 2021 15:00:11 +0200 Subject: [PATCH 30/80] Move CallViewHeader css to separate file --- res/css/_components.scss | 1 + res/css/views/voip/_CallView.scss | 113 --------------------- res/css/views/voip/_CallViewHeader.scss | 128 ++++++++++++++++++++++++ 3 files changed, 129 insertions(+), 113 deletions(-) create mode 100644 res/css/views/voip/_CallViewHeader.scss diff --git a/res/css/_components.scss b/res/css/_components.scss index 76551b51f8..174dc76d7f 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -273,6 +273,7 @@ @import "./views/voip/_CallView.scss"; @import "./views/voip/_CallViewForRoom.scss"; @import "./views/voip/_CallViewSidebar.scss"; +@import "./views/voip/_CallViewHeader.scss"; @import "./views/voip/_DialPad.scss"; @import "./views/voip/_DialPadContextMenu.scss"; @import "./views/voip/_DialPadModal.scss"; diff --git a/res/css/views/voip/_CallView.scss b/res/css/views/voip/_CallView.scss index eff865f20c..6505bbfcbd 100644 --- a/res/css/views/voip/_CallView.scss +++ b/res/css/views/voip/_CallView.scss @@ -201,119 +201,6 @@ limitations under the License. } } -.mx_CallView_header { - height: 44px; - display: flex; - flex-direction: row; - align-items: center; - justify-content: left; - flex-shrink: 0; -} - -.mx_CallView_header_callType { - font-size: 1.2rem; - font-weight: bold; - vertical-align: middle; -} - -.mx_CallView_header_secondaryCallInfo { - &::before { - content: '·'; - margin-left: 6px; - margin-right: 6px; - } -} - -.mx_CallView_header_controls { - margin-left: auto; -} - -.mx_CallView_header_button { - display: inline-block; - vertical-align: middle; - cursor: pointer; - - &::before { - content: ''; - display: inline-block; - height: 20px; - width: 20px; - vertical-align: middle; - background-color: $secondary-fg-color; - mask-repeat: no-repeat; - mask-size: contain; - mask-position: center; - } -} - -.mx_CallView_header_button_fullscreen { - &::before { - mask-image: url('$(res)/img/element-icons/call/fullscreen.svg'); - } -} - -.mx_CallView_header_button_expand { - &::before { - mask-image: url('$(res)/img/element-icons/call/expand.svg'); - } -} - -.mx_CallView_header_callInfo { - margin-left: 12px; - margin-right: 16px; -} - -.mx_CallView_header_roomName { - font-weight: bold; - font-size: 12px; - line-height: initial; - height: 15px; -} - -.mx_CallView_secondaryCall_roomName { - margin-left: 4px; -} - -.mx_CallView_header_callTypeSmall { - font-size: 12px; - color: $secondary-fg-color; - line-height: initial; - height: 15px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - max-width: 240px; -} - -.mx_CallView_header_callTypeIcon { - display: inline-block; - margin-right: 6px; - height: 16px; - width: 16px; - vertical-align: middle; - - &::before { - content: ''; - display: inline-block; - vertical-align: top; - - height: 16px; - width: 16px; - background-color: $secondary-fg-color; - mask-repeat: no-repeat; - mask-size: contain; - mask-position: center; - } - - &.mx_CallView_header_callTypeIcon_voice::before { - mask-image: url('$(res)/img/element-icons/call/voice-call.svg'); - } - - &.mx_CallView_header_callTypeIcon_video::before { - mask-image: url('$(res)/img/element-icons/call/video-call.svg'); - } -} - .mx_CallView_callControls { position: absolute; display: flex; diff --git a/res/css/views/voip/_CallViewHeader.scss b/res/css/views/voip/_CallViewHeader.scss new file mode 100644 index 0000000000..9dff07090f --- /dev/null +++ b/res/css/views/voip/_CallViewHeader.scss @@ -0,0 +1,128 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +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. +*/ + +.mx_CallView_header { + height: 44px; + display: flex; + flex-direction: row; + align-items: center; + justify-content: left; + flex-shrink: 0; +} + +.mx_CallView_header_callType { + font-size: 1.2rem; + font-weight: bold; + vertical-align: middle; +} + +.mx_CallView_header_secondaryCallInfo { + &::before { + content: '·'; + margin-left: 6px; + margin-right: 6px; + } +} + +.mx_CallView_header_controls { + margin-left: auto; +} + +.mx_CallView_header_button { + display: inline-block; + vertical-align: middle; + cursor: pointer; + + &::before { + content: ''; + display: inline-block; + height: 20px; + width: 20px; + vertical-align: middle; + background-color: $secondary-fg-color; + mask-repeat: no-repeat; + mask-size: contain; + mask-position: center; + } +} + +.mx_CallView_header_button_fullscreen { + &::before { + mask-image: url('$(res)/img/element-icons/call/fullscreen.svg'); + } +} + +.mx_CallView_header_button_expand { + &::before { + mask-image: url('$(res)/img/element-icons/call/expand.svg'); + } +} + +.mx_CallView_header_callInfo { + margin-left: 12px; + margin-right: 16px; +} + +.mx_CallView_header_roomName { + font-weight: bold; + font-size: 12px; + line-height: initial; + height: 15px; +} + +.mx_CallView_secondaryCall_roomName { + margin-left: 4px; +} + +.mx_CallView_header_callTypeSmall { + font-size: 12px; + color: $secondary-fg-color; + line-height: initial; + height: 15px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + max-width: 240px; +} + +.mx_CallView_header_callTypeIcon { + display: inline-block; + margin-right: 6px; + height: 16px; + width: 16px; + vertical-align: middle; + + &::before { + content: ''; + display: inline-block; + vertical-align: top; + + height: 16px; + width: 16px; + background-color: $secondary-fg-color; + mask-repeat: no-repeat; + mask-size: contain; + mask-position: center; + } + + &.mx_CallView_header_callTypeIcon_voice::before { + mask-image: url('$(res)/img/element-icons/call/voice-call.svg'); + } + + &.mx_CallView_header_callTypeIcon_video::before { + mask-image: url('$(res)/img/element-icons/call/video-call.svg'); + } +} From 08b27a7c8284e0c5358f452d330eabf5ae4a9471 Mon Sep 17 00:00:00 2001 From: Dariusz Niemczyk <3636685+Palid@users.noreply.github.com> Date: Tue, 3 Aug 2021 15:15:30 +0200 Subject: [PATCH 31/80] Update src/components/views/voip/PictureInPictureDragger.tsx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Šimon Brandner --- src/components/views/voip/PictureInPictureDragger.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/views/voip/PictureInPictureDragger.tsx b/src/components/views/voip/PictureInPictureDragger.tsx index 8fbe88f2ce..a066ee1fc8 100644 --- a/src/components/views/voip/PictureInPictureDragger.tsx +++ b/src/components/views/voip/PictureInPictureDragger.tsx @@ -38,7 +38,6 @@ interface IProps { className?: string; children: (startMovingEventHandler: (event: React.MouseEvent) => void) => React.ReactNode; draggable: boolean; - app?: IApp; } interface IState { @@ -206,4 +205,3 @@ export class PictureInPictureDragger extends React.Component { ); } } - From 8a1def1d26b01ccaaa224a31c2942c45bb0c4bb5 Mon Sep 17 00:00:00 2001 From: Dariusz Niemczyk <3636685+Palid@users.noreply.github.com> Date: Tue, 3 Aug 2021 15:15:35 +0200 Subject: [PATCH 32/80] Update src/components/views/voip/CallView/CallViewHeader.tsx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Šimon Brandner --- src/components/views/voip/CallView/CallViewHeader.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/views/voip/CallView/CallViewHeader.tsx b/src/components/views/voip/CallView/CallViewHeader.tsx index 018089d5d5..117dab0b96 100644 --- a/src/components/views/voip/CallView/CallViewHeader.tsx +++ b/src/components/views/voip/CallView/CallViewHeader.tsx @@ -13,6 +13,7 @@ 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 { CallType } from 'matrix-js-sdk/src/webrtc/call'; import { Room } from 'matrix-js-sdk/src/models/room'; import React from 'react'; From b18d03be329b18becd1e61c78bf6b90c410a8626 Mon Sep 17 00:00:00 2001 From: Dariusz Niemczyk Date: Tue, 3 Aug 2021 15:20:55 +0200 Subject: [PATCH 33/80] Fix final style issues --- .../views/voip/CallView/CallViewHeader.tsx | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/components/views/voip/CallView/CallViewHeader.tsx b/src/components/views/voip/CallView/CallViewHeader.tsx index 117dab0b96..a5f9de3b7d 100644 --- a/src/components/views/voip/CallView/CallViewHeader.tsx +++ b/src/components/views/voip/CallView/CallViewHeader.tsx @@ -21,6 +21,7 @@ import { _t } from '../../../../languageHandler'; import RoomAvatar from '../../avatars/RoomAvatar'; import AccessibleButton from '../../elements/AccessibleButton'; import dis from '../../../../dispatcher/dispatcher'; +import classNames from 'classnames'; const callTypeTranslationByType: Record string> = { [CallType.Video]: () => _t("Video Call"), @@ -58,7 +59,7 @@ const onExpandClick = (roomId: string) => { type CallControlsProps = Pick & { roomId: string; }; -function CallViewHeaderControls({ pipMode = false, type, roomId }: CallControlsProps): JSX.Element { +const CallViewHeaderControls: React.FC = ({ pipMode = false, type, roomId }) => { return
{ (pipMode && type === CallType.Video) &&
}
; -} -function SecondaryCallInfo({ callRoom }: {callRoom: Room}): JSX.Element { +}; +const SecondaryCallInfo: React.FC<{ callRoom: Room }> = ({ callRoom }) => { return onRoomAvatarClick(callRoom.roomId)}> @@ -80,16 +81,16 @@ function SecondaryCallInfo({ callRoom }: {callRoom: Room}): JSX.Element { ; -} +}; -function CallTypeIcon({ type }: {type: CallType}) { - const classes = { - [CallType.Video]: 'mx_CallView_header_callTypeIcon_video', - [CallType.Voice]: 'mx_CallView_header_callTypeIcon_voice', - }; - const iconClass = classes[type] ?? ''; - return
; -} +const CallTypeIcon: React.FC<{ type: CallType }> = ({ type }) => { + const classes = classNames({ + 'mx_CallView_header_callTypeIcon': true, + 'mx_CallView_header_callTypeIcon_video': type === CallType.Video, + 'mx_CallView_header_callTypeIcon_voice': type === CallType.Voice, + }); + return
; +}; export const CallViewHeader: React.FC = ({ type, From 7487636f90f53306ac9fcca40876b7f0b97dea1c Mon Sep 17 00:00:00 2001 From: Dariusz Niemczyk Date: Tue, 3 Aug 2021 18:00:11 +0200 Subject: [PATCH 34/80] Fix linter again --- src/components/views/voip/PictureInPictureDragger.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/views/voip/PictureInPictureDragger.tsx b/src/components/views/voip/PictureInPictureDragger.tsx index a066ee1fc8..d3b4c8a5d1 100644 --- a/src/components/views/voip/PictureInPictureDragger.tsx +++ b/src/components/views/voip/PictureInPictureDragger.tsx @@ -16,7 +16,6 @@ limitations under the License. import React, { createRef } from 'react'; import UIStore from '../../../stores/UIStore'; -import { IApp } from '../../../stores/WidgetStore'; import { lerp } from '../../../utils/AnimationUtils'; import { MarkedExecution } from '../../../utils/MarkedExecution'; import { replaceableComponent } from '../../../utils/replaceableComponent'; From 806b36856dbd69e331624f9d4260cfcde34b0c98 Mon Sep 17 00:00:00 2001 From: Dariusz Niemczyk Date: Tue, 3 Aug 2021 18:01:34 +0200 Subject: [PATCH 35/80] Fix i18n --- src/i18n/strings/en_EN.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 3ad8daa85c..235701c86a 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -907,11 +907,6 @@ "%(sharerName)s is presenting": "%(sharerName)s is presenting", "Your camera is turned off": "Your camera is turned off", "Your camera is still enabled": "Your camera is still enabled", - "Video Call": "Video Call", - "Voice Call": "Voice Call", - "Fill Screen": "Fill Screen", - "Return to call": "Return to call", - "%(name)s on hold": "%(name)s on hold", "Unknown caller": "Unknown caller", "Incoming voice call": "Incoming voice call", "Incoming video call": "Incoming video call", @@ -920,6 +915,11 @@ "Silence call": "Silence call", "Decline": "Decline", "Accept": "Accept", + "Video Call": "Video Call", + "Voice Call": "Voice Call", + "Fill Screen": "Fill Screen", + "Return to call": "Return to call", + "%(name)s on hold": "%(name)s on hold", "The other party cancelled the verification.": "The other party cancelled the verification.", "Verified!": "Verified!", "You've successfully verified this user.": "You've successfully verified this user.", From fea30e5f5f6b6c46efebfdbb69ff2c8fa31118f4 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 5 Aug 2021 12:38:15 -0600 Subject: [PATCH 36/80] Fix disabled state for voice messages + send button tooltip Fixes https://github.com/vector-im/element-web/issues/18413 --- src/components/views/rooms/BasicMessageComposer.tsx | 5 ++--- src/components/views/rooms/MessageComposer.tsx | 9 +++++++-- src/i18n/strings/en_EN.json | 2 +- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/components/views/rooms/BasicMessageComposer.tsx b/src/components/views/rooms/BasicMessageComposer.tsx index 55baf9cb73..db3d3eee5e 100644 --- a/src/components/views/rooms/BasicMessageComposer.tsx +++ b/src/components/views/rooms/BasicMessageComposer.tsx @@ -1,6 +1,5 @@ /* -Copyright 2019 New Vector Ltd -Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2019 - 2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -725,7 +724,7 @@ export default class BasicMessageEditor extends React.Component
void; + title?: string; // defaults to something generic } function SendButton(props: ISendButtonProps) { @@ -65,7 +66,7 @@ function SendButton(props: ISendButtonProps) { ); } @@ -401,7 +402,11 @@ export default class MessageComposer extends React.Component { if (!this.state.isComposerEmpty || this.state.haveRecording) { controls.push( - , + , ); } } else if (this.state.tombstone) { diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index b1846f5ae9..f68c72702f 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1527,6 +1527,7 @@ "Send a reply…": "Send a reply…", "Send an encrypted message…": "Send an encrypted message…", "Send a message…": "Send a message…", + "Send voice message": "Send voice message", "The conversation continues here.": "The conversation continues here.", "This room has been replaced and is no longer active.": "This room has been replaced and is no longer active.", "You do not have permission to post to this room": "You do not have permission to post to this room", @@ -1701,7 +1702,6 @@ "We were unable to access your microphone. Please check your browser settings and try again.": "We were unable to access your microphone. Please check your browser settings and try again.", "No microphone found": "No microphone found", "We didn't find a microphone on your device. Please check your settings and try again.": "We didn't find a microphone on your device. Please check your settings and try again.", - "Send voice message": "Send voice message", "Stop recording": "Stop recording", "Error updating main address": "Error updating main address", "There was an error updating the room's main address. It may not be allowed by the server or a temporary failure occurred.": "There was an error updating the room's main address. It may not be allowed by the server or a temporary failure occurred.", From 015e0b6d7791f3f0dd9bbfa0c2d11c47368be667 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 6 Aug 2021 07:51:57 +0200 Subject: [PATCH 37/80] Make into a labs feature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/structures/TimelinePanel.tsx | 2 +- .../views/settings/tabs/user/LabsUserSettingsTab.js | 2 +- src/settings/Settings.tsx | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/structures/TimelinePanel.tsx b/src/components/structures/TimelinePanel.tsx index 612cab103d..533216df64 100644 --- a/src/components/structures/TimelinePanel.tsx +++ b/src/components/structures/TimelinePanel.tsx @@ -758,7 +758,7 @@ class TimelinePanel extends React.Component { this.lastRMSentEventId = this.state.readMarkerEventId; const roomId = this.props.timelineSet.room.roomId; - const hiddenRR = !SettingsStore.getValue("sendReadReceipts", roomId); + const hiddenRR = SettingsStore.getValue("feature_hiddenReadReceipts", roomId); debuglog('TimelinePanel: Sending Read Markers for ', this.props.timelineSet.room.roomId, diff --git a/src/components/views/settings/tabs/user/LabsUserSettingsTab.js b/src/components/views/settings/tabs/user/LabsUserSettingsTab.js index 5274d5191d..07abf0aa2e 100644 --- a/src/components/views/settings/tabs/user/LabsUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/LabsUserSettingsTab.js @@ -79,7 +79,7 @@ export default class LabsUserSettingsTab extends React.Component { let hiddenReadReceipts; if (this.state.showHiddenReadReceipts) { hiddenReadReceipts = ( - + ); } diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index 554674d5d9..408de4c2c5 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -333,12 +333,12 @@ export const SETTINGS: {[setting: string]: ISetting} = { supportedLevels: LEVELS_ACCOUNT_SETTINGS, default: null, }, - "sendReadReceipts": { - supportedLevels: LEVELS_ROOM_SETTINGS, + "feature_hiddenReadReceipts": { + supportedLevels: LEVELS_FEATURE, displayName: _td( - "Send read receipts for messages (requires compatible homeserver to disable)", + "Don't send read receipts", ), - default: true, + default: false, }, "baseFontSize": { displayName: _td("Font size"), From f7e750df60bb0bb6782701cbcab7e0f6e1219dcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 6 Aug 2021 07:54:56 +0200 Subject: [PATCH 38/80] i18n MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/i18n/strings/en_EN.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 89252c5f07..b1c865dafa 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -817,7 +817,7 @@ "Enable advanced debugging for the room list": "Enable advanced debugging for the room list", "Show info about bridges in room settings": "Show info about bridges in room settings", "New layout switcher (with message bubbles)": "New layout switcher (with message bubbles)", - "Send read receipts for messages (requires compatible homeserver to disable)": "Send read receipts for messages (requires compatible homeserver to disable)", + "Don't send read receipts": "Don't send read receipts", "Font size": "Font size", "Use custom size": "Use custom size", "Enable Emoji suggestions while typing": "Enable Emoji suggestions while typing", From 44acded0a00f2ee8e0c69e9316e1a374a9ddc8c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 6 Aug 2021 07:55:08 +0200 Subject: [PATCH 39/80] Use snake case MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/structures/TimelinePanel.tsx | 2 +- src/components/views/settings/tabs/user/LabsUserSettingsTab.js | 2 +- src/settings/Settings.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/structures/TimelinePanel.tsx b/src/components/structures/TimelinePanel.tsx index 533216df64..f62676a4fc 100644 --- a/src/components/structures/TimelinePanel.tsx +++ b/src/components/structures/TimelinePanel.tsx @@ -758,7 +758,7 @@ class TimelinePanel extends React.Component { this.lastRMSentEventId = this.state.readMarkerEventId; const roomId = this.props.timelineSet.room.roomId; - const hiddenRR = SettingsStore.getValue("feature_hiddenReadReceipts", roomId); + const hiddenRR = SettingsStore.getValue("feature_hidden_read_receipts", roomId); debuglog('TimelinePanel: Sending Read Markers for ', this.props.timelineSet.room.roomId, diff --git a/src/components/views/settings/tabs/user/LabsUserSettingsTab.js b/src/components/views/settings/tabs/user/LabsUserSettingsTab.js index 07abf0aa2e..19a97151d6 100644 --- a/src/components/views/settings/tabs/user/LabsUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/LabsUserSettingsTab.js @@ -79,7 +79,7 @@ export default class LabsUserSettingsTab extends React.Component { let hiddenReadReceipts; if (this.state.showHiddenReadReceipts) { hiddenReadReceipts = ( - + ); } diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index 408de4c2c5..79a82f56b7 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -333,7 +333,7 @@ export const SETTINGS: {[setting: string]: ISetting} = { supportedLevels: LEVELS_ACCOUNT_SETTINGS, default: null, }, - "feature_hiddenReadReceipts": { + "feature_hidden_read_receipts": { supportedLevels: LEVELS_FEATURE, displayName: _td( "Don't send read receipts", From 67fdbf97e5483c5a0c4fc123b632160a330de0f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 6 Aug 2021 09:17:00 +0200 Subject: [PATCH 40/80] Round percentageOfViewport MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/messages/TextualBody.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/views/messages/TextualBody.tsx b/src/components/views/messages/TextualBody.tsx index 969caccaee..83fe7f5a3d 100644 --- a/src/components/views/messages/TextualBody.tsx +++ b/src/components/views/messages/TextualBody.tsx @@ -136,7 +136,8 @@ export default class TextualBody extends React.Component { private addCodeExpansionButton(div: HTMLDivElement, pre: HTMLPreElement): void { // Calculate how many percent does the pre element take up. // If it's less than 30% we don't add the expansion button. - const percentageOfViewport = pre.offsetHeight / UIStore.instance.windowHeight * 100; + // We also round the number as it sometimes can be 29.99... + const percentageOfViewport = Math.round(pre.offsetHeight / UIStore.instance.windowHeight * 100); if (percentageOfViewport < 30) return; const button = document.createElement("span"); From 85f5ec3a94402a14667486518353036a2d431f0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 6 Aug 2021 10:27:18 +0200 Subject: [PATCH 41/80] Move LayoutSwitcher into a separate component MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- .../views/settings/LayoutSwitcher.tsx | 129 ++++++++++++++++++ .../tabs/user/AppearanceUserSettingsTab.tsx | 98 +++---------- 2 files changed, 145 insertions(+), 82 deletions(-) create mode 100644 src/components/views/settings/LayoutSwitcher.tsx diff --git a/src/components/views/settings/LayoutSwitcher.tsx b/src/components/views/settings/LayoutSwitcher.tsx new file mode 100644 index 0000000000..4796519ee1 --- /dev/null +++ b/src/components/views/settings/LayoutSwitcher.tsx @@ -0,0 +1,129 @@ +/* +Copyright 2021 Šimon Brandner + +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 React from "react"; +import classNames from "classnames"; +import SettingsStore from "../../../settings/SettingsStore"; +import EventTilePreview from "../elements/EventTilePreview"; +import StyledRadioButton from "../elements/StyledRadioButton"; +import { _t } from "../../../languageHandler"; +import { Layout } from "../../../settings/Layout"; +import { SettingLevel } from "../../../settings/SettingLevel"; + +interface IProps { + userId: string; + displayName: string; + avatarUrl: string; + messagePreviewText: string; + onLayoutChanged?: (layout: Layout) => void; +} + +interface IState { + layout: Layout; +} + +export default class LayoutSwitcher extends React.Component { + constructor(props: IProps) { + super(props); + + this.state = { + layout: SettingsStore.getValue("layout"), + }; + } + + private onLayoutChange = (e: React.ChangeEvent): void => { + const layout = e.target.value as Layout; + + this.setState({ layout: layout }); + SettingsStore.setValue("layout", null, SettingLevel.DEVICE, layout); + this.props.onLayoutChanged(layout); + }; + + public render(): JSX.Element { + const ircClasses = classNames("mx_AppearanceUserSettingsTab_Layout_RadioButton", { + mx_AppearanceUserSettingsTab_Layout_RadioButton_selected: this.state.layout == Layout.IRC, + }); + const groupClasses = classNames("mx_AppearanceUserSettingsTab_Layout_RadioButton", { + mx_AppearanceUserSettingsTab_Layout_RadioButton_selected: this.state.layout == Layout.Group, + }); + const bubbleClasses = classNames("mx_AppearanceUserSettingsTab_Layout_RadioButton", { + mx_AppearanceUserSettingsTab_Layout_RadioButton_selected: this.state.layout === Layout.Bubble, + }); + + return
+ + { _t("Message layout") } + + +
+ + + +
+
; + } +} diff --git a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx index 44873816dc..e4cae80406 100644 --- a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx @@ -37,10 +37,9 @@ import StyledRadioGroup from "../../../elements/StyledRadioGroup"; import { SettingLevel } from "../../../../../settings/SettingLevel"; import { UIFeature } from "../../../../../settings/UIFeature"; import { Layout } from "../../../../../settings/Layout"; -import classNames from 'classnames'; -import StyledRadioButton from '../../../elements/StyledRadioButton'; import { replaceableComponent } from "../../../../../utils/replaceableComponent"; import { compare } from "../../../../../utils/strings"; +import LayoutSwitcher from "../../LayoutSwitcher"; interface IProps { } @@ -243,17 +242,8 @@ export default class AppearanceUserSettingsTab extends React.Component): void => { - let layout; - switch (e.target.value) { - case "irc": layout = Layout.IRC; break; - case "group": layout = Layout.Group; break; - case "bubble": layout = Layout.Bubble; break; - } - + private onLayoutChanged = (layout: Layout): void => { this.setState({ layout: layout }); - - SettingsStore.setValue("layout", null, SettingLevel.DEVICE, layout); }; private onIRCLayoutChange = (enabled: boolean) => { @@ -391,75 +381,6 @@ export default class AppearanceUserSettingsTab extends React.Component; } - private renderLayoutSection = () => { - return
- { _t("Message layout") } - -
- - - -
-
; - }; - private renderAdvancedSection() { if (!SettingsStore.getValue(UIFeature.AdvancedSettings)) return null; @@ -527,6 +448,19 @@ export default class AppearanceUserSettingsTab extends React.Component + ); + } + return (
{ _t("Customise your appearance") }
@@ -534,7 +468,7 @@ export default class AppearanceUserSettingsTab extends React.Component { this.renderThemeSection() } - { SettingsStore.getValue("feature_new_layout_switcher") ? this.renderLayoutSection() : null } + { layoutSection } { this.renderFontSection() } { this.renderAdvancedSection() }
From 186acf92a988e7f1b338b59aa8739655e65dde76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 6 Aug 2021 10:30:38 +0200 Subject: [PATCH 42/80] Wrap in () MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- .../views/settings/LayoutSwitcher.tsx | 122 +++++++++--------- 1 file changed, 62 insertions(+), 60 deletions(-) diff --git a/src/components/views/settings/LayoutSwitcher.tsx b/src/components/views/settings/LayoutSwitcher.tsx index 4796519ee1..a96161d781 100644 --- a/src/components/views/settings/LayoutSwitcher.tsx +++ b/src/components/views/settings/LayoutSwitcher.tsx @@ -63,67 +63,69 @@ export default class LayoutSwitcher extends React.Component { mx_AppearanceUserSettingsTab_Layout_RadioButton_selected: this.state.layout === Layout.Bubble, }); - return
- - { _t("Message layout") } - + return ( +
+ + { _t("Message layout") } + -
- - - +
+ + + +
-
; + ); } } From 25c6b216b0976759d642f963b076bd0bb37b3d6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 6 Aug 2021 10:37:42 +0200 Subject: [PATCH 43/80] Move LayoutSwitcher CSS to a separate file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/_components.scss | 1 + res/css/views/settings/_LayoutSwitcher.scss | 90 +++++++++++++++++++ .../tabs/user/_AppearanceUserSettingsTab.scss | 73 --------------- .../views/settings/LayoutSwitcher.tsx | 22 ++--- 4 files changed, 102 insertions(+), 84 deletions(-) create mode 100644 res/css/views/settings/_LayoutSwitcher.scss diff --git a/res/css/_components.scss b/res/css/_components.scss index 92d2bfe7f3..4bef7bf14a 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -240,6 +240,7 @@ @import "./views/settings/_E2eAdvancedPanel.scss"; @import "./views/settings/_EmailAddresses.scss"; @import "./views/settings/_IntegrationManager.scss"; +@import "./views/settings/_LayoutSwitcher.scss"; @import "./views/settings/_Notifications.scss"; @import "./views/settings/_PhoneNumbers.scss"; @import "./views/settings/_ProfileSettings.scss"; diff --git a/res/css/views/settings/_LayoutSwitcher.scss b/res/css/views/settings/_LayoutSwitcher.scss new file mode 100644 index 0000000000..1cf312f4d1 --- /dev/null +++ b/res/css/views/settings/_LayoutSwitcher.scss @@ -0,0 +1,90 @@ +/* +Copyright 2021 Šimon Brandner + +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. +*/ + +.mx_LayoutSwitcher { + .mx_LayoutSwitcher_RadioButtons { + display: flex; + flex-direction: row; + gap: 24px; + + color: $primary-fg-color; + + > .mx_LayoutSwitcher_RadioButton { + flex-grow: 0; + flex-shrink: 1; + display: flex; + flex-direction: column; + + width: 300px; + + border: 1px solid $appearance-tab-border-color; + border-radius: 10px; + + .mx_EventTile_msgOption, + .mx_MessageActionBar { + display: none; + } + + .mx_LayoutSwitcher_RadioButton_preview { + flex-grow: 1; + display: flex; + align-items: center; + padding: 10px; + pointer-events: none; + } + + .mx_RadioButton { + flex-grow: 0; + padding: 10px; + } + + .mx_EventTile_content { + margin-right: 0; + } + + &.mx_LayoutSwitcher_RadioButton_selected { + border-color: $accent-color; + } + } + + .mx_RadioButton { + border-top: 1px solid $appearance-tab-border-color; + + > input + div { + border-color: rgba($muted-fg-color, 0.2); + } + } + + .mx_RadioButton_checked { + background-color: rgba($accent-color, 0.08); + } + + .mx_EventTile { + margin: 0; + &[data-layout=bubble] { + margin-right: 40px; + } + &[data-layout=irc] { + > a { + display: none; + } + } + .mx_EventTile_line { + max-width: 90%; + } + } + } +} diff --git a/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss b/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss index ca5a6f0a66..55124dfde1 100644 --- a/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss @@ -155,79 +155,6 @@ limitations under the License. margin-left: calc($font-16px + 10px); } -.mx_AppearanceUserSettingsTab_Layout_RadioButtons { - display: flex; - flex-direction: row; - gap: 24px; - - color: $primary-fg-color; - - > .mx_AppearanceUserSettingsTab_Layout_RadioButton { - flex-grow: 0; - flex-shrink: 1; - display: flex; - flex-direction: column; - - width: 300px; - - border: 1px solid $appearance-tab-border-color; - border-radius: 10px; - - .mx_EventTile_msgOption, - .mx_MessageActionBar { - display: none; - } - - .mx_AppearanceUserSettingsTab_Layout_RadioButton_preview { - flex-grow: 1; - display: flex; - align-items: center; - padding: 10px; - pointer-events: none; - } - - .mx_RadioButton { - flex-grow: 0; - padding: 10px; - } - - .mx_EventTile_content { - margin-right: 0; - } - - &.mx_AppearanceUserSettingsTab_Layout_RadioButton_selected { - border-color: $accent-color; - } - } - - .mx_RadioButton { - border-top: 1px solid $appearance-tab-border-color; - - > input + div { - border-color: rgba($muted-fg-color, 0.2); - } - } - - .mx_RadioButton_checked { - background-color: rgba($accent-color, 0.08); - } - - .mx_EventTile { - margin: 0; - &[data-layout=bubble] { - margin-right: 40px; - } - &[data-layout=irc] { - > a { - display: none; - } - } - .mx_EventTile_line { - max-width: 90%; - } - } -} - .mx_AppearanceUserSettingsTab_Advanced { color: $primary-fg-color; diff --git a/src/components/views/settings/LayoutSwitcher.tsx b/src/components/views/settings/LayoutSwitcher.tsx index a96161d781..9afdf205f8 100644 --- a/src/components/views/settings/LayoutSwitcher.tsx +++ b/src/components/views/settings/LayoutSwitcher.tsx @@ -53,26 +53,26 @@ export default class LayoutSwitcher extends React.Component { }; public render(): JSX.Element { - const ircClasses = classNames("mx_AppearanceUserSettingsTab_Layout_RadioButton", { - mx_AppearanceUserSettingsTab_Layout_RadioButton_selected: this.state.layout == Layout.IRC, + const ircClasses = classNames("mx_LayoutSwitcher_RadioButton", { + mx_LayoutSwitcher_RadioButton_selected: this.state.layout == Layout.IRC, }); - const groupClasses = classNames("mx_AppearanceUserSettingsTab_Layout_RadioButton", { - mx_AppearanceUserSettingsTab_Layout_RadioButton_selected: this.state.layout == Layout.Group, + const groupClasses = classNames("mx_LayoutSwitcher_RadioButton", { + mx_LayoutSwitcher_RadioButton_selected: this.state.layout == Layout.Group, }); - const bubbleClasses = classNames("mx_AppearanceUserSettingsTab_Layout_RadioButton", { - mx_AppearanceUserSettingsTab_Layout_RadioButton_selected: this.state.layout === Layout.Bubble, + const bubbleClasses = classNames("mx_LayoutSwitcher_RadioButton", { + mx_LayoutSwitcher_RadioButton_selected: this.state.layout === Layout.Bubble, }); return ( -
+
{ _t("Message layout") } -
+