Skip to content

Commit

Permalink
feat(filament): added custom BelongsTo field & BelongsToColumn (#713)
Browse files Browse the repository at this point in the history
  • Loading branch information
Kyrch authored Jul 9, 2024
1 parent 36d7c3e commit 85878dc
Show file tree
Hide file tree
Showing 75 changed files with 547 additions and 392 deletions.
3 changes: 2 additions & 1 deletion app/Concerns/Filament/Actions/HasPivotActionLogs.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public function pivotActionLog(string $actionName, BaseRelationManager $livewire
if ($relation instanceof BelongsToMany) {
$pivotClass = $relation->getPivotClass();

// This needs to be updated/fixed for member/group artist relation
$pivot = $pivotClass::query()
->where($ownerRecord->getKeyName(), $ownerRecord->getKey())
->where($record->getKeyName(), $record->getKey())
Expand All @@ -41,7 +42,7 @@ public function pivotActionLog(string $actionName, BaseRelationManager $livewire
$actionName,
$ownerRecord,
$record,
$pivot,
$pivot ?? $record,
);
}
}
Expand Down
24 changes: 24 additions & 0 deletions app/Concerns/Filament/Actions/ModelHasActionLogs.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace App\Concerns\Filament\Actions;

use App\Models\Admin\ActionLog;
use Illuminate\Database\Eloquent\Relations\MorphMany;

/**
* Trait ModelHasActionLogs.
*/
trait ModelHasActionLogs
{
/**
* Get the action logs for the model.
*
* @return MorphMany
*/
public function actionlogs(): MorphMany
{
return $this->morphMany(ActionLog::class, 'actionable');
}
}
48 changes: 24 additions & 24 deletions app/Filament/Actions/Base/AttachAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
use App\Filament\Components\Fields\Select;
use App\Filament\RelationManagers\BaseRelationManager;
use App\Filament\RelationManagers\Wiki\ResourceRelationManager;
use App\Filament\Resources\Wiki\Artist\RelationManagers\GroupArtistRelationManager;
use App\Filament\Resources\Wiki\Artist\RelationManagers\MemberArtistRelationManager;
use App\Filament\Resources\Wiki\Artist\RelationManagers\SongArtistRelationManager;
use App\Filament\Resources\Wiki\ExternalResource\RelationManagers\AnimeResourceRelationManager;
use App\Filament\Resources\Wiki\ExternalResource\RelationManagers\ArtistResourceRelationManager;
Expand All @@ -17,7 +19,6 @@
use App\Pivots\Wiki\AnimeResource;
use App\Pivots\Wiki\ArtistSong;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
use Filament\Tables\Actions\AttachAction as DefaultAttachAction;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;

Expand Down Expand Up @@ -50,31 +51,30 @@ protected function setUp(): void
->useScout($model);
});

$this->form(function (Form $form, AttachAction $action) {
return $form
->schema([
$action->getRecordSelect(),
$this->form(fn (AttachAction $action): array => [
$action->getRecordSelect(),

TextInput::make(AnimeResource::ATTRIBUTE_AS)
->label(__('filament.fields.anime.resources.as.name'))
->helperText(__('filament.fields.anime.resources.as.help'))
->visibleOn([
AnimeResourceRelationManager::class,
ArtistResourceRelationManager::class,
SongResourceRelationManager::class,
StudioResourceRelationManager::class,
ResourceRelationManager::class,
]),
TextInput::make(AnimeResource::ATTRIBUTE_AS)
->label(__('filament.fields.anime.resources.as.name'))
->helperText(__('filament.fields.anime.resources.as.help'))
->visibleOn([
AnimeResourceRelationManager::class,
ArtistResourceRelationManager::class,
SongResourceRelationManager::class,
StudioResourceRelationManager::class,
ResourceRelationManager::class,
]),

TextInput::make(ArtistSong::ATTRIBUTE_AS)
->label(__('filament.fields.artist.songs.as.name'))
->helperText(__('filament.fields.artist.songs.as.help'))
->visibleOn([
ArtistSongRelationManager::class,
SongArtistRelationManager::class,
]),
]);
});
TextInput::make(ArtistSong::ATTRIBUTE_AS)
->label(__('filament.fields.artist.songs.as.name'))
->helperText(__('filament.fields.artist.songs.as.help'))
->visibleOn([
ArtistSongRelationManager::class,
MemberArtistRelationManager::class,
GroupArtistRelationManager::class,
SongArtistRelationManager::class,
]),
]);

