Split frontend UI into feature modules
This commit is contained in:
103
wwwroot/js/admin-ui.js
Normal file
103
wwwroot/js/admin-ui.js
Normal file
@@ -0,0 +1,103 @@
|
||||
import { t } from "./i18n.js";
|
||||
import { state } from "./state.js";
|
||||
import { $ } from "./dom.js";
|
||||
import { buildLinkOptionLabel, escapeHtml, 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;
|
||||
}
|
||||
|
||||
export function renderAdminVoteStatus() {
|
||||
if (!state.me?.isAdmin) 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 statusText = displayPlayerStatus(v);
|
||||
const gamesTooltip = escapeHtml((v.suggestionTitles || []).join(", "));
|
||||
const nameText = escapeHtml(truncate(v.name, 28));
|
||||
const userText = escapeHtml(truncate(v.username, 24));
|
||||
tr.innerHTML = `
|
||||
<td title="${escapeHtml(v.name)}">${nameText}</td>
|
||||
<td class="muted small" title="${escapeHtml(v.username)}">${userText}</td>
|
||||
<td>${statusText}</td>
|
||||
<td title="${gamesTooltip}">${v.suggestionCount ?? 0}</td>
|
||||
<td><button class="chip" data-grant-joker="${v.playerId}" type="button">${v.hasJoker ? "🎟" : t("admin.grantJokerChip")}</button></td>
|
||||
<td><button class="chip danger-chip" data-delete-player="${v.playerId}" data-name="${v.name}" type="button">✕</button></td>
|
||||
`;
|
||||
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();
|
||||
}
|
||||
Reference in New Issue
Block a user