feat: Ability to send voice message to channel (#4064)
Created the possibility to send audio as an attachment via the dashboard. The channels that can send audio are the same channels that can send any type of attachment. Used RecordRTC (https://github.com/muaz-khan/RecordRTC) to capture the audio and Wavesurfer (https://github.com/katspaugh/wavesurfer.js) to display the audio waves. RecordRTC can be used to record videos if necessary. Fixes #1973
This commit is contained in:
parent
b94e67f5d7
commit
96b719017b
8 changed files with 372 additions and 5 deletions
|
@ -25,6 +25,7 @@
|
|||
</div>
|
||||
<div class="remove-file-wrap">
|
||||
<woot-button
|
||||
v-if="!isTypeAudio(attachment.resource)"
|
||||
class="remove--attachment clear secondary"
|
||||
icon="dismiss"
|
||||
@click="() => onRemoveAttachment(index)"
|
||||
|
@ -58,6 +59,10 @@ export default {
|
|||
const type = file.content_type || file.type;
|
||||
return type.includes('image');
|
||||
},
|
||||
isTypeAudio(file) {
|
||||
const type = file.content_type || file.type;
|
||||
return type.includes('audio');
|
||||
},
|
||||
fileName(file) {
|
||||
return file.filename || file.name;
|
||||
},
|
||||
|
|
|
@ -0,0 +1,223 @@
|
|||
<template>
|
||||
<div class="audio-wave-wrapper">
|
||||
<div id="audio-wave"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import WaveSurfer from 'wavesurfer.js';
|
||||
import MicrophonePlugin from 'wavesurfer.js/dist/plugin/wavesurfer.microphone.js';
|
||||
import RecordRTC from 'recordrtc';
|
||||
import inboxMixin from '../../../../shared/mixins/inboxMixin';
|
||||
import alertMixin from '../../../../shared/mixins/alertMixin';
|
||||
|
||||
WaveSurfer.microphone = MicrophonePlugin;
|
||||
|
||||
export default {
|
||||
name: 'WootAudioRecorder',
|
||||
mixins: [inboxMixin, alertMixin],
|
||||
data() {
|
||||
return {
|
||||
wavesurfer: false,
|
||||
recorder: false,
|
||||
recordingInterval: false,
|
||||
recordingDateStarted: new Date().getTime(),
|
||||
timeDuration: '00:00',
|
||||
initialTimeDuration: '00:00',
|
||||
options: {
|
||||
container: '#audio-wave',
|
||||
backend: 'WebAudio',
|
||||
interact: true,
|
||||
cursorWidth: 1,
|
||||
plugins: [
|
||||
WaveSurfer.microphone.create({
|
||||
bufferSize: 4096,
|
||||
numberOfInputChannels: 1,
|
||||
numberOfOutputChannels: 1,
|
||||
constraints: {
|
||||
video: false,
|
||||
audio: true,
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
optionsRecorder: {
|
||||
type: 'audio',
|
||||
mimeType: 'audio/wav',
|
||||
disableLogs: true,
|
||||
recorderType: RecordRTC.StereoAudioRecorder,
|
||||
sampleRate: 44100,
|
||||
numberOfAudioChannels: 2,
|
||||
checkForInactiveTracks: true,
|
||||
bufferSize: 4096,
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isRecording() {
|
||||
if (this.recorder) {
|
||||
return this.recorder.getState() === 'recording';
|
||||
}
|
||||
return false;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.wavesurfer = WaveSurfer.create(this.options);
|
||||
this.wavesurfer.on('play', this.playingRecorder);
|
||||
this.wavesurfer.on('pause', this.pausedRecorder);
|
||||
this.wavesurfer.microphone.on('deviceReady', this.startRecording);
|
||||
this.wavesurfer.microphone.on('deviceError', this.deviceError);
|
||||
this.wavesurfer.microphone.start();
|
||||
this.fireStateRecorderTimerChanged(this.initialTimeDuration);
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.recorder) {
|
||||
this.recorder.destroy();
|
||||
}
|
||||
if (this.wavesurfer) {
|
||||
this.wavesurfer.destroy();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
startRecording(stream) {
|
||||
this.recorder = RecordRTC(stream, this.optionsRecorder);
|
||||
this.recorder.onStateChanged = this.onStateRecorderChanged;
|
||||
this.recorder.startRecording();
|
||||
},
|
||||
stopAudioRecording() {
|
||||
if (this.isRecording) {
|
||||
this.recorder.stopRecording(() => {
|
||||
this.wavesurfer.microphone.stopDevice();
|
||||
this.wavesurfer.loadBlob(this.recorder.getBlob());
|
||||
this.wavesurfer.stop();
|
||||
this.fireRecorderBlob(this.getAudioFile());
|
||||
});
|
||||
}
|
||||
},
|
||||
getAudioFile() {
|
||||
if (this.hasAudio()) {
|
||||
return new File([this.recorder.getBlob()], this.getAudioFileName(), {
|
||||
type: 'audio/wav',
|
||||
});
|
||||
}
|
||||
return false;
|
||||
},
|
||||
hasAudio() {
|
||||
return !(this.isRecording || this.wavesurfer.isPlaying());
|
||||
},
|
||||
playingRecorder() {
|
||||
this.fireStateRecorderChanged('playing');
|
||||
},
|
||||
pausedRecorder() {
|
||||
this.fireStateRecorderChanged('paused');
|
||||
},
|
||||
deviceError(err) {
|
||||
if (
|
||||
err?.name &&
|
||||
(err.name.toLowerCase().includes('notallowederror') ||
|
||||
err.name.toLowerCase().includes('permissiondeniederror'))
|
||||
) {
|
||||
this.showAlert(
|
||||
this.$t('CONVERSATION.REPLYBOX.TIP_AUDIORECORDER_PERMISSION')
|
||||
);
|
||||
this.fireStateRecorderChanged('notallowederror');
|
||||
} else {
|
||||
this.showAlert(
|
||||
this.$t('CONVERSATION.REPLYBOX.TIP_AUDIORECORDER_ERROR')
|
||||
);
|
||||
}
|
||||
},
|
||||
onStateRecorderChanged(state) {
|
||||
// recording stopped inactive destroyed
|
||||
switch (state) {
|
||||
case 'recording':
|
||||
this.timerDurationChanged();
|
||||
break;
|
||||
case 'stopped':
|
||||
this.timerDurationChanged();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this.fireStateRecorderChanged(state);
|
||||
},
|
||||
timerDurationChanged() {
|
||||
if (this.isRecording) {
|
||||
this.recordingInterval = setInterval(() => {
|
||||
this.calculateTimeDuration(
|
||||
(new Date().getTime() - this.recordingDateStarted) / 1000
|
||||
);
|
||||
this.fireStateRecorderTimerChanged(this.timeDuration);
|
||||
}, 1000);
|
||||
} else {
|
||||
clearInterval(this.recordingInterval);
|
||||
}
|
||||
},
|
||||
calculateTimeDuration(secs) {
|
||||
let hr = Math.floor(secs / 3600);
|
||||
let min = Math.floor((secs - hr * 3600) / 60);
|
||||
let sec = Math.floor(secs - hr * 3600 - min * 60);
|
||||
if (min < 10) {
|
||||
min = '0' + min;
|
||||
}
|
||||
if (sec < 10) {
|
||||
sec = '0' + sec;
|
||||
}
|
||||
if (hr <= 0) {
|
||||
this.timeDuration = min + ':' + sec;
|
||||
} else {
|
||||
if (hr < 10) {
|
||||
hr = '0' + hr;
|
||||
}
|
||||
this.timeDuration = hr + ':' + min + ':' + sec;
|
||||
}
|
||||
},
|
||||
playPause() {
|
||||
this.wavesurfer.playPause();
|
||||
},
|
||||
fireRecorderBlob(blob) {
|
||||
this.$emit('recorder-blob', {
|
||||
name: blob.name,
|
||||
type: blob.type,
|
||||
size: blob.size,
|
||||
file: blob,
|
||||
});
|
||||
},
|
||||
fireStateRecorderChanged(state) {
|
||||
this.$emit('state-recorder-changed', state);
|
||||
},
|
||||
fireStateRecorderTimerChanged(duration) {
|
||||
this.$emit('state-recorder-timer-changed', duration);
|
||||
},
|
||||
getAudioFileName() {
|
||||
const d = new Date();
|
||||
return `audio-${d.getFullYear()}-${d.getMonth()}-${d.getDate()}-${this.getRandomString()}.wav`;
|
||||
},
|
||||
getRandomString() {
|
||||
if (
|
||||
window.crypto &&
|
||||
window.crypto.getRandomValues &&
|
||||
navigator.userAgent.indexOf('Safari') === -1
|
||||
) {
|
||||
let a = window.crypto.getRandomValues(new Uint32Array(3));
|
||||
let token = '';
|
||||
for (let i = 0, l = a.length; i < l; i += 1) {
|
||||
token += a[i].toString(36);
|
||||
}
|
||||
return token.toLowerCase();
|
||||
}
|
||||
return (Math.random() * new Date().getTime())
|
||||
.toString(36)
|
||||
.replace(/\./g, '');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.audio-wave-wrapper {
|
||||
min-height: 8rem;
|
||||
max-height: 12rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
|
@ -11,7 +11,6 @@
|
|||
size="small"
|
||||
@click="toggleEmojiPicker"
|
||||
/>
|
||||
|
||||
<!-- ensure the same validations for attachment types are implemented in backend models as well -->
|
||||
<file-upload
|
||||
ref="upload"
|
||||
|
@ -49,6 +48,27 @@
|
|||
:title="$t('CONVERSATION.REPLYBOX.TIP_FORMAT_ICON')"
|
||||
@click="toggleFormatMode"
|
||||
/>
|
||||
<woot-button
|
||||
v-if="showAudioRecorderButton"
|
||||
:icon="!isRecordingAudio ? 'microphone' : 'microphone-off'"
|
||||
emoji="🎤"
|
||||
:color-scheme="!isRecordingAudio ? 'secondary' : 'alert'"
|
||||
variant="smooth"
|
||||
size="small"
|
||||
:title="$t('CONVERSATION.REPLYBOX.TIP_AUDIORECORDER_ICON')"
|
||||
@click="toggleAudioRecorder"
|
||||
/>
|
||||
<woot-button
|
||||
v-if="showAudioPlayStopButton"
|
||||
:icon="audioRecorderPlayStopIcon"
|
||||
emoji="🎤"
|
||||
color-scheme="secondary"
|
||||
variant="smooth"
|
||||
size="small"
|
||||
@click="toggleAudioRecorderPlayPause"
|
||||
>
|
||||
<span>{{ recordingAudioDurationText }}</span>
|
||||
</woot-button>
|
||||
<woot-button
|
||||
v-if="showMessageSignatureButton"
|
||||
v-tooltip.top-end="signatureToggleTooltip"
|
||||
|
@ -126,6 +146,10 @@ export default {
|
|||
type: String,
|
||||
default: '',
|
||||
},
|
||||
recordingAudioDurationText: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
inbox: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
|
@ -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;
|
||||
},
|
||||
|
|
|
@ -33,8 +33,15 @@
|
|||
:cc-emails.sync="ccEmails"
|
||||
:bcc-emails.sync="bccEmails"
|
||||
/>
|
||||
<woot-audio-recorder
|
||||
v-if="showAudioRecorderEditor"
|
||||
ref="audioRecorderInput"
|
||||
@state-recorder-timer-changed="onStateRecorderTimerChanged"
|
||||
@state-recorder-changed="onStateRecorderChanged"
|
||||
@recorder-blob="onRecorderBlob"
|
||||
/>
|
||||
<resizable-text-area
|
||||
v-if="!showRichContentEditor"
|
||||
v-else-if="!showRichContentEditor"
|
||||
ref="messageInput"
|
||||
v-model="message"
|
||||
class="input"
|
||||
|
@ -89,10 +96,16 @@
|
|||
: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="sendMessage"
|
||||
:is-send-disabled="isReplyButtonDisabled"
|
||||
:recording-audio-duration-text="recordingAudioDuration"
|
||||
:recording-audio-state="recordingAudioState"
|
||||
:is-recording-audio="isRecordingAudio"
|
||||
:set-format-mode="setFormatMode"
|
||||
:is-on-private-note="isOnPrivateNote"
|
||||
:is-format-mode="showRichContentEditor"
|
||||
|
@ -119,6 +132,7 @@ import ReplyBottomPanel from 'dashboard/components/widgets/WootWriter/ReplyBotto
|
|||
import Banner from 'dashboard/components/ui/Banner.vue';
|
||||
import { REPLY_EDITOR_MODES } from 'dashboard/components/widgets/WootWriter/constants';
|
||||
import WootMessageEditor from 'dashboard/components/widgets/WootWriter/Editor';
|
||||
import WootAudioRecorder from 'dashboard/components/widgets/WootWriter/AudioRecorder';
|
||||
import messageFormatterMixin from 'shared/mixins/messageFormatterMixin';
|
||||
import { checkFileSizeLimit } from 'shared/helpers/FileHelper';
|
||||
import { MAXIMUM_FILE_UPLOAD_SIZE } from 'shared/constants/messages';
|
||||
|
@ -146,6 +160,7 @@ export default {
|
|||
ReplyEmailHead,
|
||||
ReplyBottomPanel,
|
||||
WootMessageEditor,
|
||||
WootAudioRecorder,
|
||||
Banner,
|
||||
},
|
||||
mixins: [
|
||||
|
@ -176,6 +191,9 @@ export default {
|
|||
showEmojiPicker: false,
|
||||
showMentions: false,
|
||||
attachedFiles: [],
|
||||
isRecordingAudio: false,
|
||||
recordingAudioState: '',
|
||||
recordingAudioDuration: '',
|
||||
isUploading: false,
|
||||
replyType: REPLY_EDITOR_MODES.REPLY,
|
||||
mentionSearchKey: '',
|
||||
|
@ -270,7 +288,7 @@ export default {
|
|||
return true;
|
||||
}
|
||||
|
||||
if (this.hasAttachments) return false;
|
||||
if (this.hasAttachments || this.hasRecordedAudio) return false;
|
||||
|
||||
return (
|
||||
this.isMessageEmpty ||
|
||||
|
@ -332,9 +350,21 @@ export default {
|
|||
hasAttachments() {
|
||||
return this.attachedFiles.length;
|
||||
},
|
||||
hasRecordedAudio() {
|
||||
return (
|
||||
this.$refs.audioRecorderInput &&
|
||||
this.$refs.audioRecorderInput.hasAudio()
|
||||
);
|
||||
},
|
||||
isRichEditorEnabled() {
|
||||
return this.isAWebWidgetInbox || this.isAnEmailChannel;
|
||||
},
|
||||
showAudioRecorder() {
|
||||
return !this.isOnPrivateNote && this.showFileUpload;
|
||||
},
|
||||
showAudioRecorderEditor() {
|
||||
return this.showAudioRecorder && this.isRecordingAudio;
|
||||
},
|
||||
isOnPrivateNote() {
|
||||
return this.replyType === REPLY_EDITOR_MODES.NOTE;
|
||||
},
|
||||
|
@ -523,6 +553,9 @@ export default {
|
|||
|
||||
if (canReply || this.isAWhatsappChannel) this.replyType = mode;
|
||||
if (this.showRichContentEditor) {
|
||||
if (this.isRecordingAudio) {
|
||||
this.toggleAudioRecorder();
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.$nextTick(() => 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;
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue