Files
ShooterHub/frontend/js/utils.js
2026-04-02 11:24:30 +02:00

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`;
}