Skip to content

Commit 02c4ad8

Browse files
committed
feat: add global blocks
1 parent c4f186a commit 02c4ad8

11 files changed

+758
-18
lines changed

README.md

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
- [disabling block in select block](#disabling-block-in-select-block)
2222
- [iframe resizing](#iframe-resizing)
2323
- [Parameter injection](#parameter-injection)
24+
- [Global blocks](#global-blocks)
2425
- [Rendering page builder items on infolist](#rendering-page-builder-items-on-infolist)
2526
- [Rendering page builder ite previews on fomrms](#rendering-page-builder-ite-previews-on-fomrms)
2627
- [Customizing actions and button rendering](#customizing-actions-and-button-rendering)
@@ -540,6 +541,90 @@ injections that are provided depends on where this function is used, if this is
540541

541542
one thing to note is that because `formatForListingView` uses `formatForSingleView` internally if you wish to inject something in `formatForSingleView` you will need to inject it in `formatForListingView` as well, otherwise it will not work.
542543

544+
### Global blocks
545+
546+
Global blocks are special blocks that have centralized configuration management. Instead of configuring the same block repeatedly across different pages, you can set up the block configuration once and reuse it everywhere.
547+
548+
#### Creating global blocks
549+
550+
You can create a global block using the make-block command with the `--global` flag:
551+
552+
```bash
553+
php artisan page-builder-plugin:make-block ContactForm --type=view --global
554+
```
555+
556+
This will:
557+
- Create a global block class in `app/Filament/{panel}/Blocks/Globals/` directory
558+
- Automatically generate a Global Blocks resource for centralized management (on first global block creation)
559+
- Create the necessary database migration for storing global block configurations
560+
561+
#### How global blocks work
562+
563+
Global blocks use the `IsGlobalBlock` trait and have two key methods:
564+
565+
```php
566+
<?php
567+
568+
use Redberry\PageBuilderPlugin\Traits\IsGlobalBlock;
569+
570+
class ContactForm extends BaseBlock
571+
{
572+
use IsGlobalBlock;
573+
574+
// Define the block's schema - this will be used in the Global Blocks resource
575+
public static function getBaseBlockSchema(?object $record = null): array
576+
{
577+
return [
578+
TextInput::make('title')->required(),
579+
Textarea::make('description'),
580+
TextInput::make('email')->email(),
581+
];
582+
}
583+
584+
// The schema returned to the page builder (empty for global blocks)
585+
public static function getBlockSchema(?object $record = null): array
586+
{
587+
$schema = static::getBaseBlockSchema($record);
588+
return static::applyGlobalConfiguration($schema);
589+
}
590+
}
591+
```
592+
593+
#### Global Blocks resource
594+
595+
When you create your first global block, a "Global Blocks" resource is automatically generated in your Filament admin panel. This resource allows you to:
596+
597+
- View all available global blocks
598+
- Configure each block's field values
599+
- Edit configurations using the actual Filament form fields defined in `getBaseBlockSchema()`
600+
601+
602+
#### Using global blocks on pages
603+
604+
When adding global blocks to pages:
605+
- **No configuration modal appears** - blocks are added instantly
606+
- **No per-page configuration** - all configuration is managed centrally
607+
- **Consistent values everywhere** - the block uses the same configured values across all pages
608+
609+
Simply add your global block to the page builder's blocks array:
610+
611+
```php
612+
<?php
613+
614+
PageBuilder::make('website_content')
615+
->blocks([
616+
ContactForm::class,
617+
// other blocks...
618+
]);
619+
```
620+
621+
#### Key benefits
622+
623+
- **Centralized management**: Configure once, use everywhere
624+
- **Consistency**: Same content and styling across all instances
625+
- **Efficiency**: No need to reconfigure the same block on multiple pages
626+
- **Maintainability**: Update global block configuration in one place
627+
543628
### Rendering page builder items on infolist
544629
outside of form you might want to render page builder items on infolist, for this we provide two prebuilt entries:
545630
`PageBuilderEntry` and `PageBuilderPreviewEntry`

src/Commands/CreatePageBuilderPluginBlockCommand.php

Lines changed: 88 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,12 @@ public function handle(): int
3838
$this->panel = $this->getPanelToCreateIn();
3939

4040
$isGlobal = $this->option('global');
41-
41+
4242
$blocksNamespace = $this->getClassNameSpaces('Blocks');
43-
43+
4444
if ($isGlobal) {
4545
$this->createGlobalsCategoryIfNotExists();
46+
$isFirstGlobalBlock = $this->isFirstGlobalBlock();
4647
$blocksNamespace .= '\\Globals';
4748
}
4849

@@ -72,11 +73,11 @@ public function handle(): int
7273
'{{ class }}' => str($blockClass)->afterLast('\\')->replace('\\', ''),
7374
'{{ namespace }}' => str($blockClass)->beforeLast('\\'),
7475
];
75-
76+
7677
if ($isGlobal) {
7778
$replacements['{{ globalsNamespace }}'] = $this->getClassNameSpaces('BlockCategories');
7879
}
79-
80+
8081
$this->createFileFromStub(
8182
$stubName,
8283
$this->appClassToPath($blockClass),
@@ -89,19 +90,19 @@ public function handle(): int
8990
if ($isGlobal) {
9091
$viewName = 'globals.' . $viewName;
9192
}
92-
93+
9394
$stubName = $isGlobal ? 'block.global.view' : 'block.view';
9495
$replacements = [
9596
'{{ class }}' => str($block)->afterLast('\\')->replace('\\', ''),
9697
'{{ namespace }}' => str($blockClass)->beforeLast('\\'),
9798
'{{ viewName }}' => $viewName,
9899
'{{ panelId }}' => $this->panel->getId(),
99100
];
100-
101+
101102
if ($isGlobal) {
102103
$replacements['{{ globalsNamespace }}'] = $this->getClassNameSpaces('BlockCategories');
103104
}
104-
105+
105106
$this->createFileFromStub(
106107
$stubName,
107108
$this->appClassToPath($blockClass),
@@ -112,13 +113,18 @@ public function handle(): int
112113
->replace('.', '/')
113114
->prepend("views/{$this->panel->getId()}/blocks/")
114115
->append('.blade.php');
115-
116+
116117
$this->createFileFromStub(
117118
'block.blade',
118119
resource_path($viewPath),
119120
);
120121
}
121122

123+
if ($isGlobal && isset($isFirstGlobalBlock) && $isFirstGlobalBlock) {
124+
$this->createGlobalBlocksResource();
125+
$this->publishGlobalBlockMigration();
126+
}
127+
122128
return self::SUCCESS;
123129
}
124130

@@ -127,12 +133,8 @@ protected function createGlobalsCategoryIfNotExists(): void
127133
$categoryNamespace = $this->getClassNameSpaces('BlockCategories');
128134
$globalsClass = $categoryNamespace . '\\Globals';
129135

130-
try {
131-
if (class_exists($globalsClass)) {
132-
return; // Category already exists
133-
}
134-
} catch (\Throwable $th) {
135-
// Class doesn't exist, proceed with creation
136+
if (class_exists($globalsClass)) {
137+
return;
136138
}
137139

138140
$this->createFileFromStub(
@@ -146,4 +148,76 @@ protected function createGlobalsCategoryIfNotExists(): void
146148

147149
$this->info("Created Globals category at: {$globalsClass}");
148150
}
151+
152+
protected function isFirstGlobalBlock(): bool
153+
{
154+
$globalBlocksPath = app_path("Filament/{$this->panel->getId()}/Blocks/Globals");
155+
156+
if (!is_dir($globalBlocksPath)) {
157+
return true;
158+
}
159+
160+
$existingBlocks = glob($globalBlocksPath . '/*.php');
161+
return empty($existingBlocks);
162+
}
163+
164+
protected function createGlobalBlocksResource(): void
165+
{
166+
$resourceNamespace = $this->getClassNameSpaces('Resources');
167+
$resourceClass = 'GlobalBlocksResource';
168+
$resourceFullClass = $resourceNamespace . '\\' . $resourceClass;
169+
170+
$this->createFileFromStub(
171+
'global-blocks-resource',
172+
$this->appClassToPath($resourceFullClass),
173+
[
174+
'{{ class }}' => $resourceClass,
175+
'{{ namespace }}' => $resourceNamespace,
176+
'{{ resourceNamespace }}' => $resourceNamespace,
177+
]
178+
);
179+
180+
$pagesNamespace = $resourceNamespace . '\\' . $resourceClass . '\\Pages';
181+
182+
$this->createFileFromStub(
183+
'global-blocks-list-page',
184+
$this->appClassToPath($pagesNamespace . '\\ListGlobalBlocks'),
185+
[
186+
'{{ class }}' => 'ListGlobalBlocks',
187+
'{{ namespace }}' => $pagesNamespace,
188+
'{{ resourceClass }}' => $resourceClass,
189+
'{{ resourceNamespace }}' => $resourceNamespace,
190+
]
191+
);
192+
193+
$this->createFileFromStub(
194+
'global-blocks-edit-page',
195+
$this->appClassToPath($pagesNamespace . '\\EditGlobalBlock'),
196+
[
197+
'{{ class }}' => 'EditGlobalBlock',
198+
'{{ namespace }}' => $pagesNamespace,
199+
'{{ resourceClass }}' => $resourceClass,
200+
'{{ resourceNamespace }}' => $resourceNamespace,
201+
]
202+
);
203+
204+
$this->info("Created Global Blocks resource at: {$resourceFullClass}");
205+
$this->info("The resource has been automatically registered and will appear in your Filament panel navigation.");
206+
}
207+
208+
protected function publishGlobalBlockMigration(): void
209+
{
210+
$timestamp = now()->format('Y_m_d_His');
211+
$migrationName = "create_global_block_configs_table";
212+
$migrationFile = database_path("migrations/{$timestamp}_{$migrationName}.php");
213+
214+
$this->createFileFromStub(
215+
'create_global_block_configs_table.php',
216+
$migrationFile,
217+
[]
218+
);
219+
220+
$this->info("Created migration: {$migrationFile}");
221+
$this->warn("Don't forget to run 'php artisan migrate' to create the global_block_configs table.");
222+
}
149223
}

src/Components/Forms/Actions/SelectBlockAction.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use Closure;
66
use Filament\Forms\Components\Actions\Action;
77
use Filament\Forms\Components\Select;
8+
use Filament\Notifications\Notification;
89
use Filament\Support\Enums\MaxWidth;
910
use Illuminate\View\ComponentAttributeBag;
1011
use Redberry\PageBuilderPlugin\Components\Forms\PageBuilder;
@@ -86,6 +87,36 @@ protected function setUp(): void
8687
$this->stickyModalFooter();
8788

8889
$this->action(function ($data, $livewire, PageBuilder $component) {
90+
$blockType = $data['block_type'];
91+
92+
$isGlobalBlock = class_exists($blockType) && method_exists($blockType, 'isGlobalBlock') && $blockType::isGlobalBlock();
93+
94+
if ($isGlobalBlock) {
95+
$state = $component->getState() ?? [];
96+
97+
$block = app($component->getModel())->{$component->relationship}()->make([
98+
'block_type' => $blockType,
99+
'data' => [],
100+
'order' => count($state) + 1,
101+
]);
102+
103+
$block->id = $block->newUniqueId();
104+
105+
$component->state([
106+
...$state,
107+
$block->toArray(),
108+
]);
109+
110+
$component->callAfterStateUpdated();
111+
112+
Notification::make()
113+
->title('Block added successfully')
114+
->success()
115+
->send();
116+
117+
return;
118+
}
119+
89120
$livewire->mountFormComponentAction(
90121
$component->getStatePath(),
91122
$component->getCreateActionName(),

0 commit comments

Comments
 (0)