diff --git a/packages/forms/resources/views/components/file-upload.blade.php b/packages/forms/resources/views/components/file-upload.blade.php index 5b33aeedbdb..6f90ac07f0d 100644 --- a/packages/forms/resources/views/components/file-upload.blade.php +++ b/packages/forms/resources/views/components/file-upload.blade.php @@ -61,7 +61,7 @@ FilePond.registerPlugin(FilePondPluginImageTransform) let config = { - acceptedFileTypes: @json($formComponent->acceptedFileTypes), + acceptedFileTypes: {{ json_encode($formComponent->acceptedFileTypes) }}, files: [], {{ $formComponent->imageCropAspectRatio !== null ? "imageCropAspectRatio: '{$formComponent->imageCropAspectRatio}'," : null }} {{ $formComponent->imagePreviewHeight !== null ? "imagePreviewHeight: {$formComponent->imagePreviewHeight}," : null }} diff --git a/packages/tables/resources/views/cells/icon.blade.php b/packages/tables/resources/views/cells/icon.blade.php index 6cb9876001b..74a63a89dd3 100644 --- a/packages/tables/resources/views/cells/icon.blade.php +++ b/packages/tables/resources/views/cells/icon.blade.php @@ -13,19 +13,19 @@ @endphp @if ($iconToShow) - @if ($column->action) + @if ($column->getAction($record) !== null) - @elseif ($column->url) + @elseif ($column->getUrl($record) !== null) shouldOpenUrlInNewTab) - target="_blank" - rel="noopener noreferrer" + target="_blank" + rel="noopener noreferrer" @endif > diff --git a/packages/tables/resources/views/cells/image.blade.php b/packages/tables/resources/views/cells/image.blade.php index 4476baf1412..2ba57a93939 100644 --- a/packages/tables/resources/views/cells/image.blade.php +++ b/packages/tables/resources/views/cells/image.blade.php @@ -1,6 +1,6 @@ -@if ($column->action) +@if ($column->getAction($record) !== null) -@elseif ($column->url) +@elseif ($column->getUrl($record) !== null) shouldOpenUrlInNewTab) diff --git a/packages/tables/resources/views/cells/text.blade.php b/packages/tables/resources/views/cells/text.blade.php index f2df7aa8aea..6cd01455299 100644 --- a/packages/tables/resources/views/cells/text.blade.php +++ b/packages/tables/resources/views/cells/text.blade.php @@ -3,15 +3,15 @@ @endphp
- @if ($column->action) + @if ($column->getAction($record) !== null) - @elseif ($column->url) + @elseif ($column->getUrl($record) !== null) action === null) return null; + + if (is_callable($this->action)) { + $callback = $this->action; + + return $callback($record); + } + + return $this->action; + } } diff --git a/packages/tables/src/Columns/Concerns/CanOpenUrl.php b/packages/tables/src/Columns/Concerns/CanOpenUrl.php index f9d85834b09..101fea1287d 100644 --- a/packages/tables/src/Columns/Concerns/CanOpenUrl.php +++ b/packages/tables/src/Columns/Concerns/CanOpenUrl.php @@ -10,6 +10,8 @@ trait CanOpenUrl public function getUrl($record) { + if ($this->url === null) return null; + if (is_callable($this->url)) { $callback = $this->url; diff --git a/packages/tables/src/Filter.php b/packages/tables/src/Filter.php index fae81db153a..b4b9e119307 100644 --- a/packages/tables/src/Filter.php +++ b/packages/tables/src/Filter.php @@ -23,13 +23,23 @@ class Filter protected $pendingIncludedContextModifications = []; - public function __construct($name, $callback = null) + public function __construct($name = null, $callback = null) { - $this->name($name); + if ($name) { + $this->name($name); + } + $this->callback($callback); + + $this->setUp(); + } + + protected function setUp() + { + // } - public static function make($name, $callback = null) + public static function make($name = null, $callback = null) { return new static($name, $callback); } @@ -41,6 +51,13 @@ public function callback($callback) return $this; } + public function apply($query) + { + $callback = $this->callback; + + return $callback($query); + } + public function context($context) { $this->context = $context; diff --git a/packages/tables/src/HasTable.php b/packages/tables/src/HasTable.php index 4609c77125f..7f04be7e490 100644 --- a/packages/tables/src/HasTable.php +++ b/packages/tables/src/HasTable.php @@ -118,9 +118,7 @@ public function getRecords() collect($this->getTable()->filters) ->filter(fn ($filter) => $filter->name === $this->filter) ->each(function ($filter) use (&$query) { - $callback = $filter->callback; - - $query = $callback($query); + $query = $filter->apply($query); }); } diff --git a/packages/tables/src/Table.php b/packages/tables/src/Table.php index c9a489d6449..5fff1618756 100644 --- a/packages/tables/src/Table.php +++ b/packages/tables/src/Table.php @@ -12,6 +12,10 @@ class Table public $pagination = true; + public $primaryColumnAction; + + public $primaryColumnUrl; + public $recordActions = []; public $searchable = true; @@ -138,6 +142,28 @@ public function pagination($enabled) return $this; } + public function primaryRecordAction($action) + { + $this->columns = collect($this->columns) + ->map(function ($column) use ($action) { + return $column->action($action); + }) + ->toArray(); + + return $this; + } + + public function primaryRecordUrl($url) + { + $this->columns = collect($this->columns) + ->map(function ($column) use ($url) { + return $column->url($url); + }) + ->toArray(); + + return $this; + } + public function recordActions($actions) { $this->recordActions = $actions; diff --git a/src/Commands/Aliases/MakeFilterCommand.php b/src/Commands/Aliases/MakeFilterCommand.php new file mode 100644 index 00000000000..570482bc13d --- /dev/null +++ b/src/Commands/Aliases/MakeFilterCommand.php @@ -0,0 +1,8 @@ +prepend($this->option('resource') ? 'Filament\\Resources\\Forms\\Components\\' : "Filament\\Forms\\Components\\") + ->prepend($this->option('resource') ? 'Filament\\Resources\\Forms\\Components\\' : 'Filament\\Forms\\Components\\') ->replace('\\', '/') ->append('.php'), ); diff --git a/src/Commands/MakeFilterCommand.php b/src/Commands/MakeFilterCommand.php new file mode 100644 index 00000000000..6b3119cbd92 --- /dev/null +++ b/src/Commands/MakeFilterCommand.php @@ -0,0 +1,53 @@ +argument('name')) + ->trim('/') + ->trim('\\') + ->trim(' ') + ->replace('/', '\\'); + $filterClass = (string) Str::of($filter)->afterLast('\\'); + $filterNamespace = Str::of($filter)->contains('\\') ? + (string) Str::of($filter)->beforeLast('\\') : + ''; + + $path = app_path( + (string) Str::of($filter) + ->prepend($this->option('resource') ? 'Filament\\Resources\\Tables\\Filters\\' : 'Filament\\Tables\\Filters\\') + ->replace('\\', '/') + ->append('.php'), + ); + + if ($this->checkForCollision([ + $path, + ])) return; + + if (! $this->option('resource')) { + $this->copyStubToApp('Filter', $path, [ + 'class' => $filterClass, + 'namespace' => 'App\\Filament\\Tables\\Filters' . ($filterNamespace !== '' ? "\\{$filterNamespace}" : ''), + ]); + } else { + $this->copyStubToApp('ResourceFilter', $path, [ + 'class' => $filterClass, + 'namespace' => 'App\\Filament\\Resources\\Tables\\Filters' . ($filterNamespace !== '' ? "\\{$filterNamespace}" : ''), + ]); + } + + $this->info("Successfully created {$filter}!"); + } +} diff --git a/src/FilamentServiceProvider.php b/src/FilamentServiceProvider.php index 394e9fe1dfe..97a680152ba 100644 --- a/src/FilamentServiceProvider.php +++ b/src/FilamentServiceProvider.php @@ -34,6 +34,7 @@ public function boot() $this->bootLoaders(); $this->bootLivewireComponents(); $this->bootPublishing(); + $this->configure(); } @@ -65,6 +66,7 @@ protected function bootCommands() Commands\MakeWidgetCommand::class, Commands\MakeFieldCommand::class, Commands\MakeThemeCommand::class, + Commands\MakeFilterCommand::class, ]); $aliases = []; @@ -195,9 +197,7 @@ protected function registerIcons() protected function registerProviders() { - $this->app->booted(function () { - $this->app->register(RouteServiceProvider::class); - }); + $this->app->register(RouteServiceProvider::class); } protected function mergeConfigFrom($path, $key) diff --git a/src/Resources/Pages/ListRecords.php b/src/Resources/Pages/ListRecords.php index d0b5e97ca61..7d477d925fa 100644 --- a/src/Resources/Pages/ListRecords.php +++ b/src/Resources/Pages/ListRecords.php @@ -80,6 +80,14 @@ public function getTable() ->context(static::class) ->filterable($this->filterable) ->pagination($this->pagination) + ->primaryRecordUrl(function ($record) { + if (! Filament::can('update', $record)) return; + + return $this->getResource()::generateUrl( + $this->recordRoute, + ['record' => $record], + ); + }) ->recordActions([ RecordActions\Link::make('edit') ->label(static::$editRecordActionLabel) diff --git a/src/Resources/RelationManager.php b/src/Resources/RelationManager.php index 19f858da598..8282ff5948c 100644 --- a/src/Resources/RelationManager.php +++ b/src/Resources/RelationManager.php @@ -166,6 +166,11 @@ public function getTable() return static::table(Table::make()) ->filterable($this->filterable) ->pagination(false) + ->primaryRecordAction(function ($record) { + if (! Filament::can('update', $record)) return; + + return 'openEdit'; + }) ->recordActions([ RecordActions\Link::make('edit') ->label(static::$editRecordActionLabel) diff --git a/src/Sushi.php b/src/Sushi.php deleted file mode 100644 index 62b035bf668..00000000000 --- a/src/Sushi.php +++ /dev/null @@ -1,174 +0,0 @@ -getFileName(); - - $states = [ - 'cache-file-found-and-up-to-date' => function () use ($cachePath) { - static::setSqliteConnection($cachePath); - }, - 'cache-file-not-found-or-stale' => function () use ($cachePath, $modelPath, $instance) { - file_put_contents($cachePath, ''); - - static::setSqliteConnection($cachePath); - - $instance->migrate(); - - touch($cachePath, filemtime($modelPath)); - }, - 'no-caching-capabilities' => function () use ($instance) { - static::setSqliteConnection(':memory:'); - - $instance->migrate(); - }, - ]; - - switch (true) { - case ! property_exists($instance, 'rows'): - $states['no-caching-capabilities'](); - break; - - case file_exists($cachePath) && filemtime($modelPath) <= filemtime($cachePath): - $states['cache-file-found-and-up-to-date'](); - break; - - case file_exists($cacheDirectory) && is_writable($cacheDirectory): - $states['cache-file-not-found-or-stale'](); - break; - - default: - $states['no-caching-capabilities'](); - break; - } - } - - public static function resolveConnection($connection = null) - { - return static::$sushiConnection; - } - - protected static function setSqliteConnection($database) - { - static::$sushiConnection = app(ConnectionFactory::class)->make([ - 'driver' => 'sqlite', - 'database' => $database, - ]); - } - - public function migrate() - { - $rows = $this->getRows(); - $tableName = $this->getTable(); - - if (count($rows)) { - $this->createTable($tableName, $rows[0]); - } else { - $this->createTableWithNoData($tableName); - } - - static::insert($rows); - } - - public function getRows() - { - return $this->rows; - } - - public function createTable(string $tableName, $firstRow) - { - static::resolveConnection()->getSchemaBuilder()->create($tableName, function ($table) use ($firstRow) { - // Add the "id" column if it doesn't already exist in the rows. - if ($this->incrementing && ! in_array($this->primaryKey, array_keys($firstRow))) { - $table->increments($this->primaryKey); - } - - foreach ($firstRow as $column => $value) { - switch (true) { - case is_int($value): - $type = 'integer'; - break; - case is_numeric($value): - $type = 'float'; - break; - case is_string($value): - $type = 'string'; - break; - case is_object($value) && $value instanceof DateTime: - $type = 'dateTime'; - break; - default: - $type = 'string'; - } - - if ($column === $this->primaryKey && $type == 'integer') { - $table->increments($this->primaryKey); - continue; - } - - $schema = $this->getSchema(); - - $type = $schema[$column] ?? $type; - - $table->{$type}($column)->nullable(); - } - - if ($this->usesTimestamps() && (! in_array('updated_at', array_keys($firstRow)) || ! in_array('created_at', array_keys($firstRow)))) { - $table->timestamps(); - } - }); - } - - public function getSchema() - { - return $this->schema ?? []; - } - - public function usesTimestamps() - { - // Override the Laravel default value of $timestamps = true; Unless otherwise set. - return (new ReflectionClass($this))->getProperty('timestamps')->class === static::class - ? parent::usesTimestamps() - : false; - } - - public function createTableWithNoData(string $tableName) - { - static::resolveConnection()->getSchemaBuilder()->create($tableName, function ($table) { - $schema = $this->schema; - - if ($this->incrementing && ! in_array($this->primaryKey, array_keys($schema))) { - $table->increments($this->primaryKey); - } - - foreach ($schema as $name => $type) { - if ($name === $this->primaryKey && $type == 'integer') { - $table->increments($this->primaryKey); - continue; - } - - $table->{$type}($name)->nullable(); - } - - if ($this->usesTimestamps() && (! in_array('updated_at', array_keys($schema)) || ! in_array('created_at', array_keys($schema)))) { - $table->timestamps(); - } - }); - } -} diff --git a/stubs/Filter.stub b/stubs/Filter.stub new file mode 100644 index 00000000000..ca26dd7d0aa --- /dev/null +++ b/stubs/Filter.stub @@ -0,0 +1,18 @@ +name('{{ name }}'); + } + + public function apply($query) + { + return $query; + } +} diff --git a/stubs/ResourceFilter.stub b/stubs/ResourceFilter.stub new file mode 100644 index 00000000000..c16ebf87731 --- /dev/null +++ b/stubs/ResourceFilter.stub @@ -0,0 +1,18 @@ +name('{{ name }}'); + } + + public function apply($query) + { + return $query; + } +}