Merge branch 'develop' into fix-notif-jumpy-ui
commit
3b6790fbf1
|
@ -1,3 +1,10 @@
|
||||||
|
Changes in [3.13.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.13.1) (2021-02-04)
|
||||||
|
=====================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.13.0...v3.13.1)
|
||||||
|
|
||||||
|
* [Release] Fix z-index of stickerpicker
|
||||||
|
[\#5618](https://github.com/matrix-org/matrix-react-sdk/pull/5618)
|
||||||
|
|
||||||
Changes in [3.13.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.13.0) (2021-02-03)
|
Changes in [3.13.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.13.0) (2021-02-03)
|
||||||
=====================================================================================================
|
=====================================================================================================
|
||||||
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.13.0-rc.1...v3.13.0)
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.13.0-rc.1...v3.13.0)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "matrix-react-sdk",
|
"name": "matrix-react-sdk",
|
||||||
"version": "3.13.0",
|
"version": "3.13.1",
|
||||||
"description": "SDK for matrix.org using React",
|
"description": "SDK for matrix.org using React",
|
||||||
"author": "matrix.org",
|
"author": "matrix.org",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|
|
@ -21,6 +21,11 @@ limitations under the License.
|
||||||
|
|
||||||
$hover-transition: 0.08s cubic-bezier(.46, .03, .52, .96); // quadratic
|
$hover-transition: 0.08s cubic-bezier(.46, .03, .52, .96); // quadratic
|
||||||
|
|
||||||
|
$EventTile_e2e_state_indicator_width: 4px;
|
||||||
|
|
||||||
|
$MessageTimestamp_width: 46px; /* 8 + 30 (avatar) + 8 */
|
||||||
|
$MessageTimestamp_width_hover: calc($MessageTimestamp_width - 2 * $EventTile_e2e_state_indicator_width);
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,6 +106,7 @@
|
||||||
@import "./views/elements/_AddressTile.scss";
|
@import "./views/elements/_AddressTile.scss";
|
||||||
@import "./views/elements/_DesktopBuildsNotice.scss";
|
@import "./views/elements/_DesktopBuildsNotice.scss";
|
||||||
@import "./views/elements/_DirectorySearchBox.scss";
|
@import "./views/elements/_DirectorySearchBox.scss";
|
||||||
|
@import "./views/elements/_DesktopCapturerSourcePicker.scss";
|
||||||
@import "./views/elements/_Dropdown.scss";
|
@import "./views/elements/_Dropdown.scss";
|
||||||
@import "./views/elements/_EditableItemList.scss";
|
@import "./views/elements/_EditableItemList.scss";
|
||||||
@import "./views/elements/_ErrorBoundary.scss";
|
@import "./views/elements/_ErrorBoundary.scss";
|
||||||
|
|
|
@ -64,28 +64,23 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomDirectory_table {
|
.mx_RoomDirectory_table {
|
||||||
font-size: $font-12px;
|
|
||||||
color: $primary-fg-color;
|
color: $primary-fg-color;
|
||||||
width: 100%;
|
display: grid;
|
||||||
|
font-size: $font-12px;
|
||||||
|
grid-template-columns: max-content auto max-content max-content max-content;
|
||||||
|
row-gap: 24px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
table-layout: fixed;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomDirectory_roomAvatar {
|
.mx_RoomDirectory_roomAvatar {
|
||||||
width: 32px;
|
padding: 2px 14px 0 0;
|
||||||
padding-right: 14px;
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_RoomDirectory_roomDescription {
|
|
||||||
padding-bottom: 16px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomDirectory_roomMemberCount {
|
.mx_RoomDirectory_roomMemberCount {
|
||||||
|
align-self: center;
|
||||||
color: $light-fg-color;
|
color: $light-fg-color;
|
||||||
width: 60px;
|
padding: 3px 10px 0;
|
||||||
padding: 0 10px;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
background-color: $light-fg-color;
|
background-color: $light-fg-color;
|
||||||
|
@ -105,8 +100,7 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomDirectory_join, .mx_RoomDirectory_preview {
|
.mx_RoomDirectory_join, .mx_RoomDirectory_preview {
|
||||||
width: 80px;
|
align-self: center;
|
||||||
text-align: center;
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 Šimon Brandner <simon.bra.ag@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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.mx_desktopCapturerSourcePicker {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_desktopCapturerSourcePicker_tabLabels {
|
||||||
|
display: flex;
|
||||||
|
padding: 0 0 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_desktopCapturerSourcePicker_tabLabel,
|
||||||
|
.mx_desktopCapturerSourcePicker_tabLabel_selected {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 8px 0;
|
||||||
|
font-size: $font-13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_desktopCapturerSourcePicker_tabLabel_selected {
|
||||||
|
background-color: $tab-label-active-bg-color;
|
||||||
|
color: $tab-label-active-fg-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_desktopCapturerSourcePicker_panel {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: flex-start;
|
||||||
|
height: 500px;
|
||||||
|
overflow: overlay;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_desktopCapturerSourcePicker_stream_button {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin: 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_desktopCapturerSourcePicker_stream_button:hover,
|
||||||
|
.mx_desktopCapturerSourcePicker_stream_button:focus {
|
||||||
|
background: $roomtile-selected-bg-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_desktopCapturerSourcePicker_stream_thumbnail {
|
||||||
|
margin: 4px;
|
||||||
|
width: 312px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_desktopCapturerSourcePicker_stream_name {
|
||||||
|
margin: 0 4px;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 312px;
|
||||||
|
}
|
|
@ -30,7 +30,7 @@ limitations under the License.
|
||||||
mask-size: contain;
|
mask-size: contain;
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 2px;
|
top: 1px;
|
||||||
left: 0;
|
left: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ $left-gutter: 64px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EventTile.mx_EventTile_info {
|
.mx_EventTile.mx_EventTile_info {
|
||||||
padding-top: 0px;
|
padding-top: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EventTile_avatar {
|
.mx_EventTile_avatar {
|
||||||
|
@ -37,7 +37,7 @@ $left-gutter: 64px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EventTile.mx_EventTile_info .mx_EventTile_avatar {
|
.mx_EventTile.mx_EventTile_info .mx_EventTile_avatar {
|
||||||
top: $font-8px;
|
top: $font-6px;
|
||||||
left: $left-gutter;
|
left: $left-gutter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -420,15 +420,15 @@ $left-gutter: 64px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EventTile:hover.mx_EventTile_verified .mx_EventTile_line {
|
.mx_EventTile:hover.mx_EventTile_verified .mx_EventTile_line {
|
||||||
border-left: $e2e-verified-color 4px solid;
|
border-left: $e2e-verified-color $EventTile_e2e_state_indicator_width solid;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EventTile:hover.mx_EventTile_unverified .mx_EventTile_line {
|
.mx_EventTile:hover.mx_EventTile_unverified .mx_EventTile_line {
|
||||||
border-left: $e2e-unverified-color 4px solid;
|
border-left: $e2e-unverified-color $EventTile_e2e_state_indicator_width solid;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EventTile:hover.mx_EventTile_unknown .mx_EventTile_line {
|
.mx_EventTile:hover.mx_EventTile_unknown .mx_EventTile_line {
|
||||||
border-left: $e2e-unknown-color 4px solid;
|
border-left: $e2e-unknown-color $EventTile_e2e_state_indicator_width solid;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EventTile:hover.mx_EventTile_verified.mx_EventTile_info .mx_EventTile_line,
|
.mx_EventTile:hover.mx_EventTile_verified.mx_EventTile_info .mx_EventTile_line,
|
||||||
|
@ -446,8 +446,7 @@ $left-gutter: 64px;
|
||||||
.mx_EventTile:hover.mx_EventTile_verified .mx_EventTile_line > a > .mx_MessageTimestamp,
|
.mx_EventTile:hover.mx_EventTile_verified .mx_EventTile_line > a > .mx_MessageTimestamp,
|
||||||
.mx_EventTile:hover.mx_EventTile_unverified .mx_EventTile_line > a > .mx_MessageTimestamp,
|
.mx_EventTile:hover.mx_EventTile_unverified .mx_EventTile_line > a > .mx_MessageTimestamp,
|
||||||
.mx_EventTile:hover.mx_EventTile_unknown .mx_EventTile_line > a > .mx_MessageTimestamp {
|
.mx_EventTile:hover.mx_EventTile_unknown .mx_EventTile_line > a > .mx_MessageTimestamp {
|
||||||
left: 3px;
|
width: $MessageTimestamp_width_hover;
|
||||||
width: auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Explicit relationships so that it doesn't apply to nested EventTile components (e.g in Replies)
|
// Explicit relationships so that it doesn't apply to nested EventTile components (e.g in Replies)
|
||||||
|
|
|
@ -34,7 +34,7 @@ $left-gutter: 64px;
|
||||||
|
|
||||||
.mx_MessageTimestamp {
|
.mx_MessageTimestamp {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 46px; /* 8 + 30 (avatar) + 8 */
|
width: $MessageTimestamp_width;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EventTile_line, .mx_EventTile_reply {
|
.mx_EventTile_line, .mx_EventTile_reply {
|
||||||
|
|
|
@ -82,6 +82,7 @@ import CountlyAnalytics from "./CountlyAnalytics";
|
||||||
import {UIFeature} from "./settings/UIFeature";
|
import {UIFeature} from "./settings/UIFeature";
|
||||||
import { CallError } from "matrix-js-sdk/src/webrtc/call";
|
import { CallError } from "matrix-js-sdk/src/webrtc/call";
|
||||||
import { logger } from 'matrix-js-sdk/src/logger';
|
import { logger } from 'matrix-js-sdk/src/logger';
|
||||||
|
import DesktopCapturerSourcePicker from "./components/views/elements/DesktopCapturerSourcePicker"
|
||||||
import { Action } from './dispatcher/actions';
|
import { Action } from './dispatcher/actions';
|
||||||
import { roomForVirtualRoom, getOrCreateVirtualRoomForRoom } from './VoipUserMapper';
|
import { roomForVirtualRoom, getOrCreateVirtualRoomForRoom } from './VoipUserMapper';
|
||||||
import { addManagedHybridWidget, isManagedHybridWidgetEnabled } from './widgets/ManagedHybrid';
|
import { addManagedHybridWidget, isManagedHybridWidgetEnabled } from './widgets/ManagedHybrid';
|
||||||
|
@ -572,7 +573,15 @@ export default class CallHandler {
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
call.placeScreenSharingCall(remoteElement, localElement);
|
|
||||||
|
call.placeScreenSharingCall(
|
||||||
|
remoteElement,
|
||||||
|
localElement,
|
||||||
|
async () : Promise<DesktopCapturerSource> => {
|
||||||
|
const {finished} = Modal.createDialog(DesktopCapturerSourcePicker);
|
||||||
|
const [source] = await finished;
|
||||||
|
return source;
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
console.error("Unknown conf call type: %s", type);
|
console.error("Unknown conf call type: %s", type);
|
||||||
}
|
}
|
||||||
|
|
|
@ -202,12 +202,13 @@ function setRoomNotifsStateUnmuted(roomId, newState) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function findOverrideMuteRule(roomId) {
|
function findOverrideMuteRule(roomId) {
|
||||||
if (!MatrixClientPeg.get().pushRules ||
|
const cli = MatrixClientPeg.get();
|
||||||
!MatrixClientPeg.get().pushRules['global'] ||
|
if (!cli.pushRules ||
|
||||||
!MatrixClientPeg.get().pushRules['global'].override) {
|
!cli.pushRules['global'] ||
|
||||||
|
!cli.pushRules['global'].override) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
for (const rule of MatrixClientPeg.get().pushRules['global'].override) {
|
for (const rule of cli.pushRules['global'].override) {
|
||||||
if (isRuleForRoom(roomId, rule)) {
|
if (isRuleForRoom(roomId, rule)) {
|
||||||
if (isMuteRule(rule) && rule.enabled) {
|
if (isMuteRule(rule) && rule.enabled) {
|
||||||
return rule;
|
return rule;
|
||||||
|
|
|
@ -755,6 +755,8 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
break;
|
break;
|
||||||
case 'on_logged_in':
|
case 'on_logged_in':
|
||||||
if (
|
if (
|
||||||
|
// Skip this handling for token login as that always calls onLoggedIn itself
|
||||||
|
!this.tokenLogin &&
|
||||||
!Lifecycle.isSoftLogout() &&
|
!Lifecycle.isSoftLogout() &&
|
||||||
this.state.view !== Views.LOGIN &&
|
this.state.view !== Views.LOGIN &&
|
||||||
this.state.view !== Views.REGISTER &&
|
this.state.view !== Views.REGISTER &&
|
||||||
|
@ -1373,6 +1375,9 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
cli.on('Session.logged_out', function(errObj) {
|
cli.on('Session.logged_out', function(errObj) {
|
||||||
if (Lifecycle.isLoggingOut()) return;
|
if (Lifecycle.isLoggingOut()) return;
|
||||||
|
|
||||||
|
// A modal might have been open when we were logged out by the server
|
||||||
|
Modal.closeCurrentModal('Session.logged_out');
|
||||||
|
|
||||||
if (errObj.httpStatus === 401 && errObj.data && errObj.data['soft_logout']) {
|
if (errObj.httpStatus === 401 && errObj.data && errObj.data['soft_logout']) {
|
||||||
console.warn("Soft logout issued by server - avoiding data deletion");
|
console.warn("Soft logout issued by server - avoiding data deletion");
|
||||||
Lifecycle.softLogout();
|
Lifecycle.softLogout();
|
||||||
|
@ -1383,6 +1388,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
title: _t('Signed Out'),
|
title: _t('Signed Out'),
|
||||||
description: _t('For security, this session has been signed out. Please sign in again.'),
|
description: _t('For security, this session has been signed out. Please sign in again.'),
|
||||||
});
|
});
|
||||||
|
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'logout',
|
action: 'logout',
|
||||||
});
|
});
|
||||||
|
@ -1652,10 +1658,16 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
// TODO: Handle encoded room/event IDs: https://github.com/vector-im/element-web/issues/9149
|
// TODO: Handle encoded room/event IDs: https://github.com/vector-im/element-web/issues/9149
|
||||||
|
|
||||||
let threepidInvite: IThreepidInvite;
|
let threepidInvite: IThreepidInvite;
|
||||||
|
// if we landed here from a 3PID invite, persist it
|
||||||
if (params.signurl && params.email) {
|
if (params.signurl && params.email) {
|
||||||
threepidInvite = ThreepidInviteStore.instance
|
threepidInvite = ThreepidInviteStore.instance
|
||||||
.storeInvite(roomString, params as IThreepidInviteWireFormat);
|
.storeInvite(roomString, params as IThreepidInviteWireFormat);
|
||||||
}
|
}
|
||||||
|
// otherwise check that this room doesn't already have a known invite
|
||||||
|
if (!threepidInvite) {
|
||||||
|
const invites = ThreepidInviteStore.instance.getInvites();
|
||||||
|
threepidInvite = invites.find(invite => invite.roomId === roomString);
|
||||||
|
}
|
||||||
|
|
||||||
// on our URLs there might be a ?via=matrix.org or similar to help
|
// on our URLs there might be a ?via=matrix.org or similar to help
|
||||||
// joins to the room succeed. We'll pass these through as an array
|
// joins to the room succeed. We'll pass these through as an array
|
||||||
|
|
|
@ -229,7 +229,7 @@ export default class MessagePanel extends React.Component {
|
||||||
|
|
||||||
onAction = (payload) => {
|
onAction = (payload) => {
|
||||||
switch (payload.action) {
|
switch (payload.action) {
|
||||||
case "message_sent":
|
case "scroll_to_bottom":
|
||||||
this.scrollToBottom();
|
this.scrollToBottom();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,6 @@ import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||||
import {Action} from "../../dispatcher/actions";
|
import {Action} from "../../dispatcher/actions";
|
||||||
import RoomSummaryCard from "../views/right_panel/RoomSummaryCard";
|
import RoomSummaryCard from "../views/right_panel/RoomSummaryCard";
|
||||||
import WidgetCard from "../views/right_panel/WidgetCard";
|
import WidgetCard from "../views/right_panel/WidgetCard";
|
||||||
import defaultDispatcher from "../../dispatcher/dispatcher";
|
|
||||||
|
|
||||||
export default class RightPanel extends React.Component {
|
export default class RightPanel extends React.Component {
|
||||||
static get propTypes() {
|
static get propTypes() {
|
||||||
|
@ -186,7 +185,7 @@ export default class RightPanel extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onCloseUserInfo = () => {
|
onClose = () => {
|
||||||
// XXX: There are three different ways of 'closing' this panel depending on what state
|
// XXX: There are three different ways of 'closing' this panel depending on what state
|
||||||
// things are in... this knows far more than it should do about the state of the rest
|
// things are in... this knows far more than it should do about the state of the rest
|
||||||
// of the app and is generally a bit silly.
|
// of the app and is generally a bit silly.
|
||||||
|
@ -198,31 +197,21 @@ export default class RightPanel extends React.Component {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: "view_home_page",
|
action: "view_home_page",
|
||||||
});
|
});
|
||||||
} else if (this.state.phase === RightPanelPhases.EncryptionPanel &&
|
} else if (
|
||||||
|
this.state.phase === RightPanelPhases.EncryptionPanel &&
|
||||||
this.state.verificationRequest && this.state.verificationRequest.pending
|
this.state.verificationRequest && this.state.verificationRequest.pending
|
||||||
) {
|
) {
|
||||||
// When the user clicks close on the encryption panel cancel the pending request first if any
|
// When the user clicks close on the encryption panel cancel the pending request first if any
|
||||||
this.state.verificationRequest.cancel();
|
this.state.verificationRequest.cancel();
|
||||||
} else {
|
} else {
|
||||||
// Otherwise we have got our user from RoomViewStore which means we're being shown
|
// the RightPanelStore has no way of knowing which mode room/group it is in, so we handle closing here
|
||||||
// within a room/group, so go back to the member panel if we were in the encryption panel,
|
|
||||||
// or the member list if we were in the member panel... phew.
|
|
||||||
const isEncryptionPhase = this.state.phase === RightPanelPhases.EncryptionPanel;
|
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: Action.ViewUser,
|
action: Action.ToggleRightPanel,
|
||||||
member: isEncryptionPhase ? this.state.member : null,
|
type: this.props.groupId ? "group" : "room",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onClose = () => {
|
|
||||||
// the RightPanelStore has no way of knowing which mode room/group it is in, so we handle closing here
|
|
||||||
defaultDispatcher.dispatch({
|
|
||||||
action: Action.ToggleRightPanel,
|
|
||||||
type: this.props.groupId ? "group" : "room",
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const MemberList = sdk.getComponent('rooms.MemberList');
|
const MemberList = sdk.getComponent('rooms.MemberList');
|
||||||
const UserInfo = sdk.getComponent('right_panel.UserInfo');
|
const UserInfo = sdk.getComponent('right_panel.UserInfo');
|
||||||
|
@ -260,7 +249,7 @@ export default class RightPanel extends React.Component {
|
||||||
user={this.state.member}
|
user={this.state.member}
|
||||||
room={this.props.room}
|
room={this.props.room}
|
||||||
key={roomId || this.state.member.userId}
|
key={roomId || this.state.member.userId}
|
||||||
onClose={this.onCloseUserInfo}
|
onClose={this.onClose}
|
||||||
phase={this.state.phase}
|
phase={this.state.phase}
|
||||||
verificationRequest={this.state.verificationRequest}
|
verificationRequest={this.state.verificationRequest}
|
||||||
verificationRequestPromise={this.state.verificationRequestPromise}
|
verificationRequestPromise={this.state.verificationRequestPromise}
|
||||||
|
@ -276,7 +265,7 @@ export default class RightPanel extends React.Component {
|
||||||
user={this.state.member}
|
user={this.state.member}
|
||||||
groupId={this.props.groupId}
|
groupId={this.props.groupId}
|
||||||
key={this.state.member.userId}
|
key={this.state.member.userId}
|
||||||
onClose={this.onCloseUserInfo} />;
|
onClose={this.onClose} />;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RightPanelPhases.GroupRoomInfo:
|
case RightPanelPhases.GroupRoomInfo:
|
||||||
|
|
|
@ -477,7 +477,7 @@ export default class RoomDirectory extends React.Component {
|
||||||
dis.dispatch(payload);
|
dis.dispatch(payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
getRow(room) {
|
createRoomCells(room) {
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
const clientRoom = client.getRoom(room.room_id);
|
const clientRoom = client.getRoom(room.room_id);
|
||||||
const hasJoinedRoom = clientRoom && clientRoom.getMyMembership() === "join";
|
const hasJoinedRoom = clientRoom && clientRoom.getMyMembership() === "join";
|
||||||
|
@ -523,31 +523,56 @@ export default class RoomDirectory extends React.Component {
|
||||||
MatrixClientPeg.get().getHomeserverUrl(),
|
MatrixClientPeg.get().getHomeserverUrl(),
|
||||||
room.avatar_url, 32, 32, "crop",
|
room.avatar_url, 32, 32, "crop",
|
||||||
);
|
);
|
||||||
return (
|
return [
|
||||||
<tr key={ room.room_id }
|
<div key={ `${room.room_id}_avatar` }
|
||||||
onClick={(ev) => this.onRoomClicked(room, ev)}
|
onClick={(ev) => this.onRoomClicked(room, ev)}
|
||||||
// cancel onMouseDown otherwise shift-clicking highlights text
|
// cancel onMouseDown otherwise shift-clicking highlights text
|
||||||
onMouseDown={(ev) => {ev.preventDefault();}}
|
onMouseDown={(ev) => {ev.preventDefault();}}
|
||||||
|
className="mx_RoomDirectory_roomAvatar"
|
||||||
>
|
>
|
||||||
<td className="mx_RoomDirectory_roomAvatar">
|
<BaseAvatar width={32} height={32} resizeMethod='crop'
|
||||||
<BaseAvatar width={32} height={32} resizeMethod='crop'
|
name={ name } idName={ name }
|
||||||
name={ name } idName={ name }
|
url={ avatarUrl }
|
||||||
url={ avatarUrl } />
|
/>
|
||||||
</td>
|
</div>,
|
||||||
<td className="mx_RoomDirectory_roomDescription">
|
<div key={ `${room.room_id}_description` }
|
||||||
<div className="mx_RoomDirectory_name">{ name }</div>
|
onClick={(ev) => this.onRoomClicked(room, ev)}
|
||||||
<div className="mx_RoomDirectory_topic"
|
// cancel onMouseDown otherwise shift-clicking highlights text
|
||||||
onClick={ (ev) => { ev.stopPropagation(); } }
|
onMouseDown={(ev) => {ev.preventDefault();}}
|
||||||
dangerouslySetInnerHTML={{ __html: topic }} />
|
className="mx_RoomDirectory_roomDescription"
|
||||||
<div className="mx_RoomDirectory_alias">{ get_display_alias_for_room(room) }</div>
|
>
|
||||||
</td>
|
<div className="mx_RoomDirectory_name">{ name }</div>
|
||||||
<td className="mx_RoomDirectory_roomMemberCount">
|
<div className="mx_RoomDirectory_topic"
|
||||||
{ room.num_joined_members }
|
onClick={ (ev) => { ev.stopPropagation(); } }
|
||||||
</td>
|
dangerouslySetInnerHTML={{ __html: topic }}
|
||||||
<td className="mx_RoomDirectory_preview">{previewButton}</td>
|
/>
|
||||||
<td className="mx_RoomDirectory_join">{joinOrViewButton}</td>
|
<div className="mx_RoomDirectory_alias">{ get_display_alias_for_room(room) }</div>
|
||||||
</tr>
|
</div>,
|
||||||
);
|
<div key={ `${room.room_id}_memberCount` }
|
||||||
|
onClick={(ev) => this.onRoomClicked(room, ev)}
|
||||||
|
// cancel onMouseDown otherwise shift-clicking highlights text
|
||||||
|
onMouseDown={(ev) => {ev.preventDefault();}}
|
||||||
|
className="mx_RoomDirectory_roomMemberCount"
|
||||||
|
>
|
||||||
|
{ room.num_joined_members }
|
||||||
|
</div>,
|
||||||
|
<div key={ `${room.room_id}_preview` }
|
||||||
|
onClick={(ev) => this.onRoomClicked(room, ev)}
|
||||||
|
// cancel onMouseDown otherwise shift-clicking highlights text
|
||||||
|
onMouseDown={(ev) => {ev.preventDefault();}}
|
||||||
|
className="mx_RoomDirectory_preview"
|
||||||
|
>
|
||||||
|
{previewButton}
|
||||||
|
</div>,
|
||||||
|
<div key={ `${room.room_id}_join` }
|
||||||
|
onClick={(ev) => this.onRoomClicked(room, ev)}
|
||||||
|
// cancel onMouseDown otherwise shift-clicking highlights text
|
||||||
|
onMouseDown={(ev) => {ev.preventDefault();}}
|
||||||
|
className="mx_RoomDirectory_join"
|
||||||
|
>
|
||||||
|
{joinOrViewButton}
|
||||||
|
</div>,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
collectScrollPanel = (element) => {
|
collectScrollPanel = (element) => {
|
||||||
|
@ -606,7 +631,8 @@ export default class RoomDirectory extends React.Component {
|
||||||
} else if (this.state.protocolsLoading) {
|
} else if (this.state.protocolsLoading) {
|
||||||
content = <Loader />;
|
content = <Loader />;
|
||||||
} else {
|
} else {
|
||||||
const rows = (this.state.publicRooms || []).map(room => this.getRow(room));
|
const cells = (this.state.publicRooms || [])
|
||||||
|
.reduce((cells, room) => cells.concat(this.createRoomCells(room)), [],);
|
||||||
// we still show the scrollpanel, at least for now, because
|
// we still show the scrollpanel, at least for now, because
|
||||||
// otherwise we don't fetch more because we don't get a fill
|
// otherwise we don't fetch more because we don't get a fill
|
||||||
// request from the scrollpanel because there isn't one
|
// request from the scrollpanel because there isn't one
|
||||||
|
@ -617,14 +643,12 @@ export default class RoomDirectory extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
let scrollpanel_content;
|
let scrollpanel_content;
|
||||||
if (rows.length === 0 && !this.state.loading) {
|
if (cells.length === 0 && !this.state.loading) {
|
||||||
scrollpanel_content = <i>{ _t('No rooms to show') }</i>;
|
scrollpanel_content = <i>{ _t('No rooms to show') }</i>;
|
||||||
} else {
|
} else {
|
||||||
scrollpanel_content = <table className="mx_RoomDirectory_table">
|
scrollpanel_content = <div className="mx_RoomDirectory_table">
|
||||||
<tbody>
|
{ cells }
|
||||||
{ rows }
|
</div>;
|
||||||
</tbody>
|
|
||||||
</table>;
|
|
||||||
}
|
}
|
||||||
const ScrollPanel = sdk.getComponent("structures.ScrollPanel");
|
const ScrollPanel = sdk.getComponent("structures.ScrollPanel");
|
||||||
content = <ScrollPanel ref={this.collectScrollPanel}
|
content = <ScrollPanel ref={this.collectScrollPanel}
|
||||||
|
|
|
@ -0,0 +1,166 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 Šimon Brandner <simon.bra.ag@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 React from 'react';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
import BaseDialog from "..//dialogs/BaseDialog"
|
||||||
|
import AccessibleButton from './AccessibleButton';
|
||||||
|
import {getDesktopCapturerSources} from "matrix-js-sdk/src/webrtc/call";
|
||||||
|
|
||||||
|
export interface DesktopCapturerSource {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
thumbnailURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum Tabs {
|
||||||
|
Screens = "screens",
|
||||||
|
Windows = "windows",
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DesktopCapturerSourceIProps {
|
||||||
|
source: DesktopCapturerSource;
|
||||||
|
onSelect(source: DesktopCapturerSource): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ExistingSource extends React.Component<DesktopCapturerSourceIProps> {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
onClick = (ev) => {
|
||||||
|
this.props.onSelect(this.props.source);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<AccessibleButton
|
||||||
|
className="mx_desktopCapturerSourcePicker_stream_button"
|
||||||
|
title={this.props.source.name}
|
||||||
|
onClick={this.onClick} >
|
||||||
|
<img
|
||||||
|
className="mx_desktopCapturerSourcePicker_stream_thumbnail"
|
||||||
|
src={this.props.source.thumbnailURL}
|
||||||
|
/>
|
||||||
|
<span className="mx_desktopCapturerSourcePicker_stream_name">{this.props.source.name}</span>
|
||||||
|
</AccessibleButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DesktopCapturerSourcePickerIState {
|
||||||
|
selectedTab: Tabs;
|
||||||
|
sources: Array<DesktopCapturerSource>;
|
||||||
|
}
|
||||||
|
export interface DesktopCapturerSourcePickerIProps {
|
||||||
|
onFinished(source: DesktopCapturerSource): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class DesktopCapturerSourcePicker extends React.Component<
|
||||||
|
DesktopCapturerSourcePickerIProps,
|
||||||
|
DesktopCapturerSourcePickerIState
|
||||||
|
> {
|
||||||
|
interval;
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
selectedTab: Tabs.Screens,
|
||||||
|
sources: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
// We update the sources every 500ms to get newer thumbnails
|
||||||
|
this.interval = setInterval(async () => {
|
||||||
|
this.setState({
|
||||||
|
sources: await getDesktopCapturerSources(),
|
||||||
|
});
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
clearInterval(this.interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelect = (source) => {
|
||||||
|
this.props.onFinished(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
onScreensClick = (ev) => {
|
||||||
|
this.setState({selectedTab: Tabs.Screens});
|
||||||
|
}
|
||||||
|
|
||||||
|
onWindowsClick = (ev) => {
|
||||||
|
this.setState({selectedTab: Tabs.Windows});
|
||||||
|
}
|
||||||
|
|
||||||
|
onCloseClick = (ev) => {
|
||||||
|
this.props.onFinished(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let sources;
|
||||||
|
if (this.state.selectedTab === Tabs.Screens) {
|
||||||
|
sources = this.state.sources
|
||||||
|
.filter((source) => {
|
||||||
|
return source.id.startsWith("screen");
|
||||||
|
})
|
||||||
|
.map((source) => {
|
||||||
|
return <ExistingSource source={source} onSelect={this.onSelect} key={source.id} />;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
sources = this.state.sources
|
||||||
|
.filter((source) => {
|
||||||
|
return source.id.startsWith("window");
|
||||||
|
})
|
||||||
|
.map((source) => {
|
||||||
|
return <ExistingSource source={source} onSelect={this.onSelect} key={source.id} />;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const buttonStyle = "mx_desktopCapturerSourcePicker_tabLabel";
|
||||||
|
const screensButtonStyle = buttonStyle + ((this.state.selectedTab === Tabs.Screens) ? "_selected" : "");
|
||||||
|
const windowsButtonStyle = buttonStyle + ((this.state.selectedTab === Tabs.Windows) ? "_selected" : "");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BaseDialog
|
||||||
|
className="mx_desktopCapturerSourcePicker"
|
||||||
|
onFinished={this.onCloseClick}
|
||||||
|
title={_t("Share your screen")}
|
||||||
|
>
|
||||||
|
<div className="mx_desktopCapturerSourcePicker_tabLabels">
|
||||||
|
<AccessibleButton
|
||||||
|
className={screensButtonStyle}
|
||||||
|
onClick={this.onScreensClick}
|
||||||
|
>
|
||||||
|
{_t("Screens")}
|
||||||
|
</AccessibleButton>
|
||||||
|
<AccessibleButton
|
||||||
|
className={windowsButtonStyle}
|
||||||
|
onClick={this.onWindowsClick}
|
||||||
|
>
|
||||||
|
{_t("Windows")}
|
||||||
|
</AccessibleButton>
|
||||||
|
</div>
|
||||||
|
<div className="mx_desktopCapturerSourcePicker_panel">
|
||||||
|
{ sources }
|
||||||
|
</div>
|
||||||
|
</BaseDialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,6 +23,7 @@ import ResizeObserver from 'resize-observer-polyfill';
|
||||||
import dis from '../../../dispatcher/dispatcher';
|
import dis from '../../../dispatcher/dispatcher';
|
||||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
||||||
|
import {isNullOrUndefined} from "matrix-js-sdk/src/utils";
|
||||||
|
|
||||||
// Shamelessly ripped off Modal.js. There's probably a better way
|
// Shamelessly ripped off Modal.js. There's probably a better way
|
||||||
// of doing reusable widgets like dialog boxes & menus where we go and
|
// of doing reusable widgets like dialog boxes & menus where we go and
|
||||||
|
@ -61,6 +62,9 @@ export default class PersistedElement extends React.Component {
|
||||||
// Any PersistedElements with the same persistKey will use
|
// Any PersistedElements with the same persistKey will use
|
||||||
// the same DOM container.
|
// the same DOM container.
|
||||||
persistKey: PropTypes.string.isRequired,
|
persistKey: PropTypes.string.isRequired,
|
||||||
|
|
||||||
|
// z-index for the element. Defaults to 9.
|
||||||
|
zIndex: PropTypes.number,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -165,7 +169,7 @@ export default class PersistedElement extends React.Component {
|
||||||
|
|
||||||
const parentRect = parent.getBoundingClientRect();
|
const parentRect = parent.getBoundingClientRect();
|
||||||
Object.assign(child.style, {
|
Object.assign(child.style, {
|
||||||
zIndex: 9,
|
zIndex: isNullOrUndefined(this.props.zIndex) ? 9 : this.props.zIndex,
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: parentRect.top + 'px',
|
top: parentRect.top + 'px',
|
||||||
left: parentRect.left + 'px',
|
left: parentRect.left + 'px',
|
||||||
|
|
|
@ -33,6 +33,7 @@ interface IProps {
|
||||||
previousPhase?: RightPanelPhases;
|
previousPhase?: RightPanelPhases;
|
||||||
closeLabel?: string;
|
closeLabel?: string;
|
||||||
onClose?(): void;
|
onClose?(): void;
|
||||||
|
refireParams?;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IGroupProps {
|
interface IGroupProps {
|
||||||
|
@ -56,6 +57,7 @@ const BaseCard: React.FC<IProps> = ({
|
||||||
withoutScrollContainer,
|
withoutScrollContainer,
|
||||||
previousPhase,
|
previousPhase,
|
||||||
children,
|
children,
|
||||||
|
refireParams,
|
||||||
}) => {
|
}) => {
|
||||||
let backButton;
|
let backButton;
|
||||||
if (previousPhase) {
|
if (previousPhase) {
|
||||||
|
@ -63,6 +65,7 @@ const BaseCard: React.FC<IProps> = ({
|
||||||
defaultDispatcher.dispatch<SetRightPanelPhasePayload>({
|
defaultDispatcher.dispatch<SetRightPanelPhasePayload>({
|
||||||
action: Action.SetRightPanelPhase,
|
action: Action.SetRightPanelPhase,
|
||||||
phase: previousPhase,
|
phase: previousPhase,
|
||||||
|
refireParams: refireParams,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
backButton = <AccessibleButton className="mx_BaseCard_back" onClick={onBackClick} title={_t("Back")} />;
|
backButton = <AccessibleButton className="mx_BaseCard_back" onClick={onBackClick} title={_t("Back")} />;
|
||||||
|
|
|
@ -60,6 +60,7 @@ import QuestionDialog from "../dialogs/QuestionDialog";
|
||||||
import ConfirmUserActionDialog from "../dialogs/ConfirmUserActionDialog";
|
import ConfirmUserActionDialog from "../dialogs/ConfirmUserActionDialog";
|
||||||
import InfoDialog from "../dialogs/InfoDialog";
|
import InfoDialog from "../dialogs/InfoDialog";
|
||||||
import { EventType } from "matrix-js-sdk/src/@types/event";
|
import { EventType } from "matrix-js-sdk/src/@types/event";
|
||||||
|
import {SetRightPanelPhasePayload} from "../../../dispatcher/payloads/SetRightPanelPhasePayload";
|
||||||
|
|
||||||
interface IDevice {
|
interface IDevice {
|
||||||
deviceId: string;
|
deviceId: string;
|
||||||
|
@ -1534,6 +1535,24 @@ const UserInfo: React.FC<Props> = ({
|
||||||
|
|
||||||
const classes = ["mx_UserInfo"];
|
const classes = ["mx_UserInfo"];
|
||||||
|
|
||||||
|
let refireParams;
|
||||||
|
let previousPhase: RightPanelPhases;
|
||||||
|
// We have no previousPhase for when viewing a UserInfo from a Group or without a Room at this time
|
||||||
|
if (room && phase === RightPanelPhases.EncryptionPanel) {
|
||||||
|
previousPhase = RightPanelPhases.RoomMemberInfo;
|
||||||
|
refireParams = {member: member};
|
||||||
|
} else if (room) {
|
||||||
|
previousPhase = RightPanelPhases.RoomMemberList;
|
||||||
|
}
|
||||||
|
|
||||||
|
const onEncryptionPanelClose = () => {
|
||||||
|
dis.dispatch<SetRightPanelPhasePayload>({
|
||||||
|
action: Action.SetRightPanelPhase,
|
||||||
|
phase: previousPhase,
|
||||||
|
refireParams: refireParams,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let content;
|
let content;
|
||||||
switch (phase) {
|
switch (phase) {
|
||||||
case RightPanelPhases.RoomMemberInfo:
|
case RightPanelPhases.RoomMemberInfo:
|
||||||
|
@ -1553,19 +1572,13 @@ const UserInfo: React.FC<Props> = ({
|
||||||
<EncryptionPanel
|
<EncryptionPanel
|
||||||
{...props as React.ComponentProps<typeof EncryptionPanel>}
|
{...props as React.ComponentProps<typeof EncryptionPanel>}
|
||||||
member={member}
|
member={member}
|
||||||
onClose={onClose}
|
onClose={onEncryptionPanelClose}
|
||||||
isRoomEncrypted={isRoomEncrypted}
|
isRoomEncrypted={isRoomEncrypted}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
let previousPhase: RightPanelPhases;
|
|
||||||
// We have no previousPhase for when viewing a UserInfo from a Group or without a Room at this time
|
|
||||||
if (room) {
|
|
||||||
previousPhase = RightPanelPhases.RoomMemberList;
|
|
||||||
}
|
|
||||||
|
|
||||||
let closeLabel = undefined;
|
let closeLabel = undefined;
|
||||||
if (phase === RightPanelPhases.EncryptionPanel) {
|
if (phase === RightPanelPhases.EncryptionPanel) {
|
||||||
const verificationRequest = (props as React.ComponentProps<typeof EncryptionPanel>).verificationRequest;
|
const verificationRequest = (props as React.ComponentProps<typeof EncryptionPanel>).verificationRequest;
|
||||||
|
@ -1581,6 +1594,7 @@ const UserInfo: React.FC<Props> = ({
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
closeLabel={closeLabel}
|
closeLabel={closeLabel}
|
||||||
previousPhase={previousPhase}
|
previousPhase={previousPhase}
|
||||||
|
refireParams={refireParams}
|
||||||
>
|
>
|
||||||
{ content }
|
{ content }
|
||||||
</BaseCard>;
|
</BaseCard>;
|
||||||
|
|
|
@ -69,19 +69,24 @@ export default class RoomProfileSettings extends React.Component {
|
||||||
// clear file upload field so same file can be selected
|
// clear file upload field so same file can be selected
|
||||||
this._avatarUpload.current.value = "";
|
this._avatarUpload.current.value = "";
|
||||||
this.setState({
|
this.setState({
|
||||||
avatarUrl: undefined,
|
avatarUrl: null,
|
||||||
avatarFile: undefined,
|
avatarFile: null,
|
||||||
enableProfileSave: true,
|
enableProfileSave: true,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
_clearProfile = async (e) => {
|
_cancelProfileChanges = async (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (!this.state.enableProfileSave) return;
|
if (!this.state.enableProfileSave) return;
|
||||||
this._removeAvatar();
|
this.setState({
|
||||||
this.setState({enableProfileSave: false, displayName: this.state.originalDisplayName});
|
enableProfileSave: false,
|
||||||
|
displayName: this.state.originalDisplayName,
|
||||||
|
topic: this.state.originalTopic,
|
||||||
|
avatarUrl: this.state.originalAvatarUrl,
|
||||||
|
avatarFile: null,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
_saveProfile = async (e) => {
|
_saveProfile = async (e) => {
|
||||||
|
@ -108,7 +113,7 @@ export default class RoomProfileSettings extends React.Component {
|
||||||
newState.originalAvatarUrl = newState.avatarUrl;
|
newState.originalAvatarUrl = newState.avatarUrl;
|
||||||
newState.avatarFile = null;
|
newState.avatarFile = null;
|
||||||
} else if (this.state.originalAvatarUrl !== this.state.avatarUrl) {
|
} else if (this.state.originalAvatarUrl !== this.state.avatarUrl) {
|
||||||
await client.sendStateEvent(this.props.roomId, 'm.room.avatar', {url: undefined}, '');
|
await client.sendStateEvent(this.props.roomId, 'm.room.avatar', {}, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.originalTopic !== this.state.topic) {
|
if (this.state.originalTopic !== this.state.topic) {
|
||||||
|
@ -164,11 +169,15 @@ export default class RoomProfileSettings extends React.Component {
|
||||||
const AvatarSetting = sdk.getComponent('settings.AvatarSetting');
|
const AvatarSetting = sdk.getComponent('settings.AvatarSetting');
|
||||||
|
|
||||||
let profileSettingsButtons;
|
let profileSettingsButtons;
|
||||||
if (this.state.canSetTopic && this.state.canSetName) {
|
if (
|
||||||
|
this.state.canSetName ||
|
||||||
|
this.state.canSetTopic ||
|
||||||
|
this.state.canSetAvatar
|
||||||
|
) {
|
||||||
profileSettingsButtons = (
|
profileSettingsButtons = (
|
||||||
<div className="mx_ProfileSettings_buttons">
|
<div className="mx_ProfileSettings_buttons">
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
onClick={this._clearProfile}
|
onClick={this._cancelProfileChanges}
|
||||||
kind="link"
|
kind="link"
|
||||||
disabled={!this.state.enableProfileSave}
|
disabled={!this.state.enableProfileSave}
|
||||||
>
|
>
|
||||||
|
|
|
@ -426,7 +426,8 @@ export default class MessageComposer extends React.Component {
|
||||||
<EmojiButton key="emoji_button" addEmoji={this.addEmoji} />,
|
<EmojiButton key="emoji_button" addEmoji={this.addEmoji} />,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (SettingsStore.getValue(UIFeature.Widgets)) {
|
if (SettingsStore.getValue(UIFeature.Widgets) &&
|
||||||
|
SettingsStore.getValue("MessageComposerInput.showStickersButton")) {
|
||||||
controls.push(<Stickerpicker key="stickerpicker_controls_button" room={this.props.room} />);
|
controls.push(<Stickerpicker key="stickerpicker_controls_button" room={this.props.room} />);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -403,6 +403,7 @@ export default class SendMessageComposer extends React.Component {
|
||||||
this._editorRef.clearUndoHistory();
|
this._editorRef.clearUndoHistory();
|
||||||
this._editorRef.focus();
|
this._editorRef.focus();
|
||||||
this._clearStoredEditorState();
|
this._clearStoredEditorState();
|
||||||
|
dis.dispatch({action: "scroll_to_bottom"});
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
|
|
@ -264,7 +264,7 @@ export default class Stickerpicker extends React.Component {
|
||||||
width: this.popoverWidth,
|
width: this.popoverWidth,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<PersistedElement persistKey={PERSISTED_ELEMENT_KEY} style={{zIndex: STICKERPICKER_Z_INDEX}}>
|
<PersistedElement persistKey={PERSISTED_ELEMENT_KEY} zIndex={STICKERPICKER_Z_INDEX}>
|
||||||
<AppTile
|
<AppTile
|
||||||
app={stickerApp}
|
app={stickerApp}
|
||||||
room={this.props.room}
|
room={this.props.room}
|
||||||
|
|
|
@ -52,19 +52,23 @@ export default class ProfileSettings extends React.Component {
|
||||||
// clear file upload field so same file can be selected
|
// clear file upload field so same file can be selected
|
||||||
this._avatarUpload.current.value = "";
|
this._avatarUpload.current.value = "";
|
||||||
this.setState({
|
this.setState({
|
||||||
avatarUrl: undefined,
|
avatarUrl: null,
|
||||||
avatarFile: undefined,
|
avatarFile: null,
|
||||||
enableProfileSave: true,
|
enableProfileSave: true,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
_clearProfile = async (e) => {
|
_cancelProfileChanges = async (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (!this.state.enableProfileSave) return;
|
if (!this.state.enableProfileSave) return;
|
||||||
this._removeAvatar();
|
this.setState({
|
||||||
this.setState({enableProfileSave: false, displayName: this.state.originalDisplayName});
|
enableProfileSave: false,
|
||||||
|
displayName: this.state.originalDisplayName,
|
||||||
|
avatarUrl: this.state.originalAvatarUrl,
|
||||||
|
avatarFile: null,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
_saveProfile = async (e) => {
|
_saveProfile = async (e) => {
|
||||||
|
@ -186,7 +190,7 @@ export default class ProfileSettings extends React.Component {
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_ProfileSettings_buttons">
|
<div className="mx_ProfileSettings_buttons">
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
onClick={this._clearProfile}
|
onClick={this._cancelProfileChanges}
|
||||||
kind="link"
|
kind="link"
|
||||||
disabled={!this.state.enableProfileSave}
|
disabled={!this.state.enableProfileSave}
|
||||||
>
|
>
|
||||||
|
|
|
@ -34,6 +34,7 @@ export default class PreferencesUserSettingsTab extends React.Component {
|
||||||
'MessageComposerInput.suggestEmoji',
|
'MessageComposerInput.suggestEmoji',
|
||||||
'sendTypingNotifications',
|
'sendTypingNotifications',
|
||||||
'MessageComposerInput.ctrlEnterToSend',
|
'MessageComposerInput.ctrlEnterToSend',
|
||||||
|
'MessageComposerInput.showStickersButton',
|
||||||
];
|
];
|
||||||
|
|
||||||
static TIMELINE_SETTINGS = [
|
static TIMELINE_SETTINGS = [
|
||||||
|
|
|
@ -795,6 +795,7 @@
|
||||||
"Font size": "Font size",
|
"Font size": "Font size",
|
||||||
"Use custom size": "Use custom size",
|
"Use custom size": "Use custom size",
|
||||||
"Enable Emoji suggestions while typing": "Enable Emoji suggestions while typing",
|
"Enable Emoji suggestions while typing": "Enable Emoji suggestions while typing",
|
||||||
|
"Show stickers button": "Show stickers button",
|
||||||
"Use a more compact ‘Modern’ layout": "Use a more compact ‘Modern’ layout",
|
"Use a more compact ‘Modern’ layout": "Use a more compact ‘Modern’ layout",
|
||||||
"Show a placeholder for removed messages": "Show a placeholder for removed messages",
|
"Show a placeholder for removed messages": "Show a placeholder for removed messages",
|
||||||
"Show join/leave messages (invites/kicks/bans unaffected)": "Show join/leave messages (invites/kicks/bans unaffected)",
|
"Show join/leave messages (invites/kicks/bans unaffected)": "Show join/leave messages (invites/kicks/bans unaffected)",
|
||||||
|
@ -1851,6 +1852,9 @@
|
||||||
"Use the <a>Desktop app</a> to search encrypted messages": "Use the <a>Desktop app</a> to search encrypted messages",
|
"Use the <a>Desktop app</a> to search encrypted messages": "Use the <a>Desktop app</a> to search encrypted messages",
|
||||||
"This version of %(brand)s does not support viewing some encrypted files": "This version of %(brand)s does not support viewing some encrypted files",
|
"This version of %(brand)s does not support viewing some encrypted files": "This version of %(brand)s does not support viewing some encrypted files",
|
||||||
"This version of %(brand)s does not support searching encrypted messages": "This version of %(brand)s does not support searching encrypted messages",
|
"This version of %(brand)s does not support searching encrypted messages": "This version of %(brand)s does not support searching encrypted messages",
|
||||||
|
"Share your screen": "Share your screen",
|
||||||
|
"Screens": "Screens",
|
||||||
|
"Windows": "Windows",
|
||||||
"Join": "Join",
|
"Join": "Join",
|
||||||
"No results": "No results",
|
"No results": "No results",
|
||||||
"Please <newIssueLink>create a new issue</newIssueLink> on GitHub so that we can investigate this bug.": "Please <newIssueLink>create a new issue</newIssueLink> on GitHub so that we can investigate this bug.",
|
"Please <newIssueLink>create a new issue</newIssueLink> on GitHub so that we can investigate this bug.": "Please <newIssueLink>create a new issue</newIssueLink> on GitHub so that we can investigate this bug.",
|
||||||
|
|
|
@ -240,6 +240,11 @@ export const SETTINGS: {[setting: string]: ISetting} = {
|
||||||
default: true,
|
default: true,
|
||||||
invertedSettingName: 'MessageComposerInput.dontSuggestEmoji',
|
invertedSettingName: 'MessageComposerInput.dontSuggestEmoji',
|
||||||
},
|
},
|
||||||
|
"MessageComposerInput.showStickersButton": {
|
||||||
|
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
|
||||||
|
displayName: _td('Show stickers button'),
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
// TODO: Wire up appropriately to UI (FTUE notifications)
|
// TODO: Wire up appropriately to UI (FTUE notifications)
|
||||||
"Notifications.alwaysShowBadgeCounts": {
|
"Notifications.alwaysShowBadgeCounts": {
|
||||||
supportedLevels: LEVELS_ROOM_OR_ACCOUNT,
|
supportedLevels: LEVELS_ROOM_OR_ACCOUNT,
|
||||||
|
|
Loading…
Reference in New Issue