parts-store/assets/part.js

78 lines
5.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// part.js — part detail: interactive PBR viewer + full curated record (specs, pins, spice, downloads).
import { mountViewer } from '/assets/viewer.js';
const esc = (s) => (s == null ? '' : String(s).replace(/[&<>]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;' }[c])));
const id = new URLSearchParams(location.search).get('id');
function dl(name, text, mime = 'text/plain') {
const a = document.createElement('a'); a.href = URL.createObjectURL(new Blob([text], { type: mime }));
a.download = name; a.click();
}
function spendCredit() {
let c = parseInt(localStorage.getItem('ppcb_credits') || '5', 10);
if (c <= 0) { alert('Out of download credits — contribute a curated part to earn 5 more.'); return false; }
localStorage.setItem('ppcb_credits', --c); document.getElementById('cred-n').textContent = c; return true;
}
(async () => {
const parts = await (await fetch('/assets/catalog.json')).json();
const p = parts.find((x) => x.id === id);
const root = document.getElementById('root');
document.getElementById('cred-n').textContent = localStorage.getItem('ppcb_credits') || '5';
if (!p) { root.innerHTML = '<div class="empty">Part not found. <a href="/">Back to catalog</a></div>'; return; }
document.title = `${p.mpn || p.id} — PromptPCB Parts`;
const params = (p.params || []).map((x) => `<tr><td>${esc(x.name)}</td><td class="v">${esc(x.value)} ${esc(x.unit || '')}${x.note ? `<span class="cond">${esc(x.note)}</span>` : ''}</td></tr>`).join('');
const pins = (p.pins || []).map((x) => `<div class="pin"><span class="n">${esc(x.pin ?? '')}</span>${esc(x.name || '')}<span class="t">${esc(x.type || '')}</span></div>`).join('');
const ucs = (p.use_cases || []).map((u) => `<li>${esc(u)}</li>`).join('');
const tagList = Object.entries(p.tags || {}).filter(([, v]) => v != null && v !== '' && !Array.isArray(v)).map(([k, v]) => `<span class="tag"><b>${esc(k)}</b> ${esc(v)}</span>`).join('');
const kr = ((p.tags || {}).key_ratings || []).map((r) => `<span class="tag">${esc(r)}</span>`).join('');
const ser = p.series ? `<div class="tags" style="margin-top:8px">${Object.entries(p.series).filter(([k]) => k !== 'mesh').map(([k, v]) => `<span class="tag"><b>${esc(k)}</b> ${esc(v)}</span>`).join('')}</div>` : '';
root.innerHTML = `<div class="detail">
<div>
<div class="viewer"><canvas id="cv"></canvas>
<div class="vbar"><span class="vtag">PBR · drag to orbit</span><span class="vtag">${esc(p.package || (p.series && p.series.package) || '')}</span><span class="vtag">${(p.pins || []).length} pins</span></div>
</div>
</div>
<div>
<div style="font-family:var(--mono);font-size:12px;color:var(--dim);text-transform:uppercase;letter-spacing:1px">${esc(p.class)}${p.type === 'series' ? ' · series' : ''}</div>
<h1 class="dh1">${esc((p.role || p.summary || '').split('—')[0])}</h1>
<div class="dmpn">${esc(p.mpn || p.id)} · ${esc(p.manufacturer || '')} · ${esc(p.lcsc || '')}</div>
<p style="color:var(--dim);line-height:1.6;margin:0 0 6px">${esc(p.summary)}</p>
<div class="dactions">
<button class="btn" id="dl-all">↓ Download files</button>
<a class="btn ghost" href="${esc(p.datasheet_url)}" target="_blank" rel="noopener">Datasheet ↗</a>
<button class="btn ghost" id="lib">+ My library</button>
</div>
<div class="assets">
<span class="asset"><span class="ok">✓</span> 3D model</span>
<span class="asset"><span class="ok">✓</span> SPICE</span>
<span class="asset"><span class="ok">✓</span> Datasheet</span>
<span class="asset"><span class="ok">✓</span> Pin map (${(p.pins || []).length})</span>
<span class="asset"><span class="ok">✓</span> Verified</span>
</div>
${p.when_to_use ? `<div class="section"><h3>When to use</h3><p style="color:var(--text);line-height:1.6;margin:0">${esc(p.when_to_use)}</p></div>` : ''}
${ucs ? `<div class="section"><h3>What it's used for</h3><ul class="uc">${ucs}</ul></div>` : ''}
${params ? `<div class="section"><h3>Specifications</h3><table class="ptable">${params}</table></div>` : ''}
${ser}
${pins ? `<div class="section"><h3>Pins &amp; pads · aligned to footprint</h3><div class="pins">${pins}</div></div>` : ''}
${(tagList || kr) ? `<div class="section"><h3>Requirement tags</h3><div class="tags">${kr}${tagList}</div></div>` : ''}
${p.spice ? `<div class="section"><h3>SPICE model</h3><pre class="spice">${esc(p.spice)}</pre></div>` : ''}
</div>
</div>`;
mountViewer(document.getElementById('cv'), p.model_3d);
document.getElementById('dl-all').onclick = async () => {
if (!spendCredit()) return;
const obj = await (await fetch(p.model_3d)).text();
dl(`${p.id}.obj`, obj);
dl(`${p.id}.cir`, `* ${p.mpn} SPICE model — PromptPCB Parts\n${p.spice || ''}\n`);
dl(`${p.id}.json`, JSON.stringify(p, null, 2), 'application/json');
};
document.getElementById('lib').onclick = () => {
const lib = JSON.parse(localStorage.getItem('ppcb_lib') || '[]');
if (!lib.includes(p.id)) { lib.push(p.id); localStorage.setItem('ppcb_lib', JSON.stringify(lib)); }
document.getElementById('lib').textContent = '✓ In library';
};
})();