All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m8s
224 lines
7.1 KiB
Vue
224 lines
7.1 KiB
Vue
<template>
|
|
<div class="space-y-2">
|
|
<!-- Debug info -->
|
|
<div class="p-2 bg-purple-900/50 rounded text-xs text-purple-300">
|
|
MediaPreview recibió {{ files.length }} archivo(s):
|
|
<span v-for="(f, i) in files" :key="i" class="ml-1">{{ f.name }} ({{ f.type }})</span>
|
|
</div>
|
|
<!-- Files grid -->
|
|
<div class="flex flex-wrap gap-2">
|
|
<div
|
|
v-for="(file, index) in files"
|
|
:key="index"
|
|
class="relative group"
|
|
>
|
|
<!-- Image preview with sticker toggle -->
|
|
<div
|
|
v-if="isImage(file)"
|
|
class="relative"
|
|
>
|
|
<img
|
|
:src="getPreviewUrl(file)"
|
|
class="h-20 w-20 object-cover rounded-lg transition-all"
|
|
:class="{ 'ring-2 ring-[var(--wa-green)]': stickerModes[index] }"
|
|
/>
|
|
<!-- Sticker toggle button -->
|
|
<button
|
|
class="absolute bottom-0 left-0 right-0 py-0.5 text-[10px] font-medium transition-colors"
|
|
:class="stickerModes[index]
|
|
? 'bg-[var(--wa-green)] text-white rounded-b-lg'
|
|
: 'bg-black/60 text-white/80 rounded-b-lg hover:bg-black/80'"
|
|
@click="toggleStickerMode(index)"
|
|
>
|
|
{{ stickerModes[index] ? 'Sticker' : 'Imagen' }}
|
|
</button>
|
|
<!-- Remove button -->
|
|
<button
|
|
class="absolute -top-2 -right-2 w-5 h-5 rounded-full bg-red-500 text-white flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity"
|
|
@click="$emit('remove', index)"
|
|
>
|
|
<UIcon name="i-lucide-x" class="w-3 h-3" />
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Video preview -->
|
|
<div
|
|
v-else-if="isVideo(file)"
|
|
class="relative"
|
|
>
|
|
<div class="h-20 w-20 bg-gray-800 rounded-lg flex items-center justify-center">
|
|
<video
|
|
:src="getPreviewUrl(file)"
|
|
class="h-full w-full object-cover rounded-lg"
|
|
/>
|
|
<div class="absolute inset-0 flex items-center justify-center">
|
|
<UIcon name="i-lucide-play-circle" class="w-8 h-8 text-white/80" />
|
|
</div>
|
|
</div>
|
|
<button
|
|
class="absolute -top-2 -right-2 w-5 h-5 rounded-full bg-red-500 text-white flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity"
|
|
@click="$emit('remove', index)"
|
|
>
|
|
<UIcon name="i-lucide-x" class="w-3 h-3" />
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Audio preview -->
|
|
<div
|
|
v-else-if="isAudio(file)"
|
|
class="relative flex items-center gap-2 px-3 py-2 bg-gray-800 rounded-lg"
|
|
>
|
|
<UIcon name="i-lucide-music" class="w-5 h-5 text-[var(--wa-green-light)]" />
|
|
<span class="text-sm text-white truncate max-w-[100px]">{{ file.name }}</span>
|
|
<button
|
|
class="w-5 h-5 rounded-full bg-red-500 text-white flex items-center justify-center"
|
|
@click="$emit('remove', index)"
|
|
>
|
|
<UIcon name="i-lucide-x" class="w-3 h-3" />
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Document preview -->
|
|
<div
|
|
v-else
|
|
class="relative flex items-center gap-2 px-3 py-2 bg-gray-800 rounded-lg"
|
|
>
|
|
<UIcon :name="getDocIcon(file)" class="w-5 h-5 text-[var(--wa-blue)]" />
|
|
<div class="flex flex-col min-w-0">
|
|
<span class="text-sm text-white truncate max-w-[100px]">{{ file.name }}</span>
|
|
<span class="text-xs text-gray-400">{{ formatSize(file.size) }}</span>
|
|
</div>
|
|
<button
|
|
class="w-5 h-5 rounded-full bg-red-500 text-white flex items-center justify-center flex-shrink-0"
|
|
@click="$emit('remove', index)"
|
|
>
|
|
<UIcon name="i-lucide-x" class="w-3 h-3" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Caption input (hide if all files are stickers) -->
|
|
<div v-if="showCaption && files.length > 0 && !allStickers">
|
|
<UInput
|
|
:model-value="caption"
|
|
placeholder="Agregar descripcion..."
|
|
size="sm"
|
|
class="bg-[var(--wa-bg-light)]"
|
|
@update:model-value="$emit('update:caption', $event)"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Info text for stickers -->
|
|
<p v-if="hasStickers" class="text-xs text-[var(--wa-text-muted)]">
|
|
Los stickers se convertiran a formato 512x512 WebP
|
|
</p>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
interface Props {
|
|
files: File[]
|
|
caption?: string
|
|
showCaption?: boolean
|
|
}
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
caption: '',
|
|
showCaption: true
|
|
})
|
|
|
|
const emit = defineEmits<{
|
|
remove: [index: number]
|
|
'update:caption': [value: string]
|
|
'update:stickerModes': [modes: boolean[]]
|
|
}>()
|
|
|
|
// Track which images should be sent as stickers
|
|
const stickerModes = ref<boolean[]>([])
|
|
|
|
// Initialize sticker modes when files change
|
|
watch(() => props.files, (files) => {
|
|
// Preserve existing modes and add false for new files
|
|
const newModes = files.map((_, i) => stickerModes.value[i] ?? false)
|
|
stickerModes.value = newModes
|
|
emit('update:stickerModes', [...newModes])
|
|
}, { immediate: true })
|
|
|
|
const toggleStickerMode = (index: number) => {
|
|
stickerModes.value[index] = !stickerModes.value[index]
|
|
emit('update:stickerModes', [...stickerModes.value])
|
|
}
|
|
|
|
// Check if all files are stickers (images marked as sticker)
|
|
const allStickers = computed(() => {
|
|
return props.files.length > 0 && props.files.every((f, i) =>
|
|
isImage(f) && stickerModes.value[i]
|
|
)
|
|
})
|
|
|
|
// Check if any file is marked as sticker
|
|
const hasStickers = computed(() => {
|
|
return props.files.some((f, i) => isImage(f) && stickerModes.value[i])
|
|
})
|
|
|
|
// Preview URL cache
|
|
const previewUrls = new Map<File, string>()
|
|
|
|
const getPreviewUrl = (file: File): string => {
|
|
if (!previewUrls.has(file)) {
|
|
previewUrls.set(file, URL.createObjectURL(file))
|
|
}
|
|
return previewUrls.get(file)!
|
|
}
|
|
|
|
// Cleanup URLs on unmount
|
|
onUnmounted(() => {
|
|
previewUrls.forEach(url => URL.revokeObjectURL(url))
|
|
previewUrls.clear()
|
|
})
|
|
|
|
const isImage = (file: File): boolean => {
|
|
return file.type.startsWith('image/')
|
|
}
|
|
|
|
const isVideo = (file: File): boolean => {
|
|
return file.type.startsWith('video/')
|
|
}
|
|
|
|
const isAudio = (file: File): boolean => {
|
|
return file.type.startsWith('audio/')
|
|
}
|
|
|
|
const getDocIcon = (file: File): string => {
|
|
const type = file.type
|
|
const name = file.name.toLowerCase()
|
|
|
|
if (type.includes('pdf') || name.endsWith('.pdf')) {
|
|
return 'i-lucide-file-text'
|
|
}
|
|
if (type.includes('spreadsheet') || name.match(/\.(xlsx?|csv)$/)) {
|
|
return 'i-lucide-file-spreadsheet'
|
|
}
|
|
if (type.includes('word') || name.match(/\.(docx?)$/)) {
|
|
return 'i-lucide-file-text'
|
|
}
|
|
if (type.includes('presentation') || name.match(/\.(pptx?)$/)) {
|
|
return 'i-lucide-file-presentation'
|
|
}
|
|
if (type.includes('zip') || name.match(/\.(zip|rar|7z)$/)) {
|
|
return 'i-lucide-file-archive'
|
|
}
|
|
|
|
return 'i-lucide-file'
|
|
}
|
|
|
|
const formatSize = (bytes: number): string => {
|
|
if (bytes === 0) return '0 B'
|
|
const k = 1024
|
|
const sizes = ['B', 'KB', 'MB', 'GB']
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
|
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`
|
|
}
|
|
</script>
|