Add player name visibility toggle and enhance display logic in lobby and results screens
Some checks failed
Publish Docker image / Push Docker image to Docker Hub (push) Failing after 3m43s
Some checks failed
Publish Docker image / Push Docker image to Docker Hub (push) Failing after 3m43s
This commit is contained in:
parent
301e08b6e6
commit
0c543a1a01
@ -179,13 +179,27 @@
|
||||
padding: 2rem
|
||||
width: 90%
|
||||
max-width: 500px
|
||||
border: 6px solid #000
|
||||
box-shadow: 10px 10px 0 rgba(0, 0, 0, 0.5)
|
||||
animation: modal-appear 0.3s ease-out
|
||||
|
||||
h2
|
||||
margin-top: 0
|
||||
color: $primary
|
||||
font-family: 'Press Start 2P', monospace
|
||||
font-size: 1.4rem
|
||||
margin-bottom: 1.5rem
|
||||
|
||||
.modal-actions
|
||||
display: flex
|
||||
justify-content: flex-end
|
||||
gap: 1rem
|
||||
margin-top: 2rem
|
||||
|
||||
@keyframes modal-appear
|
||||
0%
|
||||
transform: scale(0.8)
|
||||
opacity: 0
|
||||
100%
|
||||
transform: scale(1)
|
||||
opacity: 1
|
||||
|
@ -243,6 +243,14 @@
|
||||
padding: 0.25rem 0.5rem
|
||||
border-radius: 1rem
|
||||
background-color: rgba(255, 255, 255, 0.1)
|
||||
margin-right: 0.5rem
|
||||
|
||||
.submitter
|
||||
display: block
|
||||
font-size: 0.8rem
|
||||
color: $text-muted
|
||||
margin-top: 0.5rem
|
||||
font-style: italic
|
||||
|
||||
.versus
|
||||
font-family: 'Bangers', cursive
|
||||
|
@ -53,8 +53,17 @@
|
||||
border: 3px solid #000
|
||||
position: relative
|
||||
cursor: pointer
|
||||
box-shadow: 3px 3px 0 rgba(0, 0, 0, 0.2)
|
||||
transition: all 0.2s ease
|
||||
|
||||
&:checked:after
|
||||
&:hover
|
||||
border-color: $secondary
|
||||
|
||||
&:checked
|
||||
background-color: rgba($primary, 0.2)
|
||||
border-color: $primary
|
||||
|
||||
&:after
|
||||
content: ''
|
||||
position: absolute
|
||||
left: 6px
|
||||
@ -86,6 +95,31 @@
|
||||
gap: 1rem
|
||||
margin-top: 1.5rem
|
||||
|
||||
// Checkbox group with helper text
|
||||
.checkbox-group
|
||||
display: flex
|
||||
flex-direction: column
|
||||
margin-bottom: 1.5rem
|
||||
|
||||
.checkbox-container
|
||||
display: flex
|
||||
align-items: center
|
||||
margin-bottom: 0.5rem
|
||||
|
||||
input[type="checkbox"]
|
||||
margin-right: 0.75rem
|
||||
min-width: 24px
|
||||
height: 24px
|
||||
|
||||
label
|
||||
margin-bottom: 0
|
||||
margin-top: 0
|
||||
cursor: pointer
|
||||
font-family: 'Press Start 2P', monospace
|
||||
font-size: 0.8rem
|
||||
text-transform: uppercase
|
||||
color: $secondary
|
||||
|
||||
// Buttons
|
||||
.btn
|
||||
display: inline-flex
|
||||
|
@ -89,7 +89,7 @@ const HomeScreen = () => {
|
||||
</div>
|
||||
|
||||
<footer className="home-footer">
|
||||
<p>ich weiß doch auch nicht</p>
|
||||
<p>ik wes doch och nüsch</p>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
|
@ -2,13 +2,15 @@ import { useState, useEffect } from 'react';
|
||||
import { useGame } from '../context/GameContext';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faUsers, faGear, faPlay, faCopy, faSignOutAlt } from '@fortawesome/free-solid-svg-icons';
|
||||
import { getDisplayName } from '../utils/playerUtils';
|
||||
|
||||
const LobbyScreen = () => {
|
||||
const { lobby, currentPlayer, isHost, updateSettings, startGame, leaveLobby } = useGame();
|
||||
const [showSettings, setShowSettings] = useState(false);
|
||||
const [settings, setSettings] = useState({
|
||||
songsPerPlayer: 4,
|
||||
maxPlayers: 10
|
||||
maxPlayers: 10,
|
||||
hidePlayerNames: false
|
||||
});
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
@ -64,9 +66,11 @@ const LobbyScreen = () => {
|
||||
<div className="players-list">
|
||||
<h2><FontAwesomeIcon icon={faUsers} /> Spieler ({lobby.players.length})</h2>
|
||||
<ul>
|
||||
{lobby.players.map(player => (
|
||||
{lobby.players.map((player, index) => (
|
||||
<li key={player.id} className={`player ${!player.isConnected ? 'disconnected' : ''} ${player.id === lobby.hostId ? 'host' : ''}`}>
|
||||
{player.name} {player.id === lobby.hostId && '(Gastgeber)'} {player.id === currentPlayer.id && '(Du)'}
|
||||
{getDisplayName(player, lobby, currentPlayer, index)}
|
||||
{player.id === lobby.hostId && ' (Gastgeber)'}
|
||||
{player.id === currentPlayer.id && !lobby.settings.hidePlayerNames && ' (Du)'}
|
||||
{!player.isConnected && <span className="status-disconnected">(Getrennt)</span>}
|
||||
</li>
|
||||
))}
|
||||
@ -78,6 +82,7 @@ const LobbyScreen = () => {
|
||||
<h3><FontAwesomeIcon icon={faGear} /> Spieleinstellungen</h3>
|
||||
<p>Lieder pro Spieler: {settings.songsPerPlayer}</p>
|
||||
<p>Maximale Spieler: {settings.maxPlayers}</p>
|
||||
<p>Spielernamen verbergen: {settings.hidePlayerNames ? 'Ja' : 'Nein'}</p>
|
||||
|
||||
{isHost && (
|
||||
<button className="btn secondary" onClick={() => setShowSettings(true)}>
|
||||
@ -138,6 +143,19 @@ const LobbyScreen = () => {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group checkbox-group">
|
||||
<div className="checkbox-container">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="hidePlayerNames"
|
||||
name="hidePlayerNames"
|
||||
checked={settings.hidePlayerNames}
|
||||
onChange={handleSettingsChange}
|
||||
/>
|
||||
<label htmlFor="hidePlayerNames">Spielernamen verbergen</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="modal-actions">
|
||||
<button className="btn secondary" onClick={() => setShowSettings(false)}>
|
||||
Abbrechen
|
||||
|
@ -3,6 +3,7 @@ import { useGame } from '../context/GameContext';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faTrophy, faHome, faRedo, faChartLine } from '@fortawesome/free-solid-svg-icons';
|
||||
import YouTubeEmbed from './YouTubeEmbed';
|
||||
import { getDisplayName } from '../utils/playerUtils';
|
||||
|
||||
const ResultsScreen = () => {
|
||||
const { lobby, currentPlayer, leaveLobby } = useGame();
|
||||
@ -73,6 +74,12 @@ const ResultsScreen = () => {
|
||||
return (match && match[2].length === 11) ? match[2] : null;
|
||||
}
|
||||
|
||||
// Find the submitter from the player list
|
||||
const findSubmitter = (submitterId) => {
|
||||
if (!lobby || !lobby.players) return null;
|
||||
return lobby.players.find(player => player.id === submitterId);
|
||||
};
|
||||
|
||||
if (!winner) {
|
||||
return (
|
||||
<div className="results-screen">
|
||||
@ -81,6 +88,9 @@ const ResultsScreen = () => {
|
||||
);
|
||||
}
|
||||
|
||||
// Find the player who submitted the winning song
|
||||
const submitter = findSubmitter(winner.submittedBy);
|
||||
|
||||
return (
|
||||
<div className="results-screen">
|
||||
<header>
|
||||
@ -91,7 +101,11 @@ const ResultsScreen = () => {
|
||||
<div className="winner-info">
|
||||
<h2>{winner.title}</h2>
|
||||
<h3>von {winner.artist}</h3>
|
||||
<p className="submitter">Eingereicht von: {winner.submittedByName}</p>
|
||||
<p className="submitter">
|
||||
Eingereicht von: {submitter
|
||||
? getDisplayName(submitter, lobby, currentPlayer)
|
||||
: (lobby?.settings?.hidePlayerNames ? 'Anonymer Spieler' : winner.submittedByName)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{winnerVideoId ? (
|
||||
@ -129,6 +143,10 @@ const ResultsScreen = () => {
|
||||
const song1VideoId = getYouTubeId(battle.song1?.youtubeLink);
|
||||
const song2VideoId = battle.song2 ? getYouTubeId(battle.song2.youtubeLink) : null;
|
||||
|
||||
// Find the players who submitted the songs
|
||||
const song1Submitter = findSubmitter(battle.song1?.submittedBy);
|
||||
const song2Submitter = battle.song2 ? findSubmitter(battle.song2?.submittedBy) : null;
|
||||
|
||||
// Freilos-Runden behandeln
|
||||
const isByeRound = battle.bye === true || !battle.song2;
|
||||
|
||||
@ -144,6 +162,13 @@ const ResultsScreen = () => {
|
||||
<h5>{battle.song1.title}</h5>
|
||||
<p>{battle.song1.artist}</p>
|
||||
<span className="votes">{battle.song1Votes} Stimmen</span>
|
||||
{!lobby?.settings?.hidePlayerNames && (
|
||||
<span className="submitter">
|
||||
Eingereicht von: {song1Submitter
|
||||
? getDisplayName(song1Submitter, lobby, currentPlayer)
|
||||
: battle.song1?.submittedByName || 'Unbekannt'}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -155,6 +180,13 @@ const ResultsScreen = () => {
|
||||
<h5>{battle.song2.title}</h5>
|
||||
<p>{battle.song2.artist}</p>
|
||||
<span className="votes">{battle.song2Votes} Stimmen</span>
|
||||
{!lobby?.settings?.hidePlayerNames && (
|
||||
<span className="submitter">
|
||||
Eingereicht von: {song2Submitter
|
||||
? getDisplayName(song2Submitter, lobby, currentPlayer)
|
||||
: battle.song2?.submittedByName || 'Unbekannt'}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
|
@ -2,6 +2,7 @@ import { useState, useEffect, useRef } from 'react';
|
||||
import { useGame } from '../context/GameContext';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faPlus, faTrash, faCheck, faMusic, faVideoCamera, faSearch, faSpinner, faExternalLinkAlt, faTimes } from '@fortawesome/free-solid-svg-icons';
|
||||
import { getDisplayName } from '../utils/playerUtils';
|
||||
|
||||
const SongSubmissionScreen = () => {
|
||||
const { lobby, currentPlayer, addSong, removeSong, setPlayerReady, searchYouTube, getYouTubeMetadata } = useGame();
|
||||
@ -502,9 +503,9 @@ const SongSubmissionScreen = () => {
|
||||
<div className="player-status">
|
||||
<h4>Spieler bereit</h4>
|
||||
<ul className="players-ready-list">
|
||||
{lobby && lobby.players.map(player => (
|
||||
{lobby && lobby.players.map((player, index) => (
|
||||
<li key={player.id} className={player.isReady ? 'ready' : 'not-ready'}>
|
||||
{player.name} {player.id === currentPlayer.id && '(Du)'}
|
||||
{getDisplayName(player, lobby, currentPlayer, index)} {player.id === currentPlayer.id && lobby.settings.hidePlayerNames && '(Du)'}
|
||||
{player.isReady ? (
|
||||
<FontAwesomeIcon icon={faCheck} className="ready-icon" />
|
||||
) : (
|
||||
|
@ -4,6 +4,7 @@ import { useGame } from '../context/GameContext';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faVoteYea, faTrophy, faMusic, faCheck, faMedal, faCrown, faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
|
||||
import YouTubeEmbed from './YouTubeEmbed';
|
||||
import { getDisplayName } from '../utils/playerUtils';
|
||||
|
||||
function VotingScreen() {
|
||||
const { lobby, currentPlayer, submitVote, isHost, isOffline, isReconnecting } = useGame();
|
||||
@ -356,7 +357,7 @@ function VotingScreen() {
|
||||
|
||||
return (
|
||||
<li key={player.id} className={`${hasPlayerVoted ? 'voted' : 'not-voted'} ${isCurrentPlayerOfflineVoted ? 'offline-voted' : ''}`}>
|
||||
{player.name} {player.id === currentPlayer.id && '(Du)'}
|
||||
{getDisplayName(player, lobby, currentPlayer)} {player.id === currentPlayer.id && lobby.settings.hidePlayerNames && '(Du)'}
|
||||
{hasPlayerVoted &&
|
||||
<FontAwesomeIcon icon={faCheck} className="vote-icon" />
|
||||
}
|
||||
|
45
client/src/utils/playerUtils.js
Normal file
45
client/src/utils/playerUtils.js
Normal file
@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Utility functions for player name handling
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns an anonymous name for a player when hidePlayerNames is enabled
|
||||
* @param {string} playerId - The player's ID
|
||||
* @param {number} index - The player's index in a list (optional)
|
||||
* @param {boolean} isCurrentPlayer - Whether this is the current player
|
||||
* @returns {string} - Anonymous name or indicator
|
||||
*/
|
||||
export const getAnonymousName = (playerId, index = null, isCurrentPlayer = false) => {
|
||||
if (isCurrentPlayer) {
|
||||
return 'Du';
|
||||
}
|
||||
|
||||
// Use index-based naming if provided
|
||||
if (index !== null) {
|
||||
return `Spieler ${index + 1}`;
|
||||
}
|
||||
|
||||
// Use last 4 characters of ID as fallback
|
||||
const shortId = playerId.slice(-4);
|
||||
return `Spieler ${shortId}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the appropriate player name based on game settings
|
||||
* @param {Object} player - The player object
|
||||
* @param {Object} lobby - The lobby object
|
||||
* @param {Object} currentPlayer - The current player
|
||||
* @param {number} index - The player's index in a list (optional)
|
||||
* @returns {string} - The player name or anonymous identifier
|
||||
*/
|
||||
export const getDisplayName = (player, lobby, currentPlayer, index = null) => {
|
||||
const isCurrentPlayer = player.id === currentPlayer?.id;
|
||||
|
||||
// If hidePlayerNames is enabled
|
||||
if (lobby?.settings?.hidePlayerNames) {
|
||||
return getAnonymousName(player.id, index, isCurrentPlayer);
|
||||
}
|
||||
|
||||
// Otherwise return the actual name
|
||||
return player.name;
|
||||
};
|
@ -28,7 +28,8 @@ class GameManager {
|
||||
settings: {
|
||||
songsPerPlayer: 3,
|
||||
maxPlayers: 10,
|
||||
minPlayers: 3
|
||||
minPlayers: 3,
|
||||
hidePlayerNames: false
|
||||
},
|
||||
players: [{
|
||||
id: hostId,
|
||||
@ -225,6 +226,11 @@ class GameManager {
|
||||
return { error: 'Max players must be between 3 and 20' };
|
||||
}
|
||||
|
||||
// Validate boolean settings
|
||||
if (settings.hidePlayerNames !== undefined && typeof settings.hidePlayerNames !== 'boolean') {
|
||||
settings.hidePlayerNames = Boolean(settings.hidePlayerNames);
|
||||
}
|
||||
|
||||
// Update settings
|
||||
lobby.settings = {
|
||||
...lobby.settings,
|
||||
|
Loading…
x
Reference in New Issue
Block a user