diff --git a/CHANGELOG.md b/CHANGELOG.md index d1cd281..6e91ffd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to `filament-page-builder` will be documented in this file. +## 3.0.0 Initial filament v3 update +- support Filamentphp v3 / Livewire v3 +- disable validation for preview +- visual fixes for preview + +Please note there is a bug, preventing you from saving blocks, in filament `Builder` in combination with the `filament/spatie-laravel-translatable-plugin` +https://github.com/filamentphp/filament/issues/8656 + ## 1.0.0 - 202X-XX-XX - initial release diff --git a/composer.json b/composer.json index 7b1f2f4..55113b0 100644 --- a/composer.json +++ b/composer.json @@ -1,37 +1,41 @@ { - "name": "haringsrob/filament-page-builder", + "name": "sevendays/filament-page-builder", "description": "A visual page builder for Filament", "keywords": [ + "Sevendays", "haringsrob", "laravel", + "filamentphp", "filament-page-builder" ], - "homepage": "https://github.com/haringsrob/filament-page-builder", + "homepage": "https://github.com/sevendays-digital/filament-page-builder", "license": "MIT", "authors": [ { "name": "Harings Rob", "email": "haringsrob@gmail.com", "role": "Developer" + }, + { + "name": "Michael Metdepenningen", + "email": "michael@sevendays.be", + "role": "Developer" } ], "require": { "php": "^8.1", - "filament/filament": "^2.0", - "spatie/laravel-package-tools": "^1.13.5", + "filament/filament": "3.0-stable", + "filament/spatie-laravel-translatable-plugin": "^3.0-stable", "illuminate/contracts": "^9.0|^10.0", - "filament/spatie-laravel-translatable-plugin": "^v2.17", - "wire-elements/modal": "^1.0" + "spatie/laravel-package-tools": "^1.13.5", + "wire-elements/modal": "^2.0" }, "require-dev": { "laravel/pint": "^1.0", "nunomaduro/collision": "^6.0", "nunomaduro/larastan": "^2.0.1", "orchestra/testbench": "^7.0", - "pestphp/pest": "^1.21", - "pestphp/pest-plugin-laravel": "^1.1", - "pestphp/pest-plugin-livewire": "^1.0", - "pestphp/pest-plugin-parallel": "^0.3", + "pestphp/pest": "^1.23", "phpstan/extension-installer": "^1.1", "phpstan/phpstan-deprecation-rules": "^1.0", "phpstan/phpstan-phpunit": "^1.0", @@ -40,13 +44,13 @@ }, "autoload": { "psr-4": { - "Haringsrob\\FilamentPageBuilder\\": "src", - "Haringsrob\\FilamentPageBuilder\\Database\\Factories\\": "database/factories" + "Sevendays\\FilamentPageBuilder\\": "src", + "Sevendays\\FilamentPageBuilder\\Database\\Factories\\": "database/factories" } }, "autoload-dev": { "psr-4": { - "Haringsrob\\FilamentPageBuilder\\Tests\\": "tests" + "Sevendays\\FilamentPageBuilder\\Tests\\": "tests" } }, "scripts": { @@ -69,10 +73,10 @@ "extra": { "laravel": { "providers": [ - "Haringsrob\\FilamentPageBuilder\\FilamentPageBuilderServiceProvider" + "Sevendays\\FilamentPageBuilder\\FilamentPageBuilderServiceProvider" ], "aliases": { - "FilamentPageBuilder": "Haringsrob\\FilamentPageBuilder\\Facades\\FilamentPageBuilder" + "FilamentPageBuilder": "Sevendays\\FilamentPageBuilder\\Facades\\FilamentPageBuilder" } } }, diff --git a/config/filament-page-builder.php b/config/filament-page-builder.php index 2d2de17..76ef79e 100644 --- a/config/filament-page-builder.php +++ b/config/filament-page-builder.php @@ -1,6 +1,6 @@ :not([hidden])~:not([hidden]){--tw-space-x-reverse:1}:is([dir=rtl] .rtl\:divide-x-reverse)>:not([hidden])~:not([hidden]){--tw-divide-x-reverse:1}:is([dir=rtl] .rtl\:border-l){border-left-width:1px}:is([dir=rtl] .rtl\:border-r-0){border-right-width:0}:is(.dark .dark\:divide-gray-700)>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(55 65 81/var(--tw-divide-opacity))}:is(.dark .dark\:border-gray-600){--tw-border-opacity:1;border-color:rgb(75 85 99/var(--tw-border-opacity))}:is(.dark .dark\:border-gray-700){--tw-border-opacity:1;border-color:rgb(55 65 81/var(--tw-border-opacity))}:is(.dark .dark\:bg-gray-500\/10){background-color:#6b72801a}:is(.dark .dark\:bg-gray-800){--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}:is(.dark .dark\:text-danger-500){--tw-text-opacity:1;color:rgb(244 63 94/var(--tw-text-opacity))}:is(.dark .dark\:text-gray-400){--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}:is(.dark .dark\:hover\:text-danger-400:hover){--tw-text-opacity:1;color:rgb(251 113 133/var(--tw-text-opacity))} \ No newline at end of file +.-top-2{top:-.5rem}.right-8{right:2rem}.top-8{top:2rem}.z-30{z-index:30}.-m-1{margin:-.25rem}.-m-1\.5{margin:-.375rem}.m-6{margin:1.5rem}.\!mt-0{margin-top:0!important}.-me-1{-webkit-margin-end:-.25rem;margin-inline-end:-.25rem}.-me-1\.5{-webkit-margin-end:-.375rem;margin-inline-end:-.375rem}.-ms-1{-webkit-margin-start:-.25rem;margin-inline-start:-.25rem}.-ms-1\.5{-webkit-margin-start:-.375rem;margin-inline-start:-.375rem}.mb-4{margin-bottom:1rem}.ms-auto{-webkit-margin-start:auto;margin-inline-start:auto}.mt-\[1rem\]{margin-top:1rem}.basis-1\/3{flex-basis:33.333333%}.basis-2\/3{flex-basis:66.666667%}.-rotate-180,.rotate-180{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rotate-180{--tw-rotate:180deg}.gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.gap-y-4{row-gap:1rem}.overscroll-contain{overscroll-behavior:contain}.border-gray-100{--tw-border-opacity:1;border-color:rgb(243 244 246/var(--tw-border-opacity))}.bg-black\/80{background-color:#000c}.bg-gray-50,.bg-white{--tw-bg-opacity:1}.text-black\/80{color:#000c}.text-gray-950{--tw-text-opacity:1;color:rgb(3 7 18/var(--tw-text-opacity))}.ring-gray-950\/5{--tw-ring-color:#0307120d}.hover\:opacity-100:hover{opacity:1}:is(.dark .dark\:border-white\/10){border-color:#ffffff1a}:is(.dark .dark\:bg-gray-900){--tw-bg-opacity:1;background-color:rgb(17 24 39/var(--tw-bg-opacity))}:is(.dark .dark\:bg-white\/5){background-color:#ffffff0d}:is(.dark .dark\:text-white){--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}:is(.dark .dark\:ring-white\/10){--tw-ring-color:#ffffff1a} \ No newline at end of file diff --git a/resources/views/block-editor.blade.php b/resources/views/block-editor.blade.php index fa2d5d8..a653d30 100644 --- a/resources/views/block-editor.blade.php +++ b/resources/views/block-editor.blade.php @@ -1,71 +1,75 @@ - - @php - $containers = $getChildComponentContainers(); + + @php + $containers = $getChildComponentContainers(); - $isCloneable = $isCloneable(); - $isCollapsible = $isCollapsible(); - $isItemCreationDisabled = $isItemCreationDisabled(); - $isItemDeletionDisabled = $isItemDeletionDisabled(); - $isItemMovementDisabled = $isItemMovementDisabled(); - @endphp + $addAction = $getAction($getAddActionName()); + $addBetweenAction = $getAction($getAddBetweenActionName()); + $cloneAction = $getAction($getCloneActionName()); + $deleteAction = $getAction($getDeleteActionName()); + $moveDownAction = $getAction($getMoveDownActionName()); + $moveUpAction = $getAction($getMoveUpActionName()); + $reorderAction = $getAction($getReorderActionName()); + $isAddable = $isAddable(); + $isCloneable = $isCloneable(); + $isCollapsible = $isCollapsible(); + $isDeletable = $isDeletable(); + $isReorderableWithButtons = $isReorderableWithButtons(); + $isReorderableWithDragAndDrop = $isReorderableWithDragAndDrop(); -
-
-
-
-
- @if (count($containers) > 1 && $isCollapsible) -
- - {{ __('forms::components.builder.buttons.collapse_all.label') }} - + $statePath = $getStatePath(); + @endphp - - {{ __('forms::components.builder.buttons.expand_all.label') }} - -
- @endif -
- Visual - builder - Close visual - builder -
-
+
merge($getExtraAttributes(), escape: false) + ->class(['fi-fo-builder grid gap-y-4']) + }} + > +
+
+
+
+ @if ((count($containers) > 1) && $isCollapsible) +
+ + {{ $getAction('collapseAll') }} + + + {{ $getAction('expandAll') }} + +
+ @endif -
merge($getExtraAttributes())->class([ - 'filament-forms-builder-component space-y-6 rounded-xl', - 'bg-gray-50 p-6' => $isInset(), - 'dark:bg-gray-500/10' => $isInset() && config('forms.dark_mode'), - ]) }}> - @if (count($containers)) -
    !$isItemCreationDisabled && !$isItemMovementDisabled, - 'space-y-6' => $isItemCreationDisabled || $isItemMovementDisabled, - ]) wire:sortable - wire:end.stop="dispatchFormEvent('builder::moveItems', '{{ $getStatePath() }}', $event.target.sortable.toArray())"> - @php - $hasBlockLabels = $hasBlockLabels(); - $hasBlockNumbers = $hasBlockNumbers(); - @endphp +
    + Visual + builder + +
    +
