From e9281a6c5d3be71f22e740a4f273ddcd2e29a081 Mon Sep 17 00:00:00 2001 From: nanaya Date: Mon, 27 Jan 2025 03:53:19 +0900 Subject: [PATCH 1/3] Team member leaderboard --- app/Http/Controllers/TeamsController.php | 30 +++++++++++ app/helpers.php | 4 ++ resources/css/bem-index.less | 2 + .../bem/team-members-leaderboard-item.less | 52 +++++++++++++++++++ .../css/bem/team-members-leaderboard.less | 11 ++++ resources/lang/en/beatmaps.php | 2 + resources/lang/en/page_title.php | 1 + resources/lang/en/teams.php | 9 +++- .../teams/_members_leaderboard.blade.php | 52 +++++++++++++++++++ resources/views/teams/leaderboard.blade.php | 45 ++++++++++++++++ routes/web.php | 1 + 11 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 resources/css/bem/team-members-leaderboard-item.less create mode 100644 resources/css/bem/team-members-leaderboard.less create mode 100644 resources/views/teams/_members_leaderboard.blade.php create mode 100644 resources/views/teams/leaderboard.blade.php diff --git a/app/Http/Controllers/TeamsController.php b/app/Http/Controllers/TeamsController.php index c58a9311823..228e76d6959 100644 --- a/app/Http/Controllers/TeamsController.php +++ b/app/Http/Controllers/TeamsController.php @@ -7,7 +7,10 @@ namespace App\Http\Controllers; +use App\Exceptions\InvariantException; +use App\Models\Beatmap; use App\Models\Team; +use App\Models\User; use App\Transformers\UserCompactTransformer; use Symfony\Component\HttpFoundation\Response; @@ -27,6 +30,11 @@ public static function pageLinks(string $current, Team $team): array 'title' => osu_trans('teams.header_links.show'), 'url' => route('teams.show', ['team' => $team->getKey()]), ], + [ + 'active' => $current === 'leaderboard', + 'title' => osu_trans('teams.header_links.leaderboard'), + 'url' => route('teams.leaderboard', ['team' => $team->getKey()]), + ], ]; if (priv_check('TeamUpdate', $team)->can()) { @@ -66,6 +74,28 @@ public function edit(string $id): Response return ext_view('teams.edit', compact('team')); } + public function leaderboard(string $id, ?string $ruleset = null): Response + { + $team = Team::findOrFail($id); + $ruleset ??= Beatmap::modeStr($team->default_ruleset_id); + $statisticsRelationName = User::statisticsRelationName($ruleset); + if ($statisticsRelationName === null) { + throw new InvariantException(osu_trans('beatmaps.invalid_ruleset')); + } + $leaderboard = $team + ->members + ->loadMissing("user.{$statisticsRelationName}") + ->map(fn ($member) => + ( + $member->user->$statisticsRelationName + ?? $member->user->$statisticsRelationName()->make() + )->setRelation('user', $member->user)) + ->sortByDesc(['rank_score', 'total_score']) + ->values(); + + return ext_view('teams.leaderboard', compact('leaderboard', 'ruleset', 'team')); + } + public function part(string $id): Response { $team = Team::findOrFail($id); diff --git a/app/helpers.php b/app/helpers.php index c7f578fb3de..64aab64d722 100644 --- a/app/helpers.php +++ b/app/helpers.php @@ -1299,6 +1299,10 @@ function i18n_date_auto(DateTimeInterface $date, string $skeleton): string function i18n_number_format($number, $style = null, $pattern = null, $precision = null, $locale = null) { + if ($number === null) { + return null; + } + if ($style === null && $pattern === null && $precision === null) { static $formatters = []; $locale ??= App::getLocale(); diff --git a/resources/css/bem-index.less b/resources/css/bem-index.less index c54c04ab27b..13422180340 100644 --- a/resources/css/bem-index.less +++ b/resources/css/bem-index.less @@ -387,6 +387,8 @@ @import "bem/team-info-entries"; @import "bem/team-info-entry"; @import "bem/team-members"; +@import "bem/team-members-leaderboard"; +@import "bem/team-members-leaderboard-item"; @import "bem/team-members-manage"; @import "bem/team-settings"; @import "bem/team-settings-description-preview"; diff --git a/resources/css/bem/team-members-leaderboard-item.less b/resources/css/bem/team-members-leaderboard-item.less new file mode 100644 index 00000000000..c3e54acbfbe --- /dev/null +++ b/resources/css/bem/team-members-leaderboard-item.less @@ -0,0 +1,52 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the GNU Affero General Public License v3.0. +// See the LICENCE file in the repository root for full licence text. + +.team-members-leaderboard-item { + --gutter: 10px; + align-items: center; + background: hsl(var(--hsl-b3)); + border-radius: @border-radius--large; + display: grid; + font-size: @font-size--title-small; + gap: 10px; + grid-column: 1 / -1; + grid-template-columns: subgrid; + padding: 4px var(--gutter); + + &:hover { + background: hsl(var(--hsl-b2)); + } + + @media @desktop { + --gutter: 20px; + } + + &__avatar { + .default-border-radius(); + align-items: center; + display: flex; + overflow: hidden; + width: 40px; + } + + &__number { + font-size: @font-size--title-small-3; + padding: 0 var(--gutter); + } + + &__number-title { + color: hsl(var(--hsl-c2)); + font-size: @font-size--normal; + } + + &__rank { + text-align: end; + } + + &__username { + display: flex; + align-items: center; + width: max-content; + gap: 10px; + } +} diff --git a/resources/css/bem/team-members-leaderboard.less b/resources/css/bem/team-members-leaderboard.less new file mode 100644 index 00000000000..8010c606ded --- /dev/null +++ b/resources/css/bem/team-members-leaderboard.less @@ -0,0 +1,11 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the GNU Affero General Public License v3.0. +// See the LICENCE file in the repository root for full licence text. + +.team-members-leaderboard { + display: grid; + margin: 0; + padding: 0; + list-style: none; + gap: 2px; + grid-template-columns: auto 1fr auto auto auto; +} diff --git a/resources/lang/en/beatmaps.php b/resources/lang/en/beatmaps.php index cd7245bd195..9ed40d3fa81 100644 --- a/resources/lang/en/beatmaps.php +++ b/resources/lang/en/beatmaps.php @@ -4,6 +4,8 @@ // See the LICENCE file in the repository root for full licence text. return [ + 'invalid_ruleset' => 'Invalid ruleset specified.', + 'change_owner' => [ 'too_many' => 'Too many guest mappers.', ], diff --git a/resources/lang/en/page_title.php b/resources/lang/en/page_title.php index 9c0db2fdbc3..91b4fdda190 100644 --- a/resources/lang/en/page_title.php +++ b/resources/lang/en/page_title.php @@ -110,6 +110,7 @@ 'teams_controller' => [ '_' => 'teams', 'edit' => 'team settings', + 'leaderboard' => 'team leaderboard', 'show' => 'team info', ], 'tournaments_controller' => [ diff --git a/resources/lang/en/teams.php b/resources/lang/en/teams.php index d7785e75068..33bf29d5ea3 100644 --- a/resources/lang/en/teams.php +++ b/resources/lang/en/teams.php @@ -59,6 +59,7 @@ 'header_links' => [ 'edit' => 'settings', + 'leaderboard' => 'leaderboard', 'show' => 'info', 'members' => [ @@ -66,6 +67,12 @@ ], ], + 'leaderboard' => [ + 'global_rank' => 'Global Rank', + 'performance' => 'Performance', + 'total_score' => 'Total Score', + ], + 'members' => [ 'destroy' => [ 'success' => 'Team member removed', @@ -118,8 +125,8 @@ ], 'sections' => [ - 'members' => 'Members', 'info' => 'Info', + 'members' => 'Members', ], ], ]; diff --git a/resources/views/teams/_members_leaderboard.blade.php b/resources/views/teams/_members_leaderboard.blade.php new file mode 100644 index 00000000000..8afbe31924e --- /dev/null +++ b/resources/views/teams/_members_leaderboard.blade.php @@ -0,0 +1,52 @@ +{{-- + Copyright (c) ppy Pty Ltd . Licensed under the GNU Affero General Public License v3.0. + See the LICENCE file in the repository root for full licence text. +--}} +
    + @foreach ($leaderboard as $i => $stats) +
  • +
    + #{{ i18n_number_format($i + 1) }} +
    + +
    +
    + {{ osu_trans('teams.leaderboard.total_score') }} +
    +
    + {{ i18n_number_format($stats->total_score) }} +
    +
    +
    +
    + {{ osu_trans('teams.leaderboard.performance') }} +
    +
    + {{ i18n_number_format($stats->pp()) ?? '-' }} +
    +
    +
    +
    + {{ osu_trans('teams.leaderboard.global_rank') }} +
    +
    + {{ i18n_number_format($stats->globalRank()) ?? '-' }} +
    +
    + @endforeach +
diff --git a/resources/views/teams/leaderboard.blade.php b/resources/views/teams/leaderboard.blade.php new file mode 100644 index 00000000000..57d0171ab39 --- /dev/null +++ b/resources/views/teams/leaderboard.blade.php @@ -0,0 +1,45 @@ +{{-- + Copyright (c) ppy Pty Ltd . Licensed under the GNU Affero General Public License v3.0. + See the LICENCE file in the repository root for full licence text. +--}} +@php + use App\Transformers\UserCompactTransformer; + + $userTransformer = new UserCompactTransformer(); + $toJson = fn ($users) => json_collection($users, $userTransformer, UserCompactTransformer::CARD_INCLUDES); + $teamMembers = array_map($toJson, $team->members->mapToGroups(fn ($member) => [ + $member->user_id === $team->leader_id ? 'leader' : 'member' => $member->userOrDeleted(), + ])->all()); + $teamMembers['member'] ??= []; + $teamMembers['leader'] ??= $toJson([$team->members()->make(['user_id' => $team->leader_id])->userOrDeleted()]); + $headerUrl = $team->header()->url(); + + $currentUser = Auth::user(); +@endphp + +@extends('master', [ + 'titlePrepend' => $team->name, +]) + +@section('content') + @component('layout._page_header_v4', ['params' => [ + 'backgroundImage' => $headerUrl, + 'links' => App\Http\Controllers\TeamsController::pageLinks('leaderboard', $team), + 'theme' => 'team', + ]]) + @slot('linksAppend') + @include('objects._ruleset_selector', [ + 'currentRuleset' => $ruleset, + 'urlFn' => fn ($r) => route('teams.leaderboard', ['team' => $team->getKey(), 'ruleset' => $r]), + ]) + @endslot + @endcomponent + +
+ +
+@endsection diff --git a/routes/web.php b/routes/web.php index a61e1d89abf..4a06aa180fd 100644 --- a/routes/web.php +++ b/routes/web.php @@ -300,6 +300,7 @@ Route::resource('applications', 'Teams\ApplicationsController', ['only' => ['destroy', 'store']]); Route::post('applications/{application}/accept', 'Teams\ApplicationsController@accept')->name('applications.accept'); Route::post('applications/{application}/reject', 'Teams\ApplicationsController@reject')->name('applications.reject'); + Route::get('leaderboard/{ruleset?}', 'TeamsController@leaderboard')->name('leaderboard'); Route::post('part', 'TeamsController@part')->name('part'); Route::resource('members', 'Teams\MembersController', ['only' => ['destroy', 'index']]); }); From 76d4781e816d1d591136c5893ef40beda9a7f4e4 Mon Sep 17 00:00:00 2001 From: nanaya Date: Fri, 14 Feb 2025 18:26:01 +0900 Subject: [PATCH 2/3] Remove leftover stuff copied from other view --- resources/views/teams/leaderboard.blade.php | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/resources/views/teams/leaderboard.blade.php b/resources/views/teams/leaderboard.blade.php index 57d0171ab39..1aceba31ae0 100644 --- a/resources/views/teams/leaderboard.blade.php +++ b/resources/views/teams/leaderboard.blade.php @@ -2,28 +2,13 @@ Copyright (c) ppy Pty Ltd . Licensed under the GNU Affero General Public License v3.0. See the LICENCE file in the repository root for full licence text. --}} -@php - use App\Transformers\UserCompactTransformer; - - $userTransformer = new UserCompactTransformer(); - $toJson = fn ($users) => json_collection($users, $userTransformer, UserCompactTransformer::CARD_INCLUDES); - $teamMembers = array_map($toJson, $team->members->mapToGroups(fn ($member) => [ - $member->user_id === $team->leader_id ? 'leader' : 'member' => $member->userOrDeleted(), - ])->all()); - $teamMembers['member'] ??= []; - $teamMembers['leader'] ??= $toJson([$team->members()->make(['user_id' => $team->leader_id])->userOrDeleted()]); - $headerUrl = $team->header()->url(); - - $currentUser = Auth::user(); -@endphp - @extends('master', [ 'titlePrepend' => $team->name, ]) @section('content') @component('layout._page_header_v4', ['params' => [ - 'backgroundImage' => $headerUrl, + 'backgroundImage' => $team->header()->url(), 'links' => App\Http\Controllers\TeamsController::pageLinks('leaderboard', $team), 'theme' => 'team', ]]) From 9fd6a913afebe8de753f12f375a565df4a6b8d8e Mon Sep 17 00:00:00 2001 From: nanaya Date: Fri, 14 Feb 2025 18:47:32 +0900 Subject: [PATCH 3/3] Mobile display...? --- .../bem/team-members-leaderboard-item.less | 23 +++++++++- .../css/bem/team-members-leaderboard.less | 6 ++- .../teams/_members_leaderboard.blade.php | 42 ++++++++++--------- 3 files changed, 48 insertions(+), 23 deletions(-) diff --git a/resources/css/bem/team-members-leaderboard-item.less b/resources/css/bem/team-members-leaderboard-item.less index c3e54acbfbe..48612b62e19 100644 --- a/resources/css/bem/team-members-leaderboard-item.less +++ b/resources/css/bem/team-members-leaderboard-item.less @@ -8,7 +8,7 @@ border-radius: @border-radius--large; display: grid; font-size: @font-size--title-small; - gap: 10px; + gap: 2px 10px; grid-column: 1 / -1; grid-template-columns: subgrid; padding: 4px var(--gutter); @@ -31,7 +31,16 @@ &__number { font-size: @font-size--title-small-3; - padding: 0 var(--gutter); + display: grid; + grid-template-columns: subgrid; + grid-column: 1 / -1; + gap: var(--gutter); + + @media @desktop { + display: block; + padding: 0 var(--gutter); + grid-column: initial; + } } &__number-title { @@ -39,6 +48,16 @@ font-size: @font-size--normal; } + &__numbers { + display: grid; + grid-template-columns: auto 1fr; + grid-column: 2 / -1; + + @media @desktop { + display: contents; + } + } + &__rank { text-align: end; } diff --git a/resources/css/bem/team-members-leaderboard.less b/resources/css/bem/team-members-leaderboard.less index 8010c606ded..cb3656da75f 100644 --- a/resources/css/bem/team-members-leaderboard.less +++ b/resources/css/bem/team-members-leaderboard.less @@ -7,5 +7,9 @@ padding: 0; list-style: none; gap: 2px; - grid-template-columns: auto 1fr auto auto auto; + grid-template-columns: auto 1fr; + + @media @desktop { + grid-template-columns: auto 1fr auto auto auto; + } } diff --git a/resources/views/teams/_members_leaderboard.blade.php b/resources/views/teams/_members_leaderboard.blade.php index 8afbe31924e..7c46ac40d10 100644 --- a/resources/views/teams/_members_leaderboard.blade.php +++ b/resources/views/teams/_members_leaderboard.blade.php @@ -24,28 +24,30 @@ class="avatar avatar--full avatar--guest" {{ $stats->user->username }} -
-
- {{ osu_trans('teams.leaderboard.total_score') }} +
+
+
+ {{ osu_trans('teams.leaderboard.total_score') }} +
+
+ {{ i18n_number_format($stats->total_score) }} +
-
- {{ i18n_number_format($stats->total_score) }} +
+
+ {{ osu_trans('teams.leaderboard.performance') }} +
+
+ {{ i18n_number_format($stats->pp()) ?? '-' }} +
-
-
-
- {{ osu_trans('teams.leaderboard.performance') }} -
-
- {{ i18n_number_format($stats->pp()) ?? '-' }} -
-
-
-
- {{ osu_trans('teams.leaderboard.global_rank') }} -
-
- {{ i18n_number_format($stats->globalRank()) ?? '-' }} +
+
+ {{ osu_trans('teams.leaderboard.global_rank') }} +
+
+ {{ i18n_number_format($stats->globalRank()) ?? '-' }} +
@endforeach