chore: Update attachment chip preview

This commit is contained in:
iamsivin
2025-01-24 17:56:53 +05:30
parent 11e77e9349
commit 077a8fd498
4 changed files with 170 additions and 40 deletions
@@ -1,8 +1,8 @@
<script setup>
import { computed, defineOptions, useAttrs } from 'vue';
import ImageChip from 'next/message/chips/Image.vue';
import VideoChip from 'next/message/chips/Video.vue';
import ImageGrid from 'next/message/chips/AttachmentGrid.vue';
import VideoGrid from 'next/message/chips/AttachmentGrid.vue';
import AudioChip from 'next/message/chips/Audio.vue';
import FileChip from 'next/message/chips/File.vue';
import { useMessageContext } from '../provider.js';
@@ -37,7 +37,7 @@ const attrs = useAttrs();
const { orientation } = useMessageContext();
const classToApply = computed(() => {
const baseClasses = [attrs.class, 'flex', 'flex-wrap'];
const baseClasses = [attrs.class, 'flex', 'flex-wrap', 'gap-2'];
if (orientation.value === 'right') {
baseClasses.push('justify-end');
@@ -50,15 +50,15 @@ const allAttachments = computed(() => {
return Array.isArray(props.attachments) ? props.attachments : [];
});
const mediaAttachments = computed(() => {
const allowedTypes = [ATTACHMENT_TYPES.IMAGE, ATTACHMENT_TYPES.VIDEO];
const mediaTypes = allAttachments.value.filter(attachment =>
allowedTypes.includes(attachment.fileType)
const imageAttachments = computed(() => {
return allAttachments.value.filter(
attachment => attachment.fileType === ATTACHMENT_TYPES.IMAGE
);
});
return mediaTypes.sort(
(a, b) =>
allowedTypes.indexOf(a.fileType) - allowedTypes.indexOf(b.fileType)
const videoAttachments = computed(() => {
return allAttachments.value.filter(
attachment => attachment.fileType === ATTACHMENT_TYPES.VIDEO
);
});
@@ -76,31 +76,30 @@ const files = computed(() => {
</script>
<template>
<div v-if="mediaAttachments.length" :class="classToApply">
<template v-for="attachment in mediaAttachments" :key="attachment.id">
<ImageChip
v-if="attachment.fileType === ATTACHMENT_TYPES.IMAGE"
:attachment="attachment"
/>
<VideoChip
v-else-if="attachment.fileType === ATTACHMENT_TYPES.VIDEO"
:attachment="attachment"
/>
</template>
</div>
<div v-if="recordings.length" :class="classToApply">
<div v-for="attachment in recordings" :key="attachment.id">
<AudioChip
class="bg-n-alpha-3 dark:bg-n-alpha-2 text-n-slate-12"
<div class="flex flex-col gap-2">
<div v-if="imageAttachments.length" :class="classToApply">
<ImageGrid :attachments="imageAttachments" type="image" />
</div>
<div v-if="videoAttachments.length" :class="classToApply">
<VideoGrid :attachments="videoAttachments" type="video" />
</div>
<div v-if="recordings.length" :class="classToApply">
<div v-for="attachment in recordings" :key="attachment.id">
<AudioChip
class="bg-n-alpha-3 dark:bg-n-alpha-2 text-n-slate-12"
:attachment="attachment"
/>
</div>
</div>
<div v-if="files.length" :class="classToApply">
<FileChip
v-for="attachment in files"
:key="attachment.id"
:attachment="attachment"
/>
</div>
</div>
<div v-if="files.length" :class="classToApply">
<FileChip
v-for="attachment in files"
:key="attachment.id"
:attachment="attachment"
/>
</div>
</template>
@@ -0,0 +1,76 @@
<script setup>
import { computed } from 'vue';
import ImageChip from './Image.vue';
import VideoChip from './Video.vue';
const props = defineProps({
attachments: {
type: Array,
required: true,
validator: value => Array.isArray(value) && value.length > 0,
},
type: {
type: String,
default: 'image',
},
});
const MAX_DISPLAYED = 5;
const visibleAttachments = computed(() =>
props.attachments.slice(0, MAX_DISPLAYED)
);
const remainingCount = computed(() =>
Math.max(0, props.attachments.length - MAX_DISPLAYED)
);
const gridClass = computed(() => {
const count = props.attachments.length;
const base = 'grid gap-1 w-full';
if (count === 1) return `${base} grid-cols-1`;
const classes = {
2: `${base} grid-cols-2 max-h-[400px]`,
3: `${base} grid-cols-2 max-h-[400px]`,
4: `${base} grid-cols-2 max-h-[400px]`,
5: `${base} grid-cols-3 max-h-[400px]`,
};
return classes[count] || classes[5];
});
const itemClass = computed(() => index => {
const count = props.attachments.length;
const base = 'relative overflow-hidden rounded-lg';
if (count === 1) return `${base} w-full h-auto`;
if (count === 3 && index === 0) return `${base} row-span-2 h-[400px]`;
if (count >= 5 && index === 0) return `${base} col-span-2 h-[200px]`;
return count === 2 ? `${base} h-[400px]` : `${base} h-[200px]`;
});
const shouldShowOverlay = computed(
() => index => remainingCount.value > 0 && index === MAX_DISPLAYED - 1
);
</script>
<template>
<div :class="gridClass">
<div
v-for="(attachment, index) in visibleAttachments"
:key="attachment.id"
:class="itemClass(index)"
>
<component
:is="type === 'image' ? ImageChip : VideoChip"
:attachment="attachment"
:remaining-count="remainingCount"
:should-show-overlay="shouldShowOverlay(index)"
/>
</div>
</div>
</template>
@@ -11,7 +11,16 @@ defineProps({
type: Object,
required: true,
},
remainingCount: {
type: Number,
required: true,
},
shouldShowOverlay: {
type: Boolean,
required: true,
},
});
const hasError = ref(false);
const showGallery = ref(false);
@@ -20,12 +29,20 @@ const { filteredCurrentChatAttachments } = useMessageContext();
const handleError = () => {
hasError.value = true;
};
const handleGalleryClick = () => {
showGallery.value = true;
};
const handleGalleryClose = () => {
showGallery.value = false;
};
</script>
<template>
<div
class="size-[72px] overflow-hidden contain-content rounded-xl cursor-pointer"
@click="showGallery = true"
class="rounded-lg overflow-hidden contain-content cursor-pointer size-full"
@click="handleGalleryClick"
>
<div
v-if="hasError"
@@ -34,6 +51,7 @@ const handleError = () => {
<Icon icon="i-lucide-circle-off" class="text-n-slate-11" />
{{ $t('COMPONENTS.MEDIA.LOADING_FAILED') }}
</div>
<img
v-else
class="object-cover w-full h-full"
@@ -41,12 +59,23 @@ const handleError = () => {
@error="handleError"
/>
</div>
<div
v-if="shouldShowOverlay"
class="absolute inset-0 flex items-center cursor-pointer justify-center bg-n-black/25 dark:bg-n-alpha-1 rounded-lg"
@click="handleGalleryClick"
>
<span class="text-white text-2xl font-semibold">
+{{ remainingCount }}
</span>
</div>
<GalleryView
v-if="showGallery"
v-model:show="showGallery"
:attachment="useSnakeCase(attachment)"
:all-attachments="filteredCurrentChatAttachments"
@error="handleError"
@close="() => (showGallery = false)"
@close="handleGalleryClose"
/>
</template>
@@ -10,17 +10,33 @@ defineProps({
type: Object,
required: true,
},
remainingCount: {
type: Number,
required: true,
},
shouldShowOverlay: {
type: Boolean,
required: true,
},
});
const showGallery = ref(false);
const { filteredCurrentChatAttachments } = useMessageContext();
const handleGalleryClick = () => {
showGallery.value = true;
};
const handleGalleryClose = () => {
showGallery.value = false;
};
</script>
<template>
<div
class="size-[72px] overflow-hidden contain-content rounded-xl cursor-pointer relative group"
@click="showGallery = true"
class="rounded-lg overflow-hidden contain-content cursor-pointer size-full"
@click="handleGalleryClick"
>
<video
:src="attachment.dataUrl"
@@ -29,6 +45,7 @@ const { filteredCurrentChatAttachments } = useMessageContext();
playsInline
/>
<div
v-if="!shouldShowOverlay"
class="absolute w-full h-full inset-0 p-1 flex items-center justify-center"
>
<div
@@ -41,12 +58,21 @@ const { filteredCurrentChatAttachments } = useMessageContext();
</div>
</div>
</div>
<div
v-if="shouldShowOverlay"
class="absolute inset-0 flex items-center cursor-pointer justify-center bg-n-black/25 dark:bg-n-alpha-1 rounded-lg"
@click="handleGalleryClick"
>
<span class="text-white text-2xl font-semibold">
+{{ remainingCount }}
</span>
</div>
<GalleryView
v-if="showGallery"
v-model:show="showGallery"
:attachment="useSnakeCase(attachment)"
:all-attachments="filteredCurrentChatAttachments"
@error="onError"
@close="() => (showGallery = false)"
@close="handleGalleryClose"
/>
</template>