/**
* Idempotently inject the passive-component preset variants into
* `scripts/component-overrides.json` (the documented extension point that
* survives metadata regeneration via `npm run generate:metadata`).
*
* Adds:
* - resistor-220 / 330 / 470 / 1k / 2k2 / 4k7 / 10k / 22k / 47k / 100k / 1m
* - cap-10p / 22p / 100p / 1n / 10n / 100n / 1u (ceramic, non-polarized)
* - capacitor-electrolytic + cap-elec-1u / 10u / 47u / 100u / 470u / 1000u
* - ind-100u / 1m / 10m
*
* Plus name/thumbnail overrides for the canonical resistor / capacitor /
* inductor entries so they read "(custom)" in the picker once presets exist.
*
* Run: node frontend/scripts/add-passive-presets.mjs
* Then: npm run generate:metadata (or just `npm run dev`)
*/
import { readFileSync, writeFileSync } from 'node:fs';
import { resolve, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const OVERRIDES_PATH = resolve(__dirname, '../../scripts/component-overrides.json');
const overrides = JSON.parse(readFileSync(OVERRIDES_PATH, 'utf8'));
// ── SVG thumbnail factories ────────────────────────────────────────────────
function frame(inner) {
return ``;
}
function valueLabel(text) {
return `${text}`;
}
// Resistor color-band lookup (matches the wokwi-resistor element).
const BAND = {
'-1': '#F1D863', '0': '#000', '1': '#8F4814', '2': '#FB0000',
'3': '#FC9700', '4': '#FCF800', '5': '#00B800', '6': '#0000FF',
'7': '#A803D6', '8': '#808080', '9': '#FCFCFC',
};
function bandsFor(ohms) {
if (!ohms || ohms <= 0) return ['0', '0', '0'];
const exp = Math.floor(Math.log10(ohms)) - 1;
const base = Math.round(ohms / 10 ** exp);
return [String(Math.floor(base / 10)), String(base % 10), String(exp)];
}
function resistorThumb(ohms, label) {
const [b1, b2, b3] = bandsFor(ohms);
return frame(`
${valueLabel(label)}`);
}
function capacitorCeramicThumb(label) {
return frame(`
${valueLabel(label)}`);
}
function capacitorElectrolyticThumb(label) {
return frame(`
−
−
${valueLabel(label)}`);
}
function inductorThumb(label) {
return frame(`
${valueLabel(label)}`);
}
// ── Preset definitions ─────────────────────────────────────────────────────
const RESISTORS = [
['resistor-220', '220 Ω', '220', 220],
['resistor-330', '330 Ω', '330', 330],
['resistor-470', '470 Ω', '470', 470],
['resistor-1k', '1 kΩ', '1000', 1000],
['resistor-2k2', '2.2 kΩ', '2200', 2200],
['resistor-4k7', '4.7 kΩ', '4700', 4700],
['resistor-10k', '10 kΩ', '10000', 10000],
['resistor-22k', '22 kΩ', '22000', 22000],
['resistor-47k', '47 kΩ', '47000', 47000],
['resistor-100k', '100 kΩ', '100000', 100000],
['resistor-1m', '1 MΩ', '1000000', 1000000],
];
const CAPS_CERAMIC = [
['cap-10p', '10 pF', '10p'],
['cap-22p', '22 pF', '22p'],
['cap-100p', '100 pF', '100p'],
['cap-1n', '1 nF', '1n'],
['cap-10n', '10 nF', '10n'],
['cap-100n', '100 nF', '100n'],
['cap-1u', '1 µF', '1u'],
];
const CAPS_ELEC = [
['cap-elec-1u', '1 µF', '1u'],
['cap-elec-10u', '10 µF', '10u'],
['cap-elec-47u', '47 µF', '47u'],
['cap-elec-100u', '100 µF', '100u'],
['cap-elec-470u', '470 µF', '470u'],
['cap-elec-1000u', '1000 µF', '1000u'],
];
const INDUCTORS = [
['ind-100u', '100 µH', '100u'],
['ind-1m', '1 mH', '1m'],
['ind-10m', '10 mH', '10m'],
];
const valueProp = (defaultValue) => ({
name: 'value', type: 'string', defaultValue, control: 'text',
});
function makeEntry({ id, tagName, name, thumbnail, defaultValue, tags }) {
return {
id,
tagName,
name,
category: 'passive',
thumbnail,
properties: [valueProp(defaultValue)],
defaultValues: { value: defaultValue },
pinCount: 0,
tags,
};
}
// Build the new entries array
const newEntries = [];
// Canonical generic non-polarized cap and inductor — used to live in the
// upstream wokwi-elements submodule but we removed those mutations, so we
// have to inject them here.
newEntries.push(makeEntry({
id: 'capacitor', tagName: 'wokwi-capacitor',
name: 'Cap. ceramic (custom)',
thumbnail: capacitorCeramicThumb('?µF'), defaultValue: '1u',
tags: ['capacitor', 'ceramic', 'custom'],
}));
newEntries.push(makeEntry({
id: 'inductor', tagName: 'wokwi-inductor',
name: 'Inductor (custom)',
thumbnail: inductorThumb('?mH'), defaultValue: '1m',
tags: ['inductor', 'custom'],
}));
for (const [id, label, value, ohms] of RESISTORS) {
newEntries.push(makeEntry({
id, tagName: 'wokwi-resistor', name: `Resistor ${label}`,
thumbnail: resistorThumb(ohms, label), defaultValue: value,
tags: ['resistor', 'preset', label.toLowerCase().replace(/\s+/g, '')],
}));
}
for (const [id, label, value] of CAPS_CERAMIC) {
newEntries.push(makeEntry({
id, tagName: 'wokwi-capacitor', name: `Cap. ${label}`,
thumbnail: capacitorCeramicThumb(label), defaultValue: value,
tags: ['capacitor', 'ceramic', 'preset', label.toLowerCase().replace(/\s+/g, '')],
}));
}
newEntries.push(makeEntry({
id: 'capacitor-electrolytic', tagName: 'velxio-capacitor-electrolytic',
name: 'Electrolytic Cap. (custom)',
thumbnail: capacitorElectrolyticThumb('?µF'), defaultValue: '10u',
tags: ['capacitor', 'electrolytic', 'polarized', 'custom'],
}));
for (const [id, label, value] of CAPS_ELEC) {
newEntries.push(makeEntry({
id, tagName: 'velxio-capacitor-electrolytic',
name: `Electrolytic ${label}`,
thumbnail: capacitorElectrolyticThumb(label), defaultValue: value,
tags: ['capacitor', 'electrolytic', 'polarized', 'preset', label.toLowerCase().replace(/\s+/g, '')],
}));
}
for (const [id, label, value] of INDUCTORS) {
newEntries.push(makeEntry({
id, tagName: 'wokwi-inductor', name: `Inductor ${label}`,
thumbnail: inductorThumb(label), defaultValue: value,
tags: ['inductor', 'preset', label.toLowerCase().replace(/\s+/g, '')],
}));
}
// ── Mutate the overrides JSON ──────────────────────────────────────────────
if (!Array.isArray(overrides._customComponents)) overrides._customComponents = [];
// Replace any existing entries with the same id (idempotent).
const newIds = new Set(newEntries.map((e) => e.id));
overrides._customComponents = [
...overrides._customComponents.filter((c) => !newIds.has(c.id)),
...newEntries,
];
// Rename canonical entries to "(custom)" via name/thumbnail overrides.
const renames = [
{ id: 'resistor', name: 'Resistor (custom)', thumbnail: resistorThumb(1000, '?Ω') },
{ id: 'capacitor', name: 'Cap. ceramic (custom)', thumbnail: capacitorCeramicThumb('?µF') },
{ id: 'inductor', name: 'Inductor (custom)', thumbnail: inductorThumb('?mH') },
];
for (const { id, name, thumbnail } of renames) {
overrides[id] = { ...(overrides[id] ?? {}), name, thumbnail };
}
writeFileSync(OVERRIDES_PATH, JSON.stringify(overrides, null, 2) + '\n');
console.log(`✓ Wrote ${newEntries.length} preset entries to _customComponents`);
console.log(`✓ Renamed ${renames.length} canonical passives to "(custom)"`);
console.log(`→ Run \`npm run generate:metadata\` (or just \`npm run dev\`) to materialize them.`);