From c6a058fb6fa0af190c117c2f3b5bc01a6eab17e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 19 Dec 2020 19:32:58 +0100 Subject: [PATCH 001/176] Added surround with MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- .../views/rooms/BasicMessageComposer.tsx | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/components/views/rooms/BasicMessageComposer.tsx b/src/components/views/rooms/BasicMessageComposer.tsx index 2ececdeaed..cd34e25926 100644 --- a/src/components/views/rooms/BasicMessageComposer.tsx +++ b/src/components/views/rooms/BasicMessageComposer.tsx @@ -418,6 +418,10 @@ export default class BasicMessageEditor extends React.Component }; private onKeyDown = (event: React.KeyboardEvent) => { + const selectionRange = getRangeForSelection(this.editorRef.current, this.props.model, document.getSelection()); + // trim the range as we want it to exclude leading/trailing spaces + selectionRange.trim(); + const model = this.props.model; const modKey = IS_MAC ? event.metaKey : event.ctrlKey; let handled = false; @@ -471,6 +475,43 @@ export default class BasicMessageEditor extends React.Component }); handled = true; // autocomplete or enter to send below shouldn't have any modifier keys pressed. + } else if (document.getSelection().type != "Caret") { + if (event.key === '(') { + this.historyManager.ensureLastChangesPushed(this.props.model); + this.modifiedFlag = true; + toggleInlineFormat(selectionRange, "(", ")"); + handled = true; + } else if (event.key === '[') { + this.historyManager.ensureLastChangesPushed(this.props.model); + this.modifiedFlag = true; + toggleInlineFormat(selectionRange, "[", "]"); + handled = true; + } else if (event.key === '{') { + this.historyManager.ensureLastChangesPushed(this.props.model); + this.modifiedFlag = true; + toggleInlineFormat(selectionRange, "{", "}"); + handled = true; + } else if (event.key === '<') { + this.historyManager.ensureLastChangesPushed(this.props.model); + this.modifiedFlag = true; + toggleInlineFormat(selectionRange, "<", ">"); + handled = true; + } else if (event.key === '"') { + this.historyManager.ensureLastChangesPushed(this.props.model); + this.modifiedFlag = true; + toggleInlineFormat(selectionRange, "\""); + handled = true; + } else if (event.key === '`') { + this.historyManager.ensureLastChangesPushed(this.props.model); + this.modifiedFlag = true; + toggleInlineFormat(selectionRange, "`"); + handled = true; + } else if (event.key === '\'') { + this.historyManager.ensureLastChangesPushed(this.props.model); + this.modifiedFlag = true; + toggleInlineFormat(selectionRange, "'"); + handled = true; + } } else { const metaOrAltPressed = event.metaKey || event.altKey; const modifierPressed = metaOrAltPressed || event.shiftKey; From e90f5ddf5b6ac14648d7fc36ecf414a04d668c4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 19 Dec 2020 19:36:56 +0100 Subject: [PATCH 002/176] Added a comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/rooms/BasicMessageComposer.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/views/rooms/BasicMessageComposer.tsx b/src/components/views/rooms/BasicMessageComposer.tsx index cd34e25926..587f13e8c2 100644 --- a/src/components/views/rooms/BasicMessageComposer.tsx +++ b/src/components/views/rooms/BasicMessageComposer.tsx @@ -512,6 +512,7 @@ export default class BasicMessageEditor extends React.Component toggleInlineFormat(selectionRange, "'"); handled = true; } + // Surround selected text with a character } else { const metaOrAltPressed = event.metaKey || event.altKey; const modifierPressed = metaOrAltPressed || event.shiftKey; From b330dd55a0cd61fe9b014d47c6dcfb085e835b93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 12 Feb 2021 07:53:09 +0100 Subject: [PATCH 003/176] Hide surround with behind a setting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/rooms/BasicMessageComposer.tsx | 3 ++- .../views/settings/tabs/user/PreferencesUserSettingsTab.js | 1 + src/settings/Settings.ts | 5 +++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/views/rooms/BasicMessageComposer.tsx b/src/components/views/rooms/BasicMessageComposer.tsx index 587f13e8c2..a91e92123b 100644 --- a/src/components/views/rooms/BasicMessageComposer.tsx +++ b/src/components/views/rooms/BasicMessageComposer.tsx @@ -418,6 +418,7 @@ export default class BasicMessageEditor extends React.Component }; private onKeyDown = (event: React.KeyboardEvent) => { + const surroundWith = SettingsStore.getValue("MessageComposerInput.surroundWith"); const selectionRange = getRangeForSelection(this.editorRef.current, this.props.model, document.getSelection()); // trim the range as we want it to exclude leading/trailing spaces selectionRange.trim(); @@ -475,7 +476,7 @@ export default class BasicMessageEditor extends React.Component }); handled = true; // autocomplete or enter to send below shouldn't have any modifier keys pressed. - } else if (document.getSelection().type != "Caret") { + } else if (surroundWith && document.getSelection().type != "Caret") { if (event.key === '(') { this.historyManager.ensureLastChangesPushed(this.props.model); this.modifiedFlag = true; diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js index 4d8493401e..2544c03a22 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js @@ -34,6 +34,7 @@ export default class PreferencesUserSettingsTab extends React.Component { 'MessageComposerInput.suggestEmoji', 'sendTypingNotifications', 'MessageComposerInput.ctrlEnterToSend', + 'MessageComposerInput.surroundWith', ]; static TIMELINE_SETTINGS = [ diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index b239b809fe..ed9b37d632 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -336,6 +336,11 @@ export const SETTINGS: {[setting: string]: ISetting} = { displayName: isMac ? _td("Use Command + Enter to send a message") : _td("Use Ctrl + Enter to send a message"), default: false, }, + "MessageComposerInput.surroundWith": { + supportedLevels: LEVELS_ACCOUNT_SETTINGS, + displayName: _td("Use surround with"), + default: false, + }, "MessageComposerInput.autoReplaceEmoji": { supportedLevels: LEVELS_ACCOUNT_SETTINGS, displayName: _td('Automatically replace plain text Emoji'), From 3f0d7673725f12b99e48b0f2e94c9c0f78f9c5d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 12 Feb 2021 07:57:15 +0100 Subject: [PATCH 004/176] 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 a9d31bb9f2..3af2a62c94 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -816,6 +816,7 @@ "Use Ctrl + F to search": "Use Ctrl + F to search", "Use Command + Enter to send a message": "Use Command + Enter to send a message", "Use Ctrl + Enter to send a message": "Use Ctrl + Enter to send a message", + "Use surround with": "Use surround with", "Automatically replace plain text Emoji": "Automatically replace plain text Emoji", "Mirror local video feed": "Mirror local video feed", "Enable Community Filter Panel": "Enable Community Filter Panel", From cf25e15eb6d3071dccb92c07e1f96bd726d56755 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20V=C3=A1gner?= Date: Sat, 12 Jun 2021 12:49:15 +0200 Subject: [PATCH 005/176] Make call control buttons accessible to screen reader users MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Peter Vágner --- src/components/views/voip/CallView.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index c084dacaa8..178df246d1 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -441,6 +441,7 @@ export default class CallView extends React.Component { const vidMuteButton = this.props.call.type === CallType.Video ? : null; // The dial pad & 'more' button actions are only relevant in a connected call @@ -450,6 +451,7 @@ export default class CallView extends React.Component { inputRef={this.dialpadButton} onClick={this.onDialpadClick} isExpanded={this.state.showDialpad} + aria-label={_t("Dialpad")} /> :
; const contextMenuButton = this.state.callState === CallState.Connected ? { onClick={this.onMoreClick} inputRef={this.contextMenuButton} isExpanded={this.state.showMoreMenu} + aria-label={_t("More")} /> :
; // in the near future, the dial pad button will go on the left. For now, it's the nothing button @@ -466,6 +469,7 @@ export default class CallView extends React.Component { Date: Sat, 12 Jun 2021 13:53:44 +0200 Subject: [PATCH 006/176] i18n MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Peter Vágner --- src/i18n/strings/en_EN.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 874dc11bd2..7df22432de 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -889,6 +889,12 @@ "sends snowfall": "sends snowfall", "Sends the given message with a space themed effect": "Sends the given message with a space themed effect", "sends space invaders": "sends space invaders", + "Start the camera": "Start the camera", + "Stop the camera": "Stop the camera", + "Dialpad": "Dialpad", + "More": "More", + "Unmute the microphone": "Unmute the microphone", + "Mute the microphone": "Mute the microphone", "unknown person": "unknown person", "Consulting with %(transferTarget)s. Transfer to %(transferee)s": "Consulting with %(transferTarget)s. Transfer to %(transferee)s", "You held the call Switch": "You held the call Switch", From a94d11235ed5352b8da0ae87964abf2a00eddb33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20V=C3=A1gner?= Date: Sat, 12 Jun 2021 14:17:10 +0200 Subject: [PATCH 007/176] Changed the buttons to TooltipButtons and added the tooltip for the hangup button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Peter Vágner --- src/components/views/voip/CallView.tsx | 21 ++++++++++++--------- src/i18n/strings/en_EN.json | 1 + 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index 178df246d1..66b3f6b2d4 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -25,6 +25,8 @@ import RoomAvatar from "../avatars/RoomAvatar"; import { CallState, CallType, MatrixCall, CallEvent } from 'matrix-js-sdk/src/webrtc/call'; import classNames from 'classnames'; import AccessibleButton from '../elements/AccessibleButton'; +import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; +import { ContextMenuTooltipButton } from "../../../accessibility/context_menu/ContextMenuTooltipButton"; import {isOnlyCtrlOrCmdKeyEvent, Key} from '../../../Keyboard'; import {alwaysAboveLeftOf, alwaysAboveRightOf, ChevronFace, ContextMenuButton} from '../../structures/ContextMenu'; import CallContextMenu from '../context_menus/CallContextMenu'; @@ -438,40 +440,40 @@ export default class CallView extends React.Component { mx_CallView_callControls_hidden: !this.state.controlsVisible, }); - const vidMuteButton = this.props.call.type === CallType.Video ? : null; // The dial pad & 'more' button actions are only relevant in a connected call // When not connected, we have to put something there to make the flexbox alignment correct - const dialpadButton = this.state.callState === CallState.Connected ? :
; - const contextMenuButton = this.state.callState === CallState.Connected ? :
; // in the near future, the dial pad button will go on the left. For now, it's the nothing button // because something needs to have margin-right: auto to make the alignment correct. const callControls =
{dialpadButton} - - { dis.dispatch({ @@ -479,6 +481,7 @@ export default class CallView extends React.Component { room_id: callRoomId, }); }} + title={_t("Hangup")} /> {vidMuteButton}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 7df22432de..d50348954a 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -895,6 +895,7 @@ "More": "More", "Unmute the microphone": "Unmute the microphone", "Mute the microphone": "Mute the microphone", + "Hangup": "Hangup", "unknown person": "unknown person", "Consulting with %(transferTarget)s. Transfer to %(transferee)s": "Consulting with %(transferTarget)s. Transfer to %(transferee)s", "You held the call Switch": "You held the call Switch", From fd7eaddb2d4c170084fbfc6ca3ca4c26f40a57b9 Mon Sep 17 00:00:00 2001 From: pvagner Date: Wed, 16 Jun 2021 10:18:35 +0200 Subject: [PATCH 008/176] Update src/components/views/voip/CallView.tsx Co-authored-by: Michael Telatynski <7t3chguy@googlemail.com> --- src/components/views/voip/CallView.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index 66b3f6b2d4..f7f82d4300 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -26,9 +26,13 @@ import { CallState, CallType, MatrixCall, CallEvent } from 'matrix-js-sdk/src/we import classNames from 'classnames'; import AccessibleButton from '../elements/AccessibleButton'; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; -import { ContextMenuTooltipButton } from "../../../accessibility/context_menu/ContextMenuTooltipButton"; import {isOnlyCtrlOrCmdKeyEvent, Key} from '../../../Keyboard'; -import {alwaysAboveLeftOf, alwaysAboveRightOf, ChevronFace, ContextMenuButton} from '../../structures/ContextMenu'; +import { + alwaysAboveLeftOf, + alwaysAboveRightOf, + ChevronFace, + ContextMenuTooltipButton, +} from '../../structures/ContextMenu'; import CallContextMenu from '../context_menus/CallContextMenu'; import { avatarUrlForMember } from '../../../Avatar'; import DialpadContextMenu from '../context_menus/DialpadContextMenu'; From 38c0cd27163effd85b5c12cd499f6291e1a06c6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 20 Jun 2021 08:21:33 +0200 Subject: [PATCH 009/176] Cache surroundWith setting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- .../views/rooms/BasicMessageComposer.tsx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/components/views/rooms/BasicMessageComposer.tsx b/src/components/views/rooms/BasicMessageComposer.tsx index f5fddff45b..de72f8a348 100644 --- a/src/components/views/rooms/BasicMessageComposer.tsx +++ b/src/components/views/rooms/BasicMessageComposer.tsx @@ -107,6 +107,7 @@ interface IState { showVisualBell?: boolean; autoComplete?: AutocompleteWrapperModel; completionIndex?: number; + surroundWith: boolean, } @replaceableComponent("views.rooms.BasicMessageEditor") @@ -125,12 +126,14 @@ export default class BasicMessageEditor extends React.Component private readonly emoticonSettingHandle: string; private readonly shouldShowPillAvatarSettingHandle: string; + private readonly surroundWithHandle: string; private readonly historyManager = new HistoryManager(); constructor(props) { super(props); this.state = { showPillAvatar: SettingsStore.getValue("Pill.shouldShowPillAvatar"), + surroundWith: SettingsStore.getValue("MessageComposerInput.surroundWith"), }; this.emoticonSettingHandle = SettingsStore.watchSetting('MessageComposerInput.autoReplaceEmoji', null, @@ -138,6 +141,8 @@ export default class BasicMessageEditor extends React.Component this.configureEmoticonAutoReplace(); this.shouldShowPillAvatarSettingHandle = SettingsStore.watchSetting("Pill.shouldShowPillAvatar", null, this.configureShouldShowPillAvatar); + this.surroundWithHandle = SettingsStore.watchSetting("MessageComposerInput.surroundWith", null, + this.surroundWithSettingChanged); } public componentDidUpdate(prevProps: IProps) { @@ -428,7 +433,6 @@ export default class BasicMessageEditor extends React.Component }; private onKeyDown = (event: React.KeyboardEvent) => { - const surroundWith = SettingsStore.getValue("MessageComposerInput.surroundWith"); const selectionRange = getRangeForSelection(this.editorRef.current, this.props.model, document.getSelection()); // trim the range as we want it to exclude leading/trailing spaces selectionRange.trim(); @@ -436,7 +440,7 @@ export default class BasicMessageEditor extends React.Component const model = this.props.model; let handled = false; - if (surroundWith && document.getSelection().type != "Caret") { + if (this.state.surroundWith && document.getSelection().type != "Caret") { // Surround selected text with a character if (event.key === '(') { this.historyManager.ensureLastChangesPushed(this.props.model); @@ -628,6 +632,11 @@ export default class BasicMessageEditor extends React.Component this.setState({ showPillAvatar }); }; + private surroundWithSettingChanged = () => { + const surroundWith = SettingsStore.getValue("MessageComposerInput.surroundWith"); + this.setState({ surroundWith }); + }; + componentWillUnmount() { document.removeEventListener("selectionchange", this.onSelectionChange); this.editorRef.current.removeEventListener("input", this.onInput, true); @@ -635,6 +644,7 @@ export default class BasicMessageEditor extends React.Component this.editorRef.current.removeEventListener("compositionend", this.onCompositionEnd, true); SettingsStore.unwatchSetting(this.emoticonSettingHandle); SettingsStore.unwatchSetting(this.shouldShowPillAvatarSettingHandle); + SettingsStore.unwatchSetting(this.surroundWithHandle); } componentDidMount() { From a772460f63681b85fbc1ad9d972d329d871cbd7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 20 Jun 2021 08:38:01 +0200 Subject: [PATCH 010/176] Simplifie surround with and make it more extensible MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- .../views/rooms/BasicMessageComposer.tsx | 45 +++++++------------ 1 file changed, 15 insertions(+), 30 deletions(-) diff --git a/src/components/views/rooms/BasicMessageComposer.tsx b/src/components/views/rooms/BasicMessageComposer.tsx index de72f8a348..239624f5d8 100644 --- a/src/components/views/rooms/BasicMessageComposer.tsx +++ b/src/components/views/rooms/BasicMessageComposer.tsx @@ -55,6 +55,14 @@ const REGEX_EMOTICON_WHITESPACE = new RegExp('(?:^|\\s)(' + EMOTICON_REGEX.sourc const IS_MAC = navigator.platform.indexOf("Mac") !== -1; +const SURROUND_WITH_CHARACTERS = ["\"", "_", "`", "'", "*", "~", "$"]; +const SURROUND_WITH_DOUBLE_CHARACTERS = new Map([ + ["(", ")"], + ["[", "]"], + ["{", "}"], + ["<", ">"], +]); + function ctrlShortcutLabel(key) { return (IS_MAC ? "⌘" : "Ctrl") + "+" + key; } @@ -441,41 +449,18 @@ export default class BasicMessageEditor extends React.Component let handled = false; if (this.state.surroundWith && document.getSelection().type != "Caret") { - // Surround selected text with a character - if (event.key === '(') { + // This surrounds the selected text with a character. This is + // intentionally left out of the keybinding manager as the keybinds + // here shouldn't be changeable + if (SURROUND_WITH_CHARACTERS.includes(event.key)) { this.historyManager.ensureLastChangesPushed(this.props.model); this.modifiedFlag = true; - toggleInlineFormat(selectionRange, "(", ")"); + toggleInlineFormat(selectionRange, event.key); handled = true; - } else if (event.key === '[') { + } else if ([...SURROUND_WITH_DOUBLE_CHARACTERS.keys()].includes(event.key)) { this.historyManager.ensureLastChangesPushed(this.props.model); this.modifiedFlag = true; - toggleInlineFormat(selectionRange, "[", "]"); - handled = true; - } else if (event.key === '{') { - this.historyManager.ensureLastChangesPushed(this.props.model); - this.modifiedFlag = true; - toggleInlineFormat(selectionRange, "{", "}"); - handled = true; - } else if (event.key === '<') { - this.historyManager.ensureLastChangesPushed(this.props.model); - this.modifiedFlag = true; - toggleInlineFormat(selectionRange, "<", ">"); - handled = true; - } else if (event.key === '"') { - this.historyManager.ensureLastChangesPushed(this.props.model); - this.modifiedFlag = true; - toggleInlineFormat(selectionRange, "\""); - handled = true; - } else if (event.key === '`') { - this.historyManager.ensureLastChangesPushed(this.props.model); - this.modifiedFlag = true; - toggleInlineFormat(selectionRange, "`"); - handled = true; - } else if (event.key === '\'') { - this.historyManager.ensureLastChangesPushed(this.props.model); - this.modifiedFlag = true; - toggleInlineFormat(selectionRange, "'"); + toggleInlineFormat(selectionRange, event.key, SURROUND_WITH_DOUBLE_CHARACTERS.get(event.key)); handled = true; } } From 3e97847e7de3830e4a63d2d884a374a9091b03eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 20 Jun 2021 08:47:21 +0200 Subject: [PATCH 011/176] Get selection range only if necessary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/rooms/BasicMessageComposer.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/components/views/rooms/BasicMessageComposer.tsx b/src/components/views/rooms/BasicMessageComposer.tsx index 239624f5d8..d8a872f1c6 100644 --- a/src/components/views/rooms/BasicMessageComposer.tsx +++ b/src/components/views/rooms/BasicMessageComposer.tsx @@ -441,10 +441,6 @@ export default class BasicMessageEditor extends React.Component }; private onKeyDown = (event: React.KeyboardEvent) => { - const selectionRange = getRangeForSelection(this.editorRef.current, this.props.model, document.getSelection()); - // trim the range as we want it to exclude leading/trailing spaces - selectionRange.trim(); - const model = this.props.model; let handled = false; @@ -452,6 +448,15 @@ export default class BasicMessageEditor extends React.Component // This surrounds the selected text with a character. This is // intentionally left out of the keybinding manager as the keybinds // here shouldn't be changeable + + const selectionRange = getRangeForSelection( + this.editorRef.current, + this.props.model, + document.getSelection(), + ); + // trim the range as we want it to exclude leading/trailing spaces + selectionRange.trim(); + if (SURROUND_WITH_CHARACTERS.includes(event.key)) { this.historyManager.ensureLastChangesPushed(this.props.model); this.modifiedFlag = true; From 314ab7a94d7868c6375d3efca8d46d6c4f97ffa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 21 Jun 2021 11:20:20 +0200 Subject: [PATCH 012/176] If there already is a Jitsi widget pin it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/CallHandler.tsx | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index 448b1cb780..5138d526dd 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -61,7 +61,6 @@ import Modal from './Modal'; import { _t } from './languageHandler'; import dis from './dispatcher/dispatcher'; import WidgetUtils from './utils/WidgetUtils'; -import WidgetEchoStore from './stores/WidgetEchoStore'; import SettingsStore from './settings/SettingsStore'; import {Jitsi} from "./widgets/Jitsi"; import {WidgetType} from "./widgets/WidgetType"; @@ -88,6 +87,7 @@ import { randomUppercaseString, randomLowercaseString } from "matrix-js-sdk/src/ import EventEmitter from 'events'; import SdkConfig from './SdkConfig'; import { ensureDMExists, findDMForUser } from './createRoom'; +import { WidgetLayoutStore, Container } from './stores/widgets/WidgetLayoutStore'; export const PROTOCOL_PSTN = 'm.protocol.pstn'; export const PROTOCOL_PSTN_PREFIXED = 'im.vector.protocol.pstn'; @@ -940,14 +940,10 @@ export default class CallHandler extends EventEmitter { // prevent double clicking the call button const room = MatrixClientPeg.get().getRoom(roomId); - const currentJitsiWidgets = WidgetUtils.getRoomWidgetsOfType(room, WidgetType.JITSI); - const hasJitsi = currentJitsiWidgets.length > 0 - || WidgetEchoStore.roomHasPendingWidgetsOfType(roomId, currentJitsiWidgets, WidgetType.JITSI); - if (hasJitsi) { - Modal.createTrackedDialog('Call already in progress', '', ErrorDialog, { - title: _t('Call in Progress'), - description: _t('A call is currently being placed!'), - }); + const jitsiWidget = WidgetStore.instance.getApps(roomId).find((app) => WidgetType.JITSI.matches(app.type)); + if (jitsiWidget) { + // If there already is a Jitsi widget pin it + WidgetLayoutStore.instance.moveToContainer(room, jitsiWidget, Container.Top); return; } From ce47662b55e4296ad73cd395109934c4fe689ec9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 21 Jun 2021 11:23:49 +0200 Subject: [PATCH 013/176] 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 deletions(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index b88dc79da5..1401fca4ba 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -65,8 +65,6 @@ "You cannot place a call with yourself.": "You cannot place a call with yourself.", "Unable to look up phone number": "Unable to look up phone number", "There was an error looking up the phone number": "There was an error looking up the phone number", - "Call in Progress": "Call in Progress", - "A call is currently being placed!": "A call is currently being placed!", "Permission Required": "Permission Required", "You do not have permission to start a conference call in this room": "You do not have permission to start a conference call in this room", "End conference": "End conference", 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 014/176] 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 d7b10e2ff4704afc17ed22983f086fa44b22d270 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 21 Jun 2021 17:26:06 +0200 Subject: [PATCH 015/176] Simplifie code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/rooms/BasicMessageComposer.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/components/views/rooms/BasicMessageComposer.tsx b/src/components/views/rooms/BasicMessageComposer.tsx index d8a872f1c6..06759d0bf5 100644 --- a/src/components/views/rooms/BasicMessageComposer.tsx +++ b/src/components/views/rooms/BasicMessageComposer.tsx @@ -457,12 +457,7 @@ export default class BasicMessageEditor extends React.Component // trim the range as we want it to exclude leading/trailing spaces selectionRange.trim(); - if (SURROUND_WITH_CHARACTERS.includes(event.key)) { - this.historyManager.ensureLastChangesPushed(this.props.model); - this.modifiedFlag = true; - toggleInlineFormat(selectionRange, event.key); - handled = true; - } else if ([...SURROUND_WITH_DOUBLE_CHARACTERS.keys()].includes(event.key)) { + if ([...SURROUND_WITH_DOUBLE_CHARACTERS.keys(), ...SURROUND_WITH_CHARACTERS].includes(event.key)) { this.historyManager.ensureLastChangesPushed(this.props.model); this.modifiedFlag = true; toggleInlineFormat(selectionRange, event.key, SURROUND_WITH_DOUBLE_CHARACTERS.get(event.key)); 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 016/176] 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 017/176] 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 018/176] 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 b2292268bc9a551a1b9ecfe535eafd3b823789d4 Mon Sep 17 00:00:00 2001 From: pvagner Date: Wed, 30 Jun 2021 07:06:10 +0200 Subject: [PATCH 019/176] Update src/components/views/voip/CallView.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.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index 6cb245f7b1..3f959622e7 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -26,7 +26,7 @@ import { CallState, CallType, MatrixCall, CallEvent } from 'matrix-js-sdk/src/we import classNames from 'classnames'; import AccessibleButton from '../elements/AccessibleButton'; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; -import { isOnlyCtrlOrCmdKeyEvent, Key} from '../../../Keyboard'; +import { isOnlyCtrlOrCmdKeyEvent, Key } from '../../../Keyboard'; import { alwaysAboveLeftOf, alwaysAboveRightOf, 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 020/176] 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 021/176] 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 022/176] 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 023/176] 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 024/176] 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 025/176] 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 026/176] 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 027/176] 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 028/176] 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 029/176] 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 030/176] 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 031/176] 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 032/176] 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 f53de5de99d3099564022c79e917dfc80c1af622 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 22 Jul 2021 18:04:24 +0200 Subject: [PATCH 033/176] Make bubble layout background color less agressive MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/themes/dark/css/_dark.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index 655492661c..7b83fe0cb2 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -230,7 +230,7 @@ $composer-shadow-color: rgba(0, 0, 0, 0.28); // Bubble tiles $eventbubble-self-bg: #143A34; $eventbubble-others-bg: #394049; -$eventbubble-bg-hover: #433C23; +$eventbubble-bg-hover: $header-panel-bg-color; $eventbubble-avatar-outline: $bg-color; $eventbubble-reply-color: #C1C6CD; From 6c0e0dc64bbd2d219dff9fae016e5e52c05d2955 Mon Sep 17 00:00:00 2001 From: Aaron Raimist Date: Thu, 22 Jul 2021 21:45:20 -0500 Subject: [PATCH 034/176] Allow all of the URL schemes that Firefox allows Signed-off-by: Aaron Raimist --- src/HtmlUtils.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/HtmlUtils.tsx b/src/HtmlUtils.tsx index af5d2b3019..27556a4012 100644 --- a/src/HtmlUtils.tsx +++ b/src/HtmlUtils.tsx @@ -57,7 +57,10 @@ const BIGEMOJI_REGEX = new RegExp(`^(${EMOJIBASE_REGEX.source})+$`, 'i'); const COLOR_REGEX = /^#[0-9a-fA-F]{6}$/; -export const PERMITTED_URL_SCHEMES = ['http', 'https', 'ftp', 'mailto', 'magnet', 'matrix']; +export const PERMITTED_URL_SCHEMES = ["bitcoin", "ftp", "geo", "http", "https", "im", "irc", + "ircs", "magnet", "mailto", "matrix", "mms", "news", + "nntp", "openpgp4fpr", "sip", "sftp", "sms", "smsto", + "ssh", "tel", "urn", "webcal", "wtai", "xmpp"]; const MEDIA_API_MXC_REGEX = /\/_matrix\/media\/r0\/(?:download|thumbnail)\/(.+?)\/(.+?)(?:[?/]|$)/; From ff37b8cc79205e57969ed44d867b215a492bbf63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 24 Jul 2021 12:47:30 +0200 Subject: [PATCH 035/176] Remove IncomingCallBox MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/voip/CallContainer.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/views/voip/CallContainer.tsx b/src/components/views/voip/CallContainer.tsx index fa963e4e28..41046b9952 100644 --- a/src/components/views/voip/CallContainer.tsx +++ b/src/components/views/voip/CallContainer.tsx @@ -1,5 +1,6 @@ /* Copyright 2020 The Matrix.org Foundation C.I.C. +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. @@ -15,7 +16,6 @@ limitations under the License. */ import React from 'react'; -import IncomingCallBox from './IncomingCallBox'; import CallPreview from './CallPreview'; import { replaceableComponent } from "../../../utils/replaceableComponent"; @@ -31,7 +31,6 @@ interface IState { export default class CallContainer extends React.PureComponent { public render() { return
-
; } From 07be6dd78065e325f1f31ce9de51efaa2b5c6388 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 24 Jul 2021 13:04:06 +0200 Subject: [PATCH 036/176] Allow suppliing whole body to toasts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/structures/ToastContainer.tsx | 22 +++++++++++++------- src/stores/ToastStore.ts | 4 +++- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/components/structures/ToastContainer.tsx b/src/components/structures/ToastContainer.tsx index b7b0b7c652..75cf4a51fc 100644 --- a/src/components/structures/ToastContainer.tsx +++ b/src/components/structures/ToastContainer.tsx @@ -58,7 +58,7 @@ export default class ToastContainer extends React.Component<{}, IState> { let containerClasses; if (totalCount !== 0) { const topToast = this.state.toasts[0]; - const { title, icon, key, component, className, props } = topToast; + const { title, icon, key, component, className, props, supplyWholeBody } = topToast; const toastClasses = classNames("mx_Toast_toast", { "mx_Toast_hasIcon": icon, [`mx_Toast_icon_${icon}`]: icon, @@ -73,16 +73,22 @@ export default class ToastContainer extends React.Component<{}, IState> { key, toastKey: key, }); - toast = (
-
-

{ title }

- { countIndicator } -
-
{ React.createElement(component, toastProps) }
-
); + + const content = React.createElement(component, toastProps); + + toast = supplyWholeBody + ? content + :
+
+

{ title }

+ { countIndicator } +
+
{ content }
+
; containerClasses = classNames("mx_ToastContainer", { "mx_ToastContainer_stacked": isStacked, + [className]: supplyWholeBody, }); } return toast diff --git a/src/stores/ToastStore.ts b/src/stores/ToastStore.ts index 850c3cb026..e831be7203 100644 --- a/src/stores/ToastStore.ts +++ b/src/stores/ToastStore.ts @@ -22,11 +22,13 @@ export interface IToast { key: string; // higher priority number will be shown on top of lower priority priority: number; - title: string; + title?: string; icon?: string; component: C; className?: string; props?: Omit, "toastKey">; // toastKey is injected by ToastContainer + supplyWholeBody?: boolean; + content?: JSX.Element; } /** From 410928745f41e86451f0f27c944577a82a800170 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 24 Jul 2021 13:05:14 +0200 Subject: [PATCH 037/176] IncomingCallBox -> IncomingCallToast 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/toasts/_IncomingCallToast.scss | 100 ++++++++++ res/css/views/voip/_CallContainer.scss | 80 -------- src/CallHandler.tsx | 17 ++ src/components/views/voip/IncomingCallBox.tsx | 176 ------------------ src/toasts/IncomingCallToast.tsx | 139 ++++++++++++++ 6 files changed, 257 insertions(+), 256 deletions(-) create mode 100644 res/css/views/toasts/_IncomingCallToast.scss delete mode 100644 src/components/views/voip/IncomingCallBox.tsx create mode 100644 src/toasts/IncomingCallToast.tsx diff --git a/res/css/_components.scss b/res/css/_components.scss index f9e3ab1160..b87b45093c 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -263,6 +263,7 @@ @import "./views/spaces/_SpacePublicShare.scss"; @import "./views/terms/_InlineTermsAgreement.scss"; @import "./views/toasts/_AnalyticsToast.scss"; +@import "./views/toasts/_IncomingCallToast.scss"; @import "./views/toasts/_NonUrgentEchoFailureToast.scss"; @import "./views/verification/_VerificationShowSas.scss"; @import "./views/voip/_CallContainer.scss"; diff --git a/res/css/views/toasts/_IncomingCallToast.scss b/res/css/views/toasts/_IncomingCallToast.scss new file mode 100644 index 0000000000..5ce99bd11e --- /dev/null +++ b/res/css/views/toasts/_IncomingCallToast.scss @@ -0,0 +1,100 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. +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_IncomingCallToast { + // mx_Toast overrides + padding: 8px !important; + display: unset !important; + top: 8px !important; + border-radius: 8px; + + min-width: 250px; + box-shadow: 0px 14px 24px rgba(0, 0, 0, 0.08); + background-color: $voipcall-plinth-color; // To match mx_Toast + + pointer-events: initial; // restore pointer events so the user can accept/decline + cursor: pointer; + + .mx_IncomingCallToast_CallerInfo { + display: flex; + direction: row; + + img, .mx_BaseAvatar_initial { + margin: 8px; + } + + > div { + display: flex; + flex-direction: column; + + justify-content: center; + } + + h1, p { + margin: 0px; + padding: 0px; + font-size: $font-14px; + line-height: $font-16px; + } + + h1 { + font-weight: bold; + } + } + + .mx_IncomingCallToast_buttons { + padding: 8px; + display: flex; + flex-direction: row; + + > .mx_IncomingCallToast_spacer { + width: 8px; + } + + > * { + flex-shrink: 0; + flex-grow: 1; + margin-right: 0; + font-size: $font-15px; + line-height: $font-24px; + } + } + + .mx_IncomingCallToast_iconButton { + position: absolute; + right: 8px; + + &::before { + content: ''; + + height: 20px; + width: 20px; + background-color: $icon-button-color; + mask-repeat: no-repeat; + mask-size: contain; + mask-position: center; + } + } + + .mx_IncomingCallToast_silence::before { + mask-image: url('$(res)/img/voip/silence.svg'); + } + + .mx_IncomingCallToast_unSilence::before { + mask-image: url('$(res)/img/voip/un-silence.svg'); + } +} diff --git a/res/css/views/voip/_CallContainer.scss b/res/css/views/voip/_CallContainer.scss index 0c09070334..181a5ee0a3 100644 --- a/res/css/views/voip/_CallContainer.scss +++ b/res/css/views/voip/_CallContainer.scss @@ -43,84 +43,4 @@ limitations under the License. .mx_AppTile_persistedWrapper div { min-width: 350px; } - - .mx_IncomingCallBox { - min-width: 250px; - background-color: $voipcall-plinth-color; - padding: 8px; - box-shadow: 0px 14px 24px rgba(0, 0, 0, 0.08); - border-radius: 8px; - - pointer-events: initial; // restore pointer events so the user can accept/decline - cursor: pointer; - - .mx_IncomingCallBox_CallerInfo { - display: flex; - direction: row; - - img, .mx_BaseAvatar_initial { - margin: 8px; - } - - > div { - display: flex; - flex-direction: column; - - justify-content: center; - } - - h1, p { - margin: 0px; - padding: 0px; - font-size: $font-14px; - line-height: $font-16px; - } - - h1 { - font-weight: bold; - } - } - - .mx_IncomingCallBox_buttons { - padding: 8px; - display: flex; - flex-direction: row; - - > .mx_IncomingCallBox_spacer { - width: 8px; - } - - > * { - flex-shrink: 0; - flex-grow: 1; - margin-right: 0; - font-size: $font-15px; - line-height: $font-24px; - } - } - - .mx_IncomingCallBox_iconButton { - position: absolute; - right: 8px; - - &::before { - content: ''; - - height: 20px; - width: 20px; - background-color: $icon-button-color; - mask-repeat: no-repeat; - mask-size: contain; - mask-position: center; - } - } - - .mx_IncomingCallBox_silence::before { - mask-image: url('$(res)/img/voip/silence.svg'); - } - - .mx_IncomingCallBox_unSilence::before { - mask-image: url('$(res)/img/voip/un-silence.svg'); - } - } } diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index e7ba1aa9fb..e9831b5315 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -88,6 +88,9 @@ import { randomUppercaseString, randomLowercaseString } from "matrix-js-sdk/src/ import EventEmitter from 'events'; import SdkConfig from './SdkConfig'; import { ensureDMExists, findDMForUser } from './createRoom'; +import { getIncomingCallToastKey } from './toasts/IncomingCallToast'; +import ToastStore from './stores/ToastStore'; +import IncomingCallToast from "./toasts/IncomingCallToast"; export const PROTOCOL_PSTN = 'm.protocol.pstn'; export const PROTOCOL_PSTN_PREFIXED = 'im.vector.protocol.pstn'; @@ -641,6 +644,20 @@ export default class CallHandler extends EventEmitter { `Call state in ${mappedRoomId} changed to ${status}`, ); + const toastKey = getIncomingCallToastKey(call.callId); + if (status === CallState.Ringing) { + ToastStore.sharedInstance().addOrReplaceToast({ + key: toastKey, + supplyWholeBody: true, + priority: 100, + component: IncomingCallToast, + className: "mx_IncomingCallToast", + props: { call }, + }); + } else { + ToastStore.sharedInstance().dismissToast(toastKey); + } + dis.dispatch({ action: 'call_state', room_id: mappedRoomId, diff --git a/src/components/views/voip/IncomingCallBox.tsx b/src/components/views/voip/IncomingCallBox.tsx deleted file mode 100644 index 95e97f1080..0000000000 --- a/src/components/views/voip/IncomingCallBox.tsx +++ /dev/null @@ -1,176 +0,0 @@ -/* -Copyright 2015, 2016 OpenMarket Ltd -Copyright 2018 New Vector Ltd -Copyright 2019, 2020 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. -*/ - -import React from 'react'; -import { MatrixClientPeg } from '../../../MatrixClientPeg'; -import dis from '../../../dispatcher/dispatcher'; -import { _t } from '../../../languageHandler'; -import { ActionPayload } from '../../../dispatcher/payloads'; -import CallHandler, { CallHandlerEvent } from '../../../CallHandler'; -import RoomAvatar from '../avatars/RoomAvatar'; -import AccessibleButton from '../elements/AccessibleButton'; -import { CallState } from 'matrix-js-sdk/src/webrtc/call'; -import { replaceableComponent } from "../../../utils/replaceableComponent"; -import AccessibleTooltipButton from '../elements/AccessibleTooltipButton'; -import classNames from 'classnames'; - -interface IProps { -} - -interface IState { - incomingCall: any; - silenced: boolean; -} - -@replaceableComponent("views.voip.IncomingCallBox") -export default class IncomingCallBox extends React.Component { - private dispatcherRef: string; - - constructor(props: IProps) { - super(props); - - this.dispatcherRef = dis.register(this.onAction); - this.state = { - incomingCall: null, - silenced: false, - }; - } - - componentDidMount = () => { - CallHandler.sharedInstance().addListener(CallHandlerEvent.SilencedCallsChanged, this.onSilencedCallsChanged); - }; - - public componentWillUnmount() { - dis.unregister(this.dispatcherRef); - CallHandler.sharedInstance().removeListener(CallHandlerEvent.SilencedCallsChanged, this.onSilencedCallsChanged); - } - - private onAction = (payload: ActionPayload) => { - switch (payload.action) { - case 'call_state': { - const call = CallHandler.sharedInstance().getCallForRoom(payload.room_id); - if (call && call.state === CallState.Ringing) { - this.setState({ - incomingCall: call, - silenced: false, // Reset silenced state for new call - }); - } else { - this.setState({ - incomingCall: null, - }); - } - } - } - }; - - private onSilencedCallsChanged = () => { - const callId = this.state.incomingCall?.callId; - if (!callId) return; - this.setState({ silenced: CallHandler.sharedInstance().isCallSilenced(callId) }); - }; - - private onAnswerClick: React.MouseEventHandler = (e) => { - e.stopPropagation(); - dis.dispatch({ - action: 'answer', - room_id: CallHandler.sharedInstance().roomIdForCall(this.state.incomingCall), - }); - }; - - private onRejectClick: React.MouseEventHandler = (e) => { - e.stopPropagation(); - dis.dispatch({ - action: 'reject', - room_id: CallHandler.sharedInstance().roomIdForCall(this.state.incomingCall), - }); - }; - - private onSilenceClick: React.MouseEventHandler = (e) => { - e.stopPropagation(); - const callId = this.state.incomingCall.callId; - this.state.silenced ? - CallHandler.sharedInstance().unSilenceCall(callId): - CallHandler.sharedInstance().silenceCall(callId); - }; - - public render() { - if (!this.state.incomingCall) { - return null; - } - - let room = null; - if (this.state.incomingCall) { - room = MatrixClientPeg.get().getRoom(CallHandler.sharedInstance().roomIdForCall(this.state.incomingCall)); - } - - const caller = room ? room.name : _t("Unknown caller"); - - let incomingCallText = null; - if (this.state.incomingCall) { - if (this.state.incomingCall.type === "voice") { - incomingCallText = _t("Incoming voice call"); - } else if (this.state.incomingCall.type === "video") { - incomingCallText = _t("Incoming video call"); - } else { - incomingCallText = _t("Incoming call"); - } - } - - const silenceClass = classNames({ - "mx_IncomingCallBox_iconButton": true, - "mx_IncomingCallBox_unSilence": this.state.silenced, - "mx_IncomingCallBox_silence": !this.state.silenced, - }); - - return
-
- -
-

{ caller }

-

{ incomingCallText }

-
- -
-
- - { _t("Decline") } - -
- - { _t("Accept") } - -
-
; - } -} diff --git a/src/toasts/IncomingCallToast.tsx b/src/toasts/IncomingCallToast.tsx new file mode 100644 index 0000000000..2a3e2bd805 --- /dev/null +++ b/src/toasts/IncomingCallToast.tsx @@ -0,0 +1,139 @@ +/* +Copyright 2015, 2016 OpenMarket Ltd +Copyright 2018 New Vector Ltd +Copyright 2019, 2020 The Matrix.org Foundation C.I.C. +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 { CallType, MatrixCall } from 'matrix-js-sdk/src/webrtc/call'; +import classNames from 'classnames'; +import { replaceableComponent } from '../utils/replaceableComponent'; +import CallHandler, { CallHandlerEvent } from '../CallHandler'; +import dis from '../dispatcher/dispatcher'; +import { MatrixClientPeg } from '../MatrixClientPeg'; +import { _t } from '../languageHandler'; +import RoomAvatar from '../components/views/avatars/RoomAvatar'; +import AccessibleTooltipButton from '../components/views/elements/AccessibleTooltipButton'; +import AccessibleButton from '../components/views/elements/AccessibleButton'; + +export const getIncomingCallToastKey = (callId: string) => `call_${callId}`; + +interface IProps { + call: MatrixCall; +} + +interface IState { + silenced: boolean; +} + +@replaceableComponent("views.voip.IncomingCallToast") +export default class IncomingCallToast extends React.Component { + constructor(props: IProps) { + super(props); + + this.state = { + silenced: false, + }; + } + + componentDidMount = () => { + CallHandler.sharedInstance().addListener(CallHandlerEvent.SilencedCallsChanged, this.onSilencedCallsChanged); + }; + + public componentWillUnmount() { + CallHandler.sharedInstance().removeListener(CallHandlerEvent.SilencedCallsChanged, this.onSilencedCallsChanged); + } + + private onSilencedCallsChanged = () => { + this.setState({ silenced: CallHandler.sharedInstance().isCallSilenced(this.props.call.callId) }); + }; + + private onAnswerClick= (e: React.MouseEvent) => { + e.stopPropagation(); + dis.dispatch({ + action: 'answer', + room_id: CallHandler.sharedInstance().roomIdForCall(this.props.call), + }); + }; + + private onRejectClick= (e: React.MouseEvent) => { + e.stopPropagation(); + dis.dispatch({ + action: 'reject', + room_id: CallHandler.sharedInstance().roomIdForCall(this.props.call), + }); + }; + + private onSilenceClick = (e: React.MouseEvent) => { + e.stopPropagation(); + const callId = this.props.call.callId; + this.state.silenced ? + CallHandler.sharedInstance().unSilenceCall(callId) : + CallHandler.sharedInstance().silenceCall(callId); + }; + + public render() { + const call = this.props.call; + let room = null; + room = MatrixClientPeg.get().getRoom(CallHandler.sharedInstance().roomIdForCall(call)); + + const caller = room ? room.name : _t("Unknown caller"); + + const incomingCallText = call.type === CallType.Voice ? _t("Incoming voice call") : _t("Incoming video call"); + + const silenceClass = classNames({ + "mx_IncomingCallToast_iconButton": true, + "mx_IncomingCallToast_unSilence": this.state.silenced, + "mx_IncomingCallToast_silence": !this.state.silenced, + }); + + return +
+ +
+

{ caller }

+

{ incomingCallText }

+
+ +
+
+ + { _t("Decline") } + +
+ + { _t("Accept") } + +
+ ; + } +} From af22588682a8b64da1e929bbccec8fe3c197aa26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 24 Jul 2021 13:39:39 +0200 Subject: [PATCH 038/176] Don't use a spacer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/toasts/_IncomingCallToast.scss | 5 +---- src/toasts/IncomingCallToast.tsx | 1 - 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/res/css/views/toasts/_IncomingCallToast.scss b/res/css/views/toasts/_IncomingCallToast.scss index 5ce99bd11e..f7edf6a7bd 100644 --- a/res/css/views/toasts/_IncomingCallToast.scss +++ b/res/css/views/toasts/_IncomingCallToast.scss @@ -60,10 +60,7 @@ limitations under the License. padding: 8px; display: flex; flex-direction: row; - - > .mx_IncomingCallToast_spacer { - width: 8px; - } + gap: 12px; > * { flex-shrink: 0; diff --git a/src/toasts/IncomingCallToast.tsx b/src/toasts/IncomingCallToast.tsx index 2a3e2bd805..83cd7aba80 100644 --- a/src/toasts/IncomingCallToast.tsx +++ b/src/toasts/IncomingCallToast.tsx @@ -125,7 +125,6 @@ export default class IncomingCallToast extends React.Component { > { _t("Decline") } -
Date: Sat, 24 Jul 2021 13:46:06 +0200 Subject: [PATCH 039/176] Correct button sizes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/toasts/_IncomingCallToast.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/res/css/views/toasts/_IncomingCallToast.scss b/res/css/views/toasts/_IncomingCallToast.scss index f7edf6a7bd..02b27a94ab 100644 --- a/res/css/views/toasts/_IncomingCallToast.scss +++ b/res/css/views/toasts/_IncomingCallToast.scss @@ -63,6 +63,8 @@ limitations under the License. gap: 12px; > * { + height: 24px; + padding: 0px 12px; flex-shrink: 0; flex-grow: 1; margin-right: 0; From 24e6fc96f6c84b1d1676ee609671abbb3663ddfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 24 Jul 2021 14:04:22 +0200 Subject: [PATCH 040/176] Reorganize content MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/toasts/_IncomingCallToast.scss | 22 +++---- src/toasts/IncomingCallToast.tsx | 61 ++++++++++---------- 2 files changed, 36 insertions(+), 47 deletions(-) diff --git a/res/css/views/toasts/_IncomingCallToast.scss b/res/css/views/toasts/_IncomingCallToast.scss index 02b27a94ab..d2947395dc 100644 --- a/res/css/views/toasts/_IncomingCallToast.scss +++ b/res/css/views/toasts/_IncomingCallToast.scss @@ -18,10 +18,12 @@ limitations under the License. .mx_IncomingCallToast { // mx_Toast overrides padding: 8px !important; - display: unset !important; + display: flex !important; top: 8px !important; border-radius: 8px; + flex-direction: row; + gap: 8px; min-width: 250px; box-shadow: 0px 14px 24px rgba(0, 0, 0, 0.08); background-color: $voipcall-plinth-color; // To match mx_Toast @@ -29,20 +31,9 @@ limitations under the License. pointer-events: initial; // restore pointer events so the user can accept/decline cursor: pointer; - .mx_IncomingCallToast_CallerInfo { + .mx_IncomingCallToast_content { display: flex; - direction: row; - - img, .mx_BaseAvatar_initial { - margin: 8px; - } - - > div { - display: flex; - flex-direction: column; - - justify-content: center; - } + flex-direction: column; h1, p { margin: 0px; @@ -57,7 +48,7 @@ limitations under the License. } .mx_IncomingCallToast_buttons { - padding: 8px; + margin-top: 8px; display: flex; flex-direction: row; gap: 12px; @@ -74,6 +65,7 @@ limitations under the License. } .mx_IncomingCallToast_iconButton { + display: flex; position: absolute; right: 8px; diff --git a/src/toasts/IncomingCallToast.tsx b/src/toasts/IncomingCallToast.tsx index 83cd7aba80..cff0c82782 100644 --- a/src/toasts/IncomingCallToast.tsx +++ b/src/toasts/IncomingCallToast.tsx @@ -87,12 +87,7 @@ export default class IncomingCallToast extends React.Component { public render() { const call = this.props.call; - let room = null; - room = MatrixClientPeg.get().getRoom(CallHandler.sharedInstance().roomIdForCall(call)); - - const caller = room ? room.name : _t("Unknown caller"); - - const incomingCallText = call.type === CallType.Voice ? _t("Incoming voice call") : _t("Incoming video call"); + const room = MatrixClientPeg.get().getRoom(CallHandler.sharedInstance().roomIdForCall(call)); const silenceClass = classNames({ "mx_IncomingCallToast_iconButton": true, @@ -101,37 +96,39 @@ export default class IncomingCallToast extends React.Component { }); return -
- -
-

{ caller }

-

{ incomingCallText }

-
+ +
+

+ { room ? room.name : _t("Unknown caller") } +

+

+ { call.type === CallType.Voice ? _t("Incoming voice call") : _t("Incoming video call") } +

-
-
- - { _t("Decline") } - - - { _t("Accept") } - +
+ + { _t("Decline") } + + + { _t("Accept") } + +
; } From dd800549d734231b68aef31e484f56cd25410146 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 24 Jul 2021 14:06:52 +0200 Subject: [PATCH 041/176] Fix the silence icon color MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/toasts/_IncomingCallToast.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/views/toasts/_IncomingCallToast.scss b/res/css/views/toasts/_IncomingCallToast.scss index d2947395dc..0a36084f7a 100644 --- a/res/css/views/toasts/_IncomingCallToast.scss +++ b/res/css/views/toasts/_IncomingCallToast.scss @@ -74,7 +74,7 @@ limitations under the License. height: 20px; width: 20px; - background-color: $icon-button-color; + background-color: $secondary-fg-color; mask-repeat: no-repeat; mask-size: contain; mask-position: center; From a6f10a4aaa25b91172c6133b484da0df7be7dbba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 24 Jul 2021 18:59:54 +0200 Subject: [PATCH 042/176] Move around some CSS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/toasts/_IncomingCallToast.scss | 66 ++++++++++---------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/res/css/views/toasts/_IncomingCallToast.scss b/res/css/views/toasts/_IncomingCallToast.scss index 0a36084f7a..a2d775c969 100644 --- a/res/css/views/toasts/_IncomingCallToast.scss +++ b/res/css/views/toasts/_IncomingCallToast.scss @@ -45,47 +45,47 @@ limitations under the License. h1 { font-weight: bold; } - } - .mx_IncomingCallToast_buttons { - margin-top: 8px; - display: flex; - flex-direction: row; - gap: 12px; + .mx_IncomingCallToast_buttons { + margin-top: 8px; + display: flex; + flex-direction: row; + gap: 12px; - > * { - height: 24px; - padding: 0px 12px; - flex-shrink: 0; - flex-grow: 1; - margin-right: 0; - font-size: $font-15px; - line-height: $font-24px; + > * { + height: 24px; + padding: 0px 12px; + flex-shrink: 0; + flex-grow: 1; + margin-right: 0; + font-size: $font-15px; + line-height: $font-24px; + } } - } - .mx_IncomingCallToast_iconButton { - display: flex; - position: absolute; - right: 8px; + .mx_IncomingCallToast_iconButton { + display: flex; + position: absolute; + right: 8px; - &::before { - content: ''; + &::before { + content: ''; - height: 20px; - width: 20px; - background-color: $secondary-fg-color; - mask-repeat: no-repeat; - mask-size: contain; - mask-position: center; + height: 20px; + width: 20px; + background-color: $secondary-fg-color; + mask-repeat: no-repeat; + mask-size: contain; + mask-position: center; + } } - } - .mx_IncomingCallToast_silence::before { - mask-image: url('$(res)/img/voip/silence.svg'); - } + .mx_IncomingCallToast_silence::before { + mask-image: url('$(res)/img/voip/silence.svg'); + } - .mx_IncomingCallToast_unSilence::before { - mask-image: url('$(res)/img/voip/un-silence.svg'); + .mx_IncomingCallToast_unSilence::before { + mask-image: url('$(res)/img/voip/un-silence.svg'); + } } } From 25d62983de958b16300e88bc6524fb247d053eac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 24 Jul 2021 19:12:12 +0200 Subject: [PATCH 043/176] Add button icons MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/toasts/_IncomingCallToast.scss | 36 +++++++++++++++++++- src/toasts/IncomingCallToast.tsx | 20 ++++++----- 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/res/css/views/toasts/_IncomingCallToast.scss b/res/css/views/toasts/_IncomingCallToast.scss index a2d775c969..665109bc0f 100644 --- a/res/css/views/toasts/_IncomingCallToast.scss +++ b/res/css/views/toasts/_IncomingCallToast.scss @@ -46,13 +46,25 @@ limitations under the License. font-weight: bold; } + &.mx_IncomingCallToast_content_voice { + .mx_IncomingCallToast_buttons .mx_IncomingCallToast_button_accept span::before { + mask-image: url('$(res)/img/element-icons/call/voice-call.svg'); + } + } + + &.mx_IncomingCallToast_content_video { + .mx_IncomingCallToast_buttons .mx_IncomingCallToast_button_accept span::before { + mask-image: url('$(res)/img/element-icons/call/video-call.svg'); + } + } + .mx_IncomingCallToast_buttons { margin-top: 8px; display: flex; flex-direction: row; gap: 12px; - > * { + .mx_IncomingCallToast_button { height: 24px; padding: 0px 12px; flex-shrink: 0; @@ -60,6 +72,28 @@ limitations under the License. margin-right: 0; font-size: $font-15px; line-height: $font-24px; + + span { + padding: 8px 0; + display: flex; + align-items: center; + + &::before { + content: ''; + display: inline-block; + background-color: $button-fg-color; + mask-position: center; + mask-repeat: no-repeat; + mask-size: 16px; + width: 16px; + height: 16px; + margin-right: 8px; + } + } + + &.mx_IncomingCallToast_button_decline span::before { + mask-image: url('$(res)/img/element-icons/call/hangup.svg'); + } } } diff --git a/src/toasts/IncomingCallToast.tsx b/src/toasts/IncomingCallToast.tsx index cff0c82782..9e5528d6a7 100644 --- a/src/toasts/IncomingCallToast.tsx +++ b/src/toasts/IncomingCallToast.tsx @@ -88,9 +88,13 @@ export default class IncomingCallToast extends React.Component { public render() { const call = this.props.call; const room = MatrixClientPeg.get().getRoom(CallHandler.sharedInstance().roomIdForCall(call)); + const isVoice = call.type === CallType.Voice; - const silenceClass = classNames({ - "mx_IncomingCallToast_iconButton": true, + const contentClass = classNames("mx_IncomingCallToast_content", { + "mx_IncomingCallToast_content_voice": isVoice, + "mx_IncomingCallToast_content_video": !isVoice, + }); + const silenceClass = classNames("mx_IncomingCallToast_iconButton", { "mx_IncomingCallToast_unSilence": this.state.silenced, "mx_IncomingCallToast_silence": !this.state.silenced, }); @@ -101,12 +105,12 @@ export default class IncomingCallToast extends React.Component { height={32} width={32} /> -
+

{ room ? room.name : _t("Unknown caller") }

- { call.type === CallType.Voice ? _t("Incoming voice call") : _t("Incoming video call") } + { isVoice ? _t("Incoming voice call") : _t("Incoming video call") }

{ />
- { _t("Decline") } + { _t("Decline") } - { _t("Accept") } + { _t("Accept") }
From 064544369a033fe3a84734466cca4710db9ac16a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 24 Jul 2021 19:30:37 +0200 Subject: [PATCH 044/176] Add call type icon MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/toasts/_IncomingCallToast.scss | 27 ++++++++++++++++++-- src/toasts/IncomingCallToast.tsx | 7 ++--- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/res/css/views/toasts/_IncomingCallToast.scss b/res/css/views/toasts/_IncomingCallToast.scss index 665109bc0f..bcfb61ed21 100644 --- a/res/css/views/toasts/_IncomingCallToast.scss +++ b/res/css/views/toasts/_IncomingCallToast.scss @@ -35,7 +35,7 @@ limitations under the License. display: flex; flex-direction: column; - h1, p { + h1, .mx_CallEvent_type { margin: 0px; padding: 0px; font-size: $font-14px; @@ -46,13 +46,36 @@ limitations under the License. font-weight: bold; } + .mx_CallEvent_type { + display: flex; + flex-direction: row; + + .mx_CallEvent_type_icon { + height: 16px; + width: 16px; + margin-right: 6px; + + &::before { + content: ''; + position: absolute; + height: inherit; + width: inherit; + background-color: $tertiary-fg-color; + mask-repeat: no-repeat; + mask-size: contain; + } + } + } + &.mx_IncomingCallToast_content_voice { + .mx_CallEvent_type .mx_CallEvent_type_icon::before, .mx_IncomingCallToast_buttons .mx_IncomingCallToast_button_accept span::before { mask-image: url('$(res)/img/element-icons/call/voice-call.svg'); } } &.mx_IncomingCallToast_content_video { + .mx_CallEvent_type .mx_CallEvent_type_icon::before, .mx_IncomingCallToast_buttons .mx_IncomingCallToast_button_accept span::before { mask-image: url('$(res)/img/element-icons/call/video-call.svg'); } @@ -107,7 +130,7 @@ limitations under the License. height: 20px; width: 20px; - background-color: $secondary-fg-color; + background-color: $tertiary-fg-color; mask-repeat: no-repeat; mask-size: contain; mask-position: center; diff --git a/src/toasts/IncomingCallToast.tsx b/src/toasts/IncomingCallToast.tsx index 9e5528d6a7..f8a7f0591f 100644 --- a/src/toasts/IncomingCallToast.tsx +++ b/src/toasts/IncomingCallToast.tsx @@ -109,9 +109,10 @@ export default class IncomingCallToast extends React.Component {

{ room ? room.name : _t("Unknown caller") }

-

- { isVoice ? _t("Incoming voice call") : _t("Incoming video call") } -

+
+
+ { isVoice ? _t("Voice call") : _t("Video call") } +
Date: Sat, 24 Jul 2021 20:39:44 +0200 Subject: [PATCH 045/176] Move silence button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/toasts/_IncomingCallToast.scss | 43 ++++++++++---------- src/toasts/IncomingCallToast.tsx | 10 ++--- 2 files changed, 26 insertions(+), 27 deletions(-) diff --git a/res/css/views/toasts/_IncomingCallToast.scss b/res/css/views/toasts/_IncomingCallToast.scss index bcfb61ed21..04f92bb095 100644 --- a/res/css/views/toasts/_IncomingCallToast.scss +++ b/res/css/views/toasts/_IncomingCallToast.scss @@ -29,7 +29,6 @@ limitations under the License. background-color: $voipcall-plinth-color; // To match mx_Toast pointer-events: initial; // restore pointer events so the user can accept/decline - cursor: pointer; .mx_IncomingCallToast_content { display: flex; @@ -119,30 +118,30 @@ limitations under the License. } } } + } - .mx_IncomingCallToast_iconButton { - display: flex; - position: absolute; - right: 8px; + .mx_IncomingCallToast_iconButton { + display: flex; + height: 20px; + width: 20px; - &::before { - content: ''; + &::before { + content: ''; - height: 20px; - width: 20px; - background-color: $tertiary-fg-color; - mask-repeat: no-repeat; - mask-size: contain; - mask-position: center; - } - } - - .mx_IncomingCallToast_silence::before { - mask-image: url('$(res)/img/voip/silence.svg'); - } - - .mx_IncomingCallToast_unSilence::before { - mask-image: url('$(res)/img/voip/un-silence.svg'); + height: inherit; + width: inherit; + background-color: $tertiary-fg-color; + mask-repeat: no-repeat; + mask-size: contain; + mask-position: center; } } + + .mx_IncomingCallToast_silence::before { + mask-image: url('$(res)/img/voip/silence.svg'); + } + + .mx_IncomingCallToast_unSilence::before { + mask-image: url('$(res)/img/voip/un-silence.svg'); + } } diff --git a/src/toasts/IncomingCallToast.tsx b/src/toasts/IncomingCallToast.tsx index f8a7f0591f..7a7aacac12 100644 --- a/src/toasts/IncomingCallToast.tsx +++ b/src/toasts/IncomingCallToast.tsx @@ -113,11 +113,6 @@ export default class IncomingCallToast extends React.Component {
{ isVoice ? _t("Voice call") : _t("Video call") }
-
{
+ ; } } From 1f9cd79bcfe4d5b3aeb024e72d316fa93297dbac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 24 Jul 2021 20:59:15 +0200 Subject: [PATCH 046/176] Fix some spacing and other tiny things MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/toasts/_IncomingCallToast.scss | 37 +++++++++++++------- src/toasts/IncomingCallToast.tsx | 4 +-- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/res/css/views/toasts/_IncomingCallToast.scss b/res/css/views/toasts/_IncomingCallToast.scss index 04f92bb095..6bdbcdb8b0 100644 --- a/res/css/views/toasts/_IncomingCallToast.scss +++ b/res/css/views/toasts/_IncomingCallToast.scss @@ -23,7 +23,6 @@ limitations under the License. border-radius: 8px; flex-direction: row; - gap: 8px; min-width: 250px; box-shadow: 0px 14px 24px rgba(0, 0, 0, 0.08); background-color: $voipcall-plinth-color; // To match mx_Toast @@ -33,21 +32,27 @@ limitations under the License. .mx_IncomingCallToast_content { display: flex; flex-direction: column; + margin-left: 8px; - h1, .mx_CallEvent_type { - margin: 0px; - padding: 0px; - font-size: $font-14px; - line-height: $font-16px; - } - - h1 { + .mx_CallEvent_caller { font-weight: bold; + font-size: $font-15px; + line-height: $font-18px; + + margin-top: 2px; } .mx_CallEvent_type { + font-size: $font-12px; + line-height: $font-15px; + color: $tertiary-fg-color; + + margin-top: 4px; + margin-bottom: 6px; + display: flex; flex-direction: row; + align-items: center; .mx_CallEvent_type_icon { height: 16px; @@ -88,7 +93,7 @@ limitations under the License. .mx_IncomingCallToast_button { height: 24px; - padding: 0px 12px; + padding: 0px 8px; flex-shrink: 0; flex-grow: 1; margin-right: 0; @@ -106,15 +111,21 @@ limitations under the License. background-color: $button-fg-color; mask-position: center; mask-repeat: no-repeat; - mask-size: 16px; - width: 16px; - height: 16px; margin-right: 8px; } } + &.mx_IncomingCallToast_button_accept span::before { + mask-size: 13px; + width: 13px; + height: 13px; + } + &.mx_IncomingCallToast_button_decline span::before { mask-image: url('$(res)/img/element-icons/call/hangup.svg'); + mask-size: 16px; + width: 16px; + height: 16px; } } } diff --git a/src/toasts/IncomingCallToast.tsx b/src/toasts/IncomingCallToast.tsx index 7a7aacac12..8d14fbd883 100644 --- a/src/toasts/IncomingCallToast.tsx +++ b/src/toasts/IncomingCallToast.tsx @@ -106,9 +106,9 @@ export default class IncomingCallToast extends React.Component { width={32} />
-

+ { room ? room.name : _t("Unknown caller") } -

+
{ isVoice ? _t("Voice call") : _t("Video call") } From 85095df4b9663fd1d078b57efe01a6fa47f4d2b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 24 Jul 2021 21:03:26 +0200 Subject: [PATCH 047/176] 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 | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index b36910b41b..46600c48f2 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -734,6 +734,13 @@ "Notifications": "Notifications", "Enable desktop notifications": "Enable desktop notifications", "Enable": "Enable", + "Unknown caller": "Unknown caller", + "Voice call": "Voice call", + "Video call": "Video call", + "Decline": "Decline", + "Accept": "Accept", + "Sound on": "Sound on", + "Silence call": "Silence call", "Use app for a better experience": "Use app for a better experience", "Element Web is experimental on mobile. For a better experience and the latest features, use our free native app.": "Element Web is experimental on mobile. For a better experience and the latest features, use our free native app.", "Use app": "Use app", @@ -911,14 +918,6 @@ "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", - "Incoming call": "Incoming call", - "Sound on": "Sound on", - "Silence call": "Silence call", - "Decline": "Decline", - "Accept": "Accept", "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.", @@ -1580,8 +1579,6 @@ "Hide Widgets": "Hide Widgets", "Show Widgets": "Show Widgets", "Search": "Search", - "Voice call": "Voice call", - "Video call": "Video call", "Invites": "Invites", "Favourites": "Favourites", "People": "People", From 379101e3ff3e0b77bcf5c215292e649d9bd4c084 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 24 Jul 2021 21:09:00 +0200 Subject: [PATCH 048/176] Remove an unused member MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/stores/ToastStore.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/stores/ToastStore.ts b/src/stores/ToastStore.ts index e831be7203..093ea9fb6b 100644 --- a/src/stores/ToastStore.ts +++ b/src/stores/ToastStore.ts @@ -28,7 +28,6 @@ export interface IToast { className?: string; props?: Omit, "toastKey">; // toastKey is injected by ToastContainer supplyWholeBody?: boolean; - content?: JSX.Element; } /** From f2204aa1ffdb16de1592778409ab909fad82c52c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 24 Jul 2021 21:12:29 +0200 Subject: [PATCH 049/176] Remove nonsense comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/toasts/_IncomingCallToast.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/res/css/views/toasts/_IncomingCallToast.scss b/res/css/views/toasts/_IncomingCallToast.scss index 6bdbcdb8b0..d49014efdb 100644 --- a/res/css/views/toasts/_IncomingCallToast.scss +++ b/res/css/views/toasts/_IncomingCallToast.scss @@ -20,12 +20,12 @@ limitations under the License. padding: 8px !important; display: flex !important; top: 8px !important; - border-radius: 8px; + border-radius: 8px; flex-direction: row; min-width: 250px; box-shadow: 0px 14px 24px rgba(0, 0, 0, 0.08); - background-color: $voipcall-plinth-color; // To match mx_Toast + background-color: $voipcall-plinth-color; pointer-events: initial; // restore pointer events so the user can accept/decline From 3287d51b179096a29b3f45f62458bad25c86d0c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 25 Jul 2021 08:06:11 +0200 Subject: [PATCH 050/176] Add some return types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/toasts/IncomingCallToast.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/toasts/IncomingCallToast.tsx b/src/toasts/IncomingCallToast.tsx index 8d14fbd883..c842581f9a 100644 --- a/src/toasts/IncomingCallToast.tsx +++ b/src/toasts/IncomingCallToast.tsx @@ -53,15 +53,15 @@ export default class IncomingCallToast extends React.Component { CallHandler.sharedInstance().addListener(CallHandlerEvent.SilencedCallsChanged, this.onSilencedCallsChanged); }; - public componentWillUnmount() { + public componentWillUnmount(): void { CallHandler.sharedInstance().removeListener(CallHandlerEvent.SilencedCallsChanged, this.onSilencedCallsChanged); } - private onSilencedCallsChanged = () => { + private onSilencedCallsChanged = (): void => { this.setState({ silenced: CallHandler.sharedInstance().isCallSilenced(this.props.call.callId) }); }; - private onAnswerClick= (e: React.MouseEvent) => { + private onAnswerClick= (e: React.MouseEvent): void => { e.stopPropagation(); dis.dispatch({ action: 'answer', @@ -69,7 +69,7 @@ export default class IncomingCallToast extends React.Component { }); }; - private onRejectClick= (e: React.MouseEvent) => { + private onRejectClick= (e: React.MouseEvent): void => { e.stopPropagation(); dis.dispatch({ action: 'reject', @@ -77,7 +77,7 @@ export default class IncomingCallToast extends React.Component { }); }; - private onSilenceClick = (e: React.MouseEvent) => { + private onSilenceClick = (e: React.MouseEvent): void => { e.stopPropagation(); const callId = this.props.call.callId; this.state.silenced ? From 1d629f2557a7d618355436f99c31cc6b9ce856f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 26 Jul 2021 10:34:59 +0200 Subject: [PATCH 051/176] More TS Co-authored-by: Germain --- src/toasts/IncomingCallToast.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/toasts/IncomingCallToast.tsx b/src/toasts/IncomingCallToast.tsx index c842581f9a..a853e1652a 100644 --- a/src/toasts/IncomingCallToast.tsx +++ b/src/toasts/IncomingCallToast.tsx @@ -49,7 +49,7 @@ export default class IncomingCallToast extends React.Component { }; } - componentDidMount = () => { + public componentDidMount = (): void => { CallHandler.sharedInstance().addListener(CallHandlerEvent.SilencedCallsChanged, this.onSilencedCallsChanged); }; From 40947a2a681f03b62013c9c002b1e4ddea7916db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 26 Jul 2021 12:21:58 +0200 Subject: [PATCH 052/176] Simplifie toast handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/toasts/_IncomingCallToast.scss | 11 +----- src/CallHandler.tsx | 3 +- src/components/structures/ToastContainer.tsx | 37 +++++++++++--------- src/stores/ToastStore.ts | 2 +- 4 files changed, 24 insertions(+), 29 deletions(-) diff --git a/res/css/views/toasts/_IncomingCallToast.scss b/res/css/views/toasts/_IncomingCallToast.scss index d49014efdb..975628f948 100644 --- a/res/css/views/toasts/_IncomingCallToast.scss +++ b/res/css/views/toasts/_IncomingCallToast.scss @@ -16,17 +16,8 @@ limitations under the License. */ .mx_IncomingCallToast { - // mx_Toast overrides - padding: 8px !important; - display: flex !important; - top: 8px !important; - - border-radius: 8px; + display: flex; flex-direction: row; - min-width: 250px; - box-shadow: 0px 14px 24px rgba(0, 0, 0, 0.08); - background-color: $voipcall-plinth-color; - pointer-events: initial; // restore pointer events so the user can accept/decline .mx_IncomingCallToast_content { diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index e9831b5315..5018c44488 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -648,10 +648,9 @@ export default class CallHandler extends EventEmitter { if (status === CallState.Ringing) { ToastStore.sharedInstance().addOrReplaceToast({ key: toastKey, - supplyWholeBody: true, priority: 100, component: IncomingCallToast, - className: "mx_IncomingCallToast", + bodyClassName: "mx_IncomingCallToast", props: { call }, }); } else { diff --git a/src/components/structures/ToastContainer.tsx b/src/components/structures/ToastContainer.tsx index 75cf4a51fc..0b0e871975 100644 --- a/src/components/structures/ToastContainer.tsx +++ b/src/components/structures/ToastContainer.tsx @@ -58,37 +58,42 @@ export default class ToastContainer extends React.Component<{}, IState> { let containerClasses; if (totalCount !== 0) { const topToast = this.state.toasts[0]; - const { title, icon, key, component, className, props, supplyWholeBody } = topToast; - const toastClasses = classNames("mx_Toast_toast", { + const { title, icon, key, component, className, bodyClassName, props } = topToast; + const bodyClasses = classNames("mx_Toast_body", bodyClassName); + const toastClasses = classNames("mx_Toast_toast", className, { "mx_Toast_hasIcon": icon, [`mx_Toast_icon_${icon}`]: icon, - }, className); - - let countIndicator; - if (isStacked || this.state.countSeen > 0) { - countIndicator = ` (${this.state.countSeen + 1}/${this.state.countSeen + totalCount})`; - } - + }); const toastProps = Object.assign({}, props, { key, toastKey: key, }); - const content = React.createElement(component, toastProps); - toast = supplyWholeBody - ? content - :
+ let countIndicator; + if (title && isStacked || this.state.countSeen > 0) { + countIndicator = ` (${this.state.countSeen + 1}/${this.state.countSeen + totalCount})`; + } + + let titleElement; + if (title) { + titleElement = (

{ title }

{ countIndicator }
-
{ content }
-
; + ); + } + + toast = ( +
+ { titleElement } +
{ content }
+
+ ); containerClasses = classNames("mx_ToastContainer", { "mx_ToastContainer_stacked": isStacked, - [className]: supplyWholeBody, }); } return toast diff --git a/src/stores/ToastStore.ts b/src/stores/ToastStore.ts index 093ea9fb6b..5e51de3e26 100644 --- a/src/stores/ToastStore.ts +++ b/src/stores/ToastStore.ts @@ -26,8 +26,8 @@ export interface IToast { icon?: string; component: C; className?: string; + bodyClassName?: string; props?: Omit, "toastKey">; // toastKey is injected by ToastContainer - supplyWholeBody?: boolean; } /** From 277fdf1711186390ee3e6c119da1574d830888aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 26 Jul 2021 12:42:18 +0200 Subject: [PATCH 053/176] voipcall-plinth-color -> quinary-content-color MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/structures/_ToastContainer.scss | 4 ++-- res/css/views/voip/_CallView.scss | 2 +- res/themes/dark/css/_dark.scss | 2 +- res/themes/legacy-dark/css/_legacy-dark.scss | 2 +- res/themes/legacy-light/css/_legacy-light.scss | 8 ++++---- res/themes/light/css/_light.scss | 8 ++++---- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/res/css/structures/_ToastContainer.scss b/res/css/structures/_ToastContainer.scss index d248568740..b6034be42d 100644 --- a/res/css/structures/_ToastContainer.scss +++ b/res/css/structures/_ToastContainer.scss @@ -28,7 +28,7 @@ limitations under the License. margin: 0 4px; grid-row: 2 / 4; grid-column: 1; - background-color: $dark-panel-bg-color; + background-color: $quinary-content-color; box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.5); border-radius: 8px; } @@ -37,7 +37,7 @@ limitations under the License. grid-row: 1 / 3; grid-column: 1; color: $primary-fg-color; - background-color: $dark-panel-bg-color; + background-color: $quinary-content-color; box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.5); border-radius: 8px; overflow: hidden; diff --git a/res/css/views/voip/_CallView.scss b/res/css/views/voip/_CallView.scss index 205d431752..2be4a4b802 100644 --- a/res/css/views/voip/_CallView.scss +++ b/res/css/views/voip/_CallView.scss @@ -39,7 +39,7 @@ limitations under the License. .mx_CallView_pip { width: 320px; padding-bottom: 8px; - background-color: $voipcall-plinth-color; + background-color: $quinary-content-color; box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.20); border-radius: 8px; diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index 655492661c..0907ccdd9a 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -113,7 +113,7 @@ $header-divider-color: $header-panel-text-primary-color; $composer-e2e-icon-color: $header-panel-text-primary-color; // this probably shouldn't have it's own colour -$voipcall-plinth-color: #394049; +$quinary-content-color: #394049; // ******************** diff --git a/res/themes/legacy-dark/css/_legacy-dark.scss b/res/themes/legacy-dark/css/_legacy-dark.scss index 0c0197cfb0..323fe0651e 100644 --- a/res/themes/legacy-dark/css/_legacy-dark.scss +++ b/res/themes/legacy-dark/css/_legacy-dark.scss @@ -112,7 +112,7 @@ $header-divider-color: $header-panel-text-primary-color; $composer-e2e-icon-color: $header-panel-text-primary-color; // this probably shouldn't have it's own colour -$voipcall-plinth-color: #394049; +$quinary-content-color: #394049; // ******************** diff --git a/res/themes/legacy-light/css/_legacy-light.scss b/res/themes/legacy-light/css/_legacy-light.scss index b7d45452ff..a4e7af2bb9 100644 --- a/res/themes/legacy-light/css/_legacy-light.scss +++ b/res/themes/legacy-light/css/_legacy-light.scss @@ -8,9 +8,9 @@ /* Noto Color Emoji contains digits, in fixed-width, therefore causing digits in flowed text to stand out. TODO: Consider putting all emoji fonts to the end rather than the front. */ -$font-family: Nunito, Twemoji, 'Apple Color Emoji', 'Segoe UI Emoji', Arial, Helvetica, Sans-Serif, 'Noto Color Emoji'; +$font-family: nunito, twemoji, 'Apple Color Emoji', 'Segoe UI Emoji', arial, helvetica, sans-serif, 'Noto Color Emoji'; -$monospace-font-family: Inconsolata, Twemoji, 'Apple Color Emoji', 'Segoe UI Emoji', Courier, monospace, 'Noto Color Emoji'; +$monospace-font-family: inconsolata, twemoji, 'Apple Color Emoji', 'Segoe UI Emoji', courier, monospace, 'Noto Color Emoji'; // unified palette // try to use these colors when possible @@ -179,7 +179,7 @@ $composer-e2e-icon-color: #91a1c0; $header-divider-color: #91a1c0; // this probably shouldn't have it's own colour -$voipcall-plinth-color: #F4F6FA; +$quinary-content-color: #F4F6FA; // ******************** @@ -390,7 +390,7 @@ $eventbubble-reply-color: #C1C6CD; @define-mixin mx_DialogButton_secondary { // flip colours for the secondary ones font-weight: 600; - border: 1px solid $accent-color ! important; + border: 1px solid $accent-color !important; color: $accent-color; background-color: $button-secondary-bg-color; } diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index 32722515d8..2f81cb3407 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -8,9 +8,9 @@ /* Noto Color Emoji contains digits, in fixed-width, therefore causing digits in flowed text to stand out. TODO: Consider putting all emoji fonts to the end rather than the front. */ -$font-family: Inter, Twemoji, 'Apple Color Emoji', 'Segoe UI Emoji', Arial, Helvetica, Sans-Serif, 'Noto Color Emoji'; +$font-family: inter, twemoji, 'Apple Color Emoji', 'Segoe UI Emoji', arial, helvetica, sans-serif, 'Noto Color Emoji'; -$monospace-font-family: Inconsolata, Twemoji, 'Apple Color Emoji', 'Segoe UI Emoji', Courier, monospace, 'Noto Color Emoji'; +$monospace-font-family: inconsolata, twemoji, 'Apple Color Emoji', 'Segoe UI Emoji', courier, monospace, 'Noto Color Emoji'; // unified palette // try to use these colors when possible @@ -168,7 +168,7 @@ $composer-e2e-icon-color: #91A1C0; $header-divider-color: #91A1C0; // this probably shouldn't have it's own colour -$voipcall-plinth-color: #F4F6FA; +$quinary-content-color: #F4F6FA; // ******************** @@ -392,7 +392,7 @@ $eventbubble-reply-color: #C1C6CD; @define-mixin mx_DialogButton_secondary { // flip colours for the secondary ones font-weight: 600; - border: 1px solid $accent-color ! important; + border: 1px solid $accent-color !important; color: $accent-color; background-color: $button-secondary-bg-color; } From 7ed5dee74bd57deb9de7e20a3b7813f1f94a07d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 26 Jul 2021 13:17:06 +0200 Subject: [PATCH 054/176] Make colors a bit clearer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/structures/_ToastContainer.scss | 4 ++-- res/css/views/voip/_CallView.scss | 2 +- res/themes/dark/css/_dark.scss | 2 +- res/themes/legacy-dark/css/_legacy-dark.scss | 2 +- res/themes/legacy-light/css/_legacy-light.scss | 4 ++-- res/themes/light/css/_light.scss | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/res/css/structures/_ToastContainer.scss b/res/css/structures/_ToastContainer.scss index b6034be42d..2c3f1c705c 100644 --- a/res/css/structures/_ToastContainer.scss +++ b/res/css/structures/_ToastContainer.scss @@ -28,7 +28,7 @@ limitations under the License. margin: 0 4px; grid-row: 2 / 4; grid-column: 1; - background-color: $quinary-content-color; + background-color: $toast-bg-color; box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.5); border-radius: 8px; } @@ -37,7 +37,7 @@ limitations under the License. grid-row: 1 / 3; grid-column: 1; color: $primary-fg-color; - background-color: $quinary-content-color; + background-color: $toast-bg-color; box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.5); border-radius: 8px; overflow: hidden; diff --git a/res/css/views/voip/_CallView.scss b/res/css/views/voip/_CallView.scss index 2be4a4b802..8299ad8f9a 100644 --- a/res/css/views/voip/_CallView.scss +++ b/res/css/views/voip/_CallView.scss @@ -39,7 +39,7 @@ limitations under the License. .mx_CallView_pip { width: 320px; padding-bottom: 8px; - background-color: $quinary-content-color; + background-color: $toast-bg-color; box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.20); border-radius: 8px; diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index 0907ccdd9a..6e8d64b807 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -112,8 +112,8 @@ $eventtile-meta-color: $roomtopic-color; $header-divider-color: $header-panel-text-primary-color; $composer-e2e-icon-color: $header-panel-text-primary-color; -// this probably shouldn't have it's own colour $quinary-content-color: #394049; +$toast-bg-color: $quinary-content-color; // ******************** diff --git a/res/themes/legacy-dark/css/_legacy-dark.scss b/res/themes/legacy-dark/css/_legacy-dark.scss index 323fe0651e..064b532bb0 100644 --- a/res/themes/legacy-dark/css/_legacy-dark.scss +++ b/res/themes/legacy-dark/css/_legacy-dark.scss @@ -111,8 +111,8 @@ $eventtile-meta-color: $roomtopic-color; $header-divider-color: $header-panel-text-primary-color; $composer-e2e-icon-color: $header-panel-text-primary-color; -// this probably shouldn't have it's own colour $quinary-content-color: #394049; +$toast-bg-color: $quinary-content-color; // ******************** diff --git a/res/themes/legacy-light/css/_legacy-light.scss b/res/themes/legacy-light/css/_legacy-light.scss index a4e7af2bb9..cbf3d6d1b6 100644 --- a/res/themes/legacy-light/css/_legacy-light.scss +++ b/res/themes/legacy-light/css/_legacy-light.scss @@ -178,8 +178,8 @@ $eventtile-meta-color: $roomtopic-color; $composer-e2e-icon-color: #91a1c0; $header-divider-color: #91a1c0; -// this probably shouldn't have it's own colour -$quinary-content-color: #F4F6FA; +$system-light-color: #F4F6FA; +$toast-bg-color: $system-light-color; // ******************** diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index 2f81cb3407..1d786383f3 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -167,8 +167,8 @@ $eventtile-meta-color: $roomtopic-color; $composer-e2e-icon-color: #91A1C0; $header-divider-color: #91A1C0; -// this probably shouldn't have it's own colour -$quinary-content-color: #F4F6FA; +$system-light-color: #F4F6FA; +$toast-bg-color: $system-light-color; // ******************** 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 055/176] 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 056/176] 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 caefefc2c22704fc4f678a680592d885880193d0 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Tue, 27 Jul 2021 17:22:49 -0400 Subject: [PATCH 057/176] Add regional indicators to emoji picker Signed-off-by: Robin Townsend --- src/emoji.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/emoji.ts b/src/emoji.ts index 321eae63f6..1445f737d6 100644 --- a/src/emoji.ts +++ b/src/emoji.ts @@ -35,6 +35,9 @@ export const EMOTICON_TO_EMOJI = new Map(); export const getEmojiFromUnicode = unicode => UNICODE_TO_EMOJI.get(stripVariation(unicode)); +const isRegionalIndicator = (x: string) => + Array.from(x).length === 1 && x >= '\u{1f1e6}' && x <= '\u{1f1ff}'; + const EMOJIBASE_GROUP_ID_TO_CATEGORY = [ "people", // smileys "people", // actually people @@ -72,7 +75,11 @@ export const EMOJI: IEmoji[] = EMOJIBASE.map((emojiData: Omit Date: Tue, 27 Jul 2021 17:35:34 -0400 Subject: [PATCH 058/176] Add more types Signed-off-by: Robin Townsend --- src/emoji.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/emoji.ts b/src/emoji.ts index 1445f737d6..e871e0bb58 100644 --- a/src/emoji.ts +++ b/src/emoji.ts @@ -35,7 +35,7 @@ export const EMOTICON_TO_EMOJI = new Map(); export const getEmojiFromUnicode = unicode => UNICODE_TO_EMOJI.get(stripVariation(unicode)); -const isRegionalIndicator = (x: string) => +const isRegionalIndicator = (x: string): boolean => Array.from(x).length === 1 && x >= '\u{1f1e6}' && x <= '\u{1f1ff}'; const EMOJIBASE_GROUP_ID_TO_CATEGORY = [ From f16b1d46b72430715a75bac495b6ac19e94efe44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 28 Jul 2021 20:58:24 +0200 Subject: [PATCH 059/176] Fix sizing issue of the screen picker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/elements/_DesktopCapturerSourcePicker.scss | 2 -- 1 file changed, 2 deletions(-) diff --git a/res/css/views/elements/_DesktopCapturerSourcePicker.scss b/res/css/views/elements/_DesktopCapturerSourcePicker.scss index 49a0a44417..bd81aafef3 100644 --- a/res/css/views/elements/_DesktopCapturerSourcePicker.scss +++ b/res/css/views/elements/_DesktopCapturerSourcePicker.scss @@ -35,7 +35,6 @@ limitations under the License. .mx_desktopCapturerSourcePicker_source_thumbnail { margin: 4px; padding: 4px; - width: 312px; border-width: 2px; border-radius: 8px; border-style: solid; @@ -53,6 +52,5 @@ limitations under the License. white-space: nowrap; text-overflow: ellipsis; overflow: hidden; - width: 312px; } } From e0df78553867add2cd18f05a487238e240ac9a35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 30 Jul 2021 08:15:45 +0200 Subject: [PATCH 060/176] Mirror only usermedia feeds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/voip/VideoFeed.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/views/voip/VideoFeed.tsx b/src/components/views/voip/VideoFeed.tsx index 51d2adb845..5fd30ed7df 100644 --- a/src/components/views/voip/VideoFeed.tsx +++ b/src/components/views/voip/VideoFeed.tsx @@ -22,6 +22,7 @@ import { CallFeed, CallFeedEvent } from 'matrix-js-sdk/src/webrtc/callFeed'; import { logger } from 'matrix-js-sdk/src/logger'; import MemberAvatar from "../avatars/MemberAvatar"; import { replaceableComponent } from "../../../utils/replaceableComponent"; +import { SDPStreamMetadataPurpose } from '../../../../../matrix-js-sdk/src/webrtc/callEventTypes'; interface IProps { call: MatrixCall; @@ -159,6 +160,7 @@ export default class VideoFeed extends React.Component { mx_VideoFeed_video: !this.state.videoMuted, mx_VideoFeed_mirror: ( this.props.feed.isLocal() && + this.props.feed.purpose === SDPStreamMetadataPurpose.Usermedia && SettingsStore.getValue('VideoView.flipVideoHorizontally') ), }; From 0c30566cae3293df8981d21ddc93e0358f1ca6b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 30 Jul 2021 08:20:36 +0200 Subject: [PATCH 061/176] 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/voip/VideoFeed.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/voip/VideoFeed.tsx b/src/components/views/voip/VideoFeed.tsx index 5fd30ed7df..170e8362b5 100644 --- a/src/components/views/voip/VideoFeed.tsx +++ b/src/components/views/voip/VideoFeed.tsx @@ -22,7 +22,7 @@ import { CallFeed, CallFeedEvent } from 'matrix-js-sdk/src/webrtc/callFeed'; import { logger } from 'matrix-js-sdk/src/logger'; import MemberAvatar from "../avatars/MemberAvatar"; import { replaceableComponent } from "../../../utils/replaceableComponent"; -import { SDPStreamMetadataPurpose } from '../../../../../matrix-js-sdk/src/webrtc/callEventTypes'; +import { SDPStreamMetadataPurpose } from 'matrix-js-sdk/src/webrtc/callEventTypes'; interface IProps { call: MatrixCall; 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 062/176] 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 2dc13dedf0f116c8ad7205cf651a5d1b5926b4c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 2 Aug 2021 15:38:45 +0200 Subject: [PATCH 063/176] Fix font MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/settings/_ProfileSettings.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/res/css/views/settings/_ProfileSettings.scss b/res/css/views/settings/_ProfileSettings.scss index 4cbcb8e708..63a5fa7edf 100644 --- a/res/css/views/settings/_ProfileSettings.scss +++ b/res/css/views/settings/_ProfileSettings.scss @@ -16,6 +16,7 @@ limitations under the License. .mx_ProfileSettings_controls_topic { & > textarea { + font-family: inherit; resize: vertical; } } From 7fd14c9ccc5c1cbee3d27d7880a7de285509971a Mon Sep 17 00:00:00 2001 From: Dariusz Niemczyk Date: Sun, 1 Aug 2021 16:18:06 +0200 Subject: [PATCH 064/176] Add data-mx-theme to theme css for hot-reload --- src/theme.js | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/theme.js b/src/theme.js index 2caf48b65a..cd14d2d9db 100644 --- a/src/theme.js +++ b/src/theme.js @@ -171,15 +171,10 @@ export async function setTheme(theme) { // look for the stylesheet elements. // styleElements is a map from style name to HTMLLinkElement. const styleElements = Object.create(null); - let a; - for (let i = 0; (a = document.getElementsByTagName("link")[i]); i++) { - const href = a.getAttribute("href"); - // shouldn't we be using the 'title' tag rather than the href? - const match = href && href.match(/^bundles\/.*\/theme-(.*)\.css$/); - if (match) { - styleElements[match[1]] = a; - } - } + const themes = Array.from(document.querySelectorAll('[data-mx-theme]')); + themes.forEach(theme => { + styleElements[theme.attributes['data-mx-theme'].value.toLowerCase()] = theme; + }); if (!(stylesheetName in styleElements)) { throw new Error("Unknown theme " + stylesheetName); From c5d11a9f17b9e523ecffe28c31102b0a3b4d63e9 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 2 Aug 2021 23:29:46 -0600 Subject: [PATCH 065/176] Improve voice messages uploading state Fixes https://github.com/vector-im/element-web/issues/18226 Fixes https://github.com/vector-im/element-web/issues/18224 --- .../views/rooms/_VoiceRecordComposerTile.scss | 11 ++++ .../views/rooms/VoiceRecordComposerTile.tsx | 54 ++++++++++++++----- src/i18n/strings/en_EN.json | 3 +- 3 files changed, 55 insertions(+), 13 deletions(-) diff --git a/res/css/views/rooms/_VoiceRecordComposerTile.scss b/res/css/views/rooms/_VoiceRecordComposerTile.scss index 5501ab343e..3d8d0e5ecc 100644 --- a/res/css/views/rooms/_VoiceRecordComposerTile.scss +++ b/res/css/views/rooms/_VoiceRecordComposerTile.scss @@ -46,6 +46,17 @@ limitations under the License. mask-image: url('$(res)/img/element-icons/trashcan.svg'); } +.mx_VoiceRecordComposerTile_uploadState { + margin-right: 21px; + color: $secondary-fg-color; + + .mx_VoiceRecordComposerTile_uploadState_badge { + display: inline-block; + margin-right: 4px; + vertical-align: middle; + } +} + .mx_MessageComposer_row .mx_VoiceMessagePrimaryContainer { // Note: remaining class properties are in the PlayerContainer CSS. diff --git a/src/components/views/rooms/VoiceRecordComposerTile.tsx b/src/components/views/rooms/VoiceRecordComposerTile.tsx index 8323320520..490d6b2231 100644 --- a/src/components/views/rooms/VoiceRecordComposerTile.tsx +++ b/src/components/views/rooms/VoiceRecordComposerTile.tsx @@ -17,10 +17,7 @@ limitations under the License. import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import { _t } from "../../../languageHandler"; import React, { ReactNode } from "react"; -import { - RecordingState, - VoiceRecording, -} from "../../../audio/VoiceRecording"; +import { IUpload, RecordingState, VoiceRecording } from "../../../audio/VoiceRecording"; import { Room } from "matrix-js-sdk/src/models/room"; import { MatrixClientPeg } from "../../../MatrixClientPeg"; import classNames from "classnames"; @@ -34,6 +31,10 @@ import { MsgType } from "matrix-js-sdk/src/@types/event"; import Modal from "../../../Modal"; import ErrorDialog from "../dialogs/ErrorDialog"; import MediaDeviceHandler, { MediaDeviceKindEnum } from "../../../MediaDeviceHandler"; +import NotificationBadge from "./NotificationBadge"; +import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState"; +import { NotificationColor } from "../../../stores/notifications/NotificationColor"; +import InlineSpinner from "../elements/InlineSpinner"; interface IProps { room: Room; @@ -42,6 +43,7 @@ interface IProps { interface IState { recorder?: VoiceRecording; recordingPhase?: RecordingState; + didUploadFail?: boolean; } /** @@ -69,9 +71,19 @@ export default class VoiceRecordComposerTile extends React.PureComponent { @@ -234,7 +245,26 @@ export default class VoiceRecordComposerTile extends React.PureComponent; } + let uploadIndicator; + if (this.state.recordingPhase === RecordingState.Uploading) { + uploadIndicator = + + { _t("Uploading...") } + ; + } else if (this.state.didUploadFail && this.state.recordingPhase === RecordingState.Ended) { + uploadIndicator = + + { /* Need to stick the badge in a span to ensure it doesn't create a block component */ } + + + { _t("Failed to upload voice message") } + ; + } + return (<> + { uploadIndicator } { deleteButton } { this.renderWaveformArea() } { recordingInfo } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 87cd9afb5b..c929bd683b 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1701,7 +1701,6 @@ "Invited by %(sender)s": "Invited by %(sender)s", "Jump to first unread message.": "Jump to first unread message.", "Mark all as read": "Mark all as read", - "The voice message failed to upload.": "The voice message failed to upload.", "Unable to access your microphone": "Unable to access your microphone", "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", @@ -1709,6 +1708,8 @@ "Record a voice message": "Record a voice message", "Stop the recording": "Stop the recording", "Delete recording": "Delete recording", + "Uploading...": "Uploading...", + "Failed to upload voice message": "Failed to upload voice message", "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.", "There was an error updating the room's alternative addresses. It may not be allowed by the server or a temporary failure occurred.": "There was an error updating the room's alternative addresses. It may not be allowed by the server or a temporary failure occurred.", From c5ea2531817e2d254692dd48bc4985184528233d Mon Sep 17 00:00:00 2001 From: James Salter Date: Tue, 3 Aug 2021 07:30:02 +0100 Subject: [PATCH 066/176] Revert "Add support for Posthog Analytics under a labs flag" --- package.json | 1 - src/@types/posthog.d.ts | 748 ------------------ src/Lifecycle.ts | 5 - src/PosthogAnalytics.ts | 355 --------- src/components/structures/MatrixChat.tsx | 6 - .../tabs/user/SecurityUserSettingsTab.js | 2 - src/i18n/strings/en_EN.json | 1 - src/settings/Settings.tsx | 8 - .../PseudonymousAnalyticsController.ts | 26 - test/PosthogAnalytics-test.ts | 232 ------ tsconfig.json | 9 +- yarn.lock | 12 - 12 files changed, 2 insertions(+), 1403 deletions(-) delete mode 100644 src/@types/posthog.d.ts delete mode 100644 src/PosthogAnalytics.ts delete mode 100644 src/settings/controllers/PseudonymousAnalyticsController.ts delete mode 100644 test/PosthogAnalytics-test.ts diff --git a/package.json b/package.json index b7e06fe012..9744aa7685 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,6 @@ "pako": "^2.0.3", "parse5": "^6.0.1", "png-chunks-extract": "^1.0.0", - "posthog-js": "1.12.1", "prop-types": "^15.7.2", "qrcode": "^1.4.4", "re-resizable": "^6.9.0", diff --git a/src/@types/posthog.d.ts b/src/@types/posthog.d.ts deleted file mode 100644 index 1ca475cd3b..0000000000 --- a/src/@types/posthog.d.ts +++ /dev/null @@ -1,748 +0,0 @@ -// A clone of the type definitions from posthog-js, stripped of references to transitive -// dependencies which we don't actually use, so that we don't need to install them. -// -// Original file lives in node_modules/posthog/dist/module.d.ts - -/* eslint-disable @typescript-eslint/member-delimiter-style */ -/* eslint-disable @typescript-eslint/naming-convention */ -/* eslint-disable camelcase */ - -// Type definitions for exported methods - -declare class posthog { - /** - * This function initializes a new instance of the PostHog capturing object. - * All new instances are added to the main posthog object as sub properties (such as - * posthog.library_name) and also returned by this function. To define a - * second instance on the page, you would call: - * - * posthog.init('new token', { your: 'config' }, 'library_name'); - * - * and use it like so: - * - * posthog.library_name.capture(...); - * - * @param {String} token Your PostHog API token - * @param {Object} [config] A dictionary of config options to override. See a list of default config options. - * @param {String} [name] The name for the new posthog instance that you want created - */ - static init(token: string, config?: posthog.Config, name?: string): posthog - - /** - * Clears super properties and generates a new random distinct_id for this instance. - * Useful for clearing data when a user logs out. - */ - static reset(reset_device_id?: boolean): void - - /** - * Capture an event. This is the most important and - * frequently used PostHog function. - * - * ### Usage: - * - * // capture an event named 'Registered' - * posthog.capture('Registered', {'Gender': 'Male', 'Age': 21}); - * - * // capture an event using navigator.sendBeacon - * posthog.capture('Left page', {'duration_seconds': 35}, {transport: 'sendBeacon'}); - * - * @param {String} event_name The name of the event. This can be anything the user does - 'Button Click', 'Sign Up', 'Item Purchased', etc. - * @param {Object} [properties] A set of properties to include with the event you're sending. These describe the user who did the event or details about the event itself. - * @param {Object} [options] Optional configuration for this capture request. - * @param {String} [options.transport] Transport method for network request ('XHR' or 'sendBeacon'). - */ - static capture( - event_name: string, - properties?: posthog.Properties, - options?: { transport: 'XHR' | 'sendBeacon' } - ): posthog.CaptureResult - - /** - * Capture a page view event, which is currently ignored by the server. - * This function is called by default on page load unless the - * capture_pageview configuration variable is false. - * - * @param {String} [page] The url of the page to record. If you don't include this, it defaults to the current url. - * @api private - */ - static capture_pageview(page?: string): void - - /** - * Register a set of super properties, which are included with all - * events. This will overwrite previous super property values. - * - * ### Usage: - * - * // register 'Gender' as a super property - * posthog.register({'Gender': 'Female'}); - * - * // register several super properties when a user signs up - * posthog.register({ - * 'Email': 'jdoe@example.com', - * 'Account Type': 'Free' - * }); - * - * @param {Object} properties An associative array of properties to store about the user - * @param {Number} [days] How many days since the user's last visit to store the super properties - */ - static register(properties: posthog.Properties, days?: number): void - - /** - * Register a set of super properties only once. This will not - * overwrite previous super property values, unlike register(). - * - * ### Usage: - * - * // register a super property for the first time only - * posthog.register_once({ - * 'First Login Date': new Date().toISOString() - * }); - * - * ### Notes: - * - * If default_value is specified, current super properties - * with that value will be overwritten. - * - * @param {Object} properties An associative array of properties to store about the user - * @param {*} [default_value] Value to override if already set in super properties (ex: 'False') Default: 'None' - * @param {Number} [days] How many days since the users last visit to store the super properties - */ - static register_once(properties: posthog.Properties, default_value?: posthog.Property, days?: number): void - - /** - * Delete a super property stored with the current user. - * - * @param {String} property The name of the super property to remove - */ - static unregister(property: string): void - - /** - * Identify a user with a unique ID instead of a PostHog - * randomly generated distinct_id. If the method is never called, - * then unique visitors will be identified by a UUID generated - * the first time they visit the site. - * - * If user properties are passed, they are also sent to posthog. - * - * ### Usage: - * - * posthog.identify('[user unique id]') - * posthog.identify('[user unique id]', { email: 'john@example.com' }) - * posthog.identify('[user unique id]', {}, { referral_code: '12345' }) - * - * ### Notes: - * - * You can call this function to overwrite a previously set - * unique ID for the current user. PostHog cannot translate - * between IDs at this time, so when you change a user's ID - * they will appear to be a new user. - * - * When used alone, posthog.identify will change the user's - * distinct_id to the unique ID provided. When used in tandem - * with posthog.alias, it will allow you to identify based on - * unique ID and map that back to the original, anonymous - * distinct_id given to the user upon her first arrival to your - * site (thus connecting anonymous pre-signup activity to - * post-signup activity). Though the two work together, do not - * call identify() at the same time as alias(). Calling the two - * at the same time can cause a race condition, so it is best - * practice to call identify on the original, anonymous ID - * right after you've aliased it. - * - * @param {String} [unique_id] A string that uniquely identifies a user. If not provided, the distinct_id currently in the persistent store (cookie or localStorage) will be used. - * @param {Object} [userProperties] Optional: An associative array of properties to store about the user - * @param {Object} [userPropertiesToSetOnce] Optional: An associative array of properties to store about the user. If property is previously set, this does not override that value. - */ - static identify( - unique_id?: string, - userPropertiesToSet?: posthog.Properties, - userPropertiesToSetOnce?: posthog.Properties - ): void - - /** - * Create an alias, which PostHog will use to link two distinct_ids going forward (not retroactively). - * Multiple aliases can map to the same original ID, but not vice-versa. Aliases can also be chained - the - * following is a valid scenario: - * - * posthog.alias('new_id', 'existing_id'); - * ... - * posthog.alias('newer_id', 'new_id'); - * - * If the original ID is not passed in, we will use the current distinct_id - probably the auto-generated GUID. - * - * ### Notes: - * - * The best practice is to call alias() when a unique ID is first created for a user - * (e.g., when a user first registers for an account and provides an email address). - * alias() should never be called more than once for a given user, except to - * chain a newer ID to a previously new ID, as described above. - * - * @param {String} alias A unique identifier that you want to use for this user in the future. - * @param {String} [original] The current identifier being used for this user. - */ - static alias(alias: string, original?: string): posthog.CaptureResult | number - - /** - * Update the configuration of a posthog library instance. - * - * The default config is: - * - * { - * // HTTP method for capturing requests - * api_method: 'POST' - * - * // transport for sending requests ('XHR' or 'sendBeacon') - * // NB: sendBeacon should only be used for scenarios such as - * // page unload where a "best-effort" attempt to send is - * // acceptable; the sendBeacon API does not support callbacks - * // or any way to know the result of the request. PostHog - * // capturing via sendBeacon will not support any event- - * // batching or retry mechanisms. - * api_transport: 'XHR' - * - * // Automatically capture clicks, form submissions and change events - * autocapture: true - * - * // Capture rage clicks (beta) - useful for session recording - * rageclick: false - * - * // super properties cookie expiration (in days) - * cookie_expiration: 365 - * - * // super properties span subdomains - * cross_subdomain_cookie: true - * - * // debug mode - * debug: false - * - * // if this is true, the posthog cookie or localStorage entry - * // will be deleted, and no user persistence will take place - * disable_persistence: false - * - * // if this is true, PostHog will automatically determine - * // City, Region and Country data using the IP address of - * //the client - * ip: true - * - * // opt users out of capturing by this PostHog instance by default - * opt_out_capturing_by_default: false - * - * // opt users out of browser data storage by this PostHog instance by default - * opt_out_persistence_by_default: false - * - * // persistence mechanism used by opt-in/opt-out methods - cookie - * // or localStorage - falls back to cookie if localStorage is unavailable - * opt_out_capturing_persistence_type: 'localStorage' - * - * // customize the name of cookie/localStorage set by opt-in/opt-out methods - * opt_out_capturing_cookie_prefix: null - * - * // type of persistent store for super properties (cookie/ - * // localStorage) if set to 'localStorage', any existing - * // posthog cookie value with the same persistence_name - * // will be transferred to localStorage and deleted - * persistence: 'cookie' - * - * // name for super properties persistent store - * persistence_name: '' - * - * // names of properties/superproperties which should never - * // be sent with capture() calls - * property_blacklist: [] - * - * // if this is true, posthog cookies will be marked as - * // secure, meaning they will only be transmitted over https - * secure_cookie: false - * - * // should we capture a page view on page load - * capture_pageview: true - * - * // if you set upgrade to be true, the library will check for - * // a cookie from our old js library and import super - * // properties from it, then the old cookie is deleted - * // The upgrade config option only works in the initialization, - * // so make sure you set it when you create the library. - * upgrade: false - * - * // extra HTTP request headers to set for each API request, in - * // the format {'Header-Name': value} - * xhr_headers: {} - * - * // protocol for fetching in-app message resources, e.g. - * // 'https://' or 'http://'; defaults to '//' (which defers to the - * // current page's protocol) - * inapp_protocol: '//' - * - * // whether to open in-app message link in new tab/window - * inapp_link_new_window: false - * - * // a set of rrweb config options that PostHog users can configure - * // see https://github.com/rrweb-io/rrweb/blob/master/guide.md - * session_recording: { - * blockClass: 'ph-no-capture', - * blockSelector: null, - * ignoreClass: 'ph-ignore-input', - * maskAllInputs: false, - * maskInputOptions: {}, - * maskInputFn: null, - * slimDOMOptions: {}, - * collectFonts: false - * } - * - * // prevent autocapture from capturing any attribute names on elements - * mask_all_element_attributes: false - * - * // prevent autocapture from capturing textContent on all elements - * mask_all_text: false - * - * // will disable requests to the /decide endpoint (please review documentation for details) - * // autocapture, feature flags, compression and session recording will be disabled when set to `true` - * advanced_disable_decide: false - * - * } - * - * - * @param {Object} config A dictionary of new configuration values to update - */ - static set_config(config: posthog.Config): void - - /** - * returns the current config object for the library. - */ - static get_config(prop_name: T): posthog.Config[T] - - /** - * Returns the value of the super property named property_name. If no such - * property is set, get_property() will return the undefined value. - * - * ### Notes: - * - * get_property() can only be called after the PostHog library has finished loading. - * init() has a loaded function available to handle this automatically. For example: - * - * // grab value for 'user_id' after the posthog library has loaded - * posthog.init('YOUR PROJECT TOKEN', { - * loaded: function(posthog) { - * user_id = posthog.get_property('user_id'); - * } - * }); - * - * @param {String} property_name The name of the super property you want to retrieve - */ - static get_property(property_name: string): posthog.Property | undefined - - /** - * Returns the current distinct id of the user. This is either the id automatically - * generated by the library or the id that has been passed by a call to identify(). - * - * ### Notes: - * - * get_distinct_id() can only be called after the PostHog library has finished loading. - * init() has a loaded function available to handle this automatically. For example: - * - * // set distinct_id after the posthog library has loaded - * posthog.init('YOUR PROJECT TOKEN', { - * loaded: function(posthog) { - * distinct_id = posthog.get_distinct_id(); - * } - * }); - */ - static get_distinct_id(): string - - /** - * Opt the user out of data capturing and cookies/localstorage for this PostHog instance - * - * ### Usage - * - * // opt user out - * posthog.opt_out_capturing(); - * - * // opt user out with different cookie configuration from PostHog instance - * posthog.opt_out_capturing({ - * cookie_expiration: 30, - * secure_cookie: true - * }); - * - * @param {Object} [options] A dictionary of config options to override - * @param {boolean} [options.clear_persistence=true] If true, will delete all data stored by the sdk in persistence - * @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable - * @param {string} [options.cookie_prefix=__ph_opt_in_out] Custom prefix to be used in the cookie/localstorage name - * @param {Number} [options.cookie_expiration] Number of days until the opt-in cookie expires (overrides value specified in this PostHog instance's config) - * @param {boolean} [options.cross_subdomain_cookie] Whether the opt-in cookie is set as cross-subdomain or not (overrides value specified in this PostHog instance's config) - * @param {boolean} [options.secure_cookie] Whether the opt-in cookie is set as secure or not (overrides value specified in this PostHog instance's config) - */ - static opt_out_capturing(options?: posthog.OptInOutCapturingOptions): void - - /** - * Opt the user in to data capturing and cookies/localstorage for this PostHog instance - * - * ### Usage - * - * // opt user in - * posthog.opt_in_capturing(); - * - * // opt user in with specific event name, properties, cookie configuration - * posthog.opt_in_capturing({ - * capture_event_name: 'User opted in', - * capture_event_properties: { - * 'Email': 'jdoe@example.com' - * }, - * cookie_expiration: 30, - * secure_cookie: true - * }); - * - * @param {Object} [options] A dictionary of config options to override - * @param {function} [options.capture] Function used for capturing a PostHog event to record the opt-in action (default is this PostHog instance's capture method) - * @param {string} [options.capture_event_name=$opt_in] Event name to be used for capturing the opt-in action - * @param {Object} [options.capture_properties] Set of properties to be captured along with the opt-in action - * @param {boolean} [options.enable_persistence=true] If true, will re-enable sdk persistence - * @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable - * @param {string} [options.cookie_prefix=__ph_opt_in_out] Custom prefix to be used in the cookie/localstorage name - * @param {Number} [options.cookie_expiration] Number of days until the opt-in cookie expires (overrides value specified in this PostHog instance's config) - * @param {boolean} [options.cross_subdomain_cookie] Whether the opt-in cookie is set as cross-subdomain or not (overrides value specified in this PostHog instance's config) - * @param {boolean} [options.secure_cookie] Whether the opt-in cookie is set as secure or not (overrides value specified in this PostHog instance's config) - */ - static opt_in_capturing(options?: posthog.OptInOutCapturingOptions): void - - /** - * Check whether the user has opted out of data capturing and cookies/localstorage for this PostHog instance - * - * ### Usage - * - * const has_opted_out = posthog.has_opted_out_capturing(); - * // use has_opted_out value - * - * @param {Object} [options] A dictionary of config options to override - * @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable - * @param {string} [options.cookie_prefix=__ph_opt_in_out] Custom prefix to be used in the cookie/localstorage name - * @returns {boolean} current opt-out status - */ - static has_opted_out_capturing(options?: posthog.HasOptedInOutCapturingOptions): boolean - - /** - * Check whether the user has opted in to data capturing and cookies/localstorage for this PostHog instance - * - * ### Usage - * - * const has_opted_in = posthog.has_opted_in_capturing(); - * // use has_opted_in value - * - * @param {Object} [options] A dictionary of config options to override - * @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable - * @param {string} [options.cookie_prefix=__ph_opt_in_out] Custom prefix to be used in the cookie/localstorage name - * @returns {boolean} current opt-in status - */ - static has_opted_in_capturing(options?: posthog.HasOptedInOutCapturingOptions): boolean - - /** - * Clear the user's opt in/out status of data capturing and cookies/localstorage for this PostHog instance - * - * ### Usage - * - * // clear user's opt-in/out status - * posthog.clear_opt_in_out_capturing(); - * - * // clear user's opt-in/out status with specific cookie configuration - should match - * // configuration used when opt_in_capturing/opt_out_capturing methods were called. - * posthog.clear_opt_in_out_capturing({ - * cookie_expiration: 30, - * secure_cookie: true - * }); - * - * @param {Object} [options] A dictionary of config options to override - * @param {boolean} [options.enable_persistence=true] If true, will re-enable sdk persistence - * @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable - * @param {string} [options.cookie_prefix=__ph_opt_in_out] Custom prefix to be used in the cookie/localstorage name - * @param {Number} [options.cookie_expiration] Number of days until the opt-in cookie expires (overrides value specified in this PostHog instance's config) - * @param {boolean} [options.cross_subdomain_cookie] Whether the opt-in cookie is set as cross-subdomain or not (overrides value specified in this PostHog instance's config) - * @param {boolean} [options.secure_cookie] Whether the opt-in cookie is set as secure or not (overrides value specified in this PostHog instance's config) - */ - static clear_opt_in_out_capturing(options?: posthog.ClearOptInOutCapturingOptions): void - - /* - * See if feature flag is enabled for user. - * - * ### Usage: - * - * if(posthog.isFeatureEnabled('beta-feature')) { // do something } - * - * @param {Object|String} prop Key of the feature flag. - * @param {Object|String} options (optional) If {send_event: false}, we won't send an $feature_flag_call event to PostHog. - */ - static isFeatureEnabled(key: string, options?: posthog.isFeatureEnabledOptions): boolean - - /* - * See if feature flags are available. - * - * ### Usage: - * - * posthog.onFeatureFlags(function(featureFlags) { // do something }) - * - * @param {Function} [callback] The callback function will be called once the feature flags are ready. It'll return a list of feature flags enabled for the user. - */ - static onFeatureFlags(callback: (flags: string[]) => void): false | undefined - - /* - * Reload all feature flags for the user. - * - * ### Usage: - * - * posthog.reloadFeatureFlags() - */ - static reloadFeatureFlags(): void - - static toString(): string - - /* Will log all capture requests to the Javascript console, including event properties for easy debugging */ - static debug(): void - - /* - * Starts session recording and updates disable_session_recording to false. - * Used for manual session recording management. By default, session recording is enabled and - * starts automatically. - * - * ### Usage: - * - * posthog.startSessionRecording() - */ - static startSessionRecording(): void - - /* - * Stops session recording and updates disable_session_recording to true. - * - * ### Usage: - * - * posthog.stopSessionRecording() - */ - static stopSessionRecording(): void - - /* - * Check if session recording is currently running. - * - * ### Usage: - * - * const isSessionRecordingOn = posthog.sessionRecordingStarted() - */ - static sessionRecordingStarted(): boolean -} - -declare namespace posthog { - /* eslint-disable @typescript-eslint/no-explicit-any */ - type Property = any; - type Properties = Record; - type CaptureResult = { event: string; properties: Properties } | undefined; - type CaptureCallback = (response: any, data: any) => void; - /* eslint-enable @typescript-eslint/no-explicit-any */ - - interface Config { - api_host?: string - api_method?: string - api_transport?: string - autocapture?: boolean - rageclick?: boolean - cdn?: string - cross_subdomain_cookie?: boolean - persistence?: 'localStorage' | 'cookie' | 'memory' - persistence_name?: string - cookie_name?: string - loaded?: (posthog_instance: typeof posthog) => void - store_google?: boolean - save_referrer?: boolean - test?: boolean - verbose?: boolean - img?: boolean - capture_pageview?: boolean - debug?: boolean - cookie_expiration?: number - upgrade?: boolean - disable_session_recording?: boolean - disable_persistence?: boolean - disable_cookie?: boolean - secure_cookie?: boolean - ip?: boolean - opt_out_capturing_by_default?: boolean - opt_out_persistence_by_default?: boolean - opt_out_capturing_persistence_type?: 'localStorage' | 'cookie' - opt_out_capturing_cookie_prefix?: string | null - respect_dnt?: boolean - property_blacklist?: string[] - xhr_headers?: { [header_name: string]: string } - inapp_protocol?: string - inapp_link_new_window?: boolean - request_batching?: boolean - sanitize_properties?: (properties: posthog.Properties, event_name: string) => posthog.Properties - properties_string_max_length?: number - mask_all_element_attributes?: boolean - mask_all_text?: boolean - advanced_disable_decide?: boolean - } - - interface OptInOutCapturingOptions { - clear_persistence: boolean - persistence_type: string - cookie_prefix: string - cookie_expiration: number - cross_subdomain_cookie: boolean - secure_cookie: boolean - } - - interface HasOptedInOutCapturingOptions { - persistence_type: string - cookie_prefix: string - } - - interface ClearOptInOutCapturingOptions { - enable_persistence: boolean - persistence_type: string - cookie_prefix: string - cookie_expiration: number - cross_subdomain_cookie: boolean - secure_cookie: boolean - } - - interface isFeatureEnabledOptions { - send_event: boolean - } - - export class persistence { - static properties(): posthog.Properties - - static load(): void - - static save(): void - - static remove(): void - - static clear(): void - - /** - * @param {Object} props - * @param {*=} default_value - * @param {number=} days - */ - static register_once(props: Properties, default_value?: Property, days?: number): boolean - - /** - * @param {Object} props - * @param {number=} days - */ - static register(props: posthog.Properties, days?: number): boolean - - static unregister(prop: string): void - - static update_campaign_params(): void - - static update_search_keyword(referrer: string): void - - static update_referrer_info(referrer: string): void - - static get_referrer_info(): posthog.Properties - - static safe_merge(props: posthog.Properties): posthog.Properties - - static update_config(config: posthog.Config): void - - static set_disabled(disabled: boolean): void - - static set_cross_subdomain(cross_subdomain: boolean): void - - static get_cross_subdomain(): boolean - - static set_secure(secure: boolean): void - - static set_event_timer(event_name: string, timestamp: Date): void - - static remove_event_timer(event_name: string): Date | undefined - } - - export class people { - /* - * Set properties on a user record. - * - * ### Usage: - * - * posthog.people.set('gender', 'm'); - * - * // or set multiple properties at once - * posthog.people.set({ - * 'Company': 'Acme', - * 'Plan': 'Premium', - * 'Upgrade date': new Date() - * }); - * // properties can be strings, integers, dates, or lists - * - * @param {Object|String} prop If a string, this is the name of the property. If an object, this is an associative array of names and values. - * @param {*} [to] A value to set on the given property name - * @param {Function} [callback] If provided, the callback will be called after capturing the event. - */ - static set( - prop: posthog.Properties | string, - to?: posthog.Property, - callback?: posthog.CaptureCallback - ): posthog.Properties - - /* - * Set properties on a user record, only if they do not yet exist. - * This will not overwrite previous people property values, unlike - * people.set(). - * - * ### Usage: - * - * posthog.people.set_once('First Login Date', new Date()); - * - * // or set multiple properties at once - * posthog.people.set_once({ - * 'First Login Date': new Date(), - * 'Starting Plan': 'Premium' - * }); - * - * // properties can be strings, integers or dates - * - * @param {Object|String} prop If a string, this is the name of the property. If an object, this is an associative array of names and values. - * @param {*} [to] A value to set on the given property name - * @param {Function} [callback] If provided, the callback will be called after capturing the event. - */ - static set_once( - prop: posthog.Properties | string, - to?: posthog.Property, - callback?: posthog.CaptureCallback - ): posthog.Properties - - static toString(): string - } - - export class featureFlags { - static getFlags(): string[] - - static reloadFeatureFlags(): void - - /* - * See if feature flag is enabled for user. - * - * ### Usage: - * - * if(posthog.isFeatureEnabled('beta-feature')) { // do something } - * - * @param {Object|String} prop Key of the feature flag. - * @param {Object|String} options (optional) If {send_event: false}, we won't send an $feature_flag_call event to PostHog. - */ - static isFeatureEnabled(key: string, options?: { send_event?: boolean }): boolean - - /* - * See if feature flags are available. - * - * ### Usage: - * - * posthog.onFeatureFlags(function(featureFlags) { // do something }) - * - * @param {Function} [callback] The callback function will be called once the feature flags are ready. It'll return a list of feature flags enabled for the user. - */ - static onFeatureFlags(callback: (flags: string[]) => void): false | undefined - } - - export class feature_flags extends featureFlags {} -} - -export type PostHog = typeof posthog; - -export default posthog; diff --git a/src/Lifecycle.ts b/src/Lifecycle.ts index e48fd52cb1..410124a637 100644 --- a/src/Lifecycle.ts +++ b/src/Lifecycle.ts @@ -48,7 +48,6 @@ import { Jitsi } from "./widgets/Jitsi"; import { SSO_HOMESERVER_URL_KEY, SSO_ID_SERVER_URL_KEY, SSO_IDP_ID_KEY } from "./BasePlatform"; import ThreepidInviteStore from "./stores/ThreepidInviteStore"; import CountlyAnalytics from "./CountlyAnalytics"; -import { PosthogAnalytics } from "./PosthogAnalytics"; import CallHandler from './CallHandler'; import LifecycleCustomisations from "./customisations/Lifecycle"; import ErrorDialog from "./components/views/dialogs/ErrorDialog"; @@ -574,8 +573,6 @@ async function doSetLoggedIn( await abortLogin(); } - PosthogAnalytics.instance.updateAnonymityFromSettings(credentials.userId); - Analytics.setLoggedIn(credentials.guest, credentials.homeserverUrl); MatrixClientPeg.replaceUsingCreds(credentials); @@ -703,8 +700,6 @@ export function logout(): void { CountlyAnalytics.instance.enable(/* anonymous = */ true); } - PosthogAnalytics.instance.logout(); - if (MatrixClientPeg.get().isGuest()) { // logout doesn't work for guest sessions // Also we sometimes want to re-log in a guest session if we abort the login. diff --git a/src/PosthogAnalytics.ts b/src/PosthogAnalytics.ts deleted file mode 100644 index 860a155aff..0000000000 --- a/src/PosthogAnalytics.ts +++ /dev/null @@ -1,355 +0,0 @@ -/* -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. -*/ - -import posthog, { PostHog } from 'posthog-js'; -import PlatformPeg from './PlatformPeg'; -import SdkConfig from './SdkConfig'; -import SettingsStore from './settings/SettingsStore'; - -/* Posthog analytics tracking. - * - * Anonymity behaviour is as follows: - * - * - If Posthog isn't configured in `config.json`, events are not sent. - * - If [Do Not Track](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/doNotTrack) is - * enabled, events are not sent (this detection is built into posthog and turned on via the - * `respect_dnt` flag being passed to `posthog.init`). - * - If the `feature_pseudonymous_analytics_opt_in` labs flag is `true`, track pseudonomously, i.e. - * hash all matrix identifiers in tracking events (user IDs, room IDs etc) using SHA-256. - * - Otherwise, if the existing `analyticsOptIn` flag is `true`, track anonymously, i.e. - * redact all matrix identifiers in tracking events. - * - If both flags are false or not set, events are not sent. - */ - -interface IEvent { - // The event name that will be used by PostHog. Event names should use snake_case. - eventName: string; - - // The properties of the event that will be stored in PostHog. This is just a placeholder, - // extending interfaces must override this with a concrete definition to do type validation. - properties: {}; -} - -export enum Anonymity { - Disabled, - Anonymous, - Pseudonymous -} - -// If an event extends IPseudonymousEvent, the event contains pseudonymous data -// that won't be sent unless the user has explicitly consented to pseudonymous tracking. -// For example, it might contain hashed user IDs or room IDs. -// Such events will be automatically dropped if PosthogAnalytics.anonymity isn't set to Pseudonymous. -export interface IPseudonymousEvent extends IEvent {} - -// If an event extends IAnonymousEvent, the event strictly contains *only* anonymous data; -// i.e. no identifiers that can be associated with the user. -export interface IAnonymousEvent extends IEvent {} - -export interface IRoomEvent extends IPseudonymousEvent { - hashedRoomId: string; -} - -interface IPageView extends IAnonymousEvent { - eventName: "$pageview"; - properties: { - durationMs?: number; - screen?: string; - }; -} - -const hashHex = async (input: string): Promise => { - const buf = new TextEncoder().encode(input); - const digestBuf = await window.crypto.subtle.digest("sha-256", buf); - return [...new Uint8Array(digestBuf)].map((b: number) => b.toString(16).padStart(2, "0")).join(""); -}; - -const whitelistedScreens = new Set([ - "register", "login", "forgot_password", "soft_logout", "new", "settings", "welcome", "home", "start", "directory", - "start_sso", "start_cas", "groups", "complete_security", "post_registration", "room", "user", "group", -]); - -export async function getRedactedCurrentLocation( - origin: string, - hash: string, - pathname: string, - anonymity: Anonymity, -): Promise { - // Redact PII from the current location. - // If anonymous is true, redact entirely, if false, substitute it with a hash. - // For known screens, assumes a URL structure of //might/be/pii - if (origin.startsWith('file://')) { - pathname = "//"; - } - - let hashStr; - if (hash == "") { - hashStr = ""; - } else { - let [beforeFirstSlash, screen, ...parts] = hash.split("/"); - - if (!whitelistedScreens.has(screen)) { - screen = ""; - } - - for (let i = 0; i < parts.length; i++) { - parts[i] = anonymity === Anonymity.Anonymous ? `` : await hashHex(parts[i]); - } - - hashStr = `${beforeFirstSlash}/${screen}/${parts.join("/")}`; - } - return origin + pathname + hashStr; -} - -interface PlatformProperties { - appVersion: string; - appPlatform: string; -} - -export class PosthogAnalytics { - /* Wrapper for Posthog analytics. - * 3 modes of anonymity are supported, governed by this.anonymity - * - Anonymity.Disabled means *no data* is passed to posthog - * - Anonymity.Anonymous means all identifers will be redacted before being passed to posthog - * - Anonymity.Pseudonymous means all identifiers will be hashed via SHA-256 before being passed - * to Posthog - * - * To update anonymity, call updateAnonymityFromSettings() or you can set it directly via setAnonymity(). - * - * To pass an event to Posthog: - * - * 1. Declare a type for the event, extending IAnonymousEvent, IPseudonymousEvent or IRoomEvent. - * 2. Call the appropriate track*() method. Pseudonymous events will be dropped when anonymity is - * Anonymous or Disabled; Anonymous events will be dropped when anonymity is Disabled. - */ - - private anonymity = Anonymity.Disabled; - // set true during the constructor if posthog config is present, otherwise false - private enabled = false; - private static _instance = null; - private platformSuperProperties = {}; - - public static get instance(): PosthogAnalytics { - if (!this._instance) { - this._instance = new PosthogAnalytics(posthog); - } - return this._instance; - } - - constructor(private readonly posthog: PostHog) { - const posthogConfig = SdkConfig.get()["posthog"]; - if (posthogConfig) { - this.posthog.init(posthogConfig.projectApiKey, { - api_host: posthogConfig.apiHost, - autocapture: false, - mask_all_text: true, - mask_all_element_attributes: true, - // This only triggers on page load, which for our SPA isn't particularly useful. - // Plus, the .capture call originating from somewhere in posthog makes it hard - // to redact URLs, which requires async code. - // - // To raise this manually, just call .capture("$pageview") or posthog.capture_pageview. - capture_pageview: false, - sanitize_properties: this.sanitizeProperties, - respect_dnt: true, - }); - this.enabled = true; - } else { - this.enabled = false; - } - } - - private sanitizeProperties = (properties: posthog.Properties): posthog.Properties => { - // Callback from posthog to sanitize properties before sending them to the server. - // - // Here we sanitize posthog's built in properties which leak PII e.g. url reporting. - // See utils.js _.info.properties in posthog-js. - - // Replace the $current_url with a redacted version. - // $redacted_current_url is injected by this class earlier in capture(), as its generation - // is async and can't be done in this non-async callback. - if (!properties['$redacted_current_url']) { - console.log("$redacted_current_url not set in sanitizeProperties, will drop $current_url entirely"); - } - properties['$current_url'] = properties['$redacted_current_url']; - delete properties['$redacted_current_url']; - - if (this.anonymity == Anonymity.Anonymous) { - // drop referrer information for anonymous users - properties['$referrer'] = null; - properties['$referring_domain'] = null; - properties['$initial_referrer'] = null; - properties['$initial_referring_domain'] = null; - - // drop device ID, which is a UUID persisted in local storage - properties['$device_id'] = null; - } - - return properties; - }; - - private static getAnonymityFromSettings(): Anonymity { - // determine the current anonymity level based on current user settings - - // "Send anonymous usage data which helps us improve Element. This will use a cookie." - const analyticsOptIn = SettingsStore.getValue("analyticsOptIn", null, true); - - // (proposed wording) "Send pseudonymous usage data which helps us improve Element. This will use a cookie." - // - // TODO: Currently, this is only a labs flag, for testing purposes. - const pseudonumousOptIn = SettingsStore.getValue("feature_pseudonymous_analytics_opt_in", null, true); - - let anonymity; - if (pseudonumousOptIn) { - anonymity = Anonymity.Pseudonymous; - } else if (analyticsOptIn) { - anonymity = Anonymity.Anonymous; - } else { - anonymity = Anonymity.Disabled; - } - - return anonymity; - } - - private registerSuperProperties(properties: posthog.Properties) { - if (this.enabled) { - this.posthog.register(properties); - } - } - - private static async getPlatformProperties(): Promise { - const platform = PlatformPeg.get(); - let appVersion; - try { - appVersion = await platform.getAppVersion(); - } catch (e) { - // this happens if no version is set i.e. in dev - appVersion = "unknown"; - } - - return { - appVersion, - appPlatform: platform.getHumanReadableName(), - }; - } - - private async capture(eventName: string, properties: posthog.Properties) { - if (!this.enabled) { - return; - } - const { origin, hash, pathname } = window.location; - properties['$redacted_current_url'] = await getRedactedCurrentLocation( - origin, hash, pathname, this.anonymity); - this.posthog.capture(eventName, properties); - } - - public isEnabled(): boolean { - return this.enabled; - } - - public setAnonymity(anonymity: Anonymity): void { - // Update this.anonymity. - // This is public for testing purposes, typically you want to call updateAnonymityFromSettings - // to ensure this value is in step with the user's settings. - if (this.enabled && (anonymity == Anonymity.Disabled || anonymity == Anonymity.Anonymous)) { - // when transitioning to Disabled or Anonymous ensure we clear out any prior state - // set in posthog e.g. distinct ID - this.posthog.reset(); - // Restore any previously set platform super properties - this.registerSuperProperties(this.platformSuperProperties); - } - this.anonymity = anonymity; - } - - public async identifyUser(userId: string): Promise { - if (this.anonymity == Anonymity.Pseudonymous) { - this.posthog.identify(await hashHex(userId)); - } - } - - public getAnonymity(): Anonymity { - return this.anonymity; - } - - public logout(): void { - if (this.enabled) { - this.posthog.reset(); - } - this.setAnonymity(Anonymity.Anonymous); - } - - public async trackPseudonymousEvent( - eventName: E["eventName"], - properties: E["properties"] = {}, - ) { - if (this.anonymity == Anonymity.Anonymous || this.anonymity == Anonymity.Disabled) return; - await this.capture(eventName, properties); - } - - public async trackAnonymousEvent( - eventName: E["eventName"], - properties: E["properties"] = {}, - ): Promise { - if (this.anonymity == Anonymity.Disabled) return; - await this.capture(eventName, properties); - } - - public async trackRoomEvent( - eventName: E["eventName"], - roomId: string, - properties: Omit, - ): Promise { - const updatedProperties = { - ...properties, - hashedRoomId: roomId ? await hashHex(roomId) : null, - }; - await this.trackPseudonymousEvent(eventName, updatedProperties); - } - - public async trackPageView(durationMs: number): Promise { - const hash = window.location.hash; - - let screen = null; - const split = hash.split("/"); - if (split.length >= 2) { - screen = split[1]; - } - - await this.trackAnonymousEvent("$pageview", { - durationMs, - screen, - }); - } - - public async updatePlatformSuperProperties(): Promise { - // Update super properties in posthog with our platform (app version, platform). - // These properties will be subsequently passed in every event. - // - // This only needs to be done once per page lifetime. Note that getPlatformProperties - // is async and can involve a network request if we are running in a browser. - this.platformSuperProperties = await PosthogAnalytics.getPlatformProperties(); - this.registerSuperProperties(this.platformSuperProperties); - } - - public async updateAnonymityFromSettings(userId?: string): Promise { - // Update this.anonymity based on the user's analytics opt-in settings - // Identify the user (via hashed user ID) to posthog if anonymity is pseudonmyous - this.setAnonymity(PosthogAnalytics.getAnonymityFromSettings()); - if (userId && this.getAnonymity() == Anonymity.Pseudonymous) { - await this.identifyUser(userId); - } - } -} diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 60c78b5f9e..8cfe35c4cf 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -107,7 +107,6 @@ import UIStore, { UI_EVENTS } from "../../stores/UIStore"; import SoftLogout from './auth/SoftLogout'; import { makeRoomPermalink } from "../../utils/permalinks/Permalinks"; import { copyPlaintext } from "../../utils/strings"; -import { PosthogAnalytics } from '../../PosthogAnalytics'; /** constants for MatrixChat.state.view */ export enum Views { @@ -388,10 +387,6 @@ export default class MatrixChat extends React.PureComponent { if (SettingsStore.getValue("analyticsOptIn")) { Analytics.enable(); } - - PosthogAnalytics.instance.updateAnonymityFromSettings(); - PosthogAnalytics.instance.updatePlatformSuperProperties(); - CountlyAnalytics.instance.enable(/* anonymous = */ true); } @@ -448,7 +443,6 @@ export default class MatrixChat extends React.PureComponent { const durationMs = this.stopPageChangeTimer(); Analytics.trackPageChange(durationMs); CountlyAnalytics.instance.trackPageChange(durationMs); - PosthogAnalytics.instance.trackPageView(durationMs); } if (this.focusComposer) { dis.fire(Action.FocusSendMessageComposer); diff --git a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js index 25b0b86cb1..79d501e712 100644 --- a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js @@ -36,7 +36,6 @@ import { UIFeature } from "../../../../../settings/UIFeature"; import { isE2eAdvancedPanelPossible } from "../../E2eAdvancedPanel"; import CountlyAnalytics from "../../../../../CountlyAnalytics"; import { replaceableComponent } from "../../../../../utils/replaceableComponent"; -import { PosthogAnalytics } from "../../../../../PosthogAnalytics"; export class IgnoredUser extends React.Component { static propTypes = { @@ -107,7 +106,6 @@ export default class SecurityUserSettingsTab extends React.Component { _updateAnalytics = (checked) => { checked ? Analytics.enable() : Analytics.disable(); CountlyAnalytics.instance.enable(/* anonymous = */ !checked); - PosthogAnalytics.instance.updateAnonymityFromSettings(MatrixClientPeg.get().getUserId()); }; _onExportE2eKeysClicked = () => { diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 87cd9afb5b..3ad8daa85c 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -813,7 +813,6 @@ "Show message previews for reactions in DMs": "Show message previews for reactions in DMs", "Show message previews for reactions in all rooms": "Show message previews for reactions in all rooms", "Offline encrypted messaging using dehydrated devices": "Offline encrypted messaging using dehydrated devices", - "Send pseudonymous analytics data": "Send pseudonymous analytics data", "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)", diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index cfe2c097fc..c36e2b90bf 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -41,7 +41,6 @@ import { Layout } from "./Layout"; import ReducedMotionController from './controllers/ReducedMotionController'; import IncompatibleController from "./controllers/IncompatibleController"; import SdkConfig from "../SdkConfig"; -import PseudonymousAnalyticsController from './controllers/PseudonymousAnalyticsController'; import NewLayoutSwitcherController from './controllers/NewLayoutSwitcherController'; // These are just a bunch of helper arrays to avoid copy/pasting a bunch of times @@ -269,13 +268,6 @@ export const SETTINGS: {[setting: string]: ISetting} = { supportedLevels: LEVELS_FEATURE, default: false, }, - "feature_pseudonymous_analytics_opt_in": { - isFeature: true, - supportedLevels: LEVELS_FEATURE, - displayName: _td('Send pseudonymous analytics data'), - default: false, - controller: new PseudonymousAnalyticsController(), - }, "advancedRoomListLogging": { // TODO: Remove flag before launch: https://github.com/vector-im/element-web/issues/14231 displayName: _td("Enable advanced debugging for the room list"), diff --git a/src/settings/controllers/PseudonymousAnalyticsController.ts b/src/settings/controllers/PseudonymousAnalyticsController.ts deleted file mode 100644 index a82b9685ef..0000000000 --- a/src/settings/controllers/PseudonymousAnalyticsController.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* -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. -*/ - -import SettingController from "./SettingController"; -import { SettingLevel } from "../SettingLevel"; -import { PosthogAnalytics } from "../../PosthogAnalytics"; -import { MatrixClientPeg } from "../../MatrixClientPeg"; - -export default class PseudonymousAnalyticsController extends SettingController { - public onChange(level: SettingLevel, roomId: string, newValue: any) { - PosthogAnalytics.instance.updateAnonymityFromSettings(MatrixClientPeg.get().getUserId()); - } -} diff --git a/test/PosthogAnalytics-test.ts b/test/PosthogAnalytics-test.ts deleted file mode 100644 index 6cb1743051..0000000000 --- a/test/PosthogAnalytics-test.ts +++ /dev/null @@ -1,232 +0,0 @@ -/* -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. -*/ - -import { - Anonymity, - getRedactedCurrentLocation, - IAnonymousEvent, - IPseudonymousEvent, - IRoomEvent, - PosthogAnalytics, -} from '../src/PosthogAnalytics'; - -import SdkConfig from '../src/SdkConfig'; - -class FakePosthog { - public capture; - public init; - public identify; - public reset; - public register; - - constructor() { - this.capture = jest.fn(); - this.init = jest.fn(); - this.identify = jest.fn(); - this.reset = jest.fn(); - this.register = jest.fn(); - } -} - -export interface ITestEvent extends IAnonymousEvent { - key: "jest_test_event"; - properties: { - foo: string; - }; -} - -export interface ITestPseudonymousEvent extends IPseudonymousEvent { - key: "jest_test_pseudo_event"; - properties: { - foo: string; - }; -} - -export interface ITestRoomEvent extends IRoomEvent { - key: "jest_test_room_event"; - properties: { - foo: string; - }; -} - -describe("PosthogAnalytics", () => { - let fakePosthog: FakePosthog; - const shaHashes = { - "42": "73475cb40a568e8da8a045ced110137e159f890ac4da883b6b17dc651b3a8049", - "some": "a6b46dd0d1ae5e86cbc8f37e75ceeb6760230c1ca4ffbcb0c97b96dd7d9c464b", - "pii": "bd75b3e080945674c0351f75e0db33d1e90986fa07b318ea7edf776f5eef38d4", - "foo": "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae", - }; - - beforeEach(() => { - fakePosthog = new FakePosthog(); - - window.crypto = { - subtle: { - digest: async (_, encodedMessage) => { - const message = new TextDecoder().decode(encodedMessage); - const hexHash = shaHashes[message]; - const bytes = []; - for (let c = 0; c < hexHash.length; c += 2) { - bytes.push(parseInt(hexHash.substr(c, 2), 16)); - } - return bytes; - }, - }, - }; - }); - - afterEach(() => { - window.crypto = null; - }); - - describe("Initialisation", () => { - it("Should not be enabled without config being set", () => { - jest.spyOn(SdkConfig, "get").mockReturnValue({}); - const analytics = new PosthogAnalytics(fakePosthog); - expect(analytics.isEnabled()).toBe(false); - }); - - it("Should be enabled if config is set", () => { - jest.spyOn(SdkConfig, "get").mockReturnValue({ - posthog: { - projectApiKey: "foo", - apiHost: "bar", - }, - }); - const analytics = new PosthogAnalytics(fakePosthog); - analytics.setAnonymity(Anonymity.Pseudonymous); - expect(analytics.isEnabled()).toBe(true); - }); - }); - - describe("Tracking", () => { - let analytics: PosthogAnalytics; - - beforeEach(() => { - jest.spyOn(SdkConfig, "get").mockReturnValue({ - posthog: { - projectApiKey: "foo", - apiHost: "bar", - }, - }); - - analytics = new PosthogAnalytics(fakePosthog); - }); - - it("Should pass trackAnonymousEvent() to posthog", async () => { - analytics.setAnonymity(Anonymity.Pseudonymous); - await analytics.trackAnonymousEvent("jest_test_event", { - foo: "bar", - }); - expect(fakePosthog.capture.mock.calls[0][0]).toBe("jest_test_event"); - expect(fakePosthog.capture.mock.calls[0][1]["foo"]).toEqual("bar"); - }); - - it("Should pass trackRoomEvent to posthog", async () => { - analytics.setAnonymity(Anonymity.Pseudonymous); - const roomId = "42"; - await analytics.trackRoomEvent("jest_test_event", roomId, { - foo: "bar", - }); - expect(fakePosthog.capture.mock.calls[0][0]).toBe("jest_test_event"); - expect(fakePosthog.capture.mock.calls[0][1]["foo"]).toEqual("bar"); - expect(fakePosthog.capture.mock.calls[0][1]["hashedRoomId"]) - .toEqual("73475cb40a568e8da8a045ced110137e159f890ac4da883b6b17dc651b3a8049"); - }); - - it("Should pass trackPseudonymousEvent() to posthog", async () => { - analytics.setAnonymity(Anonymity.Pseudonymous); - await analytics.trackPseudonymousEvent("jest_test_pseudo_event", { - foo: "bar", - }); - expect(fakePosthog.capture.mock.calls[0][0]).toBe("jest_test_pseudo_event"); - expect(fakePosthog.capture.mock.calls[0][1]["foo"]).toEqual("bar"); - }); - - it("Should not track pseudonymous messages if anonymous", async () => { - analytics.setAnonymity(Anonymity.Anonymous); - await analytics.trackPseudonymousEvent("jest_test_event", { - foo: "bar", - }); - expect(fakePosthog.capture.mock.calls.length).toBe(0); - }); - - it("Should not track any events if disabled", async () => { - analytics.setAnonymity(Anonymity.Disabled); - await analytics.trackPseudonymousEvent("jest_test_event", { - foo: "bar", - }); - await analytics.trackAnonymousEvent("jest_test_event", { - foo: "bar", - }); - await analytics.trackRoomEvent("room id", "foo", { - foo: "bar", - }); - await analytics.trackPageView(200); - expect(fakePosthog.capture.mock.calls.length).toBe(0); - }); - - it("Should pseudonymise a location of a known screen", async () => { - const location = await getRedactedCurrentLocation( - "https://foo.bar", "#/register/some/pii", "/", Anonymity.Pseudonymous); - expect(location).toBe( - `https://foo.bar/#/register/\ -a6b46dd0d1ae5e86cbc8f37e75ceeb6760230c1ca4ffbcb0c97b96dd7d9c464b/\ -bd75b3e080945674c0351f75e0db33d1e90986fa07b318ea7edf776f5eef38d4`); - }); - - it("Should anonymise a location of a known screen", async () => { - const location = await getRedactedCurrentLocation( - "https://foo.bar", "#/register/some/pii", "/", Anonymity.Anonymous); - expect(location).toBe("https://foo.bar/#/register//"); - }); - - it("Should pseudonymise a location of an unknown screen", async () => { - const location = await getRedactedCurrentLocation( - "https://foo.bar", "#/not_a_screen_name/some/pii", "/", Anonymity.Pseudonymous); - expect(location).toBe( - `https://foo.bar/#//\ -a6b46dd0d1ae5e86cbc8f37e75ceeb6760230c1ca4ffbcb0c97b96dd7d9c464b/\ -bd75b3e080945674c0351f75e0db33d1e90986fa07b318ea7edf776f5eef38d4`); - }); - - it("Should anonymise a location of an unknown screen", async () => { - const location = await getRedactedCurrentLocation( - "https://foo.bar", "#/not_a_screen_name/some/pii", "/", Anonymity.Anonymous); - expect(location).toBe("https://foo.bar/#///"); - }); - - it("Should handle an empty hash", async () => { - const location = await getRedactedCurrentLocation( - "https://foo.bar", "", "/", Anonymity.Anonymous); - expect(location).toBe("https://foo.bar/"); - }); - - it("Should identify the user to posthog if pseudonymous", async () => { - analytics.setAnonymity(Anonymity.Pseudonymous); - await analytics.identifyUser("foo"); - expect(fakePosthog.identify.mock.calls[0][0]) - .toBe("2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae"); - }); - - it("Should not identify the user to posthog if anonymous", async () => { - analytics.setAnonymity(Anonymity.Anonymous); - await analytics.identifyUser("foo"); - expect(fakePosthog.identify.mock.calls.length).toBe(0); - }); - }); -}); diff --git a/tsconfig.json b/tsconfig.json index b982d40b07..b139e8e8d1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,15 +22,10 @@ "es2019", "dom", "dom.iterable" - ], - "paths": { - "posthog-js": [ - "./src/@types/posthog.d.ts" - ] - } + ] }, "include": [ "./src/**/*.ts", "./src/**/*.tsx" - ], + ] } diff --git a/yarn.lock b/yarn.lock index daed6f4377..2a03f640ee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3601,11 +3601,6 @@ fbjs@^0.8.4: setimmediate "^1.0.5" ua-parser-js "^0.7.18" -fflate@^0.4.1: - version "0.4.8" - resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.4.8.tgz#f90b82aefbd8ac174213abb338bd7ef848f0f5ae" - integrity sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA== - file-entry-cache@^6.0.0, file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" @@ -6254,13 +6249,6 @@ postcss@^8.0.2: nanoid "^3.1.23" source-map-js "^0.6.2" -posthog-js@1.12.1: - version "1.12.1" - resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.12.1.tgz#97834ee2574f34ffb5db2f5b07452c847e3c4d27" - integrity sha512-Y3lzcWkS8xFY6Ryj3I4ees7qWP2WGkLw0Arcbk5xaT0+5YlA6UC2jlL/+fN9bz/Bl62EoN3BML901Cuot/QNjg== - dependencies: - fflate "^0.4.1" - prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" From dc7aad1abfbd16808c7eb9f345b4f156e0aed4a3 Mon Sep 17 00:00:00 2001 From: James Salter Date: Tue, 3 Aug 2021 11:55:02 +0100 Subject: [PATCH 067/176] Revert "Revert "Add support for Posthog Analytics under a labs flag"" This reverts commit c5ea2531817e2d254692dd48bc4985184528233d. --- package.json | 1 + src/@types/posthog.d.ts | 748 ++++++++++++++++++ src/Lifecycle.ts | 5 + src/PosthogAnalytics.ts | 355 +++++++++ src/components/structures/MatrixChat.tsx | 6 + .../tabs/user/SecurityUserSettingsTab.js | 2 + src/i18n/strings/en_EN.json | 1 + src/settings/Settings.tsx | 8 + .../PseudonymousAnalyticsController.ts | 26 + test/PosthogAnalytics-test.ts | 232 ++++++ tsconfig.json | 9 +- yarn.lock | 12 + 12 files changed, 1403 insertions(+), 2 deletions(-) create mode 100644 src/@types/posthog.d.ts create mode 100644 src/PosthogAnalytics.ts create mode 100644 src/settings/controllers/PseudonymousAnalyticsController.ts create mode 100644 test/PosthogAnalytics-test.ts diff --git a/package.json b/package.json index 9744aa7685..b7e06fe012 100644 --- a/package.json +++ b/package.json @@ -87,6 +87,7 @@ "pako": "^2.0.3", "parse5": "^6.0.1", "png-chunks-extract": "^1.0.0", + "posthog-js": "1.12.1", "prop-types": "^15.7.2", "qrcode": "^1.4.4", "re-resizable": "^6.9.0", diff --git a/src/@types/posthog.d.ts b/src/@types/posthog.d.ts new file mode 100644 index 0000000000..1ca475cd3b --- /dev/null +++ b/src/@types/posthog.d.ts @@ -0,0 +1,748 @@ +// A clone of the type definitions from posthog-js, stripped of references to transitive +// dependencies which we don't actually use, so that we don't need to install them. +// +// Original file lives in node_modules/posthog/dist/module.d.ts + +/* eslint-disable @typescript-eslint/member-delimiter-style */ +/* eslint-disable @typescript-eslint/naming-convention */ +/* eslint-disable camelcase */ + +// Type definitions for exported methods + +declare class posthog { + /** + * This function initializes a new instance of the PostHog capturing object. + * All new instances are added to the main posthog object as sub properties (such as + * posthog.library_name) and also returned by this function. To define a + * second instance on the page, you would call: + * + * posthog.init('new token', { your: 'config' }, 'library_name'); + * + * and use it like so: + * + * posthog.library_name.capture(...); + * + * @param {String} token Your PostHog API token + * @param {Object} [config] A dictionary of config options to override. See a list of default config options. + * @param {String} [name] The name for the new posthog instance that you want created + */ + static init(token: string, config?: posthog.Config, name?: string): posthog + + /** + * Clears super properties and generates a new random distinct_id for this instance. + * Useful for clearing data when a user logs out. + */ + static reset(reset_device_id?: boolean): void + + /** + * Capture an event. This is the most important and + * frequently used PostHog function. + * + * ### Usage: + * + * // capture an event named 'Registered' + * posthog.capture('Registered', {'Gender': 'Male', 'Age': 21}); + * + * // capture an event using navigator.sendBeacon + * posthog.capture('Left page', {'duration_seconds': 35}, {transport: 'sendBeacon'}); + * + * @param {String} event_name The name of the event. This can be anything the user does - 'Button Click', 'Sign Up', 'Item Purchased', etc. + * @param {Object} [properties] A set of properties to include with the event you're sending. These describe the user who did the event or details about the event itself. + * @param {Object} [options] Optional configuration for this capture request. + * @param {String} [options.transport] Transport method for network request ('XHR' or 'sendBeacon'). + */ + static capture( + event_name: string, + properties?: posthog.Properties, + options?: { transport: 'XHR' | 'sendBeacon' } + ): posthog.CaptureResult + + /** + * Capture a page view event, which is currently ignored by the server. + * This function is called by default on page load unless the + * capture_pageview configuration variable is false. + * + * @param {String} [page] The url of the page to record. If you don't include this, it defaults to the current url. + * @api private + */ + static capture_pageview(page?: string): void + + /** + * Register a set of super properties, which are included with all + * events. This will overwrite previous super property values. + * + * ### Usage: + * + * // register 'Gender' as a super property + * posthog.register({'Gender': 'Female'}); + * + * // register several super properties when a user signs up + * posthog.register({ + * 'Email': 'jdoe@example.com', + * 'Account Type': 'Free' + * }); + * + * @param {Object} properties An associative array of properties to store about the user + * @param {Number} [days] How many days since the user's last visit to store the super properties + */ + static register(properties: posthog.Properties, days?: number): void + + /** + * Register a set of super properties only once. This will not + * overwrite previous super property values, unlike register(). + * + * ### Usage: + * + * // register a super property for the first time only + * posthog.register_once({ + * 'First Login Date': new Date().toISOString() + * }); + * + * ### Notes: + * + * If default_value is specified, current super properties + * with that value will be overwritten. + * + * @param {Object} properties An associative array of properties to store about the user + * @param {*} [default_value] Value to override if already set in super properties (ex: 'False') Default: 'None' + * @param {Number} [days] How many days since the users last visit to store the super properties + */ + static register_once(properties: posthog.Properties, default_value?: posthog.Property, days?: number): void + + /** + * Delete a super property stored with the current user. + * + * @param {String} property The name of the super property to remove + */ + static unregister(property: string): void + + /** + * Identify a user with a unique ID instead of a PostHog + * randomly generated distinct_id. If the method is never called, + * then unique visitors will be identified by a UUID generated + * the first time they visit the site. + * + * If user properties are passed, they are also sent to posthog. + * + * ### Usage: + * + * posthog.identify('[user unique id]') + * posthog.identify('[user unique id]', { email: 'john@example.com' }) + * posthog.identify('[user unique id]', {}, { referral_code: '12345' }) + * + * ### Notes: + * + * You can call this function to overwrite a previously set + * unique ID for the current user. PostHog cannot translate + * between IDs at this time, so when you change a user's ID + * they will appear to be a new user. + * + * When used alone, posthog.identify will change the user's + * distinct_id to the unique ID provided. When used in tandem + * with posthog.alias, it will allow you to identify based on + * unique ID and map that back to the original, anonymous + * distinct_id given to the user upon her first arrival to your + * site (thus connecting anonymous pre-signup activity to + * post-signup activity). Though the two work together, do not + * call identify() at the same time as alias(). Calling the two + * at the same time can cause a race condition, so it is best + * practice to call identify on the original, anonymous ID + * right after you've aliased it. + * + * @param {String} [unique_id] A string that uniquely identifies a user. If not provided, the distinct_id currently in the persistent store (cookie or localStorage) will be used. + * @param {Object} [userProperties] Optional: An associative array of properties to store about the user + * @param {Object} [userPropertiesToSetOnce] Optional: An associative array of properties to store about the user. If property is previously set, this does not override that value. + */ + static identify( + unique_id?: string, + userPropertiesToSet?: posthog.Properties, + userPropertiesToSetOnce?: posthog.Properties + ): void + + /** + * Create an alias, which PostHog will use to link two distinct_ids going forward (not retroactively). + * Multiple aliases can map to the same original ID, but not vice-versa. Aliases can also be chained - the + * following is a valid scenario: + * + * posthog.alias('new_id', 'existing_id'); + * ... + * posthog.alias('newer_id', 'new_id'); + * + * If the original ID is not passed in, we will use the current distinct_id - probably the auto-generated GUID. + * + * ### Notes: + * + * The best practice is to call alias() when a unique ID is first created for a user + * (e.g., when a user first registers for an account and provides an email address). + * alias() should never be called more than once for a given user, except to + * chain a newer ID to a previously new ID, as described above. + * + * @param {String} alias A unique identifier that you want to use for this user in the future. + * @param {String} [original] The current identifier being used for this user. + */ + static alias(alias: string, original?: string): posthog.CaptureResult | number + + /** + * Update the configuration of a posthog library instance. + * + * The default config is: + * + * { + * // HTTP method for capturing requests + * api_method: 'POST' + * + * // transport for sending requests ('XHR' or 'sendBeacon') + * // NB: sendBeacon should only be used for scenarios such as + * // page unload where a "best-effort" attempt to send is + * // acceptable; the sendBeacon API does not support callbacks + * // or any way to know the result of the request. PostHog + * // capturing via sendBeacon will not support any event- + * // batching or retry mechanisms. + * api_transport: 'XHR' + * + * // Automatically capture clicks, form submissions and change events + * autocapture: true + * + * // Capture rage clicks (beta) - useful for session recording + * rageclick: false + * + * // super properties cookie expiration (in days) + * cookie_expiration: 365 + * + * // super properties span subdomains + * cross_subdomain_cookie: true + * + * // debug mode + * debug: false + * + * // if this is true, the posthog cookie or localStorage entry + * // will be deleted, and no user persistence will take place + * disable_persistence: false + * + * // if this is true, PostHog will automatically determine + * // City, Region and Country data using the IP address of + * //the client + * ip: true + * + * // opt users out of capturing by this PostHog instance by default + * opt_out_capturing_by_default: false + * + * // opt users out of browser data storage by this PostHog instance by default + * opt_out_persistence_by_default: false + * + * // persistence mechanism used by opt-in/opt-out methods - cookie + * // or localStorage - falls back to cookie if localStorage is unavailable + * opt_out_capturing_persistence_type: 'localStorage' + * + * // customize the name of cookie/localStorage set by opt-in/opt-out methods + * opt_out_capturing_cookie_prefix: null + * + * // type of persistent store for super properties (cookie/ + * // localStorage) if set to 'localStorage', any existing + * // posthog cookie value with the same persistence_name + * // will be transferred to localStorage and deleted + * persistence: 'cookie' + * + * // name for super properties persistent store + * persistence_name: '' + * + * // names of properties/superproperties which should never + * // be sent with capture() calls + * property_blacklist: [] + * + * // if this is true, posthog cookies will be marked as + * // secure, meaning they will only be transmitted over https + * secure_cookie: false + * + * // should we capture a page view on page load + * capture_pageview: true + * + * // if you set upgrade to be true, the library will check for + * // a cookie from our old js library and import super + * // properties from it, then the old cookie is deleted + * // The upgrade config option only works in the initialization, + * // so make sure you set it when you create the library. + * upgrade: false + * + * // extra HTTP request headers to set for each API request, in + * // the format {'Header-Name': value} + * xhr_headers: {} + * + * // protocol for fetching in-app message resources, e.g. + * // 'https://' or 'http://'; defaults to '//' (which defers to the + * // current page's protocol) + * inapp_protocol: '//' + * + * // whether to open in-app message link in new tab/window + * inapp_link_new_window: false + * + * // a set of rrweb config options that PostHog users can configure + * // see https://github.com/rrweb-io/rrweb/blob/master/guide.md + * session_recording: { + * blockClass: 'ph-no-capture', + * blockSelector: null, + * ignoreClass: 'ph-ignore-input', + * maskAllInputs: false, + * maskInputOptions: {}, + * maskInputFn: null, + * slimDOMOptions: {}, + * collectFonts: false + * } + * + * // prevent autocapture from capturing any attribute names on elements + * mask_all_element_attributes: false + * + * // prevent autocapture from capturing textContent on all elements + * mask_all_text: false + * + * // will disable requests to the /decide endpoint (please review documentation for details) + * // autocapture, feature flags, compression and session recording will be disabled when set to `true` + * advanced_disable_decide: false + * + * } + * + * + * @param {Object} config A dictionary of new configuration values to update + */ + static set_config(config: posthog.Config): void + + /** + * returns the current config object for the library. + */ + static get_config(prop_name: T): posthog.Config[T] + + /** + * Returns the value of the super property named property_name. If no such + * property is set, get_property() will return the undefined value. + * + * ### Notes: + * + * get_property() can only be called after the PostHog library has finished loading. + * init() has a loaded function available to handle this automatically. For example: + * + * // grab value for 'user_id' after the posthog library has loaded + * posthog.init('YOUR PROJECT TOKEN', { + * loaded: function(posthog) { + * user_id = posthog.get_property('user_id'); + * } + * }); + * + * @param {String} property_name The name of the super property you want to retrieve + */ + static get_property(property_name: string): posthog.Property | undefined + + /** + * Returns the current distinct id of the user. This is either the id automatically + * generated by the library or the id that has been passed by a call to identify(). + * + * ### Notes: + * + * get_distinct_id() can only be called after the PostHog library has finished loading. + * init() has a loaded function available to handle this automatically. For example: + * + * // set distinct_id after the posthog library has loaded + * posthog.init('YOUR PROJECT TOKEN', { + * loaded: function(posthog) { + * distinct_id = posthog.get_distinct_id(); + * } + * }); + */ + static get_distinct_id(): string + + /** + * Opt the user out of data capturing and cookies/localstorage for this PostHog instance + * + * ### Usage + * + * // opt user out + * posthog.opt_out_capturing(); + * + * // opt user out with different cookie configuration from PostHog instance + * posthog.opt_out_capturing({ + * cookie_expiration: 30, + * secure_cookie: true + * }); + * + * @param {Object} [options] A dictionary of config options to override + * @param {boolean} [options.clear_persistence=true] If true, will delete all data stored by the sdk in persistence + * @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable + * @param {string} [options.cookie_prefix=__ph_opt_in_out] Custom prefix to be used in the cookie/localstorage name + * @param {Number} [options.cookie_expiration] Number of days until the opt-in cookie expires (overrides value specified in this PostHog instance's config) + * @param {boolean} [options.cross_subdomain_cookie] Whether the opt-in cookie is set as cross-subdomain or not (overrides value specified in this PostHog instance's config) + * @param {boolean} [options.secure_cookie] Whether the opt-in cookie is set as secure or not (overrides value specified in this PostHog instance's config) + */ + static opt_out_capturing(options?: posthog.OptInOutCapturingOptions): void + + /** + * Opt the user in to data capturing and cookies/localstorage for this PostHog instance + * + * ### Usage + * + * // opt user in + * posthog.opt_in_capturing(); + * + * // opt user in with specific event name, properties, cookie configuration + * posthog.opt_in_capturing({ + * capture_event_name: 'User opted in', + * capture_event_properties: { + * 'Email': 'jdoe@example.com' + * }, + * cookie_expiration: 30, + * secure_cookie: true + * }); + * + * @param {Object} [options] A dictionary of config options to override + * @param {function} [options.capture] Function used for capturing a PostHog event to record the opt-in action (default is this PostHog instance's capture method) + * @param {string} [options.capture_event_name=$opt_in] Event name to be used for capturing the opt-in action + * @param {Object} [options.capture_properties] Set of properties to be captured along with the opt-in action + * @param {boolean} [options.enable_persistence=true] If true, will re-enable sdk persistence + * @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable + * @param {string} [options.cookie_prefix=__ph_opt_in_out] Custom prefix to be used in the cookie/localstorage name + * @param {Number} [options.cookie_expiration] Number of days until the opt-in cookie expires (overrides value specified in this PostHog instance's config) + * @param {boolean} [options.cross_subdomain_cookie] Whether the opt-in cookie is set as cross-subdomain or not (overrides value specified in this PostHog instance's config) + * @param {boolean} [options.secure_cookie] Whether the opt-in cookie is set as secure or not (overrides value specified in this PostHog instance's config) + */ + static opt_in_capturing(options?: posthog.OptInOutCapturingOptions): void + + /** + * Check whether the user has opted out of data capturing and cookies/localstorage for this PostHog instance + * + * ### Usage + * + * const has_opted_out = posthog.has_opted_out_capturing(); + * // use has_opted_out value + * + * @param {Object} [options] A dictionary of config options to override + * @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable + * @param {string} [options.cookie_prefix=__ph_opt_in_out] Custom prefix to be used in the cookie/localstorage name + * @returns {boolean} current opt-out status + */ + static has_opted_out_capturing(options?: posthog.HasOptedInOutCapturingOptions): boolean + + /** + * Check whether the user has opted in to data capturing and cookies/localstorage for this PostHog instance + * + * ### Usage + * + * const has_opted_in = posthog.has_opted_in_capturing(); + * // use has_opted_in value + * + * @param {Object} [options] A dictionary of config options to override + * @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable + * @param {string} [options.cookie_prefix=__ph_opt_in_out] Custom prefix to be used in the cookie/localstorage name + * @returns {boolean} current opt-in status + */ + static has_opted_in_capturing(options?: posthog.HasOptedInOutCapturingOptions): boolean + + /** + * Clear the user's opt in/out status of data capturing and cookies/localstorage for this PostHog instance + * + * ### Usage + * + * // clear user's opt-in/out status + * posthog.clear_opt_in_out_capturing(); + * + * // clear user's opt-in/out status with specific cookie configuration - should match + * // configuration used when opt_in_capturing/opt_out_capturing methods were called. + * posthog.clear_opt_in_out_capturing({ + * cookie_expiration: 30, + * secure_cookie: true + * }); + * + * @param {Object} [options] A dictionary of config options to override + * @param {boolean} [options.enable_persistence=true] If true, will re-enable sdk persistence + * @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable + * @param {string} [options.cookie_prefix=__ph_opt_in_out] Custom prefix to be used in the cookie/localstorage name + * @param {Number} [options.cookie_expiration] Number of days until the opt-in cookie expires (overrides value specified in this PostHog instance's config) + * @param {boolean} [options.cross_subdomain_cookie] Whether the opt-in cookie is set as cross-subdomain or not (overrides value specified in this PostHog instance's config) + * @param {boolean} [options.secure_cookie] Whether the opt-in cookie is set as secure or not (overrides value specified in this PostHog instance's config) + */ + static clear_opt_in_out_capturing(options?: posthog.ClearOptInOutCapturingOptions): void + + /* + * See if feature flag is enabled for user. + * + * ### Usage: + * + * if(posthog.isFeatureEnabled('beta-feature')) { // do something } + * + * @param {Object|String} prop Key of the feature flag. + * @param {Object|String} options (optional) If {send_event: false}, we won't send an $feature_flag_call event to PostHog. + */ + static isFeatureEnabled(key: string, options?: posthog.isFeatureEnabledOptions): boolean + + /* + * See if feature flags are available. + * + * ### Usage: + * + * posthog.onFeatureFlags(function(featureFlags) { // do something }) + * + * @param {Function} [callback] The callback function will be called once the feature flags are ready. It'll return a list of feature flags enabled for the user. + */ + static onFeatureFlags(callback: (flags: string[]) => void): false | undefined + + /* + * Reload all feature flags for the user. + * + * ### Usage: + * + * posthog.reloadFeatureFlags() + */ + static reloadFeatureFlags(): void + + static toString(): string + + /* Will log all capture requests to the Javascript console, including event properties for easy debugging */ + static debug(): void + + /* + * Starts session recording and updates disable_session_recording to false. + * Used for manual session recording management. By default, session recording is enabled and + * starts automatically. + * + * ### Usage: + * + * posthog.startSessionRecording() + */ + static startSessionRecording(): void + + /* + * Stops session recording and updates disable_session_recording to true. + * + * ### Usage: + * + * posthog.stopSessionRecording() + */ + static stopSessionRecording(): void + + /* + * Check if session recording is currently running. + * + * ### Usage: + * + * const isSessionRecordingOn = posthog.sessionRecordingStarted() + */ + static sessionRecordingStarted(): boolean +} + +declare namespace posthog { + /* eslint-disable @typescript-eslint/no-explicit-any */ + type Property = any; + type Properties = Record; + type CaptureResult = { event: string; properties: Properties } | undefined; + type CaptureCallback = (response: any, data: any) => void; + /* eslint-enable @typescript-eslint/no-explicit-any */ + + interface Config { + api_host?: string + api_method?: string + api_transport?: string + autocapture?: boolean + rageclick?: boolean + cdn?: string + cross_subdomain_cookie?: boolean + persistence?: 'localStorage' | 'cookie' | 'memory' + persistence_name?: string + cookie_name?: string + loaded?: (posthog_instance: typeof posthog) => void + store_google?: boolean + save_referrer?: boolean + test?: boolean + verbose?: boolean + img?: boolean + capture_pageview?: boolean + debug?: boolean + cookie_expiration?: number + upgrade?: boolean + disable_session_recording?: boolean + disable_persistence?: boolean + disable_cookie?: boolean + secure_cookie?: boolean + ip?: boolean + opt_out_capturing_by_default?: boolean + opt_out_persistence_by_default?: boolean + opt_out_capturing_persistence_type?: 'localStorage' | 'cookie' + opt_out_capturing_cookie_prefix?: string | null + respect_dnt?: boolean + property_blacklist?: string[] + xhr_headers?: { [header_name: string]: string } + inapp_protocol?: string + inapp_link_new_window?: boolean + request_batching?: boolean + sanitize_properties?: (properties: posthog.Properties, event_name: string) => posthog.Properties + properties_string_max_length?: number + mask_all_element_attributes?: boolean + mask_all_text?: boolean + advanced_disable_decide?: boolean + } + + interface OptInOutCapturingOptions { + clear_persistence: boolean + persistence_type: string + cookie_prefix: string + cookie_expiration: number + cross_subdomain_cookie: boolean + secure_cookie: boolean + } + + interface HasOptedInOutCapturingOptions { + persistence_type: string + cookie_prefix: string + } + + interface ClearOptInOutCapturingOptions { + enable_persistence: boolean + persistence_type: string + cookie_prefix: string + cookie_expiration: number + cross_subdomain_cookie: boolean + secure_cookie: boolean + } + + interface isFeatureEnabledOptions { + send_event: boolean + } + + export class persistence { + static properties(): posthog.Properties + + static load(): void + + static save(): void + + static remove(): void + + static clear(): void + + /** + * @param {Object} props + * @param {*=} default_value + * @param {number=} days + */ + static register_once(props: Properties, default_value?: Property, days?: number): boolean + + /** + * @param {Object} props + * @param {number=} days + */ + static register(props: posthog.Properties, days?: number): boolean + + static unregister(prop: string): void + + static update_campaign_params(): void + + static update_search_keyword(referrer: string): void + + static update_referrer_info(referrer: string): void + + static get_referrer_info(): posthog.Properties + + static safe_merge(props: posthog.Properties): posthog.Properties + + static update_config(config: posthog.Config): void + + static set_disabled(disabled: boolean): void + + static set_cross_subdomain(cross_subdomain: boolean): void + + static get_cross_subdomain(): boolean + + static set_secure(secure: boolean): void + + static set_event_timer(event_name: string, timestamp: Date): void + + static remove_event_timer(event_name: string): Date | undefined + } + + export class people { + /* + * Set properties on a user record. + * + * ### Usage: + * + * posthog.people.set('gender', 'm'); + * + * // or set multiple properties at once + * posthog.people.set({ + * 'Company': 'Acme', + * 'Plan': 'Premium', + * 'Upgrade date': new Date() + * }); + * // properties can be strings, integers, dates, or lists + * + * @param {Object|String} prop If a string, this is the name of the property. If an object, this is an associative array of names and values. + * @param {*} [to] A value to set on the given property name + * @param {Function} [callback] If provided, the callback will be called after capturing the event. + */ + static set( + prop: posthog.Properties | string, + to?: posthog.Property, + callback?: posthog.CaptureCallback + ): posthog.Properties + + /* + * Set properties on a user record, only if they do not yet exist. + * This will not overwrite previous people property values, unlike + * people.set(). + * + * ### Usage: + * + * posthog.people.set_once('First Login Date', new Date()); + * + * // or set multiple properties at once + * posthog.people.set_once({ + * 'First Login Date': new Date(), + * 'Starting Plan': 'Premium' + * }); + * + * // properties can be strings, integers or dates + * + * @param {Object|String} prop If a string, this is the name of the property. If an object, this is an associative array of names and values. + * @param {*} [to] A value to set on the given property name + * @param {Function} [callback] If provided, the callback will be called after capturing the event. + */ + static set_once( + prop: posthog.Properties | string, + to?: posthog.Property, + callback?: posthog.CaptureCallback + ): posthog.Properties + + static toString(): string + } + + export class featureFlags { + static getFlags(): string[] + + static reloadFeatureFlags(): void + + /* + * See if feature flag is enabled for user. + * + * ### Usage: + * + * if(posthog.isFeatureEnabled('beta-feature')) { // do something } + * + * @param {Object|String} prop Key of the feature flag. + * @param {Object|String} options (optional) If {send_event: false}, we won't send an $feature_flag_call event to PostHog. + */ + static isFeatureEnabled(key: string, options?: { send_event?: boolean }): boolean + + /* + * See if feature flags are available. + * + * ### Usage: + * + * posthog.onFeatureFlags(function(featureFlags) { // do something }) + * + * @param {Function} [callback] The callback function will be called once the feature flags are ready. It'll return a list of feature flags enabled for the user. + */ + static onFeatureFlags(callback: (flags: string[]) => void): false | undefined + } + + export class feature_flags extends featureFlags {} +} + +export type PostHog = typeof posthog; + +export default posthog; diff --git a/src/Lifecycle.ts b/src/Lifecycle.ts index 410124a637..e48fd52cb1 100644 --- a/src/Lifecycle.ts +++ b/src/Lifecycle.ts @@ -48,6 +48,7 @@ import { Jitsi } from "./widgets/Jitsi"; import { SSO_HOMESERVER_URL_KEY, SSO_ID_SERVER_URL_KEY, SSO_IDP_ID_KEY } from "./BasePlatform"; import ThreepidInviteStore from "./stores/ThreepidInviteStore"; import CountlyAnalytics from "./CountlyAnalytics"; +import { PosthogAnalytics } from "./PosthogAnalytics"; import CallHandler from './CallHandler'; import LifecycleCustomisations from "./customisations/Lifecycle"; import ErrorDialog from "./components/views/dialogs/ErrorDialog"; @@ -573,6 +574,8 @@ async function doSetLoggedIn( await abortLogin(); } + PosthogAnalytics.instance.updateAnonymityFromSettings(credentials.userId); + Analytics.setLoggedIn(credentials.guest, credentials.homeserverUrl); MatrixClientPeg.replaceUsingCreds(credentials); @@ -700,6 +703,8 @@ export function logout(): void { CountlyAnalytics.instance.enable(/* anonymous = */ true); } + PosthogAnalytics.instance.logout(); + if (MatrixClientPeg.get().isGuest()) { // logout doesn't work for guest sessions // Also we sometimes want to re-log in a guest session if we abort the login. diff --git a/src/PosthogAnalytics.ts b/src/PosthogAnalytics.ts new file mode 100644 index 0000000000..860a155aff --- /dev/null +++ b/src/PosthogAnalytics.ts @@ -0,0 +1,355 @@ +/* +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. +*/ + +import posthog, { PostHog } from 'posthog-js'; +import PlatformPeg from './PlatformPeg'; +import SdkConfig from './SdkConfig'; +import SettingsStore from './settings/SettingsStore'; + +/* Posthog analytics tracking. + * + * Anonymity behaviour is as follows: + * + * - If Posthog isn't configured in `config.json`, events are not sent. + * - If [Do Not Track](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/doNotTrack) is + * enabled, events are not sent (this detection is built into posthog and turned on via the + * `respect_dnt` flag being passed to `posthog.init`). + * - If the `feature_pseudonymous_analytics_opt_in` labs flag is `true`, track pseudonomously, i.e. + * hash all matrix identifiers in tracking events (user IDs, room IDs etc) using SHA-256. + * - Otherwise, if the existing `analyticsOptIn` flag is `true`, track anonymously, i.e. + * redact all matrix identifiers in tracking events. + * - If both flags are false or not set, events are not sent. + */ + +interface IEvent { + // The event name that will be used by PostHog. Event names should use snake_case. + eventName: string; + + // The properties of the event that will be stored in PostHog. This is just a placeholder, + // extending interfaces must override this with a concrete definition to do type validation. + properties: {}; +} + +export enum Anonymity { + Disabled, + Anonymous, + Pseudonymous +} + +// If an event extends IPseudonymousEvent, the event contains pseudonymous data +// that won't be sent unless the user has explicitly consented to pseudonymous tracking. +// For example, it might contain hashed user IDs or room IDs. +// Such events will be automatically dropped if PosthogAnalytics.anonymity isn't set to Pseudonymous. +export interface IPseudonymousEvent extends IEvent {} + +// If an event extends IAnonymousEvent, the event strictly contains *only* anonymous data; +// i.e. no identifiers that can be associated with the user. +export interface IAnonymousEvent extends IEvent {} + +export interface IRoomEvent extends IPseudonymousEvent { + hashedRoomId: string; +} + +interface IPageView extends IAnonymousEvent { + eventName: "$pageview"; + properties: { + durationMs?: number; + screen?: string; + }; +} + +const hashHex = async (input: string): Promise => { + const buf = new TextEncoder().encode(input); + const digestBuf = await window.crypto.subtle.digest("sha-256", buf); + return [...new Uint8Array(digestBuf)].map((b: number) => b.toString(16).padStart(2, "0")).join(""); +}; + +const whitelistedScreens = new Set([ + "register", "login", "forgot_password", "soft_logout", "new", "settings", "welcome", "home", "start", "directory", + "start_sso", "start_cas", "groups", "complete_security", "post_registration", "room", "user", "group", +]); + +export async function getRedactedCurrentLocation( + origin: string, + hash: string, + pathname: string, + anonymity: Anonymity, +): Promise { + // Redact PII from the current location. + // If anonymous is true, redact entirely, if false, substitute it with a hash. + // For known screens, assumes a URL structure of //might/be/pii + if (origin.startsWith('file://')) { + pathname = "//"; + } + + let hashStr; + if (hash == "") { + hashStr = ""; + } else { + let [beforeFirstSlash, screen, ...parts] = hash.split("/"); + + if (!whitelistedScreens.has(screen)) { + screen = ""; + } + + for (let i = 0; i < parts.length; i++) { + parts[i] = anonymity === Anonymity.Anonymous ? `` : await hashHex(parts[i]); + } + + hashStr = `${beforeFirstSlash}/${screen}/${parts.join("/")}`; + } + return origin + pathname + hashStr; +} + +interface PlatformProperties { + appVersion: string; + appPlatform: string; +} + +export class PosthogAnalytics { + /* Wrapper for Posthog analytics. + * 3 modes of anonymity are supported, governed by this.anonymity + * - Anonymity.Disabled means *no data* is passed to posthog + * - Anonymity.Anonymous means all identifers will be redacted before being passed to posthog + * - Anonymity.Pseudonymous means all identifiers will be hashed via SHA-256 before being passed + * to Posthog + * + * To update anonymity, call updateAnonymityFromSettings() or you can set it directly via setAnonymity(). + * + * To pass an event to Posthog: + * + * 1. Declare a type for the event, extending IAnonymousEvent, IPseudonymousEvent or IRoomEvent. + * 2. Call the appropriate track*() method. Pseudonymous events will be dropped when anonymity is + * Anonymous or Disabled; Anonymous events will be dropped when anonymity is Disabled. + */ + + private anonymity = Anonymity.Disabled; + // set true during the constructor if posthog config is present, otherwise false + private enabled = false; + private static _instance = null; + private platformSuperProperties = {}; + + public static get instance(): PosthogAnalytics { + if (!this._instance) { + this._instance = new PosthogAnalytics(posthog); + } + return this._instance; + } + + constructor(private readonly posthog: PostHog) { + const posthogConfig = SdkConfig.get()["posthog"]; + if (posthogConfig) { + this.posthog.init(posthogConfig.projectApiKey, { + api_host: posthogConfig.apiHost, + autocapture: false, + mask_all_text: true, + mask_all_element_attributes: true, + // This only triggers on page load, which for our SPA isn't particularly useful. + // Plus, the .capture call originating from somewhere in posthog makes it hard + // to redact URLs, which requires async code. + // + // To raise this manually, just call .capture("$pageview") or posthog.capture_pageview. + capture_pageview: false, + sanitize_properties: this.sanitizeProperties, + respect_dnt: true, + }); + this.enabled = true; + } else { + this.enabled = false; + } + } + + private sanitizeProperties = (properties: posthog.Properties): posthog.Properties => { + // Callback from posthog to sanitize properties before sending them to the server. + // + // Here we sanitize posthog's built in properties which leak PII e.g. url reporting. + // See utils.js _.info.properties in posthog-js. + + // Replace the $current_url with a redacted version. + // $redacted_current_url is injected by this class earlier in capture(), as its generation + // is async and can't be done in this non-async callback. + if (!properties['$redacted_current_url']) { + console.log("$redacted_current_url not set in sanitizeProperties, will drop $current_url entirely"); + } + properties['$current_url'] = properties['$redacted_current_url']; + delete properties['$redacted_current_url']; + + if (this.anonymity == Anonymity.Anonymous) { + // drop referrer information for anonymous users + properties['$referrer'] = null; + properties['$referring_domain'] = null; + properties['$initial_referrer'] = null; + properties['$initial_referring_domain'] = null; + + // drop device ID, which is a UUID persisted in local storage + properties['$device_id'] = null; + } + + return properties; + }; + + private static getAnonymityFromSettings(): Anonymity { + // determine the current anonymity level based on current user settings + + // "Send anonymous usage data which helps us improve Element. This will use a cookie." + const analyticsOptIn = SettingsStore.getValue("analyticsOptIn", null, true); + + // (proposed wording) "Send pseudonymous usage data which helps us improve Element. This will use a cookie." + // + // TODO: Currently, this is only a labs flag, for testing purposes. + const pseudonumousOptIn = SettingsStore.getValue("feature_pseudonymous_analytics_opt_in", null, true); + + let anonymity; + if (pseudonumousOptIn) { + anonymity = Anonymity.Pseudonymous; + } else if (analyticsOptIn) { + anonymity = Anonymity.Anonymous; + } else { + anonymity = Anonymity.Disabled; + } + + return anonymity; + } + + private registerSuperProperties(properties: posthog.Properties) { + if (this.enabled) { + this.posthog.register(properties); + } + } + + private static async getPlatformProperties(): Promise { + const platform = PlatformPeg.get(); + let appVersion; + try { + appVersion = await platform.getAppVersion(); + } catch (e) { + // this happens if no version is set i.e. in dev + appVersion = "unknown"; + } + + return { + appVersion, + appPlatform: platform.getHumanReadableName(), + }; + } + + private async capture(eventName: string, properties: posthog.Properties) { + if (!this.enabled) { + return; + } + const { origin, hash, pathname } = window.location; + properties['$redacted_current_url'] = await getRedactedCurrentLocation( + origin, hash, pathname, this.anonymity); + this.posthog.capture(eventName, properties); + } + + public isEnabled(): boolean { + return this.enabled; + } + + public setAnonymity(anonymity: Anonymity): void { + // Update this.anonymity. + // This is public for testing purposes, typically you want to call updateAnonymityFromSettings + // to ensure this value is in step with the user's settings. + if (this.enabled && (anonymity == Anonymity.Disabled || anonymity == Anonymity.Anonymous)) { + // when transitioning to Disabled or Anonymous ensure we clear out any prior state + // set in posthog e.g. distinct ID + this.posthog.reset(); + // Restore any previously set platform super properties + this.registerSuperProperties(this.platformSuperProperties); + } + this.anonymity = anonymity; + } + + public async identifyUser(userId: string): Promise { + if (this.anonymity == Anonymity.Pseudonymous) { + this.posthog.identify(await hashHex(userId)); + } + } + + public getAnonymity(): Anonymity { + return this.anonymity; + } + + public logout(): void { + if (this.enabled) { + this.posthog.reset(); + } + this.setAnonymity(Anonymity.Anonymous); + } + + public async trackPseudonymousEvent( + eventName: E["eventName"], + properties: E["properties"] = {}, + ) { + if (this.anonymity == Anonymity.Anonymous || this.anonymity == Anonymity.Disabled) return; + await this.capture(eventName, properties); + } + + public async trackAnonymousEvent( + eventName: E["eventName"], + properties: E["properties"] = {}, + ): Promise { + if (this.anonymity == Anonymity.Disabled) return; + await this.capture(eventName, properties); + } + + public async trackRoomEvent( + eventName: E["eventName"], + roomId: string, + properties: Omit, + ): Promise { + const updatedProperties = { + ...properties, + hashedRoomId: roomId ? await hashHex(roomId) : null, + }; + await this.trackPseudonymousEvent(eventName, updatedProperties); + } + + public async trackPageView(durationMs: number): Promise { + const hash = window.location.hash; + + let screen = null; + const split = hash.split("/"); + if (split.length >= 2) { + screen = split[1]; + } + + await this.trackAnonymousEvent("$pageview", { + durationMs, + screen, + }); + } + + public async updatePlatformSuperProperties(): Promise { + // Update super properties in posthog with our platform (app version, platform). + // These properties will be subsequently passed in every event. + // + // This only needs to be done once per page lifetime. Note that getPlatformProperties + // is async and can involve a network request if we are running in a browser. + this.platformSuperProperties = await PosthogAnalytics.getPlatformProperties(); + this.registerSuperProperties(this.platformSuperProperties); + } + + public async updateAnonymityFromSettings(userId?: string): Promise { + // Update this.anonymity based on the user's analytics opt-in settings + // Identify the user (via hashed user ID) to posthog if anonymity is pseudonmyous + this.setAnonymity(PosthogAnalytics.getAnonymityFromSettings()); + if (userId && this.getAnonymity() == Anonymity.Pseudonymous) { + await this.identifyUser(userId); + } + } +} diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 8cfe35c4cf..60c78b5f9e 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -107,6 +107,7 @@ import UIStore, { UI_EVENTS } from "../../stores/UIStore"; import SoftLogout from './auth/SoftLogout'; import { makeRoomPermalink } from "../../utils/permalinks/Permalinks"; import { copyPlaintext } from "../../utils/strings"; +import { PosthogAnalytics } from '../../PosthogAnalytics'; /** constants for MatrixChat.state.view */ export enum Views { @@ -387,6 +388,10 @@ export default class MatrixChat extends React.PureComponent { if (SettingsStore.getValue("analyticsOptIn")) { Analytics.enable(); } + + PosthogAnalytics.instance.updateAnonymityFromSettings(); + PosthogAnalytics.instance.updatePlatformSuperProperties(); + CountlyAnalytics.instance.enable(/* anonymous = */ true); } @@ -443,6 +448,7 @@ export default class MatrixChat extends React.PureComponent { const durationMs = this.stopPageChangeTimer(); Analytics.trackPageChange(durationMs); CountlyAnalytics.instance.trackPageChange(durationMs); + PosthogAnalytics.instance.trackPageView(durationMs); } if (this.focusComposer) { dis.fire(Action.FocusSendMessageComposer); diff --git a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js index 79d501e712..25b0b86cb1 100644 --- a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js @@ -36,6 +36,7 @@ import { UIFeature } from "../../../../../settings/UIFeature"; import { isE2eAdvancedPanelPossible } from "../../E2eAdvancedPanel"; import CountlyAnalytics from "../../../../../CountlyAnalytics"; import { replaceableComponent } from "../../../../../utils/replaceableComponent"; +import { PosthogAnalytics } from "../../../../../PosthogAnalytics"; export class IgnoredUser extends React.Component { static propTypes = { @@ -106,6 +107,7 @@ export default class SecurityUserSettingsTab extends React.Component { _updateAnalytics = (checked) => { checked ? Analytics.enable() : Analytics.disable(); CountlyAnalytics.instance.enable(/* anonymous = */ !checked); + PosthogAnalytics.instance.updateAnonymityFromSettings(MatrixClientPeg.get().getUserId()); }; _onExportE2eKeysClicked = () => { diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 3ad8daa85c..87cd9afb5b 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -813,6 +813,7 @@ "Show message previews for reactions in DMs": "Show message previews for reactions in DMs", "Show message previews for reactions in all rooms": "Show message previews for reactions in all rooms", "Offline encrypted messaging using dehydrated devices": "Offline encrypted messaging using dehydrated devices", + "Send pseudonymous analytics data": "Send pseudonymous analytics data", "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)", diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index c36e2b90bf..cfe2c097fc 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -41,6 +41,7 @@ import { Layout } from "./Layout"; import ReducedMotionController from './controllers/ReducedMotionController'; import IncompatibleController from "./controllers/IncompatibleController"; import SdkConfig from "../SdkConfig"; +import PseudonymousAnalyticsController from './controllers/PseudonymousAnalyticsController'; import NewLayoutSwitcherController from './controllers/NewLayoutSwitcherController'; // These are just a bunch of helper arrays to avoid copy/pasting a bunch of times @@ -268,6 +269,13 @@ export const SETTINGS: {[setting: string]: ISetting} = { supportedLevels: LEVELS_FEATURE, default: false, }, + "feature_pseudonymous_analytics_opt_in": { + isFeature: true, + supportedLevels: LEVELS_FEATURE, + displayName: _td('Send pseudonymous analytics data'), + default: false, + controller: new PseudonymousAnalyticsController(), + }, "advancedRoomListLogging": { // TODO: Remove flag before launch: https://github.com/vector-im/element-web/issues/14231 displayName: _td("Enable advanced debugging for the room list"), diff --git a/src/settings/controllers/PseudonymousAnalyticsController.ts b/src/settings/controllers/PseudonymousAnalyticsController.ts new file mode 100644 index 0000000000..a82b9685ef --- /dev/null +++ b/src/settings/controllers/PseudonymousAnalyticsController.ts @@ -0,0 +1,26 @@ +/* +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. +*/ + +import SettingController from "./SettingController"; +import { SettingLevel } from "../SettingLevel"; +import { PosthogAnalytics } from "../../PosthogAnalytics"; +import { MatrixClientPeg } from "../../MatrixClientPeg"; + +export default class PseudonymousAnalyticsController extends SettingController { + public onChange(level: SettingLevel, roomId: string, newValue: any) { + PosthogAnalytics.instance.updateAnonymityFromSettings(MatrixClientPeg.get().getUserId()); + } +} diff --git a/test/PosthogAnalytics-test.ts b/test/PosthogAnalytics-test.ts new file mode 100644 index 0000000000..6cb1743051 --- /dev/null +++ b/test/PosthogAnalytics-test.ts @@ -0,0 +1,232 @@ +/* +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. +*/ + +import { + Anonymity, + getRedactedCurrentLocation, + IAnonymousEvent, + IPseudonymousEvent, + IRoomEvent, + PosthogAnalytics, +} from '../src/PosthogAnalytics'; + +import SdkConfig from '../src/SdkConfig'; + +class FakePosthog { + public capture; + public init; + public identify; + public reset; + public register; + + constructor() { + this.capture = jest.fn(); + this.init = jest.fn(); + this.identify = jest.fn(); + this.reset = jest.fn(); + this.register = jest.fn(); + } +} + +export interface ITestEvent extends IAnonymousEvent { + key: "jest_test_event"; + properties: { + foo: string; + }; +} + +export interface ITestPseudonymousEvent extends IPseudonymousEvent { + key: "jest_test_pseudo_event"; + properties: { + foo: string; + }; +} + +export interface ITestRoomEvent extends IRoomEvent { + key: "jest_test_room_event"; + properties: { + foo: string; + }; +} + +describe("PosthogAnalytics", () => { + let fakePosthog: FakePosthog; + const shaHashes = { + "42": "73475cb40a568e8da8a045ced110137e159f890ac4da883b6b17dc651b3a8049", + "some": "a6b46dd0d1ae5e86cbc8f37e75ceeb6760230c1ca4ffbcb0c97b96dd7d9c464b", + "pii": "bd75b3e080945674c0351f75e0db33d1e90986fa07b318ea7edf776f5eef38d4", + "foo": "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae", + }; + + beforeEach(() => { + fakePosthog = new FakePosthog(); + + window.crypto = { + subtle: { + digest: async (_, encodedMessage) => { + const message = new TextDecoder().decode(encodedMessage); + const hexHash = shaHashes[message]; + const bytes = []; + for (let c = 0; c < hexHash.length; c += 2) { + bytes.push(parseInt(hexHash.substr(c, 2), 16)); + } + return bytes; + }, + }, + }; + }); + + afterEach(() => { + window.crypto = null; + }); + + describe("Initialisation", () => { + it("Should not be enabled without config being set", () => { + jest.spyOn(SdkConfig, "get").mockReturnValue({}); + const analytics = new PosthogAnalytics(fakePosthog); + expect(analytics.isEnabled()).toBe(false); + }); + + it("Should be enabled if config is set", () => { + jest.spyOn(SdkConfig, "get").mockReturnValue({ + posthog: { + projectApiKey: "foo", + apiHost: "bar", + }, + }); + const analytics = new PosthogAnalytics(fakePosthog); + analytics.setAnonymity(Anonymity.Pseudonymous); + expect(analytics.isEnabled()).toBe(true); + }); + }); + + describe("Tracking", () => { + let analytics: PosthogAnalytics; + + beforeEach(() => { + jest.spyOn(SdkConfig, "get").mockReturnValue({ + posthog: { + projectApiKey: "foo", + apiHost: "bar", + }, + }); + + analytics = new PosthogAnalytics(fakePosthog); + }); + + it("Should pass trackAnonymousEvent() to posthog", async () => { + analytics.setAnonymity(Anonymity.Pseudonymous); + await analytics.trackAnonymousEvent("jest_test_event", { + foo: "bar", + }); + expect(fakePosthog.capture.mock.calls[0][0]).toBe("jest_test_event"); + expect(fakePosthog.capture.mock.calls[0][1]["foo"]).toEqual("bar"); + }); + + it("Should pass trackRoomEvent to posthog", async () => { + analytics.setAnonymity(Anonymity.Pseudonymous); + const roomId = "42"; + await analytics.trackRoomEvent("jest_test_event", roomId, { + foo: "bar", + }); + expect(fakePosthog.capture.mock.calls[0][0]).toBe("jest_test_event"); + expect(fakePosthog.capture.mock.calls[0][1]["foo"]).toEqual("bar"); + expect(fakePosthog.capture.mock.calls[0][1]["hashedRoomId"]) + .toEqual("73475cb40a568e8da8a045ced110137e159f890ac4da883b6b17dc651b3a8049"); + }); + + it("Should pass trackPseudonymousEvent() to posthog", async () => { + analytics.setAnonymity(Anonymity.Pseudonymous); + await analytics.trackPseudonymousEvent("jest_test_pseudo_event", { + foo: "bar", + }); + expect(fakePosthog.capture.mock.calls[0][0]).toBe("jest_test_pseudo_event"); + expect(fakePosthog.capture.mock.calls[0][1]["foo"]).toEqual("bar"); + }); + + it("Should not track pseudonymous messages if anonymous", async () => { + analytics.setAnonymity(Anonymity.Anonymous); + await analytics.trackPseudonymousEvent("jest_test_event", { + foo: "bar", + }); + expect(fakePosthog.capture.mock.calls.length).toBe(0); + }); + + it("Should not track any events if disabled", async () => { + analytics.setAnonymity(Anonymity.Disabled); + await analytics.trackPseudonymousEvent("jest_test_event", { + foo: "bar", + }); + await analytics.trackAnonymousEvent("jest_test_event", { + foo: "bar", + }); + await analytics.trackRoomEvent("room id", "foo", { + foo: "bar", + }); + await analytics.trackPageView(200); + expect(fakePosthog.capture.mock.calls.length).toBe(0); + }); + + it("Should pseudonymise a location of a known screen", async () => { + const location = await getRedactedCurrentLocation( + "https://foo.bar", "#/register/some/pii", "/", Anonymity.Pseudonymous); + expect(location).toBe( + `https://foo.bar/#/register/\ +a6b46dd0d1ae5e86cbc8f37e75ceeb6760230c1ca4ffbcb0c97b96dd7d9c464b/\ +bd75b3e080945674c0351f75e0db33d1e90986fa07b318ea7edf776f5eef38d4`); + }); + + it("Should anonymise a location of a known screen", async () => { + const location = await getRedactedCurrentLocation( + "https://foo.bar", "#/register/some/pii", "/", Anonymity.Anonymous); + expect(location).toBe("https://foo.bar/#/register//"); + }); + + it("Should pseudonymise a location of an unknown screen", async () => { + const location = await getRedactedCurrentLocation( + "https://foo.bar", "#/not_a_screen_name/some/pii", "/", Anonymity.Pseudonymous); + expect(location).toBe( + `https://foo.bar/#//\ +a6b46dd0d1ae5e86cbc8f37e75ceeb6760230c1ca4ffbcb0c97b96dd7d9c464b/\ +bd75b3e080945674c0351f75e0db33d1e90986fa07b318ea7edf776f5eef38d4`); + }); + + it("Should anonymise a location of an unknown screen", async () => { + const location = await getRedactedCurrentLocation( + "https://foo.bar", "#/not_a_screen_name/some/pii", "/", Anonymity.Anonymous); + expect(location).toBe("https://foo.bar/#///"); + }); + + it("Should handle an empty hash", async () => { + const location = await getRedactedCurrentLocation( + "https://foo.bar", "", "/", Anonymity.Anonymous); + expect(location).toBe("https://foo.bar/"); + }); + + it("Should identify the user to posthog if pseudonymous", async () => { + analytics.setAnonymity(Anonymity.Pseudonymous); + await analytics.identifyUser("foo"); + expect(fakePosthog.identify.mock.calls[0][0]) + .toBe("2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae"); + }); + + it("Should not identify the user to posthog if anonymous", async () => { + analytics.setAnonymity(Anonymity.Anonymous); + await analytics.identifyUser("foo"); + expect(fakePosthog.identify.mock.calls.length).toBe(0); + }); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index b139e8e8d1..b982d40b07 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,10 +22,15 @@ "es2019", "dom", "dom.iterable" - ] + ], + "paths": { + "posthog-js": [ + "./src/@types/posthog.d.ts" + ] + } }, "include": [ "./src/**/*.ts", "./src/**/*.tsx" - ] + ], } diff --git a/yarn.lock b/yarn.lock index 2a03f640ee..daed6f4377 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3601,6 +3601,11 @@ fbjs@^0.8.4: setimmediate "^1.0.5" ua-parser-js "^0.7.18" +fflate@^0.4.1: + version "0.4.8" + resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.4.8.tgz#f90b82aefbd8ac174213abb338bd7ef848f0f5ae" + integrity sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA== + file-entry-cache@^6.0.0, file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" @@ -6249,6 +6254,13 @@ postcss@^8.0.2: nanoid "^3.1.23" source-map-js "^0.6.2" +posthog-js@1.12.1: + version "1.12.1" + resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.12.1.tgz#97834ee2574f34ffb5db2f5b07452c847e3c4d27" + integrity sha512-Y3lzcWkS8xFY6Ryj3I4ees7qWP2WGkLw0Arcbk5xaT0+5YlA6UC2jlL/+fN9bz/Bl62EoN3BML901Cuot/QNjg== + dependencies: + fflate "^0.4.1" + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" From ae411b94011ce1a74a771f458c55d8b064d279b1 Mon Sep 17 00:00:00 2001 From: Dariusz Niemczyk Date: Tue, 3 Aug 2021 12:40:37 +0200 Subject: [PATCH 068/176] 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 c66d0017de6d0540f428b26cfc66e8bf7461fba5 Mon Sep 17 00:00:00 2001 From: James Salter Date: Tue, 3 Aug 2021 11:32:51 +0100 Subject: [PATCH 069/176] Patch posthog's type definitions using patch-package Remove definitions for sentry and rrweb-snapshot --- package.json | 7 +- patches/posthog-js+1.12.2.patch | 78 ++++ src/@types/posthog.d.ts | 748 -------------------------------- tsconfig.json | 5 - yarn.lock | 102 ++++- 5 files changed, 177 insertions(+), 763 deletions(-) create mode 100644 patches/posthog-js+1.12.2.patch delete mode 100644 src/@types/posthog.d.ts diff --git a/package.json b/package.json index b7e06fe012..e7ea85ccd4 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,8 @@ "lint:style": "stylelint 'res/css/**/*.scss'", "test": "jest", "test:e2e": "./test/end-to-end-tests/run.sh --app-url http://localhost:8080", - "coverage": "yarn test --coverage" + "coverage": "yarn test --coverage", + "postinstall": "patch-package" }, "dependencies": { "@babel/runtime": "^7.12.5", @@ -87,7 +88,7 @@ "pako": "^2.0.3", "parse5": "^6.0.1", "png-chunks-extract": "^1.0.0", - "posthog-js": "1.12.1", + "posthog-js": "1.12.2", "prop-types": "^15.7.2", "qrcode": "^1.4.4", "re-resizable": "^6.9.0", @@ -165,6 +166,8 @@ "matrix-mock-request": "^1.2.3", "matrix-react-test-utils": "^0.2.3", "matrix-web-i18n": "github:matrix-org/matrix-web-i18n", + "patch-package": "^6.4.7", + "postinstall-postinstall": "^2.1.0", "react-test-renderer": "^17.0.2", "rimraf": "^3.0.2", "stylelint": "^13.9.0", diff --git a/patches/posthog-js+1.12.2.patch b/patches/posthog-js+1.12.2.patch new file mode 100644 index 0000000000..430a9f0a62 --- /dev/null +++ b/patches/posthog-js+1.12.2.patch @@ -0,0 +1,78 @@ +diff --git a/node_modules/posthog-js/dist/module.d.ts b/node_modules/posthog-js/dist/module.d.ts +index d3cba02..8c87ef8 100644 +--- a/node_modules/posthog-js/dist/module.d.ts ++++ b/node_modules/posthog-js/dist/module.d.ts +@@ -1,7 +1,5 @@ + // Type definitions for exported methods +- +-import { EventProcessor, Integration } from '@sentry/types' +-import { MaskInputOptions, SlimDOMOptions } from 'rrweb-snapshot' ++// Patched to remove references to sentry and rrweb-snapshot which aren't otherwise SDK dependencies + + declare class posthog { + /** +@@ -485,25 +483,6 @@ declare class posthog { + */ + static reloadFeatureFlags(): void + +- /** +- * Integrate Sentry with PostHog. This will add a direct link to the person in Sentry, and an $exception event in PostHog +- * +- * ### Usage +- * +- * Sentry.init({ +- * dsn: 'https://example', +- * integrations: [ +- * new posthog.SentryIntegration(posthog) +- * ] +- * }) +- * +- * @param {Object} [posthog] The posthog object +- * @param {string} [organization] Optional: The Sentry organization, used to send a direct link from PostHog to Sentry +- * @param {Number} [projectId] Optional: The Sentry project id, used to send a direct link from PostHog to Sentry +- * @param {string} [prefix] Optional: Url of a self-hosted sentry instance (default: https://sentry.io/organizations/) +- */ +- static SentryIntegration: typeof SentryIntegration +- + static toString(): string + + /* Will log all capture requests to the Javascript console, including event properties for easy debugging */ +@@ -585,7 +564,6 @@ declare namespace posthog { + request_batching?: boolean + sanitize_properties?: (properties: posthog.Properties, event_name: string) => posthog.Properties + properties_string_max_length?: number +- session_recording?: SessionRecordingOptions + mask_all_element_attributes?: boolean + mask_all_text?: boolean + advanced_disable_decide?: boolean +@@ -618,17 +596,6 @@ declare namespace posthog { + send_event: boolean + } + +- interface SessionRecordingOptions { +- blockClass?: string | RegExp +- blockSelector?: string +- ignoreClass?: string +- maskAllInputs?: boolean +- maskInputOptions?: MaskInputOptions +- maskInputFn?: (text: string) => string +- slimDOMOptions?: SlimDOMOptions | 'all' | true +- collectFonts?: boolean +- } +- + export class persistence { + static properties(): posthog.Properties + +@@ -768,12 +735,6 @@ declare namespace posthog { + export class feature_flags extends featureFlags {} + } + +-export class SentryIntegration implements Integration { +- constructor(posthog: posthog, organization?: string, projectId?: number, prefix?: string) +- name: string +- setupOnce(addGlobalEventProcessor: (callback: EventProcessor) => void): void +-} +- + export type PostHog = typeof posthog + + export default posthog diff --git a/src/@types/posthog.d.ts b/src/@types/posthog.d.ts deleted file mode 100644 index 1ca475cd3b..0000000000 --- a/src/@types/posthog.d.ts +++ /dev/null @@ -1,748 +0,0 @@ -// A clone of the type definitions from posthog-js, stripped of references to transitive -// dependencies which we don't actually use, so that we don't need to install them. -// -// Original file lives in node_modules/posthog/dist/module.d.ts - -/* eslint-disable @typescript-eslint/member-delimiter-style */ -/* eslint-disable @typescript-eslint/naming-convention */ -/* eslint-disable camelcase */ - -// Type definitions for exported methods - -declare class posthog { - /** - * This function initializes a new instance of the PostHog capturing object. - * All new instances are added to the main posthog object as sub properties (such as - * posthog.library_name) and also returned by this function. To define a - * second instance on the page, you would call: - * - * posthog.init('new token', { your: 'config' }, 'library_name'); - * - * and use it like so: - * - * posthog.library_name.capture(...); - * - * @param {String} token Your PostHog API token - * @param {Object} [config] A dictionary of config options to override. See a list of default config options. - * @param {String} [name] The name for the new posthog instance that you want created - */ - static init(token: string, config?: posthog.Config, name?: string): posthog - - /** - * Clears super properties and generates a new random distinct_id for this instance. - * Useful for clearing data when a user logs out. - */ - static reset(reset_device_id?: boolean): void - - /** - * Capture an event. This is the most important and - * frequently used PostHog function. - * - * ### Usage: - * - * // capture an event named 'Registered' - * posthog.capture('Registered', {'Gender': 'Male', 'Age': 21}); - * - * // capture an event using navigator.sendBeacon - * posthog.capture('Left page', {'duration_seconds': 35}, {transport: 'sendBeacon'}); - * - * @param {String} event_name The name of the event. This can be anything the user does - 'Button Click', 'Sign Up', 'Item Purchased', etc. - * @param {Object} [properties] A set of properties to include with the event you're sending. These describe the user who did the event or details about the event itself. - * @param {Object} [options] Optional configuration for this capture request. - * @param {String} [options.transport] Transport method for network request ('XHR' or 'sendBeacon'). - */ - static capture( - event_name: string, - properties?: posthog.Properties, - options?: { transport: 'XHR' | 'sendBeacon' } - ): posthog.CaptureResult - - /** - * Capture a page view event, which is currently ignored by the server. - * This function is called by default on page load unless the - * capture_pageview configuration variable is false. - * - * @param {String} [page] The url of the page to record. If you don't include this, it defaults to the current url. - * @api private - */ - static capture_pageview(page?: string): void - - /** - * Register a set of super properties, which are included with all - * events. This will overwrite previous super property values. - * - * ### Usage: - * - * // register 'Gender' as a super property - * posthog.register({'Gender': 'Female'}); - * - * // register several super properties when a user signs up - * posthog.register({ - * 'Email': 'jdoe@example.com', - * 'Account Type': 'Free' - * }); - * - * @param {Object} properties An associative array of properties to store about the user - * @param {Number} [days] How many days since the user's last visit to store the super properties - */ - static register(properties: posthog.Properties, days?: number): void - - /** - * Register a set of super properties only once. This will not - * overwrite previous super property values, unlike register(). - * - * ### Usage: - * - * // register a super property for the first time only - * posthog.register_once({ - * 'First Login Date': new Date().toISOString() - * }); - * - * ### Notes: - * - * If default_value is specified, current super properties - * with that value will be overwritten. - * - * @param {Object} properties An associative array of properties to store about the user - * @param {*} [default_value] Value to override if already set in super properties (ex: 'False') Default: 'None' - * @param {Number} [days] How many days since the users last visit to store the super properties - */ - static register_once(properties: posthog.Properties, default_value?: posthog.Property, days?: number): void - - /** - * Delete a super property stored with the current user. - * - * @param {String} property The name of the super property to remove - */ - static unregister(property: string): void - - /** - * Identify a user with a unique ID instead of a PostHog - * randomly generated distinct_id. If the method is never called, - * then unique visitors will be identified by a UUID generated - * the first time they visit the site. - * - * If user properties are passed, they are also sent to posthog. - * - * ### Usage: - * - * posthog.identify('[user unique id]') - * posthog.identify('[user unique id]', { email: 'john@example.com' }) - * posthog.identify('[user unique id]', {}, { referral_code: '12345' }) - * - * ### Notes: - * - * You can call this function to overwrite a previously set - * unique ID for the current user. PostHog cannot translate - * between IDs at this time, so when you change a user's ID - * they will appear to be a new user. - * - * When used alone, posthog.identify will change the user's - * distinct_id to the unique ID provided. When used in tandem - * with posthog.alias, it will allow you to identify based on - * unique ID and map that back to the original, anonymous - * distinct_id given to the user upon her first arrival to your - * site (thus connecting anonymous pre-signup activity to - * post-signup activity). Though the two work together, do not - * call identify() at the same time as alias(). Calling the two - * at the same time can cause a race condition, so it is best - * practice to call identify on the original, anonymous ID - * right after you've aliased it. - * - * @param {String} [unique_id] A string that uniquely identifies a user. If not provided, the distinct_id currently in the persistent store (cookie or localStorage) will be used. - * @param {Object} [userProperties] Optional: An associative array of properties to store about the user - * @param {Object} [userPropertiesToSetOnce] Optional: An associative array of properties to store about the user. If property is previously set, this does not override that value. - */ - static identify( - unique_id?: string, - userPropertiesToSet?: posthog.Properties, - userPropertiesToSetOnce?: posthog.Properties - ): void - - /** - * Create an alias, which PostHog will use to link two distinct_ids going forward (not retroactively). - * Multiple aliases can map to the same original ID, but not vice-versa. Aliases can also be chained - the - * following is a valid scenario: - * - * posthog.alias('new_id', 'existing_id'); - * ... - * posthog.alias('newer_id', 'new_id'); - * - * If the original ID is not passed in, we will use the current distinct_id - probably the auto-generated GUID. - * - * ### Notes: - * - * The best practice is to call alias() when a unique ID is first created for a user - * (e.g., when a user first registers for an account and provides an email address). - * alias() should never be called more than once for a given user, except to - * chain a newer ID to a previously new ID, as described above. - * - * @param {String} alias A unique identifier that you want to use for this user in the future. - * @param {String} [original] The current identifier being used for this user. - */ - static alias(alias: string, original?: string): posthog.CaptureResult | number - - /** - * Update the configuration of a posthog library instance. - * - * The default config is: - * - * { - * // HTTP method for capturing requests - * api_method: 'POST' - * - * // transport for sending requests ('XHR' or 'sendBeacon') - * // NB: sendBeacon should only be used for scenarios such as - * // page unload where a "best-effort" attempt to send is - * // acceptable; the sendBeacon API does not support callbacks - * // or any way to know the result of the request. PostHog - * // capturing via sendBeacon will not support any event- - * // batching or retry mechanisms. - * api_transport: 'XHR' - * - * // Automatically capture clicks, form submissions and change events - * autocapture: true - * - * // Capture rage clicks (beta) - useful for session recording - * rageclick: false - * - * // super properties cookie expiration (in days) - * cookie_expiration: 365 - * - * // super properties span subdomains - * cross_subdomain_cookie: true - * - * // debug mode - * debug: false - * - * // if this is true, the posthog cookie or localStorage entry - * // will be deleted, and no user persistence will take place - * disable_persistence: false - * - * // if this is true, PostHog will automatically determine - * // City, Region and Country data using the IP address of - * //the client - * ip: true - * - * // opt users out of capturing by this PostHog instance by default - * opt_out_capturing_by_default: false - * - * // opt users out of browser data storage by this PostHog instance by default - * opt_out_persistence_by_default: false - * - * // persistence mechanism used by opt-in/opt-out methods - cookie - * // or localStorage - falls back to cookie if localStorage is unavailable - * opt_out_capturing_persistence_type: 'localStorage' - * - * // customize the name of cookie/localStorage set by opt-in/opt-out methods - * opt_out_capturing_cookie_prefix: null - * - * // type of persistent store for super properties (cookie/ - * // localStorage) if set to 'localStorage', any existing - * // posthog cookie value with the same persistence_name - * // will be transferred to localStorage and deleted - * persistence: 'cookie' - * - * // name for super properties persistent store - * persistence_name: '' - * - * // names of properties/superproperties which should never - * // be sent with capture() calls - * property_blacklist: [] - * - * // if this is true, posthog cookies will be marked as - * // secure, meaning they will only be transmitted over https - * secure_cookie: false - * - * // should we capture a page view on page load - * capture_pageview: true - * - * // if you set upgrade to be true, the library will check for - * // a cookie from our old js library and import super - * // properties from it, then the old cookie is deleted - * // The upgrade config option only works in the initialization, - * // so make sure you set it when you create the library. - * upgrade: false - * - * // extra HTTP request headers to set for each API request, in - * // the format {'Header-Name': value} - * xhr_headers: {} - * - * // protocol for fetching in-app message resources, e.g. - * // 'https://' or 'http://'; defaults to '//' (which defers to the - * // current page's protocol) - * inapp_protocol: '//' - * - * // whether to open in-app message link in new tab/window - * inapp_link_new_window: false - * - * // a set of rrweb config options that PostHog users can configure - * // see https://github.com/rrweb-io/rrweb/blob/master/guide.md - * session_recording: { - * blockClass: 'ph-no-capture', - * blockSelector: null, - * ignoreClass: 'ph-ignore-input', - * maskAllInputs: false, - * maskInputOptions: {}, - * maskInputFn: null, - * slimDOMOptions: {}, - * collectFonts: false - * } - * - * // prevent autocapture from capturing any attribute names on elements - * mask_all_element_attributes: false - * - * // prevent autocapture from capturing textContent on all elements - * mask_all_text: false - * - * // will disable requests to the /decide endpoint (please review documentation for details) - * // autocapture, feature flags, compression and session recording will be disabled when set to `true` - * advanced_disable_decide: false - * - * } - * - * - * @param {Object} config A dictionary of new configuration values to update - */ - static set_config(config: posthog.Config): void - - /** - * returns the current config object for the library. - */ - static get_config(prop_name: T): posthog.Config[T] - - /** - * Returns the value of the super property named property_name. If no such - * property is set, get_property() will return the undefined value. - * - * ### Notes: - * - * get_property() can only be called after the PostHog library has finished loading. - * init() has a loaded function available to handle this automatically. For example: - * - * // grab value for 'user_id' after the posthog library has loaded - * posthog.init('YOUR PROJECT TOKEN', { - * loaded: function(posthog) { - * user_id = posthog.get_property('user_id'); - * } - * }); - * - * @param {String} property_name The name of the super property you want to retrieve - */ - static get_property(property_name: string): posthog.Property | undefined - - /** - * Returns the current distinct id of the user. This is either the id automatically - * generated by the library or the id that has been passed by a call to identify(). - * - * ### Notes: - * - * get_distinct_id() can only be called after the PostHog library has finished loading. - * init() has a loaded function available to handle this automatically. For example: - * - * // set distinct_id after the posthog library has loaded - * posthog.init('YOUR PROJECT TOKEN', { - * loaded: function(posthog) { - * distinct_id = posthog.get_distinct_id(); - * } - * }); - */ - static get_distinct_id(): string - - /** - * Opt the user out of data capturing and cookies/localstorage for this PostHog instance - * - * ### Usage - * - * // opt user out - * posthog.opt_out_capturing(); - * - * // opt user out with different cookie configuration from PostHog instance - * posthog.opt_out_capturing({ - * cookie_expiration: 30, - * secure_cookie: true - * }); - * - * @param {Object} [options] A dictionary of config options to override - * @param {boolean} [options.clear_persistence=true] If true, will delete all data stored by the sdk in persistence - * @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable - * @param {string} [options.cookie_prefix=__ph_opt_in_out] Custom prefix to be used in the cookie/localstorage name - * @param {Number} [options.cookie_expiration] Number of days until the opt-in cookie expires (overrides value specified in this PostHog instance's config) - * @param {boolean} [options.cross_subdomain_cookie] Whether the opt-in cookie is set as cross-subdomain or not (overrides value specified in this PostHog instance's config) - * @param {boolean} [options.secure_cookie] Whether the opt-in cookie is set as secure or not (overrides value specified in this PostHog instance's config) - */ - static opt_out_capturing(options?: posthog.OptInOutCapturingOptions): void - - /** - * Opt the user in to data capturing and cookies/localstorage for this PostHog instance - * - * ### Usage - * - * // opt user in - * posthog.opt_in_capturing(); - * - * // opt user in with specific event name, properties, cookie configuration - * posthog.opt_in_capturing({ - * capture_event_name: 'User opted in', - * capture_event_properties: { - * 'Email': 'jdoe@example.com' - * }, - * cookie_expiration: 30, - * secure_cookie: true - * }); - * - * @param {Object} [options] A dictionary of config options to override - * @param {function} [options.capture] Function used for capturing a PostHog event to record the opt-in action (default is this PostHog instance's capture method) - * @param {string} [options.capture_event_name=$opt_in] Event name to be used for capturing the opt-in action - * @param {Object} [options.capture_properties] Set of properties to be captured along with the opt-in action - * @param {boolean} [options.enable_persistence=true] If true, will re-enable sdk persistence - * @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable - * @param {string} [options.cookie_prefix=__ph_opt_in_out] Custom prefix to be used in the cookie/localstorage name - * @param {Number} [options.cookie_expiration] Number of days until the opt-in cookie expires (overrides value specified in this PostHog instance's config) - * @param {boolean} [options.cross_subdomain_cookie] Whether the opt-in cookie is set as cross-subdomain or not (overrides value specified in this PostHog instance's config) - * @param {boolean} [options.secure_cookie] Whether the opt-in cookie is set as secure or not (overrides value specified in this PostHog instance's config) - */ - static opt_in_capturing(options?: posthog.OptInOutCapturingOptions): void - - /** - * Check whether the user has opted out of data capturing and cookies/localstorage for this PostHog instance - * - * ### Usage - * - * const has_opted_out = posthog.has_opted_out_capturing(); - * // use has_opted_out value - * - * @param {Object} [options] A dictionary of config options to override - * @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable - * @param {string} [options.cookie_prefix=__ph_opt_in_out] Custom prefix to be used in the cookie/localstorage name - * @returns {boolean} current opt-out status - */ - static has_opted_out_capturing(options?: posthog.HasOptedInOutCapturingOptions): boolean - - /** - * Check whether the user has opted in to data capturing and cookies/localstorage for this PostHog instance - * - * ### Usage - * - * const has_opted_in = posthog.has_opted_in_capturing(); - * // use has_opted_in value - * - * @param {Object} [options] A dictionary of config options to override - * @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable - * @param {string} [options.cookie_prefix=__ph_opt_in_out] Custom prefix to be used in the cookie/localstorage name - * @returns {boolean} current opt-in status - */ - static has_opted_in_capturing(options?: posthog.HasOptedInOutCapturingOptions): boolean - - /** - * Clear the user's opt in/out status of data capturing and cookies/localstorage for this PostHog instance - * - * ### Usage - * - * // clear user's opt-in/out status - * posthog.clear_opt_in_out_capturing(); - * - * // clear user's opt-in/out status with specific cookie configuration - should match - * // configuration used when opt_in_capturing/opt_out_capturing methods were called. - * posthog.clear_opt_in_out_capturing({ - * cookie_expiration: 30, - * secure_cookie: true - * }); - * - * @param {Object} [options] A dictionary of config options to override - * @param {boolean} [options.enable_persistence=true] If true, will re-enable sdk persistence - * @param {string} [options.persistence_type=localStorage] Persistence mechanism used - cookie or localStorage - falls back to cookie if localStorage is unavailable - * @param {string} [options.cookie_prefix=__ph_opt_in_out] Custom prefix to be used in the cookie/localstorage name - * @param {Number} [options.cookie_expiration] Number of days until the opt-in cookie expires (overrides value specified in this PostHog instance's config) - * @param {boolean} [options.cross_subdomain_cookie] Whether the opt-in cookie is set as cross-subdomain or not (overrides value specified in this PostHog instance's config) - * @param {boolean} [options.secure_cookie] Whether the opt-in cookie is set as secure or not (overrides value specified in this PostHog instance's config) - */ - static clear_opt_in_out_capturing(options?: posthog.ClearOptInOutCapturingOptions): void - - /* - * See if feature flag is enabled for user. - * - * ### Usage: - * - * if(posthog.isFeatureEnabled('beta-feature')) { // do something } - * - * @param {Object|String} prop Key of the feature flag. - * @param {Object|String} options (optional) If {send_event: false}, we won't send an $feature_flag_call event to PostHog. - */ - static isFeatureEnabled(key: string, options?: posthog.isFeatureEnabledOptions): boolean - - /* - * See if feature flags are available. - * - * ### Usage: - * - * posthog.onFeatureFlags(function(featureFlags) { // do something }) - * - * @param {Function} [callback] The callback function will be called once the feature flags are ready. It'll return a list of feature flags enabled for the user. - */ - static onFeatureFlags(callback: (flags: string[]) => void): false | undefined - - /* - * Reload all feature flags for the user. - * - * ### Usage: - * - * posthog.reloadFeatureFlags() - */ - static reloadFeatureFlags(): void - - static toString(): string - - /* Will log all capture requests to the Javascript console, including event properties for easy debugging */ - static debug(): void - - /* - * Starts session recording and updates disable_session_recording to false. - * Used for manual session recording management. By default, session recording is enabled and - * starts automatically. - * - * ### Usage: - * - * posthog.startSessionRecording() - */ - static startSessionRecording(): void - - /* - * Stops session recording and updates disable_session_recording to true. - * - * ### Usage: - * - * posthog.stopSessionRecording() - */ - static stopSessionRecording(): void - - /* - * Check if session recording is currently running. - * - * ### Usage: - * - * const isSessionRecordingOn = posthog.sessionRecordingStarted() - */ - static sessionRecordingStarted(): boolean -} - -declare namespace posthog { - /* eslint-disable @typescript-eslint/no-explicit-any */ - type Property = any; - type Properties = Record; - type CaptureResult = { event: string; properties: Properties } | undefined; - type CaptureCallback = (response: any, data: any) => void; - /* eslint-enable @typescript-eslint/no-explicit-any */ - - interface Config { - api_host?: string - api_method?: string - api_transport?: string - autocapture?: boolean - rageclick?: boolean - cdn?: string - cross_subdomain_cookie?: boolean - persistence?: 'localStorage' | 'cookie' | 'memory' - persistence_name?: string - cookie_name?: string - loaded?: (posthog_instance: typeof posthog) => void - store_google?: boolean - save_referrer?: boolean - test?: boolean - verbose?: boolean - img?: boolean - capture_pageview?: boolean - debug?: boolean - cookie_expiration?: number - upgrade?: boolean - disable_session_recording?: boolean - disable_persistence?: boolean - disable_cookie?: boolean - secure_cookie?: boolean - ip?: boolean - opt_out_capturing_by_default?: boolean - opt_out_persistence_by_default?: boolean - opt_out_capturing_persistence_type?: 'localStorage' | 'cookie' - opt_out_capturing_cookie_prefix?: string | null - respect_dnt?: boolean - property_blacklist?: string[] - xhr_headers?: { [header_name: string]: string } - inapp_protocol?: string - inapp_link_new_window?: boolean - request_batching?: boolean - sanitize_properties?: (properties: posthog.Properties, event_name: string) => posthog.Properties - properties_string_max_length?: number - mask_all_element_attributes?: boolean - mask_all_text?: boolean - advanced_disable_decide?: boolean - } - - interface OptInOutCapturingOptions { - clear_persistence: boolean - persistence_type: string - cookie_prefix: string - cookie_expiration: number - cross_subdomain_cookie: boolean - secure_cookie: boolean - } - - interface HasOptedInOutCapturingOptions { - persistence_type: string - cookie_prefix: string - } - - interface ClearOptInOutCapturingOptions { - enable_persistence: boolean - persistence_type: string - cookie_prefix: string - cookie_expiration: number - cross_subdomain_cookie: boolean - secure_cookie: boolean - } - - interface isFeatureEnabledOptions { - send_event: boolean - } - - export class persistence { - static properties(): posthog.Properties - - static load(): void - - static save(): void - - static remove(): void - - static clear(): void - - /** - * @param {Object} props - * @param {*=} default_value - * @param {number=} days - */ - static register_once(props: Properties, default_value?: Property, days?: number): boolean - - /** - * @param {Object} props - * @param {number=} days - */ - static register(props: posthog.Properties, days?: number): boolean - - static unregister(prop: string): void - - static update_campaign_params(): void - - static update_search_keyword(referrer: string): void - - static update_referrer_info(referrer: string): void - - static get_referrer_info(): posthog.Properties - - static safe_merge(props: posthog.Properties): posthog.Properties - - static update_config(config: posthog.Config): void - - static set_disabled(disabled: boolean): void - - static set_cross_subdomain(cross_subdomain: boolean): void - - static get_cross_subdomain(): boolean - - static set_secure(secure: boolean): void - - static set_event_timer(event_name: string, timestamp: Date): void - - static remove_event_timer(event_name: string): Date | undefined - } - - export class people { - /* - * Set properties on a user record. - * - * ### Usage: - * - * posthog.people.set('gender', 'm'); - * - * // or set multiple properties at once - * posthog.people.set({ - * 'Company': 'Acme', - * 'Plan': 'Premium', - * 'Upgrade date': new Date() - * }); - * // properties can be strings, integers, dates, or lists - * - * @param {Object|String} prop If a string, this is the name of the property. If an object, this is an associative array of names and values. - * @param {*} [to] A value to set on the given property name - * @param {Function} [callback] If provided, the callback will be called after capturing the event. - */ - static set( - prop: posthog.Properties | string, - to?: posthog.Property, - callback?: posthog.CaptureCallback - ): posthog.Properties - - /* - * Set properties on a user record, only if they do not yet exist. - * This will not overwrite previous people property values, unlike - * people.set(). - * - * ### Usage: - * - * posthog.people.set_once('First Login Date', new Date()); - * - * // or set multiple properties at once - * posthog.people.set_once({ - * 'First Login Date': new Date(), - * 'Starting Plan': 'Premium' - * }); - * - * // properties can be strings, integers or dates - * - * @param {Object|String} prop If a string, this is the name of the property. If an object, this is an associative array of names and values. - * @param {*} [to] A value to set on the given property name - * @param {Function} [callback] If provided, the callback will be called after capturing the event. - */ - static set_once( - prop: posthog.Properties | string, - to?: posthog.Property, - callback?: posthog.CaptureCallback - ): posthog.Properties - - static toString(): string - } - - export class featureFlags { - static getFlags(): string[] - - static reloadFeatureFlags(): void - - /* - * See if feature flag is enabled for user. - * - * ### Usage: - * - * if(posthog.isFeatureEnabled('beta-feature')) { // do something } - * - * @param {Object|String} prop Key of the feature flag. - * @param {Object|String} options (optional) If {send_event: false}, we won't send an $feature_flag_call event to PostHog. - */ - static isFeatureEnabled(key: string, options?: { send_event?: boolean }): boolean - - /* - * See if feature flags are available. - * - * ### Usage: - * - * posthog.onFeatureFlags(function(featureFlags) { // do something }) - * - * @param {Function} [callback] The callback function will be called once the feature flags are ready. It'll return a list of feature flags enabled for the user. - */ - static onFeatureFlags(callback: (flags: string[]) => void): false | undefined - } - - export class feature_flags extends featureFlags {} -} - -export type PostHog = typeof posthog; - -export default posthog; diff --git a/tsconfig.json b/tsconfig.json index b982d40b07..2e0131609c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -23,11 +23,6 @@ "dom", "dom.iterable" ], - "paths": { - "posthog-js": [ - "./src/@types/posthog.d.ts" - ] - } }, "include": [ "./src/**/*.ts", diff --git a/yarn.lock b/yarn.lock index daed6f4377..6bb5654db8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1790,6 +1790,11 @@ object.fromentries "^2.0.0" prop-types "^15.7.0" +"@yarnpkg/lockfile@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" + integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== + abab@^2.0.3, abab@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" @@ -2713,7 +2718,7 @@ cross-fetch@^3.0.4: dependencies: node-fetch "2.6.1" -cross-spawn@^6.0.0: +cross-spawn@^6.0.0, cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== @@ -3664,6 +3669,13 @@ find-up@^4.0.0, find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" +find-yarn-workspace-root@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz#f47fb8d239c900eb78179aa81b66673eac88f7bd" + integrity sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ== + dependencies: + micromatch "^4.0.2" + flat-cache@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" @@ -3743,6 +3755,15 @@ fragment-cache@^0.2.1: dependencies: map-cache "^0.2.2" +fs-extra@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" + integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + fs-readdir-recursive@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz#e32fc030a2ccee44a6b5371308da54be0b397d27" @@ -3919,7 +3940,7 @@ gonzales-pe@^4.3.0: dependencies: minimist "^1.2.5" -graceful-fs@^4.1.11, graceful-fs@^4.2.4: +graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.4: version "4.2.6" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== @@ -4595,7 +4616,7 @@ is-windows@^1.0.2: resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== -is-wsl@^2.2.0: +is-wsl@^2.1.1, is-wsl@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== @@ -5217,6 +5238,13 @@ json5@^2.1.2: dependencies: minimist "^1.2.5" +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= + optionalDependencies: + graceful-fs "^4.1.6" + jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -5266,6 +5294,13 @@ kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3: resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== +klaw-sync@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/klaw-sync/-/klaw-sync-6.0.0.tgz#1fd2cfd56ebb6250181114f0a581167099c2b28c" + integrity sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ== + dependencies: + graceful-fs "^4.1.11" + kleur@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" @@ -5937,6 +5972,14 @@ onetime@^5.1.0: dependencies: mimic-fn "^2.1.0" +open@^7.4.2: + version "7.4.2" + resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321" + integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q== + dependencies: + is-docker "^2.0.0" + is-wsl "^2.1.1" + optionator@^0.8.1: version "0.8.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" @@ -5966,6 +6009,11 @@ opus-recorder@^8.0.3: resolved "https://registry.yarnpkg.com/opus-recorder/-/opus-recorder-8.0.3.tgz#f7b44f8f68500c9b96a15042a69f915fd9c1716d" integrity sha512-8vXGiRwlJAavT9D3yYzukNVXQ8vEcKHcsQL/zXO24DQtJ0PLXvoPHNQPJrbMCdB4ypJgWDExvHF4JitQDL7dng== +os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= + p-each-series@^2.1.0: version "2.2.0" resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.2.0.tgz#105ab0357ce72b202a8a8b94933672657b5e2a9a" @@ -6074,6 +6122,25 @@ pascalcase@^0.1.1: resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= +patch-package@^6.4.7: + version "6.4.7" + resolved "https://registry.yarnpkg.com/patch-package/-/patch-package-6.4.7.tgz#2282d53c397909a0d9ef92dae3fdeb558382b148" + integrity sha512-S0vh/ZEafZ17hbhgqdnpunKDfzHQibQizx9g8yEf5dcVk3KOflOfdufRXQX8CSEkyOQwuM/bNz1GwKvFj54kaQ== + dependencies: + "@yarnpkg/lockfile" "^1.1.0" + chalk "^2.4.2" + cross-spawn "^6.0.5" + find-yarn-workspace-root "^2.0.0" + fs-extra "^7.0.1" + is-ci "^2.0.0" + klaw-sync "^6.0.0" + minimist "^1.2.0" + open "^7.4.2" + rimraf "^2.6.3" + semver "^5.6.0" + slash "^2.0.0" + tmp "^0.0.33" + path-exists@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" @@ -6254,13 +6321,18 @@ postcss@^8.0.2: nanoid "^3.1.23" source-map-js "^0.6.2" -posthog-js@1.12.1: - version "1.12.1" - resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.12.1.tgz#97834ee2574f34ffb5db2f5b07452c847e3c4d27" - integrity sha512-Y3lzcWkS8xFY6Ryj3I4ees7qWP2WGkLw0Arcbk5xaT0+5YlA6UC2jlL/+fN9bz/Bl62EoN3BML901Cuot/QNjg== +posthog-js@^1.12.2: + version "1.12.2" + resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.12.2.tgz#ff76e26634067e003f8af7df654d7ea0e647d946" + integrity sha512-I0d6c+Yu2f91PFidz65AIkkqZM219EY9Z1wlbTkW5Zqfq5oXqogBMKS8BaDBOrMc46LjLX7IH67ytCcBFRo1uw== dependencies: fflate "^0.4.1" +postinstall-postinstall@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/postinstall-postinstall/-/postinstall-postinstall-2.1.0.tgz#4f7f77441ef539d1512c40bd04c71b06a4704ca3" + integrity sha512-7hQX6ZlZXIoRiWNrbMQaLzUUfH+sSx39u8EJ9HYuDc1kLo9IXKWjM5RSquZN1ad5GnH8CGFM78fsAAQi3OKEEQ== + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -6834,6 +6906,13 @@ rfc4648@^1.4.0: resolved "https://registry.yarnpkg.com/rfc4648/-/rfc4648-1.5.0.tgz#1ba940ec1649685ec4d88788dc57fb8e18855055" integrity sha512-FA6W9lDNeX8WbMY31io1xWg+TpZCbeDKsBo0ocwACZiWnh9TUAyk9CCuBQuOPmYnwwdEQZmraQ2ZK7yJsxErBg== +rimraf@^2.6.3: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" + rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" @@ -7520,6 +7599,13 @@ tmatch@^2.0.1: resolved "https://registry.yarnpkg.com/tmatch/-/tmatch-2.0.1.tgz#0c56246f33f30da1b8d3d72895abaf16660f38cf" integrity sha1-DFYkbzPzDaG409colauvFmYPOM8= +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + tmpl@1.0.x: version "1.0.4" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1" @@ -7765,7 +7851,7 @@ unist-util-stringify-position@^2.0.0: dependencies: "@types/unist" "^2.0.2" -universalify@^0.1.2: +universalify@^0.1.0, universalify@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== From 36efa448b2ef673d8c282ad87bc3da6bb4d01cf2 Mon Sep 17 00:00:00 2001 From: Dariusz Niemczyk Date: Tue, 3 Aug 2021 13:24:41 +0200 Subject: [PATCH 070/176] 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 071/176] 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 81b45f8aa929a5d132441d575999256de7578a72 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 3 Aug 2021 13:44:14 +0200 Subject: [PATCH 072/176] Make defaultProps a static property of LogoutDialog --- src/components/views/dialogs/LogoutDialog.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/dialogs/LogoutDialog.js b/src/components/views/dialogs/LogoutDialog.js index 469cd48093..e7e361a5b6 100644 --- a/src/components/views/dialogs/LogoutDialog.js +++ b/src/components/views/dialogs/LogoutDialog.js @@ -26,7 +26,7 @@ import { replaceableComponent } from "../../../utils/replaceableComponent"; @replaceableComponent("views.dialogs.LogoutDialog") export default class LogoutDialog extends React.Component { - defaultProps = { + static defaultProps = { onFinished: function() {}, }; 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 073/176] 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 54c685bfa8bba5f436332134dcf5e4454b94b6f6 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 3 Aug 2021 13:52:46 +0200 Subject: [PATCH 074/176] Migrate LogoutDialog to TypeScript --- .../{LogoutDialog.js => LogoutDialog.tsx} | 65 ++++++++++--------- 1 file changed, 36 insertions(+), 29 deletions(-) rename src/components/views/dialogs/{LogoutDialog.js => LogoutDialog.tsx} (83%) diff --git a/src/components/views/dialogs/LogoutDialog.js b/src/components/views/dialogs/LogoutDialog.tsx similarity index 83% rename from src/components/views/dialogs/LogoutDialog.js rename to src/components/views/dialogs/LogoutDialog.tsx index e7e361a5b6..8c035dcbba 100644 --- a/src/components/views/dialogs/LogoutDialog.js +++ b/src/components/views/dialogs/LogoutDialog.tsx @@ -16,6 +16,7 @@ limitations under the License. */ import React from 'react'; +import { IKeyBackupInfo } from "matrix-js-sdk/src/crypto/keybackup"; import Modal from '../../../Modal'; import * as sdk from '../../../index'; import dis from '../../../dispatcher/dispatcher'; @@ -24,19 +25,25 @@ import { MatrixClientPeg } from '../../../MatrixClientPeg'; import RestoreKeyBackupDialog from './security/RestoreKeyBackupDialog'; import { replaceableComponent } from "../../../utils/replaceableComponent"; +interface IProps { + onFinished: (success: boolean) => void; +} + +interface IState { + shouldLoadBackupStatus: boolean; + loading: boolean; + backupInfo: IKeyBackupInfo; + error?: string; +} + @replaceableComponent("views.dialogs.LogoutDialog") -export default class LogoutDialog extends React.Component { +export default class LogoutDialog extends React.Component { static defaultProps = { onFinished: function() {}, }; - constructor() { - super(); - this._onSettingsLinkClick = this._onSettingsLinkClick.bind(this); - this._onExportE2eKeysClicked = this._onExportE2eKeysClicked.bind(this); - this._onFinished = this._onFinished.bind(this); - this._onSetRecoveryMethodClick = this._onSetRecoveryMethodClick.bind(this); - this._onLogoutConfirm = this._onLogoutConfirm.bind(this); + constructor(props) { + super(props); const cli = MatrixClientPeg.get(); const shouldLoadBackupStatus = cli.isCryptoEnabled() && !cli.getKeyBackupEnabled(); @@ -49,11 +56,11 @@ export default class LogoutDialog extends React.Component { }; if (shouldLoadBackupStatus) { - this._loadBackupStatus(); + this.loadBackupStatus(); } } - async _loadBackupStatus() { + private async loadBackupStatus() { try { const backupInfo = await MatrixClientPeg.get().getKeyBackupVersion(); this.setState({ @@ -69,29 +76,29 @@ export default class LogoutDialog extends React.Component { } } - _onSettingsLinkClick() { + private onSettingsLinkClick = (): void => { // close dialog - this.props.onFinished(); - } + this.props.onFinished(true); + }; - _onExportE2eKeysClicked() { + private onExportE2eKeysClicked = (): void => { Modal.createTrackedDialogAsync('Export E2E Keys', '', import('../../../async-components/views/dialogs/security/ExportE2eKeysDialog'), { matrixClient: MatrixClientPeg.get(), }, ); - } + }; - _onFinished(confirmed) { + private onFinished = (confirmed: boolean): void => { if (confirmed) { dis.dispatch({ action: 'logout' }); } // close dialog - this.props.onFinished(); - } + this.props.onFinished(confirmed); + }; - _onSetRecoveryMethodClick() { + private onSetRecoveryMethodClick = (): void => { if (this.state.backupInfo) { // A key backup exists for this account, but the creating device is not // verified, so restore the backup which will give us the keys from it and @@ -108,15 +115,15 @@ export default class LogoutDialog extends React.Component { } // close dialog - this.props.onFinished(); - } + this.props.onFinished(true); + }; - _onLogoutConfirm() { + private onLogoutConfirm = (): void => { dis.dispatch({ action: 'logout' }); // close dialog - this.props.onFinished(); - } + this.props.onFinished(true); + }; render() { if (this.state.shouldLoadBackupStatus) { @@ -152,16 +159,16 @@ export default class LogoutDialog extends React.Component {
-
{ _t("Advanced") } -

@@ -174,7 +181,7 @@ export default class LogoutDialog extends React.Component { title={_t("You'll lose access to your encrypted messages")} contentId='mx_Dialog_content' hasCancel={true} - onFinished={this._onFinished} + onFinished={this.onFinished} > { dialogContent } ); @@ -187,7 +194,7 @@ export default class LogoutDialog extends React.Component { "Are you sure you want to sign out?", )} button={_t("Sign out")} - onFinished={this._onFinished} + onFinished={this.onFinished} />); } } 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 075/176] 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 076/176] 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 37f51c7536d878ad93767155037d6f9264bac50f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 3 Aug 2021 14:48:39 +0200 Subject: [PATCH 077/176] Don't ring if it's disabled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/CallHandler.tsx | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index e7c1dda54f..d4a29a11b9 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -1,7 +1,8 @@ /* Copyright 2015, 2016 OpenMarket Ltd Copyright 2017, 2018 New Vector Ltd -Copyright 2019, 2020 The Matrix.org Foundation C.I.C. +Copyright 2019 - 2021 The Matrix.org Foundation C.I.C. +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. @@ -86,6 +87,8 @@ import { randomUppercaseString, randomLowercaseString } from "matrix-js-sdk/src/ import EventEmitter from 'events'; import SdkConfig from './SdkConfig'; import { ensureDMExists, findDMForUser } from './createRoom'; +import { IPushRule, RuleId, TweakName, Tweaks } from "matrix-js-sdk/src/@types/PushRules"; +import { PushProcessor } from 'matrix-js-sdk/src/pushprocessor'; export const PROTOCOL_PSTN = 'm.protocol.pstn'; export const PROTOCOL_PSTN_PREFIXED = 'im.vector.protocol.pstn'; @@ -475,9 +478,22 @@ export default class CallHandler extends EventEmitter { this.silencedCalls.delete(call.callId); } + const incomingCallPushRule = ( + new PushProcessor(MatrixClientPeg.get()).getPushRuleById(RuleId.IncomingCall) as IPushRule + ); switch (newState) { case CallState.Ringing: - this.play(AudioID.Ring); + if ( + incomingCallPushRule?.enabled && + incomingCallPushRule.actions.some((a: Tweaks) => ( + a.set_tweak === TweakName.Sound && + a.value === "ring" + )) + ) { + this.play(AudioID.Ring); + } else { + this.silenceCall(call.callId); + } break; case CallState.InviteSent: this.play(AudioID.Ringback); From e787d14cde2783e3e83613a40355a0afba477191 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 3 Aug 2021 14:58:50 +0200 Subject: [PATCH 078/176] Set silenced state as soon as we get the call MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/voip/IncomingCallBox.tsx | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/components/views/voip/IncomingCallBox.tsx b/src/components/views/voip/IncomingCallBox.tsx index 95e97f1080..66fc2847f7 100644 --- a/src/components/views/voip/IncomingCallBox.tsx +++ b/src/components/views/voip/IncomingCallBox.tsx @@ -63,16 +63,12 @@ export default class IncomingCallBox extends React.Component { private onAction = (payload: ActionPayload) => { switch (payload.action) { case 'call_state': { - const call = CallHandler.sharedInstance().getCallForRoom(payload.room_id); - if (call && call.state === CallState.Ringing) { - this.setState({ - incomingCall: call, - silenced: false, // Reset silenced state for new call - }); + const incomingCall = CallHandler.sharedInstance().getCallForRoom(payload.room_id); + const silenced = CallHandler.sharedInstance().isCallSilenced(incomingCall.callId); + if (incomingCall && incomingCall.state === CallState.Ringing) { + this.setState({ incomingCall, silenced }); } else { - this.setState({ - incomingCall: null, - }); + this.setState({ incomingCall: null }); } } } From 75948587e43cf539dbef39c1f09a37ff7ed1d155 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 3 Aug 2021 15:13:40 +0200 Subject: [PATCH 079/176] Avoid calling with undefined MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/voip/IncomingCallBox.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/views/voip/IncomingCallBox.tsx b/src/components/views/voip/IncomingCallBox.tsx index 66fc2847f7..4479c02e30 100644 --- a/src/components/views/voip/IncomingCallBox.tsx +++ b/src/components/views/voip/IncomingCallBox.tsx @@ -64,9 +64,11 @@ export default class IncomingCallBox extends React.Component { switch (payload.action) { case 'call_state': { const incomingCall = CallHandler.sharedInstance().getCallForRoom(payload.room_id); - const silenced = CallHandler.sharedInstance().isCallSilenced(incomingCall.callId); if (incomingCall && incomingCall.state === CallState.Ringing) { - this.setState({ incomingCall, silenced }); + this.setState({ + incomingCall, + silenced: CallHandler.sharedInstance().isCallSilenced(incomingCall.callId), + }); } else { this.setState({ incomingCall: null }); } From f592d37f3931625effd62280ec70bea0de3f9a2a Mon Sep 17 00:00:00 2001 From: Dariusz Niemczyk Date: Tue, 3 Aug 2021 14:15:14 +0200 Subject: [PATCH 080/176] 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 081/176] 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 082/176] 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 083/176] 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 084/176] 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 085/176] 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 086/176] 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 2a378f30b7f3b71deddcba306078c78332c647a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 3 Aug 2021 15:38:12 +0200 Subject: [PATCH 087/176] Attempt to fix tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- test/test-utils.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/test-utils.js b/test/test-utils.js index 5e29fd242e..6d0d930d93 100644 --- a/test/test-utils.js +++ b/test/test-utils.js @@ -48,6 +48,7 @@ export function createTestClient() { getDomain: jest.fn().mockReturnValue("matrix.rog"), getUserId: jest.fn().mockReturnValue("@userId:matrix.rog"), + getPushRuleById: jest.fn().mockReturnValue(null), getPushActionsForEvent: jest.fn(), getRoom: jest.fn().mockImplementation(mkStubRoom), getRooms: jest.fn().mockReturnValue([]), From a18f41ceed92de96a2df3d21ac23799660142a9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 3 Aug 2021 15:43:56 +0200 Subject: [PATCH 088/176] Fix tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- test/test-utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test-utils.js b/test/test-utils.js index 6d0d930d93..fb2d866962 100644 --- a/test/test-utils.js +++ b/test/test-utils.js @@ -48,7 +48,6 @@ export function createTestClient() { getDomain: jest.fn().mockReturnValue("matrix.rog"), getUserId: jest.fn().mockReturnValue("@userId:matrix.rog"), - getPushRuleById: jest.fn().mockReturnValue(null), getPushActionsForEvent: jest.fn(), getRoom: jest.fn().mockImplementation(mkStubRoom), getRooms: jest.fn().mockReturnValue([]), @@ -96,6 +95,7 @@ export function createTestClient() { getItem: jest.fn(), }, }, + pushRules: {}, decryptEventIfNeeded: () => Promise.resolve(), isUserIgnored: jest.fn().mockReturnValue(false), getCapabilities: jest.fn().mockResolvedValue({}), From 7487636f90f53306ac9fcca40876b7f0b97dea1c Mon Sep 17 00:00:00 2001 From: Dariusz Niemczyk Date: Tue, 3 Aug 2021 18:00:11 +0200 Subject: [PATCH 089/176] 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 090/176] 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 28f5dc483b7f016ca1966fcb984d0e66fd322d93 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 3 Aug 2021 17:07:37 +0100 Subject: [PATCH 091/176] Update eslint plugin & fix silly indenting As per https://github.com/matrix-org/eslint-plugin-matrix-org/pull/15 this caused a bunch of silly indenting to creep in, so this fixes it back to the previous style. --- package.json | 2 +- src/IdentityAuthClient.js | 30 +++++++++---------- src/accessibility/KeyboardShortcuts.tsx | 2 +- .../views/dialogs/DevtoolsDialog.tsx | 6 ++-- .../dialogs/KeySignatureUploadFailedDialog.js | 8 ++--- .../views/right_panel/PinnedMessagesCard.tsx | 2 +- src/components/views/right_panel/UserInfo.tsx | 4 +-- src/components/views/rooms/NewRoomIntro.tsx | 6 ++-- .../views/rooms/ReadReceiptMarker.js | 8 ++--- .../elements/MemberEventListSummary-test.js | 6 ++-- test/notifications/ContentRules-test.js | 2 +- test/test-utils.js | 4 +-- test/utils/ShieldUtils-test.js | 12 ++++---- yarn.lock | 6 ++-- 14 files changed, 49 insertions(+), 49 deletions(-) diff --git a/package.json b/package.json index 9744aa7685..704315ca69 100644 --- a/package.json +++ b/package.json @@ -153,7 +153,7 @@ "enzyme": "^3.11.0", "eslint": "7.18.0", "eslint-config-google": "^0.14.0", - "eslint-plugin-matrix-org": "github:matrix-org/eslint-plugin-matrix-org#main", + "eslint-plugin-matrix-org": "github:matrix-org/eslint-plugin-matrix-org#2306b3d4da4eba908b256014b979f1d3d43d2945", "eslint-plugin-react": "^7.22.0", "eslint-plugin-react-hooks": "^4.2.0", "glob": "^7.1.6", diff --git a/src/IdentityAuthClient.js b/src/IdentityAuthClient.js index e91e1d72cf..ffece510de 100644 --- a/src/IdentityAuthClient.js +++ b/src/IdentityAuthClient.js @@ -146,23 +146,23 @@ export default class IdentityAuthClient { const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); const { finished } = Modal.createTrackedDialog('Default identity server terms warning', '', QuestionDialog, { - title: _t("Identity server has no terms of service"), - description: ( -
-

{ _t( - "This action requires accessing the default identity server " + + title: _t("Identity server has no terms of service"), + description: ( +

+

{ _t( + "This action requires accessing the default identity server " + " to validate an email address or phone number, " + "but the server does not have any terms of service.", {}, - { - server: () => { abbreviateUrl(identityServerUrl) }, - }, - ) }

-

{ _t( - "Only continue if you trust the owner of the server.", - ) }

-
- ), - button: _t("Trust"), + { + server: () => { abbreviateUrl(identityServerUrl) }, + }, + ) }

+

{ _t( + "Only continue if you trust the owner of the server.", + ) }

+
+ ), + button: _t("Trust"), }); const [confirmed] = await finished; if (confirmed) { diff --git a/src/accessibility/KeyboardShortcuts.tsx b/src/accessibility/KeyboardShortcuts.tsx index 9cc7b60c99..c66984191f 100644 --- a/src/accessibility/KeyboardShortcuts.tsx +++ b/src/accessibility/KeyboardShortcuts.tsx @@ -163,7 +163,7 @@ const shortcuts: Record = { modifiers: [Modifiers.SHIFT], key: Key.PAGE_UP, }], - description: _td("Jump to oldest unread message"), + description: _td("Jump to oldest unread message"), }, { keybinds: [{ modifiers: [CMD_OR_CTRL, Modifiers.SHIFT], diff --git a/src/components/views/dialogs/DevtoolsDialog.tsx b/src/components/views/dialogs/DevtoolsDialog.tsx index 8ae9d0654f..30e6c70f0e 100644 --- a/src/components/views/dialogs/DevtoolsDialog.tsx +++ b/src/components/views/dialogs/DevtoolsDialog.tsx @@ -490,9 +490,9 @@ class RoomStateExplorer extends React.PureComponent; } diff --git a/src/components/views/dialogs/KeySignatureUploadFailedDialog.js b/src/components/views/dialogs/KeySignatureUploadFailedDialog.js index 9fa9fc1373..6b36c19977 100644 --- a/src/components/views/dialogs/KeySignatureUploadFailedDialog.js +++ b/src/components/views/dialogs/KeySignatureUploadFailedDialog.js @@ -20,10 +20,10 @@ import { _t } from '../../../languageHandler'; import SdkConfig from '../../../SdkConfig'; export default function KeySignatureUploadFailedDialog({ - failures, - source, - continuation, - onFinished, + failures, + source, + continuation, + onFinished, }) { const RETRIES = 2; const BaseDialog = sdk.getComponent('dialogs.BaseDialog'); diff --git a/src/components/views/right_panel/PinnedMessagesCard.tsx b/src/components/views/right_panel/PinnedMessagesCard.tsx index 19ffe81ac1..c82e5a3f80 100644 --- a/src/components/views/right_panel/PinnedMessagesCard.tsx +++ b/src/components/views/right_panel/PinnedMessagesCard.tsx @@ -152,7 +152,7 @@ const PinnedMessagesCard = ({ room, onClose }: IProps) => {

{ _t("Nothing pinned, yet") }

{ _t("If you have permissions, open the menu on any message and select " + "Pin to stick them here.", {}, { - b: sub => { sub }, + b: sub => { sub }, }) }
; diff --git a/src/components/views/right_panel/UserInfo.tsx b/src/components/views/right_panel/UserInfo.tsx index c837e814c8..ba6bb26cbf 100644 --- a/src/components/views/right_panel/UserInfo.tsx +++ b/src/components/views/right_panel/UserInfo.tsx @@ -1381,8 +1381,8 @@ const BasicUserInfo: React.FC<{ className="mx_UserInfo_field" onClick={() => { dis.dispatch({ - action: Action.ViewUserSettings, - initialTabId: UserTab.Security, + action: Action.ViewUserSettings, + initialTabId: UserTab.Security, }); }} > diff --git a/src/components/views/rooms/NewRoomIntro.tsx b/src/components/views/rooms/NewRoomIntro.tsx index cdca9ab75d..674bcdaec2 100644 --- a/src/components/views/rooms/NewRoomIntro.tsx +++ b/src/components/views/rooms/NewRoomIntro.tsx @@ -64,9 +64,9 @@ const NewRoomIntro = () => { height={AVATAR_SIZE} onClick={() => { defaultDispatcher.dispatch({ - action: Action.ViewUser, - // XXX: We should be using a real member object and not assuming what the receiver wants. - member: member || { userId: dmPartner } as User, + action: Action.ViewUser, + // XXX: We should be using a real member object and not assuming what the receiver wants. + member: member || { userId: dmPartner } as User, }); }} /> diff --git a/src/components/views/rooms/ReadReceiptMarker.js b/src/components/views/rooms/ReadReceiptMarker.js index 4ea16f69a3..c9688b4d29 100644 --- a/src/components/views/rooms/ReadReceiptMarker.js +++ b/src/components/views/rooms/ReadReceiptMarker.js @@ -145,7 +145,7 @@ export default class ReadReceiptMarker extends React.PureComponent { if (oldInfo && oldInfo.left) { // start at the old height and in the old h pos startStyles.push({ top: startTopOffset+"px", - left: toPx(oldInfo.left) }); + left: toPx(oldInfo.left) }); } startStyles.push({ top: startTopOffset+'px', left: '0' }); @@ -174,14 +174,14 @@ export default class ReadReceiptMarker extends React.PureComponent { title = _t( "Seen by %(userName)s at %(dateTime)s", { userName: this.props.fallbackUserId, - dateTime: dateString }, + dateTime: dateString }, ); } else { title = _t( "Seen by %(displayName)s (%(userName)s) at %(dateTime)s", { displayName: this.props.member.rawDisplayName, - userName: this.props.fallbackUserId, - dateTime: dateString }, + userName: this.props.fallbackUserId, + dateTime: dateString }, ); } } diff --git a/test/components/views/elements/MemberEventListSummary-test.js b/test/components/views/elements/MemberEventListSummary-test.js index e2d51f13a4..dcb895f09e 100644 --- a/test/components/views/elements/MemberEventListSummary-test.js +++ b/test/components/views/elements/MemberEventListSummary-test.js @@ -106,7 +106,7 @@ describe('MemberEventListSummary', function() { const result = wrapper.props.children; expect(result.props.children).toEqual([ -
Expanded membership
, +
Expanded membership
, ]); }); @@ -129,8 +129,8 @@ describe('MemberEventListSummary', function() { const result = wrapper.props.children; expect(result.props.children).toEqual([ -
Expanded membership
, -
Expanded membership
, +
Expanded membership
, +
Expanded membership
, ]); }); diff --git a/test/notifications/ContentRules-test.js b/test/notifications/ContentRules-test.js index 9c21c05da7..2b18a18488 100644 --- a/test/notifications/ContentRules-test.js +++ b/test/notifications/ContentRules-test.js @@ -56,7 +56,7 @@ describe("ContentRules", function() { describe("parseContentRules", function() { it("should handle there being no keyword rules", function() { const rules = { 'global': { 'content': [ - USERNAME_RULE, + USERNAME_RULE, ] } }; const parsed = ContentRules.parseContentRules(rules); expect(parsed.rules).toEqual([]); diff --git a/test/test-utils.js b/test/test-utils.js index 5e29fd242e..217c399443 100644 --- a/test/test-utils.js +++ b/test/test-utils.js @@ -129,8 +129,8 @@ export function mkEvent(opts) { if (opts.skey) { event.state_key = opts.skey; } else if (["m.room.name", "m.room.topic", "m.room.create", "m.room.join_rules", - "m.room.power_levels", "m.room.topic", "m.room.history_visibility", "m.room.encryption", - "com.example.state"].indexOf(opts.type) !== -1) { + "m.room.power_levels", "m.room.topic", "m.room.history_visibility", "m.room.encryption", + "com.example.state"].indexOf(opts.type) !== -1) { event.state_key = ""; } return opts.event ? new MatrixEvent(event) : event; diff --git a/test/utils/ShieldUtils-test.js b/test/utils/ShieldUtils-test.js index b70031dc21..85f9de3150 100644 --- a/test/utils/ShieldUtils-test.js +++ b/test/utils/ShieldUtils-test.js @@ -49,7 +49,7 @@ describe("shieldStatusForMembership self-trust behaviour", function() { it.each( [[true, true], [true, false], - [false, true], [false, false]], + [false, true], [false, false]], )("2 unverified: returns 'normal', self-trust = %s, DM = %s", async (trusted, dm) => { const client = mkClient(trusted); const room = { @@ -62,7 +62,7 @@ describe("shieldStatusForMembership self-trust behaviour", function() { it.each( [["verified", true, true], ["verified", true, false], - ["verified", false, true], ["warning", false, false]], + ["verified", false, true], ["warning", false, false]], )("2 verified: returns '%s', self-trust = %s, DM = %s", async (result, trusted, dm) => { const client = mkClient(trusted); const room = { @@ -75,7 +75,7 @@ describe("shieldStatusForMembership self-trust behaviour", function() { it.each( [["normal", true, true], ["normal", true, false], - ["normal", false, true], ["warning", false, false]], + ["normal", false, true], ["warning", false, false]], )("2 mixed: returns '%s', self-trust = %s, DM = %s", async (result, trusted, dm) => { const client = mkClient(trusted); const room = { @@ -88,7 +88,7 @@ describe("shieldStatusForMembership self-trust behaviour", function() { it.each( [["verified", true, true], ["verified", true, false], - ["warning", false, true], ["warning", false, false]], + ["warning", false, true], ["warning", false, false]], )("0 others: returns '%s', self-trust = %s, DM = %s", async (result, trusted, dm) => { const client = mkClient(trusted); const room = { @@ -101,7 +101,7 @@ describe("shieldStatusForMembership self-trust behaviour", function() { it.each( [["verified", true, true], ["verified", true, false], - ["verified", false, true], ["verified", false, false]], + ["verified", false, true], ["verified", false, false]], )("1 verified: returns '%s', self-trust = %s, DM = %s", async (result, trusted, dm) => { const client = mkClient(trusted); const room = { @@ -114,7 +114,7 @@ describe("shieldStatusForMembership self-trust behaviour", function() { it.each( [["normal", true, true], ["normal", true, false], - ["normal", false, true], ["normal", false, false]], + ["normal", false, true], ["normal", false, false]], )("1 unverified: returns '%s', self-trust = %s, DM = %s", async (result, trusted, dm) => { const client = mkClient(trusted); const room = { diff --git a/yarn.lock b/yarn.lock index 2a03f640ee..f121112a1b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3233,9 +3233,9 @@ eslint-config-google@^0.14.0: resolved "https://registry.yarnpkg.com/eslint-config-google/-/eslint-config-google-0.14.0.tgz#4f5f8759ba6e11b424294a219dbfa18c508bcc1a" integrity sha512-WsbX4WbjuMvTdeVL6+J3rK1RGhCTqjsFjX7UMSMgZiyxxaNLkoJENbrGExzERFeoTpGw3F3FypTiWAP9ZXzkEw== -"eslint-plugin-matrix-org@github:matrix-org/eslint-plugin-matrix-org#main": - version "0.3.4" - resolved "https://codeload.github.com/matrix-org/eslint-plugin-matrix-org/tar.gz/45f6937539192e3820edcafc4d6d4d4187e85a6a" +"eslint-plugin-matrix-org@github:matrix-org/eslint-plugin-matrix-org#2306b3d4da4eba908b256014b979f1d3d43d2945": + version "0.3.5" + resolved "https://codeload.github.com/matrix-org/eslint-plugin-matrix-org/tar.gz/2306b3d4da4eba908b256014b979f1d3d43d2945" eslint-plugin-react-hooks@^4.2.0: version "4.2.0" From 612384a6a36bf468cc4575d8fccb9d1cbf941ff0 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 3 Aug 2021 18:03:46 +0100 Subject: [PATCH 092/176] Switch to new changelog generator allchange is typescript so has a 'prepare' script to tsc it into javascript so it can be a binscript - hopefully this won't cause it to make too much of a pain of itself causing tsc to run on every yarn add/install --- package.json | 1 + yarn.lock | 311 ++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 309 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 704315ca69..fcb1e9133c 100644 --- a/package.json +++ b/package.json @@ -147,6 +147,7 @@ "@typescript-eslint/eslint-plugin": "^4.17.0", "@typescript-eslint/parser": "^4.17.0", "@wojtekmaj/enzyme-adapter-react-17": "^0.6.1", + "allchange": "github:matrix-org/allchange", "babel-jest": "^26.6.3", "chokidar": "^3.5.1", "concurrently": "^5.3.0", diff --git a/yarn.lock b/yarn.lock index f121112a1b..2b504d2366 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1324,6 +1324,107 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@octokit/auth-token@^2.4.4": + version "2.4.5" + resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-2.4.5.tgz#568ccfb8cb46f36441fac094ce34f7a875b197f3" + integrity sha512-BpGYsPgJt05M7/L/5FoE1PiAbdxXFZkX/3kDYcsvd1v6UhlnE5e96dTDr0ezX/EFwciQxf3cNV0loipsURU+WA== + dependencies: + "@octokit/types" "^6.0.3" + +"@octokit/core@^3.5.0": + version "3.5.1" + resolved "https://registry.yarnpkg.com/@octokit/core/-/core-3.5.1.tgz#8601ceeb1ec0e1b1b8217b960a413ed8e947809b" + integrity sha512-omncwpLVxMP+GLpLPgeGJBF6IWJFjXDS5flY5VbppePYX9XehevbDykRH9PdCdvqt9TS5AOTiDide7h0qrkHjw== + dependencies: + "@octokit/auth-token" "^2.4.4" + "@octokit/graphql" "^4.5.8" + "@octokit/request" "^5.6.0" + "@octokit/request-error" "^2.0.5" + "@octokit/types" "^6.0.3" + before-after-hook "^2.2.0" + universal-user-agent "^6.0.0" + +"@octokit/endpoint@^6.0.1": + version "6.0.12" + resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-6.0.12.tgz#3b4d47a4b0e79b1027fb8d75d4221928b2d05658" + integrity sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA== + dependencies: + "@octokit/types" "^6.0.3" + is-plain-object "^5.0.0" + universal-user-agent "^6.0.0" + +"@octokit/graphql@^4.5.8": + version "4.6.4" + resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-4.6.4.tgz#0c3f5bed440822182e972317122acb65d311a5ed" + integrity sha512-SWTdXsVheRmlotWNjKzPOb6Js6tjSqA2a8z9+glDJng0Aqjzti8MEWOtuT8ZSu6wHnci7LZNuarE87+WJBG4vg== + dependencies: + "@octokit/request" "^5.6.0" + "@octokit/types" "^6.0.3" + universal-user-agent "^6.0.0" + +"@octokit/openapi-types@^9.3.0": + version "9.3.0" + resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-9.3.0.tgz#160347858d727527901c6aae7f7d5c2414cc1f2e" + integrity sha512-oz60hhL+mDsiOWhEwrj5aWXTOMVtQgcvP+sRzX4C3cH7WOK9QSAoEtjWh0HdOf6V3qpdgAmUMxnQPluzDWR7Fw== + +"@octokit/plugin-paginate-rest@^2.6.2": + version "2.15.0" + resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.15.0.tgz#9c956c3710b2bd786eb3814eaf5a2b17392c150d" + integrity sha512-/vjcb0w6ggVRtsb8OJBcRR9oEm+fpdo8RJk45khaWw/W0c8rlB2TLCLyZt/knmC17NkX7T9XdyQeEY7OHLSV1g== + dependencies: + "@octokit/types" "^6.23.0" + +"@octokit/plugin-request-log@^1.0.2": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz#5e50ed7083a613816b1e4a28aeec5fb7f1462e85" + integrity sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA== + +"@octokit/plugin-rest-endpoint-methods@5.6.0": + version "5.6.0" + resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.6.0.tgz#c28833b88d0f07bf94093405d02d43d73c7de99b" + integrity sha512-2G7lIPwjG9XnTlNhe/TRnpI8yS9K2l68W4RP/ki3wqw2+sVeTK8hItPxkqEI30VeH0UwnzpuksMU/yHxiVVctw== + dependencies: + "@octokit/types" "^6.23.0" + deprecation "^2.3.1" + +"@octokit/request-error@^2.0.5", "@octokit/request-error@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-2.1.0.tgz#9e150357831bfc788d13a4fd4b1913d60c74d677" + integrity sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg== + dependencies: + "@octokit/types" "^6.0.3" + deprecation "^2.0.0" + once "^1.4.0" + +"@octokit/request@^5.6.0": + version "5.6.0" + resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.6.0.tgz#6084861b6e4fa21dc40c8e2a739ec5eff597e672" + integrity sha512-4cPp/N+NqmaGQwbh3vUsYqokQIzt7VjsgTYVXiwpUP2pxd5YiZB2XuTedbb0SPtv9XS7nzAKjAuQxmY8/aZkiA== + dependencies: + "@octokit/endpoint" "^6.0.1" + "@octokit/request-error" "^2.1.0" + "@octokit/types" "^6.16.1" + is-plain-object "^5.0.0" + node-fetch "^2.6.1" + universal-user-agent "^6.0.0" + +"@octokit/rest@^18.6.7": + version "18.8.0" + resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-18.8.0.tgz#ba24f7ba554f015a7ae2b7cc2aecef5386ddfea5" + integrity sha512-lsuNRhgzGnLMn/NmQTNCit/6jplFWiTUlPXhqN0zCMLwf2/9pseHzsnTW+Cjlp4bLMEJJNPa5JOzSLbSCOahKw== + dependencies: + "@octokit/core" "^3.5.0" + "@octokit/plugin-paginate-rest" "^2.6.2" + "@octokit/plugin-request-log" "^1.0.2" + "@octokit/plugin-rest-endpoint-methods" "5.6.0" + +"@octokit/types@^6.0.3", "@octokit/types@^6.16.1", "@octokit/types@^6.23.0": + version "6.23.0" + resolved "https://registry.yarnpkg.com/@octokit/types/-/types-6.23.0.tgz#b39f242b20036e89fa8f34f7962b4e9b7ff8f65b" + integrity sha512-eG3clC31GSS7K3oBK6C6o7wyXPrkP+mu++eus8CSZdpRytJ5PNszYxudOQ0spWZQ3S9KAtoTG6v1WK5prJcJrA== + dependencies: + "@octokit/openapi-types" "^9.3.0" + "@peculiar/asn1-schema@^2.0.27", "@peculiar/asn1-schema@^2.0.32": version "2.0.37" resolved "https://registry.yarnpkg.com/@peculiar/asn1-schema/-/asn1-schema-2.0.37.tgz#700476512ab903d809f64a3040fb1b2fe6fb6d4b" @@ -1850,6 +1951,17 @@ ajv@^8.0.1: require-from-string "^2.0.2" uri-js "^4.2.2" +"allchange@github:matrix-org/allchange": + version "0.0.1" + resolved "https://codeload.github.com/matrix-org/allchange/tar.gz/56b37b06339a3ac3fe771f3ec3d0bff798df8dab" + dependencies: + "@octokit/rest" "^18.6.7" + cli-color "^2.0.0" + js-yaml "^4.1.0" + loglevel "^1.7.1" + semver "^7.3.5" + yargs "^17.0.1" + another-json@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/another-json/-/another-json-0.2.0.tgz#b5f4019c973b6dd5c6506a2d93469cb6d32aeedc" @@ -1867,6 +1979,11 @@ ansi-escapes@^4.2.1: dependencies: type-fest "^0.21.3" +ansi-regex@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= + ansi-regex@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" @@ -1914,6 +2031,11 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + arr-diff@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" @@ -2206,6 +2328,11 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" +before-after-hook@^2.2.0: + version "2.2.2" + resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.2.tgz#a6e8ca41028d90ee2c24222f201c90956091613e" + integrity sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ== + binary-extensions@^1.0.0: version "1.13.1" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" @@ -2506,6 +2633,18 @@ classnames@*, classnames@^2.2.6: resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e" integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA== +cli-color@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/cli-color/-/cli-color-2.0.0.tgz#11ecfb58a79278cf6035a60c54e338f9d837897c" + integrity sha512-a0VZ8LeraW0jTuCkuAGMNufareGHhyZU9z8OGsW0gXd1hZGi1SRuNRXdbGkraBBKnhyUhyebFWnRbp+dIn0f0A== + dependencies: + ansi-regex "^2.1.1" + d "^1.0.1" + es5-ext "^0.10.51" + es6-iterator "^2.0.3" + memoizee "^0.4.14" + timers-ext "^0.1.7" + cliui@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" @@ -2524,6 +2663,15 @@ cliui@^6.0.0: strip-ansi "^6.0.0" wrap-ansi "^6.2.0" +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + clone-deep@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" @@ -2788,6 +2936,14 @@ csstype@^3.0.2: resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.8.tgz#d2266a792729fb227cd216fb572f43728e1ad340" integrity sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw== +d@1, d@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" + integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== + dependencies: + es5-ext "^0.10.50" + type "^1.0.1" + dashdash@^1.12.0: version "1.14.1" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" @@ -2895,6 +3051,11 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= +deprecation@^2.0.0, deprecation@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919" + integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ== + detect-newline@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" @@ -3191,6 +3352,42 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" +es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@^0.10.51, es5-ext@^0.10.53, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46: + version "0.10.53" + resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.53.tgz#93c5a3acfdbef275220ad72644ad02ee18368de1" + integrity sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q== + dependencies: + es6-iterator "~2.0.3" + es6-symbol "~3.1.3" + next-tick "~1.0.0" + +es6-iterator@^2.0.3, es6-iterator@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" + integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c= + dependencies: + d "1" + es5-ext "^0.10.35" + es6-symbol "^3.1.1" + +es6-symbol@^3.1.1, es6-symbol@~3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" + integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== + dependencies: + d "^1.0.1" + ext "^1.1.2" + +es6-weak-map@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.3.tgz#b6da1f16cc2cc0d9be43e6bdbfc5e7dfcdf31d53" + integrity sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA== + dependencies: + d "1" + es5-ext "^0.10.46" + es6-iterator "^2.0.3" + es6-symbol "^3.1.1" + escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" @@ -3383,6 +3580,14 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== +event-emitter@^0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39" + integrity sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk= + dependencies: + d "1" + es5-ext "~0.10.14" + events@^3.2.0: version "3.3.0" resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" @@ -3478,6 +3683,13 @@ expect@^26.6.2: jest-message-util "^26.6.2" jest-regex-util "^26.0.0" +ext@^1.1.2: + version "1.4.0" + resolved "https://registry.yarnpkg.com/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244" + integrity sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A== + dependencies: + type "^2.0.0" + extend-shallow@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" @@ -3783,7 +3995,7 @@ gensync@^1.0.0-beta.2: resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== -get-caller-file@^2.0.1: +get-caller-file@^2.0.1, get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== @@ -4502,6 +4714,11 @@ is-potential-custom-element-name@^1.0.1: resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== +is-promise@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" + integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== + is-regex@^1.0.3, is-regex@^1.0.5, is-regex@^1.1.2, is-regex@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.3.tgz#d029f9aff6448b93ebbe3f33dac71511fdcbef9f" @@ -5122,6 +5339,13 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" @@ -5396,6 +5620,13 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +lru-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/lru-queue/-/lru-queue-0.1.0.tgz#2738bd9f0d3cf4f84490c5736c48699ac632cda3" + integrity sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM= + dependencies: + es5-ext "~0.10.2" + make-dir@^2.0.0, make-dir@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" @@ -5528,6 +5759,20 @@ memoize-one@^5.1.1: resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e" integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q== +memoizee@^0.4.14: + version "0.4.15" + resolved "https://registry.yarnpkg.com/memoizee/-/memoizee-0.4.15.tgz#e6f3d2da863f318d02225391829a6c5956555b72" + integrity sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ== + dependencies: + d "^1.0.1" + es5-ext "^0.10.53" + es6-weak-map "^2.0.3" + event-emitter "^0.3.5" + is-promise "^2.2.2" + lru-queue "^0.1.0" + next-tick "^1.1.0" + timers-ext "^0.1.7" + meow@^9.0.0: version "9.0.0" resolved "https://registry.yarnpkg.com/meow/-/meow-9.0.0.tgz#cd9510bc5cac9dee7d03c73ee1f9ad959f4ea364" @@ -5701,12 +5946,22 @@ nearley@^2.7.10: railroad-diagrams "^1.0.0" randexp "0.4.6" +next-tick@1, next-tick@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb" + integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ== + +next-tick@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" + integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= + nice-try@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== -node-fetch@2.6.1: +node-fetch@2.6.1, node-fetch@^2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== @@ -7498,6 +7753,14 @@ throat@^5.0.0: resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b" integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA== +timers-ext@^0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/timers-ext/-/timers-ext-0.1.7.tgz#6f57ad8578e07a3fb9f91d9387d65647555e25c6" + integrity sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ== + dependencies: + es5-ext "~0.10.46" + next-tick "1" + tiny-invariant@^1.0.6: version "1.1.0" resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875" @@ -7657,6 +7920,16 @@ type-fest@^0.8.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== +type@^1.0.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" + integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== + +type@^2.0.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/type/-/type-2.5.0.tgz#0a2e78c2e77907b252abe5f298c1b01c63f0db3d" + integrity sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw== + typedarray-to-buffer@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" @@ -7753,6 +8026,11 @@ unist-util-stringify-position@^2.0.0: dependencies: "@types/unist" "^2.0.2" +universal-user-agent@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.0.tgz#3381f8503b251c0d9cd21bc1de939ec9df5480ee" + integrity sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w== + universalify@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" @@ -8056,6 +8334,15 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" @@ -8091,6 +8378,11 @@ y18n@^4.0.0: resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + yallist@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" @@ -8117,7 +8409,7 @@ yargs-parser@^18.1.2: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^20.2.3: +yargs-parser@^20.2.2, yargs-parser@^20.2.3: version "20.2.9" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== @@ -8155,6 +8447,19 @@ yargs@^15.4.1: y18n "^4.0.0" yargs-parser "^18.1.2" +yargs@^17.0.1: + version "17.0.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.0.1.tgz#6a1ced4ed5ee0b388010ba9fd67af83b9362e0bb" + integrity sha512-xBBulfCc8Y6gLFcrPvtqKz9hz8SO0l1Ni8GgDekvBX2ro0HRQImDGnikfc33cgzcYUSncapnNcZDjVFIH3f6KQ== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + zwitch@^1.0.0: version "1.0.5" resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920" From 82c644564870090dc9293bdef86b5fb69d0f6dad Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 30 Jul 2021 21:35:11 +0100 Subject: [PATCH 093/176] Add release_config for changelogging --- release_config.yaml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 release_config.yaml diff --git a/release_config.yaml b/release_config.yaml new file mode 100644 index 0000000000..12e857cbdd --- /dev/null +++ b/release_config.yaml @@ -0,0 +1,4 @@ +subprojects: + matrix-js-sdk: + includeByDefault: false + From 7b565db02dacc067ae0d3b9adc7fbf3a277f8ee3 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 3 Aug 2021 12:52:21 -0600 Subject: [PATCH 094/176] Update uploading state designs --- res/css/views/rooms/_VoiceRecordComposerTile.scss | 8 ++++++-- src/components/views/rooms/VoiceRecordComposerTile.tsx | 9 ++++----- src/i18n/strings/en_EN.json | 2 -- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/res/css/views/rooms/_VoiceRecordComposerTile.scss b/res/css/views/rooms/_VoiceRecordComposerTile.scss index 3d8d0e5ecc..7acec5a133 100644 --- a/res/css/views/rooms/_VoiceRecordComposerTile.scss +++ b/res/css/views/rooms/_VoiceRecordComposerTile.scss @@ -46,9 +46,13 @@ limitations under the License. mask-image: url('$(res)/img/element-icons/trashcan.svg'); } -.mx_VoiceRecordComposerTile_uploadState { - margin-right: 21px; +.mx_VoiceRecordComposerTile_uploadingState { + margin-right: 10px; color: $secondary-fg-color; +} + +.mx_VoiceRecordComposerTile_failedState { + margin-right: 21px; .mx_VoiceRecordComposerTile_uploadState_badge { display: inline-block; diff --git a/src/components/views/rooms/VoiceRecordComposerTile.tsx b/src/components/views/rooms/VoiceRecordComposerTile.tsx index 490d6b2231..1f481d7531 100644 --- a/src/components/views/rooms/VoiceRecordComposerTile.tsx +++ b/src/components/views/rooms/VoiceRecordComposerTile.tsx @@ -246,20 +246,19 @@ export default class VoiceRecordComposerTile extends React.PureComponent + if (this.state.recordingPhase === RecordingState.Uploading || true) { + uploadIndicator = - { _t("Uploading...") } ; } else if (this.state.didUploadFail && this.state.recordingPhase === RecordingState.Ended) { - uploadIndicator = + uploadIndicator = { /* Need to stick the badge in a span to ensure it doesn't create a block component */ } - { _t("Failed to upload voice message") } + { _t("Failed to send") } ; } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index c929bd683b..9a717e31a4 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1708,8 +1708,6 @@ "Record a voice message": "Record a voice message", "Stop the recording": "Stop the recording", "Delete recording": "Delete recording", - "Uploading...": "Uploading...", - "Failed to upload voice message": "Failed to upload voice message", "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.", "There was an error updating the room's alternative addresses. It may not be allowed by the server or a temporary failure occurred.": "There was an error updating the room's alternative addresses. It may not be allowed by the server or a temporary failure occurred.", From c4e6cc797302fd12af7c33c05a7b91cf11226524 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 3 Aug 2021 12:57:16 -0600 Subject: [PATCH 095/176] Remove debugging --- src/components/views/rooms/VoiceRecordComposerTile.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/rooms/VoiceRecordComposerTile.tsx b/src/components/views/rooms/VoiceRecordComposerTile.tsx index 1f481d7531..b980b9295b 100644 --- a/src/components/views/rooms/VoiceRecordComposerTile.tsx +++ b/src/components/views/rooms/VoiceRecordComposerTile.tsx @@ -246,7 +246,7 @@ export default class VoiceRecordComposerTile extends React.PureComponent ; From 32442068cf53144f101523d56b3dcd7ab80a82ce Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 3 Aug 2021 13:26:27 -0600 Subject: [PATCH 096/176] Use a default waveform when recording to ease component pop-in Fixes https://github.com/vector-im/element-web/issues/18225 --- src/components/views/audio_messages/LiveRecordingWaveform.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/audio_messages/LiveRecordingWaveform.tsx b/src/components/views/audio_messages/LiveRecordingWaveform.tsx index 9c33889884..9f981e8ca2 100644 --- a/src/components/views/audio_messages/LiveRecordingWaveform.tsx +++ b/src/components/views/audio_messages/LiveRecordingWaveform.tsx @@ -17,7 +17,7 @@ limitations under the License. import React from "react"; import { IRecordingUpdate, RECORDING_PLAYBACK_SAMPLES, VoiceRecording } from "../../../audio/VoiceRecording"; import { replaceableComponent } from "../../../utils/replaceableComponent"; -import { arrayFastResample } from "../../../utils/arrays"; +import { arrayFastResample, arraySeed } from "../../../utils/arrays"; import { percentageOf } from "../../../utils/numbers"; import Waveform from "./Waveform"; import { MarkedExecution } from "../../../utils/MarkedExecution"; @@ -48,7 +48,7 @@ export default class LiveRecordingWaveform extends React.PureComponent Date: Tue, 3 Aug 2021 13:33:42 -0600 Subject: [PATCH 097/176] Improve hover states (tooltips) for voice message interactions Fixes https://github.com/vector-im/element-web/issues/18375 --- src/components/views/rooms/VoiceRecordComposerTile.tsx | 6 +++--- src/i18n/strings/en_EN.json | 5 ++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/components/views/rooms/VoiceRecordComposerTile.tsx b/src/components/views/rooms/VoiceRecordComposerTile.tsx index 8323320520..7949f41784 100644 --- a/src/components/views/rooms/VoiceRecordComposerTile.tsx +++ b/src/components/views/rooms/VoiceRecordComposerTile.tsx @@ -209,9 +209,9 @@ export default class VoiceRecordComposerTile extends React.PureComponent; } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 3ad8daa85c..cf06112b14 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1705,9 +1705,8 @@ "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.", - "Record a voice message": "Record a voice message", - "Stop the recording": "Stop the recording", - "Delete recording": "Delete recording", + "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.", "There was an error updating the room's alternative addresses. It may not be allowed by the server or a temporary failure occurred.": "There was an error updating the room's alternative addresses. It may not be allowed by the server or a temporary failure occurred.", From 24da0291a05a4ddfc4c008415b3ea6688836a73a Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 3 Aug 2021 13:33:58 -0600 Subject: [PATCH 098/176] Re-center recording LED Fixes https://github.com/vector-im/element-web/issues/18375 --- res/css/views/rooms/_VoiceRecordComposerTile.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/views/rooms/_VoiceRecordComposerTile.scss b/res/css/views/rooms/_VoiceRecordComposerTile.scss index 5501ab343e..8c13018c51 100644 --- a/res/css/views/rooms/_VoiceRecordComposerTile.scss +++ b/res/css/views/rooms/_VoiceRecordComposerTile.scss @@ -68,7 +68,7 @@ limitations under the License. height: 10px; position: absolute; left: 12px; // 12px from the left edge for container padding - top: 18px; // vertically center (middle align with clock) + top: 16px; // vertically center (middle align with clock) border-radius: 10px; } } From 1b9fe46733f8ae587ee592786a80a8b5b42a3ac7 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 3 Aug 2021 13:51:11 -0600 Subject: [PATCH 099/176] Remove unnecessary rescaling of voice waveforms Fixes https://github.com/vector-im/element-web/issues/18364 --- src/audio/Playback.ts | 14 +++----------- .../views/audio_messages/LiveRecordingWaveform.tsx | 9 ++------- 2 files changed, 5 insertions(+), 18 deletions(-) diff --git a/src/audio/Playback.ts b/src/audio/Playback.ts index 33d346629a..9dad828a79 100644 --- a/src/audio/Playback.ts +++ b/src/audio/Playback.ts @@ -38,17 +38,9 @@ function makePlaybackWaveform(input: number[]): number[] { // First, convert negative amplitudes to positive so we don't detect zero as "noisy". const noiseWaveform = input.map(v => Math.abs(v)); - // Next, we'll resample the waveform using a smoothing approach so we can keep the same rough shape. - // We also rescale the waveform to be 0-1 for the remaining function logic. - const resampled = arrayRescale(arraySmoothingResample(noiseWaveform, PLAYBACK_WAVEFORM_SAMPLES), 0, 1); - - // Then, we'll do a high and low pass filter to isolate actual speaking volumes within the rescaled - // waveform. Most speech happens below the 0.5 mark. - const filtered = resampled.map(v => clamp(v, 0.1, 0.5)); - - // Finally, we'll rescale the filtered waveform (0.1-0.5 becomes 0-1 again) so the user sees something - // sensible. This is what we return to keep our contract of "values between zero and one". - return arrayRescale(filtered, 0, 1); + // Then, we'll resample the waveform using a smoothing approach so we can keep the same rough shape. + // We also rescale the waveform to be 0-1 so we end up with a clamped waveform to rely upon. + return arrayRescale(arraySmoothingResample(noiseWaveform, PLAYBACK_WAVEFORM_SAMPLES), 0, 1); } export class Playback extends EventEmitter implements IDestroyable { diff --git a/src/components/views/audio_messages/LiveRecordingWaveform.tsx b/src/components/views/audio_messages/LiveRecordingWaveform.tsx index 9f981e8ca2..73e18626fe 100644 --- a/src/components/views/audio_messages/LiveRecordingWaveform.tsx +++ b/src/components/views/audio_messages/LiveRecordingWaveform.tsx @@ -18,7 +18,6 @@ import React from "react"; import { IRecordingUpdate, RECORDING_PLAYBACK_SAMPLES, VoiceRecording } from "../../../audio/VoiceRecording"; import { replaceableComponent } from "../../../utils/replaceableComponent"; import { arrayFastResample, arraySeed } from "../../../utils/arrays"; -import { percentageOf } from "../../../utils/numbers"; import Waveform from "./Waveform"; import { MarkedExecution } from "../../../utils/MarkedExecution"; @@ -54,12 +53,8 @@ export default class LiveRecordingWaveform extends React.PureComponent { - const bars = arrayFastResample(Array.from(update.waveform), RECORDING_PLAYBACK_SAMPLES); - // The incoming data is between zero and one, but typically even screaming into a - // microphone won't send you over 0.6, so we artificially adjust the gain for the - // waveform. This results in a slightly more cinematic/animated waveform for the - // user. - this.waveform = bars.map(b => percentageOf(b, 0, 0.50)); + // The incoming data is between zero and one, so we don't need to clamp/rescale it. + this.waveform = arrayFastResample(Array.from(update.waveform), RECORDING_PLAYBACK_SAMPLES); this.scheduledUpdate.mark(); }); } From 372cbbfe8ef3a4dd0396c2de49f7e71be2311511 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 4 Aug 2021 08:11:05 +0200 Subject: [PATCH 100/176] Increase snapping speed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/voip/CallPreview.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/voip/CallPreview.tsx b/src/components/views/voip/CallPreview.tsx index 6261b9965f..46ff8ca838 100644 --- a/src/components/views/voip/CallPreview.tsx +++ b/src/components/views/voip/CallPreview.tsx @@ -36,7 +36,7 @@ const PIP_VIEW_WIDTH = 336; const PIP_VIEW_HEIGHT = 232; const MOVING_AMT = 0.2; -const SNAPPING_AMT = 0.05; +const SNAPPING_AMT = 0.1; const PADDING = { top: 58, From 7fc632d0cfc25e45d31a164c0e22179b1effe782 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 4 Aug 2021 08:42:07 +0200 Subject: [PATCH 101/176] Put font names into "" so that stylelint shuts up about them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/themes/legacy-light/css/_legacy-light.scss | 5 +++-- res/themes/light/css/_light.scss | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/res/themes/legacy-light/css/_legacy-light.scss b/res/themes/legacy-light/css/_legacy-light.scss index b7d45452ff..b37b4300c9 100644 --- a/res/themes/legacy-light/css/_legacy-light.scss +++ b/res/themes/legacy-light/css/_legacy-light.scss @@ -8,9 +8,10 @@ /* Noto Color Emoji contains digits, in fixed-width, therefore causing digits in flowed text to stand out. TODO: Consider putting all emoji fonts to the end rather than the front. */ -$font-family: Nunito, Twemoji, 'Apple Color Emoji', 'Segoe UI Emoji', Arial, Helvetica, Sans-Serif, 'Noto Color Emoji'; +$font-family: 'Nunito', 'Twemoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Arial', 'Helvetica', 'Sans-Serif', 'Noto Color Emoji'; + +$monospace-font-family: 'Inconsolata', 'Twemoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Courier', 'monospace', 'Noto Color Emoji'; -$monospace-font-family: Inconsolata, Twemoji, 'Apple Color Emoji', 'Segoe UI Emoji', Courier, monospace, 'Noto Color Emoji'; // unified palette // try to use these colors when possible diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index 32722515d8..db3aa95442 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -8,9 +8,9 @@ /* Noto Color Emoji contains digits, in fixed-width, therefore causing digits in flowed text to stand out. TODO: Consider putting all emoji fonts to the end rather than the front. */ -$font-family: Inter, Twemoji, 'Apple Color Emoji', 'Segoe UI Emoji', Arial, Helvetica, Sans-Serif, 'Noto Color Emoji'; +$font-family: 'Inter', 'Twemoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Arial', 'Helvetica', 'Sans-Serif', 'Noto Color Emoji'; -$monospace-font-family: Inconsolata, Twemoji, 'Apple Color Emoji', 'Segoe UI Emoji', Courier, monospace, 'Noto Color Emoji'; +$monospace-font-family: 'Inconsolata', 'Twemoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Courier', 'monospace', 'Noto Color Emoji'; // unified palette // try to use these colors when possible From 050e260655ba1399cff0f835eba0da35a2f5360d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 4 Aug 2021 08:45:58 +0200 Subject: [PATCH 102/176] Fix weird spacing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/themes/legacy-light/css/_legacy-light.scss | 2 +- res/themes/light/css/_light.scss | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/res/themes/legacy-light/css/_legacy-light.scss b/res/themes/legacy-light/css/_legacy-light.scss index b37b4300c9..c784f18a2b 100644 --- a/res/themes/legacy-light/css/_legacy-light.scss +++ b/res/themes/legacy-light/css/_legacy-light.scss @@ -391,7 +391,7 @@ $eventbubble-reply-color: #C1C6CD; @define-mixin mx_DialogButton_secondary { // flip colours for the secondary ones font-weight: 600; - border: 1px solid $accent-color ! important; + border: 1px solid $accent-color !important; color: $accent-color; background-color: $button-secondary-bg-color; } diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index db3aa95442..1412b900f9 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -392,7 +392,7 @@ $eventbubble-reply-color: #C1C6CD; @define-mixin mx_DialogButton_secondary { // flip colours for the secondary ones font-weight: 600; - border: 1px solid $accent-color ! important; + border: 1px solid $accent-color !important; color: $accent-color; background-color: $button-secondary-bg-color; } From ca94518de5e3263f20f7778995bdb481e3ce82cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 4 Aug 2021 08:46:09 +0200 Subject: [PATCH 103/176] Improve bubble colors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/themes/dark/css/_dark.scss | 17 ++++++++++------- res/themes/legacy-light/css/_legacy-light.scss | 12 +++++++----- res/themes/light/css/_light.scss | 15 +++++++++------ 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index 7b83fe0cb2..6adc821487 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -1,3 +1,6 @@ +// Colors from Figma Compound https://www.figma.com/file/X4XTH9iS2KGJ2wFKDqkyed/Compound?node-id=557%3A0 +$system-dark: #21262C; + // unified palette // try to use these colors when possible $bg-color: #15191E; @@ -47,7 +50,7 @@ $inverted-bg-color: $base-color; $selected-color: $room-highlight-color; // selected for hoverover & selected event tiles -$event-selected-color: #21262c; +$event-selected-color: $system-dark; // used for the hairline dividers in RoomView $primary-hairline-color: transparent; @@ -91,7 +94,7 @@ $lightbox-background-bg-color: #000; $lightbox-background-bg-opacity: 0.85; $settings-grey-fg-color: #a2a2a2; -$settings-profile-placeholder-bg-color: #21262c; +$settings-profile-placeholder-bg-color: $system-dark; $settings-profile-overlay-placeholder-fg-color: #454545; $settings-profile-button-bg-color: #e7e7e7; $settings-profile-button-fg-color: $settings-profile-overlay-placeholder-fg-color; @@ -175,7 +178,7 @@ $button-link-bg-color: transparent; $togglesw-off-color: $room-highlight-color; $progressbar-fg-color: $accent-color; -$progressbar-bg-color: #21262c; +$progressbar-bg-color: $system-dark; $visual-bell-bg-color: #800; @@ -210,7 +213,7 @@ $user-tile-hover-bg-color: $header-panel-bg-color; $message-body-panel-fg-color: $secondary-fg-color; $message-body-panel-bg-color: #394049; // "Dark Tile" $message-body-panel-icon-fg-color: $secondary-fg-color; -$message-body-panel-icon-bg-color: #21262C; // "System Dark" +$message-body-panel-icon-bg-color: $system-dark; // "System Dark" $voice-record-stop-border-color: $quaternary-fg-color; $voice-record-waveform-incomplete-fg-color: $quaternary-fg-color; @@ -228,9 +231,9 @@ $groupFilterPanel-background-blur-amount: 30px; $composer-shadow-color: rgba(0, 0, 0, 0.28); // Bubble tiles -$eventbubble-self-bg: #143A34; -$eventbubble-others-bg: #394049; -$eventbubble-bg-hover: $header-panel-bg-color; +$eventbubble-self-bg: #14322E; +$eventbubble-others-bg: $event-selected-color; +$eventbubble-bg-hover: #1C2026; $eventbubble-avatar-outline: $bg-color; $eventbubble-reply-color: #C1C6CD; diff --git a/res/themes/legacy-light/css/_legacy-light.scss b/res/themes/legacy-light/css/_legacy-light.scss index c784f18a2b..e9bfe46c89 100644 --- a/res/themes/legacy-light/css/_legacy-light.scss +++ b/res/themes/legacy-light/css/_legacy-light.scss @@ -12,6 +12,8 @@ $font-family: 'Nunito', 'Twemoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Arial $monospace-font-family: 'Inconsolata', 'Twemoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Courier', 'monospace', 'Noto Color Emoji'; +// Colors from Figma Compound https://www.figma.com/file/X4XTH9iS2KGJ2wFKDqkyed/Compound?node-id=557%3A0 +$system-light: #F4F6FA; // unified palette // try to use these colors when possible @@ -180,7 +182,7 @@ $composer-e2e-icon-color: #91a1c0; $header-divider-color: #91a1c0; // this probably shouldn't have it's own colour -$voipcall-plinth-color: #F4F6FA; +$voipcall-plinth-color: $system-light; // ******************** @@ -332,7 +334,7 @@ $user-tile-hover-bg-color: $header-panel-bg-color; $message-body-panel-fg-color: $secondary-fg-color; $message-body-panel-bg-color: #E3E8F0; $message-body-panel-icon-fg-color: $secondary-fg-color; -$message-body-panel-icon-bg-color: #F4F6FA; +$message-body-panel-icon-bg-color: $system-light; // See non-legacy _light for variable information $voice-record-stop-symbol-color: #ff4b55; @@ -349,9 +351,9 @@ $appearance-tab-border-color: $input-darker-bg-color; $composer-shadow-color: tranparent; // Bubble tiles -$eventbubble-self-bg: #F8FDFC; -$eventbubble-others-bg: #F7F8F9; -$eventbubble-bg-hover: rgb(242, 242, 242); +$eventbubble-self-bg: #F0FBF8; +$eventbubble-others-bg: $system-light; +$eventbubble-bg-hover: #FAFBFD; $eventbubble-avatar-outline: #fff; $eventbubble-reply-color: #C1C6CD; diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index 1412b900f9..90ae808adc 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -12,6 +12,9 @@ $font-family: 'Inter', 'Twemoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Arial' $monospace-font-family: 'Inconsolata', 'Twemoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Courier', 'monospace', 'Noto Color Emoji'; +// Colors from Figma Compound https://www.figma.com/file/X4XTH9iS2KGJ2wFKDqkyed/Compound?node-id=557%3A0 +$system-light: #F4F6FA; + // unified palette // try to use these colors when possible $accent-color: #0DBD8B; @@ -138,7 +141,7 @@ $blockquote-bar-color: #ddd; $blockquote-fg-color: #777; $settings-grey-fg-color: #a2a2a2; -$settings-profile-placeholder-bg-color: #f4f6fa; +$settings-profile-placeholder-bg-color: $system-light; $settings-profile-overlay-placeholder-fg-color: #2e2f32; $settings-profile-button-bg-color: #e7e7e7; $settings-profile-button-fg-color: $settings-profile-overlay-placeholder-fg-color; @@ -168,7 +171,7 @@ $composer-e2e-icon-color: #91A1C0; $header-divider-color: #91A1C0; // this probably shouldn't have it's own colour -$voipcall-plinth-color: #F4F6FA; +$voipcall-plinth-color: $system-light; // ******************** @@ -327,7 +330,7 @@ $user-tile-hover-bg-color: $header-panel-bg-color; $message-body-panel-fg-color: $secondary-fg-color; $message-body-panel-bg-color: #E3E8F0; // "Separator" $message-body-panel-icon-fg-color: $secondary-fg-color; -$message-body-panel-icon-bg-color: #F4F6FA; +$message-body-panel-icon-bg-color: $system-light; // These two don't change between themes. They are the $warning-color, but we don't // want custom themes to affect them by accident. @@ -350,9 +353,9 @@ $groupFilterPanel-background-blur-amount: 20px; $composer-shadow-color: rgba(0, 0, 0, 0.04); // Bubble tiles -$eventbubble-self-bg: #F8FDFC; -$eventbubble-others-bg: #F7F8F9; -$eventbubble-bg-hover: #FEFCF5; +$eventbubble-self-bg: #F0FBF8; +$eventbubble-others-bg: $system-light; +$eventbubble-bg-hover: #FAFBFD; $eventbubble-avatar-outline: $primary-bg-color; $eventbubble-reply-color: #C1C6CD; From 4b78edd65241256351e68b799b05c607de5bd574 Mon Sep 17 00:00:00 2001 From: James Salter Date: Wed, 4 Aug 2021 09:30:04 +0100 Subject: [PATCH 104/176] Remove patch-package and postinstall-postinstall --- package.json | 5 +- patches/posthog-js+1.12.2.patch | 78 --------------------------- yarn.lock | 96 ++------------------------------- 3 files changed, 6 insertions(+), 173 deletions(-) delete mode 100644 patches/posthog-js+1.12.2.patch diff --git a/package.json b/package.json index e7ea85ccd4..8f5459ab99 100644 --- a/package.json +++ b/package.json @@ -51,8 +51,7 @@ "lint:style": "stylelint 'res/css/**/*.scss'", "test": "jest", "test:e2e": "./test/end-to-end-tests/run.sh --app-url http://localhost:8080", - "coverage": "yarn test --coverage", - "postinstall": "patch-package" + "coverage": "yarn test --coverage" }, "dependencies": { "@babel/runtime": "^7.12.5", @@ -166,8 +165,6 @@ "matrix-mock-request": "^1.2.3", "matrix-react-test-utils": "^0.2.3", "matrix-web-i18n": "github:matrix-org/matrix-web-i18n", - "patch-package": "^6.4.7", - "postinstall-postinstall": "^2.1.0", "react-test-renderer": "^17.0.2", "rimraf": "^3.0.2", "stylelint": "^13.9.0", diff --git a/patches/posthog-js+1.12.2.patch b/patches/posthog-js+1.12.2.patch deleted file mode 100644 index 430a9f0a62..0000000000 --- a/patches/posthog-js+1.12.2.patch +++ /dev/null @@ -1,78 +0,0 @@ -diff --git a/node_modules/posthog-js/dist/module.d.ts b/node_modules/posthog-js/dist/module.d.ts -index d3cba02..8c87ef8 100644 ---- a/node_modules/posthog-js/dist/module.d.ts -+++ b/node_modules/posthog-js/dist/module.d.ts -@@ -1,7 +1,5 @@ - // Type definitions for exported methods -- --import { EventProcessor, Integration } from '@sentry/types' --import { MaskInputOptions, SlimDOMOptions } from 'rrweb-snapshot' -+// Patched to remove references to sentry and rrweb-snapshot which aren't otherwise SDK dependencies - - declare class posthog { - /** -@@ -485,25 +483,6 @@ declare class posthog { - */ - static reloadFeatureFlags(): void - -- /** -- * Integrate Sentry with PostHog. This will add a direct link to the person in Sentry, and an $exception event in PostHog -- * -- * ### Usage -- * -- * Sentry.init({ -- * dsn: 'https://example', -- * integrations: [ -- * new posthog.SentryIntegration(posthog) -- * ] -- * }) -- * -- * @param {Object} [posthog] The posthog object -- * @param {string} [organization] Optional: The Sentry organization, used to send a direct link from PostHog to Sentry -- * @param {Number} [projectId] Optional: The Sentry project id, used to send a direct link from PostHog to Sentry -- * @param {string} [prefix] Optional: Url of a self-hosted sentry instance (default: https://sentry.io/organizations/) -- */ -- static SentryIntegration: typeof SentryIntegration -- - static toString(): string - - /* Will log all capture requests to the Javascript console, including event properties for easy debugging */ -@@ -585,7 +564,6 @@ declare namespace posthog { - request_batching?: boolean - sanitize_properties?: (properties: posthog.Properties, event_name: string) => posthog.Properties - properties_string_max_length?: number -- session_recording?: SessionRecordingOptions - mask_all_element_attributes?: boolean - mask_all_text?: boolean - advanced_disable_decide?: boolean -@@ -618,17 +596,6 @@ declare namespace posthog { - send_event: boolean - } - -- interface SessionRecordingOptions { -- blockClass?: string | RegExp -- blockSelector?: string -- ignoreClass?: string -- maskAllInputs?: boolean -- maskInputOptions?: MaskInputOptions -- maskInputFn?: (text: string) => string -- slimDOMOptions?: SlimDOMOptions | 'all' | true -- collectFonts?: boolean -- } -- - export class persistence { - static properties(): posthog.Properties - -@@ -768,12 +735,6 @@ declare namespace posthog { - export class feature_flags extends featureFlags {} - } - --export class SentryIntegration implements Integration { -- constructor(posthog: posthog, organization?: string, projectId?: number, prefix?: string) -- name: string -- setupOnce(addGlobalEventProcessor: (callback: EventProcessor) => void): void --} -- - export type PostHog = typeof posthog - - export default posthog diff --git a/yarn.lock b/yarn.lock index 6bb5654db8..891822a66e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1790,11 +1790,6 @@ object.fromentries "^2.0.0" prop-types "^15.7.0" -"@yarnpkg/lockfile@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" - integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== - abab@^2.0.3, abab@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" @@ -2718,7 +2713,7 @@ cross-fetch@^3.0.4: dependencies: node-fetch "2.6.1" -cross-spawn@^6.0.0, cross-spawn@^6.0.5: +cross-spawn@^6.0.0: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== @@ -3669,13 +3664,6 @@ find-up@^4.0.0, find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" -find-yarn-workspace-root@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz#f47fb8d239c900eb78179aa81b66673eac88f7bd" - integrity sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ== - dependencies: - micromatch "^4.0.2" - flat-cache@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" @@ -3755,15 +3743,6 @@ fragment-cache@^0.2.1: dependencies: map-cache "^0.2.2" -fs-extra@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" - integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== - dependencies: - graceful-fs "^4.1.2" - jsonfile "^4.0.0" - universalify "^0.1.0" - fs-readdir-recursive@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz#e32fc030a2ccee44a6b5371308da54be0b397d27" @@ -3940,7 +3919,7 @@ gonzales-pe@^4.3.0: dependencies: minimist "^1.2.5" -graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.4: +graceful-fs@^4.1.11, graceful-fs@^4.2.4: version "4.2.6" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== @@ -4616,7 +4595,7 @@ is-windows@^1.0.2: resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== -is-wsl@^2.1.1, is-wsl@^2.2.0: +is-wsl@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== @@ -5238,13 +5217,6 @@ json5@^2.1.2: dependencies: minimist "^1.2.5" -jsonfile@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" - integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= - optionalDependencies: - graceful-fs "^4.1.6" - jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -5294,13 +5266,6 @@ kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3: resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== -klaw-sync@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/klaw-sync/-/klaw-sync-6.0.0.tgz#1fd2cfd56ebb6250181114f0a581167099c2b28c" - integrity sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ== - dependencies: - graceful-fs "^4.1.11" - kleur@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" @@ -5972,14 +5937,6 @@ onetime@^5.1.0: dependencies: mimic-fn "^2.1.0" -open@^7.4.2: - version "7.4.2" - resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321" - integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q== - dependencies: - is-docker "^2.0.0" - is-wsl "^2.1.1" - optionator@^0.8.1: version "0.8.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" @@ -6009,11 +5966,6 @@ opus-recorder@^8.0.3: resolved "https://registry.yarnpkg.com/opus-recorder/-/opus-recorder-8.0.3.tgz#f7b44f8f68500c9b96a15042a69f915fd9c1716d" integrity sha512-8vXGiRwlJAavT9D3yYzukNVXQ8vEcKHcsQL/zXO24DQtJ0PLXvoPHNQPJrbMCdB4ypJgWDExvHF4JitQDL7dng== -os-tmpdir@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= - p-each-series@^2.1.0: version "2.2.0" resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.2.0.tgz#105ab0357ce72b202a8a8b94933672657b5e2a9a" @@ -6122,25 +6074,6 @@ pascalcase@^0.1.1: resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= -patch-package@^6.4.7: - version "6.4.7" - resolved "https://registry.yarnpkg.com/patch-package/-/patch-package-6.4.7.tgz#2282d53c397909a0d9ef92dae3fdeb558382b148" - integrity sha512-S0vh/ZEafZ17hbhgqdnpunKDfzHQibQizx9g8yEf5dcVk3KOflOfdufRXQX8CSEkyOQwuM/bNz1GwKvFj54kaQ== - dependencies: - "@yarnpkg/lockfile" "^1.1.0" - chalk "^2.4.2" - cross-spawn "^6.0.5" - find-yarn-workspace-root "^2.0.0" - fs-extra "^7.0.1" - is-ci "^2.0.0" - klaw-sync "^6.0.0" - minimist "^1.2.0" - open "^7.4.2" - rimraf "^2.6.3" - semver "^5.6.0" - slash "^2.0.0" - tmp "^0.0.33" - path-exists@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" @@ -6321,18 +6254,13 @@ postcss@^8.0.2: nanoid "^3.1.23" source-map-js "^0.6.2" -posthog-js@^1.12.2: +posthog-js@1.12.2: version "1.12.2" resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.12.2.tgz#ff76e26634067e003f8af7df654d7ea0e647d946" integrity sha512-I0d6c+Yu2f91PFidz65AIkkqZM219EY9Z1wlbTkW5Zqfq5oXqogBMKS8BaDBOrMc46LjLX7IH67ytCcBFRo1uw== dependencies: fflate "^0.4.1" -postinstall-postinstall@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/postinstall-postinstall/-/postinstall-postinstall-2.1.0.tgz#4f7f77441ef539d1512c40bd04c71b06a4704ca3" - integrity sha512-7hQX6ZlZXIoRiWNrbMQaLzUUfH+sSx39u8EJ9HYuDc1kLo9IXKWjM5RSquZN1ad5GnH8CGFM78fsAAQi3OKEEQ== - prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -6906,13 +6834,6 @@ rfc4648@^1.4.0: resolved "https://registry.yarnpkg.com/rfc4648/-/rfc4648-1.5.0.tgz#1ba940ec1649685ec4d88788dc57fb8e18855055" integrity sha512-FA6W9lDNeX8WbMY31io1xWg+TpZCbeDKsBo0ocwACZiWnh9TUAyk9CCuBQuOPmYnwwdEQZmraQ2ZK7yJsxErBg== -rimraf@^2.6.3: - version "2.7.1" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" - integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== - dependencies: - glob "^7.1.3" - rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" @@ -7599,13 +7520,6 @@ tmatch@^2.0.1: resolved "https://registry.yarnpkg.com/tmatch/-/tmatch-2.0.1.tgz#0c56246f33f30da1b8d3d72895abaf16660f38cf" integrity sha1-DFYkbzPzDaG409colauvFmYPOM8= -tmp@^0.0.33: - version "0.0.33" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" - integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== - dependencies: - os-tmpdir "~1.0.2" - tmpl@1.0.x: version "1.0.4" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1" @@ -7851,7 +7765,7 @@ unist-util-stringify-position@^2.0.0: dependencies: "@types/unist" "^2.0.2" -universalify@^0.1.0, universalify@^0.1.2: +universalify@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== From 17b935cbc4d7b9ab86e2a885c5da0bd0ff5a252b Mon Sep 17 00:00:00 2001 From: James Salter Date: Wed, 4 Aug 2021 09:34:12 +0100 Subject: [PATCH 105/176] Add @sentry/types and rrweb-snapshot as dev dependencies This is neccessary to resolve re-exported types referred to by posthog-js' type definitions. This isn't ideal, but * We intend to start using sentry anyway * Discussion with posthog maintainers about rrweb-snapshot at https://github.com/PostHog/posthog-js/issues/252, perhaps we can find another solution soon --- package.json | 2 ++ yarn.lock | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/package.json b/package.json index 8f5459ab99..0bcf86bff0 100644 --- a/package.json +++ b/package.json @@ -124,6 +124,7 @@ "@babel/traverse": "^7.12.12", "@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz", "@peculiar/webcrypto": "^1.1.4", + "@sentry/types": "^6.10.0", "@sinonjs/fake-timers": "^7.0.2", "@types/classnames": "^2.2.11", "@types/commonmark": "^0.27.4", @@ -167,6 +168,7 @@ "matrix-web-i18n": "github:matrix-org/matrix-web-i18n", "react-test-renderer": "^17.0.2", "rimraf": "^3.0.2", + "rrweb-snapshot": "1.1.7", "stylelint": "^13.9.0", "stylelint-config-standard": "^20.0.0", "stylelint-scss": "^3.18.0", diff --git a/yarn.lock b/yarn.lock index 891822a66e..836301b002 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1352,6 +1352,11 @@ tslib "^2.2.0" webcrypto-core "^1.2.0" +"@sentry/types@^6.10.0": + version "6.10.0" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-6.10.0.tgz#6b1f44e5ed4dbc2710bead24d1b32fb08daf04e1" + integrity sha512-M7s0JFgG7/6/yNVYoPUbxzaXDhnzyIQYRRJJKRaTD77YO4MHvi4Ke8alBWqD5fer0cPIfcSkBqa9BLdqRqcMWw== + "@sinonjs/commons@^1.7.0": version "1.8.3" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" @@ -6841,6 +6846,11 @@ rimraf@^3.0.0, rimraf@^3.0.2: dependencies: glob "^7.1.3" +rrweb-snapshot@1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/rrweb-snapshot/-/rrweb-snapshot-1.1.7.tgz#92a3b47b1112a1b566c2fae2edb02fa48a6f6653" + integrity sha512-+f2kCCvIQ1hbEeCWnV7mPVPDEdWEExqwcYqMd/r1nfK52QE7qU52jefUOyTe85Vy67rZGqWnfK/B25e/OTSgYg== + rst-selector-parser@^2.2.3: version "2.2.3" resolved "https://registry.yarnpkg.com/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz#81b230ea2fcc6066c89e3472de794285d9b03d91" From 7cb21c845faa2ef835414dd647999479877c9b9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 4 Aug 2021 10:33:37 +0200 Subject: [PATCH 106/176] Fix indent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/HtmlUtils.tsx | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/src/HtmlUtils.tsx b/src/HtmlUtils.tsx index 27556a4012..2eee5214af 100644 --- a/src/HtmlUtils.tsx +++ b/src/HtmlUtils.tsx @@ -57,10 +57,33 @@ const BIGEMOJI_REGEX = new RegExp(`^(${EMOJIBASE_REGEX.source})+$`, 'i'); const COLOR_REGEX = /^#[0-9a-fA-F]{6}$/; -export const PERMITTED_URL_SCHEMES = ["bitcoin", "ftp", "geo", "http", "https", "im", "irc", - "ircs", "magnet", "mailto", "matrix", "mms", "news", - "nntp", "openpgp4fpr", "sip", "sftp", "sms", "smsto", - "ssh", "tel", "urn", "webcal", "wtai", "xmpp"]; +export const PERMITTED_URL_SCHEMES = [ + "bitcoin", + "ftp", + "geo", + "http", + "https", + "im", + "irc", + "ircs", + "magnet", + "mailto", + "matrix", + "mms", + "news", + "nntp", + "openpgp4fpr", + "sip", + "sftp", + "sms", + "smsto", + "ssh", + "tel", + "urn", + "webcal", + "wtai", + "xmpp", +]; const MEDIA_API_MXC_REGEX = /\/_matrix\/media\/r0\/(?:download|thumbnail)\/(.+?)\/(.+?)(?:[?/]|$)/; From c9c8177f58353fc4b71aa39cb6b8d40ee74d4779 Mon Sep 17 00:00:00 2001 From: Andrew Morgan <1342360+anoadragon453@users.noreply.github.com> Date: Wed, 4 Aug 2021 09:46:39 +0100 Subject: [PATCH 107/176] Start a call immediately after creating a room via the dial pad (#6529) --- src/CallHandler.tsx | 2 ++ test/CallHandler-test.ts | 6 +++++- test/test-utils.js | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index caf010200a..30fcf46790 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -930,6 +930,8 @@ export default class CallHandler extends EventEmitter { action: 'view_room', room_id: roomId, }); + + await this.placeCall(roomId, PlaceCallType.Voice, null); } private async startTransferToPhoneNumber(call: MatrixCall, destination: string, consultFirst: boolean) { diff --git a/test/CallHandler-test.ts b/test/CallHandler-test.ts index 4c3dd25fb4..1a4560ad4a 100644 --- a/test/CallHandler-test.ts +++ b/test/CallHandler-test.ts @@ -156,13 +156,14 @@ describe('CallHandler', () => { DMRoomMap.setShared(null); // @ts-ignore window.mxCallHandler = null; + fakeCall = null; MatrixClientPeg.unset(); document.body.removeChild(audioElement); SdkConfig.unset(); }); - it('should look up the correct user and open the room when a phone number is dialled', async () => { + it('should look up the correct user and start a call in the room when a phone number is dialled', async () => { MatrixClientPeg.get().getThirdpartyUser = jest.fn().mockResolvedValue([{ userid: '@user2:example.org', protocol: "im.vector.protocol.sip_native", @@ -179,6 +180,9 @@ describe('CallHandler', () => { const viewRoomPayload = await untilDispatch('view_room'); expect(viewRoomPayload.room_id).toEqual(MAPPED_ROOM_ID); + + // Check that a call was started + expect(fakeCall.roomId).toEqual(MAPPED_ROOM_ID); }); it('should move calls between rooms when remote asserted identity changes', async () => { diff --git a/test/test-utils.js b/test/test-utils.js index 217c399443..bbab47589a 100644 --- a/test/test-utils.js +++ b/test/test-utils.js @@ -78,6 +78,7 @@ export function createTestClient() { }, mxcUrlToHttp: (mxc) => 'http://this.is.a.url/', setAccountData: jest.fn(), + setRoomAccountData: jest.fn(), sendTyping: jest.fn().mockResolvedValue({}), sendMessage: () => jest.fn().mockResolvedValue({}), getSyncState: () => "SYNCING", From b4b788e8d5b223fe4ec0b5ff29d9b993a890f3f7 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Wed, 4 Aug 2021 11:21:52 +0200 Subject: [PATCH 108/176] Fix right margin for events on IRC layout Fixes vector-im/element-web#18354 --- res/css/views/rooms/_EventTile.scss | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 6e207d674b..1c9d8e87d9 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -310,14 +310,12 @@ $hover-select-border: 4px; } .mx_RoomView_timeline_rr_enabled { - - .mx_EventTile:not([data-layout=bubble]) { + .mx_EventTile[data-layout=group] { .mx_EventTile_line { /* ideally should be 100px, but 95px gives us a max thumbnail size of 800x600, which is nice */ margin-right: 110px; } } - // on ELS we need the margin to allow interaction with the expand/collapse button which is normally in the RR gutter } From c31f9335dc4ee7a6f5a601e0afda46d3dcc6e1d6 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 4 Aug 2021 11:51:55 +0100 Subject: [PATCH 109/176] Declare node 14 requirement Mostly for netlify (we need node 14 now because element-web's webpack config uses the `??` operator). --- .node-version | 1 + 1 file changed, 1 insertion(+) create mode 100644 .node-version diff --git a/.node-version b/.node-version new file mode 100644 index 0000000000..8351c19397 --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +14 From 6350cecb5e4c389ef19c17aca9569b03553bd18a Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 4 Aug 2021 12:02:14 +0100 Subject: [PATCH 110/176] emoty commit to try & wake netlify up From 4a1789be53212266073de2b9f021b344d9f7b479 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 4 Aug 2021 13:04:23 +0200 Subject: [PATCH 111/176] Update copy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/messages/CallEvent.tsx | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/components/views/messages/CallEvent.tsx b/src/components/views/messages/CallEvent.tsx index 2de66f897a..12e7f22173 100644 --- a/src/components/views/messages/CallEvent.tsx +++ b/src/components/views/messages/CallEvent.tsx @@ -117,14 +117,12 @@ export default class CallEvent extends React.Component { if (state === CallState.Ended) { const hangupReason = this.props.callEventGrouper.hangupReason; const gotRejected = this.props.callEventGrouper.gotRejected; - const rejectParty = this.props.callEventGrouper.rejectParty; if (gotRejected) { - const weDeclinedCall = MatrixClientPeg.get().getUserId() === rejectParty; return (
- { weDeclinedCall ? _t("You declined this call") : _t("They declined this call") } - { this.renderCallBackButton(weDeclinedCall ? _t("Call back") : _t("Call again")) } + { _t("Call declined") } + { this.renderCallBackButton(_t("Call back")) }
); } else if (([CallErrorCode.UserHangup, "user hangup"].includes(hangupReason) || !hangupReason)) { @@ -136,14 +134,14 @@ export default class CallEvent extends React.Component { // Also, if we don't have a reason return (
- { _t("This call has ended") } + { _t("Call ended") }
); } else if (hangupReason === CallErrorCode.InviteTimeout) { return (
- { _t("They didn't pick up") } - { this.renderCallBackButton(_t("Call again")) } + { _t("Missed call") } + { this.renderCallBackButton(_t("Call back")) }
); } @@ -176,7 +174,8 @@ export default class CallEvent extends React.Component { className="mx_CallEvent_content_tooltip" kind={InfoTooltipKind.Warning} /> - { _t("This call has failed") } + { _t("Connection failed") } + { this.renderCallBackButton(_t("Retry")) }
); } @@ -190,7 +189,7 @@ export default class CallEvent extends React.Component { if (state === CustomCallState.Missed) { return (
- { _t("You missed this call") } + { _t("Missed call") } { this.renderCallBackButton(_t("Call back")) }
); From d05be441134fd9d1b66cf6269a1e669e3faf2035 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 4 Aug 2021 13:07:54 +0200 Subject: [PATCH 112/176] 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 | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 6867f28b74..aa736bb1e8 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1865,19 +1865,15 @@ "Verification cancelled": "Verification cancelled", "Compare emoji": "Compare emoji", "Connected": "Connected", - "You declined this call": "You declined this call", - "They declined this call": "They declined this call", + "Call declined": "Call declined", "Call back": "Call back", - "Call again": "Call again", - "This call has ended": "This call has ended", - "They didn't pick up": "They didn't pick up", + "Missed call": "Missed call", "Could not connect media": "Could not connect media", "Connection failed": "Connection failed", "Their device couldn't start the camera or microphone": "Their device couldn't start the camera or microphone", "An unknown error occurred": "An unknown error occurred", "Unknown failure: %(reason)s)": "Unknown failure: %(reason)s)", - "This call has failed": "This call has failed", - "You missed this call": "You missed this call", + "Retry": "Retry", "The call is in an unknown state!": "The call is in an unknown state!", "Sunday": "Sunday", "Monday": "Monday", @@ -1900,7 +1896,6 @@ "Error processing audio message": "Error processing audio message", "React": "React", "Edit": "Edit", - "Retry": "Retry", "Reply": "Reply", "Message Actions": "Message Actions", "Download %(text)s": "Download %(text)s", From f97f410d0939f7c59e8efed819d60967ce528218 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 4 Aug 2021 13:16:01 +0200 Subject: [PATCH 113/176] Unused import MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/messages/CallEvent.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/views/messages/CallEvent.tsx b/src/components/views/messages/CallEvent.tsx index 12e7f22173..a204907caa 100644 --- a/src/components/views/messages/CallEvent.tsx +++ b/src/components/views/messages/CallEvent.tsx @@ -25,7 +25,6 @@ import { CallErrorCode, CallState } from 'matrix-js-sdk/src/webrtc/call'; import InfoTooltip, { InfoTooltipKind } from '../elements/InfoTooltip'; import classNames from 'classnames'; import AccessibleTooltipButton from '../elements/AccessibleTooltipButton'; -import { MatrixClientPeg } from '../../../MatrixClientPeg'; interface IProps { mxEvent: MatrixEvent; From d0b295f1a737808e4602999a7dfaf901c65008ee Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 4 Aug 2021 13:28:31 +0100 Subject: [PATCH 114/176] Add CODEOWNERS --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000000..2c068fff33 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @matrix-org/element-web From 5877e936c06c383ecde92db4eea1a58c2c8a309f Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Wed, 4 Aug 2021 09:24:18 -0400 Subject: [PATCH 115/176] Fix fallback fonts Signed-off-by: Robin Townsend --- res/themes/legacy-light/css/_legacy-light.scss | 4 ++-- res/themes/light/css/_light.scss | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/res/themes/legacy-light/css/_legacy-light.scss b/res/themes/legacy-light/css/_legacy-light.scss index e945efb219..1a63c9bd07 100644 --- a/res/themes/legacy-light/css/_legacy-light.scss +++ b/res/themes/legacy-light/css/_legacy-light.scss @@ -8,9 +8,9 @@ /* Noto Color Emoji contains digits, in fixed-width, therefore causing digits in flowed text to stand out. TODO: Consider putting all emoji fonts to the end rather than the front. */ -$font-family: 'Nunito', 'Twemoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Arial', 'Helvetica', 'Sans-Serif', 'Noto Color Emoji'; +$font-family: 'Nunito', 'Twemoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Arial', 'Helvetica', sans-serif, 'Noto Color Emoji'; -$monospace-font-family: 'Inconsolata', 'Twemoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Courier', 'monospace', 'Noto Color Emoji'; +$monospace-font-family: 'Inconsolata', 'Twemoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Courier', monospace, 'Noto Color Emoji'; // Colors from Figma Compound https://www.figma.com/file/X4XTH9iS2KGJ2wFKDqkyed/Compound?node-id=557%3A0 $system-light: #F4F6FA; diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index aa17dddc56..eff9abe5af 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -8,9 +8,9 @@ /* Noto Color Emoji contains digits, in fixed-width, therefore causing digits in flowed text to stand out. TODO: Consider putting all emoji fonts to the end rather than the front. */ -$font-family: 'Inter', 'Twemoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Arial', 'Helvetica', 'Sans-Serif', 'Noto Color Emoji'; +$font-family: 'Inter', 'Twemoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Arial', 'Helvetica', sans-serif, 'Noto Color Emoji'; -$monospace-font-family: 'Inconsolata', 'Twemoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Courier', 'monospace', 'Noto Color Emoji'; +$monospace-font-family: 'Inconsolata', 'Twemoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Courier', monospace, 'Noto Color Emoji'; // Colors from Figma Compound https://www.figma.com/file/X4XTH9iS2KGJ2wFKDqkyed/Compound?node-id=557%3A0 $system-light: #F4F6FA; From 79e4a95b13bded8accd3a6a4b7e6002ed246fde9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 4 Aug 2021 16:25:44 +0200 Subject: [PATCH 116/176] Move stuff out of if statement for better readability MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/CallHandler.tsx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index d4a29a11b9..8d862498d2 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -481,15 +481,14 @@ export default class CallHandler extends EventEmitter { const incomingCallPushRule = ( new PushProcessor(MatrixClientPeg.get()).getPushRuleById(RuleId.IncomingCall) as IPushRule ); + const pushRuleEnabled = incomingCallPushRule?.enabled; + const tweakSetToRing = incomingCallPushRule.actions.some((action: Tweaks) => ( + action.set_tweak === TweakName.Sound && + action.value === "ring" + )); switch (newState) { case CallState.Ringing: - if ( - incomingCallPushRule?.enabled && - incomingCallPushRule.actions.some((a: Tweaks) => ( - a.set_tweak === TweakName.Sound && - a.value === "ring" - )) - ) { + if (pushRuleEnabled && tweakSetToRing) { this.play(AudioID.Ring); } else { this.silenceCall(call.callId); From 023d8749499605da27d949308e91d58e25cb7183 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 4 Aug 2021 16:34:59 +0200 Subject: [PATCH 117/176] Add missing ? MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/CallHandler.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index 0de6723d33..0cb916578e 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -485,7 +485,7 @@ export default class CallHandler extends EventEmitter { new PushProcessor(MatrixClientPeg.get()).getPushRuleById(RuleId.IncomingCall) as IPushRule ); const pushRuleEnabled = incomingCallPushRule?.enabled; - const tweakSetToRing = incomingCallPushRule.actions.some((action: Tweaks) => ( + const tweakSetToRing = incomingCallPushRule?.actions.some((action: Tweaks) => ( action.set_tweak === TweakName.Sound && action.value === "ring" )); From 74e1342fa8266c97e6d4dcae664b9a1d4447f0b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 4 Aug 2021 16:35:46 +0200 Subject: [PATCH 118/176] Wrap cases in {} MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/CallHandler.tsx | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index 0cb916578e..77569711df 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -481,27 +481,29 @@ export default class CallHandler extends EventEmitter { this.silencedCalls.delete(call.callId); } - const incomingCallPushRule = ( - new PushProcessor(MatrixClientPeg.get()).getPushRuleById(RuleId.IncomingCall) as IPushRule - ); - const pushRuleEnabled = incomingCallPushRule?.enabled; - const tweakSetToRing = incomingCallPushRule?.actions.some((action: Tweaks) => ( - action.set_tweak === TweakName.Sound && - action.value === "ring" - )); switch (newState) { - case CallState.Ringing: + case CallState.Ringing: { + const incomingCallPushRule = ( + new PushProcessor(MatrixClientPeg.get()).getPushRuleById(RuleId.IncomingCall) as IPushRule + ); + const pushRuleEnabled = incomingCallPushRule?.enabled; + const tweakSetToRing = incomingCallPushRule?.actions.some((action: Tweaks) => ( + action.set_tweak === TweakName.Sound && + action.value === "ring" + )); + if (pushRuleEnabled && tweakSetToRing) { this.play(AudioID.Ring); } else { this.silenceCall(call.callId); } break; - case CallState.InviteSent: + } + case CallState.InviteSent: { this.play(AudioID.Ringback); break; - case CallState.Ended: - { + } + case CallState.Ended: { const hangupReason = call.hangupReason; Analytics.trackEvent('voip', 'callEnded', 'hangupReason', hangupReason); this.removeCallForRoom(mappedRoomId); From fe643b2401d38457deb891af17bd6543ec5526c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 4 Aug 2021 17:09:48 +0200 Subject: [PATCH 119/176] Use flex-start as it has more universal support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/auth/_InteractiveAuthEntryComponents.scss | 2 +- res/css/views/rooms/_EventBubbleTile.scss | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/res/css/views/auth/_InteractiveAuthEntryComponents.scss b/res/css/views/auth/_InteractiveAuthEntryComponents.scss index ffaad3cd7a..ec07b765fd 100644 --- a/res/css/views/auth/_InteractiveAuthEntryComponents.scss +++ b/res/css/views/auth/_InteractiveAuthEntryComponents.scss @@ -85,7 +85,7 @@ limitations under the License. .mx_InteractiveAuthEntryComponents_termsPolicy { display: flex; flex-direction: row; - justify-content: start; + justify-content: flex-start; align-items: center; } diff --git a/res/css/views/rooms/_EventBubbleTile.scss b/res/css/views/rooms/_EventBubbleTile.scss index 1e25deba26..c6170bf7c0 100644 --- a/res/css/views/rooms/_EventBubbleTile.scss +++ b/res/css/views/rooms/_EventBubbleTile.scss @@ -271,7 +271,7 @@ limitations under the License. display: flex; align-items: center; - justify-content: start; + justify-content: flex-start; padding: 5px 0; .mx_EventTile_avatar { From e2bc76a1294158a9f149b3e6048b0586bbfedd41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 4 Aug 2021 17:17:21 +0200 Subject: [PATCH 120/176] Fix voice feed cut-off MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/voip/_CallView.scss | 2 -- res/css/views/voip/_CallViewSidebar.scss | 2 -- res/css/views/voip/_VideoFeed.scss | 1 + 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/res/css/views/voip/_CallView.scss b/res/css/views/voip/_CallView.scss index 60b7aa69ea..fb18c6a5ee 100644 --- a/res/css/views/voip/_CallView.scss +++ b/res/css/views/voip/_CallView.scss @@ -75,8 +75,6 @@ limitations under the License. height: 100%; &.mx_VideoFeed_voice { - // We don't want to collide with the call controls that have 52px of height - margin-bottom: 52px; background-color: $inverted-bg-color; display: flex; justify-content: center; diff --git a/res/css/views/voip/_CallViewSidebar.scss b/res/css/views/voip/_CallViewSidebar.scss index 892a137a32..dbadc22028 100644 --- a/res/css/views/voip/_CallViewSidebar.scss +++ b/res/css/views/voip/_CallViewSidebar.scss @@ -40,8 +40,6 @@ limitations under the License. display: flex; align-items: center; justify-content: center; - - aspect-ratio: 16 / 9; } .mx_VideoFeed_video { diff --git a/res/css/views/voip/_VideoFeed.scss b/res/css/views/voip/_VideoFeed.scss index 3a0f62636e..7a8d39dfe3 100644 --- a/res/css/views/voip/_VideoFeed.scss +++ b/res/css/views/voip/_VideoFeed.scss @@ -20,6 +20,7 @@ limitations under the License. &.mx_VideoFeed_voice { background-color: $inverted-bg-color; + aspect-ratio: 16 / 9; } .mx_VideoFeed_video { From 91b4b50969719db75d9c75d2af9d1647add38112 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 4 Aug 2021 17:25:12 +0200 Subject: [PATCH 121/176] Fix wrong cursor being used in PiP MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/voip/_CallContainer.scss | 1 - res/css/views/voip/_CallView.scss | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/views/voip/_CallContainer.scss b/res/css/views/voip/_CallContainer.scss index 181a5ee0a3..d11ab9bf9f 100644 --- a/res/css/views/voip/_CallContainer.scss +++ b/res/css/views/voip/_CallContainer.scss @@ -28,7 +28,6 @@ limitations under the License. .mx_CallPreview { pointer-events: initial; // restore pointer events so the user can leave/interact - cursor: pointer; .mx_VideoFeed_remote.mx_VideoFeed_voice { min-height: 150px; diff --git a/res/css/views/voip/_CallView.scss b/res/css/views/voip/_CallView.scss index 60b7aa69ea..c473a1fc79 100644 --- a/res/css/views/voip/_CallView.scss +++ b/res/css/views/voip/_CallView.scss @@ -208,6 +208,7 @@ limitations under the License. align-items: center; justify-content: left; flex-shrink: 0; + cursor: pointer; } .mx_CallView_header_callType { From ee500ad68403fd79730bfbec0b01f6c44c368a50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 4 Aug 2021 18:14:32 +0200 Subject: [PATCH 122/176] Update copy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/settings/Settings.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index 8c30a55f8d..f196f1cda9 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -473,7 +473,7 @@ export const SETTINGS: {[setting: string]: ISetting} = { }, "MessageComposerInput.surroundWith": { supportedLevels: LEVELS_ACCOUNT_SETTINGS, - displayName: _td("Use surround with"), + displayName: _td("Surround selected text when typing special characters"), default: false, }, "MessageComposerInput.autoReplaceEmoji": { From b9b98c6a817b33f1cb23b76b4cf1adbea4d325fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 4 Aug 2021 18:15:11 +0200 Subject: [PATCH 123/176] 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 3eedb9510c..70bb1e436b 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -847,7 +847,7 @@ "Use Ctrl + F to search timeline": "Use Ctrl + F to search timeline", "Use Command + Enter to send a message": "Use Command + Enter to send a message", "Use Ctrl + Enter to send a message": "Use Ctrl + Enter to send a message", - "Use surround with": "Use surround with", + "Surround selected text when typing special characters": "Surround selected text when typing special characters", "Automatically replace plain text Emoji": "Automatically replace plain text Emoji", "Mirror local video feed": "Mirror local video feed", "Enable Community Filter Panel": "Enable Community Filter Panel", From 4d5bdf70b70aa57d6e4394eacfe0baef9e4d189c Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 4 Aug 2021 15:08:22 -0600 Subject: [PATCH 124/176] Fix worklet reference for new webpack pipeline --- src/audio/VoiceRecording.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/audio/VoiceRecording.ts b/src/audio/VoiceRecording.ts index efd616e5ae..67b2acda0c 100644 --- a/src/audio/VoiceRecording.ts +++ b/src/audio/VoiceRecording.ts @@ -30,6 +30,7 @@ import { IEncryptedFile } from "matrix-js-sdk/src/@types/event"; import { uploadFile } from "../ContentMessages"; import { FixedRollingArray } from "../utils/FixedRollingArray"; import { clamp } from "../utils/numbers"; +import mxRecorderWorkletPath from "./RecorderWorklet"; const CHANNELS = 1; // stereo isn't important export const SAMPLE_RATE = 48000; // 48khz is what WebRTC uses. 12khz is where we lose quality. @@ -113,16 +114,10 @@ export class VoiceRecording extends EventEmitter implements IDestroyable { }); this.recorderSource = this.recorderContext.createMediaStreamSource(this.recorderStream); - // Set up our worklet. We use this for timing information and waveform analysis: the - // web audio API prefers this be done async to avoid holding the main thread with math. - const mxRecorderWorkletPath = document.body.dataset.vectorRecorderWorkletScript; - if (!mxRecorderWorkletPath) { - // noinspection ExceptionCaughtLocallyJS - throw new Error("Unable to create recorder: no worklet script registered"); - } - // Connect our inputs and outputs if (this.recorderContext.audioWorklet) { + // Set up our worklet. We use this for timing information and waveform analysis: the + // web audio API prefers this be done async to avoid holding the main thread with math. await this.recorderContext.audioWorklet.addModule(mxRecorderWorkletPath); this.recorderWorklet = new AudioWorkletNode(this.recorderContext, WORKLET_NAME); this.recorderSource.connect(this.recorderWorklet); From 9b32a1c60923fa9dc22ffb8c7be60fa65ae5fd52 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 4 Aug 2021 15:23:35 -0600 Subject: [PATCH 125/176] Appease Jest --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index cccb92d5d9..2445e3c973 100644 --- a/package.json +++ b/package.json @@ -193,7 +193,8 @@ "decoderWorker\\.min\\.js": "/__mocks__/empty.js", "decoderWorker\\.min\\.wasm": "/__mocks__/empty.js", "waveWorker\\.min\\.js": "/__mocks__/empty.js", - "workers/(.+)\\.worker\\.ts": "/__mocks__/workerMock.js" + "workers/(.+)\\.worker\\.ts": "/__mocks__/workerMock.js", + "RecorderWorklet": "/__mocks__/empty.js" }, "transformIgnorePatterns": [ "/node_modules/(?!matrix-js-sdk).+$" From 5e3b79eecafb1d70a3cf076b5048d50d5c4ec3e5 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Wed, 4 Aug 2021 18:42:47 -0400 Subject: [PATCH 126/176] Remove seams from pin icon Signed-off-by: Robin Townsend --- res/img/element-icons/room/pin.svg | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/res/img/element-icons/room/pin.svg b/res/img/element-icons/room/pin.svg index 2448fc61c5..f090f60be8 100644 --- a/res/img/element-icons/room/pin.svg +++ b/res/img/element-icons/room/pin.svg @@ -1,7 +1,3 @@ - - - - - + From a9d7d010141052f6b1d85c2aefab7de5c6b1a314 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 5 Aug 2021 12:30:22 +0100 Subject: [PATCH 127/176] Null guard space inviter to prevent the app exploding --- src/components/structures/SpaceRoomView.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/structures/SpaceRoomView.tsx b/src/components/structures/SpaceRoomView.tsx index 4064b2f48e..6f63ea090c 100644 --- a/src/components/structures/SpaceRoomView.tsx +++ b/src/components/structures/SpaceRoomView.tsx @@ -192,11 +192,11 @@ const SpacePreview = ({ space, onJoinButtonClicked, onRejectButtonClicked }) => if (inviteSender) { inviterSection =
- +
{ _t(" invites you", {}, { - inviter: () => { inviter.name || inviteSender }, + inviter: () => { inviter?.name || inviteSender }, }) }
{ inviter ?
From 99adbfd1ea896e6fbaf6417d6ecf8aab50d58a8b Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 5 Aug 2021 13:04:20 +0100 Subject: [PATCH 128/176] Skip sending a thumbnail if it is not a sufficient saving over the original --- src/ContentMessages.tsx | 49 +++++++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/src/ContentMessages.tsx b/src/ContentMessages.tsx index c5bcb226ff..8973219b49 100644 --- a/src/ContentMessages.tsx +++ b/src/ContentMessages.tsx @@ -209,6 +209,14 @@ async function loadImageElement(imageFile: File) { return { width, height, img }; } +// Minimum size for image files before we generate a thumbnail for them. +const IMAGE_SIZE_THRESHOLD_THUMBNAIL = 1 << 15; // 32KB +// Minimum size improvement for image thumbnails, if both are not met then don't bother uploading thumbnail. +const IMAGE_THUMBNAIL_MIN_REDUCTION_SIZE = 1 << 16; // 1MB +const IMAGE_THUMBNAIL_MIN_REDUCTION_PERCENT = 0.1; // 10% +// We don't apply these thresholds to video thumbnails as a poster image is always useful +// and videos tend to be much larger. + /** * Read the metadata for an image file and create and upload a thumbnail of the image. * @@ -217,23 +225,40 @@ async function loadImageElement(imageFile: File) { * @param {File} imageFile The image to read and thumbnail. * @return {Promise} A promise that resolves with the attachment info. */ -function infoForImageFile(matrixClient, roomId, imageFile) { +async function infoForImageFile(matrixClient: MatrixClient, roomId: string, imageFile: File) { + if (imageFile.size <= IMAGE_SIZE_THRESHOLD_THUMBNAIL) { + // don't bother generating a thumbnail, image is small enough already + return {}; + } + let thumbnailType = "image/png"; if (imageFile.type === "image/jpeg") { thumbnailType = "image/jpeg"; } - let imageInfo; - return loadImageElement(imageFile).then((r) => { - return createThumbnail(r.img, r.width, r.height, thumbnailType); - }).then((result) => { - imageInfo = result.info; - return uploadFile(matrixClient, roomId, result.thumbnail); - }).then((result) => { - imageInfo.thumbnail_url = result.url; - imageInfo.thumbnail_file = result.file; - return imageInfo; - }); + const imageElement = await loadImageElement(imageFile); + + if (imageElement.width < MAX_WIDTH && imageElement.height < MAX_HEIGHT) { + // don't bother uploading thumbnail as it'd be the same resolution as the original. + return {}; + } + + const result = await createThumbnail(imageElement.img, imageElement.width, imageElement.height, thumbnailType); + const imageInfo = result.info; + + const sizeDifference = imageFile.size - imageInfo.thumbnail_info.size; + if (sizeDifference <= IMAGE_THUMBNAIL_MIN_REDUCTION_SIZE && + sizeDifference <= (imageFile.size * IMAGE_THUMBNAIL_MIN_REDUCTION_PERCENT) + ) { + // don't bother uploading thumbnail as it not sufficiently smaller than the original. + return {}; + } + + const uploadResult = await uploadFile(matrixClient, roomId, result.thumbnail); + + imageInfo["thumbnail_url"] = uploadResult.url; + imageInfo["thumbnail_file"] = uploadResult.file; + return imageInfo; } /** From 980b284642cbbdbcb88920b8dd1e89bf312cc820 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 5 Aug 2021 13:12:01 +0100 Subject: [PATCH 129/176] Fix image & blurhash info when skipping thumbnail due to thresholds --- src/ContentMessages.tsx | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/src/ContentMessages.tsx b/src/ContentMessages.tsx index 8973219b49..14a0c1ed51 100644 --- a/src/ContentMessages.tsx +++ b/src/ContentMessages.tsx @@ -226,11 +226,6 @@ const IMAGE_THUMBNAIL_MIN_REDUCTION_PERCENT = 0.1; // 10% * @return {Promise} A promise that resolves with the attachment info. */ async function infoForImageFile(matrixClient: MatrixClient, roomId: string, imageFile: File) { - if (imageFile.size <= IMAGE_SIZE_THRESHOLD_THUMBNAIL) { - // don't bother generating a thumbnail, image is small enough already - return {}; - } - let thumbnailType = "image/png"; if (imageFile.type === "image/jpeg") { thumbnailType = "image/jpeg"; @@ -238,20 +233,18 @@ async function infoForImageFile(matrixClient: MatrixClient, roomId: string, imag const imageElement = await loadImageElement(imageFile); - if (imageElement.width < MAX_WIDTH && imageElement.height < MAX_HEIGHT) { - // don't bother uploading thumbnail as it'd be the same resolution as the original. - return {}; - } - const result = await createThumbnail(imageElement.img, imageElement.width, imageElement.height, thumbnailType); const imageInfo = result.info; + // we do all sizing checks here because we still rely on thumbnail generation for making a blurhash from. const sizeDifference = imageFile.size - imageInfo.thumbnail_info.size; - if (sizeDifference <= IMAGE_THUMBNAIL_MIN_REDUCTION_SIZE && - sizeDifference <= (imageFile.size * IMAGE_THUMBNAIL_MIN_REDUCTION_PERCENT) + if ( + imageFile.size <= IMAGE_SIZE_THRESHOLD_THUMBNAIL || // image is small enough already + (sizeDifference <= IMAGE_THUMBNAIL_MIN_REDUCTION_SIZE && // thumbnail is not sufficiently smaller than original + sizeDifference <= (imageFile.size * IMAGE_THUMBNAIL_MIN_REDUCTION_PERCENT)) ) { - // don't bother uploading thumbnail as it not sufficiently smaller than the original. - return {}; + delete imageInfo["thumbnail_info"]; + return imageInfo; } const uploadResult = await uploadFile(matrixClient, roomId, result.thumbnail); From a8b050a385f8831904c083f7ef555a083e26eeff Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 5 Aug 2021 13:37:23 +0100 Subject: [PATCH 130/176] Post-merge conflict resolution and improve alignment of tooltips --- src/components/views/voip/CallView.tsx | 50 ++++++++++++++++++++------ 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index 356e642d65..3178566e68 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -23,11 +23,16 @@ import { MatrixClientPeg } from '../../../MatrixClientPeg'; import { _t, _td } from '../../../languageHandler'; import VideoFeed from './VideoFeed'; import RoomAvatar from "../avatars/RoomAvatar"; -import { CallState, CallType, MatrixCall, CallEvent } from 'matrix-js-sdk/src/webrtc/call'; +import { CallEvent, CallState, CallType, MatrixCall } from 'matrix-js-sdk/src/webrtc/call'; import classNames from 'classnames'; import AccessibleButton from '../elements/AccessibleButton'; import { isOnlyCtrlOrCmdKeyEvent, Key } from '../../../Keyboard'; -import { alwaysAboveLeftOf, alwaysAboveRightOf, ChevronFace, ContextMenuButton } from '../../structures/ContextMenu'; +import { + alwaysAboveLeftOf, + alwaysAboveRightOf, + ChevronFace, + ContextMenuTooltipButton, +} from '../../structures/ContextMenu'; import CallContextMenu from '../context_menus/CallContextMenu'; import { avatarUrlForMember } from '../../../Avatar'; import DialpadContextMenu from '../context_menus/DialpadContextMenu'; @@ -37,6 +42,8 @@ import DesktopCapturerSourcePicker from "../elements/DesktopCapturerSourcePicker import Modal from '../../../Modal'; import { SDPStreamMetadataPurpose } from 'matrix-js-sdk/src/webrtc/callEventTypes'; import CallViewSidebar from './CallViewSidebar'; +import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; +import { Alignment } from "../elements/Tooltip"; interface IProps { // The call for us to display @@ -115,7 +122,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); @@ -479,9 +485,12 @@ export default class CallView extends React.Component { let vidMuteButton; if (this.props.call.type === CallType.Video) { vidMuteButton = ( - ); } @@ -496,9 +505,15 @@ export default class CallView extends React.Component { this.props.call.state === CallState.Connected ) { screensharingButton = ( - ); } @@ -518,6 +533,7 @@ export default class CallView extends React.Component { ); } @@ -526,22 +542,28 @@ export default class CallView extends React.Component { let contextMenuButton; if (this.state.callState === CallState.Connected) { contextMenuButton = ( - ); } let dialpadButton; if (this.state.callState === CallState.Connected && this.props.call.opponentSupportsDTMF()) { dialpadButton = ( - ); } @@ -583,9 +605,12 @@ export default class CallView extends React.Component { { dialPad } { contextMenu } { dialpadButton } - { vidMuteButton }
@@ -593,9 +618,12 @@ export default class CallView extends React.Component { { screensharingButton } { sidebarButton } { contextMenuButton } -
); @@ -820,7 +848,7 @@ export default class CallView extends React.Component { let fullScreenButton; if (!this.props.pipMode) { fullScreenButton = ( -
{ let expandButton; if (this.props.pipMode) { - expandButton =
Date: Thu, 5 Aug 2021 13:50:50 +0100 Subject: [PATCH 131/176] i18n --- src/i18n/strings/en_EN.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 9664b2ca14..41ef0cc04f 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -906,8 +906,12 @@ "sends space invaders": "sends space invaders", "Start the camera": "Start the camera", "Stop the camera": "Stop the camera", - "Dialpad": "Dialpad", + "Stop sharing your screen": "Stop sharing your screen", + "Start sharing your screen": "Start sharing your screen", + "Hide sidebar": "Hide sidebar", + "Show sidebar": "Show sidebar", "More": "More", + "Dialpad": "Dialpad", "Unmute the microphone": "Unmute the microphone", "Mute the microphone": "Mute the microphone", "Hangup": "Hangup", From e1b62ae38b15b503468f212f247dd9470e18c47a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 5 Aug 2021 14:20:31 +0100 Subject: [PATCH 132/176] Increase yOffset by 4px away --- src/components/views/voip/CallView.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index 3178566e68..945cc55ed3 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -490,7 +490,7 @@ export default class CallView extends React.Component { onClick={this.onVidMuteClick} title={this.state.vidMuted ? _t("Start the camera") : _t("Stop the camera")} alignment={Alignment.Top} - yOffset={-20} + yOffset={-24} /> ); } @@ -513,7 +513,7 @@ export default class CallView extends React.Component { : _t("Start sharing your screen") } alignment={Alignment.Top} - yOffset={-20} + yOffset={-24} /> ); } @@ -549,7 +549,7 @@ export default class CallView extends React.Component { isExpanded={this.state.showMoreMenu} title={_t("More")} alignment={Alignment.Top} - yOffset={-20} + yOffset={-24} /> ); } @@ -563,7 +563,7 @@ export default class CallView extends React.Component { isExpanded={this.state.showDialpad} title={_t("Dialpad")} alignment={Alignment.Top} - yOffset={-20} + yOffset={-24} /> ); } @@ -610,7 +610,7 @@ export default class CallView extends React.Component { onClick={this.onMicMuteClick} title={this.state.micMuted ? _t("Unmute the microphone") : _t("Mute the microphone")} alignment={Alignment.Top} - yOffset={-20} + yOffset={-24} /> { vidMuteButton }
@@ -623,7 +623,7 @@ export default class CallView extends React.Component { onClick={this.onHangupClick} title={_t("Hangup")} alignment={Alignment.Top} - yOffset={-20} + yOffset={-24} />
); From b860acca8043ea9c8c937380650339566439f775 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 5 Aug 2021 14:43:44 +0100 Subject: [PATCH 133/176] Extract tooltipYOffset to a const --- src/components/views/voip/CallView.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index 945cc55ed3..d301a1e43b 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -82,6 +82,8 @@ interface IState { sidebarShown: boolean; } +const tooltipYOffset = -24; + function getFullScreenElement() { return ( document.fullscreenElement || @@ -490,7 +492,7 @@ export default class CallView extends React.Component { onClick={this.onVidMuteClick} title={this.state.vidMuted ? _t("Start the camera") : _t("Stop the camera")} alignment={Alignment.Top} - yOffset={-24} + yOffset={tooltipYOffset} /> ); } @@ -513,7 +515,7 @@ export default class CallView extends React.Component { : _t("Start sharing your screen") } alignment={Alignment.Top} - yOffset={-24} + yOffset={tooltipYOffset} /> ); } @@ -549,7 +551,7 @@ export default class CallView extends React.Component { isExpanded={this.state.showMoreMenu} title={_t("More")} alignment={Alignment.Top} - yOffset={-24} + yOffset={tooltipYOffset} /> ); } @@ -563,7 +565,7 @@ export default class CallView extends React.Component { isExpanded={this.state.showDialpad} title={_t("Dialpad")} alignment={Alignment.Top} - yOffset={-24} + yOffset={tooltipYOffset} /> ); } @@ -610,7 +612,7 @@ export default class CallView extends React.Component { onClick={this.onMicMuteClick} title={this.state.micMuted ? _t("Unmute the microphone") : _t("Mute the microphone")} alignment={Alignment.Top} - yOffset={-24} + yOffset={tooltipYOffset} /> { vidMuteButton }
@@ -623,7 +625,7 @@ export default class CallView extends React.Component { onClick={this.onHangupClick} title={_t("Hangup")} alignment={Alignment.Top} - yOffset={-24} + yOffset={tooltipYOffset} />
); From df888a1886aeb9783091caf506d7b4216495a533 Mon Sep 17 00:00:00 2001 From: Andrew Morgan <1342360+anoadragon453@users.noreply.github.com> Date: Thu, 5 Aug 2021 16:33:22 +0100 Subject: [PATCH 134/176] Fix in-call context menus when in PiP mode (#6552) Mounting them as children when in PiP mode doesn't work. Condition mounting the context menus as children of the current component based on whether PiP mode is active. --- src/components/views/voip/CallView.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index 356e642d65..d5ec2e71d3 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -554,7 +554,11 @@ export default class CallView extends React.Component { ChevronFace.None, CONTEXT_MENU_VPADDING, )} - mountAsChild={true} + // We mount the context menus as a as a child typically in order to include the + // context menus when fullscreening the call content. + // However, this does not work as well when the call is embedded in a + // picture-in-picture frame. Thus, only mount as child when we are *not* in PiP. + mountAsChild={!this.props.pipMode} onFinished={this.closeDialpad} call={this.props.call} />; @@ -568,7 +572,7 @@ export default class CallView extends React.Component { ChevronFace.None, CONTEXT_MENU_VPADDING, )} - mountAsChild={true} + mountAsChild={!this.props.pipMode} onFinished={this.closeContextMenu} call={this.props.call} />; From 81ddaf2efaf0eda2c296b0e2fb43834b7aae5575 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 5 Aug 2021 11:21:22 -0600 Subject: [PATCH 135/176] Properly set style attribute on shared usercontent iframe Fixes https://github.com/vector-im/element-web/issues/18414 --- src/utils/FileDownloader.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/utils/FileDownloader.ts b/src/utils/FileDownloader.ts index a22ff506de..5ec91d71cc 100644 --- a/src/utils/FileDownloader.ts +++ b/src/utils/FileDownloader.ts @@ -43,9 +43,8 @@ function getManagedIframe(): { iframe: HTMLIFrameElement, onLoadPromise: Promise // Dev note: the reassignment warnings are entirely incorrect here. - // @ts-ignore - // noinspection JSConstantReassignment - managedIframe.style = { display: "none" }; + managedIframe.style.display = "none"; + // @ts-ignore // noinspection JSConstantReassignment managedIframe.sandbox = "allow-scripts allow-downloads allow-downloads-without-user-activation"; From fea30e5f5f6b6c46efebfdbb69ff2c8fa31118f4 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 5 Aug 2021 12:38:15 -0600 Subject: [PATCH 136/176] 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 17a3dc5e6dc79ccd9e926c49a9c24f0caae0e0f5 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 5 Aug 2021 12:44:12 -0600 Subject: [PATCH 137/176] Stop voice messages that are playing when starting a recording Fixes https://github.com/vector-im/element-web/issues/18410 --- src/components/views/rooms/VoiceRecordComposerTile.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/views/rooms/VoiceRecordComposerTile.tsx b/src/components/views/rooms/VoiceRecordComposerTile.tsx index 019e39d415..1b583444a3 100644 --- a/src/components/views/rooms/VoiceRecordComposerTile.tsx +++ b/src/components/views/rooms/VoiceRecordComposerTile.tsx @@ -35,6 +35,7 @@ import NotificationBadge from "./NotificationBadge"; import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState"; import { NotificationColor } from "../../../stores/notifications/NotificationColor"; import InlineSpinner from "../elements/InlineSpinner"; +import { PlaybackManager } from "../../../audio/PlaybackManager"; interface IProps { room: Room; @@ -177,6 +178,9 @@ export default class VoiceRecordComposerTile extends React.PureComponent Date: Thu, 5 Aug 2021 17:56:16 -0400 Subject: [PATCH 138/176] Add comments to isRegionalIndicator Signed-off-by: Robin Townsend --- src/emoji.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/emoji.ts b/src/emoji.ts index e871e0bb58..ee84583fc9 100644 --- a/src/emoji.ts +++ b/src/emoji.ts @@ -35,8 +35,15 @@ export const EMOTICON_TO_EMOJI = new Map(); export const getEmojiFromUnicode = unicode => UNICODE_TO_EMOJI.get(stripVariation(unicode)); -const isRegionalIndicator = (x: string): boolean => - Array.from(x).length === 1 && x >= '\u{1f1e6}' && x <= '\u{1f1ff}'; +const isRegionalIndicator = (x: string): boolean => { + // First verify that the string is a single character. We use Array.from + // to make sure we count by characters, not UTF-8 code units. + return Array.from(x).length === 1 && + // Next verify that the character is within the code point range for + // regional indicators. + // http://unicode.org/charts/PDF/Unicode-6.0/U60-1F100.pdf + x >= '\u{1f1e6}' && x <= '\u{1f1ff}'; +}; const EMOJIBASE_GROUP_ID_TO_CATEGORY = [ "people", // smileys 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 139/176] 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 140/176] 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 141/176] 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 142/176] 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 143/176] 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 144/176] 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 145/176] 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") } -
+