From e879703041f4f800305141b12b74146739c80d5e Mon Sep 17 00:00:00 2001 From: Daniel Legt Date: Thu, 5 Mar 2026 22:38:31 +0200 Subject: [PATCH] Update --- src/state/manager.go | 4 ++++ src/templates/index.html | 2 ++ src/templates/room.html | 2 ++ static/css/cards.css | 38 ++++++++++++++++++++++++++++++ static/css/layout.css | 8 ------- static/css/main.css | 35 ---------------------------- static/js/cards.js | 41 ++++++++++++++++++++++++++++++++ static/js/config.js | 38 ++++-------------------------- static/js/room.js | 50 +++++++++++----------------------------- 9 files changed, 106 insertions(+), 112 deletions(-) create mode 100644 static/css/cards.css create mode 100644 static/js/cards.js diff --git a/src/state/manager.go b/src/state/manager.go index 30570eb..8728ebd 100644 --- a/src/state/manager.go +++ b/src/state/manager.go @@ -529,6 +529,10 @@ func (m *Manager) marshalRoomState(room *Room, viewerParticipantID string) ([]by participants := make([]PublicParticipant, 0, len(room.Participants)) for _, participant := range sortParticipants(room.Participants) { + if !participant.Connected { + continue + } + public := PublicParticipant{ ID: participant.ID, Username: participant.Username, diff --git a/src/templates/index.html b/src/templates/index.html index 7be694d..c3bf564 100644 --- a/src/templates/index.html +++ b/src/templates/index.html @@ -9,6 +9,7 @@ + @@ -195,6 +196,7 @@ + diff --git a/src/templates/room.html b/src/templates/room.html index 925993d..f00514c 100644 --- a/src/templates/room.html +++ b/src/templates/room.html @@ -9,6 +9,7 @@ + @@ -161,6 +162,7 @@ + diff --git a/static/css/cards.css b/static/css/cards.css new file mode 100644 index 0000000..94fb4b1 --- /dev/null +++ b/static/css/cards.css @@ -0,0 +1,38 @@ +.vote-card, +.preview-card { + border: var(--card-border-width) solid var(--card-border); + background: var(--card-bg); + color: var(--card-text); + position: relative; + display: inline-flex; + align-items: center; + justify-content: center; +} + +.card-corner { + position: absolute; + z-index: 1; + font-weight: 700; + font-size: 0.82rem; + line-height: 1; + pointer-events: none; + user-select: none; +} + +.card-corner.top-left { + top: 0.34rem; + left: 0.34rem; +} + +.card-corner.bottom-right { + right: 0.34rem; + bottom: 0.34rem; + transform: rotate(180deg); +} + +.card-center-icon { + z-index: 1; + line-height: 1; + pointer-events: none; + user-select: none; +} diff --git a/static/css/layout.css b/static/css/layout.css index bc8da5c..c4dacb4 100644 --- a/static/css/layout.css +++ b/static/css/layout.css @@ -110,10 +110,6 @@ width: 3.15rem; height: 4.45rem; border-radius: 0.32rem; - display: inline-flex; - align-items: center; - justify-content: center; - position: relative; user-select: none; cursor: grab; transition: transform 170ms ease; @@ -290,10 +286,6 @@ width: 4.3rem; height: 6rem; border-radius: 0.4rem; - position: relative; - display: inline-flex; - align-items: center; - justify-content: center; cursor: pointer; transition: transform 120ms ease; } diff --git a/static/css/main.css b/static/css/main.css index 4363f8b..03a2b02 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -158,41 +158,6 @@ input[type="number"]::-webkit-inner-spin-button { text-align: left; } -.vote-card, -.preview-card { - border: var(--card-border-width) solid var(--card-border); - background: var(--card-bg); - color: var(--card-text); -} - -.card-corner { - position: absolute; - z-index: 1; - font-weight: 700; - font-size: 0.82rem; - line-height: 1; - pointer-events: none; - user-select: none; -} - -.card-corner.top-left { - top: 0.34rem; - left: 0.34rem; -} - -.card-corner.bottom-right { - right: 0.34rem; - bottom: 0.34rem; - transform: rotate(180deg); -} - -.card-center-icon { - z-index: 1; - line-height: 1; - pointer-events: none; - user-select: none; -} - .vote-card.is-selected { outline: var(--selected-outline); outline-offset: -0.35rem; diff --git a/static/js/cards.js b/static/js/cards.js new file mode 100644 index 0000000..11b0988 --- /dev/null +++ b/static/js/cards.js @@ -0,0 +1,41 @@ +(() => { + const CARD_ICONS = ['★', '◆', '✦', '☀', '☘', '⚙', '♣', '♠', '♥', '♦', '✚', '⚡', '☾', '✿']; + + function iconForValue(value) { + const normalized = String(value || ''); + if (normalized === '?') return '❓'; + if (normalized === '☕') return '☕'; + if (normalized === '∞') return '∞'; + + let hash = 0; + for (let i = 0; i < normalized.length; i += 1) { + hash = (hash * 31 + normalized.charCodeAt(i)) >>> 0; + } + return CARD_ICONS[hash % CARD_ICONS.length]; + } + + function appendFace(el, value) { + const normalized = String(value || ''); + + const topLeft = document.createElement('span'); + topLeft.className = 'card-corner top-left'; + topLeft.textContent = normalized; + + const center = document.createElement('span'); + center.className = 'card-center-icon'; + center.textContent = iconForValue(normalized); + + const bottomRight = document.createElement('span'); + bottomRight.className = 'card-corner bottom-right'; + bottomRight.textContent = normalized; + + el.appendChild(topLeft); + el.appendChild(center); + el.appendChild(bottomRight); + } + + window.CardUI = { + iconForValue, + appendFace, + }; +})(); diff --git a/static/js/config.js b/static/js/config.js index d160215..8c3e712 100644 --- a/static/js/config.js +++ b/static/js/config.js @@ -13,8 +13,6 @@ const SPECIAL_CARD_ORDER = { '☕': 3, }; -const CARD_ICONS = ['★', '◆', '✦', '☀', '☘', '⚙', '♣', '♠', '♥', '♦', '✚', '⚡', '☾', '✿']; - const roomConfigForm = document.getElementById('room-config-form'); const statusLine = document.getElementById('config-status'); const scaleSelect = document.getElementById('estimation-scale'); @@ -49,6 +47,10 @@ if (savedUsername && !usernameInput.value) { usernameInput.value = savedUsername; } +if (!window.CardUI || typeof window.CardUI.appendFace !== 'function') { + throw new Error('CardUI is not loaded. Ensure /static/js/cards.js is included before config.js.'); +} + function parseNumericCard(value) { if (!/^-?\d+(\.\d+)?$/.test(value)) { @@ -84,36 +86,6 @@ function createCard(value) { return { id: String(nextCardID++), value: value.toString() }; } -function iconForCard(value) { - if (value === '?') return '❓'; - if (value === '☕') return '☕'; - if (value === '∞') return '∞'; - - let hash = 0; - for (let i = 0; i < value.length; i += 1) { - hash = (hash * 31 + value.charCodeAt(i)) >>> 0; - } - return CARD_ICONS[hash % CARD_ICONS.length]; -} - -function appendCardFace(el, value) { - const topLeft = document.createElement('span'); - topLeft.className = 'card-corner top-left'; - topLeft.textContent = value; - - const center = document.createElement('span'); - center.className = 'card-center-icon'; - center.textContent = iconForCard(value); - - const bottomRight = document.createElement('span'); - bottomRight.className = 'card-corner bottom-right'; - bottomRight.textContent = value; - - el.appendChild(topLeft); - el.appendChild(center); - el.appendChild(bottomRight); -} - function getCardsForScale(scale) { return (SCALE_PRESETS[scale] || SCALE_PRESETS.fibonacci).map(createCard); } @@ -190,7 +162,7 @@ function buildCardElement(card) { cardEl.className = 'preview-card'; cardEl.dataset.cardId = card.id; cardEl.draggable = true; - appendCardFace(cardEl, card.value); + window.CardUI.appendFace(cardEl, card.value); const removeBtn = document.createElement('button'); removeBtn.type = 'button'; diff --git a/static/js/room.js b/static/js/room.js index 5c58419..0527714 100644 --- a/static/js/room.js +++ b/static/js/room.js @@ -1,5 +1,4 @@ const USERNAME_KEY = 'scrumPoker.username'; -const CARD_ICONS = ['★', '◆', '✦', '☀', '☘', '⚙', '♣', '♠', '♥', '♦', '✚', '⚡', '☾', '✿']; const roomID = document.body.dataset.roomId; const params = new URLSearchParams(window.location.search); @@ -37,6 +36,10 @@ const savedUsername = localStorage.getItem(USERNAME_KEY) || ''; joinUsernameInput.value = savedUsername; joinAdminTokenInput.value = adminToken; +if (!window.CardUI || typeof window.CardUI.appendFace !== 'function') { + throw new Error('CardUI is not loaded. Ensure /static/js/cards.js is included before room.js.'); +} + function setJoinError(message) { if (!message) { @@ -100,7 +103,8 @@ async function joinRoom({ username, role, password, participantIdOverride }) { function renderParticipants(participants, isRevealed) { participantList.innerHTML = ''; - participants.forEach((participant) => { + const visibleParticipants = participants.filter((participant) => participant.connected); + visibleParticipants.forEach((participant) => { const item = document.createElement('li'); item.className = 'participant-item'; @@ -138,41 +142,15 @@ function parseNumericVote(value) { return Number(value); } -function iconForCard(value) { - if (value === '?') return '❓'; - if (value === '☕') return '☕'; - if (value === '∞') return '∞'; - - let hash = 0; - for (let i = 0; i < value.length; i += 1) { - hash = (hash * 31 + value.charCodeAt(i)) >>> 0; - } - return CARD_ICONS[hash % CARD_ICONS.length]; -} - -function appendCardFace(el, value) { - const topLeft = document.createElement('span'); - topLeft.className = 'card-corner top-left'; - topLeft.textContent = value; - - const center = document.createElement('span'); - center.className = 'card-center-icon'; - center.textContent = iconForCard(value); - - const bottomRight = document.createElement('span'); - bottomRight.className = 'card-corner bottom-right'; - bottomRight.textContent = value; - - el.appendChild(topLeft); - el.appendChild(center); - el.appendChild(bottomRight); -} - function calculateSummary(state) { const rows = new Map(); const numericVotes = []; state.participants.forEach((participant) => { + if (!participant.connected) { + return; + } + if (participant.role !== 'participant' || !participant.hasVoted || !participant.voteValue) { return; } @@ -242,7 +220,7 @@ function renderSummary(state) { } function renderCards(cards, participants, isRevealed) { - const self = participants.find((participant) => participant.id === participantID); + const self = participants.find((participant) => participant.id === participantID && participant.connected); const canVote = self && self.role === 'participant'; const selfVote = self ? self.voteValue : ''; @@ -253,7 +231,7 @@ function renderCards(cards, participants, isRevealed) { card.type = 'button'; card.className = 'vote-card'; card.setAttribute('aria-label', `Vote ${value}`); - appendCardFace(card, value); + window.CardUI.appendFace(card, value); if (selfVote === value && !isRevealed) { card.classList.add('is-selected'); @@ -289,8 +267,8 @@ function renderState(state) { adminControls.classList.add('hidden'); } - const votedCount = state.participants.filter((p) => p.role === 'participant' && p.hasVoted).length; - const totalParticipants = state.participants.filter((p) => p.role === 'participant').length; + const votedCount = state.participants.filter((p) => p.connected && p.role === 'participant' && p.hasVoted).length; + const totalParticipants = state.participants.filter((p) => p.connected && p.role === 'participant').length; roomStatus.textContent = `Votes: ${votedCount}/${totalParticipants}`; }