feat: added better labels & fixed filament search (#799)

This commit is contained in:
Kyrch
2025-04-29 00:40:50 -03:00
committed by GitHub
parent bc9df20162
commit 88aeaecafa
24 changed files with 181 additions and 116 deletions
@@ -13,6 +13,7 @@ use App\Actions\Models\Wiki\Anime\ApiAction\MalAnimeApiAction;
use App\Concerns\Models\CanCreateAnimeSynonym;
use App\Concerns\Models\CanCreateStudio;
use App\Enums\Actions\ActionStatus;
use App\Enums\Models\Wiki\AnimeSynonymType;
use App\Models\Wiki\Anime;
use Exception;
use Illuminate\Support\Arr;
@@ -141,8 +142,15 @@ class BackfillAnimeAction extends BackfillWikiAction
{
if (!$this->toBackfill[self::SYNONYMS]) return;
$texts = [];
foreach ($api->getSynonyms() as $type => $text) {
if ($type === AnimeSynonymType::OTHER->value && in_array($text, $texts, true)) {
Log::info("Skipping duplicate synonym '$text' for Anime {$this->getModel()->getName()}");
continue;
}
$this->createAnimeSynonym($text, $type, $this->getModel());
$texts[] = $text;
}
if ($this->getModel()->animesynonyms()->exists()) {
+22 -1
View File
@@ -90,7 +90,7 @@ class BelongsTo extends ComponentsSelect
if (in_array(Searchable::class, class_uses_recursive($model))) {
return $this
->getSearchResultsUsing(function (string $search) use ($model) {
$search = preg_replace('/[^A-Za-z0-9 ]/', '', $search);
$search = $this->escapeReservedChars($search);
/** @phpstan-ignore-next-line */
return $model::search($search)
->take(25)
@@ -125,4 +125,25 @@ class BelongsTo extends ComponentsSelect
->with('image', $model instanceof User ? $model->getFilamentAvatarUrl() : null)
->render();
}
/**
* Prepare the search query for Elasticsearch.
*
* @param string $search
* @return string
*/
public function escapeReservedChars(string $search) : string
{
return preg_replace(
[
'_[<>]+_',
'_[-+=!(){}[\]^"~*?:\\/\\\\]|&(?=&)|\|(?=\|)_',
],
[
'',
'\\\\$0',
],
$search
);
}
}
+22 -1
View File
@@ -31,7 +31,7 @@ class Select extends ComponentsSelect
->searchable()
->getOptionLabelUsing(fn ($state) => BelongsTo::getSearchLabelWithBlade($model::find($state)))
->getSearchResultsUsing(function (string $search) use ($livewire, $model, $loadRelation) {
$search = preg_replace('/[^A-Za-z0-9 ]/', '', $search);
$search = $this->escapeReservedChars($search);
/** @phpstan-ignore-next-line */
return $model::search($search)
->query(function (Builder $query) use ($livewire) {
@@ -54,4 +54,25 @@ class Select extends ComponentsSelect
return $this->searchable();
}
/**
* Prepare the search query for Elasticsearch.
*
* @param string $search
* @return string
*/
public function escapeReservedChars(string $search) : string
{
return preg_replace(
[
'_[<>]+_',
'_[-+=!(){}[\]^"~*?:\\/\\\\]|&(?=&)|\|(?=\|)_',
],
[
'',
'\\\\$0',
],
$search
);
}
}
@@ -52,9 +52,11 @@ class DateFilter extends Filter
->schema([
DatePicker::make($this->getName().'_'.'from')
->label($this->fromLabel)
->native(false)
->required(),
DatePicker::make($this->getName().'_'.'to')
->label($this->toLabel)
->native(false)
->required(),
]),
])
@@ -35,7 +35,7 @@ class GlobalSearchScoutProvider implements GlobalSearchProvider
continue;
}
$query = preg_replace('/[^A-Za-z0-9 ]/', '', $query);
$query = $this->escapeReservedChars($query);
/** @var ScoutBuilder $scoutBuilder */
$scoutBuilder = $resource::getModel()::search($query);
@@ -68,4 +68,25 @@ class GlobalSearchScoutProvider implements GlobalSearchProvider
return $builder;
}
/**
* Prepare the search query for Elasticsearch.
*
* @param string $search
* @return string
*/
public function escapeReservedChars(string $search) : string
{
return preg_replace(
[
'_[<>]+_',
'_[-+=!(){}[\]^"~*?:\\/\\\\]|&(?=&)|\|(?=\|)_',
],
[
'',
'\\\\$0',
],
$search
);
}
}
+4 -18
View File
@@ -136,30 +136,16 @@ class FeaturedTheme extends BaseResource
DatePicker::make(FeaturedThemeModel::ATTRIBUTE_START_AT)
->label(__('filament.fields.featured_theme.start_at.name'))
->helperText(__('filament.fields.featured_theme.start_at.help'))
->native(false)
->required()
->rules([
'required',
str('date_format:')
->append(implode(',', $allowedDateFormats))
->__toString(),
str('after:')
->append(FeaturedThemeModel::ATTRIBUTE_START_AT)
->__toString(),
]),
->before(FeaturedThemeModel::ATTRIBUTE_END_AT),
DatePicker::make(FeaturedThemeModel::ATTRIBUTE_END_AT)
->label(__('filament.fields.featured_theme.end_at.name'))
->helperText(__('filament.fields.featured_theme.end_at.help'))
->native(false)
->required()
->rules([
'required',
str('date_format:')
->append(implode(',', $allowedDateFormats))
->__toString(),
str('after:')
->append(FeaturedThemeModel::ATTRIBUTE_START_AT)
->__toString(),
]),
->after(FeaturedThemeModel::ATTRIBUTE_START_AT),
BelongsTo::make(FeaturedThemeModel::ATTRIBUTE_ENTRY)
->resource(EntryResource::class)
@@ -6,6 +6,8 @@ namespace App\Filament\Resources\Base;
use App\Filament\HeaderActions\Base\CreateHeaderAction;
use Filament\Resources\Pages\ListRecords;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
/**
* Class BaseListResources.
@@ -25,4 +27,51 @@ abstract class BaseListResources extends ListRecords
CreateHeaderAction::make(),
];
}
/**
* Using Laravel Scout to search.
*
* @param Builder $query
* @param class-string<Model> $modelClass
* @return Builder
*/
protected function makeScout(Builder $query, string $modelClass): Builder
{
$this->applyColumnSearchesToTableQuery($query);
$model = new $modelClass;
if (filled($search = $this->getTableSearch())) {
$search = $this->escapeReservedChars($search);
/** @phpstan-ignore-next-line */
$keys = $modelClass::search($search)->take(25)->keys();
$query
->whereIn($model->getKeyName(), $keys)
->orderByRaw("FIELD({$this->getResource()::getRecordRouteKeyName()}, " . $keys->implode(',') . ')');
}
return $query;
}
/**
* Prepare the search query for Elasticsearch.
*
* @param string $search
* @return string
*/
public function escapeReservedChars(string $search) : string
{
return preg_replace(
[
'_[<>]+_',
'_[-+=!(){}[\]^"~*?:\\/\\\\]|&(?=&)|\|(?=\|)_',
],
[
'',
'\\\\$0',
],
$search
);
}
}
+5 -2
View File
@@ -81,7 +81,8 @@ abstract class BaseResource extends Resource
public static function table(Table $table): Table
{
return $table
->defaultSort(static::getRecordRouteKeyName(), 'desc')
->striped()
->defaultSort(fn (Table $table) => $table->hasSearch() ? null : static::getRecordRouteKeyName(), fn (Table $table) => $table->hasSearch() ? null : 'desc')
->filters(static::getFilters())
->filtersFormMaxHeight('400px')
->actions(static::getActions())
@@ -89,7 +90,9 @@ abstract class BaseResource extends Resource
->headerActions(static::getTableActions())
->recordUrl(fn (Model $record): string => static::getUrl('view', ['record' => $record]))
->paginated([10, 25, 50, 100, 'all'])
->defaultPaginationPageOption(25);
->defaultPaginationPageOption(25)
->extremePaginationLinks()
->deferLoading(!app()->runningUnitTests());
}
/**
@@ -38,13 +38,6 @@ class ListExternalProfiles extends BaseListResources
*/
protected function applySearchToTableQuery(Builder $query): Builder
{
$this->applyColumnSearchesToTableQuery($query);
if (filled($search = $this->getTableSearch())) {
$search = preg_replace('/[^A-Za-z0-9 ]/', '', $search);
$query->whereIn(ExternalProfileModel::ATTRIBUTE_ID, ExternalProfileModel::search($search)->take(25)->keys());
}
return $query;
return $this->makeScout($query, ExternalProfileModel::class);
}
}
@@ -38,13 +38,6 @@ class ListPlaylists extends BaseListResources
*/
protected function applySearchToTableQuery(Builder $query): Builder
{
$this->applyColumnSearchesToTableQuery($query);
if (filled($search = $this->getTableSearch())) {
$search = preg_replace('/[^A-Za-z0-9 ]/', '', $search);
$query->whereIn(PlaylistModel::ATTRIBUTE_ID, PlaylistModel::search($search)->take(25)->keys());
}
return $query;
return $this->makeScout($query, PlaylistModel::class);
}
}
@@ -55,14 +55,7 @@ class ListAnimes extends BaseListResources
*/
protected function applySearchToTableQuery(Builder $query): Builder
{
$this->applyColumnSearchesToTableQuery($query);
if (filled($search = $this->getTableSearch())) {
$search = preg_replace('/[^A-Za-z0-9 ]/', '', $search);
$query->whereIn(AnimeModel::ATTRIBUTE_ID, AnimeModel::search($search)->take(25)->keys());
}
return $query;
return $this->makeScout($query, AnimeModel::class);
}
/**
@@ -38,13 +38,6 @@ class ListSynonyms extends BaseListResources
*/
protected function applySearchToTableQuery(Builder $query): Builder
{
$this->applyColumnSearchesToTableQuery($query);
if (filled($search = $this->getTableSearch())) {
$search = preg_replace('/[^A-Za-z0-9 ]/', '', $search);
$query->whereIn(AnimeSynonym::ATTRIBUTE_ID, AnimeSynonym::search($search)->take(25)->keys());
}
return $query;
return $this->makeScout($query, AnimeSynonym::class);
}
}
+11 -2
View File
@@ -42,11 +42,11 @@ use Filament\Infolists\Components\RepeatableEntry;
use Filament\Infolists\Components\Section;
use Filament\Infolists\Infolist;
use Filament\Resources\RelationManagers\RelationGroup;
use Filament\Tables\Filters\Filter;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Illuminate\Validation\Rules\Enum;
@@ -155,7 +155,11 @@ class Theme extends BaseResource
$query = parent::getEloquentQuery();
// Necessary to prevent lazy loading when loading related resources
return $query->with([AnimeTheme::RELATION_ANIME, AnimeTheme::RELATION_SONG, AnimeTheme::RELATION_GROUP]);
return $query->with([
AnimeTheme::RELATION_ANIME,
AnimeTheme::RELATION_SONG,
AnimeTheme::RELATION_GROUP
]);
}
/**
@@ -396,6 +400,11 @@ class Theme extends BaseResource
NumberFilter::make(ThemeModel::ATTRIBUTE_SEQUENCE)
->label(__('filament.fields.anime_theme.sequence.name')),
Filter::make(ThemeType::IN->localize())
->label(__('filament.filters.anime_theme.without_in'))
->query(fn (Builder $query) => $query->whereNot(ThemeModel::ATTRIBUTE_TYPE, ThemeType::IN->value))
->default(true),
...parent::getFilters(),
];
}
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Filament\Resources\Wiki\Anime\Theme;
use App\Enums\Models\Wiki\ThemeType;
use App\Filament\Components\Columns\BelongsToColumn;
use App\Filament\Components\Columns\TextColumn;
use App\Filament\Components\Fields\BelongsTo;
@@ -33,6 +34,7 @@ use Filament\Infolists\Components\Section;
use Filament\Infolists\Infolist;
use Filament\Resources\RelationManagers\RelationGroup;
use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Filters\Filter;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
@@ -336,6 +338,11 @@ class Entry extends BaseResource
CheckboxFilter::make(EntryModel::ATTRIBUTE_SPOILER)
->label(__('filament.fields.anime_theme_entry.spoiler.name')),
Filter::make(ThemeType::IN->localize())
->label(__('filament.filters.anime_theme.without_in'))
->query(fn (Builder $query) => $query->whereDoesntHaveRelation(AnimeThemeEntry::RELATION_THEME, ThemeModel::ATTRIBUTE_TYPE, ThemeType::IN->value))
->default(true),
...parent::getFilters(),
];
}
@@ -38,13 +38,6 @@ class ListEntries extends BaseListResources
*/
protected function applySearchToTableQuery(Builder $query): Builder
{
$this->applyColumnSearchesToTableQuery($query);
if (filled($search = $this->getTableSearch())) {
$search = preg_replace('/[^A-Za-z0-9 ]/', '', $search);
$query->whereIn(AnimeThemeEntry::ATTRIBUTE_ID, AnimeThemeEntry::search($search)->take(25)->keys());
}
return $query;
return $this->makeScout($query, AnimeThemeEntry::class);
}
}
@@ -38,13 +38,6 @@ class ListThemes extends BaseListResources
*/
protected function applySearchToTableQuery(Builder $query): Builder
{
$this->applyColumnSearchesToTableQuery($query);
if (filled($search = $this->getTableSearch())) {
$search = preg_replace('/[^A-Za-z0-9 ]/', '', $search);
$query->whereIn(AnimeTheme::ATTRIBUTE_ID, AnimeTheme::search($search)->take(25)->keys());
}
return $query;
return $this->makeScout($query, AnimeTheme::class);
}
}
@@ -54,14 +54,7 @@ class ListArtists extends BaseListResources
*/
protected function applySearchToTableQuery(Builder $query): Builder
{
$this->applyColumnSearchesToTableQuery($query);
if (filled($search = $this->getTableSearch())) {
$search = preg_replace('/[^A-Za-z0-9 ]/', '', $search);
$query->whereIn(ArtistModel::ATTRIBUTE_ID, ArtistModel::search($search)->take(25)->keys());
}
return $query;
return $this->makeScout($query, ArtistModel::class);
}
/**
@@ -38,13 +38,6 @@ class ListSeries extends BaseListResources
*/
protected function applySearchToTableQuery(Builder $query): Builder
{
$this->applyColumnSearchesToTableQuery($query);
if (filled($search = $this->getTableSearch())) {
$search = preg_replace('/[^A-Za-z0-9 ]/', '', $search);
$query->whereIn(SeriesModel::ATTRIBUTE_ID, SeriesModel::search($search)->take(25)->keys());
}
return $query;
return $this->makeScout($query, SeriesModel::class);
}
}
@@ -49,14 +49,7 @@ class ListSongs extends BaseListResources
*/
protected function applySearchToTableQuery(Builder $query): Builder
{
$this->applyColumnSearchesToTableQuery($query);
if (filled($search = $this->getTableSearch())) {
$search = preg_replace('/[^A-Za-z0-9 ]/', '', $search);
$query->whereIn(SongModel::ATTRIBUTE_ID, SongModel::search($search)->take(25)->keys());
}
return $query;
return $this->makeScout($query, SongModel::class);
}
/**
@@ -412,6 +412,8 @@ class Performance extends BaseResource
Repeater::make('memberships')
->label(__('filament.resources.label.memberships'))
->helperText(__('filament.fields.performance.memberships.help'))
->addActionLabel(__('filament.fields.performance.memberships.add'))
->collapsible()
->defaultItems(0)
->columns(3)
@@ -420,6 +422,7 @@ class Performance extends BaseResource
BelongsTo::make(Membership::ATTRIBUTE_MEMBER)
->resource(ArtistResource::class)
->showCreateOption()
->label(__('filament.fields.membership.member'))
->required()
->rules(['required']),
@@ -50,14 +50,7 @@ class ListStudios extends BaseListResources
*/
protected function applySearchToTableQuery(Builder $query): Builder
{
$this->applyColumnSearchesToTableQuery($query);
if (filled($search = $this->getTableSearch())) {
$search = preg_replace('/[^A-Za-z0-9 ]/', '', $search);
$query->whereIn(StudioModel::ATTRIBUTE_ID, StudioModel::search($search)->take(25)->keys());
}
return $query;
return $this->makeScout($query, StudioModel::class);
}
/**
@@ -45,14 +45,7 @@ class ListVideos extends BaseListResources
*/
protected function applySearchToTableQuery(Builder $query): Builder
{
$this->applyColumnSearchesToTableQuery($query);
if (filled($search = $this->getTableSearch())) {
$search = preg_replace('/[^A-Za-z0-9 ]/', '', $search);
$query->whereIn(VideoModel::ATTRIBUTE_ID, VideoModel::search($search)->take(25)->keys());
}
return $query;
return $this->makeScout($query, VideoModel::class);
}
/**
@@ -149,7 +149,12 @@ class AnimeThemeEntry extends BaseModel implements InteractsWithSchema
public function toSearchableArray(): array
{
$array = $this->toArray();
$array['theme'] = $this->animetheme->toSearchableArray();
$theme = is_null($this->animetheme)
? $this->animetheme()->withoutGlobalScope(WithoutInsertSongScope::class)->first()
: $this->animetheme;
$array['theme'] = $theme->toSearchableArray();
// Overwrite version with readable format "V{#}"
$array['version'] = Str::of(empty($this->version) ? '1' : $this->version)->prepend('V')->__toString();
+9 -2
View File
@@ -735,11 +735,11 @@ return [
],
'membership' => [
'alias' => [
'help' => 'Used in place of the Artist if the performance is using an alias.',
'help' => 'Used in place of the Member if the performance is using an alias.',
'name' => 'Alias',
],
'as' => [
'help' => 'Used alongside the Artist name if the performance is made as a character.',
'help' => 'Used alongside the Member name if the performance is made as a character.',
'name' => 'As',
],
'group' => 'Group',
@@ -776,6 +776,10 @@ return [
'load_members' => [
'name' => 'Load Members',
],
'memberships' => [
'help' => 'When a group and its members are credited, you need to add the members (called memberships) that performed the song.',
'add' => 'Add Member',
],
],
'permission' => [
'name' => 'Name',
@@ -954,6 +958,9 @@ return [
'from' => 'From',
'to' => 'To',
],
'anime_theme' => [
'without_in' => 'Without Insert Songs',
],
],
'resources' => [
'group' => [