elemes/frontend/src/lib/actions/renderCircuitEmbeds.ts

73 lines
2.0 KiB
TypeScript

/**
* Find `.circuit-embed` divs (generated by backend from ```circuit fences)
* and replace them with live CircuitJS iframes. Uses IntersectionObserver
* for lazy loading so multiple embeds don't all load at once.
*/
function loadCircuitEmbed(div: HTMLElement) {
const width = div.dataset.width || '100%';
const height = div.dataset.height || '400px';
const dataEl = div.querySelector('.circuit-data');
const circuitData = dataEl?.textContent?.trim() || '';
const wrapper = document.createElement('div');
wrapper.className = 'circuit-embed-wrapper';
wrapper.style.width = width;
wrapper.style.height = height;
const iframe = document.createElement('iframe');
iframe.src = '/circuitjs1/circuitjs.html';
iframe.style.width = '100%';
iframe.style.height = '100%';
iframe.style.border = 'none';
iframe.title = 'Circuit Simulator';
const loadCircuit = (api: any) => {
if (!circuitData) return;
try {
api.importCircuit(circuitData, false);
api.setSimRunning(true);
api.updateCircuit();
} catch (e) {
console.error('Circuit embed load error:', e);
}
const loadingEl = div.querySelector('.circuit-embed-loading');
if (loadingEl) loadingEl.remove();
};
iframe.onload = () => {
const win = iframe.contentWindow as any;
win.oncircuitjsloaded = (api: any) => loadCircuit(api);
// Fallback if callback doesn't fire
setTimeout(() => {
const api = win?.CircuitJS1;
if (api) loadCircuit(api);
}, 3000);
};
wrapper.appendChild(iframe);
div.appendChild(wrapper);
div.dataset.rendered = 'true';
}
export function renderCircuitEmbeds(container: HTMLElement) {
const embeds = container.querySelectorAll('.circuit-embed:not([data-rendered])');
if (embeds.length === 0) return;
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
loadCircuitEmbed(entry.target as HTMLElement);
observer.unobserve(entry.target);
}
});
},
{ rootMargin: '200px' }
);
embeds.forEach((el) => observer.observe(el));
}