@hadoku/watchparty-ui 0.45.17 → 0.45.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunks/{AdminPanel-DQAQsKh3.js → AdminPanel-D6pPHN-s.js} +4 -4
- package/dist/chunks/{AdminPanel-DQAQsKh3.js.map → AdminPanel-D6pPHN-s.js.map} +1 -1
- package/dist/chunks/{MediaDrawer-CM-SiHns.js → MediaDrawer-DE91ldYF.js} +4 -4
- package/dist/chunks/MediaDrawer-DE91ldYF.js.map +1 -0
- package/dist/chunks/{PlaylistPanel-DgRm9nsi.js → PlaylistPanel-e34BfPg1.js} +3 -3
- package/dist/chunks/{PlaylistPanel-DgRm9nsi.js.map → PlaylistPanel-e34BfPg1.js.map} +1 -1
- package/dist/chunks/{Room-DgsMxWGJ.js → Room-DNZSWr2-.js} +7 -7
- package/dist/chunks/Room-DNZSWr2-.js.map +1 -0
- package/dist/chunks/{index-Ip1nTIQR.js → index-CdBFUWZq.js} +2 -2
- package/dist/chunks/index-CdBFUWZq.js.map +1 -0
- package/dist/chunks/{submissionsApi-DZ3dGiaA.js → submissionsApi-CKxyNd3q.js} +2 -2
- package/dist/chunks/{submissionsApi-DZ3dGiaA.js.map → submissionsApi-CKxyNd3q.js.map} +1 -1
- package/dist/index.js +1 -1
- package/dist/room-chrome.js.map +1 -1
- package/package.json +3 -2
- package/dist/chunks/MediaDrawer-CM-SiHns.js.map +0 -1
- package/dist/chunks/Room-DgsMxWGJ.js.map +0 -1
- package/dist/chunks/index-Ip1nTIQR.js.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as e, jsxs as d } from "react/jsx-runtime";
|
|
2
2
|
import L, { useState as N, useEffect as T, useEffectEvent as A } from "react";
|
|
3
|
-
import { u as h, az as D, aA as B, b as U, A as W, aB as V, aC as E, aD as z, d as R, aE as O, ai as F, aF as G, N as K, aG as H, aH as $, ax as q } from "./index-
|
|
4
|
-
import { d as P, S as _ } from "./Room-
|
|
3
|
+
import { u as h, az as D, aA as B, b as U, A as W, aB as V, aC as E, aD as z, d as R, aE as O, ai as F, aF as G, N as K, aG as H, aH as $, ax as q } from "./index-CdBFUWZq.js";
|
|
4
|
+
import { d as P, S as _ } from "./Room-DNZSWr2-.js";
|
|
5
5
|
const Y = 3e3, J = {
|
|
6
6
|
super_upvote: 4,
|
|
7
7
|
upvote: 3,
|
|
@@ -325,4 +325,4 @@ function de() {
|
|
|
325
325
|
export {
|
|
326
326
|
de as default
|
|
327
327
|
};
|
|
328
|
-
//# sourceMappingURL=PlaylistPanel-
|
|
328
|
+
//# sourceMappingURL=PlaylistPanel-e34BfPg1.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlaylistPanel-DgRm9nsi.js","sources":["../../src/components/PlaylistRevealPopup.tsx","../../src/components/PlaylistPanel.tsx"],"sourcesContent":["import { useEffect, useState, type ReactNode } from 'react';\nimport type { TPlaylistResultsOut, TVoteValue } from '@watchparty/shared';\nimport { useAppStore } from '../lib/store';\nimport { isCharacterId } from '../lib/characters';\nimport { usePersonColor } from '../lib/pictureColor';\nimport AvatarImage from './AvatarImage';\nimport { StarIcon } from './controls/Icons';\nimport { useIsLeader } from '../hooks/useStoreSelectors';\nimport { emitVoteRemove } from '../lib/socket';\n\n/** How long the ✕ stays armed after the first click before it disarms. */\nconst REMOVE_ARM_MS = 3000;\n\ninterface Props {\n results: TPlaylistResultsOut;\n onClose: () => void;\n}\n\nconst VOTE_WEIGHTS: Record<TVoteValue, number> = {\n super_upvote: 4,\n upvote: 3,\n mid: 2,\n downvote: 1,\n};\n\nconst VOTE_ICONS: Record<TVoteValue, { icon: ReactNode; color: string }> = {\n // Bigger than the other marks on purpose: a super-upvote is the strongest\n // vote and was the hardest to see (a 10px star under the avatar).\n super_upvote: { icon: <StarIcon className=\"w-4 h-4\" />, color: 'bg-warning-bg text-on-warning-bg rounded' },\n upvote: { icon: '▲', color: 'bg-success-bg text-on-success-bg rounded px-0.5' },\n mid: { icon: '─', color: 'text-text-secondary' },\n downvote: { icon: '▼', color: 'bg-danger-bg text-on-danger-bg rounded px-0.5' },\n};\n\n/** One voter's face in the results: their picture, else their character. */\nfunction VoterFace({ sprite, url, alt, size, ring }: {\n sprite: string | undefined;\n url: string | undefined;\n alt: string;\n size: 'w-4 h-4' | 'w-6 h-6';\n ring: boolean;\n}) {\n const color = usePersonColor(sprite, url);\n const style = ring ? { border: `1.5px solid ${color}` } : undefined;\n return (\n <AvatarImage\n sprite={sprite}\n url={url}\n alt={alt}\n className={`${size} rounded-full object-cover`}\n style={style}\n fallback={<span className={`${size} rounded-full bg-bg-alt block`} style={style} />}\n />\n );\n}\n\nfunction computeScores(results: TPlaylistResultsOut) {\n const { playlist, votes } = results;\n return playlist.videos.map(video => {\n let totalScore = 0;\n let voterCount = 0;\n for (const clientVotes of Object.values(votes)) {\n const v = clientVotes[video.mediaId] as TVoteValue | undefined;\n if (v) { totalScore += VOTE_WEIGHTS[v]; voterCount++; }\n }\n const avgScore = voterCount > 0 ? totalScore / voterCount : 0;\n return { video, avgScore, voterCount };\n }).sort((a, b) => b.avgScore - a.avgScore);\n}\n\nexport default function PlaylistRevealPopup({ results, onClose }: Props) {\n const roster = useAppStore(s => s.roster);\n const isLeader = useIsLeader();\n const scored = computeScores(results);\n // Two clicks to remove: the first arms the ✕, the second sends. A stray\n // click mid-party must not silently drop someone's ballot.\n const [armed, setArmed] = useState<string | null>(null);\n useEffect(() => {\n if (!armed) return;\n const t = setTimeout(() => setArmed(null), REMOVE_ARM_MS);\n return () => clearTimeout(t);\n }, [armed]);\n\n // Ordered list of clients who voted\n const voters = Object.keys(results.votes).filter(\n id => Object.keys(results.votes[id]).length > 0\n );\n // Frozen at reveal by the server, so someone who has since LEFT (and is\n // gone from the live roster) still has an avatar.\n const spriteOf = (id: string) => {\n const sprite = roster[id]?.sprite ?? results.sprites?.[id];\n return isCharacterId(sprite) ? sprite : undefined;\n };\n const pictureOf = (id: string) => roster[id]?.avatarUrl ?? results.pictures?.[id];\n\n function onRemove(voterKey: string) {\n if (armed !== voterKey) { setArmed(voterKey); return; }\n setArmed(null);\n emitVoteRemove(voterKey);\n }\n\n return (\n <div\n className=\"fixed inset-0 bg-black/70 flex items-center justify-center z-50\"\n onClick={e => { if (e.target === e.currentTarget) onClose(); }}\n >\n <div data-testid=\"reveal-popup\" className=\"bg-bg-card border border-border rounded-lg shadow-xl w-full max-w-2xl max-h-[80vh] flex flex-col mx-4\">\n {/* Header */}\n <div className=\"flex items-center justify-between px-4 py-3 border-b border-border shrink-0\">\n <h2 className=\"text-sm font-semibold text-text\">\n {results.playlist.name} — Results\n </h2>\n <button\n onClick={onClose}\n aria-label=\"Close results\"\n className=\"p-1 text-text-muted hover:text-text transition-colors cursor-pointer\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n\n {/* Voters — the leader can drop one; the table regenerates for everyone */}\n {voters.length > 0 && (\n <div className=\"flex flex-wrap items-center gap-1.5 px-4 py-2 border-b border-border shrink-0\">\n {voters.map(id => {\n return (\n <span key={id} className=\"reveal-voter\">\n <VoterFace sprite={spriteOf(id)} url={pictureOf(id)} alt=\"\" size=\"w-4 h-4\" ring={false} />\n <span className=\"text-xs text-text truncate max-w-[8rem]\">{results.names[id] || id}</span>\n {isLeader && (\n <button\n type=\"button\"\n className=\"reveal-voter-remove\"\n data-armed={armed === id ? '' : undefined}\n onClick={() => onRemove(id)}\n aria-label={`Remove ${results.names[id] || id}'s votes`}\n title={armed === id ? 'Click again to remove' : `Remove ${results.names[id] || id}'s votes`}\n >\n {armed === id ? 'remove?' : '✕'}\n </button>\n )}\n </span>\n );\n })}\n </div>\n )}\n\n {/* Results list */}\n <div className=\"overflow-y-auto flex-1 py-1\">\n {scored.map(({ video, avgScore, voterCount }, rank) => (\n <div\n key={video.mediaId}\n className={`flex items-center gap-3 px-4 py-2 ${rank % 2 === 0 ? '' : 'bg-bg-alt/20'}`}\n >\n {/* Rank */}\n <span className=\"text-xs text-text-muted w-5 shrink-0 text-right\">{rank + 1}</span>\n\n {/* Video name */}\n <span className=\"text-xs text-text truncate flex-1 min-w-0\">\n {video.displayName}\n </span>\n\n {/* Per-voter avatars + vote icon */}\n <div className=\"flex items-center gap-1.5 shrink-0\">\n {voters.map(clientId => {\n const vote = results.votes[clientId]?.[video.mediaId] as TVoteValue | undefined;\n if (!vote) return null;\n const voteStyle = VOTE_ICONS[vote];\n\n return (\n <div key={clientId} className=\"flex flex-col items-center gap-0.5\">\n {/* Avatar */}\n <div className=\"relative w-6 h-6 shrink-0\">\n <VoterFace\n sprite={spriteOf(clientId)}\n url={pictureOf(clientId)}\n alt={results.names[clientId] || clientId}\n size=\"w-6 h-6\"\n ring\n />\n </div>\n {/* Vote icon */}\n <span className={`text-[9px] leading-none font-bold ${voteStyle.color}`}>\n {voteStyle.icon}\n </span>\n </div>\n );\n })}\n {/* Show dashes for non-voters */}\n {voters.map(clientId => {\n const voted = !!results.votes[clientId]?.[video.mediaId];\n if (voted) return null;\n return (\n <div key={`no-${clientId}`} className=\"flex flex-col items-center gap-0.5 opacity-20\">\n <div className=\"w-6 h-6 rounded-full bg-bg-alt border border-border\" />\n <span className=\"text-[9px] leading-none text-text-muted\">·</span>\n </div>\n );\n })}\n </div>\n\n {/* Avg score */}\n <span className=\"text-xs text-text-secondary w-8 text-right shrink-0 font-medium\">\n {voterCount > 0 ? avgScore.toFixed(1) : '—'}\n </span>\n </div>\n ))}\n </div>\n </div>\n </div>\n );\n}\n","import React, { useEffectEvent, useState } from 'react';\nimport { useAppStore } from '../lib/store';\nimport { useIsLeader } from '../hooks/useStoreSelectors';\nimport { emitPlaylistLoad, emitPlaylistSelect, emitVoteUpdate, emitRevealRequest } from '../lib/socket';\nimport { resolveMediaAsset, resolveCardImage } from '../lib/mediaUrls';\nimport { debug } from '../lib/debug';\nimport { STORAGE_KEYS } from '../lib/storageKeys';\nimport { voteOwnerScope } from '../lib/auth';\nimport type { TVoteValue } from '@watchparty/shared';\nimport PlaylistRevealPopup from './PlaylistRevealPopup';\nimport { StarIcon } from './controls/Icons';\nimport { readStored, writeStored } from '../lib/browserStorage';\n\n// ── Vote icons ────────────────────────────────────────────────────────────────\n\n\nfunction UpvoteIcon() {\n return (\n <svg className=\"w-3.5 h-3.5\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth={2.5}>\n <polyline points=\"18 15 12 9 6 15\" />\n </svg>\n );\n}\n\nfunction MidIcon() {\n return (\n <svg className=\"w-3.5 h-3.5\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth={2.5}>\n <line x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\" />\n </svg>\n );\n}\n\nfunction DownvoteIcon() {\n return (\n <svg className=\"w-3.5 h-3.5\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth={2.5}>\n <polyline points=\"6 9 12 15 18 9\" />\n </svg>\n );\n}\n\n// Colours live in `.vote-btn` (styles.css), keyed by `data-vote`: a <button>'s\n// colour/background utilities are discarded by the unlayered `button` reset, so\n// the per-vote `text-*` classes these carried never rendered — every vote icon\n// was the same white, selected or not.\nconst VOTE_OPTIONS: { value: TVoteValue; icon: React.ReactNode; label: string }[] = [\n { value: 'super_upvote', icon: <StarIcon className=\"w-4 h-4\" />, label: 'Super' },\n { value: 'upvote', icon: <UpvoteIcon />, label: 'Up' },\n { value: 'mid', icon: <MidIcon />, label: 'Mid' },\n { value: 'downvote', icon: <DownvoteIcon />, label: 'Down' },\n];\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\nfunction formatDuration(ms: number): string {\n const total = Math.round(ms / 1000);\n const h = Math.floor(total / 3600);\n const m = Math.floor((total % 3600) / 60);\n const s = total % 60;\n return h > 0\n ? `${h}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`\n : `${m}:${String(s).padStart(2, '0')}`;\n}\n\n// ── Load form (leader, no playlist loaded) ────────────────────────────────────\n\nfunction LoadForm() {\n const [input, setInput] = useState('');\n const [loading, setLoading] = useState(false);\n const [err, setErr] = useState<string | null>(null);\n\n const handleLoad = () => {\n const id = input.trim();\n if (!id) return;\n setLoading(true);\n setErr(null);\n emitPlaylistLoad(id);\n // Error will arrive via the 'error' socket event and show as a toast.\n // Reset loading state after a short delay.\n setTimeout(() => setLoading(false), 1500);\n };\n\n return (\n <div className=\"p-4 flex flex-col gap-3\">\n <p className=\"text-xs text-text-muted\">Enter a playlist ID to load it for the room.</p>\n <div className=\"flex gap-2\">\n <input\n type=\"text\"\n value={input}\n onChange={e => setInput(e.target.value)}\n onKeyDown={e => e.key === 'Enter' && handleLoad()}\n placeholder=\"Playlist ID (e.g. april-4)\"\n className=\"modal-input flex-1 text-xs px-2 py-1.5\"\n disabled={loading}\n />\n <button\n onClick={handleLoad}\n disabled={loading || !input.trim()}\n className=\"modal-btn-update text-xs px-3 py-1.5 rounded disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n Load\n </button>\n </div>\n {err && <p className=\"text-xs bg-danger-bg text-on-danger-bg px-2 py-1 rounded\">{err}</p>}\n </div>\n );\n}\n\n// ── Empty state (viewer, no playlist) ─────────────────────────────────────────\n\nfunction EmptyState() {\n return (\n <div className=\"flex-1 flex items-center justify-center p-4\">\n <p className=\"text-xs text-text-muted text-center\">No playlist loaded yet.</p>\n </div>\n );\n}\n\n// ── Main panel ────────────────────────────────────────────────────────────────\n\nexport default function PlaylistPanel() {\n const isLeader = useIsLeader();\n const playlist = useAppStore(s => s.playlist);\n const activeIndex = useAppStore(s => s.activePlaylistIndex);\n const myVotes = useAppStore(s => s.myVotes);\n const voteProgress = useAppStore(s => s.voteProgress);\n const results = useAppStore(s => s.playlistResults);\n const currentMediaId = useAppStore(s => s.snapshot.mediaId);\n\n // Show results popup when a reveal arrives. NOT on an `update` (a voter was\n // removed): that re-sends the table to everyone, and reopening it would put\n // the popup back in front of people who had already closed it.\n const [showResults, setShowResults] = useState(() => !!results && !results.update);\n const [seenResults, setSeenResults] = useState(results);\n if (results !== seenResults) {\n setSeenResults(results);\n if (results && !results.update) setShowResults(true);\n }\n\n // Restore votes from localStorage when a playlist is first loaded. The body\n // reads the current playlist, index and votes; only a new playlist id runs it.\n const restoreVotes = useEffectEvent(() => {\n if (!playlist || playlist.type !== 'vote') return;\n const savedVotes: Record<string, TVoteValue> = {};\n for (const video of playlist.videos) {\n const key = STORAGE_KEYS.voteKey(voteOwnerScope(), playlist.id, video.mediaId);\n const saved = readStored(key) as TVoteValue | null;\n if (saved && VOTE_OPTIONS.some(o => o.value === saved)) {\n savedVotes[video.mediaId] = saved;\n }\n }\n debug('playlist', 'Checked localStorage votes', { playlistId: playlist.id, found: Object.keys(savedVotes).length });\n if (Object.keys(savedVotes).length === 0) return;\n useAppStore.getState().applyPlaylistState({ playlist, activeIndex, myVotes: { ...myVotes, ...savedVotes } });\n for (const [mediaId, vote] of Object.entries(savedVotes)) {\n emitVoteUpdate(playlist.id, mediaId, vote);\n }\n });\n const playlistId = playlist?.id;\n React.useEffect(() => { restoreVotes(); }, [playlistId]);\n\n if (!playlist) {\n return isLeader ? <LoadForm /> : <EmptyState />;\n }\n\n // `basic` playlists are a plain watch-through — no vote buttons, progress,\n // or reveal. Only `vote` playlists show the rating mechanic.\n const isVoting = playlist.type === 'vote';\n const totalUsers = voteProgress.length;\n\n const handleVote = (mediaId: string, vote: TVoteValue) => {\n emitVoteUpdate(playlist.id, mediaId, vote);\n debug('playlist', 'Vote emitted', { playlistId: playlist.id, mediaId, vote });\n writeStored(STORAGE_KEYS.voteKey(voteOwnerScope(), playlist.id, mediaId), vote);\n\n // Mirror the server's super_upvote exclusivity rule: if this vote is a star,\n // downgrade any other video's star to upvote so the visual ring updates immediately.\n const updatedVotes = { ...myVotes, [mediaId]: vote };\n if (vote === 'super_upvote') {\n for (const [otherId, otherVote] of Object.entries(updatedVotes)) {\n if (otherId !== mediaId && otherVote === 'super_upvote') {\n updatedVotes[otherId] = 'upvote';\n writeStored(STORAGE_KEYS.voteKey(voteOwnerScope(), playlist.id, otherId), 'upvote');\n }\n }\n }\n\n useAppStore.getState().applyPlaylistState({\n playlist,\n activeIndex,\n myVotes: updatedVotes,\n });\n };\n\n return (\n <div className=\"flex flex-col flex-1 min-h-0\">\n {/* Header */}\n <div className=\"flex items-center gap-2 px-3 py-2 border-b border-border shrink-0\">\n {playlist.cover && (\n <img\n src={resolveMediaAsset(playlist.cover)}\n alt=\"\"\n className=\"w-8 h-8 object-cover rounded shrink-0 bg-bg-alt\"\n />\n )}\n <span className=\"text-xs font-medium text-text truncate flex-1\">\n {playlist.name}\n </span>\n {isLeader && isVoting && (\n <button\n onClick={() => emitRevealRequest()}\n className=\"text-xs text-text-muted hover:text-text transition-colors cursor-pointer shrink-0\"\n title=\"Reveal votes to all\"\n >\n Reveal\n </button>\n )}\n </div>\n\n {/* Vote progress bar (leader only, vote playlists only) */}\n {isLeader && isVoting && totalUsers > 0 && (\n <div className=\"px-3 py-1.5 border-b border-border shrink-0\">\n <div className=\"flex items-center gap-1.5\">\n <span className=\"text-[10px] text-text-muted shrink-0\">Votes:</span>\n <div className=\"flex gap-1 flex-wrap flex-1\">\n {voteProgress.map(p => (\n <span key={p.clientId} className=\"text-[10px] text-text-secondary\">\n {p.name} {p.votedCount}/{p.totalVideos}\n </span>\n ))}\n </div>\n </div>\n </div>\n )}\n\n {/* Video list */}\n <div className=\"flex-1 overflow-y-auto\">\n {playlist.videos.map((video, i) => {\n const isActive = activeIndex === i;\n const myVote = myVotes[video.mediaId] as TVoteValue | undefined;\n const isCurrentMedia = currentMediaId === video.mediaId;\n\n return (\n <div\n key={video.mediaId}\n className={`flex items-center gap-2 px-3 py-2 border-b border-border/50 group ${\n isActive ? 'bg-primary/10' : 'hover:bg-bg-alt/50'\n }`}\n >\n {/* Play indicator */}\n <div className=\"w-3.5 shrink-0 text-text-muted\">\n {isCurrentMedia ? (\n <svg className=\"w-3.5 h-3.5 text-text\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <polygon points=\"5 3 19 12 5 21 5 3\" />\n </svg>\n ) : (\n <span className=\"text-[10px] text-text-muted\">{i + 1}</span>\n )}\n </div>\n\n {/* Thumbnail. No longer flat-playlist-only: `poster.jpg` is\n derived by convention when a clip carries no explicit\n thumbnail (see resolveCardImage), and a 404 hides the image. */}\n <div className=\"relative shrink-0\">\n <img\n src={resolveCardImage(video.mediaId, video.thumbnail)}\n alt=\"\"\n loading=\"lazy\"\n onError={e => {\n const el = e.currentTarget as HTMLImageElement;\n // Hide the whole slot, not just the img — the duration\n // badge is positioned against it.\n (el.parentElement ?? el).style.display = 'none';\n }}\n className=\"w-14 h-10 object-cover rounded bg-bg-alt\"\n />\n {video.durationMs ? (\n <span className=\"absolute bottom-0 right-0 px-1 text-[9px] leading-tight rounded-tl rounded-br bg-bg-card/90 text-text-secondary\">\n {formatDuration(video.durationMs)}\n </span>\n ) : null}\n </div>\n\n {/* Video name — leader can click to select */}\n <button\n className={`flex-1 text-left text-xs truncate cursor-pointer transition-colors min-w-0 ${\n isLeader\n ? 'hover:text-text text-text-secondary'\n : 'text-text-secondary cursor-default'\n } ${isActive ? 'text-text font-medium' : ''}`}\n onClick={isLeader ? () => emitPlaylistSelect(i) : undefined}\n title={isLeader ? `Play: ${video.displayName}` : video.displayName}\n disabled={!isLeader}\n >\n {video.displayName}\n </button>\n\n {/* Vote buttons (vote playlists only) */}\n {isVoting && (\n <div className=\"flex gap-0.5 shrink-0\">\n {VOTE_OPTIONS.map(opt => {\n const isSelected = myVote === opt.value;\n return (\n <button\n key={opt.value}\n onClick={() => handleVote(video.mediaId, opt.value)}\n title={opt.label}\n className=\"vote-btn\"\n data-vote={opt.value}\n data-selected={isSelected ? '' : undefined}\n aria-pressed={isSelected}\n >\n {opt.icon}\n </button>\n );\n })}\n </div>\n )}\n </div>\n );\n })}\n </div>\n\n {/* Results popup (vote playlists only) */}\n {isVoting && showResults && results && (\n <PlaylistRevealPopup\n results={results}\n onClose={() => setShowResults(false)}\n />\n )}\n </div>\n );\n}\n"],"names":["REMOVE_ARM_MS","VOTE_WEIGHTS","VOTE_ICONS","jsx","StarIcon","VoterFace","sprite","url","alt","size","ring","color","usePersonColor","style","AvatarImage","computeScores","results","playlist","votes","video","totalScore","voterCount","clientVotes","v","avgScore","a","b","PlaylistRevealPopup","onClose","roster","useAppStore","isLeader","useIsLeader","scored","armed","setArmed","useState","useEffect","t","voters","id","spriteOf","isCharacterId","pictureOf","onRemove","voterKey","emitVoteRemove","e","jsxs","rank","clientId","vote","voteStyle","UpvoteIcon","MidIcon","DownvoteIcon","VOTE_OPTIONS","formatDuration","ms","total","h","m","s","LoadForm","input","setInput","loading","setLoading","err","setErr","handleLoad","emitPlaylistLoad","EmptyState","PlaylistPanel","activeIndex","myVotes","voteProgress","currentMediaId","showResults","setShowResults","seenResults","setSeenResults","restoreVotes","useEffectEvent","savedVotes","key","STORAGE_KEYS","voteOwnerScope","saved","readStored","o","debug","mediaId","emitVoteUpdate","playlistId","React","isVoting","totalUsers","handleVote","writeStored","updatedVotes","otherId","otherVote","resolveMediaAsset","emitRevealRequest","p","isActive","myVote","isCurrentMedia","resolveCardImage","el","emitPlaylistSelect","opt","isSelected"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PlaylistPanel-e34BfPg1.js","sources":["../../src/components/PlaylistRevealPopup.tsx","../../src/components/PlaylistPanel.tsx"],"sourcesContent":["import { useEffect, useState, type ReactNode } from 'react';\nimport type { TPlaylistResultsOut, TVoteValue } from '@watchparty/shared';\nimport { useAppStore } from '../lib/store';\nimport { isCharacterId } from '../lib/characters';\nimport { usePersonColor } from '../lib/pictureColor';\nimport AvatarImage from './AvatarImage';\nimport { StarIcon } from './controls/Icons';\nimport { useIsLeader } from '../hooks/useStoreSelectors';\nimport { emitVoteRemove } from '../lib/socket';\n\n/** How long the ✕ stays armed after the first click before it disarms. */\nconst REMOVE_ARM_MS = 3000;\n\ninterface Props {\n results: TPlaylistResultsOut;\n onClose: () => void;\n}\n\nconst VOTE_WEIGHTS: Record<TVoteValue, number> = {\n super_upvote: 4,\n upvote: 3,\n mid: 2,\n downvote: 1,\n};\n\nconst VOTE_ICONS: Record<TVoteValue, { icon: ReactNode; color: string }> = {\n // Bigger than the other marks on purpose: a super-upvote is the strongest\n // vote and was the hardest to see (a 10px star under the avatar).\n super_upvote: { icon: <StarIcon className=\"w-4 h-4\" />, color: 'bg-warning-bg text-on-warning-bg rounded' },\n upvote: { icon: '▲', color: 'bg-success-bg text-on-success-bg rounded px-0.5' },\n mid: { icon: '─', color: 'text-text-secondary' },\n downvote: { icon: '▼', color: 'bg-danger-bg text-on-danger-bg rounded px-0.5' },\n};\n\n/** One voter's face in the results: their picture, else their character. */\nfunction VoterFace({ sprite, url, alt, size, ring }: {\n sprite: string | undefined;\n url: string | undefined;\n alt: string;\n size: 'w-4 h-4' | 'w-6 h-6';\n ring: boolean;\n}) {\n const color = usePersonColor(sprite, url);\n const style = ring ? { border: `1.5px solid ${color}` } : undefined;\n return (\n <AvatarImage\n sprite={sprite}\n url={url}\n alt={alt}\n className={`${size} rounded-full object-cover`}\n style={style}\n fallback={<span className={`${size} rounded-full bg-bg-alt block`} style={style} />}\n />\n );\n}\n\nfunction computeScores(results: TPlaylistResultsOut) {\n const { playlist, votes } = results;\n return playlist.videos.map(video => {\n let totalScore = 0;\n let voterCount = 0;\n for (const clientVotes of Object.values(votes)) {\n const v = clientVotes[video.mediaId] as TVoteValue | undefined;\n if (v) { totalScore += VOTE_WEIGHTS[v]; voterCount++; }\n }\n const avgScore = voterCount > 0 ? totalScore / voterCount : 0;\n return { video, avgScore, voterCount };\n }).sort((a, b) => b.avgScore - a.avgScore);\n}\n\nexport default function PlaylistRevealPopup({ results, onClose }: Props) {\n const roster = useAppStore(s => s.roster);\n const isLeader = useIsLeader();\n const scored = computeScores(results);\n // Two clicks to remove: the first arms the ✕, the second sends. A stray\n // click mid-party must not silently drop someone's ballot.\n const [armed, setArmed] = useState<string | null>(null);\n useEffect(() => {\n if (!armed) return;\n const t = setTimeout(() => setArmed(null), REMOVE_ARM_MS);\n return () => clearTimeout(t);\n }, [armed]);\n\n // Ordered list of clients who voted\n const voters = Object.keys(results.votes).filter(\n id => Object.keys(results.votes[id]).length > 0\n );\n // Frozen at reveal by the server, so someone who has since LEFT (and is\n // gone from the live roster) still has an avatar.\n const spriteOf = (id: string) => {\n const sprite = roster[id]?.sprite ?? results.sprites?.[id];\n return isCharacterId(sprite) ? sprite : undefined;\n };\n const pictureOf = (id: string) => roster[id]?.avatarUrl ?? results.pictures?.[id];\n\n function onRemove(voterKey: string) {\n if (armed !== voterKey) { setArmed(voterKey); return; }\n setArmed(null);\n emitVoteRemove(voterKey);\n }\n\n return (\n <div\n className=\"fixed inset-0 bg-black/70 flex items-center justify-center z-50\"\n onClick={e => { if (e.target === e.currentTarget) onClose(); }}\n >\n <div data-testid=\"reveal-popup\" className=\"bg-bg-card border border-border rounded-lg shadow-xl w-full max-w-2xl max-h-[80vh] flex flex-col mx-4\">\n {/* Header */}\n <div className=\"flex items-center justify-between px-4 py-3 border-b border-border shrink-0\">\n <h2 className=\"text-sm font-semibold text-text\">\n {results.playlist.name} — Results\n </h2>\n <button\n onClick={onClose}\n aria-label=\"Close results\"\n className=\"p-1 text-text-muted hover:text-text transition-colors cursor-pointer\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n\n {/* Voters — the leader can drop one; the table regenerates for everyone */}\n {voters.length > 0 && (\n <div className=\"flex flex-wrap items-center gap-1.5 px-4 py-2 border-b border-border shrink-0\">\n {voters.map(id => {\n return (\n <span key={id} className=\"reveal-voter\">\n <VoterFace sprite={spriteOf(id)} url={pictureOf(id)} alt=\"\" size=\"w-4 h-4\" ring={false} />\n <span className=\"text-xs text-text truncate max-w-[8rem]\">{results.names[id] || id}</span>\n {isLeader && (\n <button\n type=\"button\"\n className=\"reveal-voter-remove\"\n data-armed={armed === id ? '' : undefined}\n onClick={() => onRemove(id)}\n aria-label={`Remove ${results.names[id] || id}'s votes`}\n title={armed === id ? 'Click again to remove' : `Remove ${results.names[id] || id}'s votes`}\n >\n {armed === id ? 'remove?' : '✕'}\n </button>\n )}\n </span>\n );\n })}\n </div>\n )}\n\n {/* Results list */}\n <div className=\"overflow-y-auto flex-1 py-1\">\n {scored.map(({ video, avgScore, voterCount }, rank) => (\n <div\n key={video.mediaId}\n className={`flex items-center gap-3 px-4 py-2 ${rank % 2 === 0 ? '' : 'bg-bg-alt/20'}`}\n >\n {/* Rank */}\n <span className=\"text-xs text-text-muted w-5 shrink-0 text-right\">{rank + 1}</span>\n\n {/* Video name */}\n <span className=\"text-xs text-text truncate flex-1 min-w-0\">\n {video.displayName}\n </span>\n\n {/* Per-voter avatars + vote icon */}\n <div className=\"flex items-center gap-1.5 shrink-0\">\n {voters.map(clientId => {\n const vote = results.votes[clientId]?.[video.mediaId] as TVoteValue | undefined;\n if (!vote) return null;\n const voteStyle = VOTE_ICONS[vote];\n\n return (\n <div key={clientId} className=\"flex flex-col items-center gap-0.5\">\n {/* Avatar */}\n <div className=\"relative w-6 h-6 shrink-0\">\n <VoterFace\n sprite={spriteOf(clientId)}\n url={pictureOf(clientId)}\n alt={results.names[clientId] || clientId}\n size=\"w-6 h-6\"\n ring\n />\n </div>\n {/* Vote icon */}\n <span className={`text-[9px] leading-none font-bold ${voteStyle.color}`}>\n {voteStyle.icon}\n </span>\n </div>\n );\n })}\n {/* Show dashes for non-voters */}\n {voters.map(clientId => {\n const voted = !!results.votes[clientId]?.[video.mediaId];\n if (voted) return null;\n return (\n <div key={`no-${clientId}`} className=\"flex flex-col items-center gap-0.5 opacity-20\">\n <div className=\"w-6 h-6 rounded-full bg-bg-alt border border-border\" />\n <span className=\"text-[9px] leading-none text-text-muted\">·</span>\n </div>\n );\n })}\n </div>\n\n {/* Avg score */}\n <span className=\"text-xs text-text-secondary w-8 text-right shrink-0 font-medium\">\n {voterCount > 0 ? avgScore.toFixed(1) : '—'}\n </span>\n </div>\n ))}\n </div>\n </div>\n </div>\n );\n}\n","import React, { useEffectEvent, useState } from 'react';\nimport { useAppStore } from '../lib/store';\nimport { useIsLeader } from '../hooks/useStoreSelectors';\nimport { emitPlaylistLoad, emitPlaylistSelect, emitVoteUpdate, emitRevealRequest } from '../lib/socket';\nimport { resolveMediaAsset, resolveCardImage } from '../lib/mediaUrls';\nimport { debug } from '../lib/debug';\nimport { STORAGE_KEYS } from '../lib/storageKeys';\nimport { voteOwnerScope } from '../lib/auth';\nimport type { TVoteValue } from '@watchparty/shared';\nimport PlaylistRevealPopup from './PlaylistRevealPopup';\nimport { StarIcon } from './controls/Icons';\nimport { readStored, writeStored } from '../lib/browserStorage';\n\n// ── Vote icons ────────────────────────────────────────────────────────────────\n\n\nfunction UpvoteIcon() {\n return (\n <svg className=\"w-3.5 h-3.5\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth={2.5}>\n <polyline points=\"18 15 12 9 6 15\" />\n </svg>\n );\n}\n\nfunction MidIcon() {\n return (\n <svg className=\"w-3.5 h-3.5\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth={2.5}>\n <line x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\" />\n </svg>\n );\n}\n\nfunction DownvoteIcon() {\n return (\n <svg className=\"w-3.5 h-3.5\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth={2.5}>\n <polyline points=\"6 9 12 15 18 9\" />\n </svg>\n );\n}\n\n// Colours live in `.vote-btn` (styles.css), keyed by `data-vote`: a <button>'s\n// colour/background utilities are discarded by the unlayered `button` reset, so\n// the per-vote `text-*` classes these carried never rendered — every vote icon\n// was the same white, selected or not.\nconst VOTE_OPTIONS: { value: TVoteValue; icon: React.ReactNode; label: string }[] = [\n { value: 'super_upvote', icon: <StarIcon className=\"w-4 h-4\" />, label: 'Super' },\n { value: 'upvote', icon: <UpvoteIcon />, label: 'Up' },\n { value: 'mid', icon: <MidIcon />, label: 'Mid' },\n { value: 'downvote', icon: <DownvoteIcon />, label: 'Down' },\n];\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\nfunction formatDuration(ms: number): string {\n const total = Math.round(ms / 1000);\n const h = Math.floor(total / 3600);\n const m = Math.floor((total % 3600) / 60);\n const s = total % 60;\n return h > 0\n ? `${h}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`\n : `${m}:${String(s).padStart(2, '0')}`;\n}\n\n// ── Load form (leader, no playlist loaded) ────────────────────────────────────\n\nfunction LoadForm() {\n const [input, setInput] = useState('');\n const [loading, setLoading] = useState(false);\n const [err, setErr] = useState<string | null>(null);\n\n const handleLoad = () => {\n const id = input.trim();\n if (!id) return;\n setLoading(true);\n setErr(null);\n emitPlaylistLoad(id);\n // Error will arrive via the 'error' socket event and show as a toast.\n // Reset loading state after a short delay.\n setTimeout(() => setLoading(false), 1500);\n };\n\n return (\n <div className=\"p-4 flex flex-col gap-3\">\n <p className=\"text-xs text-text-muted\">Enter a playlist ID to load it for the room.</p>\n <div className=\"flex gap-2\">\n <input\n type=\"text\"\n value={input}\n onChange={e => setInput(e.target.value)}\n onKeyDown={e => e.key === 'Enter' && handleLoad()}\n placeholder=\"Playlist ID (e.g. april-4)\"\n className=\"modal-input flex-1 text-xs px-2 py-1.5\"\n disabled={loading}\n />\n <button\n onClick={handleLoad}\n disabled={loading || !input.trim()}\n className=\"modal-btn-update text-xs px-3 py-1.5 rounded disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n Load\n </button>\n </div>\n {err && <p className=\"text-xs bg-danger-bg text-on-danger-bg px-2 py-1 rounded\">{err}</p>}\n </div>\n );\n}\n\n// ── Empty state (viewer, no playlist) ─────────────────────────────────────────\n\nfunction EmptyState() {\n return (\n <div className=\"flex-1 flex items-center justify-center p-4\">\n <p className=\"text-xs text-text-muted text-center\">No playlist loaded yet.</p>\n </div>\n );\n}\n\n// ── Main panel ────────────────────────────────────────────────────────────────\n\nexport default function PlaylistPanel() {\n const isLeader = useIsLeader();\n const playlist = useAppStore(s => s.playlist);\n const activeIndex = useAppStore(s => s.activePlaylistIndex);\n const myVotes = useAppStore(s => s.myVotes);\n const voteProgress = useAppStore(s => s.voteProgress);\n const results = useAppStore(s => s.playlistResults);\n const currentMediaId = useAppStore(s => s.snapshot.mediaId);\n\n // Show results popup when a reveal arrives. NOT on an `update` (a voter was\n // removed): that re-sends the table to everyone, and reopening it would put\n // the popup back in front of people who had already closed it.\n const [showResults, setShowResults] = useState(() => !!results && !results.update);\n const [seenResults, setSeenResults] = useState(results);\n if (results !== seenResults) {\n setSeenResults(results);\n if (results && !results.update) setShowResults(true);\n }\n\n // Restore votes from localStorage when a playlist is first loaded. The body\n // reads the current playlist, index and votes; only a new playlist id runs it.\n const restoreVotes = useEffectEvent(() => {\n if (!playlist || playlist.type !== 'vote') return;\n const savedVotes: Record<string, TVoteValue> = {};\n for (const video of playlist.videos) {\n const key = STORAGE_KEYS.voteKey(voteOwnerScope(), playlist.id, video.mediaId);\n const saved = readStored(key) as TVoteValue | null;\n if (saved && VOTE_OPTIONS.some(o => o.value === saved)) {\n savedVotes[video.mediaId] = saved;\n }\n }\n debug('playlist', 'Checked localStorage votes', { playlistId: playlist.id, found: Object.keys(savedVotes).length });\n if (Object.keys(savedVotes).length === 0) return;\n useAppStore.getState().applyPlaylistState({ playlist, activeIndex, myVotes: { ...myVotes, ...savedVotes } });\n for (const [mediaId, vote] of Object.entries(savedVotes)) {\n emitVoteUpdate(playlist.id, mediaId, vote);\n }\n });\n const playlistId = playlist?.id;\n React.useEffect(() => { restoreVotes(); }, [playlistId]);\n\n if (!playlist) {\n return isLeader ? <LoadForm /> : <EmptyState />;\n }\n\n // `basic` playlists are a plain watch-through — no vote buttons, progress,\n // or reveal. Only `vote` playlists show the rating mechanic.\n const isVoting = playlist.type === 'vote';\n const totalUsers = voteProgress.length;\n\n const handleVote = (mediaId: string, vote: TVoteValue) => {\n emitVoteUpdate(playlist.id, mediaId, vote);\n debug('playlist', 'Vote emitted', { playlistId: playlist.id, mediaId, vote });\n writeStored(STORAGE_KEYS.voteKey(voteOwnerScope(), playlist.id, mediaId), vote);\n\n // Mirror the server's super_upvote exclusivity rule: if this vote is a star,\n // downgrade any other video's star to upvote so the visual ring updates immediately.\n const updatedVotes = { ...myVotes, [mediaId]: vote };\n if (vote === 'super_upvote') {\n for (const [otherId, otherVote] of Object.entries(updatedVotes)) {\n if (otherId !== mediaId && otherVote === 'super_upvote') {\n updatedVotes[otherId] = 'upvote';\n writeStored(STORAGE_KEYS.voteKey(voteOwnerScope(), playlist.id, otherId), 'upvote');\n }\n }\n }\n\n useAppStore.getState().applyPlaylistState({\n playlist,\n activeIndex,\n myVotes: updatedVotes,\n });\n };\n\n return (\n <div className=\"flex flex-col flex-1 min-h-0\">\n {/* Header */}\n <div className=\"flex items-center gap-2 px-3 py-2 border-b border-border shrink-0\">\n {playlist.cover && (\n <img\n src={resolveMediaAsset(playlist.cover)}\n alt=\"\"\n className=\"w-8 h-8 object-cover rounded shrink-0 bg-bg-alt\"\n />\n )}\n <span className=\"text-xs font-medium text-text truncate flex-1\">\n {playlist.name}\n </span>\n {isLeader && isVoting && (\n <button\n onClick={() => emitRevealRequest()}\n className=\"text-xs text-text-muted hover:text-text transition-colors cursor-pointer shrink-0\"\n title=\"Reveal votes to all\"\n >\n Reveal\n </button>\n )}\n </div>\n\n {/* Vote progress bar (leader only, vote playlists only) */}\n {isLeader && isVoting && totalUsers > 0 && (\n <div className=\"px-3 py-1.5 border-b border-border shrink-0\">\n <div className=\"flex items-center gap-1.5\">\n <span className=\"text-[10px] text-text-muted shrink-0\">Votes:</span>\n <div className=\"flex gap-1 flex-wrap flex-1\">\n {voteProgress.map(p => (\n <span key={p.clientId} className=\"text-[10px] text-text-secondary\">\n {p.name} {p.votedCount}/{p.totalVideos}\n </span>\n ))}\n </div>\n </div>\n </div>\n )}\n\n {/* Video list */}\n <div className=\"flex-1 overflow-y-auto\">\n {playlist.videos.map((video, i) => {\n const isActive = activeIndex === i;\n const myVote = myVotes[video.mediaId] as TVoteValue | undefined;\n const isCurrentMedia = currentMediaId === video.mediaId;\n\n return (\n <div\n key={video.mediaId}\n className={`flex items-center gap-2 px-3 py-2 border-b border-border/50 group ${\n isActive ? 'bg-primary/10' : 'hover:bg-bg-alt/50'\n }`}\n >\n {/* Play indicator */}\n <div className=\"w-3.5 shrink-0 text-text-muted\">\n {isCurrentMedia ? (\n <svg className=\"w-3.5 h-3.5 text-text\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <polygon points=\"5 3 19 12 5 21 5 3\" />\n </svg>\n ) : (\n <span className=\"text-[10px] text-text-muted\">{i + 1}</span>\n )}\n </div>\n\n {/* Thumbnail. No longer flat-playlist-only: `poster.jpg` is\n derived by convention when a clip carries no explicit\n thumbnail (see resolveCardImage), and a 404 hides the image. */}\n <div className=\"relative shrink-0\">\n <img\n src={resolveCardImage(video.mediaId, video.thumbnail)}\n alt=\"\"\n loading=\"lazy\"\n onError={e => {\n const el = e.currentTarget as HTMLImageElement;\n // Hide the whole slot, not just the img — the duration\n // badge is positioned against it.\n (el.parentElement ?? el).style.display = 'none';\n }}\n className=\"w-14 h-10 object-cover rounded bg-bg-alt\"\n />\n {video.durationMs ? (\n <span className=\"absolute bottom-0 right-0 px-1 text-[9px] leading-tight rounded-tl rounded-br bg-bg-card/90 text-text-secondary\">\n {formatDuration(video.durationMs)}\n </span>\n ) : null}\n </div>\n\n {/* Video name — leader can click to select */}\n <button\n className={`flex-1 text-left text-xs truncate cursor-pointer transition-colors min-w-0 ${\n isLeader\n ? 'hover:text-text text-text-secondary'\n : 'text-text-secondary cursor-default'\n } ${isActive ? 'text-text font-medium' : ''}`}\n onClick={isLeader ? () => emitPlaylistSelect(i) : undefined}\n title={isLeader ? `Play: ${video.displayName}` : video.displayName}\n disabled={!isLeader}\n >\n {video.displayName}\n </button>\n\n {/* Vote buttons (vote playlists only) */}\n {isVoting && (\n <div className=\"flex gap-0.5 shrink-0\">\n {VOTE_OPTIONS.map(opt => {\n const isSelected = myVote === opt.value;\n return (\n <button\n key={opt.value}\n onClick={() => handleVote(video.mediaId, opt.value)}\n title={opt.label}\n className=\"vote-btn\"\n data-vote={opt.value}\n data-selected={isSelected ? '' : undefined}\n aria-pressed={isSelected}\n >\n {opt.icon}\n </button>\n );\n })}\n </div>\n )}\n </div>\n );\n })}\n </div>\n\n {/* Results popup (vote playlists only) */}\n {isVoting && showResults && results && (\n <PlaylistRevealPopup\n results={results}\n onClose={() => setShowResults(false)}\n />\n )}\n </div>\n );\n}\n"],"names":["REMOVE_ARM_MS","VOTE_WEIGHTS","VOTE_ICONS","jsx","StarIcon","VoterFace","sprite","url","alt","size","ring","color","usePersonColor","style","AvatarImage","computeScores","results","playlist","votes","video","totalScore","voterCount","clientVotes","v","avgScore","a","b","PlaylistRevealPopup","onClose","roster","useAppStore","isLeader","useIsLeader","scored","armed","setArmed","useState","useEffect","t","voters","id","spriteOf","isCharacterId","pictureOf","onRemove","voterKey","emitVoteRemove","e","jsxs","rank","clientId","vote","voteStyle","UpvoteIcon","MidIcon","DownvoteIcon","VOTE_OPTIONS","formatDuration","ms","total","h","m","s","LoadForm","input","setInput","loading","setLoading","err","setErr","handleLoad","emitPlaylistLoad","EmptyState","PlaylistPanel","activeIndex","myVotes","voteProgress","currentMediaId","showResults","setShowResults","seenResults","setSeenResults","restoreVotes","useEffectEvent","savedVotes","key","STORAGE_KEYS","voteOwnerScope","saved","readStored","o","debug","mediaId","emitVoteUpdate","playlistId","React","isVoting","totalUsers","handleVote","writeStored","updatedVotes","otherId","otherVote","resolveMediaAsset","emitRevealRequest","p","isActive","myVote","isCurrentMedia","resolveCardImage","el","emitPlaylistSelect","opt","isSelected"],"mappings":";;;;AAWA,MAAMA,IAAgB,KAOhBC,IAA2C;AAAA,EAC/C,cAAc;AAAA,EACd,QAAQ;AAAA,EACR,KAAK;AAAA,EACL,UAAU;AACZ,GAEMC,IAAqE;AAAA;AAAA;AAAA,EAGzE,cAAc,EAAE,MAAM,gBAAAC,EAACC,KAAS,WAAU,UAAA,CAAU,GAAI,OAAO,2CAAA;AAAA,EAC/D,QAAc,EAAE,MAAM,KAAK,OAAO,kDAAA;AAAA,EAClC,KAAc,EAAE,MAAM,KAAK,OAAO,sBAAA;AAAA,EAClC,UAAc,EAAE,MAAM,KAAK,OAAO,gDAAA;AACpC;AAGA,SAASC,EAAU,EAAE,QAAAC,GAAQ,KAAAC,GAAK,KAAAC,GAAK,MAAAC,GAAM,MAAAC,KAM1C;AACD,QAAMC,IAAQC,EAAeN,GAAQC,CAAG,GAClCM,IAAQH,IAAO,EAAE,QAAQ,eAAeC,CAAK,OAAO;AAC1D,SACE,gBAAAR;AAAA,IAACW;AAAA,IAAA;AAAA,MACC,QAAAR;AAAA,MACA,KAAAC;AAAA,MACA,KAAAC;AAAA,MACA,WAAW,GAAGC,CAAI;AAAA,MAClB,OAAAI;AAAA,MACA,UAAU,gBAAAV,EAAC,QAAA,EAAK,WAAW,GAAGM,CAAI,iCAAiC,OAAAI,EAAA,CAAc;AAAA,IAAA;AAAA,EAAA;AAGvF;AAEA,SAASE,EAAcC,GAA8B;AACnD,QAAM,EAAE,UAAAC,GAAU,OAAAC,EAAA,IAAUF;AAC5B,SAAOC,EAAS,OAAO,IAAI,CAAAE,MAAS;AAClC,QAAIC,IAAa,GACbC,IAAa;AACjB,eAAWC,KAAe,OAAO,OAAOJ,CAAK,GAAG;AAC9C,YAAMK,IAAID,EAAYH,EAAM,OAAO;AACnC,MAAII,MAAKH,KAAcnB,EAAasB,CAAC,GAAGF;AAAA,IAC1C;AACA,UAAMG,IAAWH,IAAa,IAAID,IAAaC,IAAa;AAC5D,WAAO,EAAE,OAAAF,GAAO,UAAAK,GAAU,YAAAH,EAAA;AAAA,EAC5B,CAAC,EAAE,KAAK,CAACI,GAAGC,MAAMA,EAAE,WAAWD,EAAE,QAAQ;AAC3C;AAEA,SAAwBE,EAAoB,EAAE,SAAAX,GAAS,SAAAY,KAAkB;AACvE,QAAMC,IAASC,EAAY,CAAA,MAAK,EAAE,MAAM,GAClCC,IAAWC,EAAA,GACXC,IAASlB,EAAcC,CAAO,GAG9B,CAACkB,GAAOC,CAAQ,IAAIC,EAAwB,IAAI;AACtD,EAAAC,EAAU,MAAM;AACd,QAAI,CAACH,EAAO;AACZ,UAAMI,IAAI,WAAW,MAAMH,EAAS,IAAI,GAAGnC,CAAa;AACxD,WAAO,MAAM,aAAasC,CAAC;AAAA,EAC7B,GAAG,CAACJ,CAAK,CAAC;AAGV,QAAMK,IAAS,OAAO,KAAKvB,EAAQ,KAAK,EAAE;AAAA,IACxC,CAAAwB,MAAM,OAAO,KAAKxB,EAAQ,MAAMwB,CAAE,CAAC,EAAE,SAAS;AAAA,EAAA,GAI1CC,IAAW,CAACD,MAAe;AAC/B,UAAMlC,IAASuB,EAAOW,CAAE,GAAG,UAAUxB,EAAQ,UAAUwB,CAAE;AACzD,WAAOE,EAAcpC,CAAM,IAAIA,IAAS;AAAA,EAC1C,GACMqC,IAAY,CAACH,MAAeX,EAAOW,CAAE,GAAG,aAAaxB,EAAQ,WAAWwB,CAAE;AAEhF,WAASI,EAASC,GAAkB;AAClC,QAAIX,MAAUW,GAAU;AAAE,MAAAV,EAASU,CAAQ;AAAG;AAAA,IAAQ;AACtD,IAAAV,EAAS,IAAI,GACbW,EAAeD,CAAQ;AAAA,EACzB;AAEA,SACE,gBAAA1C;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAU;AAAA,MACV,SAAS,CAAA4C,MAAK;AAAE,QAAIA,EAAE,WAAWA,EAAE,iBAAenB,EAAA;AAAA,MAAW;AAAA,MAE7D,UAAA,gBAAAoB,EAAC,OAAA,EAAI,eAAY,gBAAe,WAAU,yGAExC,UAAA;AAAA,QAAA,gBAAAA,EAAC,OAAA,EAAI,WAAU,+EACb,UAAA;AAAA,UAAA,gBAAAA,EAAC,MAAA,EAAG,WAAU,mCACX,UAAA;AAAA,YAAAhC,EAAQ,SAAS;AAAA,YAAK;AAAA,UAAA,GACzB;AAAA,UACA,gBAAAb;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,SAASyB;AAAA,cACT,cAAW;AAAA,cACX,WAAU;AAAA,cAEV,UAAA,gBAAAzB,EAAC,SAAI,WAAU,WAAU,MAAK,QAAO,SAAQ,aAAY,QAAO,gBAAe,aAAa,GAC1F,UAAA,gBAAAA,EAAC,UAAK,eAAc,SAAQ,gBAAe,SAAQ,GAAE,wBAAuB,EAAA,CAC9E;AAAA,YAAA;AAAA,UAAA;AAAA,QACF,GACF;AAAA,QAGCoC,EAAO,SAAS,KACf,gBAAApC,EAAC,SAAI,WAAU,iFACZ,UAAAoC,EAAO,IAAI,CAAAC,MAER,gBAAAQ,EAAC,QAAA,EAAc,WAAU,gBACvB,UAAA;AAAA,UAAA,gBAAA7C,EAACE,GAAA,EAAU,QAAQoC,EAASD,CAAE,GAAG,KAAKG,EAAUH,CAAE,GAAG,KAAI,IAAG,MAAK,WAAU,MAAM,IAAO;AAAA,UACxF,gBAAArC,EAAC,UAAK,WAAU,2CAA2C,YAAQ,MAAMqC,CAAE,KAAKA,GAAG;AAAA,UAClFT,KACC,gBAAA5B;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,MAAK;AAAA,cACL,WAAU;AAAA,cACV,cAAY+B,MAAUM,IAAK,KAAK;AAAA,cAChC,SAAS,MAAMI,EAASJ,CAAE;AAAA,cAC1B,cAAY,UAAUxB,EAAQ,MAAMwB,CAAE,KAAKA,CAAE;AAAA,cAC7C,OAAON,MAAUM,IAAK,0BAA0B,UAAUxB,EAAQ,MAAMwB,CAAE,KAAKA,CAAE;AAAA,cAEhF,UAAAN,MAAUM,IAAK,YAAY;AAAA,YAAA;AAAA,UAAA;AAAA,QAC9B,EAAA,GAbOA,CAeX,CAEH,EAAA,CACH;AAAA,QAIF,gBAAArC,EAAC,OAAA,EAAI,WAAU,+BACZ,UAAA8B,EAAO,IAAI,CAAC,EAAE,OAAAd,GAAO,UAAAK,GAAU,YAAAH,EAAA,GAAc4B,MAC5C,gBAAAD;AAAA,UAAC;AAAA,UAAA;AAAA,YAEC,WAAW,qCAAqCC,IAAO,MAAM,IAAI,KAAK,cAAc;AAAA,YAGpF,UAAA;AAAA,cAAA,gBAAA9C,EAAC,QAAA,EAAK,WAAU,mDAAmD,UAAA8C,IAAO,GAAE;AAAA,cAG5E,gBAAA9C,EAAC,QAAA,EAAK,WAAU,6CACb,YAAM,aACT;AAAA,cAGA,gBAAA6C,EAAC,OAAA,EAAI,WAAU,sCACZ,UAAA;AAAA,gBAAAT,EAAO,IAAI,CAAAW,MAAY;AACtB,wBAAMC,IAAOnC,EAAQ,MAAMkC,CAAQ,IAAI/B,EAAM,OAAO;AACpD,sBAAI,CAACgC,EAAM,QAAO;AAClB,wBAAMC,IAAYlD,EAAWiD,CAAI;AAEjC,yBACE,gBAAAH,EAAC,OAAA,EAAmB,WAAU,sCAE5B,UAAA;AAAA,oBAAA,gBAAA7C,EAAC,OAAA,EAAI,WAAU,6BACb,UAAA,gBAAAA;AAAA,sBAACE;AAAA,sBAAA;AAAA,wBACC,QAAQoC,EAASS,CAAQ;AAAA,wBACzB,KAAKP,EAAUO,CAAQ;AAAA,wBACvB,KAAKlC,EAAQ,MAAMkC,CAAQ,KAAKA;AAAA,wBAChC,MAAK;AAAA,wBACL,MAAI;AAAA,sBAAA;AAAA,oBAAA,GAER;AAAA,oBAEA,gBAAA/C,EAAC,UAAK,WAAW,qCAAqCiD,EAAU,KAAK,IAClE,YAAU,KAAA,CACb;AAAA,kBAAA,EAAA,GAdQF,CAeV;AAAA,gBAEJ,CAAC;AAAA,gBAEAX,EAAO,IAAI,CAAAW,MACMlC,EAAQ,MAAMkC,CAAQ,IAAI/B,EAAM,OAAO,IACrC,OAEhB,gBAAA6B,EAAC,OAAA,EAA2B,WAAU,iDACpC,UAAA;AAAA,kBAAA,gBAAA7C,EAAC,OAAA,EAAI,WAAU,sDAAA,CAAsD;AAAA,kBACrE,gBAAAA,EAAC,QAAA,EAAK,WAAU,2CAA0C,UAAA,IAAA,CAAC;AAAA,gBAAA,EAAA,GAFnD,MAAM+C,CAAQ,EAGxB,CAEH;AAAA,cAAA,GACH;AAAA,cAGA,gBAAA/C,EAAC,QAAA,EAAK,WAAU,mEACb,UAAAkB,IAAa,IAAIG,EAAS,QAAQ,CAAC,IAAI,IAAA,CAC1C;AAAA,YAAA;AAAA,UAAA;AAAA,UArDKL,EAAM;AAAA,QAAA,CAuDd,EAAA,CACH;AAAA,MAAA,EAAA,CACF;AAAA,IAAA;AAAA,EAAA;AAGN;ACrMA,SAASkC,KAAa;AACpB,2BACG,OAAA,EAAI,WAAU,eAAc,SAAQ,aAAY,MAAK,QAAO,QAAO,gBAAe,aAAa,KAC9F,UAAA,gBAAAlD,EAAC,YAAA,EAAS,QAAO,mBAAkB,GACrC;AAEJ;AAEA,SAASmD,KAAU;AACjB,SACE,gBAAAnD,EAAC,SAAI,WAAU,eAAc,SAAQ,aAAY,MAAK,QAAO,QAAO,gBAAe,aAAa,KAC9F,UAAA,gBAAAA,EAAC,QAAA,EAAK,IAAG,KAAI,IAAG,MAAK,IAAG,MAAK,IAAG,KAAA,CAAK,EAAA,CACvC;AAEJ;AAEA,SAASoD,KAAe;AACtB,2BACG,OAAA,EAAI,WAAU,eAAc,SAAQ,aAAY,MAAK,QAAO,QAAO,gBAAe,aAAa,KAC9F,UAAA,gBAAApD,EAAC,YAAA,EAAS,QAAO,kBAAiB,GACpC;AAEJ;AAMA,MAAMqD,IAA8E;AAAA,EAClF,EAAE,OAAO,gBAAgB,MAAM,gBAAArD,EAACC,KAAS,WAAU,UAAA,CAAU,GAAI,OAAO,QAAA;AAAA,EACxE,EAAE,OAAO,UAAgB,wBAAOiD,IAAA,CAAA,CAAW,GAAI,OAAO,KAAA;AAAA,EACtD,EAAE,OAAO,OAAgB,wBAAOC,IAAA,CAAA,CAAQ,GAAO,OAAO,MAAA;AAAA,EACtD,EAAE,OAAO,YAAgB,wBAAOC,IAAA,CAAA,CAAa,GAAI,OAAO,OAAA;AAC1D;AAIA,SAASE,GAAeC,GAAoB;AAC1C,QAAMC,IAAQ,KAAK,MAAMD,IAAK,GAAI,GAC5BE,IAAI,KAAK,MAAMD,IAAQ,IAAI,GAC3BE,IAAI,KAAK,MAAOF,IAAQ,OAAQ,EAAE,GAClCG,IAAIH,IAAQ;AAClB,SAAOC,IAAI,IACP,GAAGA,CAAC,IAAI,OAAOC,CAAC,EAAE,SAAS,GAAG,GAAG,CAAC,IAAI,OAAOC,CAAC,EAAE,SAAS,GAAG,GAAG,CAAC,KAChE,GAAGD,CAAC,IAAI,OAAOC,CAAC,EAAE,SAAS,GAAG,GAAG,CAAC;AACxC;AAIA,SAASC,KAAW;AAClB,QAAM,CAACC,GAAOC,CAAQ,IAAI7B,EAAS,EAAE,GAC/B,CAAC8B,GAASC,CAAU,IAAI/B,EAAS,EAAK,GACtC,CAACgC,GAAKC,CAAM,IAAIjC,EAAwB,IAAI,GAE5CkC,IAAa,MAAM;AACvB,UAAM9B,IAAKwB,EAAM,KAAA;AACjB,IAAKxB,MACL2B,EAAW,EAAI,GACfE,EAAO,IAAI,GACXE,EAAiB/B,CAAE,GAGnB,WAAW,MAAM2B,EAAW,EAAK,GAAG,IAAI;AAAA,EAC1C;AAEA,SACE,gBAAAnB,EAAC,OAAA,EAAI,WAAU,2BACb,UAAA;AAAA,IAAA,gBAAA7C,EAAC,KAAA,EAAE,WAAU,2BAA0B,UAAA,gDAA4C;AAAA,IACnF,gBAAA6C,EAAC,OAAA,EAAI,WAAU,cACb,UAAA;AAAA,MAAA,gBAAA7C;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,MAAK;AAAA,UACL,OAAO6D;AAAA,UACP,UAAU,CAAAjB,MAAKkB,EAASlB,EAAE,OAAO,KAAK;AAAA,UACtC,WAAW,CAAAA,MAAKA,EAAE,QAAQ,WAAWuB,EAAA;AAAA,UACrC,aAAY;AAAA,UACZ,WAAU;AAAA,UACV,UAAUJ;AAAA,QAAA;AAAA,MAAA;AAAA,MAEZ,gBAAA/D;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,SAASmE;AAAA,UACT,UAAUJ,KAAW,CAACF,EAAM,KAAA;AAAA,UAC5B,WAAU;AAAA,UACX,UAAA;AAAA,QAAA;AAAA,MAAA;AAAA,IAED,GACF;AAAA,IACCI,KAAO,gBAAAjE,EAAC,KAAA,EAAE,WAAU,4DAA4D,UAAAiE,EAAA,CAAI;AAAA,EAAA,GACvF;AAEJ;AAIA,SAASI,KAAa;AACpB,SACE,gBAAArE,EAAC,SAAI,WAAU,+CACb,4BAAC,KAAA,EAAE,WAAU,uCAAsC,UAAA,0BAAA,CAAuB,EAAA,CAC5E;AAEJ;AAIA,SAAwBsE,KAAgB;AACtC,QAAM1C,IAAWC,EAAA,GACXf,IAAWa,EAAY,CAAAgC,MAAKA,EAAE,QAAQ,GACtCY,IAAc5C,EAAY,CAAAgC,MAAKA,EAAE,mBAAmB,GACpDa,IAAU7C,EAAY,CAAAgC,MAAKA,EAAE,OAAO,GACpCc,IAAe9C,EAAY,CAAAgC,MAAKA,EAAE,YAAY,GAC9C9C,IAAUc,EAAY,CAAAgC,MAAKA,EAAE,eAAe,GAC5Ce,IAAiB/C,EAAY,CAAAgC,MAAKA,EAAE,SAAS,OAAO,GAKpD,CAACgB,GAAaC,CAAc,IAAI3C,EAAS,MAAM,CAAC,CAACpB,KAAW,CAACA,EAAQ,MAAM,GAC3E,CAACgE,GAAaC,CAAc,IAAI7C,EAASpB,CAAO;AACtD,EAAIA,MAAYgE,MACdC,EAAejE,CAAO,GAClBA,KAAW,CAACA,EAAQ,YAAuB,EAAI;AAKrD,QAAMkE,IAAeC,EAAe,MAAM;AACxC,QAAI,CAAClE,KAAYA,EAAS,SAAS,OAAQ;AAC3C,UAAMmE,IAAyC,CAAA;AAC/C,eAAWjE,KAASF,EAAS,QAAQ;AACnC,YAAMoE,IAAMC,EAAa,QAAQC,EAAA,GAAkBtE,EAAS,IAAIE,EAAM,OAAO,GACvEqE,IAAQC,EAAWJ,CAAG;AAC5B,MAAIG,KAAShC,EAAa,KAAK,OAAKkC,EAAE,UAAUF,CAAK,MACnDJ,EAAWjE,EAAM,OAAO,IAAIqE;AAAA,IAEhC;AAEA,QADAG,EAAM,YAAY,8BAA8B,EAAE,YAAY1E,EAAS,IAAI,OAAO,OAAO,KAAKmE,CAAU,EAAE,OAAA,CAAQ,GAC9G,OAAO,KAAKA,CAAU,EAAE,WAAW,GACvC;AAAA,MAAAtD,EAAY,SAAA,EAAW,mBAAmB,EAAE,UAAAb,GAAU,aAAAyD,GAAa,SAAS,EAAE,GAAGC,GAAS,GAAGS,EAAA,GAAc;AAC3G,iBAAW,CAACQ,GAASzC,CAAI,KAAK,OAAO,QAAQiC,CAAU;AACrD,QAAAS,EAAe5E,EAAS,IAAI2E,GAASzC,CAAI;AAAA;AAAA,EAE7C,CAAC,GACK2C,IAAa7E,GAAU;AAG7B,MAFA8E,EAAM,UAAU,MAAM;AAAE,IAAAb,EAAA;AAAA,EAAgB,GAAG,CAACY,CAAU,CAAC,GAEnD,CAAC7E;AACH,WAAOc,IAAW,gBAAA5B,EAAC4D,IAAA,CAAA,CAAS,sBAAMS,IAAA,EAAW;AAK/C,QAAMwB,IAAW/E,EAAS,SAAS,QAC7BgF,IAAarB,EAAa,QAE1BsB,IAAa,CAACN,GAAiBzC,MAAqB;AACxD,IAAA0C,EAAe5E,EAAS,IAAI2E,GAASzC,CAAI,GACzCwC,EAAM,YAAY,gBAAgB,EAAE,YAAY1E,EAAS,IAAI,SAAA2E,GAAS,MAAAzC,GAAM,GAC5EgD,EAAYb,EAAa,QAAQC,EAAA,GAAkBtE,EAAS,IAAI2E,CAAO,GAAGzC,CAAI;AAI9E,UAAMiD,IAAe,EAAE,GAAGzB,GAAS,CAACiB,CAAO,GAAGzC,EAAA;AAC9C,QAAIA,MAAS;AACX,iBAAW,CAACkD,GAASC,CAAS,KAAK,OAAO,QAAQF,CAAY;AAC5D,QAAIC,MAAYT,KAAWU,MAAc,mBACvCF,EAAaC,CAAO,IAAI,UACxBF,EAAYb,EAAa,QAAQC,EAAA,GAAkBtE,EAAS,IAAIoF,CAAO,GAAG,QAAQ;AAKxF,IAAAvE,EAAY,SAAA,EAAW,mBAAmB;AAAA,MACxC,UAAAb;AAAA,MACA,aAAAyD;AAAA,MACA,SAAS0B;AAAA,IAAA,CACV;AAAA,EACH;AAEA,SACE,gBAAApD,EAAC,OAAA,EAAI,WAAU,gCAEb,UAAA;AAAA,IAAA,gBAAAA,EAAC,OAAA,EAAI,WAAU,qEACZ,UAAA;AAAA,MAAA/B,EAAS,SACR,gBAAAd;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,KAAKoG,EAAkBtF,EAAS,KAAK;AAAA,UACrC,KAAI;AAAA,UACJ,WAAU;AAAA,QAAA;AAAA,MAAA;AAAA,MAGd,gBAAAd,EAAC,QAAA,EAAK,WAAU,iDACb,YAAS,MACZ;AAAA,MACC4B,KAAYiE,KACX,gBAAA7F;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,SAAS,MAAMqG,EAAA;AAAA,UACf,WAAU;AAAA,UACV,OAAM;AAAA,UACP,UAAA;AAAA,QAAA;AAAA,MAAA;AAAA,IAED,GAEJ;AAAA,IAGCzE,KAAYiE,KAAYC,IAAa,KACpC,gBAAA9F,EAAC,OAAA,EAAI,WAAU,+CACb,UAAA,gBAAA6C,EAAC,OAAA,EAAI,WAAU,6BACb,UAAA;AAAA,MAAA,gBAAA7C,EAAC,QAAA,EAAK,WAAU,wCAAuC,UAAA,UAAM;AAAA,MAC7D,gBAAAA,EAAC,OAAA,EAAI,WAAU,+BACZ,UAAAyE,EAAa,IAAI,CAAA6B,MAChB,gBAAAzD,EAAC,QAAA,EAAsB,WAAU,mCAC9B,UAAA;AAAA,QAAAyD,EAAE;AAAA,QAAK;AAAA,QAAEA,EAAE;AAAA,QAAW;AAAA,QAAEA,EAAE;AAAA,MAAA,KADlBA,EAAE,QAEb,CACD,EAAA,CACH;AAAA,IAAA,EAAA,CACF,EAAA,CACF;AAAA,IAIF,gBAAAtG,EAAC,SAAI,WAAU,0BACZ,YAAS,OAAO,IAAI,CAACgB,GAAO,MAAM;AACjC,YAAMuF,IAAWhC,MAAgB,GAC3BiC,IAAShC,EAAQxD,EAAM,OAAO,GAC9ByF,IAAiB/B,MAAmB1D,EAAM;AAEhD,aACE,gBAAA6B;AAAA,QAAC;AAAA,QAAA;AAAA,UAEC,WAAW,qEACT0D,IAAW,kBAAkB,oBAC/B;AAAA,UAGA,UAAA;AAAA,YAAA,gBAAAvG,EAAC,OAAA,EAAI,WAAU,kCACZ,UAAAyG,IACC,gBAAAzG,EAAC,SAAI,WAAU,yBAAwB,SAAQ,aAAY,MAAK,gBAC9D,4BAAC,WAAA,EAAQ,QAAO,qBAAA,CAAqB,EAAA,CACvC,IAEA,gBAAAA,EAAC,UAAK,WAAU,+BAA+B,UAAA,IAAI,EAAA,CAAE,GAEzD;AAAA,YAKA,gBAAA6C,EAAC,OAAA,EAAI,WAAU,qBACX,UAAA;AAAA,cAAA,gBAAA7C;AAAA,gBAAC;AAAA,gBAAA;AAAA,kBACC,KAAK0G,EAAiB1F,EAAM,SAASA,EAAM,SAAS;AAAA,kBACpD,KAAI;AAAA,kBACJ,SAAQ;AAAA,kBACR,SAAS,CAAA4B,MAAK;AACZ,0BAAM+D,IAAK/D,EAAE;AAGb,qBAAC+D,EAAG,iBAAiBA,GAAI,MAAM,UAAU;AAAA,kBAC3C;AAAA,kBACA,WAAU;AAAA,gBAAA;AAAA,cAAA;AAAA,cAEX3F,EAAM,aACL,gBAAAhB,EAAC,QAAA,EAAK,WAAU,mHACb,UAAAsD,GAAetC,EAAM,UAAU,EAAA,CAClC,IACE;AAAA,YAAA,GACR;AAAA,YAGA,gBAAAhB;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,WAAW,8EACT4B,IACI,wCACA,oCACN,IAAI2E,IAAW,0BAA0B,EAAE;AAAA,gBAC3C,SAAS3E,IAAW,MAAMgF,EAAmB,CAAC,IAAI;AAAA,gBAClD,OAAOhF,IAAW,SAASZ,EAAM,WAAW,KAAKA,EAAM;AAAA,gBACvD,UAAU,CAACY;AAAA,gBAEV,UAAAZ,EAAM;AAAA,cAAA;AAAA,YAAA;AAAA,YAIR6E,KACC,gBAAA7F,EAAC,OAAA,EAAI,WAAU,yBACZ,UAAAqD,EAAa,IAAI,CAAAwD,MAAO;AACvB,oBAAMC,IAAaN,MAAWK,EAAI;AAClC,qBACE,gBAAA7G;AAAA,gBAAC;AAAA,gBAAA;AAAA,kBAEC,SAAS,MAAM+F,EAAW/E,EAAM,SAAS6F,EAAI,KAAK;AAAA,kBAClD,OAAOA,EAAI;AAAA,kBACX,WAAU;AAAA,kBACV,aAAWA,EAAI;AAAA,kBACf,iBAAeC,IAAa,KAAK;AAAA,kBACjC,gBAAcA;AAAA,kBAEb,UAAAD,EAAI;AAAA,gBAAA;AAAA,gBARAA,EAAI;AAAA,cAAA;AAAA,YAWf,CAAC,EAAA,CACH;AAAA,UAAA;AAAA,QAAA;AAAA,QAxEG7F,EAAM;AAAA,MAAA;AAAA,IA4EjB,CAAC,EAAA,CACH;AAAA,IAGC6E,KAAYlB,KAAe9D,KAC1B,gBAAAb;AAAA,MAACwB;AAAA,MAAA;AAAA,QACC,SAAAX;AAAA,QACA,SAAS,MAAM+D,EAAe,EAAK;AAAA,MAAA;AAAA,IAAA;AAAA,EACrC,GAEJ;AAEJ;"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as d, jsxs as A, Fragment as Se } from "react/jsx-runtime";
|
|
2
2
|
import dt, { useState as $, useRef as P, useEffect as L, useCallback as H, useMemo as Ce, Component as Hr, useContext as jr, createContext as Gr, useId as Wr, useEffectEvent as fe } from "react";
|
|
3
|
-
import { k as Vr, M as Kr, m as zr, f as Jr, a as Qr, C as ut, g as zt, i as qr, S as Yr, r as yt, d as E, e as ee, u as x, b as et, A as tt, D as it, w as C, c as Xr, h as Zr, s as $n, j as Pn, l as Bn, n as es, L as ts, o as ns, p as rs, q as Jt, t as ss, v as as, x as is, y as os, z as Dn, B as Qt, E as On, F as Un, G as cs, H as ls, I as Ie, J as dn, T as At, K as ds, N as Hn, O as us, P as fs, Q as jn, R as Y, U as hs, V as Gn, W as Wn, X as Ye, Y as ps, Z as ms, _ as gs, $ as bs, a0 as ys, a1 as un, a2 as Ke, a3 as vs, a4 as fn, a5 as Ss, a6 as ws, a7 as hn, a8 as xs, a9 as Es, aa as Ms, ab as ks, ac as _s, ad as pn, ae as mn, af as As, ag as Vn, ah as gn, ai as Ts, aj as mt, ak as Is, al as Cs, am as Rs, an as Fs, ao as Ns, ap as Ls, aq as $s, ar as Ps, as as Bs, at as Ds } from "./index-
|
|
3
|
+
import { k as Vr, M as Kr, m as zr, f as Jr, a as Qr, C as ut, g as zt, i as qr, S as Yr, r as yt, d as E, e as ee, u as x, b as et, A as tt, D as it, w as C, c as Xr, h as Zr, s as $n, j as Pn, l as Bn, n as es, L as ts, o as ns, p as rs, q as Jt, t as ss, v as as, x as is, y as os, z as Dn, B as Qt, E as On, F as Un, G as cs, H as ls, I as Ie, J as dn, T as At, K as ds, N as Hn, O as us, P as fs, Q as jn, R as Y, U as hs, V as Gn, W as Wn, X as Ye, Y as ps, Z as ms, _ as gs, $ as bs, a0 as ys, a1 as un, a2 as Ke, a3 as vs, a4 as fn, a5 as Ss, a6 as ws, a7 as hn, a8 as xs, a9 as Es, aa as Ms, ab as ks, ac as _s, ad as pn, ae as mn, af as As, ag as Vn, ah as gn, ai as Ts, aj as mt, ak as Is, al as Cs, am as Rs, an as Fs, ao as Ns, ap as Ls, aq as $s, ar as Ps, as as Bs, at as Ds } from "./index-CdBFUWZq.js";
|
|
4
4
|
import { createPortal as St } from "react-dom";
|
|
5
5
|
import { Icon as ft, THEMES as Os, useHadokuThemeOptional as Us } from "@wolffm/themes";
|
|
6
6
|
import { HadokuThemePicker as Hs, ConnectedSettings as js } from "@wolffm/task-ui-components";
|
|
@@ -1002,7 +1002,7 @@ function $a({ draw: t }) {
|
|
|
1002
1002
|
return t(n) ?? void 0;
|
|
1003
1003
|
}, [t]), /* @__PURE__ */ d("div", { ref: e, "data-testid": "game-settings" });
|
|
1004
1004
|
}
|
|
1005
|
-
const Pa = Jt(() => import("./AdminPanel-
|
|
1005
|
+
const Pa = Jt(() => import("./AdminPanel-D6pPHN-s.js")), Ba = Jt(() => import("./PlaylistPanel-e34BfPg1.js"));
|
|
1006
1006
|
function Da() {
|
|
1007
1007
|
const t = x((p) => p.chatCollapsed), e = wt(), n = x((p) => p.sidebarTab), r = x((p) => p.setSidebarTab), s = x((p) => p.healthSnapshot?.clients.length ?? 0), i = x((p) => !!p.playlist), a = nt(), c = Zt(), o = x((p) => p.gameSettings), l = x((p) => p.snapshot.mediaId), u = xt() !== null;
|
|
1008
1008
|
if (qt({ hasMedia: !!l, chatCollapsed: t, isFullscreen: e }))
|
|
@@ -1506,7 +1506,7 @@ function ei({ client: t, pictureUrl: e, fading: n, onFadeEnd: r }) {
|
|
|
1506
1506
|
);
|
|
1507
1507
|
}
|
|
1508
1508
|
function ti({ clientId: t, sprite: e, pictureUrl: n, name: r, isReady: s, isSelf: i, onToggle: a }) {
|
|
1509
|
-
const c = et(e, n), o = s ? "
|
|
1509
|
+
const c = et(e, n), o = s ? "success" : "neutral", l = s ? /* @__PURE__ */ d(ft, { name: "tick" }) : /* @__PURE__ */ d(ft, { name: "hourglass" });
|
|
1510
1510
|
return /* @__PURE__ */ A(
|
|
1511
1511
|
"button",
|
|
1512
1512
|
{
|
|
@@ -1545,8 +1545,8 @@ function ti({ clientId: t, sprite: e, pictureUrl: n, name: r, isReady: s, isSelf
|
|
|
1545
1545
|
"aria-label": s ? "ready" : "pending",
|
|
1546
1546
|
className: "absolute -bottom-1 -right-1 text-xs font-bold rounded-full",
|
|
1547
1547
|
style: {
|
|
1548
|
-
background: o
|
|
1549
|
-
color:
|
|
1548
|
+
background: `var(--color-${o})`,
|
|
1549
|
+
color: `var(--color-on-${o})`,
|
|
1550
1550
|
width: 20,
|
|
1551
1551
|
height: 20,
|
|
1552
1552
|
display: "flex",
|
|
@@ -6090,7 +6090,7 @@ const Zo = (t) => {
|
|
|
6090
6090
|
id: "player",
|
|
6091
6091
|
name: "Media player",
|
|
6092
6092
|
load: () => Promise.resolve(ec)
|
|
6093
|
-
}, nc = Jt(() => import("./MediaDrawer-
|
|
6093
|
+
}, nc = Jt(() => import("./MediaDrawer-DE91ldYF.js"));
|
|
6094
6094
|
function rc() {
|
|
6095
6095
|
const t = x((u) => u.snapshot), e = x((u) => u.ensureAutoIdentity), n = xt(), r = Ce(
|
|
6096
6096
|
() => n ? Ha(n, window.location.origin) : null,
|
|
@@ -6169,4 +6169,4 @@ export {
|
|
|
6169
6169
|
pc as s,
|
|
6170
6170
|
Zt as u
|
|
6171
6171
|
};
|
|
6172
|
-
//# sourceMappingURL=Room-
|
|
6172
|
+
//# sourceMappingURL=Room-DNZSWr2-.js.map
|