feat: add zoom and fullscreen controls to preview, enhance card fit options
This commit is contained in:
@@ -57,7 +57,8 @@ class YotoCoversApp {
|
||||
cardsPerRow: 2,
|
||||
cardsPerColumn: 5,
|
||||
pageMargin: 10,
|
||||
cardSpacing: 5
|
||||
cardSpacing: 5,
|
||||
cardFit: 0
|
||||
};
|
||||
this.templateSettings = {
|
||||
title: '',
|
||||
@@ -70,6 +71,15 @@ class YotoCoversApp {
|
||||
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();
|
||||
@@ -103,6 +113,11 @@ class YotoCoversApp {
|
||||
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;
|
||||
@@ -181,6 +196,36 @@ class YotoCoversApp {
|
||||
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) => {
|
||||
@@ -637,6 +682,9 @@ class YotoCoversApp {
|
||||
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');
|
||||
@@ -940,6 +988,14 @@ class YotoCoversApp {
|
||||
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();
|
||||
@@ -984,6 +1040,7 @@ class YotoCoversApp {
|
||||
// Apply zoom after iframe loads
|
||||
iframe.onload = () => {
|
||||
this.initDragFunctionality();
|
||||
this.updateZoom();
|
||||
};
|
||||
|
||||
// Clean up the previous blob URL
|
||||
@@ -1025,6 +1082,7 @@ class YotoCoversApp {
|
||||
let cardFooterFontSize, cardFooterX, cardFooterY, cardFooterDraggable;
|
||||
let cardShowAccent;
|
||||
let cardPanelMode, cardTitlePanel, cardImagePanel, cardFooterPanel;
|
||||
let cardFit;
|
||||
|
||||
if (this.cardVariations.length > 0) {
|
||||
// Use variation settings
|
||||
@@ -1059,6 +1117,7 @@ class YotoCoversApp {
|
||||
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;
|
||||
@@ -1091,6 +1150,7 @@ class YotoCoversApp {
|
||||
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==';
|
||||
@@ -1125,6 +1185,7 @@ class YotoCoversApp {
|
||||
titlePanel: cardTitlePanel,
|
||||
imagePanel: cardImagePanel,
|
||||
footerPanel: cardFooterPanel,
|
||||
cardFit: cardFit,
|
||||
isVisible: true
|
||||
});
|
||||
} else {
|
||||
@@ -1155,14 +1216,17 @@ class YotoCoversApp {
|
||||
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 cardWidthMm = 54;
|
||||
const cardHeightMm = 85.6;
|
||||
const baseCardWidthMm = 54;
|
||||
const baseCardHeightMm = 85.6;
|
||||
const cardWidthMm = baseCardWidthMm;
|
||||
const cardHeightMm = baseCardHeightMm;
|
||||
|
||||
// Calculate grid layout
|
||||
const pageWidthMm = 210; // A4 width
|
||||
@@ -1180,23 +1244,33 @@ class YotoCoversApp {
|
||||
const gridCols = this.printLayout.cardsPerRow;
|
||||
const gridRows = this.printLayout.cardsPerColumn;
|
||||
|
||||
htmlContent += `
|
||||
<div class="print-page" style="page-break-after: ${page < pagesNeeded - 1 ? 'always' : 'auto'}; width: ${pageWidthMm}mm; padding: ${this.printLayout.pageMargin}mm; box-sizing: border-box;">
|
||||
<div class="print-sheet" style="
|
||||
display: grid;
|
||||
grid-template-columns: repeat(${gridCols}, ${cardWidthMm}mm);
|
||||
grid-template-rows: repeat(${gridRows}, ${cardHeightMm}mm);
|
||||
gap: ${this.printLayout.cardSpacing}mm;
|
||||
width: ${gridCols * cardWidthMm + (gridCols - 1) * this.printLayout.cardSpacing}mm;
|
||||
height: ${gridRows * cardHeightMm + (gridRows - 1) * this.printLayout.cardSpacing}mm;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
">
|
||||
${pageCards.map((card, index) => `
|
||||
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;
|
||||
@@ -1243,7 +1317,7 @@ class YotoCoversApp {
|
||||
-webkit-text-stroke: #fff calc(var(--o));
|
||||
text-transform: uppercase;
|
||||
mix-blend-mode: screen;
|
||||
font: clamp(1em, min(65vh, 35vw), 2em)/ 0.75 'Bebas Neue', sans-serif;
|
||||
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; }
|
||||
@@ -1353,7 +1427,7 @@ class YotoCoversApp {
|
||||
-webkit-text-stroke: #fff calc(var(--o));
|
||||
text-transform: uppercase;
|
||||
mix-blend-mode: screen;
|
||||
font: clamp(2em, min(65vh, 35vw), 3.9em)/ 0.75 'Bebas Neue', sans-serif;
|
||||
font: ${card.titleFontSize}px/ 0.75 'Bebas Neue', sans-serif;
|
||||
${card.titleDraggable ? 'cursor: move; user-select: none;' : ''}
|
||||
@supports (line-height: 1cap) { line-height: 1cap; }
|
||||
}
|
||||
@@ -1394,7 +1468,7 @@ class YotoCoversApp {
|
||||
`}
|
||||
` : ''}
|
||||
</div>
|
||||
`).join('')}
|
||||
`;}).join('')}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
@@ -1427,6 +1501,14 @@ class YotoCoversApp {
|
||||
.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};
|
||||
@@ -1624,12 +1706,69 @@ class YotoCoversApp {
|
||||
}, 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user