feat: Attachment preview

This commit is contained in:
iamsivin
2024-11-18 23:47:58 +05:30
parent 2e30d4b825
commit 610b05ed5a
7 changed files with 200 additions and 12 deletions
@@ -5,6 +5,7 @@ import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import { useAlert } from 'dashboard/composables';
import { debounce } from '@chatwoot/utils';
import { useKeyboardEvents } from 'dashboard/composables/useKeyboardEvents';
import {
searchContacts,
createNewContact,
@@ -115,6 +116,17 @@ watch(
onMounted(() => {
onContactSearch('');
});
const keyboardEvents = {
Escape: {
action: () => {
if (showComposeNewConversation.value) {
showComposeNewConversation.value = false;
}
},
},
};
useKeyboardEvents(keyboardEvents);
</script>
<template>
@@ -5,12 +5,17 @@ import { useI18n } from 'vue-i18n';
import { useUISettings } from 'dashboard/composables/useUISettings';
import { useFileUpload } from 'dashboard/composables/useFileUpload';
import { ALLOWED_FILE_TYPES } from 'shared/constants/messages';
import { useKeyboardEvents } from 'dashboard/composables/useKeyboardEvents';
import FileUpload from 'vue-upload-component';
import Button from 'dashboard/components-next/button/Button.vue';
import WhatsAppOptions from './WhatsAppOptions.vue';
const props = defineProps({
attachedFiles: {
type: Array,
default: () => [],
},
isWhatsappInbox: {
type: Boolean,
default: false,
@@ -59,7 +64,6 @@ const { t } = useI18n();
const uploadAttachment = ref(null);
const isEmojiPickerOpen = ref(false);
const attachedFiles = ref([]);
const EmojiInput = defineAsyncComponent(
() => import('shared/components/emoji/EmojiInput.vue')
@@ -112,12 +116,17 @@ const { onFileUpload } = useFileUpload({
thumb: reader.result,
blobSignedId: blob?.signed_id,
};
attachedFiles.value = [...attachedFiles.value, newFile];
emit('attachFile', attachedFiles.value);
emit('attachFile', [...props.attachedFiles, newFile]);
};
},
});
const keyboardEvents = {
Enter: {
action: () => !props.isWhatsappInbox && emit('sendMessage'),
},
};
useKeyboardEvents(keyboardEvents);
</script>
<template>
@@ -167,12 +176,7 @@ const { onFileUpload } = useFileUpload({
color="slate"
size="sm"
class="!w-10 relative"
>
<span
v-if="attachedFiles.length > 0"
class="absolute top-0 right-0 rounded-full size-2.5 bg-n-brand"
/>
</Button>
/>
</FileUpload>
<Button
v-if="isEmailOrWebWidgetInbox"
@@ -0,0 +1,88 @@
<script setup>
import { computed } from 'vue';
import { fileNameWithEllipsis } from 'shared/helpers/FileHelper';
import Button from 'dashboard/components-next/button/Button.vue';
const props = defineProps({
attachments: {
type: Array,
required: true,
},
});
const emit = defineEmits(['update:attachments']);
const isTypeImage = file => {
const type = file.content_type || file.type;
return type.includes('image');
};
const filteredImageAttachments = computed(() => {
return props.attachments.filter(attachment =>
isTypeImage(attachment.resource)
);
});
const filteredNonImageAttachments = computed(() => {
return props.attachments.filter(
attachment => !isTypeImage(attachment.resource)
);
});
const removeAttachment = id => {
const updatedAttachments = props.attachments.filter(
attachment => attachment.resource.id !== id
);
emit('update:attachments', updatedAttachments);
};
</script>
<template>
<div class="flex flex-col gap-4 p-4">
<div
v-if="filteredImageAttachments.length > 0"
class="flex flex-wrap gap-3"
>
<div
v-for="attachment in filteredImageAttachments"
:key="attachment.id"
class="relative group/image w-[72px] h-[72px]"
>
<img
class="object-cover w-[72px] h-[72px] rounded-lg"
:src="attachment.thumb"
/>
<Button
variant="ghost"
icon="i-lucide-trash"
color="slate"
class="absolute top-1 right-1 !w-5 !h-5 transition-opacity duration-150 ease-in-out opacity-0 group-hover/image:opacity-100"
@click="removeAttachment(attachment.resource.id)"
/>
</div>
</div>
<div
v-if="filteredNonImageAttachments.length > 0"
class="flex flex-wrap gap-3"
>
<div
v-for="attachment in filteredNonImageAttachments"
:key="attachment.id"
class="max-w-[300px] inline-flex items-center h-8 min-w-0 bg-n-solid-3 rounded-lg gap-3 ltr:pl-3 rtl:pr-3 ltr:pr-2 rtl:pl-2"
>
<span class="text-sm font-medium text-n-slate-11">
{{ fileNameWithEllipsis(attachment.resource) }}
</span>
<Button
variant="ghost"
icon="i-lucide-x"
color="slate"
size="xs"
class="shrink-0 !h-5 !w-5"
@click="removeAttachment(attachment.resource.id)"
/>
</div>
</div>
</div>
</template>
@@ -23,6 +23,7 @@ import EmailOptions from './EmailOptions.vue';
import MessageEditor from './MessageEditor.vue';
import ActionButtons from './ActionButtons.vue';
import InboxEmptyState from './InboxEmptyState.vue';
import AttachmentPreviews from './AttachmentPreviews.vue';
const props = defineProps({
contacts: {
@@ -203,15 +204,18 @@ const handleInboxAction = ({ value, action, ...rest }) => {
v$.value.$reset();
emit('updateTargetInbox', { ...rest });
showInboxesDropdown.value = false;
state.attachedFiles = [];
};
const removeTargetInbox = value => {
v$.value.$reset();
emit('updateTargetInbox', value);
state.attachedFiles = [];
};
const clearSelectedContact = () => {
emit('clearSelectedContact');
state.attachedFiles = [];
};
const onClickInsertEmoji = emoji => {
@@ -345,9 +349,17 @@ const handleSendWhatsappMessage = async ({ message, templateParams }) => {
v-model="state.message"
:is-email-or-web-widget-inbox="inboxTypes.isEmailOrWebWidget"
:has-errors="validationStates.isMessageInvalid"
:has-attachments="state.attachedFiles.length > 0"
/>
<AttachmentPreviews
v-if="state.attachedFiles.length > 0"
:attachments="state.attachedFiles"
@update:attachments="state.attachedFiles = $event"
/>
<ActionButtons
:attached-files="state.attachedFiles"
:is-whatsapp-inbox="inboxTypes.isWhatsapp"
:is-email-or-web-widget-inbox="inboxTypes.isEmailOrWebWidget"
:is-twilio-sms-inbox="inboxTypes.isTwilioSMS"
@@ -17,6 +17,10 @@ defineProps({
type: Boolean,
default: false,
},
hasAttachments: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(['update:modelValue']);
@@ -25,7 +29,11 @@ const { t } = useI18n();
</script>
<template>
<div v-if="isEmailOrWebWidgetInbox" class="flex-1 h-full min-h-[200px]">
<div
v-if="isEmailOrWebWidgetInbox"
class="flex-1 h-full"
:class="!hasAttachments && 'min-h-[200px]'"
>
<Editor
:model-value="modelValue"
:placeholder="
@@ -41,7 +49,7 @@ const { t } = useI18n();
@update:model-value="emit('update:modelValue', $event)"
/>
</div>
<div v-else class="flex-1 h-full min-h-[200px]">
<div v-else class="flex-1 h-full" :class="!hasAttachments && 'min-h-[200px]'">
<TextArea
:model-value="modelValue"
:placeholder="