+
+ @if (count($containers)) +
    + @php + $hasBlockLabels = $hasBlockLabels(); + $hasBlockNumbers = $hasBlockNumbers(); + @endphp - @foreach ($containers as $uuid => $item) -
  • getId() }}.{{ $item->getStatePath() }}.{{ $field::class }}.item" + x-data="{ + isCollapsed: @js($isCollapsed($item)), + }" + x-on:builder-expand.window="$event.detail === '{{ $statePath }}' && (isCollapsed = false)" + x-on:builder-collapse.window="$event.detail === '{{ $statePath }}' && (isCollapsed = true)" + x-on:expand-concealing-component.window=" error = $el.querySelector('[data-validation-error]') if (! error) { @@ -78,187 +82,194 @@ return } - setTimeout(() => $el.scrollIntoView({ behavior: 'smooth', block: 'start', inline: 'start' }), 200) + setTimeout( + () => + $el.scrollIntoView({ + behavior: 'smooth', + block: 'start', + inline: 'start', + }), + 200, + ) " - @class([ - 'bg-white border border-gray-300 shadow-sm rounded-xl relative', - 'dark:bg-gray-800 dark:border-gray-600' => config('forms.dark_mode'), - ])> - @if (!$isItemMovementDisabled || $hasBlockLabels || !$isItemDeletionDisabled || $isCollapsible || $isCloneable) -
    config('forms.dark_mode'), - 'cursor-pointer' => $isCollapsible, - ])> - @unless($isItemMovementDisabled) - - @endunless + x-sortable-item="{{ $uuid }}" + class="fi-fo-builder-item rounded-xl bg-white shadow-sm ring-1 ring-gray-950/5 dark:bg-white/5 dark:ring-white/10" + > + @if ($isReorderableWithDragAndDrop || $isReorderableWithButtons || $hasBlockLabels || $isCloneable || $isDeletable || $isCollapsible) +
    + @if ($isReorderableWithDragAndDrop || $isReorderableWithButtons) +
      + @if ($isReorderableWithDragAndDrop) +
    • + {{ $reorderAction }} +
    • + @endif - @if ($hasBlockLabels) -

      config('forms.dark_mode'), - ])> - @php - $block = $item->getParentComponent(); + @if ($isReorderableWithButtons) +

    • + {{ $moveUpAction(['item' => $uuid])->disabled($loop->first) }} +
    • - $block->labelState($item->getRawState()); - @endphp +
    • + {{ $moveDownAction(['item' => $uuid])->disabled($loop->last) }} +
    • + @endif +
    + @endif - {{ $item->getParentComponent()->getLabel() }} + @if ($hasBlockLabels) +

    + @php + $block = $item->getParentComponent(); - @php - $block->labelState(null); - @endphp + $block->labelState($item->getRawState()); + @endphp - @if ($hasBlockNumbers) - {{ $loop->iteration }} - @endif -

    - @endif - -
    + {{ $item->getParentComponent()->getLabel() }} -
      config('forms.dark_mode'), - ])> - @if ($isCloneable) -
    • - -
    • - @endif + @if ($hasBlockNumbers) + {{ $loop->iteration }} + @endif +

    + @endif - @unless($isItemDeletionDisabled) -
  • - -
  • - @endunless + @if ($isDeletable) +
  • + {{ $deleteAction(['item' => $uuid]) }} +
  • + @endif - @if ($isCollapsible) -
  • -
