91 lines
4.3 KiB
JavaScript
91 lines
4.3 KiB
JavaScript
// ── Shared UI utilities ────────────────────────────────────────────────────────
|
|
// Loaded after api.js; available to all page scripts.
|
|
|
|
// ── HTML escaping ─────────────────────────────────────────────────────────────
|
|
|
|
function esc(s) {
|
|
const d = document.createElement('div');
|
|
d.textContent = String(s ?? '');
|
|
return d.innerHTML;
|
|
}
|
|
|
|
// ── Toast notifications ───────────────────────────────────────────────────────
|
|
|
|
function showToast(msg, type = 'success') {
|
|
const el = document.createElement('div');
|
|
el.className = `toast align-items-center text-bg-${type} border-0 show`;
|
|
el.innerHTML = `<div class="d-flex"><div class="toast-body">${msg}</div>
|
|
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button></div>`;
|
|
document.getElementById('toastContainer').appendChild(el);
|
|
setTimeout(() => el.remove(), 4000);
|
|
}
|
|
|
|
// ── Public / private toggle ───────────────────────────────────────────────────
|
|
// Usage: renderPublicToggle(isPublic, { btnId, iconId, labelId, privateClass })
|
|
// privateClass defaults to 'btn-outline-secondary' (use 'btn-outline-light' for dark backgrounds)
|
|
|
|
function renderPublicToggle(isPublic, {
|
|
btnId = 'togglePublicBtn',
|
|
iconId = 'togglePublicIcon',
|
|
labelId = 'togglePublicLabel',
|
|
privateClass = 'btn-outline-secondary',
|
|
} = {}) {
|
|
const btn = document.getElementById(btnId);
|
|
const icon = document.getElementById(iconId);
|
|
const label = document.getElementById(labelId);
|
|
if (!btn || !icon || !label) return;
|
|
if (isPublic) {
|
|
icon.className = 'bi bi-globe2 me-1 text-success';
|
|
label.textContent = 'Public';
|
|
btn.className = 'btn btn-sm btn-outline-success';
|
|
} else {
|
|
icon.className = 'bi bi-lock me-1';
|
|
label.textContent = 'Private';
|
|
btn.className = `btn btn-sm ${privateClass}`;
|
|
}
|
|
}
|
|
|
|
// ── Unit preferences ──────────────────────────────────────────────────────────
|
|
|
|
function getDistUnit() { return localStorage.getItem('units.dist') || 'mm'; }
|
|
function getVelUnit() { return localStorage.getItem('units.vel') || 'fps'; }
|
|
function setDistUnit(u) { localStorage.setItem('units.dist', u); }
|
|
function setVelUnit(u) { localStorage.setItem('units.vel', u); }
|
|
|
|
// ── Distance unit button sync ─────────────────────────────────────────────────
|
|
// Marks the active [data-dist-unit] button inside container (or document).
|
|
|
|
function applyDistUnitButtons(container) {
|
|
const u = getDistUnit();
|
|
(container || document).querySelectorAll('[data-dist-unit]').forEach(btn => {
|
|
btn.classList.toggle('active', btn.dataset.distUnit === u);
|
|
});
|
|
}
|
|
|
|
// ── Distance / group-size formatting ─────────────────────────────────────────
|
|
// mm: value in mm (float or string)
|
|
// moa: precomputed MOA from server (may be null)
|
|
// distM: shooting distance in metres (for MRAD computation)
|
|
|
|
function fDist(mm, moa, distM) {
|
|
const u = getDistUnit();
|
|
const mmV = parseFloat(mm);
|
|
if (u === 'moa') {
|
|
if (moa != null) return `${parseFloat(moa).toFixed(2)} MOA`;
|
|
if (distM) return `${(mmV / (distM * 0.29089)).toFixed(2)} MOA`;
|
|
return `${mmV.toFixed(1)} mm`;
|
|
}
|
|
if (u === 'mrad') {
|
|
if (distM) return `${(mmV / distM).toFixed(2)} MRAD`;
|
|
return `${mmV.toFixed(1)} mm`;
|
|
}
|
|
return `${mmV.toFixed(1)} mm`;
|
|
}
|
|
|
|
// ── Velocity formatting ───────────────────────────────────────────────────────
|
|
// fps / mps are the server values (strings or numbers)
|
|
|
|
function fVel(fps, mps) {
|
|
return getVelUnit() === 'mps' ? `${mps} m/s` : `${fps} fps`;
|
|
}
|