Skip to content

Commit a657a4b

Browse files
committed
Add section-aware fieldset imports with preserve/flatten controls
1 parent 1552a2a commit a657a4b

8 files changed

Lines changed: 263 additions & 4 deletions

File tree

resources/js/components/blueprints/ImportField.vue

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
<a class="cursor-pointer overflow-hidden text-ellipsis text-sm text-ui-accent-text hover:text-ui-accent-text/80" :href="fieldsetEditUrl" v-text="fieldsetTitle" v-tooltip="__('Edit fieldset')" />
1212
<ui-icon name="link" class="text-gray-400" />
1313
<span class="text-gray-500 font-mono text-2xs" v-text="__('Fieldset')" />
14+
<ui-badge v-if="sectionBadgeText" size="sm" color="gray" :text="sectionBadgeText" />
1415
<ui-badge
1516
v-if="field.prefix"
1617
size="sm"
@@ -62,10 +63,38 @@ export default {
6263
return title ? __(title) : this.field.fieldset;
6364
},
6465
66+
fieldsetHasSections() {
67+
return this.$page?.props?.fieldsets?.[this.field.fieldset]?.has_sections === true;
68+
},
69+
70+
fieldsetSectionsCount() {
71+
return this.$page?.props?.fieldsets?.[this.field.fieldset]?.sections_count ?? 0;
72+
},
73+
6574
fieldsetEditUrl() {
6675
return cp_url(`fields/fieldsets/${this.field.fieldset}/edit`);
6776
},
6877
78+
sectionBehavior() {
79+
return this.field.section_behavior ?? 'preserve';
80+
},
81+
82+
sectionBadgeText() {
83+
if (!this.fieldsetHasSections) {
84+
return null;
85+
}
86+
87+
if (this.sectionBehavior === 'flatten') {
88+
return this.fieldsetSectionsCount === 1
89+
? __('Ignoring Section')
90+
: __('Ignoring Sections');
91+
}
92+
93+
return this.fieldsetSectionsCount === 1
94+
? __('Has Section')
95+
: __('Has Sections');
96+
},
97+
6998
fieldConfig() {
7099
const { _id, type, ...config } = this.field;
71100
return config;

resources/js/components/fields/ImportSettings.vue

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,36 @@
1616
<Field :label="__('Prefix')" :instructions="__('messages.fieldset_import_prefix_instructions')" class="form-group field-w-100">
1717
<Input autofocus :model-value="config.prefix" @update:model-value="updateField('prefix', $event)" />
1818
</Field>
19+
20+
<Field
21+
v-if="fieldsetHasSections"
22+
:label="__('Section Behavior')"
23+
:instructions="sectionBehaviorInstructions"
24+
class="form-group field-w-100"
25+
>
26+
<RadioGroup
27+
:model-value="sectionBehavior"
28+
@update:model-value="updateField('section_behavior', $event)"
29+
>
30+
<Radio
31+
v-for="option in sectionBehaviorOptions"
32+
:key="option.value"
33+
:label="option.label"
34+
:description="option.description"
35+
:value="option.value"
36+
/>
37+
</RadioGroup>
38+
</Field>
1939
</div>
2040
</CardPanel>
2141
</StackContent>
2242
</template>
2343

2444
<script>
25-
import { Button, Heading, CardPanel, Field, Input, StackHeader, StackContent } from '@/components/ui';
45+
import { Button, Heading, CardPanel, Field, Input, StackHeader, StackContent, RadioGroup, Radio } from '@/components/ui';
2646
2747
export default {
28-
components: { StackContent, StackHeader, Heading, Button, CardPanel, Field, Input },
48+
components: { StackContent, StackHeader, Heading, Button, CardPanel, Field, Input, RadioGroup, Radio },
2949
3050
props: ['config', 'isInsideSet'],
3151
@@ -46,13 +66,52 @@ export default {
4666
};
4767
},
4868
69+
computed: {
70+
fieldsetMeta() {
71+
const handle = this.values.fieldset;
72+
73+
return this.$page?.props?.fieldsets?.[handle] ?? null;
74+
},
75+
76+
fieldsetHasSections() {
77+
return this.fieldsetMeta?.has_sections === true;
78+
},
79+
80+
sectionBehavior() {
81+
return this.values.section_behavior ?? 'preserve';
82+
},
83+
84+
sectionBehaviorInstructions() {
85+
return __('Choose whether imported fieldset sections should be preserved or flattened into this section.');
86+
},
87+
88+
sectionBehaviorOptions() {
89+
return [
90+
{
91+
label: __('Preserve'),
92+
description: __('Keep imported sections as-is.'),
93+
value: 'preserve',
94+
},
95+
{
96+
label: __('Flatten'),
97+
description: __('Merge all fields into this section.'),
98+
value: 'flatten',
99+
},
100+
];
101+
},
102+
},
103+
49104
methods: {
50105
focus() {
51106
this.$els.display.select();
52107
},
53108
54109
updateField(handle, value) {
55110
this.values[handle] = value;
111+
112+
if (handle === 'fieldset' && ! this.fieldsetHasSections) {
113+
this.values.section_behavior = 'preserve';
114+
}
56115
},
57116
58117
commit(shouldCommitParent = false) {

src/Fields/FieldTransformer.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ private static function importTabField(array $submitted)
2121
return array_filter([
2222
'import' => $submitted['fieldset'],
2323
'prefix' => $submitted['prefix'] ?? null,
24+
'section_behavior' => ($submitted['section_behavior'] ?? 'preserve') === 'flatten'
25+
? 'flatten'
26+
: null,
2427
]);
2528
}
2629

@@ -183,6 +186,7 @@ private static function importFieldToVue($field): array
183186
'type' => 'import',
184187
'fieldset' => $field['import'],
185188
'prefix' => $field['prefix'] ?? null,
189+
'section_behavior' => $field['section_behavior'] ?? 'preserve',
186190
];
187191
}
188192