+ @endif +
+ @endif - +
+ {{ $item }} +
+ - - {{ __('forms::components.builder.buttons.expand_item.label') }} - - - - @endif - - - @endif + @if ((! $loop->last) && $isAddable) + + @endif + @endforeach + + @endif -
- {{ $item }} + @if ($isAddable) + + + {{ $addAction }} + + + @endif
- -
- {{ __('forms::components.builder.collapsed') }} +
+
+
+ +
+{{-- todo breakpoints not working, disabled for now--}} +{{--
--}} +{{-- Mobile--}} +{{-- Tablet--}} +{{-- Desktop--}} +{{--
--}} + @if($containers) +
+ @foreach ($containers as $uuid => $item) + + {!! $preview($item) !!} + + @endforeach
- - @if (!$loop->last && !$isItemCreationDisabled && !$isItemMovementDisabled) -
- - - - - -
@endif - - @endforeach - - @endif - - @if (!$isItemCreationDisabled) - - - - {{ $getCreateItemButtonLabel() }} - - - - @endif -
-
- -
-
- -
-
- Mobile - Tablet - Desktop -
-
- @foreach ($containers as $uuid => $item) - - {!! $preview($item) !!} - - @endforeach -
+
+
-
-
+ diff --git a/src/BlockRenderer.php b/src/BlockRenderer.php index 95cb582..53ea2ed 100644 --- a/src/BlockRenderer.php +++ b/src/BlockRenderer.php @@ -1,12 +1,12 @@ option('force')) { return false; } - return $this->writeView(); + $this->writeView(); + + return true; } /** @@ -67,7 +69,7 @@ protected function qualifyClass($name) /** * Write the view for the component. */ - protected function writeView(?Closure $onSuccess = null): void + protected function writeView(Closure $onSuccess = null): void { $path = $this->viewPath( str_replace('.', '/', 'filament.blocks.'.$this->getView()).'.blade.php' diff --git a/src/Commands/stubs/block.stub b/src/Commands/stubs/block.stub index ee0384f..fb9841c 100644 --- a/src/Commands/stubs/block.stub +++ b/src/Commands/stubs/block.stub @@ -3,7 +3,7 @@ namespace {{ namespace }}; use Filament\Forms\Components\TextInput; -use Haringsrob\FilamentPageBuilder\Blocks\BlockEditorBlock; +use Sevendays\FilamentPageBuilder\Blocks\BlockEditorBlock; use Illuminate\Contracts\View\View; class {{ class }} extends BlockEditorBlock { diff --git a/src/Facades/BlockRenderer.php b/src/Facades/BlockRenderer.php index 2d6ffc5..bbf08e8 100644 --- a/src/Facades/BlockRenderer.php +++ b/src/Facades/BlockRenderer.php @@ -1,6 +1,6 @@ __DIR__.'/../resources/dist/filament-page-builder.css', - ]; - - protected array $scripts = [ - 'plugin-filament-page-builder' => __DIR__.'/../resources/dist/filament-page-builder.js', - ]; - public function configurePackage(Package $package): void { $package->name(static::$name) - ->hasViews(static::$name) + ->hasViews() ->runsMigrations() ->hasMigration('2023_02_07_153528_create_blocks_table') ->hasMigration('2023_06_17_183553_add_shared_to_blocks') ->hasCommand(MakePageBuilderBlock::class); } + public function packageBooted(): void + { + FilamentAsset::register([ + Css::make('plugin-filament-page-builder', __DIR__.'/../resources/dist/filament-page-builder.css'), + ], 'sevendays/filament-page-builder'); + } + public function register(): void { parent::register(); diff --git a/src/Forms/Components/BlockEditor.php b/src/Forms/Components/BlockEditor.php index 74c0f69..42973a9 100644 --- a/src/Forms/Components/BlockEditor.php +++ b/src/Forms/Components/BlockEditor.php @@ -1,21 +1,21 @@ getCachedExistingRecords() : null; - return collect($this->getState()) - ->filter(fn(array $itemData): bool => $this->hasBlock($itemData['type'])) + $container = collect($this->getState()) + ->filter(fn (array $itemData): bool => $this->hasBlock($itemData['type'])) ->map( - fn(array $itemData, $itemIndex): ComponentContainer => $this + fn (array $itemData, $itemIndex): ComponentContainer => $this ->getBlock($itemData['type']) ->getChildComponentContainer() ->model($relationship ? $records[$itemIndex] ?? $this->getRelatedModel() : null) - ->getClone() ->statePath("{$itemIndex}.data") - ->inlineLabel(false), + ->inlineLabel(false) + ->getClone(), ) ->all(); + + return $container; } - public function relationship(string|Closure|null $name = null, ?Closure $callback = null): static + public function relationship(string|Closure $name = null, Closure $callback = null): static { $this->relationship = $name ?? $this->getName(); $this->modifyRelationshipQueryUsing = $callback; @@ -124,16 +126,19 @@ public function relationship(string|Closure|null $name = null, ?Closure $callbac } $relationship - ->whereIn($relationship->getRelated()->getQualifiedKeyName(), $recordsToDelete) + //todo check if we can use same way as in Repeater + //->whereIn($relationship->getRelated()->getQualifiedKeyName(), $recordsToDelete) + ->whereKey($recordsToDelete) ->get() - ->each(static fn(Model $record) => $record->delete()); + ->each(static fn (Model $record) => $record->delete()); $childComponentContainers = $component->getChildComponentContainers(); $itemOrder = 1; $orderColumn = $component->getOrderColumn(); - $activeLocale = $livewire->getActiveFormLocale(); + $activeLocale = $livewire->getActiveFormsLocale(); + $translatableContentDriver = $livewire->makeFilamentTranslatableContentDriver(); foreach ($childComponentContainers as $itemKey => $item) { $itemData = $item->getState(shouldCallHooksBefore: false); @@ -146,24 +151,30 @@ public function relationship(string|Closure|null $name = null, ?Closure $callbac /** @var Model $record */ if ($record = ($existingRecords[$itemKey] ?? null)) { - $activeLocale && method_exists($record, 'setLocale') && $record->setLocale($activeLocale); + //$activeLocale && method_exists($record, 'setLocale') && $record->setLocale($activeLocale); $itemData = $component->mutateRelationshipDataBeforeSave($itemData, record: $record); - if ($activeLocale && $record instanceof Block) { - // Handle locale saving. - $record->fill(Arr::except($itemData, $record->getTranslatableAttributes())); - - foreach (Arr::only($itemData, $record->getTranslatableAttributes()) as $key => $value) { - $record->setTranslation($key, $activeLocale, $value); - } - - $record->save(); - } else { + $translatableContentDriver ? + $translatableContentDriver->updateRecord($record, $itemData) : $record->fill($itemData)->save(); - } continue; + + // if ($activeLocale && $record instanceof Block) { + // // Handle locale saving. + // $record->fill(Arr::except($itemData, $record->getTranslatableAttributes())); + // + // foreach (Arr::only($itemData, $record->getTranslatableAttributes()) as $key => $value) { + // $record->setTranslation($key, $activeLocale, $value); + // } + // + // $record->save(); + // } else { + // $record->fill($itemData)->save(); + // } + // + // continue; } $relatedModel = $component->getRelatedModel(); @@ -180,16 +191,15 @@ public function relationship(string|Closure|null $name = null, ?Closure $callbac if ($activeLocale && $record instanceof Block) { // Handle locale saving. $record->fill(Arr::except($itemData, $record->getTranslatableAttributes())); - foreach (Arr::only($itemData, $record->getTranslatableAttributes()) as $key => $value) { $record->setTranslation($key, $activeLocale, $value); } + } else { $record->fill($itemData); } $record = $relationship->save($record); - $item->model($record)->saveRelationships(); } }); @@ -238,7 +248,7 @@ public function getCachedExistingRecords(): Collection $relatedKeyName = $relationship->getRelated()->getKeyName(); return $this->cachedExistingRecords = $relationshipQuery->get()->mapWithKeys( - fn(Model $item): array => ["record-{$item[$relatedKeyName]}" => $item], + fn (Model $item): array => ["record-{$item[$relatedKeyName]}" => $item], ); } @@ -267,28 +277,22 @@ protected function getStateFromRelatedRecords(Collection $records): array return []; } - $activeLocale = $this->getLivewire()->getActiveFormLocale(); - - return $records - ->map(function (Model $record) use ($activeLocale): array { - $state = $record->attributesToArray(); + $translatableContentDriver = $this->getLivewire()->makeFilamentTranslatableContentDriver(); - if ( - $activeLocale && - method_exists($record, 'getTranslatableAttributes') && - method_exists($record, 'getTranslation') - ) { - foreach ($record->getTranslatableAttributes() as $attribute) { - $state[$attribute] = $record->getTranslation($attribute, $activeLocale); - } - } + $state = $records + ->map(function (Model $record) use ($translatableContentDriver): array { + $data = $translatableContentDriver ? + $translatableContentDriver->getRecordAttributesToArray($record) : + $record->attributesToArray(); - return $this->mutateRelationshipDataBeforeFill($state); + return $this->mutateRelationshipDataBeforeFill($data); }) ->toArray(); + + return $state; } - public function mutateRelationshipDataBeforeCreate(array $data, BlockEditorBlock $item): array + public function mutateRelationshipDataBeforeCreate(array $data, Component|null|BlockEditorBlock $item): array { if ($this->mutateRelationshipDataBeforeCreateUsing instanceof Closure) { $data = $this->evaluate($this->mutateRelationshipDataBeforeCreateUsing, [ @@ -383,11 +387,17 @@ public function preview(ComponentContainer $container): View|string } try { + $state = []; + try { + $state = $container->getState(false); + } catch (\Illuminate\Validation\ValidationException $e) { + } + return view( $view, - ['preview' => $container->getParentComponent()->renderDisplay($container->getState())] + ['preview' => $container->getParentComponent()->renderDisplay($state)] ); - } catch (ErrorException|Exception $e) { + } catch (ErrorException|\Exception $e) { return __('Error when rendering: :phError', ['phError' => $e->getMessage()]); } } diff --git a/src/Models/Block.php b/src/Models/Block.php index a103275..1b527c9 100644 --- a/src/Models/Block.php +++ b/src/Models/Block.php @@ -1,6 +1,6 @@ 'array', - 'shared' => 'array' + 'shared' => 'array', ]; public function blockable(): MorphTo diff --git a/src/Models/Contracts/Blockable.php b/src/Models/Contracts/Blockable.php index 76d64c3..ccb992b 100644 --- a/src/Models/Contracts/Blockable.php +++ b/src/Models/Contracts/Blockable.php @@ -1,6 +1,6 @@ toBeTrue(); -}); +it('can test') + ->expect(true) + ->toBeTrue(); diff --git a/tests/Pest.php b/tests/Pest.php index f2a90c2..5bb3b9d 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -1,5 +1,5 @@ in(__DIR__); diff --git a/tests/TestCase.php b/tests/TestCase.php index 6ea945c..22c3086 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -1,12 +1,25 @@