Merge pull request #281 from laravel-filament/refactor/pages

refactor: Pages
This commit is contained in:
Dan Harrin
2021-03-25 00:20:17 +00:00
committed by GitHub
152 changed files with 4175 additions and 20308 deletions
Generated
+307 -384
View File
File diff suppressed because it is too large Load Diff
@@ -6,6 +6,11 @@ use Illuminate\Support\Facades\Schema;
class CreateFilamentUsersTable extends Migration
{
public function down()
{
Schema::dropIfExists('filament_users');
}
public function up()
{
Schema::create('filament_users', function (Blueprint $table) {
@@ -20,9 +25,4 @@ class CreateFilamentUsersTable extends Migration
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('filament_users');
}
}
@@ -6,6 +6,11 @@ use Illuminate\Support\Facades\Schema;
class CreateFilamentPasswordResetsTable extends Migration
{
public function down()
{
Schema::dropIfExists('filament_password_resets');
}
public function up()
{
Schema::create('filament_password_resets', function (Blueprint $table) {
@@ -14,9 +19,4 @@ class CreateFilamentPasswordResetsTable extends Migration
$table->timestamp('created_at')->nullable();
});
}
public function down()
{
Schema::dropIfExists('filament_password_resets');
}
}
+1 -3
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -1,4 +1,4 @@
{
"/js/filament.js": "/js/filament.js?id=f6f25b9e29038b97ab02",
"/css/filament.css": "/css/filament.css?id=e3264b5b30c72fdb723e"
"/js/filament.js": "/js/filament.js?id=d0400b4255850498d8d2",
"/css/filament.css": "/css/filament.css?id=48fc5bb45100c9be4f17"
}
-1
View File
@@ -8,7 +8,6 @@ use Symplify\MonorepoBuilder\Release\ReleaseWorker\SetNextMutualDependenciesRele
use Symplify\MonorepoBuilder\Release\ReleaseWorker\TagVersionReleaseWorker;
use Symplify\MonorepoBuilder\Release\ReleaseWorker\UpdateBranchAliasReleaseWorker;
use Symplify\MonorepoBuilder\Release\ReleaseWorker\UpdateReplaceReleaseWorker;
use Symplify\MonorepoBuilder\ValueObject\Option;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
+1492 -16569
View File
File diff suppressed because it is too large Load Diff
+8 -7
View File
@@ -12,19 +12,20 @@
"start": "npm run watch"
},
"devDependencies": {
"@popperjs/core": "^2.7.0",
"@popperjs/core": "^2.9.1",
"@tailwindcss/forms": "^0.2.1",
"@tailwindcss/jit": "^0.1.7",
"@tailwindcss/typography": "^0.4.0",
"alpinejs": "^2.8.0",
"autoprefixer": "^10.2.4",
"alpinejs": "^2.8.2",
"autoprefixer": "^10.2.5",
"dotenv": "^8.2.0",
"focus-visible": "^5.2.0",
"laravel-mix": "^6.0.11",
"postcss": "^8.2.6",
"laravel-mix": "^6.0.13",
"postcss": "^8.2.8",
"postcss-import": "^14.0.0",
"postcss-nested": "^5.0.3",
"postcss-nested": "^5.0.5",
"prettier": "^2.2.1",
"rimraf": "^3.0.2",
"tailwindcss": "^2.0.3"
"tailwindcss": "^2.0.4"
}
}
@@ -0,0 +1,3 @@
<div class="bg-white space-y-6 shadow-xl rounded p-4 md:p-6">
<x-forms::form :schema="$formComponent->getSchema()" />
</div>
@@ -1,12 +0,0 @@
@props([
'form',
])
<form
wire:submit.prevent="{{ $form->getSubmitMethod() }}"
{{ $attributes }}
>
<x-forms::layout :schema="$form->getSchema()" :columns="$form->getColumns()" />
{{ $slot }}
</form>
@@ -24,5 +24,5 @@
</legend>
@endif
<x-forms::layout :schema="$formComponent->getSchema()" :columns="$formComponent->getColumns()" />
<x-forms::form :schema="$formComponent->getSchema()" :columns="$formComponent->getColumns()" />
</fieldset>
@@ -35,57 +35,6 @@
<script src="https://unpkg.com/filepond-plugin-image-preview/dist/filepond-plugin-image-preview.min.js"></script>
<script src="https://unpkg.com/filepond-plugin-image-resize/dist/filepond-plugin-image-resize.js"></script>
<script src="https://unpkg.com/filepond-plugin-image-transform/dist/filepond-plugin-image-transform.js"></script>
<script>
function fileUpload(config) {
return {
disk: config.disk,
name: config.name,
pond: null,
value: config.value,
init: function () {
FilePond.registerPlugin(FilePondPluginFileValidateSize)
FilePond.registerPlugin(FilePondPluginFileValidateType)
FilePond.registerPlugin(FilePondPluginImageCrop)
FilePond.registerPlugin(FilePondPluginImageExifOrientation)
FilePond.registerPlugin(FilePondPluginImagePreview)
FilePond.registerPlugin(FilePondPluginImageResize)
FilePond.registerPlugin(FilePondPluginImageTransform)
this.$wire.getUploadedFileUrl(this.name, this.disk).then((uploadedFileUrl) => {
if (uploadedFileUrl) {
config.files = [{
source: uploadedFileUrl,
options: {
type: 'local',
},
}]
}
this.pond = FilePond.create(this.$refs.input, config)
})
this.$watch('value', () => {
this.$wire.getUploadedFileUrl(this.name, this.disk).then((uploadedFileUrl) => {
if (uploadedFileUrl) {
this.pond.files = [{
source: uploadedFileUrl,
options: {
type: 'local',
},
}]
} else {
this.pond.files = []
}
})
})
},
}
}
</script>
@endpushonce
<x-forms::field-group
@@ -98,55 +47,93 @@
:required="$formComponent->isRequired()"
>
<div
x-data="fileUpload({
acceptedFileTypes: {{ json_encode($formComponent->getAcceptedFileTypes()) }},
x-data="{
disk: '{{ $formComponent->getDiskName() }}',
files: [],
{{ $formComponent->getImageCropAspectRatio() !== null ? "imageCropAspectRatio: '{$formComponent->getImageCropAspectRatio()}'," : null }}
{{ $formComponent->getImagePreviewHeight() !== null ? "imagePreviewHeight: {$formComponent->getImagePreviewHeight()}," : null }}
{{ $formComponent->getImageResizeTargetHeight() !== null ? "imageResizeTargetHeight: {$formComponent->getImageResizeTargetHeight()}," : null }}
{{ $formComponent->getImageResizeTargetWidth() !== null ? "imageResizeTargetWidth: {$formComponent->getImageResizeTargetWidth()}," : null }}
{{ __($formComponent->getPlaceholder()) !== null ? 'labelIdle: \'' . __($formComponent->getPlaceholder()) . '\',' : null }}
{{ $formComponent->getMaxSize() !== null ? "maxFileSize: '{$formComponent->getMaxSize()} KB'," : null }}
{{ $formComponent->getMinSize() !== null ? "minFileSize: '{$formComponent->getMinSize()} KB'," : null }}
initialUrl: {{ $formComponent->getUploadedFileUrl() ? "'{$formComponent->getUploadedFileUrl()}'" : 'null' }},
name: '{{ $formComponent->getName() }}',
server: {
load: (source, load, error, progress, abort, headers) => {
fetch(source).then((response) => {
response.blob().then((blob) => {
load(blob)
})
})
},
process: (fieldName, file, metadata, load, error, progress, abort, transfer, options) => {
@this.upload(
'{{ $formComponent->getTemporaryUploadedFilePropertyName() }}',
file,
(uploadedFilename) => load(uploadedFilename),
() => error(),
(event) => progress(event.detail.progress)
)
},
remove: (source, load, error) => {
@this.removeUploadedFile('{{ $formComponent->getName() }}').then(() => {
load()
})
},
revert: (uniqueFileId, load, error) => {
@this.clearTemporaryUploadedFile('{{ $formComponent->getName() }}').then(() => {
load()
})
},
},
styleButtonProcessItemPosition: '{{ $formComponent->getUploadButtonPosition() }}',
styleButtonRemoveItemPosition: '{{ $formComponent->getRemoveUploadButtonPosition() }}',
styleLoadIndicatorPosition: '{{ $formComponent->getLoadingIndicatorPosition() }}',
{{ $formComponent->getPanelAspectRatio() !== null ? "stylePanelAspectRatio: '{$formComponent->getPanelAspectRatio()}'," : null }}
{{ $formComponent->getPanelLayout() !== null ? "stylePanelLayout: '{$formComponent->getPanelLayout()}'," : null }}
styleProgressIndicatorPosition: '{{ $formComponent->getUploadProgressIndicatorPosition() }}',
pond: null,
value: @entangle($formComponent->getName()){{ Str::of($formComponent->getBindingAttribute())->after('wire:model') }},
})"
x-init="init()"
}"
x-init="
FilePond.registerPlugin(FilePondPluginFileValidateSize)
FilePond.registerPlugin(FilePondPluginFileValidateType)
FilePond.registerPlugin(FilePondPluginImageCrop)
FilePond.registerPlugin(FilePondPluginImageExifOrientation)
FilePond.registerPlugin(FilePondPluginImagePreview)
FilePond.registerPlugin(FilePondPluginImageResize)
FilePond.registerPlugin(FilePondPluginImageTransform)
if (initialUrl) {
files = [{
source: initialUrl,
options: {
type: 'local',
},
}]
}
pond = FilePond.create($refs.input, {
acceptedFileTypes: {{ json_encode($formComponent->getAcceptedFileTypes()) }},
files: files,
{{ $formComponent->getImageCropAspectRatio() !== null ? "imageCropAspectRatio: '{$formComponent->getImageCropAspectRatio()}'," : null }}
{{ $formComponent->getImagePreviewHeight() !== null ? "imagePreviewHeight: {$formComponent->getImagePreviewHeight()}," : null }}
{{ $formComponent->getImageResizeTargetHeight() !== null ? "imageResizeTargetHeight: {$formComponent->getImageResizeTargetHeight()}," : null }}
{{ $formComponent->getImageResizeTargetWidth() !== null ? "imageResizeTargetWidth: {$formComponent->getImageResizeTargetWidth()}," : null }}
{{ __($formComponent->getPlaceholder()) !== null ? 'labelIdle: \'' . __($formComponent->getPlaceholder()) . '\',' : null }}
{{ $formComponent->getMaxSize() !== null ? "maxFileSize: '{$formComponent->getMaxSize()} KB'," : null }}
{{ $formComponent->getMinSize() !== null ? "minFileSize: '{$formComponent->getMinSize()} KB'," : null }}
server: {
load: (source, load, error, progress, abort, headers) => {
fetch(source).then((response) => {
response.blob().then((blob) => {
load(blob)
})
})
},
process: (fieldName, file, metadata, load, error, progress, abort, transfer, options) => {
@this.upload(
'{{ $formComponent->getTemporaryUploadedFilePropertyName() }}',
file,
(uploadedFilename) => load(uploadedFilename),
() => error(),
(event) => progress(event.detail.progress)
)
},
remove: (source, load, error) => {
@this.removeUploadedFile('{{ $formComponent->getName() }}').then(() => {
load()
})
},
revert: (uniqueFileId, load, error) => {
@this.clearTemporaryUploadedFile('{{ $formComponent->getName() }}').then(() => {
load()
})
},
},
styleButtonProcessItemPosition: '{{ $formComponent->getUploadButtonPosition() }}',
styleButtonRemoveItemPosition: '{{ $formComponent->getRemoveUploadButtonPosition() }}',
styleLoadIndicatorPosition: '{{ $formComponent->getLoadingIndicatorPosition() }}',
{{ $formComponent->getPanelAspectRatio() !== null ? "stylePanelAspectRatio: '{$formComponent->getPanelAspectRatio()}'," : null }}
{{ $formComponent->getPanelLayout() !== null ? "stylePanelLayout: '{$formComponent->getPanelLayout()}'," : null }}
styleProgressIndicatorPosition: '{{ $formComponent->getUploadProgressIndicatorPosition() }}',
})
$watch('value', () => {
@this.getUploadedFileUrl(name, disk).then((uploadedFileUrl) => {
if (uploadedFileUrl) {
pond.files = [{
source: uploadedFileUrl,
options: {
type: 'local',
},
}]
} else {
pond.files = []
}
})
})
"
wire:ignore
{!! Filament\format_attributes($formComponent->getExtraAttributes()) !!}
>
@@ -23,7 +23,7 @@
@if (count($schema))
<div {{ $attributes }}>
<div class="grid {{ $columnsClasses }} gap-6">
<div class="grid gap-6 {{ $columnsClasses }}">
@foreach ($schema as $component)
{{ $component->render() }}
@endforeach
@@ -17,5 +17,5 @@
@endphp
<div class="{{ $columnSpanClass }}">
<x-forms::layout :schema="$formComponent->getSchema()" :columns="$formComponent->getColumns()" />
<x-forms::form :schema="$formComponent->getSchema()" :columns="$formComponent->getColumns()" />
</div>
@@ -1 +1 @@
<x-forms::layout :schema="$formComponent->getSchema()" />
<x-forms::form :schema="$formComponent->getSchema()" />
@@ -55,6 +55,6 @@
</div>
<div id="{{ $formComponent->getId() }}-content" x-show.transition="open" :aria-expanded="open.toString()">
<x-forms::layout :schema="$formComponent->getSchema()" :columns="$formComponent->getColumns()" />
<x-forms::form :schema="$formComponent->getSchema()" :columns="$formComponent->getColumns()" />
</div>
</section>
@@ -6,5 +6,5 @@
x-show="tab === '{{ "{$formComponent->getId()}" }}'"
class="p-4 md:p-6"
>
<x-forms::layout :schema="$formComponent->getSchema()" :columns="$formComponent->getColumns()" />
<x-forms::form :schema="$formComponent->getSchema()" :columns="$formComponent->getColumns()" />
</div>
+11
View File
@@ -0,0 +1,11 @@
<?php
namespace Filament\Forms\Components;
class Card extends Component
{
public static function make($schema = [])
{
return (new static())->schema($schema);
}
}
+5 -36
View File
@@ -3,7 +3,6 @@
namespace Filament\Forms\Components;
use Filament\Forms\Form;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
use Illuminate\Support\Traits\Tappable;
@@ -148,9 +147,9 @@ class Component
return $this->label;
}
public function getModel()
public function getLivewire()
{
return $this->getForm()->getModel();
return $this->getForm()->getLivewire();
}
public function getParent()
@@ -158,11 +157,6 @@ class Component
return $this->parent;
}
public function getRecord()
{
return $this->getForm()->getRecord();
}
public function getRules($field = null)
{
if ($field !== null) {
@@ -175,12 +169,8 @@ class Component
$rules = $this instanceof Field ? $this->rules : [];
foreach ($rules as $field => $conditions) {
$rules[$field] = $this->transformConditions($conditions);
}
foreach ($this->getSubform()->getRules() as $field => $conditions) {
$rules[$field] = array_merge($rules[$field] ?? [], $this->transformConditions($conditions));
$rules[$field] = array_merge($rules[$field] ?? [], $conditions);
}
return $rules;
@@ -193,11 +183,7 @@ class Component
public function getSubform()
{
return Form::make()
->context($this->getContext())
->model($this->getModel())
->record($this->getRecord())
->schema($this->getSchema());
return Form::extend($this)->schema($this->getSchema());
}
public function getValidationAttributes()
@@ -331,12 +317,8 @@ class Component
$callback = fn ($component) => $component->visible();
}
if ($this->getRecord() === null) {
return $this;
}
try {
$shouldExecuteCallback = $condition($this->getRecord());
$shouldExecuteCallback = $condition($this->getLivewire());
} catch (\Exception $exception) {
$shouldExecuteCallback = false;
}
@@ -348,17 +330,4 @@ class Component
return $this;
}
protected function transformConditions($conditions)
{
return collect($conditions)
->map(function ($condition) {
if (! is_string($condition)) {
return $condition;
}
return (string) Str::of($condition)->replace('{{ record }}', $this->getRecord() instanceof Model ? $this->getRecord()->getKey() : '');
})
->toArray();
}
}
@@ -4,12 +4,13 @@ namespace Filament\Forms\Components\Concerns;
trait CanBeUnique
{
public function unique($table, $column = null, $exceptCurrentRecord = false)
public function unique($table, $column = null, $except = false)
{
$this->configure(function () use ($column, $exceptCurrentRecord, $table) {
$this->configure(function () use ($column, $except, $table) {
$rule = "unique:$table,$column";
if ($exceptCurrentRecord) {
$rule .= ',{{ record }}';
if ($except) {
$rule .= ",{$except}";
}
$this->addRules([$this->getName() => [$rule]]);
+6 -1
View File
@@ -8,7 +8,7 @@ class Field extends Component
{
protected $bindingAttribute = 'wire:model.defer';
protected $defaultValue = null;
protected $defaultValue;
protected $extraAttributes = [];
@@ -196,6 +196,11 @@ class Field extends Component
return $this->validationAttribute;
}
public function getValue()
{
return $this->getLivewire()->getPropertyValue($this->getName());
}
public function helpMessage($message)
{
$this->configure(function () use ($message) {
+32 -2
View File
@@ -2,7 +2,9 @@
namespace Filament\Forms\Components;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use League\Flysystem\AwsS3v3\AwsS3Adapter;
class FileUpload extends Field
{
@@ -28,9 +30,9 @@ class FileUpload extends Field
protected $minSize;
protected $panelAspectRatio = null;
protected $panelAspectRatio;
protected $panelLayout = null;
protected $panelLayout;
protected $removeUploadButtonPosition = 'left';
@@ -109,6 +111,11 @@ class FileUpload extends Field
return $this->directory;
}
public function getDisk()
{
return Storage::disk($this->getDiskName());
}
public function getDiskName()
{
return $this->diskName ?? config('forms.default_filesystem_disk');
@@ -174,6 +181,29 @@ class FileUpload extends Field
return $this->uploadButtonPosition;
}
public function getUploadedFileUrl()
{
$path = $this->getValue();
if ($path === null) {
return null;
}
$storage = $this->getDisk();
if (
$storage->getDriver()->getAdapter() instanceof AwsS3Adapter &&
$storage->getVisibility($path) === 'private'
) {
return $storage->temporaryUrl(
$path,
now()->addMinutes(5),
);
}
return $storage->url($path);
}
public function getUploadProgressIndicatorPosition()
{
return $this->uploadProgressIndicatorPosition;
+1 -1
View File
@@ -17,7 +17,7 @@ class Grid extends Component
public function getColumns()
{
return $this->columns;
return value($this->columns);
}
public function getSubform()
@@ -0,0 +1,20 @@
<?php
namespace Filament\Forms\Concerns;
use Filament\Forms\Components\Select;
trait CanHandleSelectFields
{
public function getSelectFieldOptionSearchResults($fieldName, $search = '')
{
$field = collect($this->getForm()->getFlatSchema())
->first(fn ($field) => $field instanceof Select && $field->getName() === $fieldName);
if (! $field) {
return [];
}
return $field->getOptionSearchResults($search);
}
}
@@ -0,0 +1,38 @@
<?php
namespace Filament\Forms\Concerns;
trait CanManipulateProperties
{
public function fillWithFormDefaults()
{
$this->fill($this->getPropertyDefaults());
}
public function getPropertyDefaults()
{
return $this->getForm()->getDefaultValues();
}
public function reset(...$properties)
{
parent::reset(...$properties);
$defaults = $this->getPropertyDefaults();
if (count($properties) && is_array($properties[0])) {
$properties = $properties[0];
}
if (empty($properties)) {
$properties = array_keys($defaults);
}
$propertiesToFill = collect($properties)
->filter(fn ($property) => in_array($property, $defaults))
->mapWithKeys(fn ($property) => [$property => $defaults[$property]])
->toArray();
$this->fill($propertiesToFill);
}
}
@@ -0,0 +1,137 @@
<?php
namespace Filament\Forms\Concerns;
use Filament\Forms\Components\Field;
use Filament\Forms\Components\FileUpload;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use League\Flysystem\AwsS3v3\AwsS3Adapter;
use Livewire\WithFileUploads;
trait CanUploadFiles
{
use WithFileUploads;
public $temporaryUploadedFiles = [];
public function clearTemporaryUploadedFile($name)
{
$this->syncInput(
static::getTemporaryUploadedFilePropertyName($name),
null,
false
);
}
public function getTemporaryUploadedFile($name)
{
return $this->getPropertyValue(
static::getTemporaryUploadedFilePropertyName($name)
);
}
public static function getTemporaryUploadedFilePropertyName($fieldName)
{
return "temporaryUploadedFiles.{$fieldName}";
}
public function getUploadedFileUrl($name, $disk)
{
$path = $this->getPropertyValue($name);
if (! $path) {
return null;
}
$storage = Storage::disk($disk);
if (
$storage->getDriver()->getAdapter() instanceof AwsS3Adapter &&
$storage->getVisibility($path) === 'private'
) {
return $storage->temporaryUrl(
$path,
now()->addMinutes(5),
);
}
return $storage->url($path);
}
public function removeUploadedFile($name)
{
$this->syncInput($name, null, false);
$this->clearTemporaryUploadedFile($name);
}
public function resetTemporaryUploadedFiles()
{
$this->temporaryUploadedFiles = [];
}
public function storeTemporaryUploadedFiles()
{
foreach ($this->getForm()->getFlatSchema() as $field) {
if (! $field instanceof FileUpload) {
continue;
}
$temporaryUploadedFile = $this->getTemporaryUploadedFile($field->getName());
if (! $temporaryUploadedFile) {
continue;
}
$storeMethod = $field->getVisibility() === 'public' ? 'storePublicly' : 'store';
$path = $temporaryUploadedFile->{$storeMethod}($field->getDirectory(), $field->getDiskName());
$this->syncInput($field->getName(), $path, false);
}
$this->resetTemporaryUploadedFiles();
}
public function validateTemporaryUploadedFiles()
{
$rules = collect($this->getRules())
->filter(function ($conditions, $field) {
return Str::of($field)->startsWith('temporaryUploadedFiles.');
})
->toArray();
if (! count($rules)) {
return;
}
try {
return parent::validate($rules);
} catch (ValidationException $exception) {
$fieldToFocus = collect($this->getForm()->getFlatSchema())
->first(function ($component) use ($exception) {
return (
$component instanceof Field &&
array_key_exists(
static::getTemporaryUploadedFilePropertyName($component->getName()),
$exception->validator->failed()
)
);
});
if ($fieldToFocus) {
$this->focusConcealedField($fieldToFocus);
}
$this->setErrorBag($exception->validator->errors());
foreach ($this->getErrorBag()->messages() as $field => $messages) {
$field = (string) Str::of($field)->after('temporaryUploadedFiles.');
foreach ($messages as $message) {
$this->addError($field, $message);
}
}
throw $exception;
}
}
}
@@ -0,0 +1,97 @@
<?php
namespace Filament\Forms\Concerns;
use Filament\Forms\Components\Concerns\CanConcealFields;
use Filament\Forms\Components\Field;
use Illuminate\Validation\ValidationException;
trait CanValidateInput
{
public function focusConcealedField($field)
{
$possiblyConcealingComponent = $field->getParent();
while ($possiblyConcealingComponent) {
if (in_array(
CanConcealFields::class,
class_uses_recursive($possiblyConcealingComponent),
)) {
$this->dispatchBrowserEvent(
'open',
$possiblyConcealingComponent->getId(),
);
break;
}
$possiblyConcealingComponent = $possiblyConcealingComponent->getParent();
}
}
public function getRules()
{
$rules = $this->getForm()->getRules();
foreach (parent::getRules() as $field => $conditions) {
if (! is_array($conditions)) {
$conditions = explode('|', $conditions);
}
$rules[$field] = array_merge($rules[$field] ?? [], $conditions);
}
return $rules;
}
public function getValidationAttributes()
{
$attributes = $this->getForm()->getValidationAttributes();
foreach (parent::getValidationAttributes() as $name => $label) {
$attributes[$name] = $label;
}
return $attributes;
}
public function validate($rules = null, $messages = [], $attributes = [])
{
try {
return parent::validate($rules, $messages, $attributes);
} catch (ValidationException $exception) {
$fieldToFocus = collect($this->getForm()->getFlatSchema())
->first(function ($field) use ($exception) {
return ($field instanceof Field &&
array_key_exists($field->getName(), $exception->validator->failed())
);
});
if ($fieldToFocus) {
$this->focusConcealedField($fieldToFocus);
}
throw $exception;
}
}
public function validateOnly($field, $rules = null, $messages = [], $attributes = [])
{
try {
return parent::validateOnly($field, $rules, $messages, $attributes);
} catch (ValidationException $exception) {
$fieldToFocus = collect($this->getForm()->getFlatSchema())
->first(function ($field) use ($exception) {
return ($field instanceof Field &&
array_key_exists($field->getName(), $exception->validator->failed())
);
});
if ($fieldToFocus) {
$this->focusConcealedField($fieldToFocus);
}
throw $exception;
}
}
}
+17 -35
View File
@@ -10,18 +10,14 @@ class Form
protected $columns = 1;
protected $context;
protected $livewire;
protected $model;
protected $record;
protected $parent;
protected $rules = [];
protected $schema = [];
protected $submitMethod = 'submit';
protected $validationAttributes = [];
public function columns($columns)
@@ -31,11 +27,14 @@ class Form
return $this;
}
public function context($context)
public static function extend($form)
{
$this->context = $context;
return (new static())->parent($form);
}
return $this;
public static function for($livewire)
{
return (new static())->livewire($livewire);
}
public function getColumns()
@@ -45,7 +44,7 @@ class Form
public function getContext()
{
return $this->context;
return get_class($this->getLivewire());
}
public function getDefaultValues()
@@ -70,14 +69,14 @@ class Form
return $schema;
}
public function getModel()
public function getLivewire()
{
return $this->model;
return $this->livewire ?? $this->getParent()->getLivewire();
}
public function getRecord()
public function getParent()
{
return $this->record;
return $this->parent;
}
public function getRules()
@@ -98,11 +97,6 @@ class Form
return $this->schema;
}
public function getSubmitMethod()
{
return $this->submitMethod;
}
public function getValidationAttributes()
{
$attributes = $this->validationAttributes;
@@ -114,21 +108,16 @@ class Form
return $attributes;
}
public static function make()
public function livewire($component)
{
return new static();
}
public function model($model)
{
$this->model = $model;
$this->livewire = $component;
return $this;
}
public function record($record)
public function parent($form)
{
$this->record = (object) $record;
$this->parent = $form;
return $this;
}
@@ -151,13 +140,6 @@ class Form
return $this;
}
public function submitMethod($method)
{
$this->submitMethod = $method;
return $this;
}
public function validationAttributes($attributes)
{
$this->validationAttributes = $attributes;
+14 -257
View File
@@ -2,271 +2,28 @@
namespace Filament\Forms;
use Filament\Forms\Components\Concerns\CanConcealFields;
use Filament\Forms\Components\Field;
use Filament\Forms\Components\FileUpload;
use Filament\Forms\Components\Select;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use League\Flysystem\AwsS3v3\AwsS3Adapter;
use Livewire\WithFileUploads;
trait HasForm
{
use WithFileUploads;
use Concerns\CanHandleSelectFields;
use Concerns\CanManipulateProperties;
use Concerns\CanUploadFiles;
use Concerns\CanValidateInput;
public $temporaryUploadedFiles = [];
protected $form;
public function clearTemporaryUploadedFile($name)
public function getForm()
{
$this->syncInput(
static::getTemporaryUploadedFilePropertyName($name),
null,
false
if ($this->form !== null) {
return $this->form;
}
return $this->form = $this->form(
Form::for($this),
);
}
public function fillWithFormDefaults()
protected function form(Form $form)
{
$this->fill($this->getPropertyDefaults());
}
public function focusConcealedField($field)
{
if ($field) {
$possiblyConcealingComponent = $field->getParent();
while ($possiblyConcealingComponent) {
if (in_array(
CanConcealFields::class,
class_uses_recursive($possiblyConcealingComponent),
)) {
$this->dispatchBrowserEvent(
'open',
$possiblyConcealingComponent->getId(),
);
break;
}
$possiblyConcealingComponent = $possiblyConcealingComponent->getParent();
}
}
}
public function getPropertyDefaults()
{
return $this->getForm()->getDefaultValues();
}
public function getRules()
{
$rules = $this->getForm()->getRules();
foreach (parent::getRules() as $field => $conditions) {
if (! is_array($conditions)) {
$conditions = explode('|', $conditions);
}
$rules[$field] = array_merge($rules[$field] ?? [], $conditions);
}
return $rules;
}
public function getSelectFieldOptionSearchResults($fieldName, $search = '')
{
$field = collect($this->getForm()->getFlatSchema())
->first(fn ($field) => $field instanceof Select && $field->getName() === $fieldName);
if (! $field) {
return [];
}
return $field->getOptionSearchResults($search);
}
public function getTemporaryUploadedFile($name)
{
return $this->getPropertyValue(
static::getTemporaryUploadedFilePropertyName($name)
);
}
public static function getTemporaryUploadedFilePropertyName($fieldName)
{
return "temporaryUploadedFiles.{$fieldName}";
}
public function getUploadedFileUrl($name, $disk)
{
$path = $this->getPropertyValue($name);
if (! $path) {
return null;
}
$storage = Storage::disk($disk);
if (
$storage->getDriver()->getAdapter() instanceof AwsS3Adapter &&
$storage->getVisibility($path) === 'private'
) {
return $storage->temporaryUrl(
$path,
now()->addMinutes(5),
);
}
return $storage->url($path);
}
public function getValidationAttributes()
{
$attributes = $this->getForm()->getValidationAttributes();
foreach (parent::getValidationAttributes() as $name => $label) {
$attributes[$name] = $label;
}
return $attributes;
}
public function removeUploadedFile($name)
{
$this->syncInput($name, null, false);
$this->clearTemporaryUploadedFile($name);
}
public function reset(...$properties)
{
parent::reset(...$properties);
$defaults = $this->getPropertyDefaults();
if (count($properties) && is_array($properties[0])) {
$properties = $properties[0];
}
if (empty($properties)) {
$properties = array_keys($defaults);
}
$propertiesToFill = collect($properties)
->filter(fn ($property) => in_array($property, $defaults))
->mapWithKeys(fn ($property) => [$property => $defaults[$property]])
->toArray();
$this->fill($propertiesToFill);
}
public function resetTemporaryUploadedFiles()
{
$this->temporaryUploadedFiles = [];
}
public function storeTemporaryUploadedFiles()
{
foreach ($this->getForm()->getFlatSchema() as $field) {
if (! $field instanceof FileUpload) {
continue;
}
$temporaryUploadedFile = $this->getTemporaryUploadedFile($field->getName());
if (! $temporaryUploadedFile) {
continue;
}
$storeMethod = $field->getVisibility() === 'public' ? 'storePublicly' : 'store';
$path = $temporaryUploadedFile->{$storeMethod}($field->getDirectory(), $field->getDiskName());
$this->syncInput($field->getName(), $path, false);
}
$this->resetTemporaryUploadedFiles();
}
public function validate($rules = null, $messages = [], $attributes = [])
{
try {
return parent::validate($rules, $messages, $attributes);
} catch (ValidationException $exception) {
$fieldToFocus = collect($this->getForm()->getFlatSchema())
->first(function ($field) use ($exception) {
return ($field instanceof Field &&
array_key_exists($field->getName(), $exception->validator->failed())
);
});
if ($fieldToFocus) {
$this->focusConcealedField($fieldToFocus);
}
throw $exception;
}
}
public function validateOnly($field, $rules = null, $messages = [], $attributes = [])
{
try {
return parent::validateOnly($field, $rules, $messages, $attributes);
} catch (ValidationException $exception) {
$fieldToFocus = collect($this->getForm()->getFlatSchema())
->first(function ($field) use ($exception) {
return ($field instanceof Field &&
array_key_exists($field->getName(), $exception->validator->failed())
);
});
if ($fieldToFocus) {
$this->focusConcealedField($fieldToFocus);
}
throw $exception;
}
}
public function validateTemporaryUploadedFiles()
{
$rules = collect($this->getRules())
->filter(function ($conditions, $field) {
return Str::of($field)->startsWith('temporaryUploadedFiles.');
})
->toArray();
if (! count($rules)) {
return;
}
try {
return parent::validate($rules);
} catch (ValidationException $exception) {
$fieldToFocus = collect($this->getForm()->getFlatSchema())
->first(function ($component) use ($exception) {
return (
$component instanceof Field &&
array_key_exists(
static::getTemporaryUploadedFilePropertyName($component->getName()),
$exception->validator->failed()
)
);
});
if ($fieldToFocus) {
$this->focusConcealedField($fieldToFocus);
}
$this->setErrorBag($exception->validator->errors());
foreach ($this->getErrorBag()->messages() as $field => $messages) {
$field = (string) Str::of($field)->after('temporaryUploadedFiles.');
foreach ($messages as $message) {
$this->addError($field, $message);
}
}
throw $exception;
}
return $form;
}
}
@@ -1,27 +0,0 @@
@props([
'records' => [],
'selected' => [],
'sortColumn' => null,
'sortDirection' => 'asc',
'table',
])
<div {{ $attributes->merge(['class' => 'space-y-4']) }}>
<div class="items-center justify-between space-y-4 sm:flex sm:space-y-0">
<x-tables::delete-selected :disabled="count($selected) === 0" />
<x-tables::filter :table="$table" />
</div>
<x-tables::table
:records="$records"
:selected="$selected"
:sort-column="$sortColumn"
:sort-direction="$sortDirection"
:table="$table"
/>
@if ($table->hasPagination())
<x-tables::pagination.paginator :paginator="$records" />
@endif
</div>
@@ -3,8 +3,8 @@
])
<div class="flex shadow-sm">
@if ($table->isFilterable())
<select wire:model="filter" class="text-sm flex-shrink-0 sm:text-base focus:border-blue-300 focus:ring focus:ring-blue-200 focus:ring-opacity-50 border-gray-300 {{ $table->isSearchable() ? 'rounded-l' : 'rounded' }}">
@if ($this->isFilterable())
<select wire:model="filter" class="text-sm flex-shrink-0 sm:text-base focus:border-blue-300 focus:ring focus:ring-blue-200 focus:ring-opacity-50 border-gray-300 {{ $this->isSearchable() ? 'rounded-l' : 'rounded' }}">
<option>{{ __('tables::table.filter.placeholder') }}</option>
@foreach ($table->getVisibleFilters() as $filter)
@@ -13,13 +13,13 @@
</select>
@endif
@if ($table->isSearchable())
@if ($this->isSearchable())
<div class="relative flex-grow">
<input
type="search"
wire:model="search"
placeholder="{{ __('tables::table.search.placeholder') }}"
class="text-sm sm:text-base pl-10 block w-full placeholder-gray-400 focus:placeholder-gray-500 placeholder-opacity-100 focus:border-blue-300 focus:ring focus:ring-blue-200 focus:ring-opacity-50 border-gray-300 {{ $table->isFilterable() ? 'rounded-r border-l-0' : 'rounded' }}"
class="text-sm sm:text-base pl-10 block w-full placeholder-gray-400 focus:placeholder-gray-500 placeholder-opacity-100 focus:border-blue-300 focus:ring focus:ring-blue-200 focus:ring-opacity-50 border-gray-300 {{ $this->isFilterable() ? 'rounded-r border-l-0' : 'rounded' }}"
/>
<div class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 pointer-events-none" aria-hidden="true">
@@ -6,6 +6,12 @@
'table',
])
@if ($this->isReorderable())
@pushonce('filament-scripts:table')
<script src="https://cdn.jsdelivr.net/gh/livewire/sortable@v0.x.x/dist/livewire-sortable.js"></script>
@endpushonce
@endif
<div class="overflow-x-auto bg-white rounded shadow-xl">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-200">
@@ -21,7 +27,7 @@
@foreach ($table->getVisibleColumns() as $column)
<th class="px-6 py-3 text-left text-gray-600" scope="col">
@if ($table->isSortable() && $column->isSortable())
@if ($this->isSortable() && $column->isSortable())
<button
wire:click="sortBy('{{ $column->getName() }}')"
type="button"
@@ -50,15 +56,23 @@
@endforeach
<th scope="col"></th>
@if ($this->isReorderable())
<th scope="col"></th>
@endif
</tr>
</thead>
<tbody class="text-sm leading-tight divide-y divide-gray-200">
<tbody
@if ($this->isReorderable()) wire:sortable="reorder" @endif
class="text-sm leading-tight divide-y divide-gray-200"
>
@forelse ($records as $record)
<tr
wire:key="{{ $record->getKey() }}"
wire:loading.class="opacity-50"
class="{{ $loop->index % 2 ? 'bg-gray-50' : null }}"
@if ($this->isReorderable()) wire:sortable.item="{{ $record->getKey() }}" @endif
class="{{ $loop->index % 2 ? 'bg-gray-50' : 'bg-white' }}"
>
<td class="p-4 whitespace-nowrap">
<input
@@ -80,12 +94,18 @@
{{ $recordAction->render($record) }}
@endforeach
</td>
@if ($this->isReorderable())
<td wire:sortable.handle class="px-6 border-l cursor-move whitespace-nowrap">
<x-heroicon-o-menu-alt-4 class="w-4 h-4 text-gray-500 mx-auto"/>
</td>
@endif
</tr>
@empty
<tr>
<td
class="px-6 py-4 whitespace-nowrap"
colspan="{{ count($table->getVisibleColumns()) + 2 }}"
colspan="{{ count($table->getVisibleColumns()) + ($this->isReorderable() ? 3 : 2) }}"
>
<div class="flex items-center justify-center h-16">
<p class="font-mono text-xs text-gray-500">
+91 -4
View File
@@ -26,6 +26,10 @@ class Column
protected $name;
protected $searchColumns;
protected $sortColumns;
protected $table;
protected $view;
@@ -105,11 +109,63 @@ class Column
return $this->label;
}
public function getLivewire()
{
return $this->getTable()->getLivewire();
}
public function getName()
{
return $this->name;
}
public function getRelationshipName()
{
return Str::of($this->getName())->beforeLast('.');
}
public function getSearchColumns()
{
$columns = $this->searchColumns;
if ($columns !== null) {
if (! is_array($columns)) {
$columns = [$columns];
}
if ($this->isRelationship()) {
$columns = collect($columns)
->map(fn ($column) => "{$this->getRelationshipName()}.{$column}")
->toArray();
}
return $columns;
}
return [$this->getName()];
}
public function getSortColumns()
{
$columns = $this->sortColumns;
if ($columns !== null) {
if (! is_array($columns)) {
$columns = [$columns];
}
if ($this->isRelationship()) {
$columns = collect($columns)
->map(fn ($column) => "{$this->getRelationshipName()}.{$column}")
->toArray();
}
return $columns;
}
return [$this->getName()];
}
public function getTable()
{
return $this->table;
@@ -174,6 +230,11 @@ class Column
return $this->isPrimary;
}
public function isRelationship()
{
return Str::of($this->getName())->contains('.');
}
public function isSearchable()
{
return $this->isSearchable && $this->getValueUsing === null;
@@ -253,19 +314,45 @@ class Column
]));
}
public function searchable()
public function searchable($columns = [])
{
$this->configure(function () {
$this->configure(function () use ($columns) {
$this->isSearchable = true;
if ($columns) {
$this->searchColumns($columns);
}
});
return $this;
}
public function sortable()
public function searchColumns($columns)
{
$this->configure(function () {
$this->configure(function () use ($columns) {
$this->searchColumns = $columns;
});
return $this;
}
public function sortable($columns = [])
{
$this->configure(function () use ($columns) {
$this->isSortable = true;
if ($columns) {
$this->sortColumns($columns);
}
});
return $this;
}
public function sortColumns($columns)
{
$this->configure(function () use ($columns) {
$this->sortColumns = $columns;
});
return $this;
@@ -0,0 +1,45 @@
<?php
namespace Filament\Tables\Concerns;
trait CanFilterRecords
{
public $filter;
public $isFilterable = true;
public function isFilterable()
{
return $this->isFilterable && count($this->getTable()->getFilters());
}
public function updatedFilter()
{
$this->selected = [];
if (! $this->hasPagination()) {
return;
}
$this->resetPage();
}
protected function applyFilters($query)
{
if (
! $this->isFilterable() ||
$this->filter === '' ||
$this->filter === null
) {
return $query;
}
collect($this->getTable()->getFilters())
->filter(fn ($filter) => $filter->getName() === $this->filter)
->each(function ($filter) use (&$query) {
$query = $filter->apply($query);
});
return $query;
}
}
@@ -0,0 +1,37 @@
<?php
namespace Filament\Tables\Concerns;
use Illuminate\Support\Str;
trait CanGetRecords
{
public function getRecords()
{
$query = static::getQuery();
$query = $this->loadRelationships($query);
$query = $this->applyFilters($query);
$query = $this->applySearch($query);
$query = $this->applySorting($query);
if ($this->hasPagination()) {
return $query->paginate($this->recordsPerPage);
}
return $query->get();
}
protected function loadRelationships($query)
{
$relationships = collect($this->getTable()->getColumns())
->filter(fn ($column) => Str::of($column->getName())->contains('.'))
->map(fn ($column) => (string) Str::of($column->getName())->beforeLast('.'))
->toArray();
return $query->with($relationships);
}
}
@@ -0,0 +1,37 @@
<?php
namespace Filament\Tables\Concerns;
use Livewire\WithPagination;
trait CanPaginateRecords
{
use WithPagination;
public $hasPagination = true;
public $recordsPerPage = 25;
public function hasPagination()
{
return $this->hasPagination;
}
public function setPage($page)
{
$this->page = $page;
$this->selected = [];
}
public function updatedRecordsPerPage()
{
$this->selected = [];
if (! $this->hasPagination()) {
return;
}
$this->resetPage();
}
}
@@ -0,0 +1,18 @@
<?php
namespace Filament\Tables\Concerns;
trait CanReorderRecords
{
public $isReorderable = true;
public function isReorderable()
{
return $this->isReorderable && $this->getTable()->isReorderable();
}
public function reorder($order)
{
return $this->getTable()->reorder($order);
}
}
@@ -0,0 +1,99 @@
<?php
namespace Filament\Tables\Concerns;
use Illuminate\Support\Str;
trait CanSearchRecords
{
public $isSearchable = true;
public $search = '';
protected $hasSearchQueriesApplied = false;
public function getSearch()
{
return Str::lower($this->search);
}
public function isSearchable()
{
return $this->isSearchable && collect($this->getTable()->getColumns())
->filter(fn ($column) => $column->isSearchable())
->count();
}
public function updatedSearch()
{
$this->selected = [];
if (! $this->hasPagination()) {
return;
}
$this->resetPage();
}
protected function applyRelationshipSearch($query, $searchColumn)
{
$relationshipName = (string) Str::of($searchColumn)->beforeLast('.');
$relatedColumnName = (string) Str::of($searchColumn)->afterLast('.');
return $query->{$this->hasNoSearchQueriesApplied() ? 'whereHas' : 'orWhereHas'}(
$relationshipName,
fn ($query) => $query->where($relatedColumnName, $this->getSearchOperator(), "%{$this->getSearch()}%"),
);
}
protected function applySearch($query)
{
if (
! $this->isSearchable() ||
$this->search === '' ||
$this->search === null
) {
return $query;
}
collect($this->getTable()->getColumns())
->filter(fn ($column) => $column->isSearchable())
->each(function ($column) use (&$query) {
foreach ($column->getSearchColumns() as $searchColumn) {
if ($this->isRelationshipSearch($searchColumn)) {
$query = $this->applyRelationshipSearch($query, $searchColumn);
} else {
$query = $query->{$this->hasNoSearchQueriesApplied() ? 'where' : 'orWhere'}(
fn ($query) => $query->where($searchColumn, $this->getSearchOperator(), "%{$this->getSearch()}%"),
);
}
$this->hasSearchQueriesApplied = true;
}
});
return $query;
}
protected function getSearchOperator()
{
return [
'pgsql' => 'ilike',
][$this->getQuery()->getConnection()->getDriverName()] ?? 'like';
}
protected function hasNoSearchQueriesApplied()
{
return ! $this->hasSearchQueriesApplied;
}
protected function hasSearchQueriesApplied()
{
return $this->hasSearchQueriesApplied;
}
protected function isRelationshipSearch($column)
{
return Str::of($column)->contains('.');
}
}
@@ -0,0 +1,43 @@
<?php
namespace Filament\Tables\Concerns;
trait CanSelectRecords
{
public $selected = [];
public function deleteSelected()
{
static::getModel()::destroy($this->selected);
$this->selected = [];
}
public function toggleSelectAll()
{
$records = $this->getRecords();
if (! $records->count()) {
return;
}
$keyName = $records->first()->getKeyName();
if ($records->count() !== count($this->selected)) {
$this->selected = $records->pluck($keyName)->all();
} else {
$this->selected = [];
}
}
public function toggleSelected($record)
{
if (! in_array($record, $this->selected)) {
$this->selected[] = $record;
} else {
$key = array_search($record, $this->selected);
unset($this->selected[$key]);
}
}
}
@@ -0,0 +1,144 @@
<?php
namespace Filament\Tables\Concerns;
use Illuminate\Support\Str;
trait CanSortRecords
{
public $defaultSortColumn;
public $defaultSortDirection;
public $isSortable = true;
public $sortColumn;
public $sortDirection = 'asc';
public function getDefaultSort()
{
return [$this->getDefaultSortColumn(), $this->getDefaultSortDirection()];
}
public function getDefaultSortColumn()
{
return $this->defaultSortColumn ?? $this->getTable()->getDefaultSortColumn();
}
public function getDefaultSortDirection()
{
return $this->defaultSortDirection ?? $this->getTable()->getDefaultSortDirection();
}
public function getSorts()
{
$sortColumn = $this->sortColumn;
$sortDirection = $this->sortDirection;
if (
! $this->isSortable() ||
$sortColumn === '' ||
$sortColumn === null
) {
if (! $this->hasDefaultSort()) {
return [];
}
return [
$this->getDefaultSort(),
];
}
$column = collect($this->getTable()->getColumns())
->filter(fn ($column) => $column->getName() === $sortColumn)
->first();
if ($column === null) {
return [];
}
return collect($column->getSortColumns())
->mapWithKeys(fn ($sortColumn) => [$sortColumn => $sortDirection])
->toArray();
}
public function hasDefaultSort()
{
return $this->getDefaultSortColumn() !== null;
}
public function isSortable()
{
return $this->isSortable && collect($this->getTable()->getColumns())
->filter(fn ($column) => $column->isSortable())
->count();
}
public function sortBy($column)
{
if ($this->sortColumn === $column) {
switch ($this->sortDirection) {
case 'asc':
$this->sortDirection = 'desc';
break;
case 'desc':
$this->sortColumn = null;
$this->sortDirection = 'asc';
break;
}
return;
}
$this->sortColumn = $column;
$this->sortDirection = 'asc';
}
protected function applyRelationshipSort($query, $sort)
{
[$sortColumn, $sortDirection] = $sort;
$parentModel = $query->getModel();
$relationshipName = (string) Str::of($sortColumn)->beforeLast('.');
$relationship = $parentModel->{$relationshipName}();
$relatedColumnName = (string) Str::of($sortColumn)->afterLast('.');
$relatedModel = $relationship->getModel();
return $query->orderBy(
$relatedModel
->query()
->select($relatedColumnName)
->whereColumn(
"{$relatedModel->getTable()}.{$relationship->getOwnerKeyName()}",
"{$parentModel->getTable()}.{$relationship->getForeignKeyName()}",
),
$sortDirection,
);
}
protected function applySorting($query)
{
foreach ($this->getSorts() as $sort) {
[$column, $direction] = $sort;
if ($this->isRelationshipSort($column)) {
$query = $this->applyRelationshipSort(
$query,
[$column, $direction],
);
} else {
$query = $query->orderBy($column, $direction);
}
}
return $query;
}
protected function isRelationshipSort($column)
{
return Str::of($column)->contains('.');
}
}
+5
View File
@@ -119,6 +119,11 @@ class Filter
return $this->label;
}
public function getLivewire()
{
return $this->getTable()->getLivewire();
}
public function getName()
{
return $this->name;
+17 -173
View File
@@ -2,187 +2,31 @@
namespace Filament\Tables;
use Illuminate\Support\Str;
use Livewire\WithPagination;
trait HasTable
{
use WithPagination;
use Concerns\CanFilterRecords;
use Concerns\CanGetRecords;
use Concerns\CanPaginateRecords;
use Concerns\CanReorderRecords;
use Concerns\CanSearchRecords;
use Concerns\CanSelectRecords;
use Concerns\CanSortRecords;
public $filter = null;
protected $table;
public $recordsPerPage = 25;
public $search = '';
public $selected = [];
public $sortColumn = null;
public $sortDirection = 'asc';
public function deleteSelected()
public function getTable()
{
static::getModel()::destroy($this->selected);
if ($this->table !== null) {
return $this->table;
}
$this->selected = [];
return $this->table = $this->table(
Table::for($this),
);
}
public function getRecords()
protected function table(Table $table)
{
$query = static::getQuery();
if ($this->getTable()->isFilterable() && $this->filter !== '' && $this->filter !== null) {
collect($this->getTable()->getFilters())
->filter(fn ($filter) => $filter->getName() === $this->filter)
->each(function ($filter) use (&$query) {
$query = $filter->apply($query);
});
}
if ($this->getTable()->isSearchable() && $this->search !== '' && $this->search !== null) {
collect($this->getTable()->getColumns())
->filter(fn ($column) => $column->isSearchable())
->each(function ($column, $index) use (&$query) {
$search = Str::lower($this->search);
$searchOperator = [
'pgsql' => 'ilike',
][$query->getConnection()->getDriverName()] ?? 'like';
$first = $index === 0;
if (Str::of($column->getName())->contains('.')) {
$relationship = (string) Str::of($column->getName())->beforeLast('.');
$query = $query->{$first ? 'whereHas' : 'orWhereHas'}(
$relationship,
function ($query) use ($column, $search, $searchOperator) {
$columnName = (string) Str::of($column->getName())->afterLast('.');
return $query->where($columnName, $searchOperator, "%{$search}%");
},
);
return;
}
$query = $query->{$first ? 'where' : 'orWhere'}(
fn ($query) => $query->where($column->getName(), $searchOperator, "%{$search}%"),
);
});
}
if ($this->getTable()->isSortable() && $this->sortColumn !== '' && $this->sortColumn !== null) {
if (Str::of($this->sortColumn)->contains('.')) {
$relationship = (string) Str::of($this->sortColumn)->beforeLast('.');
$query = $query->with([
$relationship => function ($query) {
return $query->orderBy(
(string) Str::of($this->sortColumn)->afterLast('.'),
$this->sortDirection,
);
},
]);
} else {
$query = $query->orderBy($this->sortColumn, $this->sortDirection);
}
}
if (! $this->getTable()->hasPagination()) {
return $query->get();
}
return $query->paginate($this->recordsPerPage);
}
public function setPage($page)
{
$this->page = $page;
$this->selected = [];
}
public function sortBy($column)
{
if ($this->sortColumn === $column) {
switch ($this->sortDirection) {
case 'asc':
$this->sortDirection = 'desc';
break;
case 'desc':
$this->sortColumn = null;
$this->sortDirection = 'asc';
break;
}
return;
}
$this->sortColumn = $column;
$this->sortDirection = 'asc';
}
public function toggleSelectAll()
{
$records = $this->getRecords();
if (! $records->count()) {
return;
}
$keyName = $records->first()->getKeyName();
if ($records->count() !== count($this->selected)) {
$this->selected = $records->pluck($keyName)->all();
} else {
$this->selected = [];
}
}
public function toggleSelected($record)
{
if (! in_array($record, $this->selected)) {
$this->selected[] = $record;
} else {
$key = array_search($record, $this->selected);
unset($this->selected[$key]);
}
}
public function updatedFilter()
{
$this->selected = [];
if (! $this->getTable()->hasPagination()) {
return;
}
$this->resetPage();
}
public function updatedRecordsPerPage()
{
$this->selected = [];
if (! $this->getTable()->hasPagination()) {
return;
}
$this->resetPage();
}
public function updatedSearch()
{
$this->selected = [];
if (! $this->getTable()->hasPagination()) {
return;
}
$this->resetPage();
return $table;
}
}
@@ -58,6 +58,11 @@ class Action
return $this;
}
public function getLivewire()
{
return $this->getTable()->getLivewire();
}
public function getName()
{
return $this->name;
+75 -67
View File
@@ -10,17 +10,13 @@ class Table
protected $columns = [];
protected $context;
protected $defaultSortColumn;
protected $defaultSortDirection = 'asc';
protected $filters = [];
protected $hasPagination = true;
protected $isFilterable = true;
protected $isSearchable = true;
protected $isSortable = true;
protected $livewire;
protected $primaryColumnAction;
@@ -28,6 +24,8 @@ class Table
protected $recordActions = [];
protected $reorderUsing;
protected $shouldPrimaryColumnUrlOpenInNewTab = false;
public function columns($columns)
@@ -41,44 +39,24 @@ class Table
return $this;
}
public function context($context)
public function defaultSort($column, $direction = 'asc')
{
$this->context = $context;
$this->defaultSortColumn($column);
$this->defaultSortDirection($direction);
return $this;
}
public function disableFiltering()
public function defaultSortColumn($column)
{
$this->isFilterable = false;
$this->defaultSortColumn = $column;
return $this;
}
public function disablePagination()
public function defaultSortDirection($direction)
{
$this->hasPagination = false;
return $this;
}
public function disableSearching()
{
$this->isSearchable = false;
return $this;
}
public function disableSorting()
{
$this->isSortable = false;
return $this;
}
public function filterable($filterable)
{
$this->isFilterable = $filterable;
$this->defaultSortDirection = $direction;
return $this;
}
@@ -94,6 +72,11 @@ class Table
return $this;
}
public static function for($livewire)
{
return (new static())->livewire($livewire);
}
public function getColumns()
{
return $this->columns;
@@ -101,7 +84,22 @@ class Table
public function getContext()
{
return $this->columns;
return get_class($this->getLivewire());
}
public function getDefaultSort()
{
return [$this->getDefaultSortColumn(), $this->getDefaultSortDirection()];
}
public function getDefaultSortColumn()
{
return $this->defaultSortColumn;
}
public function getDefaultSortDirection()
{
return $this->defaultSortDirection;
}
public function getFilters()
@@ -109,6 +107,11 @@ class Table
return $this->filters;
}
public function getLivewire()
{
return $this->livewire;
}
public function getPrimaryColumnAction($record = null)
{
$action = $this->primaryColumnAction;
@@ -154,33 +157,16 @@ class Table
return $filters;
}
public function hasPagination()
public function isReorderable()
{
return $this->hasPagination;
return $this->reorderUsing !== null;
}
public function isFilterable()
public function livewire($component)
{
return $this->isFilterable && count($this->getFilters());
}
$this->livewire = $component;
public function isSearchable()
{
return $this->isSearchable && collect($this->getColumns())
->filter(fn ($column) => $column->isSearchable())
->count();
}
public function isSortable()
{
return $this->isSortable && collect($this->columns)
->filter(fn ($column) => $column->isSortable())
->count();
}
public static function make()
{
return new static();
return $this;
}
public function openPrimaryColumnUrlInNewTab()
@@ -226,9 +212,38 @@ class Table
return $this;
}
public function searchable($searchable)
public function reorder($order)
{
$this->searchable = $searchable;
$callback = $this->reorderUsing;
return $callback($order, $this->getLivewire());
}
public function reorderOn($column)
{
$this->reorderUsing(function ($order) use ($column) {
foreach ($order as $item) {
$record = $this->getLivewire()::getQuery()->find($item['value']);
if (! $record) {
return;
}
$record->{$column} = $item['order'];
$record->save();
}
});
if ($this->getDefaultSortColumn() === null) {
$this->defaultSortColumn($column);
}
return $this;
}
public function reorderUsing($callback)
{
$this->reorderUsing = $callback;
return $this;
}
@@ -237,11 +252,4 @@ class Table
{
return $this->shouldPrimaryColumnUrlOpenInNewTab;
}
public function sortable($sortable)
{
$this->sortable = $sortable;
return $this;
}
}
+1 -1
View File
@@ -2,7 +2,7 @@ module.exports = {
plugins: [
require("postcss-import"),
require("postcss-nested"),
require("tailwindcss"),
require("@tailwindcss/jit"),
require("autoprefixer"),
],
};
File diff suppressed because it is too large Load Diff
+1 -5
View File
@@ -1,8 +1,4 @@
// import 'alpinejs'
// A custom build of Alpine is being used until #1160 (https://github.com/alpinejs/alpine/pull/1160)
// is merged in and released.
import './alpine.js'
import 'alpinejs'
import 'focus-visible'
import {createPopper} from '@popperjs/core/lib/popper-lite.js'
import flip from '@popperjs/core/lib/modifiers/flip.js'
@@ -13,7 +13,7 @@ return [
],
'detach' => [
'label' => 'حذف المحدد'
'label' => 'حذف المحدد',
],
],
+1 -1
View File
@@ -34,4 +34,4 @@ return [
'title' => 'Accedir al meu compte',
];
];
+1 -1
View File
@@ -6,4 +6,4 @@ return [
'label' => 'Tancar sessió',
],
];
];
+1 -1
View File
@@ -33,4 +33,4 @@ return [
'title' => 'Restablir la contrasenya',
];
];
+1 -1
View File
@@ -38,4 +38,4 @@ return [
'title' => 'Restablir la contrasenya',
];
];
+1 -1
View File
@@ -38,4 +38,4 @@ return [
],
];
];
+1 -1
View File
@@ -16,4 +16,4 @@ return [
'title' => 'Compte',
];
];
+1 -1
View File
@@ -14,4 +14,4 @@ return [
],
];
];
@@ -10,4 +10,4 @@ return [
],
];
];
@@ -42,4 +42,4 @@ return [
'saved' => 'Desat!',
],
];
];
@@ -13,7 +13,7 @@ return [
],
'detach' => [
'label' => 'Separar seleccionat'
'label' => 'Separar seleccionat',
],
],
@@ -128,4 +128,4 @@ return [
],
];
];
@@ -81,4 +81,4 @@ return [
],
];
];
@@ -13,7 +13,7 @@ return [
],
'detach' => [
'label' => 'Ausgewählte trennen'
'label' => 'Ausgewählte trennen',
],
],
@@ -4,10 +4,22 @@ return [
'buttons' => [
'cancel' => [
'label' => 'Cancel',
],
'create' => [
'label' => 'Create',
],
'createAnother' => [
'label' => 'Create & Create Another',
],
],
'messages' => [
'created' => 'Created!',
],
];
@@ -4,6 +4,10 @@ return [
'buttons' => [
'cancel' => [
'label' => 'Cancel',
],
'delete' => [
'label' => 'Delete',
],
@@ -13,7 +13,7 @@ return [
],
'detach' => [
'label' => 'Detach selected'
'label' => 'Detach selected',
],
],
@@ -13,7 +13,7 @@ return [
],
'detach' => [
'label' => 'Desvincular seleccionado'
'label' => 'Desvincular seleccionado',
],
],
@@ -13,7 +13,7 @@ return [
],
'detach' => [
'label' => 'Détacher la sélection'
'label' => 'Détacher la sélection',
],
],
@@ -13,7 +13,7 @@ return [
],
'detach' => [
'label' => 'Putuskan yang terpilih'
'label' => 'Putuskan yang terpilih',
],
],
@@ -13,7 +13,7 @@ return [
],
'detach' => [
'label' => 'Scollega selezionati'
'label' => 'Scollega selezionati',
],
],
@@ -13,7 +13,7 @@ return [
],
'detach' => [
'label' => 'კავშირის გაწყვეტა'
'label' => 'კავშირის გაწყვეტა',
],
],
@@ -13,7 +13,7 @@ return [
],
'detach' => [
'label' => 'Ontkoppel geselecteerde'
'label' => 'Ontkoppel geselecteerde',
],
],
@@ -13,7 +13,7 @@ return [
],
'detach' => [
'label' => 'Desvincular selecionado'
'label' => 'Desvincular selecionado',
],
],
+1 -1
View File
@@ -6,4 +6,4 @@ return [
'label' => 'Выход',
],
];
];
+1 -1
View File
@@ -33,4 +33,4 @@ return [
'title' => 'Восстановить пароль',
];
];
+1 -1
View File
@@ -38,4 +38,4 @@ return [
],
];
];
+1 -1
View File
@@ -14,4 +14,4 @@ return [
],
];
];
@@ -10,4 +10,4 @@ return [
],
];
];
@@ -42,4 +42,4 @@ return [
'saved' => 'Сохранено!',
],
];
];
@@ -22,4 +22,4 @@ return [
],
];
];
@@ -13,7 +13,7 @@ return [
],
'detach' => [
'label' => 'Отсоединить выбранное'
'label' => 'Отсоединить выбранное',
],
],
@@ -128,4 +128,4 @@ return [
],
];
];
@@ -81,4 +81,4 @@ return [
],
];
];
+7 -4
View File
@@ -1,8 +1,11 @@
<x-forms::container :form="$this->getForm()" class="space-y-6">
<x-filament::button type="submit" color="primary" class="w-full">
<x-filament::loader class="w-6 h-6 absolute left-0 ml-2 pointer-events-none" wire:loading />
<form
wire:submit.prevent="submit"
class="space-y-6"
>
<x-forms::form :schema="$this->getForm()->getSchema()" :columns="$this->getForm()->getColumns()" />
<x-filament::button type="submit" color="primary" class="w-full">
{{ __('filament::auth/login.buttons.submit.label') }}
</x-filament::button>
</x-forms::container>
</form>
+7 -4
View File
@@ -1,7 +1,10 @@
<x-forms::container :form="$this->getForm()" class="space-y-6">
<x-filament::button type="submit" color="primary" class="w-full">
<x-filament::loader class="w-6 h-6 absolute left-0 ml-2 pointer-events-none" wire:loading />
<form
wire:submit.prevent="submit"
class="space-y-6"
>
<x-forms::form :schema="$this->getForm()->getSchema()" :columns="$this->getForm()->getColumns()" />
<x-filament::button type="submit" color="primary" class="w-full">
{{ __('filament::auth.register') }}
</x-filament::button>
</x-forms::container>
</form>
@@ -1,7 +1,10 @@
<x-forms::container :form="$this->getForm()" class="space-y-6">
<x-filament::button type="submit" color="primary" class="w-full">
<x-filament::loader class="w-6 h-6 absolute left-0 ml-2 pointer-events-none" wire:loading />
<form
wire:submit.prevent="submit"
class="space-y-6"
>
<x-forms::form :schema="$this->getForm()->getSchema()" :columns="$this->getForm()->getColumns()" />
<x-filament::button type="submit" color="primary" class="w-full">
{{ __('filament::auth/request-password.buttons.submit.label') }}
</x-filament::button>
</x-forms::container>
</form>
@@ -1,7 +1,10 @@
<x-forms::container :form="$this->getForm()" class="space-y-6">
<x-filament::button type="submit" color="primary" class="w-full">
<x-filament::loader class="w-6 h-6 absolute left-0 ml-2 pointer-events-none" wire:loading />
<form
wire:submit.prevent="submit"
class="space-y-6"
>
<x-forms::form :schema="$this->getForm()->getSchema()" :columns="$this->getForm()->getColumns()" />
<x-filament::button type="submit" color="primary" class="w-full">
{{ __('filament::auth/reset-password.buttons.submit.label') }}
</x-filament::button>
</x-forms::container>
</form>
@@ -1,3 +1,3 @@
<main id="content" {{ $attributes->merge(['class' => 'flex-grow pb-4 md:mb-8 px-4 md:px-8']) }}>
<main id="content" {{ $attributes->merge(['class' => 'flex-grow max-w-screen-xl mx-auto pb-4 md:mb-8 px-4 md:px-8']) }}>
{{ $slot }}
</main>
@@ -3,7 +3,7 @@
'title',
])
<header class="flex items-center justify-between p-4 space-x-4 md:py-6 md:px-8">
<header class="flex items-center justify-between max-w-screen-xl mx-auto p-4 space-x-4 md:py-6 md:px-8">
<div class="flex items-center">
<button
type="button"
@@ -0,0 +1,16 @@
@props([
'actions' => [],
])
<div class="space-x-3">
@foreach ($actions as $button)
<x-filament::button
:color="$button->getColor()"
:href="$button->getUrl()"
:type="$button->getType()"
:wire:click="$button->getAction() ?? false"
>
{{ __($button->getLabel()) }}
</x-filament::button>
@endforeach
</div>
+7 -2
View File
@@ -3,14 +3,19 @@
<x-filament::app-content>
<x-filament::card>
<x-forms::container :form="$this->getForm()" class="space-y-6">
<form
wire:submit.prevent="save"
class="space-y-6"
>
<x-forms::form :schema="$this->getForm()->getSchema()" :columns="$this->getForm()->getColumns()" />
<x-filament::button
type="submit"
color="primary"
>
{{ __('filament::edit-account.buttons.save.label') }}
</x-filament::button>
</x-forms::container>
</form>
</x-filament::card>
</x-filament::app-content>
</div>
+26 -91
View File
@@ -1,48 +1,20 @@
@if (\Filament\Filament::can('viewAny', $this->getModel()))
<x-filament::card class="space-y-4">
<h3 class="text-lg font-medium leading-tight">
{{ __(static::getTitle()) }}
</h3>
<div>
@if (\Filament\Filament::can('viewAny', $this->getModel()))
<x-filament::card class="space-y-4">
<h3 class="text-lg font-medium leading-tight">
{{ __(static::getTitle()) }}
</h3>
<div class="items-center justify-between space-y-4 lg:flex lg:space-y-0 lg:space-x-4">
<div class="space-x-3 flex-shink-0">
@if ($this->canCreate())
<x-filament::button wire:click="openCreate">
{{ __(static::$createButtonLabel) }}
</x-filament::button>
@endif
@if ($this->canAttach())
<x-filament::button wire:click="openAttach">
{{ __(static::$attachButtonLabel) }}
</x-filament::button>
@endif
@if ($this->canDetachSelected())
<x-filament::button
wire:click="openDetach"
color="danger"
:disabled="count($selected) === 0"
>
{{ __(static::$detachButtonLabel) }}
</x-filament::button>
@endif
@if ($this->canDelete())
<x-tables::delete-selected :disabled="! $this->canDeleteSelected()" />
@endif
</div>
<x-tables::filter :table="$this->getTable()" />
</div>
<x-tables::table
:records="$records"
:selected="$selected"
:sort-column="$sortColumn"
:sort-direction="$sortDirection"
:table="$this->getTable()"
/>
@livewire(static::getComponent('list'), [
'canAttach' => $this->canAttach(),
'canCreate' => $this->canCreate(),
'canDelete' => $this->canDelete(),
'canDetach' => $this->canDetach(),
'manager' => static::class,
'model' => $this->getModel(),
'owner' => $this->getOwner(),
])
</x-filament::card>
@if ($this->canCreate())
<x-filament::modal
@@ -52,13 +24,10 @@
<x-filament::card class="space-y-5">
<x-filament::card-header :title="static::$createModalHeading" />
@livewire(\Livewire\Livewire::getAlias(Filament\Resources\RelationManager\CreateRecord::class), [
'cancelButtonLabel' => static::$createModalCancelButtonLabel,
'createAnotherButtonLabel' => static::$createModalCreateAnotherButtonLabel,
'createButtonLabel' => static::$createModalCreateButtonLabel,
'createdMessage' => static::$createModalCreatedMessage,
@livewire(static::getComponent('create'), [
'manager' => static::class,
'owner' => $this->owner,
'model' => $this->getModel(),
'owner' => $this->getOwner(),
])
</x-filament::card>
</x-filament::modal>
@@ -71,12 +40,10 @@
<x-filament::card class="space-y-5">
<x-filament::card-header :title="static::$editModalHeading" />
@livewire(\Livewire\Livewire::getAlias(Filament\Resources\RelationManager\EditRecord::class), [
'cancelButtonLabel' => static::$editModalCancelButtonLabel,
@livewire(static::getComponent('edit'), [
'manager' => static::class,
'owner' => $this->owner,
'saveButtonLabel' => static::$editModalSaveButtonLabel,
'savedMessage' => static::$editModalSavedMessage,
'model' => $this->getModel(),
'owner' => $this->getOwner(),
])
</x-filament::card>
</x-filament::modal>
@@ -89,44 +56,12 @@
<x-filament::card class="w-full space-y-5">
<x-filament::card-header :title="static::$attachModalHeading" />
@livewire(\Livewire\Livewire::getAlias(Filament\Resources\RelationManager\AttachRecord::class), [
'cancelButtonLabel' => static::$attachModalCancelButtonLabel,
'attachAnotherButtonLabel' => static::$attachModalAttachAnotherButtonLabel,
'attachButtonLabel' => static::$attachModalAttachButtonLabel,
'attachedMessage' => static::$attachModalAttachedMessage,
@livewire(static::getComponent('attach'), [
'manager' => static::class,
'owner' => $this->owner,
'owner' => $this->getOwner(),
])
</x-filament::card>
</x-filament::modal>
@endif
@if ($this->canDetachSelected())
<x-filament::modal
:name="static::class . 'RelationManagerDetachModal'"
>
<x-filament::card class="space-y-5">
<x-filament::card-header :title="static::$detachModalHeading">
<p class="text-sm text-gray-500">
{{ __(static::$detachModalDescription) }}
</p>
</x-filament::card-header>
<div class="space-y-3 sm:space-y-0 sm:space-x-3 sm:flex sm:justify-end">
<x-filament::button x-on:click="$dispatch('close', '{{ (string) Str::of(get_class($this))->replace('\\', '\\\\') }}RelationManagerDetachModal')">
{{ __(static::$detachModalCancelButtonLabel) }}
</x-filament::button>
<x-filament::button
type="button"
color="danger"
wire:click="detachSelected"
>
{{ __(static::$detachModalDetachButtonLabel) }}
</x-filament::button>
</div>
</x-filament::card>
</x-filament::modal>
@endif
</x-filament::card>
@endif
@endif
</div>
@@ -5,16 +5,23 @@
/>
<x-filament::app-content>
<x-filament::card>
<x-forms::container :form="$this->getForm()" class="space-y-6">
<x-filament::button
type="submit"
color="primary"
>
{{ __(static::$createButtonLabel) }}
</x-filament::button>
</x-forms::container>
</x-filament::card>
@php
$schema = $this->getForm()->getSchema();
@endphp
<form wire:submit.prevent="create" class="space-y-6">
@if ($this->getForm()->hasWrapper())
<x-filament::card class="space-y-6">
<x-forms::form :schema="$this->getForm()->getSchema()" :columns="$this->getForm()->getColumns()" />
<x-filament::resources.forms.actions :actions="$this->getActions()" />
</x-filament::card>
@else
<x-forms::form :schema="$this->getForm()->getSchema()" :columns="$this->getForm()->getColumns()" />
<x-filament::resources.forms.actions :actions="$this->getActions()" />
@endif
</form>
</x-filament::app-content>
<div
@@ -43,16 +43,23 @@
</x-filament::app-header>
<x-filament::app-content class="space-y-6">
<x-filament::card>
<x-forms::container :form="$this->getForm()" class="space-y-6">
<x-filament::button
type="submit"
color="primary"
>
{{ __(static::$saveButtonLabel) }}
</x-filament::button>
</x-forms::container>
</x-filament::card>
@php
$schema = $this->getForm()->getSchema();
@endphp
<form wire:submit.prevent="save" class="space-y-6">
@if ($this->getForm()->hasWrapper())
<x-filament::card class="space-y-6">
<x-forms::form :schema="$this->getForm()->getSchema()" :columns="$this->getForm()->getColumns()" />
<x-filament::resources.forms.actions :actions="$this->getActions()" />
</x-filament::card>
@else
<x-forms::form :schema="$this->getForm()->getSchema()" :columns="$this->getForm()->getColumns()" />
<x-filament::resources.forms.actions :actions="$this->getActions()" />
@endif
</form>
<x-filament::resources.relations
:owner="$record"
@@ -31,7 +31,7 @@
:table="$this->getTable()"
/>
@if ($this->getTable()->hasPagination())
@if ($this->hasPagination())
<x-tables::pagination.paginator :paginator="$records" />
@endif
</x-filament::app-content>
@@ -1,29 +1,10 @@
<div>
<x-forms::container :form="$this->getForm()" class="space-y-6">
<div class="space-y-3 sm:space-x-3 sm:space-y-0">
<x-filament::button
type="submit"
color="primary"
class="w-full sm:w-auto"
>
{{ __($attachButtonLabel) }}
</x-filament::button>
<form
wire:submit.prevent="attach"
class="space-y-6"
>
<x-forms::form :schema="$this->getForm()->getSchema()" :columns="$this->getForm()->getColumns()" />
<x-filament::button
type="button"
color="primary"
wire:click="attach(true)"
class="w-full sm:w-auto"
>
{{ __($attachAnotherButtonLabel) }}
</x-filament::button>
<x-filament::button
x-on:click="$dispatch('close', '{{ (string) Str::of($manager)->replace('\\', '\\\\') }}RelationManagerAttachModal')"
class="w-full sm:w-auto"
>
{{ __($cancelButtonLabel) }}
</x-filament::button>
</div>
</x-forms::container>
<x-filament::resources.forms.actions :actions="$this->getActions()" />
</form>
</div>
@@ -1,24 +1,10 @@
<div>
<x-forms::container :form="$this->getForm()" class="space-y-6">
<div class="space-x-3">
<x-filament::button
type="submit"
color="primary"
>
{{ __($createButtonLabel) }}
</x-filament::button>
<form
wire:submit.prevent="create"
class="space-y-6"
>
<x-forms::form :schema="$this->getForm()->getSchema()" :columns="$this->getForm()->getColumns()" />
<x-filament::button
type="button"
color="primary"
wire:click="create(true)"
>
{{ __($createAnotherButtonLabel) }}
</x-filament::button>
<x-filament::button x-on:click="$dispatch('close', '{{ (string) Str::of($manager)->replace('\\', '\\\\') }}RelationManagerCreateModal')">
{{ __($cancelButtonLabel) }}
</x-filament::button>
</div>
</x-forms::container>
<x-filament::resources.forms.actions :actions="$this->getActions()" />
</form>
</div>
@@ -1,16 +1,10 @@
<div>
<x-forms::container :form="$this->getForm()" class="space-y-6">
<div class="space-x-3">
<x-filament::button
type="submit"
color="primary"
>
{{ __($saveButtonLabel) }}
</x-filament::button>
<form
wire:submit.prevent="save"
class="space-y-6"
>
<x-forms::form :schema="$this->getForm()->getSchema()" :columns="$this->getForm()->getColumns()" />
<x-filament::button x-on:click="$dispatch('close', '{{ (string) Str::of($manager)->replace('\\', '\\\\') }}RelationManagerEditModal')">
{{ __($cancelButtonLabel) }}
</x-filament::button>
</div>
</x-forms::container>
<x-filament::resources.forms.actions :actions="$this->getActions()" />
</form>
</div>
@@ -0,0 +1,69 @@
<div class="space-y-4">
<div class="items-center justify-between space-y-4 lg:flex lg:space-y-0 lg:space-x-4">
<div class="space-x-3 flex-shink-0">
@if ($this->canCreate)
<x-filament::button wire:click="openCreate">
{{ __($this->manager::$createButtonLabel) }}
</x-filament::button>
@endif
@if ($this->canAttach)
<x-filament::button wire:click="openAttach">
{{ __($this->manager::$attachButtonLabel) }}
</x-filament::button>
@endif
@if ($this->canDetach)
<x-filament::button
wire:click="openDetach"
color="danger"
:disabled="count($selected) === 0"
>
{{ __($this->manager::$detachButtonLabel) }}
</x-filament::button>
@endif
@if ($this->canDelete)
<x-tables::delete-selected :disabled="! $this->canDeleteSelected()" />
@endif
</div>
<x-tables::filter :table="$this->getTable()" />
</div>
<x-tables::table
:records="$records"
:selected="$selected"
:sort-column="$sortColumn"
:sort-direction="$sortDirection"
:table="$this->getTable()"
/>
@if ($this->canDetach)
<x-filament::modal
:name="$this->manager . 'RelationManagerDetachModal'"
>
<x-filament::card class="space-y-5">
<x-filament::card-header :title="$this->manager::$detachModalHeading">
<p class="text-sm text-gray-500">
{{ __($this->manager::$detachModalDescription) }}
</p>
</x-filament::card-header>
<div class="space-y-3 sm:space-y-0 sm:space-x-3 sm:flex sm:justify-end">
<x-filament::button x-on:click="$dispatch('close', '{{ (string) Str::of($this->manager)->replace('\\', '\\\\') }}RelationManagerDetachModal')">
{{ __($this->manager::$detachModalCancelButtonLabel) }}
</x-filament::button>
<x-filament::button
type="button"
color="danger"
wire:click="detachSelected"
>
{{ __($this->manager::$detachModalDetachButtonLabel) }}
</x-filament::button>
</div>
</x-filament::card>
</x-filament::modal>
@endif
</div>
-1
View File
@@ -3,7 +3,6 @@
namespace Filament\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Str;
class UpgradeCommand extends Command
{
+21 -22
View File
@@ -21,28 +21,6 @@ class Login extends Component
public $remember = false;
public function getForm()
{
return Form::make()
->context(static::class)
->schema([
Components\TextInput::make('email')
->label('filament::auth/login.form.email.label')
->email()
->autofocus()
->autocomplete('email')
->required(),
Components\TextInput::make('password')
->label('filament::auth/login.form.password.label')
->hint('[' . __('filament::auth/login.form.password.hint') . '](' . route('filament.auth.password.request') . ')')
->password()
->autocomplete('current-password')
->required(),
Components\Checkbox::make('remember')
->label('filament::auth/login.form.remember.label'),
]);
}
public function render()
{
return view('filament::auth.login')
@@ -74,4 +52,25 @@ class Login extends Component
return redirect()->intended(route('filament.dashboard'));
}
protected function form(Form $form)
{
return $form
->schema([
Components\TextInput::make('email')
->label('filament::auth/login.form.email.label')
->email()
->autofocus()
->autocomplete('email')
->required(),
Components\TextInput::make('password')
->label('filament::auth/login.form.password.label')
->hint('[' . __('filament::auth/login.form.password.hint') . '](' . route('filament.auth.password.request') . ')')
->password()
->autocomplete('current-password')
->required(),
Components\Checkbox::make('remember')
->label('filament::auth/login.form.remember.label'),
]);
}
}

Some files were not shown because too many files have changed in this diff Show More