pharma-see / components /image-manager.js
steake's picture
pull all image prompts up to a single JS block accessible from the top of the page.
e855e5e verified
/**
* Image Manager Web Component
* Centralized management of all image prompts and configurations
* Provides a single source of truth for all image URLs in the application
*/
class ImageManager extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.config = null;
this.imageCache = new Map();
}
connectedCallback() {
this.loadConfig();
this.render();
}
loadConfig() {
// Try to load from JSON config element
const configElement = document.getElementById('imagePromptsConfig');
if (configElement) {
try {
this.config = JSON.parse(configElement.textContent);
} catch (e) {
console.error('Failed to parse image prompts config:', e);
this.config = this.getDefaultConfig();
}
} else {
this.config = this.getDefaultConfig();
}
// Store in global window object for easy access
window.imagePrompts = this.config;
}
getDefaultConfig() {
return {
hero: "https://static.photos/medical/800x600/1",
productImages: [
"https://static.photos/medical/400x300/101",
"https://static.photos/medical/400x300/102",
"https://static.photos/medical/400x300/103",
"https://static.photos/medical/400x300/104"
],
about: "https://static.photos/workspace/800x600/201",
testimonials: [
"https://static.photos/people/48x48/301",
"https://static.photos/people/48x48/302",
"https://static.photos/people/48x48/303"
],
categories: {
medical: "medical",
workspace: "workspace",
people: "people",
abstract: "abstract"
},
placeholder: "https://static.photos/abstract/100x100",
fallback: "https://static.photos/abstract/400x300"
};
}
render() {
this.shadowRoot.innerHTML = `
<style>
:host {
display: none;
}
.image-manager-panel {
position: fixed;
top: 100px;
right: 20px;
width: 300px;
background: white;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0,0,0,0.15);
padding: 20px;
z-index: 9999;
display: none;
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
}
.image-manager-panel.active {
display: block;
}
.image-manager-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 1px solid #e5e5e5;
}
.image-manager-title {
font-weight: bold;
font-size: 16px;
color: #333;
}
.image-manager-close {
background: none;
border: none;
font-size: 20px;
cursor: pointer;
color: #666;
}
.image-manager-section {
margin-bottom: 15px;
}
.image-manager-section-title {
font-size: 14px;
font-weight: 600;
color: #555;
margin-bottom: 8px;
}
.image-manager-list {
display: flex;
flex-direction: column;
gap: 5px;
}
.image-manager-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px;
background: #f8f9fa;
border-radius: 4px;
font-size: 12px;
}
.image-manager-item:hover {
background: #e9ecef;
}
.image-manager-url {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 180px;
}
.image-manager-copy {
background: #5d9245;
color: white;
border: none;
border-radius: 3px;
padding: 3px 8px;
font-size: 11px;
cursor: pointer;
}
.image-manager-copy:hover {
background: #3f7344;
}
.image-manager-toggle {
position: fixed;
top: 150px;
right: 20px;
background: #5d9245;
color: white;
border: none;
border-radius: 50%;
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
z-index: 9998;
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
}
</style>
<button class="image-manager-toggle">
<i class="fas fa-images"></i>
</button>
<div class="image-manager-panel">
<div class="image-manager-header">
<div class="image-manager-title">Image Prompts Manager</div>
<button class="image-manager-close">&times;</button>
</div>
<div class="image-manager-section">
<div class="image-manager-section-title">Hero Image</div>
<div class="image-manager-list">
<div class="image-manager-item">
<div class="image-manager-url" title="${this.config?.hero || ''}">
${this.config?.hero ? this.config.hero.substring(0, 30) + '...' : ''}
</div>
<button class="image-manager-copy" data-url="${this.config?.hero || ''}">Copy</button>
</div>
</div>
</div>
<div class="image-manager-section">
<div class="image-manager-section-title">Product Images (${this.config?.productImages?.length || 0})</div>
<div class="image-manager-list">
${this.config?.productImages?.map((url, index) => `
<div class="image-manager-item">
<div class="image-manager-url" title="${url}">
Product ${index + 1}: ${url.substring(0, 25)}...
</div>
<button class="image-manager-copy" data-url="${url}">Copy</button>
</div>
`).join('') || '<div style="padding: 8px; color: #666; font-size: 12px;">No product images configured</div>'}
</div>
</div>
<div class="image-manager-section">
<div class="image-manager-section-title">Testimonial Images (${this.config?.testimonials?.length || 0})</div>
<div class="image-manager-list">
${this.config?.testimonials?.map((url, index) => `
<div class="image-manager-item">
<div class="image-manager-url" title="${url}">
Testimonial ${index + 1}: ${url.substring(0, 25)}...
</div>
<button class="image-manager-copy" data-url="${url}">Copy</button>
</div>
`).join('') || '<div style="padding: 8px; color: #666; font-size: 12px;">No testimonial images configured</div>'}
</div>
</div>
<div class="image-manager-section">
<div class="image-manager-section-title">Configuration</div>
<div class="image-manager-list">
<div class="image-manager-item">
<div>Categories: ${this.config?.categories ? Object.keys(this.config.categories).join(', ') : ''}</div>
<button class="image-manager-copy" data-url="${JSON.stringify(this.config)}">Copy Config</button>
</div>
</div>
</div>
</div>
`;
this.addEventListeners();
}
addEventListeners() {
const toggleBtn = this.shadowRoot.querySelector('.image-manager-toggle');
const closeBtn = this.shadowRoot.querySelector('.image-manager-close');
const panel = this.shadowRoot.querySelector('.image-manager-panel');
const copyButtons = this.shadowRoot.querySelectorAll('.image-manager-copy');
if (toggleBtn && panel) {
toggleBtn.addEventListener('click', () => {
panel.classList.toggle('active');
});
}
if (closeBtn && panel) {
closeBtn.addEventListener('click', () => {
panel.classList.remove('active');
});
}
if (copyButtons) {
copyButtons.forEach(btn => {
btn.addEventListener('click', (e) => {
const url = e.currentTarget.getAttribute('data-url');
this.copyToClipboard(url);
// Show feedback
const originalText = e.currentTarget.textContent;
e.currentTarget.textContent = 'Copied!';
e.currentTarget.style.background = '#28a745';
setTimeout(() => {
e.currentTarget.textContent = originalText;
e.currentTarget.style.background = '#5d9245';
}, 1500);
});
});
}
}
copyToClipboard(text) {
navigator.clipboard.writeText(text).then(() => {
console.log('Image URL copied to clipboard:', text);
}).catch(err => {
console.error('Failed to copy:', err);
// Fallback method
const textArea = document.createElement('textarea');
textArea.value = text;
document.body.appendChild(textArea);
textArea.select();
document.execCommand('copy');
document.body.removeChild(textArea);
});
}
// Public API methods
getImage(category, index = 0) {
if (!this.config) return this.config?.fallback || '';
switch(category) {
case 'hero':
return this.config.hero;
case 'product':
return this.config.productImages[index] || this.config.fallback;
case 'testimonial':
return this.config.testimonials[index] || this.config.fallback;
case 'about':
return this.config.about;
case 'placeholder':
return this.config.placeholder;
default:
return this.config.fallback;
}
}
updateImage(category, value, index = 0) {
if (!this.config) return false;
switch(category) {
case 'hero':
this.config.hero = value;
break;
case 'product':
if (this.config.productImages && index < this.config.productImages.length) {
this.config.productImages[index] = value;
}
break;
case 'testimonial':
if (this.config.testimonials && index < this.config.testimonials.length) {
this.config.testimonials[index] = value;
}
break;
case 'about':
this.config.about = value;
break;
default:
return false;
}
// Update the JSON config element
const configElement = document.getElementById('imagePromptsConfig');
if (configElement) {
configElement.textContent = JSON.stringify(this.config, null, 2);
}
return true;
}
static get observedAttributes() {
return ['config'];
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === 'config' && oldValue !== newValue) {
this.loadConfig();
this.render();
}
}
}
customElements.define('image-manager', ImageManager);