Merge remote-tracking branch 'origin/develop' into hs/bridge-info-v2
This commit is contained in:
commit
d71b2cf04f
58 changed files with 5736 additions and 333 deletions
|
@ -397,7 +397,8 @@ export const toRightOf = (elementRect: DOMRect, chevronOffset = 12) => {
|
|||
return {left, top, chevronOffset};
|
||||
};
|
||||
|
||||
// Placement method for <ContextMenu /> to position context menu right-aligned and flowing to the left of elementRect
|
||||
// Placement method for <ContextMenu /> to position context menu right-aligned and flowing to the left of elementRect,
|
||||
// and either above or below: wherever there is more space (maybe this should be aboveOrBelowLeftOf?)
|
||||
export const aboveLeftOf = (elementRect: DOMRect, chevronFace = ChevronFace.None, vPadding = 0) => {
|
||||
const menuOptions: IPosition & { chevronFace: ChevronFace } = { chevronFace };
|
||||
|
||||
|
@ -416,6 +417,41 @@ export const aboveLeftOf = (elementRect: DOMRect, chevronFace = ChevronFace.None
|
|||
return menuOptions;
|
||||
};
|
||||
|
||||
// Placement method for <ContextMenu /> to position context menu right-aligned and flowing to the left of elementRect
|
||||
// and always above elementRect
|
||||
export const alwaysAboveLeftOf = (elementRect: DOMRect, chevronFace = ChevronFace.None, vPadding = 0) => {
|
||||
const menuOptions: IPosition & { chevronFace: ChevronFace } = { chevronFace };
|
||||
|
||||
const buttonRight = elementRect.right + window.pageXOffset;
|
||||
const buttonBottom = elementRect.bottom + window.pageYOffset;
|
||||
const buttonTop = elementRect.top + window.pageYOffset;
|
||||
// Align the right edge of the menu to the right edge of the button
|
||||
menuOptions.right = window.innerWidth - buttonRight;
|
||||
// Align the menu vertically on whichever side of the button has more space available.
|
||||
if (buttonBottom < window.innerHeight / 2) {
|
||||
menuOptions.top = buttonBottom + vPadding;
|
||||
} else {
|
||||
menuOptions.bottom = (window.innerHeight - buttonTop) + vPadding;
|
||||
}
|
||||
|
||||
return menuOptions;
|
||||
};
|
||||
|
||||
// Placement method for <ContextMenu /> to position context menu right-aligned and flowing to the right of elementRect
|
||||
// and always above elementRect
|
||||
export const alwaysAboveRightOf = (elementRect: DOMRect, chevronFace = ChevronFace.None, vPadding = 0) => {
|
||||
const menuOptions: IPosition & { chevronFace: ChevronFace } = { chevronFace };
|
||||
|
||||
const buttonLeft = elementRect.left + window.pageXOffset;
|
||||
const buttonTop = elementRect.top + window.pageYOffset;
|
||||
// Align the left edge of the menu to the left edge of the button
|
||||
menuOptions.left = buttonLeft;
|
||||
// Align the menu vertically above the menu
|
||||
menuOptions.bottom = (window.innerHeight - buttonTop) + vPadding;
|
||||
|
||||
return menuOptions;
|
||||
};
|
||||
|
||||
type ContextMenuTuple<T> = [boolean, RefObject<T>, () => void, () => void, (val: boolean) => void];
|
||||
export const useContextMenu = <T extends any = HTMLElement>(): ContextMenuTuple<T> => {
|
||||
const button = useRef<T>(null);
|
||||
|
|
|
@ -140,7 +140,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||
protected readonly _matrixClient: MatrixClient;
|
||||
protected readonly _roomView: React.RefObject<any>;
|
||||
protected readonly _resizeContainer: React.RefObject<ResizeHandle>;
|
||||
protected readonly _compactLayoutWatcherRef: string;
|
||||
protected compactLayoutWatcherRef: string;
|
||||
protected resizer: Resizer;
|
||||
|
||||
constructor(props, context) {
|
||||
|
@ -157,18 +157,6 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||
|
||||
CallMediaHandler.loadDevices();
|
||||
|
||||
document.addEventListener('keydown', this._onNativeKeyDown, false);
|
||||
|
||||
this._updateServerNoticeEvents();
|
||||
|
||||
this._matrixClient.on("accountData", this.onAccountData);
|
||||
this._matrixClient.on("sync", this.onSync);
|
||||
this._matrixClient.on("RoomState.events", this.onRoomStateEvents);
|
||||
|
||||
this._compactLayoutWatcherRef = SettingsStore.watchSetting(
|
||||
"useCompactLayout", null, this.onCompactLayoutChanged,
|
||||
);
|
||||
|
||||
fixupColorFonts();
|
||||
|
||||
this._roomView = React.createRef();
|
||||
|
@ -176,6 +164,24 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
document.addEventListener('keydown', this._onNativeKeyDown, false);
|
||||
|
||||
this._updateServerNoticeEvents();
|
||||
|
||||
this._matrixClient.on("accountData", this.onAccountData);
|
||||
this._matrixClient.on("sync", this.onSync);
|
||||
// Call `onSync` with the current state as well
|
||||
this.onSync(
|
||||
this._matrixClient.getSyncState(),
|
||||
null,
|
||||
this._matrixClient.getSyncStateData(),
|
||||
);
|
||||
this._matrixClient.on("RoomState.events", this.onRoomStateEvents);
|
||||
|
||||
this.compactLayoutWatcherRef = SettingsStore.watchSetting(
|
||||
"useCompactLayout", null, this.onCompactLayoutChanged,
|
||||
);
|
||||
|
||||
this.resizer = this._createResizer();
|
||||
this.resizer.attach();
|
||||
this._loadResizerPreferences();
|
||||
|
@ -186,7 +192,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||
this._matrixClient.removeListener("accountData", this.onAccountData);
|
||||
this._matrixClient.removeListener("sync", this.onSync);
|
||||
this._matrixClient.removeListener("RoomState.events", this.onRoomStateEvents);
|
||||
SettingsStore.unwatchSetting(this._compactLayoutWatcherRef);
|
||||
SettingsStore.unwatchSetting(this.compactLayoutWatcherRef);
|
||||
this.resizer.detach();
|
||||
}
|
||||
|
||||
|
|
|
@ -72,9 +72,12 @@ export default class SoftLogout extends React.Component {
|
|||
|
||||
this._initLogin();
|
||||
|
||||
MatrixClientPeg.get().countSessionsNeedingBackup().then(remaining => {
|
||||
this.setState({keyBackupNeeded: remaining > 0});
|
||||
});
|
||||
const cli = MatrixClientPeg.get();
|
||||
if (cli.isCryptoEnabled()) {
|
||||
cli.countSessionsNeedingBackup().then(remaining => {
|
||||
this.setState({ keyBackupNeeded: remaining > 0 });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onClearAll = () => {
|
||||
|
|
59
src/components/views/context_menus/DialpadContextMenu.tsx
Normal file
59
src/components/views/context_menus/DialpadContextMenu.tsx
Normal file
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { ContextMenu, IProps as IContextMenuProps } from '../../structures/ContextMenu';
|
||||
import { MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
|
||||
import Dialpad from '../voip/DialPad';
|
||||
|
||||
interface IProps extends IContextMenuProps {
|
||||
call: MatrixCall;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
value: string;
|
||||
}
|
||||
|
||||
export default class DialpadContextMenu extends React.Component<IProps, IState> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
value: '',
|
||||
}
|
||||
}
|
||||
|
||||
onDigitPress = (digit) => {
|
||||
this.props.call.sendDtmfDigit(digit);
|
||||
this.setState({value: this.state.value + digit});
|
||||
}
|
||||
|
||||
render() {
|
||||
return <ContextMenu {...this.props}>
|
||||
<div className="mx_DialPadContextMenu_header">
|
||||
<div>
|
||||
<span className="mx_DialPadContextMenu_title">{_t("Dial pad")}</span>
|
||||
</div>
|
||||
<div className="mx_DialPadContextMenu_dialled">{this.state.value}</div>
|
||||
</div>
|
||||
<div className="mx_DialPadContextMenu_horizSep" />
|
||||
<div className="mx_DialPadContextMenu_dialPad">
|
||||
<Dialpad onDigitPress={this.onDigitPress} hasDialAndDelete={false} />
|
||||
</div>
|
||||
</ContextMenu>;
|
||||
}
|
||||
}
|
|
@ -26,7 +26,6 @@ import {WidgetMessagingStore} from "../../../stores/widgets/WidgetMessagingStore
|
|||
import RoomContext from "../../../contexts/RoomContext";
|
||||
import dis from "../../../dispatcher/dispatcher";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import {SettingLevel} from "../../../settings/SettingLevel";
|
||||
import Modal from "../../../Modal";
|
||||
import QuestionDialog from "../dialogs/QuestionDialog";
|
||||
import {WidgetType} from "../../../widgets/WidgetType";
|
||||
|
@ -127,7 +126,8 @@ const WidgetContextMenu: React.FC<IProps> = ({
|
|||
console.info("Revoking permission for widget to load: " + app.eventId);
|
||||
const current = SettingsStore.getValue("allowedWidgets", roomId);
|
||||
current[app.eventId] = false;
|
||||
SettingsStore.setValue("allowedWidgets", roomId, SettingLevel.ROOM_ACCOUNT, current).catch(err => {
|
||||
const level = SettingsStore.firstSupportedLevel("allowedWidgets");
|
||||
SettingsStore.setValue("allowedWidgets", roomId, level, current).catch(err => {
|
||||
console.error(err);
|
||||
// We don't really need to do anything about this - the user will just hit the button again.
|
||||
});
|
||||
|
|
|
@ -32,6 +32,8 @@ import {
|
|||
PHASE_STARTED,
|
||||
PHASE_CANCELLED,
|
||||
} from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
|
||||
import WidgetStore from "../../../stores/WidgetStore";
|
||||
import {UPDATE_EVENT} from "../../../stores/AsyncStore";
|
||||
|
||||
class GenericEditor extends React.PureComponent {
|
||||
// static propTypes = {onBack: PropTypes.func.isRequired};
|
||||
|
@ -701,6 +703,97 @@ class VerificationExplorer extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
class WidgetExplorer extends React.Component {
|
||||
static getLabel() {
|
||||
return _t("Active Widgets");
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
query: '',
|
||||
editWidget: null, // set to an IApp when editing
|
||||
};
|
||||
}
|
||||
|
||||
onWidgetStoreUpdate = () => {
|
||||
this.forceUpdate();
|
||||
};
|
||||
|
||||
onQueryChange = (query) => {
|
||||
this.setState({query});
|
||||
};
|
||||
|
||||
onEditWidget = (widget) => {
|
||||
this.setState({editWidget: widget});
|
||||
};
|
||||
|
||||
onBack = () => {
|
||||
const widgets = WidgetStore.instance.getApps(this.props.room.roomId);
|
||||
if (this.state.editWidget && widgets.includes(this.state.editWidget)) {
|
||||
this.setState({editWidget: null});
|
||||
} else {
|
||||
this.props.onBack();
|
||||
}
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
WidgetStore.instance.on(UPDATE_EVENT, this.onWidgetStoreUpdate);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
WidgetStore.instance.off(UPDATE_EVENT, this.onWidgetStoreUpdate);
|
||||
}
|
||||
|
||||
render() {
|
||||
const room = this.props.room;
|
||||
|
||||
const editWidget = this.state.editWidget;
|
||||
const widgets = WidgetStore.instance.getApps(room.roomId);
|
||||
if (editWidget && widgets.includes(editWidget)) {
|
||||
const allState = Array.from(Array.from(room.currentState.events.values()).map(e => e.values()))
|
||||
.reduce((p, c) => {p.push(...c); return p;}, []);
|
||||
const stateEv = allState.find(ev => ev.getId() === editWidget.eventId);
|
||||
if (!stateEv) { // "should never happen"
|
||||
return <div>
|
||||
{_t("There was an error finding this widget.")}
|
||||
<div className="mx_Dialog_buttons">
|
||||
<button onClick={this.onBack}>{_t("Back")}</button>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
return <SendCustomEvent
|
||||
onBack={this.onBack}
|
||||
room={room}
|
||||
forceStateEvent={true}
|
||||
inputs={{
|
||||
eventType: stateEv.getType(),
|
||||
evContent: JSON.stringify(stateEv.getContent(), null, '\t'),
|
||||
stateKey: stateEv.getStateKey(),
|
||||
}}
|
||||
/>;
|
||||
}
|
||||
|
||||
return (<div>
|
||||
<div className="mx_Dialog_content">
|
||||
<FilteredList query={this.state.query} onChange={this.onQueryChange}>
|
||||
{widgets.map(w => {
|
||||
return <button
|
||||
className='mx_DevTools_RoomStateExplorer_button'
|
||||
key={w.url + w.eventId}
|
||||
onClick={() => this.onEditWidget(w)}
|
||||
>{w.url}</button>;
|
||||
})}
|
||||
</FilteredList>
|
||||
</div>
|
||||
<div className="mx_Dialog_buttons">
|
||||
<button onClick={this.onBack}>{_t("Back")}</button>
|
||||
</div>
|
||||
</div>);
|
||||
}
|
||||
}
|
||||
|
||||
const Entries = [
|
||||
SendCustomEvent,
|
||||
RoomStateExplorer,
|
||||
|
@ -708,6 +801,7 @@ const Entries = [
|
|||
AccountDataExplorer,
|
||||
ServersInRoomList,
|
||||
VerificationExplorer,
|
||||
WidgetExplorer,
|
||||
];
|
||||
|
||||
export default class DevtoolsDialog extends React.PureComponent {
|
||||
|
|
|
@ -298,7 +298,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent {
|
|||
<div className="mx_AccessSecretStorageDialog_recoveryKeyEntry">
|
||||
<div className="mx_AccessSecretStorageDialog_recoveryKeyEntry_textInput">
|
||||
<Field
|
||||
type="text"
|
||||
type="password"
|
||||
label={_t('Security Key')}
|
||||
value={this.state.recoveryKey}
|
||||
onChange={this._onRecoveryKeyChange}
|
||||
|
|
|
@ -33,7 +33,6 @@ import SettingsStore from "../../../settings/SettingsStore";
|
|||
import {aboveLeftOf, ContextMenuButton} from "../../structures/ContextMenu";
|
||||
import PersistedElement, {getPersistKey} from "./PersistedElement";
|
||||
import {WidgetType} from "../../../widgets/WidgetType";
|
||||
import {SettingLevel} from "../../../settings/SettingLevel";
|
||||
import {StopGapWidget} from "../../../stores/widgets/StopGapWidget";
|
||||
import {ElementWidgetActions} from "../../../stores/widgets/ElementWidgetActions";
|
||||
import {MatrixCapabilities} from "matrix-widget-api";
|
||||
|
@ -240,7 +239,8 @@ export default class AppTile extends React.Component {
|
|||
console.info("Granting permission for widget to load: " + this.props.app.eventId);
|
||||
const current = SettingsStore.getValue("allowedWidgets", roomId);
|
||||
current[this.props.app.eventId] = true;
|
||||
SettingsStore.setValue("allowedWidgets", roomId, SettingLevel.ROOM_ACCOUNT, current).then(() => {
|
||||
const level = SettingsStore.firstSupportedLevel("allowedWidgets");
|
||||
SettingsStore.setValue("allowedWidgets", roomId, level, current).then(() => {
|
||||
this.setState({hasPermissionToLoad: true});
|
||||
|
||||
// Fetch a token for the integration manager, now that we're allowed to
|
||||
|
|
|
@ -15,14 +15,15 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React, {useContext, useRef, useState} from 'react';
|
||||
import {EventType} from 'matrix-js-sdk/src/@types/event';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import AccessibleButton from "./AccessibleButton";
|
||||
import Tooltip from './Tooltip';
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import {useTimeout} from "../../../hooks/useTimeout";
|
||||
import Analytics from "../../../Analytics";
|
||||
import CountlyAnalytics from '../../../CountlyAnalytics';
|
||||
import RoomContext from "../../../contexts/RoomContext";
|
||||
|
||||
export const AVATAR_SIZE = 52;
|
||||
|
||||
|
@ -50,6 +51,11 @@ const MiniAvatarUploader: React.FC<IProps> = ({ hasAvatar, hasAvatarLabel, noAva
|
|||
|
||||
const label = (hasAvatar || busy) ? hasAvatarLabel : noAvatarLabel;
|
||||
|
||||
const {room} = useContext(RoomContext);
|
||||
const canSetAvatar = room?.currentState.maySendStateEvent(EventType.RoomAvatar, cli.getUserId());
|
||||
if (!canSetAvatar) return <React.Fragment>{ children }</React.Fragment>;
|
||||
|
||||
const visible = !!label && (hover || show);
|
||||
return <React.Fragment>
|
||||
<input
|
||||
type="file"
|
||||
|
@ -82,11 +88,13 @@ const MiniAvatarUploader: React.FC<IProps> = ({ hasAvatar, hasAvatarLabel, noAva
|
|||
>
|
||||
{ children }
|
||||
|
||||
<Tooltip
|
||||
label={label}
|
||||
visible={!!label && (hover || show)}
|
||||
forceOnRight
|
||||
/>
|
||||
<div className={classNames("mx_Tooltip", {
|
||||
"mx_Tooltip_visible": visible,
|
||||
"mx_Tooltip_invisible": !visible,
|
||||
})}>
|
||||
<div className="mx_Tooltip_chevron" />
|
||||
{ label }
|
||||
</div>
|
||||
</AccessibleButton>
|
||||
</React.Fragment>;
|
||||
};
|
||||
|
|
|
@ -410,7 +410,10 @@ export default class TextualBody extends React.Component {
|
|||
ref: this._content,
|
||||
});
|
||||
if (this.props.replacingEventId) {
|
||||
body = [body, this._renderEditedMarker()];
|
||||
body = <>
|
||||
{body}
|
||||
{this._renderEditedMarker()}
|
||||
</>;
|
||||
}
|
||||
|
||||
if (this.props.highlightLink) {
|
||||
|
|
|
@ -29,7 +29,7 @@ import ActiveRoomObserver from "../../../ActiveRoomObserver";
|
|||
import { _t } from "../../../languageHandler";
|
||||
import { ChevronFace, ContextMenuTooltipButton } from "../../structures/ContextMenu";
|
||||
import { DefaultTagID, TagID } from "../../../stores/room-list/models";
|
||||
import { MessagePreviewStore, ROOM_PREVIEW_CHANGED } from "../../../stores/room-list/MessagePreviewStore";
|
||||
import { MessagePreviewStore } from "../../../stores/room-list/MessagePreviewStore";
|
||||
import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
|
||||
import { ALL_MESSAGES, ALL_MESSAGES_LOUD, MENTIONS_ONLY, MUTE } from "../../../RoomNotifs";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
|
@ -51,7 +51,6 @@ import IconizedContextMenu, {
|
|||
IconizedContextMenuRadio,
|
||||
} from "../context_menus/IconizedContextMenu";
|
||||
import { CommunityPrototypeStore, IRoomProfile } from "../../../stores/CommunityPrototypeStore";
|
||||
import { UPDATE_EVENT } from "../../../stores/AsyncStore";
|
||||
|
||||
interface IProps {
|
||||
room: Room;
|
||||
|
@ -99,12 +98,18 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
|
|||
|
||||
ActiveRoomObserver.addListener(this.props.room.roomId, this.onActiveRoomUpdate);
|
||||
this.dispatcherRef = defaultDispatcher.register(this.onAction);
|
||||
MessagePreviewStore.instance.on(ROOM_PREVIEW_CHANGED, this.onRoomPreviewChanged);
|
||||
MessagePreviewStore.instance.on(
|
||||
MessagePreviewStore.getPreviewChangedEventName(this.props.room),
|
||||
this.onRoomPreviewChanged,
|
||||
);
|
||||
this.notificationState = RoomNotificationStateStore.instance.getRoomState(this.props.room);
|
||||
this.notificationState.on(NOTIFICATION_STATE_UPDATE, this.onNotificationUpdate);
|
||||
this.roomProps = EchoChamber.forRoom(this.props.room);
|
||||
this.roomProps.on(PROPERTY_UPDATED, this.onRoomPropertyUpdate);
|
||||
CommunityPrototypeStore.instance.on(UPDATE_EVENT, this.onCommunityUpdate);
|
||||
CommunityPrototypeStore.instance.on(
|
||||
CommunityPrototypeStore.getUpdateEventName(this.props.room.roomId),
|
||||
this.onCommunityUpdate,
|
||||
);
|
||||
}
|
||||
|
||||
private onNotificationUpdate = () => {
|
||||
|
@ -128,6 +133,24 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
|
|||
if (prevProps.showMessagePreview !== this.props.showMessagePreview && this.showMessagePreview) {
|
||||
this.setState({messagePreview: this.generatePreview()});
|
||||
}
|
||||
if (prevProps.room?.roomId !== this.props.room?.roomId) {
|
||||
MessagePreviewStore.instance.off(
|
||||
MessagePreviewStore.getPreviewChangedEventName(prevProps.room),
|
||||
this.onRoomPreviewChanged,
|
||||
);
|
||||
MessagePreviewStore.instance.on(
|
||||
MessagePreviewStore.getPreviewChangedEventName(this.props.room),
|
||||
this.onRoomPreviewChanged,
|
||||
);
|
||||
CommunityPrototypeStore.instance.off(
|
||||
CommunityPrototypeStore.getUpdateEventName(prevProps.room?.roomId),
|
||||
this.onCommunityUpdate,
|
||||
);
|
||||
CommunityPrototypeStore.instance.on(
|
||||
CommunityPrototypeStore.getUpdateEventName(this.props.room?.roomId),
|
||||
this.onCommunityUpdate,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
|
@ -140,11 +163,17 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
|
|||
public componentWillUnmount() {
|
||||
if (this.props.room) {
|
||||
ActiveRoomObserver.removeListener(this.props.room.roomId, this.onActiveRoomUpdate);
|
||||
MessagePreviewStore.instance.off(
|
||||
MessagePreviewStore.getPreviewChangedEventName(this.props.room),
|
||||
this.onRoomPreviewChanged,
|
||||
);
|
||||
CommunityPrototypeStore.instance.off(
|
||||
CommunityPrototypeStore.getUpdateEventName(this.props.room.roomId),
|
||||
this.onCommunityUpdate,
|
||||
);
|
||||
}
|
||||
defaultDispatcher.unregister(this.dispatcherRef);
|
||||
MessagePreviewStore.instance.off(ROOM_PREVIEW_CHANGED, this.onRoomPreviewChanged);
|
||||
this.notificationState.off(NOTIFICATION_STATE_UPDATE, this.onNotificationUpdate);
|
||||
CommunityPrototypeStore.instance.off(UPDATE_EVENT, this.onCommunityUpdate);
|
||||
}
|
||||
|
||||
private onAction = (payload: ActionPayload) => {
|
||||
|
|
|
@ -27,9 +27,10 @@ import { CallEvent } from 'matrix-js-sdk/src/webrtc/call';
|
|||
import classNames from 'classnames';
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import {isOnlyCtrlOrCmdKeyEvent, Key} from '../../../Keyboard';
|
||||
import {aboveLeftOf, ChevronFace, ContextMenuButton} from '../../structures/ContextMenu';
|
||||
import {alwaysAboveLeftOf, alwaysAboveRightOf, ChevronFace, ContextMenuButton} from '../../structures/ContextMenu';
|
||||
import CallContextMenu from '../context_menus/CallContextMenu';
|
||||
import { avatarUrlForMember } from '../../../Avatar';
|
||||
import DialpadContextMenu from '../context_menus/DialpadContextMenu';
|
||||
|
||||
interface IProps {
|
||||
// The call for us to display
|
||||
|
@ -60,6 +61,7 @@ interface IState {
|
|||
callState: CallState,
|
||||
controlsVisible: boolean,
|
||||
showMoreMenu: boolean,
|
||||
showDialpad: boolean,
|
||||
}
|
||||
|
||||
function getFullScreenElement() {
|
||||
|
@ -102,6 +104,7 @@ export default class CallView extends React.Component<IProps, IState> {
|
|||
private dispatcherRef: string;
|
||||
private contentRef = createRef<HTMLDivElement>();
|
||||
private controlsHideTimer: number = null;
|
||||
private dialpadButton = createRef<HTMLDivElement>();
|
||||
private contextMenuButton = createRef<HTMLDivElement>();
|
||||
|
||||
constructor(props: IProps) {
|
||||
|
@ -115,6 +118,7 @@ export default class CallView extends React.Component<IProps, IState> {
|
|||
callState: this.props.call.state,
|
||||
controlsVisible: true,
|
||||
showMoreMenu: false,
|
||||
showDialpad: false,
|
||||
}
|
||||
|
||||
this.updateCallListeners(null, this.props.call);
|
||||
|
@ -226,7 +230,7 @@ export default class CallView extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
private showControls() {
|
||||
if (this.state.showMoreMenu) return;
|
||||
if (this.state.showMoreMenu || this.state.showDialpad) return;
|
||||
|
||||
if (!this.state.controlsVisible) {
|
||||
this.setState({
|
||||
|
@ -239,6 +243,29 @@ export default class CallView extends React.Component<IProps, IState> {
|
|||
this.controlsHideTimer = window.setTimeout(this.onControlsHideTimer, CONTROLS_HIDE_DELAY);
|
||||
}
|
||||
|
||||
private onDialpadClick = () => {
|
||||
if (!this.state.showDialpad) {
|
||||
if (this.controlsHideTimer) {
|
||||
clearTimeout(this.controlsHideTimer);
|
||||
this.controlsHideTimer = null;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
showDialpad: true,
|
||||
controlsVisible: true,
|
||||
});
|
||||
} else {
|
||||
if (this.controlsHideTimer !== null) {
|
||||
clearTimeout(this.controlsHideTimer);
|
||||
}
|
||||
this.controlsHideTimer = window.setTimeout(this.onControlsHideTimer, CONTROLS_HIDE_DELAY);
|
||||
|
||||
this.setState({
|
||||
showDialpad: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private onMicMuteClick = () => {
|
||||
const newVal = !this.state.micMuted;
|
||||
|
||||
|
@ -265,6 +292,13 @@ export default class CallView extends React.Component<IProps, IState> {
|
|||
});
|
||||
}
|
||||
|
||||
private closeDialpad = () => {
|
||||
this.setState({
|
||||
showDialpad: false,
|
||||
});
|
||||
this.controlsHideTimer = window.setTimeout(this.onControlsHideTimer, CONTROLS_HIDE_DELAY);
|
||||
}
|
||||
|
||||
private closeContextMenu = () => {
|
||||
this.setState({
|
||||
showMoreMenu: false,
|
||||
|
@ -323,20 +357,29 @@ export default class CallView extends React.Component<IProps, IState> {
|
|||
CallHandler.sharedInstance().setActiveCallRoomId(this.props.call.roomId);
|
||||
}
|
||||
|
||||
private onSecondaryCallResumeClick = () => {
|
||||
CallHandler.sharedInstance().setActiveCallRoomId(this.props.secondaryCall.roomId);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const client = MatrixClientPeg.get();
|
||||
const callRoom = client.getRoom(this.props.call.roomId);
|
||||
const secCallRoom = this.props.secondaryCall ? client.getRoom(this.props.secondaryCall.roomId) : null;
|
||||
|
||||
let dialPad;
|
||||
let contextMenu;
|
||||
|
||||
if (this.state.showDialpad) {
|
||||
dialPad = <DialpadContextMenu
|
||||
{...alwaysAboveRightOf(
|
||||
this.dialpadButton.current.getBoundingClientRect(),
|
||||
ChevronFace.None,
|
||||
CONTEXT_MENU_VPADDING,
|
||||
)}
|
||||
onFinished={this.closeDialpad}
|
||||
call={this.props.call}
|
||||
/>;
|
||||
}
|
||||
|
||||
if (this.state.showMoreMenu) {
|
||||
contextMenu = <CallContextMenu
|
||||
{...aboveLeftOf(
|
||||
{...alwaysAboveLeftOf(
|
||||
this.contextMenuButton.current.getBoundingClientRect(),
|
||||
ChevronFace.None,
|
||||
CONTEXT_MENU_VPADDING,
|
||||
|
@ -384,8 +427,15 @@ export default class CallView extends React.Component<IProps, IState> {
|
|||
onClick={this.onVidMuteClick}
|
||||
/> : null;
|
||||
|
||||
// The 'more' button actions are only relevant in a connected call
|
||||
// The dial pad & 'more' button actions are only relevant in a connected call
|
||||
// When not connected, we have to put something there to make the flexbox alignment correct
|
||||
const dialpadButton = this.state.callState === CallState.Connected ? <ContextMenuButton
|
||||
className="mx_CallView_callControls_button mx_CallView_callControls_dialpad"
|
||||
inputRef={this.dialpadButton}
|
||||
onClick={this.onDialpadClick}
|
||||
isExpanded={this.state.showDialpad}
|
||||
/> : <div className="mx_CallView_callControls_button mx_CallView_callControls_button_dialpad_hidden" />;
|
||||
|
||||
const contextMenuButton = this.state.callState === CallState.Connected ? <ContextMenuButton
|
||||
className="mx_CallView_callControls_button mx_CallView_callControls_button_more"
|
||||
onClick={this.onMoreClick}
|
||||
|
@ -396,7 +446,7 @@ export default class CallView extends React.Component<IProps, IState> {
|
|||
// in the near future, the dial pad button will go on the left. For now, it's the nothing button
|
||||
// because something needs to have margin-right: auto to make the alignment correct.
|
||||
const callControls = <div className={callControlsClasses}>
|
||||
<div className="mx_CallView_callControls_button mx_CallView_callControls_nothing" />
|
||||
{dialpadButton}
|
||||
<AccessibleButton
|
||||
className={micClasses}
|
||||
onClick={this.onMicMuteClick}
|
||||
|
@ -558,6 +608,7 @@ export default class CallView extends React.Component<IProps, IState> {
|
|||
return <div className={"mx_CallView " + myClassName}>
|
||||
{header}
|
||||
{contentView}
|
||||
{dialPad}
|
||||
{contextMenu}
|
||||
</div>;
|
||||
}
|
||||
|
|
|
@ -54,8 +54,9 @@ class DialPadButton extends React.PureComponent<IButtonProps> {
|
|||
|
||||
interface IProps {
|
||||
onDigitPress: (string) => void;
|
||||
onDeletePress: (string) => void;
|
||||
onDialPress: (string) => void;
|
||||
hasDialAndDelete: boolean;
|
||||
onDeletePress?: (string) => void;
|
||||
onDialPress?: (string) => void;
|
||||
}
|
||||
|
||||
export default class Dialpad extends React.PureComponent<IProps> {
|
||||
|
@ -68,12 +69,14 @@ export default class Dialpad extends React.PureComponent<IProps> {
|
|||
/>);
|
||||
}
|
||||
|
||||
buttonNodes.push(<DialPadButton key="del" kind={DialPadButtonKind.Delete}
|
||||
onButtonPress={this.props.onDeletePress}
|
||||
/>);
|
||||
buttonNodes.push(<DialPadButton key="dial" kind={DialPadButtonKind.Dial}
|
||||
onButtonPress={this.props.onDialPress}
|
||||
/>);
|
||||
if (this.props.hasDialAndDelete) {
|
||||
buttonNodes.push(<DialPadButton key="del" kind={DialPadButtonKind.Delete}
|
||||
onButtonPress={this.props.onDeletePress}
|
||||
/>);
|
||||
buttonNodes.push(<DialPadButton key="dial" kind={DialPadButtonKind.Dial}
|
||||
onButtonPress={this.props.onDialPress}
|
||||
/>);
|
||||
}
|
||||
|
||||
return <div className="mx_DialPad">
|
||||
{buttonNodes}
|
||||
|
|
|
@ -101,7 +101,8 @@ export default class DialpadModal extends React.PureComponent<IProps, IState> {
|
|||
</div>
|
||||
<div className="mx_DialPadModal_horizSep" />
|
||||
<div className="mx_DialPadModal_dialPad">
|
||||
<DialPad onDigitPress={this.onDigitPress}
|
||||
<DialPad hasDialAndDelete={true}
|
||||
onDigitPress={this.onDigitPress}
|
||||
onDeletePress={this.onDeletePress}
|
||||
onDialPress={this.onDialPress}
|
||||
/>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue