fix: Update the reply box to handle play, pause callbacks from WaveSurfer (#10223)

- Implemented custom @play, @pause methods to update the state of the recording. Once the recording is finished the button icon changes from stop button to play/pause button.
- Removes the console error undefined hasAudio

Fixes https://linear.app/chatwoot/issue/CW-3609/audio-recorder-issue

---------

Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
This commit is contained in:
Pranav
2024-10-05 00:17:11 -07:00
committed by GitHub
parent 095aaf3de6
commit 0677d8763d
3 changed files with 59 additions and 56 deletions

View File

@@ -13,7 +13,12 @@ const props = defineProps({
}, },
}); });
const emit = defineEmits(['recorderProgressChanged', 'finishRecord']); const emit = defineEmits([
'recorderProgressChanged',
'finishRecord',
'pause',
'play',
]);
const waveformContainer = ref(null); const waveformContainer = ref(null);
const wavesurfer = ref(null); const wavesurfer = ref(null);
@@ -47,6 +52,9 @@ const initWaveSurfer = () => {
], ],
}); });
wavesurfer.value.on('pause', () => emit('pause'));
wavesurfer.value.on('play', () => emit('play'));
record.value = wavesurfer.value.plugins[0]; record.value = wavesurfer.value.plugins[0];
wavesurfer.value.on('finish', () => { wavesurfer.value.on('finish', () => {
@@ -106,11 +114,9 @@ onUnmounted(() => {
} }
}); });
defineExpose({ playPause, stopRecording }); defineExpose({ playPause, stopRecording, record });
</script> </script>
<template> <template>
<div class="w-full"> <div ref="waveformContainer" class="w-full p-1" />
<div ref="waveformContainer" />
</div>
</template> </template>

View File

