Notify voters when suggestions change during refresh

This commit is contained in:
2026-02-06 23:19:34 +01:00
parent b75e25fb92
commit b2b3996d78
3 changed files with 39 additions and 1 deletions

View File

@@ -1,5 +1,5 @@
import { api, adminApi } from "./api.js"; import { api, adminApi } from "./api.js";
import { handleAuthError, renderAllSuggestions, renderCounts, renderMySuggestions, renderPhasePill, renderPhaseTitles, renderResults, renderVotes, renderWelcome, setAuthUI, syncVoteScores, updatePhaseNav, openResultsRelockModal } from "./ui.js"; import { handleAuthError, renderAllSuggestions, renderCounts, renderMySuggestions, renderPhasePill, renderPhaseTitles, renderResults, renderVotes, renderWelcome, setAuthUI, syncVoteScores, updatePhaseNav, openResultsRelockModal, openSuggestionsChangedModal } from "./ui.js";
import { state, clearUserState } from "./state.js"; import { state, clearUserState } from "./state.js";
export async function loadState() { export async function loadState() {
@@ -29,9 +29,27 @@ export async function loadSuggestData() {
export async function loadRevealData() { export async function loadRevealData() {
if (state.phase === "Vote" || state.phase === "Results") { if (state.phase === "Vote" || state.phase === "Results") {
const prev = state.allSuggestions ?? [];
const prevById = Object.fromEntries(prev.map((s) => [s.id, s]));
const latest = await api.allSuggestions(); const latest = await api.allSuggestions();
const latestSig = signatureSuggestions(latest); const latestSig = signatureSuggestions(latest);
const changed = latestSig !== state.allSuggestionsSig; const changed = latestSig !== state.allSuggestionsSig;
if (changed && state.phase === "Vote" && state.allSuggestionsSig) {
const added = latest
.filter((s) => !prevById[s.id])
.map((s) => s.name);
const relinked = latest
.filter((s) => {
const prevItem = prevById[s.id];
return (
prevItem &&
prevItem.parentSuggestionId !== s.parentSuggestionId
);
})
.map((s) => s.name);
const names = [...added, ...relinked];
openSuggestionsChangedModal(names);
}
state.allSuggestions = latest; state.allSuggestions = latest;
state.allSuggestionsSig = latestSig; state.allSuggestionsSig = latestSig;
renderAllSuggestions(); renderAllSuggestions();

View File

@@ -108,6 +108,9 @@ const translations = {
"results.relockedTitle": "Results closed", "results.relockedTitle": "Results closed",
"results.relockedBody": "Results have been locked again. Youre back in the voting phase and your finalized status was cleared. Adjust scores and re-finalize when ready.", "results.relockedBody": "Results have been locked again. Youre back in the voting phase and your finalized status was cleared. Adjust scores and re-finalize when ready.",
"results.relockedConfirm": "Got it", "results.relockedConfirm": "Got it",
"vote.listUpdatedTitle": "Vote list updated",
"vote.listUpdatedBody": "New or linked games: {names}",
"vote.listUpdatedConfirm": "OK",
"admin.title": "Admin", "admin.title": "Admin",
"admin.tools": "Admin tools", "admin.tools": "Admin tools",
@@ -280,6 +283,9 @@ const translations = {
"results.relockedTitle": "Ergebnisse geschlossen", "results.relockedTitle": "Ergebnisse geschlossen",
"results.relockedBody": "Die Ergebnisse wurden wieder gesperrt. Du bist zurück in der Bewertungsphase und deine Finalisierung wurde zurückgesetzt. Passe deine Bewertungen an und schließe erneut ab, wenn du bereit bist.", "results.relockedBody": "Die Ergebnisse wurden wieder gesperrt. Du bist zurück in der Bewertungsphase und deine Finalisierung wurde zurückgesetzt. Passe deine Bewertungen an und schließe erneut ab, wenn du bereit bist.",
"results.relockedConfirm": "Verstanden", "results.relockedConfirm": "Verstanden",
"vote.listUpdatedTitle": "Liste aktualisiert",
"vote.listUpdatedBody": "Neue oder verknüpfte Spiele: {names}",
"vote.listUpdatedConfirm": "OK",
"admin.title": "Admin", "admin.title": "Admin",
"admin.tools": "Admin-Werkzeuge", "admin.tools": "Admin-Werkzeuge",

View File

@@ -172,6 +172,7 @@ export function renderAllSuggestions() {
export function renderVotes() { export function renderVotes() {
const list = $("vote-list"); const list = $("vote-list");
if (!list) return; if (!list) return;
const prevScroll = list.scrollTop;
list.innerHTML = ""; list.innerHTML = "";
const votesMap = Object.fromEntries( const votesMap = Object.fromEntries(
state.myVotes.map((v) => [v.suggestionId, v.score]), state.myVotes.map((v) => [v.suggestionId, v.score]),
@@ -205,6 +206,7 @@ export function renderVotes() {
}); });
updatePhaseNav(); updatePhaseNav();
updateMissingBadgeFromDom(); updateMissingBadgeFromDom();
list.scrollTop = prevScroll;
list.querySelectorAll("input[type=range]").forEach((input) => { list.querySelectorAll("input[type=range]").forEach((input) => {
input.addEventListener("input", (e) => { input.addEventListener("input", (e) => {
if (state.votesFinal) return; if (state.votesFinal) return;
@@ -734,6 +736,18 @@ export function openResultsRelockModal() {
}); });
} }
export function openSuggestionsChangedModal(names) {
const uniq = Array.from(new Set(names)).filter(Boolean);
if (uniq.length === 0) return;
openConfirmModal({
title: t("vote.listUpdatedTitle"),
body: t("vote.listUpdatedBody", { names: uniq.join(", ") }),
confirmLabel: t("vote.listUpdatedConfirm"),
cancelLabel: null,
onConfirm: (close) => close(),
});
}
export function openConfirmModal({ title, body, confirmLabel, cancelLabel = t("modal.cancel"), onConfirm }) { export function openConfirmModal({ title, body, confirmLabel, cancelLabel = t("modal.cancel"), onConfirm }) {
const overlay = document.createElement("div"); const overlay = document.createElement("div");
overlay.className = "edit-modal"; overlay.className = "edit-modal";