Cambios minimos al canvas
This commit is contained in:
parent
21f5d3a761
commit
b2095e4559
@ -6,6 +6,7 @@ import TiptapEditor from "./TiptapEditor.vue";
|
|||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
element: { type: Object, required: true },
|
element: { type: Object, required: true },
|
||||||
isSelected: { type: Boolean, default: false },
|
isSelected: { type: Boolean, default: false },
|
||||||
|
pageDimensions: { type: Object, required: true },
|
||||||
});
|
});
|
||||||
const emit = defineEmits([
|
const emit = defineEmits([
|
||||||
"select",
|
"select",
|
||||||
@ -100,13 +101,19 @@ const handleMouseMove = (event) => {
|
|||||||
if (isDragging.value) {
|
if (isDragging.value) {
|
||||||
const deltaX = event.clientX - dragStart.value.mouseX;
|
const deltaX = event.clientX - dragStart.value.mouseX;
|
||||||
const deltaY = event.clientY - dragStart.value.mouseY;
|
const deltaY = event.clientY - dragStart.value.mouseY;
|
||||||
const newX = dragStart.value.elementX + deltaX;
|
let newX = dragStart.value.elementX + deltaX;
|
||||||
const newY = dragStart.value.elementY + deltaY;
|
let newY = dragStart.value.elementY + deltaY;
|
||||||
emit("move", {
|
|
||||||
id: props.element.id,
|
// Límites
|
||||||
x: Math.max(0, newX),
|
const pageW = props.pageDimensions.width;
|
||||||
y: Math.max(0, newY),
|
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) {
|
} else if (isResizing.value) {
|
||||||
handleResizeMove(event);
|
handleResizeMove(event);
|
||||||
}
|
}
|
||||||
@ -138,8 +145,19 @@ const handleResizeMove = (event) => {
|
|||||||
if (!isResizing.value) return;
|
if (!isResizing.value) return;
|
||||||
const deltaX = event.clientX - resizeStart.value.x;
|
const deltaX = event.clientX - resizeStart.value.x;
|
||||||
const deltaY = event.clientY - resizeStart.value.y;
|
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 });
|
emit("update", { id: props.element.id, width: newWidth, height: newHeight });
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -159,6 +177,17 @@ const handleFileSelect = (event) => {
|
|||||||
}
|
}
|
||||||
event.target.value = null;
|
event.target.value = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const textStyles = computed(() => {
|
||||||
|
if (props.element.type !== "text") return {};
|
||||||
|
|
||||||
|
return {
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
overflow: "hidden",
|
||||||
|
position: "relative",
|
||||||
|
};
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -181,13 +210,19 @@ const handleFileSelect = (event) => {
|
|||||||
class="absolute inset-0 z-10 cursor-move"
|
class="absolute inset-0 z-10 cursor-move"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TiptapEditor
|
<div
|
||||||
v-if="element.type === 'text'"
|
v-if="element.type === 'text'"
|
||||||
:model-value="element.content"
|
:style="textStyles"
|
||||||
:editable="isEditing"
|
class="text-container"
|
||||||
@update:model-value="handleContentUpdate"
|
>
|
||||||
@focus="handleEditorFocus"
|
<TiptapEditor
|
||||||
/>
|
class="w-full h-full overflow-auto"
|
||||||
|
:model-value="element.content"
|
||||||
|
:editable="isEditing"
|
||||||
|
@update:model-value="handleContentUpdate"
|
||||||
|
@focus="handleEditorFocus"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-else-if="element.type === 'image'"
|
v-else-if="element.type === 'image'"
|
||||||
|
|||||||
@ -17,13 +17,11 @@ const pageSize = ref('A4');
|
|||||||
|
|
||||||
const pageSizes = {
|
const pageSizes = {
|
||||||
'A4': { width: 794, height: 1123, label: 'A4 (210 x 297 mm)' },
|
'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)' },
|
'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)' }
|
'Tabloid': { width: 1056, height: 1632, label: 'Tabloide (279 x 432 mm)' }
|
||||||
};
|
};
|
||||||
|
|
||||||
const ZOOM_LEVEL = 0.65;
|
const ZOOM_LEVEL = 1.0;
|
||||||
|
|
||||||
/** Propiedades computadas */
|
/** Propiedades computadas */
|
||||||
const currentPageSize = computed(() => pageSizes[pageSize.value] || pageSizes['A4']);
|
const currentPageSize = computed(() => pageSizes[pageSize.value] || pageSizes['A4']);
|
||||||
@ -134,7 +132,7 @@ const deletePage = (pageIndex) => {
|
|||||||
class="absolute inset-0"
|
class="absolute inset-0"
|
||||||
:style="{ transform: `scale(${ZOOM_LEVEL})`, transformOrigin: 'top left' }"
|
:style="{ transform: `scale(${ZOOM_LEVEL})`, transformOrigin: 'top left' }"
|
||||||
>
|
>
|
||||||
<slot name="elements" :page="page" />
|
<slot name="elements" :page="page" :dimensions="currentPageSize" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -52,37 +52,70 @@ defineExpose({ editor });
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<EditorContent :editor="editor" />
|
<div class="tiptap-wrapper">
|
||||||
|
<EditorContent :editor="editor" />
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.tiptap-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
:deep(.ProseMirror) {
|
:deep(.ProseMirror) {
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
outline: none;
|
outline: none;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
word-wrap: break-word;
|
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 */
|
/* Estilos para cuando NO es editable */
|
||||||
:deep(.ProseMirror[contenteditable="false"]) {
|
:deep(.ProseMirror[contenteditable="false"]) {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Estilos para cuando SÍ es editable */
|
/* Estilos para cuando SÍ es editable */
|
||||||
:deep(.ProseMirror[contenteditable="true"]:focus) {
|
:deep(.ProseMirror[contenteditable="true"]:focus) {
|
||||||
outline: none;
|
outline: none;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
/* Reglas generales que ya teníamos */
|
|
||||||
:deep(.ProseMirror span[style]) {
|
/* Controlar listas */
|
||||||
display: inline !important;
|
|
||||||
}
|
|
||||||
:deep(.ProseMirror p) {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
:deep(.ProseMirror ul),
|
:deep(.ProseMirror ul),
|
||||||
:deep(.ProseMirror ol) {
|
:deep(.ProseMirror ol) {
|
||||||
padding-left: 1.5rem;
|
padding-left: 1.5rem;
|
||||||
|
margin: 0 0 0.5em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Alineación de texto */
|
||||||
:deep(p[style*="text-align: center"]) {
|
:deep(p[style*="text-align: center"]) {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
@ -92,4 +125,4 @@ defineExpose({ editor });
|
|||||||
:deep(p[style*="text-align: left"]) {
|
:deep(p[style*="text-align: left"]) {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -126,30 +126,46 @@ const handleKeydown = (event) => {
|
|||||||
onMounted(() => document.addEventListener("keydown", handleKeydown));
|
onMounted(() => document.addEventListener("keydown", handleKeydown));
|
||||||
onBeforeUnmount(() => document.removeEventListener("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 = () => {
|
const exportPDF = () => {
|
||||||
isExporting.value = true;
|
isExporting.value = true;
|
||||||
try {
|
try {
|
||||||
const content = [];
|
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) => {
|
pages.value.forEach((page, pageIndex) => {
|
||||||
const pageContent = page.elements
|
const pageContent = page.elements
|
||||||
.map((element) => {
|
.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) {
|
if (element.type === "text" && element.content) {
|
||||||
return {
|
return {
|
||||||
columns: [
|
stack: htmlToPdfMake(element.content),
|
||||||
{
|
width: (element.width || 250) * scale,
|
||||||
stack: htmlToPdfMake(element.content),
|
|
||||||
width: (element.width || 250) * 0.75,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
absolutePosition: position,
|
absolutePosition: position,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (element.type === "image" && element.content) {
|
if (element.type === "image" && element.content) {
|
||||||
return {
|
return {
|
||||||
image: element.content,
|
image: element.content,
|
||||||
width: (element.width || 150) * 0.75,
|
width: (element.width || 150) * scale,
|
||||||
absolutePosition: position,
|
absolutePosition: position,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -163,19 +179,15 @@ const exportPDF = () => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
const numCols = element.content.data[0]?.length || 1;
|
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);
|
const widths = Array(numCols).fill(colWidth);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
columns: [
|
table: {
|
||||||
{
|
body: body,
|
||||||
table: {
|
widths: widths,
|
||||||
body: body,
|
},
|
||||||
widths: widths,
|
width: (element.width || 400) * scale,
|
||||||
},
|
|
||||||
width: element.width * 0.75,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
absolutePosition: position,
|
absolutePosition: position,
|
||||||
layout: {
|
layout: {
|
||||||
hLineWidth: () => 0.5,
|
hLineWidth: () => 0.5,
|
||||||
@ -200,12 +212,15 @@ const exportPDF = () => {
|
|||||||
const docDefinition = {
|
const docDefinition = {
|
||||||
content,
|
content,
|
||||||
pageSize: currentPageSize.value.toUpperCase(),
|
pageSize: currentPageSize.value.toUpperCase(),
|
||||||
pageMargins: [0, 0, 0, 0],
|
pageMargins: [40, 40, 40, 40],
|
||||||
|
styles: {
|
||||||
|
p: {
|
||||||
|
margin: [0, 0, 0, 0],
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
window.pdfMake
|
window.pdfMake.createPdf(docDefinition).download(`${documentTitle.value || "documento"}.pdf`);
|
||||||
.createPdf(docDefinition)
|
|
||||||
.download(`${documentTitle.value || "documento"}.pdf`);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Error al exportar PDF:", e);
|
console.error("Error al exportar PDF:", e);
|
||||||
alert("Hubo un error al generar el PDF.");
|
alert("Hubo un error al generar el PDF.");
|
||||||
@ -305,7 +320,7 @@ const availableElements = [
|
|||||||
@click="deselectAll"
|
@click="deselectAll"
|
||||||
@page-size-change="handlePageSizeChange"
|
@page-size-change="handlePageSizeChange"
|
||||||
>
|
>
|
||||||
<template #elements="{ page }">
|
<template #elements="{ page, dimensions }">
|
||||||
<CanvasElement
|
<CanvasElement
|
||||||
v-for="element in page.elements"
|
v-for="element in page.elements"
|
||||||
:key="element.id"
|
:key="element.id"
|
||||||
@ -316,6 +331,7 @@ const availableElements = [
|
|||||||
"
|
"
|
||||||
:element="element"
|
:element="element"
|
||||||
:is-selected="selectedElementId === element.id"
|
:is-selected="selectedElementId === element.id"
|
||||||
|
:page-dimensions="dimensions"
|
||||||
@select="selectElement"
|
@select="selectElement"
|
||||||
@delete="deleteElement"
|
@delete="deleteElement"
|
||||||
@update="updateElement"
|
@update="updateElement"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user