src/Fields/Tab.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,10 @@ private function isSectionedFieldsetImport(array $field): bool
142142
return false;
143143
}
144144

145+
if (($field['section_behavior'] ?? 'preserve') === 'flatten') {
146+
return false;
147+
}
148+
145149
$fieldset = FieldsetRepository::find($field['import']);
146150

147151
return $fieldset && $fieldset->hasSections();

src/Http/Controllers/CP/Fields/FieldsetController.php

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ public function update(Request $request, $fieldset)
137137
]);
138138

139139
if ($request->has('sections')) {
140-
$contents['sections'] = collect($request->sections)->map(function ($section) {
140+
$sections = collect($request->sections)->map(function ($section) {
141141
return Arr::removeNullValues([
142142
'display' => Arr::get($section, 'display'),
143143
'instructions' => Arr::get($section, 'instructions'),
@@ -148,6 +148,13 @@ public function update(Request $request, $fieldset)
148148
})->all(),
149149
]);
150150
})->all();
151+
152+
if ($this->shouldStoreAsFlatFields($sections)) {
153+
$contents['fields'] = Arr::get($sections, '0.fields', []);
154+
unset($contents['sections']);
155+
} else {
156+
$contents['sections'] = $sections;
157+
}
151158
} else {
152159
$contents['fields'] = collect($request->fields)->map(function ($field) {
153160
return FieldTransformer::fromVue($field);
@@ -257,4 +264,19 @@ private function sectionsToVue(Fieldset $fieldset): array
257264
];
258265
})->all();
259266
}
267+
268+
private function shouldStoreAsFlatFields(array $sections): bool
269+
{
270+
if (count($sections) !== 1) {
271+
return false;
272+
}
273+
274+
$section = $sections[0];
275+
$display = Arr::get($section, 'display');
276+
277+
return in_array($display, [null, '', __('Fields')], true)
278+
&& Arr::get($section, 'instructions') === null
279+
&& Arr::get($section, 'collapsible') !== true
280+
&& Arr::get($section, 'collapsed') !== true;
281+
}
260282
}

