Skip to content
This repository has been archived by the owner on Jun 30, 2022. It is now read-only.

add callnotification,callStartmsg,iframe code #617

Merged
merged 21 commits into from
Jul 22, 2021
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified .loki/reference/chrome_Components_Icons_all.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .loki/reference/chrome_Components_Icons_phone.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions src/components/Calls/CallIFrame.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { h } from 'preact';

import { Screen } from '../Screen';
import { createClassName } from '../helpers';
import styles from './styles.scss';


export const CallIFrame = (url) => (
<Screen.Content nopadding>
<div className={createClassName(styles, 'call-iframe')}>
<iframe className={createClassName(styles, 'call-iframe__content')} src={url} />
</div>
</Screen.Content>
);
64 changes: 64 additions & 0 deletions src/components/Calls/CallNotification.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { h } from 'preact';
import { useState } from 'preact/compat';

import I18n from '../../i18n';
import PhoneAccept from '../../icons/phone.svg';
import PhoneDecline from '../../icons/phoneOff.svg';
import constants from '../../lib/constants';
import { Avatar } from '../Avatar';
import { Button } from '../Button';
import { createClassName, getAvatarUrl } from '../helpers';
import styles from './styles.scss';


export const CallNotification = ({ callProvider, callerUsername, url, dispatch } = { callProvider: undefined, callerUsername: undefined, dispatch: undefined }) => {
const [show, setShow] = useState(!!callProvider && !!callerUsername && !!dispatch && !!url);

const acceptClick = async () => {
setShow(!{ show });

switch (callProvider) {
case constants.jitsiCallStartedMessageType: {
window.open(url);
await dispatch({ incomingCallAlert: null });
break;
}
case constants.webrtcCallStartedMessageType: {
// TODO: add webrtc code here
break;
}
}
};

const declineClick = async () => {
await dispatch({ incomingCallAlert: null });
};

return (
<div className={createClassName(styles, 'call-notification')}>
{ show
? (
<div className={createClassName(styles, 'call-notification__content')}>
<div className={createClassName(styles, 'call-notification__content-avatar')}>
<Avatar
src={getAvatarUrl(callerUsername)}
large
/>
</div>
<div className={createClassName(styles, 'call-notification__content-message')}>
{ I18n.t('Incoming video Call') }
</div>
<div className={createClassName(styles, 'call-notification__content-actions')}>
<Button onClick={declineClick} className={createClassName(styles, 'call-notification__content-actions-accept')}>
<PhoneDecline width={20} height={20} /> <span style='margin-left:5px'> {I18n.t('Decline')} </span>
</Button>
<Button onClick={acceptClick} className={createClassName(styles, 'call-notification__content-actions-decline')} >
<PhoneAccept width={20} height={20} /><span style='margin-left:5px'> {I18n.t('Accept')} </span>
</Button>
</div>
</div>
)
: null
}
</div>);
};
78 changes: 78 additions & 0 deletions src/components/Calls/styles.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
@import '../../styles/colors';
@import '../../styles/variables';

.call-notification {
position: relative;

display: flex;

width: 100%;
height: 50%;

&__content {
display: flex;
flex-direction: column;

width: 100%;
height: 100%;

background: #1f2329;

font-weight: 600;
justify-content: space-evenly;

&-avatar {
display: flex;

margin: 0 auto;
align-self: flex-end;
}

&-message {
margin: 0 auto;

color: #ffffff;
}

&-actions {
display: flex;
flex-direction: row;

margin: 0 auto;
margin-bottom: 15px;

color: white;

align-items: flex-end;

> button {
margin-bottom: 0;
margin-left: 10px;
}

&-accept {
border-color: green;
background-color: #2de0a5;
}

&-decline {
border-color: red;
background-color: #f5455c;
}
}
}
}

.call-iframe {
position: relative;
top: 0;

display: flex;

width: 100%;
height: 50%;

&__content {
height: 100%;
}
}
8 changes: 6 additions & 2 deletions src/components/Messages/Message/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { h } from 'preact';

import I18n from '../../../i18n';
import { getAttachmentUrl, memo, normalizeTransferHistoryMessage } from '../../helpers';
import { getAttachmentUrl, memo, normalizeTransferHistoryMessage, normalizeCallTimeMessage } from '../../helpers';
import { AudioAttachment } from '../AudioAttachment';
import { FileAttachment } from '../FileAttachment';
import { ImageAttachment } from '../ImageAttachment';
Expand All @@ -22,6 +22,8 @@ import {
MESSAGE_TYPE_WELCOME,
MESSAGE_TYPE_LIVECHAT_CLOSED,
MESSAGE_TYPE_LIVECHAT_STARTED,
MESSAGE_WEBRTC_CALL,
MESSAGE_JITSI_CALL,
MESSAGE_TYPE_LIVECHAT_TRANSFER_HISTORY,
} from '../constants';

Expand Down Expand Up @@ -80,7 +82,7 @@ const renderContent = ({
),
].filter(Boolean);

