Merge pull request #5523 from matrix-org/dbkr/dial_pad
Add a dialpad UI for PSTN lookup
This commit is contained in:
commit
2d37151750
13 changed files with 461 additions and 6 deletions
|
@ -236,4 +236,6 @@
|
||||||
@import "./views/verification/_VerificationShowSas.scss";
|
@import "./views/verification/_VerificationShowSas.scss";
|
||||||
@import "./views/voip/_CallContainer.scss";
|
@import "./views/voip/_CallContainer.scss";
|
||||||
@import "./views/voip/_CallView.scss";
|
@import "./views/voip/_CallView.scss";
|
||||||
|
@import "./views/voip/_DialPad.scss";
|
||||||
|
@import "./views/voip/_DialPadModal.scss";
|
||||||
@import "./views/voip/_VideoFeed.scss";
|
@import "./views/voip/_VideoFeed.scss";
|
||||||
|
|
|
@ -24,6 +24,9 @@ limitations under the License.
|
||||||
.mx_RoomList_iconExplore::before {
|
.mx_RoomList_iconExplore::before {
|
||||||
mask-image: url('$(res)/img/element-icons/roomlist/explore.svg');
|
mask-image: url('$(res)/img/element-icons/roomlist/explore.svg');
|
||||||
}
|
}
|
||||||
|
.mx_RoomList_iconDialpad::before {
|
||||||
|
mask-image: url('$(res)/img/element-icons/roomlist/dialpad.svg');
|
||||||
|
}
|
||||||
|
|
||||||
.mx_RoomList_explorePrompt {
|
.mx_RoomList_explorePrompt {
|
||||||
margin: 4px 12px 4px;
|
margin: 4px 12px 4px;
|
||||||
|
|
62
res/css/views/voip/_DialPad.scss
Normal file
62
res/css/views/voip/_DialPad.scss
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.mx_DialPad {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_DialPad_button {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
background-color: $theme-button-bg-color;
|
||||||
|
border-radius: 40px;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
line-height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_DialPad_deleteButton, .mx_DialPad_dialButton {
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
height: 40px;
|
||||||
|
width: 40px;
|
||||||
|
vertical-align: middle;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-size: 20px;
|
||||||
|
mask-position: center;
|
||||||
|
background-color: $primary-bg-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_DialPad_deleteButton {
|
||||||
|
background-color: $notice-primary-color;
|
||||||
|
&::before {
|
||||||
|
mask-image: url('$(res)/img/element-icons/call/delete.svg');
|
||||||
|
mask-position: 9px; // delete icon is right-heavy so have to be slightly to the left to look centered
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_DialPad_dialButton {
|
||||||
|
background-color: $accent-color;
|
||||||
|
&::before {
|
||||||
|
mask-image: url('$(res)/img/element-icons/call/voice-call.svg');
|
||||||
|
}
|
||||||
|
}
|
74
res/css/views/voip/_DialPadModal.scss
Normal file
74
res/css/views/voip/_DialPadModal.scss
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.mx_Dialog_dialPadWrapper .mx_Dialog {
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_DialPadModal {
|
||||||
|
width: 192px;
|
||||||
|
height: 368px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_DialPadModal_header {
|
||||||
|
margin-top: 12px;
|
||||||
|
margin-left: 12px;
|
||||||
|
margin-right: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_DialPadModal_title {
|
||||||
|
color: $muted-fg-color;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_DialPadModal_cancel {
|
||||||
|
float: right;
|
||||||
|
mask: url('$(res)/img/feather-customised/cancel.svg');
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-position: center;
|
||||||
|
mask-size: cover;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
background-color: $dialog-close-fg-color;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_DialPadModal_field {
|
||||||
|
border: none;
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_DialPadModal_field input {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_DialPadModal_dialPad {
|
||||||
|
margin-left: 16px;
|
||||||
|
margin-right: 16px;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_DialPadModal_horizSep {
|
||||||
|
position: relative;
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
border-bottom: 1px solid $input-darker-bg-color;
|
||||||
|
}
|
||||||
|
}
|
10
res/img/element-icons/call/delete.svg
Normal file
10
res/img/element-icons/call/delete.svg
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0)">
|
||||||
|
<path d="M18.3333 2.49951H5.83333C5.25833 2.49951 4.80833 2.79118 4.50833 3.23285L0 9.99951L4.50833 16.7578C4.80833 17.1995 5.25833 17.4995 5.83333 17.4995H18.3333C19.25 17.4995 20 16.7495 20 15.8328V4.16618C20 3.24951 19.25 2.49951 18.3333 2.49951ZM15.8333 12.9912L14.6583 14.1662L11.6667 11.1745L8.675 14.1662L7.5 12.9912L10.4917 9.99951L7.5 7.00784L8.675 5.83284L11.6667 8.82451L14.6583 5.83284L15.8333 7.00784L12.8417 9.99951L15.8333 12.9912Z" fill="white"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0">
|
||||||
|
<rect width="20" height="20" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 692 B |
3
res/img/element-icons/roomlist/dialpad.svg
Normal file
3
res/img/element-icons/roomlist/dialpad.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M9.99999 15.8335C9.08333 15.8335 8.33333 16.5835 8.33333 17.5002C8.33333 18.4168 9.08333 19.1668 9.99999 19.1668C10.9167 19.1668 11.6667 18.4168 11.6667 17.5002C11.6667 16.5835 10.9167 15.8335 9.99999 15.8335ZM4.99999 0.833496C4.08333 0.833496 3.33333 1.5835 3.33333 2.50016C3.33333 3.41683 4.08333 4.16683 4.99999 4.16683C5.91666 4.16683 6.66666 3.41683 6.66666 2.50016C6.66666 1.5835 5.91666 0.833496 4.99999 0.833496ZM4.99999 5.8335C4.08333 5.8335 3.33333 6.5835 3.33333 7.50016C3.33333 8.41683 4.08333 9.16683 4.99999 9.16683C5.91666 9.16683 6.66666 8.41683 6.66666 7.50016C6.66666 6.5835 5.91666 5.8335 4.99999 5.8335ZM4.99999 10.8335C4.08333 10.8335 3.33333 11.5835 3.33333 12.5002C3.33333 13.4168 4.08333 14.1668 4.99999 14.1668C5.91666 14.1668 6.66666 13.4168 6.66666 12.5002C6.66666 11.5835 5.91666 10.8335 4.99999 10.8335ZM15 4.16683C15.9167 4.16683 16.6667 3.41683 16.6667 2.50016C16.6667 1.5835 15.9167 0.833496 15 0.833496C14.0833 0.833496 13.3333 1.5835 13.3333 2.50016C13.3333 3.41683 14.0833 4.16683 15 4.16683ZM9.99999 10.8335C9.08333 10.8335 8.33333 11.5835 8.33333 12.5002C8.33333 13.4168 9.08333 14.1668 9.99999 14.1668C10.9167 14.1668 11.6667 13.4168 11.6667 12.5002C11.6667 11.5835 10.9167 10.8335 9.99999 10.8335ZM15 10.8335C14.0833 10.8335 13.3333 11.5835 13.3333 12.5002C13.3333 13.4168 14.0833 14.1668 15 14.1668C15.9167 14.1668 16.6667 13.4168 16.6667 12.5002C16.6667 11.5835 15.9167 10.8335 15 10.8335ZM15 5.8335C14.0833 5.8335 13.3333 6.5835 13.3333 7.50016C13.3333 8.41683 14.0833 9.16683 15 9.16683C15.9167 9.16683 16.6667 8.41683 16.6667 7.50016C16.6667 6.5835 15.9167 5.8335 15 5.8335ZM9.99999 5.8335C9.08333 5.8335 8.33333 6.5835 8.33333 7.50016C8.33333 8.41683 9.08333 9.16683 9.99999 9.16683C10.9167 9.16683 11.6667 8.41683 11.6667 7.50016C11.6667 6.5835 10.9167 5.8335 9.99999 5.8335ZM9.99999 0.833496C9.08333 0.833496 8.33333 1.5835 8.33333 2.50016C8.33333 3.41683 9.08333 4.16683 9.99999 4.16683C10.9167 4.16683 11.6667 3.41683 11.6667 2.50016C11.6667 1.5835 10.9167 0.833496 9.99999 0.833496Z" fill="#8D99A5"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.1 KiB |
|
@ -82,6 +82,9 @@ 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 { Action } from './dispatcher/actions';
|
||||||
|
|
||||||
|
const CHECK_PSTN_SUPPORT_ATTEMPTS = 3;
|
||||||
|
|
||||||
enum AudioID {
|
enum AudioID {
|
||||||
Ring = 'ringAudio',
|
Ring = 'ringAudio',
|
||||||
|
@ -119,6 +122,8 @@ export default class CallHandler {
|
||||||
private calls = new Map<string, MatrixCall>(); // roomId -> call
|
private calls = new Map<string, MatrixCall>(); // roomId -> call
|
||||||
private audioPromises = new Map<AudioID, Promise<void>>();
|
private audioPromises = new Map<AudioID, Promise<void>>();
|
||||||
private dispatcherRef: string = null;
|
private dispatcherRef: string = null;
|
||||||
|
private supportsPstnProtocol = null;
|
||||||
|
private pstnSupportCheckTimer: NodeJS.Timeout; // number actually because we're in the browser
|
||||||
|
|
||||||
static sharedInstance() {
|
static sharedInstance() {
|
||||||
if (!window.mxCallHandler) {
|
if (!window.mxCallHandler) {
|
||||||
|
@ -145,6 +150,8 @@ export default class CallHandler {
|
||||||
if (SettingsStore.getValue(UIFeature.Voip)) {
|
if (SettingsStore.getValue(UIFeature.Voip)) {
|
||||||
MatrixClientPeg.get().on('Call.incoming', this.onCallIncoming);
|
MatrixClientPeg.get().on('Call.incoming', this.onCallIncoming);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.checkForPstnSupport(CHECK_PSTN_SUPPORT_ATTEMPTS);
|
||||||
}
|
}
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
|
@ -158,6 +165,33 @@ export default class CallHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async checkForPstnSupport(maxTries) {
|
||||||
|
try {
|
||||||
|
const protocols = await MatrixClientPeg.get().getThirdpartyProtocols();
|
||||||
|
if (protocols['im.vector.protocol.pstn'] !== undefined) {
|
||||||
|
this.supportsPstnProtocol = protocols['im.vector.protocol.pstn'];
|
||||||
|
} else if (protocols['m.protocol.pstn'] !== undefined) {
|
||||||
|
this.supportsPstnProtocol = protocols['m.protocol.pstn'];
|
||||||
|
} else {
|
||||||
|
this.supportsPstnProtocol = null;
|
||||||
|
}
|
||||||
|
dis.dispatch({action: Action.PstnSupportUpdated});
|
||||||
|
} catch (e) {
|
||||||
|
if (maxTries === 1) {
|
||||||
|
console.log("Failed to check for pstn protocol support and no retries remain: assuming no support", e);
|
||||||
|
} else {
|
||||||
|
console.log("Failed to check for pstn protocol support: will retry", e);
|
||||||
|
this.pstnSupportCheckTimer = setTimeout(() => {
|
||||||
|
this.checkForPstnSupport(maxTries - 1);
|
||||||
|
}, 10000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getSupportsPstnProtocol() {
|
||||||
|
return this.supportsPstnProtocol;
|
||||||
|
}
|
||||||
|
|
||||||
private onCallIncoming = (call) => {
|
private onCallIncoming = (call) => {
|
||||||
// we dispatch this synchronously to make sure that the event
|
// we dispatch this synchronously to make sure that the event
|
||||||
// handlers on the call are set up immediately (so that if
|
// handlers on the call are set up immediately (so that if
|
||||||
|
|
|
@ -80,6 +80,7 @@ import CreateCommunityPrototypeDialog from "../views/dialogs/CreateCommunityProt
|
||||||
import ThreepidInviteStore, { IThreepidInvite, IThreepidInviteWireFormat } from "../../stores/ThreepidInviteStore";
|
import ThreepidInviteStore, { IThreepidInvite, IThreepidInviteWireFormat } from "../../stores/ThreepidInviteStore";
|
||||||
import {UIFeature} from "../../settings/UIFeature";
|
import {UIFeature} from "../../settings/UIFeature";
|
||||||
import { CommunityPrototypeStore } from "../../stores/CommunityPrototypeStore";
|
import { CommunityPrototypeStore } from "../../stores/CommunityPrototypeStore";
|
||||||
|
import DialPadModal from "../views/voip/DialPadModal";
|
||||||
|
|
||||||
/** constants for MatrixChat.state.view */
|
/** constants for MatrixChat.state.view */
|
||||||
export enum Views {
|
export enum Views {
|
||||||
|
@ -703,6 +704,9 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
this.state.resizeNotifier.notifyLeftHandleResized();
|
this.state.resizeNotifier.notifyLeftHandleResized();
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
case Action.OpenDialPad:
|
||||||
|
Modal.createTrackedDialog('Dial pad', '', DialPadModal, {}, "mx_Dialog_dialPadWrapper");
|
||||||
|
break;
|
||||||
case 'on_logged_in':
|
case 'on_logged_in':
|
||||||
if (
|
if (
|
||||||
!Lifecycle.isSoftLogout() &&
|
!Lifecycle.isSoftLogout() &&
|
||||||
|
|
|
@ -46,6 +46,7 @@ import { objectShallowClone, objectWithOnly } from "../../../utils/objects";
|
||||||
import { IconizedContextMenuOption, IconizedContextMenuOptionList } from "../context_menus/IconizedContextMenu";
|
import { IconizedContextMenuOption, IconizedContextMenuOptionList } from "../context_menus/IconizedContextMenu";
|
||||||
import AccessibleButton from "../elements/AccessibleButton";
|
import AccessibleButton from "../elements/AccessibleButton";
|
||||||
import { CommunityPrototypeStore } from "../../../stores/CommunityPrototypeStore";
|
import { CommunityPrototypeStore } from "../../../stores/CommunityPrototypeStore";
|
||||||
|
import CallHandler from "../../../CallHandler";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
onKeyDown: (ev: React.KeyboardEvent) => void;
|
onKeyDown: (ev: React.KeyboardEvent) => void;
|
||||||
|
@ -89,10 +90,44 @@ interface ITagAesthetics {
|
||||||
defaultHidden: boolean;
|
defaultHidden: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TAG_AESTHETICS: {
|
interface ITagAestheticsMap {
|
||||||
// @ts-ignore - TS wants this to be a string but we know better
|
// @ts-ignore - TS wants this to be a string but we know better
|
||||||
[tagId: TagID]: ITagAesthetics;
|
[tagId: TagID]: ITagAesthetics;
|
||||||
} = {
|
}
|
||||||
|
|
||||||
|
// If we have no dialer support, we just show the create chat dialog
|
||||||
|
const dmOnAddRoom = (dispatcher?: Dispatcher<ActionPayload>) => {
|
||||||
|
(dispatcher || defaultDispatcher).dispatch({action: 'view_create_chat'});
|
||||||
|
};
|
||||||
|
|
||||||
|
// If we have dialer support, show a context menu so the user can pick between
|
||||||
|
// the dialer and the create chat dialog
|
||||||
|
const dmAddRoomContextMenu = (onFinished: () => void) => {
|
||||||
|
return <IconizedContextMenuOptionList first>
|
||||||
|
<IconizedContextMenuOption
|
||||||
|
label={_t("Start a Conversation")}
|
||||||
|
iconClassName="mx_RoomList_iconPlus"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
onFinished();
|
||||||
|
defaultDispatcher.dispatch({action: "view_create_chat"});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<IconizedContextMenuOption
|
||||||
|
label={_t("Open dial pad")}
|
||||||
|
iconClassName="mx_RoomList_iconDialpad"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
onFinished();
|
||||||
|
defaultDispatcher.fire(Action.OpenDialPad);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</IconizedContextMenuOptionList>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const TAG_AESTHETICS: ITagAestheticsMap = {
|
||||||
[DefaultTagID.Invite]: {
|
[DefaultTagID.Invite]: {
|
||||||
sectionLabel: _td("Invites"),
|
sectionLabel: _td("Invites"),
|
||||||
isInvite: true,
|
isInvite: true,
|
||||||
|
@ -108,9 +143,8 @@ const TAG_AESTHETICS: {
|
||||||
isInvite: false,
|
isInvite: false,
|
||||||
defaultHidden: false,
|
defaultHidden: false,
|
||||||
addRoomLabel: _td("Start chat"),
|
addRoomLabel: _td("Start chat"),
|
||||||
onAddRoom: (dispatcher?: Dispatcher<ActionPayload>) => {
|
// Either onAddRoom or addRoomContextMenu are set depending on whether we
|
||||||
(dispatcher || defaultDispatcher).dispatch({action: 'view_create_chat'});
|
// have dialer support.
|
||||||
},
|
|
||||||
},
|
},
|
||||||
[DefaultTagID.Untagged]: {
|
[DefaultTagID.Untagged]: {
|
||||||
sectionLabel: _td("Rooms"),
|
sectionLabel: _td("Rooms"),
|
||||||
|
@ -178,6 +212,7 @@ function customTagAesthetics(tagId: TagID): ITagAesthetics {
|
||||||
export default class RoomList extends React.PureComponent<IProps, IState> {
|
export default class RoomList extends React.PureComponent<IProps, IState> {
|
||||||
private dispatcherRef;
|
private dispatcherRef;
|
||||||
private customTagStoreRef;
|
private customTagStoreRef;
|
||||||
|
private tagAesthetics: ITagAestheticsMap;
|
||||||
|
|
||||||
constructor(props: IProps) {
|
constructor(props: IProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
@ -187,6 +222,10 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
|
||||||
isNameFiltering: !!RoomListStore.instance.getFirstNameFilterCondition(),
|
isNameFiltering: !!RoomListStore.instance.getFirstNameFilterCondition(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// shallow-copy from the template as we need to make modifications to it
|
||||||
|
this.tagAesthetics = objectShallowClone(TAG_AESTHETICS);
|
||||||
|
this.updateDmAddRoomAction();
|
||||||
|
|
||||||
this.dispatcherRef = defaultDispatcher.register(this.onAction);
|
this.dispatcherRef = defaultDispatcher.register(this.onAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,6 +241,17 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
|
||||||
if (this.customTagStoreRef) this.customTagStoreRef.remove();
|
if (this.customTagStoreRef) this.customTagStoreRef.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private updateDmAddRoomAction() {
|
||||||
|
const dmTagAesthetics = objectShallowClone(TAG_AESTHETICS[DefaultTagID.DM]);
|
||||||
|
if (CallHandler.sharedInstance().getSupportsPstnProtocol()) {
|
||||||
|
dmTagAesthetics.addRoomContextMenu = dmAddRoomContextMenu;
|
||||||
|
} else {
|
||||||
|
dmTagAesthetics.onAddRoom = dmOnAddRoom;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.tagAesthetics[DefaultTagID.DM] = dmTagAesthetics;
|
||||||
|
}
|
||||||
|
|
||||||
private onAction = (payload: ActionPayload) => {
|
private onAction = (payload: ActionPayload) => {
|
||||||
if (payload.action === Action.ViewRoomDelta) {
|
if (payload.action === Action.ViewRoomDelta) {
|
||||||
const viewRoomDeltaPayload = payload as ViewRoomDeltaPayload;
|
const viewRoomDeltaPayload = payload as ViewRoomDeltaPayload;
|
||||||
|
@ -214,6 +264,9 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
|
||||||
show_room_tile: true, // to make sure the room gets scrolled into view
|
show_room_tile: true, // to make sure the room gets scrolled into view
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
} else if (payload.action === Action.PstnSupportUpdated) {
|
||||||
|
this.updateDmAddRoomAction();
|
||||||
|
this.updateLists();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -355,7 +408,7 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
|
||||||
|
|
||||||
const aesthetics: ITagAesthetics = isCustomTag(orderedTagId)
|
const aesthetics: ITagAesthetics = isCustomTag(orderedTagId)
|
||||||
? customTagAesthetics(orderedTagId)
|
? customTagAesthetics(orderedTagId)
|
||||||
: TAG_AESTHETICS[orderedTagId];
|
: this.tagAesthetics[orderedTagId];
|
||||||
if (!aesthetics) throw new Error(`Tag ${orderedTagId} does not have aesthetics`);
|
if (!aesthetics) throw new Error(`Tag ${orderedTagId} does not have aesthetics`);
|
||||||
|
|
||||||
components.push(<RoomSublist
|
components.push(<RoomSublist
|
||||||
|
|
82
src/components/views/voip/DialPad.tsx
Normal file
82
src/components/views/voip/DialPad.tsx
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as React from "react";
|
||||||
|
import AccessibleButton from "../elements/AccessibleButton";
|
||||||
|
|
||||||
|
const BUTTONS = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '0', '#'];
|
||||||
|
|
||||||
|
enum DialPadButtonKind {
|
||||||
|
Digit,
|
||||||
|
Delete,
|
||||||
|
Dial,
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IButtonProps {
|
||||||
|
kind: DialPadButtonKind;
|
||||||
|
digit?: string;
|
||||||
|
onButtonPress: (string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
class DialPadButton extends React.PureComponent<IButtonProps> {
|
||||||
|
onClick = () => {
|
||||||
|
this.props.onButtonPress(this.props.digit);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
switch (this.props.kind) {
|
||||||
|
case DialPadButtonKind.Digit:
|
||||||
|
return <AccessibleButton className="mx_DialPad_button" onClick={this.onClick}>
|
||||||
|
{this.props.digit}
|
||||||
|
</AccessibleButton>;
|
||||||
|
case DialPadButtonKind.Delete:
|
||||||
|
return <AccessibleButton className="mx_DialPad_button mx_DialPad_deleteButton"
|
||||||
|
onClick={this.onClick}
|
||||||
|
/>;
|
||||||
|
case DialPadButtonKind.Dial:
|
||||||
|
return <AccessibleButton className="mx_DialPad_button mx_DialPad_dialButton" onClick={this.onClick} />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
onDigitPress: (string) => void;
|
||||||
|
onDeletePress: (string) => void;
|
||||||
|
onDialPress: (string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Dialpad extends React.PureComponent<IProps> {
|
||||||
|
render() {
|
||||||
|
const buttonNodes = [];
|
||||||
|
|
||||||
|
for (const button of BUTTONS) {
|
||||||
|
buttonNodes.push(<DialPadButton key={button} kind={DialPadButtonKind.Digit}
|
||||||
|
digit={button} onButtonPress={this.props.onDigitPress}
|
||||||
|
/>);
|
||||||
|
}
|
||||||
|
|
||||||
|
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}
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
}
|
111
src/components/views/voip/DialPadModal.tsx
Normal file
111
src/components/views/voip/DialPadModal.tsx
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as React from "react";
|
||||||
|
import { ensureDMExists } from "../../../createRoom";
|
||||||
|
import { _t } from "../../../languageHandler";
|
||||||
|
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||||
|
import AccessibleButton from "../elements/AccessibleButton";
|
||||||
|
import Field from "../elements/Field";
|
||||||
|
import DialPad from './DialPad';
|
||||||
|
import dis from '../../../dispatcher/dispatcher';
|
||||||
|
import Modal from "../../../Modal";
|
||||||
|
import ErrorDialog from "../../views/dialogs/ErrorDialog";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
onFinished: (boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IState {
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class DialpadModal extends React.PureComponent<IProps, IState> {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
value: '',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onCancelClick = () => {
|
||||||
|
this.props.onFinished(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
onChange = (ev) => {
|
||||||
|
this.setState({value: ev.target.value});
|
||||||
|
}
|
||||||
|
|
||||||
|
onFormSubmit = (ev) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
this.onDialPress();
|
||||||
|
}
|
||||||
|
|
||||||
|
onDigitPress = (digit) => {
|
||||||
|
this.setState({value: this.state.value + digit});
|
||||||
|
}
|
||||||
|
|
||||||
|
onDeletePress = () => {
|
||||||
|
if (this.state.value.length === 0) return;
|
||||||
|
this.setState({value: this.state.value.slice(0, -1)});
|
||||||
|
}
|
||||||
|
|
||||||
|
onDialPress = async () => {
|
||||||
|
const results = await MatrixClientPeg.get().getThirdpartyUser('im.vector.protocol.pstn', {
|
||||||
|
'm.id.phone': this.state.value,
|
||||||
|
});
|
||||||
|
if (!results || results.length === 0 || !results[0].userid) {
|
||||||
|
Modal.createTrackedDialog('', '', ErrorDialog, {
|
||||||
|
title: _t("Unable to look up phone number"),
|
||||||
|
description: _t("There was an error looking up the phone number"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const userId = results[0].userid;
|
||||||
|
|
||||||
|
const roomId = await ensureDMExists(MatrixClientPeg.get(), userId);
|
||||||
|
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'view_room',
|
||||||
|
room_id: roomId,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.props.onFinished(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return <div className="mx_DialPadModal">
|
||||||
|
<div className="mx_DialPadModal_header">
|
||||||
|
<div>
|
||||||
|
<span className="mx_DialPadModal_title">{_t("Dial pad")}</span>
|
||||||
|
<AccessibleButton className="mx_DialPadModal_cancel" onClick={this.onCancelClick} />
|
||||||
|
</div>
|
||||||
|
<form onSubmit={this.onFormSubmit}>
|
||||||
|
<Field className="mx_DialPadModal_field" id="dialpad_number"
|
||||||
|
value={this.state.value} autoFocus={true}
|
||||||
|
onChange={this.onChange}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div className="mx_DialPadModal_horizSep" />
|
||||||
|
<div className="mx_DialPadModal_dialPad">
|
||||||
|
<DialPad onDigitPress={this.onDigitPress}
|
||||||
|
onDeletePress={this.onDeletePress}
|
||||||
|
onDialPress={this.onDialPress}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
}
|
|
@ -94,4 +94,16 @@ export enum Action {
|
||||||
* Trigged after the phase of the right panel is set. Should be used with AfterRightPanelPhaseChangePayload.
|
* Trigged after the phase of the right panel is set. Should be used with AfterRightPanelPhaseChangePayload.
|
||||||
*/
|
*/
|
||||||
AfterRightPanelPhaseChange = "after_right_panel_phase_change",
|
AfterRightPanelPhaseChange = "after_right_panel_phase_change",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the modal dial pad
|
||||||
|
*/
|
||||||
|
OpenDialPad = "open_dial_pad",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fired when CallHandler has checked for PSTN protocol support
|
||||||
|
* payload: none
|
||||||
|
* XXX: Is an action the right thing for this?
|
||||||
|
*/
|
||||||
|
PstnSupportUpdated = "pstn_support_updated",
|
||||||
}
|
}
|
||||||
|
|
|
@ -861,6 +861,9 @@
|
||||||
"Fill Screen": "Fill Screen",
|
"Fill Screen": "Fill Screen",
|
||||||
"Return to call": "Return to call",
|
"Return to call": "Return to call",
|
||||||
"%(name)s on hold": "%(name)s on hold",
|
"%(name)s on hold": "%(name)s on hold",
|
||||||
|
"Unable to look up phone number": "Unable to look up phone number",
|
||||||
|
"There was an error looking up the phone number": "There was an error looking up the phone number",
|
||||||
|
"Dial pad": "Dial pad",
|
||||||
"Unknown caller": "Unknown caller",
|
"Unknown caller": "Unknown caller",
|
||||||
"Incoming voice call": "Incoming voice call",
|
"Incoming voice call": "Incoming voice call",
|
||||||
"Incoming video call": "Incoming video call",
|
"Incoming video call": "Incoming video call",
|
||||||
|
@ -1459,6 +1462,8 @@
|
||||||
"Hide Widgets": "Hide Widgets",
|
"Hide Widgets": "Hide Widgets",
|
||||||
"Show Widgets": "Show Widgets",
|
"Show Widgets": "Show Widgets",
|
||||||
"Search": "Search",
|
"Search": "Search",
|
||||||
|
"Start a Conversation": "Start a Conversation",
|
||||||
|
"Open dial pad": "Open dial pad",
|
||||||
"Invites": "Invites",
|
"Invites": "Invites",
|
||||||
"Favourites": "Favourites",
|
"Favourites": "Favourites",
|
||||||
"People": "People",
|
"People": "People",
|
||||||
|
|
Loading…
Reference in a new issue