|
|
|
|
|
'use strict'; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const state = { |
|
|
data: [], |
|
|
currentIndex: 0, |
|
|
pdfDoc: null, |
|
|
scale: 1, |
|
|
originalPdfBytes: null, |
|
|
currentFileName: '', |
|
|
bg1Obj: '', bg1B64: '', bg2Obj: '', bg2B64: '', |
|
|
currentView: 'template', |
|
|
designMode: false, |
|
|
selectedFieldId: null, |
|
|
fieldConfig: {}, |
|
|
currentLayoutConfig: { |
|
|
fields: {}, |
|
|
pages: [], |
|
|
metadata: {} |
|
|
}, |
|
|
isLayoutLoaded: false |
|
|
}; |
|
|
|
|
|
const DB_NAME = 'PDFWizardDB'; |
|
|
const STORE = 'docs'; |
|
|
let db; |
|
|
|
|
|
|
|
|
const fieldToKey = { |
|
|
'photo': '4', 'f1': '7', 'f2': '1', 'f3': '2', 'f4': '8', 'f5': '9', |
|
|
'f6': '1', 'f7': '5', 'f8': '6', 'f9': '3', 'f10': '10', |
|
|
'f12': '11', 'f13': '12', 'f14': '7', 'f15': '1', |
|
|
'f16': '3', 'f17': '10', 'f18': '8' |
|
|
}; |
|
|
const fieldIds = Object.keys(fieldToKey).filter(k => k.startsWith('f')); |
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', async () => { |
|
|
try { |
|
|
await initDB(); |
|
|
loadFieldConfig(); |
|
|
updateFileCount(); |
|
|
|
|
|
|
|
|
if (window.pdfjsLib) { |
|
|
window.pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js'; |
|
|
} |
|
|
|
|
|
setupGlobalListeners(); |
|
|
debugLog('System Ready'); |
|
|
} catch (e) { |
|
|
toast('Initialization Error: ' + e.message, 'error'); |
|
|
debugLog(e); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
function setupGlobalListeners() { |
|
|
|
|
|
document.addEventListener('toggle-theme', () => toggleTheme()); |
|
|
|
|
|
|
|
|
document.addEventListener('toggle-sidebar', () => toggleSidebar()); |
|
|
|
|
|
|
|
|
document.addEventListener('toggle-design-mode', () => toggleDesignMode()); |
|
|
|
|
|
document.getElementById('bg1-file')?.addEventListener('change', e => handleBgFile('1', e.target.files[0])); |
|
|
document.getElementById('bg2-file')?.addEventListener('change', e => handleBgFile('2', e.target.files[0])); |
|
|
document.getElementById('json-file')?.addEventListener('change', handleJsonUpload); |
|
|
document.getElementById('pdf-upload')?.addEventListener('change', handlePdfUpload); |
|
|
document.getElementById('layout-import')?.addEventListener('change', (e) => importLayout(e.target.files[0])); |
|
|
|
|
|
|
|
|
document.getElementById('btn-apply-bg')?.addEventListener('click', applyBg); |
|
|
document.getElementById('btn-load-sample')?.addEventListener('click', loadSample); |
|
|
document.getElementById('btn-download-pdf')?.addEventListener('click', downloadPDF); |
|
|
document.getElementById('btn-download-all')?.addEventListener('click', downloadAllPDFs); |
|
|
document.getElementById('btn-qr-modal')?.addEventListener('click', openQRModal); |
|
|
document.getElementById('btn-export-layout')?.addEventListener('click', exportLayout); |
|
|
document.getElementById('btn-save-layout')?.addEventListener('click', saveLayoutToDB); |
|
|
document.getElementById('btn-new-layout')?.addEventListener('click', newLayout); |
|
|
|
|
|
document.getElementById('btn-export-design')?.addEventListener('click', exportLayout); |
|
|
document.getElementById('btn-save-design')?.addEventListener('click', saveLayoutToDB); |
|
|
document.getElementById('btn-new-design')?.addEventListener('click', newLayout); |
|
|
|
|
|
|
|
|
document.querySelectorAll('.tab-btn').forEach(btn => { |
|
|
btn.addEventListener('click', () => switchTab(btn.dataset.tab)); |
|
|
}); |
|
|
|
|
|
|
|
|
document.getElementById('zoom-in').addEventListener('click', zoomIn); |
|
|
document.getElementById('zoom-out').addEventListener('click', zoomOut); |
|
|
document.getElementById('current-pg').addEventListener('change', (e) => goToPage(e.target.value)); |
|
|
|
|
|
['prop-x', 'prop-y', 'prop-size', 'prop-weight', 'prop-color'].forEach(id => { |
|
|
const el = document.getElementById(id); |
|
|
if (el) { |
|
|
el.addEventListener('input', updateFieldFromInputs); |
|
|
} |
|
|
}); |
|
|
|
|
|
document.querySelectorAll('.close-modal, #btn-qr-cancel').forEach(btn => { |
|
|
btn.addEventListener('click', (e) => { |
|
|
const modal = e.target.closest('.fixed.z-\\[200\\]'); |
|
|
if(modal) modal.classList.add('hidden'); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
document.getElementById('qr-url')?.addEventListener('input', updateQRPreview); |
|
|
document.getElementById('btn-qr-apply')?.addEventListener('click', applyQRAndDownload); |
|
|
|
|
|
|
|
|
document.addEventListener('show-history', showHistory); |
|
|
|
|
|
|
|
|
document.addEventListener('layout-saved', updateFileCount); |
|
|
|
|
|
|
|
|
document.addEventListener('keydown', (e) => { |
|
|
if (e.ctrlKey || e.metaKey) { |
|
|
if (e.key === '=') { e.preventDefault(); zoomIn(); } |
|
|
if (e.key === '-') { e.preventDefault(); zoomOut(); } |
|
|
if (e.key === 's') { e.preventDefault(); downloadPDF(); } |
|
|
} |
|
|
if (e.key === 'Escape') { |
|
|
document.querySelectorAll('.fixed.z-\\[200\\]').forEach(m => m.classList.add('hidden')); |
|
|
if(state.designMode) toggleDesignMode(); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
document.addEventListener('mousedown', onMouseDown); |
|
|
document.addEventListener('mousemove', onMouseMove); |
|
|
document.addEventListener('mouseup', onMouseUp); |
|
|
} |
|
|
|
|
|
|
|
|
function debugLog(msg) { |
|
|
const c = document.getElementById('debug-console'); |
|
|
if(c) { |
|
|
const div = document.createElement('div'); |
|
|
div.textContent = `> ${msg}`; |
|
|
c.appendChild(div); |
|
|
c.scrollTop = c.scrollHeight; |
|
|
} |
|
|
console.log(msg); |
|
|
} |
|
|
|
|
|
function toast(msg, type = 'info') { |
|
|
const t = document.getElementById('toast'); |
|
|
const m = document.getElementById('toast-message'); |
|
|
m.textContent = msg; |
|
|
|
|
|
t.className = `fixed top-20 left-1/2 transform -translate-x-1/2 bg-slate-800 text-white px-6 py-3 rounded-lg shadow-xl z-[1000] transition-all duration-300 border-l-4 flex items-center gap-3 max-w-[90%] pointer-events-none translate-y-0 opacity-100 ${ |
|
|
type === 'success' ? 'border-green-500' : |
|
|
type === 'error' ? 'border-red-500' : 'border-blue-500' |
|
|
}`; |
|
|
|
|
|
const icon = t.querySelector('i'); |
|
|
icon.setAttribute('data-feather', type === 'success' ? 'check-circle' : type === 'error' ? 'alert-circle' : 'info'); |
|
|
feather.replace(); |
|
|
|
|
|
setTimeout(() => { |
|
|
t.classList.remove('translate-y-0', 'opacity-100'); |
|
|
t.classList.add('-translate-y-24', 'opacity-0'); |
|
|
}, 3000); |
|
|
} |
|
|
|
|
|
function loading(show, text) { |
|
|
const o = document.getElementById('loading-overlay'); |
|
|
const txt = document.getElementById('loading-text'); |
|
|
if (show) { |
|
|
txt.textContent = text || 'Processing...'; |
|
|
o.classList.remove('hidden'); |
|
|
o.classList.add('flex'); |
|
|
} else { |
|
|
o.classList.add('hidden'); |
|
|
o.classList.remove('flex'); |
|
|
} |
|
|
} |
|
|
|
|
|
function sanitize(str) { |
|
|
return String(str || '').replace(/[&<>'"`]/g, tag => ({ |
|
|
'&': '&', '<': '<', '>': '>', '"': '"', "'": ''', '`': '`' |
|
|
}[tag])); |
|
|
} |
|
|
|
|
|
|
|
|
function initDB() { |
|
|
return new Promise((resolve, reject) => { |
|
|
const req = indexedDB.open(DB_NAME, 1); |
|
|
req.onupgradeneeded = (e) => { |
|
|
const d = e.target.result; |
|
|
if (!d.objectStoreNames.contains(STORE)) d.createObjectStore(STORE, { keyPath: 'id' }); |
|
|
}; |
|
|
req.onsuccess = () => { db = req.result; resolve(); }; |
|
|
req.onerror = () => reject(req.error); |
|
|
}); |
|
|
} |
|
|
|
|
|
async function saveDoc(doc) { |
|
|
if (!doc.id) doc.id = Date.now().toString(); |
|
|
return new Promise((resolve, reject) => { |
|
|
const tx = db.transaction(STORE, 'readwrite'); |
|
|
tx.objectStore(STORE).put(doc); |
|
|
tx.oncomplete = () => { resolve(); updateFileCount(); }; |
|
|
tx.onerror = () => reject(tx.error); |
|
|
}); |
|
|
} |
|
|
|
|
|
async function getAllDocs() { |
|
|
return new Promise((resolve, reject) => { |
|
|
const tx = db.transaction(STORE, 'readonly'); |
|
|
const req = tx.objectStore(STORE).getAll(); |
|
|
req.onsuccess = () => resolve(req.result || []); |
|
|
req.onerror = () => reject(req.error); |
|
|
}); |
|
|
} |
|
|
|
|
|
async function updateFileCount() { |
|
|
const docs = await getAllDocs(); |
|
|
const footerCount = document.querySelector('#file-count'); |
|
|
if (footerCount) footerCount.textContent = docs.length; |
|
|
} |
|
|
|
|
|
|
|
|
function toggleTheme() { |
|
|
const html = document.documentElement; |
|
|
const current = html.getAttribute('data-theme'); |
|
|
const next = current === 'dark' ? 'light' : 'dark'; |
|
|
html.setAttribute('data-theme', next); |
|
|
toast(`Switched to ${next} mode`, 'success'); |
|
|
} |
|
|
|
|
|
function toggleSidebar() { |
|
|
const sb = document.getElementById('sidebar'); |
|
|
const ov = document.getElementById('sidebar-overlay'); |
|
|
sb.classList.toggle('-translate-x-full'); |
|
|
ov.classList.toggle('hidden'); |
|
|
} |
|
|
|
|
|
function switchTab(tab) { |
|
|
state.currentView = tab; |
|
|
document.querySelectorAll('.tab-btn').forEach(b => b.classList.toggle('active', b.dataset.tab === tab)); |
|
|
document.getElementById('template-view').classList.toggle('hidden', tab !== 'template'); |
|
|
document.getElementById('template-view').classList.toggle('flex', tab === 'template'); |
|
|
document.getElementById('pdf-view').classList.toggle('hidden', tab !== 'pdf'); |
|
|
document.getElementById('pdf-view').classList.toggle('flex', tab === 'pdf'); |
|
|
|
|
|
if (tab === 'pdf' && state.pdfDoc) { |
|
|
renderPDF(); |
|
|
document.getElementById('total-pg').textContent = state.pdfDoc.numPages; |
|
|
} else { |
|
|
document.getElementById('total-pg').textContent = '2'; |
|
|
} |
|
|
state.scale = 1; |
|
|
updateZoomDisplay(); |
|
|
} |
|
|
|
|
|
function toggleDesignMode() { |
|
|
state.designMode = !state.designMode; |
|
|
document.body.classList.toggle('design-mode', state.designMode); |
|
|
|
|
|
|
|
|
const standardPanel = document.getElementById('sidebar-standard'); |
|
|
const designPanel = document.getElementById('sidebar-design'); |
|
|
|
|
|
if (state.designMode) { |
|
|
if(standardPanel) standardPanel.style.display = 'none'; |
|
|
if(designPanel) designPanel.style.display = 'block'; |
|
|
toast('Design Mode Active', 'info'); |
|
|
updateFieldPositionsFromState(); |
|
|
} else { |
|
|
if(standardPanel) standardPanel.style.display = 'block'; |
|
|
if(designPanel) designPanel.style.display = 'none'; |
|
|
deselectField(); |
|
|
toast('Design Mode Off', 'info'); |
|
|
} |
|
|
} |
|
|
|
|
|
function updateFieldPositionsFromState() { |
|
|
if (state.currentLayoutConfig.fields) { |
|
|
Object.keys(state.currentLayoutConfig.fields).forEach(fieldId => { |
|
|
const field = state.currentLayoutConfig.fields[fieldId]; |
|
|
const element = document.getElementById(fieldId); |
|
|
if (element && field.position) { |
|
|
element.style.top = field.position.top; |
|
|
element.style.left = field.position.left; |
|
|
if (field.style) { |
|
|
Object.assign(element.style, field.style); |
|
|
} |
|
|
} |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
function selectField(id) { |
|
|
if (!state.designMode) return; |
|
|
deselectField(); |
|
|
state.selectedFieldId = id; |
|
|
const el = document.getElementById(id); |
|
|
if (el) { |
|
|
el.classList.add('selected'); |
|
|
|
|
|
|
|
|
const noSel = document.getElementById('no-field-selected'); |
|
|
const controls = document.getElementById('field-controls'); |
|
|
if (noSel) noSel.style.display = 'none'; |
|
|
if (controls) controls.style.display = 'block'; |
|
|
|
|
|
document.getElementById('prop-id').textContent = id; |
|
|
document.getElementById('prop-x').value = parseFloat(el.style.left) || 0; |
|
|
document.getElementById('prop-y').value = parseFloat(el.style.top) || 0; |
|
|
|
|
|
if (el.tagName === 'IMG') { |
|
|
document.getElementById('prop-size').parentElement.style.display = 'none'; |
|
|
document.getElementById('prop-color').parentElement.style.display = 'none'; |
|
|
document.getElementById('prop-weight').parentElement.style.display = 'none'; |
|
|
} else { |
|
|
document.getElementById('prop-size').parentElement.style.display = 'block'; |
|
|
document.getElementById('prop-color').parentElement.style.display = 'block'; |
|
|
document.getElementById('prop-weight').parentElement.style.display = 'block'; |
|
|
|
|
|
document.getElementById('prop-size').value = parseFloat(el.style.fontSize) || 14; |
|
|
document.getElementById('prop-weight').value = el.style.fontWeight || 'normal'; |
|
|
document.getElementById('prop-color').value = rgbToHex(el.style.color) || '#000000'; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
function deselectField() { |
|
|
if (state.selectedFieldId) { |
|
|
const el = document.getElementById(state.selectedFieldId); |
|
|
if (el) el.classList.remove('selected'); |
|
|
} |
|
|
state.selectedFieldId = null; |
|
|
const noSel = document.getElementById('no-field-selected'); |
|
|
const controls = document.getElementById('field-controls'); |
|
|
if (noSel) noSel.style.display = 'block'; |
|
|
if (controls) controls.style.display = 'none'; |
|
|
} |
|
|
|
|
|
function updateFieldFromInputs() { |
|
|
if (!state.selectedFieldId) return; |
|
|
const el = document.getElementById(state.selectedFieldId); |
|
|
if (!el) return; |
|
|
|
|
|
const x = parseFloat(document.getElementById('prop-x').value) || 0; |
|
|
const y = parseFloat(document.getElementById('prop-y').value) || 0; |
|
|
|
|
|
el.style.left = x + 'px'; |
|
|
el.style.top = y + 'px'; |
|
|
|
|
|
|
|
|
if (!state.fieldConfig[state.selectedFieldId]) { |
|
|
state.fieldConfig[state.selectedFieldId] = {}; |
|
|
} |
|
|
|
|
|
state.fieldConfig[state.selectedFieldId].top = y + 'px'; |
|
|
state.fieldConfig[state.selectedFieldId].left = x + 'px'; |
|
|
|
|
|
if (el.tagName !== 'IMG') { |
|
|
const size = document.getElementById('prop-size').value || '14'; |
|
|
const color = document.getElementById('prop-color').value || '#000000'; |
|
|
const weight = document.getElementById('prop-weight').value || 'normal'; |
|
|
|
|
|
el.style.fontSize = size + 'px'; |
|
|
el.style.color = color; |
|
|
el.style.fontWeight = weight; |
|
|
|
|
|
state.fieldConfig[state.selectedFieldId].fontSize = size + 'px'; |
|
|
state.fieldConfig[state.selectedFieldId].color = color; |
|
|
state.fieldConfig[state.selectedFieldId].fontWeight = weight; |
|
|
} |
|
|
|
|
|
saveFieldConfig(); |
|
|
} |
|
|
|
|
|
let dragInfo = { active: false, el: null, offsetX: 0, offsetY: 0 }; |
|
|
|
|
|
function onMouseDown(e) { |
|
|
if (!state.designMode) return; |
|
|
if (e.target.classList.contains('field')) { |
|
|
dragInfo.active = true; |
|
|
dragInfo.el = e.target; |
|
|
const rect = dragInfo.el.getBoundingClientRect(); |
|
|
dragInfo.offsetX = e.clientX - rect.left; |
|
|
dragInfo.offsetY = e.clientY - rect.top; |
|
|
selectField(dragInfo.el.id); |
|
|
e.preventDefault(); |
|
|
} |
|
|
} |
|
|
|
|
|
function onMouseMove(e) { |
|
|
if (!dragInfo.active || !dragInfo.el) return; |
|
|
e.preventDefault(); |
|
|
|
|
|
const parent = dragInfo.el.parentElement; |
|
|
const pRect = parent.getBoundingClientRect(); |
|
|
|
|
|
|
|
|
let scale = state.scale; |
|
|
|
|
|
let newLeft = (e.clientX - pRect.left - dragInfo.offsetX) / scale; |
|
|
let newTop = (e.clientY - pRect.top - dragInfo.offsetY) / scale; |
|
|
|
|
|
|
|
|
newLeft = Math.max(0, newLeft); |
|
|
newTop = Math.max(0, newTop); |
|
|
|
|
|
dragInfo.el.style.left = newLeft + 'px'; |
|
|
dragInfo.el.style.top = newTop + 'px'; |
|
|
|
|
|
|
|
|
if (state.selectedFieldId === dragInfo.el.id) { |
|
|
document.getElementById('prop-x').value = Math.round(newLeft); |
|
|
document.getElementById('prop-y').value = Math.round(newTop); |
|
|
} |
|
|
} |
|
|
|
|
|
function onMouseUp() { |
|
|
if (dragInfo.active && dragInfo.el) { |
|
|
|
|
|
if (!state.fieldConfig[dragInfo.el.id]) { |
|
|
state.fieldConfig[dragInfo.el.id] = {}; |
|
|
} |
|
|
state.fieldConfig[dragInfo.el.id].left = dragInfo.el.style.left; |
|
|
state.fieldConfig[dragInfo.el.id].top = dragInfo.el.style.top; |
|
|
saveFieldConfig(); |
|
|
} |
|
|
dragInfo.active = false; |
|
|
dragInfo.el = null; |
|
|
} |
|
|
|
|
|
function saveFieldConfig() { |
|
|
localStorage.setItem('pdf_field_config', JSON.stringify(state.fieldConfig)); |
|
|
saveLayoutToStorage(); |
|
|
} |
|
|
|
|
|
function loadFieldConfig() { |
|
|
const saved = localStorage.getItem('pdf_field_config'); |
|
|
if (saved) { |
|
|
try { |
|
|
state.fieldConfig = JSON.parse(saved); |
|
|
Object.keys(state.fieldConfig).forEach(id => { |
|
|
const el = document.getElementById(id); |
|
|
const cfg = state.fieldConfig[id]; |
|
|
if (el && cfg) { |
|
|
Object.assign(el.style, cfg); |
|
|
} |
|
|
}); |
|
|
} catch (e) { |
|
|
console.warn('Failed to load field config:', e); |
|
|
} |
|
|
} |
|
|
loadLayoutFromStorage(); |
|
|
} |
|
|
|
|
|
function saveLayoutToStorage() { |
|
|
const layoutData = { |
|
|
fields: {}, |
|
|
pages: [], |
|
|
metadata: { |
|
|
version: '1.0', |
|
|
savedAt: new Date().toISOString(), |
|
|
name: state.currentLayoutName || 'default' |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
document.querySelectorAll('.field').forEach(field => { |
|
|
layoutData.fields[field.id] = { |
|
|
position: { |
|
|
top: field.style.top, |
|
|
left: field.style.left |
|
|
}, |
|
|
style: { |
|
|
fontSize: field.style.fontSize, |
|
|
fontWeight: field.style.fontWeight, |
|
|
color: field.style.color, |
|
|
fontFamily: field.style.fontFamily |
|
|
}, |
|
|
type: field.tagName === 'IMG' ? 'image' : 'text', |
|
|
page: field.closest('.page')?.id || 'page1' |
|
|
}; |
|
|
}); |
|
|
|
|
|
|
|
|
['page1', 'page2'].forEach(pageId => { |
|
|
const page = document.getElementById(pageId); |
|
|
if (page) { |
|
|
layoutData.pages.push({ |
|
|
id: pageId, |
|
|
background: page.querySelector('img[src*="bg"]')?.src || '', |
|
|
dimensions: { |
|
|
width: page.offsetWidth, |
|
|
height: page.offsetHeight |
|
|
} |
|
|
}); |
|
|
} |
|
|
}); |
|
|
|
|
|
state.currentLayoutConfig = layoutData; |
|
|
localStorage.setItem('pdf_layout_config', JSON.stringify(layoutData)); |
|
|
return layoutData; |
|
|
} |
|
|
|
|
|
function loadLayoutFromStorage() { |
|
|
const saved = localStorage.getItem('pdf_layout_config'); |
|
|
if (saved) { |
|
|
try { |
|
|
const parsed = JSON.parse(saved); |
|
|
state.currentLayoutConfig = parsed; |
|
|
applyLayoutConfig(parsed); |
|
|
state.isLayoutLoaded = true; |
|
|
} catch (e) { |
|
|
console.warn('Failed to load layout config:', e); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
function applyLayoutConfig(config) { |
|
|
if (!config || !config.fields) return; |
|
|
|
|
|
|
|
|
Object.keys(config.fields).forEach(fieldId => { |
|
|
const fieldConfig = config.fields[fieldId]; |
|
|
const element = document.getElementById(fieldId); |
|
|
if (element && fieldConfig.position) { |
|
|
element.style.top = fieldConfig.position.top; |
|
|
element.style.left = fieldConfig.position.left; |
|
|
if (fieldConfig.style) { |
|
|
Object.assign(element.style, fieldConfig.style); |
|
|
} |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
config.pages?.forEach(pageConfig => { |
|
|
const pageElement = document.getElementById(pageConfig.id); |
|
|
if (pageElement && pageConfig.background) { |
|
|
const bgImg = pageElement.querySelector('img[id^="bg"]'); |
|
|
if (bgImg) { |
|
|
bgImg.src = pageConfig.background; |
|
|
} |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
function handleBgFile(page, file) { |
|
|
if (!file) return; |
|
|
|
|
|
if (file.type !== 'application/pdf' && !file.type.startsWith('image/')) { |
|
|
toast('Invalid file type. Use PDF or Image.', 'error'); |
|
|
return; |
|
|
} |
|
|
|
|
|
const obj = URL.createObjectURL(file); |
|
|
if (page === '1') { |
|
|
if (state.bg1Obj) URL.revokeObjectURL(state.bg1Obj); |
|
|
state.bg1Obj = obj; |
|
|
document.getElementById('bg1-name').textContent = file.name; |
|
|
} else { |
|
|
if (state.bg2Obj) URL.revokeObjectURL(state.bg2Obj); |
|
|
state.bg2Obj = obj; |
|
|
document.getElementById('bg2-name').textContent = file.name; |
|
|
} |
|
|
|
|
|
|
|
|
const reader = new FileReader(); |
|
|
reader.onload = e => { |
|
|
if (page === '1') state.bg1B64 = e.target.result; |
|
|
else state.bg2B64 = e.target.result; |
|
|
}; |
|
|
reader.readAsDataURL(file); |
|
|
} |
|
|
|
|
|
function applyBg() { |
|
|
if (state.bg1Obj) document.getElementById('bg1').src = state.bg1Obj; |
|
|
if (state.bg2Obj) document.getElementById('bg2').src = state.bg2Obj; |
|
|
toast('Background Applied', 'success'); |
|
|
} |
|
|
|
|
|
function handleJsonUpload(e) { |
|
|
const file = e.target.files[0]; |
|
|
if (!file) return; |
|
|
if (!file.name.toLowerCase().endsWith('.json')) { |
|
|
toast('Please select a JSON file', 'error'); |
|
|
return; |
|
|
} |
|
|
|
|
|
document.getElementById('json-name').textContent = file.name; |
|
|
const reader = new FileReader(); |
|
|
reader.onload = ev => { |
|
|
try { |
|
|
const raw = JSON.parse(ev.target.result); |
|
|
if (!Array.isArray(raw) || raw.length === 0) throw new Error('Empty or invalid data'); |
|
|
state.data = raw; |
|
|
state.currentIndex = 0; |
|
|
populateDataDropdown(); |
|
|
showData(0); |
|
|
toast(`Loaded ${raw.length} records`, 'success'); |
|
|
} catch (err) { |
|
|
toast('Invalid JSON Format', 'error'); |
|
|
debugLog(err); |
|
|
} |
|
|
}; |
|
|
reader.readAsText(file); |
|
|
} |
|
|
|
|
|
function loadSample() { |
|
|
state.data = [ |
|
|
{ "1": "Mr. Sample One", "2": "Position 1", "3": "Dept A", "4": "", "5": "01/01/2025", "6": "31/12/2025", "7": "Test Co., Ltd.", "8": "Bangkok", "9": "10110", "10": "Thailand", "11": "REF001", "12": "DOC001" }, |
|
|
{ "1": "Ms. Sample Two", "2": "Position 2", "3": "Dept B", "4": "", "5": "01/02/2025", "6": "28/02/2026", "7": "Example Co., Ltd.", "8": "Chiang Mai", "9": "50200", "10": "Thailand", "11": "REF002", "12": "DOC002" } |
|
|
]; |
|
|
state.currentIndex = 0; |
|
|
populateDataDropdown(); |
|
|
showData(0); |
|
|
toast('Sample Data Loaded', 'success'); |
|
|
} |
|
|
|
|
|
function populateDataDropdown() { |
|
|
const sel = document.getElementById('data-select'); |
|
|
sel.innerHTML = ''; |
|
|
state.data.forEach((item, idx) => { |
|
|
const opt = document.createElement('option'); |
|
|
opt.value = idx; |
|
|
opt.textContent = item['1'] || `Item ${idx+1}`; |
|
|
sel.appendChild(opt); |
|
|
}); |
|
|
sel.disabled = state.data.length <= 1; |
|
|
sel.value = state.currentIndex; |
|
|
sel.onchange = (e) => showData(e.target.value); |
|
|
} |
|
|
|
|
|
function showData(idx) { |
|
|
state.currentIndex = parseInt(idx); |
|
|
const data = state.data[state.currentIndex]; |
|
|
|
|
|
fieldIds.forEach(id => { |
|
|
const key = fieldToKey[id]; |
|
|
const val = data[key] || ''; |
|
|
const el = document.getElementById(id); |
|
|
if (el) { |
|
|
el.innerHTML = val ? `<b>${sanitize(val)}</b>` : `<span style="color:#94a3b8">${id}</span>`; |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
let photoUrl = data[fieldToKey['photo']] || ''; |
|
|
if (typeof photoUrl === 'string' && !photoUrl.startsWith('http') && !photoUrl.startsWith('data')) { |
|
|
photoUrl = ''; |
|
|
} |
|
|
document.getElementById('photo').src = photoUrl || 'https://via.placeholder.com/110x138?text=No+Photo'; |
|
|
|
|
|
|
|
|
const qrApi = 'https://api.qrserver.com/v1/create-qr-code/?size=300x300&data='; |
|
|
document.getElementById('qr1').src = qrApi + encodeURIComponent(window.location.href + '?id=' + (data['12']||'')); |
|
|
document.getElementById('qr2').src = qrApi + encodeURIComponent(window.location.href + '?p=2&id=' + (data['12']||'')); |
|
|
} |
|
|
|
|
|
|
|
|
async function handlePdfUpload(e) { |
|
|
const file = e.target.files[0]; |
|
|
if (!file || file.type !== 'application/pdf') { |
|
|
toast('Invalid PDF file', 'error'); |
|
|
return; |
|
|
} |
|
|
|
|
|
document.getElementById('pdf-name').textContent = file.name; |
|
|
const reader = new FileReader(); |
|
|
reader.onload = async (ev) => { |
|
|
loading(true, 'Loading PDF...'); |
|
|
try { |
|
|
const buffer = ev.target.result; |
|
|
state.originalPdfBytes = buffer; |
|
|
state.currentFileName = file.name; |
|
|
state.pdfDoc = await window.pdfjsLib.getDocument({ data: buffer }).promise; |
|
|
switchTab('pdf'); |
|
|
} catch (err) { |
|
|
toast('Failed to load PDF', 'error'); |
|
|
debugLog(err); |
|
|
} finally { |
|
|
loading(false); |
|
|
} |
|
|
}; |
|
|
reader.readAsArrayBuffer(file); |
|
|
} |
|
|
|
|
|
async function renderPDF() { |
|
|
if (!state.pdfDoc) return; |
|
|
const container = document.getElementById('pdf-content'); |
|
|
container.innerHTML = ''; |
|
|
|
|
|
for (let i = 1; i <= state.pdfDoc.numPages; i++) { |
|
|
const page = await state.pdfDoc.getPage(i); |
|
|
const viewport = page.getViewport({ scale: 1.5 * state.scale }); |
|
|
|
|
|
const canvas = document.createElement('canvas'); |
|
|
canvas.width = viewport.width; |
|
|
canvas.height = viewport.height; |
|
|
|
|
|
await page.render({ |
|
|
canvasContext: canvas.getContext('2d'), |
|
|
viewport: viewport |
|
|
}).promise; |
|
|
|
|
|
const div = document.createElement('div'); |
|
|
div.className = 'pdf-page-wrapper mb-4'; |
|
|
div.appendChild(canvas); |
|
|
container.appendChild(div); |
|
|
} |
|
|
} |
|
|
|
|
|
function goToPage(num) { |
|
|
|
|
|
|
|
|
document.getElementById('current-pg').value = num; |
|
|
} |
|
|
|
|
|
function zoomIn() { |
|
|
state.scale = Math.min(3, state.scale + 0.1); |
|
|
updateZoomDisplay(); |
|
|
if (state.currentView === 'pdf') renderPDF(); |
|
|
else updateTemplateZoom(); |
|
|
} |
|
|
|
|
|
function zoomOut() { |
|
|
state.scale = Math.max(0.2, state.scale - 0.1); |
|
|
updateZoomDisplay(); |
|
|
if (state.currentView === 'pdf') renderPDF(); |
|
|
else updateTemplateZoom(); |
|
|
} |
|
|
|
|
|
function updateZoomDisplay() { |
|
|
document.getElementById('zoom-text').textContent = Math.round(state.scale * 100) + '%'; |
|
|
} |
|
|
|
|
|
function updateTemplateZoom() { |
|
|
document.querySelectorAll('.page').forEach(p => { |
|
|
p.style.transform = `scale(${state.scale})`; |
|
|
|
|
|
p.style.marginBottom = `${(1261 * (state.scale - 1))}px`; |
|
|
}); |
|
|
} |
|
|
|
|
|
async function exportLayout() { |
|
|
const layout = saveLayoutToStorage(); |
|
|
const name = layout.metadata.name || 'layout'; |
|
|
|
|
|
const blob = new Blob([JSON.stringify(layout, null, 2)], { type: 'application/json' }); |
|
|
const link = document.createElement('a'); |
|
|
link.href = URL.createObjectURL(blob); |
|
|
link.download = `${name}_${new Date().getTime()}.json`; |
|
|
link.click(); |
|
|
|
|
|
toast('Layout Exported', 'success'); |
|
|
} |
|
|
async function importLayout(file) { |
|
|
if (!file || !file.name.endsWith('.json')) { |
|
|
toast('Please select a layout JSON file', 'error'); |
|
|
return; |
|
|
} |
|
|
|
|
|
document.getElementById('layout-import-name').textContent = file.name; |
|
|
|
|
|
const reader = new FileReader(); |
|
|
reader.onload = async (e) => { |
|
|
try { |
|
|
const layout = JSON.parse(e.target.result); |
|
|
if (!layout.fields || !layout.pages) { |
|
|
throw new Error('Invalid layout format'); |
|
|
} |
|
|
|
|
|
state.currentLayoutConfig = layout; |
|
|
applyLayoutConfig(layout); |
|
|
|
|
|
const layoutNameInput = document.getElementById('layout-name-input') || document.getElementById('design-layout-name'); |
|
|
if (layoutNameInput) { |
|
|
layoutNameInput.value = layout.metadata?.name || 'Imported Layout'; |
|
|
} |
|
|
|
|
|
|
|
|
applyLayoutConfig(layout); |
|
|
|
|
|
toast('Layout Imported Successfully', 'success'); |
|
|
state.isLayoutLoaded = true; |
|
|
} catch (err) { |
|
|
toast('Failed to import layout: ' + err.message, 'error'); |
|
|
debugLog(err); |
|
|
} |
|
|
}; |
|
|
reader.readAsText(file); |
|
|
} |
|
|
|
|
|
async function downloadPDF() { |
|
|
if (state.data.length === 0) { |
|
|
toast('No data to export', 'error'); |
|
|
return; |
|
|
} |
|
|
|
|
|
loading(true, 'Generating PDF...'); |
|
|
try { |
|
|
const data = state.data[state.currentIndex]; |
|
|
const layout = await saveLayoutToStorage(); |
|
|
const element = document.createElement('div'); |
|
|
|
|
|
|
|
|
element.style.width = '892px'; |
|
|
element.style.position = 'absolute'; |
|
|
element.style.left = '-9999px'; |
|
|
|
|
|
['page1', 'page2'].forEach(pid => { |
|
|
const clone = document.getElementById(pid).cloneNode(true); |
|
|
|
|
|
clone.querySelectorAll('.field').forEach(f => { |
|
|
f.classList.remove('selected'); |
|
|
f.style.border = 'none'; |
|
|
f.style.background = 'transparent'; |
|
|
}); |
|
|
element.appendChild(clone); |
|
|
}); |
|
|
document.body.appendChild(element); |
|
|
|
|
|
|
|
|
const bg1 = element.querySelector('#bg1'); |
|
|
const bg2 = element.querySelector('#bg2'); |
|
|
if(state.bg1B64) bg1.src = state.bg1B64; |
|
|
if(state.bg2B64) bg2.src = state.bg2B64; |
|
|
|
|
|
|
|
|
fieldIds.forEach(id => { |
|
|
const key = fieldToKey[id]; |
|
|
const val = data[key] || ''; |
|
|
const el = element.querySelector('#'+id); |
|
|
if(el) el.innerHTML = val ? `<b>${sanitize(val)}</b>` : ''; |
|
|
}); |
|
|
|
|
|
const photo = element.querySelector('#photo'); |
|
|
photo.src = data[fieldToKey['photo']] || 'https://via.placeholder.com/110x138?text=No+Photo'; |
|
|
|
|
|
await new Promise(r => setTimeout(r, 500)); |
|
|
|
|
|
const opt = { |
|
|
margin: 0, |
|
|
filename: `${data['1'] || state.currentLayoutName || 'doc'}.pdf`, |
|
|
image: { type: 'jpeg', quality: 0.98 }, |
|
|
html2canvas: { scale: 2, useCORS: true }, |
|
|
jsPDF: { unit: 'px', format: [892, 1261], orientation: 'portrait' } |
|
|
}; |
|
|
|
|
|
await html2pdf().set(opt).from(element).save(); |
|
|
document.body.removeChild(element); |
|
|
|
|
|
const doc = { |
|
|
id: Date.now().toString(), |
|
|
fileName: data['1'] ? `${data['1']}.pdf` : 'generated.pdf', |
|
|
date: new Date().toISOString(), |
|
|
layout: layout, |
|
|
data: data |
|
|
}; |
|
|
await saveDoc(doc); |
|
|
|
|
|
toast('PDF Downloaded & Layout Saved', 'success'); |
|
|
} catch (e) { |
|
|
toast('Generation Failed', 'error'); |
|
|
debugLog(e); |
|
|
} finally { |
|
|
loading(false); |
|
|
} |
|
|
} |
|
|
|
|
|
async function exportLayout() { |
|
|
const layout = await saveLayoutToStorage(); |
|
|
const blob = new Blob([JSON.stringify(layout, null, 2)], { type: 'application/json' }); |
|
|
const link = document.createElement('a'); |
|
|
link.href = URL.createObjectURL(blob); |
|
|
link.download = `${layout.metadata.name || 'layout'}_${new Date().getTime()}.json`; |
|
|
link.click(); |
|
|
toast('Layout Exported', 'success'); |
|
|
} |
|
|
|
|
|
async function importLayout(file) { |
|
|
if (!file || !file.name.endsWith('.json')) { |
|
|
toast('Please select a layout JSON file', 'error'); |
|
|
return; |
|
|
} |
|
|
|
|
|
const reader = new FileReader(); |
|
|
reader.onload = async (e) => { |
|
|
try { |
|
|
const layout = JSON.parse(e.target.result); |
|
|
if (!layout.fields || !layout.pages) { |
|
|
throw new Error('Invalid layout format'); |
|
|
} |
|
|
state.currentLayoutConfig = layout; |
|
|
applyLayoutConfig(layout); |
|
|
|
|
|
const layoutNameInput = document.getElementById('layout-name-input') || document.getElementById('design-layout-name'); |
|
|
if (layoutNameInput) { |
|
|
layoutNameInput.value = layout.metadata?.name || 'Unnamed Layout'; |
|
|
} |
|
|
|
|
|
toast('Layout Imported Successfully', 'success'); |
|
|
state.isLayoutLoaded = true; |
|
|
} catch (err) { |
|
|
toast('Failed to import layout: ' + err.message, 'error'); |
|
|
debugLog(err); |
|
|
} |
|
|
}; |
|
|
reader.readAsText(file); |
|
|
} |
|
|
|
|
|
async function saveLayoutToDB() { |
|
|
try { |
|
|
const layout = saveLayoutToStorage(); |
|
|
const doc = { |
|
|
id: `layout_${Date.now()}`, |
|
|
fileName: `${layout.metadata.name}_layout.json`, |
|
|
date: new Date().toISOString(), |
|
|
type: 'layout', |
|
|
data: layout, |
|
|
metadata: layout.metadata |
|
|
}; |
|
|
await saveDoc(doc); |
|
|
toast('Layout Saved to History', 'success'); |
|
|
return layout; |
|
|
} catch (err) { |
|
|
toast('Failed to save layout', 'error'); |
|
|
debugLog(err); |
|
|
return null; |
|
|
} |
|
|
} |
|
|
|
|
|
function newLayout() { |
|
|
const confirmNew = confirm('Create new layout? Unsaved changes will be lost.'); |
|
|
if (!confirmNew) return; |
|
|
|
|
|
|
|
|
document.querySelectorAll('.field').forEach(field => { |
|
|
field.style.top = ''; |
|
|
field.style.left = ''; |
|
|
field.style.fontSize = ''; |
|
|
field.style.fontWeight = ''; |
|
|
field.style.color = ''; |
|
|
field.classList.remove('selected'); |
|
|
}); |
|
|
|
|
|
state.fieldConfig = {}; |
|
|
state.currentLayoutConfig = { |
|
|
fields: {}, |
|
|
pages: [], |
|
|
metadata: {} |
|
|
}; |
|
|
state.isLayoutLoaded = false; |
|
|
|
|
|
|
|
|
const layoutNameInput = document.getElementById('layout-name-input') || document.getElementById('design-layout-name'); |
|
|
if (layoutNameInput) { |
|
|
layoutNameInput.value = 'New Layout'; |
|
|
} |
|
|
|
|
|
deselectField(); |
|
|
localStorage.removeItem('pdf_field_config'); |
|
|
localStorage.removeItem('pdf_layout_config'); |
|
|
|
|
|
toast('New Layout Created', 'success'); |
|
|
} |
|
|
async function downloadAllPDFs() { |
|
|
if (state.data.length === 0) return; |
|
|
if (!confirm(`Download ${state.data.length} PDFs?`)) return; |
|
|
|
|
|
for (let i = 0; i < state.data.length; i++) { |
|
|
state.currentIndex = i; |
|
|
showData(i); |
|
|
await new Promise(r => setTimeout(r, 200)); |
|
|
await downloadPDF(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function openQRModal() { |
|
|
document.getElementById('qr-url').value = window.location.href; |
|
|
updateQRPreview(); |
|
|
document.getElementById('qr-modal').classList.remove('hidden'); |
|
|
} |
|
|
|
|
|
function updateQRPreview() { |
|
|
const url = document.getElementById('qr-url').value; |
|
|
const canvas = document.getElementById('qr-preview-canvas'); |
|
|
const ctx = canvas.getContext('2d'); |
|
|
|
|
|
|
|
|
|
|
|
const img = new Image(); |
|
|
img.crossOrigin = "Anonymous"; |
|
|
img.src = `https://api.qrserver.com/v1/create-qr-code/?size=150x150&data=${encodeURIComponent(url)}`; |
|
|
img.onload = () => { |
|
|
ctx.clearRect(0,0,150,136); |
|
|
ctx.drawImage(img, 0, 0, 150, 136); |
|
|
}; |
|
|
} |
|
|
|
|
|
async function applyQRAndDownload() { |
|
|
|
|
|
|
|
|
if (!state.originalPdfBytes && state.currentView === 'pdf') { |
|
|
toast('Please upload a base PDF first', 'error'); |
|
|
return; |
|
|
} |
|
|
|
|
|
if (state.data.length > 0) { |
|
|
|
|
|
await downloadPDF(); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
try { |
|
|
loading(true, 'Processing...'); |
|
|
const url = document.getElementById('qr-url').value; |
|
|
|
|
|
|
|
|
const qrCanvas = document.createElement('canvas'); |
|
|
|
|
|
|
|
|
const qrImgUrl = `https://api.qrserver.com/v1/create-qr-code/?size=150x150&data=${encodeURIComponent(url)}`; |
|
|
|
|
|
const pdfDoc = await PDFLib.PDFDocument.load(state.originalPdfBytes); |
|
|
const pages = pdfDoc.getPages(); |
|
|
const qrImage = await pdfDoc.embedPng(qrImgUrl); |
|
|
|
|
|
const pngDims = qrImage.scale(0.5); |
|
|
|
|
|
pages[0].drawImage(qrImage, { |
|
|
x: 50, |
|
|
y: 50, |
|
|
width: pngDims.width, |
|
|
height: pngDims.height, |
|
|
}); |
|
|
|
|
|
const pdfBytes = await pdfDoc.save(); |
|
|
const blob = new Blob([pdfBytes], { type: 'application/pdf' }); |
|
|
const link = document.createElement('a'); |
|
|
link.href = URL.createObjectURL(blob); |
|
|
link.download = `qr_${state.currentFileName}`; |
|
|
link.click(); |
|
|
|
|
|
toast('QR Added & Downloaded', 'success'); |
|
|
document.getElementById('qr-modal').classList.add('hidden'); |
|
|
} catch (e) { |
|
|
toast('Error adding QR', 'error'); |
|
|
debugLog(e); |
|
|
} finally { |
|
|
loading(false); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
async function showHistory() { |
|
|
const list = document.getElementById('history-list'); |
|
|
list.innerHTML = ''; |
|
|
const docs = await getAllDocs(); |
|
|
|
|
|
if (docs.length === 0) { |
|
|
list.innerHTML = '<div class="p-4 text-center text-slate-500">No History</div>'; |
|
|
} else { |
|
|
docs.forEach(doc => { |
|
|
const div = document.createElement('div'); |
|
|
div.className = 'flex items-center gap-3 p-3 hover:bg-slate-50 dark:hover:bg-slate-700/50 rounded-lg cursor-pointer'; |
|
|
div.innerHTML = ` |
|
|
<div class="bg-blue-100 p-2 rounded-lg text-blue-600"><i data-feather="file-text"></i></div> |
|
|
<div class="flex-1 min-w-0"> |
|
|
<div class="font-bold text-slate-800 dark:text-slate-200 truncate">${doc.fileName || 'Untitled'}</div> |
|
|
<div class="text-xs text-slate-500">${new Date(doc.date).toLocaleString()}</div> |
|
|
</div> |
|
|
`; |
|
|
div.onclick = () => { |
|
|
if(doc.data) { |
|
|
state.data = doc.data; |
|
|
populateDataDropdown(); |
|
|
showData(0); |
|
|
} |
|
|
document.getElementById('history-modal').classList.add('hidden'); |
|
|
toast('History Loaded', 'success'); |
|
|
}; |
|
|
list.appendChild(div); |
|
|
}); |
|
|
feather.replace(); |
|
|
} |
|
|
document.getElementById('history-modal').classList.remove('hidden'); |
|
|
} |
|
|
|
|
|
function rgbToHex(rgb) { |
|
|
if (!rgb) return '#000000'; |
|
|
if (rgb.startsWith('#')) return rgb; |
|
|
const rgbValues = rgb.match(/\d+/g); |
|
|
if (!rgbValues) return '#000000'; |
|
|
return "#" + ((1 << 24) + (parseInt(rgbValues[0]) << 16) + (parseInt(rgbValues[1]) << 8) + parseInt(rgbValues[2])).toString(16).slice(1); |
|
|
} |
|
|
|
|
|
|
|
|
function showUsageExamples() { |
|
|
console.log('=== PDF Layout Wizard - Complete Usage Examples ===\n'); |
|
|
|
|
|
console.log('1. BASIC USAGE:'); |
|
|
console.log(' - Load sample data: Click "Load Sample" in sidebar'); |
|
|
console.log(' - Edit field positions: Toggle Design Mode (pencil icon in navbar)'); |
|
|
console.log(' - Download PDF: Click "Download PDF" in sidebar'); |
|
|
|
|
|
console.log('\n2. ADVANCED FEATURES:'); |
|
|
console.log(' ├─ Layout Management:'); |
|
|
console.log(' │ • Export Layout: Design Mode → Export Layout'); |
|
|
console.log(' │ • Import Layout: Sidebar → Import Layout'); |
|
|
console.log(' │ • Save Layout: Design Mode → Save Layout to History'); |
|
|
console.log(' │ • New Layout: Design Mode → New Layout'); |
|
|
console.log(' ├─ Data Management:'); |
|
|
console.log(' │ • JSON Import: Sidebar → Import JSON'); |
|
|
console.log(' │ • Data Dashboard: Navbar → Database icon'); |
|
|
console.log(' │ • Record Switching: Sidebar dropdown'); |
|
|
console.log(' ├─ PDF Features:'); |
|
|
console.log(' │ • PDF Upload: Sidebar → Import PDF'); |
|
|
console.log(' │ • PDF Viewer: Tab switch → PDF Viewer'); |
|
|
console.log(' │ • QR Integration: Navbar → Grid icon'); |
|
|
console.log(' └─ Export Options:'); |
|
|
console.log(' • Single PDF: Sidebar → Download PDF'); |
|
|
console.log(' • Batch Export: Sidebar → Download All'); |
|
|
console.log(' • Layout Export: Design Mode → Export Layout'); |
|
|
|
|
|
console.log('\n3. KEYBOARD SHORTCUTS:'); |
|
|
console.log(' - Ctrl + S: Download PDF'); |
|
|
console.log(' - Ctrl + =: Zoom In'); |
|
|
console.log(' - Ctrl + -: Zoom Out'); |
|
|
console.log(' - ESC: Close modals / Exit Design Mode'); |
|
|
|
|
|
console.log('\n4. DESIGN MODE WORKFLOW:'); |
|
|
console.log(' Step 1: Click Design Mode button (pencil icon)'); |
|
|
console.log(' Step 2: Click and drag fields to position'); |
|
|
console.log(' Step 3: Select field to edit properties (size, color, weight)'); |
|
|
console.log(' Step 4: Export layout for reuse'); |
|
|
console.log(' Step 5: Exit Design Mode (ESC or button)'); |
|
|
|
|
|
console.log('\n5. DATA WORKFLOW:'); |
|
|
console.log(' • JSON Format: Must be array of objects with numbered keys (1-12)'); |
|
|
console.log(' • Dashboard: Open in new tab for full CRUD operations'); |
|
|
console.log(' • Real-time Update: Dashboard changes reflect in main app'); |
|
|
|
|
|
console.log('\n6. PDF WORKFLOW:'); |
|
|
console.log(' • Upload Base PDF: Use PDF as template'); |
|
|
console.log(' • View Mode: Switch to PDF tab to preview'); |
|
|
console.log(' • QR Addition: Add QR codes to existing PDF'); |
|
|
console.log(' • Zoom Controls: Bottom HUD controls'); |
|
|
|
|
|
console.log('\n7. HISTORY & STORAGE:'); |
|
|
console.log(' • Auto-save: All generations saved to history'); |
|
|
console.log(' • Layout Persistence: Saved in browser IndexedDB'); |
|
|
console.log(' • Restore: Click history items to restore'); |
|
|
console.log(' • Export/Import: Share layouts across devices'); |
|
|
|
|
|
console.log('\n8. CUSTOMIZATION:'); |
|
|
console.log(' • CSS Variables: Modify :root colors in style.css'); |
|
|
console.log(' • Field Mapping: Edit fieldToKey object in script.js'); |
|
|
console.log(' • Default Layouts: Create preset layouts'); |
|
|
console.log(' • Branding: Update navbar title and icons'); |
|
|
console.log(' • Theme: Toggle light/dark mode'); |
|
|
|
|
|
console.log('\n9. SAMPLE COMMANDS:'); |
|
|
console.log(' loadSample() // Load demo data'); |
|
|
console.log(' toggleDesignMode() // Toggle field editing'); |
|
|
console.log(' exportLayout() // Save current layout'); |
|
|
console.log(' newLayout() // Reset to blank layout'); |
|
|
console.log(' showData(0) // Show first data record'); |
|
|
console.log(' downloadPDF() // Generate PDF'); |
|
|
console.log(' saveLayoutToDB() // Save layout to history'); |
|
|
console.log(' importLayout(file) // Import layout JSON'); |
|
|
|
|
|
console.log('\n10. TROUBLESHOOTING:'); |
|
|
console.log(' • Images not showing: Enable CORS in browser'); |
|
|
console.log(' • PDF generation slow: Reduce image sizes'); |
|
|
console.log(' • Layout not saving: Check browser storage limits'); |
|
|
console.log(' • QR codes broken: Verify URL includes protocol'); |
|
|
console.log(' • Fields misaligned: Check zoom level is 100%'); |
|
|
|
|
|
console.log('\n=== Ready to use! Check dashboard.html for data management ==='); |
|
|
} |
|
|
|
|
|
|
|
|
if (!localStorage.getItem('pdf_wizard_usage_shown')) { |
|
|
setTimeout(showUsageExamples, 2000); |
|
|
localStorage.setItem('pdf_wizard_usage_shown', 'true'); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|