Skip to content

Commit efc8b2b

Browse files
committed
Hub: Lazy loading most solved puzzle
1 parent 432a0f7 commit efc8b2b

File tree

5 files changed

+292
-55
lines changed

5 files changed

+292
-55
lines changed
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace SpeedPuzzling\Web\Component;
6+
7+
use SpeedPuzzling\Web\Query\GetMostSolvedPuzzles as GetMostSolvedPuzzlesQuery;
8+
use SpeedPuzzling\Web\Query\GetRanking;
9+
use SpeedPuzzling\Web\Results\MostSolvedPuzzle;
10+
use SpeedPuzzling\Web\Services\RetrieveLoggedUserProfile;
11+
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
12+
use Symfony\UX\LiveComponent\Attribute\LiveAction;
13+
use Symfony\UX\LiveComponent\Attribute\LiveArg;
14+
use Symfony\UX\LiveComponent\Attribute\LiveProp;
15+
use Symfony\UX\LiveComponent\DefaultActionTrait;
16+
17+
#[AsLiveComponent]
18+
final class MostSolvedPuzzles
19+
{
20+
use DefaultActionTrait;
21+
22+
#[LiveProp(writable: true)]
23+
public string $timespan = 'this_month';
24+
25+
#[LiveProp]
26+
public int $limit = 20;
27+
28+
#[LiveProp]
29+
public int $showLimit = 4;
30+
31+
public function __construct(
32+
readonly private GetMostSolvedPuzzlesQuery $getMostSolvedPuzzles,
33+
readonly private GetRanking $getRanking,
34+
readonly private RetrieveLoggedUserProfile $retrieveLoggedUserProfile,
35+
) {
36+
}
37+
38+
/**
39+
* @return array<MostSolvedPuzzle>
40+
*/
41+
public function getPuzzles(): array
42+
{
43+
return match ($this->timespan) {
44+
'this_month' => $this->getMostSolvedPuzzles->topInMonth(
45+
$this->limit,
46+
$this->getCurrentMonth(),
47+
$this->getCurrentYear(),
48+
),
49+
'last_month' => $this->getMostSolvedPuzzles->topInMonth(
50+
$this->limit,
51+
$this->getLastMonth(),
52+
$this->getLastYear(),
53+
),
54+
'all_time' => $this->getMostSolvedPuzzles->top($this->limit),
55+
default => [],
56+
};
57+
}
58+
59+
/**
60+
* @return array<string, mixed>
61+
*/
62+
public function getRanking(): array
63+
{
64+
$profile = $this->retrieveLoggedUserProfile->getProfile();
65+
66+
if ($profile === null) {
67+
return [];
68+
}
69+
70+
return $this->getRanking->allForPlayer($profile->playerId);
71+
}
72+
73+
#[LiveAction]
74+
public function changeTimespan(#[LiveArg] string $timespan): void
75+
{
76+
if (in_array($timespan, ['this_month', 'last_month', 'all_time'], true)) {
77+
$this->timespan = $timespan;
78+
}
79+
}
80+
81+
private function getCurrentMonth(): int
82+
{
83+
return (int) date('m');
84+
}
85+
86+
private function getCurrentYear(): int
87+
{
88+
return (int) date('Y');
89+
}
90+
91+
private function getLastMonth(): int
92+
{
93+
$currentMonth = $this->getCurrentMonth();
94+
95+
if ($currentMonth === 1) {
96+
return 12;
97+
}
98+
99+
return $currentMonth - 1;
100+
}
101+
102+
private function getLastYear(): int
103+
{
104+
$currentMonth = $this->getCurrentMonth();
105+
106+
if ($currentMonth === 1) {
107+
return $this->getCurrentYear() - 1;
108+
}
109+
110+
return $this->getCurrentYear();
111+
}
112+
}

src/Controller/HubController.php

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
namespace SpeedPuzzling\Web\Controller;
66

7-
use SpeedPuzzling\Web\Query\GetMostSolvedPuzzles;
87
use SpeedPuzzling\Web\Query\GetStatistics;
98
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
109
use Symfony\Component\HttpFoundation\Response;
@@ -14,7 +13,6 @@ final class HubController extends AbstractController
1413
{
1514
public function __construct(
1615
readonly private GetStatistics $getStatistics,
17-
readonly private GetMostSolvedPuzzles $getMostSolvedPuzzles,
1816
) {
1917
}
2018

@@ -43,9 +41,6 @@ public function __invoke(): Response
4341
}
4442

4543
return $this->render('hub.html.twig', [
46-
'this_month_most_solved_puzzle' => $this->getMostSolvedPuzzles->topInMonth(20, $thisMonth, $thisYear),
47-
'last_month_most_solved_puzzle' => $this->getMostSolvedPuzzles->topInMonth(20, $lastMonth, $lastYear),
48-
'all_time_most_solved_puzzle' => $this->getMostSolvedPuzzles->top(20),
4944
'this_month_global_statistics' => $this->getStatistics->globallyInMonth($thisMonth, $thisYear),
5045
'last_month_global_statistics' => $this->getStatistics->globallyInMonth($lastMonth, $lastYear),
5146
'all_time_global_statistics' => $this->getStatistics->globally(),

templates/components/MostActiveSoloPlayers.html.twig

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,5 +143,11 @@
143143
</tbody>
144144
</table>
145145
</div>
146+
147+
{% if props.showLimit > 0 %}
148+
<button class="mt-3 btn btn-outline-primary btn-sm disabled" style="width: 100px;">
149+
<span class="spinner-border spinner-border-sm"></span>
150+
</button>
151+
{% endif %}
146152
</div>
147153
{% endmacro %}
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
<div {{ attributes }}>
2+
<h3 class="h4 mt-5 mb-0">{{ 'hub.most_solved_puzzle'|trans }}</h3>
3+
4+
<ul class="nav nav-fill nav-tabs mt-0" role="tablist">
5+
<li class="nav-item">
6+
<a class="nav-link {{ this.timespan == 'this_month' ? 'active' }}"
7+
href="#"
8+
data-action="live#action"
9+
data-live-action-param="changeTimespan"
10+
data-live-timespan-param="this_month"
11+
>
12+
<div class="text-center">
13+
<h6 class="media-tab-title text-nowrap pt-2 mb-0">{{ 'hub.this_month'|trans|raw }}</h6>
14+
</div>
15+
</a>
16+
</li>
17+
18+
<li class="nav-item">
19+
<a class="nav-link {{ this.timespan == 'last_month' ? 'active' }}"
20+
href="#"
21+
data-action="live#action"
22+
data-live-action-param="changeTimespan"
23+
data-live-timespan-param="last_month"
24+
>
25+
<div class="text-center">
26+
<h6 class="media-tab-title text-nowrap pt-2 mb-0">{{ 'hub.last_month'|trans|raw }}</h6>
27+
</div>
28+
</a>
29+
</li>
30+
31+
<li class="nav-item">
32+
<a class="nav-link {{ this.timespan == 'all_time' ? 'active' }}"
33+
href="#"
34+
data-action="live#action"
35+
data-live-action-param="changeTimespan"
36+
data-live-timespan-param="all_time"
37+
>
38+
<div class="text-center">
39+
<h6 class="media-tab-title text-nowrap pt-2 mb-0">{{ 'hub.all_time'|trans|raw }}</h6>
40+
</div>
41+
</a>
42+
</li>
43+
</ul>
44+
45+
<div data-controller="show-more-recent-activity">
46+
<div class="table-responsive custom-table-wrapper">
47+
<table class="table table-striped table-hover mb-0">
48+
<tbody>
49+
{% set ranking = this.ranking %}
50+
{% for most_solved_puzzle in this.puzzles %}
51+
<tr class="{{ this.showLimit > 0 and loop.index > this.showLimit ? 'd-none' }}">
52+
<td class="text-center">
53+
<a href="{{ path('puzzle_detail', {puzzleId: most_solved_puzzle.puzzleId}) }}">
54+
<img class="rounded-2" style="max-width: 80px;max-height: 80px;" alt="{{ 'puzzle_img_alt'|trans({'%puzzle%': most_solved_puzzle.manufacturerName ~ ' ' ~ most_solved_puzzle.puzzleName}) }}" src="{{ most_solved_puzzle.puzzleImage|puzzle_image('puzzle_small') }}">
55+
</a>
56+
</td>
57+
58+
<td class="d-none d-sm-table-cell">
59+
<a class="d-inline-block" style="line-height: 120%" href="{{ path('puzzle_detail', {puzzleId: most_solved_puzzle.puzzleId}) }}">
60+
<span style="color: #111;">
61+
{{ most_solved_puzzle.manufacturerName }}<br>
62+
<small>{{ 'pieces_count'|trans({ '%count%': most_solved_puzzle.piecesCount })|raw }}</small>
63+
</span><br>
64+
65+
<small class="puzzle-title mt-2">
66+
{{ most_solved_puzzle.puzzleName }}
67+
</small>
68+
</a>
69+
</td>
70+
71+
<td>
72+
<small style="color: #000;">{{ 'puzzle_solved_times'|trans({'%count%': most_solved_puzzle.solvedTimes})|capitalize|raw }}</small><br>
73+
74+
<p class="mt-0 mb-1 d-sm-none low-line-height">
75+
<a href="{{ path('puzzle_detail', {puzzleId: most_solved_puzzle.puzzleId}) }}">
76+
<small>{{ most_solved_puzzle.manufacturerName }}</small><br>
77+
<small>{{ 'pieces_count'|trans({ '%count%': most_solved_puzzle.piecesCount })|raw }}</small>
78+
</a>
79+
</p>
80+
</td>
81+
<td class="text-end">
82+
<small class="low-line-height d-inline-block">{{ 'fastest_time_short'|trans({ '%time%': most_solved_puzzle.fastestTimeSolo|puzzlingTime }) }}</small>
83+
84+
{% if logged_user.profile is not null %}
85+
{% if ranking[most_solved_puzzle.puzzleId] is defined %}
86+
<br>
87+
<small class="mt-1 low-line-height d-inline-block">{{ 'my_time'|trans }} {{ ranking[most_solved_puzzle.puzzleId].time|puzzlingTime }}</small>
88+
{% endif %}
89+
{% endif %}
90+
</td>
91+
</tr>
92+
{% endfor %}
93+
</tbody>
94+
</table>
95+
</div>
96+
97+
{% if this.showLimit > 0 and this.puzzles|length > this.showLimit %}
98+
<button class="mt-3 btn btn-outline-primary btn-sm" data-action="click->show-more-recent-activity#revealRows" data-show-more-recent-activity-target="button">{{ 'show_more'|trans }}</button>
99+
{% endif %}
100+
</div>
101+
</div>
102+
103+
{% macro placeholder(props) %}
104+
<div>
105+
<h3 class="h4 mt-5 mb-0">{{ 'hub.most_solved_puzzle'|trans }}</h3>
106+
107+
<ul class="nav nav-fill nav-tabs mt-0" role="tablist">
108+
<li class="nav-item">
109+
<a class="nav-link active" href="#">
110+
<div class="text-center">
111+
<h6 class="media-tab-title text-nowrap pt-2 mb-0">{{ 'hub.this_month'|trans|raw }}</h6>
112+
</div>
113+
</a>
114+
</li>
115+
<li class="nav-item">
116+
<a class="nav-link" href="#">
117+
<div class="text-center">
118+
<h6 class="media-tab-title text-nowrap pt-2 mb-0">{{ 'hub.last_month'|trans|raw }}</h6>
119+
</div>
120+
</a>
121+
</li>
122+
<li class="nav-item">
123+
<a class="nav-link" href="#">
124+
<div class="text-center">
125+
<h6 class="media-tab-title text-nowrap pt-2 mb-0">{{ 'hub.all_time'|trans|raw }}</h6>
126+
</div>
127+
</a>
128+
</li>
129+
</ul>
130+
131+
<div class="table-responsive custom-table-wrapper">
132+
<table class="table table-striped table-hover mb-0 placeholder-glow">
133+
<tbody>
134+
{% set rowCount = props.showLimit > 0 ? props.showLimit : props.limit|default(4) %}
135+
{% for i in 1..rowCount %}
136+
<tr>
137+
<td class="text-center">
138+
<span class="rounded-2 d-inline-block" style="width: 80px; height: 58px; background: #e9ecef;"></span>
139+
</td>
140+
<td class="d-none d-sm-table-cell">
141+
<span class="d-inline-block" style="line-height: 120%;">
142+
<span style="color: #111;">
143+
<span class="placeholder" style="width: 85px;"></span><br>
144+
<small><span class="placeholder placeholder-sm" style="width: 55px;"></span></small>
145+
</span><br>
146+
<small class="puzzle-title mt-2"><span class="placeholder placeholder-sm" style="width: 110px;"></span></small>
147+
</span>
148+
</td>
149+
<td>
150+
<small><span class="placeholder" style="width: 75px;"></span></small><br>
151+
<p class="mt-0 mb-1 d-sm-none low-line-height">
152+
<small><span class="placeholder placeholder-sm" style="width: 65px;"></span></small><br>
153+
<small><span class="placeholder placeholder-sm" style="width: 45px;"></span></small>
154+
</p>
155+
</td>
156+
<td class="text-end">
157+
<small class="low-line-height d-inline-block">
158+
<span class="placeholder placeholder-sm" style="width: 70px;"></span>
159+
</small>
160+
</td>
161+
</tr>
162+
{% endfor %}
163+
</tbody>
164+
</table>
165+
</div>
166+
167+
{% if props.showLimit > 0 %}
168+
<button class="mt-3 btn btn-outline-primary btn-sm disabled" style="width: 100px;">
169+
<span class="spinner-border spinner-border-sm"></span>
170+
</button>
171+
{% endif %}
172+
</div>
173+
{% endmacro %}

templates/hub.html.twig

Lines changed: 1 addition & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -59,56 +59,7 @@
5959
</div>
6060

6161
<div class="col-lg-6">
62-
<h3 class="h4 mt-5 mb-0">{{ 'hub.most_solved_puzzle'|trans }}</h3>
63-
64-
<ul class="nav nav-fill nav-tabs mt-0" role="tablist" data-controller="tabs">
65-
<li class="nav-item">
66-
<a class="nav-link active" href="#most-solved-puzzle-this-month" data-bs-toggle="tab" role="tab" data-action="tabs#showTab">
67-
<div class="text-center">
68-
<h6 class="media-tab-title text-nowrap pt-2 mb-0">{{ 'hub.this_month'|trans|raw }}</h6>
69-
</div>
70-
</a>
71-
</li>
72-
73-
<li class="nav-item">
74-
<a class="nav-link" href="#most-solved-puzzle-last-month" data-bs-toggle="tab" role="tab" data-action="tabs#showTab">
75-
<div class="text-center">
76-
<h6 class="media-tab-title text-nowrap pt-2 mb-0">{{ 'hub.last_month'|trans|raw }}</h6>
77-
</div>
78-
</a>
79-
</li>
80-
81-
<li class="nav-item">
82-
<a class="nav-link" href="#most-solved-puzzle-all-time" data-bs-toggle="tab" role="tab" data-action="tabs#showTab">
83-
<div class="text-center">
84-
<h6 class="media-tab-title text-nowrap pt-2 mb-0">{{ 'hub.all_time'|trans|raw }}</h6>
85-
</div>
86-
</a>
87-
</li>
88-
</ul>
89-
90-
<div class="tab-content">
91-
<div class="tab-pane fade show active" id="most-solved-puzzle-this-month" role="tabpanel">
92-
{{ include('_most_solved_puzzle.html.twig', {
93-
'most_solved_puzzles': this_month_most_solved_puzzle,
94-
show_limit: 4,
95-
}) }}
96-
</div>
97-
98-
<div class="tab-pane fade" id="most-solved-puzzle-last-month" role="tabpanel">
99-
{{ include('_most_solved_puzzle.html.twig', {
100-
'most_solved_puzzles': last_month_most_solved_puzzle,
101-
show_limit: 4,
102-
}) }}
103-
</div>
104-
105-
<div class="tab-pane fade" id="most-solved-puzzle-all-time" role="tabpanel">
106-
{{ include('_most_solved_puzzle.html.twig', {
107-
'most_solved_puzzles': all_time_most_solved_puzzle,
108-
show_limit: 4,
109-
}) }}
110-
</div>
111-
</div>
62+
<twig:MostSolvedPuzzles loading="lazy" :showLimit="4" />
11263
</div>
11364

11465
<div class="col-lg-6">

0 commit comments

Comments
 (0)