const getSystemMessageText = ({ t, conversationFinishedMessage, transferData }) =>
const getSystemMessageText = ({ t, conversationFinishedMessage, transferData, callStatus }) =>
(t === MESSAGE_TYPE_ROOM_NAME_CHANGED && I18n.t('Room name changed'))
|| (t === MESSAGE_TYPE_USER_ADDED && I18n.t('User added by'))
|| (t === MESSAGE_TYPE_USER_REMOVED && I18n.t('User removed by'))
Expand All @@ -89,6 +91,8 @@ const getSystemMessageText = ({ t, conversationFinishedMessage, transferData })
|| (t === MESSAGE_TYPE_WELCOME && I18n.t('Welcome'))
|| (t === MESSAGE_TYPE_LIVECHAT_CLOSED && (conversationFinishedMessage || I18n.t('Conversation finished')))
|| (t === MESSAGE_TYPE_LIVECHAT_STARTED && I18n.t('Chat started'))
|| (t === MESSAGE_WEBRTC_CALL && normalizeCallTimeMessage(callStatus))
|| (t === MESSAGE_JITSI_CALL && normalizeCallTimeMessage(callStatus))
|| (t === MESSAGE_TYPE_LIVECHAT_TRANSFER_HISTORY && normalizeTransferHistoryMessage(transferData));

const getMessageUsernames = (compact, message) => {
Expand Down
2 changes: 2 additions & 0 deletions src/components/Messages/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ export const MESSAGE_TYPE_WELCOME = 'wm';
export const MESSAGE_TYPE_LIVECHAT_CLOSED = 'livechat-close';
export const MESSAGE_TYPE_LIVECHAT_STARTED = 'livechat-started';
export const MESSAGE_TYPE_LIVECHAT_TRANSFER_HISTORY = 'livechat_transfer_history';
export const MESSAGE_JITSI_CALL = 'webrtc_call_started';
export const MESSAGE_WEBRTC_CALL = 'jitsi_call_started';
19 changes: 18 additions & 1 deletion src/components/helpers.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import format from 'date-fns/format';
import { parseISO } from 'date-fns/fp';
import isToday from 'date-fns/isToday';
import { Component } from 'preact';

import { Livechat } from '../api';
Expand Down Expand Up @@ -113,7 +116,7 @@ export const createToken = () => Math.random().toString(36).substring(2, 15) + M

export const getAvatarUrl = (username) => (username ? `${ Livechat.client.host }/avatar/${ username }` : null);

export const msgTypesNotRendered = ['livechat_video_call', 'livechat_navigation_history', 'au', 'command', 'uj', 'ul', 'livechat-close'];
export const msgTypesNotRendered = ['livechat_video_call', 'livechat_navigation_history', 'au', 'command', 'uj', 'ul', 'livechat-close', 'webRTC_call_started', 'jitsi_call_started'];

export const canRenderMessage = ({ t }) => !msgTypesNotRendered.includes(t);

Expand All @@ -126,6 +129,20 @@ export const sortArrayByColumn = (array, column, inverted) => array.sort((a, b)
return 1;
});

export const normalizeCallTimeMessage = (callStatus) => {
const timestamp = new Date().toISOString();
const time = format(parseISO(timestamp), isToday(parseISO(timestamp)) ? 'HH:mm' : 'dddd HH:mm');
if (!callStatus) {
return;
}
if (callStatus === 'accept') {
return I18n.t('Call Started at %{time}', { time });
murtaza98 marked this conversation as resolved.
Show resolved Hide resolved
}
if (callStatus === 'endCall') {
return I18n.t('Call Ended at %{time}', { time });
murtaza98 marked this conversation as resolved.
Show resolved Hide resolved
}
};

export const normalizeTransferHistoryMessage = (transferData) => {
if (!transferData) {
return;
Expand Down
3 changes: 2 additions & 1 deletion src/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"drop_here_to_upload_a_file_e5f4dd60": "Drop here to upload a file",
"email_22a7d52d": "Email",
"enable_notifications_a3daf4b1": "Enable notifications",
"error_getting_call_alert": "Error occurred while receiving a call notification",
"error_closing_chat_4c5e29d7": "Error closing chat.",
"error_removing_user_data_ce507478": "Error removing user data.",
"error_starting_a_new_conversation_reason_a1b491a1": "Error starting a new conversation: %{reason}",
Expand Down Expand Up @@ -84,4 +85,4 @@
"your_spot_is_spot_a35cd288": "Your spot is #%{spot}",
"your_spot_is_spot_estimated_wait_time_estimatedwai_d0ff46e0": "Your spot is #%{spot} (Estimated wait time: %{estimatedWaitTime})"
}
}
}
6 changes: 6 additions & 0 deletions src/icons/phone.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions src/icons/phoneOff.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions src/lib/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ export default {
livechatConnectedAlertId: 'LIVECHAT_CONNECTED',
livechatDisconnectedAlertId: 'LIVECHAT_DISCONNECTED',
livechatQueueMessageId: 'LIVECHAT_QUEUE_MESSAGE',
webrtcCallStartedMessageType: 'webRTC_call_started',
jitsiCallStartedMessageType: 'jitsi_call_started',
};
35 changes: 34 additions & 1 deletion src/lib/room.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { route } from 'preact-router';

