Skip to content

Commit f8930a4

Browse files
committed
feat(scopes): manage scope via form select and humanize labels
1 parent d68db29 commit f8930a4

8 files changed

Lines changed: 145 additions & 2 deletions

File tree

packages/category/src/Moox/Entities/Categories/Category/CategoryResource.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ public static function form(Schema $schema): Schema
120120
]),
121121
Section::make('')
122122
->schema([
123+
static::getScopeSelectField(),
123124
static::getTranslationStatusSelect(),
124125
static::getPublishDateField(),
125126
static::getUnpublishDateField(),

packages/core/src/Entities/Items/Draft/Pages/BaseEditDraft.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Astrotomic\Translatable\Contracts\Translatable as TranslatableContract;
66
use Filament\Actions\Action;
7+
use Filament\Forms\Components\Select;
78
use Filament\Resources\Pages\EditRecord;
89
use Illuminate\Database\Eloquent\Model;
910
use Moox\Core\Traits\CanResolveResourceClass;

packages/core/src/Entities/Items/Draft/Pages/BaseViewDraft.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Moox\Core\Entities\Items\Draft\Pages;
44

55
use Filament\Actions\Action;
6+
use Filament\Forms\Components\Select;
67
use Filament\Resources\Pages\ViewRecord;
78
use Illuminate\Database\Eloquent\Model;
89
use Moox\Core\Traits\CanResolveResourceClass;

packages/core/src/Support/Resources/Concerns/HasScopedChildResource.php

Lines changed: 132 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Illuminate\Database\Eloquent\Model;
1111
use Illuminate\Support\Facades\Auth;
1212
use Illuminate\Support\Facades\Schema;
13+
use Illuminate\Support\Str;
1314
use Moox\Core\Models\Scope;
1415
use Moox\Core\Services\ScopeAssignmentValidator;
1516
use Moox\Core\Services\ScopeRegistry;
@@ -20,6 +21,124 @@ trait HasScopedChildResource
2021
{
2122
public const ASSIGN_GLOBAL_SCOPE = '__global__';
2223

24+
public static function canAssignScopes(): bool
25+
{
26+
return static::hasMultipleAssignableScopes();
27+
}
28+
29+
public static function formatScopeForDisplay(?string $scope): string
30+
{
31+
if ($scope === null || $scope === '') {
32+
return 'Global';
33+
}
34+
35+
$parsed = ScopeValue::parse($scope);
36+
if ($parsed === null) {
37+
return $scope;
38+
}
39+
40+
$origin = static::humanizeScopeSegment($parsed->origin());
41+
$source = static::humanizeScopeSegment($parsed->source());
42+
$context = static::humanizeScopeSegment($parsed->context());
43+
$boundary = static::humanizeScopeSegment($parsed->boundary());
44+
45+
return "{$origin}{$source}{$context} ({$boundary})";
46+
}
47+
48+
protected static function humanizeScopeSegment(string $value): string
49+
{
50+
return Str::headline(Str::snake($value));
51+
}
52+
53+
/**
54+
* @return array<string, string>
55+
*/
56+
public static function getAssignableScopeOptionsForRecord(?Model $record = null): array
57+
{
58+
return static::getAssignableScopeOptions();
59+
}
60+
61+
public static function getDefaultAssignableScopeForRecord(?Model $record = null): string
62+
{
63+
if ($record && is_string($record->scope) && $record->scope !== '') {
64+
$options = static::getAssignableScopeOptions();
65+
if (array_key_exists($record->scope, $options)) {
66+
return $record->scope;
67+
}
68+
}
69+
70+
if ($record && ($record->scope === null || $record->scope === '')) {
71+
return static::ASSIGN_GLOBAL_SCOPE;
72+
}
73+
74+
return static::getDefaultAssignableScope();
75+
}
76+
77+
/**
78+
* @return array{updated: bool, message?: string}
79+
*/
80+
public static function assignScopeToRecord(Model $record, string $selectedScope): array
81+
{
82+
if (! static::recordSupportsScopeColumn($record)) {
83+
return ['updated' => false, 'message' => 'This record is not scopable.'];
84+
}
85+
86+
$actor = Auth::user();
87+
$validator = app(ScopeAssignmentValidator::class);
88+
89+
$targetScope = $selectedScope === static::ASSIGN_GLOBAL_SCOPE ? '' : $selectedScope;
90+
91+
$validation = $validator->validate($record, $targetScope, $actor);
92+
if (! ($validation['allowed'] ?? false)) {
93+
return ['updated' => false, 'message' => 'Target scope was inactive or boundary rules were not fulfilled.'];
94+
}
95+
96+
if ($selectedScope === static::ASSIGN_GLOBAL_SCOPE) {
97+
$record->setAttribute('scope', null);
98+
} else {
99+
$record->setAttribute('scope', ScopeValue::toStringOrNull($selectedScope));
100+
}
101+
102+
$record->save();
103+
104+
return ['updated' => true];
105+
}
106+
107+
public static function getScopeSelectField(string $name = 'scope'): Select
108+
{
109+
return Select::make($name)
110+
->label('Scope')
111+
->dehydrated(false)
112+
->live()
113+
->options(fn (?Model $record) => static::getAssignableScopeOptionsForRecord($record))
114+
->default(fn (?Model $record) => static::getDefaultAssignableScopeForRecord($record))
115+
->afterStateUpdated(function ($state, ?Model $record, Select $component): void {
116+
if (! $record) {
117+
return;
118+
}
119+
120+
$result = static::assignScopeToRecord($record, (string) $state);
121+
122+
if (! ($result['updated'] ?? false)) {
123+
Notification::make()
124+
->warning()
125+
->title('Scope not allowed')
126+
->body($result['message'] ?? 'Unable to update scope.')
127+
->send();
128+
129+
$record->refresh();
130+
$component->state(static::getDefaultAssignableScopeForRecord($record));
131+
132+
return;
133+
}
134+
135+
Notification::make()
136+
->success()
137+
->title('Scope updated')
138+
->send();
139+
});
140+
}
141+
23142
public static function scopeQuery(Builder $query): Builder
24143
{
25144
return ScopedResourceContext::applyScope($query, static::class);
@@ -170,8 +289,19 @@ protected static function getAssignableScopeOptions(): array
170289
->toArray();
171290

172291
foreach ($rows as $scope => $row) {
173-
$base = $row['label'] ?: $row['scope'];
174-
$options[$scope] = "{$base}{$row['source']}/{$row['context']} ({$row['boundary']})";
292+
$parsed = ScopeValue::parse($scope);
293+
if ($parsed === null) {
294+
$options[$scope] = $scope;
295+
296+
continue;
297+
}
298+
299+
$originLabel = static::humanizeScopeSegment($parsed->origin());
300+
$sourceLabel = static::humanizeScopeSegment($parsed->source());
301+
$contextLabel = static::humanizeScopeSegment($parsed->context());
302+
$boundaryLabel = static::humanizeScopeSegment($parsed->boundary());
303+
304+
$options[$scope] = "{$originLabel}{$sourceLabel}{$contextLabel} ({$boundaryLabel})";
175305
}
176306

177307
return $options;

packages/draft/config/draft.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,9 @@
163163
'navigation_group' => 'DEV',
164164

165165
'scope_registry' => [
166+
'origins' => [
167+
'draft' => \Moox\Draft\Models\Draft::class,
168+
],
166169
'sources' => [
167170
'draft' => \Moox\Draft\Models\Draft::class,
168171
],

packages/draft/src/Moox/Entities/Drafts/Draft/DraftResource.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Filament\Tables\Table;
1919
use Illuminate\Validation\Rules\Unique;
2020
use Moox\Core\Entities\Items\Draft\BaseDraftResource;
21+
use Moox\Core\Support\Resources\Concerns\HasScopedChildResource;
2122
use Moox\Core\Traits\Tabs\HasResourceTabs;
2223
use Moox\Core\Traits\Taxonomy\HasResourceTaxonomy;
2324
use Moox\Draft\Models\Draft;
@@ -33,6 +34,7 @@ class DraftResource extends BaseDraftResource
3334
{
3435
use HasResourceTabs;
3536
use HasResourceTaxonomy;
37+
use HasScopedChildResource;
3638

3739
protected static ?string $model = Draft::class;
3840

@@ -120,6 +122,7 @@ public static function form(Schema $form): Schema
120122
Section::make('')
121123
->schema([
122124
static::getTypeSelect(),
125+
static::getScopeSelectField(),
123126
static::getTranslationStatusSelect(),
124127
static::getPublishDateField(),
125128
static::getUnpublishDateField(),

packages/media/src/Resources/MediaResource.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,9 @@ public static function form(Schema $schema): Schema
411411
$record->save();
412412
}
413413
}),
414+
415+
static::getScopeSelectField()
416+
->disabled(fn ($record) => $record?->getOriginal('write_protected')),
414417
]),
415418
])
416419
->columnSpanFull(),

packages/tag/src/Resources/TagResource.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ public static function form(Schema $schema): Schema
107107
]),
108108
Section::make('')
109109
->schema([
110+
static::getScopeSelectField(),
110111
static::getTranslationStatusSelect(),
111112
static::getPublishDateField(),
112113
static::getUnpublishDateField(),

0 commit comments

Comments
 (0)