feat: kickstart media bubble
This commit is contained in:
@@ -13,10 +13,25 @@ import Avatar from 'next/avatar/Avatar.vue';
|
||||
|
||||
import TextBubble from './bubbles/Text.vue';
|
||||
import ActivityBubble from './bubbles/Activity.vue';
|
||||
import MediaBubble from './bubbles/Media.vue';
|
||||
|
||||
import MessageError from './MessageError.vue';
|
||||
import MessageMeta from './MessageMeta.vue';
|
||||
|
||||
/**
|
||||
* @typedef {Object} Attachment
|
||||
* @property {number} id - Unique identifier for the attachment
|
||||
* @property {number} messageId - ID of the associated message
|
||||
* @property {'image'|'audio'|'video'|'file'|'location'|'fallback'|'share'|'story_mention'|'contact'|'ig_reel'} fileType - Type of the attachment (file or image)
|
||||
* @property {number} accountId - ID of the associated account
|
||||
* @property {string|null} extension - File extension
|
||||
* @property {string} dataUrl - URL to access the full attachment data
|
||||
* @property {string} thumbUrl - URL to access the thumbnail version
|
||||
* @property {number} fileSize - Size of the file in bytes
|
||||
* @property {number|null} width - Width of the image if applicable
|
||||
* @property {number|null} height - Height of the image if applicable
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} Sender
|
||||
* @property {Object} additional_attributes - Additional attributes of the sender
|
||||
@@ -37,18 +52,19 @@ import MessageMeta from './MessageMeta.vue';
|
||||
|
||||
/**
|
||||
* @typedef {Object} Props
|
||||
* @property {('sent'|'delivered'|'read'|'failed')} status - The delivery status of the message
|
||||
* @property {ContentAttributes} [contentAttributes={}] - Additional attributes of the message content
|
||||
* @property {Attachment[]} [attachments=[]] - The attachments associated with the message
|
||||
* @property {Sender|null} [sender=null] - The sender information
|
||||
* @property {boolean} [private=false] - Whether the message is private
|
||||
* @property {number|null} [senderId=null] - The ID of the sender
|
||||
* @property {number} createdAt - Timestamp when the message was created
|
||||
* @property {number} currentUserId - The ID of the current user
|
||||
* @property {number} id - The unique identifier for the message
|
||||
* @property {number} messageType - The type of message (must be one of MESSAGE_TYPES)
|
||||
* @property {('sent'|'delivered'|'read'|'failed')} status - The delivery status of the message
|
||||
* @property {boolean} [private=false] - Whether the message is private
|
||||
* @property {number} createdAt - Timestamp when the message was created
|
||||
* @property {Sender|null} [sender=null] - The sender information
|
||||
* @property {ContentAttributes} [contentAttributes={}] - Additional attributes of the message content
|
||||
* @property {number|null} [senderId=null] - The ID of the sender
|
||||
* @property {string|null} [senderType=null] - The type of the sender
|
||||
* @property {string|null} [error=null] - Error message if the message failed to send
|
||||
* @property {string|null} [senderType=null] - The type of the sender
|
||||
* @property {string} content - The message content
|
||||
* @property {number} currentUserId - The ID of the current user
|
||||
*/
|
||||
|
||||
const props = defineProps({
|
||||
@@ -63,6 +79,10 @@ const props = defineProps({
|
||||
required: true,
|
||||
validator: value => Object.values(MESSAGE_STATUS).includes(value),
|
||||
},
|
||||
attachments: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
private: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
@@ -188,6 +208,14 @@ const shouldShowAvatar = computed(() => {
|
||||
return true;
|
||||
});
|
||||
|
||||
const componentToRender = computed(() => {
|
||||
if (props.attachments.length === 1) {
|
||||
return MediaBubble;
|
||||
}
|
||||
|
||||
return TextBubble;
|
||||
});
|
||||
|
||||
provideMessageContext({
|
||||
variant,
|
||||
orientation,
|
||||
@@ -218,7 +246,11 @@ provideMessageContext({
|
||||
>
|
||||
<Avatar :name="sender ? sender.name : ''" src="" :size="24" />
|
||||
</div>
|
||||
<TextBubble :content="content" class="[grid-area:bubble]" />
|
||||
<Component
|
||||
:is="componentToRender"
|
||||
v-bind="props"
|
||||
class="[grid-area:bubble]"
|
||||
/>
|
||||
<MessageError
|
||||
v-if="contentAttributes.externalError"
|
||||
class="[grid-area:meta]"
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import BaseBubble from './Base.vue';
|
||||
import Button from 'next/button/Button.vue';
|
||||
|
||||
/**
|
||||
* @typedef {Object} Attachment
|
||||
* @property {number} id - Unique identifier for the attachment
|
||||
* @property {number} messageId - ID of the associated message
|
||||
* @property {'image'|'audio'|'video'|'file'|'location'|'fallback'|'share'|'story_mention'|'contact'|'ig_reel'} fileType - Type of the attachment (file or image)
|
||||
* @property {number} accountId - ID of the associated account
|
||||
* @property {string|null} extension - File extension
|
||||
* @property {string} dataUrl - URL to access the full attachment data
|
||||
* @property {string} thumbUrl - URL to access the thumbnail version
|
||||
* @property {number} fileSize - Size of the file in bytes
|
||||
* @property {number|null} width - Width of the image if applicable
|
||||
* @property {number|null} height - Height of the image if applicable
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} Props
|
||||
* @property {Attachment[]} [attachments=[]] - The attachments associated with the message
|
||||
*/
|
||||
|
||||
const props = defineProps({
|
||||
attachments: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const attachment = computed(() => {
|
||||
return props.attachments[0];
|
||||
});
|
||||
|
||||
const hasError = ref(false);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BaseBubble class="overflow-hidden relative group">
|
||||
<img :src="attachment.thumbUrl" @click="onClick" @error="hasError = true" />
|
||||
<div
|
||||
class="inset-0 absolute bg-gradient-to-t from-n-slate-12/10 to-transparent hidden group-hover:flex items-end justify-end"
|
||||
>
|
||||
<Button xs faded slate icon="i-lucide-download" />
|
||||
</div>
|
||||
</BaseBubble>
|
||||
</template>
|
||||
@@ -33,3 +33,16 @@ export const MESSAGE_STATUS = {
|
||||
FAILED: 'failed',
|
||||
PROGRESS: 'progress',
|
||||
};
|
||||
|
||||
export const ATTACHMENT_TYPES = {
|
||||
IMAGE: 'image',
|
||||
AUDIO: 'audio',
|
||||
VIDEO: 'video',
|
||||
FILE: 'file',
|
||||
LOCATION: 'location',
|
||||
FALLBACK: 'fallback',
|
||||
SHARE: 'share',
|
||||
STORY_MENTION: 'story_mention',
|
||||
CONTACT: 'contact',
|
||||
IG_REEL: 'ig_reel',
|
||||
};
|
||||
|
||||
@@ -332,9 +332,9 @@ export default camelcaseKeys(
|
||||
account_id: 2,
|
||||
extension: null,
|
||||
data_url:
|
||||
'http://localhost:3000/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBaUFLIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--0f76a0caba1df9a5217630b5ca1fdfb09c9111d3/image.png',
|
||||
'https://images.pexels.com/photos/28379994/pexels-photo-28379994/free-photo-of-a-laptop-with-a-yellow-phone-on-top-of-it.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2',
|
||||
thumb_url:
|
||||
'http://localhost:3000/rails/active_storage/representations/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBaUFLIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--0f76a0caba1df9a5217630b5ca1fdfb09c9111d3/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdCem9MWm05eWJXRjBTU0lJY0c1bkJqb0dSVlE2RTNKbGMybDZaVjkwYjE5bWFXeHNXd2RwQWZvdyIsImV4cCI6bnVsbCwicHVyIjoidmFyaWF0aW9uIn19--ebe60765d222d11ade39165eae49cc4b2de18d89/image.png',
|
||||
'https://images.pexels.com/photos/28379994/pexels-photo-28379994/free-photo-of-a-laptop-with-a-yellow-phone-on-top-of-it.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2',
|
||||
file_size: 983912,
|
||||
width: null,
|
||||
height: null,
|
||||
|
||||
Reference in New Issue
Block a user