Spaces:
Running
Running
ทำให้ปรับแต่งได้ยึดหยุ่นกว่านี้สามารถนำเข้าไฟล์แสดงผลและบันทึกได้จากหน้าเว็บ
Browse files- index.html +23 -24
- script.js +217 -9
index.html
CHANGED
|
@@ -59,40 +59,39 @@
|
|
| 59 |
<div class="template-viewer flex flex-col items-center gap-8 pb-20" id="template-view">
|
| 60 |
|
| 61 |
<!-- Page 1 -->
|
| 62 |
-
<div id="page1" class="page bg-white shadow-2xl relative overflow-hidden origin-top transition-transform duration-200">
|
| 63 |
<img id="bg1" loading="lazy" class="absolute inset-0 w-full h-full object-cover pointer-events-none z-0" src="" alt="BG 1">
|
| 64 |
-
<img id="photo" loading="lazy" class="field absolute border border-slate-200 z-10 rounded object-cover bg-slate-100" style="top:46px;left:725px;width:110px;height:138px;" src="https://via.placeholder.com/110x138?text=Photo" alt="Photo">
|
| 65 |
-
<img id="qr1" loading="lazy" class="field absolute z-10 bg-white" style="top:977px;left:762.5px;width:69px;height:69px;" src="" alt="QR 1">
|
| 66 |
|
| 67 |
<!-- Dynamic Text Fields -->
|
| 68 |
-
<div id="f1" class="field absolute whitespace-nowrap z-20 font-sans font-bold cursor-default select-none border border-transparent" style="top:80px;left:230px;font-size:16px;">f1</div>
|
| 69 |
-
<div id="f2" class="field absolute whitespace-nowrap z-20 font-sans font-bold cursor-default select-none border border-transparent" style="top:110px;left:230px;font-size:16px;">f2</div>
|
| 70 |
-
<div id="f3" class="field absolute whitespace-nowrap z-20 font-sans font-normal cursor-default select-none border border-transparent" style="top:150px;left:230px;font-size:14px;">f3</div>
|
| 71 |
-
<div id="f4" class="field absolute whitespace-nowrap z-20 font-sans font-normal cursor-default select-none border border-transparent" style="top:180px;left:230px;font-size:14px;">f4</div>
|
| 72 |
-
<div id="f5" class="field absolute whitespace-nowrap z-20 font-sans font-normal cursor-default select-none border border-transparent" style="top:180px;left:580px;font-size:14px;">f5</div>
|
| 73 |
-
<div id="f6" class="field absolute whitespace-nowrap z-20 font-sans font-normal cursor-default select-none border border-transparent" style="top:210px;left:580px;font-size:14px;">f6</div>
|
| 74 |
-
<div id="f7" class="field absolute whitespace-nowrap z-20 font-sans font-normal cursor-default select-none border border-transparent" style="top:240px;left:230px;font-size:14px;">f7</div>
|
| 75 |
-
<div id="f8" class="field absolute whitespace-nowrap z-20 font-sans font-normal cursor-default select-none border border-transparent" style="top:240px;left:580px;font-size:14px;">f8</div>
|
| 76 |
-
<div id="f9" class="field absolute whitespace-nowrap z-20 font-sans font-normal cursor-default select-none border border-transparent" style="top:270px;left:230px;font-size:14px;">f9</div>
|
| 77 |
-
<div id="f10" class="field absolute whitespace-nowrap z-20 font-sans font-normal cursor-default select-none border border-transparent" style="top:300px;left:230px;font-size:14px;">f10</div>
|
| 78 |
</div>
|
| 79 |
|
| 80 |
<!-- Page 2 -->
|
| 81 |
-
<div id="page2" class="page bg-white shadow-2xl relative overflow-hidden origin-top transition-transform duration-200">
|
| 82 |
<img id="bg2" loading="lazy" class="absolute inset-0 w-full h-full object-cover pointer-events-none z-0" src="" alt="BG 2">
|
| 83 |
-
<img id="qr2" loading="lazy" class="field absolute z-10 bg-white" style="top:925px;left:120px;width:90px;height:90px;" src="" alt="QR 2">
|
| 84 |
|
| 85 |
-
<div id="f12" class="field absolute whitespace-nowrap z-20 font-sans font-normal cursor-default select-none border border-transparent" style="top:60px;left:200px;font-size:19px;">f12</div>
|
| 86 |
-
<div id="f13" class="field absolute whitespace-nowrap z-20 font-sans font-normal cursor-default select-none border border-transparent" style="top:100px;left:200px;font-size:19px;">f13</div>
|
| 87 |
-
<div id="f14" class="field absolute whitespace-nowrap z-20 font-sans font-light cursor-default select-none border border-transparent" style="top:140px;left:200px;font-size:19px;">f14</div>
|
| 88 |
-
<div id="f15" class="field absolute whitespace-nowrap z-20 font-sans font-light cursor-default select-none border border-transparent" style="top:180px;left:200px;font-size:19px;">f15</div>
|
| 89 |
-
<div id="f16" class="field absolute whitespace-nowrap z-20 font-sans font-light cursor-default select-none border border-transparent" style="top:180px;left:550px;font-size:19px;">f16</div>
|
| 90 |
-
<div id="f17" class="field absolute whitespace-nowrap z-20 font-sans font-light cursor-default select-none border border-transparent" style="top:220px;left:200px;font-size:19px;">f17</div>
|
| 91 |
-
<div id="f18" class="field absolute whitespace-nowrap z-20 font-sans font-light cursor-default select-none border border-transparent" style="top:220px;left:550px;font-size:19px;">f18</div>
|
| 92 |
</div>
|
| 93 |
</div>
|
| 94 |
-
|
| 95 |
-
<!-- PDF View -->
|
| 96 |
<div class="pdf-viewer hidden flex-col items-center gap-6 pb-20" id="pdf-view">
|
| 97 |
<div id="pdf-content" class="flex flex-col items-center"></div>
|
| 98 |
</div>
|
|
|
|
| 59 |
<div class="template-viewer flex flex-col items-center gap-8 pb-20" id="template-view">
|
| 60 |
|
| 61 |
<!-- Page 1 -->
|
| 62 |
+
<div id="page1" class="page bg-white shadow-2xl relative overflow-hidden origin-top transition-transform duration-200" data-page="1">
|
| 63 |
<img id="bg1" loading="lazy" class="absolute inset-0 w-full h-full object-cover pointer-events-none z-0" src="" alt="BG 1">
|
| 64 |
+
<img id="photo" loading="lazy" class="field absolute border border-slate-200 z-10 rounded object-cover bg-slate-100" style="top:46px;left:725px;width:110px;height:138px;" src="https://via.placeholder.com/110x138?text=Photo" draggable="true" data-field-type="image" alt="Photo">
|
| 65 |
+
<img id="qr1" loading="lazy" class="field absolute z-10 bg-white" style="top:977px;left:762.5px;width:69px;height:69px;" src="" draggable="true" data-field-type="image" alt="QR 1">
|
| 66 |
|
| 67 |
<!-- Dynamic Text Fields -->
|
| 68 |
+
<div id="f1" class="field absolute whitespace-nowrap z-20 font-sans font-bold cursor-default select-none border border-transparent" style="top:80px;left:230px;font-size:16px;" draggable="true" data-field-type="text">f1</div>
|
| 69 |
+
<div id="f2" class="field absolute whitespace-nowrap z-20 font-sans font-bold cursor-default select-none border border-transparent" style="top:110px;left:230px;font-size:16px;" draggable="true" data-field-type="text">f2</div>
|
| 70 |
+
<div id="f3" class="field absolute whitespace-nowrap z-20 font-sans font-normal cursor-default select-none border border-transparent" style="top:150px;left:230px;font-size:14px;" draggable="true" data-field-type="text">f3</div>
|
| 71 |
+
<div id="f4" class="field absolute whitespace-nowrap z-20 font-sans font-normal cursor-default select-none border border-transparent" style="top:180px;left:230px;font-size:14px;" draggable="true" data-field-type="text">f4</div>
|
| 72 |
+
<div id="f5" class="field absolute whitespace-nowrap z-20 font-sans font-normal cursor-default select-none border border-transparent" style="top:180px;left:580px;font-size:14px;" draggable="true" data-field-type="text">f5</div>
|
| 73 |
+
<div id="f6" class="field absolute whitespace-nowrap z-20 font-sans font-normal cursor-default select-none border border-transparent" style="top:210px;left:580px;font-size:14px;" draggable="true" data-field-type="text">f6</div>
|
| 74 |
+
<div id="f7" class="field absolute whitespace-nowrap z-20 font-sans font-normal cursor-default select-none border border-transparent" style="top:240px;left:230px;font-size:14px;" draggable="true" data-field-type="text">f7</div>
|
| 75 |
+
<div id="f8" class="field absolute whitespace-nowrap z-20 font-sans font-normal cursor-default select-none border border-transparent" style="top:240px;left:580px;font-size:14px;" draggable="true" data-field-type="text">f8</div>
|
| 76 |
+
<div id="f9" class="field absolute whitespace-nowrap z-20 font-sans font-normal cursor-default select-none border border-transparent" style="top:270px;left:230px;font-size:14px;" draggable="true" data-field-type="text">f9</div>
|
| 77 |
+
<div id="f10" class="field absolute whitespace-nowrap z-20 font-sans font-normal cursor-default select-none border border-transparent" style="top:300px;left:230px;font-size:14px;" draggable="true" data-field-type="text">f10</div>
|
| 78 |
</div>
|
| 79 |
|
| 80 |
<!-- Page 2 -->
|
| 81 |
+
<div id="page2" class="page bg-white shadow-2xl relative overflow-hidden origin-top transition-transform duration-200" data-page="2">
|
| 82 |
<img id="bg2" loading="lazy" class="absolute inset-0 w-full h-full object-cover pointer-events-none z-0" src="" alt="BG 2">
|
| 83 |
+
<img id="qr2" loading="lazy" class="field absolute z-10 bg-white" style="top:925px;left:120px;width:90px;height:90px;" src="" draggable="true" data-field-type="image" alt="QR 2">
|
| 84 |
|
| 85 |
+
<div id="f12" class="field absolute whitespace-nowrap z-20 font-sans font-normal cursor-default select-none border border-transparent" style="top:60px;left:200px;font-size:19px;" draggable="true" data-field-type="text">f12</div>
|
| 86 |
+
<div id="f13" class="field absolute whitespace-nowrap z-20 font-sans font-normal cursor-default select-none border border-transparent" style="top:100px;left:200px;font-size:19px;" draggable="true" data-field-type="text">f13</div>
|
| 87 |
+
<div id="f14" class="field absolute whitespace-nowrap z-20 font-sans font-light cursor-default select-none border border-transparent" style="top:140px;left:200px;font-size:19px;" draggable="true" data-field-type="text">f14</div>
|
| 88 |
+
<div id="f15" class="field absolute whitespace-nowrap z-20 font-sans font-light cursor-default select-none border border-transparent" style="top:180px;left:200px;font-size:19px;" draggable="true" data-field-type="text">f15</div>
|
| 89 |
+
<div id="f16" class="field absolute whitespace-nowrap z-20 font-sans font-light cursor-default select-none border border-transparent" style="top:180px;left:550px;font-size:19px;" draggable="true" data-field-type="text">f16</div>
|
| 90 |
+
<div id="f17" class="field absolute whitespace-nowrap z-20 font-sans font-light cursor-default select-none border border-transparent" style="top:220px;left:200px;font-size:19px;" draggable="true" data-field-type="text">f17</div>
|
| 91 |
+
<div id="f18" class="field absolute whitespace-nowrap z-20 font-sans font-light cursor-default select-none border border-transparent" style="top:220px;left:550px;font-size:19px;" draggable="true" data-field-type="text">f18</div>
|
| 92 |
</div>
|
| 93 |
</div>
|
| 94 |
+
<!-- PDF View -->
|
|
|
|
| 95 |
<div class="pdf-viewer hidden flex-col items-center gap-6 pb-20" id="pdf-view">
|
| 96 |
<div id="pdf-content" class="flex flex-col items-center"></div>
|
| 97 |
</div>
|
script.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
|
|
| 1 |
'use strict';
|
| 2 |
|
| 3 |
/**
|
|
@@ -17,9 +18,14 @@ const state = {
|
|
| 17 |
currentView: 'template', // 'template' | 'pdf'
|
| 18 |
designMode: false,
|
| 19 |
selectedFieldId: null,
|
| 20 |
-
fieldConfig: {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
};
|
| 22 |
-
|
| 23 |
// --- Database Config ---
|
| 24 |
const DB_NAME = 'PDFWizardDB';
|
| 25 |
const STORE = 'docs';
|
|
@@ -263,6 +269,7 @@ function toggleDesignMode() {
|
|
| 263 |
if(standardPanel) standardPanel.style.display = 'none';
|
| 264 |
if(designPanel) designPanel.style.display = 'block';
|
| 265 |
toast('Design Mode Active', 'info');
|
|
|
|
| 266 |
} else {
|
| 267 |
if(standardPanel) standardPanel.style.display = 'block';
|
| 268 |
if(designPanel) designPanel.style.display = 'none';
|
|
@@ -271,6 +278,21 @@ function toggleDesignMode() {
|
|
| 271 |
}
|
| 272 |
}
|
| 273 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 274 |
function selectField(id) {
|
| 275 |
if (!state.designMode) return;
|
| 276 |
deselectField();
|
|
@@ -400,6 +422,7 @@ function onMouseUp() {
|
|
| 400 |
// --- Layout Config ---
|
| 401 |
function saveFieldConfig() {
|
| 402 |
localStorage.setItem('pdf_field_config', JSON.stringify(state.fieldConfig));
|
|
|
|
| 403 |
}
|
| 404 |
|
| 405 |
function loadFieldConfig() {
|
|
@@ -414,8 +437,95 @@ function loadFieldConfig() {
|
|
| 414 |
}
|
| 415 |
});
|
| 416 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 417 |
}
|
| 418 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 419 |
// --- File Handling ---
|
| 420 |
function handleBgFile(page, file) {
|
| 421 |
if (!file) return;
|
|
@@ -624,9 +734,10 @@ async function downloadPDF() {
|
|
| 624 |
loading(true, 'Generating PDF...');
|
| 625 |
try {
|
| 626 |
const data = state.data[state.currentIndex];
|
|
|
|
| 627 |
const element = document.createElement('div');
|
| 628 |
|
| 629 |
-
// Prepare HTML snapshot
|
| 630 |
element.style.width = '892px';
|
| 631 |
element.style.position = 'absolute';
|
| 632 |
element.style.left = '-9999px';
|
|
@@ -650,14 +761,11 @@ async function downloadPDF() {
|
|
| 650 |
if(state.bg2B64) bg2.src = state.bg2B64;
|
| 651 |
|
| 652 |
// Populate Data
|
| 653 |
-
const f1 = element.querySelector('#f1');
|
| 654 |
-
// ... (simplified population, assuming showData already updated DOM, but we are cloning)
|
| 655 |
-
// Better to rely on the DOM state before clone or manually inject
|
| 656 |
fieldIds.forEach(id => {
|
| 657 |
const key = fieldToKey[id];
|
| 658 |
const val = data[key] || '';
|
| 659 |
const el = element.querySelector('#'+id);
|
| 660 |
-
if(el) el.innerHTML = val ? `<b>${val}</b>` : '';
|
| 661 |
});
|
| 662 |
|
| 663 |
const photo = element.querySelector('#photo');
|
|
@@ -667,7 +775,7 @@ async function downloadPDF() {
|
|
| 667 |
|
| 668 |
const opt = {
|
| 669 |
margin: 0,
|
| 670 |
-
filename: `${data['1'] || 'doc'}.pdf`,
|
| 671 |
image: { type: 'jpeg', quality: 0.98 },
|
| 672 |
html2canvas: { scale: 2, useCORS: true },
|
| 673 |
jsPDF: { unit: 'px', format: [892, 1261], orientation: 'portrait' }
|
|
@@ -675,7 +783,18 @@ async function downloadPDF() {
|
|
| 675 |
|
| 676 |
await html2pdf().set(opt).from(element).save();
|
| 677 |
document.body.removeChild(element);
|
| 678 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 679 |
} catch (e) {
|
| 680 |
toast('Generation Failed', 'error');
|
| 681 |
debugLog(e);
|
|
@@ -684,6 +803,95 @@ async function downloadPDF() {
|
|
| 684 |
}
|
| 685 |
}
|
| 686 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 687 |
async function downloadAllPDFs() {
|
| 688 |
if (state.data.length === 0) return;
|
| 689 |
if (!confirm(`Download ${state.data.length} PDFs?`)) return;
|
|
|
|
| 1 |
+
|
| 2 |
'use strict';
|
| 3 |
|
| 4 |
/**
|
|
|
|
| 18 |
currentView: 'template', // 'template' | 'pdf'
|
| 19 |
designMode: false,
|
| 20 |
selectedFieldId: null,
|
| 21 |
+
fieldConfig: {},
|
| 22 |
+
currentLayoutConfig: {
|
| 23 |
+
fields: {},
|
| 24 |
+
pages: [],
|
| 25 |
+
metadata: {}
|
| 26 |
+
},
|
| 27 |
+
isLayoutLoaded: false
|
| 28 |
};
|
|
|
|
| 29 |
// --- Database Config ---
|
| 30 |
const DB_NAME = 'PDFWizardDB';
|
| 31 |
const STORE = 'docs';
|
|
|
|
| 269 |
if(standardPanel) standardPanel.style.display = 'none';
|
| 270 |
if(designPanel) designPanel.style.display = 'block';
|
| 271 |
toast('Design Mode Active', 'info');
|
| 272 |
+
updateFieldPositionsFromState();
|
| 273 |
} else {
|
| 274 |
if(standardPanel) standardPanel.style.display = 'block';
|
| 275 |
if(designPanel) designPanel.style.display = 'none';
|
|
|
|
| 278 |
}
|
| 279 |
}
|
| 280 |
|
| 281 |
+
function updateFieldPositionsFromState() {
|
| 282 |
+
if (state.currentLayoutConfig.fields) {
|
| 283 |
+
Object.keys(state.currentLayoutConfig.fields).forEach(fieldId => {
|
| 284 |
+
const field = state.currentLayoutConfig.fields[fieldId];
|
| 285 |
+
const element = document.getElementById(fieldId);
|
| 286 |
+
if (element && field.position) {
|
| 287 |
+
element.style.top = field.position.top;
|
| 288 |
+
element.style.left = field.position.left;
|
| 289 |
+
if (field.style) {
|
| 290 |
+
Object.assign(element.style, field.style);
|
| 291 |
+
}
|
| 292 |
+
}
|
| 293 |
+
});
|
| 294 |
+
}
|
| 295 |
+
}
|
| 296 |
function selectField(id) {
|
| 297 |
if (!state.designMode) return;
|
| 298 |
deselectField();
|
|
|
|
| 422 |
// --- Layout Config ---
|
| 423 |
function saveFieldConfig() {
|
| 424 |
localStorage.setItem('pdf_field_config', JSON.stringify(state.fieldConfig));
|
| 425 |
+
saveLayoutToStorage();
|
| 426 |
}
|
| 427 |
|
| 428 |
function loadFieldConfig() {
|
|
|
|
| 437 |
}
|
| 438 |
});
|
| 439 |
}
|
| 440 |
+
loadLayoutFromStorage();
|
| 441 |
+
}
|
| 442 |
+
|
| 443 |
+
function saveLayoutToStorage() {
|
| 444 |
+
const layoutData = {
|
| 445 |
+
fields: {},
|
| 446 |
+
pages: [],
|
| 447 |
+
metadata: {
|
| 448 |
+
version: '1.0',
|
| 449 |
+
savedAt: new Date().toISOString(),
|
| 450 |
+
name: state.currentLayoutName || 'default'
|
| 451 |
+
}
|
| 452 |
+
};
|
| 453 |
+
|
| 454 |
+
// Capture all field positions and styles
|
| 455 |
+
document.querySelectorAll('.field').forEach(field => {
|
| 456 |
+
layoutData.fields[field.id] = {
|
| 457 |
+
position: {
|
| 458 |
+
top: field.style.top,
|
| 459 |
+
left: field.style.left
|
| 460 |
+
},
|
| 461 |
+
style: {
|
| 462 |
+
fontSize: field.style.fontSize,
|
| 463 |
+
fontWeight: field.style.fontWeight,
|
| 464 |
+
color: field.style.color,
|
| 465 |
+
fontFamily: field.style.fontFamily
|
| 466 |
+
},
|
| 467 |
+
type: field.tagName === 'IMG' ? 'image' : 'text',
|
| 468 |
+
page: field.closest('.page')?.id || 'page1'
|
| 469 |
+
};
|
| 470 |
+
});
|
| 471 |
+
|
| 472 |
+
// Capture page configurations
|
| 473 |
+
['page1', 'page2'].forEach(pageId => {
|
| 474 |
+
const page = document.getElementById(pageId);
|
| 475 |
+
if (page) {
|
| 476 |
+
layoutData.pages.push({
|
| 477 |
+
id: pageId,
|
| 478 |
+
background: page.querySelector('img[src*="bg"]')?.src || '',
|
| 479 |
+
dimensions: {
|
| 480 |
+
width: page.offsetWidth,
|
| 481 |
+
height: page.offsetHeight
|
| 482 |
+
}
|
| 483 |
+
});
|
| 484 |
+
}
|
| 485 |
+
});
|
| 486 |
+
|
| 487 |
+
state.currentLayoutConfig = layoutData;
|
| 488 |
+
return layoutData;
|
| 489 |
+
}
|
| 490 |
+
|
| 491 |
+
function loadLayoutFromStorage() {
|
| 492 |
+
const saved = localStorage.getItem('pdf_layout_config');
|
| 493 |
+
if (saved) {
|
| 494 |
+
try {
|
| 495 |
+
state.currentLayoutConfig = JSON.parse(saved);
|
| 496 |
+
applyLayoutConfig(state.currentLayoutConfig);
|
| 497 |
+
state.isLayoutLoaded = true;
|
| 498 |
+
} catch (e) {
|
| 499 |
+
console.warn('Failed to load layout config:', e);
|
| 500 |
+
}
|
| 501 |
+
}
|
| 502 |
}
|
| 503 |
|
| 504 |
+
function applyLayoutConfig(config) {
|
| 505 |
+
// Apply field configurations
|
| 506 |
+
Object.keys(config.fields).forEach(fieldId => {
|
| 507 |
+
const fieldConfig = config.fields[fieldId];
|
| 508 |
+
const element = document.getElementById(fieldId);
|
| 509 |
+
if (element && fieldConfig.position) {
|
| 510 |
+
element.style.top = fieldConfig.position.top;
|
| 511 |
+
element.style.left = fieldConfig.position.left;
|
| 512 |
+
if (fieldConfig.style) {
|
| 513 |
+
Object.assign(element.style, fieldConfig.style);
|
| 514 |
+
}
|
| 515 |
+
}
|
| 516 |
+
});
|
| 517 |
+
|
| 518 |
+
// Apply page backgrounds
|
| 519 |
+
config.pages?.forEach(pageConfig => {
|
| 520 |
+
const pageElement = document.getElementById(pageConfig.id);
|
| 521 |
+
if (pageElement && pageConfig.background) {
|
| 522 |
+
const bgImg = pageElement.querySelector('img[id^="bg"]');
|
| 523 |
+
if (bgImg) {
|
| 524 |
+
bgImg.src = pageConfig.background;
|
| 525 |
+
}
|
| 526 |
+
}
|
| 527 |
+
});
|
| 528 |
+
}
|
| 529 |
// --- File Handling ---
|
| 530 |
function handleBgFile(page, file) {
|
| 531 |
if (!file) return;
|
|
|
|
| 734 |
loading(true, 'Generating PDF...');
|
| 735 |
try {
|
| 736 |
const data = state.data[state.currentIndex];
|
| 737 |
+
const layout = await saveLayoutToStorage(); // Capture current layout
|
| 738 |
const element = document.createElement('div');
|
| 739 |
|
| 740 |
+
// Prepare HTML snapshot with current layout
|
| 741 |
element.style.width = '892px';
|
| 742 |
element.style.position = 'absolute';
|
| 743 |
element.style.left = '-9999px';
|
|
|
|
| 761 |
if(state.bg2B64) bg2.src = state.bg2B64;
|
| 762 |
|
| 763 |
// Populate Data
|
|
|
|
|
|
|
|
|
|
| 764 |
fieldIds.forEach(id => {
|
| 765 |
const key = fieldToKey[id];
|
| 766 |
const val = data[key] || '';
|
| 767 |
const el = element.querySelector('#'+id);
|
| 768 |
+
if(el) el.innerHTML = val ? `<b>${sanitize(val)}</b>` : '';
|
| 769 |
});
|
| 770 |
|
| 771 |
const photo = element.querySelector('#photo');
|
|
|
|
| 775 |
|
| 776 |
const opt = {
|
| 777 |
margin: 0,
|
| 778 |
+
filename: `${data['1'] || state.currentLayoutName || 'doc'}.pdf`,
|
| 779 |
image: { type: 'jpeg', quality: 0.98 },
|
| 780 |
html2canvas: { scale: 2, useCORS: true },
|
| 781 |
jsPDF: { unit: 'px', format: [892, 1261], orientation: 'portrait' }
|
|
|
|
| 783 |
|
| 784 |
await html2pdf().set(opt).from(element).save();
|
| 785 |
document.body.removeChild(element);
|
| 786 |
+
|
| 787 |
+
// Save layout to history
|
| 788 |
+
const doc = {
|
| 789 |
+
id: Date.now().toString(),
|
| 790 |
+
fileName: data['1'] ? `${data['1']}.pdf` : 'generated.pdf',
|
| 791 |
+
date: new Date().toISOString(),
|
| 792 |
+
layout: layout,
|
| 793 |
+
data: data
|
| 794 |
+
};
|
| 795 |
+
await saveDoc(doc);
|
| 796 |
+
|
| 797 |
+
toast('PDF Downloaded & Layout Saved', 'success');
|
| 798 |
} catch (e) {
|
| 799 |
toast('Generation Failed', 'error');
|
| 800 |
debugLog(e);
|
|
|
|
| 803 |
}
|
| 804 |
}
|
| 805 |
|
| 806 |
+
async function exportLayout() {
|
| 807 |
+
const layout = await saveLayoutToStorage();
|
| 808 |
+
const blob = new Blob([JSON.stringify(layout, null, 2)], { type: 'application/json' });
|
| 809 |
+
const link = document.createElement('a');
|
| 810 |
+
link.href = URL.createObjectURL(blob);
|
| 811 |
+
link.download = `${layout.metadata.name || 'layout'}_${new Date().getTime()}.json`;
|
| 812 |
+
link.click();
|
| 813 |
+
toast('Layout Exported', 'success');
|
| 814 |
+
}
|
| 815 |
+
|
| 816 |
+
async function importLayout(file) {
|
| 817 |
+
if (!file || !file.name.endsWith('.json')) {
|
| 818 |
+
toast('Please select a layout JSON file', 'error');
|
| 819 |
+
return;
|
| 820 |
+
}
|
| 821 |
+
|
| 822 |
+
const reader = new FileReader();
|
| 823 |
+
reader.onload = async (e) => {
|
| 824 |
+
try {
|
| 825 |
+
const layout = JSON.parse(e.target.result);
|
| 826 |
+
if (!layout.fields || !layout.pages) {
|
| 827 |
+
throw new Error('Invalid layout format');
|
| 828 |
+
}
|
| 829 |
+
state.currentLayoutConfig = layout;
|
| 830 |
+
applyLayoutConfig(layout);
|
| 831 |
+
|
| 832 |
+
// Update form inputs
|
| 833 |
+
document.getElementById('layout-name-input').value = layout.metadata?.name || 'Unnamed Layout';
|
| 834 |
+
|
| 835 |
+
toast('Layout Imported Successfully', 'success');
|
| 836 |
+
state.isLayoutLoaded = true;
|
| 837 |
+
} catch (err) {
|
| 838 |
+
toast('Failed to import layout: ' + err.message, 'error');
|
| 839 |
+
debugLog(err);
|
| 840 |
+
}
|
| 841 |
+
};
|
| 842 |
+
reader.readAsText(file);
|
| 843 |
+
}
|
| 844 |
+
|
| 845 |
+
async function saveLayoutToDB() {
|
| 846 |
+
try {
|
| 847 |
+
const layout = saveLayoutToStorage();
|
| 848 |
+
const doc = {
|
| 849 |
+
id: `layout_${Date.now()}`,
|
| 850 |
+
fileName: `${layout.metadata.name}_layout.json`,
|
| 851 |
+
date: new Date().toISOString(),
|
| 852 |
+
type: 'layout',
|
| 853 |
+
data: layout,
|
| 854 |
+
metadata: layout.metadata
|
| 855 |
+
};
|
| 856 |
+
await saveDoc(doc);
|
| 857 |
+
toast('Layout Saved to History', 'success');
|
| 858 |
+
return layout;
|
| 859 |
+
} catch (err) {
|
| 860 |
+
toast('Failed to save layout', 'error');
|
| 861 |
+
debugLog(err);
|
| 862 |
+
return null;
|
| 863 |
+
}
|
| 864 |
+
}
|
| 865 |
+
|
| 866 |
+
function newLayout() {
|
| 867 |
+
const confirmNew = confirm('Create new layout? Unsaved changes will be lost.');
|
| 868 |
+
if (!confirmNew) return;
|
| 869 |
+
|
| 870 |
+
// Reset all fields to default positions
|
| 871 |
+
document.querySelectorAll('.field').forEach(field => {
|
| 872 |
+
field.style.top = '';
|
| 873 |
+
field.style.left = '';
|
| 874 |
+
field.style.fontSize = '';
|
| 875 |
+
field.style.fontWeight = '';
|
| 876 |
+
field.style.color = '';
|
| 877 |
+
field.classList.remove('selected');
|
| 878 |
+
});
|
| 879 |
+
|
| 880 |
+
state.fieldConfig = {};
|
| 881 |
+
state.currentLayoutConfig = {
|
| 882 |
+
fields: {},
|
| 883 |
+
pages: [],
|
| 884 |
+
metadata: {}
|
| 885 |
+
};
|
| 886 |
+
state.isLayoutLoaded = false;
|
| 887 |
+
state.currentLayoutName = 'New Layout';
|
| 888 |
+
|
| 889 |
+
deselectField();
|
| 890 |
+
localStorage.removeItem('pdf_field_config');
|
| 891 |
+
localStorage.removeItem('pdf_layout_config');
|
| 892 |
+
|
| 893 |
+
toast('New Layout Created', 'success');
|
| 894 |
+
}
|
| 895 |
async function downloadAllPDFs() {
|
| 896 |
if (state.data.length === 0) return;
|
| 897 |
if (!confirm(`Download ${state.data.length} PDFs?`)) return;
|