$this->after(fn ($livewire, $record) => $this->pivotActionLog('Attach', $livewire, $record));
}
Expand Down
64 changes: 64 additions & 0 deletions app/Filament/Components/Columns/BelongsToColumn.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

declare(strict_types=1);

namespace App\Filament\Components\Columns;

use App\Filament\Resources\BaseResource;
use App\Models\BaseModel;
use Filament\Support\Enums\FontWeight;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;

/**
* Class BelongsToColumn.
*/
class BelongsToColumn extends TextColumn
{
protected ?BaseResource $resource = null;

/**
* This should reload after every method.
*
* @return void
*/
public function reload(): void
{
$relation = explode('.', $this->getName())[0];

$this->placeholder('-');
$this->label($this->resource->getModelLabel());
$this->tooltip(fn (BelongsToColumn $column) => is_array($column->getState()) ? null : $column->getState());
$this->weight(FontWeight::SemiBold);
$this->html();
$this->url(function (BaseModel|Model $record) use ($relation) {
foreach (explode('.', $relation) as $element) {
$record = Arr::get($record, $element);
if ($record === null) return null;
}

$this->formatStateUsing(function () use ($record) {
$name = $record->getName();
$nameLimited = Str::limit($name, $this->getCharacterLimit() ?? 100);
return "<p style='color: rgb(64, 184, 166);'>{$nameLimited}</p>";
});

return (new $this->resource)::getUrl('view', ['record' => $record]);
});
}

/**
* Set the filament resource for the relation.
*
* @param class-string<BaseResource> $resource
* @return static
*/
public function resource(string $resource): static
{
$this->resource = new $resource;
$this->reload();

return $this;
}
}
36 changes: 0 additions & 36 deletions app/Filament/Components/Columns/TextColumn.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,49 +4,13 @@

namespace App\Filament\Components\Columns;

use App\Filament\Resources\BaseResource;
use App\Models\BaseModel;
use Filament\Support\Enums\FontWeight;
use Filament\Tables\Columns\TextColumn as ColumnsTextColumn;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;

