Files
GameList/wwwroot/js/admin-ui.js

180 lines
6.4 KiB
JavaScript

import { t } from "./i18n.js";
import { state } from "./state.js";
import { $ } from "./dom.js";
import { buildLinkOptionLabel, truncate } from "./ui-utils.js";
function displayPlayerStatus(player) {
if (!player) return "";
const phase = player.phase;
if (phase === "Suggest") return t("admin.statusSuggesting");
if (phase === "Vote")
return player.finalized
? t("admin.statusFinished")
: t("admin.statusVoting");
if (phase === "Results") return t("admin.statusFinished");
return phase;
}
function buildStatusSelect(player) {
const canMoveToSuggest = player.phase === "Vote";
const select = document.createElement("select");
select.className = "chip admin-status-select";
select.dataset.setPlayerPhase = player.playerId;
select.setAttribute("aria-label", t("admin.playerStatus"));
const current = document.createElement("option");
current.value = "";
current.selected = true;
current.textContent = displayPlayerStatus(player);
const suggest = document.createElement("option");
suggest.value = "Suggest";
suggest.disabled = !canMoveToSuggest;
suggest.textContent = t("admin.statusMoveToSuggest");
select.append(current, suggest);
return select;
}
export function renderAdminVoteStatus() {
if (!state.me?.isAdmin) return;
if (state.adminStatusSelectActive) return;
const statusBadge = $("admin-ready-status");
const table = $("admin-player-table")?.querySelector("tbody");
if (!state.adminVoteStatus || !statusBadge || !table) return;
table.innerHTML = "";
state.adminVoteStatus.voters.forEach((v) => {
const tr = document.createElement("tr");
const gamesTooltip = (v.suggestionTitles || []).join(", ");
const nameCell = document.createElement("td");
nameCell.title = v.name ?? "";
nameCell.textContent = truncate(v.name, 28);
const usernameCell = document.createElement("td");
usernameCell.className = "muted small";
usernameCell.title = v.username ?? "";
usernameCell.textContent = truncate(v.username, 24);
const statusCell = document.createElement("td");
statusCell.appendChild(buildStatusSelect(v));
const countCell = document.createElement("td");
countCell.title = gamesTooltip;
countCell.textContent = String(v.suggestionCount ?? 0);
const jokerCell = document.createElement("td");
const jokerButton = document.createElement("button");
jokerButton.className = "chip";
jokerButton.dataset.grantJoker = v.playerId;
jokerButton.type = "button";
jokerButton.textContent = v.hasJoker ? "🎟" : t("admin.grantJokerChip");
jokerCell.appendChild(jokerButton);
const adminCell = document.createElement("td");
if (v.isOwner) {
const ownerLabel = document.createElement("span");
ownerLabel.className = "muted small";
ownerLabel.textContent = t("admin.owner");
adminCell.appendChild(ownerLabel);
} else {
const adminCheckbox = document.createElement("input");
adminCheckbox.type = "checkbox";
adminCheckbox.dataset.setPlayerAdmin = v.playerId;
adminCheckbox.checked = !!v.isAdmin;
adminCheckbox.setAttribute("aria-label", t("admin.playerAdmin"));
adminCell.appendChild(adminCheckbox);
}
const deleteCell = document.createElement("td");
const deleteButton = document.createElement("button");
deleteButton.className = "chip danger-chip";
deleteButton.dataset.deletePlayer = v.playerId;
deleteButton.dataset.name = v.name ?? "";
deleteButton.type = "button";
deleteButton.textContent = "✕";
deleteCell.appendChild(deleteButton);
tr.append(
nameCell,
usernameCell,
statusCell,
countCell,
jokerCell,
adminCell,
deleteCell,
);
table.appendChild(tr);
});
const waiting = state.adminVoteStatus.waiting;
const ready = waiting.length === 0;
const waitingDisplay = waiting.map((name) =>
name?.length > 24 ? `${name.slice(0, 21)}...` : name,
);
statusBadge.textContent = ready
? t("admin.readyForResults")
: t("admin.waitingForPlayers", { names: waitingDisplay.join(", ") });
statusBadge.className = ready ? "badge" : "badge warning";
}
export function renderAdminLinker() {
const wrap = $("admin-linker");
const source = $("link-source");
const target = $("link-target");
if (!wrap || !source || !target) return;
const visible = state.me?.isAdmin && state.phase === "Vote";
wrap.classList.toggle("hidden", !visible);
if (!visible) return;
const previousSource = source.value;
const previousTarget = target.value;
const options = (state.allSuggestions ?? [])
.slice()
.sort((a, b) => a.name.localeCompare(b.name));
const fillSelect = (select, placeholderKey) => {
select.innerHTML = "";
const placeholder = document.createElement("option");
placeholder.value = "";
placeholder.textContent = t(placeholderKey);
placeholder.disabled = true;
placeholder.selected = true;
select.appendChild(placeholder);
options.forEach((s) => {
const opt = document.createElement("option");
opt.value = s.id;
opt.textContent = buildLinkOptionLabel(s);
select.appendChild(opt);
});
};
fillSelect(source, "admin.linkSourcePlaceholder");
fillSelect(target, "admin.linkTargetPlaceholder");
if (previousSource && options.some((s) => String(s.id) === previousSource))
source.value = previousSource;
if (previousTarget && options.some((s) => String(s.id) === previousTarget))
target.value = previousTarget;
const preventSameSelection = () => {
const sourceVal = source.value;
const targetVal = target.value;
Array.from(target.options).forEach((opt) => {
if (!opt.value) return;
opt.disabled = opt.value === sourceVal;
});
Array.from(source.options).forEach((opt) => {
if (!opt.value) return;
opt.disabled = opt.value === targetVal;
});
};
source.onchange = preventSameSelection;
target.onchange = preventSameSelection;
preventSameSelection();
}