Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(manage): add leaderboard management to game resources #2576

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/Filament/Pages/ResourceAuditLog.php
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ protected function getEventColor(string $event): string
'deleted' => 'danger',
'pivotAttached' => 'info',
'pivotDetached' => 'warning',
'resetAllLeaderboardEntries' => 'danger',
default => 'info',
};
}
Expand Down
1 change: 0 additions & 1 deletion app/Filament/Resources/AchievementResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,6 @@ public static function getPages(): array
{
return [
'index' => Pages\Index::route('/'),
'create' => Pages\Create::route('/create'),
'view' => Pages\Details::route('/{record}'),
'edit' => Pages\Edit::route('/{record}/edit'),
'audit-log' => Pages\AuditLog::route('/{record}/audit-log'),
Expand Down
13 changes: 0 additions & 13 deletions app/Filament/Resources/AchievementResource/Pages/Create.php

This file was deleted.

2 changes: 2 additions & 0 deletions app/Filament/Resources/GameResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use App\Filament\Resources\GameResource\Pages;
use App\Filament\Resources\GameResource\RelationManagers\AchievementsRelationManager;
use App\Filament\Resources\GameResource\RelationManagers\GameHashesRelationManager;
use App\Filament\Resources\GameResource\RelationManagers\LeaderboardsRelationManager;
use App\Filament\Resources\GameResource\RelationManagers\MemoryNotesRelationManager;
use App\Filament\Rules\ExistsInForumTopics;
use App\Filament\Rules\IsAllowedGuideUrl;
Expand Down Expand Up @@ -449,6 +450,7 @@ public static function getRelations(): array
{
return [
AchievementsRelationManager::class,
LeaderboardsRelationManager::class,
GameHashesRelationManager::class,
MemoryNotesRelationManager::class,
];
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
<?php

declare(strict_types=1);

namespace App\Filament\Resources\GameResource\RelationManagers;

use App\Models\Game;
use App\Models\Leaderboard;
use App\Models\User;
use App\Platform\Enums\ValueFormat;
use Filament\Forms\Form;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Tables;
use Filament\Tables\Actions\Action;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;

class LeaderboardsRelationManager extends RelationManager
{
protected static string $relationship = 'leaderboards';

public static function canViewForRecord(Model $ownerRecord, string $pageClass): bool
{
/** @var User $user */
$user = auth()->user();

return $user->can('manage', Leaderboard::class);
}

public function form(Form $form): Form
{
return $form
->schema([

]);
}

public function table(Table $table): Table
{
/** @var User $user */
$user = auth()->user();

return $table
->recordTitleAttribute('title')
->columns([
Tables\Columns\TextColumn::make('ID')
->label('ID')
->searchable()
->toggleable(),

Tables\Columns\TextColumn::make('Title')
->label('Title')
->description(fn (Leaderboard $record): string => $record->description)
->searchable(),

Tables\Columns\TextColumn::make('Format')
->label('Format')
->formatStateUsing(fn (string $state) => ValueFormat::toString($state))
->toggleable(),

Tables\Columns\TextColumn::make('entries_count')
->label('Entries')
->counts('entries')
->numeric()
->toggleable(),

Tables\Columns\TextColumn::make('LowerIsBetter')
->label('Lower Is Better')
->toggleable(isToggledHiddenByDefault: true),

Tables\Columns\TextColumn::make('DisplayOrder')
->label('Display Order')
->toggleable(isToggledHiddenByDefault: true),
])
->searchPlaceholder('Search (ID, Title)')
->filters([

])
->headerActions([

])
->actions([
Tables\Actions\ActionGroup::make([
Action::make('view_entries')
->label('View entries')
->url(fn (Leaderboard $leaderboard) => route('filament.admin.resources.leaderboards.view', ['record' => $leaderboard]))
->visible(function (Leaderboard $leaderboard) {
/** @var User $user */
$user = auth()->user();

return $user->can('manage', $leaderboard) && !$user->can('update', $leaderboard);
}),

Action::make('edit')
->label('Edit')
->icon('heroicon-s-pencil')
->url(fn (Leaderboard $leaderboard) => route('filament.admin.resources.leaderboards.edit', ['record' => $leaderboard]))
->visible(function (Leaderboard $leaderboard) {
/** @var User $user */
$user = auth()->user();

return $user->can('update', $leaderboard);
}),

Action::make('reset_all_entries')
->label('Delete All Entries')
->icon('heroicon-s-trash')
->color('danger')
->requiresConfirmation()
->modalDescription("Are you sure you want to permanently delete all entries of this leaderboard?")
->action(function (Leaderboard $leaderboard) {
/** @var User $user */
$user = auth()->user();

if (!$user->can('resetAllEntries', $leaderboard)) {
return;
}

$leaderboard->entries()->delete();

activity()
->useLog('default')
->causedBy($user)
->performedOn($leaderboard)
->event('resetAllLeaderboardEntries')
->log('Reset All Leaderboard Entries');
})
->visible(function (Leaderboard $leaderboard) {
/** @var User $user */
$user = auth()->user();

return $user->can('resetAllEntries', $leaderboard);
}),

Action::make('delete_leaderboard')
->label('Delete Leaderboard')
->icon('heroicon-s-trash')
->color('danger')
->requiresConfirmation()
->modalDescription("Are you sure you want to permanently delete this leaderboard?")
->action(function (Leaderboard $leaderboard) {
/** @var User $user */
$user = auth()->user();

// TODO use soft deletes
if (!$user->can('forceDelete', $leaderboard)) {
return;
}

$leaderboard->forceDelete();
})
->visible(function (Leaderboard $leaderboard) {
/** @var User $user */
$user = auth()->user();

return $user->can('forceDelete', $leaderboard);
}),
]),
])
->bulkActions([

])
->paginated([25, 50, 100])
->defaultSort(function (Builder $query): Builder {
return $query
->orderBy('DisplayOrder')
->orderBy('Created', 'asc');
})
->reorderRecordsTriggerAction(
fn (Action $action, bool $isReordering) => $action
->button()
->label($isReordering ? 'Stop reordering' : 'Start reordering'),
)
->reorderable('DisplayOrder', $this->canReorderLeaderboards())
->checkIfRecordIsSelectableUsing(
fn (Model $record): bool => $user->can('update', $record->loadMissing('game')),
);
}

public function reorderTable(array $order): void
{
parent::reorderTable($order);

/** @var User $user */
$user = auth()->user();
/** @var Game $game */
$game = $this->getOwnerRecord();

// We don't want to flood the logs with reordering activity.
// We'll throttle these events by 10 minutes.
$recentReorderingActivity = DB::table('audit_log')
->where('causer_id', $user->id)
->where('subject_id', $game->id)
->where('subject_type', 'game')
->where('event', 'reorderedLeaderboards')
->where('created_at', '>=', now()->subMinutes(10))
->first();

// If the user didn't recently reorder leaderboards, write a new log.
if (!$recentReorderingActivity) {
activity()
->useLog('default')
->causedBy(auth()->user())
->performedOn($game)
->event('reorderedLeaderboards')
->log('Reordered Leaderboards');
}
}

private function canReorderLeaderboards(): bool
{
/** @var User $user */
$user = auth()->user();

/** @var Leaderboard $game */
$game = $this->getOwnerRecord();

return $user->can('update', $game);
}
}
58 changes: 56 additions & 2 deletions app/Filament/Resources/LeaderboardResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Filament\Forms\Form;
use Filament\Infolists;
use Filament\Infolists\Infolist;
use Filament\Pages\Page;
use Filament\Tables;
use Filament\Tables\Table;
use Illuminate\Contracts\Support\Htmlable;
Expand Down Expand Up @@ -63,9 +64,29 @@ public static function infolist(Infolist $infolist): Infolist
Infolists\Components\Section::make('Metadata')
->columns(['md' => 2, 'xl' => 3, '2xl' => 4])
->schema([
Infolists\Components\TextEntry::make('game.title')
->url(function (Leaderboard $record) {
if (request()->user()->can('manage', Game::class)) {
return GameResource::getUrl('view', ['record' => $record->game->id]);
}

return null;
})
->extraAttributes(function (): array {
if (request()->user()->can('manage', Game::class)) {
return ['class' => 'underline'];
}

return [];
}),

Infolists\Components\TextEntry::make('Title'),

Infolists\Components\TextEntry::make('game.title'),
Infolists\Components\TextEntry::make('Description'),

Infolists\Components\TextEntry::make('LowerIsBetter')
->label('Lower Is Better')
->formatStateUsing(fn (string $state): string => $state === '1' ? 'Yes' : 'No'),
]),
]);
}
Expand All @@ -74,7 +95,31 @@ public static function form(Form $form): Form
{
return $form
->schema([

Forms\Components\Section::make('Primary Details')
->icon('heroicon-m-key')
->columns(['md' => 2, 'xl' => 3, '2xl' => 4])
->schema([
Forms\Components\TextInput::make('Title')
->required()
->minLength(2)
->maxLength(255),

Forms\Components\TextInput::make('Description')
->maxLength(255),

Forms\Components\Select::make('Format')
->options(
collect(ValueFormat::cases())
->mapWithKeys(fn ($format) => [$format => ValueFormat::toString($format)])
->toArray()
)
->required(),

Forms\Components\Toggle::make('LowerIsBetter')
->label('Lower Is Better')
->inline(false)
->helperText('Useful for speedrun leaderboards and similar scenarios.'),
]),
]);
}

Expand Down Expand Up @@ -179,12 +224,21 @@ public static function getRelations(): array
];
}

public static function getRecordSubNavigation(Page $page): array
{
return $page->generateNavigationitems([
Pages\Details::class,
Pages\AuditLog::class,
]);
}

public static function getPages(): array
{
return [
'index' => Pages\Index::route('/'),
'view' => Pages\Details::route('/{record}'),
'edit' => Pages\Edit::route('/{record}/edit'),
'audit-log' => Pages\AuditLog::route('/{record}/audit-log'),
];
}

Expand Down
11 changes: 11 additions & 0 deletions app/Filament/Resources/LeaderboardResource/Pages/AuditLog.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace App\Filament\Resources\LeaderboardResource\Pages;

use App\Filament\Pages\ResourceAuditLog;
use App\Filament\Resources\LeaderboardResource;

class AuditLog extends ResourceAuditLog
{
protected static string $resource = LeaderboardResource::class;
}
Loading
Loading