Merge branch 'develop' into gsouquet/switch-rooms

This commit is contained in:
Germain Souquet 2021-06-01 09:02:28 +01:00
commit d894cc6f7a
13 changed files with 216 additions and 122 deletions

View file

@ -61,6 +61,7 @@ limitations under the License.
&.mx_RoomSublist_headerContainer_sticky { &.mx_RoomSublist_headerContainer_sticky {
position: fixed; position: fixed;
height: 32px; // to match the header container height: 32px; // to match the header container
// width set by JS because of a compat issue between Firefox and Chrome
width: calc(100% - 15px); width: calc(100% - 15px);
} }

View file

@ -43,6 +43,7 @@ import TypingStore from "../stores/TypingStore";
import { EventIndexPeg } from "../indexing/EventIndexPeg"; import { EventIndexPeg } from "../indexing/EventIndexPeg";
import {VoiceRecordingStore} from "../stores/VoiceRecordingStore"; import {VoiceRecordingStore} from "../stores/VoiceRecordingStore";
import PerformanceMonitor from "../performance"; import PerformanceMonitor from "../performance";
import UIStore from "../stores/UIStore";
declare global { declare global {
interface Window { interface Window {
@ -82,6 +83,7 @@ declare global {
mxEventIndexPeg: EventIndexPeg; mxEventIndexPeg: EventIndexPeg;
mxPerformanceMonitor: PerformanceMonitor; mxPerformanceMonitor: PerformanceMonitor;
mxPerformanceEntryNames: any; mxPerformanceEntryNames: any;
mxUIStore: UIStore;
} }
interface Document { interface Document {

View file

@ -462,6 +462,9 @@ export default class CallHandler extends EventEmitter {
if (call.hangupReason === CallErrorCode.UserHangup) { if (call.hangupReason === CallErrorCode.UserHangup) {
title = _t("Call Declined"); title = _t("Call Declined");
description = _t("The other party declined the call."); description = _t("The other party declined the call.");
} else if (call.hangupReason === CallErrorCode.UserBusy) {
title = _t("User Busy");
description = _t("The user you called is busy.");
} else if (call.hangupReason === CallErrorCode.InviteTimeout) { } else if (call.hangupReason === CallErrorCode.InviteTimeout) {
title = _t("Call Failed"); title = _t("Call Failed");
// XXX: full stop appended as some relic here, but these // XXX: full stop appended as some relic here, but these

View file

@ -67,6 +67,7 @@ const cssClasses = [
@replaceableComponent("structures.LeftPanel") @replaceableComponent("structures.LeftPanel")
export default class LeftPanel extends React.Component<IProps, IState> { export default class LeftPanel extends React.Component<IProps, IState> {
private ref: React.RefObject<HTMLDivElement> = createRef();
private listContainerRef: React.RefObject<HTMLDivElement> = createRef(); private listContainerRef: React.RefObject<HTMLDivElement> = createRef();
private groupFilterPanelWatcherRef: string; private groupFilterPanelWatcherRef: string;
private bgImageWatcherRef: string; private bgImageWatcherRef: string;
@ -93,6 +94,11 @@ export default class LeftPanel extends React.Component<IProps, IState> {
}); });
} }
public componentDidMount() {
UIStore.instance.trackElementDimensions("ListContainer", this.listContainerRef.current);
UIStore.instance.on("ListContainer", this.refreshStickyHeaders);
}
public componentWillUnmount() { public componentWillUnmount() {
SettingsStore.unwatchSetting(this.groupFilterPanelWatcherRef); SettingsStore.unwatchSetting(this.groupFilterPanelWatcherRef);
SettingsStore.unwatchSetting(this.bgImageWatcherRef); SettingsStore.unwatchSetting(this.bgImageWatcherRef);
@ -100,6 +106,14 @@ export default class LeftPanel extends React.Component<IProps, IState> {
RoomListStore.instance.off(LISTS_UPDATE_EVENT, this.onBreadcrumbsUpdate); RoomListStore.instance.off(LISTS_UPDATE_EVENT, this.onBreadcrumbsUpdate);
OwnProfileStore.instance.off(UPDATE_EVENT, this.onBackgroundImageUpdate); OwnProfileStore.instance.off(UPDATE_EVENT, this.onBackgroundImageUpdate);
SpaceStore.instance.off(UPDATE_SELECTED_SPACE, this.updateActiveSpace); SpaceStore.instance.off(UPDATE_SELECTED_SPACE, this.updateActiveSpace);
UIStore.instance.stopTrackingElementDimensions("ListContainer");
UIStore.instance.removeListener("ListContainer", this.refreshStickyHeaders);
}
public componentDidUpdate(prevProps: IProps, prevState: IState): void {
if (prevState.activeSpace !== this.state.activeSpace) {
this.refreshStickyHeaders();
}
} }
private updateActiveSpace = (activeSpace: Room) => { private updateActiveSpace = (activeSpace: Room) => {
@ -245,10 +259,24 @@ export default class LeftPanel extends React.Component<IProps, IState> {
if (!header.classList.contains("mx_RoomSublist_headerContainer_sticky")) { if (!header.classList.contains("mx_RoomSublist_headerContainer_sticky")) {
header.classList.add("mx_RoomSublist_headerContainer_sticky"); header.classList.add("mx_RoomSublist_headerContainer_sticky");
} }
const listDimensions = UIStore.instance.getElementDimensions("ListContainer");
if (listDimensions) {
const headerRightMargin = 15; // calculated from margins and widths to align with non-sticky tiles
const headerStickyWidth = listDimensions.width - headerRightMargin;
const newWidth = `${headerStickyWidth}px`;
if (header.style.width !== newWidth) {
header.style.width = newWidth;
}
}
} else if (!style.stickyTop && !style.stickyBottom) { } else if (!style.stickyTop && !style.stickyBottom) {
if (header.classList.contains("mx_RoomSublist_headerContainer_sticky")) { if (header.classList.contains("mx_RoomSublist_headerContainer_sticky")) {
header.classList.remove("mx_RoomSublist_headerContainer_sticky"); header.classList.remove("mx_RoomSublist_headerContainer_sticky");
} }
if (header.style.width) {
header.style.removeProperty('width');
}
} }
} }
@ -407,6 +435,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
onBlur={this.onBlur} onBlur={this.onBlur}
isMinimized={this.props.isMinimized} isMinimized={this.props.isMinimized}
activeSpace={this.state.activeSpace} activeSpace={this.state.activeSpace}
onListCollapse={this.refreshStickyHeaders}
/>; />;
const containerClasses = classNames({ const containerClasses = classNames({
@ -420,7 +449,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
); );
return ( return (
<div className={containerClasses}> <div className={containerClasses} ref={this.ref}>
{leftLeftPanel} {leftLeftPanel}
<aside className="mx_LeftPanel_roomListContainer"> <aside className="mx_LeftPanel_roomListContainer">
{this.renderHeader()} {this.renderHeader()}

View file

@ -232,6 +232,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
private accountPasswordTimer?: NodeJS.Timeout; private accountPasswordTimer?: NodeJS.Timeout;
private focusComposer: boolean; private focusComposer: boolean;
private subTitleStatus: string; private subTitleStatus: string;
private prevWindowWidth: number;
private readonly loggedInView: React.RefObject<LoggedInViewType>; private readonly loggedInView: React.RefObject<LoggedInViewType>;
private readonly dispatcherRef: any; private readonly dispatcherRef: any;
@ -277,6 +278,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
} }
} }
this.prevWindowWidth = UIStore.instance.windowWidth || 1000;
UIStore.instance.on(UI_EVENTS.Resize, this.handleResize); UIStore.instance.on(UI_EVENTS.Resize, this.handleResize);
this.pageChanging = false; this.pageChanging = false;
@ -1821,13 +1823,15 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
const LHS_THRESHOLD = 1000; const LHS_THRESHOLD = 1000;
const width = UIStore.instance.windowWidth; const width = UIStore.instance.windowWidth;
if (width <= LHS_THRESHOLD && !this.state.collapseLhs) { if (this.prevWindowWidth < LHS_THRESHOLD && width >= LHS_THRESHOLD) {
dis.dispatch({ action: 'hide_left_panel' });
}
if (width > LHS_THRESHOLD && this.state.collapseLhs) {
dis.dispatch({ action: 'show_left_panel' }); dis.dispatch({ action: 'show_left_panel' });
} }
if (this.prevWindowWidth >= LHS_THRESHOLD && width < LHS_THRESHOLD) {
dis.dispatch({ action: 'hide_left_panel' });
}
this.prevWindowWidth = width;
this.state.resizeNotifier.notifyWindowResized(); this.state.resizeNotifier.notifyWindowResized();
}; };

View file

@ -319,7 +319,7 @@ export const HierarchyLevel = ({
key={roomId} key={roomId}
room={rooms.get(roomId)} room={rooms.get(roomId)}
numChildRooms={Array.from(relations.get(roomId)?.values() || []) numChildRooms={Array.from(relations.get(roomId)?.values() || [])
.filter(ev => rooms.get(ev.state_key)?.room_type !== RoomType.Space).length} .filter(ev => rooms.has(ev.state_key) && !rooms.get(ev.state_key).room_type).length}
suggested={relations.get(spaceId)?.get(roomId)?.content.suggested} suggested={relations.get(spaceId)?.get(roomId)?.content.suggested}
selected={selectedMap?.get(spaceId)?.has(roomId)} selected={selectedMap?.get(spaceId)?.has(roomId)}
onViewRoomClick={(autoJoin) => { onViewRoomClick={(autoJoin) => {
@ -437,7 +437,7 @@ export const SpaceHierarchy: React.FC<IHierarchyProps> = ({
let content; let content;
if (roomsMap) { if (roomsMap) {
const numRooms = Array.from(roomsMap.values()).filter(r => r.room_type !== RoomType.Space).length; const numRooms = Array.from(roomsMap.values()).filter(r => !r.room_type).length;
const numSpaces = roomsMap.size - numRooms - 1; // -1 at the end to exclude the space we are looking at const numSpaces = roomsMap.size - numRooms - 1; // -1 at the end to exclude the space we are looking at
let countsStr; let countsStr;

View file

@ -47,10 +47,18 @@ import { MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
import {replaceableComponent} from "../../../utils/replaceableComponent"; import {replaceableComponent} from "../../../utils/replaceableComponent";
import {mediaFromMxc} from "../../../customisations/Media"; import {mediaFromMxc} from "../../../customisations/Media";
import {getAddressType} from "../../../UserAddress"; import {getAddressType} from "../../../UserAddress";
import BaseAvatar from '../avatars/BaseAvatar';
import AccessibleButton from '../elements/AccessibleButton';
// we have a number of types defined from the Matrix spec which can't reasonably be altered here. // we have a number of types defined from the Matrix spec which can't reasonably be altered here.
/* eslint-disable camelcase */ /* eslint-disable camelcase */
interface IRecentUser {
userId: string,
user: RoomMember,
lastActive: number,
}
export const KIND_DM = "dm"; export const KIND_DM = "dm";
export const KIND_INVITE = "invite"; export const KIND_INVITE = "invite";
export const KIND_CALL_TRANSFER = "call_transfer"; export const KIND_CALL_TRANSFER = "call_transfer";
@ -61,43 +69,41 @@ const INCREMENT_ROOMS_SHOWN = 5; // Number of rooms to add when 'show more' is c
// This is the interface that is expected by various components in this file. It is a bit // 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 // awkward because it also matches the RoomMember class from the js-sdk with some extra support
// for 3PIDs/email addresses. // for 3PIDs/email addresses.
// abstract class Member {
// XXX: We should use TypeScript interfaces instead of this weird "abstract" class.
class Member {
/** /**
* The display name of this Member. For users this should be their profile's display * 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). * name or user ID if none set. For 3PIDs this should be the 3PID address (email).
*/ */
get name(): string { throw new Error("Member class not implemented"); } public abstract get name(): string;
/** /**
* The ID of this Member. For users this should be their user ID. For 3PIDs this should * The ID of this Member. For users this should be their user ID. For 3PIDs this should
* be the 3PID address (email). * be the 3PID address (email).
*/ */
get userId(): string { throw new Error("Member class not implemented"); } public abstract get userId(): string;
/** /**
* Gets the MXC URL of this Member's avatar. For users this should be their profile's * 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. * avatar MXC URL or null if none set. For 3PIDs this should always be null.
*/ */
getMxcAvatarUrl(): string { throw new Error("Member class not implemented"); } public abstract getMxcAvatarUrl(): string;
} }
class DirectoryMember extends Member { class DirectoryMember extends Member {
_userId: string; private readonly _userId: string;
_displayName: string; private readonly displayName: string;
_avatarUrl: string; private readonly avatarUrl: string;
constructor(userDirResult: {user_id: string, display_name: string, avatar_url: string}) { constructor(userDirResult: {user_id: string, display_name: string, avatar_url: string}) {
super(); super();
this._userId = userDirResult.user_id; this._userId = userDirResult.user_id;
this._displayName = userDirResult.display_name; this.displayName = userDirResult.display_name;
this._avatarUrl = userDirResult.avatar_url; this.avatarUrl = userDirResult.avatar_url;
} }
// These next class members are for the Member interface // These next class members are for the Member interface
get name(): string { get name(): string {
return this._displayName || this._userId; return this.displayName || this._userId;
} }
get userId(): string { get userId(): string {
@ -105,32 +111,32 @@ class DirectoryMember extends Member {
} }
getMxcAvatarUrl(): string { getMxcAvatarUrl(): string {
return this._avatarUrl; return this.avatarUrl;
} }
} }
class ThreepidMember extends Member { class ThreepidMember extends Member {
_id: string; private readonly id: string;
constructor(id: string) { constructor(id: string) {
super(); super();
this._id = id; this.id = id;
} }
// This is a getter that would be falsey on all other implementations. Until we have // This is a getter that would be falsey on all other implementations. Until we have
// better type support in the react-sdk we can use this trick to determine the kind // better type support in the react-sdk we can use this trick to determine the kind
// of 3PID we're dealing with, if any. // of 3PID we're dealing with, if any.
get isEmail(): boolean { get isEmail(): boolean {
return this._id.includes('@'); return this.id.includes('@');
} }
// These next class members are for the Member interface // These next class members are for the Member interface
get name(): string { get name(): string {
return this._id; return this.id;
} }
get userId(): string { get userId(): string {
return this._id; return this.id;
} }
getMxcAvatarUrl(): string { getMxcAvatarUrl(): string {
@ -140,11 +146,11 @@ class ThreepidMember extends Member {
interface IDMUserTileProps { interface IDMUserTileProps {
member: RoomMember; member: RoomMember;
onRemove: (RoomMember) => any; onRemove(member: RoomMember): void;
} }
class DMUserTile extends React.PureComponent<IDMUserTileProps> { class DMUserTile extends React.PureComponent<IDMUserTileProps> {
_onRemove = (e) => { private onRemove = (e) => {
// Stop the browser from highlighting text // Stop the browser from highlighting text
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@ -153,9 +159,6 @@ class DMUserTile extends React.PureComponent<IDMUserTileProps> {
}; };
render() { render() {
const BaseAvatar = sdk.getComponent("views.avatars.BaseAvatar");
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
const avatarSize = 20; const avatarSize = 20;
const avatar = this.props.member.isEmail const avatar = this.props.member.isEmail
? <img ? <img
@ -177,7 +180,7 @@ class DMUserTile extends React.PureComponent<IDMUserTileProps> {
closeButton = ( closeButton = (
<AccessibleButton <AccessibleButton
className='mx_InviteDialog_userTile_remove' className='mx_InviteDialog_userTile_remove'
onClick={this._onRemove} onClick={this.onRemove}
> >
<img src={require("../../../../res/img/icon-pill-remove.svg")} <img src={require("../../../../res/img/icon-pill-remove.svg")}
alt={_t('Remove')} width={8} height={8} alt={_t('Remove')} width={8} height={8}
@ -201,13 +204,13 @@ class DMUserTile extends React.PureComponent<IDMUserTileProps> {
interface IDMRoomTileProps { interface IDMRoomTileProps {
member: RoomMember; member: RoomMember;
lastActiveTs: number; lastActiveTs: number;
onToggle: (RoomMember) => any; onToggle(member: RoomMember): void;
highlightWord: string; highlightWord: string;
isSelected: boolean; isSelected: boolean;
} }
class DMRoomTile extends React.PureComponent<IDMRoomTileProps> { class DMRoomTile extends React.PureComponent<IDMRoomTileProps> {
_onClick = (e) => { private onClick = (e) => {
// Stop the browser from highlighting text // Stop the browser from highlighting text
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@ -215,7 +218,7 @@ class DMRoomTile extends React.PureComponent<IDMRoomTileProps> {
this.props.onToggle(this.props.member); this.props.onToggle(this.props.member);
}; };
_highlightName(str: string) { private highlightName(str: string) {
if (!this.props.highlightWord) return str; if (!this.props.highlightWord) return str;
// We convert things to lowercase for index searching, but pull substrings from // We convert things to lowercase for index searching, but pull substrings from
@ -252,8 +255,6 @@ class DMRoomTile extends React.PureComponent<IDMRoomTileProps> {
} }
render() { render() {
const BaseAvatar = sdk.getComponent("views.avatars.BaseAvatar");
let timestamp = null; let timestamp = null;
if (this.props.lastActiveTs) { if (this.props.lastActiveTs) {
const humanTs = humanizeTime(this.props.lastActiveTs); const humanTs = humanizeTime(this.props.lastActiveTs);
@ -291,13 +292,13 @@ class DMRoomTile extends React.PureComponent<IDMRoomTileProps> {
const caption = this.props.member.isEmail const caption = this.props.member.isEmail
? _t("Invite by email") ? _t("Invite by email")
: this._highlightName(this.props.member.userId); : this.highlightName(this.props.member.userId);
return ( return (
<div className='mx_InviteDialog_roomTile' onClick={this._onClick}> <div className='mx_InviteDialog_roomTile' onClick={this.onClick}>
{stackedAvatar} {stackedAvatar}
<span className="mx_InviteDialog_roomTile_nameStack"> <span className="mx_InviteDialog_roomTile_nameStack">
<div className='mx_InviteDialog_roomTile_name'>{this._highlightName(this.props.member.name)}</div> <div className='mx_InviteDialog_roomTile_name'>{this.highlightName(this.props.member.name)}</div>
<div className='mx_InviteDialog_roomTile_userId'>{caption}</div> <div className='mx_InviteDialog_roomTile_userId'>{caption}</div>
</span> </span>
{timestamp} {timestamp}
@ -308,7 +309,7 @@ class DMRoomTile extends React.PureComponent<IDMRoomTileProps> {
interface IInviteDialogProps { interface IInviteDialogProps {
// Takes an array of user IDs/emails to invite. // Takes an array of user IDs/emails to invite.
onFinished: (toInvite?: string[]) => any; onFinished: (toInvite?: string[]) => void;
// The kind of invite being performed. Assumed to be KIND_DM if // The kind of invite being performed. Assumed to be KIND_DM if
// not provided. // not provided.
@ -349,8 +350,9 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
initialText: "", initialText: "",
}; };
_debounceTimer: NodeJS.Timeout = null; // actually number because we're in the browser private debounceTimer: NodeJS.Timeout = null; // actually number because we're in the browser
_editorRef: any = null; private editorRef = createRef<HTMLInputElement>();
private unmounted = false;
constructor(props) { constructor(props) {
super(props); super(props);
@ -378,7 +380,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
filterText: this.props.initialText, filterText: this.props.initialText,
recents: InviteDialog.buildRecents(alreadyInvited), recents: InviteDialog.buildRecents(alreadyInvited),
numRecentsShown: INITIAL_ROOMS_SHOWN, numRecentsShown: INITIAL_ROOMS_SHOWN,
suggestions: this._buildSuggestions(alreadyInvited), suggestions: this.buildSuggestions(alreadyInvited),
numSuggestionsShown: INITIAL_ROOMS_SHOWN, numSuggestionsShown: INITIAL_ROOMS_SHOWN,
serverResultsMixin: [], serverResultsMixin: [],
threepidResultsMixin: [], threepidResultsMixin: [],
@ -390,21 +392,23 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
busy: false, busy: false,
errorText: null, errorText: null,
}; };
this._editorRef = createRef();
} }
componentDidMount() { componentDidMount() {
if (this.props.initialText) { if (this.props.initialText) {
this._updateSuggestions(this.props.initialText); this.updateSuggestions(this.props.initialText);
} }
} }
componentWillUnmount() {
this.unmounted = true;
}
private onConsultFirstChange = (ev) => { private onConsultFirstChange = (ev) => {
this.setState({consultFirst: ev.target.checked}); this.setState({consultFirst: ev.target.checked});
} }
static buildRecents(excludedTargetIds: Set<string>): {userId: string, user: RoomMember, lastActive: number}[] { public static buildRecents(excludedTargetIds: Set<string>): IRecentUser[] {
const rooms = DMRoomMap.shared().getUniqueRoomsWithIndividuals(); // map of userId => js-sdk Room const rooms = DMRoomMap.shared().getUniqueRoomsWithIndividuals(); // map of userId => js-sdk Room
// Also pull in all the rooms tagged as DefaultTagID.DM so we don't miss anything. Sometimes the // Also pull in all the rooms tagged as DefaultTagID.DM so we don't miss anything. Sometimes the
@ -467,7 +471,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
return recents; return recents;
} }
_buildSuggestions(excludedTargetIds: Set<string>): {userId: string, user: RoomMember}[] { private buildSuggestions(excludedTargetIds: Set<string>): {userId: string, user: RoomMember}[] {
const maxConsideredMembers = 200; const maxConsideredMembers = 200;
const joinedRooms = MatrixClientPeg.get().getRooms() const joinedRooms = MatrixClientPeg.get().getRooms()
.filter(r => r.getMyMembership() === 'join' && r.getJoinedMemberCount() <= maxConsideredMembers); .filter(r => r.getMyMembership() === 'join' && r.getJoinedMemberCount() <= maxConsideredMembers);
@ -585,7 +589,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
return members.map(m => ({userId: m.member.userId, user: m.member})); return members.map(m => ({userId: m.member.userId, user: m.member}));
} }
_shouldAbortAfterInviteError(result): boolean { private shouldAbortAfterInviteError(result): boolean {
const failedUsers = Object.keys(result.states).filter(a => result.states[a] === 'error'); const failedUsers = Object.keys(result.states).filter(a => result.states[a] === 'error');
if (failedUsers.length > 0) { if (failedUsers.length > 0) {
console.log("Failed to invite users: ", result); console.log("Failed to invite users: ", result);
@ -600,7 +604,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
return false; return false;
} }
_convertFilter(): Member[] { private convertFilter(): Member[] {
// Check to see if there's anything to convert first // Check to see if there's anything to convert first
if (!this.state.filterText || !this.state.filterText.includes('@')) return this.state.targets || []; if (!this.state.filterText || !this.state.filterText.includes('@')) return this.state.targets || [];
@ -617,10 +621,10 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
return newTargets; return newTargets;
} }
_startDm = async () => { private startDm = async () => {
this.setState({busy: true}); this.setState({busy: true});
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
const targets = this._convertFilter(); const targets = this.convertFilter();
const targetIds = targets.map(t => t.userId); const targetIds = targets.map(t => t.userId);
// Check if there is already a DM with these people and reuse it if possible. // Check if there is already a DM with these people and reuse it if possible.
@ -694,11 +698,11 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
} }
}; };
_inviteUsers = async () => { private inviteUsers = async () => {
const startTime = CountlyAnalytics.getTimestamp(); const startTime = CountlyAnalytics.getTimestamp();
this.setState({busy: true}); this.setState({busy: true});
this._convertFilter(); this.convertFilter();
const targets = this._convertFilter(); const targets = this.convertFilter();
const targetIds = targets.map(t => t.userId); const targetIds = targets.map(t => t.userId);
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
@ -715,7 +719,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
try { try {
const result = await inviteMultipleToRoom(this.props.roomId, targetIds) const result = await inviteMultipleToRoom(this.props.roomId, targetIds)
CountlyAnalytics.instance.trackSendInvite(startTime, this.props.roomId, targetIds.length); CountlyAnalytics.instance.trackSendInvite(startTime, this.props.roomId, targetIds.length);
if (!this._shouldAbortAfterInviteError(result)) { // handles setting error message too if (!this.shouldAbortAfterInviteError(result)) { // handles setting error message too
this.props.onFinished(); this.props.onFinished();
} }
@ -749,9 +753,9 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
} }
}; };
_transferCall = async () => { private transferCall = async () => {
this._convertFilter(); this.convertFilter();
const targets = this._convertFilter(); const targets = this.convertFilter();
const targetIds = targets.map(t => t.userId); const targetIds = targets.map(t => t.userId);
if (targetIds.length > 1) { if (targetIds.length > 1) {
this.setState({ this.setState({
@ -790,26 +794,26 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
} }
}; };
_onKeyDown = (e) => { private onKeyDown = (e) => {
if (this.state.busy) return; if (this.state.busy) return;
const value = e.target.value.trim(); const value = e.target.value.trim();
const hasModifiers = e.ctrlKey || e.shiftKey || e.metaKey; const hasModifiers = e.ctrlKey || e.shiftKey || e.metaKey;
if (!value && this.state.targets.length > 0 && e.key === Key.BACKSPACE && !hasModifiers) { if (!value && this.state.targets.length > 0 && e.key === Key.BACKSPACE && !hasModifiers) {
// when the field is empty and the user hits backspace remove the right-most target // when the field is empty and the user hits backspace remove the right-most target
e.preventDefault(); e.preventDefault();
this._removeMember(this.state.targets[this.state.targets.length - 1]); this.removeMember(this.state.targets[this.state.targets.length - 1]);
} else if (value && e.key === Key.ENTER && !hasModifiers) { } else if (value && e.key === Key.ENTER && !hasModifiers) {
// when the user hits enter with something in their field try to convert it // when the user hits enter with something in their field try to convert it
e.preventDefault(); e.preventDefault();
this._convertFilter(); this.convertFilter();
} else if (value && e.key === Key.SPACE && !hasModifiers && value.includes("@") && !value.includes(" ")) { } else if (value && e.key === Key.SPACE && !hasModifiers && value.includes("@") && !value.includes(" ")) {
// when the user hits space and their input looks like an e-mail/MXID then try to convert it // when the user hits space and their input looks like an e-mail/MXID then try to convert it
e.preventDefault(); e.preventDefault();
this._convertFilter(); this.convertFilter();
} }
}; };
_updateSuggestions = async (term) => { private updateSuggestions = async (term) => {
MatrixClientPeg.get().searchUserDirectory({term}).then(async r => { MatrixClientPeg.get().searchUserDirectory({term}).then(async r => {
if (term !== this.state.filterText) { if (term !== this.state.filterText) {
// Discard the results - we were probably too slow on the server-side to make // Discard the results - we were probably too slow on the server-side to make
@ -918,30 +922,30 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
} }
}; };
_updateFilter = (e) => { private updateFilter = (e) => {
const term = e.target.value; const term = e.target.value;
this.setState({filterText: term}); this.setState({filterText: term});
// Debounce server lookups to reduce spam. We don't clear the existing server // Debounce server lookups to reduce spam. We don't clear the existing server
// results because they might still be vaguely accurate, likewise for races which // results because they might still be vaguely accurate, likewise for races which
// could happen here. // could happen here.
if (this._debounceTimer) { if (this.debounceTimer) {
clearTimeout(this._debounceTimer); clearTimeout(this.debounceTimer);
} }
this._debounceTimer = setTimeout(() => { this.debounceTimer = setTimeout(() => {
this._updateSuggestions(term); this.updateSuggestions(term);
}, 150); // 150ms debounce (human reaction time + some) }, 150); // 150ms debounce (human reaction time + some)
}; };
_showMoreRecents = () => { private showMoreRecents = () => {
this.setState({numRecentsShown: this.state.numRecentsShown + INCREMENT_ROOMS_SHOWN}); this.setState({numRecentsShown: this.state.numRecentsShown + INCREMENT_ROOMS_SHOWN});
}; };
_showMoreSuggestions = () => { private showMoreSuggestions = () => {
this.setState({numSuggestionsShown: this.state.numSuggestionsShown + INCREMENT_ROOMS_SHOWN}); this.setState({numSuggestionsShown: this.state.numSuggestionsShown + INCREMENT_ROOMS_SHOWN});
}; };
_toggleMember = (member: Member) => { private toggleMember = (member: Member) => {
if (!this.state.busy) { if (!this.state.busy) {
let filterText = this.state.filterText; let filterText = this.state.filterText;
const targets = this.state.targets.map(t => t); // cheap clone for mutation const targets = this.state.targets.map(t => t); // cheap clone for mutation
@ -954,13 +958,13 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
} }
this.setState({targets, filterText}); this.setState({targets, filterText});
if (this._editorRef && this._editorRef.current) { if (this.editorRef && this.editorRef.current) {
this._editorRef.current.focus(); this.editorRef.current.focus();
} }
} }
}; };
_removeMember = (member: Member) => { private removeMember = (member: Member) => {
const targets = this.state.targets.map(t => t); // cheap clone for mutation const targets = this.state.targets.map(t => t); // cheap clone for mutation
const idx = targets.indexOf(member); const idx = targets.indexOf(member);
if (idx >= 0) { if (idx >= 0) {
@ -968,12 +972,12 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
this.setState({targets}); this.setState({targets});
} }
if (this._editorRef && this._editorRef.current) { if (this.editorRef && this.editorRef.current) {
this._editorRef.current.focus(); this.editorRef.current.focus();
} }
}; };
_onPaste = async (e) => { private onPaste = async (e) => {
if (this.state.filterText) { if (this.state.filterText) {
// if the user has already typed something, just let them // if the user has already typed something, just let them
// paste normally. // paste normally.
@ -1027,6 +1031,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
failed.push(address); failed.push(address);
} }
} }
if (this.unmounted) return;
if (failed.length > 0) { if (failed.length > 0) {
const QuestionDialog = sdk.getComponent('dialogs.QuestionDialog'); const QuestionDialog = sdk.getComponent('dialogs.QuestionDialog');
@ -1043,17 +1048,17 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
this.setState({targets: [...this.state.targets, ...toAdd]}); this.setState({targets: [...this.state.targets, ...toAdd]});
}; };
_onClickInputArea = (e) => { private onClickInputArea = (e) => {
// Stop the browser from highlighting text // Stop the browser from highlighting text
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
if (this._editorRef && this._editorRef.current) { if (this.editorRef && this.editorRef.current) {
this._editorRef.current.focus(); this.editorRef.current.focus();
} }
}; };
_onUseDefaultIdentityServerClick = (e) => { private onUseDefaultIdentityServerClick = (e) => {
e.preventDefault(); e.preventDefault();
// Update the IS in account data. Actually using it may trigger terms. // Update the IS in account data. Actually using it may trigger terms.
@ -1062,21 +1067,21 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
this.setState({canUseIdentityServer: true, tryingIdentityServer: false}); this.setState({canUseIdentityServer: true, tryingIdentityServer: false});
}; };
_onManageSettingsClick = (e) => { private onManageSettingsClick = (e) => {
e.preventDefault(); e.preventDefault();
dis.fire(Action.ViewUserSettings); dis.fire(Action.ViewUserSettings);
this.props.onFinished(); this.props.onFinished();
}; };
_onCommunityInviteClick = (e) => { private onCommunityInviteClick = (e) => {
this.props.onFinished(); this.props.onFinished();
showCommunityInviteDialog(CommunityPrototypeStore.instance.getSelectedCommunityId()); showCommunityInviteDialog(CommunityPrototypeStore.instance.getSelectedCommunityId());
}; };
_renderSection(kind: "recents"|"suggestions") { private renderSection(kind: "recents"|"suggestions") {
let sourceMembers = kind === 'recents' ? this.state.recents : this.state.suggestions; let sourceMembers = kind === 'recents' ? this.state.recents : this.state.suggestions;
let showNum = kind === 'recents' ? this.state.numRecentsShown : this.state.numSuggestionsShown; let showNum = kind === 'recents' ? this.state.numRecentsShown : this.state.numSuggestionsShown;
const showMoreFn = kind === 'recents' ? this._showMoreRecents.bind(this) : this._showMoreSuggestions.bind(this); const showMoreFn = kind === 'recents' ? this.showMoreRecents.bind(this) : this.showMoreSuggestions.bind(this);
const lastActive = (m) => kind === 'recents' ? m.lastActive : null; const lastActive = (m) => kind === 'recents' ? m.lastActive : null;
let sectionName = kind === 'recents' ? _t("Recent Conversations") : _t("Suggestions"); let sectionName = kind === 'recents' ? _t("Recent Conversations") : _t("Suggestions");
let sectionSubname = null; let sectionSubname = null;
@ -1156,7 +1161,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
member={r.user} member={r.user}
lastActiveTs={lastActive(r)} lastActiveTs={lastActive(r)}
key={r.userId} key={r.userId}
onToggle={this._toggleMember} onToggle={this.toggleMember}
highlightWord={this.state.filterText} highlightWord={this.state.filterText}
isSelected={this.state.targets.some(t => t.userId === r.userId)} isSelected={this.state.targets.some(t => t.userId === r.userId)}
/> />
@ -1171,32 +1176,32 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
); );
} }
_renderEditor() { private renderEditor() {
const targets = this.state.targets.map(t => ( const targets = this.state.targets.map(t => (
<DMUserTile member={t} onRemove={!this.state.busy && this._removeMember} key={t.userId} /> <DMUserTile member={t} onRemove={!this.state.busy && this.removeMember} key={t.userId} />
)); ));
const input = ( const input = (
<input <input
type="text" type="text"
onKeyDown={this._onKeyDown} onKeyDown={this.onKeyDown}
onChange={this._updateFilter} onChange={this.updateFilter}
value={this.state.filterText} value={this.state.filterText}
ref={this._editorRef} ref={this.editorRef}
onPaste={this._onPaste} onPaste={this.onPaste}
autoFocus={true} autoFocus={true}
disabled={this.state.busy} disabled={this.state.busy}
autoComplete="off" autoComplete="off"
/> />
); );
return ( return (
<div className='mx_InviteDialog_editor' onClick={this._onClickInputArea}> <div className='mx_InviteDialog_editor' onClick={this.onClickInputArea}>
{targets} {targets}
{input} {input}
</div> </div>
); );
} }
_renderIdentityServerWarning() { private renderIdentityServerWarning() {
if (!this.state.tryingIdentityServer || this.state.canUseIdentityServer || if (!this.state.tryingIdentityServer || this.state.canUseIdentityServer ||
!SettingsStore.getValue(UIFeature.IdentityServer) !SettingsStore.getValue(UIFeature.IdentityServer)
) { ) {
@ -1214,8 +1219,8 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
defaultIdentityServerName: abbreviateUrl(defaultIdentityServerUrl), defaultIdentityServerName: abbreviateUrl(defaultIdentityServerUrl),
}, },
{ {
default: sub => <a href="#" onClick={this._onUseDefaultIdentityServerClick}>{sub}</a>, default: sub => <a href="#" onClick={this.onUseDefaultIdentityServerClick}>{sub}</a>,
settings: sub => <a href="#" onClick={this._onManageSettingsClick}>{sub}</a>, settings: sub => <a href="#" onClick={this.onManageSettingsClick}>{sub}</a>,
}, },
)}</div> )}</div>
); );
@ -1225,7 +1230,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
"Use an identity server to invite by email. " + "Use an identity server to invite by email. " +
"Manage in <settings>Settings</settings>.", "Manage in <settings>Settings</settings>.",
{}, { {}, {
settings: sub => <a href="#" onClick={this._onManageSettingsClick}>{sub}</a>, settings: sub => <a href="#" onClick={this.onManageSettingsClick}>{sub}</a>,
}, },
)}</div> )}</div>
); );
@ -1298,7 +1303,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
return ( return (
<AccessibleButton <AccessibleButton
kind="link" kind="link"
onClick={this._onCommunityInviteClick} onClick={this.onCommunityInviteClick}
>{sub}</AccessibleButton> >{sub}</AccessibleButton>
); );
}, },
@ -1309,7 +1314,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
</React.Fragment>; </React.Fragment>;
} }
buttonText = _t("Go"); buttonText = _t("Go");
goButtonFn = this._startDm; goButtonFn = this.startDm;
} else if (this.props.kind === KIND_INVITE) { } else if (this.props.kind === KIND_INVITE) {
const room = MatrixClientPeg.get()?.getRoom(this.props.roomId); const room = MatrixClientPeg.get()?.getRoom(this.props.roomId);
const isSpace = SettingsStore.getValue("feature_spaces") && room?.isSpaceRoom(); const isSpace = SettingsStore.getValue("feature_spaces") && room?.isSpaceRoom();
@ -1348,7 +1353,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
}); });
buttonText = _t("Invite"); buttonText = _t("Invite");
goButtonFn = this._inviteUsers; goButtonFn = this.inviteUsers;
if (cli.isRoomEncrypted(this.props.roomId)) { if (cli.isRoomEncrypted(this.props.roomId)) {
const room = cli.getRoom(this.props.roomId); const room = cli.getRoom(this.props.roomId);
@ -1370,7 +1375,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
} else if (this.props.kind === KIND_CALL_TRANSFER) { } else if (this.props.kind === KIND_CALL_TRANSFER) {
title = _t("Transfer"); title = _t("Transfer");
buttonText = _t("Transfer"); buttonText = _t("Transfer");
goButtonFn = this._transferCall; goButtonFn = this.transferCall;
consultSection = <div> consultSection = <div>
<label> <label>
<input type="checkbox" checked={this.state.consultFirst} onChange={this.onConsultFirstChange} /> <input type="checkbox" checked={this.state.consultFirst} onChange={this.onConsultFirstChange} />
@ -1393,7 +1398,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
<div className='mx_InviteDialog_content'> <div className='mx_InviteDialog_content'>
<p className='mx_InviteDialog_helpText'>{helpText}</p> <p className='mx_InviteDialog_helpText'>{helpText}</p>
<div className='mx_InviteDialog_addressBar'> <div className='mx_InviteDialog_addressBar'>
{this._renderEditor()} {this.renderEditor()}
<div className='mx_InviteDialog_buttonAndSpinner'> <div className='mx_InviteDialog_buttonAndSpinner'>
<AccessibleButton <AccessibleButton
kind="primary" kind="primary"
@ -1407,11 +1412,11 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
</div> </div>
</div> </div>
{keySharingWarning} {keySharingWarning}
{this._renderIdentityServerWarning()} {this.renderIdentityServerWarning()}
<div className='error'>{this.state.errorText}</div> <div className='error'>{this.state.errorText}</div>
<div className='mx_InviteDialog_userSections'> <div className='mx_InviteDialog_userSections'>
{this._renderSection('recents')} {this.renderSection('recents')}
{this._renderSection('suggestions')} {this.renderSection('suggestions')}
</div> </div>
{consultSection} {consultSection}
</div> </div>

View file

@ -55,6 +55,7 @@ interface IProps {
onKeyDown: (ev: React.KeyboardEvent) => void; onKeyDown: (ev: React.KeyboardEvent) => void;
onFocus: (ev: React.FocusEvent) => void; onFocus: (ev: React.FocusEvent) => void;
onBlur: (ev: React.FocusEvent) => void; onBlur: (ev: React.FocusEvent) => void;
onListCollapse?: (isExpanded: boolean) => void;
resizeNotifier: ResizeNotifier; resizeNotifier: ResizeNotifier;
isMinimized: boolean; isMinimized: boolean;
activeSpace: Room; activeSpace: Room;
@ -538,6 +539,7 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
extraTiles={extraTiles} extraTiles={extraTiles}
resizeNotifier={this.props.resizeNotifier} resizeNotifier={this.props.resizeNotifier}
alwaysVisible={ALWAYS_VISIBLE_TAGS.includes(orderedTagId)} alwaysVisible={ALWAYS_VISIBLE_TAGS.includes(orderedTagId)}
onListCollapse={this.props.onListCollapse}
/> />
}); });
} }

View file

@ -78,6 +78,7 @@ interface IProps {
alwaysVisible?: boolean; alwaysVisible?: boolean;
resizeNotifier: ResizeNotifier; resizeNotifier: ResizeNotifier;
extraTiles?: ReactComponentElement<typeof ExtraTile>[]; extraTiles?: ReactComponentElement<typeof ExtraTile>[];
onListCollapse?: (isExpanded: boolean) => void;
// TODO: Account for https://github.com/vector-im/element-web/issues/14179 // TODO: Account for https://github.com/vector-im/element-web/issues/14179
} }
@ -472,6 +473,9 @@ export default class RoomSublist extends React.Component<IProps, IState> {
private toggleCollapsed = () => { private toggleCollapsed = () => {
this.layout.isCollapsed = this.state.isExpanded; this.layout.isCollapsed = this.state.isExpanded;
this.setState({isExpanded: !this.layout.isCollapsed}); this.setState({isExpanded: !this.layout.isCollapsed});
if (this.props.onListCollapse) {
this.props.onListCollapse(!this.layout.isCollapsed)
}
}; };
private onHeaderKeyDown = (ev: React.KeyboardEvent) => { private onHeaderKeyDown = (ev: React.KeyboardEvent) => {

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, {useState} from "react"; import React, { useEffect, useState } from "react";
import classNames from "classnames"; import classNames from "classnames";
import {Room} from "matrix-js-sdk/src/models/room"; import {Room} from "matrix-js-sdk/src/models/room";
@ -127,6 +127,12 @@ const SpacePanel = () => {
const [invites, spaces, activeSpace] = useSpaces(); const [invites, spaces, activeSpace] = useSpaces();
const [isPanelCollapsed, setPanelCollapsed] = useState(true); const [isPanelCollapsed, setPanelCollapsed] = useState(true);
useEffect(() => {
if (!isPanelCollapsed && menuDisplayed) {
closeMenu();
}
}, [isPanelCollapsed]); // eslint-disable-line react-hooks/exhaustive-deps
const newClasses = classNames("mx_SpaceButton_new", { const newClasses = classNames("mx_SpaceButton_new", {
mx_SpaceButton_newCancel: menuDisplayed, mx_SpaceButton_newCancel: menuDisplayed,
}); });
@ -235,18 +241,15 @@ const SpacePanel = () => {
className={newClasses} className={newClasses}
tooltip={menuDisplayed ? _t("Cancel") : _t("Create a space")} tooltip={menuDisplayed ? _t("Cancel") : _t("Create a space")}
onClick={menuDisplayed ? closeMenu : () => { onClick={menuDisplayed ? closeMenu : () => {
openMenu();
if (!isPanelCollapsed) setPanelCollapsed(true); if (!isPanelCollapsed) setPanelCollapsed(true);
openMenu();
}} }}
isNarrow={isPanelCollapsed} isNarrow={isPanelCollapsed}
/> />
</AutoHideScrollbar> </AutoHideScrollbar>
<AccessibleTooltipButton <AccessibleTooltipButton
className={classNames("mx_SpacePanel_toggleCollapse", {expanded: !isPanelCollapsed})} className={classNames("mx_SpacePanel_toggleCollapse", {expanded: !isPanelCollapsed})}
onClick={() => { onClick={() => setPanelCollapsed(!isPanelCollapsed)}
setPanelCollapsed(!isPanelCollapsed);
if (menuDisplayed) closeMenu();
}}
title={expandCollapseButtonTitle} title={expandCollapseButtonTitle}
/> />
{ contextMenu } { contextMenu }

View file

@ -37,6 +37,8 @@
"Call Failed": "Call Failed", "Call Failed": "Call Failed",
"Call Declined": "Call Declined", "Call Declined": "Call Declined",
"The other party declined the call.": "The other party declined the call.", "The other party declined the call.": "The other party declined the call.",
"User Busy": "User Busy",
"The user you called is busy.": "The user you called is busy.",
"The remote side failed to pick up": "The remote side failed to pick up", "The remote side failed to pick up": "The remote side failed to pick up",
"The call could not be established": "The call could not be established", "The call could not be established": "The call could not be established",
"Answered Elsewhere": "Answered Elsewhere", "Answered Elsewhere": "Answered Elsewhere",

View file

@ -24,12 +24,14 @@ export enum UI_EVENTS {
export type ResizeObserverCallbackFunction = (entries: ResizeObserverEntry[]) => void; export type ResizeObserverCallbackFunction = (entries: ResizeObserverEntry[]) => void;
export default class UIStore extends EventEmitter { export default class UIStore extends EventEmitter {
private static _instance: UIStore = null; private static _instance: UIStore = null;
private resizeObserver: ResizeObserver; private resizeObserver: ResizeObserver;
private uiElementDimensions = new Map<string, DOMRectReadOnly>();
private trackedUiElements = new Map<Element, string>();
public windowWidth: number; public windowWidth: number;
public windowHeight: number; public windowHeight: number;
@ -60,14 +62,51 @@ export default class UIStore extends EventEmitter {
} }
} }
private resizeObserverCallback = (entries: ResizeObserverEntry[]) => { public getElementDimensions(name: string): DOMRectReadOnly {
const { width, height } = entries return this.uiElementDimensions.get(name);
.find(entry => entry.target === document.body) }
.contentRect;
this.windowWidth = width; public trackElementDimensions(name: string, element: Element): void {
this.windowHeight = height; this.trackedUiElements.set(element, name);
this.resizeObserver.observe(element);
}
public stopTrackingElementDimensions(name: string): void {
let trackedElement: Element;
this.trackedUiElements.forEach((trackedElementName, element) => {
if (trackedElementName === name) {
trackedElement = element;
}
});
if (trackedElement) {
this.resizeObserver.unobserve(trackedElement);
this.uiElementDimensions.delete(name);
this.trackedUiElements.delete(trackedElement);
}
}
public isTrackingElementDimensions(name: string): boolean {
return this.uiElementDimensions.has(name);
}
private resizeObserverCallback = (entries: ResizeObserverEntry[]) => {
const windowEntry = entries.find(entry => entry.target === document.body);
if (windowEntry) {
this.windowWidth = windowEntry.contentRect.width;
this.windowHeight = windowEntry.contentRect.height;
}
entries.forEach(entry => {
const trackedElementName = this.trackedUiElements.get(entry.target);
if (trackedElementName) {
this.uiElementDimensions.set(trackedElementName, entry.contentRect);
this.emit(trackedElementName, UI_EVENTS.Resize, entry);
}
});
this.emit(UI_EVENTS.Resize, entries); this.emit(UI_EVENTS.Resize, entries);
} }
} }
window.mxUIStore = UIStore.instance;

View file

@ -8438,9 +8438,9 @@ write@1.0.3:
mkdirp "^0.5.1" mkdirp "^0.5.1"
ws@^7.2.3: ws@^7.2.3:
version "7.4.2" version "7.4.6"
resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.2.tgz#782100048e54eb36fe9843363ab1c68672b261dd" resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c"
integrity sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA== integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==
xml-name-validator@^3.0.0: xml-name-validator@^3.0.0:
version "3.0.0" version "3.0.0"