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 01/85] 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 02/85] 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 03/85] 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 60e7089c770592b90194f478c57a3c587a71bad0 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 11 May 2021 11:14:21 +0100 Subject: [PATCH 04/85] 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 78f569de94b623021aed496a6c848b4d6ec6e4bb Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 18 May 2021 12:56:23 +0100 Subject: [PATCH 05/85] 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 28eaac0ef808bb5e7604faef3aa240a6c4c2ae54 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 20 May 2021 22:25:19 +0100 Subject: [PATCH 06/85] remove dead code and fix some types --- src/components/views/rooms/Autocomplete.tsx | 8 ++++---- .../views/rooms/BasicMessageComposer.tsx | 5 ++--- src/editor/autocomplete.ts | 20 ------------------- src/editor/model.ts | 2 +- 4 files changed, 7 insertions(+), 28 deletions(-) diff --git a/src/components/views/rooms/Autocomplete.tsx b/src/components/views/rooms/Autocomplete.tsx index 1eb2be473b..d9b4268917 100644 --- a/src/components/views/rooms/Autocomplete.tsx +++ b/src/components/views/rooms/Autocomplete.tsx @@ -33,9 +33,9 @@ interface IProps { // the query string for which to show autocomplete suggestions query: string; // method invoked with range and text content when completion is confirmed - onConfirm: (ICompletion) => void; + onConfirm: (completion: ICompletion) => void; // method invoked when selected (if any) completion changes - onSelectionChange?: (ICompletion, number) => void; + onSelectionChange?: (partIndex: number) => void; selection: ISelectionRange; // The room in which we're autocompleting room: Room; @@ -172,7 +172,7 @@ export default class Autocomplete extends React.PureComponent { if (anyMatches) { hide = false; if (this.props.onSelectionChange) { - this.props.onSelectionChange(this.state.completionList[selectionOffset - 1], selectionOffset - 1); + this.props.onSelectionChange(selectionOffset - 1); } } @@ -258,7 +258,7 @@ export default class Autocomplete extends React.PureComponent { setSelection(selectionOffset: number) { this.setState({selectionOffset, hide: false}); if (this.props.onSelectionChange) { - this.props.onSelectionChange(this.state.completionList[selectionOffset - 1], selectionOffset - 1); + this.props.onSelectionChange(selectionOffset - 1); } } diff --git a/src/components/views/rooms/BasicMessageComposer.tsx b/src/components/views/rooms/BasicMessageComposer.tsx index a2cae654f3..16332fda8f 100644 --- a/src/components/views/rooms/BasicMessageComposer.tsx +++ b/src/components/views/rooms/BasicMessageComposer.tsx @@ -575,10 +575,9 @@ export default class BasicMessageEditor extends React.Component this.props.model.autoComplete.onComponentConfirm(completion); }; - private onAutoCompleteSelectionChange = (completion: ICompletion, completionIndex: number) => { + private onAutoCompleteSelectionChange = (completionIndex: number) => { this.modifiedFlag = true; - // this.props.model.autoComplete.onComponentSelectionChange(completion); - this.setState({completionIndex}); + this.setState({ completionIndex }); }; private configureEmoticonAutoReplace = () => { diff --git a/src/editor/autocomplete.ts b/src/editor/autocomplete.ts index fe09406ca1..ea943c3f2c 100644 --- a/src/editor/autocomplete.ts +++ b/src/editor/autocomplete.ts @@ -32,7 +32,6 @@ export type GetAutocompleterComponent = () => Autocomplete; export type UpdateQuery = (test: string) => Promise; export default class AutocompleteWrapperModel { - private queryPart: Part; private partIndex: number; constructor( @@ -45,10 +44,6 @@ export default class AutocompleteWrapperModel { public onEscape(e: KeyboardEvent) { this.getAutocompleterComponent().onEscape(e); - this.updateCallback({ - replaceParts: [this.partCreator.plain(this.queryPart.text)], - close: true, - }); } public close() { @@ -89,25 +84,10 @@ export default class AutocompleteWrapperModel { } public onPartUpdate(part: Part, pos: DocumentPosition) { - // cache the typed value and caret here - // so we can restore it in onComponentSelectionChange when the value is undefined (meaning it should be the typed text) - this.queryPart = part; this.partIndex = pos.index; return this.updateQuery(part.text); } - public onComponentSelectionChange(completion: ICompletion) { - if (!completion) { - this.updateCallback({ - replaceParts: [this.queryPart], - }); - } else { - this.updateCallback({ - replaceParts: this.partForCompletion(completion), - }); - } - } - public onComponentConfirm(completion: ICompletion) { this.updateCallback({ replaceParts: this.partForCompletion(completion), diff --git a/src/editor/model.ts b/src/editor/model.ts index f1b6f90957..90765da047 100644 --- a/src/editor/model.ts +++ b/src/editor/model.ts @@ -237,7 +237,7 @@ export default class EditorModel { } } } - // not _autoComplete, only there if active part is autocomplete part + // not autoComplete, only there if active part is autocomplete part if (this.autoComplete) { return this.autoComplete.onPartUpdate(part, pos); } From b88d67bb005b3b02ae9f31a9c35ff63dfd5a8cba Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 7 Jul 2021 11:08:53 +0100 Subject: [PATCH 07/85] Convert SearchResult, InteractiveAuth, PushProcessor and Scheduler to Typescript --- src/components/structures/InteractiveAuth.js | 300 ------------------ src/components/structures/InteractiveAuth.tsx | 300 ++++++++++++++++++ .../auth/InteractiveAuthEntryComponents.tsx | 51 ++- .../views/dialogs/DeactivateAccountDialog.tsx | 10 +- .../controllers/NotificationControllers.ts | 4 +- 5 files changed, 331 insertions(+), 334 deletions(-) delete mode 100644 src/components/structures/InteractiveAuth.js create mode 100644 src/components/structures/InteractiveAuth.tsx diff --git a/src/components/structures/InteractiveAuth.js b/src/components/structures/InteractiveAuth.js deleted file mode 100644 index 9ff830f66a..0000000000 --- a/src/components/structures/InteractiveAuth.js +++ /dev/null @@ -1,300 +0,0 @@ -/* -Copyright 2017 Vector Creations 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 { InteractiveAuth } from "matrix-js-sdk/src/interactive-auth"; -import React, { createRef } from 'react'; -import PropTypes from 'prop-types'; - -import getEntryComponentForLoginType from '../views/auth/InteractiveAuthEntryComponents'; - -import * as sdk from '../../index'; -import { replaceableComponent } from "../../utils/replaceableComponent"; - -export const ERROR_USER_CANCELLED = new Error("User cancelled auth session"); - -@replaceableComponent("structures.InteractiveAuthComponent") -export default class InteractiveAuthComponent extends React.Component { - static propTypes = { - // matrix client to use for UI auth requests - matrixClient: PropTypes.object.isRequired, - - // response from initial request. If not supplied, will do a request on - // mount. - authData: PropTypes.shape({ - flows: PropTypes.array, - params: PropTypes.object, - session: PropTypes.string, - }), - - // callback - makeRequest: PropTypes.func.isRequired, - - // callback called when the auth process has finished, - // successfully or unsuccessfully. - // @param {bool} status True if the operation requiring - // auth was completed sucessfully, false if canceled. - // @param {object} result The result of the authenticated call - // if successful, otherwise the error object. - // @param {object} extra Additional information about the UI Auth - // process: - // * emailSid {string} If email auth was performed, the sid of - // the auth session. - // * clientSecret {string} The client secret used in auth - // sessions with the ID server. - onAuthFinished: PropTypes.func.isRequired, - - // Inputs provided by the user to the auth process - // and used by various stages. As passed to js-sdk - // interactive-auth - inputs: PropTypes.object, - - // As js-sdk interactive-auth - requestEmailToken: PropTypes.func, - sessionId: PropTypes.string, - clientSecret: PropTypes.string, - emailSid: PropTypes.string, - - // If true, poll to see if the auth flow has been completed - // out-of-band - poll: PropTypes.bool, - - // If true, components will be told that the 'Continue' button - // is managed by some other party and should not be managed by - // the component itself. - continueIsManaged: PropTypes.bool, - - // Called when the stage changes, or the stage's phase changes. First - // argument is the stage, second is the phase. Some stages do not have - // phases and will be counted as 0 (numeric). - onStagePhaseChange: PropTypes.func, - - // continueText and continueKind are passed straight through to the AuthEntryComponent. - continueText: PropTypes.string, - continueKind: PropTypes.string, - }; - - constructor(props) { - super(props); - - this.state = { - authStage: null, - busy: false, - errorText: null, - stageErrorText: null, - submitButtonEnabled: false, - }; - - this._unmounted = false; - this._authLogic = new InteractiveAuth({ - authData: this.props.authData, - doRequest: this._requestCallback, - busyChanged: this._onBusyChanged, - inputs: this.props.inputs, - stateUpdated: this._authStateUpdated, - matrixClient: this.props.matrixClient, - sessionId: this.props.sessionId, - clientSecret: this.props.clientSecret, - emailSid: this.props.emailSid, - requestEmailToken: this._requestEmailToken, - }); - - this._intervalId = null; - if (this.props.poll) { - this._intervalId = setInterval(() => { - this._authLogic.poll(); - }, 2000); - } - - this._stageComponent = createRef(); - } - - // TODO: [REACT-WARNING] Replace component with real class, use constructor for refs - UNSAFE_componentWillMount() { // eslint-disable-line camelcase - this._authLogic.attemptAuth().then((result) => { - const extra = { - emailSid: this._authLogic.getEmailSid(), - clientSecret: this._authLogic.getClientSecret(), - }; - this.props.onAuthFinished(true, result, extra); - }).catch((error) => { - this.props.onAuthFinished(false, error); - console.error("Error during user-interactive auth:", error); - if (this._unmounted) { - return; - } - - const msg = error.message || error.toString(); - this.setState({ - errorText: msg, - }); - }); - } - - componentWillUnmount() { - this._unmounted = true; - - if (this._intervalId !== null) { - clearInterval(this._intervalId); - } - } - - _requestEmailToken = async (...args) => { - this.setState({ - busy: true, - }); - try { - return await this.props.requestEmailToken(...args); - } finally { - this.setState({ - busy: false, - }); - } - }; - - tryContinue = () => { - if (this._stageComponent.current && this._stageComponent.current.tryContinue) { - this._stageComponent.current.tryContinue(); - } - }; - - _authStateUpdated = (stageType, stageState) => { - const oldStage = this.state.authStage; - this.setState({ - busy: false, - authStage: stageType, - stageState: stageState, - errorText: stageState.error, - }, () => { - if (oldStage !== stageType) { - this._setFocus(); - } else if ( - !stageState.error && this._stageComponent.current && - this._stageComponent.current.attemptFailed - ) { - this._stageComponent.current.attemptFailed(); - } - }); - }; - - _requestCallback = (auth) => { - // This wrapper just exists because the js-sdk passes a second - // 'busy' param for backwards compat. This throws the tests off - // so discard it here. - return this.props.makeRequest(auth); - }; - - _onBusyChanged = (busy) => { - // if we've started doing stuff, reset the error messages - if (busy) { - this.setState({ - busy: true, - errorText: null, - stageErrorText: null, - }); - } - // The JS SDK eagerly reports itself as "not busy" right after any - // immediate work has completed, but that's not really what we want at - // the UI layer, so we ignore this signal and show a spinner until - // there's a new screen to show the user. This is implemented by setting - // `busy: false` in `_authStateUpdated`. - // See also https://github.com/vector-im/element-web/issues/12546 - }; - - _setFocus() { - if (this._stageComponent.current && this._stageComponent.current.focus) { - this._stageComponent.current.focus(); - } - } - - _submitAuthDict = authData => { - this._authLogic.submitAuthDict(authData); - }; - - _onPhaseChange = newPhase => { - if (this.props.onStagePhaseChange) { - this.props.onStagePhaseChange(this.state.authStage, newPhase || 0); - } - }; - - _onStageCancel = () => { - this.props.onAuthFinished(false, ERROR_USER_CANCELLED); - }; - - _renderCurrentStage() { - const stage = this.state.authStage; - if (!stage) { - if (this.state.busy) { - const Loader = sdk.getComponent("elements.Spinner"); - return ; - } else { - return null; - } - } - - const StageComponent = getEntryComponentForLoginType(stage); - return ( - - ); - } - - _onAuthStageFailed = e => { - this.props.onAuthFinished(false, e); - }; - - _setEmailSid = sid => { - this._authLogic.setEmailSid(sid); - }; - - render() { - let error = null; - if (this.state.errorText) { - error = ( -
- { this.state.errorText } -
- ); - } - - return ( -
-
- { this._renderCurrentStage() } - { error } -
-
- ); - } -} diff --git a/src/components/structures/InteractiveAuth.tsx b/src/components/structures/InteractiveAuth.tsx new file mode 100644 index 0000000000..017e34184f --- /dev/null +++ b/src/components/structures/InteractiveAuth.tsx @@ -0,0 +1,300 @@ +/* +Copyright 2017 - 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 { + AuthType, + IAuthData, + IAuthDict, + IInputs, + InteractiveAuth, + IStageStatus, +} from "matrix-js-sdk/src/interactive-auth"; +import { MatrixClient } from "matrix-js-sdk/src/client"; +import React, { createRef } from 'react'; + +import getEntryComponentForLoginType, { IStageComponent } from '../views/auth/InteractiveAuthEntryComponents'; +import Spinner from "../views/elements/Spinner"; +import { replaceableComponent } from "../../utils/replaceableComponent"; + +export const ERROR_USER_CANCELLED = new Error("User cancelled auth session"); + +interface IProps { + // matrix client to use for UI auth requests + matrixClient: MatrixClient; + // response from initial request. If not supplied, will do a request on mount. + authData: IAuthData; + // Inputs provided by the user to the auth process + // and used by various stages. As passed to js-sdk + // interactive-auth + inputs?: IInputs; + sessionId?: string; + clientSecret?: string; + emailSid?: string; + // If true, poll to see if the auth flow has been completed out-of-band + poll?: boolean; + // If true, components will be told that the 'Continue' button + // is managed by some other party and should not be managed by + // the component itself. + continueIsManaged?: boolean; + // continueText and continueKind are passed straight through to the AuthEntryComponent. + continueText?: string; + continueKind?: string; + // callback + makeRequest(auth: IAuthData): Promise; + // callback called when the auth process has finished, + // successfully or unsuccessfully. + // @param {boolean} status True if the operation requiring + // auth was completed successfully, false if canceled. + // @param {object} result The result of the authenticated call + // if successful, otherwise the error object. + // @param {object} extra Additional information about the UI Auth + // process: + // * emailSid {string} If email auth was performed, the sid of + // the auth session. + // * clientSecret {string} The client secret used in auth + // sessions with the ID server. + onAuthFinished( + status: boolean, + result: IAuthData | Error, + extra?: { emailSid?: string, clientSecret?: string }, + ): void; + // As js-sdk interactive-auth + requestEmailToken?(email: string, secret: string, attempt: number, session: string): Promise<{ sid: string }>, + // Called when the stage changes, or the stage's phase changes. First + // argument is the stage, second is the phase. Some stages do not have + // phases and will be counted as 0 (numeric). + onStagePhaseChange?(stage: string, phase: string | number): void, +} + +interface IState { + authStage?: AuthType; + stageState?: IStageStatus; + busy: boolean; + errorText?: string; + stageErrorText?: string; + submitButtonEnabled: boolean; +} + +@replaceableComponent("structures.InteractiveAuthComponent") +export default class InteractiveAuthComponent extends React.Component { + private readonly authLogic: InteractiveAuth; + private readonly _intervalId: NodeJS.Timeout = null; + private readonly stageComponent = createRef(); + + private unmounted = false; + + constructor(props) { + super(props); + + this.state = { + authStage: null, + busy: false, + errorText: null, + stageErrorText: null, + submitButtonEnabled: false, + }; + + this.authLogic = new InteractiveAuth({ + authData: this.props.authData, + doRequest: this.requestCallback, + busyChanged: this.onBusyChanged, + inputs: this.props.inputs, + stateUpdated: this.authStateUpdated, + matrixClient: this.props.matrixClient, + sessionId: this.props.sessionId, + clientSecret: this.props.clientSecret, + emailSid: this.props.emailSid, + requestEmailToken: this.requestEmailToken, + }); + + if (this.props.poll) { + this._intervalId = setInterval(() => { + this.authLogic.poll(); + }, 2000); + } + } + + // TODO: [REACT-WARNING] Replace component with real class, use constructor for refs + UNSAFE_componentWillMount() { // eslint-disable-line camelcase + this.authLogic.attemptAuth().then((result) => { + const extra = { + emailSid: this.authLogic.getEmailSid(), + clientSecret: this.authLogic.getClientSecret(), + }; + this.props.onAuthFinished(true, result, extra); + }).catch((error) => { + this.props.onAuthFinished(false, error); + console.error("Error during user-interactive auth:", error); + if (this.unmounted) { + return; + } + + const msg = error.message || error.toString(); + this.setState({ + errorText: msg, + }); + }); + } + + componentWillUnmount() { + this.unmounted = true; + + if (this._intervalId !== null) { + clearInterval(this._intervalId); + } + } + + private requestEmailToken = async ( + email: string, + secret: string, + attempt: number, + session: string, + ): Promise<{sid: string}> => { + this.setState({ + busy: true, + }); + try { + return await this.props.requestEmailToken(email, secret, attempt, session); + } finally { + this.setState({ + busy: false, + }); + } + }; + + private tryContinue = (): void => { + this.stageComponent.current?.tryContinue?.(); + }; + + private authStateUpdated = (stageType: AuthType, stageState: IStageStatus): void => { + const oldStage = this.state.authStage; + this.setState({ + busy: false, + authStage: stageType, + stageState: stageState, + errorText: stageState.error, + }, () => { + if (oldStage !== stageType) { + this.setFocus(); + } else if (!stageState.error) { + this.stageComponent.current?.attemptFailed?.(); + } + }); + }; + + private requestCallback = (auth: IAuthData, background: boolean): Promise => { + // This wrapper just exists because the js-sdk passes a second + // 'busy' param for backwards compat. This throws the tests off + // so discard it here. + return this.props.makeRequest(auth); + }; + + private onBusyChanged = (busy: boolean): void => { + // if we've started doing stuff, reset the error messages + if (busy) { + this.setState({ + busy: true, + errorText: null, + stageErrorText: null, + }); + } + // The JS SDK eagerly reports itself as "not busy" right after any + // immediate work has completed, but that's not really what we want at + // the UI layer, so we ignore this signal and show a spinner until + // there's a new screen to show the user. This is implemented by setting + // `busy: false` in `authStateUpdated`. + // See also https://github.com/vector-im/element-web/issues/12546 + }; + + private setFocus(): void { + this.stageComponent.current?.focus?.(); + } + + private submitAuthDict = (authData: IAuthDict): void => { + this.authLogic.submitAuthDict(authData); + }; + + private onPhaseChange = (newPhase: number): void => { + this.props.onStagePhaseChange?.(this.state.authStage, newPhase || 0); + }; + + private onStageCancel = (): void => { + this.props.onAuthFinished(false, ERROR_USER_CANCELLED); + }; + + private renderCurrentStage() { + const stage = this.state.authStage; + if (!stage) { + if (this.state.busy) { + return ; + } else { + return null; + } + } + + const StageComponent = getEntryComponentForLoginType(stage); + return ( + + ); + } + + private onAuthStageFailed = (e: Error): void => { + this.props.onAuthFinished(false, e); + }; + + private setEmailSid = (sid: string): void => { + this.authLogic.setEmailSid(sid); + }; + + render() { + let error = null; + if (this.state.errorText) { + error = ( +
+ { this.state.errorText } +
+ ); + } + + return ( +
+
+ { this.renderCurrentStage() } + { error } +
+
+ ); + } +} diff --git a/src/components/views/auth/InteractiveAuthEntryComponents.tsx b/src/components/views/auth/InteractiveAuthEntryComponents.tsx index e002eb5717..a032414eb0 100644 --- a/src/components/views/auth/InteractiveAuthEntryComponents.tsx +++ b/src/components/views/auth/InteractiveAuthEntryComponents.tsx @@ -14,9 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { ChangeEvent, createRef, FormEvent, MouseEvent } from 'react'; +import React, { ChangeEvent, ComponentClass, createRef, FormEvent, MouseEvent, RefObject } from 'react'; import classNames from 'classnames'; import { MatrixClient } from "matrix-js-sdk/src/client"; +import { AuthType, IAuthDict, IInputs, IStageStatus } from 'matrix-js-sdk/src/interactive-auth'; import * as sdk from '../../../index'; import { _t } from '../../../languageHandler'; @@ -73,33 +74,6 @@ import { LocalisedPolicy, Policies } from '../../../Terms'; * focus: set the input focus appropriately in the form. */ -enum AuthType { - Password = "m.login.password", - Recaptcha = "m.login.recaptcha", - Terms = "m.login.terms", - Email = "m.login.email.identity", - Msisdn = "m.login.msisdn", - Sso = "m.login.sso", - SsoUnstable = "org.matrix.login.sso", -} - -/* eslint-disable camelcase */ -interface IAuthDict { - type?: AuthType; - // TODO: Remove `user` once servers support proper UIA - // See https://github.com/vector-im/element-web/issues/10312 - user?: string; - identifier?: any; - password?: string; - response?: string; - // TODO: Remove `threepid_creds` once servers support proper UIA - // See https://github.com/vector-im/element-web/issues/10312 - // See https://github.com/matrix-org/matrix-doc/issues/2220 - threepid_creds?: any; - threepidCreds?: any; -} -/* eslint-enable camelcase */ - export const DEFAULT_PHASE = 0; interface IAuthEntryProps { @@ -837,7 +811,26 @@ export class FallbackAuthEntry extends React.Component { } } -export default function getEntryComponentForLoginType(loginType: AuthType): typeof React.Component { +export interface IStageComponentProps extends IAuthEntryProps { + clientSecret?: string; + stageParams?: Record; + inputs?: IInputs; + stageState?: IStageStatus; + showContinue?: boolean; + continueText?: string; + continueKind?: string; + fail?(e: Error): void; + setEmailSid?(sid: string): void; + onCancel?(): void; +} + +export interface IStageComponent extends React.ComponentClass> { + tryContinue?(): void; + attemptFailed?(): void; + focus?(): void; +} + +export default function getEntryComponentForLoginType(loginType: AuthType): IStageComponent { switch (loginType) { case AuthType.Password: return PasswordAuthEntry; diff --git a/src/components/views/dialogs/DeactivateAccountDialog.tsx b/src/components/views/dialogs/DeactivateAccountDialog.tsx index 6df6056670..d30f90d111 100644 --- a/src/components/views/dialogs/DeactivateAccountDialog.tsx +++ b/src/components/views/dialogs/DeactivateAccountDialog.tsx @@ -16,6 +16,7 @@ limitations under the License. */ import React from 'react'; +import { AuthType, IAuthData } from 'matrix-js-sdk/src/interactive-auth'; import * as sdk from '../../../index'; import Analytics from '../../../Analytics'; @@ -65,7 +66,7 @@ export default class DeactivateAccountDialog extends React.Component { + private onStagePhaseChange = (stage: AuthType, phase: string): void => { const dialogAesthetics = { [SSOAuthEntry.PHASE_PREAUTH]: { body: _t("Confirm your account deactivation by using Single Sign On to prove your identity."), @@ -115,7 +116,10 @@ export default class DeactivateAccountDialog extends React.Component { + private onUIAuthComplete = (auth: IAuthData): void => { + // XXX: this should be returning a promise to maintain the state inside the state machine correct + // but given that a deactivation is followed by a local logout and all object instances being thrown away + // this isn't done. MatrixClientPeg.get().deactivateAccount(auth, this.state.shouldErase).then(r => { // Deactivation worked - logout & close this dialog Analytics.trackEvent('Account', 'Deactivate Account'); @@ -182,7 +186,7 @@ export default class DeactivateAccountDialog extends React.Component Date: Thu, 15 Jul 2021 10:09:24 +0100 Subject: [PATCH 08/85] delint and improve ts --- src/components/views/rooms/Autocomplete.tsx | 28 +++++++++---------- .../views/rooms/BasicMessageComposer.tsx | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/components/views/rooms/Autocomplete.tsx b/src/components/views/rooms/Autocomplete.tsx index 06386c61b4..34909baef1 100644 --- a/src/components/views/rooms/Autocomplete.tsx +++ b/src/components/views/rooms/Autocomplete.tsx @@ -85,7 +85,7 @@ export default class Autocomplete extends React.PureComponent { this.applyNewProps(); } - private applyNewProps(oldQuery?: string, oldRoom?: Room) { + private applyNewProps(oldQuery?: string, oldRoom?: Room): void { if (oldRoom && this.props.room.roomId !== oldRoom.roomId) { this.autocompleter.destroy(); this.autocompleter = new Autocompleter(this.props.room); @@ -103,7 +103,7 @@ export default class Autocomplete extends React.PureComponent { this.autocompleter.destroy(); } - complete(query: string, selection: ISelectionRange) { + private complete(query: string, selection: ISelectionRange): Promise { this.queryRequested = query; if (this.debounceCompletionsRequest) { clearTimeout(this.debounceCompletionsRequest); @@ -134,7 +134,7 @@ export default class Autocomplete extends React.PureComponent { }); } - processQuery(query: string, selection: ISelectionRange) { + private processQuery(query: string, selection: ISelectionRange): Promise { return this.autocompleter.getCompletions( query, selection, this.state.forceComplete, MAX_PROVIDER_MATCHES, ).then((completions) => { @@ -146,7 +146,7 @@ export default class Autocomplete extends React.PureComponent { }); } - processCompletions(completions: IProviderCompletions[]) { + private processCompletions(completions: IProviderCompletions[]): void { const completionList = flatMap(completions, (provider) => provider.completions); // Reset selection when completion list becomes empty. @@ -186,16 +186,16 @@ export default class Autocomplete extends React.PureComponent { }); } - hasSelection(): boolean { + public hasSelection(): boolean { return this.countCompletions() > 0 && this.state.selectionOffset !== 0; } - countCompletions(): number { + public countCompletions(): number { return this.state.completionList.length; } // called from MessageComposerInput - moveSelection(delta: number) { + public moveSelection(delta: number): void { const completionCount = this.countCompletions(); if (completionCount === 0) return; // there are no items to move the selection through @@ -204,7 +204,7 @@ export default class Autocomplete extends React.PureComponent { this.setSelection(1 + index); } - onEscape(e: KeyboardEvent): boolean { + public onEscape(e: KeyboardEvent): boolean { const completionCount = this.countCompletions(); if (completionCount === 0) { // autocomplete is already empty, so don't preventDefault @@ -217,7 +217,7 @@ export default class Autocomplete extends React.PureComponent { this.hide(); } - hide = () => { + private hide = (): void => { this.setState({ hide: true, selectionOffset: 1, @@ -226,7 +226,7 @@ export default class Autocomplete extends React.PureComponent { }); }; - forceComplete() { + public forceComplete(): Promise { return new Promise((resolve) => { this.setState({ forceComplete: true, @@ -239,11 +239,11 @@ export default class Autocomplete extends React.PureComponent { }); } - onConfirmCompletion = () => { + public onConfirmCompletion = (): void => { this.onCompletionClicked(this.state.selectionOffset); - } + }; - onCompletionClicked = (selectionOffset: number): boolean => { + private onCompletionClicked = (selectionOffset: number): boolean => { const count = this.countCompletions(); if (count === 0 || selectionOffset < 1 || selectionOffset > count) { return false; @@ -255,7 +255,7 @@ export default class Autocomplete extends React.PureComponent { return true; }; - setSelection(selectionOffset: number) { + private setSelection(selectionOffset: number): void { this.setState({ selectionOffset, hide: false }); if (this.props.onSelectionChange) { this.props.onSelectionChange(selectionOffset - 1); diff --git a/src/components/views/rooms/BasicMessageComposer.tsx b/src/components/views/rooms/BasicMessageComposer.tsx index fb3df6cd78..f87c735b6f 100644 --- a/src/components/views/rooms/BasicMessageComposer.tsx +++ b/src/components/views/rooms/BasicMessageComposer.tsx @@ -552,7 +552,7 @@ export default class BasicMessageEditor extends React.Component model.autoComplete.close(); } } else { - this.setState({showVisualBell: true}); + this.setState({ showVisualBell: true }); } } catch (err) { console.error(err); From 4c7c10abf824aab0dd21fdb7a8ff214b35690f9f Mon Sep 17 00:00:00 2001 From: Libexus Date: Wed, 21 Jul 2021 09:44:39 +0200 Subject: [PATCH 09/85] Remove misplaced bracket in a translation string --- src/components/views/messages/CallEvent.tsx | 2 +- src/i18n/strings/en_EN.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/messages/CallEvent.tsx b/src/components/views/messages/CallEvent.tsx index c0be3b46bb..534458e75b 100644 --- a/src/components/views/messages/CallEvent.tsx +++ b/src/components/views/messages/CallEvent.tsx @@ -138,7 +138,7 @@ export default class CallEvent extends React.Component { } else if (hangupReason === CallErrorCode.UserBusy) { reason = _t("The user you called is busy."); } else { - reason = _t('Unknown failure: %(reason)s)', { reason: hangupReason }); + reason = _t('Unknown failure: %(reason)s', { reason: hangupReason }); } return ( diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index f081ec823b..538eb74954 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1849,7 +1849,7 @@ "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", "No answer": "No answer", - "Unknown failure: %(reason)s)": "Unknown failure: %(reason)s)", + "Unknown failure: %(reason)s": "Unknown failure: %(reason)s", "This call has failed": "This call has failed", "You missed this call": "You missed this call", "Call back": "Call back", From a6e5112be08eabf3e7c7b0d138e81178ad209edb Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 4 Aug 2021 10:37:35 +0100 Subject: [PATCH 10/85] Offer a way to create a space based on existing community --- res/css/_components.scss | 1 + res/css/structures/_GroupView.scss | 55 +++ res/css/structures/_SpaceRoomView.scss | 14 +- .../context_menus/_TagTileContextMenu.scss | 4 + .../_CreateSpaceFromCommunityDialog.scss | 176 +++++++++ .../user/_PreferencesUserSettingsTab.scss | 20 ++ src/components/structures/GroupView.js | 26 ++ src/components/structures/SpaceRoomView.tsx | 44 ++- .../views/context_menus/TagTileContextMenu.js | 18 + .../CreateSpaceFromCommunityDialog.tsx | 338 ++++++++++++++++++ .../views/dialogs/CreateSubspaceDialog.tsx | 29 +- .../views/dialogs/UserSettingsDialog.tsx | 2 +- src/components/views/right_panel/UserInfo.tsx | 2 +- .../tabs/user/PreferencesUserSettingsTab.tsx | 114 +++++- .../views/spaces/SpaceCreateMenu.tsx | 68 ++-- src/i18n/strings/en_EN.json | 31 +- src/stores/GroupFilterOrderStore.js | 8 +- src/stores/GroupStore.js | 4 +- src/utils/space.tsx | 9 + 19 files changed, 895 insertions(+), 68 deletions(-) create mode 100644 res/css/views/dialogs/_CreateSpaceFromCommunityDialog.scss create mode 100644 src/components/views/dialogs/CreateSpaceFromCommunityDialog.tsx diff --git a/res/css/_components.scss b/res/css/_components.scss index 76551b51f8..dd1b0d220b 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -75,6 +75,7 @@ @import "./views/dialogs/_CreateCommunityPrototypeDialog.scss"; @import "./views/dialogs/_CreateGroupDialog.scss"; @import "./views/dialogs/_CreateRoomDialog.scss"; +@import "./views/dialogs/_CreateSpaceFromCommunityDialog.scss"; @import "./views/dialogs/_CreateSubspaceDialog.scss"; @import "./views/dialogs/_DeactivateAccountDialog.scss"; @import "./views/dialogs/_DevtoolsDialog.scss"; diff --git a/res/css/structures/_GroupView.scss b/res/css/structures/_GroupView.scss index 60f9ebdd08..21137d8a12 100644 --- a/res/css/structures/_GroupView.scss +++ b/res/css/structures/_GroupView.scss @@ -368,6 +368,61 @@ limitations under the License. padding: 40px 20px; } +.mx_GroupView_spaceUpgradePrompt { + padding: 16px 50px; + background-color: $header-panel-bg-color; + border-radius: 8px; + max-width: 632px; + font-size: $font-15px; + line-height: $font-24px; + margin-top: 24px; + position: relative; + + > h2 { + font-size: inherit; + font-weight: $font-semi-bold; + } + + > p, h2 { + margin: 0; + } + + &::before { + content: ""; + position: absolute; + height: $font-24px; + width: 20px; + left: 18px; + mask-repeat: no-repeat; + mask-position: center; + mask-size: contain; + mask-image: url('$(res)/img/element-icons/room/room-summary.svg'); + background-color: $secondary-fg-color; + } + + .mx_AccessibleButton { + width: 16px; + height: 16px; + border-radius: 8px; + background-color: $input-darker-bg-color; + position: absolute; + top: 16px; + right: 16px; + + &::before { + content: ""; + position: absolute; + width: inherit; + height: inherit; + mask-repeat: no-repeat; + mask-position: center; + mask-size: 8px; + mask-image: url('$(res)/img/image-view/close.svg'); + background-color: $secondary-fg-color; + } + } +} + .mx_GroupView .mx_MemberInfo .mx_AutoHideScrollbar > :not(.mx_MemberInfo_avatar) { padding-left: 16px; padding-right: 16px; diff --git a/res/css/structures/_SpaceRoomView.scss b/res/css/structures/_SpaceRoomView.scss index 58a4b426c2..945de01eba 100644 --- a/res/css/structures/_SpaceRoomView.scss +++ b/res/css/structures/_SpaceRoomView.scss @@ -180,6 +180,18 @@ $SpaceRoomViewInnerWidth: 428px; } } + .mx_SpaceRoomView_preview_migratedCommunity { + margin-bottom: 16px; + padding: 8px 12px; + border-radius: 8px; + border: 1px solid $input-border-color; + width: max-content; + + .mx_BaseAvatar { + margin-right: 4px; + } + } + .mx_SpaceRoomView_preview_inviter { display: flex; align-items: center; @@ -342,7 +354,7 @@ $SpaceRoomViewInnerWidth: 428px; .mx_SpaceFeedbackPrompt { padding: 7px; // 8px - 1px border - border: 1px solid $menu-border-color; + border: 1px solid rgba($primary-fg-color, .1); border-radius: 8px; width: max-content; margin: 0 0 -40px auto; // collapse its own height to not push other components down diff --git a/res/css/views/context_menus/_TagTileContextMenu.scss b/res/css/views/context_menus/_TagTileContextMenu.scss index d707f4ce7c..14f5ec817e 100644 --- a/res/css/views/context_menus/_TagTileContextMenu.scss +++ b/res/css/views/context_menus/_TagTileContextMenu.scss @@ -51,6 +51,10 @@ limitations under the License. mask-image: url('$(res)/img/element-icons/hide.svg'); } +.mx_TagTileContextMenu_createSpace::before { + mask-image: url('$(res)/img/element-icons/message/fwd.svg'); +} + .mx_TagTileContextMenu_separator { margin-top: 0; margin-bottom: 0; diff --git a/res/css/views/dialogs/_CreateSpaceFromCommunityDialog.scss b/res/css/views/dialogs/_CreateSpaceFromCommunityDialog.scss new file mode 100644 index 0000000000..059d38c77c --- /dev/null +++ b/res/css/views/dialogs/_CreateSpaceFromCommunityDialog.scss @@ -0,0 +1,176 @@ +/* +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_CreateSpaceFromCommunityDialog_wrapper { + .mx_Dialog { + display: flex; + flex-direction: column; + } +} + +.mx_CreateSpaceFromCommunityDialog { + width: 480px; + color: $primary-fg-color; + display: flex; + flex-direction: column; + flex-wrap: nowrap; + min-height: 0; + + .mx_CreateSpaceFromCommunityDialog_content { + > p { + font-size: $font-15px; + line-height: $font-24px; + color: $secondary-fg-color; + + &.mx_CreateSpaceFromCommunityDialog_flairNotice { + font-size: $font-12px; + line-height: $font-15px; + } + } + + .mx_SpaceBasicSettings { + > p { + font-size: $font-12px; + line-height: $font-15px; + margin: 8px 0 16px; + } + } + + .mx_JoinRuleDropdown .mx_Dropdown_menu { + width: auto !important; // override fixed width + } + } + + .mx_CreateSpaceFromCommunityDialog_footer { + display: flex; + margin-top: 20px; + + > span { + flex-grow: 1; + font-size: $font-12px; + line-height: $font-15px; + color: $secondary-fg-color; + + .mx_ProgressBar { + height: 8px; + width: 100%; + + @mixin ProgressBarBorderRadius 8px; + } + + .mx_CreateSpaceFromCommunityDialog_progressText { + margin-top: 8px; + font-size: $font-15px; + line-height: $font-24px; + color: $primary-fg-color; + } + + > * { + vertical-align: middle; + } + } + + .mx_CreateSpaceFromCommunityDialog_error { + padding-left: 12px; + + > img { + align-self: center; + } + + .mx_CreateSpaceFromCommunityDialog_errorHeading { + font-weight: $font-semi-bold; + font-size: $font-15px; + line-height: $font-18px; + color: $notice-primary-color; + } + + .mx_CreateSpaceFromCommunityDialog_errorCaption { + margin-top: 4px; + font-size: $font-12px; + line-height: $font-15px; + color: $primary-fg-color; + } + } + + .mx_AccessibleButton { + display: inline-block; + align-self: center; + } + + .mx_AccessibleButton_kind_primary { + padding: 8px 36px; + margin-left: 24px; + } + + .mx_AccessibleButton_kind_primary_outline { + margin-left: auto; + } + + .mx_CreateSpaceFromCommunityDialog_retryButton { + margin-left: 12px; + padding-left: 24px; + position: relative; + + &::before { + content: ''; + position: absolute; + background-color: $primary-fg-color; + mask-repeat: no-repeat; + mask-position: center; + mask-size: contain; + mask-image: url('$(res)/img/element-icons/retry.svg'); + width: 18px; + height: 18px; + left: 0; + } + } + + .mx_AccessibleButton_kind_link { + padding: 0; + } + } +} + +.mx_CreateSpaceFromCommunityDialog_SuccessInfoDialog { + .mx_InfoDialog { + max-width: 500px; + } + + .mx_AccessibleButton_kind_link { + padding: 0; + } + + .mx_CreateSpaceFromCommunityDialog_SuccessInfoDialog_checkmark { + position: relative; + border-radius: 50%; + border: 3px solid $accent-color; + width: 68px; + height: 68px; + margin: 12px auto 32px; + + &::before { + width: inherit; + height: inherit; + content: ''; + position: absolute; + background-color: $accent-color; + mask-repeat: no-repeat; + mask-position: center; + mask-image: url('$(res)/img/element-icons/roomlist/checkmark.svg'); + mask-size: 48px; + } + } +} diff --git a/res/css/views/settings/tabs/user/_PreferencesUserSettingsTab.scss b/res/css/views/settings/tabs/user/_PreferencesUserSettingsTab.scss index be0af9123b..efd2548d3d 100644 --- a/res/css/views/settings/tabs/user/_PreferencesUserSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_PreferencesUserSettingsTab.scss @@ -22,4 +22,24 @@ limitations under the License. .mx_SettingsTab_section { margin-bottom: 30px; } + + .mx_PreferencesUserSettingsTab_CommunityMigrator { + margin-right: 200px; + + > div { + font-weight: $font-semi-bold; + font-size: $font-15px; + line-height: $font-18px; + color: $primary-fg-color; + + .mx_BaseAvatar { + margin-right: 12px; + vertical-align: middle; + } + + .mx_AccessibleButton { + float: right; + } + } + } } diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js index 99fa94e62b..26bb1b8ae7 100644 --- a/src/components/structures/GroupView.js +++ b/src/components/structures/GroupView.js @@ -399,6 +399,8 @@ class FeaturedUser extends React.Component { const GROUP_JOINPOLICY_OPEN = "open"; const GROUP_JOINPOLICY_INVITE = "invite"; +const UPGRADE_NOTICE_LS_KEY = "mx_hide_community_upgrade_notice"; + @replaceableComponent("structures.GroupView") export default class GroupView extends React.Component { static propTypes = { @@ -422,6 +424,7 @@ export default class GroupView extends React.Component { publicityBusy: false, inviterProfile: null, showRightPanel: RightPanelStore.getSharedInstance().isOpenForGroup, + showUpgradeNotice: !localStorage.getItem(UPGRADE_NOTICE_LS_KEY), }; componentDidMount() { @@ -807,6 +810,11 @@ export default class GroupView extends React.Component { showGroupAddRoomDialog(this.props.groupId); }; + _dismissUpgradeNotice = () => { + localStorage.setItem(UPGRADE_NOTICE_LS_KEY, "true"); + this.setState({ showUpgradeNotice: false }); + } + _getGroupSection() { const groupSettingsSectionClasses = classnames({ "mx_GroupView_group": this.state.editing, @@ -843,10 +851,28 @@ export default class GroupView extends React.Component { }, ) }
:
; + + let communitiesUpgradeNotice; + if (this.state.showUpgradeNotice) { + communitiesUpgradeNotice =
+

{ _t("Communities can now be made into Spaces") }

+

+ { _t("Spaces are a new way to make a community, with new features coming.") } +   + { _t("Ask the admins of this community to make it into a Space " + + "and keep a look out for the invite.") } +   + { _t("Communities won't receive further updates.") } +

+ +
; + } + return
{ header } { hostingSignup } { changeDelayWarning } + { communitiesUpgradeNotice } { this._getJoinableNode() } { this._getLongDescriptionNode() } { this._getRoomsNode() } diff --git a/src/components/structures/SpaceRoomView.tsx b/src/components/structures/SpaceRoomView.tsx index 4064b2f48e..4eb9b855f7 100644 --- a/src/components/structures/SpaceRoomView.tsx +++ b/src/components/structures/SpaceRoomView.tsx @@ -74,6 +74,10 @@ import { BetaPill } from "../views/beta/BetaCard"; import { UserTab } from "../views/dialogs/UserSettingsDialog"; import { EffectiveMembership, getEffectiveMembership } from "../../utils/membership"; import { SpaceFeedbackPrompt } from "../views/spaces/SpaceCreateMenu"; +import { CreateEventField, IGroupSummary } from "../views/dialogs/CreateSpaceFromCommunityDialog"; +import { useAsyncMemo } from "../../hooks/useAsyncMemo"; +import Spinner from "../views/elements/Spinner"; +import GroupAvatar from "../views/avatars/GroupAvatar"; interface IProps { space: Room; @@ -158,7 +162,33 @@ const onBetaClick = () => { }); }; -const SpacePreview = ({ space, onJoinButtonClicked, onRejectButtonClicked }) => { +// XXX: temporary community migration component +const GroupTile = ({ groupId }: { groupId: string }) => { + const cli = useContext(MatrixClientContext); + const groupSummary = useAsyncMemo(() => cli.getGroupSummary(groupId), [cli, groupId]); + + if (!groupSummary) return ; + + return <> + + { groupSummary.profile.name } + ; +}; + +interface ISpacePreviewProps { + space: Room; + onJoinButtonClicked(): void; + onRejectButtonClicked(): void; +} + +const SpacePreview = ({ space, onJoinButtonClicked, onRejectButtonClicked }: ISpacePreviewProps) => { const cli = useContext(MatrixClientContext); const myMembership = useMyRoomMembership(space); @@ -270,8 +300,18 @@ const SpacePreview = ({ space, onJoinButtonClicked, onRejectButtonClicked }) =>
; } + let migratedCommunitySection: JSX.Element; + const createContent = space.currentState.getStateEvents(EventType.RoomCreate, "")?.getContent(); + if (createContent[CreateEventField]) { + migratedCommunitySection =
+ { _t("Created from ", {}, { + Community: () => , + }) } +
; + } + return
- + { migratedCommunitySection } { inviterSection }

