Skip to content

Commit 8e956f8

Browse files
feat: New Tab and Tabs classes for blueprints
1 parent 5dcfcd0 commit 8e956f8

17 files changed

Lines changed: 808 additions & 85 deletions

File tree

config/api/models/FileBlueprint.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
'fields' => [
1010
'name' => fn (FileBlueprint $blueprint) => $blueprint->name(),
1111
'options' => fn (FileBlueprint $blueprint) => $blueprint->options(),
12-
'tabs' => fn (FileBlueprint $blueprint) => $blueprint->tabs(),
12+
'tabs' => fn (FileBlueprint $blueprint) => array_values($blueprint->tabs()->toArray()),
1313
'title' => fn (FileBlueprint $blueprint) => $blueprint->title(),
1414
],
1515
'type' => FileBlueprint::class,

config/api/models/PageBlueprint.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
'options' => fn (PageBlueprint $blueprint) => $blueprint->options(),
1313
'preview' => fn (PageBlueprint $blueprint) => $blueprint->preview(),
1414
'status' => fn (PageBlueprint $blueprint) => $blueprint->status(),
15-
'tabs' => fn (PageBlueprint $blueprint) => $blueprint->tabs(),
15+
'tabs' => fn (PageBlueprint $blueprint) => array_values($blueprint->tabs()->toArray()),
1616
'title' => fn (PageBlueprint $blueprint) => $blueprint->title(),
1717
],
1818
'type' => PageBlueprint::class,

config/api/models/SiteBlueprint.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
'fields' => [
1010
'name' => fn (SiteBlueprint $blueprint) => $blueprint->name(),
1111
'options' => fn (SiteBlueprint $blueprint) => $blueprint->options(),
12-
'tabs' => fn (SiteBlueprint $blueprint) => $blueprint->tabs(),
12+
'tabs' => fn (SiteBlueprint $blueprint) => array_values($blueprint->tabs()->toArray()),
1313
'title' => fn (SiteBlueprint $blueprint) => $blueprint->title(),
1414
],
1515
'type' => SiteBlueprint::class,

config/api/models/UserBlueprint.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
'fields' => [
1010
'name' => fn (UserBlueprint $blueprint) => $blueprint->name(),
1111
'options' => fn (UserBlueprint $blueprint) => $blueprint->options(),
12-
'tabs' => fn (UserBlueprint $blueprint) => $blueprint->tabs(),
12+
'tabs' => fn (UserBlueprint $blueprint) => array_values($blueprint->tabs()->toArray()),
1313
'title' => fn (UserBlueprint $blueprint) => $blueprint->title(),
1414
],
1515
'type' => UserBlueprint::class,

panel/src/components/Navigation/ModelTabs.test.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,11 @@ import ModelTabs from "./ModelTabs.vue";
55
const tabs = [
66
{
77
name: "main",
8-
columns: [
9-
{ fields: { headline: { type: "text" } } },
10-
{ fields: { Text: { type: "textarea" } } }
11-
]
8+
fields: ["headline", "Text"]
129
},
1310
{
1411
name: "meta",
15-
columns: [{ fields: { seo: { type: "text" } } }]
12+
fields: ["seo"]
1613
}
1714
];
1815

@@ -30,7 +27,7 @@ describe("ModelTabs.vue", () => {
3027
});
3128

