forked from quic-issues/427e7578-d7bf-49c8-aee9-2dd999e25316
feat: add combined codebase
This commit is contained in:
127
src/components/PollList.ts
Normal file
127
src/components/PollList.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
import * as Y from "yjs";
|
||||
import type { OptionRecord } from "../state";
|
||||
import { PollOption } from "./PollOption";
|
||||
|
||||
export function PollList(
|
||||
yOptions: Y.Map<OptionRecord>,
|
||||
yVotes: Y.Map<string>,
|
||||
userId: string,
|
||||
isVotingClosed: () => boolean,
|
||||
onVote: (optionId: string) => void,
|
||||
onDelete: (optionId: string) => void,
|
||||
): HTMLElement {
|
||||
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;
|
||||
}> = [];
|
||||
|
||||
// Tally votes per option
|
||||
const tally = new Map<string, number>();
|
||||
for (const optionId of yVotes.values()) {
|
||||
tally.set(optionId, (tally.get(optionId) ?? 0) + 1);
|
||||
}
|
||||
|
||||
const myVote = yVotes.get(userId) ?? null;
|
||||
|
||||
yOptions.forEach((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,
|
||||
onDelete,
|
||||
});
|
||||
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.observeDeep(() => render());
|
||||
yVotes.observe(() => render());
|
||||
render();
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
Reference in New Issue
Block a user