diff --git a/src/app.ts b/src/app.ts index bf00075..b6ad391 100644 --- a/src/app.ts +++ b/src/app.ts @@ -2,10 +2,9 @@ import { getUserId } from "./identity"; import { addOption, toggleVote, - deleteOption, setDeadline, clearDeadline, - createViewModel, + getDeadline, } from "./state"; import { initSync } from "./sync"; import { StatusBar } from "./components/StatusBar"; @@ -48,18 +47,13 @@ export function initApp(container: HTMLElement): () => void { const actions = { addOption: (label: string) => { - const vm = createViewModel(getViewModelParams()); - if (vm.votingClosed) return; + if (isVotingClosed()) return; return addOption(sync.options, label, userId); }, toggleVote: (optionId: string) => { - const vm = createViewModel(getViewModelParams()); - if (vm.votingClosed) return; + if (isVotingClosed()) return; toggleVote(sync.votes, userId, optionId); }, - deleteOption: (optionId: string) => { - deleteOption(sync.options, sync.votes, optionId); - }, startDeadline: (durationMs: number) => { setDeadline(sync.deadlineMap, durationMs); }, @@ -68,18 +62,10 @@ export function initApp(container: HTMLElement): () => void { }, }; - function getViewModelParams() { - return { - yTitle: sync.yTitle, - options: sync.options, - votes: sync.votes, - deadlineMap: sync.deadlineMap, - roomId, - shareUrl, - connectionStatus: sync.getConnectionStatus(), - peerCount: sync.getPeerCount(), - userId, - }; + function isVotingClosed() { + const deadline = getDeadline(sync.deadlineMap); + const votingClosed = deadline !== null && Date.now() >= deadline; + return votingClosed; } // --- Build UI --- @@ -112,10 +98,7 @@ export function initApp(container: HTMLElement): () => void { if (result && !result.ok) return result.error; return null; }); - const pollList = PollList(sync.options, sync.votes, userId, () => { - const vm = createViewModel(getViewModelParams()); - return vm.votingClosed; - }, actions.toggleVote, actions.deleteOption); + const pollList = PollList(sync.options, sync.votes, userId, isVotingClosed, actions.toggleVote); const deadlineTimer = DeadlineTimer( sync.deadlineMap, actions.startDeadline, diff --git a/src/components/PollList.ts b/src/components/PollList.ts index 5c99505..4828495 100644 --- a/src/components/PollList.ts +++ b/src/components/PollList.ts @@ -9,7 +9,6 @@ export function PollList( userId: string, isVotingClosed: () => boolean, onVote: (optionId: string) => void, - onDelete: (optionId: string) => void, ): HTMLElement { var currentOptions : { [x: string]: any; } | undefined = undefined @@ -112,7 +111,6 @@ export function PollList( totalVotes: total, votingClosed, onVote, - onDelete, }); const currentEl = list.children[i] as HTMLElement | undefined; diff --git a/src/components/PollOption.ts b/src/components/PollOption.ts index c10ef22..70eff3b 100644 --- a/src/components/PollOption.ts +++ b/src/components/PollOption.ts @@ -8,11 +8,10 @@ export interface PollOptionProps { totalVotes: number; votingClosed: boolean; onVote: (id: string) => void; - onDelete: (id: string) => void; } export function PollOption(props: PollOptionProps): HTMLElement { - const { id, name, votes, voted, totalVotes, votingClosed, onVote, onDelete } = props; + const { id, name, votes, voted, totalVotes, votingClosed, onVote } = props; const row = document.createElement("div"); row.className = `poll-option${voted ? " poll-option--voted" : ""}`; @@ -30,17 +29,11 @@ export function PollOption(props: PollOptionProps): HTMLElement { - `; row.querySelector(".poll-option__vote-btn")!.addEventListener("click", () => onVote(id)); - row.querySelector(".poll-option__delete-btn")!.addEventListener("click", () => onDelete(id)); return row; } diff --git a/src/state.ts b/src/state.ts index 65fd560..0d08971 100644 --- a/src/state.ts +++ b/src/state.ts @@ -11,25 +11,6 @@ export interface OptionRecord { createdBy: string; } -export interface PollOptionViewModel extends OptionRecord { - voteCount: number; - isVotedByMe: boolean; - percentage: number; -} - -export interface PollViewModel { - title: string; - roomId: string; - shareUrl: string; - connectionStatus: ConnectionStatus; - peerCount: number; - options: PollOptionViewModel[]; - totalVotes: number; - myVoteOptionId: string | null; - deadline: number | null; - votingClosed: boolean; -} - // --- Helpers --- export function createOptionId(): string { @@ -104,20 +85,6 @@ export function toggleVote( } } -export function deleteOption( - options: Y.Map, - votes: Y.Map, - optionId: string, -): void { - options.delete(optionId); - // Clean up votes pointing to this option - for (const [userId, votedOptionId] of votes.entries()) { - if (votedOptionId === optionId) { - votes.delete(userId); - } - } -} - // --- Deadline --- export function setDeadline( @@ -135,73 +102,3 @@ export function getDeadline(deadlineMap: Y.Map): number | null { const val = deadlineMap.get("deadline"); return typeof val === "number" ? val : null; } - -// --- ViewModel --- - -export function createViewModel(params: { - yTitle: Y.Text; - options: Y.Map; - votes: Y.Map; - deadlineMap: Y.Map; - roomId: string; - shareUrl: string; - connectionStatus: ConnectionStatus; - peerCount: number; - userId: string; -}): PollViewModel { - const { - yTitle, - options, - votes, - deadlineMap, - roomId, - shareUrl, - connectionStatus, - peerCount, - userId, - } = params; - - // Tally votes per option - const tally = new Map(); - for (const optionId of votes.values()) { - tally.set(optionId, (tally.get(optionId) ?? 0) + 1); - } - - let totalVotes = 0; - for (const count of tally.values()) { - totalVotes += count; - } - - const myVoteOptionId = votes.get(userId) ?? null; - const deadline = getDeadline(deadlineMap); - const votingClosed = deadline !== null && Date.now() >= deadline; - - // Sort by votes desc, then alphabetically - const sortedOptions = Array.from(options.values()).sort((a, b) => { - const aVotes = tally.get(a.id) ?? 0; - const bVotes = tally.get(b.id) ?? 0; - if (bVotes !== aVotes) return bVotes - aVotes; - return a.label.localeCompare(b.label); - }); - - return { - title: getPollTitle(yTitle), - roomId, - shareUrl, - connectionStatus, - peerCount, - myVoteOptionId, - totalVotes, - deadline, - votingClosed, - options: sortedOptions.map((option) => ({ - ...option, - voteCount: tally.get(option.id) ?? 0, - isVotedByMe: myVoteOptionId === option.id, - percentage: - totalVotes > 0 - ? Math.round(((tally.get(option.id) ?? 0) / totalVotes) * 100) - : 0, - })), - }; -}