144 lines
5.7 KiB
Vue
144 lines
5.7 KiB
Vue
<script setup>
|
|
import { ref, computed, nextTick, watch } from 'vue';
|
|
import GoogleIcon from '@Shared/GoogleIcon.vue';
|
|
import PageSizeSelector from '@Holos/PDF/PageSizeSelector.vue';
|
|
|
|
/** Propiedades */
|
|
const props = defineProps({
|
|
pages: { type: Array, default: () => [] },
|
|
currentPage: { type: Number, default: 1 }
|
|
});
|
|
|
|
/** Eventos */
|
|
const emit = defineEmits(['drop', 'add-page', 'delete-page', 'page-change', 'page-size-change', 'click']);
|
|
|
|
/** Referencias */
|
|
const pageSize = ref('A4');
|
|
|
|
const pageSizes = {
|
|
'A4': { width: 794, height: 1123, label: 'A4 (210 x 297 mm)' },
|
|
'Letter': { width: 816, height: 1056, label: 'Carta (216 x 279 mm)' },
|
|
'A3': { width: 1123, height: 1587, label: 'A3 (297 x 420 mm)' },
|
|
'Legal': { width: 816, height: 1344, label: 'Oficio (216 x 356 mm)' },
|
|
'Tabloid': { width: 1056, height: 1632, label: 'Tabloide (279 x 432 mm)' }
|
|
};
|
|
|
|
const ZOOM_LEVEL = 0.65;
|
|
|
|
/** Propiedades computadas */
|
|
const currentPageSize = computed(() => pageSizes[pageSize.value] || pageSizes['A4']);
|
|
const scaledPageWidth = computed(() => currentPageSize.value.width * ZOOM_LEVEL);
|
|
const scaledPageHeight = computed(() => currentPageSize.value.height * ZOOM_LEVEL);
|
|
const totalPages = computed(() => props.pages.length);
|
|
|
|
watch(pageSize, (newSize) => {
|
|
emit('page-size-change', { size: newSize, dimensions: pageSizes[newSize] });
|
|
});
|
|
|
|
const handleDrop = (event, pageIndex) => {
|
|
event.preventDefault();
|
|
const rect = event.currentTarget.getBoundingClientRect();
|
|
const x = (event.clientX - rect.left) / ZOOM_LEVEL;
|
|
const y = (event.clientY - rect.top) / ZOOM_LEVEL;
|
|
emit('drop', { originalEvent: event, pageIndex, x, y });
|
|
};
|
|
|
|
const handleDragOver = (event) => {
|
|
event.preventDefault();
|
|
};
|
|
|
|
const setCurrentPage = (pageNumber) => {
|
|
emit('page-change', pageNumber);
|
|
};
|
|
|
|
const addPageAndNavigate = () => {
|
|
emit('add-page');
|
|
nextTick(() => {
|
|
setCurrentPage(totalPages.value);
|
|
});
|
|
};
|
|
|
|
const handleNextPage = () => {
|
|
if (props.currentPage >= totalPages.value) {
|
|
addPageAndNavigate();
|
|
} else {
|
|
setCurrentPage(props.currentPage + 1);
|
|
}
|
|
};
|
|
|
|
const deletePage = (pageIndex) => {
|
|
if (totalPages.value > 1) {
|
|
emit('delete-page', pageIndex);
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<div class="flex-1 flex flex-col bg-gray-100">
|
|
<div class="flex items-center justify-between px-4 py-3 bg-white border-b">
|
|
<div class="flex items-center gap-4">
|
|
<span class="text-sm font-medium text-gray-700">
|
|
Página {{ currentPage }} de {{ totalPages }}
|
|
</span>
|
|
<div class="flex items-center gap-1 border-l pl-4">
|
|
<button
|
|
@click="setCurrentPage(Math.max(1, currentPage - 1))"
|
|
:disabled="currentPage <= 1"
|
|
class="p-2 text-gray-500 hover:text-gray-700 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
title="Página anterior"
|
|
>
|
|
<GoogleIcon name="keyboard_arrow_left" class="text-xl" />
|
|
</button>
|
|
<button
|
|
@click="handleNextPage"
|
|
class="p-2 text-gray-500 hover:text-gray-700"
|
|
:title="currentPage >= totalPages ? 'Crear nueva página' : 'Página siguiente'"
|
|
>
|
|
<GoogleIcon name="keyboard_arrow_right" class="text-xl" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="flex-shrink-0">
|
|
<PageSizeSelector v-model="pageSize" />
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex-1 overflow-auto p-8" @click="$emit('click', $event)">
|
|
<div class="flex items-start justify-center gap-8 min-h-full">
|
|
<div v-for="(page, pageIndex) in pages" :key="page.id" class="relative group flex-shrink-0">
|
|
<div class="absolute -top-6 left-1/2 transform -translate-x-1/2 text-xs text-gray-500 whitespace-nowrap">
|
|
Página {{ pageIndex + 1 }}
|
|
</div>
|
|
|
|
<button
|
|
v-if="totalPages > 1"
|
|
@click="deletePage(pageIndex)"
|
|
class="absolute -top-6 right-0 w-6 h-6 bg-white rounded-full text-red-500 opacity-0 group-hover:opacity-100 flex items-center justify-center shadow-md hover:shadow-lg transition-all z-10"
|
|
title="Eliminar página"
|
|
>
|
|
<GoogleIcon name="delete" class="text-sm" />
|
|
</button>
|
|
|
|
<div
|
|
class="pdf-page relative bg-white shadow-lg rounded-md border transition-all duration-200 overflow-hidden"
|
|
:class="{
|
|
'ring-2 ring-blue-500': currentPage === pageIndex + 1,
|
|
'hover:shadow-xl': currentPage !== pageIndex + 1
|
|
}"
|
|
:style="{ width: `${scaledPageWidth}px`, height: `${scaledPageHeight}px` }"
|
|
@drop="(e) => handleDrop(e, pageIndex)"
|
|
@dragover="handleDragOver"
|
|
@click="setCurrentPage(pageIndex + 1)"
|
|
>
|
|
<div
|
|
class="absolute inset-0"
|
|
:style="{ transform: `scale(${ZOOM_LEVEL})`, transformOrigin: 'top left' }"
|
|
>
|
|
<slot name="elements" :page="page" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template> |