src/Http/Controllers/CP/Fields/ManagesFields.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,16 @@ private function fieldProps()
2121
private function fieldsets()
2222
{
2323
return Fieldset::all()->mapWithKeys(function ($fieldset) {
24+
$fields = $fieldset->hasSections()
25+
? $fieldset->sections()->flatMap(fn ($section) => Arr::get($section, 'fields', []))
26+
: collect(Arr::get($fieldset->contents(), 'fields', []));
27+
2428
return [$fieldset->handle() => [
2529
'handle' => $fieldset->handle(),
2630
'title' => $fieldset->title(),
27-
'fields' => collect(Arr::get($fieldset->contents(), 'fields'))->map(function ($field) {
31+
'has_sections' => $fieldset->hasSections(),
32+
'sections_count' => $fieldset->hasSections() ? $fieldset->sections()->count() : 0,
33+
'fields' => $fields->map(function ($field) {
2834
return FieldTransformer::toVue($field);
2935
})->sortBy('config.display')->values()->all(),
3036
]];

tests/Feature/Fieldsets/UpdateFieldsetTest.php

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,105 @@ public function fieldset_gets_saved_with_sections()
187187
], Facades\Fieldset::find('test')->contents());
188188
}
189189

190+
#[Test]
191+
public function import_section_behavior_is_saved_when_flattening_sections()
192+
{
193+
$user = tap(Facades\User::make()->makeSuper())->save();
194+
(new Fieldset)->setHandle('seo_defaults')->setContents([
195+
'title' => 'SEO Defaults',
196+
'fields' => [
197+
['handle' => 'canonical_url', 'field' => ['type' => 'text']],
198+
],
199+
])->save();
200+
201+
$fieldset = (new Fieldset)->setHandle('test')->setContents([
202+
'title' => 'Test',
203+
'fields' => [],
204+
])->save();
205+
206+
$this
207+
->actingAs($user)
208+
->submit($fieldset, [
209+
'sections' => [
210+
[
211+
'_id' => 'section-1',
212+
'display' => 'Main',
213+
'fields' => [
214+
[
215+
'_id' => 'section-1-field-1',
216+
'type' => 'import',
217+
'fieldset' => 'seo_defaults',
218+
'section_behavior' => 'flatten',
219+
],
220+
],
221+
],
222+
],
223+
])
224+
->assertStatus(204);
225+
226+
$this->assertEquals([
227+
'title' => 'Updated',
228+
'fields' => [],
229+
'sections' => [
230+
[
231+
'display' => 'Main',
232+
'fields' => [
233+
[
234+
'import' => 'seo_defaults',
235+
'section_behavior' => 'flatten',
236+
],
237+
],
238+
],
239+
],
240+
], Facades\Fieldset::find('test')->contents());
241+
}
242+
243+
#[Test]
244+
public function single_default_section_is_collapsed_back_to_flat_fields()
245+
{
246+
$user = tap(Facades\User::make()->makeSuper())->save();
247+
$fieldset = (new Fieldset)->setHandle('test')->setContents([
248+
'title' => 'Test',
249+
'fields' => [],
250+
])->save();
251+
252+
$this
253+
->actingAs($user)
254+
->submit($fieldset, [
255+
'sections' => [
256+
[
257+
'_id' => 'section-1',
258+
'display' => 'Fields',
259+
'fields' => [
260+
[
261+
'_id' => 'section-1-field-1',
262+
'handle' => 'meta_title',
263+
'type' => 'inline',
264+
'config' => [
265+
'type' => 'text',
266+
'display' => 'Meta title',
267+
],
268+
],
269+
],
270+
],
271+
],
272+
])
273+
->assertStatus(204);
274+
275+
$this->assertEquals([
276+
'title' => 'Updated',
277+
'fields' => [
278+
[
279+
'handle' => 'meta_title',
280+
'field' => [
281+
'type' => 'text',
282+
'display' => 'Meta title',
283+
],
284+
],
285+
],
286+
], Facades\Fieldset::find('test')->contents());
287+
}
288+
190289
#[Test]
191290
public function title_is_required()
192291
{

tests/Fields/TabTest.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,4 +277,40 @@ public function it_applies_prefixes_to_fields_inside_imported_fieldset_sections(
277277
$this->assertEquals('seo_meta_title', $field['handle']);
278278
$this->assertEquals('seo_', $field['prefix']);
279279
}
280+
281+
#[Test]
282+
public function it_can_flatten_imported_fieldset_sections_in_place()
283+
{
284+
FieldsetRepository::shouldReceive('find')
285+
->with('seo')
286+
->andReturn((new Fieldset)->setHandle('seo')->setContents([
287+
'sections' => [
288+
[
289+
'display' => 'SEO',
290+
'fields' => [
291+
['handle' => 'meta_title', 'field' => ['type' => 'text']],
292+
],
293+
],
294+
],
295+
]));
296+
297+
$tab = (new Tab('main'))->setContents([
298+
'sections' => [
299+
[
300+
'display' => 'Main',
301+
'fields' => [
302+
['handle' => 'title', 'field' => ['type' => 'text']],
303+
['import' => 'seo', 'section_behavior' => 'flatten'],
304+
['handle' => 'summary', 'field' => ['type' => 'textarea']],
305+
],
306+
],
307+
],
308+
]);
309+
310+
$publish = $tab->toPublishArray();
311+
312+
$this->assertCount(1, $publish['sections']);
313+
$this->assertEquals('Main', $publish['sections'][0]['display']);
314+
$this->assertEquals(['title', 'meta_title', 'summary'], collect($publish['sections'][0]['fields'])->pluck('handle')->all());
315+
}
280316
}

0 commit comments

Comments
 (0)