Skip to content

Commit b9c4a62

Browse files
authoredFeb 14, 2025
Merge pull request #11827 from nanaya/team-leaderboard
Team member leaderboard
2 parents 28e6db6 + 931abdc commit b9c4a62

11 files changed

+218
-1
lines changed
 

Diff for: ‎app/Http/Controllers/TeamsController.php

+30
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@
77

88
namespace App\Http\Controllers;
99

10+
use App\Exceptions\InvariantException;
11+
use App\Models\Beatmap;
1012
use App\Models\Team;
13+
use App\Models\User;
1114
use App\Transformers\UserCompactTransformer;
1215
use Symfony\Component\HttpFoundation\Response;
1316

@@ -27,6 +30,11 @@ public static function pageLinks(string $current, Team $team): array
2730
'title' => osu_trans('teams.header_links.show'),
2831
'url' => route('teams.show', ['team' => $team->getKey()]),
2932
],
33+
[
34+
'active' => $current === 'leaderboard',
35+
'title' => osu_trans('teams.header_links.leaderboard'),
36+
'url' => route('teams.leaderboard', ['team' => $team->getKey()]),
37+
],
3038
];
3139

3240
if (priv_check('TeamUpdate', $team)->can()) {
@@ -66,6 +74,28 @@ public function edit(string $id): Response
6674
return ext_view('teams.edit', compact('team'));
6775
}
6876

