diff --git a/app/javascript/dashboard/components/widgets/AttachmentsPreview.vue b/app/javascript/dashboard/components/widgets/AttachmentsPreview.vue index 250c5c3c6..3d5846215 100644 --- a/app/javascript/dashboard/components/widgets/AttachmentsPreview.vue +++ b/app/javascript/dashboard/components/widgets/AttachmentsPreview.vue @@ -25,6 +25,7 @@
+
+
+
+ + + + + diff --git a/app/javascript/dashboard/components/widgets/WootWriter/ReplyBottomPanel.vue b/app/javascript/dashboard/components/widgets/WootWriter/ReplyBottomPanel.vue index 6df1b2858..6dcb47532 100644 --- a/app/javascript/dashboard/components/widgets/WootWriter/ReplyBottomPanel.vue +++ b/app/javascript/dashboard/components/widgets/WootWriter/ReplyBottomPanel.vue @@ -11,7 +11,6 @@ size="small" @click="toggleEmojiPicker" /> - + + + {{ recordingAudioDurationText }} + ({}), @@ -134,6 +158,10 @@ export default { type: Boolean, default: false, }, + showAudioRecorder: { + type: Boolean, + default: false, + }, onFileUpload: { type: Function, default: () => {}, @@ -146,6 +174,22 @@ export default { type: Function, default: () => {}, }, + toggleAudioRecorder: { + type: Function, + default: () => {}, + }, + toggleAudioRecorderPlayPause: { + type: Function, + default: () => {}, + }, + isRecordingAudio: { + type: Boolean, + default: false, + }, + recordingAudioState: { + type: String, + default: '', + }, isSendDisabled: { type: Boolean, default: false, @@ -192,9 +236,28 @@ export default { showAttachButton() { return this.showFileUpload || this.isNote; }, + showAudioRecorderButton() { + return this.showAudioRecorder; + }, + showAudioPlayStopButton() { + return this.showAudioRecorder && this.isRecordingAudio; + }, allowedFileTypes() { return ALLOWED_FILE_TYPES; }, + audioRecorderPlayStopIcon() { + switch (this.recordingAudioState) { + // playing paused recording stopped inactive destroyed + case 'playing': + return 'microphone-pause'; + case 'paused': + return 'microphone-play'; + case 'stopped': + return 'microphone-play'; + default: + return 'microphone-stop'; + } + }, showMessageSignatureButton() { return !this.isPrivate && this.isAnEmailChannel; }, diff --git a/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue b/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue index 37fbb4367..5a9253fba 100644 --- a/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue +++ b/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue @@ -33,8 +33,15 @@ :cc-emails.sync="ccEmails" :bcc-emails.sync="bccEmails" /> + this.$refs.messageInput.focus()); @@ -535,10 +568,26 @@ export default { this.attachedFiles = []; this.ccEmails = ''; this.bccEmails = ''; + this.isRecordingAudio = false; }, toggleEmojiPicker() { this.showEmojiPicker = !this.showEmojiPicker; }, + toggleAudioRecorder() { + this.isRecordingAudio = !this.isRecordingAudio; + this.isRecorderAudioStopped = !this.isRecordingAudio; + if (!this.isRecordingAudio) { + this.clearMessage(); + } + }, + toggleAudioRecorderPlayPause() { + if (this.isRecordingAudio && !this.isRecorderAudioStopped) { + this.isRecorderAudioStopped = true; + this.$refs.audioRecorderInput.stopAudioRecording(); + } else if (this.isRecordingAudio && this.isRecorderAudioStopped) { + this.$refs.audioRecorderInput.playPause(); + } + }, hideEmojiPicker() { if (this.showEmojiPicker) { this.toggleEmojiPicker(); @@ -559,6 +608,20 @@ export default { onFocus() { this.isFocused = true; }, + onStateRecorderTimerChanged(time) { + this.recordingAudioDuration = time; + }, + onStateRecorderChanged(state) { + this.recordingAudioState = state; + if (state.includes('notallowederror')) { + this.toggleAudioRecorder(); + } + }, + onRecorderBlob(file) { + if (file) { + this.onFileUpload(file); + } + }, toggleTyping(status) { const conversationId = this.currentChat.id; const isPrivate = this.isPrivate; diff --git a/app/javascript/dashboard/i18n/locale/en/conversation.json b/app/javascript/dashboard/i18n/locale/en/conversation.json index 96ba4b429..ac25a5aed 100644 --- a/app/javascript/dashboard/i18n/locale/en/conversation.json +++ b/app/javascript/dashboard/i18n/locale/en/conversation.json @@ -74,8 +74,14 @@ "TIP_FORMAT_ICON": "Show rich text editor", "TIP_EMOJI_ICON": "Show emoji selector", "TIP_ATTACH_ICON": "Attach files", + "TIP_AUDIORECORDER_ICON": "Record audio", + "TIP_AUDIORECORDER_PERMISSION": "Allow access to audio", + "TIP_AUDIORECORDER_ERROR": "Could not open the audio", "ENTER_TO_SEND": "Enter to send", "DRAG_DROP": "Drag and drop here to attach", + "START_AUDIO_RECORDING": "Start audio recording", + "STOP_AUDIO_RECORDING": "Stop audio recording", + "": "", "EMAIL_HEAD": { "ADD_BCC": "Add bcc", "CC": { diff --git a/app/javascript/shared/components/FluentIcon/dashboard-icons.json b/app/javascript/shared/components/FluentIcon/dashboard-icons.json index 39064412c..0cbc85d22 100644 --- a/app/javascript/shared/components/FluentIcon/dashboard-icons.json +++ b/app/javascript/shared/components/FluentIcon/dashboard-icons.json @@ -85,6 +85,11 @@ "merge-outline": "M3 6.75A.75.75 0 0 1 3.75 6h4.5a.75.75 0 0 1 .53.22L13.56 11h5.878L15.72 7.28a.75.75 0 1 1 1.06-1.06l4.998 5a.75.75 0 0 1 0 1.06l-4.998 5a.75.75 0 1 1-1.06-1.06l3.718-3.72H13.56l-4.78 4.78a.75.75 0 0 1-.531.22h-4.5a.75.75 0 0 1 0-1.5h4.19l4.25-4.25L7.94 7.5H3.75A.75.75 0 0 1 3 6.75Z", "more-horizontal-outline": "M7.75 12a1.75 1.75 0 1 1-3.5 0 1.75 1.75 0 0 1 3.5 0ZM13.75 12a1.75 1.75 0 1 1-3.5 0 1.75 1.75 0 0 1 3.5 0ZM18 13.75a1.75 1.75 0 1 0 0-3.5 1.75 1.75 0 0 0 0 3.5Z", "more-vertical-outline": "M12 7.75a1.75 1.75 0 1 1 0-3.5 1.75 1.75 0 0 1 0 3.5ZM12 13.75a1.75 1.75 0 1 1 0-3.5 1.75 1.75 0 0 1 0 3.5ZM10.25 18a1.75 1.75 0 1 0 3.5 0 1.75 1.75 0 0 0-3.5 0Z", + "microphone-outline": "M12,2A3,3 0 0,1 15,5V11A3,3 0 0,1 12,14A3,3 0 0,1 9,11V5A3,3 0 0,1 12,2M19,11C19,14.53 16.39,17.44 13,17.93V21H11V17.93C7.61,17.44 5,14.53 5,11H7A5,5 0 0,0 12,16A5,5 0 0,0 17,11H19Z", + "microphone-off-outline": "M19,11C19,12.19 18.66,13.3 18.1,14.28L16.87,13.05C17.14,12.43 17.3,11.74 17.3,11H19M15,11.16L9,5.18V5A3,3 0 0,1 12,2A3,3 0 0,1 15,5V11L15,11.16M4.27,3L21,19.73L19.73,21L15.54,16.81C14.77,17.27 13.91,17.58 13,17.72V21H11V17.72C7.72,17.23 5,14.41 5,11H6.7C6.7,14 9.24,16.1 12,16.1C12.81,16.1 13.6,15.91 14.31,15.58L12.65,13.92L12,14A3,3 0 0,1 9,11V10.28L3,4.27L4.27,3Z", + "microphone-stop-outline": "M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4M9,9V15H15V9", + "microphone-pause-outline": "M13,16V8H15V16H13M9,16V8H11V16H9M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4Z", + "microphone-play-outline": "M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M10,16.5L16,12L10,7.5V16.5Z", "number-symbol-outline": "M10.987 2.89a.75.75 0 1 0-1.474-.28L8.494 7.999 3.75 8a.75.75 0 1 0 0 1.5l4.46-.002-.946 5-4.514.002a.75.75 0 0 0 0 1.5l4.23-.002-.967 5.116a.75.75 0 1 0 1.474.278l1.02-5.395 5.474-.002-.968 5.119a.75.75 0 1 0 1.474.278l1.021-5.398 4.742-.002a.75.75 0 1 0 0-1.5l-4.458.002.946-5 4.512-.002a.75.75 0 1 0 0-1.5l-4.229.002.966-5.104a.75.75 0 0 0-1.474-.28l-1.018 5.385-5.474.002.966-5.107Zm-1.25 6.608 5.474-.003-.946 5-5.474.002.946-5Z", "open-outline": "M6.25 4.5A1.75 1.75 0 0 0 4.5 6.25v11.5c0 .966.783 1.75 1.75 1.75h11.5a1.75 1.75 0 0 0 1.75-1.75v-4a.75.75 0 0 1 1.5 0v4A3.25 3.25 0 0 1 17.75 21H6.25A3.25 3.25 0 0 1 3 17.75V6.25A3.25 3.25 0 0 1 6.25 3h4a.75.75 0 0 1 0 1.5h-4ZM13 3.75a.75.75 0 0 1 .75-.75h6.5a.75.75 0 0 1 .75.75v6.5a.75.75 0 0 1-1.5 0V5.56l-5.22 5.22a.75.75 0 0 1-1.06-1.06l5.22-5.22h-4.69a.75.75 0 0 1-.75-.75Z", "people-outline": "M4 13.999 13 14a2 2 0 0 1 1.995 1.85L15 16v1.5C14.999 21 11.284 22 8.5 22c-2.722 0-6.335-.956-6.495-4.27L2 17.5v-1.501c0-1.054.816-1.918 1.85-1.995L4 14ZM15.22 14H20c1.054 0 1.918.816 1.994 1.85L22 16v1c-.001 3.062-2.858 4-5 4a7.16 7.16 0 0 1-2.14-.322c.336-.386.607-.827.802-1.327A6.19 6.19 0 0 0 17 19.5l.267-.006c.985-.043 3.086-.363 3.226-2.289L20.5 17v-1a.501.501 0 0 0-.41-.492L20 15.5h-4.051a2.957 2.957 0 0 0-.595-1.34L15.22 14H20h-4.78ZM4 15.499l-.1.01a.51.51 0 0 0-.254.136.506.506 0 0 0-.136.253l-.01.101V17.5c0 1.009.45 1.722 1.417 2.242.826.445 2.003.714 3.266.753l.317.005.317-.005c1.263-.039 2.439-.308 3.266-.753.906-.488 1.359-1.145 1.412-2.057l.005-.186V16a.501.501 0 0 0-.41-.492L13 15.5l-9-.001ZM8.5 3a4.5 4.5 0 1 1 0 9 4.5 4.5 0 0 1 0-9Zm9 2a3.5 3.5 0 1 1 0 7 3.5 3.5 0 0 1 0-7Zm-9-.5c-1.654 0-3 1.346-3 3s1.346 3 3 3 3-1.346 3-3-1.346-3-3-3Zm9 2c-1.103 0-2 .897-2 2s.897 2 2 2 2-.897 2-2-.897-2-2-2Z", diff --git a/app/javascript/shared/helpers/FileHelper.js b/app/javascript/shared/helpers/FileHelper.js index a784b2ba2..d9ca9f943 100644 --- a/app/javascript/shared/helpers/FileHelper.js +++ b/app/javascript/shared/helpers/FileHelper.js @@ -19,7 +19,7 @@ export const fileSizeInMegaBytes = bytes => { }; export const checkFileSizeLimit = (file, maximumUploadLimit) => { - const fileSize = file?.file?.size; + const fileSize = file?.file?.size || file?.size; const fileSizeInMB = fileSizeInMegaBytes(fileSize); return fileSizeInMB <= maximumUploadLimit; }; diff --git a/package.json b/package.json index 1703f63e0..4f794fa6d 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "prosemirror-state": "1.3.4", "prosemirror-view": "1.18.4", "query-string": "5", + "recordrtc": "^5.6.2", "semver": "7.3.5", "spinkit": "~1.2.5", "tailwindcss": "^1.9.6", @@ -71,7 +72,8 @@ "vuedraggable": "^2.24.3", "vuelidate": "0.7.6", "vuex": "~2.1.1", - "vuex-router-sync": "~4.1.2" + "vuex-router-sync": "~4.1.2", + "wavesurfer.js": "^5.2.0" }, "devDependencies": { "@babel/core": "7.13.16",