feat: add id in some searching contexts (#1210)

This commit is contained in:
Kyrch
2026-06-05 08:21:56 -03:00
committed by GitHub
parent 6a81ddd1ee
commit 5476445ccc
33 changed files with 21 additions and 1323 deletions
-2
View File
@@ -98,8 +98,6 @@ FFPROBE_BINARIES=
# filament
FILAMENT_URL=http://admin.animethemes.test
FILAMENT_PATH=
FILAMENT_SUBMISSION_URL=http://admin.animethemes.test
FILAMENT_SUBMISSION_PATH=submission
# filesystems
FILESYSTEM_DISK=local
-2
View File
@@ -96,8 +96,6 @@ FFPROBE_BINARIES=
# filament
FILAMENT_URL=http://admin.animethemes.test
FILAMENT_PATH=
FILAMENT_SUBMISSION_URL=http://admin.animethemes.test
FILAMENT_SUBMISSION_PATH=submission
# filesystems
FILESYSTEM_DISK=local
@@ -1,100 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Actions\Submission;
use App\Enums\Models\User\SubmissionComparisonAction;
use App\Enums\Models\User\SubmissionStatus;
use App\Filament\Resources\User\SubmissionResource;
use App\Models\Auth\User;
use App\Models\User\Submission;
use App\Models\User\Submission\SubmissionAnime;
use App\Models\User\Submission\SubmissionComparison;
use App\Models\User\Submission\SubmissionSeries;
use App\Models\User\Submission\SubmissionStudio;
use App\Models\User\Submission\SubmissionSynonym;
use App\Models\Wiki\ExternalResource;
use App\Models\Wiki\Series;
use App\Models\Wiki\Studio;
use Exception;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class SubmitNewAnimeAction
{
public function handle(User $user, array $data): void
{
try {
DB::beginTransaction();
$submission = Submission::query()->create([
Submission::ATTRIBUTE_SOURCE => Arr::get($data, Submission::ATTRIBUTE_SOURCE),
Submission::ATTRIBUTE_STATUS => SubmissionStatus::PENDING,
Submission::ATTRIBUTE_USER => $user->getKey(),
]);
$anime = SubmissionAnime::query()->create(Arr::get($data, 'anime'));
$submission->submitted()->associate($anime);
$this->handleSynonyms($submission, $data);
// TODO: handle themes.
$this->handleRelation($submission, $data, SubmissionAnime::RELATION_SERIES, SubmissionSeries::class, Series::class);
$this->handleRelation($submission, $data, SubmissionAnime::RELATION_RESOURCES, SubmissionResource::class, ExternalResource::class);
$this->handleRelation($submission, $data, SubmissionAnime::RELATION_STUDIOS, SubmissionStudio::class, Studio::class);
DB::commit();
} catch (Exception $e) {
DB::rollBack();
Log::error($e->getMessage());
throw $e;
}
}
protected function handleSynonyms(Submission $submission, array $data): void
{
foreach (Arr::get($data, SubmissionAnime::RELATION_SYNONYMS) as $synonym) {
$submissionSynonym = SubmissionSynonym::query()->create($synonym);
$submissionSynonym->submission()->attach([
$submission->getKey() => [
SubmissionComparison::ATTRIBUTE_ACTION => SubmissionComparisonAction::CREATE,
],
]);
}
}
protected function handleRelation(Submission $submission, array $data, string $relation, string $submissionModel, string $model): void
{
$list = Arr::get($data, $relation, []);
foreach ($list as $item) {
$type = Arr::string($item, 'type');
if ($type === 'create') {
SubmissionComparison::query()->create([
SubmissionComparison::ATTRIBUTE_ACTION => SubmissionComparisonAction::CREATE,
SubmissionComparison::ATTRIBUTE_SUBMISSION => $submission->getKey(),
SubmissionComparison::ATTRIBUTE_SUBMITTED_TYPE => Relation::getMorphAlias($submissionModel),
SubmissionComparison::ATTRIBUTE_SUBMITTED_ID => $submissionModel::query()->create($item['data'])->getKey(),
]);
}
if ($type === 'attach') {
SubmissionComparison::query()->create([
SubmissionComparison::ATTRIBUTE_ACTION => SubmissionComparisonAction::ATTACH,
SubmissionComparison::ATTRIBUTE_SUBMISSION => $submission->getKey(),
SubmissionComparison::ATTRIBUTE_ACTIONABLE_TYPE => Relation::getMorphAlias($model),
SubmissionComparison::ATTRIBUTE_ACTIONABLE_ID => $item['data']['id'],
]);
}
}
}
}
@@ -77,6 +77,7 @@ class BelongsTo extends Select
->search()
->items()
)
->merge($modelClass::query()->whereKey($this->escapeReservedChars($search))->get())
->mapWithKeys(fn (Model $model): array => [$model->getKey() => static::getSearchLabelWithBlade($model, $this->withSubtitle)])
->toArray()
);
@@ -42,6 +42,7 @@ class Select extends ComponentsSelect
})
->items()
)
->merge($modelClass::query()->whereKey($this->escapeReservedChars($search))->get())
->mapWithKeys(fn (Model $model): array => [$model->getKey() => BelongsTo::getSearchLabelWithBlade($model)])
->toArray()
);
@@ -33,11 +33,14 @@ class GlobalSearchScoutProvider implements GlobalSearchProvider
continue;
}
$query = $this->escapeReservedChars($query);
$resourceResults = collect(
Search::getSearch($modelClass, new Criteria($this->escapeReservedChars($query)))
Search::getSearch($modelClass, new Criteria($query))
->search(fn (Builder $builder) => $builder->with($resource::getEloquentQuery()->getEagerLoads()))
->items()
)
->merge($modelClass::query()->whereKey($query)->get())
->map(function (Model $record) use ($resource): ?GlobalSearchResult {
$url = $resource::getUrl('view', ['record' => $record]);
@@ -37,7 +37,11 @@ abstract class BaseListResources extends ListRecords
$model = new $modelClass;
if (filled($search = $this->getTableSearch())) {
$keys = Search::getSearch($modelClass, new Criteria($this->escapeReservedChars($search)))
$search = $this->escapeReservedChars($search);
$keys = is_numeric($search)
? [$search]
: Search::getSearch($modelClass, new Criteria($search))
->search()
->getCollection()
->map(fn (Model $model) => $model->getKey())
@@ -1,13 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\User\Submission\Pages;
use App\Filament\Resources\Base\BaseListResources;
use App\Filament\Resources\User\SubmissionResource;
class ListSubmissions extends BaseListResources
{
protected static string $resource = SubmissionResource::class;
}
@@ -1,13 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\User\Submission\Pages;
use App\Filament\Resources\Base\BaseViewResource;
use App\Filament\Resources\User\SubmissionResource;
class ViewSubmission extends BaseViewResource
{
protected static string $resource = SubmissionResource::class;
}
@@ -1,143 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\User;
use App\Enums\Filament\NavigationGroup;
use App\Enums\Models\User\SubmissionStatus;
use App\Filament\Components\Columns\BelongsToColumn;
use App\Filament\Components\Columns\TextColumn;
use App\Filament\Components\Infolist\BelongsToEntry;
use App\Filament\Components\Infolist\TextEntry;
use App\Filament\Components\Infolist\TimestampSection;
use App\Filament\Resources\Auth\UserResource;
use App\Filament\Resources\BaseResource;
use App\Filament\Resources\User\Submission\Pages\ListSubmissions;
use App\Filament\Resources\User\Submission\Pages\ViewSubmission;
use App\Models\User\Submission;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;
use Filament\Support\Icons\Heroicon;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
class SubmissionResource extends BaseResource
{
/**
* The model the resource corresponds to.
*
* @var class-string<Model>|null
*/
protected static ?string $model = Submission::class;
public static function getModelLabel(): string
{
return __('filament.resources.singularLabel.submission');
}
public static function getPluralModelLabel(): string
{
return __('filament.resources.label.submissions');
}
public static function getNavigationGroup(): NavigationGroup
{
return NavigationGroup::USER;
}
public static function getNavigationIcon(): Heroicon
{
return Heroicon::OutlinedLightBulb;
}
public static function getRecordSlug(): string
{
return 'submissions';
}
public static function getRecordTitleAttribute(): string
{
return Submission::ATTRIBUTE_ID;
}
public static function getEloquentQuery(): Builder
{
$query = parent::getEloquentQuery();
// Necessary to prevent lazy loading when loading related resources
return $query->with([
Submission::RELATION_USER,
Submission::RELATION_ASSIGNEE,
]);
}
public static function form(Schema $schema): Schema
{
return $schema;
}
public static function table(Table $table): Table
{
return parent::table($table)
->columns([
TextColumn::make(Submission::ATTRIBUTE_ID)
->label(__('filament.fields.base.id')),
TextColumn::make(Submission::ATTRIBUTE_STATUS)
->label(__('filament.fields.submission.status'))
->formatStateUsing(fn (SubmissionStatus $state): ?string => $state->localize())
->badge(),
BelongsToColumn::make(Submission::RELATION_USER, UserResource::class),
BelongsToColumn::make(Submission::RELATION_ASSIGNEE, UserResource::class)
->label(__('filament.fields.submission.moderator')),
TextColumn::make(Submission::ATTRIBUTE_FINISHED_AT)
->label(__('filament.fields.submission.finished_at'))
->dateTime(),
]);
}
public static function infolist(Schema $schema): Schema
{
return $schema
->components([
Section::make(static::getRecordTitle($schema->getRecord()))
->schema([
TextEntry::make(Submission::ATTRIBUTE_ID)
->label(__('filament.fields.base.id')),
TextEntry::make(Submission::ATTRIBUTE_STATUS)
->label(__('filament.fields.submission.status'))
->formatStateUsing(fn (SubmissionStatus $state): ?string => $state->localize())
->badge(),
BelongsToEntry::make(Submission::RELATION_USER, UserResource::class),
BelongsToEntry::make(Submission::RELATION_ASSIGNEE, UserResource::class)
->label(__('filament.fields.submission.moderator')),
TextEntry::make(Submission::ATTRIBUTE_FINISHED_AT)
->label(__('filament.fields.submission.finished_at'))
->dateTime(),
])
->columns(3),
TimestampSection::make(),
]);
}
/**
* @return array<string, \Filament\Resources\Pages\PageRegistration>
*/
public static function getPages(): array
{
return [
'index' => ListSubmissions::route('/'),
'view' => ViewSubmission::route('/{record:submission_id}'),
];
}
}
@@ -26,7 +26,6 @@ use App\Filament\Resources\Wiki\Anime\Theme\RelationManagers\EntryThemeRelationM
use App\Filament\Resources\Wiki\Anime\ThemeResource;
use App\Filament\Resources\Wiki\AnimeResource;
use App\Filament\Resources\Wiki\SongResource;
use App\Filament\Submission\Resources\Anime\Pages\CreateAnimeSubmission;
use App\Models\Wiki\Anime\AnimeTheme;
use App\Models\Wiki\Anime\Theme\AnimeThemeEntry;
use App\Models\Wiki\Song;
@@ -167,10 +166,6 @@ class EntryResource extends BaseResource
->rule(new AnimeThemeEntryResourceLinkFormatRule(ResourceSite::YOUTUBE))
->uri()
->saveRelationshipsUsing(function (AnimeThemeEntry $record, AttachResourceAction $action, ?Uri $state, $livewire): void {
if ($livewire instanceof CreateAnimeSubmission) {
return;
}
$action->handle($record, [ResourceSite::YOUTUBE->name => $state], [ResourceSite::YOUTUBE]);
}),
])
@@ -1,42 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Filament\Submission\Components;
use App\Filament\Components\Fields\Select;
use Filament\Forms\Components\Builder;
use Filament\Forms\Components\Builder\Block;
use Filament\Schemas\Schema;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
class SelectBuilder extends Builder
{
/**
* @param class-string<Model> $model
* @param class-string $form
*/
public function set(string $model, string $form): static
{
$singularLabel = Str::afterLast($this->getAddActionLabel(), ' ');
$this->blockNumbers(false);
$this->blocks([
Block::make('create')
->label(__('filament.buttons.create', ['label' => $singularLabel]))
->schema(fn (Schema $schema) => $form::configure($schema)->getComponents()),
Block::make('attach')
->label(__('filament.buttons.attach', ['label' => $singularLabel]))
->schema(fn ($livewire): array => [
Select::make('id')
->label($singularLabel)
->useScout($livewire, $model),
]),
]);
return $this;
}
}
@@ -1,208 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Filament\Submission\Resources\Anime\Pages;
use App\Actions\Submission\SubmitNewAnimeAction;
use App\Filament\Components\Fields\TextInput;
use App\Filament\Submission\Components\SelectBuilder;
use App\Filament\Submission\Resources\AnimeSubmissionResource;
use App\Filament\Submission\Resources\Schema\AnimeForm;
use App\Filament\Submission\Resources\Schema\ArtistForm;
use App\Filament\Submission\Resources\Schema\EntryForm;
use App\Filament\Submission\Resources\Schema\ResourceForm;
use App\Filament\Submission\Resources\Schema\SeriesForm;
use App\Filament\Submission\Resources\Schema\SongForm;
use App\Filament\Submission\Resources\Schema\StudioForm;
use App\Filament\Submission\Resources\Schema\SynonymForm;
use App\Filament\Submission\Resources\Schema\ThemeForm;
use App\Models\User\Submission;
use App\Models\User\Submission\SubmissionAnime;
use App\Models\User\Submission\SubmissionPerformance;
use App\Models\Wiki\Artist;
use App\Models\Wiki\Series;
use App\Models\Wiki\Song;
use App\Models\Wiki\Studio;
use Filament\Actions\Action;
use Filament\Forms\Components\Repeater;
use Filament\Forms\Components\Textarea;
use Filament\Resources\Pages\CreateRecord;
use Filament\Schemas\Components\Tabs;
use Filament\Schemas\Components\Tabs\Tab;
use Filament\Schemas\Schema;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Auth;
class CreateAnimeSubmission extends CreateRecord
{
protected static string $resource = AnimeSubmissionResource::class;
public function form(Schema $schema): Schema
{
return $schema
->components([
Textarea::make(Submission::ATTRIBUTE_SOURCE)
->label(__('submissions.fields.base.source.name'))
->helperText(__('submissions.fields.base.source.help'))
->required()
->rows(5),
Tabs::make('tabs')
->vertical()
->columnSpanFull()
->tabs([
Tab::make('anime')
->statePath('anime')
->label(__('filament.resources.label.anime'))
->columns(2)
->schema(AnimeForm::configure($schema)->getComponents()),
Tab::make(SubmissionAnime::RELATION_SYNONYMS)
->label(__('filament.resources.label.synonyms'))
->schema([
Repeater::make(SubmissionAnime::RELATION_SYNONYMS)
->label(__('filament.resources.label.synonyms'))
->addActionLabel(__('filament.buttons.add', ['label' => __('filament.resources.singularLabel.synonym')]))
->defaultItems(0)
->schema(SynonymForm::configure($schema)->getComponents()),
]),
Tab::make(SubmissionAnime::RELATION_THEMES)
->label(__('filament.resources.label.anime_themes'))
->schema([
Repeater::make(SubmissionAnime::RELATION_THEMES)
->label(__('filament.resources.label.anime_themes'))
->addActionLabel(__('filament.buttons.add', ['label' => __('filament.resources.singularLabel.anime_theme')]))
->schema([
Tabs::make('Tabs')
->schema([
Tab::make('theme')
->label(__('filament.resources.singularLabel.anime_theme'))
->schema(ThemeForm::configure($schema)->getComponents()),
Tab::make('song')
->label(__('filament.resources.singularLabel.song'))
->schema([
SelectBuilder::make('song')
->label(__('filament.resources.singularLabel.song'))
->addActionLabel(__('filament.buttons.add', ['label' => __('filament.resources.singularLabel.song')]))
->maxItems(1)
->reorderable(false)
->set(Song::class, SongForm::class),
Repeater::make('performances')
->label(__('filament.resources.label.artists'))
->live(true)
->key('song.performances')
->collapsible()
->defaultItems(0)
->columns(3)
->columnSpanFull()
->schema([
SelectBuilder::make('artist')
->label(__('filament.resources.singularLabel.artist'))
->maxItems(1)
->reorderable(false)
->columnSpanFull()
->set(Artist::class, ArtistForm::class),
TextInput::make(SubmissionPerformance::ATTRIBUTE_AS)
->label(__('filament.fields.performance.as.name'))
->helperText(__('filament.fields.performance.as.help')),
TextInput::make(SubmissionPerformance::ATTRIBUTE_ALIAS)
->label(__('filament.fields.performance.alias.name'))
->helperText(__('filament.fields.performance.alias.help')),
Repeater::make('members')
->label(__('filament.resources.label.members'))
->helperText(__('filament.fields.performance.members.help'))
->collapsible()
->defaultItems(0)
->columns(3)
->columnSpanFull()
->schema([
SelectBuilder::make('member')
->label(__('filament.fields.performance.member'))
->maxItems(1)
->reorderable(false)
->columnSpanFull()
->set(Artist::class, ArtistForm::class),
TextInput::make(SubmissionPerformance::ATTRIBUTE_MEMBER_AS)
->label(__('filament.fields.performance.member_as.name'))
->helperText(__('filament.fields.performance.member_as.help')),
TextInput::make(SubmissionPerformance::ATTRIBUTE_MEMBER_ALIAS)
->label(__('filament.fields.performance.member_alias.name'))
->helperText(__('filament.fields.performance.member_alias.help')),
]),
]),
]),
Tab::make('entries')
->label(__('filament.resources.label.anime_theme_entries'))
->schema([
Repeater::make('entries')
->label(__('filament.resources.label.anime_theme_entries'))
->addActionLabel(__('filament.buttons.add', ['label' => __('filament.resources.singularLabel.anime_theme_entry')]))
->schema(EntryForm::configure($schema)->getComponents()),
]),
]),
]),
]),
Tab::make(SubmissionAnime::RELATION_SERIES)
->label(__('filament.resources.label.series'))
->schema([
SelectBuilder::make(SubmissionAnime::RELATION_SERIES)
->label(__('filament.resources.label.series'))
->addActionLabel(__('filament.buttons.add', ['label' => __('filament.resources.singularLabel.series')]))
->set(Series::class, SeriesForm::class),
]),
Tab::make(SubmissionAnime::RELATION_RESOURCES)
->label(__('filament.resources.label.external_resources'))
->schema([
Repeater::make(SubmissionAnime::RELATION_RESOURCES)
->label(__('filament.resources.label.external_resources'))
->addActionLabel(__('filament.buttons.add', ['label' => __('filament.resources.singularLabel.external_resource')]))
->defaultItems(0)
->schema(ResourceForm::configure($schema)->getComponents()),
]),
Tab::make(SubmissionAnime::RELATION_STUDIOS)
->label(__('filament.resources.label.studios'))
->schema([
SelectBuilder::make(SubmissionAnime::RELATION_STUDIOS)
->label(__('filament.resources.label.studios'))
->addActionLabel(__('filament.buttons.add', ['label' => __('filament.resources.singularLabel.studio')]))
->set(Studio::class, StudioForm::class),
]),
]),
]);
}
// TODO: Refactor common submission creation logic
protected function handleRecordCreation(array $data): Model
{
new SubmitNewAnimeAction()->handle(Auth::user(), $data);
$this->halt();
return new Submission();
}
protected function getCreateFormAction(): Action
{
return parent::getCreateFormAction()
->label(__('submissions.buttons.submit'));
}
protected function getCreateAnotherFormAction(): Action
{
return parent::getCreateAnotherFormAction()
->label(__('submissions.buttons.submit_another'));
}
}
@@ -1,26 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Filament\Submission\Resources\Anime\Pages;
use App\Filament\Resources\Base\BaseListResources;
use App\Filament\Submission\Resources\AnimeSubmissionResource;
use Filament\Actions\CreateAction;
class ListAnimeSubmissions extends BaseListResources
{
/**
* Get the header actions available.
*
* @return \Filament\Actions\Action[]
*/
protected function getHeaderActions(): array
{
return [
CreateAction::make(),
];
}
protected static string $resource = AnimeSubmissionResource::class;
}
@@ -1,118 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Filament\Submission\Resources;
use App\Enums\Models\User\SubmissionStatus;
use App\Filament\Components\Columns\TextColumn;
use App\Filament\Submission\Resources\Anime\Pages\CreateAnimeSubmission;
use App\Filament\Submission\Resources\Anime\Pages\ListAnimeSubmissions;
use App\Models\User\Submission;
use App\Models\User\Submission\SubmissionAnime;
use App\Models\Wiki\Anime;
use Filament\Support\Icons\Heroicon;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Support\Facades\Auth;
class AnimeSubmissionResource extends BaseSubmissionResource
{
/**
* The model the resource corresponds to.
*
* @var class-string<Model>|null
*/
protected static ?string $model = Submission::class;
public static function getModelLabel(): string
{
return __('submissions.resources.singularLabel.anime');
}
public static function getPluralModelLabel(): string
{
return __('submissions.resources.label.anime');
}
public static function getNavigationIcon(): Heroicon
{
return Heroicon::OutlinedTv;
}
public static function getRecordTitleAttribute(): string
{
return Submission::ATTRIBUTE_ID;
}
public static function canGloballySearch(): bool
{
return true;
}
public static function getRecordSlug(): string
{
return 'anime';
}
public static function getEloquentQuery(): Builder
{
$builder = parent::getEloquentQuery();
$builder->where(
fn (Builder $query) => $query->orWhere(Submission::ATTRIBUTE_ACTIONABLE_TYPE, Relation::getMorphAlias(Anime::class))
->orWhere(Submission::ATTRIBUTE_SUBMITTED_TYPE, Relation::getMorphAlias(SubmissionAnime::class))
);
$builder->whereBelongsTo(Auth::user(), Submission::RELATION_USER);
return $builder->with([
Submission::RELATION_ACTIONABLE,
Submission::RELATION_SUBMITTED,
]);
}
public static function table(Table $table): Table
{
return parent::table($table)
->columns([
TextColumn::make(Submission::ATTRIBUTE_ID)
->label(__('filament.fields.base.id')),
TextColumn::make('name')
->label(__('submissions.fields.submission.name'))
// @phpstan-ignore-next-line
->state(fn (Submission $submission) => $submission->actionable?->getName() ?? $submission->submitted->getName()),
TextColumn::make(Submission::ATTRIBUTE_STATUS)
->label(__('submissions.fields.submission.status'))
->formatStateUsing(fn (SubmissionStatus $state): string => $state->localize())
->badge(),
TextColumn::make(Submission::ATTRIBUTE_FINISHED_AT)
->label(__('submissions.fields.submission.finished_at'))
->dateTime(),
]);
}
/**
* @return \Filament\Tables\Filters\BaseFilter[]
*/
public static function getFilters(): array
{
return [];
}
/**
* @return array<string, \Filament\Resources\Pages\PageRegistration>
*/
public static function getPages(): array
{
return [
'index' => ListAnimeSubmissions::route('/'),
'create' => CreateAnimeSubmission::route('/submit'),
];
}
}
@@ -1,142 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Filament\Submission\Resources;
use App\Contracts\Models\SoftDeletable;
use App\Filament\Actions\Base\ViewAction;
use App\Models\BaseModel;
use Filament\Actions\Action;
use Filament\Actions\ActionGroup;
use Filament\Panel;
use Filament\QueryBuilder\Constraints\Constraint;
use Filament\QueryBuilder\Constraints\DateConstraint;
use Filament\Resources\Resource;
use Filament\Support\Enums\Width;
use Filament\Tables\Enums\FiltersLayout;
use Filament\Tables\Filters\TrashedFilter;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletingScope;
use Illuminate\Support\Facades\Gate;
abstract class BaseSubmissionResource extends Resource
{
public static function canGloballySearch(): bool
{
return false;
}
public static function getRecordRouteKeyName(): string
{
return (new static::$model)->getKeyName();
}
public static function getRecordTitleAttribute(): string
{
return (new static::$model)->getKeyName();
}
public static function table(Table $table): Table
{
return $table
->defaultSort(fn (Table $table): ?string => $table->hasSearch() ? null : static::getRecordRouteKeyName(), fn (Table $table): ?string => $table->hasSearch() ? null : 'desc')
->filters(static::getFilters())
->filtersLayout(FiltersLayout::Modal)
->filtersFormWidth(Width::FourExtraLarge)
->filtersFormMaxHeight('400px')
->recordActions(static::getActions())
->toolbarActions(static::getBulkActions())
->headerActions(static::getTableActions())
->recordUrl(fn (Model $record): string => static::getUrl('create', ['record' => $record]))
->paginated([10, 25, 50, 100, 'all'])
->defaultPaginationPageOption(25)
->extremePaginationLinks()
->deferLoading(! app()->runningUnitTests());
}
/**
* @return \Filament\Tables\Filters\BaseFilter[]
*/
public static function getFilters(): array
{
return [
TrashedFilter::make()
->visible(in_array(SoftDeletable::class, class_implements(static::getModel()))),
];
}
/**
* @return Constraint[]
*/
public static function getConstraints(): array
{
return [
DateConstraint::make(BaseModel::ATTRIBUTE_CREATED_AT)
->label(__('filament.fields.base.created_at')),
DateConstraint::make(BaseModel::ATTRIBUTE_UPDATED_AT)
->label(__('filament.fields.base.updated_at')),
];
}
/**
* @return array<int, Action|ActionGroup>
*/
public static function getActions(): array
{
return [
...(Gate::allows('viewAny', static::$model) ? [ViewAction::make()] : []),
];
}
/**
* @return Action[]
*/
public static function getRecordActions(): array
{
return [];
}
/**
* @param array<int, ActionGroup|Action>|null $actionsIncludedInGroup
* @return array<int, ActionGroup|Action>
*/
public static function getBulkActions(?array $actionsIncludedInGroup = []): array
{
return [];
}
/**
* @return array<int, ActionGroup|Action>
*/
public static function getTableActions(): array
{
return [];
}
public static function getEloquentQuery(): Builder
{
return parent::getEloquentQuery()
->withoutGlobalScopes([
SoftDeletingScope::class,
]);
}
/**
* @return array<int, \Filament\Resources\RelationManagers\RelationGroup|class-string<\Filament\Resources\RelationManagers\RelationManager>>
*/
public static function getBaseRelations(): array
{
return [];
}
public static function getSlug(?Panel $panel = null): string
{
return 'submissions/'.static::getRecordSlug();
}
abstract public static function getRecordSlug(): string;
}
@@ -1,73 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Filament\Submission\Resources\Schema;
use App\Enums\Models\Wiki\AnimeFormat;
use App\Enums\Models\Wiki\AnimeSeason;
use App\Filament\Components\Fields\Select;
use App\Filament\Components\Fields\TextInput;
use App\Models\User\Submission\SubmissionAnime;
use App\Models\Wiki\Anime;
use Filament\Forms\Components\MarkdownEditor;
use Filament\Schemas\Schema;
class AnimeForm
{
public static function configure(Schema $schema): Schema
{
return $schema
->components([
TextInput::make(SubmissionAnime::ATTRIBUTE_NAME)
->label(__('filament.fields.anime.name.name'))
->helperText(__('filament.fields.anime.name.help'))
->required()
->maxLength(255)
->afterStateUpdatedJs(<<<'JS'
$set('slug', slug($state ?? ''));
JS),
TextInput::make(SubmissionAnime::ATTRIBUTE_SLUG)
->label(__('filament.fields.anime.slug.name'))
->helperText(__('filament.fields.anime.slug.help'))
->required()
->alphaDash()
->maxLength(192)
->unique(Anime::TABLE, Anime::ATTRIBUTE_SLUG),
TextInput::make(SubmissionAnime::ATTRIBUTE_YEAR)
->label(__('filament.fields.anime.year.name'))
->helperText(__('filament.fields.anime.year.help'))
->required()
->integer()
->length(4)
->default(date('Y'))
->minValue(1960)
->maxValue(intval(date('Y')) + 1),
Select::make(SubmissionAnime::ATTRIBUTE_SEASON)
->label(__('filament.fields.anime.season.name'))
->helperText(__('filament.fields.anime.season.help'))
->options(AnimeSeason::asSelectArrayStyled())
->required()
->enum(AnimeSeason::class)
->default(AnimeSeason::getCurrentSeason())
->searchable()
->allowHtml(),
Select::make(SubmissionAnime::ATTRIBUTE_FORMAT)
->label(__('filament.fields.anime.format.name'))
->helperText(__('filament.fields.anime.format.help'))
->options(AnimeFormat::class)
->required(),
MarkdownEditor::make(SubmissionAnime::ATTRIBUTE_SYNOPSIS)
->label(__('filament.fields.anime.synopsis.name'))
->helperText(__('filament.fields.anime.synopsis.help'))
->columnSpan(2)
->maxLength(65535),
])
->columns(2);
}
}
@@ -1,46 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Filament\Submission\Resources\Schema;
use App\Filament\Components\Fields\TextInput;
use App\Models\User\Submission\SubmissionArtist;
use App\Models\Wiki\Artist;
use Filament\Forms\Components\MarkdownEditor;
use Filament\Schemas\Schema;
class ArtistForm
{
public static function configure(Schema $schema): Schema
{
return $schema
->components([
TextInput::make(SubmissionArtist::ATTRIBUTE_NAME)
->label(__('filament.fields.artist.name.name'))
->helperText(__('filament.fields.artist.name.help'))
->required()
->maxLength(192)
->columnSpan(1)
->afterStateUpdatedJs(<<<'JS'
$set('slug', slug($state ?? ''));
JS),
TextInput::make(SubmissionArtist::ATTRIBUTE_SLUG)
->label(__('filament.fields.artist.slug.name'))
->helperText(__('filament.fields.artist.slug.help'))
->columnSpan(1)
->required()
->alphaDash()
->maxLength(192)
->unique(Artist::TABLE, Artist::ATTRIBUTE_SLUG),
MarkdownEditor::make(SubmissionArtist::ATTRIBUTE_INFORMATION)
->label(__('filament.fields.artist.information.name'))
->helperText(__('filament.fields.artist.information.help'))
->columnSpanFull()
->maxLength(65535),
])
->columns(3);
}
}
@@ -1,45 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Filament\Submission\Resources\Schema;
use App\Filament\Components\Fields\TextInput;
use App\Models\User\Submission\SubmissionAnimeThemeEntry;
use Filament\Forms\Components\Checkbox;
use Filament\Schemas\Schema;
class EntryForm
{
public static function configure(Schema $schema): Schema
{
return $schema
->components([
TextInput::make(SubmissionAnimeThemeEntry::ATTRIBUTE_VERSION)
->label(__('filament.fields.anime_theme_entry.version.name'))
->helperText(__('filament.fields.anime_theme_entry.version.help'))
->default(1)
->integer()
->required(),
TextInput::make(SubmissionAnimeThemeEntry::ATTRIBUTE_EPISODES)
->label(__('filament.fields.anime_theme_entry.episodes.name'))
->helperText(__('filament.fields.anime_theme_entry.episodes.help'))
->maxLength(192),
Checkbox::make(SubmissionAnimeThemeEntry::ATTRIBUTE_NSFW)
->label(__('filament.fields.anime_theme_entry.nsfw.name'))
->helperText(__('filament.fields.anime_theme_entry.nsfw.help')),
Checkbox::make(SubmissionAnimeThemeEntry::ATTRIBUTE_SPOILER)
->label(__('filament.fields.anime_theme_entry.spoiler.name'))
->helperText(__('filament.fields.anime_theme_entry.spoiler.help')),
TextInput::make(SubmissionAnimeThemeEntry::ATTRIBUTE_NOTES)
->label(__('filament.fields.anime_theme_entry.notes.name'))
->helperText(__('filament.fields.anime_theme_entry.notes.help'))
->maxLength(192),
])
->columns(1);
}
}
@@ -1,47 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Filament\Submission\Resources\Schema;
use App\Enums\Models\Wiki\ResourceSite;
use App\Filament\Components\Fields\Select;
use App\Filament\Components\Fields\TextInput;
use App\Models\User\Submission\SubmissionExternalResource;
use Filament\Schemas\Components\Utilities\Set;
use Filament\Schemas\Schema;
class ResourceForm
{
public static function configure(Schema $schema): Schema
{
return $schema
->components([
Select::make(SubmissionExternalResource::ATTRIBUTE_SITE)
->label(__('filament.fields.external_resource.site.name'))
->helperText(__('filament.fields.external_resource.site.help'))
->options(ResourceSite::class)
->required(),
TextInput::make(SubmissionExternalResource::ATTRIBUTE_LINK)
->label(__('filament.fields.external_resource.link.name'))
->helperText(__('filament.fields.external_resource.link.help'))
->required()
->live()
->uri()
->partiallyRenderComponentsAfterStateUpdated([SubmissionExternalResource::ATTRIBUTE_SITE, SubmissionExternalResource::ATTRIBUTE_EXTERNAL_ID])
->afterStateUpdated(function (Set $set, ?string $state): void {
if ($state !== null) {
$set(SubmissionExternalResource::ATTRIBUTE_SITE, ResourceSite::valueOf($state) ?? ResourceSite::OFFICIAL_SITE);
$set(SubmissionExternalResource::ATTRIBUTE_EXTERNAL_ID, ResourceSite::parseIdFromLink($state));
}
}),
TextInput::make(SubmissionExternalResource::ATTRIBUTE_EXTERNAL_ID)
->label(__('filament.fields.external_resource.external_id.name'))
->helperText(__('filament.fields.external_resource.external_id.help'))
->integer(),
])
->columns(1);
}
}
@@ -1,37 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Filament\Submission\Resources\Schema;
use App\Filament\Components\Fields\TextInput;
use App\Models\User\Submission\SubmissionSeries;
use App\Models\Wiki\Series;
use Filament\Schemas\Schema;
class SeriesForm
{
public static function configure(Schema $schema): Schema
{
return $schema
->components([
TextInput::make(SubmissionSeries::ATTRIBUTE_NAME)
->label(__('filament.fields.series.name.name'))
->helperText(__('filament.fields.series.name.help'))
->required()
->maxLength(192)
->afterStateUpdatedJs(<<<'JS'
$set('slug', slug($state ?? ''));
JS),
TextInput::make(SubmissionSeries::ATTRIBUTE_SLUG)
->label(__('filament.fields.series.slug.name'))
->helperText(__('filament.fields.series.slug.help'))
->required()
->alphaDash()
->maxLength(192)
->unique(Series::TABLE, Series::ATTRIBUTE_SLUG),
])
->columns(1);
}
}
@@ -1,29 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Filament\Submission\Resources\Schema;
use App\Filament\Components\Fields\TextInput;
use App\Models\User\Submission\SubmissionSong;
use Filament\Schemas\Schema;
class SongForm
{
public static function configure(Schema $schema): Schema
{
return $schema
->components([
TextInput::make(SubmissionSong::ATTRIBUTE_TITLE)
->label(__('filament.fields.song.title.name'))
->helperText(__('filament.fields.song.title.help'))
->required()
->maxLength(192),
TextInput::make(SubmissionSong::ATTRIBUTE_TITLE_NATIVE)
->label(__('filament.fields.song.title_native.name'))
->helperText(__('filament.fields.song.title_native.help'))
->maxLength(192),
]);
}
}
@@ -1,37 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Filament\Submission\Resources\Schema;
use App\Filament\Components\Fields\TextInput;
use App\Models\User\Submission\SubmissionStudio;
use App\Models\Wiki\Studio;
use Filament\Schemas\Schema;
class StudioForm
{
public static function configure(Schema $schema): Schema
{
return $schema
->components([
TextInput::make(SubmissionStudio::ATTRIBUTE_NAME)
->label(__('filament.fields.studio.name.name'))
->helperText(__('filament.fields.studio.name.help'))
->required()
->maxLength(192)
->afterStateUpdatedJs(<<<'JS'
$set('slug', slug($state ?? ''));
JS),
TextInput::make(SubmissionStudio::ATTRIBUTE_SLUG)
->label(__('filament.fields.studio.slug.name'))
->helperText(__('filament.fields.studio.slug.help'))
->required()
->alphaDash()
->maxLength(192)
->unique(Studio::TABLE, Studio::ATTRIBUTE_SLUG),
])
->columns(1);
}
}
@@ -1,33 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Filament\Submission\Resources\Schema;
use App\Enums\Models\Wiki\SynonymType;
use App\Filament\Components\Fields\Select;
use App\Filament\Components\Fields\TextInput;
use App\Models\User\Submission\SubmissionSynonym;
use Filament\Schemas\Schema;
class SynonymForm
{
public static function configure(Schema $schema): Schema
{
return $schema
->components([
Select::make(SubmissionSynonym::ATTRIBUTE_TYPE)
->label(__('filament.fields.synonym.type.name'))
->helperText(__('filament.fields.synonym.type.help'))
->options(SynonymType::class)
->required(),
TextInput::make(SubmissionSynonym::ATTRIBUTE_TEXT)
->label(__('filament.fields.synonym.text.name'))
->helperText(__('filament.fields.synonym.text.help'))
->required()
->maxLength(255),
])
->columns(1);
}
}
@@ -1,37 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Filament\Submission\Resources\Schema;
use App\Enums\Models\Wiki\ThemeType;
use App\Filament\Components\Fields\Select;
use App\Filament\Components\Fields\TextInput;
use App\Models\User\Submission\SubmissionAnimeTheme;
use App\Models\Wiki\Group;
use Filament\Schemas\Schema;
class ThemeForm
{
public static function configure(Schema $schema): Schema
{
return $schema
->components([
Select::make(SubmissionAnimeTheme::ATTRIBUTE_TYPE)
->label(__('filament.fields.anime_theme.type.name'))
->helperText(__('filament.fields.anime_theme.type.help'))
->options(ThemeType::class)
->required(),
TextInput::make(SubmissionAnimeTheme::ATTRIBUTE_SEQUENCE)
->label(__('filament.fields.anime_theme.sequence.name'))
->helperText(__('filament.fields.anime_theme.sequence.help'))
->integer(),
Select::make(SubmissionAnimeTheme::ATTRIBUTE_GROUP)
->label(__('filament.resources.singularLabel.group'))
->useScout($schema->getLivewire(), Group::class),
])
->columns(1);
}
}
+2 -1
View File
@@ -84,6 +84,7 @@ class SearchQuery
$page,
);
return collect($searchBuilder->items());
return collect($searchBuilder->items())
->when(is_numeric($term), fn (Collection $collection) => $collection->merge($modelClass::query()->whereKey($term)->get()));
}
}
+3 -7
View File
@@ -49,13 +49,13 @@ use OwenIt\Auditing\Contracts\Auditable;
use Spatie\Permission\Traits\HasRoles;
/**
* @property Collection<int, Submission> $assignedSubmissions
* @property Carbon $created_at
* @property string $email
* @property Carbon|null $email_verified_at
* @property Collection<int, ExternalProfile> $externalprofiles
* @property int $id
* @property Collection<int, Like> $likes
* @property Collection<int, Submission> $managedSubmissions
* @property string $name
* @property string $password
* @property Collection<int, Playlist> $playlists
@@ -100,9 +100,9 @@ class User extends Authenticatable implements Auditable, FilamentUser, HasAvatar
final public const string ATTRIBUTE_TWO_FACTOR_RECOVERY_CODES = 'two_factor_recovery_codes';
final public const string ATTRIBUTE_TWO_FACTOR_SECRET = 'two_factor_secret';
final public const string RELATION_ASSIGNED_SUBMISSIONS = 'assignedSubmissions';
final public const string RELATION_EXTERNAL_PROFILES = 'externalprofiles';
final public const string RELATION_LIKES = 'likes';
final public const string RELATION_MANAGED_SUBMISSIONS = 'managedSubmissions';
final public const string RELATION_NOTIFICATIONS = 'notifications';
final public const string RELATION_PERMISSIONS = 'permissions';
final public const string RELATION_PROHIBITIONS = 'prohibitions';
@@ -170,10 +170,6 @@ class User extends Authenticatable implements Auditable, FilamentUser, HasAvatar
*/
public function canAccessPanel(Panel $panel): bool
{
if ($panel->getId() === 'submission') {
return $this->hasAnyPermission(SpecialPermission::MAKE_SUBMISSION->value);
}
if ($this->hasVerifiedEmail() && $this->hasAnyPermission(SpecialPermission::VIEW_FILAMENT->value)) {
return true;
}
@@ -223,7 +219,7 @@ class User extends Authenticatable implements Auditable, FilamentUser, HasAvatar
/**
* Get the submissions that the admin managed.
*/
public function managedSubmissions(): HasMany
public function assignedSubmissions(): HasMany
{
return $this->hasMany(Submission::class, Submission::ATTRIBUTE_ASSIGNEE);
}
@@ -1,55 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Providers\Filament;
use Filament\Http\Middleware\Authenticate;
use Filament\Http\Middleware\AuthenticateSession;
use Filament\Http\Middleware\DisableBladeIconComponents;
use Filament\Http\Middleware\DispatchServingFilamentEvent;
use Filament\Panel;
use Filament\PanelProvider;
use Filament\Support\Colors\Color;
use Filament\Support\Enums\Width;
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
use Illuminate\Cookie\Middleware\EncryptCookies;
use Illuminate\Foundation\Http\Middleware\PreventRequestForgery;
use Illuminate\Routing\Middleware\SubstituteBindings;
use Illuminate\Session\Middleware\StartSession;
use Illuminate\Support\Facades\Config;
use Illuminate\View\Middleware\ShareErrorsFromSession;
class SubmissionPanelProvider extends PanelProvider
{
public function panel(Panel $panel): Panel
{
return $panel
->id(Config::get('filament.submission.path'))
->path(Config::get('filament.submission.path'))
->domain(Config::get('filament.submission.domain'))
->login()
->maxContentWidth(Width::Full)
->globalSearch(false)
->colors([
'primary' => Color::Violet,
])
->discoverResources(in: app_path('Filament/Submission/Resources'), for: 'App\Filament\Submission\Resources')
->discoverPages(in: app_path('Filament/Submission/Pages'), for: 'App\Filament\Submission\Pages')
->discoverWidgets(in: app_path('Filament/Submission/Widgets'), for: 'App\Filament\Submission\Widgets')
->middleware([
EncryptCookies::class,
AddQueuedCookiesToResponse::class,
StartSession::class,
AuthenticateSession::class,
ShareErrorsFromSession::class,
PreventRequestForgery::class,
SubstituteBindings::class,
DisableBladeIconComponents::class,
DispatchServingFilamentEvent::class,
])
->authMiddleware([
Authenticate::class,
]);
}
}
-1
View File
@@ -232,7 +232,6 @@ return [
App\Providers\GraphQLServiceProvider::class,
App\Providers\HorizonServiceProvider::class,
App\Providers\PulseServiceProvider::class,
App\Providers\Filament\SubmissionPanelProvider::class,
App\Providers\RouteServiceProvider::class,
])->toArray(),
-14
View File
@@ -57,20 +57,6 @@ return [
'path' => env('FILAMENT_PATH'),
/*
|--------------------------------------------------------------------------
| Submission
|--------------------------------------------------------------------------
|
| Submission-related configuration.
|
*/
'submission' => [
'domain' => env('FILAMENT_SUBMISSION_URL', env('FILAMENT_URL')),
'path' => env('FILAMENT_SUBMISSION_PATH', ''),
],
/*
|--------------------------------------------------------------------------
| Default Filesystem Disk
+1
View File
@@ -274,6 +274,7 @@ return [
],
'search-parameters' => [
'query_by' => 'name,synonyms',
'query_by_weights' => '8,5',
],
],
AnimeTheme::class => [
-10
View File
@@ -871,14 +871,6 @@ return [
'prohibition' => [
'name' => 'Name',
],
'submission' => [
'finished_at' => 'Finished At',
'locked' => 'Locked',
'moderator' => 'Moderator',
'moderator_notes' => 'Moderator Notes',
'notes' => 'Notes',
'status' => 'Status',
],
'synonym' => [
'text' => [
'help' => 'For alternative titles, licensed titles, common abbreviations and/or shortenings.',
@@ -1039,7 +1031,6 @@ return [
'playlist_tracks' => 'Playlist Tracks',
'playlists' => 'Playlists',
'prohibitions' => 'Prohibitions',
'submissions' => 'Submissions',
'roles' => 'Roles',
'sanctions' => 'Sanctions',
'series' => 'Series',
@@ -1076,7 +1067,6 @@ return [
'playlist' => 'Playlist',
'prohibition' => 'Prohibition',
'sanction' => 'Sanction',
'submission' => 'Submission',
'role' => 'Role',
'series' => 'Series',
'song' => 'Song',
-31
View File
@@ -1,31 +0,0 @@
<?php
declare(strict_types=1);
return [
'buttons' => [
'submit' => 'Submit',
'submit_another' => 'Submit & Submit another',
],
'fields' => [
'base' => [
'source' => [
'help' => 'Add any relevant notes or sources that support this submission.',
'name' => 'Sources',
],
],
'submission' => [
'name' => 'Name',
'finished_at' => 'Finished At',
'status' => 'Status',
],
],
'resources' => [
'label' => [
'anime' => 'Anime Submissions',
],
'singularLabel' => [
'anime' => 'Anime Submission',
],
],
];