@@ -35,7 +35,7 @@ export default {
}, },
recordingAudioDurationText: { recordingAudioDurationText: {
type: String, type: String,
default: '', default: '00:00',
}, },
// inbox prop is used in /mixins/inboxMixin, // inbox prop is used in /mixins/inboxMixin,
// remove this props when refactoring to composable if not needed // remove this props when refactoring to composable if not needed
@@ -259,7 +259,6 @@ export default {
v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_EMOJI_ICON')" v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_EMOJI_ICON')"
:title="$t('CONVERSATION.REPLYBOX.TIP_EMOJI_ICON')" :title="$t('CONVERSATION.REPLYBOX.TIP_EMOJI_ICON')"
icon="emoji" icon="emoji"
emoji="😊"
color-scheme="secondary" color-scheme="secondary"
variant="smooth" variant="smooth"
size="small" size="small"
@@ -285,7 +284,6 @@ export default {
class-names="button--upload" class-names="button--upload"
:title="$t('CONVERSATION.REPLYBOX.TIP_ATTACH_ICON')" :title="$t('CONVERSATION.REPLYBOX.TIP_ATTACH_ICON')"
icon="attach" icon="attach"
emoji="📎"
color-scheme="secondary" color-scheme="secondary"
variant="smooth" variant="smooth"
size="small" size="small"
@@ -295,7 +293,6 @@ export default {
v-if="showAudioRecorderButton" v-if="showAudioRecorderButton"
v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_AUDIORECORDER_ICON')" v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_AUDIORECORDER_ICON')"
:icon="!isRecordingAudio ? 'microphone' : 'microphone-off'" :icon="!isRecordingAudio ? 'microphone' : 'microphone-off'"
emoji="🎤"
:color-scheme="!isRecordingAudio ? 'secondary' : 'alert'" :color-scheme="!isRecordingAudio ? 'secondary' : 'alert'"
variant="smooth" variant="smooth"
size="small" size="small"
@@ -305,7 +302,6 @@ export default {
v-if="showEditorToggle" v-if="showEditorToggle"
v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_FORMAT_ICON')" v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_FORMAT_ICON')"
icon="quote" icon="quote"
emoji="🖊️"
color-scheme="secondary" color-scheme="secondary"
variant="smooth" variant="smooth"
size="small" size="small"
@@ -314,7 +310,6 @@ export default {
<woot-button <woot-button
v-if="showAudioPlayStopButton" v-if="showAudioPlayStopButton"
:icon="audioRecorderPlayStopIcon" :icon="audioRecorderPlayStopIcon"
emoji="🎤"
color-scheme="secondary" color-scheme="secondary"
variant="smooth" variant="smooth"
size="small" size="small"

View File

@@ -19,7 +19,7 @@ import MessageSignatureMissingAlert from './MessageSignatureMissingAlert.vue';
import Banner from 'dashboard/components/ui/Banner.vue'; import Banner from 'dashboard/components/ui/Banner.vue';
import { REPLY_EDITOR_MODES } from 'dashboard/components/widgets/WootWriter/constants'; import { REPLY_EDITOR_MODES } from 'dashboard/components/widgets/WootWriter/constants';
import WootMessageEditor from 'dashboard/components/widgets/WootWriter/Editor.vue'; import WootMessageEditor from 'dashboard/components/widgets/WootWriter/Editor.vue';
import WootAudioRecorder from 'dashboard/components/widgets/WootWriter/AudioRecorder.vue'; import AudioRecorder from 'dashboard/components/widgets/WootWriter/AudioRecorder.vue';
import { AUDIO_FORMATS } from 'shared/constants/messages'; import { AUDIO_FORMATS } from 'shared/constants/messages';
import { BUS_EVENTS } from 'shared/constants/busEvents'; import { BUS_EVENTS } from 'shared/constants/busEvents';
import { import {
@@ -50,20 +50,20 @@ const EmojiInput = defineAsyncComponent(
export default { export default {
components: { components: {
EmojiInput,
CannedResponse,
ReplyToMessage,
ResizableTextArea,
AttachmentPreview,
ReplyTopPanel,
ReplyEmailHead,
ReplyBottomPanel,
WootMessageEditor,
WootAudioRecorder,
Banner,
WhatsappTemplates,
MessageSignatureMissingAlert,
ArticleSearchPopover, ArticleSearchPopover,
AttachmentPreview,
AudioRecorder,
Banner,
CannedResponse,
EmojiInput,
MessageSignatureMissingAlert,
ReplyBottomPanel,
ReplyEmailHead,
ReplyToMessage,
ReplyTopPanel,
ResizableTextArea,
WhatsappTemplates,
WootMessageEditor,
}, },
mixins: [inboxMixin, fileUploadMixin, keyboardEventListenerMixins], mixins: [inboxMixin, fileUploadMixin, keyboardEventListenerMixins],
emits: ['update:popoutReplyBox', 'togglePopout'], emits: ['update:popoutReplyBox', 'togglePopout'],
@@ -115,6 +115,7 @@ export default {
showVariablesMenu: false, showVariablesMenu: false,
newConversationModalActive: false, newConversationModalActive: false,
showArticleSearchPopover: false, showArticleSearchPopover: false,
hasRecordedAudio: false,
}; };
}, },
computed: { computed: {
@@ -279,12 +280,6 @@ export default {
hasAttachments() { hasAttachments() {
return this.attachedFiles.length; return this.attachedFiles.length;
}, },
hasRecordedAudio() {
return (
this.$refs.audioRecorderInput &&
this.$refs.audioRecorderInput.hasAudio()
);
},
isRichEditorEnabled() { isRichEditorEnabled() {
return this.isAWebWidgetInbox || this.isAnEmailChannel; return this.isAWebWidgetInbox || this.isAnEmailChannel;
}, },
@@ -364,7 +359,7 @@ export default {
return AUDIO_FORMATS.MP3; return AUDIO_FORMATS.MP3;
} }
if (this.isAPIInbox) { if (this.isAPIInbox) {
return AUDIO_FORMATS.OGG; return AUDIO_FORMATS.MP3;
} }
return AUDIO_FORMATS.WAV; return AUDIO_FORMATS.WAV;
}, },
@@ -803,19 +798,14 @@ export default {
this.attachedFiles = []; this.attachedFiles = [];
this.isRecordingAudio = false; this.isRecordingAudio = false;
this.resetReplyToMessage(); this.resetReplyToMessage();
this.resetAudioRecorderInput();
}, },
clearEmailField() { clearEmailField() {
this.ccEmails = ''; this.ccEmails = '';
this.bccEmails = ''; this.bccEmails = '';
this.toEmails = ''; this.toEmails = '';
}, },
clearRecorder() {
this.isRecordingAudio = false;
// Only clear the recorded audio when we click toggle button.
this.attachedFiles = this.attachedFiles.filter(
file => !file?.isRecordedAudio
);
},
toggleEmojiPicker() { toggleEmojiPicker() {
this.showEmojiPicker = !this.showEmojiPicker; this.showEmojiPicker = !this.showEmojiPicker;
}, },
@@ -823,17 +813,18 @@ export default {
this.isRecordingAudio = !this.isRecordingAudio; this.isRecordingAudio = !this.isRecordingAudio;
this.isRecorderAudioStopped = !this.isRecordingAudio; this.isRecorderAudioStopped = !this.isRecordingAudio;
if (!this.isRecordingAudio) { if (!this.isRecordingAudio) {
this.clearRecorder(); this.resetAudioRecorderInput();
} }
}, },
toggleAudioRecorderPlayPause() { toggleAudioRecorderPlayPause() {
if (this.isRecordingAudio) { if (!this.isRecordingAudio) {
if (!this.isRecorderAudioStopped) { return;
this.isRecorderAudioStopped = true; }
this.$refs.audioRecorderInput.stopRecording(); if (!this.isRecorderAudioStopped) {
} else if (this.isRecorderAudioStopped) { this.isRecorderAudioStopped = true;
this.$refs.audioRecorderInput.playPause(); this.$refs.audioRecorderInput.stopRecording();
} } else if (this.isRecorderAudioStopped) {
this.$refs.audioRecorderInput.playPause();
} }
}, },
hideEmojiPicker() { hideEmojiPicker() {
@@ -860,13 +851,9 @@ export default {
onRecordProgressChanged(duration) { onRecordProgressChanged(duration) {
this.recordingAudioDurationText = duration; this.recordingAudioDurationText = duration;
}, },
onStateRecorderChanged(state) {
this.recordingAudioState = state;
if (state && 'notallowederror'.includes(state)) {
this.toggleAudioRecorder();
}
},
onFinishRecorder(file) { onFinishRecorder(file) {
this.recordingAudioState = 'stopped';
this.hasRecordedAudio = true;
// Added a new key isRecordedAudio to the file to find it's and recorded audio // Added a new key isRecordedAudio to the file to find it's and recorded audio
// Because to filter and show only non recorded audio and other attachments // Because to filter and show only non recorded audio and other attachments
const autoRecordedFile = { const autoRecordedFile = {
@@ -1067,6 +1054,16 @@ export default {
toggleInsertArticle() { toggleInsertArticle() {
this.showArticleSearchPopover = !this.showArticleSearchPopover; this.showArticleSearchPopover = !this.showArticleSearchPopover;
}, },
resetAudioRecorderInput() {
this.recordingAudioDurationText = '00:00';
this.isRecordingAudio = false;
this.recordingAudioState = '';
this.hasRecordedAudio = false;
// Only clear the recorded audio when we click toggle button.
this.attachedFiles = this.attachedFiles.filter(
file => !file?.isRecordedAudio
);
},
}, },
}; };
</script> </script>
@@ -1122,13 +1119,14 @@ export default {
v-model:bcc-emails="bccEmails" v-model:bcc-emails="bccEmails"
v-model:to-emails="toEmails" v-model:to-emails="toEmails"
/> />
<WootAudioRecorder <AudioRecorder
v-if="showAudioRecorderEditor" v-if="showAudioRecorderEditor"
ref="audioRecorderInput" ref="audioRecorderInput"
:audio-record-format="audioRecordFormat" :audio-record-format="audioRecordFormat"
@recorder-progress-changed="onRecordProgressChanged" @recorder-progress-changed="onRecordProgressChanged"
@state-recorder-changed="onStateRecorderChanged"
@finish-record="onFinishRecorder" @finish-record="onFinishRecorder"
@play="recordingAudioState = 'playing'"
@pause="recordingAudioState = 'paused'"
/> />
<ResizableTextArea <ResizableTextArea
v-else-if="!showRichContentEditor" v-else-if="!showRichContentEditor"
@@ -1169,7 +1167,11 @@ export default {
@clear-selection="clearEditorSelection" @clear-selection="clearEditorSelection"
/> />
</div> </div>
<div v-if="hasAttachments" class="attachment-preview-box" @paste="onPaste"> <div
v-if="hasAttachments && !showAudioRecorderEditor"
class="attachment-preview-box"
@paste="onPaste"
>
<AttachmentPreview <AttachmentPreview
class="flex-col mt-4" class="flex-col mt-4"
:attachments="attachedFiles" :attachments="attachedFiles"