Skip to content

Commit

Permalink
feat(manage): add leaderboard management to game resources (#2576)
Browse files Browse the repository at this point in the history
  • Loading branch information
wescopeland authored Jul 28, 2024
1 parent 3b79ccd commit cb8a6b4
Show file tree
Hide file tree
Showing 10 changed files with 354 additions and 23 deletions.
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

0 comments on commit cb8a6b4

Please sign in to comment.