mirror of
https://github.com/lingble/chatwoot.git
synced 2025-11-03 04:27:53 +00:00
feat: Add video call option with Dyte in the dashboard (#6207)
Co-authored-by: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com> Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<woot-button
|
||||
v-if="isVideoIntegrationEnabled"
|
||||
v-tooltip.top-end="
|
||||
$t('INTEGRATION_SETTINGS.DYTE.START_VIDEO_CALL_HELP_TEXT')
|
||||
"
|
||||
icon="video"
|
||||
:is-loading="isLoading"
|
||||
color-scheme="secondary"
|
||||
variant="smooth"
|
||||
size="small"
|
||||
@click="onClick"
|
||||
/>
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import DyteAPI from 'dashboard/api/integrations/dyte';
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
|
||||
export default {
|
||||
mixins: [alertMixin],
|
||||
props: {
|
||||
conversationId: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return { isLoading: false };
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({ appIntegrations: 'integrations/getAppIntegrations' }),
|
||||
isVideoIntegrationEnabled() {
|
||||
return this.appIntegrations.find(
|
||||
integration => integration.id === 'dyte' && !!integration.hooks.length
|
||||
);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (!this.appIntegrations.length) {
|
||||
this.$store.dispatch('integrations/get');
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async onClick() {
|
||||
this.isLoading = true;
|
||||
try {
|
||||
await DyteAPI.createAMeeting(this.conversationId);
|
||||
} catch (error) {
|
||||
this.showAlert(this.$t('INTEGRATION_SETTINGS.DYTE.CREATE_ERROR'));
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -87,6 +87,10 @@
|
||||
:title="'Whatsapp Templates'"
|
||||
@click="$emit('selectWhatsappTemplate')"
|
||||
/>
|
||||
<video-call-button
|
||||
v-if="(isAWebWidgetInbox || isAPIInbox) && !isOnPrivateNote"
|
||||
:conversation-id="conversationId"
|
||||
/>
|
||||
<transition name="modal-fade">
|
||||
<div
|
||||
v-show="$refs.upload && $refs.upload.dropActive"
|
||||
@@ -124,13 +128,13 @@ import {
|
||||
ALLOWED_FILE_TYPES,
|
||||
ALLOWED_FILE_TYPES_FOR_TWILIO_WHATSAPP,
|
||||
} from 'shared/constants/messages';
|
||||
|
||||
import VideoCallButton from '../VideoCallButton';
|
||||
import { REPLY_EDITOR_MODES } from './constants';
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
name: 'ReplyBottomPanel',
|
||||
components: { FileUpload },
|
||||
components: { FileUpload, VideoCallButton },
|
||||
mixins: [eventListenerMixins, uiSettingsMixin, inboxMixin],
|
||||
props: {
|
||||
mode: {
|
||||
@@ -209,6 +213,10 @@ export default {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
conversationId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
@@ -269,7 +277,7 @@ export default {
|
||||
}
|
||||
},
|
||||
showMessageSignatureButton() {
|
||||
return !this.isPrivate && this.isAnEmailChannel;
|
||||
return !this.isOnPrivateNote && this.isAnEmailChannel;
|
||||
},
|
||||
sendWithSignature() {
|
||||
const { send_with_signature: isEnabled } = this.uiSettings;
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<template>
|
||||
<li
|
||||
v-if="hasAttachments || data.content || isEmailContentType"
|
||||
:class="alignBubble"
|
||||
>
|
||||
<li v-if="shouldRenderMessage" :class="alignBubble">
|
||||
<div :class="wrapClass">
|
||||
<div v-tooltip.top-start="messageToolTip" :class="bubbleClass">
|
||||
<bubble-mail-head
|
||||
@@ -17,6 +14,11 @@
|
||||
:is-email="isEmailContentType"
|
||||
:display-quoted-button="displayQuotedButton"
|
||||
/>
|
||||
<bubble-integration
|
||||
:message-id="data.id"
|
||||
:content-attributes="contentAttributes"
|
||||
:inbox-id="data.inbox_id"
|
||||
/>
|
||||
<span
|
||||
v-if="isPending && hasAttachments"
|
||||
class="chat-bubble has-attachment agent"
|
||||
@@ -111,14 +113,14 @@
|
||||
</template>
|
||||
<script>
|
||||
import messageFormatterMixin from 'shared/mixins/messageFormatterMixin';
|
||||
import BubbleActions from './bubble/Actions';
|
||||
import BubbleFile from './bubble/File';
|
||||
import BubbleImage from './bubble/Image';
|
||||
import BubbleIntegration from './bubble/Integration.vue';
|
||||
import BubbleLocation from './bubble/Location';
|
||||
import BubbleMailHead from './bubble/MailHead';
|
||||
import BubbleText from './bubble/Text';
|
||||
import BubbleImage from './bubble/Image';
|
||||
import BubbleFile from './bubble/File';
|
||||
import BubbleVideo from './bubble/Video.vue';
|
||||
import BubbleActions from './bubble/Actions';
|
||||
import BubbleLocation from './bubble/Location';
|
||||
|
||||
import Spinner from 'shared/components/Spinner';
|
||||
import ContextMenu from 'dashboard/modules/conversations/components/MessageContextMenu';
|
||||
|
||||
@@ -130,12 +132,13 @@ import { generateBotMessageContent } from './helpers/botMessageContentHelper';
|
||||
export default {
|
||||
components: {
|
||||
BubbleActions,
|
||||
BubbleText,
|
||||
BubbleImage,
|
||||
BubbleFile,
|
||||
BubbleVideo,
|
||||
BubbleMailHead,
|
||||
BubbleImage,
|
||||
BubbleIntegration,
|
||||
BubbleLocation,
|
||||
BubbleMailHead,
|
||||
BubbleText,
|
||||
BubbleVideo,
|
||||
ContextMenu,
|
||||
Spinner,
|
||||
},
|
||||
@@ -169,6 +172,14 @@ export default {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
shouldRenderMessage() {
|
||||
return (
|
||||
this.hasAttachments ||
|
||||
this.data.content ||
|
||||
this.isEmailContentType ||
|
||||
this.isAnIntegrationMessage
|
||||
);
|
||||
},
|
||||
emailMessageContent() {
|
||||
const {
|
||||
html_content: { full: fullHTMLContent } = {},
|
||||
@@ -274,6 +285,9 @@ export default {
|
||||
isTemplate() {
|
||||
return this.data.message_type === MESSAGE_TYPE.TEMPLATE;
|
||||
},
|
||||
isAnIntegrationMessage() {
|
||||
return this.contentType === 'integrations';
|
||||
},
|
||||
emailHeadAttributes() {
|
||||
return {
|
||||
email: this.contentAttributes.email,
|
||||
|
||||
@@ -96,25 +96,26 @@
|
||||
</p>
|
||||
</div>
|
||||
<reply-bottom-panel
|
||||
:mode="replyType"
|
||||
:inbox="inbox"
|
||||
:send-button-text="replyButtonLabel"
|
||||
:on-file-upload="onFileUpload"
|
||||
:show-file-upload="showFileUpload"
|
||||
:show-audio-recorder="showAudioRecorder"
|
||||
:toggle-emoji-picker="toggleEmojiPicker"
|
||||
:toggle-audio-recorder="toggleAudioRecorder"
|
||||
:toggle-audio-recorder-play-pause="toggleAudioRecorderPlayPause"
|
||||
:show-emoji-picker="showEmojiPicker"
|
||||
:on-send="onSendReply"
|
||||
:is-send-disabled="isReplyButtonDisabled"
|
||||
:recording-audio-duration-text="recordingAudioDurationText"
|
||||
:recording-audio-state="recordingAudioState"
|
||||
:is-recording-audio="isRecordingAudio"
|
||||
:is-on-private-note="isOnPrivateNote"
|
||||
:show-editor-toggle="isAPIInbox && !isOnPrivateNote"
|
||||
:conversation-id="conversationId"
|
||||
:enable-multiple-file-upload="enableMultipleFileUpload"
|
||||
:has-whatsapp-templates="hasWhatsappTemplates"
|
||||
:inbox="inbox"
|
||||
:is-on-private-note="isOnPrivateNote"
|
||||
:is-recording-audio="isRecordingAudio"
|
||||
:is-send-disabled="isReplyButtonDisabled"
|
||||
:mode="replyType"
|
||||
:on-file-upload="onFileUpload"
|
||||
:on-send="onSendReply"
|
||||
:recording-audio-duration-text="recordingAudioDurationText"
|
||||
:recording-audio-state="recordingAudioState"
|
||||
:send-button-text="replyButtonLabel"
|
||||
:show-audio-recorder="showAudioRecorder"
|
||||
:show-editor-toggle="isAPIInbox && !isOnPrivateNote"
|
||||
:show-emoji-picker="showEmojiPicker"
|
||||
:show-file-upload="showFileUpload"
|
||||
:toggle-audio-recorder-play-pause="toggleAudioRecorderPlayPause"
|
||||
:toggle-audio-recorder="toggleAudioRecorder"
|
||||
:toggle-emoji-picker="toggleEmojiPicker"
|
||||
@selectWhatsappTemplate="openWhatsappTemplateModal"
|
||||
@toggle-editor="toggleRichContentEditor"
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<dyte-video-call
|
||||
v-if="showDyteIntegration"
|
||||
:message-id="messageId"
|
||||
:meeting-data="contentAttributes.data"
|
||||
/>
|
||||
</template>
|
||||
<script>
|
||||
import DyteVideoCall from './integrations/Dyte.vue';
|
||||
import inboxMixin from 'shared/mixins/inboxMixin';
|
||||
|
||||
export default {
|
||||
components: { DyteVideoCall },
|
||||
mixins: [inboxMixin],
|
||||
props: {
|
||||
messageId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
contentAttributes: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
inboxId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
showDyteIntegration() {
|
||||
const isEnabledOnTheInbox = this.isAPIInbox || this.isAWebWidgetInbox;
|
||||
return isEnabledOnTheInbox && this.contentAttributes.type === 'dyte';
|
||||
},
|
||||
inbox() {
|
||||
return this.$store.getters['inboxes/getInbox'](this.inboxId);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<div>
|
||||
<woot-button
|
||||
size="small"
|
||||
variant="smooth"
|
||||
color-scheme="secondary"
|
||||
icon="video-add"
|
||||
class="join-call-button"
|
||||
:is-loading="isLoading"
|
||||
@click="joinTheCall"
|
||||
>
|
||||
{{ $t('INTEGRATION_SETTINGS.DYTE.CLICK_HERE_TO_JOIN') }}
|
||||
</woot-button>
|
||||
<div v-if="dyteAuthToken" class="video-call--container">
|
||||
<iframe
|
||||
:src="meetingLink"
|
||||
allow="camera;microphone;fullscreen;display-capture;picture-in-picture;clipboard-write;"
|
||||
/>
|
||||
<woot-button
|
||||
size="small"
|
||||
variant="smooth"
|
||||
color-scheme="secondary"
|
||||
class="join-call-button"
|
||||
@click="leaveTheRoom"
|
||||
>
|
||||
{{ $t('INTEGRATION_SETTINGS.DYTE.LEAVE_THE_ROOM') }}
|
||||
</woot-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import DyteAPI from 'dashboard/api/integrations/dyte';
|
||||
|
||||
const DYTE_MEETING_LINK = 'https://app.dyte.in/meeting/stage/';
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
|
||||
export default {
|
||||
mixins: [alertMixin],
|
||||
props: {
|
||||
messageId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
meetingData: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return { isLoading: false, dyteAuthToken: '', isSDKMounted: false };
|
||||
},
|
||||
computed: {
|
||||
meetingLink() {
|
||||
return `${DYTE_MEETING_LINK}${this.meetingData.room_name}?authToken=${this.dyteAuthToken}&showSetupScreen=true&disableVideoBackground=true`;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async joinTheCall() {
|
||||
this.isLoading = true;
|
||||
try {
|
||||
const {
|
||||
data: { authResponse: { authToken } = {} } = {},
|
||||
} = await DyteAPI.addParticipantToMeeting(this.messageId);
|
||||
this.dyteAuthToken = authToken;
|
||||
} catch (err) {
|
||||
this.showAlert(this.$t('INTEGRATION_SETTINGS.DYTE.JOIN_ERROR'));
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
},
|
||||
leaveTheRoom() {
|
||||
this.dyteAuthToken = '';
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.join-call-button {
|
||||
margin: var(--space-small) 0;
|
||||
}
|
||||
|
||||
.video-call--container {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: var(--z-index-high);
|
||||
padding: var(--space-smaller);
|
||||
background: var(--b-800);
|
||||
|
||||
iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
button {
|
||||
position: absolute;
|
||||
top: var(--space-smaller);
|
||||
right: 16rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user