feat: add environment controls, API URL display, and improved auto-sizing
Features: - Add ENABLE_UI and ENABLE_API environment variables for deployment flexibility - Add API URL display textbox with copy button in web interface - Create /api/calculate-auto-values endpoint to centralize sizing logic - Dimension overlay now calls API for accurate calculations instead of duplicating logic Improvements: - Enhance auto-sizing algorithm with aspect-ratio awareness - Improve padding calculation using geometric mean formula - Font detection now uses all available fonts from get_available_fonts() - API URL includes all parameters (font_path, font_size, padding, etc.) UI/UX: - Remove hidden header section and logo copy buttons - API URL textarea wraps and grows vertically to show full URL - Clean up unused code and debug console.log statements Dependencies: - Add gunicorn==21.2.0 to requirements.txt Documentation: - Document environment variables with Docker and Python examples - Add production deployment guidance - Update API documentation with font_path parameter - Add API URL feature to usage instructions Bug fixes: - Change debug=False for production - Remove unused 're' import from app.py
This commit is contained in:
@@ -161,10 +161,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 40px;
|
||||
}
|
||||
@@ -678,30 +674,6 @@
|
||||
text-overflow: ellipsis;
|
||||
padding: 0 6px;
|
||||
}
|
||||
|
||||
.logo-copy-icon {
|
||||
position: absolute;
|
||||
bottom: 34px;
|
||||
right: 6px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
background: rgba(42, 42, 42, 0.9);
|
||||
border: 1px solid #444;
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
transition: all 0.2s;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.logo-copy-icon:hover {
|
||||
background: rgba(102, 126, 234, 0.95);
|
||||
border-color: #667eea;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
text-align: center;
|
||||
@@ -726,11 +698,6 @@
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>📺 Logo Text Adder</h1>
|
||||
<p>Add custom text to your TV station and network logos</p>
|
||||
</div>
|
||||
|
||||
<div class="main-layout">
|
||||
<div class="form-container">
|
||||
<div class="content">
|
||||
@@ -893,6 +860,11 @@
|
||||
Show dimensions on preview
|
||||
</label>
|
||||
<div class="preview-download-hint">Click image to download</div>
|
||||
<div style="margin-top: 20px; max-width: 800px; margin-left: auto; margin-right: auto;">
|
||||
<label for="apiUrl" style="display: block; margin-bottom: 8px; color: #e0e0e0; font-weight: 600; font-size: 0.95em;">API Call URL:</label>
|
||||
<textarea id="apiUrl" readonly style="width: 100%; padding: 12px 15px; border: 2px solid #333; border-radius: 8px; font-size: 14px; background: #2a2a2a; color: #e0e0e0; font-family: monospace; cursor: text; user-select: all; resize: vertical; min-height: 44px; line-height: 1.5; overflow-y: auto; white-space: pre-wrap; word-break: break-all;" onclick="this.select()"></textarea>
|
||||
<small style="display: block; margin-top: 6px; color: #666; font-size: 0.85em;">Use this URL to programmatically generate the same logo via the API</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -966,6 +938,8 @@
|
||||
|
||||
// Trigger live preview when font changes
|
||||
triggerLivePreview();
|
||||
// Update API URL when font changes
|
||||
updateApiUrl();
|
||||
}
|
||||
|
||||
// Load available fonts
|
||||
@@ -1071,7 +1045,6 @@
|
||||
}
|
||||
|
||||
allLogos = data.logos;
|
||||
console.log(`Loaded ${data.total || allLogos.length} total logos`);
|
||||
|
||||
// Populate country filter if not already done
|
||||
if (allCountries.length === 0 && data.countries) {
|
||||
@@ -1114,24 +1087,9 @@
|
||||
logoItem.className = 'logo-item';
|
||||
logoItem.innerHTML = `
|
||||
<img src="${logo.thumbnail}" alt="${logo.name}" loading="lazy">
|
||||
<div class="logo-copy-icon" data-url="${logo.url}" title="Copy direct link to image">📋</div>
|
||||
<div class="logo-item-name" title="${logo.name} (${logo.country})">${logo.name}</div>
|
||||
`;
|
||||
|
||||
// Add click handler for copy icon
|
||||
const copyIcon = logoItem.querySelector('.logo-copy-icon');
|
||||
copyIcon.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
const url = e.target.dataset.url;
|
||||
navigator.clipboard.writeText(url).then(() => {
|
||||
const originalText = e.target.textContent;
|
||||
e.target.textContent = '✅';
|
||||
setTimeout(() => {
|
||||
e.target.textContent = originalText;
|
||||
}, 2000);
|
||||
});
|
||||
});
|
||||
|
||||
logoItem.addEventListener('click', () => {
|
||||
// Remove selected class from all items
|
||||
document.querySelectorAll('.logo-item').forEach(item => {
|
||||
@@ -1184,24 +1142,9 @@
|
||||
logoItem.className = 'logo-item';
|
||||
logoItem.innerHTML = `
|
||||
<img src="${logo.thumbnail}" alt="${logo.name}" loading="lazy">
|
||||
<div class="logo-copy-icon" data-url="${logo.url}" title="Copy direct link to image">📋</div>
|
||||
<div class="logo-item-name" title="${logo.name} (${logo.country})">${logo.name}</div>
|
||||
`;
|
||||
|
||||
// Add click handler for copy icon
|
||||
const copyIcon = logoItem.querySelector('.logo-copy-icon');
|
||||
copyIcon.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
const url = e.target.dataset.url;
|
||||
navigator.clipboard.writeText(url).then(() => {
|
||||
const originalText = e.target.textContent;
|
||||
e.target.textContent = '✅';
|
||||
setTimeout(() => {
|
||||
e.target.textContent = originalText;
|
||||
}, 2000);
|
||||
});
|
||||
});
|
||||
|
||||
logoItem.addEventListener('click', () => {
|
||||
document.querySelectorAll('.logo-item').forEach(item => {
|
||||
item.classList.remove('selected');
|
||||
@@ -1424,166 +1367,185 @@
|
||||
const naturalHeight = previewImage.naturalHeight;
|
||||
const scale = imgWidth / naturalWidth;
|
||||
|
||||
// Parse actual values (could be 'auto' or a number)
|
||||
// For left/right: auto font size is 20% of height, for above/below: 12% of width
|
||||
let fontSizeNum;
|
||||
if (fontSize === 'auto') {
|
||||
if (position === 'left' || position === 'right') {
|
||||
fontSizeNum = Math.round(originalHeight * 0.20);
|
||||
// Use API to get calculated values if auto, otherwise use provided values
|
||||
const updateVisualization = async () => {
|
||||
let fontSizeNum, paddingNum;
|
||||
|
||||
if (fontSize === 'auto' || padding === 'auto') {
|
||||
try {
|
||||
const params = new URLSearchParams({
|
||||
width: originalWidth,
|
||||
height: originalHeight,
|
||||
position: position,
|
||||
font_size: fontSize,
|
||||
padding: padding
|
||||
});
|
||||
|
||||
const response = await fetch(`/api/calculate-auto-values?${params}`);
|
||||
const data = await response.json();
|
||||
|
||||
fontSizeNum = fontSize === 'auto' ? data.font_size : parseInt(fontSize);
|
||||
paddingNum = padding === 'auto' ? data.padding : parseInt(padding);
|
||||
} catch (error) {
|
||||
console.error('Failed to calculate auto values:', error);
|
||||
// Fallback to manual values if API fails
|
||||
fontSizeNum = parseInt(fontSize) || 60;
|
||||
paddingNum = parseInt(padding) || 20;
|
||||
}
|
||||
} else {
|
||||
fontSizeNum = Math.round(originalWidth * 0.12);
|
||||
fontSizeNum = parseInt(fontSize);
|
||||
paddingNum = parseInt(padding);
|
||||
}
|
||||
} else {
|
||||
fontSizeNum = parseInt(fontSize);
|
||||
}
|
||||
const paddingNum = padding === 'auto' ? Math.round(fontSizeNum * 0.25) : parseInt(padding);
|
||||
|
||||
fontSizeLabel.textContent = `Font: ${fontSize === 'auto' ? `~${fontSizeNum} (auto)` : fontSize}px`;
|
||||
paddingLabel.textContent = `Padding: ${padding === 'auto' ? `~${paddingNum} (auto)` : padding}px`;
|
||||
|
||||
// Backend uses inner_padding (full padding) and outer_padding (padding/4)
|
||||
const innerPadding = paddingNum * scale;
|
||||
const outerPadding = (paddingNum / 4) * scale;
|
||||
|
||||
// For horizontal text (left/right positions), we need to estimate actual text width
|
||||
// Use canvas to measure text width more accurately
|
||||
const text = document.getElementById('text').value;
|
||||
let textWidth, textHeight;
|
||||
|
||||
if (position === 'left' || position === 'right') {
|
||||
// For left/right: use actual text width and actual bounding box height
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.font = `${fontSizeNum}px Arial`;
|
||||
const metrics = ctx.measureText(text);
|
||||
textWidth = metrics.width * scale;
|
||||
// Use actual text bounding box height (tighter than font size)
|
||||
textHeight = fontSizeNum * 0.75 * scale;
|
||||
} else {
|
||||
// For above/below: text height is tighter bounding box
|
||||
textHeight = fontSizeNum * 0.75 * scale;
|
||||
textWidth = fontSizeNum * scale; // Not used in above/below
|
||||
}
|
||||
|
||||
// Calculate text_area dimensions (matches backend exactly)
|
||||
let textAreaHeight, textAreaWidth;
|
||||
if (position === 'below' || position === 'above') {
|
||||
textAreaHeight = textHeight + outerPadding + innerPadding;
|
||||
} else {
|
||||
textAreaWidth = textWidth + outerPadding + innerPadding;
|
||||
}
|
||||
|
||||
// Calculate where the original logo is positioned in the final image
|
||||
const scaledOriginalWidth = originalWidth * scale;
|
||||
const scaledOriginalHeight = originalHeight * scale;
|
||||
|
||||
// Reset all styles completely
|
||||
fontSizeIndicator.style.cssText = 'position: absolute; border: 2px dashed #667eea; background: rgba(102, 126, 234, 0.1); display: block;';
|
||||
paddingIndicator.style.cssText = 'position: absolute; border: 2px dashed #4caf50; background: rgba(76, 175, 80, 0.1); display: block;';
|
||||
fontSizeLabel.style.cssText = 'position: absolute; background: #667eea; color: white; padding: 2px 8px; border-radius: 4px; font-size: 11px; white-space: nowrap;';
|
||||
paddingLabel.style.cssText = 'position: absolute; background: #4caf50; color: white; padding: 2px 8px; border-radius: 4px; font-size: 11px; white-space: nowrap;';
|
||||
|
||||
if (position === 'below') {
|
||||
// Layout: [logo at y=0] [innerPadding] [text] [outerPadding]
|
||||
|
||||
// Blue box: shows ONLY the text bounding box - FULL WIDTH
|
||||
fontSizeIndicator.style.left = '0px';
|
||||
fontSizeIndicator.style.right = '0px';
|
||||
fontSizeIndicator.style.top = `${scaledOriginalHeight + innerPadding}px`;
|
||||
fontSizeIndicator.style.height = `${textHeight}px`;
|
||||
|
||||
// Font label on the LEFT side, anchored to BOTTOM edge of text box
|
||||
fontSizeLabel.style.right = '100%';
|
||||
fontSizeLabel.style.bottom = '0px';
|
||||
fontSizeLabel.style.marginRight = '8px';
|
||||
|
||||
// Green box: shows ONLY innerPadding - FULL WIDTH
|
||||
paddingIndicator.style.left = '0px';
|
||||
paddingIndicator.style.right = '0px';
|
||||
paddingIndicator.style.top = `${scaledOriginalHeight}px`;
|
||||
paddingIndicator.style.height = `${innerPadding}px`;
|
||||
|
||||
// Padding label on the RIGHT side, anchored to BOTTOM edge of padding box
|
||||
paddingLabel.style.left = '100%';
|
||||
paddingLabel.style.bottom = '0px';
|
||||
paddingLabel.style.marginLeft = '8px';
|
||||
|
||||
} else if (position === 'above') {
|
||||
// Layout: [outerPadding] [text] [innerPadding] [logo]
|
||||
|
||||
// Blue box: shows ONLY the text bounding box - FULL WIDTH
|
||||
fontSizeIndicator.style.left = '0px';
|
||||
fontSizeIndicator.style.right = '0px';
|
||||
fontSizeIndicator.style.top = `${outerPadding}px`;
|
||||
fontSizeIndicator.style.height = `${textHeight}px`;
|
||||
|
||||
// Font label on the LEFT side, anchored to TOP edge of text box
|
||||
fontSizeLabel.style.right = '100%';
|
||||
fontSizeLabel.style.top = '0px';
|
||||
fontSizeLabel.style.marginRight = '8px';
|
||||
|
||||
// Green box: shows ONLY innerPadding - FULL WIDTH
|
||||
paddingIndicator.style.left = '0px';
|
||||
paddingIndicator.style.right = '0px';
|
||||
paddingIndicator.style.top = `${outerPadding + textHeight}px`;
|
||||
paddingIndicator.style.height = `${innerPadding}px`;
|
||||
|
||||
// Padding label on the RIGHT side, anchored to TOP edge of padding box
|
||||
paddingLabel.style.left = '100%';
|
||||
paddingLabel.style.top = '0px';
|
||||
paddingLabel.style.marginLeft = '8px';
|
||||
|
||||
} else if (position === 'right') {
|
||||
// Layout: [logo at x=0] [innerPadding] [text] [outerPadding]
|
||||
|
||||
// Blue box: shows ONLY the text width - FULL HEIGHT
|
||||
fontSizeIndicator.style.left = `${scaledOriginalWidth + innerPadding}px`;
|
||||
fontSizeIndicator.style.top = '0px';
|
||||
fontSizeIndicator.style.bottom = '0px';
|
||||
fontSizeIndicator.style.width = `${textWidth}px`;
|
||||
|
||||
// Font label ABOVE the box, anchored to RIGHT edge of text box
|
||||
fontSizeLabel.style.right = '0px';
|
||||
fontSizeLabel.style.bottom = '100%';
|
||||
fontSizeLabel.style.marginBottom = '8px';
|
||||
|
||||
// Green box: shows ONLY innerPadding - FULL HEIGHT
|
||||
paddingIndicator.style.left = `${scaledOriginalWidth}px`;
|
||||
paddingIndicator.style.top = '0px';
|
||||
paddingIndicator.style.bottom = '0px';
|
||||
paddingIndicator.style.width = `${innerPadding}px`;
|
||||
|
||||
// Padding label BELOW the box, anchored to RIGHT edge of padding box
|
||||
paddingLabel.style.right = '0px';
|
||||
paddingLabel.style.top = '100%';
|
||||
paddingLabel.style.marginTop = '8px';
|
||||
|
||||
} else {
|
||||
// position === 'left'
|
||||
// Layout: [outerPadding] [text] [innerPadding] [logo]
|
||||
|
||||
// Blue box: shows ONLY the text width - FULL HEIGHT
|
||||
fontSizeIndicator.style.left = `${outerPadding}px`;
|
||||
fontSizeIndicator.style.top = '0px';
|
||||
fontSizeIndicator.style.bottom = '0px';
|
||||
fontSizeIndicator.style.width = `${textWidth}px`;
|
||||
|
||||
// Font label ABOVE the box, anchored to LEFT edge of text box
|
||||
fontSizeLabel.style.left = '0px';
|
||||
fontSizeLabel.style.bottom = '100%';
|
||||
fontSizeLabel.style.marginBottom = '8px';
|
||||
|
||||
// Green box: shows ONLY innerPadding - FULL HEIGHT
|
||||
paddingIndicator.style.left = `${outerPadding + textWidth}px`;
|
||||
paddingIndicator.style.top = '0px';
|
||||
paddingIndicator.style.bottom = '0px';
|
||||
paddingIndicator.style.width = `${innerPadding}px`;
|
||||
|
||||
// Padding label BELOW the box, anchored to LEFT edge of padding box
|
||||
paddingLabel.style.left = '0px';
|
||||
paddingLabel.style.top = '100%';
|
||||
paddingLabel.style.marginTop = '8px';
|
||||
}
|
||||
};
|
||||
|
||||
fontSizeLabel.textContent = `Font: ${fontSize === 'auto' ? `~${fontSizeNum} (auto)` : fontSize}px`;
|
||||
paddingLabel.textContent = `Padding: ${padding === 'auto' ? `~${paddingNum} (auto)` : padding}px`;
|
||||
|
||||
// Backend uses inner_padding (full padding) and outer_padding (padding/4)
|
||||
const innerPadding = paddingNum * scale;
|
||||
const outerPadding = (paddingNum / 4) * scale;
|
||||
|
||||
// For horizontal text (left/right positions), we need to estimate actual text width
|
||||
// Use canvas to measure text width more accurately
|
||||
const text = document.getElementById('text').value;
|
||||
let textWidth, textHeight;
|
||||
|
||||
if (position === 'left' || position === 'right') {
|
||||
// For left/right: use actual text width and actual bounding box height
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.font = `${fontSizeNum}px Arial`;
|
||||
const metrics = ctx.measureText(text);
|
||||
textWidth = metrics.width * scale;
|
||||
// Use actual text bounding box height (tighter than font size)
|
||||
textHeight = fontSizeNum * 0.75 * scale;
|
||||
} else {
|
||||
// For above/below: text height is tighter bounding box
|
||||
textHeight = fontSizeNum * 0.75 * scale;
|
||||
textWidth = fontSizeNum * scale; // Not used in above/below
|
||||
}
|
||||
|
||||
// Calculate text_area dimensions (matches backend exactly)
|
||||
let textAreaHeight, textAreaWidth;
|
||||
if (position === 'below' || position === 'above') {
|
||||
textAreaHeight = textHeight + outerPadding + innerPadding;
|
||||
} else {
|
||||
textAreaWidth = textWidth + outerPadding + innerPadding;
|
||||
}
|
||||
|
||||
// Calculate where the original logo is positioned in the final image
|
||||
const scaledOriginalWidth = originalWidth * scale;
|
||||
const scaledOriginalHeight = originalHeight * scale;
|
||||
|
||||
// Reset all styles completely
|
||||
fontSizeIndicator.style.cssText = 'position: absolute; border: 2px dashed #667eea; background: rgba(102, 126, 234, 0.1); display: block;';
|
||||
paddingIndicator.style.cssText = 'position: absolute; border: 2px dashed #4caf50; background: rgba(76, 175, 80, 0.1); display: block;';
|
||||
fontSizeLabel.style.cssText = 'position: absolute; background: #667eea; color: white; padding: 2px 8px; border-radius: 4px; font-size: 11px; white-space: nowrap;';
|
||||
paddingLabel.style.cssText = 'position: absolute; background: #4caf50; color: white; padding: 2px 8px; border-radius: 4px; font-size: 11px; white-space: nowrap;';
|
||||
|
||||
if (position === 'below') {
|
||||
// Layout: [logo at y=0] [innerPadding] [text] [outerPadding]
|
||||
|
||||
// Blue box: shows ONLY the text bounding box - FULL WIDTH
|
||||
fontSizeIndicator.style.left = '0px';
|
||||
fontSizeIndicator.style.right = '0px';
|
||||
fontSizeIndicator.style.top = `${scaledOriginalHeight + innerPadding}px`;
|
||||
fontSizeIndicator.style.height = `${textHeight}px`;
|
||||
|
||||
// Font label on the LEFT side, anchored to BOTTOM edge of text box
|
||||
fontSizeLabel.style.right = '100%';
|
||||
fontSizeLabel.style.bottom = '0px';
|
||||
fontSizeLabel.style.marginRight = '8px';
|
||||
|
||||
// Green box: shows ONLY innerPadding - FULL WIDTH
|
||||
paddingIndicator.style.left = '0px';
|
||||
paddingIndicator.style.right = '0px';
|
||||
paddingIndicator.style.top = `${scaledOriginalHeight}px`;
|
||||
paddingIndicator.style.height = `${innerPadding}px`;
|
||||
|
||||
// Padding label on the RIGHT side, anchored to BOTTOM edge of padding box
|
||||
paddingLabel.style.left = '100%';
|
||||
paddingLabel.style.bottom = '0px';
|
||||
paddingLabel.style.marginLeft = '8px';
|
||||
|
||||
} else if (position === 'above') {
|
||||
// Layout: [outerPadding] [text] [innerPadding] [logo]
|
||||
|
||||
// Blue box: shows ONLY the text bounding box - FULL WIDTH
|
||||
fontSizeIndicator.style.left = '0px';
|
||||
fontSizeIndicator.style.right = '0px';
|
||||
fontSizeIndicator.style.top = `${outerPadding}px`;
|
||||
fontSizeIndicator.style.height = `${textHeight}px`;
|
||||
|
||||
// Font label on the LEFT side, anchored to TOP edge of text box
|
||||
fontSizeLabel.style.right = '100%';
|
||||
fontSizeLabel.style.top = '0px';
|
||||
fontSizeLabel.style.marginRight = '8px';
|
||||
|
||||
// Green box: shows ONLY innerPadding - FULL WIDTH
|
||||
paddingIndicator.style.left = '0px';
|
||||
paddingIndicator.style.right = '0px';
|
||||
paddingIndicator.style.top = `${outerPadding + textHeight}px`;
|
||||
paddingIndicator.style.height = `${innerPadding}px`;
|
||||
|
||||
// Padding label on the RIGHT side, anchored to TOP edge of padding box
|
||||
paddingLabel.style.left = '100%';
|
||||
paddingLabel.style.top = '0px';
|
||||
paddingLabel.style.marginLeft = '8px';
|
||||
|
||||
} else if (position === 'right') {
|
||||
// Layout: [logo at x=0] [innerPadding] [text] [outerPadding]
|
||||
|
||||
// Blue box: shows ONLY the text width - FULL HEIGHT
|
||||
fontSizeIndicator.style.left = `${scaledOriginalWidth + innerPadding}px`;
|
||||
fontSizeIndicator.style.top = '0px';
|
||||
fontSizeIndicator.style.bottom = '0px';
|
||||
fontSizeIndicator.style.width = `${textWidth}px`;
|
||||
|
||||
// Font label ABOVE the box, anchored to RIGHT edge of text box
|
||||
fontSizeLabel.style.right = '0px';
|
||||
fontSizeLabel.style.bottom = '100%';
|
||||
fontSizeLabel.style.marginBottom = '8px';
|
||||
|
||||
// Green box: shows ONLY innerPadding - FULL HEIGHT
|
||||
paddingIndicator.style.left = `${scaledOriginalWidth}px`;
|
||||
paddingIndicator.style.top = '0px';
|
||||
paddingIndicator.style.bottom = '0px';
|
||||
paddingIndicator.style.width = `${innerPadding}px`;
|
||||
|
||||
// Padding label BELOW the box, anchored to RIGHT edge of padding box
|
||||
paddingLabel.style.right = '0px';
|
||||
paddingLabel.style.top = '100%';
|
||||
paddingLabel.style.marginTop = '8px';
|
||||
|
||||
} else {
|
||||
// position === 'left'
|
||||
// Layout: [outerPadding] [text] [innerPadding] [logo]
|
||||
|
||||
// Blue box: shows ONLY the text width - FULL HEIGHT
|
||||
fontSizeIndicator.style.left = `${outerPadding}px`;
|
||||
fontSizeIndicator.style.top = '0px';
|
||||
fontSizeIndicator.style.bottom = '0px';
|
||||
fontSizeIndicator.style.width = `${textWidth}px`;
|
||||
|
||||
// Font label ABOVE the box, anchored to LEFT edge of text box
|
||||
fontSizeLabel.style.left = '0px';
|
||||
fontSizeLabel.style.bottom = '100%';
|
||||
fontSizeLabel.style.marginBottom = '8px';
|
||||
|
||||
// Green box: shows ONLY innerPadding - FULL HEIGHT
|
||||
paddingIndicator.style.left = `${outerPadding + textWidth}px`;
|
||||
paddingIndicator.style.top = '0px';
|
||||
paddingIndicator.style.bottom = '0px';
|
||||
paddingIndicator.style.width = `${innerPadding}px`;
|
||||
|
||||
// Padding label BELOW the box, anchored to LEFT edge of padding box
|
||||
paddingLabel.style.left = '0px';
|
||||
paddingLabel.style.top = '100%';
|
||||
paddingLabel.style.marginTop = '8px';
|
||||
}
|
||||
updateVisualization();
|
||||
}
|
||||
|
||||
showDimensions.addEventListener('change', updateDimensionOverlay);
|
||||
@@ -1893,6 +1855,92 @@
|
||||
imageFile.addEventListener('change', () => {
|
||||
setTimeout(triggerLivePreview, 100);
|
||||
});
|
||||
|
||||
// API URL generation and updating
|
||||
const apiUrlInput = document.getElementById('apiUrl');
|
||||
|
||||
function updateApiUrl() {
|
||||
const text = document.getElementById('text').value;
|
||||
|
||||
// Don't show API URL if no text or no image source
|
||||
if (!text.trim()) {
|
||||
apiUrlInput.value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
let imageUrl = '';
|
||||
|
||||
// Get the image URL based on active input mode
|
||||
if (activeInputMode === 'browse') {
|
||||
imageUrl = selectedLogoUrl.value.trim();
|
||||
} else if (activeInputMode === 'url') {
|
||||
imageUrl = document.getElementById('imageUrl').value.trim();
|
||||
} else {
|
||||
// File upload mode - can't generate API URL
|
||||
apiUrlInput.value = 'API URL only available for URL/Browse modes (not file upload)';
|
||||
return;
|
||||
}
|
||||
|
||||
if (!imageUrl) {
|
||||
apiUrlInput.value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
// Build API URL
|
||||
const position = document.querySelector('input[name="position"]:checked').value;
|
||||
const fontSizeAutoChecked = document.getElementById('fontSizeAuto').checked;
|
||||
const fontSize = fontSizeAutoChecked ? 'auto' : document.getElementById('fontSize').value;
|
||||
const paddingAutoChecked = document.getElementById('paddingAuto').checked;
|
||||
const padding = paddingAutoChecked ? 'auto' : document.getElementById('padding').value;
|
||||
const textColor = textColorInput.value;
|
||||
const bgColor = bgColorInput.value;
|
||||
const fontPath = fontSelect.value;
|
||||
|
||||
const params = new URLSearchParams({
|
||||
url: imageUrl,
|
||||
text: text,
|
||||
position: position,
|
||||
font_size: fontSize,
|
||||
padding: padding,
|
||||
text_color: textColor,
|
||||
bg_color: bgColor
|
||||
});
|
||||
|
||||
// Only include font_path if it's not 'auto' (since auto is the default)
|
||||
if (fontPath && fontPath !== 'auto') {
|
||||
params.append('font_path', fontPath);
|
||||
}
|
||||
|
||||
// Build full URL (use current origin)
|
||||
const baseUrl = window.location.origin;
|
||||
apiUrlInput.value = `${baseUrl}/api/image?${params.toString()}`;
|
||||
}
|
||||
|
||||
// Update API URL whenever relevant inputs change
|
||||
document.getElementById('text').addEventListener('input', updateApiUrl);
|
||||
document.querySelectorAll('input[name="position"]').forEach(radio => {
|
||||
radio.addEventListener('change', updateApiUrl);
|
||||
});
|
||||
fontSizeInput.addEventListener('input', updateApiUrl);
|
||||
fontSizeSlider.addEventListener('input', updateApiUrl);
|
||||
fontSizeAuto.addEventListener('change', updateApiUrl);
|
||||
paddingInput.addEventListener('input', updateApiUrl);
|
||||
paddingSlider.addEventListener('input', updateApiUrl);
|
||||
paddingAuto.addEventListener('change', updateApiUrl);
|
||||
textColorInput.addEventListener('input', updateApiUrl);
|
||||
textColorPicker.addEventListener('input', updateApiUrl);
|
||||
bgColorInput.addEventListener('input', updateApiUrl);
|
||||
bgColorPicker.addEventListener('input', updateApiUrl);
|
||||
fontSelect.addEventListener('change', updateApiUrl);
|
||||
imageUrl.addEventListener('input', updateApiUrl);
|
||||
selectedLogoUrl.addEventListener('change', updateApiUrl);
|
||||
|
||||
// Update when tab changes
|
||||
tabBtns.forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
setTimeout(updateApiUrl, 100);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user