|
|
@@ -0,0 +1,160 @@
|
|
|
+<script setup>
|
|
|
+import { onMounted, onUnmounted, ref, watch } from "vue";
|
|
|
+
|
|
|
+const props = defineProps({
|
|
|
+ url: { type: String, required: true },
|
|
|
+ tipo: { type: String, required: true }, // 'imagem' | 'video'
|
|
|
+});
|
|
|
+
|
|
|
+const emit = defineEmits(["close"]);
|
|
|
+
|
|
|
+const zoom = ref(1);
|
|
|
+const panX = ref(0);
|
|
|
+const panY = ref(0);
|
|
|
+const arrastando = ref(false);
|
|
|
+let arrasteInicio = { x: 0, y: 0, panX: 0, panY: 0 };
|
|
|
+
|
|
|
+function resetZoom() {
|
|
|
+ zoom.value = 1;
|
|
|
+ panX.value = 0;
|
|
|
+ panY.value = 0;
|
|
|
+}
|
|
|
+
|
|
|
+watch(() => props.url, resetZoom);
|
|
|
+
|
|
|
+function aplicarZoom(delta) {
|
|
|
+ zoom.value = Math.min(4, Math.max(1, +(zoom.value + delta).toFixed(2)));
|
|
|
+ if (zoom.value === 1) {
|
|
|
+ panX.value = 0;
|
|
|
+ panY.value = 0;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function aoRolar(e) {
|
|
|
+ e.preventDefault();
|
|
|
+ aplicarZoom(e.deltaY < 0 ? 0.25 : -0.25);
|
|
|
+}
|
|
|
+
|
|
|
+function aoDuploClique() {
|
|
|
+ zoom.value === 1 ? aplicarZoom(1) : resetZoom();
|
|
|
+}
|
|
|
+
|
|
|
+function iniciarArraste(e) {
|
|
|
+ if (zoom.value <= 1) return;
|
|
|
+ arrastando.value = true;
|
|
|
+ arrasteInicio = { x: e.clientX, y: e.clientY, panX: panX.value, panY: panY.value };
|
|
|
+}
|
|
|
+
|
|
|
+function moverArraste(e) {
|
|
|
+ if (!arrastando.value) return;
|
|
|
+ panX.value = arrasteInicio.panX + (e.clientX - arrasteInicio.x);
|
|
|
+ panY.value = arrasteInicio.panY + (e.clientY - arrasteInicio.y);
|
|
|
+}
|
|
|
+
|
|
|
+function pararArraste() {
|
|
|
+ arrastando.value = false;
|
|
|
+}
|
|
|
+
|
|
|
+function aoTeclado(e) {
|
|
|
+ if (e.key === "Escape") emit("close");
|
|
|
+}
|
|
|
+
|
|
|
+onMounted(() => window.addEventListener("keydown", aoTeclado));
|
|
|
+onUnmounted(() => window.removeEventListener("keydown", aoTeclado));
|
|
|
+</script>
|
|
|
+
|
|
|
+<template>
|
|
|
+ <Teleport to="body">
|
|
|
+ <Transition name="modal-midia">
|
|
|
+ <div class="fixed inset-0 z-[9999]">
|
|
|
+ <div class="absolute inset-0 bg-black/90" @click="emit('close')" />
|
|
|
+
|
|
|
+ <button
|
|
|
+ type="button"
|
|
|
+ class="absolute right-4 top-4 z-10 flex h-9 w-9 items-center justify-center rounded-full bg-white/10 text-white hover:bg-white/20"
|
|
|
+ title="Fechar"
|
|
|
+ @click="emit('close')"
|
|
|
+ >
|
|
|
+ <svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" />
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+
|
|
|
+ <div class="relative flex h-full w-full items-center justify-center p-6" @click.self="emit('close')">
|
|
|
+ <img
|
|
|
+ v-if="tipo === 'imagem'"
|
|
|
+ :src="url"
|
|
|
+ alt="Mídia enviada na conversa"
|
|
|
+ class="max-h-[85vh] max-w-full select-none rounded-lg object-contain"
|
|
|
+ :class="zoom > 1 ? (arrastando ? 'cursor-grabbing' : 'cursor-grab') : 'cursor-zoom-in'"
|
|
|
+ :style="{ transform: `scale(${zoom}) translate(${panX / zoom}px, ${panY / zoom}px)`, transition: arrastando ? 'none' : 'transform 0.15s ease' }"
|
|
|
+ draggable="false"
|
|
|
+ @wheel="aoRolar"
|
|
|
+ @dblclick="aoDuploClique"
|
|
|
+ @mousedown="iniciarArraste"
|
|
|
+ @mousemove="moverArraste"
|
|
|
+ @mouseup="pararArraste"
|
|
|
+ @mouseleave="pararArraste"
|
|
|
+ />
|
|
|
+ <video
|
|
|
+ v-else
|
|
|
+ :src="url"
|
|
|
+ controls
|
|
|
+ autoplay
|
|
|
+ class="max-h-[85vh] max-w-full rounded-lg"
|
|
|
+ @click.stop
|
|
|
+ ></video>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div
|
|
|
+ v-if="tipo === 'imagem'"
|
|
|
+ class="absolute bottom-6 left-1/2 z-10 flex -translate-x-1/2 items-center gap-1.5 rounded-full bg-white/10 px-2 py-1.5 backdrop-blur"
|
|
|
+ >
|
|
|
+ <button
|
|
|
+ type="button"
|
|
|
+ class="flex h-8 w-8 items-center justify-center rounded-full text-white hover:bg-white/20 disabled:opacity-30"
|
|
|
+ title="Diminuir zoom"
|
|
|
+ :disabled="zoom <= 1"
|
|
|
+ @click="aplicarZoom(-0.25)"
|
|
|
+ >
|
|
|
+ <svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" d="M5 12h14" />
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ <span class="w-10 text-center text-xs font-medium text-white">{{ Math.round(zoom * 100) }}%</span>
|
|
|
+ <button
|
|
|
+ type="button"
|
|
|
+ class="flex h-8 w-8 items-center justify-center rounded-full text-white hover:bg-white/20 disabled:opacity-30"
|
|
|
+ title="Aumentar zoom"
|
|
|
+ :disabled="zoom >= 4"
|
|
|
+ @click="aplicarZoom(0.25)"
|
|
|
+ >
|
|
|
+ <svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" d="M12 5v14M5 12h14" />
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ v-if="zoom > 1"
|
|
|
+ type="button"
|
|
|
+ class="ml-1 rounded-full px-2.5 py-1 text-xs font-medium text-white hover:bg-white/20"
|
|
|
+ title="Restaurar zoom"
|
|
|
+ @click="resetZoom"
|
|
|
+ >
|
|
|
+ Redefinir
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </Transition>
|
|
|
+ </Teleport>
|
|
|
+</template>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.modal-midia-enter-active,
|
|
|
+.modal-midia-leave-active {
|
|
|
+ transition: opacity 0.2s ease;
|
|
|
+}
|
|
|
+.modal-midia-enter-from,
|
|
|
+.modal-midia-leave-to {
|
|
|
+ opacity: 0;
|
|
|
+}
|
|
|
+</style>
|