- @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;
+ }
+}