Add ability to make new call
This commit is contained in:
@@ -25,14 +25,14 @@
|
||||
left: 2px;
|
||||
}
|
||||
|
||||
.answer {
|
||||
.answer, .makeCall {
|
||||
composes: button;
|
||||
background-color: #45bd57;
|
||||
}
|
||||
.answer:hover {
|
||||
.answer:hover, .makeCall:hover {
|
||||
background-image: linear-gradient(#84b68c, #5fb86c);
|
||||
}
|
||||
.answer:after {
|
||||
.answer:after, .makeCall:after{
|
||||
content: url(/phone-solid.svg);
|
||||
transform: scale(0.65);
|
||||
}
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
import React from 'react';
|
||||
const DialBox = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
export default DialBox;
|
||||
@@ -1,11 +0,0 @@
|
||||
import React from 'react';
|
||||
import { DeviceState } from '../../hooks/voice-state';
|
||||
|
||||
export default function DisplayDeviceState({deviceState}: {deviceState: DeviceState})
|
||||
{
|
||||
return(
|
||||
<div>
|
||||
Device is currently {deviceState}.
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -2,47 +2,47 @@ import React from 'react';
|
||||
import { Voice } from '../../wrappers/voice';
|
||||
import { voiceToken } from '../../wrappers/default-config';
|
||||
import Button from './Button/button';
|
||||
import DeviceState from './display-device-state';
|
||||
import DtmfButtons from './DtmfButtons/dtmf-buttons';
|
||||
import DeviceState from './DeviceState/display-device-state';
|
||||
import DialButtons from './DialButtons/dial-buttons';
|
||||
import DialBox from './DialBox/dial-box';
|
||||
|
||||
export default function Phone({voice:{ startDevice, answer, reject, hangup, dial, dtmf, voiceState} }: {voice: Voice})
|
||||
export default function Phone({voice:{ startDevice, answer, reject, hangup, dialDigit, makeCall, voiceState} }: {voice: Voice})
|
||||
{
|
||||
if (voiceState.deviceState === "Offline")
|
||||
{
|
||||
return (
|
||||
<div className="Phone">
|
||||
<DeviceState deviceState = {voiceState.deviceState}/>
|
||||
<Button title = "connect" onClick = {() => startDevice(voiceToken)}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
else if(voiceState.deviceState === "Ringing")
|
||||
{
|
||||
return (
|
||||
<div className="Phone">
|
||||
<DeviceState deviceState = {voiceState.deviceState}/>
|
||||
<Button title = "reject" onClick = {() => reject()}/>
|
||||
<Button title = "answer" onClick = {() => answer()}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
else if(voiceState.deviceState === "Connected")
|
||||
{
|
||||
return (
|
||||
<div className="Phone">
|
||||
<DeviceState deviceState = {voiceState.deviceState}/>
|
||||
<DtmfButtons dtmf = {dtmf} />
|
||||
<Button title = "hangup" onClick = {() => hangup()}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (
|
||||
<div className="Phone">
|
||||
<DeviceState deviceState = {voiceState.deviceState}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
switch (voiceState.deviceState)
|
||||
{
|
||||
case "Offline":
|
||||
return (
|
||||
<div className="Phone">
|
||||
<DeviceState deviceState = {voiceState.deviceState} phoneNumber = {voiceState.callPhoneNumber}/>
|
||||
<Button title = "connect" onClick = {() => startDevice(voiceToken)}/>
|
||||
</div>
|
||||
);
|
||||
case "Ringing":
|
||||
return (
|
||||
<div className="Phone">
|
||||
<DeviceState deviceState = {voiceState.deviceState} phoneNumber = {voiceState.callPhoneNumber}/>
|
||||
<Button title = "reject" onClick = {() => reject()}/>
|
||||
<Button title = "answer" onClick = {() => answer()}/>
|
||||
</div>
|
||||
);
|
||||
case "Connected":
|
||||
return (
|
||||
<div className="Phone">
|
||||
<DeviceState deviceState = {voiceState.deviceState} phoneNumber = {voiceState.callPhoneNumber}/>
|
||||
<DialBox dialDigits = {voiceState.dialDigits} />
|
||||
<DialButtons action = {dialDigit} />
|
||||
<Button title = "hangup" onClick = {() => hangup()}/>
|
||||
</div>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<div className="Phone">
|
||||
<DeviceState deviceState = {voiceState.deviceState} phoneNumber = {voiceState.callPhoneNumber}/>
|
||||
<DialBox dialDigits = {voiceState.dialDigits} />
|
||||
<DialButtons action = {dialDigit} />
|
||||
<Button title = "makeCall" onClick = {() => makeCall()}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -8,11 +8,13 @@ export interface VoiceState
|
||||
callCreatedTime: number;
|
||||
callEstablishedTime: number;
|
||||
callPhoneNumber: string;
|
||||
dialDigits: string;
|
||||
}
|
||||
|
||||
export interface VoiceStateManager
|
||||
{
|
||||
onDeviceStateUpdate(state: DeviceState, phoneNumber?: string): void;
|
||||
setDialDigits(digits: string): void;
|
||||
voiceState: VoiceState;
|
||||
}
|
||||
|
||||
@@ -23,6 +25,9 @@ export default function useVoiceState(): VoiceStateManager
|
||||
const [callEstablishedTime, setCallEstablishedTime] = useState<number>(0);
|
||||
const [callPhoneNumber, setCallPhoneNumber] = useState<string>("");
|
||||
|
||||
// Used for DTMF and number to dial
|
||||
const [dialDigits, setDialDigits] = useState<string>("");
|
||||
|
||||
const onDeviceStateUpdate = (state: DeviceState, phoneNumber?: string) =>
|
||||
{
|
||||
|
||||
@@ -39,15 +44,20 @@ export default function useVoiceState(): VoiceStateManager
|
||||
|
||||
setDeviceState(state);
|
||||
if (phoneNumber) { setCallPhoneNumber(phoneNumber); }
|
||||
|
||||
// Wipe of the dialing digits on every state change.
|
||||
setDialDigits("");
|
||||
}
|
||||
|
||||
return {
|
||||
onDeviceStateUpdate,
|
||||
setDialDigits,
|
||||
voiceState: {
|
||||
deviceState,
|
||||
callCreatedTime,
|
||||
callEstablishedTime,
|
||||
callPhoneNumber
|
||||
callPhoneNumber,
|
||||
dialDigits
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,10 @@ import { defaultDelay } from "./mock-device";
|
||||
|
||||
export class MockConnection implements Connection
|
||||
{
|
||||
private from = "+16133713909";
|
||||
private to = "+123456789";
|
||||
private from: string = "+16133713909";
|
||||
private to: string = "+123456789";
|
||||
|
||||
public parameters: ConnectionParameters = { From: this.from, To: this.to } as ConnectionParameters;
|
||||
public get parameters() { return { From: this.from, To: this.to } as ConnectionParameters; }
|
||||
public customParameters: Map<string, string> = new Map();
|
||||
public options: object = {};
|
||||
|
||||
@@ -14,6 +14,16 @@ export class MockConnection implements Connection
|
||||
private readonly handlers = {} as {[key in ConnectionEvent]: ((mute?: boolean) => void)[]};
|
||||
private muted: boolean = false;
|
||||
|
||||
constructor(phoneNumber?: string)
|
||||
{
|
||||
if (phoneNumber)
|
||||
{
|
||||
this.to = phoneNumber;
|
||||
this.from = "";
|
||||
this.executeHandler("accept");
|
||||
}
|
||||
}
|
||||
|
||||
private executeHandler(handlerName: ConnectionEvent)
|
||||
{
|
||||
setTimeout(() => this.handlers[handlerName]?.forEach(handler => handler()), defaultDelay);
|
||||
|
||||
@@ -8,23 +8,31 @@ export class MockDevice implements Device
|
||||
public audio: DeviceAudio = {} as DeviceAudio;
|
||||
private deviceStatus: DeviceStatus = "offline";
|
||||
private readonly handlers = {} as {[key in DeviceEvent]: ((connection?: Connection) => void)[]};
|
||||
private currentConnection: Connection | undefined;
|
||||
|
||||
|
||||
constructor(token: string, params?: SetupParams)
|
||||
{
|
||||
setTimeout(() => this.handlers["ready"].forEach(handler => handler()), defaultDelay);
|
||||
setTimeout(() => this.handlers["incoming"].forEach(handler => handler(new MockConnection())), 2000);
|
||||
setTimeout(() =>
|
||||
{
|
||||
this.currentConnection = new MockConnection();
|
||||
this.handlers["incoming"].forEach(handler => handler(this.currentConnection));
|
||||
},
|
||||
2000);
|
||||
}
|
||||
|
||||
public setup(token: string, params?: SetupParams): void {}
|
||||
|
||||
public connect(params: any, audioConstraints?: any): Connection
|
||||
{
|
||||
return {} as Connection;
|
||||
this.currentConnection = new MockConnection(params.To);
|
||||
return this.currentConnection;
|
||||
}
|
||||
|
||||
public activeConnection(): Connection
|
||||
public activeConnection(): Connection | undefined
|
||||
{
|
||||
return {} as Connection;
|
||||
return this.currentConnection;
|
||||
}
|
||||
|
||||
public destroy(): void
|
||||
|
||||
@@ -9,8 +9,8 @@ export interface Voice
|
||||
answer(): void;
|
||||
reject(): void;
|
||||
hangup(): void;
|
||||
dial(phoneNumber: string): void;
|
||||
dtmf(digit: string): void;
|
||||
dialDigit(digit: string): void;
|
||||
makeCall(): void;
|
||||
voiceState: VoiceState;
|
||||
}
|
||||
|
||||
@@ -72,20 +72,27 @@ export default function useVoice()
|
||||
device?.disconnectAll();
|
||||
}
|
||||
|
||||
const dial = (phoneNumber: string) =>
|
||||
const dialDigit = (digit: string) =>
|
||||
{
|
||||
const connection = device?.connect({To: phoneNumber});
|
||||
|
||||
connection?.on("accept", () => state.onDeviceStateUpdate("Connected"));
|
||||
connection?.on("disconnect", () => state.onDeviceStateUpdate("Disconnected"));
|
||||
connection?.on("mute", muted => state.onDeviceStateUpdate(muted ? "Muted" : "Connected"));
|
||||
|
||||
setConnection(connection);
|
||||
if (state.voiceState.deviceState === "Connected")
|
||||
{
|
||||
connection?.sendDigits(digit);
|
||||
}
|
||||
state.setDialDigits(state.voiceState.dialDigits + digit);
|
||||
}
|
||||
|
||||
const dtmf = (digit: string) =>
|
||||
const makeCall = () =>
|
||||
{
|
||||
connection?.sendDigits(digit);
|
||||
if (state.voiceState.dialDigits)
|
||||
{
|
||||
const connection = device?.connect({To: state.voiceState.dialDigits});
|
||||
|
||||
connection?.on("accept", () => state.onDeviceStateUpdate("Connected", connection.parameters.To));
|
||||
connection?.on("disconnect", () => state.onDeviceStateUpdate("Disconnected"));
|
||||
connection?.on("mute", muted => state.onDeviceStateUpdate(muted ? "Muted" : "Connected"));
|
||||
|
||||
setConnection(connection);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -93,8 +100,8 @@ export default function useVoice()
|
||||
answer,
|
||||
reject,
|
||||
hangup,
|
||||
dial,
|
||||
dtmf,
|
||||
dialDigit,
|
||||
makeCall,
|
||||
voiceState: state.voiceState,
|
||||
} as Voice;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
.phoneNumber {
|
||||
font-size: 15px;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
13
src/components/Phone/DeviceState/display-device-state.tsx
Normal file
13
src/components/Phone/DeviceState/display-device-state.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import React from 'react';
|
||||
import { DeviceState } from '../../../hooks/voice-state';
|
||||
import styles from './display-device-state.module.css';
|
||||
|
||||
export default function DisplayDeviceState({deviceState, phoneNumber}: {deviceState: DeviceState, phoneNumber: string})
|
||||
{
|
||||
return(
|
||||
<div>
|
||||
Device is currently {deviceState}.
|
||||
<p className={styles.phoneNumber} >{phoneNumber}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
0
src/components/Phone/DialBox/dial-box.module.css
Normal file
0
src/components/Phone/DialBox/dial-box.module.css
Normal file
7
src/components/Phone/DialBox/dial-box.tsx
Normal file
7
src/components/Phone/DialBox/dial-box.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import React from 'react';
|
||||
import style from './dial-box.module.css';
|
||||
|
||||
export default function DialBox({dialDigits} : {dialDigits: string})
|
||||
{
|
||||
return <div>{dialDigits}</div>;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
.dtmf-button {
|
||||
.dial-button {
|
||||
border-radius: 50%;
|
||||
border: 1px solid black;
|
||||
width: 70px;
|
||||
@@ -7,7 +7,7 @@
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.dtmf-button:hover {
|
||||
.dial-button:hover {
|
||||
background-color: lightgray;
|
||||
}
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import React from 'react';
|
||||
import { chunk } from 'lodash';
|
||||
import { Voice } from '../../../wrappers/voice';
|
||||
import styles from './dtmf-buttons.module.css';
|
||||
import styles from './dial-buttons.module.css';
|
||||
|
||||
interface DtmfButton
|
||||
interface DialButton
|
||||
{
|
||||
title: string;
|
||||
subTitle:string;
|
||||
}
|
||||
|
||||
const buttons: DtmfButton[] = [
|
||||
const buttons: DialButton[] = [
|
||||
{title: "1", subTitle: " "},
|
||||
{title: "2", subTitle: "abc"},
|
||||
{title: "3", subTitle: "efg"},
|
||||
@@ -24,13 +23,13 @@ const buttons: DtmfButton[] = [
|
||||
{title: "#", subTitle: " "},
|
||||
];
|
||||
|
||||
export default function DtmfButtons({dtmf}: {dtmf: Voice["dtmf"]}) {
|
||||
export default function DialButtons({action}: {action: (digit: string) => void}) {
|
||||
return (
|
||||
<div>
|
||||
{ chunk(buttons, 3).map((buttonRow, rowIndex) =>
|
||||
<div key={rowIndex}>
|
||||
{ buttonRow.map(({title, subTitle}, index) =>
|
||||
<button key={title} className={styles["dtmf-button"]} onClick = {() => dtmf(title)}>
|
||||
<button key={title} className={styles["dial-button"]} onClick = {() => action(title)}>
|
||||
<div className={styles["title"]}>{ title }</div>
|
||||
<div className={styles["sub-title"]}>{ subTitle }</div>
|
||||
</button>) }
|
||||
2
types/twilio-client.d.ts
vendored
2
types/twilio-client.d.ts
vendored
@@ -91,7 +91,7 @@ declare module "twilio-client"
|
||||
constructor(token: string, params?: SetupParams);
|
||||
public setup(token: string, params?: SetupParams): void;
|
||||
public connect(params: any, audioConstraints?: any): Connection;
|
||||
public activeConnection(): Connection;
|
||||
public activeConnection(): Connection | undefined;
|
||||
public destroy(): void;
|
||||
public disconnectAll(): void;
|
||||
public status(): DeviceStatus;
|
||||
|
||||
Reference in New Issue
Block a user