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:
@@ -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>
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user