3229
describe("withBadges", () => {
33-
it("counts the changed fields of all columns of a tab", () => {
30+
it("counts the changed fields of a tab", () => {
3431
expect(badges({ headline: "a", seo: "b" })).toStrictEqual([
3532
{ text: 1 },
3633
{ text: 1 }

panel/src/components/Navigation/ModelTabs.vue

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,8 @@ export default {
2525
const changes = Object.keys(this.diff);
2626
2727
return this.tabs.map((tab) => {
28-
// collect all fields per tab
29-
const fields = [];
30-
31-
for (const column in tab.columns) {
32-
for (const field in tab.columns[column].fields) {
33-
fields.push(field);
34-
}
35-
}
28+
// all field names of the tab
29+
const fields = tab.fields ?? [];
3630
3731
// get count of changed fields in this tab
3832
const changesInTab = fields.filter((field) =>

src/Blueprint/Blueprint.php

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@ class Blueprint
5252

5353
// Cache for sections created from `section` fields
5454
protected array $sections = [];
55-
protected array $tabs = [];
55+
56+
// Collected tabs, see `tabs()`
57+
protected Tabs|null $tabs = null;
5658

5759
/**
5860
* Magic getter/caller for any blueprint prop
@@ -88,13 +90,11 @@ public function __construct(array $props)
8890

8991
// convert all shortcuts and normalize the props
9092
$normalizer = new Normalizer(
91-
model: $this->model,
9293
props: $props
9394
);
9495

9596
$this->fields = $normalizer->fields();
9697
$this->props = $normalizer->props();
97-
$this->tabs = $normalizer->tabs();
9898
}
9999

100100
/**
@@ -461,23 +461,30 @@ public function sections(): array
461461
}
462462

463463
/**
464-
* Returns a single tab by name
464+
* Returns a single tab by name or the
465+
* first tab if no name is given
465466
*/
466-
public function tab(string|null $name = null): array|null
467+
public function tab(string|null $name = null): Tab|null
467468
{
469+
$tabs = $this->tabs();
470+
468471
if ($name === null) {
469-
return A::first($this->tabs);
472+
return $tabs->first();
470473
}
471474

472-
return $this->tabs[$name] ?? null;
475+
return $tabs->get($name);
473476
}
474477

475478
/**
476-
* Returns all tabs
479+
* Creates and caches the collection of all tabs
480+
* from the normalized tab props
477481
*/
478-
public function tabs(): array
482+
public function tabs(): Tabs
479483
{
480-
return array_values($this->tabs);
484+
return $this->tabs ??= new Tabs(
485+
tabs: $this->props['tabs'] ?? [],
486+
model: $this->model
487+
);
481488
}
482489

483490
/**
@@ -493,6 +500,9 @@ public function title(): string
493500
*/
494501
public function toArray(): array
495502
{
496-
return $this->props;
503+
return [
504+
...$this->props,
505+
'tabs' => $this->tabs()->toArray()
506+
];
497507
}
498508
}

src/Blueprint/Normalizer.php

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
namespace Kirby\Blueprint;
44

5-
use Kirby\Cms\ModelWithContent;
65
use Kirby\Exception\InvalidArgumentException;
76
use Kirby\Form\Field;
87
use Kirby\Toolkit\A;
@@ -36,10 +35,8 @@ class Normalizer
3635
// Collected blueprint props
3736
protected array $props;
3837

39-
public function __construct(
40-
protected ModelWithContent $model,
41-
array $props
42-
) {
38+
public function __construct(array $props)
39+
{
4340
$this->props = $this->normalize($props);
4441
}
4542

@@ -488,7 +485,6 @@ protected function normalizeTabs(mixed $tabs): array
488485
'columns' => $this->normalizeColumns($tabName, $tabProps['columns'] ?? []),
489486
'icon' => $tabProps['icon'] ?? null,
490487
'label' => $this->i18n($tabProps['label'] ?? Str::label($tabName)),
491-
'link' => $this->model->panel()->url(true) . '/?tab=' . $tabName,
492488
'name' => $tabName,
493489
];
494490
}

src/Blueprint/Tab.php

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
<?php
2+
3+
namespace Kirby\Blueprint;
4+
5+
use Kirby\Cms\ModelWithContent;
6+
use Kirby\Form\Fields;
7+
use Kirby\Toolkit\I18n;
8+
use Kirby\Toolkit\Str;
9+
10+
/**
11+
* Represents a single tab of a blueprint. Tabs are
12+
* the top level of the blueprint layout. They hold
13+
* columns, which hold fields.
14+
*
15+
* @copyright Bastian Allgeier
16+
* @license https://getkirby.com/license
17+
* @since 6.0.0
18+
*/
19+
class Tab
20+
{
21+
public function __construct(
22+
protected ModelWithContent $model,
23+
protected string $name,
24+
protected array $columns = [],
25+
protected string|null $icon = null,
26+
protected array|string|null $label = null,
27+
protected array $props = []
28+
) {
29+
}
30+
31+
/**
32+
* Returns all normalized columns of the tab.
33+
*
34+
* If a `Fields` collection is passed, the blueprint field
35+
* definitions of each column are replaced by the resolved
36+
* field props for the Panel.
37+
*/
38+
public function columns(Fields|null $fields = null): array
39+
{
40+
if ($fields === null) {
41+
return $this->columns;
42+
}
43+
44+
$columns = $this->columns;
45+
46+
foreach ($columns as $key => $column) {
47+
$names = array_map(
48+
'strtolower',
49+
array_keys($column['fields'] ?? [])
50+
);
51+
52+
// only resolve the props of the fields in this tab,
53+
// as that can be expensive for list fields
54+
$columns[$key]['fields'] = $fields
55+
->filter(fn ($field) => in_array($field->name(), $names, true))
56+
->toProps();
57+
}
58+
59+
return $columns;
60+
}
61+
62+
/**
63+
* Returns a flat list of the lowercase names of all
64+
* fields in the tab. The Panel uses this list to count
65+
* the unsaved changes per tab.
66+
*/
67+
public function fieldNames(): array
68+
{
69+
$names = [];
70+
71+
foreach ($this->columns as $column) {
72+
foreach (array_keys($column['fields'] ?? []) as $name) {
73+
$names[] = Str::lower($name);
74+
}
75+
}
76+
77+
return $names;
78+
}
79+
80+
/**
81+
* Returns the icon of the tab
82+
*/
83+
public function icon(): string|null
84+
{
85+
return $this->icon;
86+
}
87+
88+
/**
89+
* Returns the translated label of the tab and
90+
* falls back to an automatic label
91+
*/
92+
public function label(): string
93+
{
94+
$label = I18n::translate($this->label, $this->label);
95+
96+
if (is_string($label) === true) {
97+
return $label;
98+
}
99+
100+
return Str::label($this->name);
101+
}
102+
103+
/**
104+
* Returns the Panel link to the tab
105+
*/
106+
public function link(): string
107+
{
108+
return $this->model->panel()->url(true) . '/?tab=' . $this->name;
109+
}
110+
111+
/**
112+
* Returns the parent model
113+
*/
114+
public function model(): ModelWithContent
115+
{
116+
return $this->model;
117+
}
118+
119+
/**
120+
* Returns the name of the tab
121+
*/
122+
public function name(): string
123+
{
124+
return $this->name;
125+
}
126+
127+
/**
128+
* Returns the reduced props for the Panel tab bar.
129+
* The columns are not included here, as the tab bar
130+
* only needs to render a button per tab.
131+
*/
132+
public function toButtonProps(): array
133+
{
134+
return [
135+
'fields' => $this->fieldNames(),
136+
'icon' => $this->icon(),
137+
'label' => $this->label(),
138+
'link' => $this->link(),
139+
'name' => $this->name(),
140+
];
141+
}
142+
143+
/**
144+
* Converts the tab to a plain array with all
145+
* columns and fields.
146+
*
147+
* If a `Fields` collection is passed, the columns
148+
* carry the resolved field props for the Panel.
149+
*/
150+
public function toViewProps(Fields|null $fields = null): array
151+
{
152+
return [
153+
...$this->props,
154+
'columns' => $this->columns($fields),
155+
'icon' => $this->icon(),
156+
'label' => $this->label(),
157+
'link' => $this->link(),
158+
'name' => $this->name(),
159+
];
160+
}
161+
}

0 commit comments

Comments
 (0)