1803 lines
84 KiB
JavaScript
1803 lines
84 KiB
JavaScript
// Yoto Up Covers Web App
|
||
class YotoCoversApp {
|
||
constructor() {
|
||
this.images = [];
|
||
this.currentTemplate = 'classic';
|
||
this.currentTitleStyle = 'folded';
|
||
|
||
// Template presets - each defines a set of feature defaults
|
||
this.templates = {
|
||
classic: {
|
||
backgroundStyle: 'solid',
|
||
fontFamily: 'Arial',
|
||
fontWeight: 'normal',
|
||
footerColor: '#666666'
|
||
},
|
||
frozen: {
|
||
backgroundStyle: 'gradient',
|
||
fontFamily: 'Arial',
|
||
fontWeight: 'normal',
|
||
footerColor: '#4a90e2'
|
||
},
|
||
minimal: {
|
||
backgroundStyle: 'solid',
|
||
fontFamily: 'Arial',
|
||
fontWeight: 'normal',
|
||
footerColor: '#999999'
|
||
},
|
||
vintage: {
|
||
backgroundStyle: 'gradient',
|
||
fontFamily: 'Georgia',
|
||
fontWeight: 'normal',
|
||
footerColor: '#654321'
|
||
},
|
||
comic: {
|
||
backgroundStyle: 'comic',
|
||
fontFamily: 'Comic Sans MS',
|
||
fontWeight: 'bold',
|
||
footerColor: '#ff6b6b',
|
||
fullBleed: false,
|
||
panelMode: true,
|
||
titlePanel: { borderWidth: 2, rotation: -2, expand: true },
|
||
imagePanel: { borderWidth: 3, rotation: 1 },
|
||
footerPanel: { borderWidth: 2, rotation: 3 }
|
||
},
|
||
polaroid: {
|
||
backgroundStyle: 'polaroid',
|
||
fontFamily: 'Arial',
|
||
fontWeight: 'normal',
|
||
footerColor: '#666666',
|
||
fullBleed: false,
|
||
showFooter: false,
|
||
imagePosition: 'center'
|
||
}
|
||
};
|
||
|
||
this.printLayout = {
|
||
cardsPerRow: 2,
|
||
cardsPerColumn: 5,
|
||
pageMargin: 10,
|
||
cardSpacing: 5,
|
||
cardFit: 0
|
||
};
|
||
this.templateSettings = {
|
||
title: '',
|
||
footer: '',
|
||
accentColor: '#f1c40f',
|
||
titleColor: '#111111',
|
||
fullBleed: true,
|
||
titleShadow: false,
|
||
titleFontSize: null,
|
||
footerFontSize: null
|
||
};
|
||
|
||
this.previewZoom = {
|
||
level: 1.0,
|
||
min: 0.25,
|
||
max: 3.0,
|
||
step: 0.25
|
||
};
|
||
|
||
this.isFullscreen = false;
|
||
|
||
this.cardVariations = [];
|
||
this.activeCardIndex = -1; // Currently selected card for editing
|
||
this.init();
|
||
}
|
||
|
||
init() {
|
||
this.bindEvents();
|
||
this.updateUI();
|
||
this.updateCardList();
|
||
}
|
||
|
||
bindEvents() {
|
||
// Print layout settings
|
||
document.getElementById('cards-per-row').addEventListener('change', (e) => {
|
||
this.printLayout.cardsPerRow = parseInt(e.target.value);
|
||
this.generatePreview();
|
||
});
|
||
|
||
document.getElementById('cards-per-column').addEventListener('change', (e) => {
|
||
this.printLayout.cardsPerColumn = parseInt(e.target.value);
|
||
this.generatePreview();
|
||
});
|
||
|
||
document.getElementById('page-margin').addEventListener('input', (e) => {
|
||
this.printLayout.pageMargin = parseInt(e.target.value);
|
||
this.generatePreview();
|
||
});
|
||
|
||
document.getElementById('card-spacing').addEventListener('input', (e) => {
|
||
this.printLayout.cardSpacing = parseInt(e.target.value);
|
||
this.generatePreview();
|
||
});
|
||
|
||
document.getElementById('global-card-fit').addEventListener('change', (e) => {
|
||
this.printLayout.cardFit = parseFloat(e.target.value);
|
||
this.generatePreview();
|
||
});
|
||
|
||
// Template settings - REMOVED: Now per-card only
|
||
// document.getElementById('template-select').addEventListener('change', (e) => {
|
||
// this.currentTemplate = e.target.value;
|
||
// this.generatePreview();
|
||
// });
|
||
|
||
// document.getElementById('title-input').addEventListener('input', (e) => {
|
||
// this.templateSettings.title = e.target.value;
|
||
// this.generatePreview();
|
||
// });
|
||
|
||
// document.getElementById('footer-input').addEventListener('input', (e) => {
|
||
// this.templateSettings.footer = e.target.value;
|
||
// this.generatePreview();
|
||
// });
|
||
|
||
// Title style selection - REMOVED: Now per-card only
|
||
// document.querySelectorAll('.title-style-option').forEach(option => {
|
||
// option.addEventListener('click', () => {
|
||
// document.querySelectorAll('.title-style-option').forEach(opt => opt.classList.remove('selected'));
|
||
// option.classList.add('selected');
|
||
// this.currentTitleStyle = option.dataset.style;
|
||
// this.generatePreview();
|
||
// });
|
||
// });
|
||
|
||
// Color pickers - REMOVED: Now per-card only
|
||
// document.getElementById('accent-color').addEventListener('input', (e) => {
|
||
// this.templateSettings.accentColor = e.target.value;
|
||
// document.getElementById('accent-color-text').value = e.target.value;
|
||
// this.generatePreview();
|
||
// });
|
||
|
||
// document.getElementById('title-color').addEventListener('input', (e) => {
|
||
// this.templateSettings.titleColor = e.target.value;
|
||
// document.getElementById('title-color-text').value = e.target.value;
|
||
// this.generatePreview();
|
||
// });
|
||
|
||
// Checkboxes - REMOVED: Now per-card only
|
||
// document.getElementById('full-bleed').addEventListener('change', (e) => {
|
||
// this.templateSettings.fullBleed = e.target.checked;
|
||
// this.generatePreview();
|
||
// });
|
||
|
||
// document.getElementById('title-shadow').addEventListener('change', (e) => {
|
||
// this.templateSettings.titleShadow = e.target.checked;
|
||
// this.generatePreview();
|
||
// });
|
||
|
||
// Image upload
|
||
const imageUpload = document.getElementById('image-upload');
|
||
const fileInputLabel = document.querySelector('.file-input-label');
|
||
|
||
imageUpload.addEventListener('change', (e) => this.handleImageUpload(e.target.files));
|
||
imageUpload.addEventListener('dragover', (e) => {
|
||
e.preventDefault();
|
||
fileInputLabel.classList.add('dragover');
|
||
});
|
||
imageUpload.addEventListener('dragleave', (e) => {
|
||
e.preventDefault();
|
||
fileInputLabel.classList.remove('dragover');
|
||
});
|
||
imageUpload.addEventListener('drop', (e) => {
|
||
e.preventDefault();
|
||
fileInputLabel.classList.remove('dragover');
|
||
this.handleImageUpload(e.dataTransfer.files);
|
||
});
|
||
|
||
// Image management
|
||
document.getElementById('clear-images').addEventListener('click', () => this.clearAllImages());
|
||
|
||
// Actions
|
||
document.getElementById('generate-preview').addEventListener('click', () => this.generatePreview());
|
||
document.getElementById('download-html').addEventListener('click', () => this.downloadHTML());
|
||
document.getElementById('open-preview').addEventListener('click', () => this.openPreviewInNewTab());
|
||
document.getElementById('print-preview').addEventListener('click', () => this.printPreview());
|
||
|
||
// Preview zoom and fullscreen
|
||
document.getElementById('zoom-in').addEventListener('click', () => this.zoomIn());
|
||
document.getElementById('zoom-out').addEventListener('click', () => this.zoomOut());
|
||
document.getElementById('zoom-reset').addEventListener('click', () => this.resetZoom());
|
||
document.getElementById('fullscreen-btn').addEventListener('click', () => this.toggleFullscreen());
|
||
|
||
// ESC key to exit fullscreen
|
||
document.addEventListener('keydown', (e) => {
|
||
if (e.key === 'Escape' && this.isFullscreen) {
|
||
this.toggleFullscreen();
|
||
}
|
||
});
|
||
|
||
// Click on preview container close button area to exit fullscreen
|
||
document.querySelector('.preview-container').addEventListener('click', (e) => {
|
||
if (this.isFullscreen) {
|
||
const rect = e.currentTarget.getBoundingClientRect();
|
||
const closeButtonArea = {
|
||
left: rect.right - 200,
|
||
right: rect.right - 20,
|
||
top: rect.top + 20,
|
||
bottom: rect.top + 60
|
||
};
|
||
if (e.clientX >= closeButtonArea.left && e.clientX <= closeButtonArea.right &&
|
||
e.clientY >= closeButtonArea.top && e.clientY <= closeButtonArea.bottom) {
|
||
this.toggleFullscreen();
|
||
}
|
||
}
|
||
});
|
||
|
||
// Card management
|
||
document.getElementById('add-card').addEventListener('click', () => this.addCardVariation());
|
||
document.getElementById('active-card-select').addEventListener('change', (e) => {
|
||
const index = parseInt(e.target.value);
|
||
if (index >= 0) {
|
||
this.selectCard(index);
|
||
} else {
|
||
this.activeCardIndex = -1;
|
||
this.updateCardList();
|
||
this.showCardEditor();
|
||
}
|
||
});
|
||
document.getElementById('update-card').addEventListener('click', () => this.updateActiveCard());
|
||
document.getElementById('delete-card').addEventListener('click', () => this.deleteActiveCard());
|
||
|
||
// Card editor auto-update
|
||
document.getElementById('card-title-input').addEventListener('input', () => this.updateActiveCard());
|
||
document.getElementById('card-footer-input').addEventListener('input', () => this.updateActiveCard());
|
||
document.getElementById('card-template-preset').addEventListener('change', (e) => {
|
||
const templateName = e.target.value;
|
||
if (templateName) {
|
||
this.applyTemplatePreset(templateName);
|
||
}
|
||
});
|
||
document.getElementById('card-background-style').addEventListener('change', () => this.updateActiveCard());
|
||
document.getElementById('card-font-family').addEventListener('change', () => this.updateActiveCard());
|
||
document.getElementById('card-font-weight').addEventListener('change', () => this.updateActiveCard());
|
||
document.getElementById('card-accent-color').addEventListener('input', (e) => {
|
||
document.getElementById('card-accent-color-text').value = e.target.value;
|
||
this.updateActiveCard();
|
||
});
|
||
document.getElementById('card-title-color').addEventListener('input', (e) => {
|
||
document.getElementById('card-title-color-text').value = e.target.value;
|
||
this.updateActiveCard();
|
||
});
|
||
document.getElementById('card-footer-color').addEventListener('input', (e) => {
|
||
document.getElementById('card-footer-color-text').value = e.target.value;
|
||
this.updateActiveCard();
|
||
});
|
||
document.getElementById('card-full-bleed').addEventListener('change', () => this.updateActiveCard());
|
||
document.getElementById('card-show-accent').addEventListener('change', (e) => {
|
||
const accentGroup = document.getElementById('accent-color-group');
|
||
accentGroup.style.display = e.target.checked ? 'block' : 'none';
|
||
this.updateActiveCard();
|
||
});
|
||
document.getElementById('card-title-shadow').addEventListener('change', () => this.updateActiveCard());
|
||
document.getElementById('card-show-title').addEventListener('change', () => {
|
||
this.updateActiveCard();
|
||
this.updateSectionVisibility();
|
||
});
|
||
document.getElementById('card-show-footer').addEventListener('change', () => {
|
||
this.updateActiveCard();
|
||
this.updateSectionVisibility();
|
||
});
|
||
document.getElementById('card-title-font-size').addEventListener('input', () => this.updateActiveCard());
|
||
document.getElementById('card-title-x').addEventListener('input', () => this.updateActiveCard());
|
||
document.getElementById('card-title-y').addEventListener('input', () => this.updateActiveCard());
|
||
document.getElementById('card-title-draggable').addEventListener('change', () => this.updateActiveCard());
|
||
document.getElementById('card-footer-font-size').addEventListener('input', () => this.updateActiveCard());
|
||
document.getElementById('card-footer-x').addEventListener('input', () => this.updateActiveCard());
|
||
document.getElementById('card-footer-y').addEventListener('input', () => this.updateActiveCard());
|
||
document.getElementById('card-footer-draggable').addEventListener('change', () => this.updateActiveCard());
|
||
document.getElementById('card-image-fit').addEventListener('change', () => this.updateActiveCard());
|
||
document.getElementById('card-image-position').addEventListener('change', () => this.updateActiveCard());
|
||
document.getElementById('card-image-select').addEventListener('change', () => this.updateActiveCard());
|
||
|
||
// Panel settings
|
||
document.getElementById('card-panel-mode').addEventListener('change', () => {
|
||
this.updateActiveCard();
|
||
this.updateSectionVisibility();
|
||
});
|
||
document.getElementById('card-title-panel-border').addEventListener('input', () => this.updateActiveCard());
|
||
document.getElementById('card-title-panel-rotation').addEventListener('input', () => this.updateActiveCard());
|
||
document.getElementById('card-title-panel-expand').addEventListener('change', () => this.updateActiveCard());
|
||
document.getElementById('card-image-panel-border').addEventListener('input', () => this.updateActiveCard());
|
||
document.getElementById('card-image-panel-rotation').addEventListener('input', () => this.updateActiveCard());
|
||
document.getElementById('card-footer-panel-border').addEventListener('input', () => this.updateActiveCard());
|
||
document.getElementById('card-footer-panel-rotation').addEventListener('input', () => this.updateActiveCard());
|
||
|
||
// Title style selection auto-update
|
||
document.querySelectorAll('.title-style-option').forEach(option => {
|
||
option.addEventListener('click', () => {
|
||
document.querySelectorAll('.title-style-option').forEach(opt => opt.classList.remove('selected'));
|
||
option.classList.add('selected');
|
||
this.updateActiveCard();
|
||
});
|
||
});
|
||
|
||
// Global title font size
|
||
// document.getElementById('global-title-font-size').addEventListener('input', (e) => {
|
||
// this.globalTitleFontSize = parseInt(e.target.value);
|
||
// this.generatePreview();
|
||
// });
|
||
|
||
// Mobile toggles
|
||
document.getElementById('toggle-settings').addEventListener('click', () => {
|
||
const panel = document.querySelector('.settings-panel');
|
||
panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
|
||
});
|
||
|
||
document.getElementById('toggle-editor').addEventListener('click', () => {
|
||
const panel = document.querySelector('.edit-panel');
|
||
panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
|
||
});
|
||
|
||
// Section header click handlers for collapsing/expanding
|
||
document.querySelectorAll('.config-section h4').forEach(header => {
|
||
header.addEventListener('click', () => {
|
||
const section = header.parentElement;
|
||
section.classList.toggle('collapsed');
|
||
});
|
||
});
|
||
}
|
||
|
||
async handleImageUpload(files) {
|
||
const initialImageCount = this.images.length;
|
||
|
||
for (const file of files) {
|
||
if (file.type.startsWith('image/')) {
|
||
try {
|
||
const imageData = await this.readFileAsDataURL(file);
|
||
const coverImage = new CoverImage(file.name, imageData);
|
||
this.images.push(coverImage);
|
||
|
||
// Automatically create a card variation for this image
|
||
const variation = {
|
||
title: '',
|
||
footer: '',
|
||
imageIndex: this.images.length - 1, // Use the index of the newly added image
|
||
template: this.currentTemplate
|
||
};
|
||
this.cardVariations.push(variation);
|
||
|
||
} catch (error) {
|
||
this.showStatusMessage(`Error loading ${file.name}: ${error.message}`, 'error');
|
||
}
|
||
}
|
||
}
|
||
|
||
// If no images were uploaded before and we now have images, remove the default single card
|
||
if (initialImageCount === 0 && this.images.length > 0 && this.cardVariations.length === 1 &&
|
||
!this.cardVariations[0].title && !this.cardVariations[0].footer && this.cardVariations[0].imageIndex === -1) {
|
||
this.cardVariations = []; // Clear the default empty variation
|
||
}
|
||
|
||
this.updateImageList();
|
||
this.updateCardList();
|
||
this.generatePreview();
|
||
}
|
||
|
||
readFileAsDataURL(file) {
|
||
return new Promise((resolve, reject) => {
|
||
const reader = new FileReader();
|
||
reader.onload = () => resolve(reader.result);
|
||
reader.onerror = () => reject(new Error('Failed to read file'));
|
||
reader.readAsDataURL(file);
|
||
});
|
||
}
|
||
|
||
updateImageList() {
|
||
const imageList = document.getElementById('image-list');
|
||
imageList.innerHTML = '';
|
||
|
||
this.images.forEach((image, index) => {
|
||
const item = document.createElement('div');
|
||
item.className = 'image-item';
|
||
item.innerHTML = `
|
||
<div class="image-info">
|
||
<div class="image-name">${image.name}</div>
|
||
<div class="image-meta">${image.width || 0} × ${image.height || 0}px</div>
|
||
</div>
|
||
<div class="image-actions">
|
||
<button class="btn btn-danger" onclick="app.removeImage(${index})">×</button>
|
||
</div>
|
||
`;
|
||
imageList.appendChild(item);
|
||
});
|
||
}
|
||
|
||
removeImage(index) {
|
||
this.images.splice(index, 1);
|
||
this.updateImageList();
|
||
this.updateCardList();
|
||
this.generatePreview();
|
||
}
|
||
|
||
clearAllImages() {
|
||
this.images = [];
|
||
this.updateImageList();
|
||
this.updateCardList();
|
||
this.generatePreview();
|
||
}
|
||
|
||
applyTemplatePreset(templateName) {
|
||
if (!this.templates[templateName]) return;
|
||
|
||
const preset = this.templates[templateName];
|
||
document.getElementById('card-background-style').value = preset.backgroundStyle;
|
||
document.getElementById('card-font-family').value = preset.fontFamily;
|
||
document.getElementById('card-font-weight').value = preset.fontWeight;
|
||
document.getElementById('card-footer-color').value = preset.footerColor;
|
||
document.getElementById('card-footer-color-text').value = preset.footerColor;
|
||
|
||
// Set additional properties if defined in preset
|
||
if (preset.fullBleed !== undefined) {
|
||
document.getElementById('card-full-bleed').checked = preset.fullBleed;
|
||
}
|
||
if (preset.showFooter !== undefined) {
|
||
document.getElementById('card-show-footer').checked = preset.showFooter;
|
||
}
|
||
if (preset.imagePosition !== undefined) {
|
||
document.getElementById('card-image-position').value = preset.imagePosition;
|
||
}
|
||
|
||
// Set panel properties if defined in preset
|
||
document.getElementById('card-panel-mode').checked = preset.panelMode === true;
|
||
if (preset.titlePanel !== undefined) {
|
||
document.getElementById('card-title-panel-border').value = preset.titlePanel.borderWidth;
|
||
document.getElementById('card-title-panel-rotation').value = preset.titlePanel.rotation;
|
||
document.getElementById('card-title-panel-expand').checked = preset.titlePanel.expand;
|
||
}
|
||
if (preset.imagePanel !== undefined) {
|
||
document.getElementById('card-image-panel-border').value = preset.imagePanel.borderWidth;
|
||
document.getElementById('card-image-panel-rotation').value = preset.imagePanel.rotation;
|
||
}
|
||
if (preset.footerPanel !== undefined) {
|
||
document.getElementById('card-footer-panel-border').value = preset.footerPanel.borderWidth;
|
||
document.getElementById('card-footer-panel-rotation').value = preset.footerPanel.rotation;
|
||
}
|
||
|
||
this.updateActiveCard();
|
||
}
|
||
|
||
addCardVariation() {
|
||
const variation = {
|
||
title: '',
|
||
footer: '',
|
||
imageIndex: -1,
|
||
backgroundStyle: 'solid',
|
||
fontFamily: 'Arial',
|
||
fontWeight: 'normal',
|
||
footerColor: '#111111',
|
||
titleStyle: 'classic',
|
||
accentColor: '#f1c40f',
|
||
showAccent: false,
|
||
titleColor: '#111111',
|
||
fullBleed: true,
|
||
titleShadow: false,
|
||
showTitle: true,
|
||
showFooter: true,
|
||
imageFit: 'cover',
|
||
imagePosition: 'center',
|
||
titleFontSize: 20,
|
||
footerFontSize: 12,
|
||
titleX: 50,
|
||
titleY: 20,
|
||
footerX: 50,
|
||
footerY: 90,
|
||
titleDraggable: false,
|
||
footerDraggable: false,
|
||
panelMode: false,
|
||
titlePanel: { borderWidth: 0, rotation: 0, expand: false },
|
||
imagePanel: { borderWidth: 0, rotation: 0 },
|
||
footerPanel: { borderWidth: 0, rotation: 0 }
|
||
};
|
||
this.cardVariations.push(variation);
|
||
this.updateCardList();
|
||
this.generatePreview();
|
||
}
|
||
|
||
updateCardVariation(index, field, value) {
|
||
this.cardVariations[index][field] = value;
|
||
this.generatePreview();
|
||
}
|
||
|
||
removeCardVariation(index) {
|
||
this.cardVariations.splice(index, 1);
|
||
this.updateCardVariations();
|
||
this.generatePreview();
|
||
}
|
||
|
||
updateCardVariations() {
|
||
// This method is now replaced by updateCardList
|
||
this.updateCardList();
|
||
}
|
||
|
||
updateCardList() {
|
||
const container = document.getElementById('card-list');
|
||
container.innerHTML = '';
|
||
|
||
if (this.cardVariations.length === 0) {
|
||
container.innerHTML = '<p style="color: #999999; text-align: center; padding: 20px;">No cards yet. Upload images or click "Add Card".</p>';
|
||
return;
|
||
}
|
||
|
||
this.cardVariations.forEach((variation, index) => {
|
||
const cardItem = document.createElement('div');
|
||
cardItem.className = `card-list-item ${index === this.activeCardIndex ? 'active' : ''}`;
|
||
cardItem.onclick = () => this.selectCard(index);
|
||
cardItem.innerHTML = `
|
||
<div style="display: flex; align-items: center; justify-content: space-between;">
|
||
<div>
|
||
<strong>Card ${index + 1}</strong>
|
||
<span style="color: #666666; margin-left: 10px;">
|
||
${variation.title || 'No title'} •
|
||
${(() => {
|
||
for (const [presetName, preset] of Object.entries(this.templates)) {
|
||
if (variation.backgroundStyle === preset.backgroundStyle &&
|
||
variation.fontFamily === preset.fontFamily &&
|
||
variation.fontWeight === preset.fontWeight &&
|
||
variation.footerColor === preset.footerColor) {
|
||
return presetName;
|
||
}
|
||
}
|
||
return 'Custom';
|
||
})()} •
|
||
${variation.imageIndex >= 0 ? `Image ${variation.imageIndex + 1}` : 'Default image'}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
`;
|
||
container.appendChild(cardItem);
|
||
});
|
||
|
||
this.updateActiveCardSelector();
|
||
}
|
||
|
||
updateActiveCardSelector() {
|
||
const selector = document.getElementById('active-card-select');
|
||
selector.innerHTML = '<option value="-1">No card selected</option>';
|
||
|
||
this.cardVariations.forEach((variation, index) => {
|
||
const option = document.createElement('option');
|
||
option.value = index;
|
||
option.textContent = `Card ${index + 1}: ${variation.title || 'Untitled'}`;
|
||
if (index === this.activeCardIndex) option.selected = true;
|
||
selector.appendChild(option);
|
||
});
|
||
}
|
||
|
||
selectCard(index) {
|
||
this.activeCardIndex = index;
|
||
this.updateCardList();
|
||
this.showCardEditor();
|
||
}
|
||
|
||
selectCardFromPreview(index) {
|
||
// Called from iframe when clicking on a card in the preview
|
||
this.selectCard(index);
|
||
}
|
||
|
||
showCardEditor() {
|
||
const editor = document.getElementById('card-editor');
|
||
if (this.activeCardIndex === -1) {
|
||
editor.style.display = 'none';
|
||
return;
|
||
}
|
||
|
||
const variation = this.cardVariations[this.activeCardIndex];
|
||
if (!variation) return;
|
||
|
||
// Populate card settings
|
||
document.getElementById('card-editor-title').textContent = `Edit Card ${this.activeCardIndex + 1}`;
|
||
|
||
// Set template preset (detect which preset matches current features)
|
||
let matchingPreset = '';
|
||
for (const [presetName, preset] of Object.entries(this.templates)) {
|
||
let matches = variation.backgroundStyle === preset.backgroundStyle &&
|
||
variation.fontFamily === preset.fontFamily &&
|
||
variation.fontWeight === preset.fontWeight &&
|
||
variation.footerColor === preset.footerColor;
|
||
|
||
// Check additional properties if defined in preset
|
||
if (preset.fullBleed !== undefined && variation.fullBleed !== preset.fullBleed) matches = false;
|
||
if (preset.showFooter !== undefined && variation.showFooter !== preset.showFooter) matches = false;
|
||
if (preset.imagePosition !== undefined && variation.imagePosition !== preset.imagePosition) matches = false;
|
||
if (preset.panelMode !== undefined && variation.panelMode !== preset.panelMode) matches = false;
|
||
if (preset.titlePanel !== undefined) {
|
||
if (variation.titlePanel?.borderWidth !== preset.titlePanel.borderWidth ||
|
||
variation.titlePanel?.rotation !== preset.titlePanel.rotation ||
|
||
variation.titlePanel?.expand !== preset.titlePanel.expand) matches = false;
|
||
}
|
||
if (preset.imagePanel !== undefined) {
|
||
if (variation.imagePanel?.borderWidth !== preset.imagePanel.borderWidth ||
|
||
variation.imagePanel?.rotation !== preset.imagePanel.rotation) matches = false;
|
||
}
|
||
if (preset.footerPanel !== undefined) {
|
||
if (variation.footerPanel?.borderWidth !== preset.footerPanel.borderWidth ||
|
||
variation.footerPanel?.rotation !== preset.footerPanel.rotation) matches = false;
|
||
}
|
||
|
||
if (matches) {
|
||
matchingPreset = presetName;
|
||
break;
|
||
}
|
||
}
|
||
document.getElementById('card-template-preset').value = matchingPreset;
|
||
|
||
// Set individual features
|
||
document.getElementById('card-background-style').value = variation.backgroundStyle || 'solid';
|
||
document.getElementById('card-font-family').value = variation.fontFamily || 'Arial';
|
||
document.getElementById('card-font-weight').value = variation.fontWeight || 'normal';
|
||
document.getElementById('card-footer-color').value = variation.footerColor || '#111111';
|
||
document.getElementById('card-footer-color-text').value = variation.footerColor || '#111111';
|
||
|
||
// Set title
|
||
document.getElementById('card-title-input').value = variation.title || '';
|
||
|
||
// Set footer
|
||
document.getElementById('card-footer-input').value = variation.footer || '';
|
||
|
||
// Set title style
|
||
document.querySelectorAll('.title-style-option').forEach(option => {
|
||
option.classList.toggle('selected', option.dataset.style === (variation.titleStyle || 'classic'));
|
||
});
|
||
|
||
// Set colors
|
||
document.getElementById('card-accent-color').value = variation.accentColor || '#f1c40f';
|
||
document.getElementById('card-accent-color-text').value = variation.accentColor || '#f1c40f';
|
||
document.getElementById('card-show-accent').checked = variation.showAccent === true;
|
||
const accentGroup = document.getElementById('accent-color-group');
|
||
accentGroup.style.display = variation.showAccent ? 'block' : 'none';
|
||
document.getElementById('card-title-color').value = variation.titleColor || '#111111';
|
||
document.getElementById('card-title-color-text').value = variation.titleColor || '#111111';
|
||
|
||
// Set checkboxes
|
||
document.getElementById('card-full-bleed').checked = variation.fullBleed !== false;
|
||
document.getElementById('card-title-shadow').checked = variation.titleShadow === true;
|
||
document.getElementById('card-show-title').checked = variation.showTitle !== false;
|
||
document.getElementById('card-show-footer').checked = variation.showFooter !== false;
|
||
|
||
// Set title options
|
||
document.getElementById('card-title-font-size').value = variation.titleFontSize || 20;
|
||
document.getElementById('card-title-x').value = variation.titleX !== undefined ? variation.titleX : 50;
|
||
document.getElementById('card-title-y').value = variation.titleY !== undefined ? variation.titleY : 20;
|
||
document.getElementById('card-title-draggable').checked = variation.titleDraggable === true;
|
||
|
||
// Set footer options
|
||
document.getElementById('card-footer-font-size').value = variation.footerFontSize || 12;
|
||
document.getElementById('card-footer-x').value = variation.footerX !== undefined ? variation.footerX : 50;
|
||
document.getElementById('card-footer-y').value = variation.footerY !== undefined ? variation.footerY : 90;
|
||
document.getElementById('card-footer-draggable').checked = variation.footerDraggable === true;
|
||
|
||
// Set image options
|
||
document.getElementById('card-image-fit').value = variation.imageFit || 'cover';
|
||
document.getElementById('card-image-position').value = variation.imagePosition || 'center';
|
||
|
||
// Set panel options
|
||
document.getElementById('card-panel-mode').checked = variation.panelMode === true;
|
||
document.getElementById('card-title-panel-border').value = variation.titlePanel?.borderWidth || 0;
|
||
document.getElementById('card-title-panel-rotation').value = variation.titlePanel?.rotation || 0;
|
||
document.getElementById('card-title-panel-expand').checked = variation.titlePanel?.expand === true;
|
||
document.getElementById('card-image-panel-border').value = variation.imagePanel?.borderWidth || 0;
|
||
document.getElementById('card-image-panel-rotation').value = variation.imagePanel?.rotation || 0;
|
||
document.getElementById('card-footer-panel-border').value = variation.footerPanel?.borderWidth || 0;
|
||
document.getElementById('card-footer-panel-rotation').value = variation.footerPanel?.rotation || 0;
|
||
|
||
// Set card fit
|
||
document.getElementById('card-fit').value = variation.cardFit !== undefined ? variation.cardFit : '';
|
||
|
||
// Populate image options
|
||
const imageSelect = document.getElementById('card-image-select');
|
||
imageSelect.innerHTML = '<option value="-1">Use default image</option>';
|
||
this.images.forEach((img, imgIndex) => {
|
||
const option = document.createElement('option');
|
||
option.value = imgIndex;
|
||
option.textContent = img.name;
|
||
if (variation.imageIndex === imgIndex) option.selected = true;
|
||
imageSelect.appendChild(option);
|
||
});
|
||
|
||
editor.style.display = 'block';
|
||
|
||
// Update section visibility based on show/hide toggles
|
||
this.updateSectionVisibility();
|
||
}
|
||
|
||
updateSectionVisibility() {
|
||
const showTitle = document.getElementById('card-show-title').checked;
|
||
const showFooter = document.getElementById('card-show-footer').checked;
|
||
const panelMode = document.getElementById('card-panel-mode').checked;
|
||
|
||
const titleSection = document.getElementById('title-section');
|
||
const footerSection = document.getElementById('footer-section');
|
||
const panelSection = document.getElementById('panel-section');
|
||
|
||
if (showTitle) {
|
||
titleSection.classList.remove('collapsed');
|
||
} else {
|
||
titleSection.classList.add('collapsed');
|
||
}
|
||
|
||
if (showFooter) {
|
||
footerSection.classList.remove('collapsed');
|
||
} else {
|
||
footerSection.classList.add('collapsed');
|
||
}
|
||
|
||
if (panelMode) {
|
||
panelSection.classList.remove('collapsed');
|
||
} else {
|
||
panelSection.classList.add('collapsed');
|
||
}
|
||
}
|
||
|
||
initDragFunctionality() {
|
||
console.log('initDragFunctionality called');
|
||
// This will be called when the preview iframe loads
|
||
const iframe = document.getElementById('preview-iframe');
|
||
if (!iframe) {
|
||
console.log('No iframe found');
|
||
return;
|
||
}
|
||
|
||
const initializeDrag = () => {
|
||
console.log('initializeDrag called');
|
||
try {
|
||
const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
|
||
if (!iframeDoc) {
|
||
console.log('No iframe document');
|
||
return;
|
||
}
|
||
const titles = iframeDoc.querySelectorAll('.title, .comic-title-panel');
|
||
console.log('Found titles:', titles.length);
|
||
|
||
titles.forEach((title, index) => {
|
||
console.log(`Title ${index} cursor:`, title.style.cursor);
|
||
// Only make draggable if cursor is set to 'move' and not a comic panel
|
||
if (title.style.cursor !== 'move' || title.classList.contains('comic-title-panel')) {
|
||
console.log(`Title ${index} not draggable, skipping`);
|
||
return;
|
||
}
|
||
|
||
let isDragging = false;
|
||
let startX, startY, initialLeft, initialTop;
|
||
|
||
title.addEventListener('mousedown', (e) => {
|
||
console.log('Title mousedown fired');
|
||
if (title.style.cursor !== 'move') return;
|
||
|
||
isDragging = true;
|
||
|
||
// Get iframe position and convert mouse coordinates to iframe-relative
|
||
const iframeRect = iframe.getBoundingClientRect();
|
||
startX = e.clientX - iframeRect.left;
|
||
startY = e.clientY - iframeRect.top;
|
||
|
||
// Store initial position
|
||
initialLeft = parseFloat(title.style.left) || 50;
|
||
initialTop = parseFloat(title.style.top) || 20;
|
||
|
||
title.style.zIndex = '1000';
|
||
title.style.userSelect = 'none';
|
||
e.preventDefault();
|
||
});
|
||
|
||
const handleMouseMove = (e) => {
|
||
if (!isDragging) return;
|
||
|
||
// Get iframe position in main document
|
||
const iframeRect = iframe.getBoundingClientRect();
|
||
|
||
// Convert mouse coordinates to be relative to iframe viewport
|
||
const mouseX = e.clientX - iframeRect.left;
|
||
const mouseY = e.clientY - iframeRect.top;
|
||
|
||
const card = title.closest('.card');
|
||
const cardRect = card.getBoundingClientRect();
|
||
|
||
// Convert card position to be relative to iframe
|
||
const cardLeftRelative = cardRect.left - iframeRect.left;
|
||
const cardTopRelative = cardRect.top - iframeRect.top;
|
||
|
||
// Calculate mouse position relative to the card (within iframe)
|
||
const relativeMouseX = mouseX - cardLeftRelative;
|
||
const relativeMouseY = mouseY - cardTopRelative;
|
||
|
||
// Convert to percentage
|
||
const newLeft = Math.max(0, Math.min(100, (relativeMouseX / cardRect.width) * 100));
|
||
const newTop = Math.max(0, Math.min(100, (relativeMouseY / cardRect.height) * 100));
|
||
|
||
console.log('Drag debug:', { mouseX, mouseY, iframeRect, cardRect, cardLeftRelative, cardTopRelative, relativeMouseX, relativeMouseY, newLeft, newTop });
|
||
|
||
title.style.left = `${newLeft}%`;
|
||
title.style.top = `${newTop}%`;
|
||
|
||
console.log('Title style set to:', title.style.left, title.style.top);
|
||
|
||
// Update the form values if this card is currently selected
|
||
const cardIndex = parseInt(card.getAttribute('data-card-index'));
|
||
if (cardIndex === this.activeCardIndex && this.cardVariations[cardIndex]) {
|
||
document.getElementById('card-title-x').value = Math.round(newLeft);
|
||
document.getElementById('card-title-y').value = Math.round(newTop);
|
||
|
||
// Update the variation data
|
||
this.cardVariations[cardIndex].titleX = Math.round(newLeft);
|
||
this.cardVariations[cardIndex].titleY = Math.round(newTop);
|
||
}
|
||
};
|
||
|
||
const handleMouseUp = () => {
|
||
if (isDragging) {
|
||
isDragging = false;
|
||
title.style.zIndex = '';
|
||
title.style.userSelect = '';
|
||
}
|
||
};
|
||
|
||
// Add event listeners to both iframe document and main document
|
||
// to handle mouse events that might occur outside the iframe
|
||
iframeDoc.addEventListener('mousemove', handleMouseMove);
|
||
iframeDoc.addEventListener('mouseup', handleMouseUp);
|
||
document.addEventListener('mousemove', handleMouseMove);
|
||
document.addEventListener('mouseup', handleMouseUp);
|
||
});
|
||
|
||
// Initialize footer drag functionality
|
||
const footers = iframeDoc.querySelectorAll('.footer, .comic-footer-panel');
|
||
console.log('Found footers:', footers.length);
|
||
|
||
footers.forEach((footer, index) => {
|
||
console.log(`Footer ${index} cursor:`, footer.style.cursor);
|
||
// Only make draggable if cursor is set to 'move' and not a comic panel
|
||
if (footer.style.cursor !== 'move' || footer.classList.contains('comic-footer-panel')) {
|
||
console.log(`Footer ${index} not draggable, skipping`);
|
||
return;
|
||
}
|
||
|
||
let isDragging = false;
|
||
let startX, startY, initialLeft, initialBottom;
|
||
|
||
footer.addEventListener('mousedown', (e) => {
|
||
console.log('Footer mousedown fired');
|
||
if (footer.style.cursor !== 'move') return;
|
||
|
||
isDragging = true;
|
||
|
||
// Get iframe position and convert mouse coordinates to iframe-relative
|
||
const iframeRect = iframe.getBoundingClientRect();
|
||
startX = e.clientX - iframeRect.left;
|
||
startY = e.clientY - iframeRect.top;
|
||
|
||
// Store initial position
|
||
initialLeft = parseFloat(footer.style.left) || 50;
|
||
initialBottom = parseFloat(footer.style.bottom) || 10;
|
||
|
||
footer.style.zIndex = '1000';
|
||
footer.style.userSelect = 'none';
|
||
e.preventDefault();
|
||
});
|
||
|
||
const handleMouseMove = (e) => {
|
||
if (!isDragging) return;
|
||
|
||
// Get iframe position in main document
|
||
const iframeRect = iframe.getBoundingClientRect();
|
||
|
||
// Convert mouse coordinates to be relative to iframe viewport
|
||
const mouseX = e.clientX - iframeRect.left;
|
||
const mouseY = e.clientY - iframeRect.top;
|
||
|
||
const card = footer.closest('.card');
|
||
const cardRect = card.getBoundingClientRect();
|
||
|
||
// Calculate new position as percentage
|
||
const deltaX = mouseX - startX;
|
||
const deltaY = mouseY - startY;
|
||
|
||
const newLeftPercent = Math.max(0, Math.min(100, initialLeft + (deltaX / cardRect.width) * 100));
|
||
const newBottomPercent = Math.max(0, Math.min(100, initialBottom - (deltaY / cardRect.height) * 100));
|
||
|
||
footer.style.left = `${newLeftPercent}%`;
|
||
footer.style.bottom = `${newBottomPercent}%`;
|
||
|
||
// Update the card data
|
||
const cardIndex = Array.from(card.parentElement.children).indexOf(card);
|
||
if (this.cardVariations[cardIndex]) {
|
||
this.cardVariations[cardIndex].footerX = Math.round(newLeftPercent);
|
||
this.cardVariations[cardIndex].footerY = Math.round(100 - newBottomPercent);
|
||
this.updateUI();
|
||
}
|
||
};
|
||
|
||
const handleMouseUp = () => {
|
||
if (isDragging) {
|
||
isDragging = false;
|
||
footer.style.zIndex = '';
|
||
footer.style.userSelect = '';
|
||
}
|
||
};
|
||
|
||
// Add event listeners to both iframe document and main document
|
||
// to handle mouse events that might occur outside the iframe
|
||
iframeDoc.addEventListener('mousemove', handleMouseMove);
|
||
iframeDoc.addEventListener('mouseup', handleMouseUp);
|
||
document.addEventListener('mousemove', handleMouseMove);
|
||
document.addEventListener('mouseup', handleMouseUp);
|
||
});
|
||
} catch (e) {
|
||
// Ignore cross-origin errors
|
||
console.log('Drag functionality initialization failed:', e);
|
||
}
|
||
};
|
||
|
||
// Try to initialize immediately if the iframe is already loaded
|
||
if (iframe.contentDocument && (iframe.contentDocument.readyState === 'complete' || iframe.contentDocument.readyState === 'interactive')) {
|
||
console.log('Iframe already loaded, initializing immediately');
|
||
initializeDrag();
|
||
} else {
|
||
console.log('Setting iframe onload');
|
||
iframe.onload = () => {
|
||
console.log('iframe onload fired');
|
||
initializeDrag();
|
||
};
|
||
}
|
||
}
|
||
|
||
updateActiveCard() {
|
||
if (this.activeCardIndex === -1) return;
|
||
|
||
const variation = this.cardVariations[this.activeCardIndex];
|
||
variation.backgroundStyle = document.getElementById('card-background-style').value;
|
||
variation.fontFamily = document.getElementById('card-font-family').value;
|
||
variation.fontWeight = document.getElementById('card-font-weight').value;
|
||
variation.footerColor = document.getElementById('card-footer-color').value;
|
||
variation.title = document.getElementById('card-title-input').value;
|
||
variation.footer = document.getElementById('card-footer-input').value;
|
||
variation.titleStyle = document.querySelector('.title-style-option.selected')?.dataset.style || 'classic';
|
||
variation.accentColor = document.getElementById('card-accent-color').value;
|
||
variation.showAccent = document.getElementById('card-show-accent').checked;
|
||
variation.titleColor = document.getElementById('card-title-color').value;
|
||
variation.fullBleed = document.getElementById('card-full-bleed').checked;
|
||
variation.titleShadow = document.getElementById('card-title-shadow').checked;
|
||
variation.showTitle = document.getElementById('card-show-title').checked;
|
||
variation.showFooter = document.getElementById('card-show-footer').checked;
|
||
variation.titleFontSize = parseInt(document.getElementById('card-title-font-size').value);
|
||
variation.titleX = parseInt(document.getElementById('card-title-x').value);
|
||
variation.titleY = parseInt(document.getElementById('card-title-y').value);
|
||
variation.titleDraggable = document.getElementById('card-title-draggable').checked;
|
||
variation.footerFontSize = parseInt(document.getElementById('card-footer-font-size').value);
|
||
variation.footerX = parseInt(document.getElementById('card-footer-x').value);
|
||
variation.footerY = parseInt(document.getElementById('card-footer-y').value);
|
||
variation.footerDraggable = document.getElementById('card-footer-draggable').checked;
|
||
variation.imageFit = document.getElementById('card-image-fit').value;
|
||
variation.imagePosition = document.getElementById('card-image-position').value;
|
||
variation.imageIndex = parseInt(document.getElementById('card-image-select').value);
|
||
|
||
// Panel settings
|
||
variation.panelMode = document.getElementById('card-panel-mode').checked;
|
||
variation.titlePanel = {
|
||
borderWidth: parseInt(document.getElementById('card-title-panel-border').value),
|
||
rotation: parseInt(document.getElementById('card-title-panel-rotation').value),
|
||
expand: document.getElementById('card-title-panel-expand').checked
|
||
};
|
||
variation.imagePanel = {
|
||
borderWidth: parseInt(document.getElementById('card-image-panel-border').value),
|
||
rotation: parseInt(document.getElementById('card-image-panel-rotation').value)
|
||
};
|
||
variation.footerPanel = {
|
||
borderWidth: parseInt(document.getElementById('card-footer-panel-border').value),
|
||
rotation: parseInt(document.getElementById('card-footer-panel-rotation').value)
|
||
};
|
||
|
||
// Update card fit
|
||
const cardFitValue = document.getElementById('card-fit').value;
|
||
if (cardFitValue === '') {
|
||
delete variation.cardFit;
|
||
} else {
|
||
variation.cardFit = parseFloat(cardFitValue);
|
||
}
|
||
|
||
this.updateCardList();
|
||
this.generatePreview();
|
||
}
|
||
|
||
deleteActiveCard() {
|
||
if (this.activeCardIndex === -1) return;
|
||
|
||
if (confirm(`Delete Card ${this.activeCardIndex + 1}?`)) {
|
||
this.cardVariations.splice(this.activeCardIndex, 1);
|
||
this.activeCardIndex = -1;
|
||
this.updateCardList();
|
||
this.showCardEditor();
|
||
this.generatePreview();
|
||
}
|
||
}
|
||
|
||
async generatePreview() {
|
||
const placeholder = document.getElementById('preview-placeholder');
|
||
const iframe = document.getElementById('preview-iframe');
|
||
|
||
if (this.images.length === 0 && !this.templateSettings.title) {
|
||
placeholder.textContent = 'Add an image or enter a title to generate preview';
|
||
placeholder.style.display = 'flex';
|
||
iframe.style.display = 'none';
|
||
return;
|
||
}
|
||
|
||
placeholder.innerHTML = '<div class="loading"></div> Generating preview...';
|
||
placeholder.style.display = 'flex';
|
||
iframe.style.display = 'none';
|
||
|
||
try {
|
||
const html = await this.generateHTML();
|
||
const blob = new Blob([html], { type: 'text/html' });
|
||
const url = URL.createObjectURL(blob);
|
||
|
||
iframe.src = url;
|
||
iframe.style.display = 'block';
|
||
placeholder.style.display = 'none';
|
||
|
||
// Apply zoom after iframe loads
|
||
iframe.onload = () => {
|
||
this.initDragFunctionality();
|
||
this.updateZoom();
|
||
};
|
||
|
||
// Clean up the previous blob URL
|
||
if (iframe.dataset.previousUrl) {
|
||
URL.revokeObjectURL(iframe.dataset.previousUrl);
|
||
}
|
||
iframe.dataset.previousUrl = url;
|
||
|
||
} catch (error) {
|
||
placeholder.textContent = `Error generating preview: ${error.message}`;
|
||
this.showStatusMessage(`Preview generation failed: ${error.message}`, 'error');
|
||
}
|
||
}
|
||
|
||
async generateHTML() {
|
||
// Determine number of cards to generate
|
||
let totalCards;
|
||
if (this.cardVariations.length > 0) {
|
||
totalCards = this.cardVariations.length;
|
||
} else if (this.images.length > 0) {
|
||
totalCards = this.images.length; // Create one card per image if no variations
|
||
} else {
|
||
totalCards = 1; // Single card if no images or variations
|
||
}
|
||
|
||
// For print sheet, arrange cards in grid, but don't exceed available space
|
||
const cardsPerPage = this.printLayout.cardsPerRow * this.printLayout.cardsPerColumn;
|
||
const pagesNeeded = Math.ceil(totalCards / cardsPerPage);
|
||
const totalCardsToGenerate = pagesNeeded * cardsPerPage; // Fill pages completely, but we'll hide empty slots
|
||
|
||
const cards = [];
|
||
|
||
// Generate cards
|
||
for (let i = 0; i < totalCardsToGenerate; i++) {
|
||
if (i < totalCards) {
|
||
let imageIndex, cardTitle, cardFooter, cardTitleStyle, cardAccentColor, cardTitleColor, cardFullBleed, cardTitleShadow;
|
||
let cardBackgroundStyle, cardFontFamily, cardFontWeight, cardFooterColor, cardShowTitle, cardShowFooter;
|
||
let cardImageFit, cardImagePosition, cardTitleFontSize, cardTitleX, cardTitleY, cardTitleDraggable;
|
||
let cardFooterFontSize, cardFooterX, cardFooterY, cardFooterDraggable;
|
||
let cardShowAccent;
|
||
let cardPanelMode, cardTitlePanel, cardImagePanel, cardFooterPanel;
|
||
let cardFit;
|
||
|
||
if (this.cardVariations.length > 0) {
|
||
// Use variation settings
|
||
const variation = this.cardVariations[i];
|
||
imageIndex = variation && variation.imageIndex >= 0 ? variation.imageIndex : 0;
|
||
cardTitle = (variation && variation.title) || `Card ${i + 1}`;
|
||
cardFooter = (variation && variation.footer) || '';
|
||
cardTitleStyle = (variation && variation.titleStyle) || 'classic';
|
||
cardAccentColor = (variation && variation.accentColor) || '#f1c40f';
|
||
cardShowAccent = variation && variation.showAccent !== undefined ? variation.showAccent : false;
|
||
cardTitleColor = (variation && variation.titleColor) || '#111111';
|
||
cardFullBleed = variation && variation.fullBleed !== undefined ? variation.fullBleed : true;
|
||
cardTitleShadow = variation && variation.titleShadow !== undefined ? variation.titleShadow : false;
|
||
// Individual features
|
||
cardBackgroundStyle = (variation && variation.backgroundStyle) || 'solid';
|
||
cardFontFamily = (variation && variation.fontFamily) || 'Arial';
|
||
cardFontWeight = (variation && variation.fontWeight) || 'normal';
|
||
cardFooterColor = (variation && variation.footerColor) || '#111111';
|
||
cardShowTitle = variation && variation.showTitle !== undefined ? variation.showTitle : true;
|
||
cardShowFooter = variation && variation.showFooter !== undefined ? variation.showFooter : true;
|
||
cardImageFit = (variation && variation.imageFit) || 'cover';
|
||
cardImagePosition = (variation && variation.imagePosition) || 'center';
|
||
cardTitleFontSize = (variation && variation.titleFontSize) || 20;
|
||
cardTitleX = variation && variation.titleX !== undefined ? variation.titleX : 50;
|
||
cardTitleY = variation && variation.titleY !== undefined ? variation.titleY : 20;
|
||
cardTitleDraggable = variation && variation.titleDraggable !== undefined ? variation.titleDraggable : false;
|
||
cardFooterFontSize = (variation && variation.footerFontSize) || 12;
|
||
cardFooterX = variation && variation.footerX !== undefined ? variation.footerX : 50;
|
||
cardFooterY = variation && variation.footerY !== undefined ? variation.footerY : 90;
|
||
cardFooterDraggable = variation && variation.footerDraggable !== undefined ? variation.footerDraggable : false;
|
||
cardPanelMode = variation && variation.panelMode !== undefined ? variation.panelMode : false;
|
||
cardTitlePanel = variation && variation.titlePanel ? variation.titlePanel : { borderWidth: 0, rotation: 0, expand: false };
|
||
cardImagePanel = variation && variation.imagePanel ? variation.imagePanel : { borderWidth: 0, rotation: 0 };
|
||
cardFooterPanel = variation && variation.footerPanel ? variation.footerPanel : { borderWidth: 0, rotation: 0 };
|
||
cardFit = variation && variation.cardFit !== undefined ? variation.cardFit : this.printLayout.cardFit;
|
||
} else {
|
||
// No variations - auto-assign images
|
||
imageIndex = i < this.images.length ? i : 0;
|
||
cardTitle = `Card ${i + 1}`;
|
||
cardFooter = '';
|
||
cardTitleStyle = 'classic';
|
||
cardAccentColor = '#f1c40f';
|
||
cardShowAccent = false;
|
||
cardTitleColor = '#111111';
|
||
cardFullBleed = true;
|
||
cardTitleShadow = false;
|
||
// Default features
|
||
cardBackgroundStyle = 'solid';
|
||
cardFontFamily = 'Arial';
|
||
cardFontWeight = 'normal';
|
||
cardFooterColor = '#111111';
|
||
cardShowTitle = true;
|
||
cardShowFooter = true;
|
||
cardImageFit = 'cover';
|
||
cardImagePosition = 'center';
|
||
cardTitleFontSize = 20;
|
||
cardTitleX = 50;
|
||
cardTitleY = 20;
|
||
cardTitleDraggable = false;
|
||
cardFooterFontSize = 12;
|
||
cardFooterX = 50;
|
||
cardFooterY = 90;
|
||
cardFooterDraggable = false;
|
||
cardPanelMode = false;
|
||
cardTitlePanel = { borderWidth: 0, rotation: 0, expand: false };
|
||
cardImagePanel = { borderWidth: 0, rotation: 0 };
|
||
cardFooterPanel = { borderWidth: 0, rotation: 0 };
|
||
cardFit = this.printLayout.cardFit;
|
||
}
|
||
|
||
const imageUrl = this.images.length > 0 ? this.images[imageIndex].dataUrl : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==';
|
||
|
||
cards.push({
|
||
imageUrl,
|
||
title: cardTitle,
|
||
footer: cardFooter,
|
||
backgroundStyle: cardBackgroundStyle,
|
||
fontFamily: cardFontFamily,
|
||
fontWeight: cardFontWeight,
|
||
footerColor: cardFooterColor,
|
||
titleStyle: cardTitleStyle,
|
||
accentColor: cardAccentColor,
|
||
showAccent: cardShowAccent,
|
||
titleColor: cardTitleColor,
|
||
fullBleed: cardFullBleed,
|
||
titleShadow: cardTitleShadow,
|
||
showTitle: cardShowTitle,
|
||
showFooter: cardShowFooter,
|
||
imageFit: cardImageFit,
|
||
imagePosition: cardImagePosition,
|
||
titleFontSize: cardTitleFontSize,
|
||
titleX: cardTitleX,
|
||
titleY: cardTitleY,
|
||
titleDraggable: cardTitleDraggable,
|
||
footerFontSize: cardFooterFontSize,
|
||
footerX: cardFooterX,
|
||
footerY: cardFooterY,
|
||
footerDraggable: cardFooterDraggable,
|
||
panelMode: cardPanelMode,
|
||
titlePanel: cardTitlePanel,
|
||
imagePanel: cardImagePanel,
|
||
footerPanel: cardFooterPanel,
|
||
cardFit: cardFit,
|
||
isVisible: true
|
||
});
|
||
} else {
|
||
// Empty card for filling the grid
|
||
cards.push({
|
||
imageUrl: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
|
||
title: '',
|
||
footer: '',
|
||
backgroundStyle: 'solid',
|
||
fontFamily: 'Arial',
|
||
fontWeight: 'normal',
|
||
footerColor: '#111111',
|
||
titleStyle: 'folded',
|
||
accentColor: '#f1c40f',
|
||
showAccent: false,
|
||
titleColor: '#111111',
|
||
fullBleed: true,
|
||
titleShadow: false,
|
||
showTitle: false,
|
||
showFooter: false,
|
||
imageFit: 'cover',
|
||
imagePosition: 'center',
|
||
titleFontSize: 20,
|
||
titleX: 50,
|
||
titleY: 10,
|
||
titleDraggable: false,
|
||
panelMode: false,
|
||
titlePanel: { borderWidth: 0, rotation: 0, expand: false },
|
||
imagePanel: { borderWidth: 0, rotation: 0 },
|
||
footerPanel: { borderWidth: 0, rotation: 0 },
|
||
cardFit: this.printLayout.cardFit,
|
||
isVisible: false
|
||
});
|
||
}
|
||
}
|
||
|
||
// Fixed card dimensions: 54mm x 85.6mm
|
||
const baseCardWidthMm = 54;
|
||
const baseCardHeightMm = 85.6;
|
||
const cardWidthMm = baseCardWidthMm;
|
||
const cardHeightMm = baseCardHeightMm;
|
||
|
||
// Calculate grid layout
|
||
const pageWidthMm = 210; // A4 width
|
||
const pageHeightMm = 297; // A4 height
|
||
|
||
// For multiple pages, we'll create multiple print-sheet divs
|
||
let htmlContent = '';
|
||
|
||
for (let page = 0; page < pagesNeeded; page++) {
|
||
const startIndex = page * cardsPerPage;
|
||
const endIndex = Math.min(startIndex + cardsPerPage, totalCardsToGenerate);
|
||
const pageCards = cards.slice(startIndex, endIndex);
|
||
|
||
// Calculate positions for this page's grid
|
||
const gridCols = this.printLayout.cardsPerRow;
|
||
const gridRows = this.printLayout.cardsPerColumn;
|
||
|
||
const pageBreak = page < pagesNeeded - 1 ? "always" : "auto";
|
||
const gridColsValue = gridCols;
|
||
const cardWidthValue = cardWidthMm;
|
||
const cardHeightValue = cardHeightMm;
|
||
const spacingValue = this.printLayout.cardSpacing;
|
||
const widthValue = gridCols * cardWidthMm + (gridCols - 1) * this.printLayout.cardSpacing;
|
||
const heightValue = gridRows * cardHeightMm + (gridRows - 1) * this.printLayout.cardSpacing;
|
||
|
||
htmlContent += `<div class="print-page" style="page-break-after: ${pageBreak}; width: ${pageWidthMm}mm; padding: ${this.printLayout.pageMargin}mm; box-sizing: border-box;">
|
||
<div class="print-sheet" style="
|
||
display: grid;
|
||
grid-template-columns: repeat(${gridColsValue}, ${cardWidthValue}mm);
|
||
grid-template-rows: repeat(${gridRows}, ${cardHeightValue}mm);
|
||
gap: ${spacingValue}mm;
|
||
width: ${widthValue}mm;
|
||
height: ${heightValue}mm;
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
">
|
||
${pageCards.map((card, index) => {
|
||
const cardFitFactor = card.cardFit || 0;
|
||
return `
|
||
<div class="card card-${startIndex + index} ${card.isVisible ? '' : 'empty-card'}"
|
||
data-card-index="${startIndex + index}"
|
||
data-background-style="${card.backgroundStyle}"
|
||
data-card-fit="${cardFitFactor}"
|
||
onclick="if (window.parent && window.parent.selectCardFromPreview) { window.parent.selectCardFromPreview(${startIndex + index}); }"
|
||
style="cursor: pointer;
|
||
width: ${cardWidthMm}mm;
|
||
height: ${cardHeightMm}mm;
|
||
box-sizing: border-box;
|
||
font-family: 'DejaVuSans', serif;
|
||
${card.isVisible && card.backgroundStyle === 'gradient' ? 'background: linear-gradient(135deg, #e8f4fd 0%, #d1e8f0 100%);' : ''}
|
||
${card.isVisible && card.backgroundStyle === 'pattern' ? 'background: linear-gradient(45deg, #f5f5dc 0%, #deb887 100%);' : ''}
|
||
${card.isVisible && card.backgroundStyle === 'comic' ? 'background: #f9f9f9; border: 1px solid #ccc;' : ''}
|
||
${card.isVisible && card.backgroundStyle === 'polaroid' ? 'border: 8px solid #fff; border-bottom: 24px solid #fff; box-shadow: 0 4px 12px rgba(0,0,0,0.3); position: relative;' : ''}
|
||
${card.isVisible && card.backgroundStyle === 'polaroid' && !card.fullBleed ? 'background: #f8f9fa;' : ''}
|
||
${card.isVisible && card.backgroundStyle === 'solid' ? 'background: #f8f9fa;' : ''}
|
||
${card.isVisible && card.fullBleed ? `background: url('${card.imageUrl}') no-repeat ${card.imagePosition}/${card.imageFit};` : ''}
|
||
position: relative;
|
||
border: ${card.isVisible ? 'none' : '1px dashed #ccc'};
|
||
overflow: hidden;
|
||
">
|
||
${card.isVisible ? `
|
||
${card.panelMode ? `
|
||
<!-- Comic Style Panels -->
|
||
<div class="comic-panels">
|
||
${card.showTitle ? (card.titleStyle === 'split' ? `
|
||
<style>
|
||
@import url('https://fonts.googleapis.com/css2?family=Bebas+Neue&display=swap');
|
||
.comic-panel.comic-title-panel.split {
|
||
--s: 4px;
|
||
--o: calc(0.5 * var(--s));
|
||
--sl: calc(50% - 0.5px), #0000 calc(50% + 0.5px);
|
||
position: absolute;
|
||
left: 8%;
|
||
top: 6%;
|
||
${card.titlePanel.expand ? 'width: auto; min-width: 30%; max-width: 60%;' : 'width: 40%;'}
|
||
height: ${card.titlePanel.expand ? 'auto' : '15%'};
|
||
border: ${card.titlePanel.borderWidth}px solid #000;
|
||
place-self: center;
|
||
place-content: center;
|
||
padding: calc(0.1em) 0.05em 0 0;
|
||
background:
|
||
linear-gradient(22.5deg, #fff var(--sl)) text,
|
||
linear-gradient(22.5deg, #000 var(--sl)),
|
||
linear-gradient(${card.titleColor} 50%, #0000 0)
|
||
0 0/ 1% var(--s);
|
||
color: #0000;
|
||
-webkit-text-stroke: #fff calc(var(--o));
|
||
text-transform: uppercase;
|
||
mix-blend-mode: screen;
|
||
font: ${card.titleFontSize * 0.8}px/ 0.75 'Bebas Neue', sans-serif;
|
||
transform: rotate(${card.titlePanel.rotation}deg);
|
||
box-shadow: 2px 2px 4px rgba(0,0,0,0.2);
|
||
@supports (line-height: 1cap) { line-height: 1cap; }
|
||
}
|
||
@media (min-width: 480px) and (min-height: 320px) {
|
||
.comic-panel.comic-title-panel.split { --s: 6px; }
|
||
}
|
||
</style>
|
||
<div class="comic-panel comic-title-panel split">${this.escapeHtml(card.title)}</div>
|
||
` : `<div class="comic-panel comic-title-panel" style="
|
||
position: absolute;
|
||
left: 8%;
|
||
top: 6%;
|
||
${card.titlePanel.expand ? 'width: auto; min-width: 30%; max-width: 60%;' : 'width: 40%;'}
|
||
height: ${card.titlePanel.expand ? 'auto' : '15%'};
|
||
border: ${card.titlePanel.borderWidth}px solid #000;
|
||
background: #fff;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
white-space: pre-line;
|
||
text-align: center;
|
||
font-size: ${card.titleFontSize * 0.8}px;
|
||
font-weight: ${card.fontWeight === 'bold' ? '700' : '400'};
|
||
font-family: '${card.fontFamily}', serif;
|
||
color: ${card.titleColor};
|
||
${card.titleShadow ? 'text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);' : ''}
|
||
transform: rotate(${card.titlePanel.rotation}deg);
|
||
box-shadow: 2px 2px 4px rgba(0,0,0,0.2);
|
||
${card.titleDraggable && !card.panelMode ? 'cursor: move; user-select: none;' : ''}
|
||
" data-panel-type="title">${this.escapeHtml(card.title)}</div>`) : ''}
|
||
|
||
<div class="comic-panel comic-image-panel" style="
|
||
position: absolute;
|
||
left: 15%;
|
||
top: 25%;
|
||
right: 15%;
|
||
bottom: ${card.showFooter ? '35%' : '15%'};
|
||
border: ${card.imagePanel.borderWidth}px solid #000;
|
||
background: ${card.fullBleed ? `url('${card.imageUrl}') no-repeat ${card.imagePosition}/${card.imageFit}` : '#f8f9fa'};
|
||
transform: rotate(${card.imagePanel.rotation}deg);
|
||
box-shadow: 3px 3px 6px rgba(0,0,0,0.3);
|
||
overflow: hidden;
|
||
">
|
||
${!card.fullBleed ? `<img src="${card.imageUrl}" alt="cover" style="
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: ${card.imageFit};
|
||
object-position: ${card.imagePosition};
|
||
"/>` : ''}
|
||
${card.showAccent ? `<div class="overlay" style="
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: ${card.accentColor}40;
|
||
pointer-events: none;
|
||
"></div>` : ''}
|
||
</div>
|
||
|
||
${card.showFooter ? `<div class="comic-panel comic-footer-panel" style="
|
||
position: absolute;
|
||
right: 8%;
|
||
bottom: 6%;
|
||
width: 35%;
|
||
height: 12%;
|
||
border: ${card.footerPanel.borderWidth}px solid #000;
|
||
background: #fff;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 10px;
|
||
font-weight: ${card.fontWeight};
|
||
font-family: '${card.fontFamily}', serif;
|
||
color: ${card.footerColor};
|
||
transform: rotate(${card.footerPanel.rotation}deg);
|
||
box-shadow: 2px 2px 4px rgba(0,0,0,0.2);
|
||
text-align: center;
|
||
padding: 2px;
|
||
white-space: pre-line;
|
||
">${this.escapeHtml(card.footer)}</div>` : ''}
|
||
</div>
|
||
` : `
|
||
<!-- Standard Layout -->
|
||
${card.showTitle ? (card.titleStyle === 'split' ? `
|
||
<style>
|
||
@import url('https://fonts.googleapis.com/css2?family=Bebas+Neue&display=swap');
|
||
.title.split {
|
||
--s: 4px;
|
||
--o: calc(0.5 * var(--s));
|
||
--sl: calc(50% - 0.5px), #0000 calc(50% + 0.5px);
|
||
position: absolute;
|
||
left: ${card.titleX}%;
|
||
top: ${card.titleY}%;
|
||
transform: translate(-50%, -50%);
|
||
place-self: center;
|
||
place-content: center;
|
||
padding: calc(0.1em) 0.05em 0 0;
|
||
height: calc(1lh - var(--o));
|
||
background:
|
||
linear-gradient(22.5deg, #fff var(--sl)) text,
|
||
linear-gradient(22.5deg, #000 var(--sl)),
|
||
linear-gradient(${card.titleColor} 50%, #0000 0)
|
||
0 0/ 1% var(--s);
|
||
color: #0000;
|
||
-webkit-text-stroke: #fff calc(var(--o));
|
||
text-transform: uppercase;
|
||
mix-blend-mode: screen;
|
||
font: ${card.titleFontSize}px/ 0.75 'Bebas Neue', sans-serif;
|
||
${card.titleDraggable ? 'cursor: move; user-select: none;' : ''}
|
||
@supports (line-height: 1cap) { line-height: 1cap; }
|
||
}
|
||
@media (min-width: 480px) and (min-height: 320px) {
|
||
.title.split { --s: 6px; }
|
||
}
|
||
</style>
|
||
<div class="title split">${this.escapeHtml(card.title)}</div>
|
||
` : `<div class="title ${card.titleStyle}" data-heading="${this.escapeHtml(card.title)}" style="
|
||
color: ${card.titleColor};
|
||
font-weight: ${card.fontWeight === 'bold' ? '700' : '400'};
|
||
font-family: '${card.fontFamily}', serif;
|
||
font-size: ${card.titleFontSize}px;
|
||
${card.titleShadow ? 'text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);' : ''}
|
||
position: absolute;
|
||
left: ${card.titleX}%;
|
||
top: ${card.titleY}%;
|
||
transform: translate(-50%, -50%);
|
||
white-space: pre-line;
|
||
text-align: center;
|
||
${card.titleDraggable ? 'cursor: move; user-select: none;' : ''}
|
||
">${this.escapeHtml(card.title)}</div>`) : ''}
|
||
${!card.fullBleed ? `<div class="hero"><img src="${card.imageUrl}" alt="cover" style="object-fit: ${card.imageFit}; object-position: ${card.imagePosition};"/></div>` : ''}
|
||
${card.showAccent ? `<div class="overlay" style="background: ${card.accentColor}40;"></div>` : `<div class="overlay" style="background: rgba(0,0,0,0);"></div>`}
|
||
${card.showFooter ? `<div class="footer" style="
|
||
color: ${card.footerColor};
|
||
font-family: '${card.fontFamily}', serif;
|
||
font-weight: ${card.fontWeight};
|
||
font-size: ${card.footerFontSize}px;
|
||
position: absolute;
|
||
left: ${card.footerX}%;
|
||
bottom: ${100 - card.footerY}%;
|
||
transform: translate(-50%, 50%);
|
||
white-space: pre-line;
|
||
text-align: center;
|
||
${card.footerDraggable ? 'cursor: move; user-select: none;' : ''}
|
||
">${this.escapeHtml(card.footer)}</div>` : ''}
|
||
`}
|
||
` : ''}
|
||
</div>
|
||
`;}).join('')}
|
||
</div>
|
||
</div>`;
|
||
}
|
||
|
||
// Generate HTML for print sheet
|
||
const html = `
|
||
<!doctype html>
|
||
<html>
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<title>Yoto Covers Print Sheet</title>
|
||
<style>
|
||
@font-face { font-family: 'DejaVuSans'; src: local('DejaVu Sans'); }
|
||
@media screen {
|
||
html, body { margin: 0; padding: 20px; background: #f5f5f5; }
|
||
.print-page { border: 2px dashed #ccc; margin: 20px auto; background: white; page-break-after: always; width: 210mm; max-width: 100%; }
|
||
.print-page:last-child { page-break-after: auto; }
|
||
.print-sheet { max-width: 100%; height: auto !important; }
|
||
}
|
||
@media print {
|
||
html, body { margin: 0; padding: 0; background: white; }
|
||
.print-page { border: none; margin: 0; page-break-after: always; }
|
||
.print-page:last-child { page-break-after: auto; }
|
||
@page { margin: 0; size: A4; }
|
||
}
|
||
.card {
|
||
box-sizing: border-box;
|
||
position: relative;
|
||
}
|
||
.empty-card {
|
||
background: #f9f9f9;
|
||
}
|
||
@media print {
|
||
${cards.map((card, index) => {
|
||
const fitFactor = card.cardFit || 0;
|
||
const widthMm = 54 * (1 + fitFactor);
|
||
const heightMm = 85.6 * (1 + fitFactor);
|
||
return `.card-${index} { width: ${widthMm}mm !important; height: ${heightMm}mm !important; }`;
|
||
}).join('\n ')}
|
||
}
|
||
${cards.map((card, index) => `
|
||
.card-${index} .title {
|
||
color: ${card.titleColor};
|
||
font-family: '${card.fontFamily}', serif;
|
||
font-weight: ${card.fontWeight === 'bold' ? '700' : '400'};
|
||
}
|
||
.card-${index} .footer {
|
||
color: ${card.footerColor};
|
||
}
|
||
`).join('')}
|
||
.title {
|
||
position: absolute;
|
||
top: 6%;
|
||
left: 6%;
|
||
right: 6%;
|
||
text-align: center;
|
||
font-size: calc(108px + 0.5rem);
|
||
color: ${this.templateSettings.titleColor};
|
||
font-weight: 900;
|
||
text-transform: uppercase;
|
||
z-index: 10;
|
||
${this.templateSettings.titleShadow ? 'text-shadow: 1px 4px 6px rgba(0,0,0,0.3);' : ''}
|
||
}
|
||
.title.classic {
|
||
/* Default classic style - already defined above */
|
||
}
|
||
.title.condensed {
|
||
font-size: calc(96px + 0.5rem);
|
||
font-weight: 900;
|
||
letter-spacing: -2px;
|
||
font-stretch: condensed;
|
||
}
|
||
.title.outlined {
|
||
-webkit-text-stroke: 2px white;
|
||
text-stroke: 2px white;
|
||
color: transparent;
|
||
}
|
||
.title.folded {
|
||
position: absolute;
|
||
left: 6%;
|
||
right: 6%;
|
||
top: 6%;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
font-family: 'Source Code Pro', monospace;
|
||
font-weight: 900;
|
||
white-space: nowrap;
|
||
color: lch(76 39.21 9.23 / 0.5);
|
||
text-transform: uppercase;
|
||
transform: skew(10deg) rotate(-10deg);
|
||
text-shadow: 1px 4px 6px lch(90 2.22 62.5), 0 0 0 lch(28 26.21 12.27), 1px 4px 6px lch(90 2.22 62.5);
|
||
z-index: 10;
|
||
}
|
||
.title.folded::before {
|
||
content: attr(data-heading);
|
||
position: absolute;
|
||
left: 0;
|
||
top: -4.8%;
|
||
overflow: hidden;
|
||
height: 50%;
|
||
color: lch(97 2.19 62.49);
|
||
transform: translate(0.15em, 0) skew(-13deg) scale(1, 1.2);
|
||
text-shadow: 2px -1px 6px rgba(0,0,0,0.2);
|
||
}
|
||
.title.folded::after {
|
||
content: attr(data-heading);
|
||
position: absolute;
|
||
left: 0;
|
||
color: lch(83 2.26 62.51);
|
||
transform: translate(0, 0) skew(13deg) scale(1, 0.8);
|
||
clip-path: polygon(0 50%, 100% 50%, 100% 100%, 0 100%);
|
||
text-shadow: 2px -1px 6px lch(0 0 0 / 0.3);
|
||
}
|
||
.hero {
|
||
position: absolute;
|
||
top: 18%;
|
||
left: 6%;
|
||
right: 6%;
|
||
bottom: 18%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
${cards.some(card => card.backgroundStyle === 'polaroid') ? `
|
||
.card[data-background-style="polaroid"] .hero {
|
||
top: 12%;
|
||
left: 8%;
|
||
right: 8%;
|
||
bottom: 25%;
|
||
}
|
||
` : ''}
|
||
.hero img {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
border-radius: 8px;
|
||
}
|
||
.overlay {
|
||
position: absolute;
|
||
top: 18%;
|
||
left: 6%;
|
||
right: 6%;
|
||
bottom: 18%;
|
||
border-radius: 8px;
|
||
pointer-events: none;
|
||
background: rgba(0,0,0,0);
|
||
}
|
||
${cards.some(card => card.backgroundStyle === 'polaroid') ? `
|
||
.card[data-background-style="polaroid"] .overlay {
|
||
top: 12%;
|
||
left: 8%;
|
||
right: 8%;
|
||
bottom: 25%;
|
||
}
|
||
` : ''}
|
||
.footer {
|
||
position: absolute;
|
||
bottom: 4%;
|
||
left: 6%;
|
||
right: 6%;
|
||
height: 10%;
|
||
background: ${this.templateSettings.accentColor};
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-weight: 700;
|
||
color: #000;
|
||
border-radius: 6px;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
${htmlContent}
|
||
</body>
|
||
</html>`;
|
||
|
||
return html;
|
||
}
|
||
|
||
escapeHtml(text) {
|
||
const div = document.createElement('div');
|
||
div.textContent = text;
|
||
return div.innerHTML.replace(/\n/g, '<br>');
|
||
}
|
||
|
||
openPreviewInNewTab() {
|
||
const iframe = document.getElementById('preview-iframe');
|
||
if (iframe.src) {
|
||
window.open(iframe.src, '_blank');
|
||
} else {
|
||
this.showStatusMessage('No preview available. Generate a preview first.', 'error');
|
||
}
|
||
}
|
||
|
||
printPreview() {
|
||
const iframe = document.getElementById('preview-iframe');
|
||
if (iframe.contentWindow) {
|
||
iframe.contentWindow.print();
|
||
} else {
|
||
this.showStatusMessage('No preview available to print.', 'error');
|
||
}
|
||
}
|
||
|
||
downloadHTML() {
|
||
if (!this.templateSettings.title && this.images.length === 0) {
|
||
this.showStatusMessage('Add content before downloading.', 'error');
|
||
return;
|
||
}
|
||
|
||
this.generateHTML().then(html => {
|
||
const blob = new Blob([html], { type: 'text/html' });
|
||
const url = URL.createObjectURL(blob);
|
||
const a = document.createElement('a');
|
||
a.href = url;
|
||
a.download = `yoto-cover-${Date.now()}.html`;
|
||
document.body.appendChild(a);
|
||
a.click();
|
||
document.body.removeChild(a);
|
||
URL.revokeObjectURL(url);
|
||
this.showStatusMessage('HTML file downloaded successfully!', 'success');
|
||
}).catch(error => {
|
||
this.showStatusMessage(`Download failed: ${error.message}`, 'error');
|
||
});
|
||
}
|
||
|
||
showStatusMessage(message, type = 'info') {
|
||
const statusDiv = document.getElementById('status-message');
|
||
statusDiv.textContent = message;
|
||
statusDiv.className = `status-message status-${type}`;
|
||
statusDiv.style.display = 'block';
|
||
|
||
setTimeout(() => {
|
||
statusDiv.style.display = 'none';
|
||
}, 5000);
|
||
}
|
||
|
||
zoomIn() {
|
||
if (this.previewZoom.level < this.previewZoom.max) {
|
||
this.previewZoom.level = Math.min(this.previewZoom.max, this.previewZoom.level + this.previewZoom.step);
|
||
this.updateZoom();
|
||
}
|
||
}
|
||
|
||
zoomOut() {
|
||
if (this.previewZoom.level > this.previewZoom.min) {
|
||
this.previewZoom.level = Math.max(this.previewZoom.min, this.previewZoom.level - this.previewZoom.step);
|
||
this.updateZoom();
|
||
}
|
||
}
|
||
|
||
resetZoom() {
|
||
this.previewZoom.level = 1.0;
|
||
this.updateZoom();
|
||
}
|
||
|
||
updateZoom() {
|
||
const iframe = document.getElementById('preview-iframe');
|
||
const zoomLevel = document.getElementById('zoom-level');
|
||
|
||
if (iframe) {
|
||
iframe.style.transform = `scale(${this.previewZoom.level})`;
|
||
iframe.style.transformOrigin = 'top left';
|
||
}
|
||
|
||
if (zoomLevel) {
|
||
zoomLevel.textContent = Math.round(this.previewZoom.level * 100) + '%';
|
||
}
|
||
}
|
||
|
||
toggleFullscreen() {
|
||
const container = document.querySelector('.preview-container');
|
||
const overlay = document.getElementById('fullscreen-overlay');
|
||
const btn = document.getElementById('fullscreen-btn');
|
||
|
||
if (this.isFullscreen) {
|
||
// Exit fullscreen
|
||
container.classList.remove('fullscreen');
|
||
overlay.classList.remove('active');
|
||
btn.textContent = '⛶ Full Screen';
|
||
this.isFullscreen = false;
|
||
} else {
|
||
// Enter fullscreen
|
||
container.classList.add('fullscreen');
|
||
overlay.classList.add('active');
|
||
btn.textContent = '⛶ Exit Full Screen';
|
||
this.isFullscreen = true;
|
||
|
||
// Add click handler to overlay to exit fullscreen
|
||
overlay.onclick = () => this.toggleFullscreen();
|
||
}
|
||
}
|
||
|
||
updateUI() {
|
||
// Initialize print layout controls
|
||
document.getElementById('cards-per-row').value = this.printLayout.cardsPerRow;
|
||
document.getElementById('cards-per-column').value = this.printLayout.cardsPerColumn;
|
||
document.getElementById('page-margin').value = this.printLayout.pageMargin;
|
||
document.getElementById('card-spacing').value = this.printLayout.cardSpacing;
|
||
document.getElementById('global-card-fit').value = this.printLayout.cardFit;
|
||
}
|
||
}
|
||
|
||
class CoverImage {
|
||
constructor(name, dataUrl) {
|
||
this.name = name;
|
||
this.dataUrl = dataUrl;
|
||
this.width = 0;
|
||
this.height = 0;
|
||
|
||
// Get image dimensions
|
||
this.getImageDimensions();
|
||
}
|
||
|
||
async getImageDimensions() {
|
||
return new Promise((resolve) => {
|
||
const img = new Image();
|
||
img.onload = () => {
|
||
this.width = img.naturalWidth;
|
||
this.height = img.naturalHeight;
|
||
resolve();
|
||
};
|
||
img.src = this.dataUrl;
|
||
});
|
||
}
|
||
}
|
||
|
||
// Initialize the app when DOM is ready
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
window.app = new YotoCoversApp();
|
||
window.selectCardFromPreview = (index) => window.app.selectCardFromPreview(index);
|
||
}); |