Files
chatwoot/app/javascript/dashboard/components/widgets/WootWriter/ReplyBottomPanel.vue
Shivam Mishra c51a458c25 style: apply fixes for eslint issues [cw-3590] (#10210)
These fixes are all auto generated and can be merged directly

Fixes the following issues

1. Event used on components should be hypenated
2. Attribute orders in components
3. Use `unmounted` instead of `destroyed`
4. Add explicit `emits` declarations for components, autofixed [using
this
script](https://gist.github.com/scmmishra/6f549109b96400006bb69bbde392eddf)


We ignore the top level v-if for now, we will fix it later
2024-10-03 15:02:12 +05:30

413 lines
11 KiB
Vue

<script>
import { useUISettings } from 'dashboard/composables/useUISettings';
import { useKeyboardEvents } from 'dashboard/composables/useKeyboardEvents';
import FileUpload from 'vue-upload-component';
import * as ActiveStorage from 'activestorage';
import inboxMixin from 'shared/mixins/inboxMixin';
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
import {
ALLOWED_FILE_TYPES,
ALLOWED_FILE_TYPES_FOR_TWILIO_WHATSAPP,
ALLOWED_FILE_TYPES_FOR_LINE,
} from 'shared/constants/messages';
import VideoCallButton from '../VideoCallButton.vue';
import AIAssistanceButton from '../AIAssistanceButton.vue';
import { REPLY_EDITOR_MODES } from './constants';
import { mapGetters } from 'vuex';
export default {
name: 'ReplyBottomPanel',
components: { FileUpload, VideoCallButton, AIAssistanceButton },
mixins: [inboxMixin],
props: {
mode: {
type: String,
default: REPLY_EDITOR_MODES.REPLY,
},
onSend: {
type: Function,
default: () => {},
},
sendButtonText: {
type: String,
default: '',
},
recordingAudioDurationText: {
type: String,
default: '',
},
// inbox prop is used in /mixins/inboxMixin,
// remove this props when refactoring to composable if not needed
// eslint-disable-next-line vue/no-unused-properties
inbox: {
type: Object,
default: () => ({}),
},
showFileUpload: {
type: Boolean,
default: false,
},
showAudioRecorder: {
type: Boolean,
default: false,
},
onFileUpload: {
type: Function,
default: () => {},
},
toggleEmojiPicker: {
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,
},
showEditorToggle: {
type: Boolean,
default: false,
},
isOnPrivateNote: {
type: Boolean,
default: false,
},
enableMultipleFileUpload: {
type: Boolean,
default: true,
},
hasWhatsappTemplates: {
type: Boolean,
default: false,
},
conversationId: {
type: Number,
required: true,
},
message: {
type: String,
default: '',
},
newConversationModalActive: {
type: Boolean,
default: false,
},
portalSlug: {
type: String,
required: true,
},
},
emits: [
'replaceText',
'toggleInsertArticle',
'toggleEditor',
'selectWhatsappTemplate',
],
setup() {
const { setSignatureFlagForInbox, fetchSignatureFlagFromUISettings } =
useUISettings();
const keyboardEvents = {
'Alt+KeyA': {
action: () => {
// TODO: This is really hacky, we need to replace the file picker component with
// a custom one, where the logic and the component markup is isolated.
// Once we have the custom component, we can remove the hacky logic below.
const uploadTriggerButton = document.querySelector(
'#conversationAttachment'
);
uploadTriggerButton.click();
},
allowOnFocusedInput: true,
},
};
useKeyboardEvents(keyboardEvents);
return {
setSignatureFlagForInbox,
fetchSignatureFlagFromUISettings,
};
},
computed: {
...mapGetters({
accountId: 'getCurrentAccountId',
isFeatureEnabledonAccount: 'accounts/isFeatureEnabledonAccount',
uiFlags: 'integrations/getUIFlags',
}),
isNote() {
return this.mode === REPLY_EDITOR_MODES.NOTE;
},
wrapClass() {
return {
'is-note-mode': this.isNote,
};
},
buttonClass() {
return {
warning: this.isNote,
};
},
showAttachButton() {
return this.showFileUpload || this.isNote;
},
showAudioRecorderButton() {
if (this.isALineChannel) {
return false;
}
// Disable audio recorder for safari browser as recording is not supported
// const isSafari = /^((?!chrome|android|crios|fxios).)*safari/i.test(
// navigator.userAgent
// );
return (
this.isFeatureEnabledonAccount(
this.accountId,
FEATURE_FLAGS.VOICE_RECORDER
) && this.showAudioRecorder
// !isSafari
);
},
showAudioPlayStopButton() {
return this.showAudioRecorder && this.isRecordingAudio;
},
allowedFileTypes() {
if (this.isATwilioWhatsAppChannel) {
return ALLOWED_FILE_TYPES_FOR_TWILIO_WHATSAPP;
}
if (this.isALineChannel) {
return ALLOWED_FILE_TYPES_FOR_LINE;
}
return ALLOWED_FILE_TYPES;
},
enableDragAndDrop() {
return !this.newConversationModalActive;
},
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.isOnPrivateNote;
},
sendWithSignature() {
// channelType is sourced from inboxMixin
return this.fetchSignatureFlagFromUISettings(this.channelType);
},
signatureToggleTooltip() {
return this.sendWithSignature
? this.$t('CONVERSATION.FOOTER.DISABLE_SIGN_TOOLTIP')
: this.$t('CONVERSATION.FOOTER.ENABLE_SIGN_TOOLTIP');
},
enableInsertArticleInReply() {
return this.portalSlug;
},
isFetchingAppIntegrations() {
return this.uiFlags.isFetching;
},
},
mounted() {
ActiveStorage.start();
},
methods: {
toggleMessageSignature() {
this.setSignatureFlagForInbox(this.channelType, !this.sendWithSignature);
},
replaceText(text) {
this.$emit('replaceText', text);
},
toggleInsertArticle() {
this.$emit('toggleInsertArticle');
},
},
};
</script>
<template>
<div class="bottom-box" :class="wrapClass">
<div class="left-wrap">
<woot-button
v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_EMOJI_ICON')"
:title="$t('CONVERSATION.REPLYBOX.TIP_EMOJI_ICON')"
icon="emoji"
emoji="😊"
color-scheme="secondary"
variant="smooth"
size="small"
@click="toggleEmojiPicker"
/>
<FileUpload
v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_ATTACH_ICON')"
input-id="conversationAttachment"
:size="4096 * 4096"
:accept="allowedFileTypes"
:multiple="enableMultipleFileUpload"
:drop="enableDragAndDrop"
:drop-directory="false"
:data="{
direct_upload_url: '/rails/active_storage/direct_uploads',
direct_upload: true,
}"
@input-file="onFileUpload"
>
<woot-button
v-if="showAttachButton"
class-names="button--upload"
:title="$t('CONVERSATION.REPLYBOX.TIP_ATTACH_ICON')"
icon="attach"
emoji="📎"
color-scheme="secondary"
variant="smooth"
size="small"
/>
</FileUpload>
<woot-button
v-if="showAudioRecorderButton"
v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_AUDIORECORDER_ICON')"
:icon="!isRecordingAudio ? 'microphone' : 'microphone-off'"
emoji="🎤"
:color-scheme="!isRecordingAudio ? 'secondary' : 'alert'"
variant="smooth"
size="small"
@click="toggleAudioRecorder"
/>
<woot-button
v-if="showEditorToggle"
v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_FORMAT_ICON')"
icon="quote"
emoji="🖊️"
color-scheme="secondary"
variant="smooth"
size="small"
@click="$emit('toggleEditor')"
/>
<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"
icon="signature"
color-scheme="secondary"
variant="smooth"
size="small"
:title="signatureToggleTooltip"
@click="toggleMessageSignature"
/>
<woot-button
v-if="hasWhatsappTemplates"
v-tooltip.top-end="$t('CONVERSATION.FOOTER.WHATSAPP_TEMPLATES')"
icon="whatsapp"
color-scheme="secondary"
variant="smooth"
size="small"
:title="$t('CONVERSATION.FOOTER.WHATSAPP_TEMPLATES')"
@click="$emit('selectWhatsappTemplate')"
/>
<VideoCallButton
v-if="(isAWebWidgetInbox || isAPIInbox) && !isOnPrivateNote"
:conversation-id="conversationId"
/>
<AIAssistanceButton
v-if="!isFetchingAppIntegrations"
:conversation-id="conversationId"
:is-private-note="isOnPrivateNote"
:message="message"
@replace-text="replaceText"
/>
<transition name="modal-fade">
<div
v-show="$refs.uploadRef && $refs.uploadRef.dropActive"
class="fixed top-0 bottom-0 left-0 right-0 z-20 flex flex-col items-center justify-center w-full h-full gap-2 text-slate-900 dark:text-slate-50 bg-modal-backdrop-light dark:bg-modal-backdrop-dark"
>
<fluent-icon icon="cloud-backup" size="40" />
<h4 class="text-2xl break-words text-slate-900 dark:text-slate-50">
{{ $t('CONVERSATION.REPLYBOX.DRAG_DROP') }}
</h4>
</div>
</transition>
<woot-button
v-if="enableInsertArticleInReply"
v-tooltip.top-end="$t('HELP_CENTER.ARTICLE_SEARCH.OPEN_ARTICLE_SEARCH')"
icon="document-text-link"
color-scheme="secondary"
variant="smooth"
size="small"
:title="$t('HELP_CENTER.ARTICLE_SEARCH.OPEN_ARTICLE_SEARCH')"
@click="toggleInsertArticle"
/>
</div>
<div class="right-wrap">
<woot-button
size="small"
:class-names="buttonClass"
:is-disabled="isSendDisabled"
@click="onSend"
>
{{ sendButtonText }}
</woot-button>
</div>
</div>
</template>
<style lang="scss" scoped>
.bottom-box {
@apply flex justify-between py-3 px-4;
&.is-note-mode {
@apply bg-yellow-100 dark:bg-yellow-800;
}
}
.left-wrap {
@apply items-center flex gap-2;
}
.right-wrap {
@apply flex;
}
::v-deep .file-uploads {
label {
@apply cursor-pointer;
}
&:hover button {
@apply dark:bg-slate-800 bg-slate-100;
}
}
</style>