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 0001/1396] 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 0002/1396] 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 0003/1396] 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 0004/1396] 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 c05eceef7f3560adad8354c0aef0f32d38684718 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 16 Feb 2021 17:56:09 +0000 Subject: [PATCH 0005/1396] Rework composer autocomplete to be smarter and not trap tab --- src/components/views/rooms/Autocomplete.tsx | 34 ++++++++++++------- .../views/rooms/BasicMessageComposer.tsx | 26 ++++++++++---- src/editor/autocomplete.ts | 5 ++- 3 files changed, 43 insertions(+), 22 deletions(-) diff --git a/src/components/views/rooms/Autocomplete.tsx b/src/components/views/rooms/Autocomplete.tsx index 15af75084a..e62a5a9bd6 100644 --- a/src/components/views/rooms/Autocomplete.tsx +++ b/src/components/views/rooms/Autocomplete.tsx @@ -24,8 +24,6 @@ import {Room} from 'matrix-js-sdk/src/models/room'; import SettingsStore from "../../../settings/SettingsStore"; import Autocompleter from '../../../autocomplete/Autocompleter'; -const COMPOSER_SELECTED = 0; - export const generateCompletionDomId = (number) => `mx_Autocomplete_Completion_${number}`; interface IProps { @@ -68,7 +66,7 @@ export default class Autocomplete extends React.PureComponent { completionList: [], // how far down the completion list we are (THIS IS 1-INDEXED!) - selectionOffset: COMPOSER_SELECTED, + selectionOffset: 1, // whether we should show completions if they're available shouldShowCompletions: true, @@ -112,7 +110,7 @@ export default class Autocomplete extends React.PureComponent { completions: [], completionList: [], // Reset selected completion - selectionOffset: COMPOSER_SELECTED, + selectionOffset: 1, // Hide the autocomplete box hide: true, }); @@ -148,26 +146,31 @@ export default class Autocomplete extends React.PureComponent { const completionList = flatMap(completions, (provider) => provider.completions); // Reset selection when completion list becomes empty. - let selectionOffset = COMPOSER_SELECTED; + let selectionOffset = 1; if (completionList.length > 0) { /* If the currently selected completion is still in the completion list, try to find it and jump to it. If not, select composer. */ - const currentSelection = this.state.selectionOffset === 0 ? null : + const currentSelection = this.state.selectionOffset <= 1 ? null : this.state.completionList[this.state.selectionOffset - 1].completion; selectionOffset = completionList.findIndex( (completion) => completion.completion === currentSelection); if (selectionOffset === -1) { - selectionOffset = COMPOSER_SELECTED; + selectionOffset = 1; } else { selectionOffset++; // selectionOffset is 1-indexed! } } - let hide = this.state.hide; + let hide = true; // If `completion.command.command` is truthy, then a provider has matched with the query const anyMatches = completions.some((completion) => !!completion.command.command); - hide = !anyMatches; + if (anyMatches) { + hide = false; + if (this.props.onSelectionChange) { + this.props.onSelectionChange(this.state.completionList[selectionOffset - 1], selectionOffset - 1); + } + } this.setState({ completions, @@ -193,8 +196,8 @@ export default class Autocomplete extends React.PureComponent { if (completionCount === 0) return; // there are no items to move the selection through // Note: selectionOffset 0 represents the unsubstituted text, while 1 means first pill selected - const index = (this.state.selectionOffset + delta + completionCount + 1) % (completionCount + 1); - this.setSelection(index); + const index = (this.state.selectionOffset + delta + completionCount - 1) % completionCount; + this.setSelection(1 + index); } onEscape(e: KeyboardEvent): boolean { @@ -213,7 +216,7 @@ export default class Autocomplete extends React.PureComponent { hide = () => { this.setState({ hide: true, - selectionOffset: 0, + selectionOffset: 1, completions: [], completionList: [], }); @@ -232,8 +235,13 @@ export default class Autocomplete extends React.PureComponent { }); } + onConfirmCompletion = () => { + this.onCompletionClicked(this.state.selectionOffset); + } + onCompletionClicked = (selectionOffset: number): boolean => { - if (this.countCompletions() === 0 || selectionOffset === COMPOSER_SELECTED) { + const count = this.countCompletions(); + if (count === 0 || selectionOffset < 1 || selectionOffset > count) { return false; } diff --git a/src/components/views/rooms/BasicMessageComposer.tsx b/src/components/views/rooms/BasicMessageComposer.tsx index 017ce77166..3c82f75b33 100644 --- a/src/components/views/rooms/BasicMessageComposer.tsx +++ b/src/components/views/rooms/BasicMessageComposer.tsx @@ -126,6 +126,7 @@ export default class BasicMessageEditor extends React.Component super(props); this.state = { showPillAvatar: SettingsStore.getValue("Pill.shouldShowPillAvatar"), + showVisualBell: false, }; this.emoticonSettingHandle = SettingsStore.watchSetting('MessageComposerInput.autoReplaceEmoji', null, @@ -201,7 +202,11 @@ export default class BasicMessageEditor extends React.Component if (isEmpty) { this.formatBarRef.current.hide(); } - this.setState({autoComplete: this.props.model.autoComplete}); + this.setState({ + autoComplete: this.props.model.autoComplete, + // if a change is happening then clear the showVisualBell + showVisualBell: diff ? false : this.state.showVisualBell, + }); this.historyManager.tryPush(this.props.model, selection, inputType, diff); let isTyping = !this.props.model.isEmpty; @@ -490,6 +495,7 @@ export default class BasicMessageEditor extends React.Component } break; case Key.TAB: + case Key.ENTER: if (!metaOrAltPressed) { autoComplete.onTab(event); handled = true; @@ -504,7 +510,7 @@ export default class BasicMessageEditor extends React.Component default: return; // don't preventDefault on anything else } - } else if (event.key === Key.TAB) { + } else if (!this.props.model.isEmpty && !this.state.showVisualBell && event.key === Key.TAB) { this.tabCompleteName(event); handled = true; } else if (event.key === Key.BACKSPACE || event.key === Key.DELETE) { @@ -545,6 +551,8 @@ export default class BasicMessageEditor extends React.Component this.setState({showVisualBell: true}); model.autoComplete.close(); } + } else { + this.setState({showVisualBell: true}); } } catch (err) { console.error(err); @@ -562,7 +570,7 @@ export default class BasicMessageEditor extends React.Component private onAutoCompleteSelectionChange = (completion: ICompletion, completionIndex: number) => { this.modifiedFlag = true; - this.props.model.autoComplete.onComponentSelectionChange(completion); + // this.props.model.autoComplete.onComponentSelectionChange(completion); this.setState({completionIndex}); }; @@ -679,6 +687,11 @@ export default class BasicMessageEditor extends React.Component }; const {completionIndex} = this.state; + const hasAutocomplete = Boolean(this.state.autoComplete); + let activeDescendant; + if (hasAutocomplete && completionIndex >= 0) { + activeDescendant = generateCompletionDomId(completionIndex); + } return (
{ autoComplete } @@ -697,10 +710,11 @@ export default class BasicMessageEditor extends React.Component aria-label={this.props.label} role="textbox" aria-multiline="true" - aria-autocomplete="both" + aria-autocomplete="list" aria-haspopup="listbox" - aria-expanded={Boolean(this.state.autoComplete)} - aria-activedescendant={completionIndex >= 0 ? generateCompletionDomId(completionIndex) : undefined} + aria-expanded={hasAutocomplete} + aria-owns="mx_Autocomplete" + aria-activedescendant={activeDescendant} dir="auto" />
); diff --git a/src/editor/autocomplete.ts b/src/editor/autocomplete.ts index d8cea961d4..4633cd56c2 100644 --- a/src/editor/autocomplete.ts +++ b/src/editor/autocomplete.ts @@ -74,10 +74,9 @@ export default class AutocompleteWrapperModel { if (acComponent.countCompletions() === 0) { // Force completions to show for the text currently entered await acComponent.forceComplete(); - // Select the first item by moving "down" - await acComponent.moveSelection(+1); } else { - await acComponent.moveSelection(e.shiftKey ? -1 : +1); + await acComponent.onConfirmCompletion(); + this.updateCallback({close: true}); } } From 9e2974d84d70cbdf4e01e8c813b53a3d73fd6133 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 18 Feb 2021 10:25:25 +0000 Subject: [PATCH 0006/1396] Improve composer keyboard trapping --- src/components/structures/LoggedInView.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index c76cd7cee7..b04d840fb0 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -495,24 +495,24 @@ class LoggedInView extends React.Component { if (handled) { ev.stopPropagation(); ev.preventDefault(); - } else if (!isModifier && !ev.altKey && !ev.ctrlKey && !ev.metaKey) { + } else if (!isModifier && !ev.ctrlKey && !ev.metaKey) { // The above condition is crafted to _allow_ characters with Shift // already pressed (but not the Shift key down itself). - const isClickShortcut = ev.target !== document.body && (ev.key === Key.SPACE || ev.key === Key.ENTER); - // Do not capture the context menu key to improve keyboard accessibility - if (ev.key === Key.CONTEXT_MENU) { - return; - } + // We explicitly allow alt to be held due to it being a common accent modifier. + // XXX: Forwarding Dead keys in this way does not work as intended but better to at least + // move focus to the composer so the user can re-type the dead key correctly. + const isPrintable = ev.key.length === 1 || ev.key === "Dead"; - if (!isClickShortcut && ev.key !== Key.TAB && !canElementReceiveInput(ev.target)) { + // If the user is entering a printable character outside of an input field + // redirect it to the composer for them. + if (!isClickShortcut && isPrintable && !canElementReceiveInput(ev.target)) { // synchronous dispatch so we focus before key generates input dis.fire(Action.FocusComposer, true); ev.stopPropagation(); - // we should *not* preventDefault() here as - // that would prevent typing in the now-focussed composer + // we should *not* preventDefault() here as that would prevent typing in the now-focused composer } } }; From 9463fda1c10a26542bbfdd6ae0556ed8aa0fbb02 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 18 Feb 2021 10:55:24 +0000 Subject: [PATCH 0007/1396] Improve VoiceOver & WebKit accessibility support Based on https://bugs.webkit.org/show_bug.cgi?id=167671#c15 (workaround) --- src/autocomplete/AutocompleteProvider.tsx | 17 +++++------------ src/autocomplete/CommandProvider.tsx | 2 +- src/autocomplete/CommunityProvider.tsx | 2 +- src/autocomplete/DuckDuckGoProvider.tsx | 2 +- src/autocomplete/EmojiProvider.tsx | 2 +- src/autocomplete/NotifProvider.tsx | 2 +- src/autocomplete/RoomProvider.tsx | 2 +- src/autocomplete/UserProvider.tsx | 2 +- src/components/views/rooms/Autocomplete.tsx | 4 ++-- 9 files changed, 14 insertions(+), 21 deletions(-) diff --git a/src/autocomplete/AutocompleteProvider.tsx b/src/autocomplete/AutocompleteProvider.tsx index a40ce7144d..cc958546e1 100644 --- a/src/autocomplete/AutocompleteProvider.tsx +++ b/src/autocomplete/AutocompleteProvider.tsx @@ -27,11 +27,11 @@ export interface ICommand { }; } -export default class AutocompleteProvider { +export default abstract class AutocompleteProvider { commandRegex: RegExp; forcedCommandRegex: RegExp; - constructor(commandRegex?: RegExp, forcedCommandRegex?: RegExp) { + protected constructor(commandRegex?: RegExp, forcedCommandRegex?: RegExp) { if (commandRegex) { if (!commandRegex.global) { throw new Error('commandRegex must have global flag set'); @@ -93,18 +93,11 @@ export default class AutocompleteProvider { }; } - async getCompletions(query: string, selection: ISelectionRange, force = false): Promise { - return []; - } + abstract getCompletions(query: string, selection: ISelectionRange, force: boolean): Promise; - getName(): string { - return 'Default Provider'; - } + abstract getName(): string; - renderCompletions(completions: React.ReactNode[]): React.ReactNode | null { - console.error('stub; should be implemented in subclasses'); - return null; - } + abstract renderCompletions(completions: React.ReactNode[]): React.ReactNode | null; // Whether we should provide completions even if triggered forcefully, without a sigil. shouldForceComplete(): boolean { diff --git a/src/autocomplete/CommandProvider.tsx b/src/autocomplete/CommandProvider.tsx index c2d1290e08..7698dfcd15 100644 --- a/src/autocomplete/CommandProvider.tsx +++ b/src/autocomplete/CommandProvider.tsx @@ -91,7 +91,7 @@ export default class CommandProvider extends AutocompleteProvider { return (
{ completions } diff --git a/src/autocomplete/CommunityProvider.tsx b/src/autocomplete/CommunityProvider.tsx index ebf5d536ec..8e2d2789cd 100644 --- a/src/autocomplete/CommunityProvider.tsx +++ b/src/autocomplete/CommunityProvider.tsx @@ -112,7 +112,7 @@ export default class CommunityProvider extends AutocompleteProvider { return (
{ completions } diff --git a/src/autocomplete/DuckDuckGoProvider.tsx b/src/autocomplete/DuckDuckGoProvider.tsx index e63f7255dc..a16c82aaf9 100644 --- a/src/autocomplete/DuckDuckGoProvider.tsx +++ b/src/autocomplete/DuckDuckGoProvider.tsx @@ -99,7 +99,7 @@ export default class DuckDuckGoProvider extends AutocompleteProvider { return (
{ completions } diff --git a/src/autocomplete/EmojiProvider.tsx b/src/autocomplete/EmojiProvider.tsx index 705474f8d0..4a237fe091 100644 --- a/src/autocomplete/EmojiProvider.tsx +++ b/src/autocomplete/EmojiProvider.tsx @@ -140,7 +140,7 @@ export default class EmojiProvider extends AutocompleteProvider { return (
{ completions } diff --git a/src/autocomplete/NotifProvider.tsx b/src/autocomplete/NotifProvider.tsx index ef1823c0ca..e948f8a985 100644 --- a/src/autocomplete/NotifProvider.tsx +++ b/src/autocomplete/NotifProvider.tsx @@ -66,7 +66,7 @@ export default class NotifProvider extends AutocompleteProvider { return (
{ completions } diff --git a/src/autocomplete/RoomProvider.tsx b/src/autocomplete/RoomProvider.tsx index 74deacf61f..6614615436 100644 --- a/src/autocomplete/RoomProvider.tsx +++ b/src/autocomplete/RoomProvider.tsx @@ -123,7 +123,7 @@ export default class RoomProvider extends AutocompleteProvider { return (
{ completions } diff --git a/src/autocomplete/UserProvider.tsx b/src/autocomplete/UserProvider.tsx index 32eea55b0b..6d909d38ad 100644 --- a/src/autocomplete/UserProvider.tsx +++ b/src/autocomplete/UserProvider.tsx @@ -178,7 +178,7 @@ export default class UserProvider extends AutocompleteProvider { return (
{ completions } diff --git a/src/components/views/rooms/Autocomplete.tsx b/src/components/views/rooms/Autocomplete.tsx index e62a5a9bd6..cdea607cea 100644 --- a/src/components/views/rooms/Autocomplete.tsx +++ b/src/components/views/rooms/Autocomplete.tsx @@ -298,7 +298,7 @@ export default class Autocomplete extends React.PureComponent { return completions.length > 0 ? ( -
+
{ completionResult.provider.getName() }
{ completionResult.provider.renderCompletions(completions) }
@@ -306,7 +306,7 @@ export default class Autocomplete extends React.PureComponent { }).filter((completion) => !!completion); return !this.state.hide && renderedCompletions.length > 0 ? ( -
+
{ renderedCompletions }
) : null; From 131f499c25828b0c52401ca7556c14b78f23bbbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 27 Feb 2021 08:04:20 +0100 Subject: [PATCH 0008/1396] Add Confirm Public Encrypted Room dialog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- .../tabs/room/SecurityRoomSettingsTab.js | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.js b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.js index f72e78fa3f..b1be77064a 100644 --- a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.js +++ b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.js @@ -147,7 +147,7 @@ export default class SecurityRoomSettingsTab extends React.Component { }); }; - _onRoomAccessRadioToggle = (roomAccess) => { + _setRoomAccess = (roomAccess) => { // join_rule // INVITE | PUBLIC // ----------------------+---------------- @@ -191,6 +191,29 @@ export default class SecurityRoomSettingsTab extends React.Component { console.error(e); this.setState({guestAccess: beforeGuestAccess}); }); + } + + _onRoomAccessRadioToggle = async (roomAccess) => { + if ( + this.state.encrypted && + this.state.joinRule != "public" && + roomAccess != "invite_only" + ) { + Modal.createTrackedDialog('Confirm Public Encrypted Room', '', QuestionDialog, { + title: _t('Confirm making this room public?'), + description: _t( + "Making end-to-end encrypted rooms public renders the " + + "encryption pointless, wastes processing power, and can cause " + + "performance problems. Please consider creating a separate " + + "unencrypted public room.", + ), + onFinished: (confirm) => { + if (confirm) this._setRoomAccess(roomAccess); + }, + }); + } else { + this._setRoomAccess(roomAccess); + } }; _onHistoryRadioToggle = (history) => { From 0d9bc009689ad2df73cc8a601ca70a4f7c3e2ddd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 27 Feb 2021 08:04:30 +0100 Subject: [PATCH 0009/1396] 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 5bbbdf60b5..10d0b56e7f 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1337,6 +1337,8 @@ "Select the roles required to change various parts of the room": "Select the roles required to change various parts of the room", "Enable encryption?": "Enable encryption?", "Once enabled, encryption for a room cannot be disabled. Messages sent in an encrypted room cannot be seen by the server, only by the participants of the room. Enabling encryption may prevent many bots and bridges from working correctly. Learn more about encryption.": "Once enabled, encryption for a room cannot be disabled. Messages sent in an encrypted room cannot be seen by the server, only by the participants of the room. Enabling encryption may prevent many bots and bridges from working correctly. Learn more about encryption.", + "Confirm making this room public?": "Confirm making this room public?", + "Making end-to-end encrypted rooms public renders the encryption pointless, wastes processing power, and can cause performance problems. Please consider creating a separate unencrypted public room.": "Making end-to-end encrypted rooms public renders the encryption pointless, wastes processing power, and can cause performance problems. Please consider creating a separate unencrypted public room.", "Guests cannot join this room even if explicitly invited.": "Guests cannot join this room even if explicitly invited.", "Click here to fix": "Click here to fix", "To link to this room, please add an address.": "To link to this room, please add an address.", From 5d6bc9a88663463587768c49495e8c03e8f77a30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 28 Feb 2021 08:29:31 +0100 Subject: [PATCH 0010/1396] Add second Confirm Public Encrypted Room dialog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- .../tabs/room/SecurityRoomSettingsTab.js | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.js b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.js index b1be77064a..e3c23c890c 100644 --- a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.js +++ b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.js @@ -91,7 +91,24 @@ export default class SecurityRoomSettingsTab extends React.Component { if (refreshWhenTypes.includes(e.getType())) this.forceUpdate(); }; - _onEncryptionChange = (e) => { + _onEncryptionChange = async (e) => { + if (this.state.joinRule == "public") { + const {finished} = Modal.createTrackedDialog('Confirm Public Encrypted Room', '', QuestionDialog, { + title: _t('Enable encryption in a public room?'), + description: _t( + "Note that enabling encryption in public rooms renders the " + + "encryption pointless, wastes processing power, and can cause " + + "performance problems. Please consider creating a separate " + + "encrypted room.", + ), + }); + const [confirm] = await finished; + if (!confirm) { + this.setState({encrypted: false}); + return; + } + } + Modal.createTrackedDialog('Enable encryption', '', QuestionDialog, { title: _t('Enable encryption?'), description: _t( From 5a55b0dcf03e1a86a1a5eb3a33a9664b186d75bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 28 Feb 2021 08:29:37 +0100 Subject: [PATCH 0011/1396] 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 10d0b56e7f..42ad88c01e 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1335,6 +1335,8 @@ "Roles & Permissions": "Roles & Permissions", "Permissions": "Permissions", "Select the roles required to change various parts of the room": "Select the roles required to change various parts of the room", + "Enable encryption in a public room?": "Enable encryption in a public room?", + "Note that enabling encryption in public rooms renders the encryption pointless, wastes processing power, and can cause performance problems. Please consider creating a separate encrypted room.": "Note that enabling encryption in public rooms renders the encryption pointless, wastes processing power, and can cause performance problems. Please consider creating a separate encrypted room.", "Enable encryption?": "Enable encryption?", "Once enabled, encryption for a room cannot be disabled. Messages sent in an encrypted room cannot be seen by the server, only by the participants of the room. Enabling encryption may prevent many bots and bridges from working correctly. Learn more about encryption.": "Once enabled, encryption for a room cannot be disabled. Messages sent in an encrypted room cannot be seen by the server, only by the participants of the room. Enabling encryption may prevent many bots and bridges from working correctly. Learn more about encryption.", "Confirm making this room public?": "Confirm making this room public?", From 24428783f53dfe88b79477374cc8f34e88fc98c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 28 Feb 2021 09:54:05 +0100 Subject: [PATCH 0012/1396] Remove unnecessary async MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- .../views/settings/tabs/room/SecurityRoomSettingsTab.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.js b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.js index e3c23c890c..ccc5470265 100644 --- a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.js +++ b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.js @@ -210,7 +210,7 @@ export default class SecurityRoomSettingsTab extends React.Component { }); } - _onRoomAccessRadioToggle = async (roomAccess) => { + _onRoomAccessRadioToggle = (roomAccess) => { if ( this.state.encrypted && this.state.joinRule != "public" && From 6a5ea970e940bb53671103fe222f0d2220db114b Mon Sep 17 00:00:00 2001 From: Panagiotis <27917356+panoschal@users.noreply.github.com> Date: Sat, 13 Mar 2021 15:20:42 +0200 Subject: [PATCH 0013/1396] fix: make call area smaller on small screens so that it doesn't need a scrollbar --- res/css/views/voip/_CallView.scss | 3 +++ 1 file changed, 3 insertions(+) diff --git a/res/css/views/voip/_CallView.scss b/res/css/views/voip/_CallView.scss index 7eb329594a..4ade5d90f8 100644 --- a/res/css/views/voip/_CallView.scss +++ b/res/css/views/voip/_CallView.scss @@ -30,6 +30,9 @@ limitations under the License. .mx_CallView_voice { height: 360px; + @media only screen and (max-height: 768px) { + height: 300px; + } } } From 879dd6eaeac1c91c66ed9329e382c830ad92cd21 Mon Sep 17 00:00:00 2001 From: Panagiotis <27917356+panoschal@users.noreply.github.com> Date: Sat, 13 Mar 2021 15:24:26 +0200 Subject: [PATCH 0014/1396] fix: make status bar area not show when it is undefined e.g. when user is in call, and there are search results --- src/components/structures/RoomView.tsx | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 706cd5ded8..2fdb193389 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -1841,6 +1841,17 @@ export default class RoomView extends React.Component { />; } + const statusBarAreaClass = classNames("mx_RoomView_statusArea", { + "mx_RoomView_statusArea_expanded": isStatusAreaExpanded, + }); + + const statusBarArea = statusBar &&
+
+
+ {statusBar} +
+
+ const roomVersionRecommendation = this.state.upgradeRecommendation; const showRoomUpgradeBar = ( roomVersionRecommendation && @@ -2052,10 +2063,6 @@ export default class RoomView extends React.Component { />); } - const statusBarAreaClass = classNames("mx_RoomView_statusArea", { - "mx_RoomView_statusArea_expanded": isStatusAreaExpanded, - }); - const showRightPanel = this.state.room && this.state.showRightPanel; const rightPanel = showRightPanel ? @@ -2104,12 +2111,7 @@ export default class RoomView extends React.Component { {messagePanel} {searchResultsPanel}
-
-
-
- {statusBar} -
-
+ {statusBarArea} {previewBar} {messageComposer}
From 4d2ecc98b0c4d0d9e5a357e930203b6c917ccb8c Mon Sep 17 00:00:00 2001 From: Panagiotis <27917356+panoschal@users.noreply.github.com> Date: Sat, 13 Mar 2021 19:11:57 +0200 Subject: [PATCH 0015/1396] fix: decrease the size of CallView on smaller screens so that when the user opens the search box, it does not disappear, and AuxPanel does not need an awkward scrollbar --- res/css/views/voip/_CallView.scss | 2 +- src/components/views/voip/CallView.tsx | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/res/css/views/voip/_CallView.scss b/res/css/views/voip/_CallView.scss index 4ade5d90f8..7f01aecbcd 100644 --- a/res/css/views/voip/_CallView.scss +++ b/res/css/views/voip/_CallView.scss @@ -31,7 +31,7 @@ limitations under the License. .mx_CallView_voice { height: 360px; @media only screen and (max-height: 768px) { - height: 300px; + height: 220px; } } } diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index 9bdc8fb11d..762a2bb941 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -538,7 +538,8 @@ export default class CallView extends React.Component { {callControls}
; } else { - const avatarSize = this.props.pipMode ? 76 : 160; + const normalAvatarSize = window.innerHeight <= 768 ? 120 : 160; + const avatarSize = this.props.pipMode ? 76 : normalAvatarSize; const classes = classNames({ mx_CallView_voice: true, mx_CallView_voice_hold: isOnHold, From e5794a4c80a3c9fe8f4e02e2f25a18220e5d4a8e Mon Sep 17 00:00:00 2001 From: Panagiotis <27917356+panoschal@users.noreply.github.com> Date: Sat, 13 Mar 2021 19:52:59 +0200 Subject: [PATCH 0016/1396] linter --- res/css/views/voip/_CallView.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/res/css/views/voip/_CallView.scss b/res/css/views/voip/_CallView.scss index 7f01aecbcd..c7e2456a16 100644 --- a/res/css/views/voip/_CallView.scss +++ b/res/css/views/voip/_CallView.scss @@ -30,6 +30,7 @@ limitations under the License. .mx_CallView_voice { height: 360px; + @media only screen and (max-height: 768px) { height: 220px; } From 4a6d8ebdf0d7143c6e41a09aa62dfbcf2620b11b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 7 May 2021 20:29:26 +0200 Subject: [PATCH 0017/1396] Add screensharing icons MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/img/voip/screensharing-off.svg | 17 +++++++++++++++++ res/img/voip/screensharing-on.svg | 17 +++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 res/img/voip/screensharing-off.svg create mode 100644 res/img/voip/screensharing-on.svg diff --git a/res/img/voip/screensharing-off.svg b/res/img/voip/screensharing-off.svg new file mode 100644 index 0000000000..c05ccdf9aa --- /dev/null +++ b/res/img/voip/screensharing-off.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/res/img/voip/screensharing-on.svg b/res/img/voip/screensharing-on.svg new file mode 100644 index 0000000000..1436ca7e37 --- /dev/null +++ b/res/img/voip/screensharing-on.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + From 198722eb41ac39bde68c41a63567dcf0c30ef33a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 7 May 2021 20:29:45 +0200 Subject: [PATCH 0018/1396] Add classes for screensharing buttons 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 | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/res/css/views/voip/_CallView.scss b/res/css/views/voip/_CallView.scss index 0be75be28c..8ebe7aa607 100644 --- a/res/css/views/voip/_CallView.scss +++ b/res/css/views/voip/_CallView.scss @@ -353,6 +353,18 @@ limitations under the License. } } +.mx_CallView_callControls_button_screensharingOn { + &::before { + background-image: url('$(res)/img/voip/screensharing-on.svg'); + } +} + +.mx_CallView_callControls_button_screensharingOff { + &::before { + background-image: url('$(res)/img/voip/screensharing-off.svg'); + } +} + .mx_CallView_callControls_button_hangup { &::before { background-image: url('$(res)/img/voip/hangup.svg'); From 1f27354439a1572040bcf7c863b1a7298edf4d59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 7 May 2021 21:34:56 +0200 Subject: [PATCH 0019/1396] Add support for up to 4 feeds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/voip/_VideoFeed.scss | 32 +++++++++++- src/components/views/voip/CallView.tsx | 69 ++++++++++++++----------- src/components/views/voip/VideoFeed.tsx | 5 +- 3 files changed, 71 insertions(+), 35 deletions(-) diff --git a/res/css/views/voip/_VideoFeed.scss b/res/css/views/voip/_VideoFeed.scss index 7d85ac264e..170bd89652 100644 --- a/res/css/views/voip/_VideoFeed.scss +++ b/res/css/views/voip/_VideoFeed.scss @@ -21,7 +21,7 @@ limitations under the License. } -.mx_VideoFeed_remote { +.mx_VideoFeed_primary { width: 100%; height: 100%; display: flex; @@ -33,7 +33,7 @@ limitations under the License. } } -.mx_VideoFeed_local { +.mx_VideoFeed_secondary { max-width: 25%; max-height: 25%; position: absolute; @@ -47,6 +47,34 @@ limitations under the License. } } +.mx_VideoFeed_tertiary { + max-width: 25%; + max-height: 25%; + position: absolute; + right: 10px; + bottom: 10px; + z-index: 100; + border-radius: 4px; + + &.mx_VideoFeed_video { + background-color: transparent; + } +} + +.mx_VideoFeed_quaternary { + max-width: 25%; + max-height: 25%; + position: absolute; + left: 10px; + top: 10px; + z-index: 100; + border-radius: 4px; + + &.mx_VideoFeed_video { + background-color: transparent; + } +} + .mx_VideoFeed_mirror { transform: scale(-1, 1); } diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index c084dacaa8..2027e997e1 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -33,6 +33,13 @@ import DialpadContextMenu from '../context_menus/DialpadContextMenu'; import { CallFeed } from 'matrix-js-sdk/src/webrtc/callFeed'; import {replaceableComponent} from "../../../utils/replaceableComponent"; +const FEED_CLASS_NAMES = [ + "mx_VideoFeed_primary", + "mx_VideoFeed_secondary", + "mx_VideoFeed_tertiary", + "mx_VideoFeed_quaternary", +]; + interface IProps { // The call for us to display call: MatrixCall, @@ -371,6 +378,34 @@ export default class CallView extends React.Component { this.props.call.transferToCall(transfereeCall); } + private renderFeeds(feeds: Array, offset = 0) { + const sortedFeeds = [...feeds].sort((a, b) => { + if (b.purpose === SDPStreamMetadataPurpose.Screenshare && !b.isLocal()) return 1; + if (a.isLocal() && !b.isLocal()) return 1; + return 0; + }); + + return sortedFeeds.map((feed, i) => { + i += offset; + // TODO: Later the CallView should probably be reworked to support + // any number of feeds but now we can't render more than 4 feeds + if (i >= 4) return; + // Here we check to hide local audio feeds to achieve the same UI/UX + // as before. But once again this might be subject to change + if (feed.isVideoMuted() && feed.isLocal()) return; + return ( + + ); + }); + } + public render() { const client = MatrixClientPeg.get(); const callRoomId = CallHandler.sharedInstance().roomIdForCall(this.props.call); @@ -594,20 +629,8 @@ export default class CallView extends React.Component { mx_CallView_voice: true, }); - const feeds = this.props.call.getLocalFeeds().map((feed, i) => { - // Here we check to hide local audio feeds to achieve the same UI/UX - // as before. But once again this might be subject to change - if (feed.isVideoMuted()) return; - return ( - - ); - }); + // We pass offset of one to avoid a feed being rendered as primary + const feeds = this.renderFeeds(this.props.call.getLocalFeeds(), 1); // Saying "Connecting" here isn't really true, but the best thing // I can come up with, but this might be subject to change as well @@ -631,23 +654,7 @@ export default class CallView extends React.Component { mx_CallView_video: true, }); - // TODO: Later the CallView should probably be reworked to support - // any number of feeds but now we can always expect there to be two - // feeds. This is because the js-sdk ignores any new incoming streams - const feeds = this.state.feeds.map((feed, i) => { - // Here we check to hide local audio feeds to achieve the same UI/UX - // as before. But once again this might be subject to change - if (feed.isVideoMuted() && feed.isLocal()) return; - return ( - - ); - }); + const feeds = this.renderFeeds(this.state.feeds); contentView =
{feeds} diff --git a/src/components/views/voip/VideoFeed.tsx b/src/components/views/voip/VideoFeed.tsx index d22fa055ce..3a7d49cfd4 100644 --- a/src/components/views/voip/VideoFeed.tsx +++ b/src/components/views/voip/VideoFeed.tsx @@ -37,6 +37,8 @@ interface IProps { // a callback which is called when the video element is resized // due to a change in video metadata onResize?: (e: Event) => void, + + className: string, } interface IState { @@ -121,8 +123,7 @@ export default class VideoFeed extends React.Component { render() { const videoClasses = { mx_VideoFeed: true, - mx_VideoFeed_local: this.props.feed.isLocal(), - mx_VideoFeed_remote: !this.props.feed.isLocal(), + [this.props.className]: true, mx_VideoFeed_voice: this.state.videoMuted, mx_VideoFeed_video: !this.state.videoMuted, mx_VideoFeed_mirror: ( From 5b2f941ce2b24d8c0909f4a40c50065ff6451c58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 7 May 2021 21:35:32 +0200 Subject: [PATCH 0020/1396] Add button to screenshare MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/voip/CallView.tsx | 29 ++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index 2027e997e1..30f5db8593 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -32,6 +32,9 @@ import { avatarUrlForMember } from '../../../Avatar'; import DialpadContextMenu from '../context_menus/DialpadContextMenu'; import { CallFeed } from 'matrix-js-sdk/src/webrtc/callFeed'; import {replaceableComponent} from "../../../utils/replaceableComponent"; +import DesktopCapturerSourcePicker from "../elements/DesktopCapturerSourcePicker"; +import Modal from '../../../Modal'; +import { SDPStreamMetadataPurpose } from 'matrix-js-sdk/src/webrtc/callEventTypes'; const FEED_CLASS_NAMES = [ "mx_VideoFeed_primary", @@ -63,6 +66,7 @@ interface IState { isRemoteOnHold: boolean, micMuted: boolean, vidMuted: boolean, + screensharing: boolean, callState: CallState, controlsVisible: boolean, showMoreMenu: boolean, @@ -119,6 +123,7 @@ export default class CallView extends React.Component { isRemoteOnHold: this.props.call.isRemoteOnHold(), micMuted: this.props.call.isMicrophoneMuted(), vidMuted: this.props.call.isLocalVideoMuted(), + screensharing: this.props.call.isScreensharing(), callState: this.props.call.state, controlsVisible: true, showMoreMenu: false, @@ -292,6 +297,18 @@ export default class CallView extends React.Component { this.setState({vidMuted: newVal}); } + private onScreenshareClick = async () => { + const isScreensharing = await this.props.call.setScreensharingEnabled( + !this.state.screensharing, + async (): Promise => { + const {finished} = Modal.createDialog(DesktopCapturerSourcePicker); + const [source] = await finished; + return source; + }, + ); + this.setState({screensharing: isScreensharing}) + } + private onMoreClick = () => { if (this.controlsHideTimer) { clearTimeout(this.controlsHideTimer); @@ -452,6 +469,12 @@ export default class CallView extends React.Component { mx_CallView_callControls_button_vidOff: this.state.vidMuted, }); + const screensharingClasses = classNames({ + mx_CallView_callControls_button: true, + mx_CallView_callControls_button_screensharingOn: this.state.screensharing, + mx_CallView_callControls_button_screensharingOff: !this.state.screensharing, + }); + // Put the other states of the mic/video icons in the document to make sure they're cached // (otherwise the icon disappears briefly when toggled) const micCacheClasses = classNames({ @@ -478,6 +501,11 @@ export default class CallView extends React.Component { onClick={this.onVidMuteClick} /> : null; + const screensharingButton = this.props.call.opponentSupportsSDPStreamMetadata() ? : 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 ? { }} /> {vidMuteButton} + {screensharingButton}
{contextMenuButton} From 4c9d9dd2140c8ff645874fcb036eeb3e06994773 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 8 May 2021 10:04:26 +0200 Subject: [PATCH 0021/1396] Enable screenshare in all video calls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/voip/CallView.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index 30f5db8593..90e442c641 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -501,10 +501,15 @@ export default class CallView extends React.Component { onClick={this.onVidMuteClick} /> : null; - const screensharingButton = this.props.call.opponentSupportsSDPStreamMetadata() ? : null; + let screensharingButton; + if (this.props.call.opponentSupportsSDPStreamMetadata() || this.props.call.type === CallType.Video) { + screensharingButton = ( + + ); + } // 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 From 430808ae2efa6248e49301a8ed7dfe8454073a41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 8 May 2021 16:49:47 +0200 Subject: [PATCH 0022/1396] Simplifie CSS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/voip/_VideoFeed.scss | 28 ++++++------------------- src/components/views/voip/VideoFeed.tsx | 1 + 2 files changed, 7 insertions(+), 22 deletions(-) diff --git a/res/css/views/voip/_VideoFeed.scss b/res/css/views/voip/_VideoFeed.scss index 170bd89652..3d39735519 100644 --- a/res/css/views/voip/_VideoFeed.scss +++ b/res/css/views/voip/_VideoFeed.scss @@ -20,7 +20,6 @@ limitations under the License. background-color: $inverted-bg-color; } - .mx_VideoFeed_primary { width: 100%; height: 100%; @@ -33,12 +32,10 @@ limitations under the License. } } -.mx_VideoFeed_secondary { +.mx_VideoFeed_nonPrimary { max-width: 25%; max-height: 25%; position: absolute; - right: 10px; - top: 10px; z-index: 100; border-radius: 4px; @@ -47,32 +44,19 @@ limitations under the License. } } +.mx_VideoFeed_secondary { + right: 10px; + top: 10px; +} + .mx_VideoFeed_tertiary { - max-width: 25%; - max-height: 25%; - position: absolute; right: 10px; bottom: 10px; - z-index: 100; - border-radius: 4px; - - &.mx_VideoFeed_video { - background-color: transparent; - } } .mx_VideoFeed_quaternary { - max-width: 25%; - max-height: 25%; - position: absolute; left: 10px; top: 10px; - z-index: 100; - border-radius: 4px; - - &.mx_VideoFeed_video { - background-color: transparent; - } } .mx_VideoFeed_mirror { diff --git a/src/components/views/voip/VideoFeed.tsx b/src/components/views/voip/VideoFeed.tsx index 3a7d49cfd4..a545f100c8 100644 --- a/src/components/views/voip/VideoFeed.tsx +++ b/src/components/views/voip/VideoFeed.tsx @@ -123,6 +123,7 @@ export default class VideoFeed extends React.Component { render() { const videoClasses = { mx_VideoFeed: true, + mx_VideoFeed_nonPrimary: this.props.className !== "mx_VideoFeed_primary", [this.props.className]: true, mx_VideoFeed_voice: this.state.videoMuted, mx_VideoFeed_video: !this.state.videoMuted, From 69b0425c1088704106b986775309c58ad89f1216 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 8 May 2021 18:50:13 +0200 Subject: [PATCH 0023/1396] Improve and fix sorting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/voip/CallView.tsx | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index 90e442c641..0b3e1d5f48 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -203,7 +203,14 @@ export default class CallView extends React.Component { }; private onFeedsChanged = (newFeeds: Array) => { - this.setState({feeds: newFeeds}); + // Sort the feeds so that screensharing and remote feeds have priority + const sortedFeeds = [...newFeeds].sort((a, b) => { + if (b.purpose === SDPStreamMetadataPurpose.Screenshare && !b.isLocal()) return 1; + if (a.isLocal() && !b.isLocal()) return 1; + return -1; + }); + + this.setState({feeds: sortedFeeds}); }; private onCallLocalHoldUnhold = () => { @@ -396,13 +403,7 @@ export default class CallView extends React.Component { } private renderFeeds(feeds: Array, offset = 0) { - const sortedFeeds = [...feeds].sort((a, b) => { - if (b.purpose === SDPStreamMetadataPurpose.Screenshare && !b.isLocal()) return 1; - if (a.isLocal() && !b.isLocal()) return 1; - return 0; - }); - - return sortedFeeds.map((feed, i) => { + return feeds.map((feed, i) => { i += offset; // TODO: Later the CallView should probably be reworked to support // any number of feeds but now we can't render more than 4 feeds From 2749715050d0e55d0c155c0b67cfdddc149dd5b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 10 May 2021 12:26:28 +0200 Subject: [PATCH 0024/1396] Remove screensharing call type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/CallHandler.tsx | 25 ------------------------ src/components/views/rooms/RoomHeader.js | 9 +++++++-- 2 files changed, 7 insertions(+), 27 deletions(-) diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index 0268ebfe46..7184deb0e7 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -80,7 +80,6 @@ import CountlyAnalytics from "./CountlyAnalytics"; import {UIFeature} from "./settings/UIFeature"; import { CallError } from "matrix-js-sdk/src/webrtc/call"; import { logger } from 'matrix-js-sdk/src/logger'; -import DesktopCapturerSourcePicker from "./components/views/elements/DesktopCapturerSourcePicker" import { Action } from './dispatcher/actions'; import VoipUserMapper from './VoipUserMapper'; import { addManagedHybridWidget, isManagedHybridWidgetEnabled } from './widgets/ManagedHybrid'; @@ -129,14 +128,9 @@ interface ThirdpartyLookupResponse { fields: ThirdpartyLookupResponseFields, } -// Unlike 'CallType' in js-sdk, this one includes screen sharing -// (because a screen sharing call is only a screen sharing call to the caller, -// to the callee it's just a video call, at least as far as the current impl -// is concerned). export enum PlaceCallType { Voice = 'voice', Video = 'video', - ScreenSharing = 'screensharing', } export enum CallHandlerEvent { @@ -689,25 +683,6 @@ export default class CallHandler extends EventEmitter { call.placeVoiceCall(); } else if (type === 'video') { call.placeVideoCall(); - } else if (type === PlaceCallType.ScreenSharing) { - const screenCapErrorString = PlatformPeg.get().screenCaptureErrorString(); - if (screenCapErrorString) { - this.removeCallForRoom(roomId); - console.log("Can't capture screen: " + screenCapErrorString); - Modal.createTrackedDialog('Call Handler', 'Unable to capture screen', ErrorDialog, { - title: _t('Unable to capture screen'), - description: screenCapErrorString, - }); - return; - } - - call.placeScreenSharingCall( - async (): Promise => { - const {finished} = Modal.createDialog(DesktopCapturerSourcePicker); - const [source] = await finished; - return source; - }, - ); } else { console.error("Unknown conf call type: " + type); } diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js index f856f7f6ef..476b0e1edb 100644 --- a/src/components/views/rooms/RoomHeader.js +++ b/src/components/views/rooms/RoomHeader.js @@ -33,6 +33,7 @@ import RoomTopic from "../elements/RoomTopic"; import RoomName from "../elements/RoomName"; import {PlaceCallType} from "../../../CallHandler"; import {replaceableComponent} from "../../../utils/replaceableComponent"; +import Modal from '../../../Modal'; @replaceableComponent("views.rooms.RoomHeader") export default class RoomHeader extends React.Component { @@ -118,6 +119,10 @@ export default class RoomHeader extends React.Component { return !(currentPinEvent.getContent().pinned && currentPinEvent.getContent().pinned.length <= 0); } + _displayInfoDialogAboutScreensharing() { + + } + render() { let searchStatus = null; let cancelButton = null; @@ -241,8 +246,8 @@ export default class RoomHeader extends React.Component { videoCallButton = this.props.onCallPlaced( - ev.shiftKey ? PlaceCallType.ScreenSharing : PlaceCallType.Video)} + onClick={(ev) => ev.shiftKey ? + this._displayInfoDialogAboutScreensharing() : this.props.onCallPlaced(PlaceCallType.Video)} title={_t("Video call")} />; } From 135cdb2255ad32aba23f8c3f0d4ba059914b4f19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 10 May 2021 12:37:40 +0200 Subject: [PATCH 0025/1396] Add dialog with info about the screensharing change MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/rooms/RoomHeader.js | 7 ++++++- src/i18n/strings/en_EN.json | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js index 476b0e1edb..126f5e6c15 100644 --- a/src/components/views/rooms/RoomHeader.js +++ b/src/components/views/rooms/RoomHeader.js @@ -34,6 +34,7 @@ import RoomName from "../elements/RoomName"; import {PlaceCallType} from "../../../CallHandler"; import {replaceableComponent} from "../../../utils/replaceableComponent"; import Modal from '../../../Modal'; +import InfoDialog from "../dialogs/InfoDialog"; @replaceableComponent("views.rooms.RoomHeader") export default class RoomHeader extends React.Component { @@ -120,7 +121,11 @@ export default class RoomHeader extends React.Component { } _displayInfoDialogAboutScreensharing() { - + Modal.createDialog(InfoDialog, { + title: _t("Screensharing has changed"), + description: _t("You don't have to shift-click anymore! You can now share " + + "your screen in any video call and in voice calls if other side supports it."), + }); } render() { diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index deeee2e60e..7175dd6910 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -53,7 +53,6 @@ "A microphone and webcam are plugged in and set up correctly": "A microphone and webcam are plugged in and set up correctly", "Permission is granted to use the webcam": "Permission is granted to use the webcam", "No other application is using the webcam": "No other application is using the webcam", - "Unable to capture screen": "Unable to capture screen", "VoIP is unsupported": "VoIP is unsupported", "You cannot place VoIP calls in this browser.": "You cannot place VoIP calls in this browser.", "Too Many Calls": "Too Many Calls", @@ -1522,6 +1521,8 @@ "Unnamed room": "Unnamed room", "World readable": "World readable", "Guests can join": "Guests can join", + "Screensharing has changed": "Screensharing has changed", + "You don't have to shift-click anymore! You can now share your screen in any video call and in voice calls if other side supports it.": "You don't have to shift-click anymore! You can now share your screen in any video call and in voice calls if other side supports it.", "(~%(count)s results)|other": "(~%(count)s results)", "(~%(count)s results)|one": "(~%(count)s result)", "Join Room": "Join Room", From 0eeb21dfaca6554eb8df8251604eeadeba3bb635 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 10 May 2021 12:41:29 +0200 Subject: [PATCH 0026/1396] Remove unnecessary import MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/CallHandler.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index 7184deb0e7..4e20238431 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -56,7 +56,6 @@ limitations under the License. import React from 'react'; import {MatrixClientPeg} from './MatrixClientPeg'; -import PlatformPeg from './PlatformPeg'; import Modal from './Modal'; import { _t } from './languageHandler'; import dis from './dispatcher/dispatcher'; From 228b2ccf2d3c749d229a2a83508701ab3309da20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 10 May 2021 13:06:25 +0200 Subject: [PATCH 0027/1396] Increase z-index of call controls 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 | 1 + 1 file changed, 1 insertion(+) diff --git a/res/css/views/voip/_CallView.scss b/res/css/views/voip/_CallView.scss index 8ebe7aa607..21c948511d 100644 --- a/res/css/views/voip/_CallView.scss +++ b/res/css/views/voip/_CallView.scss @@ -291,6 +291,7 @@ limitations under the License. width: 100%; opacity: 1; transition: opacity 0.5s; + z-index: 200; // To be above _all_ feeds } .mx_CallView_callControls_hidden { From 90f4ad7a830e2f7c4c4150d2d0f6b83fcc6c311c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 10 May 2021 13:21:02 +0200 Subject: [PATCH 0028/1396] Always sort 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/CallView.tsx | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index 0b3e1d5f48..a7ad6ef8c6 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -128,7 +128,7 @@ export default class CallView extends React.Component { controlsVisible: true, showMoreMenu: false, showDialpad: false, - feeds: this.props.call.getFeeds(), + feeds: this.sortFeeds(this.props.call.getFeeds()), } this.updateCallListeners(null, this.props.call); @@ -203,14 +203,7 @@ export default class CallView extends React.Component { }; private onFeedsChanged = (newFeeds: Array) => { - // Sort the feeds so that screensharing and remote feeds have priority - const sortedFeeds = [...newFeeds].sort((a, b) => { - if (b.purpose === SDPStreamMetadataPurpose.Screenshare && !b.isLocal()) return 1; - if (a.isLocal() && !b.isLocal()) return 1; - return -1; - }); - - this.setState({feeds: sortedFeeds}); + this.setState({feeds: this.sortFeeds(newFeeds)}); }; private onCallLocalHoldUnhold = () => { @@ -253,6 +246,15 @@ export default class CallView extends React.Component { this.showControls(); } + private sortFeeds(feeds: Array) { + // Sort the feeds so that screensharing and remote feeds have priority + return [...feeds].sort((a, b) => { + if (b.purpose === SDPStreamMetadataPurpose.Screenshare && !b.isLocal()) return 1; + if (a.isLocal() && !b.isLocal()) return 1; + return -1; + }); + } + private showControls() { if (this.state.showMoreMenu || this.state.showDialpad) return; From acd0fa4c0e929fa4293ba430257d13f4749d0c11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 10 May 2021 14:42:06 +0200 Subject: [PATCH 0029/1396] Add a comment about when it is possible to screenshare MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/voip/CallView.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index a7ad6ef8c6..b49e729244 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -505,6 +505,9 @@ export default class CallView extends React.Component { /> : null; let screensharingButton; + // Screensharing is possible, if we can send a second stream and identify + // it using SDPStreamMetadata or if we can replace the already existing + // usermedia track by a screensharing track if (this.props.call.opponentSupportsSDPStreamMetadata() || this.props.call.type === CallType.Video) { screensharingButton = ( Date: Tue, 11 May 2021 11:14:21 +0100 Subject: [PATCH 0030/1396] post-merge fixes, the new keybindings stuff made it messy --- src/KeyBindingsDefaults.ts | 14 ++-- src/KeyBindingsManager.ts | 12 ++- .../views/rooms/BasicMessageComposer.tsx | 75 ++++++++++--------- src/editor/autocomplete.ts | 6 +- 4 files changed, 52 insertions(+), 55 deletions(-) diff --git a/src/KeyBindingsDefaults.ts b/src/KeyBindingsDefaults.ts index 63c4ac0f86..1270491d08 100644 --- a/src/KeyBindingsDefaults.ts +++ b/src/KeyBindingsDefaults.ts @@ -161,31 +161,29 @@ const messageComposerBindings = (): KeyBinding[] => { const autocompleteBindings = (): KeyBinding[] => { return [ { - action: AutocompleteAction.CompleteOrNextSelection, + action: AutocompleteAction.ForceComplete, keyCombo: { key: Key.TAB, }, }, { - action: AutocompleteAction.CompleteOrNextSelection, + action: AutocompleteAction.ForceComplete, keyCombo: { key: Key.TAB, ctrlKey: true, }, }, { - action: AutocompleteAction.CompleteOrPrevSelection, + action: AutocompleteAction.Complete, keyCombo: { - key: Key.TAB, - shiftKey: true, + key: Key.ENTER, }, }, { - action: AutocompleteAction.CompleteOrPrevSelection, + action: AutocompleteAction.Complete, keyCombo: { - key: Key.TAB, + key: Key.ENTER, ctrlKey: true, - shiftKey: true, }, }, { diff --git a/src/KeyBindingsManager.ts b/src/KeyBindingsManager.ts index aac14bde20..4a84b13257 100644 --- a/src/KeyBindingsManager.ts +++ b/src/KeyBindingsManager.ts @@ -52,13 +52,11 @@ export enum MessageComposerAction { /** Actions for text editing autocompletion */ export enum AutocompleteAction { - /** - * Select previous selection or, if the autocompletion window is not shown, open the window and select the first - * selection. - */ - CompleteOrPrevSelection = 'ApplySelection', - /** Select next selection or, if the autocompletion window is not shown, open it and select the first selection */ - CompleteOrNextSelection = 'CompleteOrNextSelection', + /** Accepts chosen autocomplete selection */ + Complete = 'Complete', + /** Accepts chosen autocomplete selection or, + * if the autocompletion window is not shown, open the window and select the first selection */ + ForceComplete = 'ForceComplete', /** Move to the previous autocomplete selection */ PrevSelection = 'PrevSelection', /** Move to the next autocomplete selection */ diff --git a/src/components/views/rooms/BasicMessageComposer.tsx b/src/components/views/rooms/BasicMessageComposer.tsx index 5377e08b5e..a2cae654f3 100644 --- a/src/components/views/rooms/BasicMessageComposer.tsx +++ b/src/components/views/rooms/BasicMessageComposer.tsx @@ -434,6 +434,45 @@ export default class BasicMessageEditor extends React.Component private onKeyDown = (event: React.KeyboardEvent) => { const model = this.props.model; let handled = false; + + const autocompleteAction = getKeyBindingsManager().getAutocompleteAction(event); + if (model.autoComplete && model.autoComplete.hasCompletions()) { + const autoComplete = model.autoComplete; + switch (autocompleteAction) { + case AutocompleteAction.ForceComplete: + case AutocompleteAction.Complete: + autoComplete.confirmCompletion(); + handled = true; + break; + case AutocompleteAction.PrevSelection: + autoComplete.selectPreviousSelection(); + handled = true; + break; + case AutocompleteAction.NextSelection: + autoComplete.selectNextSelection(); + handled = true; + break; + case AutocompleteAction.Cancel: + autoComplete.onEscape(event); + handled = true; + break; + default: + return; // don't preventDefault on anything else + } + } else if (autocompleteAction === AutocompleteAction.ForceComplete) { + // there is no current autocomplete window, try to open it + this.tabCompleteName(); + handled = true; + } else if (event.key === Key.BACKSPACE || event.key === Key.DELETE) { + this.formatBarRef.current.hide(); + } + + if (handled) { + event.preventDefault(); + event.stopPropagation(); + return; + } + const action = getKeyBindingsManager().getMessageComposerAction(event); switch (action) { case MessageComposerAction.FormatBold: @@ -485,42 +524,6 @@ export default class BasicMessageEditor extends React.Component handled = true; break; } - if (handled) { - event.preventDefault(); - event.stopPropagation(); - return; - } - - const autocompleteAction = getKeyBindingsManager().getAutocompleteAction(event); - if (model.autoComplete && model.autoComplete.hasCompletions()) { - const autoComplete = model.autoComplete; - switch (autocompleteAction) { - case AutocompleteAction.CompleteOrPrevSelection: - case AutocompleteAction.PrevSelection: - autoComplete.selectPreviousSelection(); - handled = true; - break; - case AutocompleteAction.CompleteOrNextSelection: - case AutocompleteAction.NextSelection: - autoComplete.selectNextSelection(); - handled = true; - break; - case AutocompleteAction.Cancel: - autoComplete.onEscape(event); - handled = true; - break; - default: - return; // don't preventDefault on anything else - } - } else if (autocompleteAction === AutocompleteAction.CompleteOrPrevSelection - || autocompleteAction === AutocompleteAction.CompleteOrNextSelection) { - // there is no current autocomplete window, try to open it - this.tabCompleteName(); - handled = true; - } else if (event.key === Key.BACKSPACE || event.key === Key.DELETE) { - this.formatBarRef.current.hide(); - } - if (handled) { event.preventDefault(); event.stopPropagation(); diff --git a/src/editor/autocomplete.ts b/src/editor/autocomplete.ts index 86260963e5..fe09406ca1 100644 --- a/src/editor/autocomplete.ts +++ b/src/editor/autocomplete.ts @@ -64,7 +64,8 @@ export default class AutocompleteWrapperModel { return ac && ac.countCompletions() > 0; } - public onEnter() { + public async confirmCompletion() { + await this.getAutocompleterComponent().onConfirmCompletion(); this.updateCallback({close: true}); } @@ -76,9 +77,6 @@ export default class AutocompleteWrapperModel { if (acComponent.countCompletions() === 0) { // Force completions to show for the text currently entered await acComponent.forceComplete(); - } else { - await acComponent.onConfirmCompletion(); - this.updateCallback({close: true}); } } From 834579f7785e304b32f1f8070a1ca7d564c63da1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 11 May 2021 13:07:30 +0200 Subject: [PATCH 0031/1396] Don't render any audio non-primary 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/CallView.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index b49e729244..dc84cbed60 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -410,9 +410,10 @@ export default class CallView extends React.Component { // TODO: Later the CallView should probably be reworked to support // any number of feeds but now we can't render more than 4 feeds if (i >= 4) return; - // Here we check to hide local audio feeds to achieve the same UI/UX - // as before. But once again this might be subject to change - if (feed.isVideoMuted() && feed.isLocal()) return; + // Here we check to hide any non-main audio feeds from the UI + // This is because we don't want them to obstruct the view + // But once again this might be subject to change + if (feed.isVideoMuted() && i > 0) return; return ( Date: Thu, 13 May 2021 18:11:47 +0200 Subject: [PATCH 0032/1396] Show screensharign button only if connected MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/voip/CallView.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index dc84cbed60..a9d605c9f1 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -506,10 +506,14 @@ export default class CallView extends React.Component { /> : null; let screensharingButton; - // Screensharing is possible, if we can send a second stream and identify - // it using SDPStreamMetadata or if we can replace the already existing - // usermedia track by a screensharing track - if (this.props.call.opponentSupportsSDPStreamMetadata() || this.props.call.type === CallType.Video) { + // Screensharing is possible, if we can send a second stream and + // identify it using SDPStreamMetadata or if we can replace the already + // existing usermedia track by a screensharing track. We also need to be + // connected to know the state of the other side + if ( + (this.props.call.opponentSupportsSDPStreamMetadata() || this.props.call.type === CallType.Video) && + this.props.call.state === CallState.Connected + ) { screensharingButton = ( Date: Tue, 18 May 2021 12:56:23 +0100 Subject: [PATCH 0033/1396] delint --- src/autocomplete/AutocompleteProvider.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/autocomplete/AutocompleteProvider.tsx b/src/autocomplete/AutocompleteProvider.tsx index 38dd34a047..1924ea48a7 100644 --- a/src/autocomplete/AutocompleteProvider.tsx +++ b/src/autocomplete/AutocompleteProvider.tsx @@ -98,9 +98,7 @@ export default abstract class AutocompleteProvider { selection: ISelectionRange, force: boolean, limit: number, - ): Promise { - return []; - } + ): Promise; abstract getName(): string; From 5c2abd291ad633518585713ca6eea08035209668 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 19 May 2021 08:46:04 +0200 Subject: [PATCH 0034/1396] Do not render the audio element if there is no audio track MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/voip/AudioFeed.tsx | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/components/views/voip/AudioFeed.tsx b/src/components/views/voip/AudioFeed.tsx index c78f0c0fc8..7fb57abcd2 100644 --- a/src/components/views/voip/AudioFeed.tsx +++ b/src/components/views/voip/AudioFeed.tsx @@ -23,9 +23,21 @@ interface IProps { feed: CallFeed, } -export default class AudioFeed extends React.Component { +interface IState { + audioMuted: boolean; +} + +export default class AudioFeed extends React.Component { private element = createRef(); + constructor(props: IProps) { + super(props); + + this.state = { + audioMuted: this.props.feed.isAudioMuted(), + }; + } + componentDidMount() { this.props.feed.addListener(CallFeedEvent.NewStream, this.onNewStream); this.playMedia(); @@ -38,6 +50,7 @@ export default class AudioFeed extends React.Component { private playMedia() { const element = this.element.current; + if (!element) return; const audioOutput = CallMediaHandler.getAudioOutput(); if (audioOutput) { @@ -75,6 +88,7 @@ export default class AudioFeed extends React.Component { private stopMedia() { const element = this.element.current; + if (!element) return; element.pause(); element.src = null; @@ -86,10 +100,16 @@ export default class AudioFeed extends React.Component { } private onNewStream = () => { + this.setState({ + audioMuted: this.props.feed.isAudioMuted(), + }); this.playMedia(); }; render() { + // Do not render the audio element if there is no audio track + if (this.state.audioMuted) return null; + return (