import { Livechat } from '../api';
import { setCookies, upsert, canRenderMessage } from '../components/helpers';
import { setCookies, upsert, canRenderMessage, createToken } from '../components/helpers';
import I18n from '../i18n';
import { store } from '../store';
import { normalizeAgent } from './api';
import Commands from './commands';
import constants from './constants';
import { loadConfig, processUnread } from './main';
import { parentCall } from './parentCall';
import { normalizeMessage, normalizeMessages } from './threads';
import { handleTranscript } from './transcript';


const commands = new Commands();

export const closeChat = async ({ transcriptRequested } = {}) => {
Expand All @@ -22,11 +25,36 @@ export const closeChat = async ({ transcriptRequested } = {}) => {
route('/chat-finished');
};

// TODO: use a separate event to listen to call start event. Listening on the message type isn't a good solution
export const processCallMessage = async (message) => {
const { alerts } = store.state;
try {
await store.setState({ incomingCallAlert: {
show: true,
callProvider: message.t,
callerUsername: message.u.username,
...message.customFields.jitsiCallUrl && { url: message.customFields.jitsiCallUrl },
} });
} catch (err) {
console.error(err);
const alert = { id: createToken(), children: I18n.t('error_getting_call_alert'), error: true, timeout: 5000 };
await store.setState({ alerts: (alerts.push(alert), alerts) });
}
};

const processMessage = async (message) => {
const { incomingCallAlert } = store.state;
if (incomingCallAlert) {
// TODO: create a new event to handle the call dismiss event, currently we're just dismissing the call alert if a new message is sent which is not a good solution
await store.setState({ incomingCallAlert: null });
}

if (message.t === 'livechat-close') {
closeChat(message);
} else if (message.t === 'command') {
commands[message.msg] && commands[message.msg]();
} else if (message.t === constants.webrtcCallStartedMessageType || message.t === constants.jitsiCallStartedMessageType) {
await processCallMessage(message);
}
};

Expand Down Expand Up @@ -171,6 +199,11 @@ export const loadMessages = async () => {
if (messages && messages.length) {
const lastMessage = messages[messages.length - 1];
await store.setState({ lastReadMessageId: lastMessage && lastMessage._id });

// TODO: create a separate event for starting the call and checking if the call is ongoing
if (lastMessage.t === constants.webrtcCallStartedMessageType || lastMessage.t === constants.jitsiCallStartedMessageType) {
await processCallMessage(lastMessage);
}
}
};

Expand Down
4 changes: 4 additions & 0 deletions src/routes/Chat/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Picker } from 'emoji-mart';
import { h, Component } from 'preact';

import { Button } from '../../components/Button';
import { CallNotification } from '../../components/Calls/CallNotification';
import { Composer, ComposerAction, ComposerActions } from '../../components/Composer';
import { FilesDropTarget } from '../../components/FilesDropTarget';
import { FooterOptions, CharCounter } from '../../components/Footer';
Expand Down Expand Up @@ -118,6 +119,8 @@ export default class Chat extends Component {
registrationRequired,
onRegisterUser,
limitTextLength,
incomingCallAlert,
dispatch,
...props
}, {
atBottom = true,
Expand All @@ -144,6 +147,7 @@ export default class Chat extends Component {
onUpload={onUpload}
>
<Screen.Content nopadding>
{ incomingCallAlert && !!incomingCallAlert.show && <CallNotification { ...incomingCallAlert } dispatch={dispatch} />}
<div className={createClassName(styles, 'chat__messages', { atBottom, loading })}>
<MessageList
ref={this.handleMessagesContainerRef}
Expand Down
2 changes: 2 additions & 0 deletions src/routes/Chat/container.js
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,7 @@ export const ChatConnector = ({ ref, ...props }) => (
lastReadMessageId,
triggerAgent,
queueInfo,
incomingCallAlert,
}) => (
<ChatContainer
ref={ref}
Expand Down Expand Up @@ -456,6 +457,7 @@ export const ChatConnector = ({ ref, ...props }) => (
nameFieldRegistrationForm={nameFieldRegistrationForm}
emailFieldRegistrationForm={emailFieldRegistrationForm}
limitTextLength={limitTextLength}
incomingCallAlert={incomingCallAlert}
/>
)}
</Consumer>
Expand Down
4 changes: 3 additions & 1 deletion src/store/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,11 @@ const initialState = {
visible: true,
minimized: true,
unread: null,
incomingCallAlert: null,
ongoingCall: null, // TODO: store call info like url, startTime, timeout, etc here
};

const dontPersist = ['messages', 'typing', 'loading', 'alerts', 'unread', 'noMoreMessages', 'modal'];
const dontPersist = ['messages', 'typing', 'loading', 'alerts', 'unread', 'noMoreMessages', 'modal', 'incomingCallAlert', 'ongoingCall']; // TODO: once the event handlers for calls are in-place, we might need to add the incomingCallAlert and ongoingCall into this array
export const store = new Store(initialState, { dontPersist });

if (process.env.NODE_ENV === 'development') {
Expand Down