added preset saving and heat transfer
This commit is contained in:
@@ -285,6 +285,46 @@
|
||||
}
|
||||
#plots-card { position: relative; }
|
||||
|
||||
/* ---- Preset bar ---- */
|
||||
.preset-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 14px;
|
||||
background: #f1f5f9;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.preset-bar select {
|
||||
flex: 1;
|
||||
min-width: 100px;
|
||||
max-width: 200px;
|
||||
font-size: 12px;
|
||||
padding: 3px 6px;
|
||||
}
|
||||
.preset-bar input[type=text] {
|
||||
flex: 1;
|
||||
min-width: 80px;
|
||||
max-width: 160px;
|
||||
font-size: 12px;
|
||||
padding: 3px 6px;
|
||||
}
|
||||
.btn-preset {
|
||||
padding: 3px 10px;
|
||||
font-size: 12px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.btn-preset-load { background: #dbeafe; color: #1d4ed8; }
|
||||
.btn-preset-load:hover { background: #bfdbfe; }
|
||||
.btn-preset-save { background: #dcfce7; color: #166534; }
|
||||
.btn-preset-save:hover { background: #bbf7d0; }
|
||||
.btn-preset-delete { background: #fee2e2; color: #b91c1c; }
|
||||
.btn-preset-delete:hover { background: #fca5a5; }
|
||||
|
||||
/* ---- Sweep results table ---- */
|
||||
.sweep-table-wrap {
|
||||
overflow-x: auto;
|
||||
@@ -334,7 +374,7 @@
|
||||
/* ---- Responsive: collapse to single column on narrow screens ---- */
|
||||
@media (max-width: 860px) {
|
||||
.layout { grid-template-columns: 1fr; }
|
||||
.plots-grid { grid-template-columns: 1fr; }
|
||||
.plots-grid { grid-template-columns: 1fr !important; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
@@ -353,6 +393,13 @@
|
||||
<!-- Core Parameters -->
|
||||
<div class="card">
|
||||
<div class="card-header">Core Parameters</div>
|
||||
<div class="preset-bar">
|
||||
<select id="preset-sel-core" title="Saved cores"></select>
|
||||
<button class="btn-preset btn-preset-load" onclick="presetLoad('core')">Load</button>
|
||||
<input type="text" id="preset-name-core" placeholder="preset name">
|
||||
<button class="btn-preset btn-preset-save" onclick="presetSave('core')">Save</button>
|
||||
<button class="btn-preset btn-preset-delete" onclick="presetDelete('core')">Delete</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="form-row three">
|
||||
<label class="field"><span>ID (mm)</span><input type="number" id="core-ID" value="21.5" min="1" step="0.1"></label>
|
||||
@@ -372,6 +419,13 @@
|
||||
<!-- Windings -->
|
||||
<div class="card">
|
||||
<div class="card-header">Windings</div>
|
||||
<div class="preset-bar">
|
||||
<select id="preset-sel-windings" title="Saved windings"></select>
|
||||
<button class="btn-preset btn-preset-load" onclick="presetLoad('windings')">Load</button>
|
||||
<input type="text" id="preset-name-windings" placeholder="preset name">
|
||||
<button class="btn-preset btn-preset-save" onclick="presetSave('windings')">Save</button>
|
||||
<button class="btn-preset btn-preset-delete" onclick="presetDelete('windings')">Delete</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="windings-container"></div>
|
||||
<div class="btn-row">
|
||||
@@ -397,6 +451,13 @@
|
||||
<!-- Simulation Parameters -->
|
||||
<div class="card">
|
||||
<div class="card-header">Simulation</div>
|
||||
<div class="preset-bar">
|
||||
<select id="preset-sel-sim" title="Saved sim settings"></select>
|
||||
<button class="btn-preset btn-preset-load" onclick="presetLoad('sim')">Load</button>
|
||||
<input type="text" id="preset-name-sim" placeholder="preset name">
|
||||
<button class="btn-preset btn-preset-save" onclick="presetSave('sim')">Save</button>
|
||||
<button class="btn-preset btn-preset-delete" onclick="presetDelete('sim')">Delete</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="form-row one">
|
||||
<label class="field">
|
||||
@@ -432,7 +493,14 @@
|
||||
|
||||
<details style="margin: 8px 0">
|
||||
<summary style="cursor:pointer; font-size:12px; color:#2d6a9f; font-weight:500">Constraints (expand)</summary>
|
||||
<div style="margin-top:8px">
|
||||
<div class="preset-bar" style="margin:6px 0; padding:4px 0; background:none; border:none">
|
||||
<select id="preset-sel-constraints" title="Saved constraints"></select>
|
||||
<button class="btn-preset btn-preset-load" onclick="presetLoad('constraints')">Load</button>
|
||||
<input type="text" id="preset-name-constraints" placeholder="preset name">
|
||||
<button class="btn-preset btn-preset-save" onclick="presetSave('constraints')">Save</button>
|
||||
<button class="btn-preset btn-preset-delete" onclick="presetDelete('constraints')">Delete</button>
|
||||
</div>
|
||||
<div style="margin-top:4px">
|
||||
<div class="form-row">
|
||||
<label class="field"><span>B max (T)</span><input type="number" id="con-B" value="0.3" min="0.01" step="0.01"></label>
|
||||
<label class="field"><span>Vp max (V)</span><input type="number" id="con-Vp" value="50" min="1" step="1"></label>
|
||||
@@ -448,6 +516,18 @@
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<div class="form-row" style="margin-top:8px">
|
||||
<label class="field">
|
||||
<span>Ambient temp (°C)</span>
|
||||
<input type="number" id="sim-tambient" value="25" min="-40" max="100" step="1">
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Conv. coeff h (W/m²K)</span>
|
||||
<input type="number" id="sim-hconv" value="6" min="1" max="100" step="0.5"
|
||||
title="Still-air natural convection ≈ 5–10 W/m²K">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="btn-row">
|
||||
<button class="btn btn-primary" id="btn-sim" onclick="runSweep()">Run Simulation</button>
|
||||
</div>
|
||||
@@ -480,10 +560,11 @@
|
||||
<select id="freq-select" onchange="updatePlots()"></select>
|
||||
<span id="plot-status" style="font-size:12px;color:#64748b;margin-left:8px"></span>
|
||||
</div>
|
||||
<div class="plots-grid">
|
||||
<div class="plots-grid" style="grid-template-columns: 1fr 1fr;">
|
||||
<div class="plot-box" id="plot-voltage"></div>
|
||||
<div class="plot-box" id="plot-current"></div>
|
||||
<div class="plot-box" id="plot-power"></div>
|
||||
<div class="plot-box" id="plot-temp"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -512,7 +593,8 @@
|
||||
// State
|
||||
// ============================================================
|
||||
let windingCount = 0;
|
||||
let sweepData = null; // last successful sweep result
|
||||
let sweepData = null; // last successful sweep result
|
||||
let designSurfaceArea = null; // A_surface_m2 from last design run
|
||||
|
||||
// ============================================================
|
||||
// Winding builder
|
||||
@@ -656,8 +738,9 @@ async function runDesign() {
|
||||
return;
|
||||
}
|
||||
setMsg(msg, 'ok', 'Design complete.');
|
||||
designSurfaceArea = data.A_surface_m2 || null;
|
||||
showDrawing(data.drawing);
|
||||
showDesignResults(data.windings);
|
||||
showDesignResults(data.windings, data.fill_factor_target, data.fill_factor_actual, data.window_area_mm2, data.A_surface_m2);
|
||||
resContainer.style.display = 'block';
|
||||
} catch(e) {
|
||||
setMsg(msg, 'error', 'Network error: ' + e.message);
|
||||
@@ -676,9 +759,29 @@ function showDrawing(b64) {
|
||||
img.style.display = 'block';
|
||||
}
|
||||
|
||||
function showDesignResults(windings) {
|
||||
function showDesignResults(windings, fillTarget, fillActual, windowArea, surfaceArea) {
|
||||
const cont = document.getElementById('design-results-tables');
|
||||
cont.innerHTML = '';
|
||||
|
||||
// Fill factor summary row
|
||||
if (fillActual != null) {
|
||||
const pct = (fillActual * 100).toFixed(1);
|
||||
const tgtPct = fillTarget != null ? (fillTarget * 100).toFixed(0) : '?';
|
||||
const over = fillActual > fillTarget * 1.005;
|
||||
const color = over ? '#b91c1c' : '#166534';
|
||||
const surfStr = surfaceArea != null
|
||||
? ` | Surface area: <strong>${(surfaceArea * 1e4).toFixed(1)} cm²</strong>` : '';
|
||||
cont.innerHTML += `
|
||||
<div style="margin-bottom:10px;padding:6px 10px;background:#f1f5f9;border-radius:6px;font-size:12px">
|
||||
Window area: <strong>${windowArea != null ? windowArea.toFixed(1) : '?'} mm²</strong>
|
||||
|
|
||||
Target fill: <strong>${tgtPct}%</strong>
|
||||
|
|
||||
Actual fill: <strong style="color:${color}">${pct}%</strong>
|
||||
${surfStr}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
windings.forEach(w => {
|
||||
const feasStr = w.feasible
|
||||
? '<span class="badge-ok">OK</span>'
|
||||
@@ -896,12 +999,39 @@ function updatePlots() {
|
||||
document.getElementById('plot-status').textContent =
|
||||
`${rows.length} points, ${rows.filter(r => r.met_target).length} met target`;
|
||||
|
||||
updateTable(rows);
|
||||
// --- Temperature plot ---
|
||||
const T_amb = parseFloat(document.getElementById('sim-tambient').value) || 25;
|
||||
const h_conv = parseFloat(document.getElementById('sim-hconv').value) || 6;
|
||||
const A_surf = designSurfaceArea; // m² from last design, null if not run yet
|
||||
|
||||
const T_rise = rows.map(r => {
|
||||
if (r.P_in_W == null || r.P_out_W == null || !A_surf) return null;
|
||||
return (r.P_in_W - r.P_out_W) / (h_conv * A_surf);
|
||||
});
|
||||
const T_copper = T_rise.map(dt => dt != null ? T_amb + dt : null);
|
||||
|
||||
Plotly.react('plot-temp', [
|
||||
{ x, y: T_rise, name: 'T rise (°C)', mode: 'lines+markers', type: 'scatter',
|
||||
text: hoverTap, hovertemplate: 'R=%{x}Ω<br>ΔT=%{y:.1f}°C<br>%{text}<extra></extra>' },
|
||||
{ x, y: T_copper, name: 'T copper (°C)', mode: 'lines+markers', type: 'scatter',
|
||||
line: {dash: 'dot'},
|
||||
text: hoverTap, hovertemplate: 'R=%{x}Ω<br>T=%{y:.1f}°C<br>%{text}<extra></extra>' },
|
||||
], {
|
||||
title: { text: `Temperature @ ${freqLabel} (T_amb=${T_amb}°C, h=${h_conv} W/m²K)`, font: {size:12} },
|
||||
xaxis: { title: 'R load (Ω)', type: 'log' },
|
||||
yaxis: { title: 'Temperature (°C)' },
|
||||
margin: {t:40, b:42, l:54, r:10},
|
||||
legend: {font:{size:11}},
|
||||
height: plotH,
|
||||
}, {responsive: true, displayModeBar: false});
|
||||
|
||||
updateTable(rows, T_rise, T_copper);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Results table (CSV-equivalent, all rows for selected freq)
|
||||
// ============================================================
|
||||
// All fields — used for CSV export only
|
||||
const _CSV_COLS = [
|
||||
{ key: 'freq_hz', label: 'freq (Hz)' },
|
||||
{ key: 'Z_load_R', label: 'R (Ω)' },
|
||||
@@ -931,7 +1061,37 @@ const _CSV_COLS = [
|
||||
{ key: 'efficiency', label: 'eff' },
|
||||
];
|
||||
|
||||
function fmtCell(key, val) {
|
||||
// Columns shown in the on-screen table (subset + computed P_loss)
|
||||
const _TABLE_COLS = [
|
||||
{ key: 'freq_hz', label: 'freq (Hz)' },
|
||||
{ key: 'Z_load_R', label: 'R (Ω)' },
|
||||
{ key: 'Z_load_X', label: 'X (Ω)' },
|
||||
{ key: 'met_target', label: 'met' },
|
||||
{ key: 'primary_tap', label: 'P tap' },
|
||||
{ key: 'secondary_tap', label: 'S tap' },
|
||||
{ key: 'Vp_rms', label: 'Vp (V)' },
|
||||
{ key: 'Np_eff', label: 'Np' },
|
||||
{ key: 'Ns_eff', label: 'Ns' },
|
||||
{ key: 'B_peak_T', label: 'B (T)' },
|
||||
{ key: 'Vs_rms', label: 'Vs (V)' },
|
||||
{ key: 'Ip_rms', label: 'Ip (A)' },
|
||||
{ key: 'Is_rms', label: 'Is (A)' },
|
||||
{ key: 'P_in_W', label: 'P_in (W)' },
|
||||
{ key: 'P_out_W', label: 'P_out (W)' },
|
||||
{ key: '_P_loss', label: 'P_loss (W)' }, // computed
|
||||
{ key: 'efficiency', label: 'eff' },
|
||||
{ key: '_T_rise', label: 'ΔT (°C)' }, // computed from thermal model
|
||||
{ key: '_T_copper', label: 'T_cu (°C)' }, // computed
|
||||
];
|
||||
|
||||
function fmtCell(key, val, row) {
|
||||
if (key === '_P_loss') {
|
||||
const pin = row && row.P_in_W != null ? row.P_in_W : null;
|
||||
const pout = row && row.P_out_W != null ? row.P_out_W : null;
|
||||
val = (pin != null && pout != null) ? pin - pout : null;
|
||||
}
|
||||
if (key === '_T_rise') { val = row ? row._T_rise : null; }
|
||||
if (key === '_T_copper') { val = row ? row._T_copper : null; }
|
||||
if (val === null || val === undefined) return '<span class="cell-null">—</span>';
|
||||
if (key === 'met_target') {
|
||||
return val
|
||||
@@ -942,22 +1102,27 @@ function fmtCell(key, val) {
|
||||
return (val * 100).toFixed(2) + '%';
|
||||
}
|
||||
if (typeof val === 'number') {
|
||||
// integers: no decimals; floats: 2 decimal places
|
||||
return Number.isInteger(val) ? val : val.toFixed(2);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
function updateTable(rows) {
|
||||
function updateTable(rows, T_rise, T_copper) {
|
||||
document.getElementById('table-card').style.display = 'block';
|
||||
|
||||
// Attach computed thermal values directly onto each row object
|
||||
rows.forEach((r, i) => {
|
||||
r._T_rise = T_rise ? T_rise[i] : null;
|
||||
r._T_copper = T_copper ? T_copper[i] : null;
|
||||
});
|
||||
|
||||
const thead = document.getElementById('sweep-table-head');
|
||||
const tbody = document.getElementById('sweep-table-body');
|
||||
|
||||
thead.innerHTML = '<tr>' + _CSV_COLS.map(c => `<th>${c.label}</th>`).join('') + '</tr>';
|
||||
thead.innerHTML = '<tr>' + _TABLE_COLS.map(c => `<th>${c.label}</th>`).join('') + '</tr>';
|
||||
|
||||
tbody.innerHTML = rows.map(r =>
|
||||
'<tr>' + _CSV_COLS.map(c => `<td>${fmtCell(c.key, r[c.key])}</td>`).join('') + '</tr>'
|
||||
'<tr>' + _TABLE_COLS.map(c => `<td>${fmtCell(c.key, r[c.key], r)}</td>`).join('') + '</tr>'
|
||||
).join('');
|
||||
}
|
||||
|
||||
@@ -1024,10 +1189,191 @@ function calcPlotHeight() {
|
||||
})();
|
||||
|
||||
// ============================================================
|
||||
// Initialise with default windings
|
||||
// Preset system
|
||||
// ============================================================
|
||||
addWinding('primary', [25, 50], [22, 22]);
|
||||
addWinding('secondary', [100, 50, 50, 50], [22, 22, 22, 26]);
|
||||
|
||||
// Serialise current UI state for each preset type
|
||||
function presetExtract(ptype) {
|
||||
if (ptype === 'core') {
|
||||
const d = {
|
||||
ID_mm: parseFloat(document.getElementById('core-ID').value),
|
||||
OD_mm: parseFloat(document.getElementById('core-OD').value),
|
||||
height_mm: parseFloat(document.getElementById('core-H').value),
|
||||
fill_factor: parseFloat(document.getElementById('core-fill').value),
|
||||
};
|
||||
const ae = document.getElementById('core-Ae').value.trim();
|
||||
const ve = document.getElementById('core-Ve').value.trim();
|
||||
if (ae !== '') d.Ae_mm2 = parseFloat(ae);
|
||||
if (ve !== '') d.Ve_mm3 = parseFloat(ve);
|
||||
return d;
|
||||
}
|
||||
if (ptype === 'windings') {
|
||||
const windings = [];
|
||||
document.querySelectorAll('.winding-block').forEach(block => {
|
||||
const name = block.querySelector('.winding-name').value.trim();
|
||||
const taps = [0], awg = [];
|
||||
block.querySelectorAll('.seg-row').forEach(row => {
|
||||
taps.push(parseInt(row.querySelector('.seg-turns').value));
|
||||
awg.push(parseInt(row.querySelector('.seg-awg').value));
|
||||
});
|
||||
windings.push({ name, taps, awg });
|
||||
});
|
||||
return { windings };
|
||||
}
|
||||
if (ptype === 'sim') {
|
||||
return {
|
||||
frequencies: document.getElementById('sim-freqs').value,
|
||||
loads: document.getElementById('sim-loads').value,
|
||||
target_power_W: parseFloat(document.getElementById('sim-target').value),
|
||||
power_tol_pct: parseFloat(document.getElementById('sim-tol').value),
|
||||
Vp_min: parseFloat(document.getElementById('sim-vpmin').value),
|
||||
Vp_max: parseFloat(document.getElementById('sim-vpmax').value),
|
||||
Vp_steps: parseInt(document.getElementById('sim-vpsteps').value),
|
||||
T_ambient_C: parseFloat(document.getElementById('sim-tambient').value),
|
||||
h_conv: parseFloat(document.getElementById('sim-hconv').value),
|
||||
};
|
||||
}
|
||||
if (ptype === 'constraints') {
|
||||
return {
|
||||
B_max_T: parseFloat(document.getElementById('con-B').value),
|
||||
Vp_max: parseFloat(document.getElementById('con-Vp').value),
|
||||
Vs_max: parseFloat(document.getElementById('con-Vs').value),
|
||||
Ip_max: parseFloat(document.getElementById('con-Ip').value),
|
||||
Is_max: parseFloat(document.getElementById('con-Is').value),
|
||||
P_out_max_W: parseFloat(document.getElementById('con-Pout').value),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Apply loaded preset data back to the UI
|
||||
function presetApply(ptype, data) {
|
||||
if (ptype === 'core') {
|
||||
document.getElementById('core-ID').value = data.ID_mm ?? '';
|
||||
document.getElementById('core-OD').value = data.OD_mm ?? '';
|
||||
document.getElementById('core-H').value = data.height_mm ?? '';
|
||||
document.getElementById('core-fill').value = data.fill_factor ?? 0.35;
|
||||
document.getElementById('core-Ae').value = data.Ae_mm2 ?? '';
|
||||
document.getElementById('core-Ve').value = data.Ve_mm3 ?? '';
|
||||
}
|
||||
if (ptype === 'windings') {
|
||||
const container = document.getElementById('windings-container');
|
||||
container.innerHTML = '';
|
||||
windingCount = 0;
|
||||
(data.windings || []).forEach(w => {
|
||||
addWinding(w.name, w.taps.slice(1), w.awg);
|
||||
});
|
||||
}
|
||||
if (ptype === 'sim') {
|
||||
if (data.frequencies !== undefined) document.getElementById('sim-freqs').value = data.frequencies;
|
||||
if (data.loads !== undefined) document.getElementById('sim-loads').value = data.loads;
|
||||
if (data.target_power_W !== undefined) document.getElementById('sim-target').value = data.target_power_W;
|
||||
if (data.power_tol_pct !== undefined) document.getElementById('sim-tol').value = data.power_tol_pct;
|
||||
if (data.Vp_min !== undefined) document.getElementById('sim-vpmin').value = data.Vp_min;
|
||||
if (data.Vp_max !== undefined) document.getElementById('sim-vpmax').value = data.Vp_max;
|
||||
if (data.Vp_steps !== undefined) document.getElementById('sim-vpsteps').value = data.Vp_steps;
|
||||
if (data.T_ambient_C !== undefined) document.getElementById('sim-tambient').value = data.T_ambient_C;
|
||||
if (data.h_conv !== undefined) document.getElementById('sim-hconv').value = data.h_conv;
|
||||
}
|
||||
if (ptype === 'constraints') {
|
||||
if (data.B_max_T !== undefined) document.getElementById('con-B').value = data.B_max_T;
|
||||
if (data.Vp_max !== undefined) document.getElementById('con-Vp').value = data.Vp_max;
|
||||
if (data.Vs_max !== undefined) document.getElementById('con-Vs').value = data.Vs_max;
|
||||
if (data.Ip_max !== undefined) document.getElementById('con-Ip').value = data.Ip_max;
|
||||
if (data.Is_max !== undefined) document.getElementById('con-Is').value = data.Is_max;
|
||||
if (data.P_out_max_W !== undefined) document.getElementById('con-Pout').value = data.P_out_max_W;
|
||||
}
|
||||
}
|
||||
|
||||
// Populate a preset dropdown from server
|
||||
async function presetRefresh(ptype) {
|
||||
const sel = document.getElementById(`preset-sel-${ptype}`);
|
||||
if (!sel) return null;
|
||||
const resp = await fetch(`/api/presets/${ptype}`);
|
||||
const data = await resp.json();
|
||||
if (!data.success) return null;
|
||||
sel.innerHTML = data.names.length === 0
|
||||
? '<option value="">(no saved presets)</option>'
|
||||
: data.names.map(n => `<option value="${n}"${n === data.last ? ' selected' : ''}>${n}</option>`).join('');
|
||||
// Sync name input to selected
|
||||
const nameEl = document.getElementById(`preset-name-${ptype}`);
|
||||
if (nameEl && data.last) nameEl.value = data.last;
|
||||
return data;
|
||||
}
|
||||
|
||||
async function presetLoad(ptype) {
|
||||
const sel = document.getElementById(`preset-sel-${ptype}`);
|
||||
const name = sel ? sel.value : '';
|
||||
if (!name) return;
|
||||
const resp = await fetch(`/api/presets/${ptype}/${encodeURIComponent(name)}`);
|
||||
const data = await resp.json();
|
||||
if (!data.success) { alert('Load failed: ' + data.error); return; }
|
||||
presetApply(ptype, data.data);
|
||||
// Sync name input
|
||||
const nameEl = document.getElementById(`preset-name-${ptype}`);
|
||||
if (nameEl) nameEl.value = name;
|
||||
}
|
||||
|
||||
async function presetSave(ptype) {
|
||||
const nameEl = document.getElementById(`preset-name-${ptype}`);
|
||||
const name = nameEl ? nameEl.value.trim() : '';
|
||||
if (!name) { alert('Enter a preset name first.'); return; }
|
||||
const payload = presetExtract(ptype);
|
||||
const resp = await fetch(`/api/presets/${ptype}/${encodeURIComponent(name)}`, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
const data = await resp.json();
|
||||
if (!data.success) { alert('Save failed: ' + data.error); return; }
|
||||
await presetRefresh(ptype);
|
||||
// Select the newly saved name
|
||||
const sel = document.getElementById(`preset-sel-${ptype}`);
|
||||
if (sel) sel.value = name;
|
||||
}
|
||||
|
||||
async function presetDelete(ptype) {
|
||||
const sel = document.getElementById(`preset-sel-${ptype}`);
|
||||
const name = sel ? sel.value : '';
|
||||
if (!name || name === '(no saved presets)') return;
|
||||
if (!confirm(`Delete preset "${name}"?`)) return;
|
||||
const resp = await fetch(`/api/presets/${ptype}/${encodeURIComponent(name)}`, { method: 'DELETE' });
|
||||
const data = await resp.json();
|
||||
if (!data.success) { alert('Delete failed: ' + data.error); return; }
|
||||
await presetRefresh(ptype);
|
||||
}
|
||||
|
||||
// On page load: refresh all dropdowns and auto-load last-used preset for each type
|
||||
async function initPresets() {
|
||||
for (const ptype of ['core', 'windings', 'sim', 'constraints']) {
|
||||
const info = await presetRefresh(ptype);
|
||||
if (info && info.last) {
|
||||
// Auto-load last used
|
||||
const resp = await fetch(`/api/presets/${ptype}/${encodeURIComponent(info.last)}`);
|
||||
const d = await resp.json();
|
||||
if (d.success) presetApply(ptype, d.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Initialise: load presets first; fall back to hardcoded defaults
|
||||
// ============================================================
|
||||
(async () => {
|
||||
// initPresets returns after applying last-used presets (if any)
|
||||
const windingsInfo = await (async () => {
|
||||
const r = await fetch('/api/presets/windings');
|
||||
const d = await r.json();
|
||||
return d.success ? d : null;
|
||||
})();
|
||||
|
||||
// Only add default windings if no saved windings preset exists
|
||||
if (!windingsInfo || !windingsInfo.last) {
|
||||
addWinding('primary', [25, 50], [22, 22]);
|
||||
addWinding('secondary', [100, 50, 50, 50], [22, 22, 22, 26]);
|
||||
}
|
||||
|
||||
await initPresets();
|
||||
})();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user