78 lines
5.2 KiB
JavaScript
78 lines
5.2 KiB
JavaScript
// 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) => ({ '&': '&', '<': '<', '>': '>' }[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 & 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';
|
||
};
|
||
})();
|