import * as Y from "yjs"; import type { OptionRecord } from "../state"; import { PollOption } from "./PollOption"; import { enforceAppendOnly } from "../yDocUtil"; export function PollList( yOptions: Y.Map, yVotes: Y.Map, userId: string, isVotingClosed: () => boolean, onVote: (optionId: string) => void, ): HTMLElement { var currentOptions : { [x: string]: any; } | undefined = undefined var currentVotes : { [x: string]: any; } | undefined = undefined const wrapper = document.createElement("div"); wrapper.className = "poll-list-wrapper"; const meta = document.createElement("div"); meta.className = "poll-list-meta"; const list = document.createElement("div"); list.className = "poll-list"; const empty = document.createElement("div"); empty.className = "poll-list-empty"; empty.innerHTML = `

No options yet — add the first one above.

`; wrapper.append(meta, list, empty); function getEntries() { const entries: Array<{ id: string; name: string; votes: number; voted: boolean; }> = []; if (currentOptions && currentVotes){ // Tally votes per option const tally = new Map(); for (const optionId of Object.values(currentVotes)) { tally.set(optionId, (tally.get(optionId) ?? 0) + 1); } const myVote = currentVotes[userId] ?? null; Object.entries(currentOptions).forEach(([id,record]) => { console.log(`${record}: ${id}`) entries.push({ id, name: record.label, votes: tally.get(id) ?? 0, voted: myVote === id, }); }); entries.sort((a, b) => b.votes - a.votes || a.name.localeCompare(b.name)); } return entries; } function getTotalVotes(): number { return yVotes.size; } function render() { const entries = getEntries(); const total = getTotalVotes(); const votingClosed = isVotingClosed(); // Meta line if (entries.length > 0) { meta.textContent = `${entries.length} option${entries.length !== 1 ? "s" : ""} \u00b7 ${total} vote${total !== 1 ? "s" : ""} total`; meta.style.display = ""; } else { meta.style.display = "none"; } // Empty state empty.style.display = entries.length === 0 ? "" : "none"; // Diff-render: reuse existing rows when possible const existing = new Map( [...list.querySelectorAll(".poll-option")].map((el) => [ el.dataset.id, el, ]), ); // Remove stale rows existing.forEach((el, id) => { if (!entries.find((e) => e.id === id)) el.remove(); }); // Update or insert rows in sorted order entries.forEach((entry, i) => { const newEl = PollOption({ ...entry, totalVotes: total, votingClosed, onVote, }); const currentEl = list.children[i] as HTMLElement | undefined; if (!currentEl) { list.appendChild(newEl); } else if (currentEl.dataset.id !== entry.id) { list.insertBefore(newEl, currentEl); const old = existing.get(entry.id); if (old && old !== currentEl) old.remove(); } else { list.replaceChild(newEl, currentEl); } }); } yOptions.observe(enforceAppendOnly(yOptions,(update : { [x: string]: any; }) => {currentOptions = update}, render)); yVotes.observe(enforceAppendOnly(yVotes,(update : { [x: string]: any; }) => {currentVotes = update},render)); currentOptions=yOptions.toJSON() currentVotes=yVotes.toJSON() render(); return wrapper; }