Skip to content

Commit b09a620

Browse files
committed
Fields Relationmanager
1 parent 60929cf commit b09a620

File tree

2 files changed

+274
-0
lines changed

2 files changed

+274
-0
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
namespace Vormkracht10\FilamentFields\Concerns;
4+
5+
use Exception;
6+
use Illuminate\Support\Str;
7+
use Vormkracht10\Fields\Facades\Fields;
8+
use Vormkracht10\FilamentFields\Enums\Field;
9+
10+
trait HasFieldTypeResolver
11+
{
12+
/** @throws Exception If the field type class cannot be resolved. */
13+
protected function getFieldTypeFormSchema(?string $fieldType): array
14+
{
15+
if (empty($fieldType)) {
16+
return [];
17+
}
18+
19+
try {
20+
$className = $this->resolveFieldTypeClassName($fieldType);
21+
22+
if (! $this->isValidFieldClass($className)) {
23+
return [];
24+
}
25+
26+
return app($className)->getForm();
27+
} catch (Exception $e) {
28+
throw new Exception(message: "Failed to resolve field type class for '{$fieldType}'", code: 0, previous: $e);
29+
}
30+
}
31+
32+
protected static function resolveFieldTypeClassName(string $fieldType): ?string
33+
{
34+
if (Field::tryFrom($fieldType)) {
35+
return sprintf('Vormkracht10\\FilamentFields\\Fields\\%s', Str::studly($fieldType));
36+
}
37+
38+
return Fields::getFields()[$fieldType] ?? null;
39+
}
40+
41+
protected function isValidFieldClass(?string $className): bool
42+
{
43+
return $className !== null
44+
&& class_exists($className)
45+
&& method_exists($className, 'getForm');
46+
}
47+
}
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
<?php
2+
3+
namespace Vormkracht10\FilamentFields\Filament\RelationManagers;
4+
5+
use Filament\Tables;
6+
use Filament\Forms\Get;
7+
use Filament\Forms\Set;
8+
use Livewire\Component;
9+
use Filament\Forms\Form;
10+
use Filament\Tables\Table;
11+
use Illuminate\Support\Str;
12+
use Filament\Forms\Components\Grid;
13+
use Filament\Forms\Components\Select;
14+
use Filament\Forms\Components\Section;
15+
use Illuminate\Database\Eloquent\Model;
16+
use Filament\Forms\Components\TextInput;
17+
use Vormkracht10\FilamentFields\Models\Field;
18+
use Filament\Resources\RelationManagers\RelationManager;
19+
use Vormkracht10\Fields\Facades\Fields;
20+
use Vormkracht10\FilamentFields\Concerns\HasFieldTypeResolver;
21+
use Vormkracht10\FilamentFields\Concerns\HasConfigurableFields;
22+
23+
class FieldsRelationManager extends RelationManager
24+
{
25+
use HasConfigurableFields;
26+
use HasFieldTypeResolver;
27+
28+
protected static string $relationship = 'fields';
29+
30+
public function form(Form $form): Form
31+
{
32+
return $form
33+
->schema([
34+
Grid::make()
35+
->columns(3)
36+
->schema([
37+
Section::make('Field')
38+
->columns(3)
39+
->schema([
40+
TextInput::make('name')
41+
->label(__('Name'))
42+
->required()
43+
->placeholder(__('Name'))
44+
->live(debounce: 250)
45+
->afterStateUpdated(fn(Set $set, ?string $state) => $set('slug', Str::slug($state))),
46+
47+
TextInput::make('slug')
48+
->readonly(),
49+
50+
Select::make('field_type')
51+
->searchable()
52+
->preload()
53+
->label(__('Field Type'))
54+
->live(debounce: 250)
55+
->reactive()
56+
->options(
57+
function () {
58+
$options = array_merge(
59+
Field::array(),
60+
$this->prepareCustomFieldOptions(Fields::getFields())
61+
);
62+
63+
asort($options);
64+
65+
return $options;
66+
}
67+
)
68+
->required()
69+
->afterStateUpdated(function ($state, Set $set) {
70+
$set('config', []);
71+
72+
$set('config', $this->initializeConfig($state));
73+
}),
74+
]),
75+
Section::make('Configuration')
76+
->columns(3)
77+
->schema(fn(Get $get) => $this->getFieldTypeFormSchema(
78+
$get('field_type')
79+
))
80+
->visible(fn(Get $get) => filled($get('field_type'))),
81+
]),
82+
]);
83+
}
84+
85+
private function formatCustomFields(array $fields): array
86+
{
87+
return collect($fields)->mapWithKeys(function ($field, $key) {
88+
$parts = explode('\\', $field);
89+
$lastPart = end($parts);
90+
$formattedName = Str::title(Str::snake($lastPart, ' '));
91+
92+
return [$key => $formattedName];
93+
})->toArray();
94+
}
95+
96+
private function initializeDefaultConfig(string $fieldType): array
97+
{
98+
$className = 'Vormkracht10\\FilamentFields\\Fields\\' . Str::studly($fieldType);
99+
100+
if (! class_exists($className)) {
101+
return [];
102+
}
103+
104+
$fieldInstance = app($className);
105+
106+
return $fieldInstance::getDefaultConfig();
107+
}
108+
109+
private function initializeCustomConfig(string $fieldType): array
110+
{
111+
$className = Fields::getFields()[$fieldType] ?? null;
112+
113+
if (! class_exists($className)) {
114+
return [];
115+
}
116+
117+
$fieldInstance = app($className);
118+
119+
return $fieldInstance::getDefaultConfig();
120+
}
121+
122+
public function table(Table $table): Table
123+
{
124+
return $table
125+
->recordTitleAttribute('name')
126+
->reorderable('position')
127+
->defaultSort('position', 'asc')
128+
->columns([
129+
Tables\Columns\TextColumn::make('name')
130+
->label(__('Name'))
131+
->searchable()
132+
->limit(),
133+
134+
Tables\Columns\TextColumn::make('field_type')
135+
->label(__('Type'))
136+
->searchable(),
137+
])
138+
->filters([])
139+
->headerActions([
140+
Tables\Actions\CreateAction::make()
141+
->slideOver()
142+
->mutateFormDataUsing(function (array $data) {
143+
return [
144+
...$data,
145+
'position' => Field::where('model_key', $this->ownerRecord->id)->get()->max('position') + 1,
146+
'model_type' => 'setting',
147+
'model_key' => $this->ownerRecord->slug,
148+
];
149+
})
150+
->after(function (Component $livewire) {
151+
$livewire->dispatch('refreshFields');
152+
}),
153+
])
154+
->actions([
155+
Tables\Actions\EditAction::make()
156+
->slideOver()
157+
->mutateRecordDataUsing(function (array $data) {
158+
return [
159+
...$data,
160+
'model_type' => 'setting',
161+
'model_key' => $this->ownerRecord->slug,
162+
];
163+
})
164+
->mutateFormDataUsing(fn(array $data, Model $record): array => $this->transferValuesOnSlugChange($data, $record))
165+
->after(function (Component $livewire) {
166+
$livewire->dispatch('refreshFields');
167+
}),
168+
Tables\Actions\DeleteAction::make()
169+
->after(function (Component $livewire, array $data, Model $record, array $arguments) {
170+
$this->ownerRecord->update([
171+
'values' => collect($this->ownerRecord->values)->forget($record->slug)->toArray(),
172+
]);
173+
$livewire->dispatch('refreshFields');
174+
}),
175+
])
176+
->bulkActions([
177+
Tables\Actions\BulkActionGroup::make([
178+
Tables\Actions\DeleteBulkAction::make()
179+
->after(function (Component $livewire) {
180+
$livewire->dispatch('refreshFields');
181+
}),
182+
]),
183+
]);
184+
}
185+
186+
public static function getTitle(Model $ownerRecord, string $pageClass): string
187+
{
188+
return __('Fields');
189+
}
190+
191+
public static function getModelLabel(): string
192+
{
193+
return __('Field');
194+
}
195+
196+
public static function getPluralModelLabel(): string
197+
{
198+
return __('Fields');
199+
}
200+
201+
private function transferValuesOnSlugChange(array $data, Model $record): array
202+
{
203+
$oldSlug = $record->slug;
204+
$newSlug = $data['slug'];
205+
206+
if ($newSlug === $oldSlug) {
207+
return $data;
208+
}
209+
210+
$existingValues = $this->ownerRecord->values;
211+
212+
// Handle slug update in existing values
213+
if (isset($existingValues[$oldSlug])) {
214+
// Transfer value from old slug to new slug
215+
$existingValues[$newSlug] = $existingValues[$oldSlug];
216+
unset($existingValues[$oldSlug]);
217+
218+
$this->ownerRecord->update([
219+
'values' => $existingValues,
220+
]);
221+
} else {
222+
$existingValues[$newSlug] = null;
223+
}
224+
225+
return $data;
226+
}
227+
}

0 commit comments

Comments
 (0)