Player text visibility option #55
@@ -119,6 +119,35 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-xl-8">
|
||||
<div class="card p-3">
|
||||
<h4 class="mb-1">Lecteur (player)</h4>
|
||||
<p class="text-muted mb-3">
|
||||
Affiche/masque le bloc “Source” et “Progress” dans <code>player.html</code> (visible uniquement pour les admins).
|
||||
</p>
|
||||
|
||||
<div class="row g-3 align-items-center">
|
||||
<div class="col-12">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="player_ui_show_source">
|
||||
<label class="form-check-label" for="player_ui_show_source">Afficher “Source”</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="player_ui_show_progress">
|
||||
<label class="form-check-label" for="player_ui_show_progress">Afficher “Progress”</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 d-flex justify-content-end gap-2">
|
||||
<button type="button" id="player_ui_reload" class="btn btn-outline-secondary">Recharger</button>
|
||||
<button type="button" id="player_ui_save" class="btn btn-warning">Enregistrer</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-xl-8">
|
||||
<div class="card p-3">
|
||||
<h4 class="mb-1">Date des Oscars (par année)</h4>
|
||||
|
||||
@@ -41,6 +41,12 @@ window.onload = async function () {
|
||||
const activeYearInput = document.getElementById('active_oscar_year');
|
||||
const saveActiveYearBtn = document.getElementById('save-active-year');
|
||||
|
||||
// Player UI (settings tab): show/hide admin status blocks in player.html
|
||||
const playerUiShowSourceEl = document.getElementById('player_ui_show_source');
|
||||
const playerUiShowProgressEl = document.getElementById('player_ui_show_progress');
|
||||
const playerUiReloadBtn = document.getElementById('player_ui_reload');
|
||||
const playerUiSaveBtn = document.getElementById('player_ui_save');
|
||||
|
||||
// Version control tab elements
|
||||
const adminVersionTabBtn = document.getElementById('admin-version-tab');
|
||||
const appVersionAlertEl = document.getElementById('app_version_alert');
|
||||
@@ -132,6 +138,11 @@ window.onload = async function () {
|
||||
bodyHtml: '',
|
||||
});
|
||||
|
||||
const DEFAULT_PLAYER_UI = Object.freeze({
|
||||
showSource: true,
|
||||
showProgress: true,
|
||||
});
|
||||
|
||||
function showResponse(kind, message) {
|
||||
responseEl.classList.remove('d-none', 'alert-success', 'alert-danger', 'alert-warning');
|
||||
responseEl.classList.add(kind === 'success' ? 'alert-success' : kind === 'warning' ? 'alert-warning' : 'alert-danger');
|
||||
@@ -147,6 +158,66 @@ window.onload = async function () {
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
function normalizePlayerUi(value) {
|
||||
const v = value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
||||
const showSource = v.showSource === undefined ? DEFAULT_PLAYER_UI.showSource : !!v.showSource;
|
||||
const showProgress = v.showProgress === undefined ? DEFAULT_PLAYER_UI.showProgress : !!v.showProgress;
|
||||
return { showSource, showProgress };
|
||||
}
|
||||
|
||||
async function fetchPlayerUi() {
|
||||
const res = await fetch('/api/settings/player-admin-status-ui', { method: 'GET' });
|
||||
const data = await res.json().catch(() => ({}));
|
||||
if (!res.ok) {
|
||||
const msg = data.message || data.error || `Erreur (${res.status})`;
|
||||
throw new Error(msg);
|
||||
}
|
||||
return normalizePlayerUi(data);
|
||||
}
|
||||
|
||||
async function savePlayerUi(payload) {
|
||||
const res = await fetch('/api/settings/player-admin-status-ui', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
const data = await res.json().catch(() => ({}));
|
||||
if (!res.ok) {
|
||||
const msg = data.message || data.error || `Erreur (${res.status})`;
|
||||
throw new Error(msg);
|
||||
}
|
||||
return normalizePlayerUi(data);
|
||||
}
|
||||
|
||||
function setPlayerUiForm(value) {
|
||||
if (!playerUiShowSourceEl || !playerUiShowProgressEl) return;
|
||||
playerUiShowSourceEl.checked = !!value.showSource;
|
||||
playerUiShowProgressEl.checked = !!value.showProgress;
|
||||
}
|
||||
|
||||
function getPlayerUiFormValue() {
|
||||
return normalizePlayerUi({
|
||||
showSource: !!playerUiShowSourceEl?.checked,
|
||||
showProgress: !!playerUiShowProgressEl?.checked,
|
||||
});
|
||||
}
|
||||
|
||||
async function loadPlayerUiIntoUi() {
|
||||
// If the section isn't present (older HTML), skip gracefully.
|
||||
if (!playerUiShowSourceEl) return;
|
||||
try {
|
||||
const value = await fetchPlayerUi();
|
||||
setPlayerUiForm(value);
|
||||
} catch (err) {
|
||||
// Keep defaults so admin can still set something.
|
||||
setPlayerUiForm(DEFAULT_PLAYER_UI);
|
||||
showResponse('warning', err.message || 'Impossible de charger les réglages du lecteur.');
|
||||
}
|
||||
}
|
||||
|
||||
async function copyToClipboard(text) {
|
||||
const t = String(text || '').trim();
|
||||
if (!t) return false;
|
||||
@@ -2337,6 +2408,41 @@ window.onload = async function () {
|
||||
|
||||
hookModal100Inputs();
|
||||
await loadCompletionModalIntoUi();
|
||||
await loadPlayerUiIntoUi();
|
||||
|
||||
if (playerUiReloadBtn) {
|
||||
playerUiReloadBtn.addEventListener('click', async () => {
|
||||
playerUiReloadBtn.disabled = true;
|
||||
const old = playerUiReloadBtn.textContent;
|
||||
playerUiReloadBtn.textContent = 'Chargement...';
|
||||
try {
|
||||
await loadPlayerUiIntoUi();
|
||||
showResponse('success', 'Réglages du lecteur rechargés.');
|
||||
} finally {
|
||||
playerUiReloadBtn.disabled = false;
|
||||
playerUiReloadBtn.textContent = old;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (playerUiSaveBtn) {
|
||||
playerUiSaveBtn.addEventListener('click', async () => {
|
||||
playerUiSaveBtn.disabled = true;
|
||||
const old = playerUiSaveBtn.textContent;
|
||||
playerUiSaveBtn.textContent = 'Enregistrement...';
|
||||
try {
|
||||
const payload = getPlayerUiFormValue();
|
||||
const saved = await savePlayerUi(payload);
|
||||
setPlayerUiForm(saved);
|
||||
showResponse('success', 'Réglages du lecteur enregistrés.');
|
||||
} catch (err) {
|
||||
showResponse('danger', err.message || 'Erreur réseau');
|
||||
} finally {
|
||||
playerUiSaveBtn.disabled = false;
|
||||
playerUiSaveBtn.textContent = old;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (saveActiveYearBtn && activeYearInput) {
|
||||
saveActiveYearBtn.addEventListener('click', async () => {
|
||||
|
||||
@@ -13,6 +13,7 @@ const COMPLETION_MODAL_KEY = "completion_modal_content";
|
||||
const WINNERS_BY_YEAR_KEY = "winners_by_year";
|
||||
const OSCAR_DATES_BY_YEAR_KEY = "oscar_date_by_year";
|
||||
const APP_VERSION_KEY = "app_version_control";
|
||||
const PLAYER_ADMIN_STATUS_UI_KEY = "player_admin_status_ui";
|
||||
|
||||
const crypto = require("crypto");
|
||||
|
||||
@@ -23,6 +24,12 @@ const DEFAULT_COMPLETION_MODAL = Object.freeze({
|
||||
bodyHtml: "",
|
||||
});
|
||||
|
||||
const DEFAULT_PLAYER_ADMIN_STATUS_UI = Object.freeze({
|
||||
// Controls the admin-only debug/status block in player.html
|
||||
showSource: true,
|
||||
showProgress: true,
|
||||
});
|
||||
|
||||
function parseOscarYear(raw) {
|
||||
if (raw === undefined || raw === null || raw === "") return null;
|
||||
const n = Number(raw);
|
||||
@@ -146,6 +153,13 @@ function normalizeCompletionModal(rawValue) {
|
||||
};
|
||||
}
|
||||
|
||||
function normalizePlayerAdminStatusUi(rawValue) {
|
||||
const v = rawValue && typeof rawValue === "object" && !Array.isArray(rawValue) ? rawValue : {};
|
||||
const showSource = v.showSource === undefined ? DEFAULT_PLAYER_ADMIN_STATUS_UI.showSource : !!v.showSource;
|
||||
const showProgress = v.showProgress === undefined ? DEFAULT_PLAYER_ADMIN_STATUS_UI.showProgress : !!v.showProgress;
|
||||
return { showSource, showProgress };
|
||||
}
|
||||
|
||||
function createId() {
|
||||
try {
|
||||
// Node 16+ / 18+: randomUUID
|
||||
@@ -431,6 +445,45 @@ router.get("/app-version", async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Public: get player admin status UI flags (source/progress visibility)
|
||||
router.get("/player-admin-status-ui", async (req, res) => {
|
||||
try {
|
||||
const existing = await Setting.findOne({ key: PLAYER_ADMIN_STATUS_UI_KEY }).select("value");
|
||||
const normalized = normalizePlayerAdminStatusUi(existing?.value);
|
||||
return res.status(200).json(normalized);
|
||||
} catch (_) {
|
||||
return res.status(200).json(DEFAULT_PLAYER_ADMIN_STATUS_UI);
|
||||
}
|
||||
});
|
||||
|
||||
// Admin-only: update player admin status UI flags
|
||||
// Body: { showSource?: boolean, showProgress?: boolean }
|
||||
router.put("/player-admin-status-ui", async (req, res) => {
|
||||
const token = req.headers.authorization?.split(" ")[1];
|
||||
if (!token) return res.status(401).json({ message: "Authentication token is required" });
|
||||
|
||||
try {
|
||||
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
||||
if (!decoded.admin) {
|
||||
return res.status(403).json({ message: "You do not have admin privileges" });
|
||||
}
|
||||
|
||||
const normalized = normalizePlayerAdminStatusUi(req.body);
|
||||
const updated = await Setting.findOneAndUpdate(
|
||||
{ key: PLAYER_ADMIN_STATUS_UI_KEY },
|
||||
{ $set: { value: normalized } },
|
||||
{ upsert: true, new: true }
|
||||
).select("value");
|
||||
|
||||
return res.status(200).json(normalizePlayerAdminStatusUi(updated?.value));
|
||||
} catch (err) {
|
||||
if (err instanceof jwt.JsonWebTokenError) {
|
||||
return res.status(401).json({ message: "Invalid or expired token" });
|
||||
}
|
||||
return res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Admin-only: create a new version entry (optionally activates if none or if activate=true)
|
||||
// Body: { version: "1.2.3", message?: "..." }
|
||||
router.post("/app-version", async (req, res) => {
|
||||
|
||||
Reference in New Issue
Block a user