Cambios minimos al canvas

This commit is contained in:
Juan Felipe Zapata Moreno 2025-09-26 16:14:42 -06:00
parent 21f5d3a761
commit b2095e4559
4 changed files with 133 additions and 51 deletions

View File

@ -6,6 +6,7 @@ import TiptapEditor from "./TiptapEditor.vue";
const props = defineProps({
element: { type: Object, required: true },
isSelected: { type: Boolean, default: false },
pageDimensions: { type: Object, required: true },
});
const emit = defineEmits([
"select",
@ -100,13 +101,19 @@ const handleMouseMove = (event) => {
if (isDragging.value) {
const deltaX = event.clientX - dragStart.value.mouseX;
const deltaY = event.clientY - dragStart.value.mouseY;
const newX = dragStart.value.elementX + deltaX;
const newY = dragStart.value.elementY + deltaY;
emit("move", {
id: props.element.id,
x: Math.max(0, newX),
y: Math.max(0, newY),
});
let newX = dragStart.value.elementX + deltaX;
let newY = dragStart.value.elementY + deltaY;
// Límites
const pageW = props.pageDimensions.width;
const pageH = props.pageDimensions.height;
const elW = props.element.width;
const elH = props.element.height;
newX = Math.max(0, Math.min(newX, pageW - elW));
newY = Math.max(0, Math.min(newY, pageH - elH));
emit("move", { id: props.element.id, x: newX, y: newY });
} else if (isResizing.value) {
handleResizeMove(event);
}
@ -138,8 +145,19 @@ const handleResizeMove = (event) => {
if (!isResizing.value) return;
const deltaX = event.clientX - resizeStart.value.x;
const deltaY = event.clientY - resizeStart.value.y;
const newWidth = Math.max(100, resizeStart.value.width + deltaX);
const newHeight = Math.max(40, resizeStart.value.height + deltaY);
// Límites
const pageW = props.pageDimensions.width;
const pageH = props.pageDimensions.height;
const elX = props.element.x;
const elY = props.element.y;
let newWidth = resizeStart.value.width + deltaX;
let newHeight = resizeStart.value.height + deltaY;
newWidth = Math.max(100, Math.min(newWidth, pageW - elX));
newHeight = Math.max(40, Math.min(newHeight, pageH - elY));
emit("update", { id: props.element.id, width: newWidth, height: newHeight });
};
@ -159,6 +177,17 @@ const handleFileSelect = (event) => {
}
event.target.value = null;
};
const textStyles = computed(() => {
if (props.element.type !== "text") return {};
return {
width: "100%",
height: "100%",
overflow: "hidden",
position: "relative",
};
});
</script>
<template>
@ -181,13 +210,19 @@ const handleFileSelect = (event) => {
class="absolute inset-0 z-10 cursor-move"
/>
<TiptapEditor
<div
v-if="element.type === 'text'"
:model-value="element.content"
:editable="isEditing"
@update:model-value="handleContentUpdate"
@focus="handleEditorFocus"
/>
:style="textStyles"
class="text-container"
>
<TiptapEditor
class="w-full h-full overflow-auto"
:model-value="element.content"
:editable="isEditing"
@update:model-value="handleContentUpdate"
@focus="handleEditorFocus"
/>
</div>
<div
v-else-if="element.type === 'image'"

View File

@ -17,13 +17,11 @@ 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;
const ZOOM_LEVEL = 1.0;
/** Propiedades computadas */
const currentPageSize = computed(() => pageSizes[pageSize.value] || pageSizes['A4']);
@ -134,7 +132,7 @@ const deletePage = (pageIndex) => {
class="absolute inset-0"
:style="{ transform: `scale(${ZOOM_LEVEL})`, transformOrigin: 'top left' }"
>
<slot name="elements" :page="page" />
<slot name="elements" :page="page" :dimensions="currentPageSize" />
</div>
</div>
</div>

View File

@ -52,37 +52,70 @@ defineExpose({ editor });
</script>
<template>
<EditorContent :editor="editor" />
<div class="tiptap-wrapper">
<EditorContent :editor="editor" />
</div>
</template>
<style scoped>
.tiptap-wrapper {
width: 100%;
height: 100%;
overflow: hidden;
display: flex;
flex-direction: column;
}
:deep(.ProseMirror) {
padding: 0.5rem;
outline: none;
width: 100%;
height: 100%;
word-wrap: break-word;
word-break: break-word;
overflow-y: auto;
overflow-x: hidden;
box-sizing: border-box;
line-height: 1.4;
}
/* Prevenir que el texto se desborde horizontalmente */
:deep(.ProseMirror p) {
margin: 0 0 0.5em 0;
word-wrap: break-word;
overflow-wrap: break-word;
hyphens: auto;
max-width: 100%;
}
/* Asegurar que spans respeten el ancho */
:deep(.ProseMirror span) {
display: inline;
word-wrap: break-word;
overflow-wrap: break-word;
max-width: 100%;
}
/* Estilos para cuando NO es editable */
:deep(.ProseMirror[contenteditable="false"]) {
cursor: default;
overflow: hidden;
}
/* Estilos para cuando SÍ es editable */
:deep(.ProseMirror[contenteditable="true"]:focus) {
outline: none;
box-shadow: none;
}
/* Reglas generales que ya teníamos */
:deep(.ProseMirror span[style]) {
display: inline !important;
}
:deep(.ProseMirror p) {
margin: 0;
}
/* Controlar listas */
:deep(.ProseMirror ul),
:deep(.ProseMirror ol) {
padding-left: 1.5rem;
margin: 0 0 0.5em 0;
}
/* Alineación de texto */
:deep(p[style*="text-align: center"]) {
text-align: center;
}

View File

@ -126,30 +126,46 @@ const handleKeydown = (event) => {
onMounted(() => document.addEventListener("keydown", handleKeydown));
onBeforeUnmount(() => document.removeEventListener("keydown", handleKeydown));
const PDF_CANVAS_SIZES = {
A4: { width: 794, height: 1123 },
A3: { width: 1123, height: 1587 },
Tabloid: { width: 1056, height: 1632 },
};
/* Tamaños en puntos */
const PDF_POINTS = {
A4: { width: 580.00, height: 841.89 },
A3: { width: 841.89, height: 1190.55 },
Tabloid: { width: 792, height: 1224 },
};
const exportPDF = () => {
isExporting.value = true;
try {
const content = [];
// calcular scale entre canvas-units y pdf-points
const canvasSize = PDF_CANVAS_SIZES[currentPageSize.value] || PDF_CANVAS_SIZES.A4;
const pdfSize = PDF_POINTS[currentPageSize.value] || PDF_POINTS.A4;
const scale = pdfSize.width / canvasSize.width;
pages.value.forEach((page, pageIndex) => {
const pageContent = page.elements
.map((element) => {
const position = { x: element.x * 0.75, y: element.y * 0.75 };
// convertir posición y dimensiones usando 'scale'
const position = { x: element.x * scale, y: element.y * scale };
if (element.type === "text" && element.content) {
return {
columns: [
{
stack: htmlToPdfMake(element.content),
width: (element.width || 250) * 0.75,
},
],
stack: htmlToPdfMake(element.content),
width: (element.width || 250) * scale,
absolutePosition: position,
};
}
if (element.type === "image" && element.content) {
return {
image: element.content,
width: (element.width || 150) * 0.75,
width: (element.width || 150) * scale,
absolutePosition: position,
};
}
@ -163,19 +179,15 @@ const exportPDF = () => {
})
);
const numCols = element.content.data[0]?.length || 1;
const colWidth = (element.width * 0.75) / numCols;
const colWidth = ((element.width || 400) * scale) / numCols;
const widths = Array(numCols).fill(colWidth);
return {
columns: [
{
table: {
body: body,
widths: widths,
},
width: element.width * 0.75,
},
],
table: {
body: body,
widths: widths,
},
width: (element.width || 400) * scale,
absolutePosition: position,
layout: {
hLineWidth: () => 0.5,
@ -200,12 +212,15 @@ const exportPDF = () => {
const docDefinition = {
content,
pageSize: currentPageSize.value.toUpperCase(),
pageMargins: [0, 0, 0, 0],
pageMargins: [40, 40, 40, 40],
styles: {
p: {
margin: [0, 0, 0, 0],
},
},
};
window.pdfMake
.createPdf(docDefinition)
.download(`${documentTitle.value || "documento"}.pdf`);
window.pdfMake.createPdf(docDefinition).download(`${documentTitle.value || "documento"}.pdf`);
} catch (e) {
console.error("Error al exportar PDF:", e);
alert("Hubo un error al generar el PDF.");
@ -305,7 +320,7 @@ const availableElements = [
@click="deselectAll"
@page-size-change="handlePageSizeChange"
>
<template #elements="{ page }">
<template #elements="{ page, dimensions }">
<CanvasElement
v-for="element in page.elements"
:key="element.id"
@ -316,6 +331,7 @@ const availableElements = [
"
:element="element"
:is-selected="selectedElementId === element.id"
:page-dimensions="dimensions"
@select="selectElement"
@delete="deleteElement"
@update="updateElement"