Scrapbook Builder

Scrapbook Builder

This is the master log of all your scrapbook content. Click any cell (except 'Action') to edit the data. Changes will update the 'Scrapbook Preview' tab.

Page # Element Type Content Action

A visual preview of your scrapbook. Use the 'Add Elements' and 'Page Settings' tabs to build your pages.

Page 1 of 1

Add new text, images, or stickers to your scrapbook pages.

Manage your scrapbook's pages and apply visual themes.

Manage Pages

Page # Theme Action

Apply Theme to Current Preview Page

This page is empty.

'; } else { elements.forEach(item => { const el = document.createElement('div'); switch (item.type) { case 'text': el.className = 'scp-element-text'; el.textContent = item.content; break; case 'image': el.className = 'scp-element-image'; el.textContent = `[Image: ${item.content}]`; break; case 'sticker': el.className = 'scp-element-sticker'; el.textContent = item.content; break; } pagePreview.appendChild(el); }); } // Update page indicator const pageIndex = scrapbookPages.findIndex(p => p.id === currentPagePreview); pageIndicator.textContent = `Page ${pageIndex + 1} of ${scrapbookPages.length}`; // Update nav buttons prevPageBtn.disabled = (pageIndex === 0); nextPageBtn.disabled = (pageIndex === scrapbookPages.length - 1); }; // Renders Tab 4 (Config) Table const renderPageConfig = () => { pagesBody.innerHTML = ''; if (scrapbookPages.length === 0) { pagesBody.innerHTML = 'No pages created.'; return; } scrapbookPages.forEach((page, index) => { const row = document.createElement('tr'); row.setAttribute('data-id', page.id); row.innerHTML = ` ${index + 1} ${page.theme} `; pagesBody.appendChild(row); }); updateAddElementPageSelect(); }; // Renders dropdown in Tab 3 (Add) const updateAddElementPageSelect = () => { addPageSelect.innerHTML = ''; scrapbookPages.forEach((page, index) => { const option = document.createElement('option'); option.value = page.id; option.textContent = `Page ${index + 1}`; addPageSelect.appendChild(option); }); }; // --- EVENT HANDLERS (TAB 1 - DASHBOARD) --- logBody.addEventListener('blur', (e) => { const cell = e.target; if (cell.isContentEditable) { const row = cell.closest('tr'); const id = parseInt(row.getAttribute('data-id'), 10); const prop = cell.getAttribute('data-prop'); let newValue = cell.textContent.trim(); const itemIndex = scrapbookElements.findIndex(item => item.id === id); if (itemIndex === -1 || !prop) return; if (prop === 'pageId') { newValue = parseInt(newValue, 10) || 1; // Check if page exists if (!scrapbookPages.find(p => p.id === newValue)) { alert(`Error: Page ${newValue} does not exist. Reverting.`); cell.textContent = scrapbookElements[itemIndex][prop]; // Revert return; } } else if (prop === 'type') { if (!['text', 'image', 'sticker'].includes(newValue)) { alert('Error: Type must be "text", "image", or "sticker". Reverting.'); cell.textContent = scrapbookElements[itemIndex][prop]; // Revert return; } } scrapbookElements[itemIndex][prop] = newValue; renderPreview(); // Update preview } }, true); logBody.addEventListener('click', (e) => { if (e.target.classList.contains('scp-delete-element')) { const row = e.target.closest('tr'); const id = parseInt(row.getAttribute('data-id'), 10); if (confirm('Are you sure you want to delete this element?')) { scrapbookElements = scrapbookElements.filter(item => item.id !== id); renderContentsLog(); renderPreview(); } } }); // --- EVENT HANDLERS (TAB 2 - PREVIEW) --- prevPageBtn.addEventListener('click', () => { const pageIndex = scrapbookPages.findIndex(p => p.id === currentPagePreview); if (pageIndex > 0) { currentPagePreview = scrapbookPages[pageIndex - 1].id; renderPreview(); } }); nextPageBtn.addEventListener('click', () => { const pageIndex = scrapbookPages.findIndex(p => p.id === currentPagePreview); if (pageIndex < scrapbookPages.length - 1) { currentPagePreview = scrapbookPages[pageIndex + 1].id; renderPreview(); } }); // --- EVENT HANDLERS (TAB 3 - ADD) --- addTypeSelect.addEventListener('change', () => { const type = addTypeSelect.value; fieldImage.style.display = (type === 'image') ? 'block' : 'none'; fieldText.style.display = (type === 'text') ? 'block' : 'none'; fieldSticker.style.display = (type === 'sticker') ? 'block' : 'none'; }); addElementBtn.addEventListener('click', () => { const pageId = parseInt(addPageSelect.value, 10); const type = addTypeSelect.value; let content = ''; if (type === 'text') content = addText.value.trim(); else if (type === 'image') content = addImageUrl.value.trim(); else if (type === 'sticker') content = addSticker.value; if (!pageId || !type || !content) { alert('Please fill in all fields.'); return; } const newId = scrapbookElements.length > 0 ? Math.max(...scrapbookElements.map(i => i.id)) + 1 : 1; scrapbookElements.push({ id: newId, pageId, type, content }); // Reset form addText.value = ''; addImageUrl.value = ''; // Update views renderContentsLog(); renderPreview(); // Switch to dashboard (Spec II.B.4.o) tabs[0].click(); }); // --- EVENT HANDLERS (TAB 4 - CONFIG) --- addPageBtn.addEventListener('click', () => { const newId = scrapbookPages.length > 0 ? Math.max(...scrapbookPages.map(p => p.id)) + 1 : 1; scrapbookPages.push({ id: newId, theme: 'scp-theme-white' }); renderPageConfig(); renderPreview(); // Update nav }); pagesBody.addEventListener('click', (e) => { if (e.target.classList.contains('scp-delete-page')) { if (scrapbookPages.length <= 1) { alert('You must have at least one page.'); return; } const row = e.target.closest('tr'); const id = parseInt(row.getAttribute('data-id'), 10); if (confirm('Are you sure you want to delete this page and all its contents?')) { // Delete page scrapbookPages = scrapbookPages.filter(p => p.id !== id); // Delete elements on that page scrapbookElements = scrapbookElements.filter(e => e.pageId !== id); // Reset preview if we deleted the current page if (currentPagePreview === id) { currentPagePreview = scrapbookPages[0].id; } renderPageConfig(); renderContentsLog(); renderPreview(); } } }); applyThemeBtn.addEventListener('click', () => { const newTheme = themeSelect.value; const pageIndex = scrapbookPages.findIndex(p => p.id === currentPagePreview); if (pageIndex > -1) { scrapbookPages[pageIndex].theme = newTheme; renderPreview(); renderPageConfig(); // Update theme name in log } }); // --- PDF DOWNLOAD LOGIC --- // PDF for Tab 1 (Data Log) downloadLogPdfBtn.addEventListener('click', () => { // Check jsPDF if (typeof jspdf === 'undefined' || typeof jspdf.jsPDF === 'undefined') { console.error('SCP Tool: jsPDF library not loaded.'); return; } if (typeof jspdf.autoTable === 'undefined') { console.error('SCP Tool: jsPDF-AutoTable plugin not loaded.'); return; } try { const { jsPDF } = jspdf; const doc = new jsPDF(); const head = [['Page #', 'Element Type', 'Content']]; const body = scrapbookElements.map(item => [item.pageId, item.type, item.content]); if (body.length === 0) { alert('Log is empty. Nothing to download.'); return; } doc.setFontSize(18); doc.text("Scrapbook Contents Log", 14, 22); jsPDF.autoTable.default(doc, { startY: 30, head: head, body: body, theme: 'grid', headStyles: { fillColor: '#007bff', textColor: '#ffffff' }, alternateRowStyles: { fillColor: '#f4f7f6' } }); doc.save('scrapbook-contents-log.pdf'); } catch (e) { console.error('SCP Tool: Error generating log PDF:', e); } }); // PDF for Tab 2 (Visual Scrapbook) downloadVisualPdfBtn.addEventListener('click', () => { // Check jsPDF if (typeof jspdf === 'undefined' || typeof jspdf.jsPDF === 'undefined') { console.error('SCP Tool: jsPDF library not loaded.'); alert('Error: The PDF library (jsPDF) failed to load.'); return; } try { const { jsPDF } = jspdf; const doc = new jsPDF('landscape', 'pt', 'a4'); // A4 landscape const themes = { 'scp-theme-white': { bg: '#ffffff', text: '#333333', border: '#cccccc' }, 'scp-theme-craft': { bg: '#d2b48c', text: '#4b382a', border: '#4b382a' }, 'scp-theme-dark': { bg: '#2b2d42', text: '#edf2f4', border: '#edf2f4' } }; scrapbookPages.forEach((page, index) => { if (index > 0) { doc.addPage(); } const theme = themes[page.theme] || themes['scp-theme-white']; const elements = scrapbookElements.filter(e => e.pageId === page.id); // A4 landscape is 842 x 595 pts. Use 4:3 ratio const pageW = 560; const pageH = 420; const x = (doc.internal.pageSize.width - pageW) / 2; const y = (doc.internal.pageSize.height - pageH) / 2; // Draw page background and border doc.setDrawColor(theme.border); doc.setFillColor(theme.bg); doc.rect(x, y, pageW, pageH, 'FD'); doc.setTextColor(theme.text); // Draw page number doc.setFontSize(10); doc.text(`Page ${index + 1}`, x + pageW / 2, y + pageH - 10, { align: 'center' }); let currentY = y + 20; // Start drawing from top elements.forEach(item => { const itemX = x + 20; // Padding switch (item.type) { case 'text': doc.setFont('times', 'italic'); doc.setFontSize(14); const textLines = doc.splitTextToSize(item.content, pageW - 40); doc.text(textLines, itemX, currentY); currentY += (textLines.length * 14) + 10; break; case 'image': doc.setFont('helvetica', 'normal'); doc.setFontSize(10); doc.setDrawColor(theme.text); doc.setLineDash([5, 5], 0); doc.rect(itemX + 40, currentY, pageW - 100, 100, 'D'); // Dashed rect doc.setLineDash([], 0); doc.text(`[Image: ${item.content}]`, x + pageW / 2, currentY + 50, { align: 'center', maxWidth: pageW - 120 }); currentY += 100 + 10; break; case 'sticker': doc.setFontSize(40); doc.text(item.content, x + pageW / 2, currentY + 40, { align: 'center' }); currentY += 40 + 10; break; } }); }); doc.save('scrapbook-preview.pdf'); } catch (e) { console.error('SCP Tool: Error generating visual PDF:', e); } }); // --- INITIALIZATION --- const init = () => { // Initial Renders renderContentsLog(); renderPageConfig(); renderPreview(); // Set up tabs updateNavButtons(); // Show the first tab on load if (tabs.length > 0) { scpShowTab('scp-dashboard-tab', tabs[0]); } // Trigger change on load addTypeSelect.dispatchEvent(new Event('change')); }; init(); });