forked from quic-issues/427e7578-d7bf-49c8-aee9-2dd999e25316
136 lines
4.0 KiB
TypeScript
136 lines
4.0 KiB
TypeScript
import * as Y from "yjs";
|
|
import type { OptionRecord } from "../state";
|
|
import { PollOption } from "./PollOption";
|
|
import { enforceAppendOnly } from "../yDocUtil";
|
|
|
|
export function PollList(
|
|
yOptions: Y.Map<OptionRecord>,
|
|
yVotes: Y.Map<string>,
|
|
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 = `
|
|
<div class="empty-icon">
|
|
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<rect x="4" y="10" width="24" height="3" rx="1.5" fill="currentColor" opacity="0.15"/>
|
|
<rect x="4" y="16" width="18" height="3" rx="1.5" fill="currentColor" opacity="0.1"/>
|
|
<rect x="4" y="22" width="21" height="3" rx="1.5" fill="currentColor" opacity="0.07"/>
|
|
</svg>
|
|
</div>
|
|
<p>No options yet — add the first one above.</p>
|
|
`;
|
|
|
|
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<string, number>();
|
|
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<HTMLElement>(".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;
|
|
}
|