fix: reveal grid solutions when abandoning game
This commit is contained in:
parent
9dcf43052d
commit
6e83355231
7 changed files with 97 additions and 35 deletions
|
|
@ -1,8 +1,7 @@
|
|||
import React from 'react';
|
||||
import GameRow from './GameRow';
|
||||
import ActorPopover from './ActorPopover';
|
||||
|
||||
export default function GameGrid({ grid, width, middle }) {
|
||||
export default function GameGrid({ grid, width, middle, revealed = false }) {
|
||||
return (
|
||||
<div className="game-grid-scroll">
|
||||
<table id="actors">
|
||||
|
|
@ -34,6 +33,7 @@ export default function GameGrid({ grid, width, middle }) {
|
|||
totalWidth={width}
|
||||
hintType={row.hintType}
|
||||
hintText={row.hintText}
|
||||
revealed={revealed}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ function isLetter(ch) {
|
|||
return /[a-zA-Z]/.test(ch);
|
||||
}
|
||||
|
||||
export default function GameRow({ actorName, pos, colStart, totalWidth, hintType, hintText }) {
|
||||
export default function GameRow({ actorName, pos, colStart, totalWidth, hintType, hintText, revealed = false }) {
|
||||
const inputRefs = useRef([]);
|
||||
const letters = actorName.split('');
|
||||
|
||||
|
|
@ -57,6 +57,8 @@ export default function GameRow({ actorName, pos, colStart, totalWidth, hintType
|
|||
inputRef={setInputRef(charIndex)}
|
||||
onNext={() => focusNextInput(charIndex, 1)}
|
||||
onPrev={() => focusNextInput(charIndex, -1)}
|
||||
value={revealed ? ch.toUpperCase() : undefined}
|
||||
disabled={revealed}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
import React, { useRef, useCallback } from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
export default function LetterInput({ highlighted, onNext, onPrev, inputRef }) {
|
||||
export default function LetterInput({ highlighted, onNext, onPrev, inputRef, value, disabled = false }) {
|
||||
const handleKeyUp = useCallback((e) => {
|
||||
if (disabled || value !== undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.key === 'Backspace') {
|
||||
e.target.value = '';
|
||||
onPrev?.();
|
||||
|
|
@ -9,7 +13,7 @@ export default function LetterInput({ highlighted, onNext, onPrev, inputRef }) {
|
|||
e.target.value = e.key.toUpperCase();
|
||||
onNext?.();
|
||||
}
|
||||
}, [onNext, onPrev]);
|
||||
}, [disabled, onNext, onPrev, value]);
|
||||
|
||||
return (
|
||||
<td>
|
||||
|
|
@ -18,6 +22,9 @@ export default function LetterInput({ highlighted, onNext, onPrev, inputRef }) {
|
|||
type="text"
|
||||
maxLength={1}
|
||||
className={`letter-input${highlighted ? ' letter-highlighted' : ''}`}
|
||||
value={value}
|
||||
disabled={disabled}
|
||||
readOnly={value !== undefined}
|
||||
onKeyUp={handleKeyUp}
|
||||
autoComplete="off"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -79,6 +79,12 @@ body {
|
|||
box-shadow: 0 0 0 3px rgba(234, 88, 12, 0.15);
|
||||
}
|
||||
|
||||
.letter-input:disabled {
|
||||
opacity: 1;
|
||||
cursor: default;
|
||||
-webkit-text-fill-color: currentColor;
|
||||
}
|
||||
|
||||
.letter-highlighted {
|
||||
background-color: var(--orange-light);
|
||||
border-color: var(--orange);
|
||||
|
|
@ -612,6 +618,22 @@ body {
|
|||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.game-result-banner {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
padding: 12px 16px;
|
||||
border: 1px solid var(--border-warm);
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--surface-warm);
|
||||
color: var(--text);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.btn-abandon {
|
||||
padding: 7px 16px;
|
||||
background: none;
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ class GameController extends AbstractController
|
|||
GameRepository $gameRepository,
|
||||
): Response {
|
||||
$this->validateCsrfToken('game_start', $request);
|
||||
$request->getSession()->remove('revealed_game_id');
|
||||
|
||||
/** @var User|null $user */
|
||||
$user = $this->getUser();
|
||||
|
|
@ -109,12 +110,13 @@ class GameController extends AbstractController
|
|||
|
||||
$game->abandon();
|
||||
$em->flush();
|
||||
$request->getSession()->set('revealed_game_id', $game->getId());
|
||||
|
||||
if (!$user) {
|
||||
$request->getSession()->remove('current_game_id');
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('app_homepage');
|
||||
return $this->redirectToRoute('app_homepage', ['revealed' => 1]);
|
||||
}
|
||||
|
||||
private function validateCsrfToken(string $tokenId, Request $request): void
|
||||
|
|
|
|||
|
|
@ -41,6 +41,27 @@ class HomepageController extends AbstractController
|
|||
}
|
||||
}
|
||||
|
||||
if ($game === null && $request->query->getBoolean('revealed')) {
|
||||
$revealedGameId = $request->getSession()->get('revealed_game_id');
|
||||
if (is_int($revealedGameId) || ctype_digit((string) $revealedGameId)) {
|
||||
$revealedGame = $gameRepository->find((int) $revealedGameId);
|
||||
if (
|
||||
$revealedGame instanceof Game
|
||||
&& $revealedGame->getStatus() === Game::STATUS_ABANDONED
|
||||
&& (
|
||||
($user === null && $revealedGame->getUser() === null)
|
||||
|| ($user !== null && $revealedGame->getUser() === $user)
|
||||
)
|
||||
) {
|
||||
$game = $revealedGame;
|
||||
} else {
|
||||
$request->getSession()->remove('revealed_game_id');
|
||||
}
|
||||
} else {
|
||||
$request->getSession()->remove('revealed_game_id');
|
||||
}
|
||||
}
|
||||
|
||||
if (!$game) {
|
||||
return $this->render('homepage/index.html.twig', [
|
||||
'game' => null,
|
||||
|
|
|
|||
|
|
@ -4,43 +4,51 @@
|
|||
{% if game %}
|
||||
<div class="game-container">
|
||||
<div class="game-actions">
|
||||
<div class="abandon-wrapper">
|
||||
<button type="button" class="btn btn-abandon" id="abandon-trigger">Abandonner</button>
|
||||
<div class="abandon-popover" id="abandon-popover">
|
||||
<p class="abandon-popover-text">Êtes-vous sûr de vouloir abandonner ?</p>
|
||||
<div class="abandon-popover-actions">
|
||||
<form method="post" action="{{ path('app_game_abandon', {id: game.id}) }}">
|
||||
<input type="hidden" name="_token" value="{{ csrf_token('game_abandon') }}">
|
||||
<button type="submit" class="btn btn-abandon-confirm">Abandonner</button>
|
||||
</form>
|
||||
<button type="button" class="btn btn-abandon-cancel" id="abandon-cancel">Non, continuer</button>
|
||||
{% if game.status == constant('App\\Entity\\Game::STATUS_IN_PROGRESS') %}
|
||||
<div class="abandon-wrapper">
|
||||
<button type="button" class="btn btn-abandon" id="abandon-trigger">Abandonner</button>
|
||||
<div class="abandon-popover" id="abandon-popover">
|
||||
<p class="abandon-popover-text">Êtes-vous sûr de vouloir abandonner ?</p>
|
||||
<div class="abandon-popover-actions">
|
||||
<form method="post" action="{{ path('app_game_abandon', {id: game.id}) }}">
|
||||
<input type="hidden" name="_token" value="{{ csrf_token('game_abandon') }}">
|
||||
<button type="submit" class="btn btn-abandon-confirm">Abandonner</button>
|
||||
</form>
|
||||
<button type="button" class="btn btn-abandon-cancel" id="abandon-cancel">Non, continuer</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
(function() {
|
||||
var trigger = document.getElementById('abandon-trigger');
|
||||
var popover = document.getElementById('abandon-popover');
|
||||
var cancel = document.getElementById('abandon-cancel');
|
||||
trigger.addEventListener('click', function() {
|
||||
popover.classList.toggle('open');
|
||||
});
|
||||
cancel.addEventListener('click', function() {
|
||||
popover.classList.remove('open');
|
||||
});
|
||||
document.addEventListener('click', function(e) {
|
||||
if (!popover.contains(e.target) && e.target !== trigger) {
|
||||
<script>
|
||||
(function() {
|
||||
var trigger = document.getElementById('abandon-trigger');
|
||||
var popover = document.getElementById('abandon-popover');
|
||||
var cancel = document.getElementById('abandon-cancel');
|
||||
trigger.addEventListener('click', function() {
|
||||
popover.classList.toggle('open');
|
||||
});
|
||||
cancel.addEventListener('click', function() {
|
||||
popover.classList.remove('open');
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
});
|
||||
document.addEventListener('click', function(e) {
|
||||
if (!popover.contains(e.target) && e.target !== trigger) {
|
||||
popover.classList.remove('open');
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
{% else %}
|
||||
<div class="game-result-banner">
|
||||
<span>Partie abandonnée, voici la solution.</span>
|
||||
<a href="{{ path('app_homepage') }}" class="btn btn-primary">Nouvelle partie</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div {{ react_component('GameGrid', {
|
||||
grid: grid,
|
||||
width: width,
|
||||
middle: middle,
|
||||
revealed: game.status == constant('App\\Entity\\Game::STATUS_ABANDONED'),
|
||||
}) }}></div>
|
||||
|
||||
<div class="game-footer">
|
||||
|
|
|
|||
Loading…
Reference in a new issue