diff --git a/src/components/views/context_menus/TagTileContextMenu.js b/src/components/views/context_menus/TagTileContextMenu.js index c40ff4207b..0c3c48a07f 100644 --- a/src/components/views/context_menus/TagTileContextMenu.js +++ b/src/components/views/context_menus/TagTileContextMenu.js @@ -24,6 +24,8 @@ import { MenuItem } from "../../structures/ContextMenu"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import { replaceableComponent } from "../../../utils/replaceableComponent"; import GroupFilterOrderStore from "../../../stores/GroupFilterOrderStore"; +import { createSpaceFromCommunity } from "../../../utils/space"; +import GroupStore from "../../../stores/GroupStore"; @replaceableComponent("views.context_menus.TagTileContextMenu") export default class TagTileContextMenu extends React.Component { @@ -49,6 +51,11 @@ export default class TagTileContextMenu extends React.Component { this.props.onFinished(); }; + _onCreateSpaceClick = () => { + createSpaceFromCommunity(this.context, this.props.tag); + this.props.onFinished(); + }; + _onMoveUp = () => { dis.dispatch(TagOrderActions.moveTag(this.context, this.props.tag, this.props.index - 1)); this.props.onFinished(); @@ -77,6 +84,16 @@ export default class TagTileContextMenu extends React.Component { ); } + let createSpaceOption; + if (GroupStore.isUserPrivileged(this.props.tag)) { + createSpaceOption = <> +
+ + { _t("Create Space") } + + ; + } + return
{ _t('View Community') } @@ -88,6 +105,7 @@ export default class TagTileContextMenu extends React.Component { { _t("Unpin") } + { createSpaceOption }
; } } diff --git a/src/components/views/dialogs/CreateSpaceFromCommunityDialog.tsx b/src/components/views/dialogs/CreateSpaceFromCommunityDialog.tsx new file mode 100644 index 0000000000..bef50b008f --- /dev/null +++ b/src/components/views/dialogs/CreateSpaceFromCommunityDialog.tsx @@ -0,0 +1,338 @@ +/* +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 React, { useEffect, useRef, useState } from "react"; +import { JoinRule } from "matrix-js-sdk/src/@types/partials"; +import { EventType } from "matrix-js-sdk/src/@types/event"; +import { MatrixClient } from "matrix-js-sdk/src/matrix"; + +import { _t } from '../../../languageHandler'; +import BaseDialog from "./BaseDialog"; +import AccessibleButton from "../elements/AccessibleButton"; +import { createSpace, SpaceCreateForm } from "../spaces/SpaceCreateMenu"; +import JoinRuleDropdown from "../elements/JoinRuleDropdown"; +import Field from "../elements/Field"; +import RoomAliasField from "../elements/RoomAliasField"; +import { GroupMember } from "../right_panel/UserInfo"; +import { parseMembersResponse, parseRoomsResponse } from "../../../stores/GroupStore"; +import { calculateRoomVia, makeRoomPermalink } from "../../../utils/permalinks/Permalinks"; +import { useAsyncMemo } from "../../../hooks/useAsyncMemo"; +import Spinner from "../elements/Spinner"; +import { mediaFromMxc } from "../../../customisations/Media"; +import SpaceStore from "../../../stores/SpaceStore"; +import Modal from "../../../Modal"; +import InfoDialog from "./InfoDialog"; +import dis from "../../../dispatcher/dispatcher"; +import { Action } from "../../../dispatcher/actions"; +import { UserTab } from "./UserSettingsDialog"; +import TagOrderActions from "../../../actions/TagOrderActions"; + +interface IProps { + matrixClient: MatrixClient; + groupId: string; + onFinished(spaceId?: string): void; +} + +export const CreateEventField = "io.element.migrated_from_community"; + +interface IGroupRoom { + displayname: string; + name?: string; + roomId: string; + canonicalAlias?: string; + avatarUrl?: string; + topic?: string; + numJoinedMembers?: number; + worldReadable?: boolean; + guestCanJoin?: boolean; + isPublic?: boolean; +} + +/* eslint-disable camelcase */ +export interface IGroupSummary { + profile: { + avatar_url?: string; + is_openly_joinable?: boolean; + is_public?: boolean; + long_description: string; + name: string; + short_description: string; + }; + rooms_section: { + rooms: unknown[]; + categories: Record; + total_room_count_estimate: number; + }; + user: { + is_privileged: boolean; + is_public: boolean; + is_publicised: boolean; + membership: string; + }; + users_section: { + users: unknown[]; + roles: Record; + total_user_count_estimate: number; + }; +} +/* eslint-enable camelcase */ + +const CreateSpaceFromCommunityDialog: React.FC = ({ matrixClient: cli, groupId, onFinished }) => { + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [busy, setBusy] = useState(false); + + const [avatar, setAvatar] = useState(null); // undefined means to remove avatar + const [name, setName] = useState(""); + const spaceNameField = useRef(); + const [alias, setAlias] = useState("#" + groupId.substring(1, groupId.indexOf(":")) + ":" + cli.getDomain()); + const spaceAliasField = useRef(); + const [topic, setTopic] = useState(""); + const [joinRule, setJoinRule] = useState(JoinRule.Public); + + const groupSummary = useAsyncMemo(() => cli.getGroupSummary(groupId), [groupId]); + useEffect(() => { + if (groupSummary) { + setName(groupSummary.profile.name || ""); + setTopic(groupSummary.profile.short_description || ""); + setJoinRule(groupSummary.profile.is_openly_joinable ? JoinRule.Public : JoinRule.Invite); + setLoading(false); + } + }, [groupSummary]); + + if (loading) { + return ; + } + + const onCreateSpaceClick = async (e) => { + e.preventDefault(); + if (busy) return; + + setError(null); + setBusy(true); + + // require & validate the space name field + if (!await spaceNameField.current.validate({ allowEmpty: false })) { + setBusy(false); + spaceNameField.current.focus(); + spaceNameField.current.validate({ allowEmpty: false, focused: true }); + return; + } + // validate the space name alias field but do not require it + if (joinRule === JoinRule.Public && !await spaceAliasField.current.validate({ allowEmpty: true })) { + setBusy(false); + spaceAliasField.current.focus(); + spaceAliasField.current.validate({ allowEmpty: true, focused: true }); + return; + } + + try { + const [rooms, members, invitedMembers] = await Promise.all([ + cli.getGroupRooms(groupId).then(parseRoomsResponse) as Promise, + cli.getGroupUsers(groupId).then(parseMembersResponse) as Promise, + cli.getGroupInvitedUsers(groupId).then(parseMembersResponse) as Promise, + ]); + + const viaMap = new Map(); + for (const { roomId, canonicalAlias } of rooms) { + const room = cli.getRoom(roomId); + if (room) { + viaMap.set(roomId, calculateRoomVia(room)); + } else if (canonicalAlias) { + try { + const { servers } = await cli.getRoomIdForAlias(canonicalAlias); + viaMap.set(roomId, servers); + } catch (e) { + console.warn("Failed to resolve alias during community migration", e); + } + } + + if (!viaMap.get(roomId)?.length) { + // XXX: lets guess the via, this might end up being incorrect. + const str = canonicalAlias || roomId; + viaMap.set(roomId, [str.substring(1, str.indexOf(":"))]); + } + } + + const spaceAvatar = avatar !== undefined ? avatar : groupSummary.profile.avatar_url; + const roomId = await createSpace(name, joinRule === JoinRule.Public, alias, topic, spaceAvatar, { + creation_content: { + [CreateEventField]: groupId, + }, + initial_state: rooms.map(({ roomId }) => ({ + type: EventType.SpaceChild, + state_key: roomId, + content: { + via: viaMap.get(roomId) || [], + }, + })), + invite: [...members, ...invitedMembers].map(m => m.userId).filter(m => m !== cli.getUserId()), + }, { + andView: false, + }); + + // eagerly remove it from the community panel + dis.dispatch(TagOrderActions.removeTag(cli, groupId)); + + // don't bother awaiting this, as we don't hugely care if it fails + cli.setGroupProfile(groupId, { + ...groupSummary.profile, + long_description: `

` + + _t("This community has been upgraded into a Space") + `


` + + groupSummary.profile.long_description, + } as IGroupSummary["profile"]).catch(e => { + console.warn("Failed to update community profile during migration", e); + }); + + onFinished(roomId); + + const onSpaceClick = () => { + dis.dispatch({ + action: "view_room", + room_id: roomId, + }); + }; + + const onPreferencesClick = () => { + dis.dispatch({ + action: Action.ViewUserSettings, + initialTabId: UserTab.Preferences, + }); + }; + + let spacesDisabledCopy; + if (!SpaceStore.spacesEnabled) { + spacesDisabledCopy = _t("To view Spaces, hide communities in Preferences", {}, { + a: sub => { sub }, + }); + } + + Modal.createDialog(InfoDialog, { + title: _t("Space created"), + description: <> +
+

+ { _t(" has been made and everyone who was a part of the community has " + + "been invited to it.", {}, { + SpaceName: () => + { name } + , + }) } +   + { spacesDisabledCopy } +

+

+ { _t("To create a Space from another community, just pick the community in Preferences.") } +

+ , + button: _t("Preferences"), + onFinished: (openPreferences: boolean) => { + if (openPreferences) { + onPreferencesClick(); + } + }, + }, "mx_CreateSpaceFromCommunityDialog_SuccessInfoDialog"); + } catch (e) { + console.error(e); + setError(e); + } + + setBusy(false); + }; + + let footer; + if (error) { + footer = <> + + + +
{ _t("Failed to migrate community") }
+
{ _t("Try again") }
+
+ + + { _t("Retry") } + + ; + } else { + footer = <> + onFinished()}> + { _t("Cancel") } + + + { busy ? _t("Creating...") : _t("Create Space") } + + ; + } + + return +
+

+ { _t("Spaces are the new version of communities - with new features coming.") } +   + { _t("All rooms will automatically be automatically added, a link to the Space will be " + + "added to your old community description and all community members will be invited.") } +

+

+ { _t("Flair won't be available in Spaces for the foreseeable future.") } +

+ + +

{ _t("This description will be shown to people when they view your space") }

+ +

{ joinRule === JoinRule.Public + ? _t("Open space for anyone, best for communities") + : _t("Invite only, best for yourself or teams") + }

+
+
+ +
+ { footer } +
+
; +}; + +export default CreateSpaceFromCommunityDialog; + diff --git a/src/components/views/dialogs/CreateSubspaceDialog.tsx b/src/components/views/dialogs/CreateSubspaceDialog.tsx index 0d71eb2de3..03927c7d62 100644 --- a/src/components/views/dialogs/CreateSubspaceDialog.tsx +++ b/src/components/views/dialogs/CreateSubspaceDialog.tsx @@ -16,8 +16,7 @@ limitations under the License. import React, { useRef, useState } from "react"; import { Room } from "matrix-js-sdk/src/models/room"; -import { JoinRule, Preset } from "matrix-js-sdk/src/@types/partials"; -import { RoomType } from "matrix-js-sdk/src/@types/event"; +import { JoinRule } from "matrix-js-sdk/src/@types/partials"; import { _t } from '../../../languageHandler'; import BaseDialog from "./BaseDialog"; @@ -27,8 +26,7 @@ import { BetaPill } from "../beta/BetaCard"; import Field from "../elements/Field"; import RoomAliasField from "../elements/RoomAliasField"; import SpaceStore from "../../../stores/SpaceStore"; -import { SpaceCreateForm } from "../spaces/SpaceCreateMenu"; -import createRoom from "../../../createRoom"; +import { createSpace, SpaceCreateForm } from "../spaces/SpaceCreateMenu"; import { SubspaceSelector } from "./AddExistingToSpaceDialog"; import JoinRuleDropdown from "../elements/JoinRuleDropdown"; @@ -81,28 +79,7 @@ const CreateSubspaceDialog: React.FC = ({ space, onAddExistingSpaceClick } try { - await createRoom({ - createOpts: { - preset: joinRule === JoinRule.Public ? Preset.PublicChat : Preset.PrivateChat, - name, - power_level_content_override: { - // Only allow Admins to write to the timeline to prevent hidden sync spam - events_default: 100, - ...joinRule === JoinRule.Public ? { invite: 0 } : {}, - }, - room_alias_name: joinRule === JoinRule.Public && alias - ? alias.substr(1, alias.indexOf(":") - 1) - : undefined, - topic, - }, - avatar, - roomType: RoomType.Space, - parentSpace, - spinner: false, - encryption: false, - andView: true, - inlineErrors: true, - }); + await createSpace(name, joinRule === JoinRule.Public, alias, topic, avatar, {}, { parentSpace }); onFinished(true); } catch (e) { diff --git a/src/components/views/dialogs/UserSettingsDialog.tsx b/src/components/views/dialogs/UserSettingsDialog.tsx index 7608d7cb55..9613b27d17 100644 --- a/src/components/views/dialogs/UserSettingsDialog.tsx +++ b/src/components/views/dialogs/UserSettingsDialog.tsx @@ -114,7 +114,7 @@ export default class UserSettingsDialog extends React.Component UserTab.Preferences, _td("Preferences"), "mx_UserSettingsDialog_preferencesIcon", - , + , )); if (SettingsStore.getValue(UIFeature.Voip)) { diff --git a/src/components/views/right_panel/UserInfo.tsx b/src/components/views/right_panel/UserInfo.tsx index c837e814c8..088c59d724 100644 --- a/src/components/views/right_panel/UserInfo.tsx +++ b/src/components/views/right_panel/UserInfo.tsx @@ -851,7 +851,7 @@ const RoomAdminToolsContainer: React.FC = ({ return
; }; -interface GroupMember { +export interface GroupMember { userId: string; displayname?: string; // XXX: GroupMember objects are inconsistent :(( avatarUrl?: string; diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx index d3da8a7784..1dea0a7770 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx @@ -15,7 +15,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from 'react'; +import React, { useContext, useEffect, useState } from 'react'; +import { EventType } from 'matrix-js-sdk/src/@types/event'; + import { _t } from "../../../../../languageHandler"; import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch"; import SettingsStore from "../../../../../settings/SettingsStore"; @@ -27,6 +29,18 @@ import SettingsFlag from '../../../elements/SettingsFlag'; import * as KeyboardShortcuts from "../../../../../accessibility/KeyboardShortcuts"; import AccessibleButton from "../../../elements/AccessibleButton"; import SpaceStore from "../../../../../stores/SpaceStore"; +import GroupAvatar from "../../../avatars/GroupAvatar"; +import dis from "../../../../../dispatcher/dispatcher"; +import GroupActions from "../../../../../actions/GroupActions"; +import MatrixClientContext from "../../../../../contexts/MatrixClientContext"; +import { useDispatcher } from "../../../../../hooks/useDispatcher"; +import { CreateEventField, IGroupSummary } from "../../../dialogs/CreateSpaceFromCommunityDialog"; +import { createSpaceFromCommunity } from "../../../../../utils/space"; +import Spinner from "../../../elements/Spinner"; + +interface IProps { + closeSettingsFn(success: boolean): void; +} interface IState { autoLaunch: boolean; @@ -42,8 +56,86 @@ interface IState { readMarkerOutOfViewThresholdMs: string; } +type Community = IGroupSummary & { + groupId: string; + spaceId?: string; +}; + +const CommunityMigrator = ({ onFinished }) => { + const cli = useContext(MatrixClientContext); + const [communities, setCommunities] = useState(null); + useEffect(() => { + dis.dispatch(GroupActions.fetchJoinedGroups(cli)); + }, [cli]); + useDispatcher(dis, async payload => { + if (payload.action === "GroupActions.fetchJoinedGroups.success") { + const communities: Community[] = []; + + const migratedSpaceMap = new Map(cli.getRooms().map(room => { + const createContent = room.currentState.getStateEvents(EventType.RoomCreate, "")?.getContent(); + if (createContent?.[CreateEventField]) { + return [createContent[CreateEventField], room.roomId] as [string, string]; + } + }).filter(Boolean)); + + for (const groupId of payload.result.groups) { + const summary = await cli.getGroupSummary(groupId) as IGroupSummary; + if (summary.user.is_privileged) { + communities.push({ + ...summary, + groupId, + spaceId: migratedSpaceMap.get(groupId), + }); + } + } + + setCommunities(communities); + } + }); + + if (!communities) { + return ; + } + + return
+ { communities.map(community => ( +
+ + { community.profile.name } + { + if (community.spaceId) { + dis.dispatch({ + action: "view_room", + room_id: community.spaceId, + }); + onFinished(); + } else { + createSpaceFromCommunity(cli, community.groupId).then(([spaceId]) => { + if (spaceId) { + community.spaceId = spaceId; + setCommunities([...communities]); // force component re-render + } + }); + } + }} + > + { community.spaceId ? _t("Open Space") : _t("Create Space") } + +
+ )) } +
; +}; + @replaceableComponent("views.settings.tabs.user.PreferencesUserSettingsTab") -export default class PreferencesUserSettingsTab extends React.Component<{}, IState> { +export default class PreferencesUserSettingsTab extends React.Component { static ROOM_LIST_SETTINGS = [ 'breadcrumbs', ]; @@ -52,6 +144,10 @@ export default class PreferencesUserSettingsTab extends React.Component<{}, ISta "Spaces.allRoomsInHome", ]; + static COMMUNITIES_SETTINGS = [ + // TODO: part of delabsing move the toggle here - https://github.com/vector-im/element-web/issues/18088 + ]; + static KEYBINDINGS_SETTINGS = [ 'ctrlFForSearch', ]; @@ -241,6 +337,20 @@ export default class PreferencesUserSettingsTab extends React.Component<{}, ISta { this.renderGroup(PreferencesUserSettingsTab.SPACES_SETTINGS) }
} +
+ { _t("Communities") } +

{ _t("Communities have been archived to make way for Spaces but you can convert your " + + "communities into Spaces below.") }

+ { _t("Convert your Communities to Spaces") } +

{ _t("Converting will ensure your conversations get the latest features.") }

+
+ { _t("Show my Communities") } +

{ _t("If a community isn't shown you may not have permission to convert it.") }

+ +
+ { this.renderGroup(PreferencesUserSettingsTab.COMMUNITIES_SETTINGS) } +
+
{ _t("Keyboard shortcuts") } diff --git a/src/components/views/spaces/SpaceCreateMenu.tsx b/src/components/views/spaces/SpaceCreateMenu.tsx index 406028dbc7..ef70deb672 100644 --- a/src/components/views/spaces/SpaceCreateMenu.tsx +++ b/src/components/views/spaces/SpaceCreateMenu.tsx @@ -18,23 +18,57 @@ import React, { ComponentProps, RefObject, SyntheticEvent, useContext, useRef, u import classNames from "classnames"; import { RoomType } from "matrix-js-sdk/src/@types/event"; import FocusLock from "react-focus-lock"; +import { HistoryVisibility, Preset } from "matrix-js-sdk/src/@types/partials"; +import { ICreateRoomOpts } from "matrix-js-sdk/src/@types/requests"; import { _t } from "../../../languageHandler"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import { ChevronFace, ContextMenu } from "../../structures/ContextMenu"; -import createRoom from "../../../createRoom"; +import createRoom, { IOpts as ICreateOpts } from "../../../createRoom"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import SpaceBasicSettings, { SpaceAvatar } from "./SpaceBasicSettings"; import AccessibleButton from "../elements/AccessibleButton"; import Field from "../elements/Field"; import withValidation from "../elements/Validation"; -import { HistoryVisibility, Preset } from "matrix-js-sdk/src/@types/partials"; import RoomAliasField from "../elements/RoomAliasField"; import SdkConfig from "../../../SdkConfig"; import Modal from "../../../Modal"; import GenericFeatureFeedbackDialog from "../dialogs/GenericFeatureFeedbackDialog"; import SettingsStore from "../../../settings/SettingsStore"; +export const createSpace = async ( + name: string, + isPublic: boolean, + alias?: string, + topic?: string, + avatar?: string | File, + createOpts: Partial = {}, + otherOpts: Partial> = {}, +) => { + return createRoom({ + createOpts: { + name, + preset: isPublic ? Preset.PublicChat : Preset.PrivateChat, + power_level_content_override: { + // Only allow Admins to write to the timeline to prevent hidden sync spam + events_default: 100, + ...isPublic ? { invite: 0 } : {}, + }, + room_alias_name: isPublic && alias ? alias.substr(1, alias.indexOf(":") - 1) : undefined, + topic, + ...createOpts, + }, + avatar, + roomType: RoomType.Space, + historyVisibility: isPublic ? HistoryVisibility.WorldReadable : HistoryVisibility.Invited, + spinner: false, + encryption: false, + andView: true, + inlineErrors: true, + ...otherOpts, + }); +}; + const SpaceCreateMenuType = ({ title, description, className, onClick }) => { return ( @@ -92,7 +126,7 @@ export const SpaceFeedbackPrompt = ({ onClick }: { onClick?: () => void }) => {
; }; -type BProps = Pick, "setAvatar" | "name" | "setName" | "topic" | "setTopic">; +type BProps = Omit, "nameDisabled" | "topicDisabled" | "avatarDisabled">; interface ISpaceCreateFormProps extends BProps { busy: boolean; alias: string; @@ -106,6 +140,7 @@ interface ISpaceCreateFormProps extends BProps { export const SpaceCreateForm: React.FC = ({ busy, onSubmit, + avatarUrl, setAvatar, name, setName, @@ -122,7 +157,7 @@ export const SpaceCreateForm: React.FC = ({ const domain = cli.getDomain(); return
- + { } try { - await createRoom({ - createOpts: { - preset: visibility === Visibility.Public ? Preset.PublicChat : Preset.PrivateChat, - name, - power_level_content_override: { - // Only allow Admins to write to the timeline to prevent hidden sync spam - events_default: 100, - ...visibility === Visibility.Public ? { invite: 0 } : {}, - }, - room_alias_name: visibility === Visibility.Public && alias - ? alias.substr(1, alias.indexOf(":") - 1) - : undefined, - topic, - }, - avatar, - roomType: RoomType.Space, - historyVisibility: visibility === Visibility.Public - ? HistoryVisibility.WorldReadable - : HistoryVisibility.Invited, - spinner: false, - encryption: false, - andView: true, - inlineErrors: true, - }); + await createSpace(name, visibility === Visibility.Public, alias, topic, avatar); onFinished(); } catch (e) { diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 3ad8daa85c..850d6d18ed 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1322,12 +1322,20 @@ "If this isn't what you want, please use a different tool to ignore users.": "If this isn't what you want, please use a different tool to ignore users.", "Room ID or address of ban list": "Room ID or address of ban list", "Subscribe": "Subscribe", + "Open Space": "Open Space", + "Create Space": "Create Space", "Start automatically after system login": "Start automatically after system login", "Warn before quitting": "Warn before quitting", "Always show the window menu bar": "Always show the window menu bar", "Show tray icon and minimize window to it on close": "Show tray icon and minimize window to it on close", "Preferences": "Preferences", "Room list": "Room list", + "Communities": "Communities", + "Communities have been archived to make way for Spaces but you can convert your communities into Spaces below.": "Communities have been archived to make way for Spaces but you can convert your communities into Spaces below.", + "Convert your Communities to Spaces": "Convert your Communities to Spaces", + "Converting will ensure your conversations get the latest features.": "Converting will ensure your conversations get the latest features.", + "Show my Communities": "Show my Communities", + "If a community isn't shown you may not have permission to convert it.": "If a community isn't shown you may not have permission to convert it.", "Keyboard shortcuts": "Keyboard shortcuts", "To view all keyboard shortcuts, click here.": "To view all keyboard shortcuts, click here.", "Displaying time": "Displaying time", @@ -2218,13 +2226,24 @@ "Visible to space members": "Visible to space members", "Block anyone not part of %(serverName)s from ever joining this room.": "Block anyone not part of %(serverName)s from ever joining this room.", "Create Room": "Create Room", + "This community has been upgraded into a Space": "This community has been upgraded into a Space", + "To view Spaces, hide communities in Preferences": "To view Spaces, hide communities in Preferences", + "Space created": "Space created", + " has been made and everyone who was a part of the community has been invited to it.": " has been made and everyone who was a part of the community has been invited to it.", + "To create a Space from another community, just pick the community in Preferences.": "To create a Space from another community, just pick the community in Preferences.", + "Failed to migrate community": "Failed to migrate community", + "Create Space from community": "Create Space from community", + "Spaces are the new version of communities - with new features coming.": "Spaces are the new version of communities - with new features coming.", + "All rooms will automatically be automatically added, a link to the Space will be added to your old community description and all community members will be invited.": "All rooms will automatically be automatically added, a link to the Space will be added to your old community description and all community members will be invited.", + "Flair won't be available in Spaces for the foreseeable future.": "Flair won't be available in Spaces for the foreseeable future.", + "This description will be shown to people when they view your space": "This description will be shown to people when they view your space", + "Space visibility": "Space visibility", + "Private space (invite only)": "Private space (invite only)", + "Public space": "Public space", "Anyone in will be able to find and join.": "Anyone in will be able to find and join.", "Anyone will be able to find and join this space, not just members of .": "Anyone will be able to find and join this space, not just members of .", "Only people invited will be able to find and join this space.": "Only people invited will be able to find and join this space.", "Add a space to a space you manage.": "Add a space to a space you manage.", - "Space visibility": "Space visibility", - "Private space (invite only)": "Private space (invite only)", - "Public space": "Public space", "Want to add an existing space instead?": "Want to add an existing space instead?", "Adding...": "Adding...", "Sign out": "Sign out", @@ -2676,7 +2695,6 @@ "You must join the room to see its files": "You must join the room to see its files", "No files visible in this room": "No files visible in this room", "Attach files from chat or just drag and drop them anywhere in a room.": "Attach files from chat or just drag and drop them anywhere in a room.", - "Communities": "Communities", "Create community": "Create community", "

HTML for your community's page

\n

\n Use the long description to introduce new members to the community, or distribute\n some important links\n

\n

\n You can even add images with Matrix URLs \n

\n": "

HTML for your community's page

\n

\n Use the long description to introduce new members to the community, or distribute\n some important links\n

\n

\n You can even add images with Matrix URLs \n

\n", "Add rooms to the community summary": "Add rooms to the community summary", @@ -2704,6 +2722,10 @@ "Community Settings": "Community Settings", "Want more than a community? Get your own server": "Want more than a community? Get your own server", "Changes made to your community name and avatar might not be seen by other users for up to 30 minutes.": "Changes made to your community name and avatar might not be seen by other users for up to 30 minutes.", + "Communities can now be made into Spaces": "Communities can now be made into Spaces", + "Spaces are a new way to make a community, with new features coming.": "Spaces are a new way to make a community, with new features coming.", + "Ask the admins of this community to make it into a Space and keep a look out for the invite.": "Ask the admins of this community to make it into a Space and keep a look out for the invite.", + "Communities won't receive further updates.": "Communities won't receive further updates.", "These rooms are displayed to community members on the community page. Community members can join the rooms by clicking on them.": "These rooms are displayed to community members on the community page. Community members can join the rooms by clicking on them.", "Featured Rooms:": "Featured Rooms:", "Featured Users:": "Featured Users:", @@ -2835,6 +2857,7 @@ "To view %(spaceName)s, turn on the Spaces beta": "To view %(spaceName)s, turn on the Spaces beta", "To join %(spaceName)s, turn on the Spaces beta": "To join %(spaceName)s, turn on the Spaces beta", "To view %(spaceName)s, you need an invite": "To view %(spaceName)s, you need an invite", + "Created from ": "Created from ", "Welcome to ": "Welcome to ", "Random": "Random", "Support": "Support", diff --git a/src/stores/GroupFilterOrderStore.js b/src/stores/GroupFilterOrderStore.js index e81d1b81f7..821fbefc4f 100644 --- a/src/stores/GroupFilterOrderStore.js +++ b/src/stores/GroupFilterOrderStore.js @@ -14,12 +14,14 @@ See the License for the specific language governing permissions and limitations under the License. */ import { Store } from 'flux/utils'; +import { EventType } from "matrix-js-sdk/src/@types/event"; import dis from '../dispatcher/dispatcher'; import GroupStore from './GroupStore'; import Analytics from '../Analytics'; import * as RoomNotifs from "../RoomNotifs"; import { MatrixClientPeg } from '../MatrixClientPeg'; import SettingsStore from "../settings/SettingsStore"; +import { CreateEventField } from "../components/views/dialogs/CreateSpaceFromCommunityDialog"; const INITIAL_STATE = { orderedTags: null, @@ -235,8 +237,12 @@ class GroupFilterOrderStore extends Store { (t) => (t[0] !== '+' || groupIds.includes(t)) && !removedTags.has(t), ); + const cli = MatrixClientPeg.get(); + const migratedCommunities = new Set(cli.getRooms().map(r => { + return r.currentState.getStateEvents(EventType.RoomCreate, "")?.getContent()[CreateEventField]; + }).filter(Boolean)); const groupIdsToAdd = groupIds.filter( - (groupId) => !tags.includes(groupId) && !removedTags.has(groupId), + (groupId) => !tags.includes(groupId) && !removedTags.has(groupId) && !migratedCommunities.has(groupId), ); return tagsToKeep.concat(groupIdsToAdd); diff --git a/src/stores/GroupStore.js b/src/stores/GroupStore.js index f1122cb945..63972b31fb 100644 --- a/src/stores/GroupStore.js +++ b/src/stores/GroupStore.js @@ -20,11 +20,11 @@ import FlairStore from './FlairStore'; import { MatrixClientPeg } from '../MatrixClientPeg'; import dis from '../dispatcher/dispatcher'; -function parseMembersResponse(response) { +export function parseMembersResponse(response) { return response.chunk.map((apiMember) => groupMemberFromApiObject(apiMember)); } -function parseRoomsResponse(response) { +export function parseRoomsResponse(response) { return response.chunk.map((apiRoom) => groupRoomFromApiObject(apiRoom)); } diff --git a/src/utils/space.tsx b/src/utils/space.tsx index fecb581e65..c1d8dbfbea 100644 --- a/src/utils/space.tsx +++ b/src/utils/space.tsx @@ -17,6 +17,7 @@ limitations under the License. import React from "react"; import { Room } from "matrix-js-sdk/src/models/room"; import { EventType } from "matrix-js-sdk/src/@types/event"; +import { MatrixClient } from "matrix-js-sdk/src/client"; import { calculateRoomVia } from "./permalinks/Permalinks"; import Modal from "../Modal"; @@ -37,6 +38,7 @@ import { leaveRoomBehaviour } from "./membership"; import Spinner from "../components/views/elements/Spinner"; import dis from "../dispatcher/dispatcher"; import LeaveSpaceDialog from "../components/views/dialogs/LeaveSpaceDialog"; +import CreateSpaceFromCommunityDialog from "../components/views/dialogs/CreateSpaceFromCommunityDialog"; export const shouldShowSpaceSettings = (space: Room) => { const userId = space.client.getUserId(); @@ -173,3 +175,10 @@ export const leaveSpace = (space: Room) => { }, }, "mx_LeaveSpaceDialog_wrapper"); }; + +export const createSpaceFromCommunity = (cli: MatrixClient, groupId: string): Promise<[string?]> => { + return Modal.createTrackedDialog('Create Space', 'from community', CreateSpaceFromCommunityDialog, { + matrixClient: cli, + groupId, + }, "mx_CreateSpaceFromCommunityDialog_wrapper").finished as Promise<[string?]>; +}; From 9e4f5719a4e2909e80f5e5774aedb94f790ec8c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 5 Aug 2021 11:47:58 +0200 Subject: [PATCH 11/85] Handle narrow layouts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/messages/_CallEvent.scss | 266 +++++++++++--------- src/components/views/messages/CallEvent.tsx | 95 ++++--- 2 files changed, 218 insertions(+), 143 deletions(-) diff --git a/res/css/views/messages/_CallEvent.scss b/res/css/views/messages/_CallEvent.scss index 0c1b41ca38..5aaaa292d1 100644 --- a/res/css/views/messages/_CallEvent.scss +++ b/res/css/views/messages/_CallEvent.scss @@ -14,126 +14,23 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_CallEvent { +.mx_CallEvent_wrapper { display: flex; - flex-direction: row; - align-items: center; - justify-content: space-between; + justify-content: center; + width: 100%; - background-color: $dark-panel-bg-color; - border-radius: 8px; - margin: 10px auto; - width: 75%; - box-sizing: border-box; - height: 60px; - - &.mx_CallEvent_voice { - .mx_CallEvent_type_icon::before, - .mx_CallEvent_content_button_callBack span::before, - .mx_CallEvent_content_button_answer span::before { - mask-image: url('$(res)/img/element-icons/call/voice-call.svg'); - } - } - - &.mx_CallEvent_video { - .mx_CallEvent_type_icon::before, - .mx_CallEvent_content_button_callBack span::before, - .mx_CallEvent_content_button_answer span::before { - mask-image: url('$(res)/img/element-icons/call/video-call.svg'); - } - } - - &.mx_CallEvent_voice.mx_CallEvent_missed .mx_CallEvent_type_icon::before { - mask-image: url('$(res)/img/voip/missed-voice.svg'); - } - - &.mx_CallEvent_video.mx_CallEvent_missed .mx_CallEvent_type_icon::before { - mask-image: url('$(res)/img/voip/missed-video.svg'); - } - - .mx_CallEvent_info { + .mx_CallEvent { + position: relative; display: flex; flex-direction: row; align-items: center; - margin-left: 12px; + justify-content: space-between; - .mx_CallEvent_info_basic { - display: flex; - flex-direction: column; - margin-left: 10px; // To match mx_CallEvent - - .mx_CallEvent_sender { - font-weight: 600; - font-size: 1.5rem; - line-height: 1.8rem; - margin-bottom: 3px; - } - - .mx_CallEvent_type { - font-weight: 400; - color: $secondary-fg-color; - font-size: 1.2rem; - line-height: $font-13px; - display: flex; - align-items: center; - - .mx_CallEvent_type_icon { - height: 13px; - width: 13px; - margin-right: 5px; - - &::before { - content: ''; - position: absolute; - height: 13px; - width: 13px; - background-color: $tertiary-fg-color; - mask-repeat: no-repeat; - mask-size: contain; - } - } - } - } - } - - .mx_CallEvent_content { - display: flex; - flex-direction: row; - align-items: center; - color: $secondary-fg-color; - margin-right: 16px; - - .mx_CallEvent_content_button { - height: 24px; - padding: 0px 12px; - margin-left: 8px; - - 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_CallEvent_content_button_reject span::before { - mask-image: url('$(res)/img/element-icons/call/hangup.svg'); - } - - .mx_CallEvent_content_tooltip { - margin-right: 5px; - } + background-color: $dark-panel-bg-color; + border-radius: 8px; + width: 75%; + box-sizing: border-box; + height: 60px; .mx_CallEvent_iconButton { display: inline-flex; @@ -158,5 +55,146 @@ limitations under the License. .mx_CallEvent_unSilence::before { mask-image: url('$(res)/img/voip/un-silence.svg'); } + + &.mx_CallEvent_voice { + .mx_CallEvent_type_icon::before, + .mx_CallEvent_content_button_callBack span::before, + .mx_CallEvent_content_button_answer span::before { + mask-image: url('$(res)/img/element-icons/call/voice-call.svg'); + } + } + + &.mx_CallEvent_video { + .mx_CallEvent_type_icon::before, + .mx_CallEvent_content_button_callBack span::before, + .mx_CallEvent_content_button_answer span::before { + mask-image: url('$(res)/img/element-icons/call/video-call.svg'); + } + } + + &.mx_CallEvent_voice.mx_CallEvent_missed .mx_CallEvent_type_icon::before { + mask-image: url('$(res)/img/voip/missed-voice.svg'); + } + + &.mx_CallEvent_video.mx_CallEvent_missed .mx_CallEvent_type_icon::before { + mask-image: url('$(res)/img/voip/missed-video.svg'); + } + + .mx_CallEvent_info { + display: flex; + flex-direction: row; + align-items: center; + margin-left: 12px; + + .mx_CallEvent_info_basic { + display: flex; + flex-direction: column; + margin-left: 10px; // To match mx_CallEvent + + .mx_CallEvent_sender { + font-weight: 600; + font-size: 1.5rem; + line-height: 1.8rem; + margin-bottom: 3px; + } + + .mx_CallEvent_type { + font-weight: 400; + color: $secondary-fg-color; + font-size: 1.2rem; + line-height: $font-13px; + display: flex; + align-items: center; + + .mx_CallEvent_type_icon { + height: 13px; + width: 13px; + margin-right: 5px; + + &::before { + content: ''; + position: absolute; + height: 13px; + width: 13px; + background-color: $tertiary-fg-color; + mask-repeat: no-repeat; + mask-size: contain; + } + } + } + } + } + + .mx_CallEvent_content { + display: flex; + flex-direction: row; + align-items: center; + color: $secondary-fg-color; + margin-right: 16px; + + .mx_CallEvent_content_button { + height: 24px; + padding: 0px 12px; + margin-left: 8px; + + 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_CallEvent_content_button_reject span::before { + mask-image: url('$(res)/img/element-icons/call/hangup.svg'); + } + + .mx_CallEvent_content_tooltip { + margin-right: 5px; + } + } + + &.mx_CallEvent_narrow { + height: unset; + width: 290px; + flex-direction: column; + align-items: unset; + gap: 16px; + + .mx_CallEvent_iconButton { + position: absolute; + margin-right: 0; + top: 12px; + right: 12px; + height: 16px; + width: 16px; + display: flex; + } + + .mx_CallEvent_info { + margin-top: 12px; + margin-right: 12px; + + .mx_CallEvent_sender { + margin-bottom: 8px; + } + } + + .mx_CallEvent_content { + margin-left: 54px; // mx_CallEvent margin (12px) + avatar (32px) + mx_CallEvent_info_basic margin (10px) + margin-bottom: 16px; + } + } } } diff --git a/src/components/views/messages/CallEvent.tsx b/src/components/views/messages/CallEvent.tsx index a204907caa..822d99d5a6 100644 --- a/src/components/views/messages/CallEvent.tsx +++ b/src/components/views/messages/CallEvent.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from 'react'; +import React, { createRef } from 'react'; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { _t, _td } from '../../../languageHandler'; @@ -26,6 +26,8 @@ import InfoTooltip, { InfoTooltipKind } from '../elements/InfoTooltip'; import classNames from 'classnames'; import AccessibleTooltipButton from '../elements/AccessibleTooltipButton'; +const MAX_NON_NARROW_WIDTH = 400 / 70 * 100; + interface IProps { mxEvent: MatrixEvent; callEventGrouper: CallEventGrouper; @@ -34,6 +36,7 @@ interface IProps { interface IState { callState: CallState | CustomCallState; silenced: boolean; + narrow: boolean; } const TEXTUAL_STATES: Map = new Map([ @@ -41,26 +44,42 @@ const TEXTUAL_STATES: Map = new Map([ [CallState.Connecting, _td("Connecting")], ]); -export default class CallEvent extends React.Component { +export default class CallEvent extends React.PureComponent { + private wrapperElement = createRef(); + private resizeObserver: ResizeObserver; + constructor(props: IProps) { super(props); this.state = { callState: this.props.callEventGrouper.state, silenced: false, + narrow: false, }; } componentDidMount() { this.props.callEventGrouper.addListener(CallEventGrouperEvent.StateChanged, this.onStateChanged); this.props.callEventGrouper.addListener(CallEventGrouperEvent.SilencedChanged, this.onSilencedChanged); + + this.resizeObserver = new ResizeObserver(this.resizeObserverCallback); + this.resizeObserver.observe(this.wrapperElement.current); } componentWillUnmount() { this.props.callEventGrouper.removeListener(CallEventGrouperEvent.StateChanged, this.onStateChanged); this.props.callEventGrouper.removeListener(CallEventGrouperEvent.SilencedChanged, this.onSilencedChanged); + + this.resizeObserver.disconnect(); } + private resizeObserverCallback = (entries: ResizeObserverEntry[]): void => { + const wrapperElementEntry = entries.find((entry) => entry.target === this.wrapperElement.current); + if (!wrapperElementEntry) return; + + this.setState({ narrow: wrapperElementEntry.contentRect.width < MAX_NON_NARROW_WIDTH }); + }; + private onSilencedChanged = (newState) => { this.setState({ silenced: newState }); }; @@ -81,21 +100,32 @@ export default class CallEvent extends React.Component { ); } + private renderSilenceIcon(): JSX.Element { + const silenceClass = classNames({ + "mx_CallEvent_iconButton": true, + "mx_CallEvent_unSilence": this.state.silenced, + "mx_CallEvent_silence": !this.state.silenced, + }); + + return ( + + ); + } + private renderContent(state: CallState | CustomCallState): JSX.Element { if (state === CallState.Ringing) { - const silenceClass = classNames({ - "mx_CallEvent_iconButton": true, - "mx_CallEvent_unSilence": this.state.silenced, - "mx_CallEvent_silence": !this.state.silenced, - }); + let silenceIcon; + if (!this.state.narrow) { + silenceIcon = this.renderSilenceIcon(); + } return (
- + { silenceIcon } { const callState = this.state.callState; const hangupReason = this.props.callEventGrouper.hangupReason; const content = this.renderContent(callState); - const className = classNames({ - mx_CallEvent: true, + const className = classNames("mx_CallEvent", { mx_CallEvent_voice: isVoice, mx_CallEvent_video: !isVoice, + mx_CallEvent_narrow: this.state.narrow, mx_CallEvent_missed: ( callState === CustomCallState.Missed || (callState === CallState.Ended && hangupReason === CallErrorCode.InviteTimeout) ), }); + let silenceIcon; + if (this.state.narrow && this.state.callState === CallState.Ringing) { + silenceIcon = this.renderSilenceIcon(); + } return ( -
-
- -
-
- { sender } -
-
-
- { callType } +
+
+ { silenceIcon } +
+ +
+
+ { sender } +
+
+
+ { callType } +
+ { content }
- { content }
); } From 5fa6ef83d207c7d1abb39780ecb510d3b95104d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 5 Aug 2021 12:35:44 +0200 Subject: [PATCH 12/85] Fix button alighnment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/messages/_CallEvent.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/views/messages/_CallEvent.scss b/res/css/views/messages/_CallEvent.scss index 5aaaa292d1..22942fdd2c 100644 --- a/res/css/views/messages/_CallEvent.scss +++ b/res/css/views/messages/_CallEvent.scss @@ -131,11 +131,11 @@ limitations under the License. align-items: center; color: $secondary-fg-color; margin-right: 16px; + gap: 8px; .mx_CallEvent_content_button { height: 24px; padding: 0px 12px; - margin-left: 8px; span { padding: 8px 0; From efeae3a84cfafb26790ee40d8a12cde2dadc8188 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 5 Aug 2021 13:39:39 +0200 Subject: [PATCH 13/85] Top align the avatar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/messages/_CallEvent.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/res/css/views/messages/_CallEvent.scss b/res/css/views/messages/_CallEvent.scss index 22942fdd2c..dde5856484 100644 --- a/res/css/views/messages/_CallEvent.scss +++ b/res/css/views/messages/_CallEvent.scss @@ -183,6 +183,7 @@ limitations under the License. } .mx_CallEvent_info { + align-items: unset; margin-top: 12px; margin-right: 12px; From f7818e01692229db352b1eaabbe147e19b5cadff Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 6 Aug 2021 12:05:14 +0100 Subject: [PATCH 14/85] iterate PR based on feedback --- res/css/views/dialogs/_CreateSpaceFromCommunityDialog.scss | 7 +++++-- res/css/views/elements/_Field.scss | 1 + .../views/dialogs/CreateSpaceFromCommunityDialog.tsx | 2 +- src/i18n/strings/en_EN.json | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/res/css/views/dialogs/_CreateSpaceFromCommunityDialog.scss b/res/css/views/dialogs/_CreateSpaceFromCommunityDialog.scss index 059d38c77c..b011fb104a 100644 --- a/res/css/views/dialogs/_CreateSpaceFromCommunityDialog.scss +++ b/res/css/views/dialogs/_CreateSpaceFromCommunityDialog.scss @@ -33,7 +33,10 @@ limitations under the License. > p { font-size: $font-15px; line-height: $font-24px; - color: $secondary-fg-color; + + &:first-of-type { + margin-top: 0; + } &.mx_CreateSpaceFromCommunityDialog_flairNotice { font-size: $font-12px; @@ -45,7 +48,7 @@ limitations under the License. > p { font-size: $font-12px; line-height: $font-15px; - margin: 8px 0 16px; + margin: 16px 0; } } diff --git a/res/css/views/elements/_Field.scss b/res/css/views/elements/_Field.scss index cae81dcc97..50cd14c4da 100644 --- a/res/css/views/elements/_Field.scss +++ b/res/css/views/elements/_Field.scss @@ -38,6 +38,7 @@ limitations under the License. .mx_Field input, .mx_Field select, .mx_Field textarea { + font-family: inherit; font-weight: normal; font-size: $font-14px; border: none; diff --git a/src/components/views/dialogs/CreateSpaceFromCommunityDialog.tsx b/src/components/views/dialogs/CreateSpaceFromCommunityDialog.tsx index bef50b008f..335be80a70 100644 --- a/src/components/views/dialogs/CreateSpaceFromCommunityDialog.tsx +++ b/src/components/views/dialogs/CreateSpaceFromCommunityDialog.tsx @@ -288,7 +288,7 @@ const CreateSpaceFromCommunityDialog: React.FC = ({ matrixClient: cli, g

{ _t("Spaces are the new version of communities - with new features coming.") }   - { _t("All rooms will automatically be automatically added, a link to the Space will be " + + { _t("All rooms will automatically be added, a link to the Space will be " + "added to your old community description and all community members will be invited.") }

diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index f3c5156e66..e0b458dd8e 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2235,7 +2235,7 @@ "Failed to migrate community": "Failed to migrate community", "Create Space from community": "Create Space from community", "Spaces are the new version of communities - with new features coming.": "Spaces are the new version of communities - with new features coming.", - "All rooms will automatically be automatically added, a link to the Space will be added to your old community description and all community members will be invited.": "All rooms will automatically be automatically added, a link to the Space will be added to your old community description and all community members will be invited.", + "All rooms will automatically be added, a link to the Space will be added to your old community description and all community members will be invited.": "All rooms will automatically be added, a link to the Space will be added to your old community description and all community members will be invited.", "Flair won't be available in Spaces for the foreseeable future.": "Flair won't be available in Spaces for the foreseeable future.", "This description will be shown to people when they view your space": "This description will be shown to people when they view your space", "Space visibility": "Space visibility", From d285a45da5a561cfbddb14a6380b1d77c0aab7e4 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 6 Aug 2021 12:28:06 +0100 Subject: [PATCH 15/85] Fix stray tabIndex on AutoHideScrollbar component in FF --- src/components/structures/AutoHideScrollbar.tsx | 4 +++- src/components/structures/LeftPanel.tsx | 3 --- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/components/structures/AutoHideScrollbar.tsx b/src/components/structures/AutoHideScrollbar.tsx index 184d883dda..a60df45770 100644 --- a/src/components/structures/AutoHideScrollbar.tsx +++ b/src/components/structures/AutoHideScrollbar.tsx @@ -61,7 +61,9 @@ export default class AutoHideScrollbar extends React.Component { style={style} className={["mx_AutoHideScrollbar", className].join(" ")} onWheel={onWheel} - tabIndex={tabIndex} + // Firefox sometimes makes this element focusable due to + // overflow:scroll;, so force it out of tab order by default. + tabIndex={tabIndex ?? -1} > { children }

); diff --git a/src/components/structures/LeftPanel.tsx b/src/components/structures/LeftPanel.tsx index 3bd2c68c6c..ff5d15d44d 100644 --- a/src/components/structures/LeftPanel.tsx +++ b/src/components/structures/LeftPanel.tsx @@ -392,9 +392,6 @@ export default class LeftPanel extends React.Component { From 38953452506c1fd8fdc65d130478cdef738f3846 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 6 Aug 2021 12:28:20 +0100 Subject: [PATCH 16/85] Fix disabled state on AccessibleButton not being exposed via ARIA --- src/components/views/elements/AccessibleButton.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/views/elements/AccessibleButton.tsx b/src/components/views/elements/AccessibleButton.tsx index 8bb6341c3d..0ce9a3a030 100644 --- a/src/components/views/elements/AccessibleButton.tsx +++ b/src/components/views/elements/AccessibleButton.tsx @@ -67,7 +67,9 @@ export default function AccessibleButton({ ...restProps }: IProps) { const newProps: IAccessibleButtonProps = restProps; - if (!disabled) { + if (disabled) { + newProps["aria-disabled"] = true; + } else { newProps.onClick = onClick; // We need to consume enter onKeyDown and space onKeyUp // otherwise we are risking also activating other keyboard focusable elements @@ -118,7 +120,7 @@ export default function AccessibleButton({ ); // React.createElement expects InputHTMLAttributes - return React.createElement(element, restProps, children); + return React.createElement(element, newProps, children); } AccessibleButton.defaultProps = { From 1a1b1738c127bc24e4f8e7036fd0d8e3d6921c48 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 6 Aug 2021 12:28:46 +0100 Subject: [PATCH 17/85] Add aria label to clickable notification badge on space panel --- src/components/views/spaces/SpaceTreeLevel.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/components/views/spaces/SpaceTreeLevel.tsx b/src/components/views/spaces/SpaceTreeLevel.tsx index bb2184853e..0862458d9e 100644 --- a/src/components/views/spaces/SpaceTreeLevel.tsx +++ b/src/components/views/spaces/SpaceTreeLevel.tsx @@ -77,11 +77,17 @@ export const SpaceButton: React.FC = ({ let notifBadge; if (notificationState) { + let ariaLabel = _t("Jump to first unread room."); + if (space.getMyMembership() === "invite") { + ariaLabel = _t("Jump to first invite."); + } + notifBadge =
SpaceStore.instance.setActiveRoomInSpace(space || null)} forceCount={false} notification={notificationState} + aria-label={ariaLabel} />
; } From facc882a1110b002aae4349b966881e86da1ab28 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 6 Aug 2021 12:50:32 +0100 Subject: [PATCH 18/85] i18n and add space null guard for home space --- src/components/views/spaces/SpaceTreeLevel.tsx | 2 +- src/i18n/strings/en_EN.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/views/spaces/SpaceTreeLevel.tsx b/src/components/views/spaces/SpaceTreeLevel.tsx index 0862458d9e..5d2f58fab2 100644 --- a/src/components/views/spaces/SpaceTreeLevel.tsx +++ b/src/components/views/spaces/SpaceTreeLevel.tsx @@ -78,7 +78,7 @@ export const SpaceButton: React.FC = ({ let notifBadge; if (notificationState) { let ariaLabel = _t("Jump to first unread room."); - if (space.getMyMembership() === "invite") { + if (space?.getMyMembership() === "invite") { ariaLabel = _t("Jump to first invite."); } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 9f30776f44..09cb877d33 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1072,6 +1072,8 @@ "Preview Space": "Preview Space", "Allow people to preview your space before they join.": "Allow people to preview your space before they join.", "Recommended for public spaces.": "Recommended for public spaces.", + "Jump to first unread room.": "Jump to first unread room.", + "Jump to first invite.": "Jump to first invite.", "Expand": "Expand", "Collapse": "Collapse", "Space options": "Space options", @@ -1665,8 +1667,6 @@ "Activity": "Activity", "A-Z": "A-Z", "List options": "List options", - "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", From 41475a3b948d99cb1955c4e9e4e2bf960fc879f3 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 6 Aug 2021 14:12:55 +0100 Subject: [PATCH 19/85] fix keyboard focus issue around the space hierarchy --- res/css/structures/_SpaceRoomDirectory.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/structures/_SpaceRoomDirectory.scss b/res/css/structures/_SpaceRoomDirectory.scss index cb91aa3c7d..03ff1d5ae5 100644 --- a/res/css/structures/_SpaceRoomDirectory.scss +++ b/res/css/structures/_SpaceRoomDirectory.scss @@ -269,7 +269,7 @@ limitations under the License. } } - &:hover { + &:hover, &:focus-within { background-color: $groupFilterPanel-bg-color; .mx_AccessibleButton { From 54fb24f359bdb9fd7e2f2f76871a9ac52d8f9df2 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 6 Aug 2021 14:21:41 +0100 Subject: [PATCH 20/85] Fix dropdown keyboard accessibility when filter is disabled --- src/components/views/elements/Dropdown.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/views/elements/Dropdown.tsx b/src/components/views/elements/Dropdown.tsx index dddcceb97c..1f3ceabb83 100644 --- a/src/components/views/elements/Dropdown.tsx +++ b/src/components/views/elements/Dropdown.tsx @@ -204,7 +204,7 @@ export default class Dropdown extends React.Component { this.props.onOptionChange(dropdownKey); }; - private onInputKeyDown = (e: React.KeyboardEvent) => { + private onKeyDown = (e: React.KeyboardEvent) => { let handled = true; // These keys don't generate keypress events and so needs to be on keyup @@ -320,7 +320,6 @@ export default class Dropdown extends React.Component { type="text" autoFocus={true} className="mx_Dropdown_option" - onKeyDown={this.onInputKeyDown} onChange={this.onInputChange} value={this.state.searchQuery} role="combobox" @@ -358,7 +357,7 @@ export default class Dropdown extends React.Component { // Note the menu sits inside the AccessibleButton div so it's anchored // to the input, but overflows below it. The root contains both. - return
+ return
Date: Fri, 6 Aug 2021 14:21:56 +0100 Subject: [PATCH 21/85] Fix dropdown negative wraparound for keyboard accessibility --- src/components/views/elements/Dropdown.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/elements/Dropdown.tsx b/src/components/views/elements/Dropdown.tsx index 1f3ceabb83..7ad27bc93b 100644 --- a/src/components/views/elements/Dropdown.tsx +++ b/src/components/views/elements/Dropdown.tsx @@ -269,7 +269,7 @@ export default class Dropdown extends React.Component { private prevOption(optionKey: string): string { const keys = Object.keys(this.childrenByKey); const index = keys.indexOf(optionKey); - return keys[(index - 1) % keys.length]; + return keys[index <= 0 ? keys.length - 1 : (index - 1) % keys.length]; } private scrollIntoView(node: Element) { From 3fd2c005161c005827ea479c83a85a0f12442346 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 6 Aug 2021 14:46:02 +0100 Subject: [PATCH 22/85] Improve aria labels around spaces avatar uploader --- src/components/views/spaces/SpaceBasicSettings.tsx | 7 ++++++- src/i18n/strings/en_EN.json | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/components/views/spaces/SpaceBasicSettings.tsx b/src/components/views/spaces/SpaceBasicSettings.tsx index 9d3696c5a9..4f305edd8b 100644 --- a/src/components/views/spaces/SpaceBasicSettings.tsx +++ b/src/components/views/spaces/SpaceBasicSettings.tsx @@ -65,6 +65,7 @@ export const SpaceAvatar = ({ }} kind="link" className="mx_SpaceBasicSettings_avatar_remove" + aria-label={_t("Delete avatar")} > { _t("Delete") } @@ -72,7 +73,11 @@ export const SpaceAvatar = ({ } else { avatarSection =
avatarUploadRef.current?.click()} /> - avatarUploadRef.current?.click()} kind="link"> + avatarUploadRef.current?.click()} + kind="link" + aria-label={_t("Upload avatar")} + > { _t("Upload") } ; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 09cb877d33..3aab3cb68c 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1014,7 +1014,9 @@ "Your server isn't responding to some requests.": "Your server isn't responding to some requests.", "Decline (%(counter)s)": "Decline (%(counter)s)", "Accept to continue:": "Accept to continue:", + "Delete avatar": "Delete avatar", "Delete": "Delete", + "Upload avatar": "Upload avatar", "Upload": "Upload", "Name": "Name", "Description": "Description", @@ -2718,7 +2720,6 @@ "Everyone": "Everyone", "Your community hasn't got a Long Description, a HTML page to show to community members.
Click here to open settings and give it one!": "Your community hasn't got a Long Description, a HTML page to show to community members.
Click here to open settings and give it one!", "Long Description (HTML)": "Long Description (HTML)", - "Upload avatar": "Upload avatar", "Community %(groupId)s not found": "Community %(groupId)s not found", "This homeserver does not support communities": "This homeserver does not support communities", "Failed to load %(groupId)s": "Failed to load %(groupId)s", From 6fddfe0d59da12421d873312788a3349d44574d9 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 6 Aug 2021 14:48:46 +0100 Subject: [PATCH 23/85] Fix dropdown keyboard selection accessibility --- src/components/views/elements/Dropdown.tsx | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/components/views/elements/Dropdown.tsx b/src/components/views/elements/Dropdown.tsx index 7ad27bc93b..b4f382c9c3 100644 --- a/src/components/views/elements/Dropdown.tsx +++ b/src/components/views/elements/Dropdown.tsx @@ -18,7 +18,7 @@ limitations under the License. import React, { ChangeEvent, createRef, CSSProperties, ReactElement, ReactNode, Ref } from 'react'; import classnames from 'classnames'; -import AccessibleButton from './AccessibleButton'; +import AccessibleButton, { ButtonEvent } from './AccessibleButton'; import { _t } from '../../../languageHandler'; import { Key } from "../../../Keyboard"; import { replaceableComponent } from "../../../utils/replaceableComponent"; @@ -178,7 +178,7 @@ export default class Dropdown extends React.Component { this.ignoreEvent = ev; }; - private onInputClick = (ev: React.MouseEvent) => { + private onAccessibleButtonClick = (ev: ButtonEvent) => { if (this.props.disabled) return; if (!this.state.expanded) { @@ -186,6 +186,10 @@ export default class Dropdown extends React.Component { expanded: true, }); ev.preventDefault(); + } else if ((ev as React.KeyboardEvent).key === Key.ENTER) { + // the accessible button consumes enter onKeyDown for firing onClick, so handle it here + this.props.onOptionChange(this.state.highlightedOption); + this.close(); } }; @@ -328,6 +332,7 @@ export default class Dropdown extends React.Component { aria-owns={`${this.props.id}_listbox`} aria-disabled={this.props.disabled} aria-label={this.props.label} + onKeyDown={this.onKeyDown} /> ); } @@ -357,16 +362,17 @@ export default class Dropdown extends React.Component { // Note the menu sits inside the AccessibleButton div so it's anchored // to the input, but overflows below it. The root contains both. - return
+ return
{ currentValue } From 0cad9ed14a7714bce46cdea8effc9ebf9bddd93c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 8 Aug 2021 11:15:18 +0200 Subject: [PATCH 24/85] Add _CallViewButtons.scss 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 + .../views/voip/CallView/_CallViewButtons.scss | 102 ++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 res/css/views/voip/CallView/_CallViewButtons.scss diff --git a/res/css/_components.scss b/res/css/_components.scss index af161c92c6..e1e6b607df 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -270,6 +270,7 @@ @import "./views/toasts/_IncomingCallToast.scss"; @import "./views/toasts/_NonUrgentEchoFailureToast.scss"; @import "./views/verification/_VerificationShowSas.scss"; +@import "./views/voip/CallView/_CallViewButtons.scss"; @import "./views/voip/_CallContainer.scss"; @import "./views/voip/_CallPreview.scss"; @import "./views/voip/_CallView.scss"; diff --git a/res/css/views/voip/CallView/_CallViewButtons.scss b/res/css/views/voip/CallView/_CallViewButtons.scss new file mode 100644 index 0000000000..8e343f0ff3 --- /dev/null +++ b/res/css/views/voip/CallView/_CallViewButtons.scss @@ -0,0 +1,102 @@ +/* +Copyright 2015, 2016 OpenMarket Ltd +Copyright 2020 - 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. +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_CallViewButtons { + position: absolute; + display: flex; + justify-content: center; + bottom: 5px; + opacity: 1; + transition: opacity 0.5s; + z-index: 200; // To be above _all_ feeds + + &.mx_CallViewButtons_hidden { + opacity: 0.001; // opacity 0 can cause a re-layout + pointer-events: none; + } + + .mx_CallViewButtons_button { + cursor: pointer; + margin-left: 2px; + margin-right: 2px; + + + &::before { + content: ''; + display: inline-block; + + height: 48px; + width: 48px; + + background-repeat: no-repeat; + background-size: contain; + background-position: center; + } + + + &.mx_CallViewButtons_dialpad::before { + background-image: url('$(res)/img/voip/dialpad.svg'); + } + + &.mx_CallViewButtons_button_micOn::before { + background-image: url('$(res)/img/voip/mic-on.svg'); + } + + &.mx_CallViewButtons_button_micOff::before { + background-image: url('$(res)/img/voip/mic-off.svg'); + } + + &.mx_CallViewButtons_button_vidOn::before { + background-image: url('$(res)/img/voip/vid-on.svg'); + } + + &.mx_CallViewButtons_button_vidOff::before { + background-image: url('$(res)/img/voip/vid-off.svg'); + } + + &.mx_CallViewButtons_button_screensharingOn::before { + background-image: url('$(res)/img/voip/screensharing-on.svg'); + } + + &.mx_CallViewButtons_button_screensharingOff::before { + background-image: url('$(res)/img/voip/screensharing-off.svg'); + } + + &.mx_CallViewButtons_button_sidebarOn::before { + background-image: url('$(res)/img/voip/sidebar-on.svg'); + } + + &.mx_CallViewButtons_button_sidebarOff::before { + background-image: url('$(res)/img/voip/sidebar-off.svg'); + } + + &.mx_CallViewButtons_button_hangup::before { + background-image: url('$(res)/img/voip/hangup.svg'); + } + + &.mx_CallViewButtons_button_more::before { + background-image: url('$(res)/img/voip/more.svg'); + } + + &.mx_CallViewButtons_button_invisible { + visibility: hidden; + pointer-events: none; + position: absolute; + } + } +} From 9f28c30145d8aeddc5aa05ddc4028678c3b93db4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 8 Aug 2021 11:16:55 +0200 Subject: [PATCH 25/85] Add CallViewButtons.tsx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- .../views/voip/CallView/CallViewButtons.tsx | 315 ++++++++++++++++++ 1 file changed, 315 insertions(+) create mode 100644 src/components/views/voip/CallView/CallViewButtons.tsx diff --git a/src/components/views/voip/CallView/CallViewButtons.tsx b/src/components/views/voip/CallView/CallViewButtons.tsx new file mode 100644 index 0000000000..8c48bd767d --- /dev/null +++ b/src/components/views/voip/CallView/CallViewButtons.tsx @@ -0,0 +1,315 @@ +/* +Copyright 2015, 2016 OpenMarket Ltd +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. +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 classNames from "classnames"; +import AccessibleTooltipButton from "../../elements/AccessibleTooltipButton"; +import CallContextMenu from "../../context_menus/CallContextMenu"; +import DialpadContextMenu from "../../context_menus/DialpadContextMenu"; +import AccessibleButton from "../../elements/AccessibleButton"; +import { MatrixCall } from "matrix-js-sdk/src/webrtc/call"; +import { Alignment } from "../../elements/Tooltip"; +import { + alwaysAboveLeftOf, + alwaysAboveRightOf, + ChevronFace, + ContextMenuTooltipButton, +} from '../../../structures/ContextMenu'; +import { _t } from "../../../../languageHandler"; + +// Height of the header duplicated from CSS because we need to subtract it from our max +// height to get the max height of the video +const CONTEXT_MENU_VPADDING = 8; // How far the context menu sits above the button (px) + +const TOOLTIP_Y_OFFSET = -24; + +const CONTROLS_HIDE_DELAY = 2000; + +interface IProps { + call: MatrixCall; + pipMode: boolean; + handlers: { + onHangupClick: () => void; + onScreenshareClick: () => void; + onToggleSidebarClick: () => void; + onMicMuteClick: () => void; + onVidMuteClick: () => void; + }; + buttonsState: { + micMuted: boolean; + vidMuted: boolean; + sidebarShown: boolean; + screensharing: boolean; + }; + buttonsVisibility: { + screensharing: boolean; + vidMute: boolean; + sidebar: boolean; + dialpad: boolean; + contextMenu: boolean; + }; +} + +interface IState { + visible: boolean; + showDialpad: boolean; + hoveringControls: boolean; + showMoreMenu: boolean; +} + +export default class CallViewButtons extends React.Component { + private dialpadButton = createRef(); + private contextMenuButton = createRef(); + private controlsHideTimer: number = null; + + constructor(props: IProps) { + super(props); + + this.state = { + showDialpad: false, + hoveringControls: false, + showMoreMenu: false, + visible: true, + }; + } + + public componentDidMount(): void { + this.showControls(); + } + + public showControls(): void { + if (this.state.showMoreMenu || this.state.showDialpad) return; + + if (!this.state.visible) { + this.setState({ + visible: true, + }); + } + if (this.controlsHideTimer !== null) { + clearTimeout(this.controlsHideTimer); + } + this.controlsHideTimer = window.setTimeout(this.onControlsHideTimer, CONTROLS_HIDE_DELAY); + } + + private onControlsHideTimer = (): void => { + if (this.state.hoveringControls || this.state.showDialpad || this.state.showMoreMenu) return; + this.controlsHideTimer = null; + this.setState({ visible: false }); + }; + + private onMouseEnter = (): void => { + this.setState({ hoveringControls: true }); + }; + + private onMouseLeave = (): void => { + this.setState({ hoveringControls: false }); + }; + + private onDialpadClick = (): void => { + if (!this.state.showDialpad) { + this.setState({ showDialpad: true }); + this.showControls(); + } else { + this.setState({ showDialpad: false }); + } + }; + + private onMoreClick = (): void => { + this.setState({ showMoreMenu: true }); + this.showControls(); + }; + + private closeDialpad = (): void => { + this.setState({ showDialpad: false }); + }; + + private closeContextMenu = (): void => { + this.setState({ showMoreMenu: false }); + }; + + public render(): JSX.Element { + const micClasses = classNames("mx_CallViewButtons_button", { + mx_CallViewButtons_button_micOn: !this.props.buttonsState.micMuted, + mx_CallViewButtons_button_micOff: this.props.buttonsState.micMuted, + }); + + const vidClasses = classNames("mx_CallViewButtons_button", { + mx_CallViewButtons_button_vidOn: !this.props.buttonsState.vidMuted, + mx_CallViewButtons_button_vidOff: this.props.buttonsState.vidMuted, + }); + + const screensharingClasses = classNames("mx_CallViewButtons_button", { + mx_CallViewButtons_button_screensharingOn: this.props.buttonsState.screensharing, + mx_CallViewButtons_button_screensharingOff: !this.props.buttonsState.screensharing, + }); + + const sidebarButtonClasses = classNames("mx_CallViewButtons_button", { + mx_CallViewButtons_button_sidebarOn: this.props.buttonsState.sidebarShown, + mx_CallViewButtons_button_sidebarOff: !this.props.buttonsState.sidebarShown, + }); + + // 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("mx_CallViewButtons_button", "mx_CallViewButtons_button_invisible", { + mx_CallViewButtons_button_micOn: this.props.buttonsState.micMuted, + mx_CallViewButtons_button_micOff: !this.props.buttonsState.micMuted, + }); + + const vidCacheClasses = classNames("mx_CallViewButtons_button", "mx_CallViewButtons_button_invisible", { + mx_CallViewButtons_button_vidOn: this.props.buttonsState.micMuted, + mx_CallViewButtons_button_vidOff: !this.props.buttonsState.micMuted, + }); + + const callControlsClasses = classNames("mx_CallViewButtons", { + mx_CallViewButtons_hidden: !this.state.visible, + }); + + let vidMuteButton; + if (this.props.buttonsVisibility.vidMute) { + vidMuteButton = ( + + ); + } + + let screensharingButton; + if (this.props.buttonsVisibility.screensharing) { + screensharingButton = ( + + ); + } + + let sidebarButton; + if (this.props.buttonsVisibility.sidebar) { + sidebarButton = ( + + ); + } + + let contextMenuButton; + if (this.props.buttonsVisibility.contextMenu) { + contextMenuButton = ( + + ); + } + let dialpadButton; + if (this.props.buttonsVisibility.dialpad) { + dialpadButton = ( + + ); + } + + let dialPad; + if (this.state.showDialpad) { + dialPad = ; + } + + let contextMenu; + if (this.state.showMoreMenu) { + contextMenu = ; + } + + return ( +
+ { dialPad } + { contextMenu } + { dialpadButton } + + { vidMuteButton } +
+
+ { screensharingButton } + { sidebarButton } + { contextMenuButton } + +
+ ); + } +} From d0e76a0ecd99d067baa92632062b360b414271a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 8 Aug 2021 11:20:17 +0200 Subject: [PATCH 26/85] Use CallViewButtons 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 | 109 +-------- src/components/views/voip/CallView.tsx | 305 ++++--------------------- 2 files changed, 46 insertions(+), 368 deletions(-) diff --git a/res/css/views/voip/_CallView.scss b/res/css/views/voip/_CallView.scss index 7752edddfa..498dd8e096 100644 --- a/res/css/views/voip/_CallView.scss +++ b/res/css/views/voip/_CallView.scss @@ -47,11 +47,11 @@ limitations under the License. height: 180px; } - .mx_CallView_callControls { + .mx_CallViewButtons { bottom: 0px; } - .mx_CallView_callControls_button { + .mx_CallViewButtons_button { &::before { width: 36px; height: 36px; @@ -199,20 +199,6 @@ limitations under the License. } } -.mx_CallView_callControls { - position: absolute; - display: flex; - justify-content: center; - bottom: 5px; - opacity: 1; - transition: opacity 0.5s; - z-index: 200; // To be above _all_ feeds -} - -.mx_CallView_callControls_hidden { - opacity: 0.001; // opacity 0 can cause a re-layout - pointer-events: none; -} .mx_CallView_presenting { opacity: 1; @@ -232,94 +218,3 @@ limitations under the License. opacity: 0.001; // opacity 0 can cause a re-layout pointer-events: none; } - -.mx_CallView_callControls_button { - cursor: pointer; - margin-left: 2px; - margin-right: 2px; - - - &::before { - content: ''; - display: inline-block; - - height: 48px; - width: 48px; - - background-repeat: no-repeat; - background-size: contain; - background-position: center; - } -} - -.mx_CallView_callControls_dialpad { - &::before { - background-image: url('$(res)/img/voip/dialpad.svg'); - } -} - -.mx_CallView_callControls_button_micOn { - &::before { - background-image: url('$(res)/img/voip/mic-on.svg'); - } -} - -.mx_CallView_callControls_button_micOff { - &::before { - background-image: url('$(res)/img/voip/mic-off.svg'); - } -} - -.mx_CallView_callControls_button_vidOn { - &::before { - background-image: url('$(res)/img/voip/vid-on.svg'); - } -} - -.mx_CallView_callControls_button_vidOff { - &::before { - background-image: url('$(res)/img/voip/vid-off.svg'); - } -} - -.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_sidebarOn { - &::before { - background-image: url('$(res)/img/voip/sidebar-on.svg'); - } -} - -.mx_CallView_callControls_button_sidebarOff { - &::before { - background-image: url('$(res)/img/voip/sidebar-off.svg'); - } -} - -.mx_CallView_callControls_button_hangup { - &::before { - background-image: url('$(res)/img/voip/hangup.svg'); - } -} - -.mx_CallView_callControls_button_more { - &::before { - background-image: url('$(res)/img/voip/more.svg'); - } -} - -.mx_CallView_callControls_button_invisible { - visibility: hidden; - pointer-events: none; - position: absolute; -} diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index d3371b8456..a6ae71713b 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -1,6 +1,6 @@ /* Copyright 2015, 2016 OpenMarket 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"); @@ -27,15 +27,7 @@ import { CallEvent, CallState, CallType, MatrixCall } from 'matrix-js-sdk/src/we import classNames from 'classnames'; import AccessibleButton from '../elements/AccessibleButton'; import { isOnlyCtrlOrCmdKeyEvent, Key } from '../../../Keyboard'; -import { - alwaysAboveLeftOf, - alwaysAboveRightOf, - ChevronFace, - ContextMenuTooltipButton, -} from '../../structures/ContextMenu'; -import CallContextMenu from '../context_menus/CallContextMenu'; 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"; @@ -43,8 +35,7 @@ import Modal from '../../../Modal'; import { SDPStreamMetadataPurpose } from 'matrix-js-sdk/src/webrtc/callEventTypes'; import CallViewSidebar from './CallViewSidebar'; import CallViewHeader from './CallView/CallViewHeader'; -import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; -import { Alignment } from "../elements/Tooltip"; +import CallViewButtons from "./CallView/CallViewButtons"; interface IProps { // The call for us to display @@ -83,8 +74,6 @@ interface IState { sidebarShown: boolean; } -const tooltipYOffset = -24; - function getFullScreenElement() { return ( document.fullscreenElement || @@ -113,18 +102,11 @@ function exitFullscreen() { if (exitMethod) exitMethod.call(document); } -const CONTROLS_HIDE_DELAY = 2000; -// Height of the header duplicated from CSS because we need to subtract it from our max -// height to get the max height of the video -const CONTEXT_MENU_VPADDING = 8; // How far the context menu sits above the button (px) - @replaceableComponent("views.voip.CallView") export default class CallView extends React.Component { private dispatcherRef: string; private contentRef = createRef(); - private controlsHideTimer: number = null; - private dialpadButton = createRef(); - private contextMenuButton = createRef(); + private buttonsRef = createRef(); constructor(props: IProps) { super(props); @@ -240,16 +222,8 @@ export default class CallView extends React.Component { }); }; - private onControlsHideTimer = () => { - if (this.state.hoveringControls || this.state.showDialpad || this.state.showMoreMenu) return; - this.controlsHideTimer = null; - this.setState({ - controlsVisible: false, - }); - }; - private onMouseMove = () => { - this.showControls(); + this.buttonsRef.current?.showControls(); }; private getOrderedFeeds(feeds: Array): { primary: CallFeed, secondary: Array } { @@ -275,29 +249,6 @@ export default class CallView extends React.Component { return { primary, secondary }; } - private showControls(): void { - if (this.state.showMoreMenu || this.state.showDialpad) return; - - if (!this.state.controlsVisible) { - this.setState({ - controlsVisible: true, - }); - } - if (this.controlsHideTimer !== null) { - clearTimeout(this.controlsHideTimer); - } - this.controlsHideTimer = window.setTimeout(this.onControlsHideTimer, CONTROLS_HIDE_DELAY); - } - - private onDialpadClick = (): void => { - if (!this.state.showDialpad) { - this.setState({ showDialpad: true }); - this.showControls(); - } else { - this.setState({ showDialpad: false }); - } - }; - private onMicMuteClick = (): void => { const newVal = !this.state.micMuted; @@ -328,19 +279,6 @@ export default class CallView extends React.Component { }); }; - private onMoreClick = (): void => { - this.setState({ showMoreMenu: true }); - this.showControls(); - }; - - private closeDialpad = (): void => { - this.setState({ showDialpad: false }); - }; - - private closeContextMenu = (): void => { - this.setState({ showMoreMenu: false }); - }; - // we register global shortcuts here, they *must not conflict* with local shortcuts elsewhere or both will fire // Note that this assumes we always have a CallView on screen at any given time // CallHandler would probably be a better place for this @@ -353,7 +291,7 @@ export default class CallView extends React.Component { if (ctrlCmdOnly) { this.onMicMuteClick(); // show the controls to give feedback - this.showControls(); + this.buttonsRef.current?.showControls(); handled = true; } break; @@ -362,7 +300,7 @@ export default class CallView extends React.Component { if (ctrlCmdOnly) { this.onVidMuteClick(); // show the controls to give feedback - this.showControls(); + this.buttonsRef.current?.showControls(); handled = true; } break; @@ -374,15 +312,6 @@ export default class CallView extends React.Component { } }; - private onCallControlsMouseEnter = (): void => { - this.setState({ hoveringControls: true }); - this.showControls(); - }; - - private onCallControlsMouseLeave = (): void => { - this.setState({ hoveringControls: false }); - }; - private onCallResumeClick = (): void => { const userFacingRoomId = CallHandler.sharedInstance().roomIdForCall(this.props.call); CallHandler.sharedInstance().setActiveCallRoomId(userFacingRoomId); @@ -401,206 +330,60 @@ export default class CallView extends React.Component { }; private onToggleSidebar = (): void => { - this.setState({ - sidebarShown: !this.state.sidebarShown, - }); + this.setState({ sidebarShown: !this.state.sidebarShown }); }; private renderCallControls(): JSX.Element { - const micClasses = classNames({ - mx_CallView_callControls_button: true, - mx_CallView_callControls_button_micOn: !this.state.micMuted, - mx_CallView_callControls_button_micOff: this.state.micMuted, - }); - - const vidClasses = classNames({ - mx_CallView_callControls_button: true, - mx_CallView_callControls_button_vidOn: !this.state.vidMuted, - 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, - }); - - const sidebarButtonClasses = classNames({ - mx_CallView_callControls_button: true, - mx_CallView_callControls_button_sidebarOn: this.state.sidebarShown, - mx_CallView_callControls_button_sidebarOff: !this.state.sidebarShown, - }); - - // 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({ - mx_CallView_callControls_button: true, - mx_CallView_callControls_button_micOn: this.state.micMuted, - mx_CallView_callControls_button_micOff: !this.state.micMuted, - mx_CallView_callControls_button_invisible: true, - }); - - const vidCacheClasses = classNames({ - mx_CallView_callControls_button: true, - mx_CallView_callControls_button_vidOn: this.state.micMuted, - mx_CallView_callControls_button_vidOff: !this.state.micMuted, - mx_CallView_callControls_button_invisible: true, - }); - - const callControlsClasses = classNames({ - mx_CallView_callControls: true, - mx_CallView_callControls_hidden: !this.state.controlsVisible, - }); - // We don't support call upgrades (yet) so hide the video mute button in voice calls - let vidMuteButton; - if (this.props.call.type === CallType.Video) { - vidMuteButton = ( - - ); - } - + const vidMuteButtonShown = 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 - let screensharingButton; - if ( + const screensharingButtonShown = ( (this.props.call.opponentSupportsSDPStreamMetadata() || this.props.call.type === CallType.Video) && this.props.call.state === CallState.Connected - ) { - screensharingButton = ( - - ); - } - + ); // To show the sidebar we need secondary feeds, if we don't have them, // we can hide this button. If we are in PiP, sidebar is also hidden, so // we can hide the button too - let sidebarButton; - if ( - !this.props.pipMode && - ( - this.state.primaryFeed?.purpose === SDPStreamMetadataPurpose.Screenshare || - this.props.call.isScreensharing() - ) - ) { - sidebarButton = ( - - ); - } - + const sidebarButtonShown = ( + this.state.primaryFeed?.purpose === SDPStreamMetadataPurpose.Screenshare || + this.props.call.isScreensharing() + ); // The dial pad & 'more' button actions are only relevant in a connected call - let contextMenuButton; - if (this.state.callState === CallState.Connected) { - contextMenuButton = ( - - ); - } - let dialpadButton; - if (this.state.callState === CallState.Connected && this.props.call.opponentSupportsDTMF()) { - dialpadButton = ( - - ); - } - - let dialPad; - if (this.state.showDialpad) { - dialPad = ; - } - - let contextMenu; - if (this.state.showMoreMenu) { - contextMenu = ; - } + const contextMenuButtonShown = this.state.callState === CallState.Connected; + const dialpadButtonShown = ( + this.state.callState === CallState.Connected && + this.props.call.opponentSupportsDTMF() + ); return ( -
- { dialPad } - { contextMenu } - { dialpadButton } - - { vidMuteButton } -
-
- { screensharingButton } - { sidebarButton } - { contextMenuButton } - -
+ ); } From eb49960497f4214918f8cdea78761885f55702d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 8 Aug 2021 11:20:52 +0200 Subject: [PATCH 27/85] 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 | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index f19de74685..826fe5ff88 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -905,6 +905,16 @@ "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", + "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", + "You held the call Resume": "You held the call Resume", + "%(peerName)s held the call": "%(peerName)s held the call", + "Connecting": "Connecting", + "You are presenting": "You are presenting", + "%(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", "Start the camera": "Start the camera", "Stop the camera": "Stop the camera", "Stop sharing your screen": "Stop sharing your screen", @@ -916,16 +926,6 @@ "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", - "You held the call Resume": "You held the call Resume", - "%(peerName)s held the call": "%(peerName)s held the call", - "Connecting": "Connecting", - "You are presenting": "You are presenting", - "%(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", From 09f20bcda78eb797c8654b11ee6d14c5d1d3e837 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 9 Aug 2021 10:29:55 +0100 Subject: [PATCH 28/85] Make space hierarchy a treeview --- src/accessibility/RovingTabIndex.tsx | 52 ++- .../structures/SpaceRoomDirectory.tsx | 418 +++++++++++------- src/components/views/spaces/SpacePanel.tsx | 1 + .../views/spaces/SpaceTreeLevel.tsx | 2 +- 4 files changed, 289 insertions(+), 184 deletions(-) diff --git a/src/accessibility/RovingTabIndex.tsx b/src/accessibility/RovingTabIndex.tsx index 87f525bdfc..4e2279e09f 100644 --- a/src/accessibility/RovingTabIndex.tsx +++ b/src/accessibility/RovingTabIndex.tsx @@ -150,13 +150,14 @@ const reducer = (state: IState, action: IAction) => { interface IProps { handleHomeEnd?: boolean; + handleUpDown?: boolean; children(renderProps: { onKeyDownHandler(ev: React.KeyboardEvent); }); onKeyDown?(ev: React.KeyboardEvent, state: IState); } -export const RovingTabIndexProvider: React.FC = ({ children, handleHomeEnd, onKeyDown }) => { +export const RovingTabIndexProvider: React.FC = ({ children, handleHomeEnd, handleUpDown, onKeyDown }) => { const [state, dispatch] = useReducer>(reducer, { activeRef: null, refs: [], @@ -167,21 +168,50 @@ export const RovingTabIndexProvider: React.FC = ({ children, handleHomeE const onKeyDownHandler = useCallback((ev) => { let handled = false; // Don't interfere with input default keydown behaviour - if (handleHomeEnd && ev.target.tagName !== "INPUT" && ev.target.tagName !== "TEXTAREA") { + if (ev.target.tagName !== "INPUT" && ev.target.tagName !== "TEXTAREA") { // check if we actually have any items switch (ev.key) { case Key.HOME: - handled = true; - // move focus to first item - if (context.state.refs.length > 0) { - context.state.refs[0].current.focus(); + if (handleHomeEnd) { + handled = true; + // move focus to first item + if (context.state.refs.length > 0) { + context.state.refs[0].current.focus(); + } } break; + case Key.END: - handled = true; - // move focus to last item - if (context.state.refs.length > 0) { - context.state.refs[context.state.refs.length - 1].current.focus(); + if (handleHomeEnd) { + handled = true; + // move focus to last item + if (context.state.refs.length > 0) { + context.state.refs[context.state.refs.length - 1].current.focus(); + } + } + break; + + case Key.ARROW_UP: + if (handleUpDown) { + handled = true; + if (context.state.refs.length > 0) { + const idx = context.state.refs.indexOf(context.state.activeRef); + if (idx > 1) { + context.state.refs[idx - 1].current.focus(); + } + } + } + break; + + case Key.ARROW_DOWN: + if (handleUpDown) { + handled = true; + if (context.state.refs.length > 0) { + const idx = context.state.refs.indexOf(context.state.activeRef); + if (idx < context.state.refs.length - 1) { + context.state.refs[idx + 1].current.focus(); + } + } } break; } @@ -193,7 +223,7 @@ export const RovingTabIndexProvider: React.FC = ({ children, handleHomeE } else if (onKeyDown) { return onKeyDown(ev, context.state); } - }, [context.state, onKeyDown, handleHomeEnd]); + }, [context.state, onKeyDown, handleHomeEnd, handleUpDown]); return { children({ onKeyDownHandler }) } diff --git a/src/components/structures/SpaceRoomDirectory.tsx b/src/components/structures/SpaceRoomDirectory.tsx index d8cc9593f0..b2d5e787f5 100644 --- a/src/components/structures/SpaceRoomDirectory.tsx +++ b/src/components/structures/SpaceRoomDirectory.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { ReactNode, useMemo, useState } from "react"; +import React, { ReactNode, KeyboardEvent, useMemo, useState } from "react"; import { Room } from "matrix-js-sdk/src/models/room"; import { EventType, RoomType } from "matrix-js-sdk/src/@types/event"; import { ISpaceSummaryRoom, ISpaceSummaryEvent } from "matrix-js-sdk/src/@types/spaces"; @@ -46,6 +46,8 @@ import { getDisplayAliasForAliasSet } from "../../Rooms"; import { useDispatcher } from "../../hooks/useDispatcher"; import defaultDispatcher from "../../dispatcher/dispatcher"; import { Action } from "../../dispatcher/actions"; +import { Key } from "../../Keyboard"; +import { IState, RovingTabIndexProvider, useRovingTabIndex } from "../../accessibility/RovingTabIndex"; interface IHierarchyProps { space: Room; @@ -80,6 +82,7 @@ const Tile: React.FC = ({ || (room.room_type === RoomType.Space ? _t("Unnamed Space") : _t("Unnamed Room")); const [showChildren, toggleShowChildren] = useStateToggle(true); + const [onFocus, isActive, ref] = useRovingTabIndex(); const onPreviewClick = (ev: ButtonEvent) => { ev.preventDefault(); @@ -94,11 +97,21 @@ const Tile: React.FC = ({ let button; if (joinedRoom) { - button = + button = { _t("View") } ; } else if (onJoinClick) { - button = + button = { _t("Join") } ; } @@ -106,13 +119,13 @@ const Tile: React.FC = ({ let checkbox; if (onToggleClick) { if (hasPermissions) { - checkbox = ; + checkbox = ; } else { checkbox = { ev.stopPropagation(); }} > - + ; } } @@ -185,19 +198,65 @@ const Tile: React.FC = ({ toggleShowChildren(); }} />; + if (showChildren) { - childSection =
+ const onChildrenKeyDown = (e) => { + if (e.key === Key.ARROW_LEFT) { + e.preventDefault(); + e.stopPropagation(); + ref.current?.focus(); + } + }; + + childSection =
{ children }
; } } + const onKeyDown = children ? (e) => { + let handled = false; + + switch (e.key) { + case Key.ARROW_LEFT: + if (showChildren) { + handled = true; + toggleShowChildren(); + } + break; + + case Key.ARROW_RIGHT: + handled = true; + if (showChildren) { + (ref.current?.nextElementSibling?.firstElementChild as HTMLElement)?.focus(); + } else { + toggleShowChildren(); + } + break; + } + + if (handled) { + e.preventDefault(); + e.stopPropagation(); + } + } : undefined; + return <> { content } { childToggle } @@ -414,176 +473,191 @@ export const SpaceHierarchy: React.FC = ({ return

{ _t("Your server does not support showing space hierarchies.") }

; } - let content; - if (roomsMap) { - const numRooms = Array.from(roomsMap.values()).filter(r => !r.room_type).length; - const numSpaces = roomsMap.size - numRooms - 1; // -1 at the end to exclude the space we are looking at - - let countsStr; - if (numSpaces > 1) { - countsStr = _t("%(count)s rooms and %(numSpaces)s spaces", { count: numRooms, numSpaces }); - } else if (numSpaces > 0) { - countsStr = _t("%(count)s rooms and 1 space", { count: numRooms, numSpaces }); - } else { - countsStr = _t("%(count)s rooms", { count: numRooms, numSpaces }); + const onKeyDown = (ev: KeyboardEvent, state: IState) => { + if (ev.key === Key.ARROW_DOWN && ev.currentTarget.classList.contains("mx_SpaceRoomDirectory_search")) { + state.refs[0]?.current?.focus(); } - - let manageButtons; - if (space.getMyMembership() === "join" && space.currentState.maySendStateEvent(EventType.SpaceChild, userId)) { - const selectedRelations = Array.from(selected.keys()).flatMap(parentId => { - return [...selected.get(parentId).values()].map(childId => [parentId, childId]) as [string, string][]; - }); - - const selectionAllSuggested = selectedRelations.every(([parentId, childId]) => { - return parentChildMap.get(parentId)?.get(childId)?.content.suggested; - }); - - const disabled = !selectedRelations.length || removing || saving; - - let Button: React.ComponentType> = AccessibleButton; - let props = {}; - if (!selectedRelations.length) { - Button = AccessibleTooltipButton; - props = { - tooltip: _t("Select a room below first"), - yOffset: -40, - }; - } - - manageButtons = <> - - - ; - } - - let results; - if (roomsMap.size) { - const hasPermissions = space?.currentState.maySendStateEvent(EventType.SpaceChild, cli.getUserId()); - - results = <> - { - setError(""); - if (!selected.has(parentId)) { - setSelected(new Map(selected.set(parentId, new Set([childId])))); - return; - } - - const parentSet = selected.get(parentId); - if (!parentSet.has(childId)) { - setSelected(new Map(selected.set(parentId, new Set([...parentSet, childId])))); - return; - } - - parentSet.delete(childId); - setSelected(new Map(selected.set(parentId, new Set(parentSet)))); - } : undefined} - onViewRoomClick={(roomId, autoJoin) => { - showRoom(roomsMap.get(roomId), Array.from(viaMap.get(roomId) || []), autoJoin); - }} - /> - { children &&
} - ; - } else { - results =
-

{ _t("No results found") }

-
{ _t("You may want to try a different search or check for typos.") }
-
; - } - - content = <> -
- { countsStr } - - { additionalButtons } - { manageButtons } - -
- { error &&
- { error } -
} - - { results } - { children } - - ; - } else { - content = ; - } + }; // TODO loading state/error state - return <> - + return + { ({ onKeyDownHandler }) => { + let content; + if (roomsMap) { + const numRooms = Array.from(roomsMap.values()).filter(r => !r.room_type).length; + const numSpaces = roomsMap.size - numRooms - 1; // -1 at the end to exclude the space we are looking at - { content } - ; + let countsStr; + if (numSpaces > 1) { + countsStr = _t("%(count)s rooms and %(numSpaces)s spaces", { count: numRooms, numSpaces }); + } else if (numSpaces > 0) { + countsStr = _t("%(count)s rooms and 1 space", { count: numRooms, numSpaces }); + } else { + countsStr = _t("%(count)s rooms", { count: numRooms, numSpaces }); + } + + let manageButtons; + if (space.getMyMembership() === "join" && + space.currentState.maySendStateEvent(EventType.SpaceChild, userId) + ) { + const selectedRelations = Array.from(selected.keys()).flatMap(parentId => { + return [ + ...selected.get(parentId).values(), + ].map(childId => [parentId, childId]) as [string, string][]; + }); + + const selectionAllSuggested = selectedRelations.every(([parentId, childId]) => { + return parentChildMap.get(parentId)?.get(childId)?.content.suggested; + }); + + const disabled = !selectedRelations.length || removing || saving; + + let Button: React.ComponentType> = AccessibleButton; + let props = {}; + if (!selectedRelations.length) { + Button = AccessibleTooltipButton; + props = { + tooltip: _t("Select a room below first"), + yOffset: -40, + }; + } + + manageButtons = <> + + + ; + } + + let results; + if (roomsMap.size) { + const hasPermissions = space?.currentState.maySendStateEvent(EventType.SpaceChild, cli.getUserId()); + + results = <> + { + setError(""); + if (!selected.has(parentId)) { + setSelected(new Map(selected.set(parentId, new Set([childId])))); + return; + } + + const parentSet = selected.get(parentId); + if (!parentSet.has(childId)) { + setSelected(new Map(selected.set(parentId, new Set([...parentSet, childId])))); + return; + } + + parentSet.delete(childId); + setSelected(new Map(selected.set(parentId, new Set(parentSet)))); + } : undefined} + onViewRoomClick={(roomId, autoJoin) => { + showRoom(roomsMap.get(roomId), Array.from(viaMap.get(roomId) || []), autoJoin); + }} + /> + { children &&
} + ; + } else { + results =
+

{ _t("No results found") }

+
{ _t("You may want to try a different search or check for typos.") }
+
; + } + + content = <> +
+ { countsStr } + + { additionalButtons } + { manageButtons } + +
+ { error &&
+ { error } +
} + + { results } + { children } + + ; + } else { + content = ; + } + + return <> + + + { content } + ; + } } +
; }; interface IProps { diff --git a/src/components/views/spaces/SpacePanel.tsx b/src/components/views/spaces/SpacePanel.tsx index 58e1db4b1d..38d530bee5 100644 --- a/src/components/views/spaces/SpacePanel.tsx +++ b/src/components/views/spaces/SpacePanel.tsx @@ -272,6 +272,7 @@ const SpacePanel = () => {
    { (provided, snapshot) => ( diff --git a/src/components/views/spaces/SpaceTreeLevel.tsx b/src/components/views/spaces/SpaceTreeLevel.tsx index 5d2f58fab2..5b32722504 100644 --- a/src/components/views/spaces/SpaceTreeLevel.tsx +++ b/src/components/views/spaces/SpaceTreeLevel.tsx @@ -328,7 +328,7 @@ const SpaceTreeLevel: React.FC = ({ isNested, parents, }) => { - return
      + return
        { spaces.map(s => { return ( Date: Mon, 9 Aug 2021 14:01:34 +0100 Subject: [PATCH 29/85] iterate spaces treeview stuff --- res/css/structures/_SpaceRoomDirectory.scss | 4 ++ src/accessibility/RovingTabIndex.tsx | 2 +- .../structures/SpaceRoomDirectory.tsx | 72 ++++++++++--------- src/components/views/spaces/SpacePanel.tsx | 18 +++-- .../views/spaces/SpaceTreeLevel.tsx | 7 +- 5 files changed, 57 insertions(+), 46 deletions(-) diff --git a/res/css/structures/_SpaceRoomDirectory.scss b/res/css/structures/_SpaceRoomDirectory.scss index 03ff1d5ae5..88e6a3f494 100644 --- a/res/css/structures/_SpaceRoomDirectory.scss +++ b/res/css/structures/_SpaceRoomDirectory.scss @@ -278,6 +278,10 @@ limitations under the License. } } + li.mx_SpaceRoomDirectory_roomTileWrapper { + list-style: none; + } + .mx_SpaceRoomDirectory_roomTile, .mx_SpaceRoomDirectory_subspace_children { &::before { diff --git a/src/accessibility/RovingTabIndex.tsx b/src/accessibility/RovingTabIndex.tsx index 4e2279e09f..68e10049fd 100644 --- a/src/accessibility/RovingTabIndex.tsx +++ b/src/accessibility/RovingTabIndex.tsx @@ -196,7 +196,7 @@ export const RovingTabIndexProvider: React.FC = ({ children, handleHomeE handled = true; if (context.state.refs.length > 0) { const idx = context.state.refs.indexOf(context.state.activeRef); - if (idx > 1) { + if (idx > 0) { context.state.refs[idx - 1].current.focus(); } } diff --git a/src/components/structures/SpaceRoomDirectory.tsx b/src/components/structures/SpaceRoomDirectory.tsx index b2d5e787f5..bcd1a4252e 100644 --- a/src/components/structures/SpaceRoomDirectory.tsx +++ b/src/components/structures/SpaceRoomDirectory.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { ReactNode, KeyboardEvent, useMemo, useState } from "react"; +import React, { ReactNode, KeyboardEvent, useMemo, useState, KeyboardEventHandler } from "react"; import { Room } from "matrix-js-sdk/src/models/room"; import { EventType, RoomType } from "matrix-js-sdk/src/@types/event"; import { ISpaceSummaryRoom, ISpaceSummaryEvent } from "matrix-js-sdk/src/@types/spaces"; @@ -185,8 +185,9 @@ const Tile: React.FC = ({
; - let childToggle; - let childSection; + let childToggle: JSX.Element; + let childSection: JSX.Element; + let onKeyDown: KeyboardEventHandler; if (children) { // the chevron is purposefully a div rather than a button as it should be ignored for a11y childToggle =
= ({ { children }
; } + + onKeyDown = (e) => { + let handled = false; + + switch (e.key) { + case Key.ARROW_LEFT: + if (showChildren) { + handled = true; + toggleShowChildren(); + } + break; + + case Key.ARROW_RIGHT: + handled = true; + if (showChildren) { + const childSection = ref.current?.nextElementSibling; + childSection?.querySelector(".mx_SpaceRoomDirectory_roomTile")?.focus(); + } else { + toggleShowChildren(); + } + break; + } + + if (handled) { + e.preventDefault(); + e.stopPropagation(); + } + }; } - const onKeyDown = children ? (e) => { - let handled = false; - - switch (e.key) { - case Key.ARROW_LEFT: - if (showChildren) { - handled = true; - toggleShowChildren(); - } - break; - - case Key.ARROW_RIGHT: - handled = true; - if (showChildren) { - (ref.current?.nextElementSibling?.firstElementChild as HTMLElement)?.focus(); - } else { - toggleShowChildren(); - } - break; - } - - if (handled) { - e.preventDefault(); - e.stopPropagation(); - } - } : undefined; - - return <> + return
  • = ({ inputRef={ref} onFocus={onFocus} tabIndex={isActive ? 0 : -1} - aria-expanded={children ? showChildren : undefined} - role="treeitem" > { content } { childToggle } { childSection } - ; +
  • ; }; export const showRoom = (room: ISpaceSummaryRoom, viaServers?: string[], autoJoin = false) => { diff --git a/src/components/views/spaces/SpacePanel.tsx b/src/components/views/spaces/SpacePanel.tsx index 38d530bee5..4e3455e1fb 100644 --- a/src/components/views/spaces/SpacePanel.tsx +++ b/src/components/views/spaces/SpacePanel.tsx @@ -100,9 +100,12 @@ const HomeButton = ({ selected, isPanelCollapsed }: IHomeButtonProps) => { return SpaceStore.instance.allRoomsInHome; }); - return
  • + return
  • SpaceStore.instance.setActiveSpace(null)} @@ -142,9 +145,12 @@ const CreateSpaceButton = ({ openMenu(); }; - return
  • + return
  • = ({ onClick={onClick} onContextMenu={openMenu} forceHide={!isNarrow || menuDisplayed} - role="treeitem" inputRef={handle} > { children } @@ -290,7 +289,7 @@ export class SpaceItem extends React.PureComponent { /> : null; return ( -
  • +
  • { avatarSize={isNested ? 24 : 32} onClick={this.onClick} onKeyDown={this.onKeyDown} - aria-expanded={!collapsed} - ContextMenuComponent={this.props.space.getMyMembership() === "join" - ? SpaceContextMenu : undefined} + ContextMenuComponent={this.props.space.getMyMembership() === "join" ? SpaceContextMenu : undefined} > { toggleCollapseButton } From 857bb9db44ee2c29428c8413a866791f88b3d0e2 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 10 Aug 2021 09:46:25 +0100 Subject: [PATCH 30/85] Add some treeview labels --- src/components/structures/SpaceRoomDirectory.tsx | 7 ++++++- src/components/views/spaces/SpacePanel.tsx | 1 + src/i18n/strings/en_EN.json | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/components/structures/SpaceRoomDirectory.tsx b/src/components/structures/SpaceRoomDirectory.tsx index bcd1a4252e..27b70c6841 100644 --- a/src/components/structures/SpaceRoomDirectory.tsx +++ b/src/components/structures/SpaceRoomDirectory.tsx @@ -639,7 +639,12 @@ export const SpaceHierarchy: React.FC = ({ { error &&
    { error }
    } - + { results } { children } diff --git a/src/components/views/spaces/SpacePanel.tsx b/src/components/views/spaces/SpacePanel.tsx index 4e3455e1fb..40016af36f 100644 --- a/src/components/views/spaces/SpacePanel.tsx +++ b/src/components/views/spaces/SpacePanel.tsx @@ -279,6 +279,7 @@ const SpacePanel = () => { className={classNames("mx_SpacePanel", { collapsed: isPanelCollapsed })} onKeyDown={onKeyDownHandler} role="tree" + aria-label={_t("Spaces")} > { (provided, snapshot) => ( diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 3aab3cb68c..6dec6b51ce 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2829,6 +2829,7 @@ "Mark as suggested": "Mark as suggested", "No results found": "No results found", "You may want to try a different search or check for typos.": "You may want to try a different search or check for typos.", + "Space": "Space", "Search names and descriptions": "Search names and descriptions", "If you can't find the room you're looking for, ask for an invite or create a new room.": "If you can't find the room you're looking for, ask for an invite or create a new room.", "Create room": "Create room", @@ -3110,7 +3111,6 @@ "Page Down": "Page Down", "Esc": "Esc", "Enter": "Enter", - "Space": "Space", "End": "End", "[number]": "[number]" } From 195b8714fc592fd2286bae36e825518a2cec55f8 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 10 Aug 2021 09:55:51 +0100 Subject: [PATCH 31/85] Iterate PR based on feedback --- res/css/views/settings/tabs/_SettingsTab.scss | 10 ++++++++-- .../tabs/user/_PreferencesUserSettingsTab.scss | 1 + .../settings/tabs/user/PreferencesUserSettingsTab.tsx | 5 ++--- src/i18n/strings/en_EN.json | 4 +--- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/res/css/views/settings/tabs/_SettingsTab.scss b/res/css/views/settings/tabs/_SettingsTab.scss index 9f40372690..3290a998ab 100644 --- a/res/css/views/settings/tabs/_SettingsTab.scss +++ b/res/css/views/settings/tabs/_SettingsTab.scss @@ -50,15 +50,21 @@ limitations under the License. } .mx_SettingsTab_section { + $right-gutter: 80px; + margin-bottom: 24px; .mx_SettingsFlag { - margin-right: 80px; + margin-right: $right-gutter; margin-bottom: 10px; } + > p { + margin-right: $right-gutter; + } + &.mx_SettingsTab_subsectionText .mx_SettingsFlag { - margin-right: 0px !important; + margin-right: 0 !important; } } diff --git a/res/css/views/settings/tabs/user/_PreferencesUserSettingsTab.scss b/res/css/views/settings/tabs/user/_PreferencesUserSettingsTab.scss index efd2548d3d..4cdfa0b40f 100644 --- a/res/css/views/settings/tabs/user/_PreferencesUserSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_PreferencesUserSettingsTab.scss @@ -31,6 +31,7 @@ limitations under the License. font-size: $font-15px; line-height: $font-18px; color: $primary-fg-color; + margin: 16px 0; .mx_BaseAvatar { margin-right: 12px; diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx index 860f42c1e8..21c3ab24ec 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx @@ -341,9 +341,8 @@ export default class PreferencesUserSettingsTab extends React.Component { _t("Communities") }

    { _t("Communities have been archived to make way for Spaces but you can convert your " + - "communities into Spaces below.") }

    - { _t("Convert your Communities to Spaces") } -

    { _t("Converting will ensure your conversations get the latest features.") }

    + "communities into Spaces below. Converting will ensure your conversations get the latest " + + "features.") }

    { _t("Show my Communities") }

    { _t("If a community isn't shown you may not have permission to convert it.") }

    diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 008cbabac3..a725cdaff7 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1342,9 +1342,7 @@ "Preferences": "Preferences", "Room list": "Room list", "Communities": "Communities", - "Communities have been archived to make way for Spaces but you can convert your communities into Spaces below.": "Communities have been archived to make way for Spaces but you can convert your communities into Spaces below.", - "Convert your Communities to Spaces": "Convert your Communities to Spaces", - "Converting will ensure your conversations get the latest features.": "Converting will ensure your conversations get the latest features.", + "Communities have been archived to make way for Spaces but you can convert your communities into Spaces below. Converting will ensure your conversations get the latest features.": "Communities have been archived to make way for Spaces but you can convert your communities into Spaces below. Converting will ensure your conversations get the latest features.", "Show my Communities": "Show my Communities", "If a community isn't shown you may not have permission to convert it.": "If a community isn't shown you may not have permission to convert it.", "Keyboard shortcuts": "Keyboard shortcuts", From d90321d8133bc71be3b316922bdd0386176e5a67 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 10 Aug 2021 11:03:03 +0100 Subject: [PATCH 32/85] Iterate PR, merge types with @types/PushRules --- src/CallHandler.tsx | 4 ++-- src/components/structures/InteractiveAuth.tsx | 16 ++++++++-------- .../auth/InteractiveAuthEntryComponents.tsx | 2 +- .../controllers/NotificationControllers.ts | 5 +++-- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index 41571666c3..f2142f56f4 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -86,7 +86,7 @@ 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 { RuleId, TweakName, Tweaks } from "matrix-js-sdk/src/@types/PushRules"; import { PushProcessor } from 'matrix-js-sdk/src/pushprocessor'; import { WidgetLayoutStore, Container } from './stores/widgets/WidgetLayoutStore'; import { getIncomingCallToastKey } from './toasts/IncomingCallToast'; @@ -484,7 +484,7 @@ export default class CallHandler extends EventEmitter { switch (newState) { case CallState.Ringing: { const incomingCallPushRule = ( - new PushProcessor(MatrixClientPeg.get()).getPushRuleById(RuleId.IncomingCall) as IPushRule + new PushProcessor(MatrixClientPeg.get()).getPushRuleById(RuleId.IncomingCall) ); const pushRuleEnabled = incomingCallPushRule?.enabled; const tweakSetToRing = incomingCallPushRule?.actions.some((action: Tweaks) => ( diff --git a/src/components/structures/InteractiveAuth.tsx b/src/components/structures/InteractiveAuth.tsx index 017e34184f..cea756adc0 100644 --- a/src/components/structures/InteractiveAuth.tsx +++ b/src/components/structures/InteractiveAuth.tsx @@ -35,7 +35,7 @@ interface IProps { // matrix client to use for UI auth requests matrixClient: MatrixClient; // response from initial request. If not supplied, will do a request on mount. - authData: IAuthData; + authData?: IAuthData; // Inputs provided by the user to the auth process // and used by various stages. As passed to js-sdk // interactive-auth @@ -72,11 +72,11 @@ interface IProps { extra?: { emailSid?: string, clientSecret?: string }, ): void; // As js-sdk interactive-auth - requestEmailToken?(email: string, secret: string, attempt: number, session: string): Promise<{ sid: string }>, + requestEmailToken?(email: string, secret: string, attempt: number, session: string): Promise<{ sid: string }>; // Called when the stage changes, or the stage's phase changes. First // argument is the stage, second is the phase. Some stages do not have // phases and will be counted as 0 (numeric). - onStagePhaseChange?(stage: string, phase: string | number): void, + onStagePhaseChange?(stage: string, phase: string | number): void; } interface IState { @@ -91,7 +91,7 @@ interface IState { @replaceableComponent("structures.InteractiveAuthComponent") export default class InteractiveAuthComponent extends React.Component { private readonly authLogic: InteractiveAuth; - private readonly _intervalId: NodeJS.Timeout = null; + private readonly intervalId: number = null; private readonly stageComponent = createRef(); private unmounted = false; @@ -121,14 +121,14 @@ export default class InteractiveAuthComponent extends React.Component { + this.intervalId = setInterval(() => { this.authLogic.poll(); }, 2000); } } // TODO: [REACT-WARNING] Replace component with real class, use constructor for refs - UNSAFE_componentWillMount() { // eslint-disable-line camelcase + UNSAFE_componentWillMount() { // eslint-disable-line @typescript-eslint/naming-convention, camelcase this.authLogic.attemptAuth().then((result) => { const extra = { emailSid: this.authLogic.getEmailSid(), @@ -152,8 +152,8 @@ export default class InteractiveAuthComponent extends React.Component Date: Tue, 10 Aug 2021 11:30:00 +0100 Subject: [PATCH 33/85] Update space create menu copy and add link to community migrator --- res/css/views/spaces/_SpaceCreateMenu.scss | 13 ++++---- .../views/spaces/SpaceCreateMenu.tsx | 30 +++++++++++++++++-- src/i18n/strings/en_EN.json | 6 ++-- 3 files changed, 36 insertions(+), 13 deletions(-) diff --git a/res/css/views/spaces/_SpaceCreateMenu.scss b/res/css/views/spaces/_SpaceCreateMenu.scss index 097b2b648e..41536bc8b1 100644 --- a/res/css/views/spaces/_SpaceCreateMenu.scss +++ b/res/css/views/spaces/_SpaceCreateMenu.scss @@ -41,7 +41,6 @@ $spacePanelWidth: 71px; > p { font-size: $font-15px; color: $secondary-fg-color; - margin: 0; } .mx_SpaceFeedbackPrompt { @@ -51,13 +50,6 @@ $spacePanelWidth: 71px; } } - // XXX remove this when spaces leaves Beta - .mx_BetaCard_betaPill { - position: absolute; - top: 24px; - right: 24px; - } - .mx_SpaceCreateMenuType { @mixin SpacePillButton; } @@ -100,6 +92,11 @@ $spacePanelWidth: 71px; width: min-content; } + .mx_AccessibleButton_kind_link { + padding: 0; + font-size: inherit; + } + .mx_AccessibleButton_disabled { cursor: not-allowed; } diff --git a/src/components/views/spaces/SpaceCreateMenu.tsx b/src/components/views/spaces/SpaceCreateMenu.tsx index ef70deb672..9921194b39 100644 --- a/src/components/views/spaces/SpaceCreateMenu.tsx +++ b/src/components/views/spaces/SpaceCreateMenu.tsx @@ -35,6 +35,9 @@ import SdkConfig from "../../../SdkConfig"; import Modal from "../../../Modal"; import GenericFeatureFeedbackDialog from "../dialogs/GenericFeatureFeedbackDialog"; import SettingsStore from "../../../settings/SettingsStore"; +import defaultDispatcher from "../../../dispatcher/dispatcher"; +import { Action } from "../../../dispatcher/actions"; +import { UserTab } from "../dialogs/UserSettingsDialog"; export const createSpace = async ( name: string, @@ -245,10 +248,23 @@ const SpaceCreateMenu = ({ onFinished }) => { let body; if (visibility === null) { + const onCreateSpaceFromCommunityClick = () => { + defaultDispatcher.dispatch({ + action: Action.ViewUserSettings, + initialTabId: UserTab.Preferences, + }); + onFinished(); + }; + body =

    { _t("Create a space") }

    -

    { _t("Spaces are a new way to group rooms and people. " + - "To join an existing space you'll need an invite.") }

    +

    + { _t("Spaces are a new way to group rooms and people.") } +   + { _t("What kind of Space do you want to create?") } +   + { _t("You can change this later.") } +

    { onClick={() => setVisibility(Visibility.Private)} /> -

    { _t("You can change this later") }

    +

    + { _t("You can also create a Space from a community.", {}, { + a: sub => + { sub } + , + }) } +   + { _t("To join an existing space you'll need an invite") } +

    ; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index a725cdaff7..f41a0dccc2 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1027,12 +1027,14 @@ "e.g. my-space": "e.g. my-space", "Address": "Address", "Create a space": "Create a space", - "Spaces are a new way to group rooms and people. To join an existing space you'll need an invite.": "Spaces are a new way to group rooms and people. To join an existing space you'll need an invite.", + "What kind of Space do you want to create?": "What kind of Space do you want to create?", + "You can change this later.": "You can change this later.", "Public": "Public", "Open space for anyone, best for communities": "Open space for anyone, best for communities", "Private": "Private", "Invite only, best for yourself or teams": "Invite only, best for yourself or teams", - "You can change this later": "You can change this later", + "You can also create a Space from a community.": "You can also create a Space from a community.", + "To join an existing space you'll need an invite": "To join an existing space you'll need an invite", "Go back": "Go back", "Your public space": "Your public space", "Your private space": "Your private space", From c872609ed315784571c85dd38d544dd41b1056b0 Mon Sep 17 00:00:00 2001 From: Dariusz Niemczyk Date: Tue, 10 Aug 2021 18:38:38 +0200 Subject: [PATCH 34/85] Modify encrypted images loading for better UX Adds proper react css transition to encrypted images placeholders, so that we can animate it's entrance and exit properly. In addition, adds simple css animations file to properly handle all of the animations in one place, so that it's much easier to properly handle prefers-reduced-motion media query. --- res/css/_animations.scss | 57 ++++++++++++++++ res/css/_common.scss | 1 + res/css/views/messages/_MImageBody.scss | 11 +++- src/components/views/messages/MImageBody.tsx | 68 +++++++++++++------- 4 files changed, 113 insertions(+), 24 deletions(-) create mode 100644 res/css/_animations.scss diff --git a/res/css/_animations.scss b/res/css/_animations.scss new file mode 100644 index 0000000000..2e6eac66a1 --- /dev/null +++ b/res/css/_animations.scss @@ -0,0 +1,57 @@ +/* +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. +*/ + +/** + * React Transition Group animations are prefixed with 'mx_rtg--' so that we + * know they should not be used anywhere outside of React Transition Groups. +*/ + +.mx_rtg--fade { + &-enter { + opacity: 0; + &-active { + opacity: 1; + transition: opacity 300ms ease; + } + } + &-exit { + opacity: 1; + &-active { + opacity: 0; + transition: opacity 300ms ease; + } + } +} + + +@keyframes mx--anim-pulse { + 0% { opacity: 1; } + 50% { opacity: 0.7; } + 100% { opacity: 1; } +} + + +@media (prefers-reduced-motion) { + @keyframes mx--anim-pulse { + // Override all keyframes in reduced-motion + } + .mx_rtg--fade-enter-active { + transition: none; + } + .mx_rtg--fade-exit-active { + transition: none; + } +} diff --git a/res/css/_common.scss b/res/css/_common.scss index 6b4e109b3a..fa925eba5b 100644 --- a/res/css/_common.scss +++ b/res/css/_common.scss @@ -18,6 +18,7 @@ limitations under the License. @import "./_font-sizes.scss"; @import "./_font-weights.scss"; +@import "./_animations.scss"; $hover-transition: 0.08s cubic-bezier(.46, .03, .52, .96); // quadratic diff --git a/res/css/views/messages/_MImageBody.scss b/res/css/views/messages/_MImageBody.scss index a748435cd8..765c74a36d 100644 --- a/res/css/views/messages/_MImageBody.scss +++ b/res/css/views/messages/_MImageBody.scss @@ -16,6 +16,12 @@ limitations under the License. $timelineImageBorderRadius: 4px; +.mx_MImageBody_thumbnail--blurhash { + position: absolute; + left: 0; + top: 0; +} + .mx_MImageBody_thumbnail { object-fit: contain; border-radius: $timelineImageBorderRadius; @@ -23,8 +29,11 @@ $timelineImageBorderRadius: 4px; display: flex; justify-content: center; align-items: center; + height: 100%; + width: 100%; - > div > canvas { + .mx_Blurhash > canvas { + animation: mx--anim-pulse 1.75s infinite cubic-bezier(.4, 0, .6, 1); border-radius: $timelineImageBorderRadius; } } diff --git a/src/components/views/messages/MImageBody.tsx b/src/components/views/messages/MImageBody.tsx index 4bf6b6fe14..5cc94d2617 100644 --- a/src/components/views/messages/MImageBody.tsx +++ b/src/components/views/messages/MImageBody.tsx @@ -25,12 +25,14 @@ import SettingsStore from "../../../settings/SettingsStore"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import InlineSpinner from '../elements/InlineSpinner'; import { replaceableComponent } from "../../../utils/replaceableComponent"; -import { mediaFromContent } from "../../../customisations/Media"; +import { Media, mediaFromContent } from "../../../customisations/Media"; import { BLURHASH_FIELD } from "../../../ContentMessages"; import { IMediaEventContent } from '../../../customisations/models/IMediaEventContent'; import ImageView from '../elements/ImageView'; import { SyncState } from 'matrix-js-sdk/src/sync.api'; import { IBodyProps } from "./IBodyProps"; +import classNames from 'classnames'; +import { CSSTransition, SwitchTransition } from 'react-transition-group'; interface IState { decryptedUrl?: string; @@ -157,19 +159,21 @@ export default class MImageBody extends React.Component { // this is only used as a fallback in case content.info.w/h is missing loadedImageDimensions = { naturalWidth, naturalHeight }; } - this.setState({ imgLoaded: true, loadedImageDimensions }); }; protected getContentUrl(): string { - const media = mediaFromContent(this.props.mxEvent.getContent()); - if (media.isEncrypted) { + if (this.media.isEncrypted) { return this.state.decryptedUrl; } else { - return media.srcHttp; + return this.media.srcHttp; } } + private get media(): Media { + return mediaFromContent(this.props.mxEvent.getContent()); + } + protected getThumbUrl(): string { // FIXME: we let images grow as wide as you like, rather than capped to 800x600. // So either we need to support custom timeline widths here, or reimpose the cap, otherwise the @@ -225,7 +229,7 @@ export default class MImageBody extends React.Component { info.w > thumbWidth || info.h > thumbHeight ); - const isLargeFileSize = info.size > 1*1024*1024; // 1mb + const isLargeFileSize = info.size > 1 * 1024 * 1024; // 1mb if (isLargeFileSize && isLargerThanThumbnail) { // image is too large physically and bytewise to clutter our timeline so @@ -374,23 +378,39 @@ export default class MImageBody extends React.Component { gifLabel =

    GIF

    ; } + const classes = classNames({ + 'mx_MImageBody_thumbnail': true, + 'mx_MImageBody_thumbnail--blurhash': this.props.mxEvent.getContent().info[BLURHASH_FIELD], + }); + + // This has incredibly broken types. + const C = CSSTransition as any; const thumbnail = (
    - { showPlaceholder && -
    + - { placeholder } -
    - } +
    + { showPlaceholder &&
    + { placeholder } +
    } +
    + +
    { img } { gifLabel } @@ -413,7 +433,7 @@ export default class MImageBody extends React.Component { // Overidden by MStickerBody protected getPlaceholder(width: number, height: number): JSX.Element { const blurhash = this.props.mxEvent.getContent().info[BLURHASH_FIELD]; - if (blurhash) return ; + if (blurhash) return ; return ( ); @@ -455,10 +475,12 @@ export default class MImageBody extends React.Component { const thumbnail = this.messageContent(contentUrl, thumbUrl, content); const fileBody = this.getFileBody(); - return
    - { thumbnail } - { fileBody } -
    ; + return ( +
    + { thumbnail } + { fileBody } +
    + ); } } From ef3737d1797fec7ea6e9389d1b7afd726380043b Mon Sep 17 00:00:00 2001 From: Dariusz Niemczyk Date: Wed, 11 Aug 2021 10:52:22 +0200 Subject: [PATCH 35/85] Add full class names to animations.scss --- res/css/_animations.scss | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/res/css/_animations.scss b/res/css/_animations.scss index 2e6eac66a1..4d3ad97141 100644 --- a/res/css/_animations.scss +++ b/res/css/_animations.scss @@ -19,21 +19,19 @@ limitations under the License. * know they should not be used anywhere outside of React Transition Groups. */ -.mx_rtg--fade { - &-enter { - opacity: 0; - &-active { - opacity: 1; - transition: opacity 300ms ease; - } - } - &-exit { - opacity: 1; - &-active { - opacity: 0; - transition: opacity 300ms ease; - } - } +.mx_rtg--fade-enter { + opacity: 0; +} +.mx_rtg--fade-enter-active { + opacity: 1; + transition: opacity 300ms ease; +} +.mx_rtg--fade-exit { + opacity: 1; +} +.mx_rtg--fade-exit-active { + opacity: 0; + transition: opacity 300ms ease; } From 723400ace6431c44835f7b39abfd5c11f66f6002 Mon Sep 17 00:00:00 2001 From: Dariusz Niemczyk Date: Wed, 11 Aug 2021 10:56:36 +0200 Subject: [PATCH 36/85] Add a comment for weirdly placed div --- src/components/views/messages/MImageBody.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/views/messages/MImageBody.tsx b/src/components/views/messages/MImageBody.tsx index 5cc94d2617..e7b77b731f 100644 --- a/src/components/views/messages/MImageBody.tsx +++ b/src/components/views/messages/MImageBody.tsx @@ -393,6 +393,7 @@ export default class MImageBody extends React.Component { key={`img-${showPlaceholder}`} timeout={300} > + { /* This weirdly looking div is necessary here, otherwise SwitchTransition fails */ }
    { showPlaceholder &&
    Date: Wed, 11 Aug 2021 15:50:45 +0100 Subject: [PATCH 37/85] Changelog --- CHANGELOG.md | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d65a524d1..a49685839f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,66 @@ +Changes in [3.28.0-rc.1](https://github.com/vector-im/element-desktop/releases/tag/v3.28.0-rc.1) (2021-07-11) +============================================================================================================= + +## ✨ Features + * Show how long a call was on call tiles ([\#6570](https://github.com/matrix-org/matrix-react-sdk/pull/6570)). Fixes vector-im/element-web#18405. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Add regional indicators to emoji picker ([\#6490](https://github.com/matrix-org/matrix-react-sdk/pull/6490)). Fixes vector-im/element-web#14963. Contributed by [robintown](https://github.com/robintown). + * Make call control buttons accessible to screen reader users ([\#6181](https://github.com/matrix-org/matrix-react-sdk/pull/6181)). Fixes vector-im/element-web#18358. Contributed by [pvagner](https://github.com/pvagner). + * Skip sending a thumbnail if it is not a sufficient saving over the original ([\#6559](https://github.com/matrix-org/matrix-react-sdk/pull/6559)). Fixes vector-im/element-web#17906. + * Increase PiP snapping speed ([\#6539](https://github.com/matrix-org/matrix-react-sdk/pull/6539)). Fixes vector-im/element-web#18371. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Improve and move the incoming call toast ([\#6470](https://github.com/matrix-org/matrix-react-sdk/pull/6470)). Fixes vector-im/element-web#17912. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Allow all of the URL schemes that Firefox allows ([\#6457](https://github.com/matrix-org/matrix-react-sdk/pull/6457)). Contributed by [aaronraimist](https://github.com/aaronraimist). + * Improve bubble layout colors ([\#6452](https://github.com/matrix-org/matrix-react-sdk/pull/6452)). Fixes vector-im/element-web#18081. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Spaces let users switch between Home and All Rooms behaviours ([\#6497](https://github.com/matrix-org/matrix-react-sdk/pull/6497)). Fixes vector-im/element-web#18093. + * Support for MSC2285 (hidden read receipts) ([\#6390](https://github.com/matrix-org/matrix-react-sdk/pull/6390)). Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Group pinned message events with MELS ([\#6349](https://github.com/matrix-org/matrix-react-sdk/pull/6349)). Fixes vector-im/element-web#17938. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Make version copiable ([\#6227](https://github.com/matrix-org/matrix-react-sdk/pull/6227)). Fixes vector-im/element-web#17603 and vector-im/element-web#18329. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Improve voice messages uploading state ([\#6530](https://github.com/matrix-org/matrix-react-sdk/pull/6530)). Fixes vector-im/element-web#18226 and vector-im/element-web#18224. + * Add surround with feature ([\#5510](https://github.com/matrix-org/matrix-react-sdk/pull/5510)). Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Improve call event tile wording ([\#6545](https://github.com/matrix-org/matrix-react-sdk/pull/6545)). Fixes vector-im/element-web#18376. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Show an avatar/a turned off microphone icon for muted users ([\#6486](https://github.com/matrix-org/matrix-react-sdk/pull/6486)). Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Prompt user to leave rooms/subspaces in a space when leaving space ([\#6424](https://github.com/matrix-org/matrix-react-sdk/pull/6424)). Fixes vector-im/element-web#18071. + * Add customisation point to override widget variables ([\#6455](https://github.com/matrix-org/matrix-react-sdk/pull/6455)). Fixes vector-im/element-web#18035. + * Add support for screen sharing in 1:1 calls ([\#5992](https://github.com/matrix-org/matrix-react-sdk/pull/5992)). Contributed by [SimonBrandner](https://github.com/SimonBrandner). + +## 🐛 Bug Fixes + * Fix [object Object] in Widget Permissions ([\#6560](https://github.com/matrix-org/matrix-react-sdk/pull/6560)). Fixes vector-im/element-web#18384. Contributed by [Palid](https://github.com/Palid). + * Fix right margin for events on IRC layout ([\#6542](https://github.com/matrix-org/matrix-react-sdk/pull/6542)). Fixes vector-im/element-web#18354. + * Mirror only usermedia feeds ([\#6512](https://github.com/matrix-org/matrix-react-sdk/pull/6512)). Fixes vector-im/element-web#5633. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Fix LogoutDialog warning + TypeScript migration ([\#6533](https://github.com/matrix-org/matrix-react-sdk/pull/6533)). + * Fix the wrong font being used in the room topic field ([\#6527](https://github.com/matrix-org/matrix-react-sdk/pull/6527)). Fixes vector-im/element-web#18339. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Fix inconsistent styling for links on hover ([\#6513](https://github.com/matrix-org/matrix-react-sdk/pull/6513)). Contributed by [janogarcia](https://github.com/janogarcia). + * Fix incorrect height for encoded placeholder images ([\#6514](https://github.com/matrix-org/matrix-react-sdk/pull/6514)). Contributed by [Palid](https://github.com/Palid). + * Fix call events layout for message bubble ([\#6465](https://github.com/matrix-org/matrix-react-sdk/pull/6465)). Fixes vector-im/element-web#18144. + * Improve subspaces and some utilities around room/space creation ([\#6458](https://github.com/matrix-org/matrix-react-sdk/pull/6458)). Fixes vector-im/element-web#18090 vector-im/element-web#18091 and vector-im/element-web#17256. + * Restore pointer cursor for SenderProfile in message bubbles ([\#6501](https://github.com/matrix-org/matrix-react-sdk/pull/6501)). Fixes vector-im/element-web#18249. + * Fix issues with the Call View ([\#6472](https://github.com/matrix-org/matrix-react-sdk/pull/6472)). Fixes vector-im/element-web#18221. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Align event list summary read receipts when using message bubbles ([\#6500](https://github.com/matrix-org/matrix-react-sdk/pull/6500)). Fixes vector-im/element-web#18143. + * Better positioning for unbubbled events in timeline ([\#6477](https://github.com/matrix-org/matrix-react-sdk/pull/6477)). Fixes vector-im/element-web#18132. + * Realign reactions row with messages in modern layout ([\#6491](https://github.com/matrix-org/matrix-react-sdk/pull/6491)). Fixes vector-im/element-web#18118. Contributed by [robintown](https://github.com/robintown). + * Fix CreateRoomDialog exploding when making public room outside of a space ([\#6492](https://github.com/matrix-org/matrix-react-sdk/pull/6492)). Fixes vector-im/element-web#18275. + * Fix call crashing because `element` was undefined ([\#6488](https://github.com/matrix-org/matrix-react-sdk/pull/6488)). Fixes vector-im/element-web#18270. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Upscale thumbnails to the container size ([\#6589](https://github.com/matrix-org/matrix-react-sdk/pull/6589)). Fixes vector-im/element-web#18307. + * Fix create room dialog in spaces no longer adding to the space ([\#6587](https://github.com/matrix-org/matrix-react-sdk/pull/6587)). Fixes vector-im/element-web#18465. + * Don't show a modal on call reject/user hangup ([\#6580](https://github.com/matrix-org/matrix-react-sdk/pull/6580)). Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Fade Call View Buttons after `componentDidMount` ([\#6581](https://github.com/matrix-org/matrix-react-sdk/pull/6581)). Fixes vector-im/element-web#18439. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Fix missing expand button on codeblocks ([\#6565](https://github.com/matrix-org/matrix-react-sdk/pull/6565)). Fixes vector-im/element-web#18388. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * allow customizing the bubble layout colors ([\#6568](https://github.com/matrix-org/matrix-react-sdk/pull/6568)). Fixes vector-im/element-web#18408. Contributed by [benneti](https://github.com/benneti). + * Don't flash "Missed call" when accepting a call ([\#6567](https://github.com/matrix-org/matrix-react-sdk/pull/6567)). Fixes vector-im/element-web#18404. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Fix clicking whitespaces on replies ([\#6571](https://github.com/matrix-org/matrix-react-sdk/pull/6571)). Fixes vector-im/element-web#18327. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Fix disabled state for voice messages + send button tooltip ([\#6562](https://github.com/matrix-org/matrix-react-sdk/pull/6562)). Fixes vector-im/element-web#18413. + * Fix voice feed being cut-off ([\#6550](https://github.com/matrix-org/matrix-react-sdk/pull/6550)). Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Fix sizing issues of the screen picker ([\#6498](https://github.com/matrix-org/matrix-react-sdk/pull/6498)). Fixes vector-im/element-web#18281. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Stop voice messages that are playing when starting a recording ([\#6563](https://github.com/matrix-org/matrix-react-sdk/pull/6563)). Fixes vector-im/element-web#18410. + * Properly set style attribute on shared usercontent iframe ([\#6561](https://github.com/matrix-org/matrix-react-sdk/pull/6561)). Fixes vector-im/element-web#18414. + * Null guard space inviter to prevent the app exploding ([\#6558](https://github.com/matrix-org/matrix-react-sdk/pull/6558)). + * Make the ringing sound mutable/disablable ([\#6534](https://github.com/matrix-org/matrix-react-sdk/pull/6534)). Fixes vector-im/element-web#15591. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Fix wrong cursor being used in PiP ([\#6551](https://github.com/matrix-org/matrix-react-sdk/pull/6551)). Fixes vector-im/element-web#18383. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Re-pin Jitsi if the widget already exists ([\#6226](https://github.com/matrix-org/matrix-react-sdk/pull/6226)). Fixes vector-im/element-web#17679. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Fix broken call notification regression ([\#6526](https://github.com/matrix-org/matrix-react-sdk/pull/6526)). Fixes vector-im/element-web#18335. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * createRoom, only send join rule event if we have a join rule to put in it ([\#6516](https://github.com/matrix-org/matrix-react-sdk/pull/6516)). Fixes vector-im/element-web#18301. + * Fix clicking pills inside replies ([\#6508](https://github.com/matrix-org/matrix-react-sdk/pull/6508)). Fixes vector-im/element-web#18283. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Fix grecaptcha regression ([\#6503](https://github.com/matrix-org/matrix-react-sdk/pull/6503)). Fixes vector-im/element-web#18284. Contributed by [Palid](https://github.com/Palid). + Changes in [3.27.0](https://github.com/vector-im/element-desktop/releases/tag/v3.27.0) (2021-07-02) =================================================================================================== From 15731848adfdfcc4cfb72678146eac7db5c03674 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Wed, 11 Aug 2021 16:00:43 +0100 Subject: [PATCH 38/85] Upgrade matrix-js-sdk to 12.3.0-rc.1 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 2445e3c973..47fa616d0b 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "katex": "^0.12.0", "linkifyjs": "^2.1.9", "lodash": "^4.17.20", - "matrix-js-sdk": "12.2.0", + "matrix-js-sdk": "12.3.0-rc.1", "matrix-widget-api": "^0.1.0-beta.15", "minimist": "^1.2.5", "opus-recorder": "^8.0.3", diff --git a/yarn.lock b/yarn.lock index a780d1ffa0..5e1699a696 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5686,10 +5686,10 @@ mathml-tag-names@^2.1.3: resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3" integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg== -matrix-js-sdk@12.2.0: - version "12.2.0" - resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-12.2.0.tgz#e1dc7ddac054289cb24ee3d11dba8a5ba5ddecf5" - integrity sha512-foSs3uKRc6uvFNhgY35eErBvLWVDd5RNIxxsdFKlmU3B+70YUf3BP3petyBNW34ORyOqNdX36IiApfLo3npNEw== +matrix-js-sdk@12.3.0-rc.1: + version "12.3.0-rc.1" + resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-12.3.0-rc.1.tgz#9e192f7bffb08d68deb1298e46082eda3273c6ee" + integrity sha512-sqvc5+y289qnoqFvq32XDiWFu3NPKOmmTTV6fB+OdtkP3VvU8In+6sXYWqY3Xz7ZD4nY+At6AijQmDPn2seJwQ== dependencies: "@babel/runtime" "^7.12.5" another-json "^0.2.0" From 429befac62ffc0b491e92083f8527d9f6d593632 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Wed, 11 Aug 2021 16:02:12 +0100 Subject: [PATCH 39/85] v3.28.0-rc.1 --- package.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 47fa616d0b..0fa143b83b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "3.27.0", + "version": "3.28.0-rc.1", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { @@ -25,7 +25,7 @@ "bin": { "reskindex": "scripts/reskindex.js" }, - "main": "./src/index.js", + "main": "./lib/index.js", "matrix_src_main": "./src/index.js", "matrix_lib_main": "./lib/index.js", "matrix_lib_typings": "./lib/index.d.ts", @@ -205,5 +205,6 @@ "coverageReporters": [ "text" ] - } + }, + "typings": "./lib/index.d.ts" } From 54d85a3864b8d8fedd7da859df8768c3f77f9c8f Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 11 Aug 2021 16:32:48 +0100 Subject: [PATCH 40/85] Iterate PR based on feedback --- res/css/views/dialogs/_CreateSpaceFromCommunityDialog.scss | 4 ++++ .../views/dialogs/CreateSpaceFromCommunityDialog.tsx | 5 +++-- src/components/views/spaces/SpaceCreateMenu.tsx | 4 ++-- src/i18n/strings/en_EN.json | 5 +++-- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/res/css/views/dialogs/_CreateSpaceFromCommunityDialog.scss b/res/css/views/dialogs/_CreateSpaceFromCommunityDialog.scss index b011fb104a..306fc77011 100644 --- a/res/css/views/dialogs/_CreateSpaceFromCommunityDialog.scss +++ b/res/css/views/dialogs/_CreateSpaceFromCommunityDialog.scss @@ -50,6 +50,10 @@ limitations under the License. line-height: $font-15px; margin: 16px 0; } + + .mx_Field_textarea { + margin-bottom: 0; + } } .mx_JoinRuleDropdown .mx_Dropdown_menu { diff --git a/src/components/views/dialogs/CreateSpaceFromCommunityDialog.tsx b/src/components/views/dialogs/CreateSpaceFromCommunityDialog.tsx index 335be80a70..c9e13d2b61 100644 --- a/src/components/views/dialogs/CreateSpaceFromCommunityDialog.tsx +++ b/src/components/views/dialogs/CreateSpaceFromCommunityDialog.tsx @@ -288,8 +288,9 @@ const CreateSpaceFromCommunityDialog: React.FC = ({ matrixClient: cli, g

    { _t("Spaces are the new version of communities - with new features coming.") }   - { _t("All rooms will automatically be added, a link to the Space will be " + - "added to your old community description and all community members will be invited.") } + { _t("A link to the Space will be put in your community description.") } +   + { _t("All rooms will be added and all community members will be invited.") }

    { _t("Flair won't be available in Spaces for the foreseeable future.") } diff --git a/src/components/views/spaces/SpaceCreateMenu.tsx b/src/components/views/spaces/SpaceCreateMenu.tsx index 9921194b39..33e4a990ef 100644 --- a/src/components/views/spaces/SpaceCreateMenu.tsx +++ b/src/components/views/spaces/SpaceCreateMenu.tsx @@ -285,8 +285,8 @@ const SpaceCreateMenu = ({ onFinished }) => { { sub } , }) } -   - { _t("To join an existing space you'll need an invite") } +
    + { _t("To join an existing space you'll need an invite.") }

    diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index f41a0dccc2..3b5a748f89 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1034,7 +1034,7 @@ "Private": "Private", "Invite only, best for yourself or teams": "Invite only, best for yourself or teams", "You can also create a Space from a community.": "You can also create a Space from a community.", - "To join an existing space you'll need an invite": "To join an existing space you'll need an invite", + "To join an existing space you'll need an invite.": "To join an existing space you'll need an invite.", "Go back": "Go back", "Your public space": "Your public space", "Your private space": "Your private space", @@ -2238,7 +2238,8 @@ "Failed to migrate community": "Failed to migrate community", "Create Space from community": "Create Space from community", "Spaces are the new version of communities - with new features coming.": "Spaces are the new version of communities - with new features coming.", - "All rooms will automatically be added, a link to the Space will be added to your old community description and all community members will be invited.": "All rooms will automatically be added, a link to the Space will be added to your old community description and all community members will be invited.", + "A link to the Space will be put in your community description.": "A link to the Space will be put in your community description.", + "All rooms will be added and all community members will be invited.": "All rooms will be added and all community members will be invited.", "Flair won't be available in Spaces for the foreseeable future.": "Flair won't be available in Spaces for the foreseeable future.", "This description will be shown to people when they view your space": "This description will be shown to people when they view your space", "Space visibility": "Space visibility", From 5bc165f2ac1475c0536f1867e4e35d2a56603b74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 11 Aug 2021 18:33:16 +0200 Subject: [PATCH 41/85] Make scrollbar dot transparent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/rooms/_EventTile.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 1c9d8e87d9..56cede0895 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -489,6 +489,10 @@ $hover-select-border: 4px; // https://github.com/vector-im/vector-web/issues/754 overflow-x: overlay; overflow-y: visible; + + &::-webkit-scrollbar-corner { + background: transparent; + } } } From 012f2c9e7e9702d10bf2cda96b3d897fc796cd4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 11 Aug 2021 19:16:55 +0200 Subject: [PATCH 42/85] This doesn't need to be here as it was moved into CallViewButtons 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 | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index fbb69a51ec..a6ae71713b 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -135,7 +135,6 @@ export default class CallView extends React.Component { public componentDidMount() { this.dispatcherRef = dis.register(this.onAction); document.addEventListener('keydown', this.onNativeKeyDown); - this.showControls(); } public componentWillUnmount() { From cf8ee19e23e494e834b25c82ef7e3c2c3b83c13c Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 11 Aug 2021 19:25:17 +0100 Subject: [PATCH 43/85] Fix Netflify builds from fork PRs Some absolutely horrenous hacks to upload the context as an artifact then download it, unzip it and set the PR number as a variable we can use, because GitHub Actions just doesn't offer any other way of doing this. Maybe we'd be better off going back to Netlify... --- .github/workflows/layered-build.yaml | 12 ++++++++++++ .github/workflows/netflify.yaml | 25 +++++++++++++++++++++++-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/.github/workflows/layered-build.yaml b/.github/workflows/layered-build.yaml index 7235b4020f..1474338a16 100644 --- a/.github/workflows/layered-build.yaml +++ b/.github/workflows/layered-build.yaml @@ -16,4 +16,16 @@ jobs: path: element-web/webapp # We'll only use this in a triggered job, then we're done with it retention-days: 1 + - uses: actions/github-script@v3.1.0 + with: + script: | + var fs = require('fs'); + fs.writeFileSync('${{github.workspace}}/context.json', JSON.stringify(context)); + - name: Upload Context + uses: actions/upload-artifact@v2 + with: + name: context.json + path: context.json + # We'll only use this in a triggered job, then we're done with it + retention-days: 1 diff --git a/.github/workflows/netflify.yaml b/.github/workflows/netflify.yaml index ccccfe7e07..007488cae9 100644 --- a/.github/workflows/netflify.yaml +++ b/.github/workflows/netflify.yaml @@ -33,7 +33,28 @@ jobs: }); var fs = require('fs'); fs.writeFileSync('${{github.workspace}}/previewbuild.zip', Buffer.from(download.data)); - - run: unzip previewbuild.zip && rm previewbuild.zip + + var contextArtifact = artifacts.data.artifacts.filter((artifact) => { + return artifact.name == "context.json" + })[0]; + var download = await github.actions.downloadArtifact({ + owner: context.repo.owner, + repo: context.repo.repo, + artifact_id: contextArtifact.id, + archive_format: 'zip', + }); + var fs = require('fs'); + fs.writeFileSync('${{github.workspace}}/context.json.zip', Buffer.from(download.data)); + - name: Extract Artifacts + run: unzip -d webapp previewbuild.zip && rm previewbuild.zip && unzip context.json && rm context.json.zip + - name: 'Read Context' + id: readctx + uses: actions/github-script@v3.1.0 + with: + script: | + var fs = require('fs'); + var ctx = JSON.parse(fs.readFileSync('${{github.workspace}}/context.json')); + console.log(`::set-output name=prnumber::${ctx.payload.pull_request.number}`); - name: Deploy to Netlify id: netlify uses: nwtgck/actions-netlify@v1.2 @@ -51,7 +72,7 @@ jobs: uses: phulsechinmay/rewritable-pr-comment@v0.3.0 with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - ISSUE_ID: ${{ github.event.workflow_run.pull_requests[0].number }} + ISSUE_ID: ${{ steps.readctx.outputs.prnumber }} message: | Preview: ${{ steps.netlify.outputs.deploy-url }} ⚠️ Do you trust the author of this PR? Maybe this build will steal your keys or give you malware. Exercise caution. Use test accounts. From bbdee0d83bf83288559568079c00532344ea5168 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 11 Aug 2021 19:41:37 +0100 Subject: [PATCH 44/85] publish the right directory --- .github/workflows/netflify.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/netflify.yaml b/.github/workflows/netflify.yaml index 007488cae9..444333fdfb 100644 --- a/.github/workflows/netflify.yaml +++ b/.github/workflows/netflify.yaml @@ -59,7 +59,7 @@ jobs: id: netlify uses: nwtgck/actions-netlify@v1.2 with: - publish-dir: . + publish-dir: webapp deploy-message: "Deploy from GitHub Actions" # These don't work because we're in workflow_run enable-pull-request-comment: false From 1fe5ace8edfe57d82f988e1b088deb3cedb7414d Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 11 Aug 2021 21:10:22 +0100 Subject: [PATCH 45/85] Edit PR Description instead of commenting We could include the magic comments in the PR template so the various automated comments were always in the same order, if we wanted. --- .github/workflows/netflify.yaml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/netflify.yaml b/.github/workflows/netflify.yaml index 444333fdfb..d9ec1f842d 100644 --- a/.github/workflows/netflify.yaml +++ b/.github/workflows/netflify.yaml @@ -68,12 +68,13 @@ jobs: NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} timeout-minutes: 1 - - name: Comment on PR - uses: phulsechinmay/rewritable-pr-comment@v0.3.0 - with: + - name: Edit PR Description + uses: velas/pr-description@v1.0.1 + env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - ISSUE_ID: ${{ steps.readctx.outputs.prnumber }} - message: | + with: + pull-request-number: ${{ steps.readctx.outputs.prnumber }} + description-message: | Preview: ${{ steps.netlify.outputs.deploy-url }} ⚠️ Do you trust the author of this PR? Maybe this build will steal your keys or give you malware. Exercise caution. Use test accounts. From 8016b340b00d7a1ca264b82db2adf43f64bfe843 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 11 Aug 2021 21:20:28 +0100 Subject: [PATCH 46/85] Just upload the PR object itself We don't know what secret info might end up in the context --- .github/workflows/layered-build.yaml | 8 ++++---- .github/workflows/netflify.yaml | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/layered-build.yaml b/.github/workflows/layered-build.yaml index 1474338a16..c9d7e89a75 100644 --- a/.github/workflows/layered-build.yaml +++ b/.github/workflows/layered-build.yaml @@ -20,12 +20,12 @@ jobs: with: script: | var fs = require('fs'); - fs.writeFileSync('${{github.workspace}}/context.json', JSON.stringify(context)); - - name: Upload Context + fs.writeFileSync('${{github.workspace}}/pr.json', JSON.stringify(context.payload.pull_request)); + - name: Upload PR Info uses: actions/upload-artifact@v2 with: - name: context.json - path: context.json + name: pr.json + path: pr.json # We'll only use this in a triggered job, then we're done with it retention-days: 1 diff --git a/.github/workflows/netflify.yaml b/.github/workflows/netflify.yaml index 444333fdfb..3cb4543820 100644 --- a/.github/workflows/netflify.yaml +++ b/.github/workflows/netflify.yaml @@ -34,27 +34,27 @@ jobs: var fs = require('fs'); fs.writeFileSync('${{github.workspace}}/previewbuild.zip', Buffer.from(download.data)); - var contextArtifact = artifacts.data.artifacts.filter((artifact) => { - return artifact.name == "context.json" + var prInfoArtifact = artifacts.data.artifacts.filter((artifact) => { + return artifact.name == "pr.json" })[0]; var download = await github.actions.downloadArtifact({ owner: context.repo.owner, repo: context.repo.repo, - artifact_id: contextArtifact.id, + artifact_id: prInfoArtifact.id, archive_format: 'zip', }); var fs = require('fs'); - fs.writeFileSync('${{github.workspace}}/context.json.zip', Buffer.from(download.data)); + fs.writeFileSync('${{github.workspace}}/pr.json.zip', Buffer.from(download.data)); - name: Extract Artifacts - run: unzip -d webapp previewbuild.zip && rm previewbuild.zip && unzip context.json && rm context.json.zip - - name: 'Read Context' + run: unzip -d webapp previewbuild.zip && rm previewbuild.zip && unzip pr.json.zip && rm pr.json.zip + - name: 'Read PR Info' id: readctx uses: actions/github-script@v3.1.0 with: script: | var fs = require('fs'); - var ctx = JSON.parse(fs.readFileSync('${{github.workspace}}/context.json')); - console.log(`::set-output name=prnumber::${ctx.payload.pull_request.number}`); + var pr = JSON.parse(fs.readFileSync('${{github.workspace}}/pr.json')); + console.log(`::set-output name=prnumber::${pr.number}`); - name: Deploy to Netlify id: netlify uses: nwtgck/actions-netlify@v1.2 From 5d98c6f02dab67eeb61b363d4b5dafac79e89776 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 11 Aug 2021 21:45:49 +0100 Subject: [PATCH 47/85] Iterate PR based on feedback --- src/components/structures/InteractiveAuth.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/InteractiveAuth.tsx b/src/components/structures/InteractiveAuth.tsx index cea756adc0..869cd29cba 100644 --- a/src/components/structures/InteractiveAuth.tsx +++ b/src/components/structures/InteractiveAuth.tsx @@ -235,7 +235,7 @@ export default class InteractiveAuthComponent extends React.Component Date: Wed, 11 Aug 2021 21:50:25 +0100 Subject: [PATCH 48/85] Use changelog generator from npm --- package.json | 2 +- yarn.lock | 63 +++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 61 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 0fa143b83b..fa1258ef24 100644 --- a/package.json +++ b/package.json @@ -149,7 +149,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", + "allchange": "^0.0.1", "babel-jest": "^26.6.3", "chokidar": "^3.5.1", "concurrently": "^5.3.0", diff --git a/yarn.lock b/yarn.lock index 5e1699a696..d91b504757 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,28 @@ # yarn lockfile v1 +"@actions/core@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@actions/core/-/core-1.4.0.tgz#cf2e6ee317e314b03886adfeb20e448d50d6e524" + integrity sha512-CGx2ilGq5i7zSLgiiGUtBCxhRRxibJYU6Fim0Q1Wg2aQL2LTnF27zbqZOrxfvFQ55eSBW0L8uVStgtKMpa0Qlg== + +"@actions/github@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@actions/github/-/github-5.0.0.tgz#1754127976c50bd88b2e905f10d204d76d1472f8" + integrity sha512-QvE9eAAfEsS+yOOk0cylLBIO/d6WyWIOvsxxzdrPFaud39G6BOkUwScXZn1iBzQzHyu9SBkkLSWlohDWdsasAQ== + dependencies: + "@actions/http-client" "^1.0.11" + "@octokit/core" "^3.4.0" + "@octokit/plugin-paginate-rest" "^2.13.3" + "@octokit/plugin-rest-endpoint-methods" "^5.1.1" + +"@actions/http-client@^1.0.11": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@actions/http-client/-/http-client-1.0.11.tgz#c58b12e9aa8b159ee39e7dd6cbd0e91d905633c0" + integrity sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg== + dependencies: + tunnel "0.0.6" + "@babel/cli@^7.12.10": version "7.14.8" resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.14.8.tgz#fac73c0e2328a8af9fd3560c06b096bfa3730933" @@ -1331,7 +1353,7 @@ dependencies: "@octokit/types" "^6.0.3" -"@octokit/core@^3.5.0": +"@octokit/core@^3.4.0", "@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== @@ -1367,6 +1389,18 @@ resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-9.3.0.tgz#160347858d727527901c6aae7f7d5c2414cc1f2e" integrity sha512-oz60hhL+mDsiOWhEwrj5aWXTOMVtQgcvP+sRzX4C3cH7WOK9QSAoEtjWh0HdOf6V3qpdgAmUMxnQPluzDWR7Fw== +"@octokit/openapi-types@^9.5.0": + version "9.7.0" + resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-9.7.0.tgz#9897cdefd629cd88af67b8dbe2e5fb19c63426b2" + integrity sha512-TUJ16DJU8mekne6+KVcMV5g6g/rJlrnIKn7aALG9QrNpnEipFc1xjoarh0PKaAWf2Hf+HwthRKYt+9mCm5RsRg== + +"@octokit/plugin-paginate-rest@^2.13.3": + version "2.15.1" + resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.15.1.tgz#264189dd3ce881c6c33758824aac05a4002e056a" + integrity sha512-47r52KkhQDkmvUKZqXzA1lKvcyJEfYh3TKAIe5+EzMeyDM3d+/s5v11i2gTk8/n6No6DPi3k5Ind6wtDbo/AEg== + dependencies: + "@octokit/types" "^6.24.0" + "@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" @@ -1387,6 +1421,14 @@ "@octokit/types" "^6.23.0" deprecation "^2.3.1" +"@octokit/plugin-rest-endpoint-methods@^5.1.1": + version "5.8.0" + resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.8.0.tgz#33b342fe41f2603fdf8b958e6652103bb3ea3f3b" + integrity sha512-qeLZZLotNkoq+it6F+xahydkkbnvSK0iDjlXFo3jNTB+Ss0qIbYQb9V/soKLMkgGw8Q2sHjY5YEXiA47IVPp4A== + dependencies: + "@octokit/types" "^6.25.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" @@ -1425,6 +1467,13 @@ dependencies: "@octokit/openapi-types" "^9.3.0" +"@octokit/types@^6.24.0", "@octokit/types@^6.25.0": + version "6.25.0" + resolved "https://registry.yarnpkg.com/@octokit/types/-/types-6.25.0.tgz#c8e37e69dbe7ce55ed98ee63f75054e7e808bf1a" + integrity sha512-bNvyQKfngvAd/08COlYIN54nRgxskmejgywodizQNyiKoXmWRAjKup2/LYwm+T9V0gsKH6tuld1gM0PzmOiB4Q== + dependencies: + "@octokit/openapi-types" "^9.5.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" @@ -1956,10 +2005,13 @@ ajv@^8.0.1: require-from-string "^2.0.2" uri-js "^4.2.2" -"allchange@github:matrix-org/allchange": +allchange@^0.0.1: version "0.0.1" - resolved "https://codeload.github.com/matrix-org/allchange/tar.gz/56b37b06339a3ac3fe771f3ec3d0bff798df8dab" + resolved "https://registry.yarnpkg.com/allchange/-/allchange-0.0.1.tgz#3f66f7b06314b942fd0c1630fef6ce2ac1b90972" + integrity sha512-YVg1ZcYzEE5/fEnexzlhfWIbPeGtjfFgJ73qUG2DNwl16Apt9sRJQE7z6dLbDPaL9tOlYpIgzf7Id74uS+ccjQ== dependencies: + "@actions/core" "^1.4.0" + "@actions/github" "^5.0.0" "@octokit/rest" "^18.6.7" cli-color "^2.0.0" js-yaml "^4.1.0" @@ -7898,6 +7950,11 @@ tunnel-agent@^0.6.0: dependencies: safe-buffer "^5.0.1" +tunnel@0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c" + integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg== + tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" From 69cf64249f4bb3ffdf3f4269b818ac691d424bd4 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 11 Aug 2021 21:50:26 +0100 Subject: [PATCH 49/85] add comment --- src/components/views/dialogs/DeactivateAccountDialog.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/views/dialogs/DeactivateAccountDialog.tsx b/src/components/views/dialogs/DeactivateAccountDialog.tsx index bbed866464..6548bd78fc 100644 --- a/src/components/views/dialogs/DeactivateAccountDialog.tsx +++ b/src/components/views/dialogs/DeactivateAccountDialog.tsx @@ -184,6 +184,8 @@ export default class DeactivateAccountDialog extends React.Component Date: Thu, 12 Aug 2021 08:11:11 +0200 Subject: [PATCH 50/85] Use AccessibleTooltipButton 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/CallViewButtons.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/views/voip/CallView/CallViewButtons.tsx b/src/components/views/voip/CallView/CallViewButtons.tsx index 8c48bd767d..466311f421 100644 --- a/src/components/views/voip/CallView/CallViewButtons.tsx +++ b/src/components/views/voip/CallView/CallViewButtons.tsx @@ -21,7 +21,6 @@ import classNames from "classnames"; import AccessibleTooltipButton from "../../elements/AccessibleTooltipButton"; import CallContextMenu from "../../context_menus/CallContextMenu"; import DialpadContextMenu from "../../context_menus/DialpadContextMenu"; -import AccessibleButton from "../../elements/AccessibleButton"; import { MatrixCall } from "matrix-js-sdk/src/webrtc/call"; import { Alignment } from "../../elements/Tooltip"; import { @@ -211,10 +210,12 @@ export default class CallViewButtons extends React.Component { let sidebarButton; if (this.props.buttonsVisibility.sidebar) { sidebarButton = ( - ); } From 71008c9e87d91651b8cba3fdfd3189f6d2283074 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 12 Aug 2021 08:14:02 +0200 Subject: [PATCH 51/85] Use AccessibleTooltipButton 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 | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index 76e6a43ca5..e56c34520c 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -502,10 +502,12 @@ export default class CallView extends React.Component { ) ) { sidebarButton = ( - ); } From ccaa69b25b8b6916b9bb33284a1fb11e2bf73434 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 12 Aug 2021 08:53:42 +0200 Subject: [PATCH 52/85] Remove unnecessary code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This somehow doesn't seem to be neccessary anymore, I don't really know why but everything seems to work without it Signed-off-by: Šimon Brandner --- .../views/rooms/BasicMessageComposer.tsx | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/src/components/views/rooms/BasicMessageComposer.tsx b/src/components/views/rooms/BasicMessageComposer.tsx index c9b7cf60c3..3f98d5d5e4 100644 --- a/src/components/views/rooms/BasicMessageComposer.tsx +++ b/src/components/views/rooms/BasicMessageComposer.tsx @@ -541,7 +541,6 @@ export default class BasicMessageEditor extends React.Component handled = true; } else if (event.key === Key.BACKSPACE || event.key === Key.DELETE) { this.formatBarRef.current.hide(); - handled = this.fakeDeletion(event.key === Key.BACKSPACE); } if (handled) { @@ -550,29 +549,6 @@ export default class BasicMessageEditor extends React.Component } }; - /** - * Because pills have contentEditable="false" there is no event emitted when - * the user tries to delete them. Therefore we need to fake what would - * normally happen - * @param direction in which to delete - * @returns handled - */ - private fakeDeletion(backward: boolean): boolean { - const selection = document.getSelection(); - // Use the default handling for ranges - if (selection.type === "Range") return false; - - this.modifiedFlag = true; - const { caret, text } = getCaretOffsetAndText(this.editorRef.current, selection); - - // Do the deletion itself - if (backward) caret.offset--; - const newText = text.slice(0, caret.offset) + text.slice(caret.offset + 1); - - this.props.model.update(newText, backward ? "deleteContentBackward" : "deleteContentForward", caret); - return true; - } - private async tabCompleteName(): Promise { try { await new Promise(resolve => this.setState({ showVisualBell: false }, resolve)); From 9d3569a5778b708ecec146226de0fbfd74912527 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 12 Aug 2021 10:58:56 +0100 Subject: [PATCH 53/85] Iterate PR based on feedback --- res/css/structures/_GroupView.scss | 6 ++- .../_CreateSpaceFromCommunityDialog.scss | 4 ++ src/components/structures/GroupView.js | 38 +++++++++++++++++-- .../CreateSpaceFromCommunityDialog.tsx | 5 ++- src/i18n/strings/en_EN.json | 4 +- 5 files changed, 49 insertions(+), 8 deletions(-) diff --git a/res/css/structures/_GroupView.scss b/res/css/structures/_GroupView.scss index 21137d8a12..fb660f4194 100644 --- a/res/css/structures/_GroupView.scss +++ b/res/css/structures/_GroupView.scss @@ -400,7 +400,11 @@ limitations under the License. background-color: $secondary-fg-color; } - .mx_AccessibleButton { + .mx_AccessibleButton_kind_link { + padding: 0; + } + + .mx_GroupView_spaceUpgradePrompt_close { width: 16px; height: 16px; border-radius: 8px; diff --git a/res/css/views/dialogs/_CreateSpaceFromCommunityDialog.scss b/res/css/views/dialogs/_CreateSpaceFromCommunityDialog.scss index 306fc77011..afa722e05e 100644 --- a/res/css/views/dialogs/_CreateSpaceFromCommunityDialog.scss +++ b/res/css/views/dialogs/_CreateSpaceFromCommunityDialog.scss @@ -59,6 +59,10 @@ limitations under the License. .mx_JoinRuleDropdown .mx_Dropdown_menu { width: auto !important; // override fixed width } + + .mx_CreateSpaceFromCommunityDialog_nonPublicSpacer { + height: 63px; // balance the height of the missing room alias field to prevent modal bouncing + } } .mx_CreateSpaceFromCommunityDialog_footer { diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js index 26bb1b8ae7..f4f1d50d63 100644 --- a/src/components/structures/GroupView.js +++ b/src/components/structures/GroupView.js @@ -41,6 +41,9 @@ import RightPanelStore from "../../stores/RightPanelStore"; import AutoHideScrollbar from "./AutoHideScrollbar"; import { mediaFromMxc } from "../../customisations/Media"; import { replaceableComponent } from "../../utils/replaceableComponent"; +import { createSpaceFromCommunity } from "../../utils/space"; +import { Action } from "../../dispatcher/actions"; +import { RightPanelPhases } from "../../stores/RightPanelStorePhases"; const LONG_DESC_PLACEHOLDER = _td( `

    HTML for your community's page

    @@ -815,6 +818,17 @@ export default class GroupView extends React.Component { this.setState({ showUpgradeNotice: false }); } + _onCreateSpaceClick = () => { + createSpaceFromCommunity(this._matrixClient, this.props.groupId); + }; + + _onAdminsLinkClick = () => { + dis.dispatch({ + action: Action.SetRightPanelPhase, + phase: RightPanelPhases.GroupMemberList, + }); + }; + _getGroupSection() { const groupSettingsSectionClasses = classnames({ "mx_GroupView_group": this.state.editing, @@ -854,17 +868,35 @@ export default class GroupView extends React.Component { let communitiesUpgradeNotice; if (this.state.showUpgradeNotice) { + let text; + if (this.state.isUserPrivileged) { + text = _t("You can create a Space from this community here.", {}, { + a: sub => + { sub } + , + }); + } else { + text = _t("Ask the admins of this community to make it into a Space " + + "and keep a look out for the invite.", {}, { + a: sub => + { sub } + , + }); + } + communitiesUpgradeNotice =

    { _t("Communities can now be made into Spaces") }

    { _t("Spaces are a new way to make a community, with new features coming.") }   - { _t("Ask the admins of this community to make it into a Space " + - "and keep a look out for the invite.") } + { text }   { _t("Communities won't receive further updates.") }

    - +
    ; } diff --git a/src/components/views/dialogs/CreateSpaceFromCommunityDialog.tsx b/src/components/views/dialogs/CreateSpaceFromCommunityDialog.tsx index c9e13d2b61..4fb0994e23 100644 --- a/src/components/views/dialogs/CreateSpaceFromCommunityDialog.tsx +++ b/src/components/views/dialogs/CreateSpaceFromCommunityDialog.tsx @@ -286,8 +286,6 @@ const CreateSpaceFromCommunityDialog: React.FC = ({ matrixClient: cli, g >

    - { _t("Spaces are the new version of communities - with new features coming.") } -   { _t("A link to the Space will be put in your community description.") }   { _t("All rooms will be added and all community members will be invited.") } @@ -326,6 +324,9 @@ const CreateSpaceFromCommunityDialog: React.FC = ({ matrixClient: cli, g ? _t("Open space for anyone, best for communities") : _t("Invite only, best for yourself or teams") }

    + { joinRule !== JoinRule.Public && +
    + }
    diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 3b5a748f89..987005a1b3 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2237,7 +2237,6 @@ "To create a Space from another community, just pick the community in Preferences.": "To create a Space from another community, just pick the community in Preferences.", "Failed to migrate community": "Failed to migrate community", "Create Space from community": "Create Space from community", - "Spaces are the new version of communities - with new features coming.": "Spaces are the new version of communities - with new features coming.", "A link to the Space will be put in your community description.": "A link to the Space will be put in your community description.", "All rooms will be added and all community members will be invited.": "All rooms will be added and all community members will be invited.", "Flair won't be available in Spaces for the foreseeable future.": "Flair won't be available in Spaces for the foreseeable future.", @@ -2727,9 +2726,10 @@ "Community Settings": "Community Settings", "Want more than a community? Get your own server": "Want more than a community? Get your own server", "Changes made to your community name and avatar might not be seen by other users for up to 30 minutes.": "Changes made to your community name and avatar might not be seen by other users for up to 30 minutes.", + "You can create a Space from this community here.": "You can create a Space from this community here.", + "Ask the admins of this community to make it into a Space and keep a look out for the invite.": "Ask the admins of this community to make it into a Space and keep a look out for the invite.", "Communities can now be made into Spaces": "Communities can now be made into Spaces", "Spaces are a new way to make a community, with new features coming.": "Spaces are a new way to make a community, with new features coming.", - "Ask the admins of this community to make it into a Space and keep a look out for the invite.": "Ask the admins of this community to make it into a Space and keep a look out for the invite.", "Communities won't receive further updates.": "Communities won't receive further updates.", "These rooms are displayed to community members on the community page. Community members can join the rooms by clicking on them.": "These rooms are displayed to community members on the community page. Community members can join the rooms by clicking on them.", "Featured Rooms:": "Featured Rooms:", From f53eb4eeedc1e7c449d92956a1ecebdbd4d4affa Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 12 Aug 2021 11:27:34 +0100 Subject: [PATCH 54/85] Fix tab trapping behaviour --- src/components/views/rooms/BasicMessageComposer.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/rooms/BasicMessageComposer.tsx b/src/components/views/rooms/BasicMessageComposer.tsx index 68dd874037..48f2e2a39b 100644 --- a/src/components/views/rooms/BasicMessageComposer.tsx +++ b/src/components/views/rooms/BasicMessageComposer.tsx @@ -462,7 +462,7 @@ export default class BasicMessageEditor extends React.Component } const autocompleteAction = getKeyBindingsManager().getAutocompleteAction(event); - if (model.autoComplete && model.autoComplete.hasCompletions()) { + if (model.autoComplete?.hasCompletions()) { const autoComplete = model.autoComplete; switch (autocompleteAction) { case AutocompleteAction.ForceComplete: @@ -485,7 +485,7 @@ export default class BasicMessageEditor extends React.Component default: return; // don't preventDefault on anything else } - } else if (autocompleteAction === AutocompleteAction.ForceComplete) { + } else if (autocompleteAction === AutocompleteAction.ForceComplete && !this.state.showVisualBell) { // there is no current autocomplete window, try to open it this.tabCompleteName(); handled = true; From aca073f5dae5b799083346186d04da86a349da4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 12 Aug 2021 16:16:13 +0200 Subject: [PATCH 55/85] $system-... -> $system 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 | 12 ++++++------ res/themes/legacy-light/css/_legacy-light.scss | 10 +++++----- res/themes/light/css/_light.scss | 14 +++++++------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index 9a85c9d2b0..060e56c7a0 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -7,9 +7,9 @@ $secondary-content: #A9B2BC; $tertiary-content: #8E99A4; $quaternary-content: #6F7882; $quinary-content: #394049; -$system-dark: #21262C; +$system: #21262C; $background: #15191E; -$panels: rgba($system-dark, 0.9); +$panels: rgba($system, 0.9); $panel-base: #8D97A5; // This color is not intended for use in the app $panel-selected: rgba($panel-base, 0.3); $panel-hover: rgba($panel-base, 0.1); @@ -67,7 +67,7 @@ $inverted-bg-color: $base-color; $selected-color: $room-highlight-color; // selected for hoverover & selected event tiles -$event-selected-color: $system-dark; +$event-selected-color: $system; // used for the hairline dividers in RoomView $primary-hairline-color: transparent; @@ -111,7 +111,7 @@ $lightbox-background-bg-color: #000; $lightbox-background-bg-opacity: 0.85; $settings-grey-fg-color: #a2a2a2; -$settings-profile-placeholder-bg-color: $system-dark; +$settings-profile-placeholder-bg-color: $system; $settings-profile-overlay-placeholder-fg-color: #454545; $settings-profile-button-bg-color: #e7e7e7; $settings-profile-button-fg-color: $settings-profile-overlay-placeholder-fg-color; @@ -194,7 +194,7 @@ $button-link-bg-color: transparent; $togglesw-off-color: $room-highlight-color; $progressbar-fg-color: $accent-color; -$progressbar-bg-color: $system-dark; +$progressbar-bg-color: $system; $visual-bell-bg-color: #800; @@ -229,7 +229,7 @@ $user-tile-hover-bg-color: $header-panel-bg-color; $message-body-panel-fg-color: $secondary-fg-color; $message-body-panel-bg-color: $quinary-content; $message-body-panel-icon-fg-color: $secondary-fg-color; -$message-body-panel-icon-bg-color: $system-dark; // "System Dark" +$message-body-panel-icon-bg-color: $system; // "System Dark" $voice-record-stop-border-color: $quaternary-fg-color; $voice-record-waveform-incomplete-fg-color: $quaternary-fg-color; diff --git a/res/themes/legacy-light/css/_legacy-light.scss b/res/themes/legacy-light/css/_legacy-light.scss index 1a63c9bd07..0edf3b64f2 100644 --- a/res/themes/legacy-light/css/_legacy-light.scss +++ b/res/themes/legacy-light/css/_legacy-light.scss @@ -13,7 +13,7 @@ $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; +$system: #F4F6FA; // unified palette // try to use these colors when possible @@ -181,8 +181,8 @@ $eventtile-meta-color: $roomtopic-color; $composer-e2e-icon-color: #91a1c0; $header-divider-color: #91a1c0; -$toast-bg-color: $system-light; -$voipcall-plinth-color: $system-light; +$toast-bg-color: $system; +$voipcall-plinth-color: $system; // ******************** @@ -334,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: $system-light; +$message-body-panel-icon-bg-color: $system; // See non-legacy _light for variable information $voice-record-stop-symbol-color: #ff4b55; @@ -352,7 +352,7 @@ $composer-shadow-color: tranparent; // Bubble tiles $eventbubble-self-bg: #F0FBF8; -$eventbubble-others-bg: $system-light; +$eventbubble-others-bg: $system; $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 0f7ede4ec9..61ca3709c2 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -21,9 +21,9 @@ $secondary-content: #737D8C; $tertiary-content: #8D97A5; $quaternary-content: #c1c6cd; $quinary-content: #E3E8F0; -$system-light: #F4F6FA; +$system: #F4F6FA; $background: #ffffff; -$panels: rgba($system-light, 0.9); +$panels: rgba($system, 0.9); $panel-selected: rgba($tertiary-content, 0.3); $panel-hover: rgba($tertiary-content, 0.1); $panel-actions: rgba($tertiary-content, 0.2); @@ -157,7 +157,7 @@ $blockquote-bar-color: #ddd; $blockquote-fg-color: #777; $settings-grey-fg-color: #a2a2a2; -$settings-profile-placeholder-bg-color: $system-light; +$settings-profile-placeholder-bg-color: $system; $settings-profile-overlay-placeholder-fg-color: #2e2f32; $settings-profile-button-bg-color: #e7e7e7; $settings-profile-button-fg-color: $settings-profile-overlay-placeholder-fg-color; @@ -186,8 +186,8 @@ $eventtile-meta-color: $roomtopic-color; $composer-e2e-icon-color: #91A1C0; $header-divider-color: #91A1C0; -$toast-bg-color: $system-light; -$voipcall-plinth-color: $system-light; +$toast-bg-color: $system; +$voipcall-plinth-color: $system; // ******************** @@ -346,7 +346,7 @@ $user-tile-hover-bg-color: $header-panel-bg-color; $message-body-panel-fg-color: $secondary-fg-color; $message-body-panel-bg-color: $quinary-content; $message-body-panel-icon-fg-color: $secondary-fg-color; -$message-body-panel-icon-bg-color: $system-light; +$message-body-panel-icon-bg-color: $system; // These two don't change between themes. They are the $warning-color, but we don't // want custom themes to affect them by accident. @@ -370,7 +370,7 @@ $composer-shadow-color: rgba(0, 0, 0, 0.04); // Bubble tiles $eventbubble-self-bg: #F0FBF8; -$eventbubble-others-bg: $system-light; +$eventbubble-others-bg: $system; $eventbubble-bg-hover: #FAFBFD; $eventbubble-avatar-outline: $primary-bg-color; $eventbubble-reply-color: $quaternary-content; From 7ce88c9966dedb79fa9ccdf1ba4b6dcb901a0fda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 12 Aug 2021 16:20:30 +0200 Subject: [PATCH 56/85] $toast-bg-color -> $system 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 | 6 +++--- res/themes/legacy-light/css/_legacy-light.scss | 1 - res/themes/light/css/_light.scss | 1 - 6 files changed, 6 insertions(+), 10 deletions(-) diff --git a/res/css/structures/_ToastContainer.scss b/res/css/structures/_ToastContainer.scss index 2c3f1c705c..5cd938f1ce 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: $toast-bg-color; + background-color: $system; 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: $toast-bg-color; + background-color: $system; 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 498dd8e096..df961d852b 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: $toast-bg-color; + background-color: $system; 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 060e56c7a0..8c305b9828 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -132,8 +132,6 @@ $eventtile-meta-color: $roomtopic-color; $header-divider-color: $header-panel-text-primary-color; $composer-e2e-icon-color: $header-panel-text-primary-color; -$toast-bg-color: $quinary-content; - // ******************** $theme-button-bg-color: #e3e8f0; diff --git a/res/themes/legacy-dark/css/_legacy-dark.scss b/res/themes/legacy-dark/css/_legacy-dark.scss index b9429318ac..3e3412c6c1 100644 --- a/res/themes/legacy-dark/css/_legacy-dark.scss +++ b/res/themes/legacy-dark/css/_legacy-dark.scss @@ -1,3 +1,6 @@ +// Colors from Figma Compound https://www.figma.com/file/X4XTH9iS2KGJ2wFKDqkyed/Compound?node-id=559%3A741 +$system: #21262C; + // unified palette // try to use these colors when possible $bg-color: #181b21; @@ -111,9 +114,6 @@ $eventtile-meta-color: $roomtopic-color; $header-divider-color: $header-panel-text-primary-color; $composer-e2e-icon-color: $header-panel-text-primary-color; -$quinary-content-color: #394049; -$toast-bg-color: $quinary-content-color; - // ******************** $theme-button-bg-color: #e3e8f0; diff --git a/res/themes/legacy-light/css/_legacy-light.scss b/res/themes/legacy-light/css/_legacy-light.scss index 0edf3b64f2..3f722bcb30 100644 --- a/res/themes/legacy-light/css/_legacy-light.scss +++ b/res/themes/legacy-light/css/_legacy-light.scss @@ -181,7 +181,6 @@ $eventtile-meta-color: $roomtopic-color; $composer-e2e-icon-color: #91a1c0; $header-divider-color: #91a1c0; -$toast-bg-color: $system; $voipcall-plinth-color: $system; // ******************** diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index 61ca3709c2..e64fe12d3b 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -186,7 +186,6 @@ $eventtile-meta-color: $roomtopic-color; $composer-e2e-icon-color: #91A1C0; $header-divider-color: #91A1C0; -$toast-bg-color: $system; $voipcall-plinth-color: $system; // ******************** From 9249a6e80c71fb50fff2ef50250aae8c79524079 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 12 Aug 2021 16:16:13 +0200 Subject: [PATCH 57/85] $system-... -> $system 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 | 10 +++++----- res/themes/legacy-light/css/_legacy-light.scss | 10 +++++----- res/themes/light/css/_light.scss | 12 ++++++------ 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index e4ea2bb57e..39658db962 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -1,5 +1,5 @@ // Colors from Figma Compound https://www.figma.com/file/X4XTH9iS2KGJ2wFKDqkyed/Compound?node-id=557%3A0 -$system-dark: #21262C; +$system: #21262C; // unified palette // try to use these colors when possible @@ -50,7 +50,7 @@ $inverted-bg-color: $base-color; $selected-color: $room-highlight-color; // selected for hoverover & selected event tiles -$event-selected-color: $system-dark; +$event-selected-color: $system; // used for the hairline dividers in RoomView $primary-hairline-color: transparent; @@ -94,7 +94,7 @@ $lightbox-background-bg-color: #000; $lightbox-background-bg-opacity: 0.85; $settings-grey-fg-color: #a2a2a2; -$settings-profile-placeholder-bg-color: $system-dark; +$settings-profile-placeholder-bg-color: $system; $settings-profile-overlay-placeholder-fg-color: #454545; $settings-profile-button-bg-color: #e7e7e7; $settings-profile-button-fg-color: $settings-profile-overlay-placeholder-fg-color; @@ -178,7 +178,7 @@ $button-link-bg-color: transparent; $togglesw-off-color: $room-highlight-color; $progressbar-fg-color: $accent-color; -$progressbar-bg-color: $system-dark; +$progressbar-bg-color: $system; $visual-bell-bg-color: #800; @@ -213,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: $system-dark; // "System Dark" +$message-body-panel-icon-bg-color: $system; // "System Dark" $voice-record-stop-border-color: $quaternary-fg-color; $voice-record-waveform-incomplete-fg-color: $quaternary-fg-color; diff --git a/res/themes/legacy-light/css/_legacy-light.scss b/res/themes/legacy-light/css/_legacy-light.scss index 1a63c9bd07..0edf3b64f2 100644 --- a/res/themes/legacy-light/css/_legacy-light.scss +++ b/res/themes/legacy-light/css/_legacy-light.scss @@ -13,7 +13,7 @@ $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; +$system: #F4F6FA; // unified palette // try to use these colors when possible @@ -181,8 +181,8 @@ $eventtile-meta-color: $roomtopic-color; $composer-e2e-icon-color: #91a1c0; $header-divider-color: #91a1c0; -$toast-bg-color: $system-light; -$voipcall-plinth-color: $system-light; +$toast-bg-color: $system; +$voipcall-plinth-color: $system; // ******************** @@ -334,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: $system-light; +$message-body-panel-icon-bg-color: $system; // See non-legacy _light for variable information $voice-record-stop-symbol-color: #ff4b55; @@ -352,7 +352,7 @@ $composer-shadow-color: tranparent; // Bubble tiles $eventbubble-self-bg: #F0FBF8; -$eventbubble-others-bg: $system-light; +$eventbubble-others-bg: $system; $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 eff9abe5af..8c79d82170 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -13,7 +13,7 @@ $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; +$system: #F4F6FA; // unified palette // try to use these colors when possible @@ -141,7 +141,7 @@ $blockquote-bar-color: #ddd; $blockquote-fg-color: #777; $settings-grey-fg-color: #a2a2a2; -$settings-profile-placeholder-bg-color: $system-light; +$settings-profile-placeholder-bg-color: $system; $settings-profile-overlay-placeholder-fg-color: #2e2f32; $settings-profile-button-bg-color: #e7e7e7; $settings-profile-button-fg-color: $settings-profile-overlay-placeholder-fg-color; @@ -170,8 +170,8 @@ $eventtile-meta-color: $roomtopic-color; $composer-e2e-icon-color: #91A1C0; $header-divider-color: #91A1C0; -$toast-bg-color: $system-light; -$voipcall-plinth-color: $system-light; +$toast-bg-color: $system; +$voipcall-plinth-color: $system; // ******************** @@ -330,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: $system-light; +$message-body-panel-icon-bg-color: $system; // These two don't change between themes. They are the $warning-color, but we don't // want custom themes to affect them by accident. @@ -354,7 +354,7 @@ $composer-shadow-color: rgba(0, 0, 0, 0.04); // Bubble tiles $eventbubble-self-bg: #F0FBF8; -$eventbubble-others-bg: $system-light; +$eventbubble-others-bg: $system; $eventbubble-bg-hover: #FAFBFD; $eventbubble-avatar-outline: $primary-bg-color; $eventbubble-reply-color: #C1C6CD; From adce43600d89d5dd809acbe38906535586c5456f Mon Sep 17 00:00:00 2001 From: Andrew Morgan <1342360+anoadragon453@users.noreply.github.com> Date: Thu, 12 Aug 2021 15:34:33 +0100 Subject: [PATCH 58/85] Update link to matrix-js-sdk CONTRIBUTING file (#6557) --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f7c8c8b1c5..f0ca3eb8a7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,4 +1,4 @@ Contributing code to The React SDK ================================== -matrix-react-sdk follows the same pattern as https://github.com/matrix-org/matrix-js-sdk/blob/master/CONTRIBUTING.rst +matrix-react-sdk follows the same pattern as https://github.com/matrix-org/matrix-js-sdk/blob/master/CONTRIBUTING.md From bf5c94572c1420afd2305e3c567ffda685445a4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 12 Aug 2021 16:20:30 +0200 Subject: [PATCH 59/85] $toast-bg-color -> $system 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 | 3 --- res/themes/legacy-dark/css/_legacy-dark.scss | 6 +++--- res/themes/legacy-light/css/_legacy-light.scss | 1 - res/themes/light/css/_light.scss | 1 - 6 files changed, 6 insertions(+), 11 deletions(-) diff --git a/res/css/structures/_ToastContainer.scss b/res/css/structures/_ToastContainer.scss index 2c3f1c705c..5cd938f1ce 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: $toast-bg-color; + background-color: $system; 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: $toast-bg-color; + background-color: $system; 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 7752edddfa..b054c21686 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: $toast-bg-color; + background-color: $system; 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 39658db962..0e1bf9e858 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -115,9 +115,6 @@ $eventtile-meta-color: $roomtopic-color; $header-divider-color: $header-panel-text-primary-color; $composer-e2e-icon-color: $header-panel-text-primary-color; -$quinary-content-color: #394049; -$toast-bg-color: $quinary-content-color; - // ******************** $theme-button-bg-color: #e3e8f0; diff --git a/res/themes/legacy-dark/css/_legacy-dark.scss b/res/themes/legacy-dark/css/_legacy-dark.scss index b9429318ac..3e3412c6c1 100644 --- a/res/themes/legacy-dark/css/_legacy-dark.scss +++ b/res/themes/legacy-dark/css/_legacy-dark.scss @@ -1,3 +1,6 @@ +// Colors from Figma Compound https://www.figma.com/file/X4XTH9iS2KGJ2wFKDqkyed/Compound?node-id=559%3A741 +$system: #21262C; + // unified palette // try to use these colors when possible $bg-color: #181b21; @@ -111,9 +114,6 @@ $eventtile-meta-color: $roomtopic-color; $header-divider-color: $header-panel-text-primary-color; $composer-e2e-icon-color: $header-panel-text-primary-color; -$quinary-content-color: #394049; -$toast-bg-color: $quinary-content-color; - // ******************** $theme-button-bg-color: #e3e8f0; diff --git a/res/themes/legacy-light/css/_legacy-light.scss b/res/themes/legacy-light/css/_legacy-light.scss index 0edf3b64f2..3f722bcb30 100644 --- a/res/themes/legacy-light/css/_legacy-light.scss +++ b/res/themes/legacy-light/css/_legacy-light.scss @@ -181,7 +181,6 @@ $eventtile-meta-color: $roomtopic-color; $composer-e2e-icon-color: #91a1c0; $header-divider-color: #91a1c0; -$toast-bg-color: $system; $voipcall-plinth-color: $system; // ******************** diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index 8c79d82170..590a2a8a1c 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -170,7 +170,6 @@ $eventtile-meta-color: $roomtopic-color; $composer-e2e-icon-color: #91A1C0; $header-divider-color: #91A1C0; -$toast-bg-color: $system; $voipcall-plinth-color: $system; // ******************** From 657dcaf9895a62629e57d0673b2dca3ec075c813 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 12 Aug 2021 18:36:57 +0100 Subject: [PATCH 60/85] Fix video call persisting when widget removed Fixes https://github.com/vector-im/element-web/issues/15703 Type: defect --- src/stores/WidgetStore.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/stores/WidgetStore.ts b/src/stores/WidgetStore.ts index 732428107f..f1a0e165e8 100644 --- a/src/stores/WidgetStore.ts +++ b/src/stores/WidgetStore.ts @@ -137,6 +137,26 @@ export default class WidgetStore extends AsyncStoreWithClient { if (edited && !this.roomMap.has(room.roomId)) { this.roomMap.set(room.roomId, roomInfo); } + + // If a persistent widget is active, check to see if it's just been removed. + // If it has, it needs to destroyed otherwise unmounting the node won't kill it + const persistentWidgetId = ActiveWidgetStore.getPersistentWidgetId(); + if (persistentWidgetId) { + if ( + ActiveWidgetStore.getRoomId(persistentWidgetId) === room.roomId && + !roomInfo.widgets.some(w => w.id === persistentWidgetId) + ) { + console.log(`Persistent widget ${persistentWidgetId} removed from room ${room.roomId}: destroying.`); + ActiveWidgetStore.destroyPersistentWidget(persistentWidgetId); + } + } + + /*if ( + oldWidgetIds.includes(ActiveWidgetStore.getPersistentWidgetId()) && + !roomInfo.widgets.map(w => w.id).includes(ActiveWidgetStore.getPersistentWidgetId())) { + ActiveWidgetStore.destroyPersistentWidget(ActiveWidgetStore.getPersistentWidgetId()); + }*/ + this.emit(room.roomId); } From 3528d87f30f686c5dfc84d902df55bbd482f7935 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 12 Aug 2021 18:39:11 +0100 Subject: [PATCH 61/85] Remove old version --- src/stores/WidgetStore.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/stores/WidgetStore.ts b/src/stores/WidgetStore.ts index f1a0e165e8..e9820eee06 100644 --- a/src/stores/WidgetStore.ts +++ b/src/stores/WidgetStore.ts @@ -151,12 +151,6 @@ export default class WidgetStore extends AsyncStoreWithClient { } } - /*if ( - oldWidgetIds.includes(ActiveWidgetStore.getPersistentWidgetId()) && - !roomInfo.widgets.map(w => w.id).includes(ActiveWidgetStore.getPersistentWidgetId())) { - ActiveWidgetStore.destroyPersistentWidget(ActiveWidgetStore.getPersistentWidgetId()); - }*/ - this.emit(room.roomId); } From 1ad35b15646611dcedcb09f3c8c8f30da39af6e0 Mon Sep 17 00:00:00 2001 From: Andrew Morgan <1342360+anoadragon453@users.noreply.github.com> Date: Thu, 12 Aug 2021 18:58:06 +0100 Subject: [PATCH 62/85] Keep number field in focus when pressing dialpad buttons (#6520) --- .../context_menus/DialpadContextMenu.tsx | 17 ++++++++++--- src/components/views/dialogs/InviteDialog.tsx | 23 ++++++++++++++--- .../views/elements/DialPadBackspaceButton.tsx | 4 +-- src/components/views/voip/DialPad.tsx | 14 +++++------ src/components/views/voip/DialPadModal.tsx | 25 ++++++++++++++++--- 5 files changed, 65 insertions(+), 18 deletions(-) diff --git a/src/components/views/context_menus/DialpadContextMenu.tsx b/src/components/views/context_menus/DialpadContextMenu.tsx index 0bb96f9397..01c7c6c1d8 100644 --- a/src/components/views/context_menus/DialpadContextMenu.tsx +++ b/src/components/views/context_menus/DialpadContextMenu.tsx @@ -14,8 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from 'react'; -import AccessibleButton from "../elements/AccessibleButton"; +import * as React from "react"; +import { createRef } from "react"; +import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton"; import { ContextMenu, IProps as IContextMenuProps } from '../../structures/ContextMenu'; import { MatrixCall } from 'matrix-js-sdk/src/webrtc/call'; import Field from "../elements/Field"; @@ -32,6 +33,8 @@ interface IState { @replaceableComponent("views.context_menus.DialpadContextMenu") export default class DialpadContextMenu extends React.Component { + private numberEntryFieldRef: React.RefObject = createRef(); + constructor(props) { super(props); @@ -40,9 +43,16 @@ export default class DialpadContextMenu extends React.Component }; } - onDigitPress = (digit) => { + onDigitPress = (digit: string, ev: ButtonEvent) => { this.props.call.sendDtmfDigit(digit); this.setState({ value: this.state.value + digit }); + + // Keep the number field focused so that keyboard entry is still available + // However, don't focus if this wasn't the result of directly clicking on the button, + // i.e someone using keyboard navigation. + if (ev.type === "click") { + this.numberEntryFieldRef.current?.focus(); + } }; onCancelClick = () => { @@ -68,6 +78,7 @@ export default class DialpadContextMenu extends React.Component
    void; private debounceTimer: number = null; // actually number because we're in the browser private editorRef = createRef(); + private numberEntryFieldRef: React.RefObject = createRef(); private unmounted = false; constructor(props) { @@ -1283,13 +1284,27 @@ export default class InviteDialog extends React.PureComponent { + private onDigitPress = (digit: string, ev: ButtonEvent) => { this.setState({ dialPadValue: this.state.dialPadValue + digit }); + + // Keep the number field focused so that keyboard entry is still available + // However, don't focus if this wasn't the result of directly clicking on the button, + // i.e someone using keyboard navigation. + if (ev.type === "click") { + this.numberEntryFieldRef.current?.focus(); + } }; - private onDeletePress = () => { + private onDeletePress = (ev: ButtonEvent) => { if (this.state.dialPadValue.length === 0) return; this.setState({ dialPadValue: this.state.dialPadValue.slice(0, -1) }); + + // Keep the number field focused so that keyboard entry is still available + // However, don't focus if this wasn't the result of directly clicking on the button, + // i.e someone using keyboard navigation. + if (ev.type === "click") { + this.numberEntryFieldRef.current?.focus(); + } }; private onTabChange = (tabId: TabId) => { @@ -1543,6 +1558,7 @@ export default class InviteDialog extends React.PureComponent; } else { dialPadField = void; + onBackspacePress: (ev: ButtonEvent) => void; } export default class DialPadBackspaceButton extends React.PureComponent { diff --git a/src/components/views/voip/DialPad.tsx b/src/components/views/voip/DialPad.tsx index 3b4a29b3f9..46584e0870 100644 --- a/src/components/views/voip/DialPad.tsx +++ b/src/components/views/voip/DialPad.tsx @@ -15,7 +15,7 @@ limitations under the License. */ import * as React from "react"; -import AccessibleButton from "../elements/AccessibleButton"; +import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton"; import { replaceableComponent } from "../../../utils/replaceableComponent"; const BUTTONS = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '0', '#']; @@ -30,12 +30,12 @@ interface IButtonProps { kind: DialPadButtonKind; digit?: string; digitSubtext?: string; - onButtonPress: (string) => void; + onButtonPress: (digit: string, ev: ButtonEvent) => void; } class DialPadButton extends React.PureComponent { - onClick = () => { - this.props.onButtonPress(this.props.digit); + onClick = (ev: ButtonEvent) => { + this.props.onButtonPress(this.props.digit, ev); }; render() { @@ -54,10 +54,10 @@ class DialPadButton extends React.PureComponent { } interface IProps { - onDigitPress: (string) => void; + onDigitPress: (digit: string, ev: ButtonEvent) => void; hasDial: boolean; - onDeletePress?: (string) => void; - onDialPress?: (string) => void; + onDeletePress?: (ev: ButtonEvent) => void; + onDialPress?: () => void; } @replaceableComponent("views.voip.DialPad") diff --git a/src/components/views/voip/DialPadModal.tsx b/src/components/views/voip/DialPadModal.tsx index a36fc37dff..4d69260565 100644 --- a/src/components/views/voip/DialPadModal.tsx +++ b/src/components/views/voip/DialPadModal.tsx @@ -15,7 +15,8 @@ limitations under the License. */ import * as React from "react"; -import AccessibleButton from "../elements/AccessibleButton"; +import { createRef } from "react"; +import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton"; import Field from "../elements/Field"; import DialPad from './DialPad'; import dis from '../../../dispatcher/dispatcher'; @@ -34,6 +35,8 @@ interface IState { @replaceableComponent("views.voip.DialPadModal") export default class DialpadModal extends React.PureComponent { + private numberEntryFieldRef: React.RefObject = createRef(); + constructor(props) { super(props); this.state = { @@ -54,13 +57,27 @@ export default class DialpadModal extends React.PureComponent { this.onDialPress(); }; - onDigitPress = (digit) => { + onDigitPress = (digit: string, ev: ButtonEvent) => { this.setState({ value: this.state.value + digit }); + + // Keep the number field focused so that keyboard entry is still available. + // However, don't focus if this wasn't the result of directly clicking on the button, + // i.e someone using keyboard navigation. + if (ev.type === "click") { + this.numberEntryFieldRef.current?.focus(); + } }; - onDeletePress = () => { + onDeletePress = (ev: ButtonEvent) => { if (this.state.value.length === 0) return; this.setState({ value: this.state.value.slice(0, -1) }); + + // Keep the number field focused so that keyboard entry is still available + // However, don't focus if this wasn't the result of directly clicking on the button, + // i.e someone using keyboard navigation. + if (ev.type === "click") { + this.numberEntryFieldRef.current?.focus(); + } }; onDialPress = async () => { @@ -82,6 +99,7 @@ export default class DialpadModal extends React.PureComponent { let dialPadField; if (this.state.value.length !== 0) { dialPadField = { />; } else { dialPadField = Date: Fri, 13 Aug 2021 10:35:31 +0200 Subject: [PATCH 63/85] Add a little padding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/messages/_CallEvent.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/res/css/views/messages/_CallEvent.scss b/res/css/views/messages/_CallEvent.scss index dde5856484..7320c5a5cb 100644 --- a/res/css/views/messages/_CallEvent.scss +++ b/res/css/views/messages/_CallEvent.scss @@ -31,6 +31,7 @@ limitations under the License. width: 75%; box-sizing: border-box; height: 60px; + margin: 4px 0; .mx_CallEvent_iconButton { display: inline-flex; From c79852a9f0e245a5ee3c1ee0513f50695b8e5ddd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 13 Aug 2021 10:59:59 +0200 Subject: [PATCH 64/85] Left align call tiles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/messages/_CallEvent.scss | 3 +-- src/components/views/rooms/EventTile.tsx | 7 +++++-- src/utils/EventUtils.ts | 4 ++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/res/css/views/messages/_CallEvent.scss b/res/css/views/messages/_CallEvent.scss index 7320c5a5cb..4bff9c6f52 100644 --- a/res/css/views/messages/_CallEvent.scss +++ b/res/css/views/messages/_CallEvent.scss @@ -16,7 +16,6 @@ limitations under the License. .mx_CallEvent_wrapper { display: flex; - justify-content: center; width: 100%; .mx_CallEvent { @@ -28,7 +27,7 @@ limitations under the License. background-color: $dark-panel-bg-color; border-radius: 8px; - width: 75%; + width: 65%; box-sizing: border-box; height: 60px; margin: 4px 0; diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index 884d004551..301e33ec42 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -932,8 +932,11 @@ export default class EventTile extends React.Component { } else if (this.props.layout == Layout.IRC) { avatarSize = 14; needsSenderProfile = true; - } else if (this.props.continuation && this.props.tileShape !== TileShape.FileGrid) { - // no avatar or sender profile for continuation messages + } else if ( + (this.props.continuation && this.props.tileShape !== TileShape.FileGrid) || + this.props.mxEvent.getType() === EventType.CallInvite + ) { + // no avatar or sender profile for continuation messages and call tiles avatarSize = 0; needsSenderProfile = false; } else { diff --git a/src/utils/EventUtils.ts b/src/utils/EventUtils.ts index e2af1c7464..7aef05c523 100644 --- a/src/utils/EventUtils.ts +++ b/src/utils/EventUtils.ts @@ -116,14 +116,14 @@ export function getEventDisplayInfo(mxEvent: MatrixEvent): { (eventType === EventType.RoomMessage && msgtype && msgtype.startsWith("m.key.verification")) || (eventType === EventType.RoomCreate) || (eventType === EventType.RoomEncryption) || - (eventType === EventType.CallInvite) || (tileHandler === "messages.MJitsiWidgetEvent") ); let isInfoMessage = ( !isBubbleMessage && eventType !== EventType.RoomMessage && eventType !== EventType.Sticker && - eventType !== EventType.RoomCreate + eventType !== EventType.RoomCreate && + eventType !== EventType.CallInvite ); // If we're showing hidden events in the timeline, we should use the From 032d2866a3b67f904202c7614f75ad848de4d840 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 13 Aug 2021 11:19:14 +0200 Subject: [PATCH 65/85] Add "No answer" state 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 | 2 +- src/i18n/strings/en_EN.json | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/views/messages/CallEvent.tsx b/src/components/views/messages/CallEvent.tsx index 655b1a42c5..2883d6b576 100644 --- a/src/components/views/messages/CallEvent.tsx +++ b/src/components/views/messages/CallEvent.tsx @@ -175,7 +175,7 @@ export default class CallEvent extends React.PureComponent { } else if (hangupReason === CallErrorCode.InviteTimeout) { return (
    - { _t("Missed call") } + { _t("No answer") } { this.renderCallBackButton(_t("Call back")) }
    ); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index c9dbc00a78..33746e3f8b 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1887,13 +1887,14 @@ "Connected": "Connected", "Call declined": "Call declined", "Call back": "Call back", - "Missed call": "Missed call", + "No answer": "No answer", "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)", "Retry": "Retry", + "Missed call": "Missed call", "The call is in an unknown state!": "The call is in an unknown state!", "Sunday": "Sunday", "Monday": "Monday", From fa204c41045f295fe4fbbf997d8aafe0e5534f3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 13 Aug 2021 11:34:11 +0200 Subject: [PATCH 66/85] Add declined call buttons MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/img/voip/declined-video.svg | 3 +++ res/img/voip/declined-voice.svg | 4 ++++ 2 files changed, 7 insertions(+) create mode 100644 res/img/voip/declined-video.svg create mode 100644 res/img/voip/declined-voice.svg diff --git a/res/img/voip/declined-video.svg b/res/img/voip/declined-video.svg new file mode 100644 index 0000000000..509ffa8fd1 --- /dev/null +++ b/res/img/voip/declined-video.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/img/voip/declined-voice.svg b/res/img/voip/declined-voice.svg new file mode 100644 index 0000000000..78e8d90cdf --- /dev/null +++ b/res/img/voip/declined-voice.svg @@ -0,0 +1,4 @@ + + + + From cda91e44e00116c0e22d5f8357d2724222d7acc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 13 Aug 2021 11:37:17 +0200 Subject: [PATCH 67/85] Use new call state icons MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/messages/_CallEvent.scss | 10 ++++++++++ src/components/views/messages/CallEvent.tsx | 7 +++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/res/css/views/messages/_CallEvent.scss b/res/css/views/messages/_CallEvent.scss index 4bff9c6f52..2d9caf1569 100644 --- a/res/css/views/messages/_CallEvent.scss +++ b/res/css/views/messages/_CallEvent.scss @@ -80,6 +80,16 @@ limitations under the License. mask-image: url('$(res)/img/voip/missed-video.svg'); } + &.mx_CallEvent_voice.mx_CallEvent_rejected .mx_CallEvent_type_icon::before, + &.mx_CallEvent_voice.mx_CallEvent_noAnswer .mx_CallEvent_type_icon::before { + mask-image: url('$(res)/img/voip/declined-voice.svg'); + } + + &.mx_CallEvent_video.mx_CallEvent_rejected .mx_CallEvent_type_icon::before, + &.mx_CallEvent_video.mx_CallEvent_noAnswer .mx_CallEvent_type_icon::before { + mask-image: url('$(res)/img/voip/declined-video.svg'); + } + .mx_CallEvent_info { display: flex; flex-direction: row; diff --git a/src/components/views/messages/CallEvent.tsx b/src/components/views/messages/CallEvent.tsx index 2883d6b576..594b0b7d99 100644 --- a/src/components/views/messages/CallEvent.tsx +++ b/src/components/views/messages/CallEvent.tsx @@ -249,10 +249,9 @@ export default class CallEvent extends React.PureComponent { mx_CallEvent_voice: isVoice, mx_CallEvent_video: !isVoice, mx_CallEvent_narrow: this.state.narrow, - mx_CallEvent_missed: ( - callState === CustomCallState.Missed || - (callState === CallState.Ended && hangupReason === CallErrorCode.InviteTimeout) - ), + mx_CallEvent_missed: callState === CustomCallState.Missed, + mx_CallEvent_noAnswer: callState === CallState.Ended && hangupReason === CallErrorCode.InviteTimeout, + mx_CallEvent_rejected: callState === CallState.Ended && this.props.callEventGrouper.gotRejected, }); let silenceIcon; if (this.state.narrow && this.state.callState === CallState.Ringing) { From 0ee59a17de3aab2f7b8ca4b77e6853deabef8f0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 13 Aug 2021 18:42:55 +0200 Subject: [PATCH 68/85] Fix PiP of held calls 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 df961d852b..63ca91267f 100644 --- a/res/css/views/voip/_CallView.scss +++ b/res/css/views/voip/_CallView.scss @@ -43,6 +43,7 @@ limitations under the License. box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.20); border-radius: 8px; + .mx_CallView_video_hold, .mx_CallView_voice { height: 180px; } From 7eabb85e61288011ceb61bf55c4b89f91adf4af7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 13 Aug 2021 18:42:55 +0200 Subject: [PATCH 69/85] Fix PiP of held calls 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 b054c21686..d36c3309a9 100644 --- a/res/css/views/voip/_CallView.scss +++ b/res/css/views/voip/_CallView.scss @@ -43,6 +43,7 @@ limitations under the License. box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.20); border-radius: 8px; + .mx_CallView_video_hold, .mx_CallView_voice { height: 180px; } From e78640572d75498a626f5f89e14f228da43a0585 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 13 Aug 2021 18:07:58 +0100 Subject: [PATCH 70/85] Convert CrossSigningPanel to TS Type: task --- ...sSigningPanel.js => CrossSigningPanel.tsx} | 76 ++++++++++--------- 1 file changed, 39 insertions(+), 37 deletions(-) rename src/components/views/settings/{CrossSigningPanel.js => CrossSigningPanel.tsx} (86%) diff --git a/src/components/views/settings/CrossSigningPanel.js b/src/components/views/settings/CrossSigningPanel.tsx similarity index 86% rename from src/components/views/settings/CrossSigningPanel.js rename to src/components/views/settings/CrossSigningPanel.tsx index 8b9d68bfa5..3dd9ea0512 100644 --- a/src/components/views/settings/CrossSigningPanel.js +++ b/src/components/views/settings/CrossSigningPanel.tsx @@ -25,35 +25,37 @@ import InteractiveAuthDialog from '../dialogs/InteractiveAuthDialog'; import ConfirmDestroyCrossSigningDialog from '../dialogs/security/ConfirmDestroyCrossSigningDialog'; import { replaceableComponent } from "../../../utils/replaceableComponent"; +interface IState { + error?: Error; + crossSigningPublicKeysOnDevice?: boolean; + crossSigningPrivateKeysInStorage?: boolean; + masterPrivateKeyCached?: boolean; + selfSigningPrivateKeyCached?: boolean; + userSigningPrivateKeyCached?: boolean; + homeserverSupportsCrossSigning?: boolean; + crossSigningReady?: boolean; +} + @replaceableComponent("views.settings.CrossSigningPanel") -export default class CrossSigningPanel extends React.PureComponent { +export default class CrossSigningPanel extends React.PureComponent<{}, IState> { + private unmounted = false; + constructor(props) { super(props); - this._unmounted = false; - - this.state = { - error: null, - crossSigningPublicKeysOnDevice: null, - crossSigningPrivateKeysInStorage: null, - masterPrivateKeyCached: null, - selfSigningPrivateKeyCached: null, - userSigningPrivateKeyCached: null, - homeserverSupportsCrossSigning: null, - crossSigningReady: null, - }; + this.state = {}; } - componentDidMount() { + public componentDidMount() { const cli = MatrixClientPeg.get(); cli.on("accountData", this.onAccountData); cli.on("userTrustStatusChanged", this.onStatusChanged); cli.on("crossSigning.keysChanged", this.onStatusChanged); - this._getUpdatedStatus(); + this.getUpdatedStatus(); } - componentWillUnmount() { - this._unmounted = true; + public componentWillUnmount() { + this.unmounted = true; const cli = MatrixClientPeg.get(); if (!cli) return; cli.removeListener("accountData", this.onAccountData); @@ -61,28 +63,28 @@ export default class CrossSigningPanel extends React.PureComponent { cli.removeListener("crossSigning.keysChanged", this.onStatusChanged); } - onAccountData = (event) => { + private onAccountData = (event): void => { const type = event.getType(); if (type.startsWith("m.cross_signing") || type.startsWith("m.secret_storage")) { - this._getUpdatedStatus(); + this.getUpdatedStatus(); } }; - _onBootstrapClick = () => { - this._bootstrapCrossSigning({ forceReset: false }); + private onBootstrapClick = () => { + this.bootstrapCrossSigning({ forceReset: false }); }; - onStatusChanged = () => { - this._getUpdatedStatus(); + private onStatusChanged = () => { + this.getUpdatedStatus(); }; - async _getUpdatedStatus() { + private async getUpdatedStatus(): Promise { const cli = MatrixClientPeg.get(); const pkCache = cli.getCrossSigningCacheCallbacks(); const crossSigning = cli.crypto.crossSigningInfo; const secretStorage = cli.crypto.secretStorage; - const crossSigningPublicKeysOnDevice = crossSigning.getId(); - const crossSigningPrivateKeysInStorage = await crossSigning.isStoredInSecretStorage(secretStorage); + const crossSigningPublicKeysOnDevice = Boolean(crossSigning.getId()); + const crossSigningPrivateKeysInStorage = Boolean(await crossSigning.isStoredInSecretStorage(secretStorage)); const masterPrivateKeyCached = !!(pkCache && await pkCache.getCrossSigningKeyCache("master")); const selfSigningPrivateKeyCached = !!(pkCache && await pkCache.getCrossSigningKeyCache("self_signing")); const userSigningPrivateKeyCached = !!(pkCache && await pkCache.getCrossSigningKeyCache("user_signing")); @@ -110,8 +112,8 @@ export default class CrossSigningPanel extends React.PureComponent { * 3. All keys are loaded and there's nothing to do. * @param {bool} [forceReset] Bootstrap again even if keys already present */ - _bootstrapCrossSigning = async ({ forceReset = false }) => { - this.setState({ error: null }); + private bootstrapCrossSigning = async ({ forceReset = false }): Promise => { + this.setState({ error: undefined }); try { const cli = MatrixClientPeg.get(); await cli.bootstrapCrossSigning({ @@ -135,20 +137,20 @@ export default class CrossSigningPanel extends React.PureComponent { this.setState({ error: e }); console.error("Error bootstrapping cross-signing", e); } - if (this._unmounted) return; - this._getUpdatedStatus(); - } + if (this.unmounted) return; + this.getUpdatedStatus(); + }; - _resetCrossSigning = () => { + private resetCrossSigning = (): void => { Modal.createDialog(ConfirmDestroyCrossSigningDialog, { onFinished: (act) => { if (!act) return; - this._bootstrapCrossSigning({ forceReset: true }); + this.bootstrapCrossSigning({ forceReset: true }); }, }); - } + }; - render() { + public render() { const AccessibleButton = sdk.getComponent("elements.AccessibleButton"); const { error, @@ -208,7 +210,7 @@ export default class CrossSigningPanel extends React.PureComponent { // TODO: determine how better to expose this to users in addition to prompts at login/toast if (!keysExistEverywhere && homeserverSupportsCrossSigning) { actions.push( - + { _t("Set up") } , ); @@ -216,7 +218,7 @@ export default class CrossSigningPanel extends React.PureComponent { if (keysExistAnywhere) { actions.push( - + { _t("Reset") } , ); From 7c8637f5dbba2f13b7cc395465e7c421cfef9371 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 13 Aug 2021 18:18:48 +0100 Subject: [PATCH 71/85] Add MatrixEvent type --- src/components/views/settings/CrossSigningPanel.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/views/settings/CrossSigningPanel.tsx b/src/components/views/settings/CrossSigningPanel.tsx index 3dd9ea0512..99b1ba3402 100644 --- a/src/components/views/settings/CrossSigningPanel.tsx +++ b/src/components/views/settings/CrossSigningPanel.tsx @@ -24,6 +24,7 @@ import Spinner from '../elements/Spinner'; import InteractiveAuthDialog from '../dialogs/InteractiveAuthDialog'; import ConfirmDestroyCrossSigningDialog from '../dialogs/security/ConfirmDestroyCrossSigningDialog'; import { replaceableComponent } from "../../../utils/replaceableComponent"; +import { MatrixEvent } from '../../../../../matrix-js-sdk/src'; interface IState { error?: Error; @@ -63,7 +64,7 @@ export default class CrossSigningPanel extends React.PureComponent<{}, IState> { cli.removeListener("crossSigning.keysChanged", this.onStatusChanged); } - private onAccountData = (event): void => { + private onAccountData = (event: MatrixEvent): void => { const type = event.getType(); if (type.startsWith("m.cross_signing") || type.startsWith("m.secret_storage")) { this.getUpdatedStatus(); From c28d449f3fec9881986faef28e2d1b0d3ba2c235 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 13 Aug 2021 18:21:59 +0100 Subject: [PATCH 72/85] Fix import thanks vscode --- src/components/views/settings/CrossSigningPanel.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/settings/CrossSigningPanel.tsx b/src/components/views/settings/CrossSigningPanel.tsx index 99b1ba3402..21e38a762a 100644 --- a/src/components/views/settings/CrossSigningPanel.tsx +++ b/src/components/views/settings/CrossSigningPanel.tsx @@ -24,7 +24,7 @@ import Spinner from '../elements/Spinner'; import InteractiveAuthDialog from '../dialogs/InteractiveAuthDialog'; import ConfirmDestroyCrossSigningDialog from '../dialogs/security/ConfirmDestroyCrossSigningDialog'; import { replaceableComponent } from "../../../utils/replaceableComponent"; -import { MatrixEvent } from '../../../../../matrix-js-sdk/src'; +import { MatrixEvent } from 'matrix-js-sdk/src'; interface IState { error?: Error; From a6cde3256ec0ed2fc6e893c3a91dd3a1f4698b7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 15 Aug 2021 12:56:29 +0200 Subject: [PATCH 73/85] Properly handle call change 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, 13 insertions(+), 4 deletions(-) diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index a6ae71713b..9d82291286 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -111,7 +111,7 @@ export default class CallView extends React.Component { constructor(props: IProps) { super(props); - const { primary, secondary } = this.getOrderedFeeds(this.props.call.getFeeds()); + const { primary, secondary } = CallView.getOrderedFeeds(this.props.call.getFeeds()); this.state = { isLocalOnHold: this.props.call.isLocalOnHold(), @@ -147,7 +147,16 @@ export default class CallView extends React.Component { dis.unregister(this.dispatcherRef); } - public componentDidUpdate(prevProps) { + static getDerivedStateFromProps(props: IProps): Partial { + const { primary, secondary } = CallView.getOrderedFeeds(props.call.getFeeds()); + + return { + primaryFeed: primary, + secondaryFeeds: secondary, + }; + } + + public componentDidUpdate(prevProps: IProps): void { if (this.props.call === prevProps.call) return; this.setState({ @@ -201,7 +210,7 @@ export default class CallView extends React.Component { }; private onFeedsChanged = (newFeeds: Array) => { - const { primary, secondary } = this.getOrderedFeeds(newFeeds); + const { primary, secondary } = CallView.getOrderedFeeds(newFeeds); this.setState({ primaryFeed: primary, secondaryFeeds: secondary, @@ -226,7 +235,7 @@ export default class CallView extends React.Component { this.buttonsRef.current?.showControls(); }; - private getOrderedFeeds(feeds: Array): { primary: CallFeed, secondary: Array } { + static getOrderedFeeds(feeds: Array): { primary: CallFeed, secondary: Array } { let primary; // Try to use a screensharing as primary, a remote one if possible From ae1905573404e51808806ada21b35c5f3150e30a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 16 Aug 2021 09:34:28 +0200 Subject: [PATCH 74/85] Fix long display names in call toasts 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 | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/res/css/views/toasts/_IncomingCallToast.scss b/res/css/views/toasts/_IncomingCallToast.scss index 975628f948..eb80f2d5cf 100644 --- a/res/css/views/toasts/_IncomingCallToast.scss +++ b/res/css/views/toasts/_IncomingCallToast.scss @@ -30,7 +30,14 @@ limitations under the License. font-size: $font-15px; line-height: $font-18px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + margin-top: 2px; + margin-right: 6px; + + max-width: 200px; } .mx_CallEvent_type { From 8770317b36fd8362acb54666e17274bcfeb8a856 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 15 Aug 2021 12:56:29 +0200 Subject: [PATCH 75/85] Properly handle call change 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, 13 insertions(+), 4 deletions(-) diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index e56c34520c..1dfd9083e5 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -129,7 +129,7 @@ export default class CallView extends React.Component { constructor(props: IProps) { super(props); - const { primary, secondary } = this.getOrderedFeeds(this.props.call.getFeeds()); + const { primary, secondary } = CallView.getOrderedFeeds(this.props.call.getFeeds()); this.state = { isLocalOnHold: this.props.call.isLocalOnHold(), @@ -166,7 +166,16 @@ export default class CallView extends React.Component { dis.unregister(this.dispatcherRef); } - public componentDidUpdate(prevProps) { + static getDerivedStateFromProps(props: IProps): Partial { + const { primary, secondary } = CallView.getOrderedFeeds(props.call.getFeeds()); + + return { + primaryFeed: primary, + secondaryFeeds: secondary, + }; + } + + public componentDidUpdate(prevProps: IProps): void { if (this.props.call === prevProps.call) return; this.setState({ @@ -220,7 +229,7 @@ export default class CallView extends React.Component { }; private onFeedsChanged = (newFeeds: Array) => { - const { primary, secondary } = this.getOrderedFeeds(newFeeds); + const { primary, secondary } = CallView.getOrderedFeeds(newFeeds); this.setState({ primaryFeed: primary, secondaryFeeds: secondary, @@ -253,7 +262,7 @@ export default class CallView extends React.Component { this.showControls(); }; - private getOrderedFeeds(feeds: Array): { primary: CallFeed, secondary: Array } { + static getOrderedFeeds(feeds: Array): { primary: CallFeed, secondary: Array } { let primary; // Try to use a screensharing as primary, a remote one if possible From d0e82ee603304acaca4af62d523559b4bd42289d Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 16 Aug 2021 13:18:10 +0100 Subject: [PATCH 76/85] Update changelog generator --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index fa1258ef24..ce9af6e5c2 100644 --- a/package.json +++ b/package.json @@ -149,7 +149,7 @@ "@typescript-eslint/eslint-plugin": "^4.17.0", "@typescript-eslint/parser": "^4.17.0", "@wojtekmaj/enzyme-adapter-react-17": "^0.6.1", - "allchange": "^0.0.1", + "allchange": "^1.0.0", "babel-jest": "^26.6.3", "chokidar": "^3.5.1", "concurrently": "^5.3.0", diff --git a/yarn.lock b/yarn.lock index d91b504757..7374276ccf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2005,10 +2005,10 @@ ajv@^8.0.1: require-from-string "^2.0.2" uri-js "^4.2.2" -allchange@^0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/allchange/-/allchange-0.0.1.tgz#3f66f7b06314b942fd0c1630fef6ce2ac1b90972" - integrity sha512-YVg1ZcYzEE5/fEnexzlhfWIbPeGtjfFgJ73qUG2DNwl16Apt9sRJQE7z6dLbDPaL9tOlYpIgzf7Id74uS+ccjQ== +allchange@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/allchange/-/allchange-1.0.0.tgz#f5177b7d97f8e97a2d059a1524db9a72d94dc6d2" + integrity sha512-O0VIaMIORxOaReyYEijDfKdpudJhbzzVYLdJR1aROyUgOLBEp9e5V/TDXQpjX23W90IFCSRZxsDb3exLRD05HA== dependencies: "@actions/core" "^1.4.0" "@actions/github" "^5.0.0" From e3930638dcd24e8dd6565b83460c16f041fa2da2 Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 16 Aug 2021 13:25:59 +0100 Subject: [PATCH 77/85] Fix dates --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a49685839f..a4a8985d73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -Changes in [3.28.0-rc.1](https://github.com/vector-im/element-desktop/releases/tag/v3.28.0-rc.1) (2021-07-11) +Changes in [3.28.0-rc.1](https://github.com/vector-im/element-desktop/releases/tag/v3.28.0-rc.1) (2021-08-11) ============================================================================================================= ## ✨ Features @@ -61,7 +61,7 @@ Changes in [3.28.0-rc.1](https://github.com/vector-im/element-desktop/releases/t * Fix clicking pills inside replies ([\#6508](https://github.com/matrix-org/matrix-react-sdk/pull/6508)). Fixes vector-im/element-web#18283. Contributed by [SimonBrandner](https://github.com/SimonBrandner). * Fix grecaptcha regression ([\#6503](https://github.com/matrix-org/matrix-react-sdk/pull/6503)). Fixes vector-im/element-web#18284. Contributed by [Palid](https://github.com/Palid). -Changes in [3.27.0](https://github.com/vector-im/element-desktop/releases/tag/v3.27.0) (2021-07-02) +Changes in [3.27.0](https://github.com/vector-im/element-desktop/releases/tag/v3.27.0) (2021-08-02) =================================================================================================== ## 🔒 SECURITY FIXES From 62e1a3053281aa92e847a7ea3826a8b9f2ad00f0 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 16 Aug 2021 14:21:59 +0100 Subject: [PATCH 78/85] Upgrade matrix-js-sdk to 12.3.0 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index ce9af6e5c2..6aafae2b65 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "katex": "^0.12.0", "linkifyjs": "^2.1.9", "lodash": "^4.17.20", - "matrix-js-sdk": "12.3.0-rc.1", + "matrix-js-sdk": "12.3.0", "matrix-widget-api": "^0.1.0-beta.15", "minimist": "^1.2.5", "opus-recorder": "^8.0.3", diff --git a/yarn.lock b/yarn.lock index 7374276ccf..61207aa03d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5738,10 +5738,10 @@ mathml-tag-names@^2.1.3: resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3" integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg== -matrix-js-sdk@12.3.0-rc.1: - version "12.3.0-rc.1" - resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-12.3.0-rc.1.tgz#9e192f7bffb08d68deb1298e46082eda3273c6ee" - integrity sha512-sqvc5+y289qnoqFvq32XDiWFu3NPKOmmTTV6fB+OdtkP3VvU8In+6sXYWqY3Xz7ZD4nY+At6AijQmDPn2seJwQ== +matrix-js-sdk@12.3.0: + version "12.3.0" + resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-12.3.0.tgz#b4de14e9c26257e3137eb020b6b13678b01e37d7" + integrity sha512-ywdlpVnwTiddLCvh/sKaZ0/0CVTqqbKPsfpsKduH9GWzwXA/Jrkwt//ryU8l9MjJTrjLnwxTYgnjZgeEp4h5kQ== dependencies: "@babel/runtime" "^7.12.5" another-json "^0.2.0" From fc50870ae5e8f8f2bc685e20f03dd3889b9fcb99 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 16 Aug 2021 14:45:12 +0100 Subject: [PATCH 79/85] Prepare changelog for v3.28.0 --- CHANGELOG.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4a8985d73..02a1956fe4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ -Changes in [3.28.0-rc.1](https://github.com/vector-im/element-desktop/releases/tag/v3.28.0-rc.1) (2021-08-11) -============================================================================================================= +Changes in [3.28.0](https://github.com/vector-im/element-desktop/releases/tag/v3.28.0) (2021-08-16) +=================================================================================================== ## ✨ Features * Show how long a call was on call tiles ([\#6570](https://github.com/matrix-org/matrix-react-sdk/pull/6570)). Fixes vector-im/element-web#18405. Contributed by [SimonBrandner](https://github.com/SimonBrandner). @@ -23,6 +23,9 @@ Changes in [3.28.0-rc.1](https://github.com/vector-im/element-desktop/releases/t * Add support for screen sharing in 1:1 calls ([\#5992](https://github.com/matrix-org/matrix-react-sdk/pull/5992)). Contributed by [SimonBrandner](https://github.com/SimonBrandner). ## 🐛 Bug Fixes + * [Release] Fix glare related regressions ([\#6622](https://github.com/matrix-org/matrix-react-sdk/pull/6622)). Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * [Release] Fix PiP of held calls ([\#6612](https://github.com/matrix-org/matrix-react-sdk/pull/6612)). Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * [Release] Fix toast colors ([\#6607](https://github.com/matrix-org/matrix-react-sdk/pull/6607)). Contributed by [SimonBrandner](https://github.com/SimonBrandner). * Fix [object Object] in Widget Permissions ([\#6560](https://github.com/matrix-org/matrix-react-sdk/pull/6560)). Fixes vector-im/element-web#18384. Contributed by [Palid](https://github.com/Palid). * Fix right margin for events on IRC layout ([\#6542](https://github.com/matrix-org/matrix-react-sdk/pull/6542)). Fixes vector-im/element-web#18354. * Mirror only usermedia feeds ([\#6512](https://github.com/matrix-org/matrix-react-sdk/pull/6512)). Fixes vector-im/element-web#5633. Contributed by [SimonBrandner](https://github.com/SimonBrandner). From c4de03ddb0dc5c3a7e4950ab2a17d6fb70835b5f Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 16 Aug 2021 14:45:13 +0100 Subject: [PATCH 80/85] v3.28.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6aafae2b65..00b187defb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "3.28.0-rc.1", + "version": "3.28.0", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { From 0f382efb4669313cd7f345ad0c2fd51a2b2ff32e Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 16 Aug 2021 15:02:29 +0100 Subject: [PATCH 81/85] Resetting package fields for development --- package.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 00b187defb..0d56a0051c 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "bin": { "reskindex": "scripts/reskindex.js" }, - "main": "./lib/index.js", + "main": "./src/index.js", "matrix_src_main": "./src/index.js", "matrix_lib_main": "./lib/index.js", "matrix_lib_typings": "./lib/index.d.ts", @@ -205,6 +205,5 @@ "coverageReporters": [ "text" ] - }, - "typings": "./lib/index.d.ts" + } } From 6e82b064eca68b33b34b909a4a850c3554c12e65 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 17 Aug 2021 09:29:20 +0100 Subject: [PATCH 82/85] Upgrade matrix-js-sdk to 12.3.1 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 00b187defb..85706ff9d1 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "katex": "^0.12.0", "linkifyjs": "^2.1.9", "lodash": "^4.17.20", - "matrix-js-sdk": "12.3.0", + "matrix-js-sdk": "12.3.1", "matrix-widget-api": "^0.1.0-beta.15", "minimist": "^1.2.5", "opus-recorder": "^8.0.3", diff --git a/yarn.lock b/yarn.lock index 61207aa03d..1c425e205c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5738,10 +5738,10 @@ mathml-tag-names@^2.1.3: resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3" integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg== -matrix-js-sdk@12.3.0: - version "12.3.0" - resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-12.3.0.tgz#b4de14e9c26257e3137eb020b6b13678b01e37d7" - integrity sha512-ywdlpVnwTiddLCvh/sKaZ0/0CVTqqbKPsfpsKduH9GWzwXA/Jrkwt//ryU8l9MjJTrjLnwxTYgnjZgeEp4h5kQ== +matrix-js-sdk@12.3.1: + version "12.3.1" + resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-12.3.1.tgz#efa9172e96722afb03cb0e866b047022a7c6cb17" + integrity sha512-ysF6FQIjz8NaQHpXRs0VD1uh5kNi4tZh7NNPq54nS9YoDC7F681n2srFhoQn4M/Bv5tBLWWGNjKufXTLHp5j5g== dependencies: "@babel/runtime" "^7.12.5" another-json "^0.2.0" From 19746f7ac193e73a5d6c4ed5b0284ea858ff1f06 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 17 Aug 2021 09:40:10 +0100 Subject: [PATCH 83/85] Prepare changelog for v3.28.1 --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02a1956fe4..6f71c1414c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +Changes in [3.28.1](https://github.com/vector-im/element-desktop/releases/tag/v3.28.1) (2021-08-17) +=================================================================================================== + +## 🐛 Bug Fixes + * Fix multiple VoIP regressions ([matrix-org/matrix-js-sdk#1860](https://github.com/matrix-org/matrix-js-sdk/pull/1860)). + Changes in [3.28.0](https://github.com/vector-im/element-desktop/releases/tag/v3.28.0) (2021-08-16) =================================================================================================== From 0dd479c46646971627796c1473ffe4f40566669e Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 17 Aug 2021 09:40:11 +0100 Subject: [PATCH 84/85] v3.28.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 85706ff9d1..e875ca28b6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "3.28.0", + "version": "3.28.1", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { From d5ffdf119673456746ca0f22aa828ecd749265d2 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 17 Aug 2021 09:42:20 +0100 Subject: [PATCH 85/85] Reset matrix-js-sdk back to develop branch --- package.json | 2 +- yarn.lock | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 570c7d85b1..985a4210c1 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "katex": "^0.12.0", "linkifyjs": "^2.1.9", "lodash": "^4.17.20", - "matrix-js-sdk": "12.3.1", + "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop", "matrix-widget-api": "^0.1.0-beta.15", "minimist": "^1.2.5", "opus-recorder": "^8.0.3", diff --git a/yarn.lock b/yarn.lock index 1c425e205c..5410819c06 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5738,10 +5738,9 @@ mathml-tag-names@^2.1.3: resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3" integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg== -matrix-js-sdk@12.3.1: +"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop": version "12.3.1" - resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-12.3.1.tgz#efa9172e96722afb03cb0e866b047022a7c6cb17" - integrity sha512-ysF6FQIjz8NaQHpXRs0VD1uh5kNi4tZh7NNPq54nS9YoDC7F681n2srFhoQn4M/Bv5tBLWWGNjKufXTLHp5j5g== + resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/3216d7e5a7a333212b00d4d7578e29a9f0e247d8" dependencies: "@babel/runtime" "^7.12.5" another-json "^0.2.0"