From 3df9557df2f886db13185872641b2f763baf41aa Mon Sep 17 00:00:00 2001
From: Ayush PS
Date: Wed, 24 Mar 2021 14:00:09 +0530
Subject: [PATCH 001/100] Dial Pad UI fix
---
res/css/views/voip/_DialPad.scss | 1 +
res/css/views/voip/_DialPadContextMenu.scss | 4 +++-
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/res/css/views/voip/_DialPad.scss b/res/css/views/voip/_DialPad.scss
index 0c7bff0ce8..fd7c5f56f6 100644
--- a/res/css/views/voip/_DialPad.scss
+++ b/res/css/views/voip/_DialPad.scss
@@ -30,6 +30,7 @@ limitations under the License.
text-align: center;
vertical-align: middle;
line-height: 40px;
+ color: #15191e;
}
.mx_DialPad_deleteButton, .mx_DialPad_dialButton {
diff --git a/res/css/views/voip/_DialPadContextMenu.scss b/res/css/views/voip/_DialPadContextMenu.scss
index 520f51cf93..c400060b6c 100644
--- a/res/css/views/voip/_DialPadContextMenu.scss
+++ b/res/css/views/voip/_DialPadContextMenu.scss
@@ -27,9 +27,11 @@ limitations under the License.
}
.mx_DialPadContextMenu_dialled {
- height: 1em;
+ height: 1.5em;
font-size: 18px;
font-weight: 600;
+ max-width: 150px;
+ overflow: auto;
}
.mx_DialPadContextMenu_dialPad {
From 1488457c3322b6d5dd6ef7882d5b14d9bf49e20f Mon Sep 17 00:00:00 2001
From: Ayush PS
Date: Thu, 25 Mar 2021 01:31:45 +0530
Subject: [PATCH 002/100] Added the class -button-bg-color for all themes
---
res/css/views/voip/_DialPad.scss | 3 +--
res/themes/dark/css/_dark.scss | 2 ++
res/themes/legacy-dark/css/_legacy-dark.scss | 1 +
res/themes/legacy-light/css/_legacy-light.scss | 2 ++
res/themes/light/css/_light.scss | 2 ++
5 files changed, 8 insertions(+), 2 deletions(-)
diff --git a/res/css/views/voip/_DialPad.scss b/res/css/views/voip/_DialPad.scss
index fd7c5f56f6..483b131bfe 100644
--- a/res/css/views/voip/_DialPad.scss
+++ b/res/css/views/voip/_DialPad.scss
@@ -23,14 +23,13 @@ limitations under the License.
.mx_DialPad_button {
width: 40px;
height: 40px;
- background-color: $theme-button-bg-color;
+ background-color: $dialpad-button-bg-color;
border-radius: 40px;
font-size: 18px;
font-weight: 600;
text-align: center;
vertical-align: middle;
line-height: 40px;
- color: #15191e;
}
.mx_DialPad_deleteButton, .mx_DialPad_dialButton {
diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss
index 7a751ad9c1..42d592c1e1 100644
--- a/res/themes/dark/css/_dark.scss
+++ b/res/themes/dark/css/_dark.scss
@@ -114,6 +114,8 @@ $voipcall-plinth-color: #21262c;
// ********************
$theme-button-bg-color: #e3e8f0;
+$dialpad-button-bg-color: #545454;
+
$roomlist-button-bg-color: rgba(141, 151, 165, 0.2); // Buttons include the filter box, explore button, and sublist buttons
$roomlist-filter-active-bg-color: $bg-color;
diff --git a/res/themes/legacy-dark/css/_legacy-dark.scss b/res/themes/legacy-dark/css/_legacy-dark.scss
index 764b8f302a..ae98141d06 100644
--- a/res/themes/legacy-dark/css/_legacy-dark.scss
+++ b/res/themes/legacy-dark/css/_legacy-dark.scss
@@ -111,6 +111,7 @@ $voipcall-plinth-color: #f2f5f8;
// ********************
$theme-button-bg-color: #e3e8f0;
+$dialpad-button-bg-color: #545454;
$roomlist-button-bg-color: #1A1D23; // Buttons include the filter box, explore button, and sublist buttons
$roomlist-filter-active-bg-color: $roomlist-button-bg-color;
diff --git a/res/themes/legacy-light/css/_legacy-light.scss b/res/themes/legacy-light/css/_legacy-light.scss
index 9ad154dd93..4313e3c0b6 100644
--- a/res/themes/legacy-light/css/_legacy-light.scss
+++ b/res/themes/legacy-light/css/_legacy-light.scss
@@ -178,6 +178,8 @@ $voipcall-plinth-color: #f2f5f8;
// ********************
$theme-button-bg-color: #e3e8f0;
+$dialpad-button-bg-color: #e3e8f0;
+
$roomlist-button-bg-color: #fff; // Buttons include the filter box, explore button, and sublist buttons
$roomlist-filter-active-bg-color: $roomlist-button-bg-color;
diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss
index 25fbd0201b..81330d07c9 100644
--- a/res/themes/light/css/_light.scss
+++ b/res/themes/light/css/_light.scss
@@ -169,6 +169,8 @@ $voipcall-plinth-color: #f2f5f8;
// ********************
$theme-button-bg-color: #e3e8f0;
+$dialpad-button-bg-color: #e3e8f0;
+
$roomlist-button-bg-color: rgba(141, 151, 165, 0.2); // Buttons include the filter box, explore button, and sublist buttons
$roomlist-filter-active-bg-color: #ffffff;
From 3201ed2f0fe0e378c741d57d9a147d79b267b842 Mon Sep 17 00:00:00 2001
From: Ayush PS
Date: Mon, 26 Apr 2021 01:40:10 +0530
Subject: [PATCH 003/100] Added color scheme for the numbers
---
res/css/views/voip/_DialPadContextMenu.scss | 2 +-
res/themes/dark/css/_dark.scss | 3 ++-
res/themes/legacy-dark/css/_legacy-dark.scss | 3 ++-
3 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/res/css/views/voip/_DialPadContextMenu.scss b/res/css/views/voip/_DialPadContextMenu.scss
index c400060b6c..9879b7da1c 100644
--- a/res/css/views/voip/_DialPadContextMenu.scss
+++ b/res/css/views/voip/_DialPadContextMenu.scss
@@ -30,7 +30,7 @@ limitations under the License.
height: 1.5em;
font-size: 18px;
font-weight: 600;
- max-width: 150px;
+ max-width: 155px;
overflow: auto;
}
diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss
index 42d592c1e1..b83bd52f76 100644
--- a/res/themes/dark/css/_dark.scss
+++ b/res/themes/dark/css/_dark.scss
@@ -114,7 +114,8 @@ $voipcall-plinth-color: #21262c;
// ********************
$theme-button-bg-color: #e3e8f0;
-$dialpad-button-bg-color: #545454;
+$dialpad-button-bg-color: #6F7882;
+;
$roomlist-button-bg-color: rgba(141, 151, 165, 0.2); // Buttons include the filter box, explore button, and sublist buttons
diff --git a/res/themes/legacy-dark/css/_legacy-dark.scss b/res/themes/legacy-dark/css/_legacy-dark.scss
index ae98141d06..ff85375d35 100644
--- a/res/themes/legacy-dark/css/_legacy-dark.scss
+++ b/res/themes/legacy-dark/css/_legacy-dark.scss
@@ -111,7 +111,8 @@ $voipcall-plinth-color: #f2f5f8;
// ********************
$theme-button-bg-color: #e3e8f0;
-$dialpad-button-bg-color: #545454;
+$dialpad-button-bg-color: #6F7882;
+;
$roomlist-button-bg-color: #1A1D23; // Buttons include the filter box, explore button, and sublist buttons
$roomlist-filter-active-bg-color: $roomlist-button-bg-color;
From 7509481bb9339e038027efa6564df9746b73518a Mon Sep 17 00:00:00 2001
From: Ayush PS
Date: Wed, 28 Apr 2021 02:46:43 +0530
Subject: [PATCH 004/100] Added the LTR support for the dialpad
---
res/css/views/voip/_DialPadContextMenu.scss | 14 ++++++++++++--
.../views/context_menus/DialpadContextMenu.tsx | 13 ++++++++++++-
2 files changed, 24 insertions(+), 3 deletions(-)
diff --git a/res/css/views/voip/_DialPadContextMenu.scss b/res/css/views/voip/_DialPadContextMenu.scss
index 9879b7da1c..c01ce0f2d9 100644
--- a/res/css/views/voip/_DialPadContextMenu.scss
+++ b/res/css/views/voip/_DialPadContextMenu.scss
@@ -30,8 +30,18 @@ limitations under the License.
height: 1.5em;
font-size: 18px;
font-weight: 600;
- max-width: 155px;
- overflow: auto;
+ max-width: 150px;
+ border: none;
+ margin: 0px;
+
+}
+.mx_DialPadContextMenu_dialled input{
+ font-size: 18px;
+ font-weight: 600;
+ overflow: hidden;
+ text-align: left;
+ direction: rtl;
+ background-color: rgb(0, 0, 0, 0);
}
.mx_DialPadContextMenu_dialPad {
diff --git a/src/components/views/context_menus/DialpadContextMenu.tsx b/src/components/views/context_menus/DialpadContextMenu.tsx
index 17abce0c61..0a1d8184f2 100644
--- a/src/components/views/context_menus/DialpadContextMenu.tsx
+++ b/src/components/views/context_menus/DialpadContextMenu.tsx
@@ -18,6 +18,7 @@ import React from 'react';
import { _t } from '../../../languageHandler';
import { ContextMenu, IProps as IContextMenuProps } from '../../structures/ContextMenu';
import { MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
+import Field from "../elements/Field";
import Dialpad from '../voip/DialPad';
import {replaceableComponent} from "../../../utils/replaceableComponent";
@@ -44,13 +45,23 @@ export default class DialpadContextMenu extends React.Component
this.setState({value: this.state.value + digit});
}
+ onChange = (ev) => {
+ this.setState({value: ev.target.value});
+ }
+
+
render() {
return
{_t("Dial pad")}
-
{this.state.value}
+
From b42872daa409d27ad4634d2418fee2bfa7dccfea Mon Sep 17 00:00:00 2001
From: Ayush PS
Date: Sun, 2 May 2021 22:10:15 +0530
Subject: [PATCH 005/100] Fixed the Dial Pad and finalized the changes
---
res/css/views/voip/_DialPadContextMenu.scss | 5 +++--
.../views/context_menus/DialpadContextMenu.tsx | 12 +++++-------
2 files changed, 8 insertions(+), 9 deletions(-)
diff --git a/res/css/views/voip/_DialPadContextMenu.scss b/res/css/views/voip/_DialPadContextMenu.scss
index c01ce0f2d9..31327113cf 100644
--- a/res/css/views/voip/_DialPadContextMenu.scss
+++ b/res/css/views/voip/_DialPadContextMenu.scss
@@ -33,14 +33,15 @@ limitations under the License.
max-width: 150px;
border: none;
margin: 0px;
-
}
-.mx_DialPadContextMenu_dialled input{
+.mx_DialPadContextMenu_dialled input {
font-size: 18px;
font-weight: 600;
overflow: hidden;
+ max-width: 150px;
text-align: left;
direction: rtl;
+ padding: 8px 0px;
background-color: rgb(0, 0, 0, 0);
}
diff --git a/src/components/views/context_menus/DialpadContextMenu.tsx b/src/components/views/context_menus/DialpadContextMenu.tsx
index 0a1d8184f2..8879629055 100644
--- a/src/components/views/context_menus/DialpadContextMenu.tsx
+++ b/src/components/views/context_menus/DialpadContextMenu.tsx
@@ -48,7 +48,7 @@ export default class DialpadContextMenu extends React.Component
onChange = (ev) => {
this.setState({value: ev.target.value});
}
-
+
render() {
return
@@ -56,12 +56,10 @@ export default class DialpadContextMenu extends React.Component
{_t("Dial pad")}
-
+
From 44b143c8c3063be7ca2bf24e6cfdb81be9351c75 Mon Sep 17 00:00:00 2001
From: Robin Townsend
Date: Sat, 8 May 2021 21:17:05 -0400
Subject: [PATCH 006/100] Match requested avatar size to displayed size
Reduces the blurriness of avatars in the EventTilePreview.
Signed-off-by: Robin Townsend
---
src/components/views/elements/EventTilePreview.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/views/elements/EventTilePreview.tsx b/src/components/views/elements/EventTilePreview.tsx
index b15fbbed2b..95f9a97058 100644
--- a/src/components/views/elements/EventTilePreview.tsx
+++ b/src/components/views/elements/EventTilePreview.tsx
@@ -61,7 +61,7 @@ interface IState {
message: string;
}
-const AVATAR_SIZE = 32;
+const AVATAR_SIZE = 30;
@replaceableComponent("views.elements.EventTilePreview")
export default class EventTilePreview extends React.Component {
From e46bc931781095447a1929938a5cb5bdbdb7de4d Mon Sep 17 00:00:00 2001
From: Robin Townsend
Date: Sat, 8 May 2021 21:22:31 -0400
Subject: [PATCH 007/100] Fall back to MXID when no display name is present
MemberAvatar requires a display name, or else it refuses to render.
Signed-off-by: Robin Townsend
---
src/components/views/elements/EventTilePreview.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/views/elements/EventTilePreview.tsx b/src/components/views/elements/EventTilePreview.tsx
index 95f9a97058..6d2ea687de 100644
--- a/src/components/views/elements/EventTilePreview.tsx
+++ b/src/components/views/elements/EventTilePreview.tsx
@@ -101,7 +101,7 @@ export default class EventTilePreview extends React.Component {
// Fake it more
event.sender = {
- name: this.props.displayName,
+ name: this.props.displayName || this.props.userId,
userId: this.props.userId,
getAvatarUrl: (..._) => {
return Avatar.avatarUrlForUser(
From 0b7d3f007aece9a15d53e53f0f953fa203da8457 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 1 Jun 2021 17:30:57 +0100
Subject: [PATCH 008/100] Remove react-beautiful-dnd
---
package.json | 1 -
src/components/structures/GroupFilterPanel.js | 32 ++---
src/components/structures/LoggedInView.tsx | 65 ++-------
src/components/views/elements/DNDTagTile.js | 32 ++---
.../views/groups/GroupPublicityToggle.js | 4 +-
src/components/views/groups/GroupTile.js | 44 +-----
test/components/views/rooms/RoomList-test.js | 5 +-
yarn.lock | 130 +-----------------
8 files changed, 36 insertions(+), 277 deletions(-)
diff --git a/package.json b/package.json
index 13047b69cf..270c86ddba 100644
--- a/package.json
+++ b/package.json
@@ -91,7 +91,6 @@
"qs": "^6.9.6",
"re-resizable": "^6.9.0",
"react": "^16.14.0",
- "react-beautiful-dnd": "^4.0.1",
"react-dom": "^16.14.0",
"react-focus-lock": "^2.5.0",
"react-transition-group": "^4.4.1",
diff --git a/src/components/structures/GroupFilterPanel.js b/src/components/structures/GroupFilterPanel.js
index 2ff91e4976..f1c28d588a 100644
--- a/src/components/structures/GroupFilterPanel.js
+++ b/src/components/structures/GroupFilterPanel.js
@@ -24,7 +24,6 @@ import * as sdk from '../../index';
import dis from '../../dispatcher/dispatcher';
import { _t } from '../../languageHandler';
-import { Droppable } from 'react-beautiful-dnd';
import classNames from 'classnames';
import MatrixClientContext from "../../contexts/MatrixClientContext";
import AutoHideScrollbar from "./AutoHideScrollbar";
@@ -83,7 +82,7 @@ class GroupFilterPanel extends React.Component {
}
};
- onMouseDown = e => {
+ onClick = e => {
// only dispatch if its not a no-op
if (this.state.selectedTags.length > 0) {
dis.dispatch({action: 'deselect_tags'});
@@ -151,28 +150,15 @@ class GroupFilterPanel extends React.Component {
return
-
- { (provided, snapshot) => (
-
- { this.renderGlobalIcon() }
- { tags }
-
- {createButton}
-
- { provided.placeholder }
-
- ) }
-
+
+ { this.renderGlobalIcon() }
+ { tags }
+
+ { createButton }
+
+
;
}
diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx
index ad5c759f0d..f5df99d8c9 100644
--- a/src/components/structures/LoggedInView.tsx
+++ b/src/components/structures/LoggedInView.tsx
@@ -19,7 +19,6 @@ limitations under the License.
import * as React from 'react';
import * as PropTypes from 'prop-types';
import { MatrixClient } from 'matrix-js-sdk/src/client';
-import { DragDropContext } from 'react-beautiful-dnd';
import {Key} from '../../Keyboard';
import PageTypes from '../../PageTypes';
@@ -569,50 +568,6 @@ class LoggedInView extends React.Component {
}
};
- _onDragEnd = (result) => {
- // Dragged to an invalid destination, not onto a droppable
- if (!result.destination) {
- return;
- }
-
- const dest = result.destination.droppableId;
-
- if (dest === 'tag-panel-droppable') {
- // Could be "GroupTile +groupId:domain"
- const draggableId = result.draggableId.split(' ').pop();
-
- // Dispatch synchronously so that the GroupFilterPanel receives an
- // optimistic update from GroupFilterOrderStore before the previous
- // state is shown.
- dis.dispatch(TagOrderActions.moveTag(
- this._matrixClient,
- draggableId,
- result.destination.index,
- ), true);
- } else if (dest.startsWith('room-sub-list-droppable_')) {
- this._onRoomTileEndDrag(result);
- }
- };
-
- _onRoomTileEndDrag = (result) => {
- let newTag = result.destination.droppableId.split('_')[1];
- let prevTag = result.source.droppableId.split('_')[1];
- if (newTag === 'undefined') newTag = undefined;
- if (prevTag === 'undefined') prevTag = undefined;
-
- const roomId = result.draggableId.split('_')[1];
-
- const oldIndex = result.source.index;
- const newIndex = result.destination.index;
-
- dis.dispatch(RoomListActions.tagRoom(
- this._matrixClient,
- this._matrixClient.getRoom(roomId),
- prevTag, newTag,
- oldIndex, newIndex,
- ), true);
- };
-
render() {
const RoomView = sdk.getComponent('structures.RoomView');
const UserView = sdk.getComponent('structures.UserView');
@@ -679,17 +634,15 @@ class LoggedInView extends React.Component {
aria-hidden={this.props.hideToSRUsers}
>
-
-
- { SettingsStore.getValue("feature_spaces") ? : null }
-
-
- { pageElement }
-
-
+
+ { SettingsStore.getValue("feature_spaces") ? : null }
+
+
+ { pageElement }
+
diff --git a/src/components/views/elements/DNDTagTile.js b/src/components/views/elements/DNDTagTile.js
index 67572d4508..eaaa0f183b 100644
--- a/src/components/views/elements/DNDTagTile.js
+++ b/src/components/views/elements/DNDTagTile.js
@@ -18,7 +18,6 @@ limitations under the License.
import TagTile from './TagTile';
import React from 'react';
-import { Draggable } from 'react-beautiful-dnd';
import { ContextMenu, toRightOf, useContextMenu } from "../../structures/ContextMenu";
import * as sdk from '../../../index';
@@ -35,28 +34,13 @@ export default function DNDTagTile(props) {
);
}
- return
-
- {(provided, snapshot) => (
-
-
-
- )}
-
+ return <>
+
{contextMenu}
-
;
+ >;
}
diff --git a/src/components/views/groups/GroupPublicityToggle.js b/src/components/views/groups/GroupPublicityToggle.js
index c06d827550..6bef141cb8 100644
--- a/src/components/views/groups/GroupPublicityToggle.js
+++ b/src/components/views/groups/GroupPublicityToggle.js
@@ -66,9 +66,7 @@ export default class GroupPublicityToggle extends React.Component {
render() {
const GroupTile = sdk.getComponent('groups.GroupTile');
return
-
+
diff --git a/src/components/views/groups/GroupTile.js b/src/components/views/groups/GroupTile.js
index 42a977fb79..ce42662462 100644
--- a/src/components/views/groups/GroupTile.js
+++ b/src/components/views/groups/GroupTile.js
@@ -16,7 +16,6 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
-import { Draggable, Droppable } from 'react-beautiful-dnd';
import * as sdk from '../../../index';
import dis from '../../../dispatcher/dispatcher';
import FlairStore from '../../../stores/FlairStore';
@@ -24,8 +23,6 @@ import MatrixClientContext from "../../../contexts/MatrixClientContext";
import {replaceableComponent} from "../../../utils/replaceableComponent";
import {mediaFromMxc} from "../../../customisations/Media";
-function nop() {}
-
@replaceableComponent("views.groups.GroupTile")
class GroupTile extends React.Component {
static propTypes = {
@@ -34,7 +31,6 @@ class GroupTile extends React.Component {
showDescription: PropTypes.bool,
// Height of the group avatar in pixels
avatarHeight: PropTypes.number,
- draggable: PropTypes.bool,
};
static contextType = MatrixClientContext;
@@ -42,7 +38,6 @@ class GroupTile extends React.Component {
static defaultProps = {
showDescription: true,
avatarHeight: 50,
- draggable: true,
};
state = {
@@ -57,7 +52,7 @@ class GroupTile extends React.Component {
});
}
- onMouseDown = e => {
+ onClick = e => {
e.preventDefault();
dis.dispatch({
action: 'view_group',
@@ -78,7 +73,7 @@ class GroupTile extends React.Component {
? mediaFromMxc(profile.avatarUrl).getSquareThumbnailHttp(avatarHeight)
: null;
- let avatarElement = (
+ const avatarElement = (
);
- if (this.props.draggable) {
- const avatarClone = avatarElement;
- avatarElement = (
-
- { (droppableProvided, droppableSnapshot) => (
-
-
- { (provided, snapshot) => (
-
-
- {avatarClone}
-
- { /* Instead of a blank placeholder, use a copy of the avatar itself. */ }
- { provided.placeholder ? avatarClone :
}
-
- ) }
-
-
- ) }
-
- );
- }
- // XXX: Use onMouseDown as a workaround for https://github.com/atlassian/react-beautiful-dnd/issues/273
- // instead of onClick. Otherwise we experience https://github.com/vector-im/element-web/issues/6156
- return
+ return
{ avatarElement }
{ name }
diff --git a/test/components/views/rooms/RoomList-test.js b/test/components/views/rooms/RoomList-test.js
index bfb8e1afd4..6aad6a90fd 100644
--- a/test/components/views/rooms/RoomList-test.js
+++ b/test/components/views/rooms/RoomList-test.js
@@ -6,7 +6,6 @@ import * as TestUtils from '../../../test-utils';
import {MatrixClientPeg} from '../../../../src/MatrixClientPeg';
import sdk from '../../../skinned-sdk';
-import { DragDropContext } from 'react-beautiful-dnd';
import dis from '../../../../src/dispatcher/dispatcher';
import DMRoomMap from '../../../../src/utils/DMRoomMap';
@@ -68,9 +67,7 @@ describe('RoomList', () => {
const RoomList = sdk.getComponent('views.rooms.RoomList');
const WrappedRoomList = TestUtils.wrapInMatrixClientContext(RoomList);
root = ReactDOM.render(
-
- {}} />
- ,
+
{}} />,
parentDiv,
);
ReactTestUtils.findRenderedComponentWithType(root, RoomList);
diff --git a/yarn.lock b/yarn.lock
index 0ff235a660..2c84237730 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1017,7 +1017,7 @@
pirates "^4.0.0"
source-map-support "^0.5.16"
-"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7":
+"@babel/runtime@^7.0.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7":
version "7.12.5"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e"
integrity sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==
@@ -2114,14 +2114,6 @@ babel-preset-jest@^26.6.2:
babel-plugin-jest-hoist "^26.6.2"
babel-preset-current-node-syntax "^1.0.0"
-babel-runtime@^6.26.0:
- version "6.26.0"
- resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
- integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4=
- dependencies:
- core-js "^2.4.0"
- regenerator-runtime "^0.11.0"
-
bail@^1.0.0:
version "1.0.5"
resolved "https://registry.yarnpkg.com/bail/-/bail-1.0.5.tgz#b6fa133404a392cbc1f8c4bf63f5953351e7a776"
@@ -2645,11 +2637,6 @@ core-js@^1.0.0:
resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636"
integrity sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=
-core-js@^2.4.0:
- version "2.6.12"
- resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec"
- integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==
-
core-util-is@1.0.2, core-util-is@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
@@ -4215,13 +4202,6 @@ highlight.js@^10.5.0:
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.5.0.tgz#3f09fede6a865757378f2d9ebdcbc15ba268f98f"
integrity sha512-xTmvd9HiIHR6L53TMC7TKolEj65zG1XU+Onr8oi86mYa+nLcIbxTTWkpW7CsEwv/vK7u1zb8alZIMLDqqN6KTw==
-hoist-non-react-statics@^3.3.0:
- version "3.3.2"
- resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
- integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
- dependencies:
- react-is "^16.7.0"
-
hosted-git-info@^2.1.4:
version "2.8.9"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
@@ -4430,13 +4410,6 @@ internal-slot@^1.0.2:
has "^1.0.3"
side-channel "^1.0.2"
-invariant@^2.2.2, invariant@^2.2.4:
- version "2.2.4"
- resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
- integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==
- dependencies:
- loose-envify "^1.0.0"
-
ip-regex@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9"
@@ -5556,11 +5529,6 @@ locate-path@^5.0.0:
dependencies:
p-locate "^4.1.0"
-lodash-es@^4.2.1:
- version "4.17.20"
- resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.20.tgz#29f6332eefc60e849f869c264bc71126ad61e8f7"
- integrity sha512-JD1COMZsq8maT6mnuz1UMV0jvYD0E0aUsSOdrr1/nAG3dhqQXwRRgeW0cSqH1U43INKcqxaiVIQNOUDld7gRDA==
-
lodash.escape@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-4.0.1.tgz#c9044690c21e04294beaa517712fded1fa88de98"
@@ -5581,7 +5549,7 @@ lodash.sortby@^4.7.0:
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=
-lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.2.1:
+lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@@ -5749,11 +5717,6 @@ mdurl@~1.0.1:
resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e"
integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=
-memoize-one@^3.0.1:
- version "3.1.1"
- resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-3.1.1.tgz#ef609811e3bc28970eac2884eece64d167830d17"
- integrity sha512-YqVh744GsMlZu6xkhGslPSqSurOv6P+kLN2J3ysBZfagLcL5FdRK/0UpgLoL8hwjjEvvAVkjJZyFP+1T6p1vgA==
-
meow@^9.0.0:
version "9.0.0"
resolved "https://registry.yarnpkg.com/meow/-/meow-9.0.0.tgz#cd9510bc5cac9dee7d03c73ee1f9ad959f4ea364"
@@ -6374,11 +6337,6 @@ path-type@^4.0.0:
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
-performance-now@^0.2.0:
- version "0.2.0"
- resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5"
- integrity sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=
-
performance-now@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
@@ -6597,7 +6555,7 @@ prop-types-exact@^1.2.0:
object.assign "^4.1.0"
reflect.ownkeys "^0.2.0"
-prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2:
+prop-types@^15.6.2, prop-types@^15.7.2:
version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
@@ -6674,12 +6632,7 @@ quick-lru@^4.0.1:
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f"
integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==
-raf-schd@^2.1.0:
- version "2.1.2"
- resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-2.1.2.tgz#ec622b5167f2912089f054dc03ebd5bcf33c8f62"
- integrity sha512-Orl0IEvMtUCgPddgSxtxreK77UiQz4nPYJy9RggVzu4mKsZkQWiAaG1y9HlYWdvm9xtN348xRaT37qkvL/+A+g==
-
-raf@^3.1.0, raf@^3.4.1:
+raf@^3.4.1:
version "3.4.1"
resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39"
integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==
@@ -6706,22 +6659,6 @@ re-resizable@^6.9.0:
dependencies:
fast-memoize "^2.5.1"
-react-beautiful-dnd@^4.0.1:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-4.0.1.tgz#3b0a49bf6be75af351176c904f012611dd292b81"
- integrity sha512-d73RMu4QOFCyjUELLWFyY/EuclnfqulI9pECx+2gIuJvV0ycf1uR88o+1x0RSB9ILD70inHMzCBKNkWVbbt+vA==
- dependencies:
- babel-runtime "^6.26.0"
- invariant "^2.2.2"
- memoize-one "^3.0.1"
- prop-types "^15.6.0"
- raf-schd "^2.1.0"
- react-motion "^0.5.2"
- react-redux "^5.0.6"
- redux "^3.7.2"
- redux-thunk "^2.2.0"
- reselect "^3.0.1"
-
react-clientside-effect@^1.2.2:
version "1.2.3"
resolved "https://registry.yarnpkg.com/react-clientside-effect/-/react-clientside-effect-1.2.3.tgz#95c95f520addfb71743608b990bfe01eb002012b"
@@ -6751,7 +6688,7 @@ react-focus-lock@^2.5.0:
use-callback-ref "^1.2.1"
use-sidecar "^1.0.1"
-react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.6:
+react-is@^16.13.1, react-is@^16.8.1, react-is@^16.8.6:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@@ -6761,33 +6698,6 @@ react-is@^17.0.1:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339"
integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==
-react-lifecycles-compat@^3.0.0:
- version "3.0.4"
- resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
- integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
-
-react-motion@^0.5.2:
- version "0.5.2"
- resolved "https://registry.yarnpkg.com/react-motion/-/react-motion-0.5.2.tgz#0dd3a69e411316567927917c6626551ba0607316"
- integrity sha512-9q3YAvHoUiWlP3cK0v+w1N5Z23HXMj4IF4YuvjvWegWqNPfLXsOBE/V7UvQGpXxHFKRQQcNcVQE31g9SB/6qgQ==
- dependencies:
- performance-now "^0.2.0"
- prop-types "^15.5.8"
- raf "^3.1.0"
-
-react-redux@^5.0.6:
- version "5.1.2"
- resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.1.2.tgz#b19cf9e21d694422727bf798e934a916c4080f57"
- integrity sha512-Ns1G0XXc8hDyH/OcBHOxNgQx9ayH3SPxBnFCOidGKSle8pKihysQw2rG/PmciUQRoclhVBO8HMhiRmGXnDja9Q==
- dependencies:
- "@babel/runtime" "^7.1.2"
- hoist-non-react-statics "^3.3.0"
- invariant "^2.2.4"
- loose-envify "^1.1.0"
- prop-types "^15.6.1"
- react-is "^16.6.0"
- react-lifecycles-compat "^3.0.0"
-
react-test-renderer@^16.0.0-0, react-test-renderer@^16.14.0:
version "16.14.0"
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.14.0.tgz#e98360087348e260c56d4fe2315e970480c228ae"
@@ -6908,21 +6818,6 @@ redent@^3.0.0:
indent-string "^4.0.0"
strip-indent "^3.0.0"
-redux-thunk@^2.2.0:
- version "2.3.0"
- resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622"
- integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw==
-
-redux@^3.7.2:
- version "3.7.2"
- resolved "https://registry.yarnpkg.com/redux/-/redux-3.7.2.tgz#06b73123215901d25d065be342eb026bc1c8537b"
- integrity sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A==
- dependencies:
- lodash "^4.2.1"
- lodash-es "^4.2.1"
- loose-envify "^1.1.0"
- symbol-observable "^1.0.3"
-
reflect.ownkeys@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz#749aceec7f3fdf8b63f927a04809e90c5c0b3460"
@@ -6940,11 +6835,6 @@ regenerate@^1.4.0:
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a"
integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==
-regenerator-runtime@^0.11.0:
- version "0.11.1"
- resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
- integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==
-
regenerator-runtime@^0.13.4:
version "0.13.7"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55"
@@ -7102,11 +6992,6 @@ require-main-filename@^2.0.0:
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
-reselect@^3.0.1:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/reselect/-/reselect-3.0.1.tgz#efdaa98ea7451324d092b2b2163a6a1d7a9a2147"
- integrity sha1-79qpjqdFEyTQkrKyFjpqHXqaIUc=
-
resize-observer-polyfill@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
@@ -7813,11 +7698,6 @@ svg-tags@^1.0.0:
resolved "https://registry.yarnpkg.com/svg-tags/-/svg-tags-1.0.0.tgz#58f71cee3bd519b59d4b2a843b6c7de64ac04764"
integrity sha1-WPcc7jvVGbWdSyqEO2x95krAR2Q=
-symbol-observable@^1.0.3:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"
- integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==
-
symbol-tree@^3.2.4:
version "3.2.4"
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
From bc3c759feb2d3f062d0dbc9489e5741fa7d8af13 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Wed, 2 Jun 2021 11:33:25 +0100
Subject: [PATCH 009/100] Add temporary mechanism for managing communities
without dnd
---
.../context_menus/_TagTileContextMenu.scss | 9 ++++
src/components/structures/MyGroups.js | 3 +-
.../views/context_menus/TagTileContextMenu.js | 49 ++++++++++++++-----
src/components/views/elements/DNDTagTile.js | 2 +-
src/components/views/groups/GroupTile.js | 23 +++++++++
src/i18n/strings/en_EN.json | 4 +-
6 files changed, 74 insertions(+), 16 deletions(-)
diff --git a/res/css/views/context_menus/_TagTileContextMenu.scss b/res/css/views/context_menus/_TagTileContextMenu.scss
index 8929c8906e..d707f4ce7c 100644
--- a/res/css/views/context_menus/_TagTileContextMenu.scss
+++ b/res/css/views/context_menus/_TagTileContextMenu.scss
@@ -38,6 +38,15 @@ limitations under the License.
mask-image: url('$(res)/img/element-icons/view-community.svg');
}
+.mx_TagTileContextMenu_moveUp::before {
+ transform: rotate(180deg);
+ mask-image: url('$(res)/img/feather-customised/chevron-down.svg');
+}
+
+.mx_TagTileContextMenu_moveDown::before {
+ mask-image: url('$(res)/img/feather-customised/chevron-down.svg');
+}
+
.mx_TagTileContextMenu_hideCommunity::before {
mask-image: url('$(res)/img/element-icons/hide.svg');
}
diff --git a/src/components/structures/MyGroups.js b/src/components/structures/MyGroups.js
index 1fab6c4348..d0a2fbff41 100644
--- a/src/components/structures/MyGroups.js
+++ b/src/components/structures/MyGroups.js
@@ -82,8 +82,7 @@ export default class MyGroups extends React.Component {
{ _t(
- "To set up a filter, drag a community avatar over to the filter panel on " +
- "the far left hand side of the screen. You can click on an avatar in the " +
+ "You can click on an avatar in the " +
"filter panel at any time to see only the rooms and people associated " +
"with that community.",
) }
diff --git a/src/components/views/context_menus/TagTileContextMenu.js b/src/components/views/context_menus/TagTileContextMenu.js
index 8dea62690c..4e381643ba 100644
--- a/src/components/views/context_menus/TagTileContextMenu.js
+++ b/src/components/views/context_menus/TagTileContextMenu.js
@@ -23,45 +23,70 @@ import TagOrderActions from '../../../actions/TagOrderActions';
import {MenuItem} from "../../structures/ContextMenu";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import {replaceableComponent} from "../../../utils/replaceableComponent";
+import GroupFilterOrderStore from "../../../stores/GroupFilterOrderStore";
@replaceableComponent("views.context_menus.TagTileContextMenu")
export default class TagTileContextMenu extends React.Component {
static propTypes = {
tag: PropTypes.string.isRequired,
+ index: PropTypes.number.isRequired,
/* callback called when the menu is dismissed */
onFinished: PropTypes.func.isRequired,
};
static contextType = MatrixClientContext;
- constructor() {
- super();
-
- this._onViewCommunityClick = this._onViewCommunityClick.bind(this);
- this._onRemoveClick = this._onRemoveClick.bind(this);
- }
-
- _onViewCommunityClick() {
+ _onViewCommunityClick = () => {
dis.dispatch({
action: 'view_group',
group_id: this.props.tag,
});
this.props.onFinished();
- }
+ };
- _onRemoveClick() {
+ _onRemoveClick = () => {
dis.dispatch(TagOrderActions.removeTag(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();
+ };
+
+ _onMoveDown = () => {
+ dis.dispatch(TagOrderActions.moveTag(this.context, this.props.tag, this.props.index + 1));
+ this.props.onFinished();
+ };
render() {
+ let moveUp;
+ let moveDown;
+ if (this.props.index > 0) {
+ moveUp = (
+
+ { _t("Move up") }
+
+ );
+ }
+ if (this.props.index < (GroupFilterOrderStore.getOrderedTags() || []).length - 1) {
+ moveDown = (
+
+ { _t("Move down") }
+
+ );
+ }
+
return
{ _t('View Community') }
+ { (moveUp || moveDown) ?
: null }
+ { moveUp }
+ { moveDown }
- { _t('Hide') }
+ { _t("Unpin") }
;
}
diff --git a/src/components/views/elements/DNDTagTile.js b/src/components/views/elements/DNDTagTile.js
index eaaa0f183b..2e88d37882 100644
--- a/src/components/views/elements/DNDTagTile.js
+++ b/src/components/views/elements/DNDTagTile.js
@@ -30,7 +30,7 @@ export default function DNDTagTile(props) {
const TagTileContextMenu = sdk.getComponent('context_menus.TagTileContextMenu');
contextMenu = (
-
+
);
}
diff --git a/src/components/views/groups/GroupTile.js b/src/components/views/groups/GroupTile.js
index ce42662462..dd8366bbe0 100644
--- a/src/components/views/groups/GroupTile.js
+++ b/src/components/views/groups/GroupTile.js
@@ -22,6 +22,9 @@ import FlairStore from '../../../stores/FlairStore';
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import {replaceableComponent} from "../../../utils/replaceableComponent";
import {mediaFromMxc} from "../../../customisations/Media";
+import { _t } from "../../../languageHandler";
+import TagOrderActions from "../../../actions/TagOrderActions";
+import GroupFilterOrderStore from "../../../stores/GroupFilterOrderStore";
@replaceableComponent("views.groups.GroupTile")
class GroupTile extends React.Component {
@@ -60,6 +63,18 @@ class GroupTile extends React.Component {
});
};
+ onPinClick = e => {
+ e.preventDefault();
+ e.stopPropagation();
+ dis.dispatch(TagOrderActions.moveTag(this.context, this.props.groupId, 0));
+ };
+
+ onUnpinClick = e => {
+ e.preventDefault();
+ e.stopPropagation();
+ dis.dispatch(TagOrderActions.removeTag(this.context, this.props.groupId));
+ };
+
render() {
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
@@ -90,6 +105,14 @@ class GroupTile extends React.Component {
{ name }
{ descElement }
{ this.props.groupId }
+ { !(GroupFilterOrderStore.getOrderedTags() || []).includes(this.props.groupId)
+ ?
+ { _t("Pin") }
+
+ :
+ { _t("Unpin") }
+
+ }
;
}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 3d6fcb8643..85647a17e5 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -2477,6 +2477,8 @@
"Update status": "Update status",
"Set status": "Set status",
"Set a new status...": "Set a new status...",
+ "Move up": "Move up",
+ "Move down": "Move down",
"View Community": "View Community",
"Unable to start audio streaming.": "Unable to start audio streaming.",
"Failed to start livestream": "Failed to start livestream",
@@ -2623,7 +2625,7 @@
"%(count)s messages deleted.|one": "%(count)s message deleted.",
"Your Communities": "Your Communities",
"Did you know: you can use communities to filter your %(brand)s experience!": "Did you know: you can use communities to filter your %(brand)s experience!",
- "To set up a filter, drag a community avatar over to the filter panel on the far left hand side of the screen. You can click on an avatar in the filter panel at any time to see only the rooms and people associated with that community.": "To set up a filter, drag a community avatar over to the filter panel on the far left hand side of the screen. You can click on an avatar in the filter panel at any time to see only the rooms and people associated with that community.",
+ "You can click on an avatar in the filter panel at any time to see only the rooms and people associated with that community.": "You can click on an avatar in the filter panel at any time to see only the rooms and people associated with that community.",
"Error whilst fetching joined communities": "Error whilst fetching joined communities",
"Create a new community": "Create a new community",
"Create a community to group together users and rooms! Build a custom homepage to mark out your space in the Matrix universe.": "Create a community to group together users and rooms! Build a custom homepage to mark out your space in the Matrix universe.",
From 35948374e91d4cf6e3110de4218d043be9ff4db0 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Wed, 2 Jun 2021 11:56:49 +0100
Subject: [PATCH 010/100] remove unused imports
---
src/components/structures/LoggedInView.tsx | 2 --
1 file changed, 2 deletions(-)
diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx
index f5df99d8c9..388616c55e 100644
--- a/src/components/structures/LoggedInView.tsx
+++ b/src/components/structures/LoggedInView.tsx
@@ -29,8 +29,6 @@ import dis from '../../dispatcher/dispatcher';
import { IMatrixClientCreds } from '../../MatrixClientPeg';
import SettingsStore from "../../settings/SettingsStore";
-import TagOrderActions from '../../actions/TagOrderActions';
-import RoomListActions from '../../actions/RoomListActions';
import ResizeHandle from '../views/elements/ResizeHandle';
import {Resizer, CollapseDistributor} from '../../resizer';
import MatrixClientContext from "../../contexts/MatrixClientContext";
From 079a5c10ad8191d6d3c357400c948253b0738997 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Wed, 2 Jun 2021 16:43:38 +0100
Subject: [PATCH 011/100] Respect space ordering field in m.tag for top level
spaces
---
.../structures/SpaceRoomDirectory.tsx | 4 +--
src/stores/SpaceStore.tsx | 33 ++++++++++++++++---
2 files changed, 31 insertions(+), 6 deletions(-)
diff --git a/src/components/structures/SpaceRoomDirectory.tsx b/src/components/structures/SpaceRoomDirectory.tsx
index 8d59fe6c68..2b4fb24c1b 100644
--- a/src/components/structures/SpaceRoomDirectory.tsx
+++ b/src/components/structures/SpaceRoomDirectory.tsx
@@ -39,7 +39,7 @@ import {mediaFromMxc} from "../../customisations/Media";
import InfoTooltip from "../views/elements/InfoTooltip";
import TextWithTooltip from "../views/elements/TextWithTooltip";
import {useStateToggle} from "../../hooks/useStateToggle";
-import {getOrder} from "../../stores/SpaceStore";
+import {getChildOrder} from "../../stores/SpaceStore";
import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton";
import {linkifyElement} from "../../HtmlUtils";
@@ -286,7 +286,7 @@ export const HierarchyLevel = ({
const children = Array.from(relations.get(spaceId)?.values() || []);
const sortedChildren = sortBy(children, ev => {
// XXX: Space Summary API doesn't give the child origin_server_ts but once it does we should use it for sorting
- return getOrder(ev.content.order, null, ev.state_key);
+ return getChildOrder(ev.content.order, null, ev.state_key);
});
const [subspaces, childRooms] = sortedChildren.reduce((result, ev: ISpaceSummaryEvent) => {
const roomId = ev.state_key;
diff --git a/src/stores/SpaceStore.tsx b/src/stores/SpaceStore.tsx
index 40997d30a8..1333fc5d37 100644
--- a/src/stores/SpaceStore.tsx
+++ b/src/stores/SpaceStore.tsx
@@ -33,6 +33,7 @@ import {EnhancedMap, mapDiff} from "../utils/maps";
import {setHasDiff} from "../utils/sets";
import {ISpaceSummaryEvent, ISpaceSummaryRoom} from "../components/structures/SpaceRoomDirectory";
import RoomViewStore from "./RoomViewStore";
+import { arrayHasOrderChange } from "../utils/arrays";
interface IState {}
@@ -60,8 +61,16 @@ const partitionSpacesAndRooms = (arr: Room[]): [Room[], Room[]] => { // [spaces,
}, [[], []]);
};
+const SpaceTagOrderingField = "org.matrix.mscXXXX.space";
+
+const getSpaceTagOrdering = (space: Room): number | undefined => {
+ return space?.getAccountData(EventType.Tag)?.getContent()?.tags?.[SpaceTagOrderingField]?.order;
+};
+
+const sortRootSpaces = (spaces: Room[]): Room[] => sortBy(spaces, [getSpaceTagOrdering, "roomId"]);
+
// For sorting space children using a validated `order`, `m.room.create`'s `origin_server_ts`, `room_id`
-export const getOrder = (order: string, creationTs: number, roomId: string): Array>> => {
+export const getChildOrder = (order: string, creationTs: number, roomId: string): Array>> => {
let validatedOrder: string = null;
if (typeof order === "string" && Array.from(order).every((c: string) => {
@@ -214,7 +223,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
const roomId = ev.getStateKey();
const childRoom = this.matrixClient?.getRoom(roomId);
const createTs = childRoom?.currentState.getStateEvents(EventType.RoomCreate, "")?.getTs();
- return getOrder(ev.getContent().order, createTs, roomId);
+ return getChildOrder(ev.getContent().order, createTs, roomId);
}).map(ev => {
return this.matrixClient.getRoom(ev.getStateKey());
}).filter(room => {
@@ -326,7 +335,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
// rootSpaces.push(space);
// });
- this.rootSpaces = rootSpaces;
+ this.rootSpaces = sortRootSpaces(rootSpaces);
this.parentMap = backrefs;
// if the currently selected space no longer exists, remove its selection
@@ -338,7 +347,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
this.emit(UPDATE_TOP_LEVEL_SPACES, this.spacePanelSpaces);
// build initial state of invited spaces as we would have missed the emitted events about the room at launch
- this._invitedSpaces = new Set(invitedSpaces);
+ this._invitedSpaces = new Set(sortRootSpaces(invitedSpaces));
this.emit(UPDATE_INVITED_SPACES, this.invitedSpaces);
}, 100, {trailing: true, leading: true});
@@ -472,6 +481,20 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
}
};
+ private onRoomAccountData = (ev: MatrixEvent, room: Room, lastEv?: MatrixEvent) => {
+ if (!room.isSpaceRoom() || ev.getType() !== EventType.Tag) return;
+
+ const order = ev.getContent()?.tags?.[SpaceTagOrderingField]?.order;
+ const lastOrder = lastEv?.getContent()?.tags?.[SpaceTagOrderingField]?.order;
+ if (order !== lastOrder) {
+ const rootSpaces = sortRootSpaces(this.rootSpaces);
+ if (arrayHasOrderChange(this.rootSpaces, rootSpaces)) {
+ this.rootSpaces = rootSpaces;
+ this.emit(UPDATE_TOP_LEVEL_SPACES, this.spacePanelSpaces);
+ }
+ }
+ };
+
private onRoomState = (ev: MatrixEvent) => {
const room = this.matrixClient.getRoom(ev.getRoomId());
if (!room) return;
@@ -516,6 +539,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
if (this.matrixClient) {
this.matrixClient.removeListener("Room", this.onRoom);
this.matrixClient.removeListener("Room.myMembership", this.onRoom);
+ this.matrixClient.removeListener("Room.accountData", this.onRoomAccountData);
this.matrixClient.removeListener("RoomState.events", this.onRoomState);
}
await this.reset();
@@ -525,6 +549,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
if (!SettingsStore.getValue("feature_spaces")) return;
this.matrixClient.on("Room", this.onRoom);
this.matrixClient.on("Room.myMembership", this.onRoom);
+ this.matrixClient.on("Room.accountData", this.onRoomAccountData);
this.matrixClient.on("RoomState.events", this.onRoomState);
await this.onSpaceUpdate(); // trigger an initial update
From 3f12b7280d801ac505caec71d2a2c095ab68d3c9 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Thu, 3 Jun 2021 08:31:06 +0100
Subject: [PATCH 012/100] Make AutoHideScrollbar pass through all unknown props
---
.../structures/AutoHideScrollbar.tsx | 18 +++++++++++-------
.../structures/IndicatorScrollbar.js | 11 +++++++----
2 files changed, 18 insertions(+), 11 deletions(-)
diff --git a/src/components/structures/AutoHideScrollbar.tsx b/src/components/structures/AutoHideScrollbar.tsx
index 66f998b616..e5fa124fed 100644
--- a/src/components/structures/AutoHideScrollbar.tsx
+++ b/src/components/structures/AutoHideScrollbar.tsx
@@ -15,9 +15,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React from "react";
+import React, {HTMLAttributes} from "react";
-interface IProps {
+interface IProps extends HTMLAttributes {
className?: string;
onScroll?: () => void;
onWheel?: () => void;
@@ -52,14 +52,18 @@ export default class AutoHideScrollbar extends React.Component {
}
public render() {
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const { className, onScroll, onWheel, style, tabIndex, wrappedRef, children, ...otherProps } = this.props;
+
return (
- { this.props.children }
+ { children }
);
}
}
diff --git a/src/components/structures/IndicatorScrollbar.js b/src/components/structures/IndicatorScrollbar.js
index 51a3b287f0..25dcaeed39 100644
--- a/src/components/structures/IndicatorScrollbar.js
+++ b/src/components/structures/IndicatorScrollbar.js
@@ -185,21 +185,24 @@ export default class IndicatorScrollbar extends React.Component {
};
render() {
+ // eslint-disable-next-line no-unused-vars
+ const { children, trackHorizontalOverflow, verticalScrollsHorizontally, ...otherProps } = this.props;
+
const leftIndicatorStyle = {left: this.state.leftIndicatorOffset};
const rightIndicatorStyle = {right: this.state.rightIndicatorOffset};
- const leftOverflowIndicator = this.props.trackHorizontalOverflow
+ const leftOverflowIndicator = trackHorizontalOverflow
?
: null;
- const rightOverflowIndicator = this.props.trackHorizontalOverflow
+ const rightOverflowIndicator = trackHorizontalOverflow
?
: null;
return (
{ leftOverflowIndicator }
- { this.props.children }
+ { children }
{ rightOverflowIndicator }
);
}
From e334ce81920723832c9260b0f009df88805e22a1 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Thu, 3 Jun 2021 08:32:36 +0100
Subject: [PATCH 013/100] First cut of space panel drag-and-drop ordering
---
package.json | 2 +
res/css/structures/_SpacePanel.scss | 7 +-
src/components/views/spaces/SpacePanel.tsx | 136 +++++++++++-------
.../views/spaces/SpaceTreeLevel.tsx | 27 ++--
src/stores/SpaceStore.tsx | 71 +++++++--
yarn.lock | 100 ++++++++++++-
6 files changed, 263 insertions(+), 80 deletions(-)
diff --git a/package.json b/package.json
index 270c86ddba..2d2506e1df 100644
--- a/package.json
+++ b/package.json
@@ -91,6 +91,7 @@
"qs": "^6.9.6",
"re-resizable": "^6.9.0",
"react": "^16.14.0",
+ "react-beautiful-dnd": "^13.1.0",
"react-dom": "^16.14.0",
"react-focus-lock": "^2.5.0",
"react-transition-group": "^4.4.1",
@@ -135,6 +136,7 @@
"@types/parse5": "^6.0.0",
"@types/qrcode": "^1.3.5",
"@types/react": "^16.9",
+ "@types/react-beautiful-dnd": "^13.0.0",
"@types/react-dom": "^16.9.10",
"@types/react-transition-group": "^4.4.0",
"@types/sanitize-html": "^2.3.1",
diff --git a/res/css/structures/_SpacePanel.scss b/res/css/structures/_SpacePanel.scss
index c433ccf275..e64057d16c 100644
--- a/res/css/structures/_SpacePanel.scss
+++ b/res/css/structures/_SpacePanel.scss
@@ -31,7 +31,6 @@ $activeBorderColor: $secondary-fg-color;
// Create another flexbox so the Panel fills the container
display: flex;
flex-direction: column;
- overflow-y: auto;
.mx_SpacePanel_spaceTreeWrapper {
flex: 1;
@@ -69,6 +68,12 @@ $activeBorderColor: $secondary-fg-color;
cursor: pointer;
}
+ .mx_SpaceItem_dragging {
+ .mx_SpaceButton_toggleCollapse {
+ visibility: hidden;
+ }
+ }
+
.mx_SpaceTreeLevel {
display: flex;
flex-direction: column;
diff --git a/src/components/views/spaces/SpacePanel.tsx b/src/components/views/spaces/SpacePanel.tsx
index eb63b21f0e..27f097e9d4 100644
--- a/src/components/views/spaces/SpacePanel.tsx
+++ b/src/components/views/spaces/SpacePanel.tsx
@@ -15,8 +15,9 @@ limitations under the License.
*/
import React, { useEffect, useState } from "react";
+import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
import classNames from "classnames";
-import {Room} from "matrix-js-sdk/src/models/room";
+import { Room } from "matrix-js-sdk/src/models/room";
import {_t} from "../../../languageHandler";
import RoomAvatar from "../avatars/RoomAvatar";
@@ -204,58 +205,89 @@ const SpacePanel = () => {
};
const activeSpaces = activeSpace ? [activeSpace] : [];
- const expandCollapseButtonTitle = isPanelCollapsed ? _t("Expand space panel") : _t("Collapse space panel");
- // TODO drag and drop for re-arranging order
- return
- {({onKeyDownHandler}) => (
-
-
-
-
SpaceStore.instance.setActiveSpace(null)}
- selected={!activeSpace}
- tooltip={_t("All rooms")}
- notificationState={RoomNotificationStateStore.instance.globalState}
- isNarrow={isPanelCollapsed}
+ return (
+ {
+ if (!result.destination) return; // dropped outside the list
+ SpaceStore.instance.moveRootSpace(result.source.index, result.destination.index);
+ }}>
+
+ {({onKeyDownHandler}) => (
+
+
+ {(provided, snapshot) => (
+
+
+ SpaceStore.instance.setActiveSpace(null)}
+ selected={!activeSpace}
+ tooltip={_t("All rooms")}
+ notificationState={RoomNotificationStateStore.instance.globalState}
+ isNarrow={isPanelCollapsed}
+ />
+ { invites.map(s => (
+ setPanelCollapsed(false)}
+ />
+ )) }
+ { spaces.map((s, i) => (
+
+ {(provided, snapshot) => (
+ setPanelCollapsed(false)}
+ />
+ )}
+
+ )) }
+ { provided.placeholder }
+
+ {
+ if (!isPanelCollapsed) setPanelCollapsed(true);
+ openMenu();
+ }}
+ isNarrow={isPanelCollapsed}
+ />
+
+ )}
+
+ setPanelCollapsed(!isPanelCollapsed)}
+ title={isPanelCollapsed ? _t("Expand space panel") : _t("Collapse space panel")}
/>
- { invites.map(s => setPanelCollapsed(false)}
- />) }
- { spaces.map(s => setPanelCollapsed(false)}
- />) }
-
- {
- if (!isPanelCollapsed) setPanelCollapsed(true);
- openMenu();
- }}
- isNarrow={isPanelCollapsed}
- />
-
- setPanelCollapsed(!isPanelCollapsed)}
- title={expandCollapseButtonTitle}
- />
- { contextMenu }
-
- )}
-
+ { contextMenu }
+
+ )}
+
+
+ );
};
export default SpacePanel;
diff --git a/src/components/views/spaces/SpaceTreeLevel.tsx b/src/components/views/spaces/SpaceTreeLevel.tsx
index f34baf256b..7ac863b239 100644
--- a/src/components/views/spaces/SpaceTreeLevel.tsx
+++ b/src/components/views/spaces/SpaceTreeLevel.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, {InputHTMLAttributes, LegacyRef} from "react";
import classNames from "classnames";
import {Room} from "matrix-js-sdk/src/models/room";
@@ -49,13 +49,14 @@ import {EventType} from "matrix-js-sdk/src/@types/event";
import {StaticNotificationState} from "../../../stores/notifications/StaticNotificationState";
import {NotificationColor} from "../../../stores/notifications/NotificationColor";
-interface IItemProps {
+interface IItemProps extends InputHTMLAttributes {
space?: Room;
activeSpaces: Room[];
isNested?: boolean;
isPanelCollapsed?: boolean;
onExpand?: Function;
parents?: Set;
+ innerRef?: LegacyRef;
}
interface IItemState {
@@ -300,18 +301,18 @@ export class SpaceItem extends React.PureComponent {
}
render() {
- const {space, activeSpaces, isNested} = this.props;
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const { space, activeSpaces, isNested, isPanelCollapsed, onExpand, parents, innerRef,
+ ...otherProps } = this.props;
- const forceCollapsed = this.props.isPanelCollapsed;
- const isNarrow = this.props.isPanelCollapsed;
- const collapsed = this.state.collapsed || forceCollapsed;
+ const collapsed = this.state.collapsed || isPanelCollapsed;
const childSpaces = SpaceStore.instance.getChildSpaces(space.roomId)
- .filter(s => !this.props.parents?.has(s.roomId));
+ .filter(s => !parents?.has(s.roomId));
const isActive = activeSpaces.includes(space);
- const itemClasses = classNames({
+ const itemClasses = classNames(this.props.className, {
"mx_SpaceItem": true,
- "mx_SpaceItem_narrow": isNarrow,
+ "mx_SpaceItem_narrow": isPanelCollapsed,
"collapsed": collapsed,
"hasSubSpaces": childSpaces && childSpaces.length,
});
@@ -320,7 +321,7 @@ export class SpaceItem extends React.PureComponent {
const classes = classNames("mx_SpaceButton", {
mx_SpaceButton_active: isActive,
mx_SpaceButton_hasMenuOpen: !!this.state.contextMenuPosition,
- mx_SpaceButton_narrow: isNarrow,
+ mx_SpaceButton_narrow: isPanelCollapsed,
mx_SpaceButton_invite: isInvite,
});
const notificationState = isInvite
@@ -333,7 +334,7 @@ export class SpaceItem extends React.PureComponent {
spaces={childSpaces}
activeSpaces={activeSpaces}
isNested={true}
- parents={new Set(this.props.parents).add(this.props.space.roomId)}
+ parents={new Set(parents).add(space.roomId)}
/>;
}
@@ -353,7 +354,7 @@ export class SpaceItem extends React.PureComponent {
/> : null;
let button;
- if (isNarrow) {
+ if (isPanelCollapsed) {
button = (
{
}
return (
-
+
{ button }
{ childItems }
diff --git a/src/stores/SpaceStore.tsx b/src/stores/SpaceStore.tsx
index 1333fc5d37..9ef961ce2d 100644
--- a/src/stores/SpaceStore.tsx
+++ b/src/stores/SpaceStore.tsx
@@ -63,12 +63,6 @@ const partitionSpacesAndRooms = (arr: Room[]): [Room[], Room[]] => { // [spaces,
const SpaceTagOrderingField = "org.matrix.mscXXXX.space";
-const getSpaceTagOrdering = (space: Room): number | undefined => {
- return space?.getAccountData(EventType.Tag)?.getContent()?.tags?.[SpaceTagOrderingField]?.order;
-};
-
-const sortRootSpaces = (spaces: Room[]): Room[] => sortBy(spaces, [getSpaceTagOrdering, "roomId"]);
-
// For sorting space children using a validated `order`, `m.room.create`'s `origin_server_ts`, `room_id`
export const getChildOrder = (order: string, creationTs: number, roomId: string): Array>> => {
let validatedOrder: string = null;
@@ -104,6 +98,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
private _activeSpace?: Room = null;
private _suggestedRooms: ISuggestedRoom[] = [];
private _invitedSpaces = new Set();
+ private spaceOrderLocalEchoMap = new Map();
public get invitedSpaces(): Room[] {
return Array.from(this._invitedSpaces);
@@ -335,7 +330,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
// rootSpaces.push(space);
// });
- this.rootSpaces = sortRootSpaces(rootSpaces);
+ this.rootSpaces = this.sortRootSpaces(rootSpaces);
this.parentMap = backrefs;
// if the currently selected space no longer exists, remove its selection
@@ -347,7 +342,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
this.emit(UPDATE_TOP_LEVEL_SPACES, this.spacePanelSpaces);
// build initial state of invited spaces as we would have missed the emitted events about the room at launch
- this._invitedSpaces = new Set(sortRootSpaces(invitedSpaces));
+ this._invitedSpaces = new Set(this.sortRootSpaces(invitedSpaces));
this.emit(UPDATE_INVITED_SPACES, this.invitedSpaces);
}, 100, {trailing: true, leading: true});
@@ -484,17 +479,22 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
private onRoomAccountData = (ev: MatrixEvent, room: Room, lastEv?: MatrixEvent) => {
if (!room.isSpaceRoom() || ev.getType() !== EventType.Tag) return;
+ this.spaceOrderLocalEchoMap.delete(room.roomId); // clear any local echo
const order = ev.getContent()?.tags?.[SpaceTagOrderingField]?.order;
const lastOrder = lastEv?.getContent()?.tags?.[SpaceTagOrderingField]?.order;
if (order !== lastOrder) {
- const rootSpaces = sortRootSpaces(this.rootSpaces);
- if (arrayHasOrderChange(this.rootSpaces, rootSpaces)) {
- this.rootSpaces = rootSpaces;
- this.emit(UPDATE_TOP_LEVEL_SPACES, this.spacePanelSpaces);
- }
+ this.notifyIfOrderChanged();
}
};
+ private notifyIfOrderChanged(): void {
+ const rootSpaces = this.sortRootSpaces(this.rootSpaces);
+ if (arrayHasOrderChange(this.rootSpaces, rootSpaces)) {
+ this.rootSpaces = rootSpaces;
+ this.emit(UPDATE_TOP_LEVEL_SPACES, this.spacePanelSpaces);
+ }
+ }
+
private onRoomState = (ev: MatrixEvent) => {
const room = this.matrixClient.getRoom(ev.getRoomId());
if (!room) return;
@@ -624,6 +624,51 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
}
childSpaces.forEach(s => this.traverseSpace(s.roomId, fn, includeRooms, newPath));
}
+
+ private getSpaceTagOrdering = (space: Room): number | undefined => {
+ if (this.spaceOrderLocalEchoMap.has(space.roomId)) return this.spaceOrderLocalEchoMap.get(space.roomId);
+ return space.tags?.[SpaceTagOrderingField]?.order;
+ };
+
+ private sortRootSpaces(spaces: Room[]): Room[] {
+ return sortBy(spaces, [this.getSpaceTagOrdering, "roomId"]);
+ }
+
+ public moveRootSpace(fromIndex: number, toIndex: number): void {
+ if (
+ fromIndex < 0 || toIndex < 0 ||
+ fromIndex > this.rootSpaces.length || toIndex > this.rootSpaces.length ||
+ fromIndex === toIndex
+ ) {
+ return;
+ }
+ const space = this.rootSpaces[fromIndex];
+ const orders = this.rootSpaces.map(this.getSpaceTagOrdering);
+
+ let prevOrder = orders[toIndex - 1];
+ let nextOrder = orders[toIndex]; // accounts for downwards displacement of existing inhabitant of this index
+
+ if (prevOrder === undefined && nextOrder === undefined) {
+ // TODO WHAT A PAIN
+ }
+
+ prevOrder = prevOrder || 0.0;
+ nextOrder = nextOrder || 1.0;
+
+ if (prevOrder !== nextOrder) {
+ const order = prevOrder + ((nextOrder - prevOrder) / 2);
+ this.spaceOrderLocalEchoMap.set(space.roomId, order);
+ this.matrixClient.setRoomAccountData(space.roomId, EventType.Tag, {
+ tags: {
+ ...space.tags,
+ [SpaceTagOrderingField]: { order },
+ },
+ });
+ this.notifyIfOrderChanged();
+ } else {
+ // TODO REBUILD
+ }
+ }
}
export default class SpaceStore {
diff --git a/yarn.lock b/yarn.lock
index 2c84237730..7e24c220e5 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1024,6 +1024,13 @@
dependencies:
regenerator-runtime "^0.13.4"
+"@babel/runtime@^7.12.1", "@babel/runtime@^7.9.2":
+ version "7.14.0"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.0.tgz#46794bc20b612c5f75e62dd071e24dfd95f1cbe6"
+ integrity sha512-JELkvo/DlpNdJ7dlyw/eY7E0suy5i5GQH+Vlxaq1nsNJ+H7f4Vtv3jMeCEgRhZZQFXTjldYfQgv2qmM6M1v5wA==
+ dependencies:
+ regenerator-runtime "^0.13.4"
+
"@babel/template@^7.10.4", "@babel/template@^7.12.7", "@babel/template@^7.3.3":
version "7.12.7"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.12.7.tgz#c817233696018e39fbb6c491d2fb684e05ed43bc"
@@ -1504,6 +1511,14 @@
dependencies:
"@types/node" "*"
+"@types/hoist-non-react-statics@^3.3.0":
+ version "3.3.1"
+ resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"
+ integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==
+ dependencies:
+ "@types/react" "*"
+ hoist-non-react-statics "^3.3.0"
+
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762"
@@ -1620,6 +1635,13 @@
dependencies:
"@types/node" "*"
+"@types/react-beautiful-dnd@^13.0.0":
+ version "13.0.0"
+ resolved "https://registry.yarnpkg.com/@types/react-beautiful-dnd/-/react-beautiful-dnd-13.0.0.tgz#e60d3d965312fcf1516894af92dc3e9249587db4"
+ integrity sha512-by80tJ8aTTDXT256Gl+RfLRtFjYbUWOnZuEigJgNsJrSEGxvFe5eY6k3g4VIvf0M/6+xoLgfYWoWonlOo6Wqdg==
+ dependencies:
+ "@types/react" "*"
+
"@types/react-dom@^16.9.10":
version "16.9.10"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.10.tgz#4485b0bec3d41f856181b717f45fd7831101156f"
@@ -1627,6 +1649,16 @@
dependencies:
"@types/react" "^16"
+"@types/react-redux@^7.1.16":
+ version "7.1.16"
+ resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.16.tgz#0fbd04c2500c12105494c83d4a3e45c084e3cb21"
+ integrity sha512-f/FKzIrZwZk7YEO9E1yoxIuDNRiDducxkFlkw/GNMGEnK9n4K8wJzlJBghpSuOVDgEUHoDkDF7Gi9lHNQR4siw==
+ dependencies:
+ "@types/hoist-non-react-statics" "^3.3.0"
+ "@types/react" "*"
+ hoist-non-react-statics "^3.3.0"
+ redux "^4.0.0"
+
"@types/react-transition-group@^4.4.0":
version "4.4.0"
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.0.tgz#882839db465df1320e4753e6e9f70ca7e9b4d46d"
@@ -2696,6 +2728,13 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.2:
shebang-command "^2.0.0"
which "^2.0.1"
+css-box-model@^1.2.0:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/css-box-model/-/css-box-model-1.2.1.tgz#59951d3b81fd6b2074a62d49444415b0d2b4d7c1"
+ integrity sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==
+ dependencies:
+ tiny-invariant "^1.0.6"
+
css-select@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.1.2.tgz#8b52b6714ed3a80d8221ec971c543f3b12653286"
@@ -4202,6 +4241,13 @@ highlight.js@^10.5.0:
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.5.0.tgz#3f09fede6a865757378f2d9ebdcbc15ba268f98f"
integrity sha512-xTmvd9HiIHR6L53TMC7TKolEj65zG1XU+Onr8oi86mYa+nLcIbxTTWkpW7CsEwv/vK7u1zb8alZIMLDqqN6KTw==
+hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
+ version "3.3.2"
+ resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
+ integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
+ dependencies:
+ react-is "^16.7.0"
+
hosted-git-info@^2.1.4:
version "2.8.9"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
@@ -5717,6 +5763,11 @@ mdurl@~1.0.1:
resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e"
integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=
+memoize-one@^5.1.1:
+ version "5.2.1"
+ resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e"
+ integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==
+
meow@^9.0.0:
version "9.0.0"
resolved "https://registry.yarnpkg.com/meow/-/meow-9.0.0.tgz#cd9510bc5cac9dee7d03c73ee1f9ad959f4ea364"
@@ -6632,6 +6683,11 @@ quick-lru@^4.0.1:
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f"
integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==
+raf-schd@^4.0.2:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-4.0.3.tgz#5d6c34ef46f8b2a0e880a8fcdb743efc5bfdbc1a"
+ integrity sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==
+
raf@^3.4.1:
version "3.4.1"
resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39"
@@ -6659,6 +6715,19 @@ re-resizable@^6.9.0:
dependencies:
fast-memoize "^2.5.1"
+react-beautiful-dnd@^13.1.0:
+ version "13.1.0"
+ resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-13.1.0.tgz#ec97c81093593526454b0de69852ae433783844d"
+ integrity sha512-aGvblPZTJowOWUNiwd6tNfEpgkX5OxmpqxHKNW/4VmvZTNTbeiq7bA3bn5T+QSF2uibXB0D1DmJsb1aC/+3cUA==
+ dependencies:
+ "@babel/runtime" "^7.9.2"
+ css-box-model "^1.2.0"
+ memoize-one "^5.1.1"
+ raf-schd "^4.0.2"
+ react-redux "^7.2.0"
+ redux "^4.0.4"
+ use-memo-one "^1.1.1"
+
react-clientside-effect@^1.2.2:
version "1.2.3"
resolved "https://registry.yarnpkg.com/react-clientside-effect/-/react-clientside-effect-1.2.3.tgz#95c95f520addfb71743608b990bfe01eb002012b"
@@ -6688,7 +6757,7 @@ react-focus-lock@^2.5.0:
use-callback-ref "^1.2.1"
use-sidecar "^1.0.1"
-react-is@^16.13.1, react-is@^16.8.1, react-is@^16.8.6:
+react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.6:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@@ -6698,6 +6767,18 @@ react-is@^17.0.1:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339"
integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==
+react-redux@^7.2.0:
+ version "7.2.4"
+ resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.4.tgz#1ebb474032b72d806de2e0519cd07761e222e225"
+ integrity sha512-hOQ5eOSkEJEXdpIKbnRyl04LhaWabkDPV+Ix97wqQX3T3d2NQ8DUblNXXtNMavc7DpswyQM6xfaN4HQDKNY2JA==
+ dependencies:
+ "@babel/runtime" "^7.12.1"
+ "@types/react-redux" "^7.1.16"
+ hoist-non-react-statics "^3.3.2"
+ loose-envify "^1.4.0"
+ prop-types "^15.7.2"
+ react-is "^16.13.1"
+
react-test-renderer@^16.0.0-0, react-test-renderer@^16.14.0:
version "16.14.0"
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.14.0.tgz#e98360087348e260c56d4fe2315e970480c228ae"
@@ -6818,6 +6899,13 @@ redent@^3.0.0:
indent-string "^4.0.0"
strip-indent "^3.0.0"
+redux@^4.0.0, redux@^4.0.4:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/redux/-/redux-4.1.0.tgz#eb049679f2f523c379f1aff345c8612f294c88d4"
+ integrity sha512-uI2dQN43zqLWCt6B/BMGRMY6db7TTY4qeHHfGeKb3EOhmOKjU3KdWvNLJyqaHRksv/ErdNH7cFZWg9jXtewy4g==
+ dependencies:
+ "@babel/runtime" "^7.9.2"
+
reflect.ownkeys@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz#749aceec7f3fdf8b63f927a04809e90c5c0b3460"
@@ -7765,6 +7853,11 @@ through@^2.3.6:
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
+tiny-invariant@^1.0.6:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875"
+ integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==
+
tmatch@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/tmatch/-/tmatch-2.0.1.tgz#0c56246f33f30da1b8d3d72895abaf16660f38cf"
@@ -8070,6 +8163,11 @@ use-callback-ref@^1.2.1:
resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.2.5.tgz#6115ed242cfbaed5915499c0a9842ca2912f38a5"
integrity sha512-gN3vgMISAgacF7sqsLPByqoePooY3n2emTH59Ur5d/M8eg4WTWu1xp8i8DHjohftIyEx0S08RiYxbffr4j8Peg==
+use-memo-one@^1.1.1:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/use-memo-one/-/use-memo-one-1.1.2.tgz#0c8203a329f76e040047a35a1197defe342fab20"
+ integrity sha512-u2qFKtxLsia/r8qG0ZKkbytbztzRb317XCkT7yP8wxL0tZ/CzK2G+WWie5vWvpyeP7+YoPIwbJoIHJ4Ba4k0oQ==
+
use-sidecar@^1.0.1:
version "1.0.4"
resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.0.4.tgz#38398c3723727f9f924bed2343dfa3db6aaaee46"
From dbaa394d65c640581f3ef89f52aba13e4801cc3a Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Thu, 3 Jun 2021 08:54:30 +0100
Subject: [PATCH 014/100] i18n
---
src/i18n/strings/en_EN.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 85647a17e5..2a5297122f 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -1018,9 +1018,9 @@
"You can change these anytime.": "You can change these anytime.",
"Creating...": "Creating...",
"Create": "Create",
+ "All rooms": "All rooms",
"Expand space panel": "Expand space panel",
"Collapse space panel": "Collapse space panel",
- "All rooms": "All rooms",
"Click to copy": "Click to copy",
"Copied!": "Copied!",
"Failed to copy": "Failed to copy",
From 43921500d3459896a8a220c870a2b46d49d34303 Mon Sep 17 00:00:00 2001
From: Robin Townsend
Date: Sat, 5 Jun 2021 22:21:10 -0400
Subject: [PATCH 015/100] Revert "Match requested avatar size to displayed
size"
This reverts commit 44b143c8c3063be7ca2bf24e6cfdb81be9351c75.
---
src/components/views/elements/EventTilePreview.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/views/elements/EventTilePreview.tsx b/src/components/views/elements/EventTilePreview.tsx
index 6d2ea687de..77db94b5dd 100644
--- a/src/components/views/elements/EventTilePreview.tsx
+++ b/src/components/views/elements/EventTilePreview.tsx
@@ -61,7 +61,7 @@ interface IState {
message: string;
}
-const AVATAR_SIZE = 30;
+const AVATAR_SIZE = 32;
@replaceableComponent("views.elements.EventTilePreview")
export default class EventTilePreview extends React.Component {
From b2b95257a8571edde2cf1a7ed110eb1a65652252 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Mon, 7 Jun 2021 08:54:41 +0100
Subject: [PATCH 016/100] Convert RoomAliasField to Typescript
---
src/components/views/elements/Field.tsx | 7 +-
.../{RoomAliasField.js => RoomAliasField.tsx} | 75 ++++++++++---------
src/i18n/strings/en_EN.json | 2 +-
3 files changed, 48 insertions(+), 36 deletions(-)
rename src/components/views/elements/{RoomAliasField.js => RoomAliasField.tsx} (67%)
diff --git a/src/components/views/elements/Field.tsx b/src/components/views/elements/Field.tsx
index 59d9a11596..1373c2df0e 100644
--- a/src/components/views/elements/Field.tsx
+++ b/src/components/views/elements/Field.tsx
@@ -29,6 +29,11 @@ function getId() {
return `${BASE_ID}_${count++}`;
}
+export interface IValidateOpts {
+ focused?: boolean;
+ allowEmpty?: boolean;
+}
+
interface IProps {
// The field's ID, which binds the input and label together. Immutable.
id?: string;
@@ -180,7 +185,7 @@ export default class Field extends React.PureComponent {
}
};
- public async validate({ focused, allowEmpty = true }: {focused?: boolean, allowEmpty?: boolean}) {
+ public async validate({ focused, allowEmpty = true }: IValidateOpts) {
if (!this.props.onValidate) {
return;
}
diff --git a/src/components/views/elements/RoomAliasField.js b/src/components/views/elements/RoomAliasField.tsx
similarity index 67%
rename from src/components/views/elements/RoomAliasField.js
rename to src/components/views/elements/RoomAliasField.tsx
index 813dd8b5cc..7eff529c46 100644
--- a/src/components/views/elements/RoomAliasField.js
+++ b/src/components/views/elements/RoomAliasField.tsx
@@ -1,5 +1,5 @@
/*
-Copyright 2019 New Vector Ltd
+Copyright 2019, 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -13,67 +13,74 @@ 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 { _t } from '../../../languageHandler';
-import React from 'react';
-import PropTypes from 'prop-types';
-import * as sdk from '../../../index';
import withValidation from './Validation';
-import {MatrixClientPeg} from '../../../MatrixClientPeg';
-import {replaceableComponent} from "../../../utils/replaceableComponent";
+import { MatrixClientPeg } from '../../../MatrixClientPeg';
+import { replaceableComponent } from "../../../utils/replaceableComponent";
+import Field, { IValidateOpts } from "./Field";
+
+interface IProps {
+ domain: string;
+ value: string;
+ label?: string;
+ placeholder?: string;
+ onChange?(value: string): void;
+}
+
+interface IState {
+ isValid: boolean;
+}
// Controlled form component wrapping Field for inputting a room alias scoped to a given domain
@replaceableComponent("views.elements.RoomAliasField")
-export default class RoomAliasField extends React.PureComponent {
- static propTypes = {
- domain: PropTypes.string.isRequired,
- onChange: PropTypes.func,
- value: PropTypes.string.isRequired,
+export default class RoomAliasField extends React.PureComponent {
+ private fieldRef = createRef();
+
+ public state = {
+ isValid: true,
};
- constructor(props) {
- super(props);
- this.state = {isValid: true};
- }
-
- _asFullAlias(localpart) {
+ private asFullAlias(localpart: string): string {
return `#${localpart}:${this.props.domain}`;
}
render() {
- const Field = sdk.getComponent('views.elements.Field');
const poundSign = (# );
const aliasPostfix = ":" + this.props.domain;
const domain = ({aliasPostfix} );
const maxlength = 255 - this.props.domain.length - 2; // 2 for # and :
return (
this._fieldRef = ref}
- onValidate={this._onValidate}
- placeholder={_t("e.g. my-room")}
- onChange={this._onChange}
+ ref={this.fieldRef}
+ onValidate={this.onValidate}
+ placeholder={this.props.placeholder || _t("e.g. my-room")}
+ onChange={this.onChange}
value={this.props.value.substring(1, this.props.value.length - this.props.domain.length - 1)}
maxLength={maxlength}
/>
);
}
- _onChange = (ev) => {
+ private onChange = (ev) => {
if (this.props.onChange) {
- this.props.onChange(this._asFullAlias(ev.target.value));
+ this.props.onChange(this.asFullAlias(ev.target.value));
}
};
- _onValidate = async (fieldState) => {
- const result = await this._validationRules(fieldState);
+ private onValidate = async (fieldState) => {
+ const result = await this.validationRules(fieldState);
this.setState({isValid: result.valid});
return result;
};
- _validationRules = withValidation({
+ private validationRules = withValidation({
rules: [
{
key: "safeLocalpart",
@@ -81,7 +88,7 @@ export default class RoomAliasField extends React.PureComponent {
if (!value) {
return true;
}
- const fullAlias = this._asFullAlias(value);
+ const fullAlias = this.asFullAlias(value);
// XXX: FIXME https://github.com/matrix-org/matrix-doc/issues/668
return !value.includes("#") && !value.includes(":") && !value.includes(",") &&
encodeURI(fullAlias) === fullAlias;
@@ -90,7 +97,7 @@ export default class RoomAliasField extends React.PureComponent {
}, {
key: "required",
test: async ({ value, allowEmpty }) => allowEmpty || !!value,
- invalid: () => _t("Please provide a room address"),
+ invalid: () => _t("Please provide an address"),
}, {
key: "taken",
final: true,
@@ -100,7 +107,7 @@ export default class RoomAliasField extends React.PureComponent {
}
const client = MatrixClientPeg.get();
try {
- await client.getRoomIdForAlias(this._asFullAlias(value));
+ await client.getRoomIdForAlias(this.asFullAlias(value));
// we got a room id, so the alias is taken
return false;
} catch (err) {
@@ -120,11 +127,11 @@ export default class RoomAliasField extends React.PureComponent {
return this.state.isValid;
}
- validate(options) {
- return this._fieldRef.validate(options);
+ validate(options: IValidateOpts) {
+ return this.fieldRef.current?.validate(options);
}
focus() {
- this._fieldRef.focus();
+ this.fieldRef.current?.focus();
}
}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 9e85ea28c8..02662aa508 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -2014,7 +2014,7 @@
"Room address": "Room address",
"e.g. my-room": "e.g. my-room",
"Some characters not allowed": "Some characters not allowed",
- "Please provide a room address": "Please provide a room address",
+ "Please provide an address": "Please provide an address",
"This address is available to use": "This address is available to use",
"This address is already in use": "This address is already in use",
"Server Options": "Server Options",
From 8c34a8461ee64949b670715cafac5948bedbca57 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Mon, 7 Jun 2021 08:57:39 +0100
Subject: [PATCH 017/100] Add way to specify address during public space
creation
---
res/css/views/spaces/_SpaceBasicSettings.scss | 2 +-
.../views/spaces/SpaceCreateMenu.tsx | 45 +++++++++++++++----
src/i18n/strings/en_EN.json | 2 +
3 files changed, 40 insertions(+), 9 deletions(-)
diff --git a/res/css/views/spaces/_SpaceBasicSettings.scss b/res/css/views/spaces/_SpaceBasicSettings.scss
index 204ccab2b7..e6e06e7181 100644
--- a/res/css/views/spaces/_SpaceBasicSettings.scss
+++ b/res/css/views/spaces/_SpaceBasicSettings.scss
@@ -16,7 +16,7 @@ limitations under the License.
.mx_SpaceBasicSettings {
.mx_Field {
- margin: 32px 0;
+ margin: 24px 0;
}
.mx_SpaceBasicSettings_avatarContainer {
diff --git a/src/components/views/spaces/SpaceCreateMenu.tsx b/src/components/views/spaces/SpaceCreateMenu.tsx
index 0ebf511018..a65b53a045 100644
--- a/src/components/views/spaces/SpaceCreateMenu.tsx
+++ b/src/components/views/spaces/SpaceCreateMenu.tsx
@@ -33,6 +33,7 @@ import {USER_LABS_TAB} from "../dialogs/UserSettingsDialog";
import Field from "../elements/Field";
import withValidation from "../elements/Validation";
import {SpaceFeedbackPrompt} from "../../structures/SpaceRoomView";
+import RoomAliasField from "../elements/RoomAliasField";
const SpaceCreateMenuType = ({ title, description, className, onClick }) => {
return (
@@ -58,6 +59,11 @@ const spaceNameValidator = withValidation({
],
});
+const nameToAlias = (name: string, domain: string): string => {
+ const localpart = name.trim().toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9_-]+/gi, "");
+ return `#${localpart}:${domain}`;
+};
+
const SpaceCreateMenu = ({ onFinished }) => {
const cli = useContext(MatrixClientContext);
const [visibility, setVisibility] = useState(null);
@@ -65,6 +71,8 @@ const SpaceCreateMenu = ({ onFinished }) => {
const [name, setName] = useState("");
const spaceNameField = useRef();
+ const [alias, setAlias] = useState("");
+ const spaceAliasField = useRef();
const [avatar, setAvatar] = useState(null);
const [topic, setTopic] = useState("");
@@ -80,6 +88,13 @@ const SpaceCreateMenu = ({ onFinished }) => {
setBusy(false);
return;
}
+ // validate the space name alias field but do not require it
+ if (visibility === Visibility.Public && !await spaceAliasField.current.validate({ allowEmpty: true })) {
+ spaceAliasField.current.focus();
+ spaceAliasField.current.validate({ allowEmpty: true, focused: true });
+ setBusy(false);
+ return;
+ }
const initialState: IStateEvent[] = [
{
@@ -97,12 +112,6 @@ const SpaceCreateMenu = ({ onFinished }) => {
content: { url },
});
}
- if (topic) {
- initialState.push({
- type: EventType.RoomTopic,
- content: { topic },
- });
- }
try {
await createRoom({
@@ -110,7 +119,6 @@ const SpaceCreateMenu = ({ onFinished }) => {
preset: visibility === Visibility.Public ? Preset.PublicChat : Preset.PrivateChat,
name,
creation_content: {
- // Based on MSC1840
[RoomCreateTypeField]: RoomType.Space,
},
initial_state: initialState,
@@ -119,6 +127,8 @@ const SpaceCreateMenu = ({ onFinished }) => {
events_default: 100,
...Visibility.Public ? { invite: 0 } : {},
},
+ room_alias_name: alias ? alias.substr(1, alias.indexOf(":") - 1) : undefined,
+ topic,
},
spinner: false,
encryption: false,
@@ -157,6 +167,7 @@ const SpaceCreateMenu = ({ onFinished }) => {
;
} else {
+ const domain = cli.getDomain();
body =
{
label={_t("Name")}
autoFocus={true}
value={name}
- onChange={ev => setName(ev.target.value)}
+ onChange={ev => {
+ const newName = ev.target.value;
+ if (!alias || alias === nameToAlias(name, domain)) {
+ setAlias(nameToAlias(newName, domain));
+ }
+ setName(newName);
+ }}
ref={spaceNameField}
onValidate={spaceNameValidator}
disabled={busy}
/>
+ { visibility === Visibility.Public
+ ?
+ : null
+ }
+
Date: Mon, 7 Jun 2021 08:59:57 +0100
Subject: [PATCH 018/100] Stash
---
src/stores/SpaceStore.tsx | 62 +++++++++++++++++++++++++--------------
1 file changed, 40 insertions(+), 22 deletions(-)
diff --git a/src/stores/SpaceStore.tsx b/src/stores/SpaceStore.tsx
index 9ef961ce2d..5e09b617a7 100644
--- a/src/stores/SpaceStore.tsx
+++ b/src/stores/SpaceStore.tsx
@@ -61,8 +61,6 @@ const partitionSpacesAndRooms = (arr: Room[]): [Room[], Room[]] => { // [spaces,
}, [[], []]);
};
-const SpaceTagOrderingField = "org.matrix.mscXXXX.space";
-
// For sorting space children using a validated `order`, `m.room.create`'s `origin_server_ts`, `room_id`
export const getChildOrder = (order: string, creationTs: number, roomId: string): Array>> => {
let validatedOrder: string = null;
@@ -98,7 +96,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
private _activeSpace?: Room = null;
private _suggestedRooms: ISuggestedRoom[] = [];
private _invitedSpaces = new Set();
- private spaceOrderLocalEchoMap = new Map();
+ private spaceOrderLocalEchoMap = new Map();
public get invitedSpaces(): Room[] {
return Array.from(this._invitedSpaces);
@@ -477,11 +475,11 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
};
private onRoomAccountData = (ev: MatrixEvent, room: Room, lastEv?: MatrixEvent) => {
- if (!room.isSpaceRoom() || ev.getType() !== EventType.Tag) return;
+ if (!room.isSpaceRoom() || ev.getType() !== EventType.SpaceOrder) return;
this.spaceOrderLocalEchoMap.delete(room.roomId); // clear any local echo
- const order = ev.getContent()?.tags?.[SpaceTagOrderingField]?.order;
- const lastOrder = lastEv?.getContent()?.tags?.[SpaceTagOrderingField]?.order;
+ const order = ev.getContent()?.order;
+ const lastOrder = lastEv?.getContent()?.order;
if (order !== lastOrder) {
this.notifyIfOrderChanged();
}
@@ -625,15 +623,21 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
childSpaces.forEach(s => this.traverseSpace(s.roomId, fn, includeRooms, newPath));
}
- private getSpaceTagOrdering = (space: Room): number | undefined => {
+ private getSpaceTagOrdering = (space: Room): string | undefined => {
if (this.spaceOrderLocalEchoMap.has(space.roomId)) return this.spaceOrderLocalEchoMap.get(space.roomId);
- return space.tags?.[SpaceTagOrderingField]?.order;
+ const order = space.getAccountData(EventType.SpaceOrder)?.getContent()?.order;
+ return typeof order === "string" ? order : undefined;
};
private sortRootSpaces(spaces: Room[]): Room[] {
return sortBy(spaces, [this.getSpaceTagOrdering, "roomId"]);
}
+ private setRootSpaceOrder(space: Room, order: string): void {
+ this.spaceOrderLocalEchoMap.set(space.roomId, order);
+ this.matrixClient.setRoomAccountData(space.roomId, EventType.SpaceOrder, { order });
+ }
+
public moveRootSpace(fromIndex: number, toIndex: number): void {
if (
fromIndex < 0 || toIndex < 0 ||
@@ -645,29 +649,43 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
const space = this.rootSpaces[fromIndex];
const orders = this.rootSpaces.map(this.getSpaceTagOrdering);
- let prevOrder = orders[toIndex - 1];
- let nextOrder = orders[toIndex]; // accounts for downwards displacement of existing inhabitant of this index
+ let prevOrder: string;
+ let nextOrder: string;
- if (prevOrder === undefined && nextOrder === undefined) {
- // TODO WHAT A PAIN
+ if (toIndex > fromIndex) {
+ prevOrder = toIndex >= 0 ? orders[toIndex] : "aaaaa";
+ nextOrder = toIndex <= orders.length ? orders[toIndex + 1] : "zzzzz";
+ } else {
+ // accounts for downwards displacement of existing inhabitant of this index
+ prevOrder = toIndex > 0 ? orders[toIndex - 1] : "aaaaa";
+ nextOrder = toIndex < orders.length ? orders[toIndex] : "zzzzz";
}
+ console.log("@@ start", {fromIndex, toIndex, orders, prevOrder, nextOrder});
- prevOrder = prevOrder || 0.0;
- nextOrder = nextOrder || 1.0;
+ if (prevOrder === undefined) {
+ const firstUndefinedIndex = orders.indexOf(undefined);
+ const numUndefined = orders.length - firstUndefinedIndex;
+ const lastOrder = orders[firstUndefinedIndex - 1];
+ console.log("@@ precalc", {firstUndefinedIndex, numUndefined, lastOrder});
+ nextOrder = lastOrder + step;
+ for (let i = firstUndefinedIndex; i < toIndex; i++, nextOrder += step) {
+ console.log("@@ preset", {i, nextOrder});
+ this.setRootSpaceOrder(this.rootSpaces[i], nextOrder);
+ }
+
+ prevOrder = nextOrder;
+ nextOrder += step;
+ }
if (prevOrder !== nextOrder) {
const order = prevOrder + ((nextOrder - prevOrder) / 2);
- this.spaceOrderLocalEchoMap.set(space.roomId, order);
- this.matrixClient.setRoomAccountData(space.roomId, EventType.Tag, {
- tags: {
- ...space.tags,
- [SpaceTagOrderingField]: { order },
- },
- });
- this.notifyIfOrderChanged();
+ console.log("@@ set", {prevOrder, nextOrder, order});
+ this.setRootSpaceOrder(space, order);
} else {
// TODO REBUILD
}
+
+ this.notifyIfOrderChanged();
}
}
From a7eb09af1ebbffd5f700250dc52dd00238e847f1 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Mon, 7 Jun 2021 15:48:55 +0100
Subject: [PATCH 019/100] Convert EditableItemList & AliasSettings to
Typescript
---
...itableItemList.js => EditableItemList.tsx} | 133 +++++++++--------
.../{AliasSettings.js => AliasSettings.tsx} | 134 ++++++++++--------
2 files changed, 146 insertions(+), 121 deletions(-)
rename src/components/views/elements/{EditableItemList.js => EditableItemList.tsx} (54%)
rename src/components/views/room_settings/{AliasSettings.js => AliasSettings.tsx} (78%)
diff --git a/src/components/views/elements/EditableItemList.js b/src/components/views/elements/EditableItemList.tsx
similarity index 54%
rename from src/components/views/elements/EditableItemList.js
rename to src/components/views/elements/EditableItemList.tsx
index d8ec5af278..89e2e1b8a0 100644
--- a/src/components/views/elements/EditableItemList.js
+++ b/src/components/views/elements/EditableItemList.tsx
@@ -1,5 +1,5 @@
/*
-Copyright 2017, 2019 New Vector Ltd.
+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.
@@ -14,48 +14,48 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React from 'react';
-import PropTypes from 'prop-types';
-import {_t} from '../../../languageHandler';
+import React from "react";
+
+import { _t } from '../../../languageHandler';
import Field from "./Field";
import AccessibleButton from "./AccessibleButton";
-import {replaceableComponent} from "../../../utils/replaceableComponent";
+import { replaceableComponent } from "../../../utils/replaceableComponent";
-export class EditableItem extends React.Component {
- static propTypes = {
- index: PropTypes.number,
- value: PropTypes.string,
- onRemove: PropTypes.func,
+interface IItemProps {
+ index?: number;
+ value?: string;
+ onRemove?(index: number): void;
+}
+
+interface IItemState {
+ verifyRemove: boolean;
+}
+
+export class EditableItem extends React.Component {
+ public state = {
+ verifyRemove: false,
};
- constructor() {
- super();
-
- this.state = {
- verifyRemove: false,
- };
- }
-
- _onRemove = (e) => {
+ private onRemove = (e) => {
e.stopPropagation();
e.preventDefault();
- this.setState({verifyRemove: true});
+ this.setState({ verifyRemove: true });
};
- _onDontRemove = (e) => {
+ private onDontRemove = (e) => {
e.stopPropagation();
e.preventDefault();
- this.setState({verifyRemove: false});
+ this.setState({ verifyRemove: false });
};
- _onActuallyRemove = (e) => {
+ private onActuallyRemove = (e) => {
e.stopPropagation();
e.preventDefault();
if (this.props.onRemove) this.props.onRemove(this.props.index);
- this.setState({verifyRemove: false});
+ this.setState({ verifyRemove: false });
};
render() {
@@ -66,14 +66,14 @@ export class EditableItem extends React.Component {
{_t("Are you sure?")}
{_t("Yes")}
@@ -85,59 +85,68 @@ export class EditableItem extends React.Component {
return (
);
}
}
+interface IProps {
+ id: string;
+ items: string[];
+ itemsLabel?: string;
+ noItemsLabel?: string;
+ placeholder?: string;
+ newItem?: string;
+ canEdit?: boolean;
+ canRemove?: boolean;
+ suggestionsListId?: string;
+ onItemAdded?(item: string): void;
+ onItemRemoved?(index: number): void;
+ onNewItemChanged?(item: string): void;
+}
+
@replaceableComponent("views.elements.EditableItemList")
-export default class EditableItemList extends React.Component {
- static propTypes = {
- id: PropTypes.string.isRequired,
- items: PropTypes.arrayOf(PropTypes.string).isRequired,
- itemsLabel: PropTypes.string,
- noItemsLabel: PropTypes.string,
- placeholder: PropTypes.string,
- newItem: PropTypes.string,
-
- onItemAdded: PropTypes.func,
- onItemRemoved: PropTypes.func,
- onNewItemChanged: PropTypes.func,
-
- canEdit: PropTypes.bool,
- canRemove: PropTypes.bool,
- };
-
- _onItemAdded = (e) => {
+export default class EditableItemList extends React.PureComponent {
+ protected onItemAdded = (e) => {
e.stopPropagation();
e.preventDefault();
if (this.props.onItemAdded) this.props.onItemAdded(this.props.newItem);
};
- _onItemRemoved = (index) => {
+ protected onItemRemoved = (index) => {
if (this.props.onItemRemoved) this.props.onItemRemoved(index);
};
- _onNewItemChanged = (e) => {
+ protected onNewItemChanged = (e) => {
if (this.props.onNewItemChanged) this.props.onNewItemChanged(e.target.value);
};
- _renderNewItemField() {
+ protected renderNewItemField() {
return (
);
@@ -153,19 +162,21 @@ export default class EditableItemList extends React.Component {
key={item}
index={index}
value={item}
- onRemove={this._onItemRemoved}
+ onRemove={this.onItemRemoved}
/>;
});
const editableItemsSection = this.props.canRemove ? editableItems : ;
const label = this.props.items.length > 0 ? this.props.itemsLabel : this.props.noItemsLabel;
- return (
-
- { label }
+ return (
+
+
+ { label }
+
+ { editableItemsSection }
+ { this.props.canEdit ? this.renderNewItemField() :
}
- { editableItemsSection }
- { this.props.canEdit ? this._renderNewItemField() :
}
-
);
+ );
}
}
diff --git a/src/components/views/room_settings/AliasSettings.js b/src/components/views/room_settings/AliasSettings.tsx
similarity index 78%
rename from src/components/views/room_settings/AliasSettings.js
rename to src/components/views/room_settings/AliasSettings.tsx
index 80e0099ab3..d6e79c4ee9 100644
--- a/src/components/views/room_settings/AliasSettings.js
+++ b/src/components/views/room_settings/AliasSettings.tsx
@@ -1,6 +1,5 @@
/*
-Copyright 2016 OpenMarket Ltd
-Copyright 2018, 2019 New Vector Ltd
+Copyright 2016-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.
@@ -15,59 +14,60 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
+import React, { ChangeEvent, createRef } from "react";
+import { MatrixEvent } from "matrix-js-sdk/src/models/event";
+
import EditableItemList from "../elements/EditableItemList";
-import React, {createRef} from 'react';
-import PropTypes from 'prop-types';
-import {MatrixClientPeg} from "../../../MatrixClientPeg";
-import * as sdk from "../../../index";
+import { MatrixClientPeg } from "../../../MatrixClientPeg";
import { _t } from '../../../languageHandler';
import Field from "../elements/Field";
+import Spinner from "../elements/Spinner";
import ErrorDialog from "../dialogs/ErrorDialog";
import AccessibleButton from "../elements/AccessibleButton";
import Modal from "../../../Modal";
import RoomPublishSetting from "./RoomPublishSetting";
-import {replaceableComponent} from "../../../utils/replaceableComponent";
+import { replaceableComponent } from "../../../utils/replaceableComponent";
+import RoomAliasField from "../elements/RoomAliasField";
-class EditableAliasesList extends EditableItemList {
- constructor(props) {
- super(props);
+interface IEditableAliasesListProps {
+ domain?: string;
+}
- this._aliasField = createRef();
- }
+class EditableAliasesList extends EditableItemList
{
+ private aliasField = createRef();
- _onAliasAdded = async () => {
- await this._aliasField.current.validate({ allowEmpty: false });
+ private onAliasAdded = async () => {
+ await this.aliasField.current.validate({ allowEmpty: false });
- if (this._aliasField.current.isValid) {
+ if (this.aliasField.current.isValid) {
if (this.props.onItemAdded) this.props.onItemAdded(this.props.newItem);
return;
}
- this._aliasField.current.focus();
- this._aliasField.current.validate({ allowEmpty: false, focused: true });
+ this.aliasField.current.focus();
+ this.aliasField.current.validate({ allowEmpty: false, focused: true });
};
- _renderNewItemField() {
+ protected renderNewItemField() {
// if we don't need the RoomAliasField,
- // we don't need to overriden version of _renderNewItemField
+ // we don't need to overriden version of renderNewItemField
if (!this.props.domain) {
- return super._renderNewItemField();
+ return super.renderNewItemField();
}
- const RoomAliasField = sdk.getComponent('views.elements.RoomAliasField');
- const onChange = (alias) => this._onNewItemChanged({target: {value: alias}});
+ const onChange = (alias) => this.onNewItemChanged({target: {value: alias}});
return (
@@ -75,15 +75,27 @@ class EditableAliasesList extends EditableItemList {
}
}
-@replaceableComponent("views.room_settings.AliasSettings")
-export default class AliasSettings extends React.Component {
- static propTypes = {
- roomId: PropTypes.string.isRequired,
- canSetCanonicalAlias: PropTypes.bool.isRequired,
- canSetAliases: PropTypes.bool.isRequired,
- canonicalAliasEvent: PropTypes.object, // MatrixEvent
- };
+interface IProps {
+ roomId: string;
+ canSetCanonicalAlias: boolean;
+ canSetAliases: boolean;
+ canonicalAliasEvent?: MatrixEvent;
+ aliasEvents?: MatrixEvent[];
+}
+interface IState {
+ altAliases: string[];
+ localAliases: string[];
+ canonicalAlias?: string;
+ updatingCanonicalAlias: boolean;
+ localAliasesLoading: boolean;
+ detailsOpen: boolean;
+ newAlias?: string;
+ newAltAlias?: string;
+}
+
+@replaceableComponent("views.room_settings.AliasSettings")
+export default class AliasSettings extends React.Component {
static defaultProps = {
canSetAliases: false,
canSetCanonicalAlias: false,
@@ -122,7 +134,7 @@ export default class AliasSettings extends React.Component {
}
}
- async loadLocalAliases() {
+ private async loadLocalAliases() {
this.setState({ localAliasesLoading: true });
try {
const cli = MatrixClientPeg.get();
@@ -139,7 +151,7 @@ export default class AliasSettings extends React.Component {
}
}
- changeCanonicalAlias(alias) {
+ private changeCanonicalAlias(alias: string) {
if (!this.props.canSetCanonicalAlias) return;
const oldAlias = this.state.canonicalAlias;
@@ -170,7 +182,7 @@ export default class AliasSettings extends React.Component {
});
}
- changeAltAliases(altAliases) {
+ private changeAltAliases(altAliases: string[]) {
if (!this.props.canSetCanonicalAlias) return;
this.setState({
@@ -181,7 +193,7 @@ export default class AliasSettings extends React.Component {
const eventContent = {};
if (this.state.canonicalAlias) {
- eventContent.alias = this.state.canonicalAlias;
+ eventContent["alias"] = this.state.canonicalAlias;
}
if (altAliases) {
eventContent["alt_aliases"] = altAliases;
@@ -202,11 +214,11 @@ export default class AliasSettings extends React.Component {
});
}
- onNewAliasChanged = (value) => {
- this.setState({newAlias: value});
+ private onNewAliasChanged = (value: string) => {
+ this.setState({ newAlias: value });
};
- onLocalAliasAdded = (alias) => {
+ private onLocalAliasAdded = (alias: string) => {
if (!alias || alias.length === 0) return; // ignore attempts to create blank aliases
const localDomain = MatrixClientPeg.get().getDomain();
@@ -232,7 +244,7 @@ export default class AliasSettings extends React.Component {
});
};
- onLocalAliasDeleted = (index) => {
+ private onLocalAliasDeleted = (index: number) => {
const alias = this.state.localAliases[index];
// TODO: In future, we should probably be making sure that the alias actually belongs
// to this room. See https://github.com/vector-im/element-web/issues/7353
@@ -261,7 +273,7 @@ export default class AliasSettings extends React.Component {
});
};
- onLocalAliasesToggled = (event) => {
+ private onLocalAliasesToggled = (event: ChangeEvent) => {
// expanded
if (event.target.open) {
// if local aliases haven't been preloaded yet at component mount
@@ -269,37 +281,37 @@ export default class AliasSettings extends React.Component {
this.loadLocalAliases();
}
}
- this.setState({detailsOpen: event.target.open});
+ this.setState({ detailsOpen: event.currentTarget.open });
};
- onCanonicalAliasChange = (event) => {
+ private onCanonicalAliasChange = (event: ChangeEvent) => {
this.changeCanonicalAlias(event.target.value);
};
- onNewAltAliasChanged = (value) => {
- this.setState({newAltAlias: value});
+ private onNewAltAliasChanged = (value: string) => {
+ this.setState({ newAltAlias: value });
}
- onAltAliasAdded = (alias) => {
+ private onAltAliasAdded = (alias: string) => {
const altAliases = this.state.altAliases.slice();
if (!altAliases.some(a => a.trim() === alias.trim())) {
altAliases.push(alias.trim());
this.changeAltAliases(altAliases);
- this.setState({newAltAlias: ""});
+ this.setState({ newAltAlias: "" });
}
}
- onAltAliasDeleted = (index) => {
+ private onAltAliasDeleted = (index: number) => {
const altAliases = this.state.altAliases.slice();
altAliases.splice(index, 1);
this.changeAltAliases(altAliases);
}
- _getAliases() {
- return this.state.altAliases.concat(this._getLocalNonAltAliases());
+ private getAliases() {
+ return this.state.altAliases.concat(this.getLocalNonAltAliases());
}
- _getLocalNonAltAliases() {
+ private getLocalNonAltAliases() {
const {altAliases} = this.state;
return this.state.localAliases.filter(alias => !altAliases.includes(alias));
}
@@ -320,7 +332,7 @@ export default class AliasSettings extends React.Component {
>
{ _t('not specified') }
{
- this._getAliases().map((alias, i) => {
+ this.getAliases().map((alias, i) => {
if (alias === this.state.canonicalAlias) found = true;
return (
@@ -340,12 +352,10 @@ export default class AliasSettings extends React.Component {
let localAliasesList;
if (this.state.localAliasesLoading) {
- const Spinner = sdk.getComponent("elements.Spinner");
localAliasesList = ;
} else {
localAliasesList = (
- {this._getLocalNonAltAliases().map(alias => {
+ {this.getLocalNonAltAliases().map(alias => {
return ;
})};
- {_t("Local Addresses")}
- {_t("Set addresses for this room so users can find this room through your homeserver (%(localDomain)s)", {localDomain})}
+
+ { _t("Local Addresses") }
+
+
+ { _t("Set addresses for this room so users can find this room " +
+ "through your homeserver (%(localDomain)s)", { localDomain }) }
+
{ this.state.detailsOpen ? _t('Show less') : _t("Show more")}
- {localAliasesList}
+ { localAliasesList }
);
From 4725a9e8fa954b3858f4dd8e8d4212f6a0d61488 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Mon, 7 Jun 2021 16:42:59 +0100
Subject: [PATCH 020/100] Extract useRoomState hook into hooks directory
---
.../views/right_panel/PinnedMessagesCard.tsx | 19 +--------
src/hooks/useRoomState.ts | 40 +++++++++++++++++++
2 files changed, 41 insertions(+), 18 deletions(-)
create mode 100644 src/hooks/useRoomState.ts
diff --git a/src/components/views/right_panel/PinnedMessagesCard.tsx b/src/components/views/right_panel/PinnedMessagesCard.tsx
index a3f1f2d9df..ad62619593 100644
--- a/src/components/views/right_panel/PinnedMessagesCard.tsx
+++ b/src/components/views/right_panel/PinnedMessagesCard.tsx
@@ -28,6 +28,7 @@ import { useEventEmitter } from "../../../hooks/useEventEmitter";
import PinningUtils from "../../../utils/PinningUtils";
import { useAsyncMemo } from "../../../hooks/useAsyncMemo";
import PinnedEventTile from "../rooms/PinnedEventTile";
+import { useRoomState } from "../../../hooks/useRoomState";
interface IProps {
room: Room;
@@ -75,24 +76,6 @@ export const useReadPinnedEvents = (room: Room): Set => {
return readPinnedEvents;
};
-const useRoomState = (room: Room, mapper: (state: RoomState) => T): T => {
- const [value, setValue] = useState(room ? mapper(room.currentState) : undefined);
-
- const update = useCallback(() => {
- if (!room) return;
- setValue(mapper(room.currentState));
- }, [room, mapper]);
-
- useEventEmitter(room?.currentState, "RoomState.events", update);
- useEffect(() => {
- update();
- return () => {
- setValue(undefined);
- };
- }, [update]);
- return value;
-};
-
const PinnedMessagesCard = ({ room, onClose }: IProps) => {
const cli = useContext(MatrixClientContext);
const canUnpin = useRoomState(room, state => state.mayClientSendStateEvent(EventType.RoomPinnedEvents, cli));
diff --git a/src/hooks/useRoomState.ts b/src/hooks/useRoomState.ts
new file mode 100644
index 0000000000..ded51c3900
--- /dev/null
+++ b/src/hooks/useRoomState.ts
@@ -0,0 +1,40 @@
+/*
+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 { useCallback, useEffect, useState } from "react";
+import { Room } from "matrix-js-sdk/src/models/room";
+import { RoomState } from "matrix-js-sdk/src/models/room-state";
+
+import { useEventEmitter } from "./useEventEmitter";
+
+// Hook to simplify watching Matrix Room state
+export const useRoomState = (room: Room, mapper: (state: RoomState) => T): T => {
+ const [value, setValue] = useState(room ? mapper(room.currentState) : undefined);
+
+ const update = useCallback(() => {
+ if (!room) return;
+ setValue(mapper(room.currentState));
+ }, [room, mapper]);
+
+ useEventEmitter(room?.currentState, "RoomState.events", update);
+ useEffect(() => {
+ update();
+ return () => {
+ setValue(undefined);
+ };
+ }, [update]);
+ return value;
+};
From 5e3ad621892786890692c987d95f74881bebe232 Mon Sep 17 00:00:00 2001
From: Robin Townsend
Date: Mon, 7 Jun 2021 19:03:04 -0400
Subject: [PATCH 021/100] Remove mysterious dot from EventTilePreviews
It was a bullet point, since EventTiles now get created as li by
default :P
Signed-off-by: Robin Townsend
---
src/components/views/elements/EventTilePreview.tsx | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/components/views/elements/EventTilePreview.tsx b/src/components/views/elements/EventTilePreview.tsx
index 77db94b5dd..20d6cbaeb3 100644
--- a/src/components/views/elements/EventTilePreview.tsx
+++ b/src/components/views/elements/EventTilePreview.tsx
@@ -128,6 +128,7 @@ export default class EventTilePreview extends React.Component {
mxEvent={event}
layout={this.props.layout}
enableFlair={SettingsStore.getValue(UIFeature.Flair)}
+ as="div"
/>
;
}
From 9454a4e6c79f0ecfa5ebd91bd7af689e478ca559 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 8 Jun 2021 16:28:02 +0100
Subject: [PATCH 022/100] Convert AdvancedRoomSettingsTab to Typescript
---
...ingsTab.js => AdvancedRoomSettingsTab.tsx} | 87 ++++++++++---------
1 file changed, 47 insertions(+), 40 deletions(-)
rename src/components/views/settings/tabs/room/{AdvancedRoomSettingsTab.js => AdvancedRoomSettingsTab.tsx} (73%)
diff --git a/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.js b/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.tsx
similarity index 73%
rename from src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.js
rename to src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.tsx
index 28aad65129..f587210095 100644
--- a/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.js
+++ b/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.tsx
@@ -1,5 +1,5 @@
/*
-Copyright 2019 New Vector Ltd
+Copyright 2019, 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -15,68 +15,75 @@ limitations under the License.
*/
import React from 'react';
-import PropTypes from 'prop-types';
-import {_t} from "../../../../../languageHandler";
-import {MatrixClientPeg} from "../../../../../MatrixClientPeg";
-import * as sdk from "../../../../..";
+
+import { _t } from "../../../../../languageHandler";
+import { MatrixClientPeg } from "../../../../../MatrixClientPeg";
import AccessibleButton from "../../../elements/AccessibleButton";
+import RoomUpgradeDialog from "../../../dialogs/RoomUpgradeDialog";
+import DevtoolsDialog from "../../../dialogs/DevtoolsDialog";
import Modal from "../../../../../Modal";
import dis from "../../../../../dispatcher/dispatcher";
-import {replaceableComponent} from "../../../../../utils/replaceableComponent";
+import { replaceableComponent } from "../../../../../utils/replaceableComponent";
+
+interface IProps {
+ roomId: string;
+ closeSettingsFn(): void;
+}
+
+interface IRecommendedVersion {
+ version: string;
+ needsUpgrade: boolean;
+ urgent: boolean;
+}
+
+interface IState {
+ upgradeRecommendation?: IRecommendedVersion;
+ oldRoomId?: string;
+ oldEventId?: string;
+ upgraded?: boolean;
+}
@replaceableComponent("views.settings.tabs.room.AdvancedRoomSettingsTab")
-export default class AdvancedRoomSettingsTab extends React.Component {
- static propTypes = {
- roomId: PropTypes.string.isRequired,
- closeSettingsFn: PropTypes.func.isRequired,
- };
-
- constructor(props) {
- super(props);
+export default class AdvancedRoomSettingsTab extends React.Component {
+ constructor(props, context) {
+ super(props, context);
this.state = {
// This is eventually set to the value of room.getRecommendedVersion()
upgradeRecommendation: null,
};
- }
- // TODO: [REACT-WARNING] Move this to constructor
- UNSAFE_componentWillMount() { // eslint-disable-line camelcase
// we handle lack of this object gracefully later, so don't worry about it failing here.
const room = MatrixClientPeg.get().getRoom(this.props.roomId);
room.getRecommendedVersion().then((v) => {
const tombstone = room.currentState.getStateEvents("m.room.tombstone", "");
- const additionalStateChanges = {};
+ const additionalStateChanges: Partial = {};
const createEvent = room.currentState.getStateEvents("m.room.create", "");
const predecessor = createEvent ? createEvent.getContent().predecessor : null;
if (predecessor && predecessor.room_id) {
- additionalStateChanges['oldRoomId'] = predecessor.room_id;
- additionalStateChanges['oldEventId'] = predecessor.event_id;
- additionalStateChanges['hasPreviousRoom'] = true;
+ additionalStateChanges.oldRoomId = predecessor.room_id;
+ additionalStateChanges.oldEventId = predecessor.event_id;
}
-
this.setState({
- upgraded: tombstone && tombstone.getContent().replacement_room,
+ upgraded: !!tombstone?.getContent().replacement_room,
upgradeRecommendation: v,
...additionalStateChanges,
});
});
}
- _upgradeRoom = (e) => {
- const RoomUpgradeDialog = sdk.getComponent('dialogs.RoomUpgradeDialog');
+ private upgradeRoom = (e) => {
const room = MatrixClientPeg.get().getRoom(this.props.roomId);
Modal.createTrackedDialog('Upgrade Room Version', '', RoomUpgradeDialog, {room: room});
};
- _openDevtools = (e) => {
- const DevtoolsDialog = sdk.getComponent('dialogs.DevtoolsDialog');
+ private openDevtools = (e) => {
Modal.createDialog(DevtoolsDialog, {roomId: this.props.roomId});
};
- _onOldRoomClicked = (e) => {
+ private onOldRoomClicked = (e) => {
e.preventDefault();
e.stopPropagation();
@@ -113,7 +120,7 @@ export default class AdvancedRoomSettingsTab extends React.Component {
},
)}
-
+
{_t("Upgrade this room to the recommended room version")}
@@ -121,13 +128,13 @@ export default class AdvancedRoomSettingsTab extends React.Component {
}
let oldRoomLink;
- if (this.state.hasPreviousRoom) {
+ if (this.state.oldRoomId) {
let name = _t("this room");
const room = MatrixClientPeg.get().getRoom(this.props.roomId);
if (room && room.name) name = room.name;
oldRoomLink = (
-
- {_t("View older messages in %(roomName)s.", {roomName: name})}
+
+ { _t("View older messages in %(roomName)s.", { roomName: name }) }
);
}
@@ -139,23 +146,23 @@ export default class AdvancedRoomSettingsTab extends React.Component {
{_t("Room information")}
{_t("Internal room ID:")}
- {this.props.roomId}
+ { this.props.roomId }
- {unfederatableSection}
+ { unfederatableSection }
{_t("Room version")}
{_t("Room version:")}
- {room.getVersion()}
+ { room.getVersion() }
- {oldRoomLink}
- {roomUpgradeButton}
+ { oldRoomLink }
+ { roomUpgradeButton }
-
{_t("Developer options")}
-
- {_t("Open Devtools")}
+ { _t("Developer options") }
+
+ { _t("Open Devtools") }
From 8d4ac90265839a98aeb83277834ef8673fc2b97e Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 8 Jun 2021 16:28:37 +0100
Subject: [PATCH 023/100] Convert RoomPublishSetting and LabelledToggleSwitch
to Typescript
---
...ggleSwitch.js => LabelledToggleSwitch.tsx} | 47 +++++++++----------
...blishSetting.js => RoomPublishSetting.tsx} | 47 ++++++++++++-------
2 files changed, 51 insertions(+), 43 deletions(-)
rename src/components/views/elements/{LabelledToggleSwitch.js => LabelledToggleSwitch.tsx} (63%)
rename src/components/views/room_settings/{RoomPublishSetting.js => RoomPublishSetting.tsx} (62%)
diff --git a/src/components/views/elements/LabelledToggleSwitch.js b/src/components/views/elements/LabelledToggleSwitch.tsx
similarity index 63%
rename from src/components/views/elements/LabelledToggleSwitch.js
rename to src/components/views/elements/LabelledToggleSwitch.tsx
index ef60eeed7b..957e3dbc97 100644
--- a/src/components/views/elements/LabelledToggleSwitch.js
+++ b/src/components/views/elements/LabelledToggleSwitch.tsx
@@ -1,5 +1,5 @@
/*
-Copyright 2019 New Vector Ltd
+Copyright 2019, 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,38 +14,33 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React from 'react';
-import PropTypes from "prop-types";
+import React from "react";
+
import ToggleSwitch from "./ToggleSwitch";
import {replaceableComponent} from "../../../utils/replaceableComponent";
+interface IProps {
+ // The value for the toggle switch
+ value: boolean;
+ // The translated label for the switch
+ label: string;
+ // Whether or not to disable the toggle switch
+ disabled?: boolean;
+ // True to put the toggle in front of the label
+ // Default false.
+ toggleInFront?: boolean;
+ // Additional class names to append to the switch. Optional.
+ className?: string;
+ // The function to call when the value changes
+ onChange(checked: boolean): void;
+}
+
@replaceableComponent("views.elements.LabelledToggleSwitch")
-export default class LabelledToggleSwitch extends React.Component {
- static propTypes = {
- // The value for the toggle switch
- value: PropTypes.bool.isRequired,
-
- // The function to call when the value changes
- onChange: PropTypes.func.isRequired,
-
- // The translated label for the switch
- label: PropTypes.string.isRequired,
-
- // Whether or not to disable the toggle switch
- disabled: PropTypes.bool,
-
- // True to put the toggle in front of the label
- // Default false.
- toggleInFront: PropTypes.bool,
-
- // Additional class names to append to the switch. Optional.
- className: PropTypes.string,
- };
-
+export default class LabelledToggleSwitch extends React.PureComponent {
render() {
// This is a minimal version of a SettingsFlag
- let firstPart = {this.props.label} ;
+ let firstPart = { this.props.label } ;
let secondPart = {
+ public state = {
+ isRoomPublished: false,
+ };
- onRoomPublishChange = (e) => {
+ private onRoomPublishChange = (e) => {
const valueBefore = this.state.isRoomPublished;
const newValue = !valueBefore;
this.setState({isRoomPublished: newValue});
@@ -52,11 +62,14 @@ export default class RoomPublishSetting extends React.PureComponent {
render() {
const client = MatrixClientPeg.get();
- return ( );
+ return (
+
+ );
}
}
From 13a2f779b962e8498ae09d9675a91baeca3cd671 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 8 Jun 2021 16:29:06 +0100
Subject: [PATCH 024/100] improve defaults for useRoomState and useStateToggle
hooks
---
src/hooks/useRoomState.ts | 5 ++++-
src/hooks/useStateToggle.ts | 2 +-
2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/src/hooks/useRoomState.ts b/src/hooks/useRoomState.ts
index ded51c3900..11ac7de49e 100644
--- a/src/hooks/useRoomState.ts
+++ b/src/hooks/useRoomState.ts
@@ -20,8 +20,11 @@ import { RoomState } from "matrix-js-sdk/src/models/room-state";
import { useEventEmitter } from "./useEventEmitter";
+type Mapper = (roomState: RoomState) => T;
+const defaultMapper: Mapper = (roomState: RoomState) => roomState;
+
// Hook to simplify watching Matrix Room state
-export const useRoomState = (room: Room, mapper: (state: RoomState) => T): T => {
+export const useRoomState = (room: Room, mapper: Mapper = defaultMapper): T => {
const [value, setValue] = useState(room ? mapper(room.currentState) : undefined);
const update = useCallback(() => {
diff --git a/src/hooks/useStateToggle.ts b/src/hooks/useStateToggle.ts
index b50a923234..33701c4f16 100644
--- a/src/hooks/useStateToggle.ts
+++ b/src/hooks/useStateToggle.ts
@@ -18,7 +18,7 @@ import {Dispatch, SetStateAction, useState} from "react";
// Hook to simplify toggling of a boolean state value
// Returns value, method to toggle boolean value and method to set the boolean value
-export const useStateToggle = (initialValue: boolean): [boolean, () => void, Dispatch>] => {
+export const useStateToggle = (initialValue = false): [boolean, () => void, Dispatch>] => {
const [value, setValue] = useState(initialValue);
const toggleValue = () => {
setValue(!value);
From 5c85ee1ea03097325680ed4574006c559fcccaa9 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 8 Jun 2021 16:30:46 +0100
Subject: [PATCH 025/100] Make AdvancedRoomSettingsTab space-aware
---
.../views/settings/tabs/room/AdvancedRoomSettingsTab.tsx | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.tsx b/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.tsx
index f587210095..7e7d9cba90 100644
--- a/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.tsx
+++ b/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.tsx
@@ -76,7 +76,7 @@ export default class AdvancedRoomSettingsTab extends React.Component {
const room = MatrixClientPeg.get().getRoom(this.props.roomId);
- Modal.createTrackedDialog('Upgrade Room Version', '', RoomUpgradeDialog, {room: room});
+ Modal.createTrackedDialog('Upgrade Room Version', '', RoomUpgradeDialog, { room });
};
private openDevtools = (e) => {
@@ -143,7 +143,9 @@ export default class AdvancedRoomSettingsTab extends React.Component
{_t("Advanced")}
-
{_t("Room information")}
+
+ { room?.isSpaceRoom() ? _t("Space information") : _t("Room information") }
+
{_t("Internal room ID:")}
{ this.props.roomId }
From 78debcc93b99eea96f270cbff8a5266a10a52db5 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 8 Jun 2021 16:31:39 +0100
Subject: [PATCH 026/100] Add method to disable StyledRadioGroup and wrap
description in element with a className
---
.../views/elements/StyledRadioGroup.tsx | 17 +++++++++++++----
1 file changed, 13 insertions(+), 4 deletions(-)
diff --git a/src/components/views/elements/StyledRadioGroup.tsx b/src/components/views/elements/StyledRadioGroup.tsx
index 6b9e992f92..744b6f2059 100644
--- a/src/components/views/elements/StyledRadioGroup.tsx
+++ b/src/components/views/elements/StyledRadioGroup.tsx
@@ -34,10 +34,19 @@ interface IProps
{
definitions: IDefinition[];
value?: T; // if not provided no options will be selected
outlined?: boolean;
+ disabled?: boolean;
onChange(newValue: T): void;
}
-function StyledRadioGroup({name, definitions, value, className, outlined, onChange}: IProps) {
+function StyledRadioGroup({
+ name,
+ definitions,
+ value,
+ className,
+ outlined,
+ disabled,
+ onChange,
+}: IProps) {
const _onChange = e => {
onChange(e.target.value);
};
@@ -50,12 +59,12 @@ function StyledRadioGroup({name, definitions, value, className
checked={d.checked !== undefined ? d.checked : d.value === value}
name={name}
value={d.value}
- disabled={d.disabled}
+ disabled={disabled || d.disabled}
outlined={outlined}
>
- {d.label}
+ { d.label }
- {d.description}
+ { d.description ? { d.description } : null }
)}
;
}
From fdecba2fe54a5bfd4e5b9e6974e1fd9467858539 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 8 Jun 2021 16:32:43 +0100
Subject: [PATCH 027/100] Make AliasSettings space-aware, remove stale unused
props
---
.../views/room_settings/AliasSettings.tsx | 34 ++++++++++++++-----
.../tabs/room/GeneralRoomSettingsTab.js | 3 +-
2 files changed, 26 insertions(+), 11 deletions(-)
diff --git a/src/components/views/room_settings/AliasSettings.tsx b/src/components/views/room_settings/AliasSettings.tsx
index d6e79c4ee9..6f83d64eaf 100644
--- a/src/components/views/room_settings/AliasSettings.tsx
+++ b/src/components/views/room_settings/AliasSettings.tsx
@@ -80,7 +80,7 @@ interface IProps {
canSetCanonicalAlias: boolean;
canSetAliases: boolean;
canonicalAliasEvent?: MatrixEvent;
- aliasEvents?: MatrixEvent[];
+ hidePublishSetting?: boolean;
}
interface IState {
@@ -99,7 +99,6 @@ export default class AliasSettings extends React.Component {
static defaultProps = {
canSetAliases: false,
canSetCanonicalAlias: false,
- aliasEvents: [],
};
constructor(props) {
@@ -317,7 +316,9 @@ export default class AliasSettings extends React.Component {
}
render() {
- const localDomain = MatrixClientPeg.get().getDomain();
+ const cli = MatrixClientPeg.get();
+ const localDomain = cli.getDomain();
+ const isSpaceRoom = cli.getRoom(this.props.roomId)?.isSpaceRoom();
let found = false;
const canonicalValue = this.state.canonicalAlias || "";
@@ -363,7 +364,9 @@ export default class AliasSettings extends React.Component {
canEdit={this.props.canSetAliases}
onItemAdded={this.onLocalAliasAdded}
onItemRemoved={this.onLocalAliasDeleted}
- noItemsLabel={_t('This room has no local addresses')}
+ noItemsLabel={isSpaceRoom
+ ? _t("This space has no local addresses")
+ : _t("This room has no local addresses")}
placeholder={_t('Local address')}
domain={localDomain}
/>);
@@ -372,10 +375,20 @@ export default class AliasSettings extends React.Component {
return (
{_t("Published Addresses")}
-
{_t("Published addresses can be used by anyone on any server to join your room. " +
- "To publish an address, it needs to be set as a local address first.")}
- {canonicalAliasSection}
-
+
+ { isSpaceRoom
+ ? _t("Published addresses can be used by anyone on any server to join your space.")
+ : _t("Published addresses can be used by anyone on any server to join your room.")}
+
+ { _t("To publish an address, it needs to be set as a local address first.") }
+
+ { canonicalAliasSection }
+ { this.props.hidePublishSetting
+ ? null
+ :
}
{this.getLocalNonAltAliases().map(alias => {
return ;
@@ -399,7 +412,10 @@ export default class AliasSettings extends React.Component {
{ _t("Local Addresses") }
- { _t("Set addresses for this room so users can find this room " +
+ { isSpaceRoom
+ ? _t("Set addresses for this space so users can find this space " +
+ "through your homeserver (%(localDomain)s)", { localDomain })
+ : _t("Set addresses for this room so users can find this room " +
"through your homeserver (%(localDomain)s)", { localDomain }) }
diff --git a/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.js b/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.js
index 139cfd5fbd..10c93c5dca 100644
--- a/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.js
+++ b/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.js
@@ -60,7 +60,6 @@ export default class GeneralRoomSettingsTab extends React.Component {
const canSetAliases = true; // Previously, we arbitrarily only allowed admins to do this
const canSetCanonical = room.currentState.mayClientSendStateEvent("m.room.canonical_alias", client);
const canonicalAliasEv = room.currentState.getStateEvents("m.room.canonical_alias", '');
- const aliasEvents = room.currentState.getStateEvents("m.room.aliases");
const canChangeGroups = room.currentState.mayClientSendStateEvent("m.room.related_groups", client);
const groupsEvent = room.currentState.getStateEvents("m.room.related_groups", "");
@@ -100,7 +99,7 @@ export default class GeneralRoomSettingsTab extends React.Component {
+ canonicalAliasEvent={canonicalAliasEv} />
{_t("Other")}
{ flairSection }
From 856a5682b90fa1e4ac3c4dbf907c1b5cb55bf0f8 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 8 Jun 2021 16:33:06 +0100
Subject: [PATCH 028/100] Improve useRoomPowerLevels hook
---
src/components/views/right_panel/UserInfo.tsx | 12 ++++--------
1 file changed, 4 insertions(+), 8 deletions(-)
diff --git a/src/components/views/right_panel/UserInfo.tsx b/src/components/views/right_panel/UserInfo.tsx
index d6c97f9cf2..48336de400 100644
--- a/src/components/views/right_panel/UserInfo.tsx
+++ b/src/components/views/right_panel/UserInfo.tsx
@@ -502,19 +502,15 @@ const isMuted = (member: RoomMember, powerLevelContent: IPowerLevelsContent) =>
return member.powerLevel < levelToSend;
};
+const getPowerLevels = room => room.currentState.getStateEvents(EventType.RoomPowerLevels, "")?.getContent() || {};
+
export const useRoomPowerLevels = (cli: MatrixClient, room: Room) => {
- const [powerLevels, setPowerLevels] = useState({});
+ const [powerLevels, setPowerLevels] = useState(getPowerLevels(room));
const update = useCallback((ev?: MatrixEvent) => {
if (!room) return;
if (ev && ev.getType() !== EventType.RoomPowerLevels) return;
-
- const event = room.currentState.getStateEvents(EventType.RoomPowerLevels, "");
- if (event) {
- setPowerLevels(event.getContent());
- } else {
- setPowerLevels({});
- }
+ setPowerLevels(getPowerLevels(room));
}, [room]);
useEventEmitter(cli, "RoomState.events", update);
From 90bb7c1482c38a234099d023b4bb1a4ae0232f57 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 8 Jun 2021 16:33:47 +0100
Subject: [PATCH 029/100] Switch Space Settings for a tabbed view with a bunch
more settings exposed
---
res/css/views/dialogs/_SettingsDialog.scss | 2 +-
.../views/dialogs/_SpaceSettingsDialog.scss | 51 ++++-
res/img/element-icons/eye.svg | 3 +
.../views/dialogs/SpaceSettingsDialog.tsx | 172 +++++------------
.../tabs/room/SecurityRoomSettingsTab.tsx | 6 +-
.../views/spaces/SpaceSettingsGeneralTab.tsx | 143 ++++++++++++++
.../spaces/SpaceSettingsVisibilityTab.tsx | 181 ++++++++++++++++++
src/i18n/strings/en_EN.json | 40 ++--
8 files changed, 456 insertions(+), 142 deletions(-)
create mode 100644 res/img/element-icons/eye.svg
create mode 100644 src/components/views/spaces/SpaceSettingsGeneralTab.tsx
create mode 100644 src/components/views/spaces/SpaceSettingsVisibilityTab.tsx
diff --git a/res/css/views/dialogs/_SettingsDialog.scss b/res/css/views/dialogs/_SettingsDialog.scss
index 6c4ed35c5a..b3b6802c3d 100644
--- a/res/css/views/dialogs/_SettingsDialog.scss
+++ b/res/css/views/dialogs/_SettingsDialog.scss
@@ -15,7 +15,7 @@ limitations under the License.
*/
// Not actually a component but things shared by settings components
-.mx_UserSettingsDialog, .mx_RoomSettingsDialog {
+.mx_UserSettingsDialog, .mx_RoomSettingsDialog, .mx_SpaceSettingsDialog {
width: 90vw;
max-width: 1000px;
// set the height too since tabbed view scrolls itself.
diff --git a/res/css/views/dialogs/_SpaceSettingsDialog.scss b/res/css/views/dialogs/_SpaceSettingsDialog.scss
index 6e5fd9c8c8..fa074fdbe8 100644
--- a/res/css/views/dialogs/_SpaceSettingsDialog.scss
+++ b/res/css/views/dialogs/_SpaceSettingsDialog.scss
@@ -15,7 +15,6 @@ limitations under the License.
*/
.mx_SpaceSettingsDialog {
- width: 480px;
color: $primary-fg-color;
.mx_SpaceSettings_errorText {
@@ -32,8 +31,44 @@ limitations under the License.
margin-left: 16px;
}
- .mx_AccessibleButton_kind_danger {
- margin-top: 28px;
+ .mx_SettingsTab_section {
+ .mx_SettingsTab_section_caption {
+ margin-top: 12px;
+ margin-bottom: 20px;
+ }
+
+ & + .mx_SettingsTab_subheading {
+ border-top: 1px solid $message-body-panel-bg-color;
+ margin-top: 0;
+ padding-top: 24px;
+ }
+
+ .mx_RadioButton {
+ margin-top: 8px;
+ margin-bottom: 4px;
+
+ .mx_RadioButton_content {
+ font-weight: $font-semi-bold;
+ line-height: $font-18px;
+ color: $primary-fg-color;
+ }
+
+ & + span {
+ font-size: $font-15px;
+ line-height: $font-18px;
+ color: $secondary-fg-color;
+ margin-left: 26px;
+ }
+ }
+
+ .mx_SettingsTab_showAdvanced {
+ margin: 16px 0;
+ padding: 0;
+ }
+
+ .mx_SettingsFlag {
+ margin-top: 24px;
+ }
}
.mx_SpaceSettingsDialog_buttons {
@@ -52,4 +87,14 @@ limitations under the License.
.mx_AccessibleButton_hasKind {
padding: 8px 22px;
}
+
+ .mx_TabbedView_tabLabel {
+ .mx_SpaceSettingsDialog_generalIcon::before {
+ mask-image: url('$(res)/img/element-icons/settings.svg');
+ }
+
+ .mx_SpaceSettingsDialog_visibilityIcon::before {
+ mask-image: url('$(res)/img/element-icons/eye.svg');
+ }
+ }
}
diff --git a/res/img/element-icons/eye.svg b/res/img/element-icons/eye.svg
new file mode 100644
index 0000000000..0460a6201d
--- /dev/null
+++ b/res/img/element-icons/eye.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/components/views/dialogs/SpaceSettingsDialog.tsx b/src/components/views/dialogs/SpaceSettingsDialog.tsx
index a135b6bc16..1273f06401 100644
--- a/src/components/views/dialogs/SpaceSettingsDialog.tsx
+++ b/src/components/views/dialogs/SpaceSettingsDialog.tsx
@@ -14,24 +14,27 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React, {useState} from 'react';
-import {Room} from "matrix-js-sdk/src/models/room";
-import {MatrixClient} from "matrix-js-sdk/src/client";
-import {EventType} from "matrix-js-sdk/src/@types/event";
+import React, { useMemo } from 'react';
+import { Room } from "matrix-js-sdk/src/models/room";
+import { MatrixClient } from "matrix-js-sdk/src/client";
-import {_t} from '../../../languageHandler';
-import {IDialogProps} from "./IDialogProps";
+import { _t, _td } from '../../../languageHandler';
+import { IDialogProps } from "./IDialogProps";
import BaseDialog from "./BaseDialog";
-import DevtoolsDialog from "./DevtoolsDialog";
-import SpaceBasicSettings from '../spaces/SpaceBasicSettings';
-import {getTopic} from "../elements/RoomTopic";
-import {avatarUrlForRoom} from "../../../Avatar";
-import ToggleSwitch from "../elements/ToggleSwitch";
-import AccessibleButton from "../elements/AccessibleButton";
-import Modal from "../../../Modal";
import defaultDispatcher from "../../../dispatcher/dispatcher";
-import {useDispatcher} from "../../../hooks/useDispatcher";
-import {SpaceFeedbackPrompt} from "../../structures/SpaceRoomView";
+import { useDispatcher } from "../../../hooks/useDispatcher";
+import TabbedView, { Tab } from "../../structures/TabbedView";
+import SpaceSettingsGeneralTab from '../spaces/SpaceSettingsGeneralTab';
+import SpaceSettingsVisibilityTab from "../spaces/SpaceSettingsVisibilityTab";
+import SettingsStore from "../../../settings/SettingsStore";
+import {UIFeature} from "../../../settings/UIFeature";
+import AdvancedRoomSettingsTab from "../settings/tabs/room/AdvancedRoomSettingsTab";
+
+export enum SpaceSettingsTab {
+ General = "SPACE_GENERAL_TAB",
+ Visibility = "SPACE_VISIBILITY_TAB",
+ Advanced = "SPACE_ADVANCED_TAB",
+}
interface IProps extends IDialogProps {
matrixClient: MatrixClient;
@@ -45,63 +48,30 @@ const SpaceSettingsDialog: React.FC = ({ matrixClient: cli, space, onFin
}
});
- const [busy, setBusy] = useState(false);
- const [error, setError] = useState("");
-
- const userId = cli.getUserId();
-
- const [newAvatar, setNewAvatar] = useState(null); // undefined means to remove avatar
- const canSetAvatar = space.currentState.maySendStateEvent(EventType.RoomAvatar, userId);
- const avatarChanged = newAvatar !== null;
-
- const [name, setName] = useState(space.name);
- const canSetName = space.currentState.maySendStateEvent(EventType.RoomName, userId);
- const nameChanged = name !== space.name;
-
- const currentTopic = getTopic(space);
- const [topic, setTopic] = useState(currentTopic);
- const canSetTopic = space.currentState.maySendStateEvent(EventType.RoomTopic, userId);
- const topicChanged = topic !== currentTopic;
-
- const currentJoinRule = space.getJoinRule();
- const [joinRule, setJoinRule] = useState(currentJoinRule);
- const canSetJoinRule = space.currentState.maySendStateEvent(EventType.RoomJoinRules, userId);
- const joinRuleChanged = joinRule !== currentJoinRule;
-
- const onSave = async () => {
- setBusy(true);
- const promises = [];
-
- if (avatarChanged) {
- if (newAvatar) {
- promises.push(cli.sendStateEvent(space.roomId, EventType.RoomAvatar, {
- url: await cli.uploadContent(newAvatar),
- }, ""));
- } else {
- promises.push(cli.sendStateEvent(space.roomId, EventType.RoomAvatar, {}, ""));
- }
- }
-
- if (nameChanged) {
- promises.push(cli.setRoomName(space.roomId, name));
- }
-
- if (topicChanged) {
- promises.push(cli.setRoomTopic(space.roomId, topic));
- }
-
- if (joinRuleChanged) {
- promises.push(cli.sendStateEvent(space.roomId, EventType.RoomJoinRules, { join_rule: joinRule }, ""));
- }
-
- const results = await Promise.allSettled(promises);
- setBusy(false);
- const failures = results.filter(r => r.status === "rejected");
- if (failures.length > 0) {
- console.error("Failed to save space settings: ", failures);
- setError(_t("Failed to save space settings."));
- }
- };
+ const tabs = useMemo(() => {
+ return [
+ new Tab(
+ SpaceSettingsTab.General,
+ _td("General"),
+ "mx_SpaceSettingsDialog_generalIcon",
+ ,
+ ),
+ new Tab(
+ SpaceSettingsTab.Visibility,
+ _td("Visibility"),
+ "mx_SpaceSettingsDialog_visibilityIcon",
+ ,
+ ),
+ SettingsStore.getValue(UIFeature.AdvancedSettings)
+ ? new Tab(
+ SpaceSettingsTab.Advanced,
+ _td("Advanced"),
+ "mx_RoomSettingsDialog_warningIcon",
+ ,
+ )
+ : null,
+ ].filter(Boolean);
+ }, [cli, space, onFinished]);
return = ({ matrixClient: cli, space, onFin
onFinished={onFinished}
fixedWidth={false}
>
-
-
{ _t("Edit settings relating to your space.") }
-
- { error &&
{ error }
}
-
-
onFinished(false)} />
-
-
-
-
- { _t("Make this space private") }
- setJoinRule(checked ? "invite" : "public")}
- disabled={!canSetJoinRule}
- aria-label={_t("Make this space private")}
- />
-
-
- {
- defaultDispatcher.dispatch({
- action: "leave_room",
- room_id: space.roomId,
- });
- }}
- >
- { _t("Leave Space") }
-
-
-
-
Modal.createDialog(DevtoolsDialog, {roomId: space.roomId})}>
- { _t("View dev tools") }
-
-
- { _t("Cancel") }
-
-
- { busy ? _t("Saving...") : _t("Save Changes") }
-
-
+
+
;
};
diff --git a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx
index 02bbcfb751..99f525364e 100644
--- a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx
+++ b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx
@@ -29,19 +29,19 @@ import {UIFeature} from "../../../../../settings/UIFeature";
import { replaceableComponent } from "../../../../../utils/replaceableComponent";
// Knock and private are reserved keywords which are not yet implemented.
-enum JoinRule {
+export enum JoinRule {
Public = "public",
Knock = "knock",
Invite = "invite",
Private = "private",
}
-enum GuestAccess {
+export enum GuestAccess {
CanJoin = "can_join",
Forbidden = "forbidden",
}
-enum HistoryVisibility {
+export enum HistoryVisibility {
Invited = "invited",
Joined = "joined",
Shared = "shared",
diff --git a/src/components/views/spaces/SpaceSettingsGeneralTab.tsx b/src/components/views/spaces/SpaceSettingsGeneralTab.tsx
new file mode 100644
index 0000000000..db0a180846
--- /dev/null
+++ b/src/components/views/spaces/SpaceSettingsGeneralTab.tsx
@@ -0,0 +1,143 @@
+/*
+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, { useState } from "react";
+import { Room } from "matrix-js-sdk/src/models/room";
+import { MatrixClient } from "matrix-js-sdk/src/client";
+import { EventType } from "matrix-js-sdk/src/@types/event";
+
+import { _t } from "../../../languageHandler";
+import AccessibleButton from "../elements/AccessibleButton";
+import { SpaceFeedbackPrompt } from "../../structures/SpaceRoomView";
+import SpaceBasicSettings from "./SpaceBasicSettings";
+import { avatarUrlForRoom } from "../../../Avatar";
+import { IDialogProps } from "../dialogs/IDialogProps";
+import { getTopic } from "../elements/RoomTopic";
+import { defaultDispatcher } from "../../../dispatcher/dispatcher";
+
+interface IProps extends IDialogProps {
+ matrixClient: MatrixClient;
+ space: Room;
+}
+
+const SpaceSettingsGeneralTab = ({ matrixClient: cli, space, onFinished }: IProps) => {
+ const [busy, setBusy] = useState(false);
+ const [error, setError] = useState("");
+
+ const userId = cli.getUserId();
+
+ const [newAvatar, setNewAvatar] = useState(null); // undefined means to remove avatar
+ const canSetAvatar = space.currentState.maySendStateEvent(EventType.RoomAvatar, userId);
+ const avatarChanged = newAvatar !== null;
+
+ const [name, setName] = useState(space.name);
+ const canSetName = space.currentState.maySendStateEvent(EventType.RoomName, userId);
+ const nameChanged = name !== space.name;
+
+ const currentTopic = getTopic(space);
+ const [topic, setTopic] = useState(currentTopic);
+ const canSetTopic = space.currentState.maySendStateEvent(EventType.RoomTopic, userId);
+ const topicChanged = topic !== currentTopic;
+
+ const onCancel = () => {
+ setNewAvatar(null);
+ setName(space.name);
+ setTopic(currentTopic);
+ };
+
+ const onSave = async () => {
+ setBusy(true);
+ const promises = [];
+
+ if (avatarChanged) {
+ if (newAvatar) {
+ promises.push(cli.sendStateEvent(space.roomId, EventType.RoomAvatar, {
+ url: await cli.uploadContent(newAvatar),
+ }, ""));
+ } else {
+ promises.push(cli.sendStateEvent(space.roomId, EventType.RoomAvatar, {}, ""));
+ }
+ }
+
+ if (nameChanged) {
+ promises.push(cli.setRoomName(space.roomId, name));
+ }
+
+ if (topicChanged) {
+ promises.push(cli.setRoomTopic(space.roomId, topic));
+ }
+
+ const results = await Promise.allSettled(promises);
+ setBusy(false);
+ const failures = results.filter(r => r.status === "rejected");
+ if (failures.length > 0) {
+ console.error("Failed to save space settings: ", failures);
+ setError(_t("Failed to save space settings."));
+ }
+ };
+
+ return
+
{_t("General")}
+
+
{ _t("Edit settings relating to your space.") }
+
+ { error &&
{ error }
}
+
+
onFinished(false)} />
+
+
+
+
+
+ { _t("Cancel") }
+
+
+ { busy ? _t("Saving...") : _t("Save Changes") }
+
+
+
+ {_t("Leave Space")}
+
+
{
+ defaultDispatcher.dispatch({
+ action: "leave_room",
+ room_id: space.roomId,
+ });
+ }}
+ >
+ { _t("Leave Space") }
+
+
+ ;
+};
+
+export default SpaceSettingsGeneralTab;
diff --git a/src/components/views/spaces/SpaceSettingsVisibilityTab.tsx b/src/components/views/spaces/SpaceSettingsVisibilityTab.tsx
new file mode 100644
index 0000000000..7fc3514b2d
--- /dev/null
+++ b/src/components/views/spaces/SpaceSettingsVisibilityTab.tsx
@@ -0,0 +1,181 @@
+/*
+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, { useState } from "react";
+import { Room } from "matrix-js-sdk/src/models/room";
+import { MatrixClient } from "matrix-js-sdk/src/client";
+import { EventType } from "matrix-js-sdk/src/@types/event";
+
+import { _t } from "../../../languageHandler";
+import AccessibleButton from "../elements/AccessibleButton";
+import AliasSettings from "../room_settings/AliasSettings";
+import { useStateToggle } from "../../../hooks/useStateToggle";
+import LabelledToggleSwitch from "../elements/LabelledToggleSwitch";
+import { GuestAccess, HistoryVisibility, JoinRule } from "../settings/tabs/room/SecurityRoomSettingsTab";
+import StyledRadioGroup from "../elements/StyledRadioGroup";
+
+interface IProps {
+ matrixClient: MatrixClient;
+ space: Room;
+}
+
+enum SpaceVisibility {
+ Unlisted = "unlisted",
+ Private = "private",
+}
+
+const useLocalEcho = (
+ currentFactory: () => T,
+ setterFn: (value: T) => Promise,
+ errorFn: (error: Error) => void,
+): [value: T, handler: (value: T) => void] => {
+ const [value, setValue] = useState(currentFactory);
+ const handler = async (value: T) => {
+ setValue(value);
+ try {
+ await setterFn(value);
+ } catch (e) {
+ setValue(currentFactory());
+ errorFn(e);
+ }
+ };
+
+ return [value, handler];
+};
+
+const SpaceSettingsVisibilityTab = ({ matrixClient: cli, space }: IProps) => {
+ const [busy, setBusy] = useState(false);
+ const [error, setError] = useState("");
+
+ const userId = cli.getUserId();
+
+ const [visibility, setVisibility] = useLocalEcho(
+ () => space.getJoinRule() === JoinRule.Private ? SpaceVisibility.Private : SpaceVisibility.Unlisted,
+ visibility => cli.sendStateEvent(space.roomId, EventType.RoomJoinRules, {
+ join_rule: visibility === SpaceVisibility.Unlisted ? JoinRule.Public : JoinRule.Private,
+ }, ""),
+ () => setError(_t("Failed to update the visibility of this space")),
+ );
+ const [guestAccessEnabled, setGuestAccessEnabled] = useLocalEcho(
+ () => space.currentState.getStateEvents(EventType.RoomGuestAccess, "")
+ ?.getContent()?.guest_access === GuestAccess.CanJoin,
+ guestAccessEnabled => cli.sendStateEvent(space.roomId, EventType.RoomGuestAccess, {
+ guest_access: guestAccessEnabled ? GuestAccess.CanJoin : GuestAccess.Forbidden,
+ }, ""),
+ () => setError(_t("Failed to update the guest access of this space")),
+ );
+ const [historyVisibility, setHistoryVisibility] = useLocalEcho(
+ () => space.currentState.getStateEvents(EventType.RoomHistoryVisibility, "")
+ ?.getContent()?.history_visibility || HistoryVisibility.Shared,
+ historyVisibility => cli.sendStateEvent(space.roomId, EventType.RoomHistoryVisibility, {
+ history_visibility: historyVisibility,
+ }, ""),
+ () => setError(_t("Failed to update the history visibility of this space")),
+ );
+
+ const [showAdvancedSection, toggleAdvancedSection] = useStateToggle();
+
+ const canSetJoinRule = space.currentState.maySendStateEvent(EventType.RoomJoinRules, userId);
+ const canSetGuestAccess = space.currentState.maySendStateEvent(EventType.RoomGuestAccess, userId);
+ const canSetHistoryVisibility = space.currentState.maySendStateEvent(EventType.RoomHistoryVisibility, userId);
+ const canSetCanonical = space.currentState.mayClientSendStateEvent(EventType.RoomCanonicalAlias, cli);
+ const canonicalAliasEv = space.currentState.getStateEvents(EventType.RoomCanonicalAlias, "");
+
+ let advancedSection;
+ if (showAdvancedSection) {
+ advancedSection = <>
+
+ { _t("Hide advanced") }
+
+
+
+
+ { _t("Guests can join a space without having an account.") }
+
+ { _t("This may be useful for public spaces.") }
+
+ >;
+ } else {
+ advancedSection = <>
+
+ { _t("Show advanced") }
+
+ >;
+ }
+
+ return
+
{_t("Visibility")}
+
+ { error &&
{ error }
}
+
+
+
+ { _t("Decide who can view and join %(spaceName)s.", { spaceName: space.name }) }
+
+
+
+
+
+
+ { advancedSection }
+
+
{
+ setHistoryVisibility(checked ? HistoryVisibility.WorldReadable : HistoryVisibility.Shared);
+ }}
+ disabled={!canSetHistoryVisibility}
+ label={_t("Preview Space")}
+ />
+ { _t("Allow people to preview your space before they join.") }
+ { _t("Recommended for public spaces.") }
+
+
+
{_t("Address")}
+
+
;
+};
+
+export default SpaceSettingsVisibilityTab;
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index a744d8e7be..8c04a81807 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -1029,6 +1029,28 @@
"Share invite link": "Share invite link",
"Invite people": "Invite people",
"Invite with email or username": "Invite with email or username",
+ "Failed to save space settings.": "Failed to save space settings.",
+ "General": "General",
+ "Edit settings relating to your space.": "Edit settings relating to your space.",
+ "Saving...": "Saving...",
+ "Save Changes": "Save Changes",
+ "Leave Space": "Leave Space",
+ "Failed to update the visibility of this space": "Failed to update the visibility of this space",
+ "Failed to update the guest access of this space": "Failed to update the guest access of this space",
+ "Failed to update the history visibility of this space": "Failed to update the history visibility of this space",
+ "Hide advanced": "Hide advanced",
+ "Enable guest access": "Enable guest access",
+ "Guests can join a space without having an account.": "Guests can join a space without having an account.",
+ "This may be useful for public spaces.": "This may be useful for public spaces.",
+ "Show advanced": "Show advanced",
+ "Visibility": "Visibility",
+ "Decide who can view and join %(spaceName)s.": "Decide who can view and join %(spaceName)s.",
+ "anyone with the link can view and join": "anyone with the link can view and join",
+ "Invite only": "Invite only",
+ "only invited people can view and join": "only invited people can view and join",
+ "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.",
"Settings": "Settings",
"Leave space": "Leave space",
"Create new room": "Create new room",
@@ -1223,8 +1245,6 @@
"Custom theme URL": "Custom theme URL",
"Add theme": "Add theme",
"Theme": "Theme",
- "Hide advanced": "Hide advanced",
- "Show advanced": "Show advanced",
"Set the name of a font installed on your system & %(brand)s will attempt to use it.": "Set the name of a font installed on your system & %(brand)s will attempt to use it.",
"Enable experimental, compact IRC style layout": "Enable experimental, compact IRC style layout",
"Customise your appearance": "Customise your appearance",
@@ -1245,7 +1265,6 @@
"Deactivate Account": "Deactivate Account",
"Deactivate account": "Deactivate account",
"Discovery": "Discovery",
- "General": "General",
"Legal": "Legal",
"Credits": "Credits",
"For help with using %(brand)s, click here .": "For help with using %(brand)s, click here .",
@@ -1351,6 +1370,7 @@
"Upgrade this room to the recommended room version": "Upgrade this room to the recommended room version",
"this room": "this room",
"View older messages in %(roomName)s.": "View older messages in %(roomName)s.",
+ "Space information": "Space information",
"Room information": "Room information",
"Internal room ID:": "Internal room ID:",
"Room version": "Room version",
@@ -1675,14 +1695,18 @@
"Error removing address": "Error removing address",
"Main address": "Main address",
"not specified": "not specified",
+ "This space has no local addresses": "This space has no local addresses",
"This room has no local addresses": "This room has no local addresses",
"Local address": "Local address",
"Published Addresses": "Published Addresses",
- "Published addresses can be used by anyone on any server to join your room. To publish an address, it needs to be set as a local address first.": "Published addresses can be used by anyone on any server to join your room. To publish an address, it needs to be set as a local address first.",
+ "Published addresses can be used by anyone on any server to join your space.": "Published addresses can be used by anyone on any server to join your space.",
+ "Published addresses can be used by anyone on any server to join your room.": "Published addresses can be used by anyone on any server to join your room.",
+ "To publish an address, it needs to be set as a local address first.": "To publish an address, it needs to be set as a local address first.",
"Other published addresses:": "Other published addresses:",
"No other published addresses yet, add one below": "No other published addresses yet, add one below",
"New published address (e.g. #alias:server)": "New published address (e.g. #alias:server)",
"Local Addresses": "Local Addresses",
+ "Set addresses for this space so users can find this space through your homeserver (%(localDomain)s)": "Set addresses for this space so users can find this space through your homeserver (%(localDomain)s)",
"Set addresses for this room so users can find this room through your homeserver (%(localDomain)s)": "Set addresses for this room so users can find this room through your homeserver (%(localDomain)s)",
"Show more": "Show more",
"Error updating flair": "Error updating flair",
@@ -2370,14 +2394,8 @@
"Share Room Message": "Share Room Message",
"Link to selected message": "Link to selected message",
"Command Help": "Command Help",
- "Failed to save space settings.": "Failed to save space settings.",
"Space settings": "Space settings",
- "Edit settings relating to your space.": "Edit settings relating to your space.",
- "Make this space private": "Make this space private",
- "Leave Space": "Leave Space",
- "View dev tools": "View dev tools",
- "Saving...": "Saving...",
- "Save Changes": "Save Changes",
+ "Settings - %(spaceName)s": "Settings - %(spaceName)s",
"To help us prevent this in future, please send us logs .": "To help us prevent this in future, please send us logs .",
"Missing session data": "Missing session data",
"Some session data, including encrypted message keys, is missing. Sign out and sign in to fix this, restoring keys from backup.": "Some session data, including encrypted message keys, is missing. Sign out and sign in to fix this, restoring keys from backup.",
From 54241f44c2b50f559862d78b795b3b714ed30746 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 8 Jun 2021 16:40:01 +0100
Subject: [PATCH 030/100] delint
---
src/components/views/right_panel/PinnedMessagesCard.tsx | 1 -
.../views/settings/tabs/room/SecurityRoomSettingsTab.tsx | 2 +-
src/components/views/spaces/SpaceSettingsVisibilityTab.tsx | 1 -
3 files changed, 1 insertion(+), 3 deletions(-)
diff --git a/src/components/views/right_panel/PinnedMessagesCard.tsx b/src/components/views/right_panel/PinnedMessagesCard.tsx
index ad62619593..3b1db91689 100644
--- a/src/components/views/right_panel/PinnedMessagesCard.tsx
+++ b/src/components/views/right_panel/PinnedMessagesCard.tsx
@@ -16,7 +16,6 @@ limitations under the License.
import React, {useCallback, useContext, useEffect, useState} from "react";
import { Room } from "matrix-js-sdk/src/models/room";
-import { RoomState } from "matrix-js-sdk/src/models/room-state";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { EventType } from 'matrix-js-sdk/src/@types/event';
diff --git a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx
index 99f525364e..bb7e194253 100644
--- a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx
+++ b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx
@@ -121,7 +121,7 @@ export default class SecurityRoomSettingsTab extends React.Component {
+ private onEncryptionChange = () => {
Modal.createTrackedDialog('Enable encryption', '', QuestionDialog, {
title: _t('Enable encryption?'),
description: _t(
diff --git a/src/components/views/spaces/SpaceSettingsVisibilityTab.tsx b/src/components/views/spaces/SpaceSettingsVisibilityTab.tsx
index 7fc3514b2d..9eb4c6eb02 100644
--- a/src/components/views/spaces/SpaceSettingsVisibilityTab.tsx
+++ b/src/components/views/spaces/SpaceSettingsVisibilityTab.tsx
@@ -57,7 +57,6 @@ const useLocalEcho = (
};
const SpaceSettingsVisibilityTab = ({ matrixClient: cli, space }: IProps) => {
- const [busy, setBusy] = useState(false);
const [error, setError] = useState("");
const userId = cli.getUserId();
From 21fc386317d8bc41f5bdbf416c17263a4f7e055d Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Thu, 10 Jun 2021 11:40:10 +0100
Subject: [PATCH 031/100] Move over to new lexicographic string sorting
---
src/stores/SpaceStore.tsx | 65 +++++++++++++++----------
src/utils/stringOrderField.ts | 56 ++++++++++++++++++++++
test/utils/stringOrderField-test.ts | 73 +++++++++++++++++++++++++++++
3 files changed, 170 insertions(+), 24 deletions(-)
create mode 100644 src/utils/stringOrderField.ts
create mode 100644 test/utils/stringOrderField-test.ts
diff --git a/src/stores/SpaceStore.tsx b/src/stores/SpaceStore.tsx
index 5e09b617a7..47c735285c 100644
--- a/src/stores/SpaceStore.tsx
+++ b/src/stores/SpaceStore.tsx
@@ -34,6 +34,12 @@ import {setHasDiff} from "../utils/sets";
import {ISpaceSummaryEvent, ISpaceSummaryRoom} from "../components/structures/SpaceRoomDirectory";
import RoomViewStore from "./RoomViewStore";
import { arrayHasOrderChange } from "../utils/arrays";
+import {
+ ALPHABET_END,
+ ALPHABET_START,
+ averageBetweenStrings,
+ midPointsBetweenStrings,
+} from "../utils/stringOrderField";
interface IState {}
@@ -61,18 +67,19 @@ const partitionSpacesAndRooms = (arr: Room[]): [Room[], Room[]] => { // [spaces,
}, [[], []]);
};
-// For sorting space children using a validated `order`, `m.room.create`'s `origin_server_ts`, `room_id`
-export const getChildOrder = (order: string, creationTs: number, roomId: string): Array>> => {
- let validatedOrder: string = null;
-
- if (typeof order === "string" && Array.from(order).every((c: string) => {
+const validOrder = (order: string): string | null => {
+ if (typeof order === "string" && order.length <= 50 && Array.from(order).every((c: string) => {
const charCode = c.charCodeAt(0);
return charCode >= 0x20 && charCode <= 0x7E;
})) {
- validatedOrder = order;
+ return order;
}
+ return undefined;
+};
- return [validatedOrder, creationTs, roomId];
+// For sorting space children using a validated `order`, `m.room.create`'s `origin_server_ts`, `room_id`
+export const getChildOrder = (order: string, creationTs: number, roomId: string): Array>> => {
+ return [validOrder(order), creationTs, roomId];
}
const getRoomFn: FetchRoomFn = (room: Room) => {
@@ -625,8 +632,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
private getSpaceTagOrdering = (space: Room): string | undefined => {
if (this.spaceOrderLocalEchoMap.has(space.roomId)) return this.spaceOrderLocalEchoMap.get(space.roomId);
- const order = space.getAccountData(EventType.SpaceOrder)?.getContent()?.order;
- return typeof order === "string" ? order : undefined;
+ return validOrder(space.getAccountData(EventType.SpaceOrder)?.getContent()?.order);
};
private sortRootSpaces(spaces: Room[]): Room[] {
@@ -635,7 +641,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
private setRootSpaceOrder(space: Room, order: string): void {
this.spaceOrderLocalEchoMap.set(space.roomId, order);
- this.matrixClient.setRoomAccountData(space.roomId, EventType.SpaceOrder, { order });
+ this.matrixClient.setRoomAccountData(space.roomId, EventType.SpaceOrder, { order }); // TODO retrying, failure
}
public moveRootSpace(fromIndex: number, toIndex: number): void {
@@ -653,32 +659,42 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
let nextOrder: string;
if (toIndex > fromIndex) {
- prevOrder = toIndex >= 0 ? orders[toIndex] : "aaaaa";
- nextOrder = toIndex <= orders.length ? orders[toIndex + 1] : "zzzzz";
+ // moving down
+ prevOrder = orders[toIndex];
+ nextOrder = orders[toIndex + 1];
} else {
// accounts for downwards displacement of existing inhabitant of this index
- prevOrder = toIndex > 0 ? orders[toIndex - 1] : "aaaaa";
- nextOrder = toIndex < orders.length ? orders[toIndex] : "zzzzz";
+ prevOrder = toIndex > 0 ? orders[toIndex - 1] : String.fromCharCode(ALPHABET_START).repeat(5); // TODO
+ nextOrder = orders[toIndex];
}
console.log("@@ start", {fromIndex, toIndex, orders, prevOrder, nextOrder});
if (prevOrder === undefined) {
+ // to be able to move to this toIndex we will first need to insert a bunch of orders for earlier elements
const firstUndefinedIndex = orders.indexOf(undefined);
const numUndefined = orders.length - firstUndefinedIndex;
- const lastOrder = orders[firstUndefinedIndex - 1];
- console.log("@@ precalc", {firstUndefinedIndex, numUndefined, lastOrder});
- nextOrder = lastOrder + step;
- for (let i = firstUndefinedIndex; i < toIndex; i++, nextOrder += step) {
- console.log("@@ preset", {i, nextOrder});
- this.setRootSpaceOrder(this.rootSpaces[i], nextOrder);
- }
+ const lastOrder = orders[firstUndefinedIndex - 1] ?? String.fromCharCode(ALPHABET_START); // TODO
+ nextOrder = String.fromCharCode(ALPHABET_END).repeat(lastOrder.length + 1);
+ const newOrders = midPointsBetweenStrings(lastOrder, nextOrder, numUndefined);
- prevOrder = nextOrder;
- nextOrder += step;
+ if (newOrders.length === numUndefined) {
+ console.log("@@ precalc", {firstUndefinedIndex, numUndefined, lastOrder, newOrders});
+ for (let i = firstUndefinedIndex, j = 0; i <= toIndex; i++, j++) {
+ if (i === toIndex && toIndex < fromIndex) continue;
+ if (i === fromIndex) continue;
+ const newOrder = newOrders[j];
+ console.log("@@ preset", {i, j, newOrder});
+ this.setRootSpaceOrder(this.rootSpaces[i], newOrder);
+ }
+
+ prevOrder = newOrders[newOrders.length - 1];
+ } else {
+ prevOrder = nextOrder; // rebuild
+ }
}
if (prevOrder !== nextOrder) {
- const order = prevOrder + ((nextOrder - prevOrder) / 2);
+ const order = averageBetweenStrings(prevOrder, nextOrder ?? String.fromCharCode(ALPHABET_END).repeat(prevOrder.length + 1));
console.log("@@ set", {prevOrder, nextOrder, order});
this.setRootSpaceOrder(space, order);
} else {
@@ -686,6 +702,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
}
this.notifyIfOrderChanged();
+ console.log("@@ done", this.rootSpaces.map(this.getSpaceTagOrdering));
}
}
diff --git a/src/utils/stringOrderField.ts b/src/utils/stringOrderField.ts
new file mode 100644
index 0000000000..fce859ddb8
--- /dev/null
+++ b/src/utils/stringOrderField.ts
@@ -0,0 +1,56 @@
+/*
+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.
+*/
+
+export const ALPHABET_START = 0x20;
+export const ALPHABET_END = 0x7E;
+export const ALPHABET = new Array(1 + ALPHABET_END - ALPHABET_START)
+ .fill(undefined)
+ .map((_, i) => String.fromCharCode(ALPHABET_START + i))
+ .join("");
+
+export const baseToString = (base: number, alphabet = ALPHABET): string => {
+ base = Math.floor(base);
+ if (base < alphabet.length) return alphabet[base];
+ return baseToString(Math.floor(base / alphabet.length), alphabet) + alphabet[base % alphabet.length];
+};
+
+export const stringToBase = (str: string, alphabet = ALPHABET): number => {
+ let result = 0;
+ for (let i = str.length - 1, j = 0; i >= 0; i--, j++) {
+ result += (str.charCodeAt(i) - alphabet.charCodeAt(0)) * (alphabet.length ** j);
+ }
+ return result;
+};
+
+const pad = (str: string, length: number, alphabet = ALPHABET): string => str.padEnd(length, alphabet[0]);
+
+export const averageBetweenStrings = (a: string, b: string, alphabet = ALPHABET): string => {
+ const n = Math.max(a.length, b.length);
+ const aBase = stringToBase(pad(a, n, alphabet), alphabet);
+ const bBase = stringToBase(pad(b, n, alphabet), alphabet);
+ return baseToString((aBase + bBase) / 2, alphabet);
+};
+
+export const midPointsBetweenStrings = (a: string, b: string, count: number, alphabet = ALPHABET): string[] => {
+ const n = Math.max(a.length, b.length);
+ const aBase = stringToBase(pad(a, n, alphabet), alphabet);
+ const bBase = stringToBase(pad(b, n, alphabet), alphabet);
+ const step = (bBase - aBase) / (count + 1);
+ if (step < 1) {
+ return [];
+ }
+ return Array(count).fill(undefined).map((_, i) => baseToString(aBase + step + (i * step), alphabet));
+};
diff --git a/test/utils/stringOrderField-test.ts b/test/utils/stringOrderField-test.ts
new file mode 100644
index 0000000000..5b8c2f3feb
--- /dev/null
+++ b/test/utils/stringOrderField-test.ts
@@ -0,0 +1,73 @@
+/*
+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 {
+ ALPHABET,
+ averageBetweenStrings,
+ baseToString,
+ midPointsBetweenStrings,
+ stringToBase,
+} from "../../src/utils/stringOrderField";
+
+describe("stringOrderField", () => {
+ it("stringToBase", () => {
+ expect(stringToBase(" ")).toBe(0);
+ expect(stringToBase("a")).toBe(65);
+ expect(stringToBase("aa")).toBe(6240);
+ expect(stringToBase("cat")).toBe(610934);
+ expect(stringToBase("doggo")).toBe(5607022724);
+ expect(stringToBase(" ")).toEqual(0);
+ expect(stringToBase("a", "abcdefghijklmnopqrstuvwxyz")).toEqual(0);
+ expect(stringToBase("a")).toEqual(65);
+ expect(stringToBase("c", "abcdefghijklmnopqrstuvwxyz")).toEqual(2);
+ expect(stringToBase("ab")).toEqual(6241);
+ expect(stringToBase("cb", "abcdefghijklmnopqrstuvwxyz")).toEqual(53);
+ });
+
+ it("baseToString", () => {
+ expect(baseToString(10)).toBe(ALPHABET[10]);
+ expect(baseToString(10, "abcdefghijklmnopqrstuvwxyz")).toEqual("k");
+ expect(baseToString(6241)).toEqual("ab");
+ expect(baseToString(53, "abcdefghijklmnopqrstuvwxyz")).toEqual("cb");
+ expect(baseToString(1234)).toBe(",~");
+ });
+
+ it("averageBetweenStrings", () => {
+ [
+ { a: "a", b: "z", output: `m` },
+ { a: "ba", b: "z", output: `n@` },
+ { a: "z", b: "ba", output: `n@` },
+ { a: "# ", b: "$8888", output: `#[[[[` },
+ { a: "cat", b: "doggo", output: `d9>Cw` },
+ { a: "cat", b: "doggo", output: "cumqh", alphabet: "abcdefghijklmnopqrstuvwxyz" },
+ { a: "aa", b: "zz", output: "mz", alphabet: "abcdefghijklmnopqrstuvwxyz" },
+ { a: "a", b: "z", output: "m", alphabet: "abcdefghijklmnopqrstuvwxyz" },
+ { a: "AA", b: "zz", output: "^." },
+ { a: "A", b: "z", output: "]" },
+ ].forEach((c) => {
+ // assert that the output string falls lexicographically between `a` and `b`
+ expect([c.a, c.b, c.output].sort()[1]).toBe(c.output);
+ expect(averageBetweenStrings(c.a, c.b, c.alphabet)).toBe(c.output);
+ });
+ });
+
+ it("midPointsBetweenStrings", () => {
+ expect(midPointsBetweenStrings("a", "e", 3)).toStrictEqual(["b", "c", "d"]);
+ expect(midPointsBetweenStrings("a", "e", 0)).toStrictEqual([]);
+ expect(midPointsBetweenStrings("a", "e", 4)).toStrictEqual([]);
+ });
+});
+
From a4fa2779d4a40bd8a2eeeb65d96fa61cacd836e5 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Fri, 11 Jun 2021 10:33:00 +0100
Subject: [PATCH 032/100] Iterate lexicographic ordering implementation
---
src/stores/SpaceStore.tsx | 69 ++++---------------------
src/utils/arrays.ts | 17 ++++++-
src/utils/stringOrderField.ts | 79 +++++++++++++++++++++++++++++
test/utils/stringOrderField-test.ts | 76 +++++++++++++++++++++++++++
4 files changed, 180 insertions(+), 61 deletions(-)
diff --git a/src/stores/SpaceStore.tsx b/src/stores/SpaceStore.tsx
index 47c735285c..d0ec573306 100644
--- a/src/stores/SpaceStore.tsx
+++ b/src/stores/SpaceStore.tsx
@@ -34,12 +34,7 @@ import {setHasDiff} from "../utils/sets";
import {ISpaceSummaryEvent, ISpaceSummaryRoom} from "../components/structures/SpaceRoomDirectory";
import RoomViewStore from "./RoomViewStore";
import { arrayHasOrderChange } from "../utils/arrays";
-import {
- ALPHABET_END,
- ALPHABET_START,
- averageBetweenStrings,
- midPointsBetweenStrings,
-} from "../utils/stringOrderField";
+import { reorderLexicographically } from "../utils/stringOrderField";
interface IState {}
@@ -645,64 +640,18 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
}
public moveRootSpace(fromIndex: number, toIndex: number): void {
- if (
- fromIndex < 0 || toIndex < 0 ||
- fromIndex > this.rootSpaces.length || toIndex > this.rootSpaces.length ||
- fromIndex === toIndex
- ) {
- return;
- }
- const space = this.rootSpaces[fromIndex];
- const orders = this.rootSpaces.map(this.getSpaceTagOrdering);
+ const currentOrders = this.rootSpaces.map(this.getSpaceTagOrdering);
+ const changes = reorderLexicographically(currentOrders, fromIndex, toIndex);
- let prevOrder: string;
- let nextOrder: string;
+ changes.forEach(({ index, order }) => {
+ this.setRootSpaceOrder(this.rootSpaces[index], order);
+ });
- if (toIndex > fromIndex) {
- // moving down
- prevOrder = orders[toIndex];
- nextOrder = orders[toIndex + 1];
+ if (changes.length) {
+ this.notifyIfOrderChanged();
} else {
- // accounts for downwards displacement of existing inhabitant of this index
- prevOrder = toIndex > 0 ? orders[toIndex - 1] : String.fromCharCode(ALPHABET_START).repeat(5); // TODO
- nextOrder = orders[toIndex];
+ // TODO
}
- console.log("@@ start", {fromIndex, toIndex, orders, prevOrder, nextOrder});
-
- if (prevOrder === undefined) {
- // to be able to move to this toIndex we will first need to insert a bunch of orders for earlier elements
- const firstUndefinedIndex = orders.indexOf(undefined);
- const numUndefined = orders.length - firstUndefinedIndex;
- const lastOrder = orders[firstUndefinedIndex - 1] ?? String.fromCharCode(ALPHABET_START); // TODO
- nextOrder = String.fromCharCode(ALPHABET_END).repeat(lastOrder.length + 1);
- const newOrders = midPointsBetweenStrings(lastOrder, nextOrder, numUndefined);
-
- if (newOrders.length === numUndefined) {
- console.log("@@ precalc", {firstUndefinedIndex, numUndefined, lastOrder, newOrders});
- for (let i = firstUndefinedIndex, j = 0; i <= toIndex; i++, j++) {
- if (i === toIndex && toIndex < fromIndex) continue;
- if (i === fromIndex) continue;
- const newOrder = newOrders[j];
- console.log("@@ preset", {i, j, newOrder});
- this.setRootSpaceOrder(this.rootSpaces[i], newOrder);
- }
-
- prevOrder = newOrders[newOrders.length - 1];
- } else {
- prevOrder = nextOrder; // rebuild
- }
- }
-
- if (prevOrder !== nextOrder) {
- const order = averageBetweenStrings(prevOrder, nextOrder ?? String.fromCharCode(ALPHABET_END).repeat(prevOrder.length + 1));
- console.log("@@ set", {prevOrder, nextOrder, order});
- this.setRootSpaceOrder(space, order);
- } else {
- // TODO REBUILD
- }
-
- this.notifyIfOrderChanged();
- console.log("@@ done", this.rootSpaces.map(this.getSpaceTagOrdering));
}
}
diff --git a/src/utils/arrays.ts b/src/utils/arrays.ts
index e527f43c29..d319631d93 100644
--- a/src/utils/arrays.ts
+++ b/src/utils/arrays.ts
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import {percentageOf, percentageWithin} from "./numbers";
+import { percentageOf, percentageWithin } from "./numbers";
/**
* Quickly resample an array to have less/more data points. If an input which is larger
@@ -223,6 +223,21 @@ export function arrayMerge(...a: T[][]): T[] {
}, new Set()));
}
+/**
+ * Moves a single element from fromIndex to toIndex.
+ * @param list the list from which to construct the new list.
+ * @param fromIndex the index of the element to move.
+ * @param toIndex the index of where to put the element.
+ * @returns A new array with the requested value moved.
+ */
+export function reorder(list: T[], fromIndex: number, toIndex: number): T[] {
+ const result = Array.from(list);
+ const [removed] = result.splice(fromIndex, 1);
+ result.splice(toIndex, 0, removed);
+
+ return result;
+}
+
/**
* Helper functions to perform LINQ-like queries on arrays.
*/
diff --git a/src/utils/stringOrderField.ts b/src/utils/stringOrderField.ts
index fce859ddb8..ab65a46cb2 100644
--- a/src/utils/stringOrderField.ts
+++ b/src/utils/stringOrderField.ts
@@ -14,6 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
+import { reorder } from "./arrays";
+
export const ALPHABET_START = 0x20;
export const ALPHABET_END = 0x7E;
export const ALPHABET = new Array(1 + ALPHABET_END - ALPHABET_START)
@@ -54,3 +56,80 @@ export const midPointsBetweenStrings = (a: string, b: string, count: number, alp
}
return Array(count).fill(undefined).map((_, i) => baseToString(aBase + step + (i * step), alphabet));
};
+
+interface IEntry {
+ index: number;
+ order: string;
+}
+
+export const reorderLexicographically = (
+ orders: Array,
+ fromIndex: number,
+ toIndex: number,
+): IEntry[] => {
+ if (
+ fromIndex < 0 || toIndex < 0 ||
+ fromIndex > orders.length || toIndex > orders.length ||
+ fromIndex === toIndex
+ ) {
+ return [];
+ }
+
+ const ordersWithIndices: IEntry[] = orders.map((order, index) => ({ index, order }));
+ const newOrder = reorder(ordersWithIndices, fromIndex, toIndex);
+
+ const isMoveTowardsRight = toIndex > fromIndex;
+ const orderToLeftUndefined = newOrder[toIndex - 1]?.order === undefined;
+
+ let leftBoundIdx = toIndex;
+ let rightBoundIdx = toIndex;
+
+ const canDisplaceLeft = isMoveTowardsRight || orderToLeftUndefined || true; // TODO
+ if (canDisplaceLeft) {
+ const nextBase = newOrder[toIndex + 1]?.order !== undefined
+ ? stringToBase(newOrder[toIndex + 1].order)
+ : Number.MAX_VALUE;
+ for (let i = toIndex - 1, j = 0; i >= 0; i--, j++) {
+ if (newOrder[i]?.order !== undefined && nextBase - stringToBase(newOrder[i].order) > j) break;
+ leftBoundIdx = i;
+ }
+ }
+
+ const canDisplaceRight = !orderToLeftUndefined;
+ // TODO check if there is enough space on the right hand side at all,
+ // I guess find the last set order and then compare it to prevBase + $requiredGap
+ if (canDisplaceRight) {
+ const prevBase = newOrder[toIndex - 1]?.order !== undefined
+ ? stringToBase(newOrder[toIndex - 1]?.order)
+ : Number.MIN_VALUE;
+ for (let i = toIndex + 1, j = 0; i < newOrder.length; i++, j++) {
+ if (newOrder[i]?.order === undefined || stringToBase(newOrder[i].order) - prevBase > j) break; // TODO verify
+ rightBoundIdx = i;
+ }
+ }
+
+ const leftDiff = toIndex - leftBoundIdx;
+ const rightDiff = rightBoundIdx - toIndex;
+
+ if (orderToLeftUndefined || leftDiff < rightDiff) {
+ rightBoundIdx = toIndex;
+ } else {
+ leftBoundIdx = toIndex;
+ }
+
+ const prevOrder = newOrder[leftBoundIdx - 1]?.order
+ ?? String.fromCharCode(ALPHABET_START).repeat(5); // TODO
+ const nextOrder = newOrder[rightBoundIdx + 1]?.order
+ ?? String.fromCharCode(ALPHABET_END).repeat(prevOrder.length + 1); // TODO
+
+ const changes = midPointsBetweenStrings(prevOrder, nextOrder, 1 + rightBoundIdx - leftBoundIdx);
+ // TODO If we exceed maxLen then reorder EVERYTHING
+
+ console.log("@@ test", { prevOrder, nextOrder, changes, leftBoundIdx, rightBoundIdx, orders, fromIndex, toIndex, newOrder, orderToLeftUndefined });
+
+ return changes.map((order, i) => {
+ const index = newOrder[leftBoundIdx + i].index;
+
+ return { index, order };
+ });
+};
diff --git a/test/utils/stringOrderField-test.ts b/test/utils/stringOrderField-test.ts
index 5b8c2f3feb..8e3ae06b79 100644
--- a/test/utils/stringOrderField-test.ts
+++ b/test/utils/stringOrderField-test.ts
@@ -14,14 +14,36 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
+import { sortBy } from "lodash";
+
import {
ALPHABET,
averageBetweenStrings,
baseToString,
midPointsBetweenStrings,
+ reorderLexicographically,
stringToBase,
} from "../../src/utils/stringOrderField";
+const moveLexicographicallyTest = (
+ orders: Array,
+ fromIndex: number,
+ toIndex: number,
+ expectedIndices: number[],
+): void => {
+ const ops = reorderLexicographically(orders, fromIndex, toIndex);
+ expect(ops.map(o => o.index).sort()).toStrictEqual(expectedIndices.sort());
+
+ const zipped: Array<[number, string | undefined]> = orders.map((o, i) => [i, o]);
+ ops.forEach(({ index, order }) => {
+ zipped[index][1] = order;
+ });
+
+ const newOrders = sortBy(zipped, i => i[1]);
+ console.log("@@ moveLexicographicallyTest", {orders, zipped, newOrders, fromIndex, toIndex, ops});
+ expect(newOrders[toIndex][0]).toBe(fromIndex);
+};
+
describe("stringOrderField", () => {
it("stringToBase", () => {
expect(stringToBase(" ")).toBe(0);
@@ -35,6 +57,9 @@ describe("stringOrderField", () => {
expect(stringToBase("c", "abcdefghijklmnopqrstuvwxyz")).toEqual(2);
expect(stringToBase("ab")).toEqual(6241);
expect(stringToBase("cb", "abcdefghijklmnopqrstuvwxyz")).toEqual(53);
+ expect(stringToBase("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")).toEqual(4.5115969857961825e+78);
+ expect(stringToBase("~".repeat(50))).toEqual(7.694497527671333e+98);
+ // expect(typeof stringToBase("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")).toEqual("bigint");
});
it("baseToString", () => {
@@ -57,11 +82,18 @@ describe("stringOrderField", () => {
{ a: "a", b: "z", output: "m", alphabet: "abcdefghijklmnopqrstuvwxyz" },
{ a: "AA", b: "zz", output: "^." },
{ a: "A", b: "z", output: "]" },
+ {
+ a: "A".repeat(50),
+ b: "Z".repeat(50),
+ output: "M}M}M}N ba`54Qpt\\\\Z+kNA#O(9}z>@2jJm]%Y^$m<8lRzz/2[Y",
+ },
].forEach((c) => {
// assert that the output string falls lexicographically between `a` and `b`
expect([c.a, c.b, c.output].sort()[1]).toBe(c.output);
expect(averageBetweenStrings(c.a, c.b, c.alphabet)).toBe(c.output);
});
+
+ expect(averageBetweenStrings("Q#!x+k", "V6yr>L")).toBe("S\\Mu5,");
});
it("midPointsBetweenStrings", () => {
@@ -69,5 +101,49 @@ describe("stringOrderField", () => {
expect(midPointsBetweenStrings("a", "e", 0)).toStrictEqual([]);
expect(midPointsBetweenStrings("a", "e", 4)).toStrictEqual([]);
});
+
+ it("moveLexicographically left", () => {
+ moveLexicographicallyTest(["a", "c", "e", "g", "i"], 2, 1, [2]);
+ });
+
+ it("moveLexicographically right", () => {
+ moveLexicographicallyTest(["a", "c", "e", "g", "i"], 1, 2, [1]);
+ });
+
+ it("moveLexicographically all undefined", () => {
+ moveLexicographicallyTest(
+ [undefined, undefined, undefined, undefined, undefined, undefined],
+ 4,
+ 1,
+ [0, 4],
+ );
+ });
+
+ it("moveLexicographically all undefined to end", () => {
+ moveLexicographicallyTest(
+ [undefined, undefined, undefined, undefined, undefined, undefined],
+ 1,
+ 4,
+ [0, 1, 2, 3, 4],
+ );
+ });
+
+ it("moveLexicographically some undefined move left", () => {
+ moveLexicographicallyTest(
+ ["a", "c", "e", undefined, undefined, undefined],
+ 5,
+ 2,
+ [5],
+ );
+ });
+
+ it("moveLexicographically some undefined move left close", () => {
+ moveLexicographicallyTest(
+ ["a", "a", "e", undefined, undefined, undefined],
+ 5,
+ 1,
+ [1, 5],
+ );
+ });
});
From 3d4411390f0f041241768dad7524f9e2cb465983 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Fri, 11 Jun 2021 16:28:07 +0100
Subject: [PATCH 033/100] write a shedload more tests
---
src/utils/stringOrderField.ts | 46 ++++---
test/utils/stringOrderField-test.ts | 191 ++++++++++++++++++++++------
2 files changed, 181 insertions(+), 56 deletions(-)
diff --git a/src/utils/stringOrderField.ts b/src/utils/stringOrderField.ts
index ab65a46cb2..8c5d7260e7 100644
--- a/src/utils/stringOrderField.ts
+++ b/src/utils/stringOrderField.ts
@@ -39,21 +39,32 @@ export const stringToBase = (str: string, alphabet = ALPHABET): number => {
const pad = (str: string, length: number, alphabet = ALPHABET): string => str.padEnd(length, alphabet[0]);
-export const averageBetweenStrings = (a: string, b: string, alphabet = ALPHABET): string => {
- const n = Math.max(a.length, b.length);
- const aBase = stringToBase(pad(a, n, alphabet), alphabet);
- const bBase = stringToBase(pad(b, n, alphabet), alphabet);
- return baseToString((aBase + bBase) / 2, alphabet);
-};
-
-export const midPointsBetweenStrings = (a: string, b: string, count: number, alphabet = ALPHABET): string[] => {
- const n = Math.max(a.length, b.length);
- const aBase = stringToBase(pad(a, n, alphabet), alphabet);
- const bBase = stringToBase(pad(b, n, alphabet), alphabet);
- const step = (bBase - aBase) / (count + 1);
- if (step < 1) {
+export const midPointsBetweenStrings = (
+ a: string,
+ b: string,
+ count: number,
+ maxLen: number,
+ alphabet = ALPHABET,
+): string[] => {
+ const n = Math.min(maxLen, Math.max(a.length, b.length));
+ const aPadded = pad(a, n, alphabet);
+ const bPadded = pad(b, n, alphabet);
+ const aBase = stringToBase(aPadded, alphabet);
+ const bBase = stringToBase(bPadded, alphabet);
+ if (bBase - aBase - 1 < count) {
+ if (n < maxLen) {
+ // this recurses once at most due to the new limit of n+1
+ return midPointsBetweenStrings(
+ pad(aPadded, n + 1, alphabet),
+ pad(bPadded, n + 1, alphabet),
+ count,
+ n + 1,
+ alphabet,
+ );
+ }
return [];
}
+ const step = (bBase - aBase) / (count + 1);
return Array(count).fill(undefined).map((_, i) => baseToString(aBase + step + (i * step), alphabet));
};
@@ -66,6 +77,7 @@ export const reorderLexicographically = (
orders: Array,
fromIndex: number,
toIndex: number,
+ maxLen = 50,
): IEntry[] => {
if (
fromIndex < 0 || toIndex < 0 ||
@@ -89,7 +101,7 @@ export const reorderLexicographically = (
const nextBase = newOrder[toIndex + 1]?.order !== undefined
? stringToBase(newOrder[toIndex + 1].order)
: Number.MAX_VALUE;
- for (let i = toIndex - 1, j = 0; i >= 0; i--, j++) {
+ for (let i = toIndex - 1, j = 1; i >= 0; i--, j++) {
if (newOrder[i]?.order !== undefined && nextBase - stringToBase(newOrder[i].order) > j) break;
leftBoundIdx = i;
}
@@ -102,7 +114,7 @@ export const reorderLexicographically = (
const prevBase = newOrder[toIndex - 1]?.order !== undefined
? stringToBase(newOrder[toIndex - 1]?.order)
: Number.MIN_VALUE;
- for (let i = toIndex + 1, j = 0; i < newOrder.length; i++, j++) {
+ for (let i = toIndex + 1, j = 1; i < newOrder.length; i++, j++) {
if (newOrder[i]?.order === undefined || stringToBase(newOrder[i].order) - prevBase > j) break; // TODO verify
rightBoundIdx = i;
}
@@ -122,10 +134,10 @@ export const reorderLexicographically = (
const nextOrder = newOrder[rightBoundIdx + 1]?.order
?? String.fromCharCode(ALPHABET_END).repeat(prevOrder.length + 1); // TODO
- const changes = midPointsBetweenStrings(prevOrder, nextOrder, 1 + rightBoundIdx - leftBoundIdx);
+ const changes = midPointsBetweenStrings(prevOrder, nextOrder, 1 + rightBoundIdx - leftBoundIdx, maxLen);
// TODO If we exceed maxLen then reorder EVERYTHING
- console.log("@@ test", { prevOrder, nextOrder, changes, leftBoundIdx, rightBoundIdx, orders, fromIndex, toIndex, newOrder, orderToLeftUndefined });
+ console.log("@@ test", { prevOrder, nextOrder, changes, leftBoundIdx, rightBoundIdx, orders, fromIndex, toIndex, newOrder, orderToLeftUndefined, leftDiff, rightDiff });
return changes.map((order, i) => {
const index = newOrder[leftBoundIdx + i].index;
diff --git a/test/utils/stringOrderField-test.ts b/test/utils/stringOrderField-test.ts
index 8e3ae06b79..335db028a8 100644
--- a/test/utils/stringOrderField-test.ts
+++ b/test/utils/stringOrderField-test.ts
@@ -18,7 +18,6 @@ import { sortBy } from "lodash";
import {
ALPHABET,
- averageBetweenStrings,
baseToString,
midPointsBetweenStrings,
reorderLexicographically,
@@ -29,10 +28,10 @@ const moveLexicographicallyTest = (
orders: Array,
fromIndex: number,
toIndex: number,
- expectedIndices: number[],
+ expectedChanges: number,
+ maxLength?: number,
): void => {
- const ops = reorderLexicographically(orders, fromIndex, toIndex);
- expect(ops.map(o => o.index).sort()).toStrictEqual(expectedIndices.sort());
+ const ops = reorderLexicographically(orders, fromIndex, toIndex, maxLength);
const zipped: Array<[number, string | undefined]> = orders.map((o, i) => [i, o]);
ops.forEach(({ index, order }) => {
@@ -42,6 +41,7 @@ const moveLexicographicallyTest = (
const newOrders = sortBy(zipped, i => i[1]);
console.log("@@ moveLexicographicallyTest", {orders, zipped, newOrders, fromIndex, toIndex, ops});
expect(newOrders[toIndex][0]).toBe(fromIndex);
+ expect(ops).toHaveLength(expectedChanges);
};
describe("stringOrderField", () => {
@@ -70,44 +70,20 @@ describe("stringOrderField", () => {
expect(baseToString(1234)).toBe(",~");
});
- it("averageBetweenStrings", () => {
- [
- { a: "a", b: "z", output: `m` },
- { a: "ba", b: "z", output: `n@` },
- { a: "z", b: "ba", output: `n@` },
- { a: "# ", b: "$8888", output: `#[[[[` },
- { a: "cat", b: "doggo", output: `d9>Cw` },
- { a: "cat", b: "doggo", output: "cumqh", alphabet: "abcdefghijklmnopqrstuvwxyz" },
- { a: "aa", b: "zz", output: "mz", alphabet: "abcdefghijklmnopqrstuvwxyz" },
- { a: "a", b: "z", output: "m", alphabet: "abcdefghijklmnopqrstuvwxyz" },
- { a: "AA", b: "zz", output: "^." },
- { a: "A", b: "z", output: "]" },
- {
- a: "A".repeat(50),
- b: "Z".repeat(50),
- output: "M}M}M}N ba`54Qpt\\\\Z+kNA#O(9}z>@2jJm]%Y^$m<8lRzz/2[Y",
- },
- ].forEach((c) => {
- // assert that the output string falls lexicographically between `a` and `b`
- expect([c.a, c.b, c.output].sort()[1]).toBe(c.output);
- expect(averageBetweenStrings(c.a, c.b, c.alphabet)).toBe(c.output);
- });
-
- expect(averageBetweenStrings("Q#!x+k", "V6yr>L")).toBe("S\\Mu5,");
- });
-
it("midPointsBetweenStrings", () => {
- expect(midPointsBetweenStrings("a", "e", 3)).toStrictEqual(["b", "c", "d"]);
- expect(midPointsBetweenStrings("a", "e", 0)).toStrictEqual([]);
- expect(midPointsBetweenStrings("a", "e", 4)).toStrictEqual([]);
+ const midpoints = ["a", ...midPointsBetweenStrings("a", "e", 3, 1), "e"].sort();
+ expect(midpoints[0]).toBe("a");
+ expect(midpoints[4]).toBe("e");
+ expect(midPointsBetweenStrings("a", "e", 0, 1)).toStrictEqual([]);
+ expect(midPointsBetweenStrings("a", "e", 4, 1)).toStrictEqual([]);
});
it("moveLexicographically left", () => {
- moveLexicographicallyTest(["a", "c", "e", "g", "i"], 2, 1, [2]);
+ moveLexicographicallyTest(["a", "c", "e", "g", "i"], 2, 1, 1);
});
it("moveLexicographically right", () => {
- moveLexicographicallyTest(["a", "c", "e", "g", "i"], 1, 2, [1]);
+ moveLexicographicallyTest(["a", "c", "e", "g", "i"], 1, 2, 1);
});
it("moveLexicographically all undefined", () => {
@@ -115,7 +91,7 @@ describe("stringOrderField", () => {
[undefined, undefined, undefined, undefined, undefined, undefined],
4,
1,
- [0, 4],
+ 2,
);
});
@@ -124,7 +100,7 @@ describe("stringOrderField", () => {
[undefined, undefined, undefined, undefined, undefined, undefined],
1,
4,
- [0, 1, 2, 3, 4],
+ 5,
);
});
@@ -133,7 +109,7 @@ describe("stringOrderField", () => {
["a", "c", "e", undefined, undefined, undefined],
5,
2,
- [5],
+ 1,
);
});
@@ -142,7 +118,144 @@ describe("stringOrderField", () => {
["a", "a", "e", undefined, undefined, undefined],
5,
1,
- [1, 5],
+ 2,
+ );
+ });
+
+ it("test A moving to the start when all is undefined", () => {
+ moveLexicographicallyTest(
+ [undefined, undefined, undefined, undefined],
+ 2,
+ 0,
+ 1,
+ );
+ });
+
+ it("test B moving to the end when all is undefined", () => {
+ moveLexicographicallyTest(
+ [undefined, undefined, undefined, undefined],
+ 1,
+ 3,
+ 4,
+ );
+ });
+
+ it("test C moving left when all is undefined", () => {
+ moveLexicographicallyTest(
+ [undefined, undefined, undefined, undefined, undefined, undefined],
+ 4,
+ 1,
+ 2,
+ );
+ });
+
+ it("test D moving right when all is undefined", () => {
+ moveLexicographicallyTest(
+ [undefined, undefined, undefined, undefined],
+ 1,
+ 2,
+ 3,
+ );
+ });
+
+ it("test E moving more right when all is undefined", () => {
+ moveLexicographicallyTest(
+ [undefined, undefined, undefined, undefined, undefined, /**/ undefined, undefined],
+ 1,
+ 4,
+ 5,
+ );
+ });
+
+ it("test F moving left when right is undefined", () => {
+ moveLexicographicallyTest(
+ ["20", undefined, undefined, undefined, undefined, undefined],
+ 4,
+ 2,
+ 2,
+ );
+ });
+
+ it("test G moving right when right is undefined", () => {
+ moveLexicographicallyTest(
+ ["50", undefined, undefined, undefined, undefined, /**/ undefined, undefined],
+ 1,
+ 4,
+ 4,
+ );
+ });
+
+ it("test H moving left when right is defined", () => {
+ moveLexicographicallyTest(
+ ["10", "20", "30", "40", undefined, undefined],
+ 3,
+ 1,
+ 1,
+ );
+ });
+
+ it("test I moving right when right is defined", () => {
+ moveLexicographicallyTest(
+ ["10", "20", "30", "40", "50", undefined],
+ 1,
+ 3,
+ 1,
+ );
+ });
+
+ it("test J moving left when all is defined", () => {
+ moveLexicographicallyTest(
+ ["11", "13", "15", "17", "19"],
+ 2,
+ 1,
+ 1,
+ );
+ });
+
+ it("test K moving right when all is defined", () => {
+ moveLexicographicallyTest(
+ ["11", "13", "15", "17", "19"],
+ 1,
+ 2,
+ 1,
+ );
+ });
+
+ it("test L moving left into no left space", () => {
+ moveLexicographicallyTest(
+ ["11", "12", "13", "14", "19"],
+ 3,
+ 1,
+ 2,
+ 2,
+ );
+ });
+
+ it("test M moving right into no right space", () => {
+ moveLexicographicallyTest(
+ ["15", "16", "17", "18", "19"],
+ 1,
+ 3,
+ 2,
+ 2,
+ );
+ });
+
+ it("test N moving right into no left space", () => {
+ moveLexicographicallyTest(
+ ["11", "12", "13", "14", "15", "16", undefined],
+ 1,
+ 3,
+ 3,
+ );
+ });
+
+ it("test O moving left into no right space", () => {
+ moveLexicographicallyTest(
+ ["15", "16", "17", "18", "19"],
+ 4,
+ 3,
+ 2,
);
});
});
From bd71bcca5a83380796ee63400c546edd43499fd5 Mon Sep 17 00:00:00 2001
From: Steffen Kolmer
Date: Fri, 11 Jun 2021 17:38:16 +0200
Subject: [PATCH 034/100] Allow modal widget buttons to be disabled when the
modal opens
---
src/components/views/dialogs/ModalWidgetDialog.tsx | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/components/views/dialogs/ModalWidgetDialog.tsx b/src/components/views/dialogs/ModalWidgetDialog.tsx
index 0c474b160c..df2ed6b335 100644
--- a/src/components/views/dialogs/ModalWidgetDialog.tsx
+++ b/src/components/views/dialogs/ModalWidgetDialog.tsx
@@ -63,7 +63,8 @@ export default class ModalWidgetDialog extends React.PureComponent = React.createRef();
state: IState = {
- disabledButtonIds: [],
+ disabledButtonIds: (this.props.widgetDefinition.buttons || []).filter(b => b.disabled)
+ .map(b => b.id),
};
constructor(props) {
From 5ae1b1444f4927f923356709d6eadd8c8c30604b Mon Sep 17 00:00:00 2001
From: Aaron Raimist
Date: Fri, 11 Jun 2021 23:24:05 -0500
Subject: [PATCH 035/100] Open local addresses section by default when there
are no existing local addresses
Signed-off-by: Aaron Raimist
---
src/components/views/room_settings/AliasSettings.js | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/components/views/room_settings/AliasSettings.js b/src/components/views/room_settings/AliasSettings.js
index 80e0099ab3..e493cba96f 100644
--- a/src/components/views/room_settings/AliasSettings.js
+++ b/src/components/views/room_settings/AliasSettings.js
@@ -134,6 +134,10 @@ export default class AliasSettings extends React.Component {
}
}
this.setState({ localAliases });
+
+ if (localAliases.length === 0) {
+ this.setState({ detailsOpen: true });
+ }
} finally {
this.setState({ localAliasesLoading: false });
}
@@ -388,7 +392,7 @@ export default class AliasSettings extends React.Component {
/>
{_t("Local Addresses")}
{_t("Set addresses for this room so users can find this room through your homeserver (%(localDomain)s)", {localDomain})}
-
+
{ this.state.detailsOpen ? _t('Show less') : _t("Show more")}
{localAliasesList}
From b8458c0ae323f80525211bdc048a8f01f5f36763 Mon Sep 17 00:00:00 2001
From: Aaron Raimist
Date: Fri, 11 Jun 2021 23:58:16 -0500
Subject: [PATCH 036/100] fix test maybe
Signed-off-by: Aaron Raimist
---
test/end-to-end-tests/src/usecases/room-settings.js | 2 --
1 file changed, 2 deletions(-)
diff --git a/test/end-to-end-tests/src/usecases/room-settings.js b/test/end-to-end-tests/src/usecases/room-settings.js
index abd4488db2..654c461296 100644
--- a/test/end-to-end-tests/src/usecases/room-settings.js
+++ b/test/end-to-end-tests/src/usecases/room-settings.js
@@ -140,8 +140,6 @@ async function changeRoomSettings(session, settings) {
if (settings.alias) {
session.log.step(`sets alias to ${settings.alias}`);
- const summary = await session.query(".mx_RoomSettingsDialog .mx_AliasSettings summary");
- await summary.click();
const aliasField = await session.query(".mx_RoomSettingsDialog .mx_AliasSettings details input[type=text]");
await session.replaceInputText(aliasField, settings.alias.substring(1, settings.alias.lastIndexOf(":")));
const addButton = await session.query(".mx_RoomSettingsDialog .mx_AliasSettings details .mx_AccessibleButton");
From 4af2675e235ec1cee07d4a1c760653b94274b58c Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Mon, 14 Jun 2021 14:37:05 +0100
Subject: [PATCH 037/100] stash bigint support
---
src/utils/stringOrderField.ts | 77 +++++++++------
test/utils/stringOrderField-test.ts | 147 +++++++++++++++++++++-------
2 files changed, 160 insertions(+), 64 deletions(-)
diff --git a/src/utils/stringOrderField.ts b/src/utils/stringOrderField.ts
index 8c5d7260e7..d837dd4cbf 100644
--- a/src/utils/stringOrderField.ts
+++ b/src/utils/stringOrderField.ts
@@ -23,16 +23,17 @@ export const ALPHABET = new Array(1 + ALPHABET_END - ALPHABET_START)
.map((_, i) => String.fromCharCode(ALPHABET_START + i))
.join("");
-export const baseToString = (base: number, alphabet = ALPHABET): string => {
- base = Math.floor(base);
- if (base < alphabet.length) return alphabet[base];
- return baseToString(Math.floor(base / alphabet.length), alphabet) + alphabet[base % alphabet.length];
+export const baseToString = (base: bigint, alphabet = ALPHABET): string => {
+ const len = BigInt(alphabet.length);
+ if (base < len) return alphabet[Number(base)];
+ return baseToString(base / len, alphabet) + alphabet[Number(base % len)];
};
-export const stringToBase = (str: string, alphabet = ALPHABET): number => {
- let result = 0;
- for (let i = str.length - 1, j = 0; i >= 0; i--, j++) {
- result += (str.charCodeAt(i) - alphabet.charCodeAt(0)) * (alphabet.length ** j);
+export const stringToBase = (str: string, alphabet = ALPHABET): bigint => {
+ let result = BigInt(0);
+ const len = BigInt(alphabet.length);
+ for (let i = str.length - 1, j = BigInt(0); i >= 0; i--, j++) {
+ result += BigInt(str.charCodeAt(i) - alphabet.charCodeAt(0)) * (len ** j);
}
return result;
};
@@ -51,7 +52,7 @@ export const midPointsBetweenStrings = (
const bPadded = pad(b, n, alphabet);
const aBase = stringToBase(aPadded, alphabet);
const bBase = stringToBase(bPadded, alphabet);
- if (bBase - aBase - 1 < count) {
+ if (bBase - aBase - BigInt(1) < count) {
if (n < maxLen) {
// this recurses once at most due to the new limit of n+1
return midPointsBetweenStrings(
@@ -64,8 +65,9 @@ export const midPointsBetweenStrings = (
}
return [];
}
- const step = (bBase - aBase) / (count + 1);
- return Array(count).fill(undefined).map((_, i) => baseToString(aBase + step + (i * step), alphabet));
+ const step = (bBase - aBase) / BigInt(count + 1);
+ const start = BigInt(aBase + step);
+ return Array(count).fill(undefined).map((_, i) => baseToString(start + (BigInt(i) * step), alphabet));
};
interface IEntry {
@@ -79,6 +81,7 @@ export const reorderLexicographically = (
toIndex: number,
maxLen = 50,
): IEntry[] => {
+ // sanity check inputs
if (
fromIndex < 0 || toIndex < 0 ||
fromIndex > orders.length || toIndex > orders.length ||
@@ -87,41 +90,56 @@ export const reorderLexicographically = (
return [];
}
+ // zip orders with their indices to simplify later index wrangling
const ordersWithIndices: IEntry[] = orders.map((order, index) => ({ index, order }));
+ // apply the fundamental order update to the zipped array
const newOrder = reorder(ordersWithIndices, fromIndex, toIndex);
- const isMoveTowardsRight = toIndex > fromIndex;
const orderToLeftUndefined = newOrder[toIndex - 1]?.order === undefined;
let leftBoundIdx = toIndex;
let rightBoundIdx = toIndex;
- const canDisplaceLeft = isMoveTowardsRight || orderToLeftUndefined || true; // TODO
- if (canDisplaceLeft) {
- const nextBase = newOrder[toIndex + 1]?.order !== undefined
- ? stringToBase(newOrder[toIndex + 1].order)
- : Number.MAX_VALUE;
- for (let i = toIndex - 1, j = 1; i >= 0; i--, j++) {
- if (newOrder[i]?.order !== undefined && nextBase - stringToBase(newOrder[i].order) > j) break;
- leftBoundIdx = i;
- }
+ let canMoveLeft = true;
+ const nextBase = newOrder[toIndex + 1]?.order !== undefined
+ ? stringToBase(newOrder[toIndex + 1].order)
+ : BigInt(Number.MAX_VALUE);
+
+ for (let i = toIndex - 1, j = 1; i >= 0; i--, j++) {
+ if (newOrder[i]?.order !== undefined && nextBase - stringToBase(newOrder[i].order) > j) break;
+ leftBoundIdx = i;
+ }
+
+ if (leftBoundIdx === 0 &&
+ newOrder[0].order !== undefined &&
+ nextBase - stringToBase(newOrder[0].order) < toIndex
+ ) {
+ canMoveLeft = false;
}
const canDisplaceRight = !orderToLeftUndefined;
- // TODO check if there is enough space on the right hand side at all,
- // I guess find the last set order and then compare it to prevBase + $requiredGap
+ let canMoveRight = canDisplaceRight;
if (canDisplaceRight) {
const prevBase = newOrder[toIndex - 1]?.order !== undefined
? stringToBase(newOrder[toIndex - 1]?.order)
- : Number.MIN_VALUE;
+ : BigInt(Number.MIN_VALUE);
+
for (let i = toIndex + 1, j = 1; i < newOrder.length; i++, j++) {
if (newOrder[i]?.order === undefined || stringToBase(newOrder[i].order) - prevBase > j) break; // TODO verify
rightBoundIdx = i;
}
+
+ if (rightBoundIdx === newOrder.length - 1 &&
+ (newOrder[rightBoundIdx]
+ ? stringToBase(newOrder[rightBoundIdx].order)
+ : BigInt(Number.MAX_VALUE)) - prevBase <= (rightBoundIdx - toIndex)
+ ) {
+ canMoveRight = false;
+ }
}
- const leftDiff = toIndex - leftBoundIdx;
- const rightDiff = rightBoundIdx - toIndex;
+ const leftDiff = canMoveLeft ? toIndex - leftBoundIdx : Number.MAX_SAFE_INTEGER;
+ const rightDiff = canMoveRight ? rightBoundIdx - toIndex : Number.MAX_SAFE_INTEGER;
if (orderToLeftUndefined || leftDiff < rightDiff) {
rightBoundIdx = toIndex;
@@ -130,14 +148,13 @@ export const reorderLexicographically = (
}
const prevOrder = newOrder[leftBoundIdx - 1]?.order
- ?? String.fromCharCode(ALPHABET_START).repeat(5); // TODO
+ ?? String.fromCharCode(ALPHABET_START).repeat(5);
const nextOrder = newOrder[rightBoundIdx + 1]?.order
- ?? String.fromCharCode(ALPHABET_END).repeat(prevOrder.length + 1); // TODO
+ ?? String.fromCharCode(ALPHABET_END).repeat(prevOrder.length);
const changes = midPointsBetweenStrings(prevOrder, nextOrder, 1 + rightBoundIdx - leftBoundIdx, maxLen);
- // TODO If we exceed maxLen then reorder EVERYTHING
- console.log("@@ test", { prevOrder, nextOrder, changes, leftBoundIdx, rightBoundIdx, orders, fromIndex, toIndex, newOrder, orderToLeftUndefined, leftDiff, rightDiff });
+ console.log("@@ test", { canMoveLeft, canMoveRight, prevOrder, nextOrder, changes, leftBoundIdx, rightBoundIdx, orders, fromIndex, toIndex, newOrder, orderToLeftUndefined, leftDiff, rightDiff });
return changes.map((order, i) => {
const index = newOrder[leftBoundIdx + i].index;
diff --git a/test/utils/stringOrderField-test.ts b/test/utils/stringOrderField-test.ts
index 335db028a8..9f92774acb 100644
--- a/test/utils/stringOrderField-test.ts
+++ b/test/utils/stringOrderField-test.ts
@@ -46,28 +46,28 @@ const moveLexicographicallyTest = (
describe("stringOrderField", () => {
it("stringToBase", () => {
- expect(stringToBase(" ")).toBe(0);
- expect(stringToBase("a")).toBe(65);
- expect(stringToBase("aa")).toBe(6240);
- expect(stringToBase("cat")).toBe(610934);
- expect(stringToBase("doggo")).toBe(5607022724);
- expect(stringToBase(" ")).toEqual(0);
- expect(stringToBase("a", "abcdefghijklmnopqrstuvwxyz")).toEqual(0);
- expect(stringToBase("a")).toEqual(65);
- expect(stringToBase("c", "abcdefghijklmnopqrstuvwxyz")).toEqual(2);
- expect(stringToBase("ab")).toEqual(6241);
- expect(stringToBase("cb", "abcdefghijklmnopqrstuvwxyz")).toEqual(53);
- expect(stringToBase("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")).toEqual(4.5115969857961825e+78);
- expect(stringToBase("~".repeat(50))).toEqual(7.694497527671333e+98);
+ expect(Number(stringToBase(" "))).toBe(0);
+ expect(Number(stringToBase("a"))).toBe(65);
+ expect(Number(stringToBase("aa"))).toBe(6240);
+ expect(Number(stringToBase("cat"))).toBe(610934);
+ expect(Number(stringToBase("doggo"))).toBe(5607022724);
+ expect(Number(stringToBase(" "))).toEqual(0);
+ expect(Number(stringToBase("a", "abcdefghijklmnopqrstuvwxyz"))).toEqual(0);
+ expect(Number(stringToBase("a"))).toEqual(65);
+ expect(Number(stringToBase("c", "abcdefghijklmnopqrstuvwxyz"))).toEqual(2);
+ expect(Number(stringToBase("ab"))).toEqual(6241);
+ expect(Number(stringToBase("cb", "abcdefghijklmnopqrstuvwxyz"))).toEqual(53);
+ expect(Number(stringToBase("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"))).toEqual(4.511596985796182e+78);
+ expect(Number(stringToBase("~".repeat(50)))).toEqual(7.694497527671333e+98);
// expect(typeof stringToBase("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")).toEqual("bigint");
});
it("baseToString", () => {
- expect(baseToString(10)).toBe(ALPHABET[10]);
- expect(baseToString(10, "abcdefghijklmnopqrstuvwxyz")).toEqual("k");
- expect(baseToString(6241)).toEqual("ab");
- expect(baseToString(53, "abcdefghijklmnopqrstuvwxyz")).toEqual("cb");
- expect(baseToString(1234)).toBe(",~");
+ expect(baseToString(BigInt(10))).toBe(ALPHABET[10]);
+ expect(baseToString(BigInt(10), "abcdefghijklmnopqrstuvwxyz")).toEqual("k");
+ expect(baseToString(BigInt(6241))).toEqual("ab");
+ expect(baseToString(BigInt(53), "abcdefghijklmnopqrstuvwxyz")).toEqual("cb");
+ expect(baseToString(BigInt(1234))).toBe(",~");
});
it("midPointsBetweenStrings", () => {
@@ -122,7 +122,7 @@ describe("stringOrderField", () => {
);
});
- it("test A moving to the start when all is undefined", () => {
+ it("test moving to the start when all is undefined", () => {
moveLexicographicallyTest(
[undefined, undefined, undefined, undefined],
2,
@@ -131,7 +131,7 @@ describe("stringOrderField", () => {
);
});
- it("test B moving to the end when all is undefined", () => {
+ it("test moving to the end when all is undefined", () => {
moveLexicographicallyTest(
[undefined, undefined, undefined, undefined],
1,
@@ -140,7 +140,7 @@ describe("stringOrderField", () => {
);
});
- it("test C moving left when all is undefined", () => {
+ it("test moving left when all is undefined", () => {
moveLexicographicallyTest(
[undefined, undefined, undefined, undefined, undefined, undefined],
4,
@@ -149,7 +149,7 @@ describe("stringOrderField", () => {
);
});
- it("test D moving right when all is undefined", () => {
+ it("test moving right when all is undefined", () => {
moveLexicographicallyTest(
[undefined, undefined, undefined, undefined],
1,
@@ -158,7 +158,7 @@ describe("stringOrderField", () => {
);
});
- it("test E moving more right when all is undefined", () => {
+ it("test moving more right when all is undefined", () => {
moveLexicographicallyTest(
[undefined, undefined, undefined, undefined, undefined, /**/ undefined, undefined],
1,
@@ -167,7 +167,7 @@ describe("stringOrderField", () => {
);
});
- it("test F moving left when right is undefined", () => {
+ it("test moving left when right is undefined", () => {
moveLexicographicallyTest(
["20", undefined, undefined, undefined, undefined, undefined],
4,
@@ -176,7 +176,7 @@ describe("stringOrderField", () => {
);
});
- it("test G moving right when right is undefined", () => {
+ it("test moving right when right is undefined", () => {
moveLexicographicallyTest(
["50", undefined, undefined, undefined, undefined, /**/ undefined, undefined],
1,
@@ -185,7 +185,7 @@ describe("stringOrderField", () => {
);
});
- it("test H moving left when right is defined", () => {
+ it("test moving left when right is defined", () => {
moveLexicographicallyTest(
["10", "20", "30", "40", undefined, undefined],
3,
@@ -194,7 +194,7 @@ describe("stringOrderField", () => {
);
});
- it("test I moving right when right is defined", () => {
+ it("test moving right when right is defined", () => {
moveLexicographicallyTest(
["10", "20", "30", "40", "50", undefined],
1,
@@ -203,7 +203,7 @@ describe("stringOrderField", () => {
);
});
- it("test J moving left when all is defined", () => {
+ it("test moving left when all is defined", () => {
moveLexicographicallyTest(
["11", "13", "15", "17", "19"],
2,
@@ -212,7 +212,7 @@ describe("stringOrderField", () => {
);
});
- it("test K moving right when all is defined", () => {
+ it("test moving right when all is defined", () => {
moveLexicographicallyTest(
["11", "13", "15", "17", "19"],
1,
@@ -221,7 +221,7 @@ describe("stringOrderField", () => {
);
});
- it("test L moving left into no left space", () => {
+ it.skip("test moving left into no left space", () => {
moveLexicographicallyTest(
["11", "12", "13", "14", "19"],
3,
@@ -231,17 +231,17 @@ describe("stringOrderField", () => {
);
});
- it("test M moving right into no right space", () => {
+ it("test moving right into no right space", () => {
moveLexicographicallyTest(
["15", "16", "17", "18", "19"],
1,
3,
- 2,
+ 3,
2,
);
});
- it("test N moving right into no left space", () => {
+ it("test moving right into no left space", () => {
moveLexicographicallyTest(
["11", "12", "13", "14", "15", "16", undefined],
1,
@@ -250,13 +250,92 @@ describe("stringOrderField", () => {
);
});
- it("test O moving left into no right space", () => {
+ it("test moving left into no right space", () => {
moveLexicographicallyTest(
["15", "16", "17", "18", "19"],
4,
3,
+ 4,
2,
);
});
+
+ it("test moving left into no left space", () => {
+ moveLexicographicallyTest(
+ [
+ ALPHABET.charAt(0),
+ ALPHABET.charAt(1),
+ ALPHABET.charAt(2),
+ ALPHABET.charAt(3),
+ ALPHABET.charAt(4),
+ ALPHABET.charAt(5),
+ ],
+ 5,
+ 1,
+ 5,
+ 1,
+ );
+ });
+
+ it("test moving right into no right space", () => {
+ moveLexicographicallyTest(
+ [
+ ALPHABET.charAt(ALPHABET.length - 5),
+ ALPHABET.charAt(ALPHABET.length - 4),
+ ALPHABET.charAt(ALPHABET.length - 3),
+ ALPHABET.charAt(ALPHABET.length - 2),
+ ALPHABET.charAt(ALPHABET.length - 1),
+ ],
+ 1,
+ 3,
+ 3,
+ 1,
+ );
+ });
+
+ it("test moving right into no left space", () => {
+ moveLexicographicallyTest(
+ ["0", "1", "2", "3", "4", "5"],
+ 1,
+ 3,
+ 3,
+ 1,
+ );
+ });
+
+ it("test moving left into no right space", () => {
+ moveLexicographicallyTest(
+ [
+ ALPHABET.charAt(ALPHABET.length - 5),
+ ALPHABET.charAt(ALPHABET.length - 4),
+ ALPHABET.charAt(ALPHABET.length - 3),
+ ALPHABET.charAt(ALPHABET.length - 2),
+ ALPHABET.charAt(ALPHABET.length - 1),
+ ],
+ 4,
+ 3,
+ 4,
+ 1,
+ );
+ });
+
+ const prev = (str: string) => baseToString(stringToBase(str) - BigInt(1));
+ const next = (str: string) => baseToString(stringToBase(str) + BigInt(1));
+
+ it("baseN calculation is correctly consecutive", () => {
+ const str = "this-is-a-test";
+ expect(next(prev(str))).toBe(str);
+ });
+
+ it("rolls over sanely", () => {
+ const maxSpaceValue = "~".repeat(50);
+ const fiftyFirstChar = "!" + " ".repeat(50);
+ expect(next(maxSpaceValue)).toBe(fiftyFirstChar);
+ expect(prev(fiftyFirstChar)).toBe(maxSpaceValue);
+ expect(stringToBase(ALPHABET[0])).toEqual(BigInt(0));
+ expect(stringToBase(ALPHABET[1])).toEqual(BigInt(1));
+ expect(ALPHABET[ALPHABET.length - 1]).toBe("~");
+ expect(ALPHABET[0]).toBe(" ");
+ });
});
From 8fd72fcf795659d5d40e25e066dec0e68ad7efbb Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Mon, 14 Jun 2021 21:28:32 +0100
Subject: [PATCH 038/100] Iterate algorithm, base it on new js-sdk string lib
---
src/utils/stringOrderField.ts | 77 ++++++++----------
test/utils/stringOrderField-test.ts | 121 +++++++++++++++-------------
2 files changed, 98 insertions(+), 100 deletions(-)
diff --git a/src/utils/stringOrderField.ts b/src/utils/stringOrderField.ts
index d837dd4cbf..e09f7fbea4 100644
--- a/src/utils/stringOrderField.ts
+++ b/src/utils/stringOrderField.ts
@@ -14,6 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
+import { alphabetPad, baseToString, stringToBase } from "matrix-js-sdk/src/utils";
+
import { reorder } from "./arrays";
export const ALPHABET_START = 0x20;
@@ -23,23 +25,6 @@ export const ALPHABET = new Array(1 + ALPHABET_END - ALPHABET_START)
.map((_, i) => String.fromCharCode(ALPHABET_START + i))
.join("");
-export const baseToString = (base: bigint, alphabet = ALPHABET): string => {
- const len = BigInt(alphabet.length);
- if (base < len) return alphabet[Number(base)];
- return baseToString(base / len, alphabet) + alphabet[Number(base % len)];
-};
-
-export const stringToBase = (str: string, alphabet = ALPHABET): bigint => {
- let result = BigInt(0);
- const len = BigInt(alphabet.length);
- for (let i = str.length - 1, j = BigInt(0); i >= 0; i--, j++) {
- result += BigInt(str.charCodeAt(i) - alphabet.charCodeAt(0)) * (len ** j);
- }
- return result;
-};
-
-const pad = (str: string, length: number, alphabet = ALPHABET): string => str.padEnd(length, alphabet[0]);
-
export const midPointsBetweenStrings = (
a: string,
b: string,
@@ -47,26 +32,28 @@ export const midPointsBetweenStrings = (
maxLen: number,
alphabet = ALPHABET,
): string[] => {
- const n = Math.min(maxLen, Math.max(a.length, b.length));
- const aPadded = pad(a, n, alphabet);
- const bPadded = pad(b, n, alphabet);
- const aBase = stringToBase(aPadded, alphabet);
- const bBase = stringToBase(bPadded, alphabet);
- if (bBase - aBase - BigInt(1) < count) {
- if (n < maxLen) {
+ const padN = Math.min(Math.max(a.length, b.length), maxLen);
+ const padA = alphabetPad(a, padN, alphabet);
+ const padB = alphabetPad(b, padN, alphabet);
+ const baseA = stringToBase(padA, alphabet);
+ const baseB = stringToBase(padB, alphabet);
+
+ if (baseB - baseA - BigInt(1) < count) {
+ if (padN < maxLen) {
// this recurses once at most due to the new limit of n+1
return midPointsBetweenStrings(
- pad(aPadded, n + 1, alphabet),
- pad(bPadded, n + 1, alphabet),
+ alphabetPad(padA, padN + 1, alphabet),
+ alphabetPad(padB, padN + 1, alphabet),
count,
- n + 1,
+ padN + 1,
alphabet,
);
}
return [];
}
- const step = (bBase - aBase) / BigInt(count + 1);
- const start = BigInt(aBase + step);
+
+ const step = (baseB - baseA) / BigInt(count + 1);
+ const start = BigInt(baseA + step);
return Array(count).fill(undefined).map((_, i) => baseToString(start + (BigInt(i) * step), alphabet));
};
@@ -95,6 +82,7 @@ export const reorderLexicographically = (
// apply the fundamental order update to the zipped array
const newOrder = reorder(ordersWithIndices, fromIndex, toIndex);
+ // check if we have to fill undefined orders to complete placement
const orderToLeftUndefined = newOrder[toIndex - 1]?.order === undefined;
let leftBoundIdx = toIndex;
@@ -105,14 +93,19 @@ export const reorderLexicographically = (
? stringToBase(newOrder[toIndex + 1].order)
: BigInt(Number.MAX_VALUE);
+ // check how far left we would have to mutate to fit in that direction
for (let i = toIndex - 1, j = 1; i >= 0; i--, j++) {
if (newOrder[i]?.order !== undefined && nextBase - stringToBase(newOrder[i].order) > j) break;
leftBoundIdx = i;
}
+ // verify the left move would be sufficient
+ const firstOrderBase = newOrder[0].order === undefined ? undefined : stringToBase(newOrder[0].order);
+ const bigToIndex = BigInt(toIndex);
if (leftBoundIdx === 0 &&
- newOrder[0].order !== undefined &&
- nextBase - stringToBase(newOrder[0].order) < toIndex
+ firstOrderBase !== undefined &&
+ nextBase - firstOrderBase <= bigToIndex &&
+ firstOrderBase <= bigToIndex
) {
canMoveLeft = false;
}
@@ -124,11 +117,13 @@ export const reorderLexicographically = (
? stringToBase(newOrder[toIndex - 1]?.order)
: BigInt(Number.MIN_VALUE);
+ // check how far right we would have to mutate to fit in that direction
for (let i = toIndex + 1, j = 1; i < newOrder.length; i++, j++) {
- if (newOrder[i]?.order === undefined || stringToBase(newOrder[i].order) - prevBase > j) break; // TODO verify
+ if (newOrder[i]?.order === undefined || stringToBase(newOrder[i].order) - prevBase > j) break;
rightBoundIdx = i;
}
+ // verify the right move would be sufficient
if (rightBoundIdx === newOrder.length - 1 &&
(newOrder[rightBoundIdx]
? stringToBase(newOrder[rightBoundIdx].order)
@@ -138,27 +133,23 @@ export const reorderLexicographically = (
}
}
+ // pick the cheaper direction
const leftDiff = canMoveLeft ? toIndex - leftBoundIdx : Number.MAX_SAFE_INTEGER;
const rightDiff = canMoveRight ? rightBoundIdx - toIndex : Number.MAX_SAFE_INTEGER;
-
if (orderToLeftUndefined || leftDiff < rightDiff) {
rightBoundIdx = toIndex;
} else {
leftBoundIdx = toIndex;
}
- const prevOrder = newOrder[leftBoundIdx - 1]?.order
- ?? String.fromCharCode(ALPHABET_START).repeat(5);
+ const prevOrder = newOrder[leftBoundIdx - 1]?.order ?? "";
const nextOrder = newOrder[rightBoundIdx + 1]?.order
- ?? String.fromCharCode(ALPHABET_END).repeat(prevOrder.length);
+ ?? String.fromCharCode(ALPHABET_END).repeat(prevOrder.length || 1);
const changes = midPointsBetweenStrings(prevOrder, nextOrder, 1 + rightBoundIdx - leftBoundIdx, maxLen);
- console.log("@@ test", { canMoveLeft, canMoveRight, prevOrder, nextOrder, changes, leftBoundIdx, rightBoundIdx, orders, fromIndex, toIndex, newOrder, orderToLeftUndefined, leftDiff, rightDiff });
-
- return changes.map((order, i) => {
- const index = newOrder[leftBoundIdx + i].index;
-
- return { index, order };
- });
+ return changes.map((order, i) => ({
+ index: newOrder[leftBoundIdx + i].index,
+ order,
+ }));
};
diff --git a/test/utils/stringOrderField-test.ts b/test/utils/stringOrderField-test.ts
index 9f92774acb..d5671ebe76 100644
--- a/test/utils/stringOrderField-test.ts
+++ b/test/utils/stringOrderField-test.ts
@@ -15,13 +15,12 @@ limitations under the License.
*/
import { sortBy } from "lodash";
+import { stringToBase, baseToString, averageBetweenStrings } from "matrix-js-sdk/src/utils";
import {
ALPHABET,
- baseToString,
midPointsBetweenStrings,
reorderLexicographically,
- stringToBase,
} from "../../src/utils/stringOrderField";
const moveLexicographicallyTest = (
@@ -39,43 +38,58 @@ const moveLexicographicallyTest = (
});
const newOrders = sortBy(zipped, i => i[1]);
- console.log("@@ moveLexicographicallyTest", {orders, zipped, newOrders, fromIndex, toIndex, ops});
expect(newOrders[toIndex][0]).toBe(fromIndex);
expect(ops).toHaveLength(expectedChanges);
};
describe("stringOrderField", () => {
it("stringToBase", () => {
- expect(Number(stringToBase(" "))).toBe(0);
- expect(Number(stringToBase("a"))).toBe(65);
- expect(Number(stringToBase("aa"))).toBe(6240);
- expect(Number(stringToBase("cat"))).toBe(610934);
- expect(Number(stringToBase("doggo"))).toBe(5607022724);
- expect(Number(stringToBase(" "))).toEqual(0);
- expect(Number(stringToBase("a", "abcdefghijklmnopqrstuvwxyz"))).toEqual(0);
- expect(Number(stringToBase("a"))).toEqual(65);
- expect(Number(stringToBase("c", "abcdefghijklmnopqrstuvwxyz"))).toEqual(2);
- expect(Number(stringToBase("ab"))).toEqual(6241);
- expect(Number(stringToBase("cb", "abcdefghijklmnopqrstuvwxyz"))).toEqual(53);
- expect(Number(stringToBase("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"))).toEqual(4.511596985796182e+78);
- expect(Number(stringToBase("~".repeat(50)))).toEqual(7.694497527671333e+98);
- // expect(typeof stringToBase("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")).toEqual("bigint");
+ expect(Number(stringToBase(""))).toBe(0);
+ expect(Number(stringToBase(" "))).toBe(1);
+ expect(Number(stringToBase("a"))).toBe(66);
+ expect(Number(stringToBase(" !"))).toBe(97);
+ expect(Number(stringToBase("aa"))).toBe(6336);
+ expect(Number(stringToBase("cat"))).toBe(620055);
+ expect(Number(stringToBase("doggo"))).toBe(5689339845);
+ expect(Number(stringToBase("a", "abcdefghijklmnopqrstuvwxyz"))).toEqual(1);
+ expect(Number(stringToBase("a"))).toEqual(66);
+ expect(Number(stringToBase("c", "abcdefghijklmnopqrstuvwxyz"))).toEqual(3);
+ expect(Number(stringToBase("ab"))).toEqual(6337);
+ expect(Number(stringToBase("cb", "abcdefghijklmnopqrstuvwxyz"))).toEqual(80);
+ expect(Number(stringToBase("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"))).toEqual(4.648312045971824e+78);
+ expect(Number(stringToBase("~".repeat(50)))).toEqual(7.776353884348688e+98);
+ expect(Number(stringToBase(" "))).toEqual(7820126496);
+ expect(Number(stringToBase(" "))).toEqual(96);
+ expect(Number(stringToBase(" !"))).toEqual(97);
+ expect(Number(stringToBase("S:J\\~"))).toEqual(4258975590);
+ expect(Number(stringToBase("!'Tu:}"))).toEqual(16173443434);
});
it("baseToString", () => {
- expect(baseToString(BigInt(10))).toBe(ALPHABET[10]);
- expect(baseToString(BigInt(10), "abcdefghijklmnopqrstuvwxyz")).toEqual("k");
- expect(baseToString(BigInt(6241))).toEqual("ab");
- expect(baseToString(BigInt(53), "abcdefghijklmnopqrstuvwxyz")).toEqual("cb");
- expect(baseToString(BigInt(1234))).toBe(",~");
+ expect(baseToString(BigInt(10))).toBe(ALPHABET[9]);
+ expect(baseToString(BigInt(10), "abcdefghijklmnopqrstuvwxyz")).toEqual("j");
+ expect(baseToString(BigInt(6241))).toEqual("`a");
+ expect(baseToString(BigInt(53), "abcdefghijklmnopqrstuvwxyz")).toEqual("ba");
+ expect(baseToString(BigInt(1234))).toBe("+}");
+ expect(baseToString(BigInt(0))).toBe(""); // TODO
+ expect(baseToString(BigInt(1))).toBe(" ");
+ expect(baseToString(BigInt(95))).toBe("~");
+ expect(baseToString(BigInt(96))).toBe(" ");
+ expect(baseToString(BigInt(97))).toBe(" !");
+ expect(baseToString(BigInt(98))).toBe(' "');
+ expect(baseToString(BigInt(1))).toBe(" ");
});
it("midPointsBetweenStrings", () => {
+ expect(averageBetweenStrings("!!", "##")).toBe('""');
const midpoints = ["a", ...midPointsBetweenStrings("a", "e", 3, 1), "e"].sort();
expect(midpoints[0]).toBe("a");
expect(midpoints[4]).toBe("e");
expect(midPointsBetweenStrings("a", "e", 0, 1)).toStrictEqual([]);
expect(midPointsBetweenStrings("a", "e", 4, 1)).toStrictEqual([]);
+ expect(midPointsBetweenStrings(" ", "!'Tu:}", 1, 50)).toStrictEqual([" S:J\\~"]);
+ expect(averageBetweenStrings(" ", "!!")).toBe(" P");
+ expect(averageBetweenStrings("! ", "!!")).toBe("! ");
});
it("moveLexicographically left", () => {
@@ -221,7 +235,7 @@ describe("stringOrderField", () => {
);
});
- it.skip("test moving left into no left space", () => {
+ it("test moving left into no left space", () => {
moveLexicographicallyTest(
["11", "12", "13", "14", "19"],
3,
@@ -229,41 +243,11 @@ describe("stringOrderField", () => {
2,
2,
);
- });
- it("test moving right into no right space", () => {
- moveLexicographicallyTest(
- ["15", "16", "17", "18", "19"],
- 1,
- 3,
- 3,
- 2,
- );
- });
-
- it("test moving right into no left space", () => {
- moveLexicographicallyTest(
- ["11", "12", "13", "14", "15", "16", undefined],
- 1,
- 3,
- 3,
- );
- });
-
- it("test moving left into no right space", () => {
- moveLexicographicallyTest(
- ["15", "16", "17", "18", "19"],
- 4,
- 3,
- 4,
- 2,
- );
- });
-
- it("test moving left into no left space", () => {
moveLexicographicallyTest(
[
ALPHABET.charAt(0),
+ // Target
ALPHABET.charAt(1),
ALPHABET.charAt(2),
ALPHABET.charAt(3),
@@ -278,6 +262,14 @@ describe("stringOrderField", () => {
});
it("test moving right into no right space", () => {
+ moveLexicographicallyTest(
+ ["15", "16", "17", "18", "19"],
+ 1,
+ 3,
+ 3,
+ 2,
+ );
+
moveLexicographicallyTest(
[
ALPHABET.charAt(ALPHABET.length - 5),
@@ -294,6 +286,13 @@ describe("stringOrderField", () => {
});
it("test moving right into no left space", () => {
+ moveLexicographicallyTest(
+ ["11", "12", "13", "14", "15", "16", undefined],
+ 1,
+ 3,
+ 3,
+ );
+
moveLexicographicallyTest(
["0", "1", "2", "3", "4", "5"],
1,
@@ -304,6 +303,14 @@ describe("stringOrderField", () => {
});
it("test moving left into no right space", () => {
+ moveLexicographicallyTest(
+ ["15", "16", "17", "18", "19"],
+ 4,
+ 3,
+ 4,
+ 2,
+ );
+
moveLexicographicallyTest(
[
ALPHABET.charAt(ALPHABET.length - 5),
@@ -329,11 +336,11 @@ describe("stringOrderField", () => {
it("rolls over sanely", () => {
const maxSpaceValue = "~".repeat(50);
- const fiftyFirstChar = "!" + " ".repeat(50);
+ const fiftyFirstChar = " ".repeat(51);
expect(next(maxSpaceValue)).toBe(fiftyFirstChar);
expect(prev(fiftyFirstChar)).toBe(maxSpaceValue);
- expect(stringToBase(ALPHABET[0])).toEqual(BigInt(0));
- expect(stringToBase(ALPHABET[1])).toEqual(BigInt(1));
+ expect(Number(stringToBase(ALPHABET[0]))).toEqual(1);
+ expect(Number(stringToBase(ALPHABET[1]))).toEqual(2);
expect(ALPHABET[ALPHABET.length - 1]).toBe("~");
expect(ALPHABET[0]).toBe(" ");
});
From 2879b9086c1ace24fc3307e6af5b404fff0059c2 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Mon, 14 Jun 2021 21:32:11 +0100
Subject: [PATCH 039/100] Use alphabet from js-sdk
---
src/utils/stringOrderField.ts | 13 ++------
test/utils/stringOrderField-test.ts | 50 +++++++++++++----------------
2 files changed, 26 insertions(+), 37 deletions(-)
diff --git a/src/utils/stringOrderField.ts b/src/utils/stringOrderField.ts
index e09f7fbea4..4336583b9d 100644
--- a/src/utils/stringOrderField.ts
+++ b/src/utils/stringOrderField.ts
@@ -14,23 +14,16 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import { alphabetPad, baseToString, stringToBase } from "matrix-js-sdk/src/utils";
+import { alphabetPad, baseToString, stringToBase, DEFAULT_ALPHABET } from "matrix-js-sdk/src/utils";
import { reorder } from "./arrays";
-export const ALPHABET_START = 0x20;
-export const ALPHABET_END = 0x7E;
-export const ALPHABET = new Array(1 + ALPHABET_END - ALPHABET_START)
- .fill(undefined)
- .map((_, i) => String.fromCharCode(ALPHABET_START + i))
- .join("");
-
export const midPointsBetweenStrings = (
a: string,
b: string,
count: number,
maxLen: number,
- alphabet = ALPHABET,
+ alphabet = DEFAULT_ALPHABET,
): string[] => {
const padN = Math.min(Math.max(a.length, b.length), maxLen);
const padA = alphabetPad(a, padN, alphabet);
@@ -144,7 +137,7 @@ export const reorderLexicographically = (
const prevOrder = newOrder[leftBoundIdx - 1]?.order ?? "";
const nextOrder = newOrder[rightBoundIdx + 1]?.order
- ?? String.fromCharCode(ALPHABET_END).repeat(prevOrder.length || 1);
+ ?? DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 1).repeat(prevOrder.length || 1);
const changes = midPointsBetweenStrings(prevOrder, nextOrder, 1 + rightBoundIdx - leftBoundIdx, maxLen);
diff --git a/test/utils/stringOrderField-test.ts b/test/utils/stringOrderField-test.ts
index d5671ebe76..a8bc00eeb9 100644
--- a/test/utils/stringOrderField-test.ts
+++ b/test/utils/stringOrderField-test.ts
@@ -15,13 +15,9 @@ limitations under the License.
*/
import { sortBy } from "lodash";
-import { stringToBase, baseToString, averageBetweenStrings } from "matrix-js-sdk/src/utils";
+import { stringToBase, baseToString, averageBetweenStrings, DEFAULT_ALPHABET } from "matrix-js-sdk/src/utils";
-import {
- ALPHABET,
- midPointsBetweenStrings,
- reorderLexicographically,
-} from "../../src/utils/stringOrderField";
+import { midPointsBetweenStrings, reorderLexicographically } from "../../src/utils/stringOrderField";
const moveLexicographicallyTest = (
orders: Array,
@@ -66,7 +62,7 @@ describe("stringOrderField", () => {
});
it("baseToString", () => {
- expect(baseToString(BigInt(10))).toBe(ALPHABET[9]);
+ expect(baseToString(BigInt(10))).toBe(DEFAULT_ALPHABET[9]);
expect(baseToString(BigInt(10), "abcdefghijklmnopqrstuvwxyz")).toEqual("j");
expect(baseToString(BigInt(6241))).toEqual("`a");
expect(baseToString(BigInt(53), "abcdefghijklmnopqrstuvwxyz")).toEqual("ba");
@@ -246,13 +242,13 @@ describe("stringOrderField", () => {
moveLexicographicallyTest(
[
- ALPHABET.charAt(0),
+ DEFAULT_ALPHABET.charAt(0),
// Target
- ALPHABET.charAt(1),
- ALPHABET.charAt(2),
- ALPHABET.charAt(3),
- ALPHABET.charAt(4),
- ALPHABET.charAt(5),
+ DEFAULT_ALPHABET.charAt(1),
+ DEFAULT_ALPHABET.charAt(2),
+ DEFAULT_ALPHABET.charAt(3),
+ DEFAULT_ALPHABET.charAt(4),
+ DEFAULT_ALPHABET.charAt(5),
],
5,
1,
@@ -272,11 +268,11 @@ describe("stringOrderField", () => {
moveLexicographicallyTest(
[
- ALPHABET.charAt(ALPHABET.length - 5),
- ALPHABET.charAt(ALPHABET.length - 4),
- ALPHABET.charAt(ALPHABET.length - 3),
- ALPHABET.charAt(ALPHABET.length - 2),
- ALPHABET.charAt(ALPHABET.length - 1),
+ DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 5),
+ DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 4),
+ DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 3),
+ DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 2),
+ DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 1),
],
1,
3,
@@ -313,11 +309,11 @@ describe("stringOrderField", () => {
moveLexicographicallyTest(
[
- ALPHABET.charAt(ALPHABET.length - 5),
- ALPHABET.charAt(ALPHABET.length - 4),
- ALPHABET.charAt(ALPHABET.length - 3),
- ALPHABET.charAt(ALPHABET.length - 2),
- ALPHABET.charAt(ALPHABET.length - 1),
+ DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 5),
+ DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 4),
+ DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 3),
+ DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 2),
+ DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 1),
],
4,
3,
@@ -339,10 +335,10 @@ describe("stringOrderField", () => {
const fiftyFirstChar = " ".repeat(51);
expect(next(maxSpaceValue)).toBe(fiftyFirstChar);
expect(prev(fiftyFirstChar)).toBe(maxSpaceValue);
- expect(Number(stringToBase(ALPHABET[0]))).toEqual(1);
- expect(Number(stringToBase(ALPHABET[1]))).toEqual(2);
- expect(ALPHABET[ALPHABET.length - 1]).toBe("~");
- expect(ALPHABET[0]).toBe(" ");
+ expect(Number(stringToBase(DEFAULT_ALPHABET[0]))).toEqual(1);
+ expect(Number(stringToBase(DEFAULT_ALPHABET[1]))).toEqual(2);
+ expect(DEFAULT_ALPHABET[DEFAULT_ALPHABET.length - 1]).toBe("~");
+ expect(DEFAULT_ALPHABET[0]).toBe(" ");
});
});
From b9f86d54c3712bcf438038376a01fb9a90c96e9f Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Mon, 14 Jun 2021 22:07:25 +0100
Subject: [PATCH 040/100] Update yarn.lock
---
yarn.lock | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/yarn.lock b/yarn.lock
index 8e6b4fa732..289d33088f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6659,7 +6659,7 @@ prompts@^2.0.1:
kleur "^3.0.3"
sisteransi "^1.0.5"
-prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.0, prop-types@^15.7.2:
+prop-types@^15.6.2, prop-types@^15.7.0, prop-types@^15.7.2:
version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
@@ -6809,7 +6809,12 @@ react-focus-lock@^2.5.0:
use-callback-ref "^1.2.1"
use-sidecar "^1.0.1"
-react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.6:
+"react-is@^16.12.0 || ^17.0.0", react-is@^17.0.0, react-is@^17.0.2:
+ version "17.0.2"
+ resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
+ integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
+
+react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
From a63d9220d2474ffd592c728371107bcfda1ae070 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 15 Jun 2021 08:26:46 +0100
Subject: [PATCH 041/100] Clear outstanding TODOs
---
src/stores/SpaceStore.tsx | 17 ++++++++++-------
test/utils/stringOrderField-test.ts | 2 +-
2 files changed, 11 insertions(+), 8 deletions(-)
diff --git a/src/stores/SpaceStore.tsx b/src/stores/SpaceStore.tsx
index d0ec573306..9ffb4eb776 100644
--- a/src/stores/SpaceStore.tsx
+++ b/src/stores/SpaceStore.tsx
@@ -634,9 +634,16 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
return sortBy(spaces, [this.getSpaceTagOrdering, "roomId"]);
}
- private setRootSpaceOrder(space: Room, order: string): void {
+ private async setRootSpaceOrder(space: Room, order: string): Promise {
this.spaceOrderLocalEchoMap.set(space.roomId, order);
- this.matrixClient.setRoomAccountData(space.roomId, EventType.SpaceOrder, { order }); // TODO retrying, failure
+ try {
+ await this.matrixClient.setRoomAccountData(space.roomId, EventType.SpaceOrder, { order });
+ } catch (e) {
+ console.log("Failed to set root space order", e);
+ if (this.spaceOrderLocalEchoMap.get(space.roomId) === order) {
+ this.spaceOrderLocalEchoMap.delete(space.roomId);
+ }
+ }
}
public moveRootSpace(fromIndex: number, toIndex: number): void {
@@ -647,11 +654,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
this.setRootSpaceOrder(this.rootSpaces[index], order);
});
- if (changes.length) {
- this.notifyIfOrderChanged();
- } else {
- // TODO
- }
+ this.notifyIfOrderChanged();
}
}
diff --git a/test/utils/stringOrderField-test.ts b/test/utils/stringOrderField-test.ts
index a8bc00eeb9..a523872023 100644
--- a/test/utils/stringOrderField-test.ts
+++ b/test/utils/stringOrderField-test.ts
@@ -67,7 +67,7 @@ describe("stringOrderField", () => {
expect(baseToString(BigInt(6241))).toEqual("`a");
expect(baseToString(BigInt(53), "abcdefghijklmnopqrstuvwxyz")).toEqual("ba");
expect(baseToString(BigInt(1234))).toBe("+}");
- expect(baseToString(BigInt(0))).toBe(""); // TODO
+ expect(baseToString(BigInt(0))).toBe("");
expect(baseToString(BigInt(1))).toBe(" ");
expect(baseToString(BigInt(95))).toBe("~");
expect(baseToString(BigInt(96))).toBe(" ");
From 5130d5e111bf32ced37df78096b85467496b9ee2 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 15 Jun 2021 12:26:09 +0100
Subject: [PATCH 042/100] Hide addresses section for private spaces
---
.../spaces/SpaceSettingsVisibilityTab.tsx | 27 ++++++++++++-------
1 file changed, 17 insertions(+), 10 deletions(-)
diff --git a/src/components/views/spaces/SpaceSettingsVisibilityTab.tsx b/src/components/views/spaces/SpaceSettingsVisibilityTab.tsx
index 9eb4c6eb02..2f80ad97a6 100644
--- a/src/components/views/spaces/SpaceSettingsVisibilityTab.tsx
+++ b/src/components/views/spaces/SpaceSettingsVisibilityTab.tsx
@@ -120,6 +120,22 @@ const SpaceSettingsVisibilityTab = ({ matrixClient: cli, space }: IProps) => {
>;
}
+ let addressesSection;
+ if (visibility !== SpaceVisibility.Private) {
+ addressesSection = <>
+ {_t("Address")}
+
+ >;
+ }
+
return
{_t("Visibility")}
@@ -164,16 +180,7 @@ const SpaceSettingsVisibilityTab = ({ matrixClient: cli, space }: IProps) => {
{ _t("Recommended for public spaces.") }
- {_t("Address")}
-
+ { addressesSection }
;
};
From 5098a304c9850c139aa73e8e594648b622a4710c Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 15 Jun 2021 12:33:46 +0100
Subject: [PATCH 043/100] delint
---
src/components/views/dialogs/RoomSettingsDialog.tsx | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/components/views/dialogs/RoomSettingsDialog.tsx b/src/components/views/dialogs/RoomSettingsDialog.tsx
index 1a664951c5..303f17c342 100644
--- a/src/components/views/dialogs/RoomSettingsDialog.tsx
+++ b/src/components/views/dialogs/RoomSettingsDialog.tsx
@@ -108,7 +108,10 @@ export default class RoomSettingsDialog extends React.Component {
ROOM_ADVANCED_TAB,
_td("Advanced"),
"mx_RoomSettingsDialog_warningIcon",
- ,
+ this.props.onFinished(true)}
+ />,
));
}
From cee294f5a7378737bc0cbaf9707250eeea5a195d Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Wed, 16 Jun 2021 09:23:06 +0100
Subject: [PATCH 044/100] iterate PR
---
.../structures/AutoHideScrollbar.tsx | 2 +-
.../structures/SpaceRoomDirectory.tsx | 28 +++++++++----------
.../views/spaces/SpaceTreeLevel.tsx | 28 +++++++++----------
src/stores/SpaceStore.tsx | 5 ++--
src/utils/arrays.ts | 2 +-
src/utils/stringOrderField.ts | 4 +--
6 files changed, 34 insertions(+), 35 deletions(-)
diff --git a/src/components/structures/AutoHideScrollbar.tsx b/src/components/structures/AutoHideScrollbar.tsx
index e5fa124fed..8650224fb3 100644
--- a/src/components/structures/AutoHideScrollbar.tsx
+++ b/src/components/structures/AutoHideScrollbar.tsx
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React, {HTMLAttributes} from "react";
+import React, { HTMLAttributes } from "react";
interface IProps extends HTMLAttributes {
className?: string;
diff --git a/src/components/structures/SpaceRoomDirectory.tsx b/src/components/structures/SpaceRoomDirectory.tsx
index 2b4fb24c1b..497f525a00 100644
--- a/src/components/structures/SpaceRoomDirectory.tsx
+++ b/src/components/structures/SpaceRoomDirectory.tsx
@@ -14,34 +14,34 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React, {ReactNode, useMemo, useState} from "react";
-import {Room} from "matrix-js-sdk/src/models/room";
-import {MatrixClient} from "matrix-js-sdk/src/client";
-import {EventType, RoomType} from "matrix-js-sdk/src/@types/event";
+import React, { ReactNode, useMemo, useState } from "react";
+import { Room } from "matrix-js-sdk/src/models/room";
+import { MatrixClient } from "matrix-js-sdk/src/client";
+import { EventType, RoomType } from "matrix-js-sdk/src/@types/event";
import classNames from "classnames";
-import {sortBy} from "lodash";
+import { sortBy } from "lodash";
-import {MatrixClientPeg} from "../../MatrixClientPeg";
+import { MatrixClientPeg } from "../../MatrixClientPeg";
import dis from "../../dispatcher/dispatcher";
-import {_t} from "../../languageHandler";
-import AccessibleButton, {ButtonEvent} from "../views/elements/AccessibleButton";
+import { _t } from "../../languageHandler";
+import AccessibleButton, { ButtonEvent } from "../views/elements/AccessibleButton";
import BaseDialog from "../views/dialogs/BaseDialog";
import Spinner from "../views/elements/Spinner";
import SearchBox from "./SearchBox";
import RoomAvatar from "../views/avatars/RoomAvatar";
import RoomName from "../views/elements/RoomName";
-import {useAsyncMemo} from "../../hooks/useAsyncMemo";
-import {EnhancedMap} from "../../utils/maps";
+import { useAsyncMemo } from "../../hooks/useAsyncMemo";
+import { EnhancedMap } from "../../utils/maps";
import StyledCheckbox from "../views/elements/StyledCheckbox";
import AutoHideScrollbar from "./AutoHideScrollbar";
import BaseAvatar from "../views/avatars/BaseAvatar";
-import {mediaFromMxc} from "../../customisations/Media";
+import { mediaFromMxc } from "../../customisations/Media";
import InfoTooltip from "../views/elements/InfoTooltip";
import TextWithTooltip from "../views/elements/TextWithTooltip";
-import {useStateToggle} from "../../hooks/useStateToggle";
-import {getChildOrder} from "../../stores/SpaceStore";
+import { useStateToggle } from "../../hooks/useStateToggle";
+import { getChildOrder } from "../../stores/SpaceStore";
import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton";
-import {linkifyElement} from "../../HtmlUtils";
+import { linkifyElement } from "../../HtmlUtils";
interface IHierarchyProps {
space: Room;
diff --git a/src/components/views/spaces/SpaceTreeLevel.tsx b/src/components/views/spaces/SpaceTreeLevel.tsx
index 7ac863b239..416b4cc6f1 100644
--- a/src/components/views/spaces/SpaceTreeLevel.tsx
+++ b/src/components/views/spaces/SpaceTreeLevel.tsx
@@ -14,23 +14,23 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React, {InputHTMLAttributes, LegacyRef} from "react";
+import React, { InputHTMLAttributes, LegacyRef } from "react";
import classNames from "classnames";
-import {Room} from "matrix-js-sdk/src/models/room";
+import { Room } from "matrix-js-sdk/src/models/room";
import RoomAvatar from "../avatars/RoomAvatar";
import SpaceStore from "../../../stores/SpaceStore";
import SpaceTreeLevelLayoutStore from "../../../stores/SpaceTreeLevelLayoutStore";
import NotificationBadge from "../rooms/NotificationBadge";
-import {RovingAccessibleButton} from "../../../accessibility/roving/RovingAccessibleButton";
-import {RovingAccessibleTooltipButton} from "../../../accessibility/roving/RovingAccessibleTooltipButton";
+import { RovingAccessibleButton } from "../../../accessibility/roving/RovingAccessibleButton";
+import { RovingAccessibleTooltipButton } from "../../../accessibility/roving/RovingAccessibleTooltipButton";
import IconizedContextMenu, {
IconizedContextMenuOption,
IconizedContextMenuOptionList,
} from "../context_menus/IconizedContextMenu";
-import {_t} from "../../../languageHandler";
-import {ContextMenuTooltipButton} from "../../../accessibility/context_menu/ContextMenuTooltipButton";
-import {toRightOf} from "../../structures/ContextMenu";
+import { _t } from "../../../languageHandler";
+import { ContextMenuTooltipButton } from "../../../accessibility/context_menu/ContextMenuTooltipButton";
+import { toRightOf } from "../../structures/ContextMenu";
import {
shouldShowSpaceSettings,
showAddExistingRooms,
@@ -39,15 +39,15 @@ import {
showSpaceSettings,
} from "../../../utils/space";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
-import AccessibleButton, {ButtonEvent} from "../elements/AccessibleButton";
+import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
import defaultDispatcher from "../../../dispatcher/dispatcher";
-import {Action} from "../../../dispatcher/actions";
+import { Action } from "../../../dispatcher/actions";
import RoomViewStore from "../../../stores/RoomViewStore";
-import {SetRightPanelPhasePayload} from "../../../dispatcher/payloads/SetRightPanelPhasePayload";
-import {RightPanelPhases} from "../../../stores/RightPanelStorePhases";
-import {EventType} from "matrix-js-sdk/src/@types/event";
-import {StaticNotificationState} from "../../../stores/notifications/StaticNotificationState";
-import {NotificationColor} from "../../../stores/notifications/NotificationColor";
+import { SetRightPanelPhasePayload } from "../../../dispatcher/payloads/SetRightPanelPhasePayload";
+import { RightPanelPhases } from "../../../stores/RightPanelStorePhases";
+import { EventType } from "matrix-js-sdk/src/@types/event";
+import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState";
+import { NotificationColor } from "../../../stores/notifications/NotificationColor";
interface IItemProps extends InputHTMLAttributes {
space?: Room;
diff --git a/src/stores/SpaceStore.tsx b/src/stores/SpaceStore.tsx
index 9ffb4eb776..b0099b3306 100644
--- a/src/stores/SpaceStore.tsx
+++ b/src/stores/SpaceStore.tsx
@@ -62,14 +62,13 @@ const partitionSpacesAndRooms = (arr: Room[]): [Room[], Room[]] => { // [spaces,
}, [[], []]);
};
-const validOrder = (order: string): string | null => {
+const validOrder = (order: string): string | undefined => {
if (typeof order === "string" && order.length <= 50 && Array.from(order).every((c: string) => {
const charCode = c.charCodeAt(0);
return charCode >= 0x20 && charCode <= 0x7E;
})) {
return order;
}
- return undefined;
};
// For sorting space children using a validated `order`, `m.room.create`'s `origin_server_ts`, `room_id`
@@ -639,7 +638,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
try {
await this.matrixClient.setRoomAccountData(space.roomId, EventType.SpaceOrder, { order });
} catch (e) {
- console.log("Failed to set root space order", e);
+ console.warn("Failed to set root space order", e);
if (this.spaceOrderLocalEchoMap.get(space.roomId) === order) {
this.spaceOrderLocalEchoMap.delete(space.roomId);
}
diff --git a/src/utils/arrays.ts b/src/utils/arrays.ts
index d319631d93..148861e5d3 100644
--- a/src/utils/arrays.ts
+++ b/src/utils/arrays.ts
@@ -230,7 +230,7 @@ export function arrayMerge(...a: T[][]): T[] {
* @param toIndex the index of where to put the element.
* @returns A new array with the requested value moved.
*/
-export function reorder(list: T[], fromIndex: number, toIndex: number): T[] {
+export function moveElement(list: T[], fromIndex: number, toIndex: number): T[] {
const result = Array.from(list);
const [removed] = result.splice(fromIndex, 1);
result.splice(toIndex, 0, removed);
diff --git a/src/utils/stringOrderField.ts b/src/utils/stringOrderField.ts
index 4336583b9d..b312b85b08 100644
--- a/src/utils/stringOrderField.ts
+++ b/src/utils/stringOrderField.ts
@@ -16,7 +16,7 @@ limitations under the License.
import { alphabetPad, baseToString, stringToBase, DEFAULT_ALPHABET } from "matrix-js-sdk/src/utils";
-import { reorder } from "./arrays";
+import { moveElement } from "./arrays";
export const midPointsBetweenStrings = (
a: string,
@@ -73,7 +73,7 @@ export const reorderLexicographically = (
// zip orders with their indices to simplify later index wrangling
const ordersWithIndices: IEntry[] = orders.map((order, index) => ({ index, order }));
// apply the fundamental order update to the zipped array
- const newOrder = reorder(ordersWithIndices, fromIndex, toIndex);
+ const newOrder = moveElement(ordersWithIndices, fromIndex, toIndex);
// check if we have to fill undefined orders to complete placement
const orderToLeftUndefined = newOrder[toIndex - 1]?.order === undefined;
From bceee7978edc0a972f67cc612a250d5f0f717dfd Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Wed, 16 Jun 2021 09:30:47 +0100
Subject: [PATCH 045/100] improve naming of tests
---
test/utils/stringOrderField-test.ts | 495 +++++++++++++---------------
1 file changed, 221 insertions(+), 274 deletions(-)
diff --git a/test/utils/stringOrderField-test.ts b/test/utils/stringOrderField-test.ts
index a523872023..ece3043d86 100644
--- a/test/utils/stringOrderField-test.ts
+++ b/test/utils/stringOrderField-test.ts
@@ -39,306 +39,253 @@ const moveLexicographicallyTest = (
};
describe("stringOrderField", () => {
- it("stringToBase", () => {
- expect(Number(stringToBase(""))).toBe(0);
- expect(Number(stringToBase(" "))).toBe(1);
- expect(Number(stringToBase("a"))).toBe(66);
- expect(Number(stringToBase(" !"))).toBe(97);
- expect(Number(stringToBase("aa"))).toBe(6336);
- expect(Number(stringToBase("cat"))).toBe(620055);
- expect(Number(stringToBase("doggo"))).toBe(5689339845);
- expect(Number(stringToBase("a", "abcdefghijklmnopqrstuvwxyz"))).toEqual(1);
- expect(Number(stringToBase("a"))).toEqual(66);
- expect(Number(stringToBase("c", "abcdefghijklmnopqrstuvwxyz"))).toEqual(3);
- expect(Number(stringToBase("ab"))).toEqual(6337);
- expect(Number(stringToBase("cb", "abcdefghijklmnopqrstuvwxyz"))).toEqual(80);
- expect(Number(stringToBase("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"))).toEqual(4.648312045971824e+78);
- expect(Number(stringToBase("~".repeat(50)))).toEqual(7.776353884348688e+98);
- expect(Number(stringToBase(" "))).toEqual(7820126496);
- expect(Number(stringToBase(" "))).toEqual(96);
- expect(Number(stringToBase(" !"))).toEqual(97);
- expect(Number(stringToBase("S:J\\~"))).toEqual(4258975590);
- expect(Number(stringToBase("!'Tu:}"))).toEqual(16173443434);
+ describe("midPointsBetweenStrings", () => {
+ it("should work", () => {
+ expect(averageBetweenStrings("!!", "##")).toBe('""');
+ const midpoints = ["a", ...midPointsBetweenStrings("a", "e", 3, 1), "e"].sort();
+ expect(midpoints[0]).toBe("a");
+ expect(midpoints[4]).toBe("e");
+ expect(midPointsBetweenStrings(" ", "!'Tu:}", 1, 50)).toStrictEqual([" S:J\\~"]);
+ });
+
+ it("should return empty array when the request is not possible", () => {
+ expect(midPointsBetweenStrings("a", "e", 0, 1)).toStrictEqual([]);
+ expect(midPointsBetweenStrings("a", "e", 4, 1)).toStrictEqual([]);
+ });
});
- it("baseToString", () => {
- expect(baseToString(BigInt(10))).toBe(DEFAULT_ALPHABET[9]);
- expect(baseToString(BigInt(10), "abcdefghijklmnopqrstuvwxyz")).toEqual("j");
- expect(baseToString(BigInt(6241))).toEqual("`a");
- expect(baseToString(BigInt(53), "abcdefghijklmnopqrstuvwxyz")).toEqual("ba");
- expect(baseToString(BigInt(1234))).toBe("+}");
- expect(baseToString(BigInt(0))).toBe("");
- expect(baseToString(BigInt(1))).toBe(" ");
- expect(baseToString(BigInt(95))).toBe("~");
- expect(baseToString(BigInt(96))).toBe(" ");
- expect(baseToString(BigInt(97))).toBe(" !");
- expect(baseToString(BigInt(98))).toBe(' "');
- expect(baseToString(BigInt(1))).toBe(" ");
- });
+ describe("reorderLexicographically", () => {
+ it("should work when moving left", () => {
+ moveLexicographicallyTest(["a", "c", "e", "g", "i"], 2, 1, 1);
+ });
- it("midPointsBetweenStrings", () => {
- expect(averageBetweenStrings("!!", "##")).toBe('""');
- const midpoints = ["a", ...midPointsBetweenStrings("a", "e", 3, 1), "e"].sort();
- expect(midpoints[0]).toBe("a");
- expect(midpoints[4]).toBe("e");
- expect(midPointsBetweenStrings("a", "e", 0, 1)).toStrictEqual([]);
- expect(midPointsBetweenStrings("a", "e", 4, 1)).toStrictEqual([]);
- expect(midPointsBetweenStrings(" ", "!'Tu:}", 1, 50)).toStrictEqual([" S:J\\~"]);
- expect(averageBetweenStrings(" ", "!!")).toBe(" P");
- expect(averageBetweenStrings("! ", "!!")).toBe("! ");
- });
+ it("should work when moving right", () => {
+ moveLexicographicallyTest(["a", "c", "e", "g", "i"], 1, 2, 1);
+ });
- it("moveLexicographically left", () => {
- moveLexicographicallyTest(["a", "c", "e", "g", "i"], 2, 1, 1);
- });
+ it("should work when all orders are undefined", () => {
+ moveLexicographicallyTest(
+ [undefined, undefined, undefined, undefined, undefined, undefined],
+ 4,
+ 1,
+ 2,
+ );
+ });
- it("moveLexicographically right", () => {
- moveLexicographicallyTest(["a", "c", "e", "g", "i"], 1, 2, 1);
- });
+ it("should work when moving to end and all orders are undefined", () => {
+ moveLexicographicallyTest(
+ [undefined, undefined, undefined, undefined, undefined, undefined],
+ 1,
+ 4,
+ 5,
+ );
+ });
- it("moveLexicographically all undefined", () => {
- moveLexicographicallyTest(
- [undefined, undefined, undefined, undefined, undefined, undefined],
- 4,
- 1,
- 2,
- );
- });
+ it("should work when moving left and some orders are undefined", () => {
+ moveLexicographicallyTest(
+ ["a", "c", "e", undefined, undefined, undefined],
+ 5,
+ 2,
+ 1,
+ );
- it("moveLexicographically all undefined to end", () => {
- moveLexicographicallyTest(
- [undefined, undefined, undefined, undefined, undefined, undefined],
- 1,
- 4,
- 5,
- );
- });
+ moveLexicographicallyTest(
+ ["a", "a", "e", undefined, undefined, undefined],
+ 5,
+ 1,
+ 2,
+ );
+ });
- it("moveLexicographically some undefined move left", () => {
- moveLexicographicallyTest(
- ["a", "c", "e", undefined, undefined, undefined],
- 5,
- 2,
- 1,
- );
- });
+ it("should work moving to the start when all is undefined", () => {
+ moveLexicographicallyTest(
+ [undefined, undefined, undefined, undefined],
+ 2,
+ 0,
+ 1,
+ );
+ });
- it("moveLexicographically some undefined move left close", () => {
- moveLexicographicallyTest(
- ["a", "a", "e", undefined, undefined, undefined],
- 5,
- 1,
- 2,
- );
- });
+ it("should work moving to the end when all is undefined", () => {
+ moveLexicographicallyTest(
+ [undefined, undefined, undefined, undefined],
+ 1,
+ 3,
+ 4,
+ );
+ });
- it("test moving to the start when all is undefined", () => {
- moveLexicographicallyTest(
- [undefined, undefined, undefined, undefined],
- 2,
- 0,
- 1,
- );
- });
+ it("should work moving left when all is undefined", () => {
+ moveLexicographicallyTest(
+ [undefined, undefined, undefined, undefined, undefined, undefined],
+ 4,
+ 1,
+ 2,
+ );
+ });
- it("test moving to the end when all is undefined", () => {
- moveLexicographicallyTest(
- [undefined, undefined, undefined, undefined],
- 1,
- 3,
- 4,
- );
- });
+ it("should work moving right when all is undefined", () => {
+ moveLexicographicallyTest(
+ [undefined, undefined, undefined, undefined],
+ 1,
+ 2,
+ 3,
+ );
+ });
- it("test moving left when all is undefined", () => {
- moveLexicographicallyTest(
- [undefined, undefined, undefined, undefined, undefined, undefined],
- 4,
- 1,
- 2,
- );
- });
+ it("should work moving more right when all is undefined", () => {
+ moveLexicographicallyTest(
+ [undefined, undefined, undefined, undefined, undefined, /**/ undefined, undefined],
+ 1,
+ 4,
+ 5,
+ );
+ });
- it("test moving right when all is undefined", () => {
- moveLexicographicallyTest(
- [undefined, undefined, undefined, undefined],
- 1,
- 2,
- 3,
- );
- });
+ it("should work moving left when right is undefined", () => {
+ moveLexicographicallyTest(
+ ["20", undefined, undefined, undefined, undefined, undefined],
+ 4,
+ 2,
+ 2,
+ );
+ });
- it("test moving more right when all is undefined", () => {
- moveLexicographicallyTest(
- [undefined, undefined, undefined, undefined, undefined, /**/ undefined, undefined],
- 1,
- 4,
- 5,
- );
- });
+ it("should work moving right when right is undefined", () => {
+ moveLexicographicallyTest(
+ ["50", undefined, undefined, undefined, undefined, /**/ undefined, undefined],
+ 1,
+ 4,
+ 4,
+ );
+ });
- it("test moving left when right is undefined", () => {
- moveLexicographicallyTest(
- ["20", undefined, undefined, undefined, undefined, undefined],
- 4,
- 2,
- 2,
- );
- });
+ it("should work moving left when right is defined", () => {
+ moveLexicographicallyTest(
+ ["10", "20", "30", "40", undefined, undefined],
+ 3,
+ 1,
+ 1,
+ );
+ });
- it("test moving right when right is undefined", () => {
- moveLexicographicallyTest(
- ["50", undefined, undefined, undefined, undefined, /**/ undefined, undefined],
- 1,
- 4,
- 4,
- );
- });
+ it("should work moving right when right is defined", () => {
+ moveLexicographicallyTest(
+ ["10", "20", "30", "40", "50", undefined],
+ 1,
+ 3,
+ 1,
+ );
+ });
- it("test moving left when right is defined", () => {
- moveLexicographicallyTest(
- ["10", "20", "30", "40", undefined, undefined],
- 3,
- 1,
- 1,
- );
- });
+ it("should work moving left when all is defined", () => {
+ moveLexicographicallyTest(
+ ["11", "13", "15", "17", "19"],
+ 2,
+ 1,
+ 1,
+ );
+ });
- it("test moving right when right is defined", () => {
- moveLexicographicallyTest(
- ["10", "20", "30", "40", "50", undefined],
- 1,
- 3,
- 1,
- );
- });
+ it("should work moving right when all is defined", () => {
+ moveLexicographicallyTest(
+ ["11", "13", "15", "17", "19"],
+ 1,
+ 2,
+ 1,
+ );
+ });
- it("test moving left when all is defined", () => {
- moveLexicographicallyTest(
- ["11", "13", "15", "17", "19"],
- 2,
- 1,
- 1,
- );
- });
+ it("should work moving left into no left space", () => {
+ moveLexicographicallyTest(
+ ["11", "12", "13", "14", "19"],
+ 3,
+ 1,
+ 2,
+ 2,
+ );
- it("test moving right when all is defined", () => {
- moveLexicographicallyTest(
- ["11", "13", "15", "17", "19"],
- 1,
- 2,
- 1,
- );
- });
+ moveLexicographicallyTest(
+ [
+ DEFAULT_ALPHABET.charAt(0),
+ // Target
+ DEFAULT_ALPHABET.charAt(1),
+ DEFAULT_ALPHABET.charAt(2),
+ DEFAULT_ALPHABET.charAt(3),
+ DEFAULT_ALPHABET.charAt(4),
+ DEFAULT_ALPHABET.charAt(5),
+ ],
+ 5,
+ 1,
+ 5,
+ 1,
+ );
+ });
- it("test moving left into no left space", () => {
- moveLexicographicallyTest(
- ["11", "12", "13", "14", "19"],
- 3,
- 1,
- 2,
- 2,
- );
+ it("should work moving right into no right space", () => {
+ moveLexicographicallyTest(
+ ["15", "16", "17", "18", "19"],
+ 1,
+ 3,
+ 3,
+ 2,
+ );
- moveLexicographicallyTest(
- [
- DEFAULT_ALPHABET.charAt(0),
- // Target
- DEFAULT_ALPHABET.charAt(1),
- DEFAULT_ALPHABET.charAt(2),
- DEFAULT_ALPHABET.charAt(3),
- DEFAULT_ALPHABET.charAt(4),
- DEFAULT_ALPHABET.charAt(5),
- ],
- 5,
- 1,
- 5,
- 1,
- );
- });
+ moveLexicographicallyTest(
+ [
+ DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 5),
+ DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 4),
+ DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 3),
+ DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 2),
+ DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 1),
+ ],
+ 1,
+ 3,
+ 3,
+ 1,
+ );
+ });
- it("test moving right into no right space", () => {
- moveLexicographicallyTest(
- ["15", "16", "17", "18", "19"],
- 1,
- 3,
- 3,
- 2,
- );
+ it("should work moving right into no left space", () => {
+ moveLexicographicallyTest(
+ ["11", "12", "13", "14", "15", "16", undefined],
+ 1,
+ 3,
+ 3,
+ );
- moveLexicographicallyTest(
- [
- DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 5),
- DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 4),
- DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 3),
- DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 2),
- DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 1),
- ],
- 1,
- 3,
- 3,
- 1,
- );
- });
+ moveLexicographicallyTest(
+ ["0", "1", "2", "3", "4", "5"],
+ 1,
+ 3,
+ 3,
+ 1,
+ );
+ });
- it("test moving right into no left space", () => {
- moveLexicographicallyTest(
- ["11", "12", "13", "14", "15", "16", undefined],
- 1,
- 3,
- 3,
- );
+ it("should work moving left into no right space", () => {
+ moveLexicographicallyTest(
+ ["15", "16", "17", "18", "19"],
+ 4,
+ 3,
+ 4,
+ 2,
+ );
- moveLexicographicallyTest(
- ["0", "1", "2", "3", "4", "5"],
- 1,
- 3,
- 3,
- 1,
- );
- });
-
- it("test moving left into no right space", () => {
- moveLexicographicallyTest(
- ["15", "16", "17", "18", "19"],
- 4,
- 3,
- 4,
- 2,
- );
-
- moveLexicographicallyTest(
- [
- DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 5),
- DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 4),
- DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 3),
- DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 2),
- DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 1),
- ],
- 4,
- 3,
- 4,
- 1,
- );
- });
-
- const prev = (str: string) => baseToString(stringToBase(str) - BigInt(1));
- const next = (str: string) => baseToString(stringToBase(str) + BigInt(1));
-
- it("baseN calculation is correctly consecutive", () => {
- const str = "this-is-a-test";
- expect(next(prev(str))).toBe(str);
- });
-
- it("rolls over sanely", () => {
- const maxSpaceValue = "~".repeat(50);
- const fiftyFirstChar = " ".repeat(51);
- expect(next(maxSpaceValue)).toBe(fiftyFirstChar);
- expect(prev(fiftyFirstChar)).toBe(maxSpaceValue);
- expect(Number(stringToBase(DEFAULT_ALPHABET[0]))).toEqual(1);
- expect(Number(stringToBase(DEFAULT_ALPHABET[1]))).toEqual(2);
- expect(DEFAULT_ALPHABET[DEFAULT_ALPHABET.length - 1]).toBe("~");
- expect(DEFAULT_ALPHABET[0]).toBe(" ");
+ moveLexicographicallyTest(
+ [
+ DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 5),
+ DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 4),
+ DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 3),
+ DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 2),
+ DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 1),
+ ],
+ 4,
+ 3,
+ 4,
+ 1,
+ );
+ });
});
});
From d4e376201f986223c16932229598c7e45a032c82 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Wed, 16 Jun 2021 09:44:37 +0100
Subject: [PATCH 046/100] Break down the SpacePanel component
---
src/components/views/spaces/SpacePanel.tsx | 134 ++++++++++++---------
1 file changed, 76 insertions(+), 58 deletions(-)
diff --git a/src/components/views/spaces/SpacePanel.tsx b/src/components/views/spaces/SpacePanel.tsx
index 27f097e9d4..2e3bfd157a 100644
--- a/src/components/views/spaces/SpacePanel.tsx
+++ b/src/components/views/spaces/SpacePanel.tsx
@@ -14,18 +14,18 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React, { useEffect, useState } from "react";
+import React, { Dispatch, ReactNode, SetStateAction, useEffect, useState } from "react";
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
import classNames from "classnames";
import { Room } from "matrix-js-sdk/src/models/room";
-import {_t} from "../../../languageHandler";
+import { _t } from "../../../languageHandler";
import RoomAvatar from "../avatars/RoomAvatar";
-import {useContextMenu} from "../../structures/ContextMenu";
+import { useContextMenu } from "../../structures/ContextMenu";
import SpaceCreateMenu from "./SpaceCreateMenu";
-import {SpaceItem} from "./SpaceTreeLevel";
+import { SpaceItem } from "./SpaceTreeLevel";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
-import {useEventEmitter} from "../../../hooks/useEventEmitter";
+import { useEventEmitter } from "../../../hooks/useEventEmitter";
import SpaceStore, {
UPDATE_INVITED_SPACES,
UPDATE_SELECTED_SPACE,
@@ -38,9 +38,9 @@ import {
RovingAccessibleTooltipButton,
RovingTabIndexProvider,
} from "../../../accessibility/RovingTabIndex";
-import {Key} from "../../../Keyboard";
-import {RoomNotificationStateStore} from "../../../stores/notifications/RoomNotificationStateStore";
-import {NotificationState} from "../../../stores/notifications/NotificationState";
+import { Key } from "../../../Keyboard";
+import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore";
+import { NotificationState } from "../../../stores/notifications/NotificationState";
interface IButtonProps {
space?: Room;
@@ -121,11 +121,62 @@ const useSpaces = (): [Room[], Room[], Room | null] => {
return [invites, spaces, activeSpace];
};
+interface IInnerSpacePanelProps {
+ children?: ReactNode;
+ isPanelCollapsed: boolean;
+ setPanelCollapsed: Dispatch>;
+}
+
+// Optimisation based on https://github.com/atlassian/react-beautiful-dnd/blob/master/docs/api/droppable.md#recommended-droppable--performance-optimisation
+const InnerSpacePanel = React.memo(({ children, isPanelCollapsed, setPanelCollapsed }) => {
+ const [invites, spaces, activeSpace] = useSpaces();
+ const activeSpaces = activeSpace ? [activeSpace] : [];
+
+ return
+ SpaceStore.instance.setActiveSpace(null)}
+ selected={!activeSpace}
+ tooltip={_t("All rooms")}
+ notificationState={RoomNotificationStateStore.instance.globalState}
+ isNarrow={isPanelCollapsed}
+ />
+ { invites.map(s => (
+ setPanelCollapsed(false)}
+ />
+ )) }
+ { spaces.map((s, i) => (
+
+ {(provided, snapshot) => (
+ setPanelCollapsed(false)}
+ />
+ )}
+
+ )) }
+ { children }
+
;
+});
+
const SpacePanel = () => {
// We don't need the handle as we position the menu in a constant location
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu();
- const [invites, spaces, activeSpace] = useSpaces();
const [isPanelCollapsed, setPanelCollapsed] = useState(true);
useEffect(() => {
@@ -134,10 +185,6 @@ const SpacePanel = () => {
}
}, [isPanelCollapsed]); // eslint-disable-line react-hooks/exhaustive-deps
- const newClasses = classNames("mx_SpaceButton_new", {
- mx_SpaceButton_newCancel: menuDisplayed,
- });
-
let contextMenu = null;
if (menuDisplayed) {
contextMenu = ;
@@ -204,7 +251,11 @@ const SpacePanel = () => {
}
};
- const activeSpaces = activeSpace ? [activeSpace] : [];
+ const onNewClick = menuDisplayed ? closeMenu : () => {
+ if (!isPanelCollapsed) setPanelCollapsed(true);
+ openMenu();
+ };
+
return (
{
if (!result.destination) return; // dropped outside the list
@@ -226,59 +277,26 @@ const SpacePanel = () => {
pointerEvents: "none",
} : undefined}
>
-
- SpaceStore.instance.setActiveSpace(null)}
- selected={!activeSpace}
- tooltip={_t("All rooms")}
- notificationState={RoomNotificationStateStore.instance.globalState}
- isNarrow={isPanelCollapsed}
- />
- { invites.map(s => (
- setPanelCollapsed(false)}
- />
- )) }
- { spaces.map((s, i) => (
-
- {(provided, snapshot) => (
- setPanelCollapsed(false)}
- />
- )}
-
- )) }
+
{ provided.placeholder }
-
+
+
{
- if (!isPanelCollapsed) setPanelCollapsed(true);
- openMenu();
- }}
+ onClick={onNewClick}
isNarrow={isPanelCollapsed}
/>
)}
setPanelCollapsed(!isPanelCollapsed)}
title={isPanelCollapsed ? _t("Expand space panel") : _t("Collapse space panel")}
/>
From 27e27b7a871f6eeb4fb37f9ee2d9dc2560442535 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Wed, 16 Jun 2021 10:18:32 +0100
Subject: [PATCH 047/100] Convert MultiInviter, RoomInvite and UserAddress to
Typescript
---
src/{RoomInvite.js => RoomInvite.tsx} | 44 +++---
src/{UserAddress.js => UserAddress.ts} | 35 +++--
.../{MultiInviter.js => MultiInviter.ts} | 143 +++++++++++-------
3 files changed, 127 insertions(+), 95 deletions(-)
rename src/{RoomInvite.js => RoomInvite.tsx} (76%)
rename src/{UserAddress.js => UserAddress.ts} (69%)
rename src/utils/{MultiInviter.js => MultiInviter.ts} (66%)
diff --git a/src/RoomInvite.js b/src/RoomInvite.tsx
similarity index 76%
rename from src/RoomInvite.js
rename to src/RoomInvite.tsx
index aa758ecbdc..7c75b5d46b 100644
--- a/src/RoomInvite.js
+++ b/src/RoomInvite.tsx
@@ -1,7 +1,5 @@
/*
-Copyright 2016 OpenMarket Ltd
-Copyright 2017, 2018 New Vector Ltd
-Copyright 2020 The Matrix.org Foundation C.I.C.
+Copyright 2016 - 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.
@@ -16,15 +14,18 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React from 'react';
-import {MatrixClientPeg} from './MatrixClientPeg';
-import MultiInviter from './utils/MultiInviter';
+import React from "react";
+import { Room } from "matrix-js-sdk/src/models/room";
+import { MatrixEvent } from "matrix-js-sdk/src/models/event";
+
+import { MatrixClientPeg } from './MatrixClientPeg';
+import MultiInviter, { CompletionStates } from './utils/MultiInviter';
import Modal from './Modal';
import * as sdk from './';
import { _t } from './languageHandler';
-import InviteDialog, {KIND_DM, KIND_INVITE} from "./components/views/dialogs/InviteDialog";
+import InviteDialog, { KIND_DM, KIND_INVITE } from "./components/views/dialogs/InviteDialog";
import CommunityPrototypeInviteDialog from "./components/views/dialogs/CommunityPrototypeInviteDialog";
-import {CommunityPrototypeStore} from "./stores/CommunityPrototypeStore";
+import { CommunityPrototypeStore } from "./stores/CommunityPrototypeStore";
/**
* Invites multiple addresses to a room
@@ -32,15 +33,18 @@ import {CommunityPrototypeStore} from "./stores/CommunityPrototypeStore";
* no option to cancel.
*
* @param {string} roomId The ID of the room to invite to
- * @param {string[]} addrs Array of strings of addresses to invite. May be matrix IDs or 3pids.
+ * @param {string[]} addresses Array of strings of addresses to invite. May be matrix IDs or 3pids.
* @returns {Promise} Promise
*/
-export function inviteMultipleToRoom(roomId, addrs) {
+export function inviteMultipleToRoom(
+ roomId: string,
+ addresses: string[],
+): Promise<{ states: CompletionStates, inviter: MultiInviter }> {
const inviter = new MultiInviter(roomId);
- return inviter.invite(addrs).then(states => Promise.resolve({states, inviter}));
+ return inviter.invite(addresses).then(states => Promise.resolve({ states, inviter }));
}
-export function showStartChatInviteDialog(initialText) {
+export function showStartChatInviteDialog(initialText = ""): void {
// This dialog handles the room creation internally - we don't need to worry about it.
const InviteDialog = sdk.getComponent("dialogs.InviteDialog");
Modal.createTrackedDialog(
@@ -49,7 +53,7 @@ export function showStartChatInviteDialog(initialText) {
);
}
-export function showRoomInviteDialog(roomId, initialText = "") {
+export function showRoomInviteDialog(roomId: string, initialText = ""): void {
// This dialog handles the room creation internally - we don't need to worry about it.
Modal.createTrackedDialog(
"Invite Users", "", InviteDialog, {
@@ -61,14 +65,14 @@ export function showRoomInviteDialog(roomId, initialText = "") {
);
}
-export function showCommunityRoomInviteDialog(roomId, communityName) {
+export function showCommunityRoomInviteDialog(roomId: string, communityName: string): void {
Modal.createTrackedDialog(
'Invite Users to Community', '', CommunityPrototypeInviteDialog, {communityName, roomId},
/*className=*/null, /*isPriority=*/false, /*isStatic=*/true,
);
}
-export function showCommunityInviteDialog(communityId) {
+export function showCommunityInviteDialog(communityId: string): void {
const chat = CommunityPrototypeStore.instance.getGeneralChat(communityId);
if (chat) {
const name = CommunityPrototypeStore.instance.getCommunityName(communityId);
@@ -83,7 +87,7 @@ export function showCommunityInviteDialog(communityId) {
* @param {MatrixEvent} event The event to check
* @returns {boolean} True if valid, false otherwise
*/
-export function isValid3pidInvite(event) {
+export function isValid3pidInvite(event: MatrixEvent): boolean {
if (!event || event.getType() !== "m.room.third_party_invite") return false;
// any events without these keys are not valid 3pid invites, so we ignore them
@@ -96,7 +100,7 @@ export function isValid3pidInvite(event) {
return true;
}
-export function inviteUsersToRoom(roomId, userIds) {
+export function inviteUsersToRoom(roomId: string, userIds: string[]): Promise {
return inviteMultipleToRoom(roomId, userIds).then((result) => {
const room = MatrixClientPeg.get().getRoom(roomId);
showAnyInviteErrors(result.states, room, result.inviter);
@@ -110,9 +114,9 @@ export function inviteUsersToRoom(roomId, userIds) {
});
}
-export function showAnyInviteErrors(addrs, room, inviter) {
+export function showAnyInviteErrors(states: CompletionStates, room: Room, inviter: MultiInviter): boolean {
// Show user any errors
- const failedUsers = Object.keys(addrs).filter(a => addrs[a] === 'error');
+ const failedUsers = Object.keys(states).filter(a => states[a] === 'error');
if (failedUsers.length === 1 && inviter.fatal) {
// Just get the first message because there was a fatal problem on the first
// user. This usually means that no other users were attempted, making it
@@ -126,7 +130,7 @@ export function showAnyInviteErrors(addrs, room, inviter) {
} else {
const errorList = [];
for (const addr of failedUsers) {
- if (addrs[addr] === "error") {
+ if (states[addr] === "error") {
const reason = inviter.getErrorText(addr);
errorList.push(addr + ": " + reason);
}
diff --git a/src/UserAddress.js b/src/UserAddress.ts
similarity index 69%
rename from src/UserAddress.js
rename to src/UserAddress.ts
index e7501a0d91..a2c546deb7 100644
--- a/src/UserAddress.js
+++ b/src/UserAddress.ts
@@ -1,5 +1,5 @@
/*
-Copyright 2017 New Vector Ltd
+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.
@@ -14,15 +14,19 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-const emailRegex = /^\S+@\S+\.\S+$/;
+import PropTypes from "prop-types";
+const emailRegex = /^\S+@\S+\.\S+$/;
const mxUserIdRegex = /^@\S+:\S+$/;
const mxRoomIdRegex = /^!\S+:\S+$/;
-import PropTypes from 'prop-types';
-export const addressTypes = [
- 'mx-user-id', 'mx-room-id', 'email',
-];
+export const addressTypes = ['mx-user-id', 'mx-room-id', 'email'];
+
+export enum AddressType {
+ Email = "email",
+ MatrixUserId = "mx-user-id",
+ MatrixRoomId = "mx-room-id",
+}
// PropType definition for an object describing
// an address that can be invited to a room (which
@@ -40,18 +44,13 @@ export const UserAddressType = PropTypes.shape({
isKnown: PropTypes.bool,
});
-export function getAddressType(inputText) {
- const isEmailAddress = emailRegex.test(inputText);
- const isUserId = mxUserIdRegex.test(inputText);
- const isRoomId = mxRoomIdRegex.test(inputText);
-
- // sanity check the input for user IDs
- if (isEmailAddress) {
- return 'email';
- } else if (isUserId) {
- return 'mx-user-id';
- } else if (isRoomId) {
- return 'mx-room-id';
+export function getAddressType(inputText: string): AddressType | null {
+ if (emailRegex.test(inputText)) {
+ return AddressType.Email;
+ } else if (mxUserIdRegex.test(inputText)) {
+ return AddressType.MatrixUserId;
+ } else if (mxRoomIdRegex.test(inputText)) {
+ return AddressType.MatrixRoomId;
} else {
return null;
}
diff --git a/src/utils/MultiInviter.js b/src/utils/MultiInviter.ts
similarity index 66%
rename from src/utils/MultiInviter.js
rename to src/utils/MultiInviter.ts
index 78f956b91b..f6a994484e 100644
--- a/src/utils/MultiInviter.js
+++ b/src/utils/MultiInviter.ts
@@ -1,6 +1,5 @@
/*
-Copyright 2016 OpenMarket Ltd
-Copyright 2017, 2018 New Vector Ltd
+Copyright 2016 - 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.
@@ -15,23 +14,51 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import {MatrixClientPeg} from '../MatrixClientPeg';
-import {getAddressType} from '../UserAddress';
+import { MatrixError } from "matrix-js-sdk/src/http-api";
+
+import { MatrixClientPeg } from '../MatrixClientPeg';
+import { AddressType, getAddressType } from '../UserAddress';
import GroupStore from '../stores/GroupStore';
-import {_t} from "../languageHandler";
-import * as sdk from "../index";
+import { _t } from "../languageHandler";
import Modal from "../Modal";
import SettingsStore from "../settings/SettingsStore";
-import {defer} from "./promise";
+import { defer, IDeferred } from "./promise";
+import AskInviteAnywayDialog from "../components/views/dialogs/AskInviteAnywayDialog";
+
+export enum InviteState {
+ Invited = "invited",
+ Error = "error",
+}
+
+interface IError {
+ errorText: string;
+ errcode: string;
+}
+
+const UNKNOWN_PROFILE_ERRORS = ['M_NOT_FOUND', 'M_USER_NOT_FOUND', 'M_PROFILE_UNDISCLOSED', 'M_PROFILE_NOT_FOUND'];
+
+export type CompletionStates = Record;
/**
* Invites multiple addresses to a room or group, handling rate limiting from the server
*/
export default class MultiInviter {
+ private readonly roomId?: string;
+ private readonly groupId?: string;
+
+ private canceled = false;
+ private addresses: string[] = [];
+ private busy = false;
+ private _fatal = false;
+ private completionStates: CompletionStates = {}; // State of each address (invited or error)
+ private errors: Record = {}; // { address: {errorText, errcode} }
+ private deferred: IDeferred = null;
+ private reason: string = null;
+
/**
* @param {string} targetId The ID of the room or group to invite to
*/
- constructor(targetId) {
+ constructor(targetId: string) {
if (targetId[0] === '+') {
this.roomId = null;
this.groupId = targetId;
@@ -39,41 +66,38 @@ export default class MultiInviter {
this.roomId = targetId;
this.groupId = null;
}
+ }
- this.canceled = false;
- this.addrs = [];
- this.busy = false;
- this.completionStates = {}; // State of each address (invited or error)
- this.errors = {}; // { address: {errorText, errcode} }
- this.deferred = null;
+ public get fatal() {
+ return this._fatal;
}
/**
* Invite users to this room. This may only be called once per
* instance of the class.
*
- * @param {array} addrs Array of addresses to invite
+ * @param {array} addresses Array of addresses to invite
* @param {string} reason Reason for inviting (optional)
* @returns {Promise} Resolved when all invitations in the queue are complete
*/
- invite(addrs, reason) {
- if (this.addrs.length > 0) {
+ public invite(addresses, reason?: string): Promise {
+ if (this.addresses.length > 0) {
throw new Error("Already inviting/invited");
}
- this.addrs.push(...addrs);
+ this.addresses.push(...addresses);
this.reason = reason;
- for (const addr of this.addrs) {
+ for (const addr of this.addresses) {
if (getAddressType(addr) === null) {
- this.completionStates[addr] = 'error';
+ this.completionStates[addr] = InviteState.Error;
this.errors[addr] = {
errcode: 'M_INVALID',
errorText: _t('Unrecognised address'),
};
}
}
- this.deferred = defer();
- this._inviteMore(0);
+ this.deferred = defer();
+ this.inviteMore(0);
return this.deferred.promise;
}
@@ -81,33 +105,36 @@ export default class MultiInviter {
/**
* Stops inviting. Causes promises returned by invite() to be rejected.
*/
- cancel() {
+ public cancel(): void {
if (!this.busy) return;
- this._canceled = true;
+ this.canceled = true;
this.deferred.reject(new Error('canceled'));
}
- getCompletionState(addr) {
+ public getCompletionState(addr: string): InviteState {
return this.completionStates[addr];
}
- getErrorText(addr) {
+ public getErrorText(addr: string): string {
return this.errors[addr] ? this.errors[addr].errorText : null;
}
- async _inviteToRoom(roomId, addr, ignoreProfile) {
+ private async inviteToRoom(roomId: string, addr: string, ignoreProfile = false): Promise<{}> {
const addrType = getAddressType(addr);
- if (addrType === 'email') {
+ if (addrType === AddressType.Email) {
return MatrixClientPeg.get().inviteByEmail(roomId, addr);
- } else if (addrType === 'mx-user-id') {
+ } else if (addrType === AddressType.MatrixUserId) {
const room = MatrixClientPeg.get().getRoom(roomId);
if (!room) throw new Error("Room not found");
const member = room.getMember(addr);
if (member && ['join', 'invite'].includes(member.membership)) {
- throw {errcode: "RIOT.ALREADY_IN_ROOM", error: "Member already invited"};
+ throw new new MatrixError({
+ errcode: "RIOT.ALREADY_IN_ROOM",
+ error: "Member already invited",
+ });
}
if (!ignoreProfile && SettingsStore.getValue("promptBeforeInviteUnknownUsers", this.roomId)) {
@@ -124,28 +151,28 @@ export default class MultiInviter {
}
}
- _doInvite(address, ignoreProfile) {
- return new Promise((resolve, reject) => {
+ private doInvite(address: string, ignoreProfile = false): Promise {
+ return new Promise((resolve, reject) => {
console.log(`Inviting ${address}`);
let doInvite;
if (this.groupId !== null) {
doInvite = GroupStore.inviteUserToGroup(this.groupId, address);
} else {
- doInvite = this._inviteToRoom(this.roomId, address, ignoreProfile);
+ doInvite = this.inviteToRoom(this.roomId, address, ignoreProfile);
}
doInvite.then(() => {
- if (this._canceled) {
+ if (this.canceled) {
return;
}
- this.completionStates[address] = 'invited';
+ this.completionStates[address] = InviteState.Invited;
delete this.errors[address];
resolve();
}).catch((err) => {
- if (this._canceled) {
+ if (this.canceled) {
return;
}
@@ -161,7 +188,7 @@ export default class MultiInviter {
} else if (err.errcode === 'M_LIMIT_EXCEEDED') {
// we're being throttled so wait a bit & try again
setTimeout(() => {
- this._doInvite(address, ignoreProfile).then(resolve, reject);
+ this.doInvite(address, ignoreProfile).then(resolve, reject);
}, 5000);
return;
} else if (['M_NOT_FOUND', 'M_USER_NOT_FOUND'].includes(err.errcode)) {
@@ -171,7 +198,7 @@ export default class MultiInviter {
} else if (err.errcode === 'M_PROFILE_NOT_FOUND' && !ignoreProfile) {
// Invite without the profile check
console.warn(`User ${address} does not have a profile - inviting anyways automatically`);
- this._doInvite(address, true).then(resolve, reject);
+ this.doInvite(address, true).then(resolve, reject);
} else if (err.errcode === "M_BAD_STATE") {
errorText = _t("The user must be unbanned before they can be invited.");
} else if (err.errcode === "M_UNSUPPORTED_ROOM_VERSION") {
@@ -180,14 +207,14 @@ export default class MultiInviter {
errorText = _t('Unknown server error');
}
- this.completionStates[address] = 'error';
- this.errors[address] = {errorText, errcode: err.errcode};
+ this.completionStates[address] = InviteState.Error;
+ this.errors[address] = { errorText, errcode: err.errcode };
this.busy = !fatal;
- this.fatal = fatal;
+ this._fatal = fatal;
if (fatal) {
- reject();
+ reject(err);
} else {
resolve();
}
@@ -195,22 +222,22 @@ export default class MultiInviter {
});
}
- _inviteMore(nextIndex, ignoreProfile) {
- if (this._canceled) {
+ private inviteMore(nextIndex: number, ignoreProfile = false): void {
+ if (this.canceled) {
return;
}
- if (nextIndex === this.addrs.length) {
+ if (nextIndex === this.addresses.length) {
this.busy = false;
if (Object.keys(this.errors).length > 0 && !this.groupId) {
// There were problems inviting some people - see if we can invite them
// without caring if they exist or not.
- const unknownProfileErrors = ['M_NOT_FOUND', 'M_USER_NOT_FOUND', 'M_PROFILE_UNDISCLOSED', 'M_PROFILE_NOT_FOUND'];
- const unknownProfileUsers = Object.keys(this.errors).filter(a => unknownProfileErrors.includes(this.errors[a].errcode));
+ const unknownProfileUsers = Object.keys(this.errors)
+ .filter(a => UNKNOWN_PROFILE_ERRORS.includes(this.errors[a].errcode));
if (unknownProfileUsers.length > 0) {
const inviteUnknowns = () => {
- const promises = unknownProfileUsers.map(u => this._doInvite(u, true));
+ const promises = unknownProfileUsers.map(u => this.doInvite(u, true));
Promise.all(promises).then(() => this.deferred.resolve(this.completionStates));
};
@@ -219,15 +246,17 @@ export default class MultiInviter {
return;
}
- const AskInviteAnywayDialog = sdk.getComponent("dialogs.AskInviteAnywayDialog");
console.log("Showing failed to invite dialog...");
Modal.createTrackedDialog('Failed to invite', '', AskInviteAnywayDialog, {
- unknownProfileUsers: unknownProfileUsers.map(u => {return {userId: u, errorText: this.errors[u].errorText};}),
+ unknownProfileUsers: unknownProfileUsers.map(u => ({
+ userId: u,
+ errorText: this.errors[u].errorText,
+ })),
onInviteAnyways: () => inviteUnknowns(),
onGiveUp: () => {
// Fake all the completion states because we already warned the user
for (const addr of unknownProfileUsers) {
- this.completionStates[addr] = 'invited';
+ this.completionStates[addr] = InviteState.Invited;
}
this.deferred.resolve(this.completionStates);
},
@@ -239,25 +268,25 @@ export default class MultiInviter {
return;
}
- const addr = this.addrs[nextIndex];
+ const addr = this.addresses[nextIndex];
// don't try to invite it if it's an invalid address
// (it will already be marked as an error though,
// so no need to do so again)
if (getAddressType(addr) === null) {
- this._inviteMore(nextIndex + 1);
+ this.inviteMore(nextIndex + 1);
return;
}
// don't re-invite (there's no way in the UI to do this, but
// for sanity's sake)
- if (this.completionStates[addr] === 'invited') {
- this._inviteMore(nextIndex + 1);
+ if (this.completionStates[addr] === InviteState.Invited) {
+ this.inviteMore(nextIndex + 1);
return;
}
- this._doInvite(addr, ignoreProfile).then(() => {
- this._inviteMore(nextIndex + 1, ignoreProfile);
+ this.doInvite(addr, ignoreProfile).then(() => {
+ this.inviteMore(nextIndex + 1, ignoreProfile);
}).catch(() => this.deferred.resolve(this.completionStates));
}
}
From 47ddd33d2121ed8e74962c2ef02b1271837d6680 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Wed, 16 Jun 2021 10:24:51 +0100
Subject: [PATCH 048/100] Remove explicit `.js` imports
---
src/components/structures/MatrixChat.tsx | 2 +-
src/components/views/dialogs/AddressPickerDialog.js | 2 +-
src/components/views/elements/AddressTile.js | 6 +++---
src/components/views/messages/SenderProfile.tsx | 2 +-
4 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx
index 0af2d3d635..2cb0fbf3f6 100644
--- a/src/components/structures/MatrixChat.tsx
+++ b/src/components/structures/MatrixChat.tsx
@@ -48,7 +48,7 @@ import createRoom, {IOpts} from "../../createRoom";
import {_t, _td, getCurrentLanguage} from '../../languageHandler';
import SettingsStore from "../../settings/SettingsStore";
import ThemeController from "../../settings/controllers/ThemeController";
-import { startAnyRegistrationFlow } from "../../Registration.js";
+import { startAnyRegistrationFlow } from "../../Registration";
import { messageForSyncError } from '../../utils/ErrorUtils';
import ResizeNotifier from "../../utils/ResizeNotifier";
import AutoDiscoveryUtils, { ValidatedServerConfig } from "../../utils/AutoDiscoveryUtils";
diff --git a/src/components/views/dialogs/AddressPickerDialog.js b/src/components/views/dialogs/AddressPickerDialog.js
index 929d688e47..77c69abc4e 100644
--- a/src/components/views/dialogs/AddressPickerDialog.js
+++ b/src/components/views/dialogs/AddressPickerDialog.js
@@ -24,7 +24,7 @@ import { _t, _td } from '../../../languageHandler';
import * as sdk from '../../../index';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
import dis from '../../../dispatcher/dispatcher';
-import { addressTypes, getAddressType } from '../../../UserAddress.js';
+import { addressTypes, getAddressType } from '../../../UserAddress';
import GroupStore from '../../../stores/GroupStore';
import * as Email from '../../../email';
import IdentityAuthClient from '../../../IdentityAuthClient';
diff --git a/src/components/views/elements/AddressTile.js b/src/components/views/elements/AddressTile.js
index df66d10a71..f8fa294b71 100644
--- a/src/components/views/elements/AddressTile.js
+++ b/src/components/views/elements/AddressTile.js
@@ -20,9 +20,9 @@ import PropTypes from 'prop-types';
import classNames from 'classnames';
import * as sdk from "../../../index";
import { _t } from '../../../languageHandler';
-import { UserAddressType } from '../../../UserAddress.js';
-import {replaceableComponent} from "../../../utils/replaceableComponent";
-import {mediaFromMxc} from "../../../customisations/Media";
+import { UserAddressType } from '../../../UserAddress';
+import { replaceableComponent } from "../../../utils/replaceableComponent";
+import { mediaFromMxc } from "../../../customisations/Media";
@replaceableComponent("views.elements.AddressTile")
export default class AddressTile extends React.Component {
diff --git a/src/components/views/messages/SenderProfile.tsx b/src/components/views/messages/SenderProfile.tsx
index 805f842fbc..de1549dffa 100644
--- a/src/components/views/messages/SenderProfile.tsx
+++ b/src/components/views/messages/SenderProfile.tsx
@@ -15,7 +15,7 @@
*/
import React from 'react';
-import Flair from '../elements/Flair.js';
+import Flair from '../elements/Flair';
import FlairStore from '../../../stores/FlairStore';
import {getUserNameColorClass} from '../../../utils/FormattingUtils';
import MatrixClientContext from "../../../contexts/MatrixClientContext";
From 384bb3af2e6a43fb28f3c17a8cb7d2612121ba4a Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Wed, 16 Jun 2021 10:37:34 +0100
Subject: [PATCH 049/100] Fix layout regression in the invite dialog for few
results
---
res/css/views/dialogs/_InviteDialog.scss | 1 +
1 file changed, 1 insertion(+)
diff --git a/res/css/views/dialogs/_InviteDialog.scss b/res/css/views/dialogs/_InviteDialog.scss
index 2e48b5d8e9..175b1cc556 100644
--- a/res/css/views/dialogs/_InviteDialog.scss
+++ b/res/css/views/dialogs/_InviteDialog.scss
@@ -294,6 +294,7 @@ limitations under the License.
flex-direction: column;
.mx_InviteDialog_content {
+ height: 100%;
overflow: hidden;
}
}
From 590ce5674055f8094c69c7ec3d490e40747dcc5d Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Wed, 16 Jun 2021 11:48:14 +0100
Subject: [PATCH 050/100] Use MultiInviter error messages in InviteDialog for
room invites
---
src/RoomInvite.tsx | 10 +++++---
src/components/views/dialogs/InviteDialog.tsx | 25 ++++++++-----------
2 files changed, 16 insertions(+), 19 deletions(-)
diff --git a/src/RoomInvite.tsx b/src/RoomInvite.tsx
index 7c75b5d46b..16141a87e8 100644
--- a/src/RoomInvite.tsx
+++ b/src/RoomInvite.tsx
@@ -27,6 +27,11 @@ import InviteDialog, { KIND_DM, KIND_INVITE } from "./components/views/dialogs/I
import CommunityPrototypeInviteDialog from "./components/views/dialogs/CommunityPrototypeInviteDialog";
import { CommunityPrototypeStore } from "./stores/CommunityPrototypeStore";
+export interface IInviteResult {
+ states: CompletionStates;
+ inviter: MultiInviter;
+}
+
/**
* Invites multiple addresses to a room
* Simpler interface to utils/MultiInviter but with
@@ -36,10 +41,7 @@ import { CommunityPrototypeStore } from "./stores/CommunityPrototypeStore";
* @param {string[]} addresses Array of strings of addresses to invite. May be matrix IDs or 3pids.
* @returns {Promise} Promise
*/
-export function inviteMultipleToRoom(
- roomId: string,
- addresses: string[],
-): Promise<{ states: CompletionStates, inviter: MultiInviter }> {
+export function inviteMultipleToRoom(roomId: string, addresses: string[]): Promise {
const inviter = new MultiInviter(roomId);
return inviter.invite(addresses).then(states => Promise.resolve({ states, inviter }));
}
diff --git a/src/components/views/dialogs/InviteDialog.tsx b/src/components/views/dialogs/InviteDialog.tsx
index 778744b783..f50f2f23d6 100644
--- a/src/components/views/dialogs/InviteDialog.tsx
+++ b/src/components/views/dialogs/InviteDialog.tsx
@@ -34,7 +34,12 @@ import {humanizeTime} from "../../../utils/humanize";
import createRoom, {
canEncryptToAllUsers, ensureDMExists, findDMForUser, privateShouldBeEncrypted,
} from "../../../createRoom";
-import {inviteMultipleToRoom, showCommunityInviteDialog} from "../../../RoomInvite";
+import {
+ IInviteResult,
+ inviteMultipleToRoom,
+ showAnyInviteErrors,
+ showCommunityInviteDialog,
+} from "../../../RoomInvite";
import {Key} from "../../../Keyboard";
import {Action} from "../../../dispatcher/actions";
import {DefaultTagID} from "../../../stores/room-list/models";
@@ -601,19 +606,9 @@ export default class InviteDialog extends React.PureComponent ({userId: m.member.userId, user: m.member}));
}
- private shouldAbortAfterInviteError(result): boolean {
- const failedUsers = Object.keys(result.states).filter(a => result.states[a] === 'error');
- if (failedUsers.length > 0) {
- console.log("Failed to invite users: ", result);
- this.setState({
- busy: false,
- errorText: _t("Failed to invite the following users to chat: %(csvUsers)s", {
- csvUsers: failedUsers.join(", "),
- }),
- });
- return true; // abort
- }
- return false;
+ private shouldAbortAfterInviteError(result: IInviteResult, room: Room): boolean {
+ this.setState({ busy: false });
+ return !showAnyInviteErrors(result.states, room, result.inviter);
}
private convertFilter(): Member[] {
@@ -731,7 +726,7 @@ export default class InviteDialog extends React.PureComponent
Date: Wed, 16 Jun 2021 12:12:00 +0100
Subject: [PATCH 051/100] remove unused imports
---
test/utils/stringOrderField-test.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/test/utils/stringOrderField-test.ts b/test/utils/stringOrderField-test.ts
index ece3043d86..331627dfc0 100644
--- a/test/utils/stringOrderField-test.ts
+++ b/test/utils/stringOrderField-test.ts
@@ -15,7 +15,7 @@ limitations under the License.
*/
import { sortBy } from "lodash";
-import { stringToBase, baseToString, averageBetweenStrings, DEFAULT_ALPHABET } from "matrix-js-sdk/src/utils";
+import { averageBetweenStrings, DEFAULT_ALPHABET } from "matrix-js-sdk/src/utils";
import { midPointsBetweenStrings, reorderLexicographically } from "../../src/utils/stringOrderField";
From 100de336a1aaf944a2a5b03b3eb016703394457a Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Wed, 16 Jun 2021 20:12:31 +0100
Subject: [PATCH 052/100] i18n
---
src/i18n/strings/en_EN.json | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 17d6f64c46..57bf5fa739 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -2241,7 +2241,6 @@
"Confirm to continue": "Confirm to continue",
"Click the button below to confirm your identity.": "Click the button below to confirm your identity.",
"Invite by email": "Invite by email",
- "Failed to invite the following users to chat: %(csvUsers)s": "Failed to invite the following users to chat: %(csvUsers)s",
"We couldn't create your DM.": "We couldn't create your DM.",
"Something went wrong trying to invite the users.": "Something went wrong trying to invite the users.",
"We couldn't invite those users. Please check the users you want to invite and try again.": "We couldn't invite those users. Please check the users you want to invite and try again.",
From 79bf7bee560856be766493cd1a6dda8043bbe932 Mon Sep 17 00:00:00 2001
From: Robin Townsend
Date: Wed, 16 Jun 2021 18:23:44 -0400
Subject: [PATCH 053/100] Fix EventTilePreview display names
Because of 91df392a2a79383fa9a8a35cc9e4def6d3d4caab, we now need to
additionally set rawDisplayName to properly fake our display name for an
event.
Signed-off-by: Robin Townsend
---
src/components/views/elements/EventTilePreview.tsx | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/components/views/elements/EventTilePreview.tsx b/src/components/views/elements/EventTilePreview.tsx
index 20d6cbaeb3..d39557c9bb 100644
--- a/src/components/views/elements/EventTilePreview.tsx
+++ b/src/components/views/elements/EventTilePreview.tsx
@@ -102,6 +102,7 @@ export default class EventTilePreview extends React.Component {
// Fake it more
event.sender = {
name: this.props.displayName || this.props.userId,
+ rawDisplayName: this.props.displayName,
userId: this.props.userId,
getAvatarUrl: (..._) => {
return Avatar.avatarUrlForUser(
From be10e77704b1635dc514b7b98201b7ee11b24d10 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Sat, 19 Jun 2021 15:37:06 +0100
Subject: [PATCH 054/100] Improve typing of Event Index Manager / Seshat
---
src/indexing/BaseEventIndexManager.ts | 78 +++++++++++++--------------
src/indexing/EventIndex.ts | 16 +++---
2 files changed, 47 insertions(+), 47 deletions(-)
diff --git a/src/indexing/BaseEventIndexManager.ts b/src/indexing/BaseEventIndexManager.ts
index debcb213ca..9478b2987b 100644
--- a/src/indexing/BaseEventIndexManager.ts
+++ b/src/indexing/BaseEventIndexManager.ts
@@ -17,7 +17,7 @@ limitations under the License.
// The following interfaces take their names and member names from seshat and the spec
/* eslint-disable camelcase */
-export interface MatrixEvent {
+export interface IMatrixEvent {
type: string;
sender: string;
content: {};
@@ -27,37 +27,37 @@ export interface MatrixEvent {
roomId: string;
}
-export interface MatrixProfile {
+export interface IMatrixProfile {
avatar_url: string;
displayname: string;
}
-export interface CrawlerCheckpoint {
+export interface ICrawlerCheckpoint {
roomId: string;
token: string;
fullCrawl?: boolean;
direction: string;
}
-export interface ResultContext {
- events_before: [MatrixEvent];
- events_after: [MatrixEvent];
- profile_info: Map;
+export interface IResultContext {
+ events_before: [IMatrixEvent];
+ events_after: [IMatrixEvent];
+ profile_info: Map;
}
-export interface ResultsElement {
+export interface IResultsElement {
rank: number;
- result: MatrixEvent;
- context: ResultContext;
+ result: IMatrixEvent;
+ context: IResultContext;
}
-export interface SearchResult {
+export interface ISearchResult {
count: number;
- results: [ResultsElement];
+ results: [IResultsElement];
highlights: [string];
}
-export interface SearchArgs {
+export interface ISearchArgs {
search_term: string;
before_limit: number;
after_limit: number;
@@ -65,19 +65,19 @@ export interface SearchArgs {
room_id?: string;
}
-export interface EventAndProfile {
- event: MatrixEvent;
- profile: MatrixProfile;
+export interface IEventAndProfile {
+ event: IMatrixEvent;
+ profile: IMatrixProfile;
}
-export interface LoadArgs {
+export interface ILoadArgs {
roomId: string;
limit: number;
fromEvent?: string;
direction?: string;
}
-export interface IndexStats {
+export interface IIndexStats {
size: number;
eventCount: number;
roomCount: number;
@@ -119,13 +119,13 @@ export default abstract class BaseEventIndexManager {
* Queue up an event to be added to the index.
*
* @param {MatrixEvent} ev The event that should be added to the index.
- * @param {MatrixProfile} profile The profile of the event sender at the
+ * @param {IMatrixProfile} profile The profile of the event sender at the
* time of the event receival.
*
* @return {Promise} A promise that will resolve when the was queued up for
* addition.
*/
- async addEventToIndex(ev: MatrixEvent, profile: MatrixProfile): Promise {
+ async addEventToIndex(ev: IMatrixEvent, profile: IMatrixProfile): Promise {
throw new Error("Unimplemented");
}
@@ -160,10 +160,10 @@ export default abstract class BaseEventIndexManager {
/**
* Get statistical information of the index.
*
- * @return {Promise} A promise that will resolve to the index
+ * @return {Promise} A promise that will resolve to the index
* statistics.
*/
- async getStats(): Promise {
+ async getStats(): Promise {
throw new Error("Unimplemented");
}
@@ -203,13 +203,13 @@ export default abstract class BaseEventIndexManager {
/**
* Search the event index using the given term for matching events.
*
- * @param {SearchArgs} searchArgs The search configuration for the search,
+ * @param {ISearchArgs} searchArgs The search configuration for the search,
* sets the search term and determines the search result contents.
*
- * @return {Promise<[SearchResult]>} A promise that will resolve to an array
+ * @return {Promise<[ISearchResult]>} A promise that will resolve to an array
* of search results once the search is done.
*/
- async searchEventIndex(searchArgs: SearchArgs): Promise {
+ async searchEventIndex(searchArgs: ISearchArgs): Promise {
throw new Error("Unimplemented");
}
@@ -218,12 +218,12 @@ export default abstract class BaseEventIndexManager {
*
* This is used to add a batch of events to the index.
*
- * @param {[EventAndProfile]} events The list of events and profiles that
+ * @param {[IEventAndProfile]} events The list of events and profiles that
* should be added to the event index.
- * @param {[CrawlerCheckpoint]} checkpoint A new crawler checkpoint that
+ * @param {[ICrawlerCheckpoint]} checkpoint A new crawler checkpoint that
* should be stored in the index which should be used to continue crawling
* the room.
- * @param {[CrawlerCheckpoint]} oldCheckpoint The checkpoint that was used
+ * @param {[ICrawlerCheckpoint]} oldCheckpoint The checkpoint that was used
* to fetch the current batch of events. This checkpoint will be removed
* from the index.
*
@@ -231,9 +231,9 @@ export default abstract class BaseEventIndexManager {
* were already added to the index, false otherwise.
*/
async addHistoricEvents(
- events: [EventAndProfile],
- checkpoint: CrawlerCheckpoint | null,
- oldCheckpoint: CrawlerCheckpoint | null,
+ events: IEventAndProfile[],
+ checkpoint: ICrawlerCheckpoint | null,
+ oldCheckpoint: ICrawlerCheckpoint | null,
): Promise {
throw new Error("Unimplemented");
}
@@ -241,36 +241,36 @@ export default abstract class BaseEventIndexManager {
/**
* Add a new crawler checkpoint to the index.
*
- * @param {CrawlerCheckpoint} checkpoint The checkpoint that should be added
+ * @param {ICrawlerCheckpoint} checkpoint The checkpoint that should be added
* to the index.
*
* @return {Promise} A promise that will resolve once the checkpoint has
* been stored.
*/
- async addCrawlerCheckpoint(checkpoint: CrawlerCheckpoint): Promise {
+ async addCrawlerCheckpoint(checkpoint: ICrawlerCheckpoint): Promise {
throw new Error("Unimplemented");
}
/**
* Add a new crawler checkpoint to the index.
*
- * @param {CrawlerCheckpoint} checkpoint The checkpoint that should be
+ * @param {ICrawlerCheckpoint} checkpoint The checkpoint that should be
* removed from the index.
*
* @return {Promise} A promise that will resolve once the checkpoint has
* been removed.
*/
- async removeCrawlerCheckpoint(checkpoint: CrawlerCheckpoint): Promise {
+ async removeCrawlerCheckpoint(checkpoint: ICrawlerCheckpoint): Promise {
throw new Error("Unimplemented");
}
/**
* Load the stored checkpoints from the index.
*
- * @return {Promise<[CrawlerCheckpoint]>} A promise that will resolve to an
+ * @return {Promise<[ICrawlerCheckpoint]>} A promise that will resolve to an
* array of crawler checkpoints once they have been loaded from the index.
*/
- async loadCheckpoints(): Promise<[CrawlerCheckpoint]> {
+ async loadCheckpoints(): Promise {
throw new Error("Unimplemented");
}
@@ -286,11 +286,11 @@ export default abstract class BaseEventIndexManager {
* @param {string} args.direction The direction to which we should continue
* loading events from. This is used only if fromEvent is used as well.
*
- * @return {Promise<[EventAndProfile]>} A promise that will resolve to an
+ * @return {Promise<[IEventAndProfile]>} A promise that will resolve to an
* array of Matrix events that contain mxc URLs accompanied with the
* historic profile of the sender.
*/
- async loadFileEvents(args: LoadArgs): Promise<[EventAndProfile]> {
+ async loadFileEvents(args: ILoadArgs): Promise {
throw new Error("Unimplemented");
}
diff --git a/src/indexing/EventIndex.ts b/src/indexing/EventIndex.ts
index c36f96f368..978a2ac813 100644
--- a/src/indexing/EventIndex.ts
+++ b/src/indexing/EventIndex.ts
@@ -28,7 +28,7 @@ import { MatrixClientPeg } from "../MatrixClientPeg";
import { sleep } from "../utils/promise";
import SettingsStore from "../settings/SettingsStore";
import { SettingLevel } from "../settings/SettingLevel";
-import {CrawlerCheckpoint, LoadArgs, SearchArgs} from "./BaseEventIndexManager";
+import { ICrawlerCheckpoint, ILoadArgs, ISearchArgs } from "./BaseEventIndexManager";
// The time in ms that the crawler will wait loop iterations if there
// have not been any checkpoints to consume in the last iteration.
@@ -45,9 +45,9 @@ interface ICrawler {
* Event indexing class that wraps the platform specific event indexing.
*/
export default class EventIndex extends EventEmitter {
- private crawlerCheckpoints: CrawlerCheckpoint[] = [];
+ private crawlerCheckpoints: ICrawlerCheckpoint[] = [];
private crawler: ICrawler = null;
- private currentCheckpoint: CrawlerCheckpoint = null;
+ private currentCheckpoint: ICrawlerCheckpoint = null;
public async init() {
const indexManager = PlatformPeg.get().getEventIndexingManager();
@@ -111,14 +111,14 @@ export default class EventIndex extends EventEmitter {
const timeline = room.getLiveTimeline();
const token = timeline.getPaginationToken("b");
- const backCheckpoint: CrawlerCheckpoint = {
+ const backCheckpoint: ICrawlerCheckpoint = {
roomId: room.roomId,
token: token,
direction: "b",
fullCrawl: true,
};
- const forwardCheckpoint: CrawlerCheckpoint = {
+ const forwardCheckpoint: ICrawlerCheckpoint = {
roomId: room.roomId,
token: token,
direction: "f",
@@ -668,13 +668,13 @@ export default class EventIndex extends EventEmitter {
/**
* Search the event index using the given term for matching events.
*
- * @param {SearchArgs} searchArgs The search configuration for the search,
+ * @param {ISearchArgs} searchArgs The search configuration for the search,
* sets the search term and determines the search result contents.
*
* @return {Promise<[SearchResult]>} A promise that will resolve to an array
* of search results once the search is done.
*/
- public async search(searchArgs: SearchArgs) {
+ public async search(searchArgs: ISearchArgs) {
const indexManager = PlatformPeg.get().getEventIndexingManager();
return indexManager.searchEventIndex(searchArgs);
}
@@ -709,7 +709,7 @@ export default class EventIndex extends EventEmitter {
const client = MatrixClientPeg.get();
const indexManager = PlatformPeg.get().getEventIndexingManager();
- const loadArgs: LoadArgs = {
+ const loadArgs: ILoadArgs = {
roomId: room.roomId,
limit: limit,
};
From 8e2a7cc3f6badace1d1cb0dacb90d449689ac8cd Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Sat, 19 Jun 2021 19:41:45 +0100
Subject: [PATCH 055/100] Convert crypto index to TS
---
src/rageshake/submit-rageshake.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/rageshake/submit-rageshake.ts b/src/rageshake/submit-rageshake.ts
index 08d8ccfd13..64d7405f17 100644
--- a/src/rageshake/submit-rageshake.ts
+++ b/src/rageshake/submit-rageshake.ts
@@ -86,8 +86,8 @@ async function collectBugReport(opts: IOpts = {}, gzipLogs = true) {
body.append('cross_signing_key', client.getCrossSigningId());
// add cross-signing status information
- const crossSigning = client.crypto._crossSigningInfo;
- const secretStorage = client.crypto._secretStorage;
+ const crossSigning = client.crypto.crossSigningInfo;
+ const secretStorage = client.crypto.secretStorage;
body.append("cross_signing_ready", String(await client.isCrossSigningReady()));
body.append("cross_signing_supported_by_hs",
From 9756a99220a34a1a4bae9c0b15c14871ef161818 Mon Sep 17 00:00:00 2001
From: Germain Souquet
Date: Mon, 21 Jun 2021 12:14:30 +0100
Subject: [PATCH 056/100] Migrate TruncatedList to TypeScript
---
.../{TruncatedList.js => TruncatedList.tsx} | 50 +++++++++----------
1 file changed, 24 insertions(+), 26 deletions(-)
rename src/components/views/elements/{TruncatedList.js => TruncatedList.tsx} (65%)
diff --git a/src/components/views/elements/TruncatedList.js b/src/components/views/elements/TruncatedList.tsx
similarity index 65%
rename from src/components/views/elements/TruncatedList.js
rename to src/components/views/elements/TruncatedList.tsx
index 0509775545..395caa9222 100644
--- a/src/components/views/elements/TruncatedList.js
+++ b/src/components/views/elements/TruncatedList.tsx
@@ -16,31 +16,29 @@ limitations under the License.
*/
import React from 'react';
-import PropTypes from 'prop-types';
import { _t } from '../../../languageHandler';
import {replaceableComponent} from "../../../utils/replaceableComponent";
-@replaceableComponent("views.elements.TruncatedList")
-export default class TruncatedList extends React.Component {
- static propTypes = {
- // The number of elements to show before truncating. If negative, no truncation is done.
- truncateAt: PropTypes.number,
- // The className to apply to the wrapping div
- className: PropTypes.string,
- // A function that returns the children to be rendered into the element.
- // function getChildren(start: number, end: number): Array
- // The start element is included, the end is not (as in `slice`).
- // If omitted, the React child elements will be used. This parameter can be used
- // to avoid creating unnecessary React elements.
- getChildren: PropTypes.func,
- // A function that should return the total number of child element available.
- // Required if getChildren is supplied.
- getChildCount: PropTypes.func,
- // A function which will be invoked when an overflow element is required.
- // This will be inserted after the children.
- createOverflowElement: PropTypes.func,
- };
+interface IProps {
+ // The number of elements to show before truncating. If negative, no truncation is done.
+ truncateAt?: number;
+ // The className to apply to the wrapping div
+ className?: string;
+ // A function that returns the children to be rendered into the element.
+ // The start element is included, the end is not (as in `slice`).
+ // If omitted, the React child elements will be used. This parameter can be used
+ // to avoid creating unnecessary React elements.
+ getChildren?: (start: number, end: number) => Array;
+ // A function that should return the total number of child element available.
+ // Required if getChildren is supplied.
+ getChildCount?: () => number;
+ // A function which will be invoked when an overflow element is required.
+ // This will be inserted after the children.
+ createOverflowElement?: (overflowCount: number, totalCount: number) => React.ReactNode;
+}
+@replaceableComponent("views.elements.TruncatedList")
+export default class TruncatedList extends React.Component {
static defaultProps ={
truncateAt: 2,
createOverflowElement(overflowCount, totalCount) {
@@ -50,7 +48,7 @@ export default class TruncatedList extends React.Component {
},
};
- _getChildren(start, end) {
+ private getChildren(start: number, end: number): Array {
if (this.props.getChildren && this.props.getChildCount) {
return this.props.getChildren(start, end);
} else {
@@ -63,7 +61,7 @@ export default class TruncatedList extends React.Component {
}
}
- _getChildCount() {
+ private getChildCount(): number {
if (this.props.getChildren && this.props.getChildCount) {
return this.props.getChildCount();
} else {
@@ -73,10 +71,10 @@ export default class TruncatedList extends React.Component {
}
}
- render() {
+ public render() {
let overflowNode = null;
- const totalChildren = this._getChildCount();
+ const totalChildren = this.getChildCount();
let upperBound = totalChildren;
if (this.props.truncateAt >= 0) {
const overflowCount = totalChildren - this.props.truncateAt;
@@ -87,7 +85,7 @@ export default class TruncatedList extends React.Component {
upperBound = this.props.truncateAt;
}
}
- const childNodes = this._getChildren(0, upperBound);
+ const childNodes = this.getChildren(0, upperBound);
return (
From d2595dcd61876d9f1def0b5e197918588692c07b Mon Sep 17 00:00:00 2001
From: Germain Souquet
Date: Mon, 21 Jun 2021 12:29:59 +0100
Subject: [PATCH 057/100] use TruncatedList to improve ForwardDialog rendering
time
---
.../views/dialogs/ForwardDialog.tsx | 37 ++++++++++++++-----
1 file changed, 28 insertions(+), 9 deletions(-)
diff --git a/src/components/views/dialogs/ForwardDialog.tsx b/src/components/views/dialogs/ForwardDialog.tsx
index a83f3f177c..b04fd9ef76 100644
--- a/src/components/views/dialogs/ForwardDialog.tsx
+++ b/src/components/views/dialogs/ForwardDialog.tsx
@@ -39,6 +39,9 @@ import NotificationBadge from "../rooms/NotificationBadge";
import {RoomPermalinkCreator} from "../../../utils/permalinks/Permalinks";
import {sortRooms} from "../../../stores/room-list/algorithms/tag-sorting/RecentAlgorithm";
import QueryMatcher from "../../../autocomplete/QueryMatcher";
+import TruncatedList from "../elements/TruncatedList";
+import EntityTile from "../rooms/EntityTile";
+import BaseAvatar from "../avatars/BaseAvatar";
const AVATAR_SIZE = 30;
@@ -195,6 +198,17 @@ const ForwardDialog: React.FC = ({ matrixClient: cli, event, permalinkCr
}).match(lcQuery);
}
+ const [truncateAt, setTruncateAt] = useState(20);
+ function overflowTile(overflowCount, totalCount) {
+ const text = _t("and %(count)s others...", { count: overflowCount });
+ return (
+
+ } name={text} presenceState="online" suppressOnHover={true}
+ onClick={() => setTruncateAt(totalCount)} />
+ );
+ }
+
return = ({ matrixClient: cli, event, permalinkCr
{ rooms.length > 0 ? (
- { rooms.map(room =>
- ,
- ) }
+ rooms.slice(start, end).map(room =>
+ ,
+ )}
+ getChildCount={() => rooms.length}
+ />
) :
{ _t("No results") }
From de4065719475d6f9d6214406ffbbbfd2a393f1c5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=A0imon=20Brandner?=
Date: Tue, 22 Jun 2021 09:12:41 +0200
Subject: [PATCH 058/100] Don't show room if we don't click on buttons
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Šimon Brandner
---
src/components/structures/RoomDirectory.tsx | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/src/components/structures/RoomDirectory.tsx b/src/components/structures/RoomDirectory.tsx
index 1e0605f263..eb7208f98d 100644
--- a/src/components/structures/RoomDirectory.tsx
+++ b/src/components/structures/RoomDirectory.tsx
@@ -337,11 +337,10 @@ export default class RoomDirectory extends React.Component {
}
private onRoomClicked = (room: IRoom, ev: ButtonEvent) => {
+ // If room was shift-clicked, remove it from the room directory
if (ev.shiftKey && !this.state.selectedCommunityId) {
ev.preventDefault();
this.removeFromDirectory(room);
- } else {
- this.showRoom(room);
}
};
From a59deeb49165429acc91032280bf8205d2b013e7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=A0imon=20Brandner?=
Date: Tue, 22 Jun 2021 09:16:45 +0200
Subject: [PATCH 059/100] Add onClick handlers
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Šimon Brandner
---
src/components/structures/RoomDirectory.tsx | 19 +++++++++++++++----
1 file changed, 15 insertions(+), 4 deletions(-)
diff --git a/src/components/structures/RoomDirectory.tsx b/src/components/structures/RoomDirectory.tsx
index eb7208f98d..731e2387cc 100644
--- a/src/components/structures/RoomDirectory.tsx
+++ b/src/components/structures/RoomDirectory.tsx
@@ -589,12 +589,23 @@ export default class RoomDirectory extends React.Component {
onMouseDown={(ev) => {ev.preventDefault();}}
className="mx_RoomDirectory_roomDescription"
>
- { name }
- { ev.stopPropagation(); } }
+
this.onRoomClicked(room, ev)}
+ >
+ { name }
+
+
this.onRoomClicked(room, ev)}
dangerouslySetInnerHTML={{ __html: topic }}
/>
-
{ getDisplayAliasForRoom(room) }
+
this.onRoomClicked(room, ev)}
+ >
+ { getDisplayAliasForRoom(room) }
+
,
this.onRoomClicked(room, ev)}
From deb075777d4d59cefff26c98c7149651eb729540 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 22 Jun 2021 08:17:09 +0100
Subject: [PATCH 060/100] Upgrade @types/react and @types/react-dom
---
package.json | 4 ++--
yarn.lock | 26 ++++++++++++++++++++------
2 files changed, 22 insertions(+), 8 deletions(-)
diff --git a/package.json b/package.json
index f232d4301b..8ebb90f342 100644
--- a/package.json
+++ b/package.json
@@ -132,8 +132,8 @@
"@types/pako": "^1.0.1",
"@types/parse5": "^6.0.0",
"@types/qrcode": "^1.3.5",
- "@types/react": "^16.9",
- "@types/react-dom": "^16.9.10",
+ "@types/react": "^17.0.2",
+ "@types/react-dom": "^17.0.2",
"@types/react-transition-group": "^4.4.0",
"@types/sanitize-html": "^2.3.1",
"@types/zxcvbn": "^4.4.0",
diff --git a/yarn.lock b/yarn.lock
index 952d08d0f6..4f17b63337 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1620,12 +1620,12 @@
dependencies:
"@types/node" "*"
-"@types/react-dom@^16.9.10":
- version "16.9.10"
- resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.10.tgz#4485b0bec3d41f856181b717f45fd7831101156f"
- integrity sha512-ItatOrnXDMAYpv6G8UCk2VhbYVTjZT9aorLtA/OzDN9XJ2GKcfam68jutoAcILdRjsRUO8qb7AmyObF77Q8QFw==
+"@types/react-dom@^17.0.2":
+ version "17.0.8"
+ resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.8.tgz#3180de6d79bf53762001ad854e3ce49f36dd71fc"
+ integrity sha512-0ohAiJAx1DAUEcY9UopnfwCE9sSMDGnY/oXjWMax6g3RpzmTt2GMyMVAXcbn0mo8XAff0SbQJl2/SBU+hjSZ1A==
dependencies:
- "@types/react" "^16"
+ "@types/react" "*"
"@types/react-transition-group@^4.4.0":
version "4.4.0"
@@ -1634,7 +1634,7 @@
dependencies:
"@types/react" "*"
-"@types/react@*", "@types/react@^16", "@types/react@^16.14", "@types/react@^16.9":
+"@types/react@*", "@types/react@^16.14":
version "16.14.2"
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.14.2.tgz#85dcc0947d0645349923c04ccef6018a1ab7538c"
integrity sha512-BzzcAlyDxXl2nANlabtT4thtvbbnhee8hMmH/CcJrISDBVcJS1iOsP1f0OAgSdGE0MsY9tqcrb9YoZcOFv9dbQ==
@@ -1642,6 +1642,15 @@
"@types/prop-types" "*"
csstype "^3.0.2"
+"@types/react@^17.0.2":
+ version "17.0.11"
+ resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.11.tgz#67fcd0ddbf5a0b083a0f94e926c7d63f3b836451"
+ integrity sha512-yFRQbD+whVonItSk7ZzP/L+gPTJVBkL/7shLEF+i9GC/1cV3JmUxEQz6+9ylhUpWSDuqo1N9qEvqS6vTj4USUA==
+ dependencies:
+ "@types/prop-types" "*"
+ "@types/scheduler" "*"
+ csstype "^3.0.2"
+
"@types/sanitize-html@^2.3.1":
version "2.3.1"
resolved "https://registry.yarnpkg.com/@types/sanitize-html/-/sanitize-html-2.3.1.tgz#094d696b83b7394b016e96342bbffa6a028795ce"
@@ -1649,6 +1658,11 @@
dependencies:
htmlparser2 "^6.0.0"
+"@types/scheduler@*":
+ version "0.16.1"
+ resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.1.tgz#18845205e86ff0038517aab7a18a62a6b9f71275"
+ integrity sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA==
+
"@types/stack-utils@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e"
From 066ef18db2eb56561bb5736b58c95b41426cb244 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=A0imon=20Brandner?=
Date: Tue, 22 Jun 2021 09:24:42 +0200
Subject: [PATCH 061/100] Replace onClick by onMouseDown
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Šimon Brandner
---
src/components/structures/RoomDirectory.tsx | 45 ++++++++++-----------
1 file changed, 21 insertions(+), 24 deletions(-)
diff --git a/src/components/structures/RoomDirectory.tsx b/src/components/structures/RoomDirectory.tsx
index 731e2387cc..62944a9d98 100644
--- a/src/components/structures/RoomDirectory.tsx
+++ b/src/components/structures/RoomDirectory.tsx
@@ -567,11 +567,11 @@ export default class RoomDirectory extends React.Component {
let avatarUrl = null;
if (room.avatar_url) avatarUrl = mediaFromMxc(room.avatar_url).getSquareThumbnailHttp(32);
+ // We use onMouseDown instead of onClick, so that we can avoid text getting selected
return [
- this.onRoomClicked(room, ev)}
- // cancel onMouseDown otherwise shift-clicking highlights text
- onMouseDown={(ev) => {ev.preventDefault();}}
+
this.onRoomClicked(room, ev)}
className="mx_RoomDirectory_roomAvatar"
>
{
url={avatarUrl}
/>
,
-
this.onRoomClicked(room, ev)}
- // cancel onMouseDown otherwise shift-clicking highlights text
- onMouseDown={(ev) => {ev.preventDefault();}}
+
this.onRoomClicked(room, ev)}
className="mx_RoomDirectory_roomDescription"
>
this.onRoomClicked(room, ev)}
+ onMouseDown={(ev) => this.onRoomClicked(room, ev)}
>
{ name }
this.onRoomClicked(room, ev)}
+ onMouseDown={(ev) => this.onRoomClicked(room, ev)}
dangerouslySetInnerHTML={{ __html: topic }}
/>
this.onRoomClicked(room, ev)}
+ onMouseDown={(ev) => this.onRoomClicked(room, ev)}
>
{ getDisplayAliasForRoom(room) }
,
-
this.onRoomClicked(room, ev)}
- // cancel onMouseDown otherwise shift-clicking highlights text
- onMouseDown={(ev) => {ev.preventDefault();}}
+
this.onRoomClicked(room, ev)}
className="mx_RoomDirectory_roomMemberCount"
>
{ room.num_joined_members }
,
-
this.onRoomClicked(room, ev)}
+
this.onRoomClicked(room, ev)}
// cancel onMouseDown otherwise shift-clicking highlights text
- onMouseDown={(ev) => {ev.preventDefault();}}
className="mx_RoomDirectory_preview"
>
- {previewButton}
+ { previewButton }
,
-
this.onRoomClicked(room, ev)}
- // cancel onMouseDown otherwise shift-clicking highlights text
- onMouseDown={(ev) => {ev.preventDefault();}}
+
this.onRoomClicked(room, ev)}
className="mx_RoomDirectory_join"
>
- {joinOrViewButton}
+ { joinOrViewButton }
,
];
}
From dd58c9f413abd9a4c654e19c675f54864f24143b Mon Sep 17 00:00:00 2001
From: Germain Souquet
Date: Tue, 22 Jun 2021 10:52:33 +0100
Subject: [PATCH 062/100] Add TruncatedList in AddExistingToSpaceDialog
---
.../dialogs/AddExistingToSpaceDialog.tsx | 39 ++++++++++++++-----
1 file changed, 29 insertions(+), 10 deletions(-)
diff --git a/src/components/views/dialogs/AddExistingToSpaceDialog.tsx b/src/components/views/dialogs/AddExistingToSpaceDialog.tsx
index 822ffc2827..8997e4a5f8 100644
--- a/src/components/views/dialogs/AddExistingToSpaceDialog.tsx
+++ b/src/components/views/dialogs/AddExistingToSpaceDialog.tsx
@@ -39,6 +39,9 @@ import ProgressBar from "../elements/ProgressBar";
import {SpaceFeedbackPrompt} from "../../structures/SpaceRoomView";
import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
import QueryMatcher from "../../../autocomplete/QueryMatcher";
+import TruncatedList from "../elements/TruncatedList";
+import EntityTile from "../rooms/EntityTile";
+import BaseAvatar from "../avatars/BaseAvatar";
interface IProps extends IDialogProps {
matrixClient: MatrixClient;
@@ -204,6 +207,17 @@ export const AddExistingToSpace: React.FC = ({
setSelectedToAdd(new Set(selectedToAdd));
} : null;
+ const [truncateAt, setTruncateAt] = useState(20);
+ function overflowTile(overflowCount, totalCount) {
+ const text = _t("and %(count)s others...", { count: overflowCount });
+ return (
+
+ } name={text} presenceState="online" suppressOnHover={true}
+ onClick={() => setTruncateAt(totalCount)} />
+ );
+ }
+
return
= ({
{ rooms.length > 0 ? (
{ _t("Rooms") }
- { rooms.map(room => {
- return {
- onChange(checked, room);
- } : null}
- />;
- }) }
+ rooms.slice(start, end).map(room =>
+ {
+ onChange(checked, room);
+ } : null}
+ />,
+ )}
+ getChildCount={() => rooms.length}
+ />
) : undefined }
From 66b3feb802b913b9500ee38c8807f2535eb4ec99 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 22 Jun 2021 11:50:00 +0100
Subject: [PATCH 063/100] Fix keyboard accessibility of the space panel
---
.../views/elements/AccessibleButton.tsx | 6 +
.../views/spaces/SpaceTreeLevel.tsx | 145 +++++++++++-------
2 files changed, 98 insertions(+), 53 deletions(-)
diff --git a/src/components/views/elements/AccessibleButton.tsx b/src/components/views/elements/AccessibleButton.tsx
index e634057a21..05bcca24b2 100644
--- a/src/components/views/elements/AccessibleButton.tsx
+++ b/src/components/views/elements/AccessibleButton.tsx
@@ -62,6 +62,8 @@ export default function AccessibleButton({
disabled,
inputRef,
className,
+ onKeyDown,
+ onKeyUp,
...restProps
}: IProps) {
const newProps: IAccessibleButtonProps = restProps;
@@ -83,6 +85,8 @@ export default function AccessibleButton({
if (e.key === Key.SPACE) {
e.stopPropagation();
e.preventDefault();
+ } else {
+ onKeyDown?.(e);
}
};
newProps.onKeyUp = (e) => {
@@ -94,6 +98,8 @@ export default function AccessibleButton({
if (e.key === Key.ENTER) {
e.stopPropagation();
e.preventDefault();
+ } else {
+ onKeyUp?.(e);
}
};
}
diff --git a/src/components/views/spaces/SpaceTreeLevel.tsx b/src/components/views/spaces/SpaceTreeLevel.tsx
index f34baf256b..b3577e436a 100644
--- a/src/components/views/spaces/SpaceTreeLevel.tsx
+++ b/src/components/views/spaces/SpaceTreeLevel.tsx
@@ -14,23 +14,22 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React from "react";
+import React, { createRef } from "react";
import classNames from "classnames";
-import {Room} from "matrix-js-sdk/src/models/room";
+import { Room } from "matrix-js-sdk/src/models/room";
import RoomAvatar from "../avatars/RoomAvatar";
import SpaceStore from "../../../stores/SpaceStore";
import SpaceTreeLevelLayoutStore from "../../../stores/SpaceTreeLevelLayoutStore";
import NotificationBadge from "../rooms/NotificationBadge";
-import {RovingAccessibleButton} from "../../../accessibility/roving/RovingAccessibleButton";
-import {RovingAccessibleTooltipButton} from "../../../accessibility/roving/RovingAccessibleTooltipButton";
+import { RovingAccessibleTooltipButton } from "../../../accessibility/roving/RovingAccessibleTooltipButton";
import IconizedContextMenu, {
IconizedContextMenuOption,
IconizedContextMenuOptionList,
} from "../context_menus/IconizedContextMenu";
-import {_t} from "../../../languageHandler";
-import {ContextMenuTooltipButton} from "../../../accessibility/context_menu/ContextMenuTooltipButton";
-import {toRightOf} from "../../structures/ContextMenu";
+import { _t } from "../../../languageHandler";
+import { ContextMenuTooltipButton } from "../../../accessibility/context_menu/ContextMenuTooltipButton";
+import { toRightOf } from "../../structures/ContextMenu";
import {
shouldShowSpaceSettings,
showAddExistingRooms,
@@ -39,15 +38,16 @@ import {
showSpaceSettings,
} from "../../../utils/space";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
-import AccessibleButton, {ButtonEvent} from "../elements/AccessibleButton";
+import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
import defaultDispatcher from "../../../dispatcher/dispatcher";
-import {Action} from "../../../dispatcher/actions";
+import { Action } from "../../../dispatcher/actions";
import RoomViewStore from "../../../stores/RoomViewStore";
-import {SetRightPanelPhasePayload} from "../../../dispatcher/payloads/SetRightPanelPhasePayload";
-import {RightPanelPhases} from "../../../stores/RightPanelStorePhases";
-import {EventType} from "matrix-js-sdk/src/@types/event";
-import {StaticNotificationState} from "../../../stores/notifications/StaticNotificationState";
-import {NotificationColor} from "../../../stores/notifications/NotificationColor";
+import { SetRightPanelPhasePayload } from "../../../dispatcher/payloads/SetRightPanelPhasePayload";
+import { RightPanelPhases } from "../../../stores/RightPanelStorePhases";
+import { EventType } from "matrix-js-sdk/src/@types/event";
+import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState";
+import { NotificationColor } from "../../../stores/notifications/NotificationColor";
+import { getKeyBindingsManager, RoomListAction } from "../../../KeyBindingsManager";
interface IItemProps {
space?: Room;
@@ -61,11 +61,14 @@ interface IItemProps {
interface IItemState {
collapsed: boolean;
contextMenuPosition: Pick;
+ childSpaces: Room[];
}
export class SpaceItem extends React.PureComponent {
static contextType = MatrixClientContext;
+ private buttonRef = createRef();
+
constructor(props) {
super(props);
@@ -78,14 +81,36 @@ export class SpaceItem extends React.PureComponent {
this.state = {
collapsed: collapsed,
contextMenuPosition: null,
+ childSpaces: this.childSpaces,
};
+
+ SpaceStore.instance.on(this.props.space.roomId, this.onSpaceUpdate);
}
- private toggleCollapse(evt) {
- if (this.props.onExpand && this.state.collapsed) {
+ componentWillUnmount() {
+ SpaceStore.instance.off(this.props.space.roomId, this.onSpaceUpdate);
+ }
+
+ private onSpaceUpdate = () => {
+ this.setState({
+ childSpaces: this.childSpaces,
+ });
+ };
+
+ private get childSpaces() {
+ return SpaceStore.instance.getChildSpaces(this.props.space.roomId)
+ .filter(s => !this.props.parents?.has(s.roomId));
+ }
+
+ private get isCollapsed() {
+ return this.state.collapsed || this.props.isPanelCollapsed;
+ }
+
+ private toggleCollapse = evt => {
+ if (this.props.onExpand && this.isCollapsed) {
this.props.onExpand();
}
- const newCollapsedState = !this.state.collapsed;
+ const newCollapsedState = !this.isCollapsed;
SpaceTreeLevelLayoutStore.instance.setSpaceCollapsedState(
this.props.space.roomId,
@@ -96,7 +121,7 @@ export class SpaceItem extends React.PureComponent {
// don't bubble up so encapsulating button for space
// doesn't get triggered
evt.stopPropagation();
- }
+ };
private onContextMenu = (ev: React.MouseEvent) => {
if (this.props.space.getMyMembership() !== "join") return;
@@ -111,6 +136,43 @@ export class SpaceItem extends React.PureComponent {
});
}
+ private onKeyDown = (ev: React.KeyboardEvent) => {
+ let handled = true;
+ const action = getKeyBindingsManager().getRoomListAction(ev);
+ const hasChildren = this.state.childSpaces?.length;
+ switch (action) {
+ case RoomListAction.CollapseSection:
+ if (hasChildren && !this.isCollapsed) {
+ this.toggleCollapse(ev);
+ } else {
+ const parentItem = this.buttonRef?.current?.parentElement?.parentElement;
+ const parentButton = parentItem?.previousElementSibling as HTMLElement;
+ parentButton?.focus();
+ }
+ break;
+
+ case RoomListAction.ExpandSection:
+ if (hasChildren) {
+ if (this.isCollapsed) {
+ this.toggleCollapse(ev);
+ } else {
+ const childLevel = this.buttonRef?.current?.nextElementSibling;
+ const firstSpaceItemChild = childLevel?.querySelector(".mx_SpaceItem");
+ firstSpaceItemChild?.querySelector(".mx_SpaceButton")?.focus();
+ }
+ }
+ break;
+
+ default:
+ handled = false;
+ }
+
+ if (handled) {
+ ev.stopPropagation();
+ ev.preventDefault();
+ }
+ };
+
private onClick = (ev: React.MouseEvent) => {
ev.preventDefault();
ev.stopPropagation();
@@ -302,18 +364,15 @@ export class SpaceItem extends React.PureComponent {
render() {
const {space, activeSpaces, isNested} = this.props;
- const forceCollapsed = this.props.isPanelCollapsed;
const isNarrow = this.props.isPanelCollapsed;
- const collapsed = this.state.collapsed || forceCollapsed;
+ const collapsed = this.isCollapsed;
- const childSpaces = SpaceStore.instance.getChildSpaces(space.roomId)
- .filter(s => !this.props.parents?.has(s.roomId));
const isActive = activeSpaces.includes(space);
const itemClasses = classNames({
"mx_SpaceItem": true,
"mx_SpaceItem_narrow": isNarrow,
"collapsed": collapsed,
- "hasSubSpaces": childSpaces && childSpaces.length,
+ "hasSubSpaces": this.state.childSpaces?.length,
});
const isInvite = space.getMyMembership() === "invite";
@@ -328,9 +387,9 @@ export class SpaceItem extends React.PureComponent {
: SpaceStore.instance.getNotificationState(space.roomId);
let childItems;
- if (childSpaces && !collapsed) {
+ if (this.state.childSpaces?.length && !collapsed) {
childItems = {
const avatarSize = isNested ? 24 : 32;
- const toggleCollapseButton = childSpaces && childSpaces.length ?
+ const toggleCollapseButton = this.state.childSpaces?.length ?
this.toggleCollapse(evt)}
+ onClick={this.toggleCollapse}
/> : null;
- let button;
- if (isNarrow) {
- button = (
+ return (
+
{ toggleCollapseButton }
+ { !isNarrow && { space.name } }
{ notifBadge }
{ this.renderContextMenu() }
- );
- } else {
- button = (
-
- { toggleCollapseButton }
-
-
- { space.name }
- { notifBadge }
- { this.renderContextMenu() }
-
-
- );
- }
- return (
-
- { button }
{ childItems }
);
From 1f0fdb95cd2f52731cc1ca2253a249a00fe8a83c Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 22 Jun 2021 11:59:04 +0100
Subject: [PATCH 064/100] Improve accessibility of subspaces in the space panel
---
src/components/views/spaces/SpaceTreeLevel.tsx | 3 +++
src/i18n/strings/en_EN.json | 2 ++
2 files changed, 5 insertions(+)
diff --git a/src/components/views/spaces/SpaceTreeLevel.tsx b/src/components/views/spaces/SpaceTreeLevel.tsx
index b3577e436a..cbc1cab86b 100644
--- a/src/components/views/spaces/SpaceTreeLevel.tsx
+++ b/src/components/views/spaces/SpaceTreeLevel.tsx
@@ -409,6 +409,8 @@ export class SpaceItem extends React.PureComponent {
: null;
return (
@@ -420,6 +422,7 @@ export class SpaceItem extends React.PureComponent {
onContextMenu={this.onContextMenu}
forceHide={!isNarrow || !!this.state.contextMenuPosition}
role="treeitem"
+ aria-expanded={!collapsed}
inputRef={this.buttonRef}
onKeyDown={this.onKeyDown}
>
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index b88dc79da5..a2fb93dc17 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -1038,6 +1038,8 @@
"Manage & explore rooms": "Manage & explore rooms",
"Explore rooms": "Explore rooms",
"Space options": "Space options",
+ "Expand": "Expand",
+ "Collapse": "Collapse",
"Remove": "Remove",
"This bridge was provisioned by .": "This bridge was provisioned by .",
"This bridge is managed by .": "This bridge is managed by .",
From 28c61cca2798f0c41fea47a468057a4a9ecf2c58 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 22 Jun 2021 17:56:52 +0100
Subject: [PATCH 065/100] Remove pinned resolution for @types/react to 16.x
---
package.json | 3 ---
yarn.lock | 10 +---------
2 files changed, 1 insertion(+), 12 deletions(-)
diff --git a/package.json b/package.json
index 8ebb90f342..9ca1224baa 100644
--- a/package.json
+++ b/package.json
@@ -167,9 +167,6 @@
"typescript": "^4.1.3",
"walk": "^2.3.14"
},
- "resolutions": {
- "**/@types/react": "^16.14"
- },
"jest": {
"testEnvironment": "./__test-utils__/environment.js",
"testMatch": [
diff --git a/yarn.lock b/yarn.lock
index 4f17b63337..b19a188014 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1634,15 +1634,7 @@
dependencies:
"@types/react" "*"
-"@types/react@*", "@types/react@^16.14":
- version "16.14.2"
- resolved "https://registry.yarnpkg.com/@types/react/-/react-16.14.2.tgz#85dcc0947d0645349923c04ccef6018a1ab7538c"
- integrity sha512-BzzcAlyDxXl2nANlabtT4thtvbbnhee8hMmH/CcJrISDBVcJS1iOsP1f0OAgSdGE0MsY9tqcrb9YoZcOFv9dbQ==
- dependencies:
- "@types/prop-types" "*"
- csstype "^3.0.2"
-
-"@types/react@^17.0.2":
+"@types/react@*", "@types/react@^17.0.2":
version "17.0.11"
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.11.tgz#67fcd0ddbf5a0b083a0f94e926c7d63f3b836451"
integrity sha512-yFRQbD+whVonItSk7ZzP/L+gPTJVBkL/7shLEF+i9GC/1cV3JmUxEQz6+9ylhUpWSDuqo1N9qEvqS6vTj4USUA==
From 7948aa6181ec34cd2c55a3945721af747321bb40 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 22 Jun 2021 21:10:29 +0100
Subject: [PATCH 066/100] Iterate PR, improve jsdoc and switch function style
---
src/utils/arrays.ts | 8 ++++----
src/utils/stringOrderField.ts | 6 +++---
2 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/src/utils/arrays.ts b/src/utils/arrays.ts
index 148861e5d3..6524debfb7 100644
--- a/src/utils/arrays.ts
+++ b/src/utils/arrays.ts
@@ -225,10 +225,10 @@ export function arrayMerge(...a: T[][]): T[] {
/**
* Moves a single element from fromIndex to toIndex.
- * @param list the list from which to construct the new list.
- * @param fromIndex the index of the element to move.
- * @param toIndex the index of where to put the element.
- * @returns A new array with the requested value moved.
+ * @param {array} list the list from which to construct the new list.
+ * @param {number} fromIndex the index of the element to move.
+ * @param {number} toIndex the index of where to put the element.
+ * @returns {array} A new array with the requested value moved.
*/
export function moveElement(list: T[], fromIndex: number, toIndex: number): T[] {
const result = Array.from(list);
diff --git a/src/utils/stringOrderField.ts b/src/utils/stringOrderField.ts
index b312b85b08..da840792ee 100644
--- a/src/utils/stringOrderField.ts
+++ b/src/utils/stringOrderField.ts
@@ -18,13 +18,13 @@ import { alphabetPad, baseToString, stringToBase, DEFAULT_ALPHABET } from "matri
import { moveElement } from "./arrays";
-export const midPointsBetweenStrings = (
+export function midPointsBetweenStrings(
a: string,
b: string,
count: number,
maxLen: number,
alphabet = DEFAULT_ALPHABET,
-): string[] => {
+): string[] {
const padN = Math.min(Math.max(a.length, b.length), maxLen);
const padA = alphabetPad(a, padN, alphabet);
const padB = alphabetPad(b, padN, alphabet);
@@ -48,7 +48,7 @@ export const midPointsBetweenStrings = (
const step = (baseB - baseA) / BigInt(count + 1);
const start = BigInt(baseA + step);
return Array(count).fill(undefined).map((_, i) => baseToString(start + (BigInt(i) * step), alphabet));
-};
+}
interface IEntry {
index: number;
From 99e3aea1e5eb0cc5f25742ab430fa7dcf18edc83 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 22 Jun 2021 21:17:33 +0100
Subject: [PATCH 067/100] i18n and regen yarn lock
---
src/i18n/strings/en_EN.json | 8 +-
yarn.lock | 200 ++++++++++++++++--------------------
2 files changed, 94 insertions(+), 114 deletions(-)
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index a94b608f2b..4ca011f404 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -1025,10 +1025,10 @@
"You can change these anytime.": "You can change these anytime.",
"Creating...": "Creating...",
"Create": "Create",
- "Expand space panel": "Expand space panel",
- "Collapse space panel": "Collapse space panel",
"All rooms": "All rooms",
"Home": "Home",
+ "Expand space panel": "Expand space panel",
+ "Collapse space panel": "Collapse space panel",
"Click to copy": "Click to copy",
"Copied!": "Copied!",
"Failed to copy": "Failed to copy",
@@ -2511,6 +2511,8 @@
"Update status": "Update status",
"Set status": "Set status",
"Set a new status...": "Set a new status...",
+ "Move up": "Move up",
+ "Move down": "Move down",
"View Community": "View Community",
"Unable to start audio streaming.": "Unable to start audio streaming.",
"Failed to start livestream": "Failed to start livestream",
@@ -2657,7 +2659,7 @@
"%(count)s messages deleted.|one": "%(count)s message deleted.",
"Your Communities": "Your Communities",
"Did you know: you can use communities to filter your %(brand)s experience!": "Did you know: you can use communities to filter your %(brand)s experience!",
- "To set up a filter, drag a community avatar over to the filter panel on the far left hand side of the screen. You can click on an avatar in the filter panel at any time to see only the rooms and people associated with that community.": "To set up a filter, drag a community avatar over to the filter panel on the far left hand side of the screen. You can click on an avatar in the filter panel at any time to see only the rooms and people associated with that community.",
+ "You can click on an avatar in the filter panel at any time to see only the rooms and people associated with that community.": "You can click on an avatar in the filter panel at any time to see only the rooms and people associated with that community.",
"Error whilst fetching joined communities": "Error whilst fetching joined communities",
"Create a new community": "Create a new community",
"Create a community to group together users and rooms! Build a custom homepage to mark out your space in the Matrix universe.": "Create a community to group together users and rooms! Build a custom homepage to mark out your space in the Matrix universe.",
diff --git a/yarn.lock b/yarn.lock
index b19a188014..0d424cb93d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1017,13 +1017,20 @@
pirates "^4.0.0"
source-map-support "^0.5.16"
-"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7":
+"@babel/runtime@^7.0.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7":
version "7.12.5"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e"
integrity sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==
dependencies:
regenerator-runtime "^0.13.4"
+"@babel/runtime@^7.12.1", "@babel/runtime@^7.9.2":
+ version "7.14.6"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.6.tgz#535203bc0892efc7dec60bdc27b2ecf6e409062d"
+ integrity sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg==
+ dependencies:
+ regenerator-runtime "^0.13.4"
+
"@babel/template@^7.10.4", "@babel/template@^7.12.7", "@babel/template@^7.3.3":
version "7.12.7"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.12.7.tgz#c817233696018e39fbb6c491d2fb684e05ed43bc"
@@ -1504,6 +1511,14 @@
dependencies:
"@types/node" "*"
+"@types/hoist-non-react-statics@^3.3.0":
+ version "3.3.1"
+ resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"
+ integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==
+ dependencies:
+ "@types/react" "*"
+ hoist-non-react-statics "^3.3.0"
+
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762"
@@ -1620,6 +1635,13 @@
dependencies:
"@types/node" "*"
+"@types/react-beautiful-dnd@^13.0.0":
+ version "13.0.0"
+ resolved "https://registry.yarnpkg.com/@types/react-beautiful-dnd/-/react-beautiful-dnd-13.0.0.tgz#e60d3d965312fcf1516894af92dc3e9249587db4"
+ integrity sha512-by80tJ8aTTDXT256Gl+RfLRtFjYbUWOnZuEigJgNsJrSEGxvFe5eY6k3g4VIvf0M/6+xoLgfYWoWonlOo6Wqdg==
+ dependencies:
+ "@types/react" "*"
+
"@types/react-dom@^17.0.2":
version "17.0.8"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.8.tgz#3180de6d79bf53762001ad854e3ce49f36dd71fc"
@@ -1627,6 +1649,16 @@
dependencies:
"@types/react" "*"
+"@types/react-redux@^7.1.16":
+ version "7.1.16"
+ resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.16.tgz#0fbd04c2500c12105494c83d4a3e45c084e3cb21"
+ integrity sha512-f/FKzIrZwZk7YEO9E1yoxIuDNRiDducxkFlkw/GNMGEnK9n4K8wJzlJBghpSuOVDgEUHoDkDF7Gi9lHNQR4siw==
+ dependencies:
+ "@types/hoist-non-react-statics" "^3.3.0"
+ "@types/react" "*"
+ hoist-non-react-statics "^3.3.0"
+ redux "^4.0.0"
+
"@types/react-transition-group@^4.4.0":
version "4.4.0"
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.0.tgz#882839db465df1320e4753e6e9f70ca7e9b4d46d"
@@ -2122,14 +2154,6 @@ babel-preset-jest@^26.6.2:
babel-plugin-jest-hoist "^26.6.2"
babel-preset-current-node-syntax "^1.0.0"
-babel-runtime@^6.26.0:
- version "6.26.0"
- resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
- integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4=
- dependencies:
- core-js "^2.4.0"
- regenerator-runtime "^0.11.0"
-
bail@^1.0.0:
version "1.0.5"
resolved "https://registry.yarnpkg.com/bail/-/bail-1.0.5.tgz#b6fa133404a392cbc1f8c4bf63f5953351e7a776"
@@ -2648,11 +2672,6 @@ core-js@^1.0.0:
resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636"
integrity sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=
-core-js@^2.4.0:
- version "2.6.12"
- resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec"
- integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==
-
core-util-is@1.0.2, core-util-is@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
@@ -2712,6 +2731,13 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.2:
shebang-command "^2.0.0"
which "^2.0.1"
+css-box-model@^1.2.0:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/css-box-model/-/css-box-model-1.2.1.tgz#59951d3b81fd6b2074a62d49444415b0d2b4d7c1"
+ integrity sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==
+ dependencies:
+ tiny-invariant "^1.0.6"
+
css-select@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.1.2.tgz#8b52b6714ed3a80d8221ec971c543f3b12653286"
@@ -4241,7 +4267,7 @@ highlight.js@^10.5.0:
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.5.0.tgz#3f09fede6a865757378f2d9ebdcbc15ba268f98f"
integrity sha512-xTmvd9HiIHR6L53TMC7TKolEj65zG1XU+Onr8oi86mYa+nLcIbxTTWkpW7CsEwv/vK7u1zb8alZIMLDqqN6KTw==
-hoist-non-react-statics@^3.3.0:
+hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
@@ -4456,13 +4482,6 @@ internal-slot@^1.0.2:
has "^1.0.3"
side-channel "^1.0.2"
-invariant@^2.2.2, invariant@^2.2.4:
- version "2.2.4"
- resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
- integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==
- dependencies:
- loose-envify "^1.0.0"
-
ip-regex@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9"
@@ -5600,11 +5619,6 @@ locate-path@^5.0.0:
dependencies:
p-locate "^4.1.0"
-lodash-es@^4.2.1:
- version "4.17.20"
- resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.20.tgz#29f6332eefc60e849f869c264bc71126ad61e8f7"
- integrity sha512-JD1COMZsq8maT6mnuz1UMV0jvYD0E0aUsSOdrr1/nAG3dhqQXwRRgeW0cSqH1U43INKcqxaiVIQNOUDld7gRDA==
-
lodash.escape@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-4.0.1.tgz#c9044690c21e04294beaa517712fded1fa88de98"
@@ -5625,7 +5639,7 @@ lodash.sortby@^4.7.0:
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=
-lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.2.1:
+lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@@ -5794,10 +5808,10 @@ mdurl@~1.0.1:
resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e"
integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=
-memoize-one@^3.0.1:
- version "3.1.1"
- resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-3.1.1.tgz#ef609811e3bc28970eac2884eece64d167830d17"
- integrity sha512-YqVh744GsMlZu6xkhGslPSqSurOv6P+kLN2J3ysBZfagLcL5FdRK/0UpgLoL8hwjjEvvAVkjJZyFP+1T6p1vgA==
+memoize-one@^5.1.1:
+ version "5.2.1"
+ resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e"
+ integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==
meow@^9.0.0:
version "9.0.0"
@@ -6443,11 +6457,6 @@ path-type@^4.0.0:
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
-performance-now@^0.2.0:
- version "0.2.0"
- resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5"
- integrity sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=
-
performance-now@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
@@ -6657,7 +6666,7 @@ prompts@^2.0.1:
kleur "^3.0.3"
sisteransi "^1.0.5"
-prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.0, prop-types@^15.7.2:
+prop-types@^15.6.2, prop-types@^15.7.0, prop-types@^15.7.2:
version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
@@ -6734,12 +6743,12 @@ quick-lru@^4.0.1:
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f"
integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==
-raf-schd@^2.1.0:
- version "2.1.2"
- resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-2.1.2.tgz#ec622b5167f2912089f054dc03ebd5bcf33c8f62"
- integrity sha512-Orl0IEvMtUCgPddgSxtxreK77UiQz4nPYJy9RggVzu4mKsZkQWiAaG1y9HlYWdvm9xtN348xRaT37qkvL/+A+g==
+raf-schd@^4.0.2:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-4.0.3.tgz#5d6c34ef46f8b2a0e880a8fcdb743efc5bfdbc1a"
+ integrity sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==
-raf@^3.1.0, raf@^3.4.1:
+raf@^3.4.1:
version "3.4.1"
resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39"
integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==
@@ -6766,21 +6775,18 @@ re-resizable@^6.9.0:
dependencies:
fast-memoize "^2.5.1"
-react-beautiful-dnd@^4.0.1:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-4.0.1.tgz#3b0a49bf6be75af351176c904f012611dd292b81"
- integrity sha512-d73RMu4QOFCyjUELLWFyY/EuclnfqulI9pECx+2gIuJvV0ycf1uR88o+1x0RSB9ILD70inHMzCBKNkWVbbt+vA==
+react-beautiful-dnd@^13.1.0:
+ version "13.1.0"
+ resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-13.1.0.tgz#ec97c81093593526454b0de69852ae433783844d"
+ integrity sha512-aGvblPZTJowOWUNiwd6tNfEpgkX5OxmpqxHKNW/4VmvZTNTbeiq7bA3bn5T+QSF2uibXB0D1DmJsb1aC/+3cUA==
dependencies:
- babel-runtime "^6.26.0"
- invariant "^2.2.2"
- memoize-one "^3.0.1"
- prop-types "^15.6.0"
- raf-schd "^2.1.0"
- react-motion "^0.5.2"
- react-redux "^5.0.6"
- redux "^3.7.2"
- redux-thunk "^2.2.0"
- reselect "^3.0.1"
+ "@babel/runtime" "^7.9.2"
+ css-box-model "^1.2.0"
+ memoize-one "^5.1.1"
+ raf-schd "^4.0.2"
+ react-redux "^7.2.0"
+ redux "^4.0.4"
+ use-memo-one "^1.1.1"
react-clientside-effect@^1.2.2:
version "1.2.3"
@@ -6815,7 +6821,7 @@ react-focus-lock@^2.5.0:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
-react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1:
+react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@@ -6825,32 +6831,17 @@ react-is@^17.0.1:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339"
integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==
-react-lifecycles-compat@^3.0.0:
- version "3.0.4"
- resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
- integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
-
-react-motion@^0.5.2:
- version "0.5.2"
- resolved "https://registry.yarnpkg.com/react-motion/-/react-motion-0.5.2.tgz#0dd3a69e411316567927917c6626551ba0607316"
- integrity sha512-9q3YAvHoUiWlP3cK0v+w1N5Z23HXMj4IF4YuvjvWegWqNPfLXsOBE/V7UvQGpXxHFKRQQcNcVQE31g9SB/6qgQ==
+react-redux@^7.2.0:
+ version "7.2.4"
+ resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.4.tgz#1ebb474032b72d806de2e0519cd07761e222e225"
+ integrity sha512-hOQ5eOSkEJEXdpIKbnRyl04LhaWabkDPV+Ix97wqQX3T3d2NQ8DUblNXXtNMavc7DpswyQM6xfaN4HQDKNY2JA==
dependencies:
- performance-now "^0.2.0"
- prop-types "^15.5.8"
- raf "^3.1.0"
-
-react-redux@^5.0.6:
- version "5.1.2"
- resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.1.2.tgz#b19cf9e21d694422727bf798e934a916c4080f57"
- integrity sha512-Ns1G0XXc8hDyH/OcBHOxNgQx9ayH3SPxBnFCOidGKSle8pKihysQw2rG/PmciUQRoclhVBO8HMhiRmGXnDja9Q==
- dependencies:
- "@babel/runtime" "^7.1.2"
- hoist-non-react-statics "^3.3.0"
- invariant "^2.2.4"
- loose-envify "^1.1.0"
- prop-types "^15.6.1"
- react-is "^16.6.0"
- react-lifecycles-compat "^3.0.0"
+ "@babel/runtime" "^7.12.1"
+ "@types/react-redux" "^7.1.16"
+ hoist-non-react-statics "^3.3.2"
+ loose-envify "^1.4.0"
+ prop-types "^15.7.2"
+ react-is "^16.13.1"
react-shallow-renderer@^16.13.1:
version "16.14.1"
@@ -6979,20 +6970,12 @@ redent@^3.0.0:
indent-string "^4.0.0"
strip-indent "^3.0.0"
-redux-thunk@^2.2.0:
- version "2.3.0"
- resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622"
- integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw==
-
-redux@^3.7.2:
- version "3.7.2"
- resolved "https://registry.yarnpkg.com/redux/-/redux-3.7.2.tgz#06b73123215901d25d065be342eb026bc1c8537b"
- integrity sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A==
+redux@^4.0.0, redux@^4.0.4:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/redux/-/redux-4.1.0.tgz#eb049679f2f523c379f1aff345c8612f294c88d4"
+ integrity sha512-uI2dQN43zqLWCt6B/BMGRMY6db7TTY4qeHHfGeKb3EOhmOKjU3KdWvNLJyqaHRksv/ErdNH7cFZWg9jXtewy4g==
dependencies:
- lodash "^4.2.1"
- lodash-es "^4.2.1"
- loose-envify "^1.1.0"
- symbol-observable "^1.0.3"
+ "@babel/runtime" "^7.9.2"
regenerate-unicode-properties@^8.2.0:
version "8.2.0"
@@ -7006,11 +6989,6 @@ regenerate@^1.4.0:
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a"
integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==
-regenerator-runtime@^0.11.0:
- version "0.11.1"
- resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
- integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==
-
regenerator-runtime@^0.13.4:
version "0.13.7"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55"
@@ -7168,11 +7146,6 @@ require-main-filename@^2.0.0:
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
-reselect@^3.0.1:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/reselect/-/reselect-3.0.1.tgz#efdaa98ea7451324d092b2b2163a6a1d7a9a2147"
- integrity sha1-79qpjqdFEyTQkrKyFjpqHXqaIUc=
-
resize-observer-polyfill@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
@@ -7895,11 +7868,6 @@ svg-tags@^1.0.0:
resolved "https://registry.yarnpkg.com/svg-tags/-/svg-tags-1.0.0.tgz#58f71cee3bd519b59d4b2a843b6c7de64ac04764"
integrity sha1-WPcc7jvVGbWdSyqEO2x95krAR2Q=
-symbol-observable@^1.0.3:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"
- integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==
-
symbol-tree@^3.2.4:
version "3.2.4"
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
@@ -7962,6 +7930,11 @@ through@^2.3.6:
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
+tiny-invariant@^1.0.6:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875"
+ integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==
+
tmatch@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/tmatch/-/tmatch-2.0.1.tgz#0c56246f33f30da1b8d3d72895abaf16660f38cf"
@@ -8277,6 +8250,11 @@ use-callback-ref@^1.2.1:
resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.2.5.tgz#6115ed242cfbaed5915499c0a9842ca2912f38a5"
integrity sha512-gN3vgMISAgacF7sqsLPByqoePooY3n2emTH59Ur5d/M8eg4WTWu1xp8i8DHjohftIyEx0S08RiYxbffr4j8Peg==
+use-memo-one@^1.1.1:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/use-memo-one/-/use-memo-one-1.1.2.tgz#0c8203a329f76e040047a35a1197defe342fab20"
+ integrity sha512-u2qFKtxLsia/r8qG0ZKkbytbztzRb317XCkT7yP8wxL0tZ/CzK2G+WWie5vWvpyeP7+YoPIwbJoIHJ4Ba4k0oQ==
+
use-sidecar@^1.0.1:
version "1.0.4"
resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.0.4.tgz#38398c3723727f9f924bed2343dfa3db6aaaee46"
From 49d20d253034ad945708da53e8d7b3f19ca7f15d Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 22 Jun 2021 21:22:30 +0100
Subject: [PATCH 068/100] consolidate the two onRoomAccountData listeners
---
src/stores/SpaceStore.tsx | 28 ++++++++++++----------------
1 file changed, 12 insertions(+), 16 deletions(-)
diff --git a/src/stores/SpaceStore.tsx b/src/stores/SpaceStore.tsx
index 8b4e3a8f3a..e498574467 100644
--- a/src/stores/SpaceStore.tsx
+++ b/src/stores/SpaceStore.tsx
@@ -527,17 +527,6 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
}
};
- private onRoomAccountData = (ev: MatrixEvent, room: Room, lastEv?: MatrixEvent) => {
- if (!room.isSpaceRoom() || ev.getType() !== EventType.SpaceOrder) return;
-
- this.spaceOrderLocalEchoMap.delete(room.roomId); // clear any local echo
- const order = ev.getContent()?.order;
- const lastOrder = lastEv?.getContent()?.order;
- if (order !== lastOrder) {
- this.notifyIfOrderChanged();
- }
- };
-
private notifyIfOrderChanged(): void {
const rootSpaces = this.sortRootSpaces(this.rootSpaces);
if (arrayHasOrderChange(this.rootSpaces, rootSpaces)) {
@@ -577,10 +566,19 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
}
};
- private onRoomAccountData = (ev: MatrixEvent, room: Room, lastEvent?: MatrixEvent) => {
- if (ev.getType() === EventType.Tag && !room.isSpaceRoom()) {
+ private onRoomAccountData = (ev: MatrixEvent, room: Room, lastEv?: MatrixEvent) => {
+ if (!room.isSpaceRoom()) return;
+
+ if (ev.getType() === EventType.SpaceOrder) {
+ this.spaceOrderLocalEchoMap.delete(room.roomId); // clear any local echo
+ const order = ev.getContent()?.order;
+ const lastOrder = lastEv?.getContent()?.order;
+ if (order !== lastOrder) {
+ this.notifyIfOrderChanged();
+ }
+ } else if (ev.getType() === EventType.Tag && !SettingsStore.getValue("feature_spaces.all_rooms")) {
// If the room was in favourites and now isn't or the opposite then update its position in the trees
- const oldTags = lastEvent?.getContent()?.tags || {};
+ const oldTags = lastEv?.getContent()?.tags || {};
const newTags = ev.getContent()?.tags || {};
if (!!oldTags[DefaultTagID.Favourite] !== !!newTags[DefaultTagID.Favourite]) {
this.onRoomUpdate(room);
@@ -625,7 +623,6 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
this.matrixClient.removeListener("Room.accountData", this.onRoomAccountData);
this.matrixClient.removeListener("RoomState.events", this.onRoomState);
if (!SettingsStore.getValue("feature_spaces.all_rooms")) {
- this.matrixClient.removeListener("Room.accountData", this.onRoomAccountData);
this.matrixClient.removeListener("accountData", this.onAccountData);
}
}
@@ -639,7 +636,6 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
this.matrixClient.on("Room.accountData", this.onRoomAccountData);
this.matrixClient.on("RoomState.events", this.onRoomState);
if (!SettingsStore.getValue("feature_spaces.all_rooms")) {
- this.matrixClient.on("Room.accountData", this.onRoomAccountData);
this.matrixClient.on("accountData", this.onAccountData);
}
From 5dc542f18947efadcea7beb4f68af8311bd36357 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 22 Jun 2021 22:01:18 +0100
Subject: [PATCH 069/100] Iterate PR
---
.../views/dialogs/SpaceSettingsDialog.tsx | 3 +--
.../views/elements/LabelledToggleSwitch.tsx | 2 +-
.../views/elements/RoomAliasField.tsx | 18 +++++++------
.../room_settings/RoomPublishSetting.tsx | 14 +++++++----
.../tabs/room/AdvancedRoomSettingsTab.tsx | 25 ++++++++++---------
.../views/spaces/SpaceSettingsGeneralTab.tsx | 2 +-
.../spaces/SpaceSettingsVisibilityTab.tsx | 2 +-
7 files changed, 37 insertions(+), 29 deletions(-)
diff --git a/src/components/views/dialogs/SpaceSettingsDialog.tsx b/src/components/views/dialogs/SpaceSettingsDialog.tsx
index 1273f06401..5e0cd96740 100644
--- a/src/components/views/dialogs/SpaceSettingsDialog.tsx
+++ b/src/components/views/dialogs/SpaceSettingsDialog.tsx
@@ -27,7 +27,7 @@ import TabbedView, { Tab } from "../../structures/TabbedView";
import SpaceSettingsGeneralTab from '../spaces/SpaceSettingsGeneralTab';
import SpaceSettingsVisibilityTab from "../spaces/SpaceSettingsVisibilityTab";
import SettingsStore from "../../../settings/SettingsStore";
-import {UIFeature} from "../../../settings/UIFeature";
+import { UIFeature } from "../../../settings/UIFeature";
import AdvancedRoomSettingsTab from "../settings/tabs/room/AdvancedRoomSettingsTab";
export enum SpaceSettingsTab {
@@ -91,4 +91,3 @@ const SpaceSettingsDialog: React.FC = ({ matrixClient: cli, space, onFin
};
export default SpaceSettingsDialog;
-
diff --git a/src/components/views/elements/LabelledToggleSwitch.tsx b/src/components/views/elements/LabelledToggleSwitch.tsx
index 957e3dbc97..d97b698fd8 100644
--- a/src/components/views/elements/LabelledToggleSwitch.tsx
+++ b/src/components/views/elements/LabelledToggleSwitch.tsx
@@ -1,5 +1,5 @@
/*
-Copyright 2019, 2021 The Matrix.org Foundation C.I.C.
+Copyright 2019 - 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
diff --git a/src/components/views/elements/RoomAliasField.tsx b/src/components/views/elements/RoomAliasField.tsx
index 7eff529c46..74af311b47 100644
--- a/src/components/views/elements/RoomAliasField.tsx
+++ b/src/components/views/elements/RoomAliasField.tsx
@@ -1,5 +1,5 @@
/*
-Copyright 2019, 2021 The Matrix.org Foundation C.I.C.
+Copyright 2019 - 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -39,9 +39,13 @@ interface IState {
export default class RoomAliasField extends React.PureComponent {
private fieldRef = createRef();
- public state = {
- isValid: true,
- };
+ constructor(props, context) {
+ super(props, context);
+
+ this.state = {
+ isValid: true,
+ };
+ }
private asFullAlias(localpart: string): string {
return `#${localpart}:${this.props.domain}`;
@@ -123,15 +127,15 @@ export default class RoomAliasField extends React.PureComponent
],
});
- get isValid() {
+ public get isValid() {
return this.state.isValid;
}
- validate(options: IValidateOpts) {
+ public validate(options: IValidateOpts) {
return this.fieldRef.current?.validate(options);
}
- focus() {
+ public focus() {
this.fieldRef.current?.focus();
}
}
diff --git a/src/components/views/room_settings/RoomPublishSetting.tsx b/src/components/views/room_settings/RoomPublishSetting.tsx
index 24df5f1c84..95b0ac100d 100644
--- a/src/components/views/room_settings/RoomPublishSetting.tsx
+++ b/src/components/views/room_settings/RoomPublishSetting.tsx
@@ -1,5 +1,5 @@
/*
-Copyright 2020, 2021 The Matrix.org Foundation C.I.C.
+Copyright 2020 - 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.
@@ -33,9 +33,13 @@ interface IState {
@replaceableComponent("views.room_settings.RoomPublishSetting")
export default class RoomPublishSetting extends React.PureComponent {
- public state = {
- isRoomPublished: false,
- };
+ constructor(props, context) {
+ super(props, context);
+
+ this.state = {
+ isRoomPublished: false,
+ };
+ }
private onRoomPublishChange = (e) => {
const valueBefore = this.state.isRoomPublished;
@@ -67,7 +71,7 @@ export default class RoomPublishSetting extends React.PureComponent
);
diff --git a/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.tsx b/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.tsx
index 7e7d9cba90..c4963d0154 100644
--- a/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.tsx
+++ b/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.tsx
@@ -1,5 +1,5 @@
/*
-Copyright 2019, 2021 The Matrix.org Foundation C.I.C.
+Copyright 2019 - 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -15,6 +15,7 @@ limitations under the License.
*/
import React from 'react';
+import { EventType } from 'matrix-js-sdk/src/@types/event';
import { _t } from "../../../../../languageHandler";
import { MatrixClientPeg } from "../../../../../MatrixClientPeg";
@@ -56,10 +57,10 @@ export default class AdvancedRoomSettingsTab extends React.Component {
- const tombstone = room.currentState.getStateEvents("m.room.tombstone", "");
+ const tombstone = room.currentState.getStateEvents(EventType.RoomTombstone, "");
const additionalStateChanges: Partial = {};
- const createEvent = room.currentState.getStateEvents("m.room.create", "");
+ const createEvent = room.currentState.getStateEvents(EventType.RoomCreate, "");
const predecessor = createEvent ? createEvent.getContent().predecessor : null;
if (predecessor && predecessor.room_id) {
additionalStateChanges.oldRoomId = predecessor.room_id;
@@ -100,9 +101,9 @@ export default class AdvancedRoomSettingsTab extends React.Component{_t('This room is not accessible by remote Matrix servers')} ;
+ unfederatableSection = { _t('This room is not accessible by remote Matrix servers') }
;
}
let roomUpgradeButton;
@@ -110,7 +111,7 @@ export default class AdvancedRoomSettingsTab extends React.Component
- {_t(
+ { _t(
"Warning : Upgrading a room will not automatically migrate room members " +
"to the new version of the room. We'll post a link to the new room in the old " +
"version of the room - room members will have to click this link to join the new room.",
@@ -118,10 +119,10 @@ export default class AdvancedRoomSettingsTab extends React.Component {sub} ,
"i": (sub) => {sub} ,
},
- )}
+ ) }
- {_t("Upgrade this room to the recommended room version")}
+ { _t("Upgrade this room to the recommended room version") }
);
@@ -141,21 +142,21 @@ export default class AdvancedRoomSettingsTab extends React.Component
- {_t("Advanced")}
+ { _t("Advanced") }
{ room?.isSpaceRoom() ? _t("Space information") : _t("Room information") }
- {_t("Internal room ID:")}
+ { _t("Internal room ID:") }
{ this.props.roomId }
{ unfederatableSection }
-
{_t("Room version")}
+
{ _t("Room version") }
- {_t("Room version:")}
+ { _t("Room version:") }
{ room.getVersion() }
{ oldRoomLink }
diff --git a/src/components/views/spaces/SpaceSettingsGeneralTab.tsx b/src/components/views/spaces/SpaceSettingsGeneralTab.tsx
index db0a180846..3afdc629e4 100644
--- a/src/components/views/spaces/SpaceSettingsGeneralTab.tsx
+++ b/src/components/views/spaces/SpaceSettingsGeneralTab.tsx
@@ -90,7 +90,7 @@ const SpaceSettingsGeneralTab = ({ matrixClient: cli, space, onFinished }: IProp
};
return
-
{_t("General")}
+
{ _t("General") }
{ _t("Edit settings relating to your space.") }
diff --git a/src/components/views/spaces/SpaceSettingsVisibilityTab.tsx b/src/components/views/spaces/SpaceSettingsVisibilityTab.tsx
index 2f80ad97a6..263823603b 100644
--- a/src/components/views/spaces/SpaceSettingsVisibilityTab.tsx
+++ b/src/components/views/spaces/SpaceSettingsVisibilityTab.tsx
@@ -137,7 +137,7 @@ const SpaceSettingsVisibilityTab = ({ matrixClient: cli, space }: IProps) => {
}
return
-
{_t("Visibility")}
+
{ _t("Visibility") }
{ error &&
{ error }
}
From 9dc8493a5c845336dafc774193353d08e1ef5971 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 22 Jun 2021 22:07:42 +0100
Subject: [PATCH 070/100] Hide communities invites and the community
autocompleter when Spaces Beta is enabled
---
src/autocomplete/Autocompleter.ts | 3 ++-
src/components/views/rooms/RoomList.tsx | 1 +
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/autocomplete/Autocompleter.ts b/src/autocomplete/Autocompleter.ts
index ea8eddbb8d..7f3f5d2c01 100644
--- a/src/autocomplete/Autocompleter.ts
+++ b/src/autocomplete/Autocompleter.ts
@@ -55,13 +55,14 @@ const PROVIDERS = [
EmojiProvider,
NotifProvider,
CommandProvider,
- CommunityProvider,
DuckDuckGoProvider,
];
// as the spaces feature is device configurable only, and toggling it refreshes the page, we can do this here
if (SettingsStore.getValue("feature_spaces")) {
PROVIDERS.push(SpaceProvider);
+} else {
+ PROVIDERS.push(CommunityProvider);
}
// Providers will get rejected if they take longer than this.
diff --git a/src/components/views/rooms/RoomList.tsx b/src/components/views/rooms/RoomList.tsx
index 5a1c3a24b3..704c3cf620 100644
--- a/src/components/views/rooms/RoomList.tsx
+++ b/src/components/views/rooms/RoomList.tsx
@@ -466,6 +466,7 @@ export default class RoomList extends React.PureComponent
{
}
private renderCommunityInvites(): ReactComponentElement[] {
+ if (SettingsStore.getValue("feature_spaces")) return [];
// TODO: Put community invites in a more sensible place (not in the room list)
// See https://github.com/vector-im/element-web/issues/14456
return MatrixClientPeg.get().getGroups().filter(g => {
From 83296b74404c6a6e594c9bd382bbb9d37f4248ab Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 22 Jun 2021 22:19:01 +0100
Subject: [PATCH 071/100] Fix typing
---
src/hooks/useRoomState.ts | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/hooks/useRoomState.ts b/src/hooks/useRoomState.ts
index 11ac7de49e..e778acf8a9 100644
--- a/src/hooks/useRoomState.ts
+++ b/src/hooks/useRoomState.ts
@@ -24,7 +24,10 @@ type Mapper = (roomState: RoomState) => T;
const defaultMapper: Mapper = (roomState: RoomState) => roomState;
// Hook to simplify watching Matrix Room state
-export const useRoomState = (room: Room, mapper: Mapper = defaultMapper): T => {
+export const useRoomState = (
+ room: Room,
+ mapper: Mapper = defaultMapper as Mapper,
+): T => {
const [value, setValue] = useState(room ? mapper(room.currentState) : undefined);
const update = useCallback(() => {
From e0ac200e27197617d9be4df78ef957441fed2fde Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 22 Jun 2021 22:23:09 +0100
Subject: [PATCH 072/100] Iterate PR
---
src/DecryptionFailureTracker.ts | 4 ++--
src/HtmlUtils.tsx | 4 ++--
src/utils/EditorStateTransfer.ts | 10 +++++-----
3 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/src/DecryptionFailureTracker.ts b/src/DecryptionFailureTracker.ts
index 960d844e9e..07c0c546fe 100644
--- a/src/DecryptionFailureTracker.ts
+++ b/src/DecryptionFailureTracker.ts
@@ -25,7 +25,7 @@ export class DecryptionFailure {
}
}
-type Fn = (count: number, trackedErrCode: string) => void;
+type TrackingFn = (count: number, trackedErrCode: string) => void;
type ErrCodeMapFn = (errcode: string) => string;
export class DecryptionFailureTracker {
@@ -73,7 +73,7 @@ export class DecryptionFailureTracker {
* @param {function?} errorCodeMapFn The function used to map error codes to the
* trackedErrorCode. If not provided, the `.code` of errors will be used.
*/
- constructor(private readonly fn: Fn, private readonly errorCodeMapFn?: ErrCodeMapFn) {
+ constructor(private readonly fn: TrackingFn, private readonly errorCodeMapFn?: ErrCodeMapFn) {
if (!fn || typeof fn !== 'function') {
throw new Error('DecryptionFailureTracker requires tracking function');
}
diff --git a/src/HtmlUtils.tsx b/src/HtmlUtils.tsx
index 5803029030..983538d65b 100644
--- a/src/HtmlUtils.tsx
+++ b/src/HtmlUtils.tsx
@@ -505,7 +505,7 @@ export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts
* @param {object} [options] Options for linkifyString. Default: linkifyMatrix.options
* @returns {string} Linkified string
*/
-export function linkifyString(str: string, options = linkifyMatrix.options) {
+export function linkifyString(str: string, options = linkifyMatrix.options): string {
return _linkifyString(str, options);
}
@@ -516,7 +516,7 @@ export function linkifyString(str: string, options = linkifyMatrix.options) {
* @param {object} [options] Options for linkifyElement. Default: linkifyMatrix.options
* @returns {object}
*/
-export function linkifyElement(element: HTMLElement, options = linkifyMatrix.options) {
+export function linkifyElement(element: HTMLElement, options = linkifyMatrix.options): HTMLElement {
return _linkifyElement(element, options);
}
diff --git a/src/utils/EditorStateTransfer.ts b/src/utils/EditorStateTransfer.ts
index 42e1a316d6..ba303f9b73 100644
--- a/src/utils/EditorStateTransfer.ts
+++ b/src/utils/EditorStateTransfer.ts
@@ -30,24 +30,24 @@ export default class EditorStateTransfer {
constructor(private readonly event: MatrixEvent) {}
- setEditorState(caret: Caret, serializedParts: SerializedPart[]) {
+ public setEditorState(caret: Caret, serializedParts: SerializedPart[]) {
this.caret = caret;
this.serializedParts = serializedParts;
}
- hasEditorState() {
+ public hasEditorState() {
return !!this.serializedParts;
}
- getSerializedParts() {
+ public getSerializedParts() {
return this.serializedParts;
}
- getCaret() {
+ public getCaret() {
return this.caret;
}
- getEvent() {
+ public getEvent() {
return this.event;
}
}
From ffaa19ef2c27f361c8fcc0367e491dca528e697b Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 22 Jun 2021 22:27:12 +0100
Subject: [PATCH 073/100] fix typing
---
.../views/dialogs/ForwardDialog.tsx | 31 ++++++++++---------
.../views/elements/EventTilePreview.tsx | 9 +++---
2 files changed, 21 insertions(+), 19 deletions(-)
diff --git a/src/components/views/dialogs/ForwardDialog.tsx b/src/components/views/dialogs/ForwardDialog.tsx
index a83f3f177c..6fbed6fc8b 100644
--- a/src/components/views/dialogs/ForwardDialog.tsx
+++ b/src/components/views/dialogs/ForwardDialog.tsx
@@ -14,30 +14,31 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React, {useMemo, useState, useEffect} from "react";
+import React, { useMemo, useState, useEffect } from "react";
import classnames from "classnames";
-import {MatrixEvent} from "matrix-js-sdk/src/models/event";
-import {Room} from "matrix-js-sdk/src/models/room";
-import {MatrixClient} from "matrix-js-sdk/src/client";
+import { MatrixEvent } from "matrix-js-sdk/src/models/event";
+import { Room } from "matrix-js-sdk/src/models/room";
+import { MatrixClient } from "matrix-js-sdk/src/client";
+import { RoomMember } from "matrix-js-sdk/src/models/room-member";
-import {_t} from "../../../languageHandler";
+import { _t } from "../../../languageHandler";
import dis from "../../../dispatcher/dispatcher";
-import {useSettingValue, useFeatureEnabled} from "../../../hooks/useSettings";
-import {UIFeature} from "../../../settings/UIFeature";
-import {Layout} from "../../../settings/Layout";
-import {IDialogProps} from "./IDialogProps";
+import { useSettingValue, useFeatureEnabled } from "../../../hooks/useSettings";
+import { UIFeature } from "../../../settings/UIFeature";
+import { Layout } from "../../../settings/Layout";
+import { IDialogProps } from "./IDialogProps";
import BaseDialog from "./BaseDialog";
-import {avatarUrlForUser} from "../../../Avatar";
+import { avatarUrlForUser } from "../../../Avatar";
import EventTile from "../rooms/EventTile";
import SearchBox from "../../structures/SearchBox";
import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
-import {Alignment} from '../elements/Tooltip';
+import { Alignment } from '../elements/Tooltip';
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
-import {StaticNotificationState} from "../../../stores/notifications/StaticNotificationState";
+import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState";
import NotificationBadge from "../rooms/NotificationBadge";
-import {RoomPermalinkCreator} from "../../../utils/permalinks/Permalinks";
-import {sortRooms} from "../../../stores/room-list/algorithms/tag-sorting/RecentAlgorithm";
+import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
+import { sortRooms } from "../../../stores/room-list/algorithms/tag-sorting/RecentAlgorithm";
import QueryMatcher from "../../../autocomplete/QueryMatcher";
const AVATAR_SIZE = 30;
@@ -171,7 +172,7 @@ const ForwardDialog: React.FC = ({ matrixClient: cli, event, permalinkCr
);
},
getMxcAvatarUrl: () => profileInfo.avatar_url,
- };
+ } as RoomMember;
const [query, setQuery] = useState("");
const lcQuery = query.toLowerCase();
diff --git a/src/components/views/elements/EventTilePreview.tsx b/src/components/views/elements/EventTilePreview.tsx
index cf3b7a6e61..366d918bcf 100644
--- a/src/components/views/elements/EventTilePreview.tsx
+++ b/src/components/views/elements/EventTilePreview.tsx
@@ -17,13 +17,14 @@ limitations under the License.
import React from 'react';
import classnames from 'classnames';
import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
+import { RoomMember } from 'matrix-js-sdk/src/models/room-member';
import * as Avatar from '../../../Avatar';
import EventTile from '../rooms/EventTile';
import SettingsStore from "../../../settings/SettingsStore";
-import {Layout} from "../../../settings/Layout";
-import {UIFeature} from "../../../settings/UIFeature";
-import {replaceableComponent} from "../../../utils/replaceableComponent";
+import { Layout } from "../../../settings/Layout";
+import { UIFeature } from "../../../settings/UIFeature";
+import { replaceableComponent } from "../../../utils/replaceableComponent";
interface IProps {
/**
@@ -110,7 +111,7 @@ export default class EventTilePreview extends React.Component {
);
},
getMxcAvatarUrl: () => this.props.avatarUrl,
- };
+ } as RoomMember;
return event;
}
From 9bceb40820baaab07f5a222641777ecc98f5ba96 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=A0imon=20Brandner?=
Date: Wed, 23 Jun 2021 09:26:33 +0200
Subject: [PATCH 074/100] CallMediaHandler -> MediaDeviceHandler
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Šimon Brandner
---
src/CallMediaHandler.js | 85 ----------------
src/MediaDeviceHandler.ts | 96 +++++++++++++++++++
src/components/structures/LoggedInView.tsx | 4 +-
.../views/rooms/VoiceRecordComposerTile.tsx | 6 +-
.../tabs/user/VoiceUserSettingsTab.js | 24 ++---
src/components/views/voip/AudioFeed.tsx | 4 +-
src/voice/VoiceRecording.ts | 4 +-
7 files changed, 117 insertions(+), 106 deletions(-)
delete mode 100644 src/CallMediaHandler.js
create mode 100644 src/MediaDeviceHandler.ts
diff --git a/src/CallMediaHandler.js b/src/CallMediaHandler.js
deleted file mode 100644
index 634f0bb336..0000000000
--- a/src/CallMediaHandler.js
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- Copyright 2017 Michael Telatynski <7t3chguy@gmail.com>
-
- 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 SettingsStore from "./settings/SettingsStore";
-import {SettingLevel} from "./settings/SettingLevel";
-import {setMatrixCallAudioInput, setMatrixCallVideoInput} from "matrix-js-sdk/src/matrix";
-
-export default {
- hasAnyLabeledDevices: async function() {
- const devices = await navigator.mediaDevices.enumerateDevices();
- return devices.some(d => !!d.label);
- },
-
- getDevices: function() {
- // Only needed for Electron atm, though should work in modern browsers
- // once permission has been granted to the webapp
- return navigator.mediaDevices.enumerateDevices().then(function(devices) {
- const audiooutput = [];
- const audioinput = [];
- const videoinput = [];
-
- devices.forEach((device) => {
- switch (device.kind) {
- case 'audiooutput': audiooutput.push(device); break;
- case 'audioinput': audioinput.push(device); break;
- case 'videoinput': videoinput.push(device); break;
- }
- });
-
- // console.log("Loaded WebRTC Devices", mediaDevices);
- return {
- audiooutput,
- audioinput,
- videoinput,
- };
- }, (error) => { console.log('Unable to refresh WebRTC Devices: ', error); });
- },
-
- loadDevices: function() {
- const audioDeviceId = SettingsStore.getValue("webrtc_audioinput");
- const videoDeviceId = SettingsStore.getValue("webrtc_videoinput");
-
- setMatrixCallAudioInput(audioDeviceId);
- setMatrixCallVideoInput(videoDeviceId);
- },
-
- setAudioOutput: function(deviceId) {
- SettingsStore.setValue("webrtc_audiooutput", null, SettingLevel.DEVICE, deviceId);
- },
-
- setAudioInput: function(deviceId) {
- SettingsStore.setValue("webrtc_audioinput", null, SettingLevel.DEVICE, deviceId);
- setMatrixCallAudioInput(deviceId);
- },
-
- setVideoInput: function(deviceId) {
- SettingsStore.setValue("webrtc_videoinput", null, SettingLevel.DEVICE, deviceId);
- setMatrixCallVideoInput(deviceId);
- },
-
- getAudioOutput: function() {
- return SettingsStore.getValueAt(SettingLevel.DEVICE, "webrtc_audiooutput");
- },
-
- getAudioInput: function() {
- return SettingsStore.getValueAt(SettingLevel.DEVICE, "webrtc_audioinput");
- },
-
- getVideoInput: function() {
- return SettingsStore.getValueAt(SettingLevel.DEVICE, "webrtc_videoinput");
- },
-};
diff --git a/src/MediaDeviceHandler.ts b/src/MediaDeviceHandler.ts
new file mode 100644
index 0000000000..96fd764b98
--- /dev/null
+++ b/src/MediaDeviceHandler.ts
@@ -0,0 +1,96 @@
+/*
+Copyright 2017 Michael Telatynski <7t3chguy@gmail.com>
+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 SettingsStore from "./settings/SettingsStore";
+import { SettingLevel } from "./settings/SettingLevel";
+import { setMatrixCallAudioInput, setMatrixCallVideoInput } from "matrix-js-sdk/src/matrix";
+
+interface IMediaDevices {
+ audioOutput: Array;
+ audioInput: Array;
+ videoInput: Array;
+}
+
+export default class MediaDeviceHandler {
+ static async hasAnyLabeledDevices(): Promise {
+ const devices = await navigator.mediaDevices.enumerateDevices();
+ return devices.some(d => Boolean(d.label));
+ }
+
+ static async getDevices(): Promise {
+ // Only needed for Electron atm, though should work in modern browsers
+ // once permission has been granted to the webapp
+
+ try {
+ const devices = await navigator.mediaDevices.enumerateDevices();
+
+ const audioOutput = [];
+ const audioInput = [];
+ const videoInput = [];
+
+ devices.forEach((device) => {
+ switch (device.kind) {
+ case 'audiooutput': audioOutput.push(device); break;
+ case 'audioinput': audioInput.push(device); break;
+ case 'videoinput': videoInput.push(device); break;
+ }
+ });
+
+ return {
+ audioOutput: audioOutput,
+ audioInput: audioInput,
+ videoInput: videoInput,
+ };
+ } catch (error) {
+ console.log('Unable to refresh WebRTC Devices: ', error);
+ }
+ }
+
+ static loadDevices() {
+ const audioDeviceId = SettingsStore.getValue("webrtc_audioinput");
+ const videoDeviceId = SettingsStore.getValue("webrtc_videoinput");
+
+ setMatrixCallAudioInput(audioDeviceId);
+ setMatrixCallVideoInput(videoDeviceId);
+ }
+
+ static setAudioOutput(deviceId: string) {
+ SettingsStore.setValue("webrtc_audiooutput", null, SettingLevel.DEVICE, deviceId);
+ }
+
+ static setAudioInput(deviceId: string) {
+ SettingsStore.setValue("webrtc_audioinput", null, SettingLevel.DEVICE, deviceId);
+ setMatrixCallAudioInput(deviceId);
+ }
+
+ static setVideoInput(deviceId: string) {
+ SettingsStore.setValue("webrtc_videoinput", null, SettingLevel.DEVICE, deviceId);
+ setMatrixCallVideoInput(deviceId);
+ }
+
+ static getAudioOutput(): string {
+ return SettingsStore.getValueAt(SettingLevel.DEVICE, "webrtc_audiooutput");
+ }
+
+ static getAudioInput(): string {
+ return SettingsStore.getValueAt(SettingLevel.DEVICE, "webrtc_audioinput");
+ }
+
+ static getVideoInput(): string {
+ return SettingsStore.getValueAt(SettingLevel.DEVICE, "webrtc_videoinput");
+ }
+}
diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx
index e3d6b1ab9c..5ad67232a4 100644
--- a/src/components/structures/LoggedInView.tsx
+++ b/src/components/structures/LoggedInView.tsx
@@ -22,7 +22,7 @@ import { MatrixClient } from 'matrix-js-sdk/src/client';
import {Key} from '../../Keyboard';
import PageTypes from '../../PageTypes';
-import CallMediaHandler from '../../CallMediaHandler';
+import MediaDeviceHandler from '../../MediaDeviceHandler';
import { fixupColorFonts } from '../../utils/FontManager';
import * as sdk from '../../index';
import dis from '../../dispatcher/dispatcher';
@@ -167,7 +167,7 @@ class LoggedInView extends React.Component {
// stash the MatrixClient in case we log out before we are unmounted
this._matrixClient = this.props.matrixClient;
- CallMediaHandler.loadDevices();
+ MediaDeviceHandler.loadDevices();
fixupColorFonts();
diff --git a/src/components/views/rooms/VoiceRecordComposerTile.tsx b/src/components/views/rooms/VoiceRecordComposerTile.tsx
index 20d8c9c5d4..122ba0ca0b 100644
--- a/src/components/views/rooms/VoiceRecordComposerTile.tsx
+++ b/src/components/views/rooms/VoiceRecordComposerTile.tsx
@@ -30,7 +30,7 @@ import RecordingPlayback from "../voice_messages/RecordingPlayback";
import {MsgType} from "matrix-js-sdk/src/@types/event";
import Modal from "../../../Modal";
import ErrorDialog from "../dialogs/ErrorDialog";
-import CallMediaHandler from "../../../CallMediaHandler";
+import MediaDeviceHandler from "../../../MediaDeviceHandler";
interface IProps {
room: Room;
@@ -129,8 +129,8 @@ export default class VoiceRecordComposerTile extends React.PureComponent
diff --git a/src/components/views/settings/tabs/user/VoiceUserSettingsTab.js b/src/components/views/settings/tabs/user/VoiceUserSettingsTab.js
index 362059f8ed..962f1fcd44 100644
--- a/src/components/views/settings/tabs/user/VoiceUserSettingsTab.js
+++ b/src/components/views/settings/tabs/user/VoiceUserSettingsTab.js
@@ -18,7 +18,7 @@ limitations under the License.
import React from 'react';
import {_t} from "../../../../../languageHandler";
import SdkConfig from "../../../../../SdkConfig";
-import CallMediaHandler from "../../../../../CallMediaHandler";
+import MediaDeviceHandler from "../../../../../MediaDeviceHandler";
import Field from "../../../elements/Field";
import AccessibleButton from "../../../elements/AccessibleButton";
import {MatrixClientPeg} from "../../../../../MatrixClientPeg";
@@ -41,7 +41,7 @@ export default class VoiceUserSettingsTab extends React.Component {
}
async componentDidMount() {
- const canSeeDeviceLabels = await CallMediaHandler.hasAnyLabeledDevices();
+ const canSeeDeviceLabels = await MediaDeviceHandler.hasAnyLabeledDevices();
if (canSeeDeviceLabels) {
this._refreshMediaDevices();
}
@@ -49,10 +49,10 @@ export default class VoiceUserSettingsTab extends React.Component {
_refreshMediaDevices = async (stream) => {
this.setState({
- mediaDevices: await CallMediaHandler.getDevices(),
- activeAudioOutput: CallMediaHandler.getAudioOutput(),
- activeAudioInput: CallMediaHandler.getAudioInput(),
- activeVideoInput: CallMediaHandler.getVideoInput(),
+ mediaDevices: await MediaDeviceHandler.getDevices(),
+ activeAudioOutput: MediaDeviceHandler.getAudioOutput(),
+ activeAudioInput: MediaDeviceHandler.getAudioInput(),
+ activeVideoInput: MediaDeviceHandler.getVideoInput(),
});
if (stream) {
// kill stream (after we've enumerated the devices, otherwise we'd get empty labels again)
@@ -100,21 +100,21 @@ export default class VoiceUserSettingsTab extends React.Component {
};
_setAudioOutput = (e) => {
- CallMediaHandler.setAudioOutput(e.target.value);
+ MediaDeviceHandler.setAudioOutput(e.target.value);
this.setState({
activeAudioOutput: e.target.value,
});
};
_setAudioInput = (e) => {
- CallMediaHandler.setAudioInput(e.target.value);
+ MediaDeviceHandler.setAudioInput(e.target.value);
this.setState({
activeAudioInput: e.target.value,
});
};
_setVideoInput = (e) => {
- CallMediaHandler.setVideoInput(e.target.value);
+ MediaDeviceHandler.setVideoInput(e.target.value);
this.setState({
activeVideoInput: e.target.value,
});
@@ -171,7 +171,7 @@ export default class VoiceUserSettingsTab extends React.Component {
}
};
- const audioOutputs = this.state.mediaDevices.audiooutput.slice(0);
+ const audioOutputs = this.state.mediaDevices.audioOutput.slice(0);
if (audioOutputs.length > 0) {
const defaultDevice = getDefaultDevice(audioOutputs);
speakerDropdown = (
@@ -183,7 +183,7 @@ export default class VoiceUserSettingsTab extends React.Component {
);
}
- const audioInputs = this.state.mediaDevices.audioinput.slice(0);
+ const audioInputs = this.state.mediaDevices.audioInput.slice(0);
if (audioInputs.length > 0) {
const defaultDevice = getDefaultDevice(audioInputs);
microphoneDropdown = (
@@ -195,7 +195,7 @@ export default class VoiceUserSettingsTab extends React.Component {
);
}
- const videoInputs = this.state.mediaDevices.videoinput.slice(0);
+ const videoInputs = this.state.mediaDevices.videoInput.slice(0);
if (videoInputs.length > 0) {
const defaultDevice = getDefaultDevice(videoInputs);
webcamDropdown = (
diff --git a/src/components/views/voip/AudioFeed.tsx b/src/components/views/voip/AudioFeed.tsx
index c78f0c0fc8..80d658e7ee 100644
--- a/src/components/views/voip/AudioFeed.tsx
+++ b/src/components/views/voip/AudioFeed.tsx
@@ -17,7 +17,7 @@ limitations under the License.
import React, {createRef} from 'react';
import { CallFeed, CallFeedEvent } from 'matrix-js-sdk/src/webrtc/callFeed';
import { logger } from 'matrix-js-sdk/src/logger';
-import CallMediaHandler from "../../../CallMediaHandler";
+import MediaDeviceHandler from "../../../MediaDeviceHandler";
interface IProps {
feed: CallFeed,
@@ -38,7 +38,7 @@ export default class AudioFeed extends React.Component {
private playMedia() {
const element = this.element.current;
- const audioOutput = CallMediaHandler.getAudioOutput();
+ const audioOutput = MediaDeviceHandler.getAudioOutput();
if (audioOutput) {
try {
diff --git a/src/voice/VoiceRecording.ts b/src/voice/VoiceRecording.ts
index fde5779fa2..8f9e03bb8e 100644
--- a/src/voice/VoiceRecording.ts
+++ b/src/voice/VoiceRecording.ts
@@ -17,7 +17,7 @@ limitations under the License.
import * as Recorder from 'opus-recorder';
import encoderPath from 'opus-recorder/dist/encoderWorker.min.js';
import {MatrixClient} from "matrix-js-sdk/src/client";
-import CallMediaHandler from "../CallMediaHandler";
+import MediaDeviceHandler from "../MediaDeviceHandler";
import {SimpleObservable} from "matrix-widget-api";
import {clamp, percentageOf, percentageWithin} from "../utils/numbers";
import EventEmitter from "events";
@@ -97,7 +97,7 @@ export class VoiceRecording extends EventEmitter implements IDestroyable {
audio: {
channelCount: CHANNELS,
noiseSuppression: true, // browsers ignore constraints they can't honour
- deviceId: CallMediaHandler.getAudioInput(),
+ deviceId: MediaDeviceHandler.getAudioInput(),
},
});
this.recorderContext = createAudioContext({
From 057f46ad9d10370416af0ad951858f17c1caff55 Mon Sep 17 00:00:00 2001
From: Germain Souquet
Date: Wed, 23 Jun 2021 08:44:48 +0100
Subject: [PATCH 075/100] fix dependency and lockfile mismatch
---
package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/package.json b/package.json
index bb703c33f0..6b369e9c27 100644
--- a/package.json
+++ b/package.json
@@ -123,7 +123,7 @@
"@sinonjs/fake-timers": "^7.0.2",
"@types/classnames": "^2.2.11",
"@types/counterpart": "^0.18.1",
- "@types/diff-match-patch": "^1.0.5",
+ "@types/diff-match-patch": "^1.0.32",
"@types/flux": "^3.1.9",
"@types/jest": "^26.0.20",
"@types/linkifyjs": "^2.1.3",
From 58151d71c5b806ece26345ba2fc37d3a5dbe7a3f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=A0imon=20Brandner?=
Date: Wed, 23 Jun 2021 09:56:37 +0200
Subject: [PATCH 076/100] Handle mid-call output changes
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Šimon Brandner
---
src/MediaDeviceHandler.ts | 27 ++++++++++++++++---
.../tabs/user/VoiceUserSettingsTab.js | 6 ++---
src/components/views/voip/AudioFeed.tsx | 18 ++++++++++---
3 files changed, 40 insertions(+), 11 deletions(-)
diff --git a/src/MediaDeviceHandler.ts b/src/MediaDeviceHandler.ts
index 96fd764b98..8780cea359 100644
--- a/src/MediaDeviceHandler.ts
+++ b/src/MediaDeviceHandler.ts
@@ -18,6 +18,7 @@ limitations under the License.
import SettingsStore from "./settings/SettingsStore";
import { SettingLevel } from "./settings/SettingLevel";
import { setMatrixCallAudioInput, setMatrixCallVideoInput } from "matrix-js-sdk/src/matrix";
+import EventEmitter from 'events';
interface IMediaDevices {
audioOutput: Array;
@@ -25,7 +26,22 @@ interface IMediaDevices {
videoInput: Array;
}
-export default class MediaDeviceHandler {
+export enum MediaDeviceHandlerEvent {
+ AudioOutputChanged = "audio_output_changed",
+ AudioInputChanged = "audio_input_changed",
+ VideoInputChanged = "video_input_changed",
+}
+
+export default class MediaDeviceHandler extends EventEmitter {
+ private static internalInstance;
+
+ public static get instance(): MediaDeviceHandler {
+ if (!MediaDeviceHandler.internalInstance) {
+ MediaDeviceHandler.internalInstance = new MediaDeviceHandler();
+ }
+ return MediaDeviceHandler.internalInstance;
+ }
+
static async hasAnyLabeledDevices(): Promise {
const devices = await navigator.mediaDevices.enumerateDevices();
return devices.some(d => Boolean(d.label));
@@ -68,18 +84,21 @@ export default class MediaDeviceHandler {
setMatrixCallVideoInput(videoDeviceId);
}
- static setAudioOutput(deviceId: string) {
+ public setAudioOutput(deviceId: string) {
SettingsStore.setValue("webrtc_audiooutput", null, SettingLevel.DEVICE, deviceId);
+ this.emit(MediaDeviceHandlerEvent.AudioOutputChanged, deviceId);
}
- static setAudioInput(deviceId: string) {
+ public setAudioInput(deviceId: string) {
SettingsStore.setValue("webrtc_audioinput", null, SettingLevel.DEVICE, deviceId);
setMatrixCallAudioInput(deviceId);
+ this.emit(MediaDeviceHandlerEvent.AudioInputChanged, deviceId);
}
- static setVideoInput(deviceId: string) {
+ public setVideoInput(deviceId: string) {
SettingsStore.setValue("webrtc_videoinput", null, SettingLevel.DEVICE, deviceId);
setMatrixCallVideoInput(deviceId);
+ this.emit(MediaDeviceHandlerEvent.VideoInputChanged, deviceId);
}
static getAudioOutput(): string {
diff --git a/src/components/views/settings/tabs/user/VoiceUserSettingsTab.js b/src/components/views/settings/tabs/user/VoiceUserSettingsTab.js
index 962f1fcd44..f730406eed 100644
--- a/src/components/views/settings/tabs/user/VoiceUserSettingsTab.js
+++ b/src/components/views/settings/tabs/user/VoiceUserSettingsTab.js
@@ -100,21 +100,21 @@ export default class VoiceUserSettingsTab extends React.Component {
};
_setAudioOutput = (e) => {
- MediaDeviceHandler.setAudioOutput(e.target.value);
+ MediaDeviceHandler.instance.setAudioOutput(e.target.value);
this.setState({
activeAudioOutput: e.target.value,
});
};
_setAudioInput = (e) => {
- MediaDeviceHandler.setAudioInput(e.target.value);
+ MediaDeviceHandler.instance.setAudioInput(e.target.value);
this.setState({
activeAudioInput: e.target.value,
});
};
_setVideoInput = (e) => {
- MediaDeviceHandler.setVideoInput(e.target.value);
+ MediaDeviceHandler.instance.setVideoInput(e.target.value);
this.setState({
activeVideoInput: e.target.value,
});
diff --git a/src/components/views/voip/AudioFeed.tsx b/src/components/views/voip/AudioFeed.tsx
index 80d658e7ee..d29caf789e 100644
--- a/src/components/views/voip/AudioFeed.tsx
+++ b/src/components/views/voip/AudioFeed.tsx
@@ -17,7 +17,7 @@ limitations under the License.
import React, {createRef} from 'react';
import { CallFeed, CallFeedEvent } from 'matrix-js-sdk/src/webrtc/callFeed';
import { logger } from 'matrix-js-sdk/src/logger';
-import MediaDeviceHandler from "../../../MediaDeviceHandler";
+import MediaDeviceHandler, { MediaDeviceHandlerEvent } from "../../../MediaDeviceHandler";
interface IProps {
feed: CallFeed,
@@ -27,19 +27,25 @@ export default class AudioFeed extends React.Component {
private element = createRef();
componentDidMount() {
+ MediaDeviceHandler.instance.addListener(
+ MediaDeviceHandlerEvent.AudioOutputChanged,
+ this.onAudioOutputChanged,
+ );
this.props.feed.addListener(CallFeedEvent.NewStream, this.onNewStream);
this.playMedia();
}
componentWillUnmount() {
+ MediaDeviceHandler.instance.removeListener(
+ MediaDeviceHandlerEvent.AudioOutputChanged,
+ this.onAudioOutputChanged,
+ );
this.props.feed.removeListener(CallFeedEvent.NewStream, this.onNewStream);
this.stopMedia();
}
- private playMedia() {
+ private onAudioOutputChanged = (audioOutput: string) => {
const element = this.element.current;
- const audioOutput = MediaDeviceHandler.getAudioOutput();
-
if (audioOutput) {
try {
// This seems quite unreliable in Chrome, although I haven't yet managed to make a jsfiddle where
@@ -52,7 +58,11 @@ export default class AudioFeed extends React.Component {
logger.warn("Couldn't set requested audio output device: using default", e);
}
}
+ }
+ private playMedia() {
+ const element = this.element.current;
+ this.onAudioOutputChanged(MediaDeviceHandler.getAudioOutput());
element.muted = false;
element.srcObject = this.props.feed.stream;
element.autoplay = true;
From 6c9e0e54e989eb2a1ea0d04645be6bc415380c12 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=A0imon=20Brandner?=
Date: Wed, 23 Jun 2021 10:27:51 +0200
Subject: [PATCH 077/100] Iterate PR
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Šimon Brandner
---
src/MediaDeviceHandler.ts | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/src/MediaDeviceHandler.ts b/src/MediaDeviceHandler.ts
index 8780cea359..b8b00963b6 100644
--- a/src/MediaDeviceHandler.ts
+++ b/src/MediaDeviceHandler.ts
@@ -42,12 +42,12 @@ export default class MediaDeviceHandler extends EventEmitter {
return MediaDeviceHandler.internalInstance;
}
- static async hasAnyLabeledDevices(): Promise {
+ public static async hasAnyLabeledDevices(): Promise {
const devices = await navigator.mediaDevices.enumerateDevices();
return devices.some(d => Boolean(d.label));
}
- static async getDevices(): Promise {
+ public static async getDevices(): Promise {
// Only needed for Electron atm, though should work in modern browsers
// once permission has been granted to the webapp
@@ -76,7 +76,7 @@ export default class MediaDeviceHandler extends EventEmitter {
}
}
- static loadDevices() {
+ public static loadDevices(): void {
const audioDeviceId = SettingsStore.getValue("webrtc_audioinput");
const videoDeviceId = SettingsStore.getValue("webrtc_videoinput");
@@ -84,32 +84,32 @@ export default class MediaDeviceHandler extends EventEmitter {
setMatrixCallVideoInput(videoDeviceId);
}
- public setAudioOutput(deviceId: string) {
+ public setAudioOutput(deviceId: string): void {
SettingsStore.setValue("webrtc_audiooutput", null, SettingLevel.DEVICE, deviceId);
this.emit(MediaDeviceHandlerEvent.AudioOutputChanged, deviceId);
}
- public setAudioInput(deviceId: string) {
+ public setAudioInput(deviceId: string): void {
SettingsStore.setValue("webrtc_audioinput", null, SettingLevel.DEVICE, deviceId);
setMatrixCallAudioInput(deviceId);
this.emit(MediaDeviceHandlerEvent.AudioInputChanged, deviceId);
}
- public setVideoInput(deviceId: string) {
+ public setVideoInput(deviceId: string): void {
SettingsStore.setValue("webrtc_videoinput", null, SettingLevel.DEVICE, deviceId);
setMatrixCallVideoInput(deviceId);
this.emit(MediaDeviceHandlerEvent.VideoInputChanged, deviceId);
}
- static getAudioOutput(): string {
+ public static getAudioOutput(): string {
return SettingsStore.getValueAt(SettingLevel.DEVICE, "webrtc_audiooutput");
}
- static getAudioInput(): string {
+ public static getAudioInput(): string {
return SettingsStore.getValueAt(SettingLevel.DEVICE, "webrtc_audioinput");
}
- static getVideoInput(): string {
+ public static getVideoInput(): string {
return SettingsStore.getValueAt(SettingLevel.DEVICE, "webrtc_videoinput");
}
}
From 95624b3fa62e4e223911720faf5da99739ac1493 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=A0imon=20Brandner?=
Date: Wed, 23 Jun 2021 10:37:15 +0200
Subject: [PATCH 078/100] Add some docs
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Šimon Brandner
---
src/MediaDeviceHandler.ts | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/src/MediaDeviceHandler.ts b/src/MediaDeviceHandler.ts
index b8b00963b6..10099bc7b8 100644
--- a/src/MediaDeviceHandler.ts
+++ b/src/MediaDeviceHandler.ts
@@ -76,6 +76,9 @@ export default class MediaDeviceHandler extends EventEmitter {
}
}
+ /**
+ * Retrieves devices from the SettingsStore and tells the js-sdk to use them
+ */
public static loadDevices(): void {
const audioDeviceId = SettingsStore.getValue("webrtc_audioinput");
const videoDeviceId = SettingsStore.getValue("webrtc_videoinput");
@@ -89,12 +92,22 @@ export default class MediaDeviceHandler extends EventEmitter {
this.emit(MediaDeviceHandlerEvent.AudioOutputChanged, deviceId);
}
+ /**
+ * This will not change the device that a potential call uses. The call will
+ * need to be ended and started again for this change to take effect
+ * @param {string} deviceId
+ */
public setAudioInput(deviceId: string): void {
SettingsStore.setValue("webrtc_audioinput", null, SettingLevel.DEVICE, deviceId);
setMatrixCallAudioInput(deviceId);
this.emit(MediaDeviceHandlerEvent.AudioInputChanged, deviceId);
}
+ /**
+ * This will not change the device that a potential call uses. The call will
+ * need to be ended and started again for this change to take effect
+ * @param {string} deviceId
+ */
public setVideoInput(deviceId: string): void {
SettingsStore.setValue("webrtc_videoinput", null, SettingLevel.DEVICE, deviceId);
setMatrixCallVideoInput(deviceId);
From 1bef985d46b507f1c0971e4b1ab54381d1c83e3f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=A0imon=20Brandner?=
Date: Wed, 23 Jun 2021 10:39:10 +0200
Subject: [PATCH 079/100] Remove emmiting that isn't useful for us now
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Šimon Brandner
---
src/MediaDeviceHandler.ts | 4 ----
1 file changed, 4 deletions(-)
diff --git a/src/MediaDeviceHandler.ts b/src/MediaDeviceHandler.ts
index 10099bc7b8..e0375df1a4 100644
--- a/src/MediaDeviceHandler.ts
+++ b/src/MediaDeviceHandler.ts
@@ -28,8 +28,6 @@ interface IMediaDevices {
export enum MediaDeviceHandlerEvent {
AudioOutputChanged = "audio_output_changed",
- AudioInputChanged = "audio_input_changed",
- VideoInputChanged = "video_input_changed",
}
export default class MediaDeviceHandler extends EventEmitter {
@@ -100,7 +98,6 @@ export default class MediaDeviceHandler extends EventEmitter {
public setAudioInput(deviceId: string): void {
SettingsStore.setValue("webrtc_audioinput", null, SettingLevel.DEVICE, deviceId);
setMatrixCallAudioInput(deviceId);
- this.emit(MediaDeviceHandlerEvent.AudioInputChanged, deviceId);
}
/**
@@ -111,7 +108,6 @@ export default class MediaDeviceHandler extends EventEmitter {
public setVideoInput(deviceId: string): void {
SettingsStore.setValue("webrtc_videoinput", null, SettingLevel.DEVICE, deviceId);
setMatrixCallVideoInput(deviceId);
- this.emit(MediaDeviceHandlerEvent.VideoInputChanged, deviceId);
}
public static getAudioOutput(): string {
From 0e582c425cfb62bfef1147d7a306199d5992ab86 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=A0imon=20Brandner?=
Date: Wed, 23 Jun 2021 10:42:19 +0200
Subject: [PATCH 080/100] Make this look nicer
Co-authored-by: Michael Telatynski <7t3chguy@googlemail.com>
---
src/MediaDeviceHandler.ts | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/src/MediaDeviceHandler.ts b/src/MediaDeviceHandler.ts
index e0375df1a4..45ebcd22eb 100644
--- a/src/MediaDeviceHandler.ts
+++ b/src/MediaDeviceHandler.ts
@@ -64,11 +64,7 @@ export default class MediaDeviceHandler extends EventEmitter {
}
});
- return {
- audioOutput: audioOutput,
- audioInput: audioInput,
- videoInput: videoInput,
- };
+ return { audioOutput, audioInput, videoInput };
} catch (error) {
console.log('Unable to refresh WebRTC Devices: ', error);
}
From 4ab9758b671d408f47399fe744290f41507b02ba Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=A0imon=20Brandner?=
Date: Wed, 23 Jun 2021 10:43:49 +0200
Subject: [PATCH 081/100] log -> warn
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Šimon Brandner
---
src/MediaDeviceHandler.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/MediaDeviceHandler.ts b/src/MediaDeviceHandler.ts
index 45ebcd22eb..49ef123def 100644
--- a/src/MediaDeviceHandler.ts
+++ b/src/MediaDeviceHandler.ts
@@ -66,7 +66,7 @@ export default class MediaDeviceHandler extends EventEmitter {
return { audioOutput, audioInput, videoInput };
} catch (error) {
- console.log('Unable to refresh WebRTC Devices: ', error);
+ console.warn('Unable to refresh WebRTC Devices: ', error);
}
}
From a8dfc4488ffd51f36d23e16e92d26b2d3750a328 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Wed, 23 Jun 2021 14:47:24 +0100
Subject: [PATCH 082/100] Convert more of js-sdk crypto and fix underscored
field accesses
---
src/SecurityManager.ts | 5 +++--
src/components/views/dialogs/DevtoolsDialog.tsx | 2 +-
src/components/views/settings/CrossSigningPanel.js | 4 ++--
src/components/views/settings/SecureBackupPanel.js | 2 +-
4 files changed, 7 insertions(+), 6 deletions(-)
diff --git a/src/SecurityManager.ts b/src/SecurityManager.ts
index 09c8d30614..1ba0d6439b 100644
--- a/src/SecurityManager.ts
+++ b/src/SecurityManager.ts
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import { ICryptoCallbacks, IDeviceTrustLevel, ISecretStorageKeyInfo } from 'matrix-js-sdk/src/matrix';
+import { ICryptoCallbacks, ISecretStorageKeyInfo } from 'matrix-js-sdk/src/matrix';
import { MatrixClient } from 'matrix-js-sdk/src/client';
import Modal from './Modal';
import * as sdk from './index';
@@ -28,6 +28,7 @@ import AccessSecretStorageDialog from './components/views/dialogs/security/Acces
import RestoreKeyBackupDialog from './components/views/dialogs/security/RestoreKeyBackupDialog';
import SettingsStore from "./settings/SettingsStore";
import SecurityCustomisations from "./customisations/Security";
+import { DeviceTrustLevel } from 'matrix-js-sdk/src/crypto/CrossSigning';
// This stores the secret storage private keys in memory for the JS SDK. This is
// only meant to act as a cache to avoid prompting the user multiple times
@@ -244,7 +245,7 @@ async function onSecretRequested(
deviceId: string,
requestId: string,
name: string,
- deviceTrust: IDeviceTrustLevel,
+ deviceTrust: DeviceTrustLevel,
): Promise {
console.log("onSecretRequested", userId, deviceId, requestId, name, deviceTrust);
const client = MatrixClientPeg.get();
diff --git a/src/components/views/dialogs/DevtoolsDialog.tsx b/src/components/views/dialogs/DevtoolsDialog.tsx
index 2690eb67d7..b1749b370a 100644
--- a/src/components/views/dialogs/DevtoolsDialog.tsx
+++ b/src/components/views/dialogs/DevtoolsDialog.tsx
@@ -766,7 +766,7 @@ class VerificationExplorer extends React.PureComponent {
render() {
const cli = this.context;
const room = this.props.room;
- const inRoomChannel = cli.crypto._inRoomVerificationRequests;
+ const inRoomChannel = cli.crypto.inRoomVerificationRequests;
const inRoomRequests = (inRoomChannel._requestsByRoomId || new Map()).get(room.roomId) || new Map();
return (
diff --git a/src/components/views/settings/CrossSigningPanel.js b/src/components/views/settings/CrossSigningPanel.js
index 0cd1a64ada..43a13a48a7 100644
--- a/src/components/views/settings/CrossSigningPanel.js
+++ b/src/components/views/settings/CrossSigningPanel.js
@@ -79,8 +79,8 @@ export default class CrossSigningPanel extends React.PureComponent {
async _getUpdatedStatus() {
const cli = MatrixClientPeg.get();
const pkCache = cli.getCrossSigningCacheCallbacks();
- const crossSigning = cli.crypto._crossSigningInfo;
- const secretStorage = cli.crypto._secretStorage;
+ const crossSigning = cli.crypto.crossSigningInfo;
+ const secretStorage = cli.crypto.secretStorage;
const crossSigningPublicKeysOnDevice = crossSigning.getId();
const crossSigningPrivateKeysInStorage = await crossSigning.isStoredInSecretStorage(secretStorage);
const masterPrivateKeyCached = !!(pkCache && await pkCache.getCrossSigningKeyCache("master"));
diff --git a/src/components/views/settings/SecureBackupPanel.js b/src/components/views/settings/SecureBackupPanel.js
index 4f3eb0bdf6..abfd18f0d3 100644
--- a/src/components/views/settings/SecureBackupPanel.js
+++ b/src/components/views/settings/SecureBackupPanel.js
@@ -131,7 +131,7 @@ export default class SecureBackupPanel extends React.PureComponent {
async _getUpdatedDiagnostics() {
const cli = MatrixClientPeg.get();
- const secretStorage = cli.crypto._secretStorage;
+ const secretStorage = cli.crypto.secretStorage;
const backupKeyStored = !!(await cli.isKeyBackupKeyStored());
const backupKeyFromCache = await cli.crypto.getSessionBackupPrivateKey();
From 99c442cea78f2e0ff3666f127d1260be2bf313e5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=A0imon=20Brandner?=
Date: Wed, 23 Jun 2021 13:45:55 +0200
Subject: [PATCH 083/100] Convert MemberList to TS
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Šimon Brandner
---
.../rooms/{MemberList.js => MemberList.tsx} | 292 +++++++++++-------
1 file changed, 174 insertions(+), 118 deletions(-)
rename src/components/views/rooms/{MemberList.js => MemberList.tsx} (64%)
diff --git a/src/components/views/rooms/MemberList.js b/src/components/views/rooms/MemberList.tsx
similarity index 64%
rename from src/components/views/rooms/MemberList.js
rename to src/components/views/rooms/MemberList.tsx
index cb50f0fff3..5353eb94ed 100644
--- a/src/components/views/rooms/MemberList.js
+++ b/src/components/views/rooms/MemberList.tsx
@@ -2,6 +2,7 @@
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Copyright 2017, 2018 New Vector Ltd
+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.
@@ -20,17 +21,28 @@ import React from 'react';
import { _t } from '../../../languageHandler';
import SdkConfig from '../../../SdkConfig';
import dis from '../../../dispatcher/dispatcher';
-import {isValid3pidInvite} from "../../../RoomInvite";
-import rate_limited_func from "../../../ratelimitedfunc";
-import {MatrixClientPeg} from "../../../MatrixClientPeg";
-import * as sdk from "../../../index";
-import {CommunityPrototypeStore} from "../../../stores/CommunityPrototypeStore";
+import { isValid3pidInvite } from "../../../RoomInvite";
+import rateLimitedFunction from "../../../ratelimitedfunc";
+import { MatrixClientPeg } from "../../../MatrixClientPeg";
+import { CommunityPrototypeStore } from "../../../stores/CommunityPrototypeStore";
import BaseCard from "../right_panel/BaseCard";
-import {RightPanelPhases} from "../../../stores/RightPanelStorePhases";
+import { RightPanelPhases } from "../../../stores/RightPanelStorePhases";
import RoomAvatar from "../avatars/RoomAvatar";
import RoomName from "../elements/RoomName";
-import {replaceableComponent} from "../../../utils/replaceableComponent";
+import { replaceableComponent } from "../../../utils/replaceableComponent";
import SettingsStore from "../../../settings/SettingsStore";
+import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
+import { Room } from 'matrix-js-sdk/src/models/room';
+import { RoomMember } from 'matrix-js-sdk/src/models/room-member';
+import { RoomState } from 'matrix-js-sdk/src/models/room-state';
+import { User } from "matrix-js-sdk/src/models/user";
+import TruncatedList from '../elements/TruncatedList';
+import Spinner from "../elements/Spinner";
+import SearchBox from "../../structures/SearchBox";
+import AccessibleButton from '../elements/AccessibleButton';
+import EntityTile from "./EntityTile";
+import MemberTile from "./MemberTile";
+import BaseAvatar from '../avatars/BaseAvatar';
const INITIAL_LOAD_NUM_MEMBERS = 30;
const INITIAL_LOAD_NUM_INVITED = 5;
@@ -40,41 +52,62 @@ const SHOW_MORE_INCREMENT = 100;
// matches all ASCII punctuation: !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
const SORT_REGEX = /[\x21-\x2F\x3A-\x40\x5B-\x60\x7B-\x7E]+/g;
+interface IProps {
+ roomId: string;
+ onClose(): void;
+}
+
+interface IState {
+ loading: boolean;
+ members: Array;
+ filteredJoinedMembers: Array;
+ filteredInvitedMembers: Array;
+ canInvite: boolean;
+ truncateAtJoined: number;
+ truncateAtInvited: number;
+ searchQuery: string;
+}
+
@replaceableComponent("views.rooms.MemberList")
-export default class MemberList extends React.Component {
+export default class MemberList extends React.Component {
+ private showPresence = true;
+ private mounted = false;
+ private collator: Intl.Collator;
+ private sortNames = new Map(); // RoomMember -> sortName
+
constructor(props) {
super(props);
const cli = MatrixClientPeg.get();
if (cli.hasLazyLoadMembersEnabled()) {
// show an empty list
- this.state = this._getMembersState([]);
+ this.state = this.getMembersState([]);
} else {
- this.state = this._getMembersState(this.roomMembers());
+ this.state = this.getMembersState(this.roomMembers());
}
cli.on("Room", this.onRoom); // invites & joining after peek
const enablePresenceByHsUrl = SdkConfig.get()["enable_presence_by_hs_url"];
const hsUrl = MatrixClientPeg.get().baseUrl;
- this._showPresence = true;
+ this.showPresence = true;
if (enablePresenceByHsUrl && enablePresenceByHsUrl[hsUrl] !== undefined) {
- this._showPresence = enablePresenceByHsUrl[hsUrl];
+ this.showPresence = enablePresenceByHsUrl[hsUrl];
}
}
// eslint-disable-next-line camelcase
UNSAFE_componentWillMount() {
const cli = MatrixClientPeg.get();
- this._mounted = true;
+ this.mounted = true;
if (cli.hasLazyLoadMembersEnabled()) {
- this._showMembersAccordingToMembershipWithLL();
+ this.showMembersAccordingToMembershipWithLL();
cli.on("Room.myMembership", this.onMyMembership);
} else {
- this._listenForMembersChanges();
+ this.listenForMembersChanges();
}
}
- _listenForMembersChanges() {
+ private listenForMembersChanges(): void {
const cli = MatrixClientPeg.get();
cli.on("RoomState.members", this.onRoomStateMember);
cli.on("RoomMember.name", this.onRoomMemberName);
@@ -89,7 +122,7 @@ export default class MemberList extends React.Component {
}
componentWillUnmount() {
- this._mounted = false;
+ this.mounted = false;
const cli = MatrixClientPeg.get();
if (cli) {
cli.removeListener("RoomState.members", this.onRoomStateMember);
@@ -103,7 +136,7 @@ export default class MemberList extends React.Component {
}
// cancel any pending calls to the rate_limited_funcs
- this._updateList.cancelPendingCall();
+ this.updateList.cancelPendingCall();
}
/**
@@ -111,7 +144,7 @@ export default class MemberList extends React.Component {
* show a spinner and load the members if the user is joined,
* or show the members available so far if the user is invited
*/
- async _showMembersAccordingToMembershipWithLL() {
+ private async showMembersAccordingToMembershipWithLL(): Promise {
const cli = MatrixClientPeg.get();
if (cli.hasLazyLoadMembersEnabled()) {
const cli = MatrixClientPeg.get();
@@ -122,31 +155,31 @@ export default class MemberList extends React.Component {
try {
await room.loadMembersIfNeeded();
} catch (ex) {/* already logged in RoomView */}
- if (this._mounted) {
- this.setState(this._getMembersState(this.roomMembers()));
- this._listenForMembersChanges();
+ if (this.mounted) {
+ this.setState(this.getMembersState(this.roomMembers()));
+ this.listenForMembersChanges();
}
} else {
// show the members we already have loaded
- this.setState(this._getMembersState(this.roomMembers()));
+ this.setState(this.getMembersState(this.roomMembers()));
}
}
}
- get canInvite() {
+ private get canInvite(): boolean {
const cli = MatrixClientPeg.get();
const room = cli.getRoom(this.props.roomId);
return room && room.canInvite(cli.getUserId());
}
- _getMembersState(members) {
- // set the state after determining _showPresence to make sure it's
- // taken into account while rerendering
+ private getMembersState(members: Array): IState {
+ // set the state after determining showPresence to make sure it's
+ // taken into account while rendering
return {
loading: false,
members: members,
- filteredJoinedMembers: this._filterMembers(members, 'join'),
- filteredInvitedMembers: this._filterMembers(members, 'invite'),
+ filteredJoinedMembers: this.filterMembers(members, 'join'),
+ filteredInvitedMembers: this.filterMembers(members, 'invite'),
canInvite: this.canInvite,
// ideally we'd size this to the page height, but
@@ -157,72 +190,72 @@ export default class MemberList extends React.Component {
};
}
- onUserPresenceChange = (event, user) => {
+ private onUserPresenceChange = (event: MatrixEvent, user: User): void => {
// Attach a SINGLE listener for global presence changes then locate the
// member tile and re-render it. This is more efficient than every tile
// ever attaching their own listener.
const tile = this.refs[user.userId];
// console.log(`Got presence update for ${user.userId}. hasTile=${!!tile}`);
if (tile) {
- this._updateList(); // reorder the membership list
+ this.updateList(); // reorder the membership list
}
};
- onRoom = room => {
+ private onRoom = (room: Room): void => {
if (room.roomId !== this.props.roomId) {
return;
}
// We listen for room events because when we accept an invite
// we need to wait till the room is fully populated with state
// before refreshing the member list else we get a stale list.
- this._showMembersAccordingToMembershipWithLL();
+ this.showMembersAccordingToMembershipWithLL();
};
- onMyMembership = (room, membership, oldMembership) => {
+ private onMyMembership = (room: Room, membership: string, oldMembership: string): void => {
if (room.roomId === this.props.roomId && membership === "join") {
- this._showMembersAccordingToMembershipWithLL();
+ this.showMembersAccordingToMembershipWithLL();
}
};
- onRoomStateMember = (ev, state, member) => {
+ private onRoomStateMember = (ev: MatrixEvent, state: RoomState, member: RoomMember): void => {
if (member.roomId !== this.props.roomId) {
return;
}
- this._updateList();
+ this.updateList();
};
- onRoomMemberName = (ev, member) => {
+ private onRoomMemberName = (ev: MatrixEvent, member: RoomMember): void => {
if (member.roomId !== this.props.roomId) {
return;
}
- this._updateList();
+ this.updateList();
};
- onRoomStateEvent = (event, state) => {
+ private onRoomStateEvent = (event: MatrixEvent, state: RoomState): void => {
if (event.getRoomId() === this.props.roomId &&
event.getType() === "m.room.third_party_invite") {
- this._updateList();
+ this.updateList();
}
if (this.canInvite !== this.state.canInvite) this.setState({ canInvite: this.canInvite });
};
- _updateList = rate_limited_func(() => {
- this._updateListNow();
+ private updateList = rateLimitedFunction(() => {
+ this.updateListNow();
}, 500);
- _updateListNow() {
- // console.log("Updating memberlist");
- const newState = {
+ private updateListNow(): void {
+ const members = this.roomMembers()
+
+ this.setState({
loading: false,
- members: this.roomMembers(),
- };
- newState.filteredJoinedMembers = this._filterMembers(newState.members, 'join', this.state.searchQuery);
- newState.filteredInvitedMembers = this._filterMembers(newState.members, 'invite', this.state.searchQuery);
- this.setState(newState);
+ members: members,
+ filteredJoinedMembers: this.filterMembers(members, 'join', this.state.searchQuery),
+ filteredInvitedMembers: this.filterMembers(members, 'invite', this.state.searchQuery),
+ });
}
- getMembersWithUser() {
+ private getMembersWithUser(): Array {
if (!this.props.roomId) return [];
const cli = MatrixClientPeg.get();
const room = cli.getRoom(this.props.roomId);
@@ -230,15 +263,18 @@ export default class MemberList extends React.Component {
const allMembers = Object.values(room.currentState.members);
- allMembers.forEach(function(member) {
+ allMembers.forEach((member) => {
// work around a race where you might have a room member object
- // before the user object exists. This may or may not cause
+ // before the user object exists. This may or may not cause
// https://github.com/vector-im/vector-web/issues/186
- if (member.user === null) {
+ if (!member.user) {
member.user = cli.getUser(member.userId);
}
- member.sortName = (member.name[0] === '@' ? member.name.substr(1) : member.name).replace(SORT_REGEX, "");
+ this.sortNames.set(
+ member,
+ (member.name[0] === '@' ? member.name.substr(1) : member.name).replace(SORT_REGEX, ""),
+ );
// XXX: this user may have no lastPresenceTs value!
// the right solution here is to fix the race rather than leave it as 0
@@ -247,7 +283,7 @@ export default class MemberList extends React.Component {
return allMembers;
}
- roomMembers() {
+ private roomMembers(): Array {
const allMembers = this.getMembersWithUser();
const filteredAndSortedMembers = allMembers.filter((m) => {
return (
@@ -255,23 +291,21 @@ export default class MemberList extends React.Component {
);
});
const language = SettingsStore.getValue("language");
- this.collator = new Intl.Collator(language, { sensitivity: 'base', usePunctuation: true });
+ this.collator = new Intl.Collator(language, { sensitivity: 'base', ignorePunctuation: false });
filteredAndSortedMembers.sort(this.memberSort);
return filteredAndSortedMembers;
}
- _createOverflowTileJoined = (overflowCount, totalCount) => {
- return this._createOverflowTile(overflowCount, totalCount, this._showMoreJoinedMemberList);
+ private createOverflowTileJoined = (overflowCount: number, totalCount: number): JSX.Element => {
+ return this.createOverflowTile(overflowCount, totalCount, this.showMoreJoinedMemberList);
};
- _createOverflowTileInvited = (overflowCount, totalCount) => {
- return this._createOverflowTile(overflowCount, totalCount, this._showMoreInvitedMemberList);
+ private createOverflowTileInvited = (overflowCount: number, totalCount: number): JSX.Element => {
+ return this.createOverflowTile(overflowCount, totalCount, this.showMoreInvitedMemberList);
};
- _createOverflowTile = (overflowCount, totalCount, onClick) => {
+ private createOverflowTile = (overflowCount: number, totalCount: number, onClick: () => void): JSX.Element=> {
// For now we'll pretend this is any entity. It should probably be a separate tile.
- const EntityTile = sdk.getComponent("rooms.EntityTile");
- const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
const text = _t("and %(count)s others...", { count: overflowCount });
return (
{
+ private showMoreJoinedMemberList = (): void => {
this.setState({
truncateAtJoined: this.state.truncateAtJoined + SHOW_MORE_INCREMENT,
});
};
- _showMoreInvitedMemberList = () => {
+ private showMoreInvitedMemberList = (): void => {
this.setState({
truncateAtInvited: this.state.truncateAtInvited + SHOW_MORE_INCREMENT,
});
};
- memberString(member) {
+ /**
+ * SHOULD ONLY BE USED BY TESTS
+ */
+ public memberString(member: RoomMember): string {
if (!member) {
return "(null)";
} else {
const u = member.user;
- return "(" + member.name + ", " + member.powerLevel + ", " + (u ? u.lastActiveAgo : "") + ", " + (u ? u.getLastActiveTs() : "") + ", " + (u ? u.currentlyActive : "") + ", " + (u ? u.presence : "") + ")";
+ return (
+ "(" +
+ member.name +
+ ", " +
+ member.powerLevel +
+ ", " +
+ (u ? u.lastActiveAgo : "") +
+ ", " +
+ (u ? u.getLastActiveTs() : "") +
+ ", " +
+ (u ? u.currentlyActive : "") +
+ ", " +
+ (u ? u.presence : "") +
+ ")"
+ );
}
}
// returns negative if a comes before b,
// returns 0 if a and b are equivalent in ordering
// returns positive if a comes after b.
- memberSort = (memberA, memberB) => {
+ private memberSort = (memberA: RoomMember, memberB: RoomMember): number => {
// order by presence, with "active now" first.
// ...and then by power level
// ...and then by last active
@@ -325,7 +376,7 @@ export default class MemberList extends React.Component {
if (!userA && userB) return 1;
// First by presence
- if (this._showPresence) {
+ if (this.showPresence) {
const convertPresence = (p) => p === 'unavailable' ? 'online' : p;
const presenceIndex = p => {
const order = ['active', 'online', 'offline'];
@@ -349,31 +400,31 @@ export default class MemberList extends React.Component {
}
// Third by last active
- if (this._showPresence && userA.getLastActiveTs() !== userB.getLastActiveTs()) {
+ if (this.showPresence && userA.getLastActiveTs() !== userB.getLastActiveTs()) {
// console.log("Comparing on last active timestamp - returning");
return userB.getLastActiveTs() - userA.getLastActiveTs();
}
// Fourth by name (alphabetical)
- return this.collator.compare(memberA.sortName, memberB.sortName);
+ return this.collator.compare(this.sortNames.get(memberA), this.sortNames.get(memberB));
};
- onSearchQueryChanged = searchQuery => {
+ private onSearchQueryChanged = (searchQuery: string): void => {
this.setState({
searchQuery,
- filteredJoinedMembers: this._filterMembers(this.state.members, 'join', searchQuery),
- filteredInvitedMembers: this._filterMembers(this.state.members, 'invite', searchQuery),
+ filteredJoinedMembers: this.filterMembers(this.state.members, 'join', searchQuery),
+ filteredInvitedMembers: this.filterMembers(this.state.members, 'invite', searchQuery),
});
};
- _onPending3pidInviteClick = inviteEvent => {
+ private onPending3pidInviteClick = (inviteEvent: MatrixEvent): void => {
dis.dispatch({
action: 'view_3pid_invite',
event: inviteEvent,
});
};
- _filterMembers(members, membership, query) {
+ private filterMembers(members: Array, membership: string, query?: string): Array {
return members.filter((m) => {
if (query) {
query = query.toLowerCase();
@@ -389,7 +440,7 @@ export default class MemberList extends React.Component {
});
}
- _getPending3PidInvites() {
+ private getPending3PidInvites(): Array {
// include 3pid invites (m.room.third_party_invite) state events.
// The HS may have already converted these into m.room.member invites so
// we shouldn't add them if the 3pid invite state key (token) is in the
@@ -409,42 +460,40 @@ export default class MemberList extends React.Component {
}
}
- _makeMemberTiles(members) {
- const MemberTile = sdk.getComponent("rooms.MemberTile");
- const EntityTile = sdk.getComponent("rooms.EntityTile");
-
+ private makeMemberTiles(members: Array) {
return members.map((m) => {
- if (m.userId) {
+ if (m instanceof RoomMember) {
// Is a Matrix invite
- return ;
+ return ;
} else {
// Is a 3pid invite
return this._onPending3pidInviteClick(m)} />;
+ onClick={() => this.onPending3pidInviteClick(m)} />;
}
});
}
- _getChildrenJoined = (start, end) => this._makeMemberTiles(this.state.filteredJoinedMembers.slice(start, end));
-
- _getChildCountJoined = () => this.state.filteredJoinedMembers.length;
-
- _getChildrenInvited = (start, end) => {
- let targets = this.state.filteredInvitedMembers;
- if (end > this.state.filteredInvitedMembers.length) {
- targets = targets.concat(this._getPending3PidInvites());
- }
-
- return this._makeMemberTiles(targets.slice(start, end));
+ private getChildrenJoined = (start: number, end: number): Array => {
+ return this.makeMemberTiles(this.state.filteredJoinedMembers.slice(start, end))
};
- _getChildCountInvited = () => {
- return this.state.filteredInvitedMembers.length + (this._getPending3PidInvites() || []).length;
+ private getChildCountJoined = (): number => this.state.filteredJoinedMembers.length;
+
+ private getChildrenInvited = (start: number, end: number): Array => {
+ let targets = this.state.filteredInvitedMembers;
+ if (end > this.state.filteredInvitedMembers.length) {
+ targets = targets.concat(this.getPending3PidInvites());
+ }
+
+ return this.makeMemberTiles(targets.slice(start, end));
+ };
+
+ private getChildCountInvited = (): number => {
+ return this.state.filteredInvitedMembers.length + (this.getPending3PidInvites() || []).length;
}
render() {
if (this.state.loading) {
- const Spinner = sdk.getComponent("elements.Spinner");
return ;
}
- const SearchBox = sdk.getComponent('structures.SearchBox');
- const TruncatedList = sdk.getComponent("elements.TruncatedList");
-
const cli = MatrixClientPeg.get();
const room = cli.getRoom(this.props.roomId);
let inviteButton;
@@ -470,22 +516,30 @@ export default class MemberList extends React.Component {
inviteButtonText = _t("Invite to this space");
}
- const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
- inviteButton =
-
+ inviteButton = (
+
{ inviteButtonText }
- ;
+
+ );
}
let invitedHeader;
let invitedSection;
- if (this._getChildCountInvited() > 0) {
+ if (this.getChildCountInvited() > 0) {
invitedHeader = { _t("Invited") } ;
- invitedSection = ;
+ invitedSection = (
+
+ );
}
const footer = (
@@ -517,17 +571,19 @@ export default class MemberList extends React.Component {
previousPhase={previousPhase}
>
-
+
{ invitedHeader }
{ invitedSection }
;
}
- onInviteButtonClick = () => {
+ onInviteButtonClick = (): void => {
if (MatrixClientPeg.get().isGuest()) {
dis.dispatch({action: 'require_registration'});
return;
From 0df6200dd0f5ac87c5897a4d7f014ce2d93afed0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=A0imon=20Brandner?=
Date: Wed, 23 Jun 2021 14:32:41 +0200
Subject: [PATCH 084/100] Convert MemberList-test to TS
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Šimon Brandner
---
...MemberList-test.js => MemberList-test.tsx} | 53 +++++++++++++------
1 file changed, 37 insertions(+), 16 deletions(-)
rename test/components/views/rooms/{MemberList-test.js => MemberList-test.tsx} (88%)
diff --git a/test/components/views/rooms/MemberList-test.js b/test/components/views/rooms/MemberList-test.tsx
similarity index 88%
rename from test/components/views/rooms/MemberList-test.js
rename to test/components/views/rooms/MemberList-test.tsx
index 28fead770c..8012c43c4b 100644
--- a/test/components/views/rooms/MemberList-test.js
+++ b/test/components/views/rooms/MemberList-test.tsx
@@ -1,21 +1,36 @@
+/*
+Copyright 2021 Šimon Brandner
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
import React from 'react';
import ReactTestUtils from 'react-dom/test-utils';
import ReactDOM from 'react-dom';
import * as TestUtils from '../../../test-utils';
-
-import {MatrixClientPeg} from '../../../../src/MatrixClientPeg';
import sdk from '../../../skinned-sdk';
-
-import {Room, RoomMember, User} from 'matrix-js-sdk';
-
+import { MatrixClientPeg } from '../../../../src/MatrixClientPeg';
+import { Room } from 'matrix-js-sdk/src/models/room';
+import { RoomMember } from 'matrix-js-sdk/src/models/room-member';
+import { User } from "matrix-js-sdk/src/models/user";
import { compare } from "../../../../src/utils/strings";
+import MemberList from "../../../../src/components/views/rooms/MemberList";
function generateRoomId() {
return '!' + Math.random().toString().slice(2, 10) + ':domain';
}
-
describe('MemberList', () => {
function createRoom(opts) {
const room = new Room(generateRoomId(), null, client.getUserId());
@@ -97,13 +112,19 @@ describe('MemberList', () => {
memberListRoom.currentState.members[member.userId] = member;
}
- const MemberList = sdk.getComponent('views.rooms.MemberList');
const WrappedMemberList = TestUtils.wrapInMatrixClientContext(MemberList);
const gatherWrappedRef = (r) => {
memberList = r;
};
- root = ReactDOM.render( , parentDiv);
+ root = ReactDOM.render(
+ (
+
+ ),
+ parentDiv,
+ );
});
afterEach((done) => {
@@ -213,8 +234,8 @@ describe('MemberList', () => {
});
// Bypass all the event listeners and skip to the good part
- memberList._showPresence = enablePresence;
- memberList._updateListNow();
+ memberList.showPresence = enablePresence;
+ memberList.updateListNow();
const tiles = ReactTestUtils.scryRenderedComponentsWithType(root, MemberTile);
expectOrderedByPresenceAndPowerLevel(tiles, enablePresence);
@@ -225,7 +246,7 @@ describe('MemberList', () => {
// Bypass all the event listeners and skip to the good part
memberList._showPresence = enablePresence;
- memberList._updateListNow();
+ memberList.updateListNow();
const tiles = ReactTestUtils.scryRenderedComponentsWithType(root, MemberTile);
expectOrderedByPresenceAndPowerLevel(tiles, enablePresence);
@@ -254,8 +275,8 @@ describe('MemberList', () => {
});
// Bypass all the event listeners and skip to the good part
- memberList._showPresence = enablePresence;
- memberList._updateListNow();
+ memberList.showPresence = enablePresence;
+ memberList.updateListNow();
const tiles = ReactTestUtils.scryRenderedComponentsWithType(root, MemberTile);
expectOrderedByPresenceAndPowerLevel(tiles, enablePresence);
@@ -273,8 +294,8 @@ describe('MemberList', () => {
});
// Bypass all the event listeners and skip to the good part
- memberList._showPresence = enablePresence;
- memberList._updateListNow();
+ memberList.showPresence = enablePresence;
+ memberList.updateListNow();
const tiles = ReactTestUtils.scryRenderedComponentsWithType(root, MemberTile);
expectOrderedByPresenceAndPowerLevel(tiles, enablePresence);
From b7a821a9e4d7bbe99f767b2fb86850936d99ebed Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=A0imon=20Brandner?=
Date: Wed, 23 Jun 2021 14:59:36 +0200
Subject: [PATCH 085/100] .tsx can also be tests
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Šimon Brandner
---
package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/package.json b/package.json
index 6b369e9c27..f87abf9e44 100644
--- a/package.json
+++ b/package.json
@@ -172,7 +172,7 @@
"jest": {
"testEnvironment": "./__test-utils__/environment.js",
"testMatch": [
- "/test/**/*-test.[jt]s"
+ "/test/**/*-test.[jt]s?(x)"
],
"setupFiles": [
"jest-canvas-mock"
From 5d93216c94f1f76a99fd30b4bbcd73459446ee2a Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Wed, 23 Jun 2021 16:10:47 +0100
Subject: [PATCH 086/100] Decrease e2e shield fill mask size so that it doesn't
overlap
---
res/css/structures/_ToastContainer.scss | 2 +-
res/css/views/messages/_common_CryptoEvent.scss | 2 +-
res/css/views/rooms/_E2EIcon.scss | 4 ++--
res/css/views/rooms/_EventTile.scss | 2 +-
4 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/res/css/structures/_ToastContainer.scss b/res/css/structures/_ToastContainer.scss
index 14e4c01389..35d6087a1b 100644
--- a/res/css/structures/_ToastContainer.scss
+++ b/res/css/structures/_ToastContainer.scss
@@ -71,7 +71,7 @@ limitations under the License.
&::before {
background-color: #ffffff;
mask-image: url('$(res)/img/e2e/normal.svg');
- mask-size: 90%;
+ mask-size: 80%;
}
&::after {
diff --git a/res/css/views/messages/_common_CryptoEvent.scss b/res/css/views/messages/_common_CryptoEvent.scss
index 4faa4b594f..bcc40f1181 100644
--- a/res/css/views/messages/_common_CryptoEvent.scss
+++ b/res/css/views/messages/_common_CryptoEvent.scss
@@ -21,7 +21,7 @@ limitations under the License.
mask-image: url('$(res)/img/e2e/normal.svg');
mask-repeat: no-repeat;
mask-position: center;
- mask-size: 90%;
+ mask-size: 80%;
}
&.mx_cryptoEvent_icon::after {
diff --git a/res/css/views/rooms/_E2EIcon.scss b/res/css/views/rooms/_E2EIcon.scss
index a3473dedec..68ad44cf6a 100644
--- a/res/css/views/rooms/_E2EIcon.scss
+++ b/res/css/views/rooms/_E2EIcon.scss
@@ -45,7 +45,7 @@ limitations under the License.
mask-image: url('$(res)/img/e2e/normal.svg');
mask-repeat: no-repeat;
mask-position: center;
- mask-size: 90%;
+ mask-size: 80%;
}
// transparent-looking border surrounding the shield for when overlain over avatars
@@ -59,7 +59,7 @@ limitations under the License.
}
// shrink the infill of the badge
&::before {
- mask-size: 65%;
+ mask-size: 60%;
}
}
diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss
index 3af266caee..27a83e58f8 100644
--- a/res/css/views/rooms/_EventTile.scss
+++ b/res/css/views/rooms/_EventTile.scss
@@ -345,7 +345,7 @@ $hover-select-border: 4px;
mask-image: url('$(res)/img/e2e/normal.svg');
mask-repeat: no-repeat;
mask-position: center;
- mask-size: 90%;
+ mask-size: 80%;
}
}
From e696a1d5dc4d6848e47caf64e0c0c58bda659c8a Mon Sep 17 00:00:00 2001
From: Travis Ralston
Date: Wed, 23 Jun 2021 10:31:08 -0600
Subject: [PATCH 087/100] Update membership reason handling, including leave
reason displaying
Incorporates ideas from https://github.com/matrix-org/matrix-react-sdk/pull/6198
---
src/TextForEvent.ts | 47 +++++++++++++++++++++++--------------
src/i18n/strings/en_EN.json | 6 ++++-
2 files changed, 35 insertions(+), 18 deletions(-)
diff --git a/src/TextForEvent.ts b/src/TextForEvent.ts
index 649c53664e..55a7813c6f 100644
--- a/src/TextForEvent.ts
+++ b/src/TextForEvent.ts
@@ -31,8 +31,8 @@ function textForMemberEvent(ev): () => string | null {
const targetName = ev.target ? ev.target.name : ev.getStateKey();
const prevContent = ev.getPrevContent();
const content = ev.getContent();
+ const reason = content.reason;
- const getReason = () => content.reason ? (_t('Reason') + ': ' + content.reason) : '';
switch (content.membership) {
case 'invite': {
const threePidContent = content.third_party_invite;
@@ -43,14 +43,16 @@ function textForMemberEvent(ev): () => string | null {
displayName: threePidContent.display_name,
});
} else {
- return () => _t('%(targetName)s accepted an invitation.', {targetName});
+ return () => _t('%(targetName)s accepted an invitation.', { targetName });
}
} else {
- return () => _t('%(senderName)s invited %(targetName)s.', {senderName, targetName});
+ return () => _t('%(senderName)s invited %(targetName)s.', { senderName, targetName });
}
}
case 'ban':
- return () => _t('%(senderName)s banned %(targetName)s.', {senderName, targetName}) + ' ' + getReason();
+ return () => reason
+ ? _t('%(senderName)s banned %(targetName)s. Reason: %(reason)s', { senderName, targetName, reason })
+ : _t('%(senderName)s banned %(targetName)s.', { senderName, targetName });
case 'join':
if (prevContent && prevContent.membership === 'join') {
if (prevContent.displayname && content.displayname && prevContent.displayname !== content.displayname) {
@@ -69,38 +71,49 @@ function textForMemberEvent(ev): () => string | null {
oldDisplayName: prevContent.displayname,
});
} else if (prevContent.avatar_url && !content.avatar_url) {
- return () => _t('%(senderName)s removed their profile picture.', {senderName});
+ return () => _t('%(senderName)s removed their profile picture.', { senderName });
} else if (prevContent.avatar_url && content.avatar_url &&
prevContent.avatar_url !== content.avatar_url) {
- return () => _t('%(senderName)s changed their profile picture.', {senderName});
+ return () => _t('%(senderName)s changed their profile picture.', { senderName });
} else if (!prevContent.avatar_url && content.avatar_url) {
- return () => _t('%(senderName)s set a profile picture.', {senderName});
+ return () => _t('%(senderName)s set a profile picture.', { senderName });
} else if (SettingsStore.getValue("showHiddenEventsInTimeline")) {
// This is a null rejoin, it will only be visible if the Labs option is enabled
- return () => _t("%(senderName)s made no change.", {senderName});
+ return () => _t("%(senderName)s made no change.", { senderName });
} else {
return null;
}
} else {
if (!ev.target) console.warn("Join message has no target! -- " + ev.getContent().state_key);
- return () => _t('%(targetName)s joined the room.', {targetName});
+ return () => _t('%(targetName)s joined the room.', { targetName });
}
case 'leave':
if (ev.getSender() === ev.getStateKey()) {
if (prevContent.membership === "invite") {
- return () => _t('%(targetName)s rejected the invitation.', {targetName});
+ return () => _t('%(targetName)s rejected the invitation.', { targetName });
} else {
- return () => _t('%(targetName)s left the room.', {targetName});
+ return () => reason
+ ? _t('%(targetName)s left the room. Reason: %(reason)s', { targetName, reason })
+ : _t('%(targetName)s left the room.', { targetName });
}
} else if (prevContent.membership === "ban") {
- return () => _t('%(senderName)s unbanned %(targetName)s.', {senderName, targetName});
+ return () => _t('%(senderName)s unbanned %(targetName)s.', { senderName, targetName });
} else if (prevContent.membership === "invite") {
- return () => _t('%(senderName)s withdrew %(targetName)s\'s invitation.', {
- senderName,
- targetName,
- }) + ' ' + getReason();
+ return () => reason
+ ? _t('%(senderName)s withdrew %(targetName)s\'s invitation. Reason: %(reason)s', {
+ senderName,
+ targetName,
+ reason,
+ })
+ : _t('%(senderName)s withdrew %(targetName)s\'s invitation.', { senderName, targetName })
} else if (prevContent.membership === "join") {
- return () => _t('%(senderName)s kicked %(targetName)s.', {senderName, targetName}) + ' ' + getReason();
+ return () => reason
+ ? _t('%(senderName)s kicked %(targetName)s. Reason: %(reason)s', {
+ senderName,
+ targetName,
+ reason,
+ })
+ : _t('%(senderName)s kicked %(targetName)s.', { senderName, targetName });
} else {
return null;
}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 7751c2eb32..5bee2e2f2c 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -489,10 +489,10 @@
"Converts the room to a DM": "Converts the room to a DM",
"Converts the DM to a room": "Converts the DM to a room",
"Displays action": "Displays action",
- "Reason": "Reason",
"%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s accepted the invitation for %(displayName)s.",
"%(targetName)s accepted an invitation.": "%(targetName)s accepted an invitation.",
"%(senderName)s invited %(targetName)s.": "%(senderName)s invited %(targetName)s.",
+ "%(senderName)s banned %(targetName)s. Reason: %(reason)s": "%(senderName)s banned %(targetName)s. Reason: %(reason)s",
"%(senderName)s banned %(targetName)s.": "%(senderName)s banned %(targetName)s.",
"%(oldDisplayName)s changed their display name to %(displayName)s.": "%(oldDisplayName)s changed their display name to %(displayName)s.",
"%(senderName)s set their display name to %(displayName)s.": "%(senderName)s set their display name to %(displayName)s.",
@@ -503,9 +503,12 @@
"%(senderName)s made no change.": "%(senderName)s made no change.",
"%(targetName)s joined the room.": "%(targetName)s joined the room.",
"%(targetName)s rejected the invitation.": "%(targetName)s rejected the invitation.",
+ "%(targetName)s left the room. Reason: %(reason)s": "%(targetName)s left the room. Reason: %(reason)s",
"%(targetName)s left the room.": "%(targetName)s left the room.",
"%(senderName)s unbanned %(targetName)s.": "%(senderName)s unbanned %(targetName)s.",
+ "%(senderName)s withdrew %(targetName)s's invitation. Reason: %(reason)s": "%(senderName)s withdrew %(targetName)s's invitation. Reason: %(reason)s",
"%(senderName)s withdrew %(targetName)s's invitation.": "%(senderName)s withdrew %(targetName)s's invitation.",
+ "%(senderName)s kicked %(targetName)s. Reason: %(reason)s": "%(senderName)s kicked %(targetName)s. Reason: %(reason)s",
"%(senderName)s kicked %(targetName)s.": "%(senderName)s kicked %(targetName)s.",
"%(senderDisplayName)s changed the topic to \"%(topic)s\".": "%(senderDisplayName)s changed the topic to \"%(topic)s\".",
"%(senderDisplayName)s removed the room name.": "%(senderDisplayName)s removed the room name.",
@@ -1410,6 +1413,7 @@
"Failed to unban": "Failed to unban",
"Unban": "Unban",
"Banned by %(displayName)s": "Banned by %(displayName)s",
+ "Reason": "Reason",
"Error changing power level requirement": "Error changing power level requirement",
"An error occurred changing the room's power level requirements. Ensure you have sufficient permissions and try again.": "An error occurred changing the room's power level requirements. Ensure you have sufficient permissions and try again.",
"Error changing power level": "Error changing power level",
From e290fdaabcb0259b26412b34b3ec4cd11e76164a Mon Sep 17 00:00:00 2001
From: Travis Ralston
Date: Wed, 23 Jun 2021 11:21:56 -0600
Subject: [PATCH 088/100] Update widget-api for
https://github.com/matrix-org/matrix-react-sdk/pull/6178
---
package.json | 2 +-
yarn.lock | 9 +++++----
2 files changed, 6 insertions(+), 5 deletions(-)
diff --git a/package.json b/package.json
index 6b369e9c27..bc79b791d4 100644
--- a/package.json
+++ b/package.json
@@ -79,7 +79,7 @@
"linkifyjs": "^2.1.9",
"lodash": "^4.17.20",
"matrix-js-sdk": "12.0.0",
- "matrix-widget-api": "^0.1.0-beta.14",
+ "matrix-widget-api": "^0.1.0-beta.15",
"minimist": "^1.2.5",
"opus-recorder": "^8.0.3",
"pako": "^2.0.3",
diff --git a/yarn.lock b/yarn.lock
index 32ca30a996..3bcb8de404 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1334,6 +1334,7 @@
"@matrix-org/olm@https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz":
version "3.2.3"
+ uid cc332fdd25c08ef0e40f4d33fc3f822a0f98b6f4
resolved "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz#cc332fdd25c08ef0e40f4d33fc3f822a0f98b6f4"
"@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents":
@@ -5772,10 +5773,10 @@ matrix-react-test-utils@^0.2.3:
"@babel/traverse" "^7.13.17"
walk "^2.3.14"
-matrix-widget-api@^0.1.0-beta.14:
- version "0.1.0-beta.14"
- resolved "https://registry.yarnpkg.com/matrix-widget-api/-/matrix-widget-api-0.1.0-beta.14.tgz#e38beed71c5ebd62c1ac1d79ef262d7150b42c70"
- integrity sha512-5tC6LO1vCblKg/Hfzf5U1eHPz1nHUZIobAm3gkEKV5vpYPgRpr8KdkLiGB78VZid0tB17CVtAb4VKI8CQ3lhAQ==
+matrix-widget-api@^0.1.0-beta.15:
+ version "0.1.0-beta.15"
+ resolved "https://registry.yarnpkg.com/matrix-widget-api/-/matrix-widget-api-0.1.0-beta.15.tgz#b02511f93fe1a3634868b6e246d736107f182745"
+ integrity sha512-sWmtb8ZarSbHVbk5ni7IHBR9jOh7m1+5R4soky0fEO9VKl+MN7skT0+qNux3J9WuUAu2D80dZW9xPUT9cxfxbg==
dependencies:
"@types/events" "^3.0.0"
events "^3.2.0"
From 0e51fcc761465447ea2983a48ff9a6ef659f77bd Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Wed, 23 Jun 2021 21:12:25 +0100
Subject: [PATCH 089/100] Fix pinning event in a room which hasn't had events
pinned in before
---
src/components/views/context_menus/MessageContextMenu.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/views/context_menus/MessageContextMenu.js b/src/components/views/context_menus/MessageContextMenu.js
index 5a1da1376d..eef10c995a 100644
--- a/src/components/views/context_menus/MessageContextMenu.js
+++ b/src/components/views/context_menus/MessageContextMenu.js
@@ -179,7 +179,7 @@ export default class MessageContextMenu extends React.Component {
pinnedIds.push(eventId);
cli.setRoomAccountData(room.roomId, ReadPinsEventId, {
event_ids: [
- ...room.getAccountData(ReadPinsEventId)?.getContent()?.event_ids,
+ ...(room.getAccountData(ReadPinsEventId)?.getContent()?.event_ids || []),
eventId,
],
});
From 72099c1a08eba2425345c09c1eb2c3a68d952564 Mon Sep 17 00:00:00 2001
From: Travis Ralston
Date: Wed, 23 Jun 2021 19:42:47 -0600
Subject: [PATCH 090/100] Update punctuation
---
src/TextForEvent.ts | 44 ++++++++++++++++++-------------------
src/i18n/strings/en_EN.json | 42 +++++++++++++++++------------------
2 files changed, 43 insertions(+), 43 deletions(-)
diff --git a/src/TextForEvent.ts b/src/TextForEvent.ts
index 55a7813c6f..ebf1645303 100644
--- a/src/TextForEvent.ts
+++ b/src/TextForEvent.ts
@@ -38,82 +38,82 @@ function textForMemberEvent(ev): () => string | null {
const threePidContent = content.third_party_invite;
if (threePidContent) {
if (threePidContent.display_name) {
- return () => _t('%(targetName)s accepted the invitation for %(displayName)s.', {
+ return () => _t('%(targetName)s accepted the invitation for %(displayName)s', {
targetName,
displayName: threePidContent.display_name,
});
} else {
- return () => _t('%(targetName)s accepted an invitation.', { targetName });
+ return () => _t('%(targetName)s accepted an invitation', { targetName });
}
} else {
- return () => _t('%(senderName)s invited %(targetName)s.', { senderName, targetName });
+ return () => _t('%(senderName)s invited %(targetName)s', { senderName, targetName });
}
}
case 'ban':
return () => reason
- ? _t('%(senderName)s banned %(targetName)s. Reason: %(reason)s', { senderName, targetName, reason })
- : _t('%(senderName)s banned %(targetName)s.', { senderName, targetName });
+ ? _t('%(senderName)s banned %(targetName)s: %(reason)s', { senderName, targetName, reason })
+ : _t('%(senderName)s banned %(targetName)s', { senderName, targetName });
case 'join':
if (prevContent && prevContent.membership === 'join') {
if (prevContent.displayname && content.displayname && prevContent.displayname !== content.displayname) {
- return () => _t('%(oldDisplayName)s changed their display name to %(displayName)s.', {
+ return () => _t('%(oldDisplayName)s changed their display name to %(displayName)s', {
oldDisplayName: prevContent.displayname,
displayName: content.displayname,
});
} else if (!prevContent.displayname && content.displayname) {
- return () => _t('%(senderName)s set their display name to %(displayName)s.', {
+ return () => _t('%(senderName)s set their display name to %(displayName)s', {
senderName: ev.getSender(),
displayName: content.displayname,
});
} else if (prevContent.displayname && !content.displayname) {
- return () => _t('%(senderName)s removed their display name (%(oldDisplayName)s).', {
+ return () => _t('%(senderName)s removed their display name (%(oldDisplayName)s)', {
senderName,
oldDisplayName: prevContent.displayname,
});
} else if (prevContent.avatar_url && !content.avatar_url) {
- return () => _t('%(senderName)s removed their profile picture.', { senderName });
+ return () => _t('%(senderName)s removed their profile picture', { senderName });
} else if (prevContent.avatar_url && content.avatar_url &&
prevContent.avatar_url !== content.avatar_url) {
- return () => _t('%(senderName)s changed their profile picture.', { senderName });
+ return () => _t('%(senderName)s changed their profile picture', { senderName });
} else if (!prevContent.avatar_url && content.avatar_url) {
- return () => _t('%(senderName)s set a profile picture.', { senderName });
+ return () => _t('%(senderName)s set a profile picture', { senderName });
} else if (SettingsStore.getValue("showHiddenEventsInTimeline")) {
- // This is a null rejoin, it will only be visible if the Labs option is enabled
- return () => _t("%(senderName)s made no change.", { senderName });
+ // This is a null rejoin, it will only be visible if using 'show hidden events' (labs)
+ return () => _t("%(senderName)s made no change", { senderName });
} else {
return null;
}
} else {
if (!ev.target) console.warn("Join message has no target! -- " + ev.getContent().state_key);
- return () => _t('%(targetName)s joined the room.', { targetName });
+ return () => _t('%(targetName)s joined the room', { targetName });
}
case 'leave':
if (ev.getSender() === ev.getStateKey()) {
if (prevContent.membership === "invite") {
- return () => _t('%(targetName)s rejected the invitation.', { targetName });
+ return () => _t('%(targetName)s rejected the invitation', { targetName });
} else {
return () => reason
- ? _t('%(targetName)s left the room. Reason: %(reason)s', { targetName, reason })
- : _t('%(targetName)s left the room.', { targetName });
+ ? _t('%(targetName)s left the room: %(reason)s', { targetName, reason })
+ : _t('%(targetName)s left the room', { targetName });
}
} else if (prevContent.membership === "ban") {
- return () => _t('%(senderName)s unbanned %(targetName)s.', { senderName, targetName });
+ return () => _t('%(senderName)s unbanned %(targetName)s', { senderName, targetName });
} else if (prevContent.membership === "invite") {
return () => reason
- ? _t('%(senderName)s withdrew %(targetName)s\'s invitation. Reason: %(reason)s', {
+ ? _t('%(senderName)s withdrew %(targetName)s\'s invitation: %(reason)s', {
senderName,
targetName,
reason,
})
- : _t('%(senderName)s withdrew %(targetName)s\'s invitation.', { senderName, targetName })
+ : _t('%(senderName)s withdrew %(targetName)s\'s invitation', { senderName, targetName })
} else if (prevContent.membership === "join") {
return () => reason
- ? _t('%(senderName)s kicked %(targetName)s. Reason: %(reason)s', {
+ ? _t('%(senderName)s kicked %(targetName)s: %(reason)s', {
senderName,
targetName,
reason,
})
- : _t('%(senderName)s kicked %(targetName)s.', { senderName, targetName });
+ : _t('%(senderName)s kicked %(targetName)s', { senderName, targetName });
} else {
return null;
}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 5bee2e2f2c..5aa0b56d52 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -489,27 +489,27 @@
"Converts the room to a DM": "Converts the room to a DM",
"Converts the DM to a room": "Converts the DM to a room",
"Displays action": "Displays action",
- "%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s accepted the invitation for %(displayName)s.",
- "%(targetName)s accepted an invitation.": "%(targetName)s accepted an invitation.",
- "%(senderName)s invited %(targetName)s.": "%(senderName)s invited %(targetName)s.",
- "%(senderName)s banned %(targetName)s. Reason: %(reason)s": "%(senderName)s banned %(targetName)s. Reason: %(reason)s",
- "%(senderName)s banned %(targetName)s.": "%(senderName)s banned %(targetName)s.",
- "%(oldDisplayName)s changed their display name to %(displayName)s.": "%(oldDisplayName)s changed their display name to %(displayName)s.",
- "%(senderName)s set their display name to %(displayName)s.": "%(senderName)s set their display name to %(displayName)s.",
- "%(senderName)s removed their display name (%(oldDisplayName)s).": "%(senderName)s removed their display name (%(oldDisplayName)s).",
- "%(senderName)s removed their profile picture.": "%(senderName)s removed their profile picture.",
- "%(senderName)s changed their profile picture.": "%(senderName)s changed their profile picture.",
- "%(senderName)s set a profile picture.": "%(senderName)s set a profile picture.",
- "%(senderName)s made no change.": "%(senderName)s made no change.",
- "%(targetName)s joined the room.": "%(targetName)s joined the room.",
- "%(targetName)s rejected the invitation.": "%(targetName)s rejected the invitation.",
- "%(targetName)s left the room. Reason: %(reason)s": "%(targetName)s left the room. Reason: %(reason)s",
- "%(targetName)s left the room.": "%(targetName)s left the room.",
- "%(senderName)s unbanned %(targetName)s.": "%(senderName)s unbanned %(targetName)s.",
- "%(senderName)s withdrew %(targetName)s's invitation. Reason: %(reason)s": "%(senderName)s withdrew %(targetName)s's invitation. Reason: %(reason)s",
- "%(senderName)s withdrew %(targetName)s's invitation.": "%(senderName)s withdrew %(targetName)s's invitation.",
- "%(senderName)s kicked %(targetName)s. Reason: %(reason)s": "%(senderName)s kicked %(targetName)s. Reason: %(reason)s",
- "%(senderName)s kicked %(targetName)s.": "%(senderName)s kicked %(targetName)s.",
+ "%(targetName)s accepted the invitation for %(displayName)s": "%(targetName)s accepted the invitation for %(displayName)s",
+ "%(targetName)s accepted an invitation": "%(targetName)s accepted an invitation",
+ "%(senderName)s invited %(targetName)s": "%(senderName)s invited %(targetName)s",
+ "%(senderName)s banned %(targetName)s: %(reason)s": "%(senderName)s banned %(targetName)s: %(reason)s",
+ "%(senderName)s banned %(targetName)s": "%(senderName)s banned %(targetName)s",
+ "%(oldDisplayName)s changed their display name to %(displayName)s": "%(oldDisplayName)s changed their display name to %(displayName)s",
+ "%(senderName)s set their display name to %(displayName)s": "%(senderName)s set their display name to %(displayName)s",
+ "%(senderName)s removed their display name (%(oldDisplayName)s)": "%(senderName)s removed their display name (%(oldDisplayName)s)",
+ "%(senderName)s removed their profile picture": "%(senderName)s removed their profile picture",
+ "%(senderName)s changed their profile picture": "%(senderName)s changed their profile picture",
+ "%(senderName)s set a profile picture": "%(senderName)s set a profile picture",
+ "%(senderName)s made no change": "%(senderName)s made no change",
+ "%(targetName)s joined the room": "%(targetName)s joined the room",
+ "%(targetName)s rejected the invitation": "%(targetName)s rejected the invitation",
+ "%(targetName)s left the room: %(reason)s": "%(targetName)s left the room: %(reason)s",
+ "%(targetName)s left the room": "%(targetName)s left the room",
+ "%(senderName)s unbanned %(targetName)s": "%(senderName)s unbanned %(targetName)s",
+ "%(senderName)s withdrew %(targetName)s's invitation: %(reason)s": "%(senderName)s withdrew %(targetName)s's invitation: %(reason)s",
+ "%(senderName)s withdrew %(targetName)s's invitation": "%(senderName)s withdrew %(targetName)s's invitation",
+ "%(senderName)s kicked %(targetName)s: %(reason)s": "%(senderName)s kicked %(targetName)s: %(reason)s",
+ "%(senderName)s kicked %(targetName)s": "%(senderName)s kicked %(targetName)s",
"%(senderDisplayName)s changed the topic to \"%(topic)s\".": "%(senderDisplayName)s changed the topic to \"%(topic)s\".",
"%(senderDisplayName)s removed the room name.": "%(senderDisplayName)s removed the room name.",
"%(senderDisplayName)s changed the room name from %(oldRoomName)s to %(newRoomName)s.": "%(senderDisplayName)s changed the room name from %(oldRoomName)s to %(newRoomName)s.",
From 47be728eb2fdb1291aa889c04579cd75eeef5c73 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Thu, 24 Jun 2021 09:21:52 +0100
Subject: [PATCH 091/100] Fix invite dialog being cut off when it has limited
results
---
res/css/views/dialogs/_InviteDialog.scss | 1 +
1 file changed, 1 insertion(+)
diff --git a/res/css/views/dialogs/_InviteDialog.scss b/res/css/views/dialogs/_InviteDialog.scss
index 2e48b5d8e9..7d9b4c0bc6 100644
--- a/res/css/views/dialogs/_InviteDialog.scss
+++ b/res/css/views/dialogs/_InviteDialog.scss
@@ -295,6 +295,7 @@ limitations under the License.
.mx_InviteDialog_content {
overflow: hidden;
+ height: 100%;
}
}
From 26d8c4d2e65a32382660b27ab2048ccd98a9bcaf Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Thu, 24 Jun 2021 10:03:32 +0100
Subject: [PATCH 092/100] Improve design of the multi inviter error dialog
---
res/css/views/dialogs/_InviteDialog.scss | 39 +++++++++++
src/RoomInvite.tsx | 67 +++++++++++++++++--
src/components/views/dialogs/InviteDialog.tsx | 67 +++++++------------
src/i18n/strings/en_EN.json | 3 +-
4 files changed, 129 insertions(+), 47 deletions(-)
diff --git a/res/css/views/dialogs/_InviteDialog.scss b/res/css/views/dialogs/_InviteDialog.scss
index 175b1cc556..ff7c232ad5 100644
--- a/res/css/views/dialogs/_InviteDialog.scss
+++ b/res/css/views/dialogs/_InviteDialog.scss
@@ -317,3 +317,42 @@ limitations under the License.
.mx_InviteDialog_helpText .mx_AccessibleButton_kind_link {
padding: 0;
}
+
+.mx_InviteDialog_multiInviterError {
+ > h4 {
+ font-size: $font-15px;
+ line-height: $font-24px;
+ color: $secondary-fg-color;
+ font-weight: normal;
+ }
+
+ > div {
+ .mx_InviteDialog_multiInviterError_entry {
+ margin-bottom: 24px;
+
+ .mx_InviteDialog_multiInviterError_entry_userProfile {
+ .mx_InviteDialog_multiInviterError_entry_name {
+ margin-left: 6px;
+ font-size: $font-15px;
+ line-height: $font-24px;
+ font-weight: $font-semi-bold;
+ color: $primary-fg-color;
+ }
+
+ .mx_InviteDialog_multiInviterError_entry_userId {
+ margin-left: 6px;
+ font-size: $font-12px;
+ line-height: $font-15px;
+ color: $tertiary-fg-color;
+ }
+ }
+
+ .mx_InviteDialog_multiInviterError_entry_error {
+ margin-left: 32px;
+ font-size: $font-15px;
+ line-height: $font-24px;
+ color: $notice-primary-color;
+ }
+ }
+ }
+}
diff --git a/src/RoomInvite.tsx b/src/RoomInvite.tsx
index 16141a87e8..e04cd03254 100644
--- a/src/RoomInvite.tsx
+++ b/src/RoomInvite.tsx
@@ -17,6 +17,7 @@ limitations under the License.
import React from "react";
import { Room } from "matrix-js-sdk/src/models/room";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
+import { User } from "matrix-js-sdk/src/models/user";
import { MatrixClientPeg } from './MatrixClientPeg';
import MultiInviter, { CompletionStates } from './utils/MultiInviter';
@@ -26,6 +27,8 @@ import { _t } from './languageHandler';
import InviteDialog, { KIND_DM, KIND_INVITE } from "./components/views/dialogs/InviteDialog";
import CommunityPrototypeInviteDialog from "./components/views/dialogs/CommunityPrototypeInviteDialog";
import { CommunityPrototypeStore } from "./stores/CommunityPrototypeStore";
+import BaseAvatar from "./components/views/avatars/BaseAvatar";
+import { mediaFromMxc } from "./customisations/Media";
export interface IInviteResult {
states: CompletionStates;
@@ -116,7 +119,12 @@ export function inviteUsersToRoom(roomId: string, userIds: string[]): Promise,
+): boolean {
// Show user any errors
const failedUsers = Object.keys(states).filter(a => states[a] === 'error');
if (failedUsers.length === 1 && inviter.fatal) {
@@ -138,13 +146,41 @@ export function showAnyInviteErrors(states: CompletionStates, room: Room, invite
}
}
+ const cli = MatrixClientPeg.get();
if (errorList.length > 0) {
// React 16 doesn't let us use `errorList.join( )` anymore, so this is our solution
- const description = {errorList.map(e =>
{e}
)}
;
+ const description =
+
{ _t("We sent the others, but the below people couldn't be invited to ", {}, {
+ RoomName: () => { room.name } ,
+ }) }
+
+ { failedUsers.map(addr => {
+ const user = userMap?.get(addr) || cli.getUser(addr);
+ const name = (user as Member).name || (user as User).rawDisplayName;
+ const avatarUrl = (user as Member).getMxcAvatarUrl?.() || (user as User).avatarUrl;
+ return
+
+
+ { name }
+ { user.userId }
+
+
+ { inviter.getErrorText(addr) }
+
+
;
+ }) }
+
+
;
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
- Modal.createTrackedDialog('Failed to invite the following users to the room', '', ErrorDialog, {
- title: _t("Failed to invite the following users to the %(roomName)s room:", {roomName: room.name}),
+ Modal.createTrackedDialog("Some invites could not be sent", "", ErrorDialog, {
+ title: _t("Some invites couldn't be sent"),
description,
});
return false;
@@ -153,3 +189,26 @@ export function showAnyInviteErrors(states: CompletionStates, room: Room, invite
return true;
}
+
+// This is the interface that is expected by various components in the Invite Dialog and RoomInvite.
+// It is a bit awkward because it also matches the RoomMember class from the js-sdk with some extra support
+// for 3PIDs/email addresses.
+export abstract class Member {
+ /**
+ * The display name of this Member. For users this should be their profile's display
+ * name or user ID if none set. For 3PIDs this should be the 3PID address (email).
+ */
+ public abstract get name(): string;
+
+ /**
+ * The ID of this Member. For users this should be their user ID. For 3PIDs this should
+ * be the 3PID address (email).
+ */
+ public abstract get userId(): string;
+
+ /**
+ * Gets the MXC URL of this Member's avatar. For users this should be their profile's
+ * avatar MXC URL or null if none set. For 3PIDs this should always be null.
+ */
+ public abstract getMxcAvatarUrl(): string;
+}
diff --git a/src/components/views/dialogs/InviteDialog.tsx b/src/components/views/dialogs/InviteDialog.tsx
index b24ed6e4ef..17b4a6dc47 100644
--- a/src/components/views/dialogs/InviteDialog.tsx
+++ b/src/components/views/dialogs/InviteDialog.tsx
@@ -17,42 +17,46 @@ limitations under the License.
import React, { createRef } from 'react';
import classNames from 'classnames';
-import {_t, _td} from "../../../languageHandler";
+import { _t, _td } from "../../../languageHandler";
import * as sdk from "../../../index";
-import {MatrixClientPeg} from "../../../MatrixClientPeg";
-import {makeRoomPermalink, makeUserPermalink} from "../../../utils/permalinks/Permalinks";
+import { MatrixClientPeg } from "../../../MatrixClientPeg";
+import { makeRoomPermalink, makeUserPermalink } from "../../../utils/permalinks/Permalinks";
import DMRoomMap from "../../../utils/DMRoomMap";
-import {RoomMember} from "matrix-js-sdk/src/models/room-member";
+import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import SdkConfig from "../../../SdkConfig";
import * as Email from "../../../email";
-import {getDefaultIdentityServerUrl, useDefaultIdentityServer} from "../../../utils/IdentityServerUtils";
-import {abbreviateUrl} from "../../../utils/UrlUtils";
+import { getDefaultIdentityServerUrl, useDefaultIdentityServer } from "../../../utils/IdentityServerUtils";
+import { abbreviateUrl } from "../../../utils/UrlUtils";
import dis from "../../../dispatcher/dispatcher";
import IdentityAuthClient from "../../../IdentityAuthClient";
import Modal from "../../../Modal";
-import {humanizeTime} from "../../../utils/humanize";
+import { humanizeTime } from "../../../utils/humanize";
import createRoom, {
- canEncryptToAllUsers, ensureDMExists, findDMForUser, privateShouldBeEncrypted,
+ canEncryptToAllUsers,
+ ensureDMExists,
+ findDMForUser,
+ privateShouldBeEncrypted,
} from "../../../createRoom";
import {
IInviteResult,
inviteMultipleToRoom,
+ Member,
showAnyInviteErrors,
showCommunityInviteDialog,
} from "../../../RoomInvite";
-import {Key} from "../../../Keyboard";
-import {Action} from "../../../dispatcher/actions";
-import {DefaultTagID} from "../../../stores/room-list/models";
+import { Key } from "../../../Keyboard";
+import { Action } from "../../../dispatcher/actions";
+import { DefaultTagID } from "../../../stores/room-list/models";
import RoomListStore from "../../../stores/room-list/RoomListStore";
-import {CommunityPrototypeStore} from "../../../stores/CommunityPrototypeStore";
+import { CommunityPrototypeStore } from "../../../stores/CommunityPrototypeStore";
import SettingsStore from "../../../settings/SettingsStore";
-import {UIFeature} from "../../../settings/UIFeature";
+import { UIFeature } from "../../../settings/UIFeature";
import CountlyAnalytics from "../../../CountlyAnalytics";
-import {Room} from "matrix-js-sdk/src/models/room";
+import { Room } from "matrix-js-sdk/src/models/room";
import { MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
-import {replaceableComponent} from "../../../utils/replaceableComponent";
-import {mediaFromMxc} from "../../../customisations/Media";
-import {getAddressType} from "../../../UserAddress";
+import { replaceableComponent } from "../../../utils/replaceableComponent";
+import { mediaFromMxc } from "../../../customisations/Media";
+import { getAddressType } from "../../../UserAddress";
import BaseAvatar from '../avatars/BaseAvatar';
import AccessibleButton from '../elements/AccessibleButton';
import { compare } from '../../../utils/strings';
@@ -79,35 +83,13 @@ export const KIND_CALL_TRANSFER = "call_transfer";
const INITIAL_ROOMS_SHOWN = 3; // Number of rooms to show at first
const INCREMENT_ROOMS_SHOWN = 5; // Number of rooms to add when 'show more' is clicked
-// This is the interface that is expected by various components in this file. It is a bit
-// awkward because it also matches the RoomMember class from the js-sdk with some extra support
-// for 3PIDs/email addresses.
-abstract class Member {
- /**
- * The display name of this Member. For users this should be their profile's display
- * name or user ID if none set. For 3PIDs this should be the 3PID address (email).
- */
- public abstract get name(): string;
-
- /**
- * The ID of this Member. For users this should be their user ID. For 3PIDs this should
- * be the 3PID address (email).
- */
- public abstract get userId(): string;
-
- /**
- * Gets the MXC URL of this Member's avatar. For users this should be their profile's
- * avatar MXC URL or null if none set. For 3PIDs this should always be null.
- */
- public abstract getMxcAvatarUrl(): string;
-}
-
class DirectoryMember extends Member {
private readonly _userId: string;
private readonly displayName: string;
private readonly avatarUrl: string;
- constructor(userDirResult: {user_id: string, display_name: string, avatar_url: string}) {
+ // eslint-disable-next-line camelcase
+ constructor(userDirResult: { user_id: string, display_name: string, avatar_url: string }) {
super();
this._userId = userDirResult.user_id;
this.displayName = userDirResult.display_name;
@@ -608,7 +590,8 @@ export default class InviteDialog extends React.PureComponent(this.state.targets.map(member => [member.userId, member]));
+ return !showAnyInviteErrors(result.states, room, result.inviter, userMap);
}
private convertFilter(): Member[] {
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 429ffbedef..586031f27a 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -396,7 +396,8 @@
"Failed to invite": "Failed to invite",
"Operation failed": "Operation failed",
"Failed to invite users to the room:": "Failed to invite users to the room:",
- "Failed to invite the following users to the %(roomName)s room:": "Failed to invite the following users to the %(roomName)s room:",
+ "We sent the others, but the below people couldn't be invited to ": "We sent the others, but the below people couldn't be invited to ",
+ "Some invites couldn't be sent": "Some invites couldn't be sent",
"You need to be logged in.": "You need to be logged in.",
"You need to be able to invite users to do that.": "You need to be able to invite users to do that.",
"Unable to create widget.": "Unable to create widget.",
From 01aadb460df9d31df7353b49a9301794b37c0388 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Thu, 24 Jun 2021 11:12:32 +0100
Subject: [PATCH 093/100] Fix cyclic imports
---
src/RoomInvite.tsx | 25 +------------------
src/components/views/dialogs/InviteDialog.tsx | 24 +++++++++++++++++-
2 files changed, 24 insertions(+), 25 deletions(-)
diff --git a/src/RoomInvite.tsx b/src/RoomInvite.tsx
index e04cd03254..c86f832b90 100644
--- a/src/RoomInvite.tsx
+++ b/src/RoomInvite.tsx
@@ -24,7 +24,7 @@ import MultiInviter, { CompletionStates } from './utils/MultiInviter';
import Modal from './Modal';
import * as sdk from './';
import { _t } from './languageHandler';
-import InviteDialog, { KIND_DM, KIND_INVITE } from "./components/views/dialogs/InviteDialog";
+import InviteDialog, { KIND_DM, KIND_INVITE, Member } from "./components/views/dialogs/InviteDialog";
import CommunityPrototypeInviteDialog from "./components/views/dialogs/CommunityPrototypeInviteDialog";
import { CommunityPrototypeStore } from "./stores/CommunityPrototypeStore";
import BaseAvatar from "./components/views/avatars/BaseAvatar";
@@ -189,26 +189,3 @@ export function showAnyInviteErrors(
return true;
}
-
-// This is the interface that is expected by various components in the Invite Dialog and RoomInvite.
-// It is a bit awkward because it also matches the RoomMember class from the js-sdk with some extra support
-// for 3PIDs/email addresses.
-export abstract class Member {
- /**
- * The display name of this Member. For users this should be their profile's display
- * name or user ID if none set. For 3PIDs this should be the 3PID address (email).
- */
- public abstract get name(): string;
-
- /**
- * The ID of this Member. For users this should be their user ID. For 3PIDs this should
- * be the 3PID address (email).
- */
- public abstract get userId(): string;
-
- /**
- * Gets the MXC URL of this Member's avatar. For users this should be their profile's
- * avatar MXC URL or null if none set. For 3PIDs this should always be null.
- */
- public abstract getMxcAvatarUrl(): string;
-}
diff --git a/src/components/views/dialogs/InviteDialog.tsx b/src/components/views/dialogs/InviteDialog.tsx
index 17b4a6dc47..553c1c544e 100644
--- a/src/components/views/dialogs/InviteDialog.tsx
+++ b/src/components/views/dialogs/InviteDialog.tsx
@@ -40,7 +40,6 @@ import createRoom, {
import {
IInviteResult,
inviteMultipleToRoom,
- Member,
showAnyInviteErrors,
showCommunityInviteDialog,
} from "../../../RoomInvite";
@@ -83,6 +82,29 @@ export const KIND_CALL_TRANSFER = "call_transfer";
const INITIAL_ROOMS_SHOWN = 3; // Number of rooms to show at first
const INCREMENT_ROOMS_SHOWN = 5; // Number of rooms to add when 'show more' is clicked
+// This is the interface that is expected by various components in the Invite Dialog and RoomInvite.
+// It is a bit awkward because it also matches the RoomMember class from the js-sdk with some extra support
+// for 3PIDs/email addresses.
+export abstract class Member {
+ /**
+ * The display name of this Member. For users this should be their profile's display
+ * name or user ID if none set. For 3PIDs this should be the 3PID address (email).
+ */
+ public abstract get name(): string;
+
+ /**
+ * The ID of this Member. For users this should be their user ID. For 3PIDs this should
+ * be the 3PID address (email).
+ */
+ public abstract get userId(): string;
+
+ /**
+ * Gets the MXC URL of this Member's avatar. For users this should be their profile's
+ * avatar MXC URL or null if none set. For 3PIDs this should always be null.
+ */
+ public abstract getMxcAvatarUrl(): string;
+}
+
class DirectoryMember extends Member {
private readonly _userId: string;
private readonly displayName: string;
From 4993fc3e7a2a2de5522f10ae5fdbd27432a40df5 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Thu, 24 Jun 2021 11:16:13 +0100
Subject: [PATCH 094/100] Fix edit history modal
defining enums in ts module declarations sadly isn't magic
---
src/@types/diff-dom.ts | 14 +-------------
src/utils/MessageDiffUtils.tsx | 18 +++++++++---------
2 files changed, 10 insertions(+), 22 deletions(-)
diff --git a/src/@types/diff-dom.ts b/src/@types/diff-dom.ts
index 884ee6126d..38ff6432cf 100644
--- a/src/@types/diff-dom.ts
+++ b/src/@types/diff-dom.ts
@@ -15,20 +15,8 @@ limitations under the License.
*/
declare module "diff-dom" {
- enum Action {
- AddElement = "addElement",
- AddTextElement = "addTextElement",
- RemoveTextElement = "removeTextElement",
- RemoveElement = "removeElement",
- ReplaceElement = "replaceElement",
- ModifyTextElement = "modifyTextElement",
- AddAttribute = "addAttribute",
- RemoveAttribute = "removeAttribute",
- ModifyAttribute = "modifyAttribute",
- }
-
export interface IDiff {
- action: Action;
+ action: string;
name: string;
text?: string;
route: number[];
diff --git a/src/utils/MessageDiffUtils.tsx b/src/utils/MessageDiffUtils.tsx
index b5d5e31432..f3dc1320dc 100644
--- a/src/utils/MessageDiffUtils.tsx
+++ b/src/utils/MessageDiffUtils.tsx
@@ -149,7 +149,7 @@ function stringAsTextNode(string: string): Text {
function renderDifferenceInDOM(originalRootNode: Node, diff: IDiff, diffMathPatch: DiffMatchPatch): void {
const {refNode, refParentNode} = findRefNodes(originalRootNode, diff.route);
switch (diff.action) {
- case Action.ReplaceElement: {
+ case "replaceElement": {
const container = document.createElement("span");
const delNode = wrapDeletion(diffTreeToDOM(diff.oldValue));
const insNode = wrapInsertion(diffTreeToDOM(diff.newValue));
@@ -158,17 +158,17 @@ function renderDifferenceInDOM(originalRootNode: Node, diff: IDiff, diffMathPatc
refNode.parentNode.replaceChild(container, refNode);
break;
}
- case Action.RemoveTextElement: {
+ case "removeTextElement": {
const delNode = wrapDeletion(stringAsTextNode(diff.value));
refNode.parentNode.replaceChild(delNode, refNode);
break;
}
- case Action.RemoveElement: {
+ case "removeElement": {
const delNode = wrapDeletion(diffTreeToDOM(diff.element));
refNode.parentNode.replaceChild(delNode, refNode);
break;
}
- case Action.ModifyTextElement: {
+ case "modifyTextElement": {
const textDiffs = diffMathPatch.diff_main(diff.oldValue, diff.newValue);
diffMathPatch.diff_cleanupSemantic(textDiffs);
const container = document.createElement("span");
@@ -184,12 +184,12 @@ function renderDifferenceInDOM(originalRootNode: Node, diff: IDiff, diffMathPatc
refNode.parentNode.replaceChild(container, refNode);
break;
}
- case Action.AddElement: {
+ case "addElement": {
const insNode = wrapInsertion(diffTreeToDOM(diff.element));
insertBefore(refParentNode, refNode, insNode);
break;
}
- case Action.AddTextElement: {
+ case "addTextElement": {
// XXX: sometimes diffDOM says insert a newline when there shouldn't be one
// but we must insert the node anyway so that we don't break the route child IDs.
// See https://github.com/fiduswriter/diffDOM/issues/100
@@ -199,9 +199,9 @@ function renderDifferenceInDOM(originalRootNode: Node, diff: IDiff, diffMathPatc
}
// e.g. when changing a the href of a link,
// show the link with old href as removed and with the new href as added
- case Action.RemoveAttribute:
- case Action.AddAttribute:
- case Action.ModifyAttribute: {
+ case "removeAttribute":
+ case "addAttribute":
+ case "modifyAttribute": {
const delNode = wrapDeletion(refNode.cloneNode(true));
const updatedNode = refNode.cloneNode(true) as HTMLElement;
if (diff.action === "addAttribute" || diff.action === "modifyAttribute") {
From 6d3e7730ef50bfcf7d3156072078ca26094488e6 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Thu, 24 Jun 2021 11:40:49 +0100
Subject: [PATCH 095/100] Fix two PRs duplicating the css attribute
---
res/css/views/dialogs/_InviteDialog.scss | 1 -
1 file changed, 1 deletion(-)
diff --git a/res/css/views/dialogs/_InviteDialog.scss b/res/css/views/dialogs/_InviteDialog.scss
index 8092f0f613..c01b43c1c4 100644
--- a/res/css/views/dialogs/_InviteDialog.scss
+++ b/res/css/views/dialogs/_InviteDialog.scss
@@ -294,7 +294,6 @@ limitations under the License.
flex-direction: column;
.mx_InviteDialog_content {
- height: 100%;
overflow: hidden;
height: 100%;
}
From a774aed365f66f3104f5ee15d43c7daf4bf7d95c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=A0imon=20Brandner?=
Date: Thu, 24 Jun 2021 14:07:43 +0200
Subject: [PATCH 096/100] Iterate PR
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Šimon Brandner
---
src/components/views/rooms/MemberList.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/views/rooms/MemberList.tsx b/src/components/views/rooms/MemberList.tsx
index 5353eb94ed..2d929725b8 100644
--- a/src/components/views/rooms/MemberList.tsx
+++ b/src/components/views/rooms/MemberList.tsx
@@ -91,7 +91,7 @@ export default class MemberList extends React.Component {
const hsUrl = MatrixClientPeg.get().baseUrl;
this.showPresence = true;
if (enablePresenceByHsUrl && enablePresenceByHsUrl[hsUrl] !== undefined) {
- this.showPresence = enablePresenceByHsUrl[hsUrl];
+ this.showPresence = enablePresenceByHsUrl?.[hsUrl] ?? true;
}
}
From d2d8cb3c24bb682c27bd3c3eebcbf4ffac11f8bd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=A0imon=20Brandner?=
Date: Thu, 24 Jun 2021 14:15:44 +0200
Subject: [PATCH 097/100] Think about what you copy-paste, Simon
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Šimon Brandner
---
src/components/views/rooms/MemberList.tsx | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/src/components/views/rooms/MemberList.tsx b/src/components/views/rooms/MemberList.tsx
index 2d929725b8..5ebe5bea59 100644
--- a/src/components/views/rooms/MemberList.tsx
+++ b/src/components/views/rooms/MemberList.tsx
@@ -89,10 +89,7 @@ export default class MemberList extends React.Component {
cli.on("Room", this.onRoom); // invites & joining after peek
const enablePresenceByHsUrl = SdkConfig.get()["enable_presence_by_hs_url"];
const hsUrl = MatrixClientPeg.get().baseUrl;
- this.showPresence = true;
- if (enablePresenceByHsUrl && enablePresenceByHsUrl[hsUrl] !== undefined) {
- this.showPresence = enablePresenceByHsUrl?.[hsUrl] ?? true;
- }
+ this.showPresence = enablePresenceByHsUrl?.[hsUrl] ?? true;
}
// eslint-disable-next-line camelcase
From cc1ff2ce1c96c45ab3f6fa9f9f06985d247724f2 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Thu, 24 Jun 2021 13:28:16 +0100
Subject: [PATCH 098/100] Remove unused import
---
src/utils/MessageDiffUtils.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/utils/MessageDiffUtils.tsx b/src/utils/MessageDiffUtils.tsx
index f3dc1320dc..5ee9970ec2 100644
--- a/src/utils/MessageDiffUtils.tsx
+++ b/src/utils/MessageDiffUtils.tsx
@@ -17,7 +17,7 @@ limitations under the License.
import React, { ReactNode } from 'react';
import classNames from 'classnames';
import { diff_match_patch as DiffMatchPatch } from 'diff-match-patch';
-import { Action, DiffDOM, IDiff } from "diff-dom";
+import { DiffDOM, IDiff } from "diff-dom";
import { IContent } from "matrix-js-sdk/src/models/event";
import { bodyToHtml, checkBlockNode, IOptsReturnString } from "../HtmlUtils";
From d83a92959bcbbef5182e0e5ae7172052ebd61c44 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Thu, 24 Jun 2021 16:30:40 +0100
Subject: [PATCH 099/100] Fix UserInfo not working when rendered without a room
---
src/components/views/right_panel/UserInfo.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/views/right_panel/UserInfo.tsx b/src/components/views/right_panel/UserInfo.tsx
index e22b2d5ff4..111e9dbf38 100644
--- a/src/components/views/right_panel/UserInfo.tsx
+++ b/src/components/views/right_panel/UserInfo.tsx
@@ -503,7 +503,7 @@ const isMuted = (member: RoomMember, powerLevelContent: IPowerLevelsContent) =>
return member.powerLevel < levelToSend;
};
-const getPowerLevels = room => room.currentState.getStateEvents(EventType.RoomPowerLevels, "")?.getContent() || {};
+const getPowerLevels = room => room?.currentState?.getStateEvents(EventType.RoomPowerLevels, "")?.getContent() || {};
export const useRoomPowerLevels = (cli: MatrixClient, room: Room) => {
const [powerLevels, setPowerLevels] = useState(getPowerLevels(room));
From 3d6c6cea89e3b4a6b5483217468ad993e0439504 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Thu, 24 Jun 2021 16:43:12 +0100
Subject: [PATCH 100/100] Fix AutoHideScrollbar typing
---
src/components/structures/AutoHideScrollbar.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/structures/AutoHideScrollbar.tsx b/src/components/structures/AutoHideScrollbar.tsx
index c5c9f0a518..e8a9872b48 100644
--- a/src/components/structures/AutoHideScrollbar.tsx
+++ b/src/components/structures/AutoHideScrollbar.tsx
@@ -17,7 +17,7 @@ limitations under the License.
import React, { HTMLAttributes, WheelEvent } from "react";
-interface IProps extends HTMLAttributes {
+interface IProps extends Omit, "onScroll"> {
className?: string;
onScroll?: (event: Event) => void;
onWheel?: (event: WheelEvent) => void;