77+
public function leaderboard(string $id, ?string $ruleset = null): Response
78+
{
79+
$team = Team::findOrFail($id);
80+
$ruleset ??= Beatmap::modeStr($team->default_ruleset_id);
81+
$statisticsRelationName = User::statisticsRelationName($ruleset);
82+
if ($statisticsRelationName === null) {
83+
throw new InvariantException(osu_trans('beatmaps.invalid_ruleset'));
84+
}
85+
$leaderboard = $team
86+
->members
87+
->loadMissing("user.{$statisticsRelationName}")
88+
->map(fn ($member) =>
89+
(
90+
$member->user->$statisticsRelationName
91+
?? $member->user->$statisticsRelationName()->make()
92+
)->setRelation('user', $member->user))
93+
->sortByDesc(['rank_score', 'total_score'])
94+
->values();
95+
96+
return ext_view('teams.leaderboard', compact('leaderboard', 'ruleset', 'team'));
97+
}
98+
6999
public function part(string $id): Response
70100
{
71101
$team = Team::findOrFail($id);

Diff for: ‎app/helpers.php

+4
Original file line numberDiff line numberDiff line change
@@ -1299,6 +1299,10 @@ function i18n_date_auto(DateTimeInterface $date, string $skeleton): string
12991299

13001300
function i18n_number_format($number, $style = null, $pattern = null, $precision = null, $locale = null)
13011301
{
1302+
if ($number === null) {
1303+
return null;
1304+
}
1305+
13021306
if ($style === null && $pattern === null && $precision === null) {
13031307
static $formatters = [];
13041308
$locale ??= App::getLocale();

Diff for: ‎resources/css/bem-index.less

+2
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,8 @@
387387
@import "bem/team-info-entries";
388388
@import "bem/team-info-entry";
389389
@import "bem/team-members";
390+
@import "bem/team-members-leaderboard";
391+
@import "bem/team-members-leaderboard-item";
390392
@import "bem/team-members-manage";
391393
@import "bem/team-settings";
392394
@import "bem/team-settings-description-preview";

Diff for: ‎resources/css/bem/team-members-leaderboard-item.less

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
2+
// See the LICENCE file in the repository root for full licence text.
3+
4+
.team-members-leaderboard-item {
5+
--gutter: 10px;
6+
align-items: center;
7+
background: hsl(var(--hsl-b3));
8+
border-radius: @border-radius--large;
9+
display: grid;
10+
font-size: @font-size--title-small;
11+
gap: 2px 10px;
12+
grid-column: 1 / -1;
13+
grid-template-columns: subgrid;
14+
padding: 4px var(--gutter);
15+
16+
&:hover {
17+
background: hsl(var(--hsl-b2));
18+
}
19+
20+
@media @desktop {
21+
--gutter: 20px;
22+
}
23+
24+
&__avatar {
25+
.default-border-radius();
26+
align-items: center;
27+
display: flex;
28+
overflow: hidden;
29+
width: 40px;
30+
}
31+
32+
&__number {
33+
font-size: @font-size--title-small-3;
34+
display: grid;
35+
grid-template-columns: subgrid;
36+
grid-column: 1 / -1;
37+
gap: var(--gutter);
38+
39+
@media @desktop {
40+
display: block;
41+
padding: 0 var(--gutter);
42+
grid-column: initial;
43+
}
44+
}
45+
46+
&__number-title {
47+
color: hsl(var(--hsl-c2));
48+
font-size: @font-size--normal;
49+
}
50+
51+
&__numbers {
52+
display: grid;
53+
grid-template-columns: auto 1fr;
54+
grid-column: 2 / -1;
55+
56+
@media @desktop {
57+
display: contents;
58+
}
59+
}
60+
61+
&__rank {
62+
text-align: end;
63+
}
64+
65+
&__username {
66+
display: flex;
67+
align-items: center;
68+
width: max-content;
69+
gap: 10px;
70+
}
71+
}

Diff for: ‎resources/css/bem/team-members-leaderboard.less

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
2+
// See the LICENCE file in the repository root for full licence text.
3+
4+
.team-members-leaderboard {
5+
display: grid;
6+
margin: 0;
7+
padding: 0;
8+
list-style: none;
9+
gap: 2px;
10+
grid-template-columns: auto 1fr;
11+
12+
@media @desktop {
13+
grid-template-columns: auto 1fr auto auto auto;
14+
}
15+
}

Diff for: ‎resources/lang/en/beatmaps.php

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
// See the LICENCE file in the repository root for full licence text.
55

66
return [
7+
'invalid_ruleset' => 'Invalid ruleset specified.',
8+
79
'change_owner' => [
810
'too_many' => 'Too many guest mappers.',
911
],

Diff for: ‎resources/lang/en/page_title.php

+1
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@
110110
'teams_controller' => [
111111
'_' => 'teams',
112112
'edit' => 'team settings',
113+
'leaderboard' => 'team leaderboard',
113114
'show' => 'team info',
114115
],
115116
'tournaments_controller' => [

Diff for: ‎resources/lang/en/teams.php

+8-1
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,20 @@
5959

6060
'header_links' => [
6161
'edit' => 'settings',
62+
'leaderboard' => 'leaderboard',
6263
'show' => 'info',
6364

6465
'members' => [
6566
'index' => 'manage members',
6667
],
6768
],
6869

70+
'leaderboard' => [
71+
'global_rank' => 'Global Rank',
72+
'performance' => 'Performance',
73+
'total_score' => 'Total Score',
74+
],
75+
6976
'members' => [
7077
'destroy' => [
7178
'success' => 'Team member removed',
@@ -118,8 +125,8 @@
118125
],
119126

120127
'sections' => [
121-
'members' => 'Members',
122128
'info' => 'Info',
129+
'members' => 'Members',
123130
],
124131
],
125132
];

Diff for: ‎resources/views/teams/_members_leaderboard.blade.php

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
{{--
2+
Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
3+
See the LICENCE file in the repository root for full licence text.
4+
--}}
5+
<ul class="team-members-leaderboard">
6+
@foreach ($leaderboard as $i => $stats)
7+
<li class="team-members-leaderboard-item">
8+
<div class="team-members-leaderboard-item__rank">
9+
#{{ i18n_number_format($i + 1) }}
10+
</div>
11+
<div class="team-members-leaderboard-item__username-container">
12+
<a
13+
class="team-members-leaderboard-item__username js-usercard"
14+
data-user-id="{{ $stats->user_id }}"
15+
href="{{ route('users.show', $stats->user_id) }}"
16+
>
17+
<span class="team-members-leaderboard-item__avatar">
18+
<span
19+
class="avatar avatar--full avatar--guest"
20+
{!! background_image($stats->user->user_avatar) !!}
21+
></span>
22+
</span>
23+
24+
{{ $stats->user->username }}
25+
</a>
26+
</div>
27+
<div class="team-members-leaderboard-item__numbers">
28+
<div class="team-members-leaderboard-item__number">
29+
<div class="team-members-leaderboard-item__number-title">
30+
{{ osu_trans('teams.leaderboard.total_score') }}
31+
</div>
32+
<div>
33+
{{ i18n_number_format($stats->total_score) }}
34+
</div>
35+
</div>
36+
<div class="team-members-leaderboard-item__number">
37+
<div class="team-members-leaderboard-item__number-title">
38+
{{ osu_trans('teams.leaderboard.performance') }}
39+
</div>
40+
<div>
41+
{{ i18n_number_format($stats->pp()) ?? '-' }}
42+
</div>
43+
</div>
44+
<div class="team-members-leaderboard-item__number">
45+
<div class="team-members-leaderboard-item__number-title">
46+
{{ osu_trans('teams.leaderboard.global_rank') }}
47+
</div>
48+
<div>
49+
{{ i18n_number_format($stats->globalRank()) ?? '-' }}
50+
</div>
51+
</div>
52+
</div>
53+
@endforeach
54+
</ul>

Diff for: ‎resources/views/teams/leaderboard.blade.php

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{{--
2+
Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
3+
See the LICENCE file in the repository root for full licence text.
4+
--}}
5+
@extends('master', [
6+
'titlePrepend' => $team->name,
7+
])
8+
9+
@section('content')
10+
@component('layout._page_header_v4', ['params' => [
11+
'backgroundImage' => $team->header()->url(),
12+
'links' => App\Http\Controllers\TeamsController::pageLinks('leaderboard', $team),
13+
'theme' => 'team',
14+
]])
15+
@slot('linksAppend')
16+
@include('objects._ruleset_selector', [
17+
'currentRuleset' => $ruleset,
18+
'urlFn' => fn ($r) => route('teams.leaderboard', ['team' => $team->getKey(), 'ruleset' => $r]),
19+
])
20+
@endslot
21+
@endcomponent
22+
23+
<div class="osu-page osu-page--generic-compact">
24+
<div class="user-profile-pages user-profile-pages--no-tabs">
25+
<div class="page-extra u-fancy-scrollbar">
26+
@include('teams._members_leaderboard')
27+
</div>
28+
</div>
29+
</div>
30+
@endsection

Diff for: ‎routes/web.php

+1
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@
300300
Route::resource('applications', 'Teams\ApplicationsController', ['only' => ['destroy', 'store']]);
301301
Route::post('applications/{application}/accept', 'Teams\ApplicationsController@accept')->name('applications.accept');
302302
Route::post('applications/{application}/reject', 'Teams\ApplicationsController@reject')->name('applications.reject');
303+
Route::get('leaderboard/{ruleset?}', 'TeamsController@leaderboard')->name('leaderboard');
303304
Route::post('part', 'TeamsController@part')->name('part');
304305
Route::resource('members', 'Teams\MembersController', ['only' => ['destroy', 'index']]);
305306
});

0 commit comments

Comments
 (0)