/**
* Class TextColumn.
*/
class TextColumn extends ColumnsTextColumn
{
/**
* Used for column relationships.
*
* @param class-string<BaseResource> $resourceRelated
* @param string $relation
* @param bool|null $shouldUseName
* @param int|null $limit
* @return static
*/
public function urlToRelated(string $resourceRelated, string $relation, ?bool $shouldUseName = false, ?int $limit = null): static
{
return $this
->weight(FontWeight::SemiBold)
->html()
->url(function (BaseModel|Model $record) use ($resourceRelated, $relation, $shouldUseName, $limit) {
foreach (explode('.', $relation) as $element) {
$record = Arr::get($record, $element);
if ($record === null) return null;
}

$this->formatStateUsing(function ($state) use ($shouldUseName, $record, $limit) {
$name = $shouldUseName ? $record->getName() : $state;
$nameLimited = Str::limit($name, $limit ?? 100);
return "<p style='color: rgb(64, 184, 166);'>{$nameLimited}</p>";
});

return (new $resourceRelated)::getUrl('view', ['record' => $record]);
});
}

/**
* Make the column copyable.
*
Expand Down
116 changes: 116 additions & 0 deletions app/Filament/Components/Fields/BelongsTo.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
<?php

declare(strict_types=1);

namespace App\Filament\Components\Fields;

use App\Enums\Http\Api\Filter\ComparisonOperator;
use App\Filament\Resources\BaseResource;
use App\Models\Auth\User;
use App\Models\BaseModel;
use Filament\Forms\Components\Select as ComponentsSelect;
use Filament\Forms\Form;
use Laravel\Scout\Searchable;

/**
* Class BelongsTo.
*/
class BelongsTo extends ComponentsSelect
{
protected ?BaseResource $resource = null;
protected bool $showCreateOption = false;

/**
* This should reload after every method.
*
* @return void
*/
protected function reload(): void
{
if ($this->showCreateOption && $this->resource !== null) {
$this->createOptionForm(fn (Form $form) => $this->resource::form($form)->getComponents());
}

if ($this->resource) {
$this->label($this->resource->getModelLabel());
}
}

/**
* Set the filament resource for the relation.
*
* @param class-string<BaseResource> $resource
* @return static
*/
public function resource(string $resource): static
{
$this->resource = new $resource;
$this->tryScout($this->resource->getModel());
$this->reload();

return $this;
}

/**
* Determine if the create option is available. The resource is required for this.
*
* @param bool $condition
* @return static
*/
public function showCreateOption(bool $condition = true): static
{
$this->showCreateOption = $condition;
$this->reload();

return $this;
}

/**
* Make the field searchable and use laravel scout if available.
*
* @param class-string<BaseModel> $model
* @return static
*/
protected function tryScout(string $model): static
{
$this->allowHtml();
$this->searchable();
$this->getOptionLabelUsing(fn ($state) => static::getSearchLabelWithBlade((new $model)::find($state)));

if (in_array(Searchable::class, class_uses_recursive($model))) {
return $this
->getSearchResultsUsing(function (string $search) use ($model) {
return (new $model)::search($search)
->take(25)
->get()
->mapWithKeys(fn (BaseModel $model) => [$model->getKey() => static::getSearchLabelWithBlade($model)])
->toArray();
});
}

return $this
->getSearchResultsUsing(function (string $search) use ($model) {
return (new $model)::query()
->where($this->resource->getRecordTitleAttribute(), ComparisonOperator::LIKE->value, "%$search%")
->take(25)
->get()
->mapWithKeys(fn (BaseModel|User $model) => [$model->getKey() => static::getSearchLabelWithBlade($model)])
->toArray();
});
}

/**
* Use the blade to make the results.
*
* @param BaseModel|User $model
* @return string
*/
public static function getSearchLabelWithBlade(BaseModel|User $model): string
{
return view('filament.components.select')
->with('name', $model->getName())
->with('subtitle', $model->getSubtitle())
->with('image', $model instanceof User ? $model->getFilamentAvatarUrl() : null)
->render();
}
}
19 changes: 2 additions & 17 deletions app/Filament/Components/Fields/Select.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

namespace App\Filament\Components\Fields;

use App\Models\Auth\User;
use App\Models\BaseModel;
use Filament\Forms\Components\Select as ComponentsSelect;
use Laravel\Scout\Searchable;
Expand All @@ -27,31 +26,17 @@ public function useScout(string $model, ?string $loadRelation = null): static
return $this
->allowHtml()
->searchable()
->getOptionLabelUsing(fn ($state) => BelongsTo::getSearchLabelWithBlade((new $model)::find($state)))
->getSearchResultsUsing(function (string $search) use ($model, $loadRelation) {
return (new $model)::search($search)
->take(25)
->get()
->load($loadRelation ?? [])
->mapWithKeys(fn (BaseModel $model) => [$model->getKey() => static::getSearchLabelWithBlade($model)])
->mapWithKeys(fn (BaseModel $model) => [$model->getKey() => BelongsTo::getSearchLabelWithBlade($model)])
->toArray();
});
}

return $this->searchable();
}

/**
* Use the blade to make the results.
*
* @param BaseModel|User $model
* @return string
*/
public static function getSearchLabelWithBlade(BaseModel|User $model): string
{
return view('filament.components.select')
->with('name', $model->getName())
->with('subtitle', $model->getSubtitle())
->with('image', $model instanceof User ? $model->getFilamentAvatarUrl() : null)
->render();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
*/
class ActionLogRelationManager extends BaseRelationManager
{
protected static string $relationship = 'action_logs';
protected static string $relationship = 'actionlogs';

protected static ?string $recordTitleAttribute = ActionLogModel::ATTRIBUTE_ID;

Expand Down
Loading

0 comments on commit 85878dc

Please sign in to comment.