Skip to content

Commit 6403b2f

Browse files
author
ghost
committed
implement players online monthly chart
1 parent 4d95117 commit 6403b2f

File tree

8 files changed

+285
-10
lines changed

8 files changed

+285
-10
lines changed

.env

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,13 @@ MESSENGER_TRANSPORT_DSN=doctrine://default?auto_setup=0
4343
# HLState
4444

4545
# Application version, used for API and media cache
46-
APP_VERSION="1.3.0"
46+
APP_VERSION="1.4.0"
47+
48+
# Memcached server
49+
APP_MEMCACHED_NAMESPACE="HLState"
50+
APP_MEMCACHED_HOST="localhost"
51+
APP_MEMCACHED_PORT=11211
52+
APP_MEMCACHED_TIMEOUT=3600
4753

4854
# Application name
4955
APP_NAME="HLState"

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Project initially written to explore [Yggdrasil](https://github.com/yggdrasil-ne
2121

2222
## Install
2323

24-
* `apt install git composer curl php php-xml php-intl php-mbstring php-curl php-sqlite3`
24+
* `apt install git composer curl memcached php php-xml php-intl php-mbstring php-curl php-sqlite3 php-memcached`
2525
* `git clone https://github.com/YGGverse/HLState.git`
2626
* `cd HLState`
2727
* `composer install`
@@ -62,6 +62,8 @@ Please create new branch from main before make PR
6262
* [SVG icons](https://icons.getbootstrap.com)
6363
* [PHP Source Query](https://github.com/xPaw/PHP-Source-Query)
6464
* [HL-PHP](https://github.com/YGGverse/hl-php)
65+
* [JS-less Graphs PHP](https://github.com/YGGverse/graph-php)
66+
* [Memcached API for PHP](https://github.com/YGGverse/cache-php)
6567
* [Favicons](https://realfavicongenerator.net)
6668

6769
## Support

composer.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@
4545
"twig/intl-extra": "^3.8",
4646
"twig/twig": "^3.8",
4747
"xpaw/php-source-query-class": "dev-master",
48+
"yggverse/cache": "^0.3.1",
49+
"yggverse/graph": "^0.2.2",
4850
"yggverse/hl": "^1.0"
4951
},
5052
"config": {

config/services.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
# Put parameters here that don't need to change on each machine where the app is deployed
55
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
66
parameters:
7+
app.memcached.host: '%env(APP_MEMCACHED_HOST)%'
8+
app.memcached.port: '%env(APP_MEMCACHED_PORT)%'
9+
app.memcached.namespace: '%env(APP_MEMCACHED_NAMESPACE)%'
10+
app.memcached.timeout: '%env(APP_MEMCACHED_TIMEOUT)%'
711
app.version: '%env(APP_VERSION)%'
812
app.name: '%env(APP_NAME)%'
913
app.theme: '%env(APP_THEME)%'

public/css/default.css

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
--color-warning: #f37b21;
1717
--color-error: #ff6363;
1818
--color-default: #999;
19+
20+
--background-color-hover-default: rgba(125, 125, 125, 0.1);
1921
}
2022

2123
*::placeholder
@@ -65,6 +67,10 @@ table td
6567
padding: 4px;
6668
}
6769

70+
table tr:hover td {
71+
background-color: var(--background-color-hover-default);
72+
}
73+
6874
ul
6975
{
7076
margin-left: 16px;
@@ -143,6 +149,26 @@ a.color-default:visited
143149
color: var(--color-default);
144150
}
145151

152+
.background-color-success
153+
{
154+
background-color: var(--color-success);
155+
}
156+
157+
.background-color-warning
158+
{
159+
background-color: var(--color-warning);
160+
}
161+
162+
.background-color-error
163+
{
164+
background-color: var(--color-error);
165+
}
166+
167+
.background-color-default
168+
{
169+
background-color: var(--color-default);
170+
}
171+
146172
.text-align-left
147173
{
148174
text-align: left;
@@ -188,4 +214,84 @@ a.color-default:visited
188214
{
189215
margin-bottom: 8px;
190216
margin-top: 8px;
217+
}
218+
219+
/*
220+
* yggverse/graph UI
221+
*
222+
* for any feedback visit official page:
223+
* https://github.com/YGGverse/graph-php
224+
*
225+
*/
226+
227+
.calendar__month {
228+
overflow: hidden
229+
}
230+
231+
.calendar__month > .day {
232+
float: left;
233+
height: 96px;
234+
margin: 2px 0;
235+
position: relative;
236+
width: 14.285714286%;
237+
}
238+
239+
.calendar__month > .day:hover {
240+
background-color: var(--background-color-hover-default);
241+
}
242+
243+
.calendar__month > .day > .number {
244+
background-color: var(--background-color-hover-default);
245+
border-radius: 50%;
246+
font-size: 10px;
247+
height: 16px;
248+
left: 4px;
249+
line-height: 16px;
250+
opacity: 0.8;
251+
position: absolute;
252+
text-align: center;
253+
top: 4px;
254+
width: 16px;
255+
z-index: 99;
256+
}
257+
258+
.calendar__month > .day:hover > .number {
259+
opacity: 1;
260+
}
261+
262+
.calendar__month > .day > .layer-0 > .label {
263+
background-color: var(--background-color-hover-default);
264+
border-radius: 3px;
265+
display: none;
266+
font-size: 10px;
267+
padding: 0 4px;
268+
position: absolute;
269+
right: 4px;
270+
top: 6px;
271+
z-index: 99;
272+
}
273+
274+
.calendar__month > .day:hover > .layer-0 > .label {
275+
display: block;
276+
}
277+
278+
.calendar__month > .day > .layer-0 > .value {
279+
bottom: 0;
280+
opacity: 0.5;
281+
position: absolute;
282+
z-index: 0;
283+
}
284+
285+
.calendar__month > .day > .layer-1 > .label {
286+
display: none
287+
}
288+
289+
.calendar__month > .day > .layer-1 > .value {
290+
bottom: 0;
291+
position: absolute;
292+
z-index: 1;
293+
}
294+
295+
.calendar__month > .day > .layer-1 > .value:hover {
296+
opacity: .8;
191297
}

src/Controller/MainController.php

Lines changed: 110 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,18 @@ class MainController extends AbstractController
2727
)]
2828
public function index(
2929
?Request $request,
30+
TranslatorInterface $translatorInterface,
3031
EntityManagerInterface $entityManagerInterface
3132
): Response
3233
{
34+
// Init memory
35+
$memory = new \Yggverse\Cache\Memory(
36+
$this->getParameter('app.memcached.host'),
37+
$this->getParameter('app.memcached.port'),
38+
$this->getParameter('app.memcached.namespace'),
39+
$this->getParameter('app.memcached.timeout') + time(),
40+
);
41+
3342
// Collect servers info
3443
$servers = [];
3544

@@ -119,11 +128,111 @@ public function index(
119128
$servers
120129
);
121130

131+
// Online calendar
132+
$time = time();
133+
134+
$month = new \Yggverse\Graph\Calendar\Month($time);
135+
136+
foreach ($month->getNodes() as $day => $node)
137+
{
138+
// Skip future days processing
139+
if ($day > date('j'))
140+
{
141+
break;
142+
}
143+
144+
// Add daily stats
145+
$total = $memory->getByMethodCallback(
146+
$entityManagerInterface->getRepository(Online::class),
147+
'getMaxPlayersByTimeInterval',
148+
[
149+
strtotime(
150+
sprintf(
151+
'%s-%s-%s 00:00',
152+
date('Y', $time),
153+
date('n', $time),
154+
$day
155+
)
156+
),
157+
strtotime(
158+
'+1 day',
159+
strtotime(
160+
sprintf(
161+
'%s-%s-%s 00:00',
162+
date('Y', $time),
163+
date('n', $time),
164+
$day
165+
)
166+
)
167+
)
168+
],
169+
time() + ($day == date('j') ? 60 : 2592000)
170+
);
171+
172+
$month->addNode(
173+
$day,
174+
$total,
175+
sprintf(
176+
$translatorInterface->trans('online %d'),
177+
$total
178+
),
179+
null,
180+
0
181+
);
182+
183+
// Add hourly stats
184+
for ($hour = 0; $hour < 24; $hour++)
185+
{
186+
$total = $memory->getByMethodCallback(
187+
$entityManagerInterface->getRepository(Online::class),
188+
'getMaxPlayersByTimeInterval',
189+
[
190+
strtotime(
191+
sprintf(
192+
'%s-%s-%s %s:00',
193+
date('Y', $time),
194+
date('n', $time),
195+
$day,
196+
$hour
197+
)
198+
),
199+
strtotime(
200+
sprintf(
201+
'%s-%s-%s %s:00',
202+
date('Y', $time),
203+
date('n', $time),
204+
$day,
205+
$hour + 1
206+
)
207+
)
208+
],
209+
time() + ($day == date('j') ? 60 : 2592000)
210+
);
211+
212+
$month->addNode(
213+
$day,
214+
$total,
215+
sprintf(
216+
$translatorInterface->trans('%s:00-%s:00 online %s'),
217+
$hour,
218+
$hour + 1,
219+
$total
220+
),
221+
'background-color-default',
222+
1
223+
);
224+
}
225+
}
226+
122227
return $this->render(
123228
'default/main/index.html.twig',
124229
[
125230
'request' => $request,
126-
'servers' => $servers
231+
'servers' => $servers,
232+
'month' =>
233+
[
234+
'online' => (array) $month->getNodes()
235+
]
127236
]
128237
);
129238
}

src/Repository/OnlineRepository.php

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,27 @@ public function getTotalByCrc32server(
2525
int $crc32server
2626
): int
2727
{
28-
return $this->createQueryBuilder('o')
29-
->select('count(o.id)')
30-
->where('o.crc32server = :crc32server')
31-
->setParameter('crc32server', $crc32server)
32-
->getQuery()
33-
->getSingleScalarResult()
34-
;
28+
return
29+
$this->createQueryBuilder('o')
30+
->select('count(o.id)')
31+
->where('o.crc32server = :crc32server')
32+
->setParameter('crc32server', $crc32server)
33+
->getQuery()
34+
->getSingleScalarResult();
35+
}
36+
37+
public function getMaxPlayersByTimeInterval(
38+
int $from,
39+
int $to
40+
): int
41+
{
42+
return (int)
43+
$this->createQueryBuilder('o')
44+
->select('max(o.players)')
45+
->where('o.time >= :from AND o.time <= :to')
46+
->setParameter('from', $from)
47+
->setParameter('to', $to)
48+
->getQuery()
49+
->getSingleScalarResult();
3550
}
3651
}

templates/default/main/index.html.twig

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,35 @@
9191
</tr>
9292
{% endif %}
9393
</table>
94+
<br />
95+
<h2>{{ 'now' | date('M, Y') }}</h2>
96+
<hr />
97+
<div class="padding-y-8-px calendar__month">
98+
{% for day, node in month.online %}
99+
{% if day <= 'now' | date('j') %}
100+
<div class="day">
101+
<div class="number">
102+
{{ day }}
103+
</div>
104+
{% for i, layers in node %}
105+
<div class="layer layer-{{ i }}">
106+
<div class="label">
107+
{% for layer in layers %}
108+
<div{# class="{{ layer.class }}"#}>
109+
{{ layer.label }}
110+
</div>
111+
{% endfor %}
112+
</div>
113+
{% for layer in layers %}
114+
<div title="{{ layer.label }}"
115+
class="value {{ layer.class }}"
116+
style="width:{{ layer.width }}%;height:{{ layer.height }}%;left:{{ layer.offset }}%"></div>
117+
{% endfor %}
118+
</div>
119+
{% endfor %}
120+
</div>
121+
{% endif %}
122+
{% endfor %}
123+
</div>
124+
<hr />
94125
{% endblock %}

0 commit comments

Comments
 (0)