diff --git a/css/layout-paragraphs-builder.css b/css/layout-paragraphs-builder.css index 99cf51a70a72dfc0e85b20e13feaf9b113d11cd2..9209ddd08bd752444a919ff4287eb283be69ed08 100644 --- a/css/layout-paragraphs-builder.css +++ b/css/layout-paragraphs-builder.css @@ -209,6 +209,9 @@ transform: translateX(-50%); transition: all .15s linear; z-index: 1000; +} +.lpb-component .lpb-btn--add, +.lpb-region .lpb-btn--add { opacity: 0; } .lpb-btn--add:focus { diff --git a/img/.DS_Store b/img/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 Binary files /dev/null and b/img/.DS_Store differ diff --git a/js/layout-paragraphs-builder.js b/js/layout-paragraphs-builder.js index b3c65a2bcf5524e2a15b9972e11ec6f069756185..6c6e85b6b8df78104698227a5dcfc8455db2c266 100644 --- a/js/layout-paragraphs-builder.js +++ b/js/layout-paragraphs-builder.js @@ -1,5 +1,5 @@ (($, Drupal, debounce, drupalSettings, dragula) => { - const idAttr = 'data-lp-builder-id'; + const idAttr = 'data-lpb-id'; const reorderComponents = debounce($element => { const id = $element.attr(idAttr); const order = $('.lpb-component', $element) @@ -206,16 +206,19 @@ Drupal.behaviors.layoutParagraphsBuilder = { attach: function attach(context, settings) { // Run only once - initialize the editor ui. - $('[data-lp-builder-id]', context) - .once('lp-builder') + $(`.has-components[${idAttr}]`) + .once('lpb-enabled') .each((index, element) => { const $element = $(element); const id = $element.attr(idAttr); const lpbSettings = settings.lpBuilder[id]; - const dragContainers = $element - .find('.lpb-components, .lpb-region') - .get(); - const drake = dragula(dragContainers, { + const drake = dragula({ + isContainer: el => { + console.log(el, $element); + return el.classList.contains('lpb-component-list') || + el.classList.contains('lpb-region'); + }, + accepts(el, target, source, sibling) { // Returns false if any registered validator returns a value. // @see addMoveValidator() @@ -270,7 +273,7 @@ // Run every time the behavior is attached. if (context.classList && context.classList.contains('lpb-component')) { $(context) - .closest('[data-lp-builder-id]') + .closest('[data-lpb-id]') .each((index, element) => { updateMoveButtons($(element)); }); diff --git a/layout_paragraphs.module b/layout_paragraphs.module index 1ac0b5d08441b4bdc6ceb1b131a733a0c1b5407a..dc95b841d48a686c0664efc3dfc932dad1170090 100644 --- a/layout_paragraphs.module +++ b/layout_paragraphs.module @@ -54,9 +54,13 @@ function layout_paragraphs_theme() { 'insert_button' => '', ], ], - 'layout_paragraphs_builder_empty_layout' => [ + 'layout_paragraphs_builder_formatter' => [ 'variables' => [ - + 'link_url' => NULL, + 'link_text' => NULL, + 'field_label' => NULL, + 'is_empty' => FALSE, + 'root_components' => [], ], ], 'layout_paragraphs_builder_controls' => [ @@ -72,11 +76,6 @@ function layout_paragraphs_theme() { 'types' => NULL, ], ], - 'layout_paragraphs_builder_section_menu' => [ - 'variables' => [ - 'types' => NULL, - ], - ], ]; } diff --git a/layout_paragraphs.routing.yml b/layout_paragraphs.routing.yml index 09106eda08bc7bb820fcd44d49edde27f1dbcc29..064e1eff20ae877aeb30027d9683a4dcaffbb063 100644 --- a/layout_paragraphs.routing.yml +++ b/layout_paragraphs.routing.yml @@ -13,6 +13,18 @@ layout_paragraphs.section_settings: requirements: _permission: 'administer site configuration' +layout_paragraphs.builder.formatter: + path: '/layout-paragraphs-builder/{layout_paragraphs_layout}/formatter' + defaults: + _controller: '\Drupal\layout_paragraphs\Controller\LayoutParagraphsBuilderFormatterController::build' + options: + parameters: + layout_paragraphs_layout: + layout_paragraphs_layout_tempstore: TRUE + _admin_route: TRUE + requirements: + _permission: 'administer content' + layout_paragraphs.builder.choose_component: path: '/layout-paragraphs-builder/{layout_paragraphs_layout}/choose-component/{sibling_uuid}/{placement}/{region}/{parent_uuid}' defaults: diff --git a/src/Controller/LayoutParagraphsBuilderFormatterController.php b/src/Controller/LayoutParagraphsBuilderFormatterController.php new file mode 100644 index 0000000000000000000000000000000000000000..99d4ba89ad9bcbd614b6a936c80077418f1c49c4 --- /dev/null +++ b/src/Controller/LayoutParagraphsBuilderFormatterController.php @@ -0,0 +1,40 @@ +<?php + +namespace Drupal\layout_paragraphs\Controller; + +use Drupal\Core\Ajax\AjaxResponse; +use Drupal\Core\Ajax\AjaxHelperTrait; +use Drupal\Core\Ajax\ReplaceCommand; +use Drupal\Core\Controller\ControllerBase; +use Drupal\layout_paragraphs\LayoutParagraphsLayout; + +/** + * Layout paragraphs builder formatter controller. + */ +class LayoutParagraphsBuilderFormatterController extends ControllerBase { + + use AjaxHelperTrait; + + /** + * Builds the layout paragraphs builder form. + * + * @param \Drupal\layout_paragraphs\LayoutParagraphsLayout $layout_paragraphs_layout + * The layout paragraphs layout object. + * + * @return mixed + * An ajax response or the form. + */ + public function build(LayoutParagraphsLayout $layout_paragraphs_layout) { + $form = $this->formBuilder()->getForm( + '\Drupal\layout_paragraphs\Form\FrontendBuilderForm', + $layout_paragraphs_layout + ); + if ($this->isAjax()) { + $response = new AjaxResponse(); + $response->addCommand(new ReplaceCommand('[data-lpb-id="' . $layout_paragraphs_layout->id() . '"]', $form)); + return $response; + } + return $form; + } + +} diff --git a/src/Element/LayoutParagraphsBuilder.php b/src/Element/LayoutParagraphsBuilder.php index da597656109cc28c42e85927757ed2f2d31340bd..b6aa3ef4b3230e6425c91786b3f1e8ffeac791b1 100644 --- a/src/Element/LayoutParagraphsBuilder.php +++ b/src/Element/LayoutParagraphsBuilder.php @@ -176,7 +176,7 @@ class LayoutParagraphsBuilder extends RenderElement implements ContainerFactoryP 'lp-builder', 'lp-builder-' . $layout->id(), ], - 'data-lp-builder-id' => $layout->id(), + 'data-lpb-id' => $layout->id(), ]; $element['#attached']['library'] = ['layout_paragraphs/layout_paragraphs_builder']; $element['#attached']['drupalSettings']['lpBuilder'][$layout->id()] = $layout->getSettings(); @@ -194,6 +194,9 @@ class LayoutParagraphsBuilder extends RenderElement implements ContainerFactoryP $uuid = $component->getEntity()->uuid(); $element['#root_components'][$uuid] =& $element['#components'][$uuid]; } + if (count($element['#root_components'])) { + $element['#attributes']['class'][] = 'has-components'; + } return $element; } diff --git a/src/Form/FrontendBuilderForm.php b/src/Form/FrontendBuilderForm.php new file mode 100644 index 0000000000000000000000000000000000000000..46b027dc4a140f41d4a2abd1fac1916e506dac9e --- /dev/null +++ b/src/Form/FrontendBuilderForm.php @@ -0,0 +1,104 @@ +<?php + +namespace Drupal\layout_paragraphs\Form; + +use Drupal\Core\Ajax\AjaxFormHelperTrait; +use Drupal\Core\Form\FormBase; +use Drupal\Core\Form\FormStateInterface; +use Drupal\layout_paragraphs\LayoutParagraphsLayout; + +/** + * Class LayoutParagraphsBuilderForm. + * + * Base form for layout paragraphs paragraph forms. + */ +class FrontendBuilderForm extends FormBase { + + /** + * A layout paragraphs layout object. + * + * @var \Drupal\layout_paragraphs\LayoutParagraphsLayout + */ + protected $layoutParagraphsLayout; + + /** + * {@inheritDoc} + */ + public function getFormId() { + return 'layout_paragraphs_frontend_builder_form'; + } + + /** + * Builds the layout paragraphs builder form. + * + * @param array $form + * The form array. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The form state object. + * @param \Drupal\layout_paragraphs\LayoutParagraphsLayout $layout_paragraphs_layout + * The layout paragraphs layout object. + */ + public function buildForm( + array $form, + FormStateInterface $form_state, + LayoutParagraphsLayout $layout_paragraphs_layout = NULL) { + $this->layoutParagraphsLayout = $layout_paragraphs_layout; + $form['layout_paragraphs_builder_ui'] = [ + '#type' => 'layout_paragraphs_builder', + '#layout_paragraphs_layout' => $this->layoutParagraphsLayout, + ]; + $form['actions'] = [ + '#type' => 'actions', + 'save' => [ + '#type' => 'submit', + '#value' => $this->t('Save'), + '#submit' => [[$this, 'saveLayout']], + '#ajax' => [ + 'callback' => [$this, 'closeBuilder'], + ], + ], + 'cancel' => [ + '#type' => 'submit', + '#value' => $this->t('Cancel'), + '#submit' => [[$this, 'closeBuilder']], + '#ajax' => [ + 'callback' => [$this, 'closeBuilder'], + ], + ], + ]; + return $form; + } + + /** + * Saves the layout to its parent entity. + * + * @param array $form + * The form array. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The form state object. + */ + public function saveLayout(array &$form, FormStateInterface $form_state) { + $entity = $this->layoutParagraphsLayout->getEntity(); + $field_name = $this->layoutParagraphsLayout->getFieldName(); + $entity->$field_name = $this->layoutParagraphsLayout->getParagraphsReferenceField(); + $entity->save(); + } + + /** + * Closes the builder and returns the rendered layout. + */ + public function closeBuilder() { + $rendered_layout = $this->layoutParagraphsLayout->getParagraphsReferenceField()->view(); + return $rendered_layout; + } + + public function refreshLayout($form, $form_state) { + return $form; + } + + /** + * {@inheritDoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) {} + +} diff --git a/src/Form/InsertComponentForm.php b/src/Form/InsertComponentForm.php index f3e100b363de421039e8daafa69e2bb4f7058426..815516ef9bd93e518fe780517026011e3f543126 100644 --- a/src/Form/InsertComponentForm.php +++ b/src/Form/InsertComponentForm.php @@ -74,7 +74,7 @@ class InsertComponentForm extends ComponentFormBase { $this->domSelector = '[data-region-uuid="' . $parent_uuid . '-' . $region . '"]'; } else { - $this->domSelector = '[data-lp-builder-id="' . $this->layoutParagraphsLayout->id() . '"]'; + $this->domSelector = '[data-lpb-id="' . $this->layoutParagraphsLayout->id() . '"]'; } return $this->buildComponentForm($form, $form_state); } diff --git a/src/LayoutParagraphsLayoutRefreshTrait.php b/src/LayoutParagraphsLayoutRefreshTrait.php index 04ee1a4b05b23b2e74de59706c09c9604f4f6e12..ddbb82de81fc9a6fa8c7684ab0d8fc89898df31f 100644 --- a/src/LayoutParagraphsLayoutRefreshTrait.php +++ b/src/LayoutParagraphsLayoutRefreshTrait.php @@ -63,7 +63,7 @@ trait LayoutParagraphsLayoutRefreshTrait { */ protected function refreshLayout(AjaxResponse $response) { $layout = $this->renderLayout(); - $dom_selector = '[data-lp-builder-id="' . $this->layoutParagraphsLayout->id() . '"]'; + $dom_selector = '[data-lpb-id="' . $this->layoutParagraphsLayout->id() . '"]'; $response->addCommand(new ReplaceCommand($dom_selector, $layout)); return $response; } diff --git a/src/Plugin/Field/FieldFormatter/LayoutParagraphsBuilderFormatter.php b/src/Plugin/Field/FieldFormatter/LayoutParagraphsBuilderFormatter.php new file mode 100644 index 0000000000000000000000000000000000000000..70dca8ed7f2d0a6b55e3daecdb1e8a3c79c6618b --- /dev/null +++ b/src/Plugin/Field/FieldFormatter/LayoutParagraphsBuilderFormatter.php @@ -0,0 +1,162 @@ +<?php + +namespace Drupal\layout_paragraphs\Plugin\Field\FieldFormatter; + +use Drupal\Core\Url; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Field\FieldItemListInterface; +use Drupal\Core\Field\FieldDefinitionInterface; +use Drupal\layout_paragraphs\LayoutParagraphsLayout; +use Drupal\Core\Logger\LoggerChannelFactoryInterface; +use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Drupal\Core\Entity\EntityDisplayRepositoryInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Drupal\layout_paragraphs\LayoutParagraphsLayoutTempstoreRepository; +use Drupal\layout_paragraphs\Plugin\Field\FieldWidget\LayoutParagraphsWidget; + +/** + * Layout Paragraphs field formatter. + * + * @FieldFormatter( + * id = "layout_paragraphs_builder", + * label = @Translation("Layout Paragraphs Builder"), + * description = @Translation("Renders editable paragraphs with layout."), + * field_types = { + * "entity_reference_revisions" + * } + * ) + */ +class LayoutParagraphsBuilderFormatter extends LayoutParagraphsFormatter implements ContainerFactoryPluginInterface { + + /** + * The tempstore. + * + * @var \Drupal\layout_paragraphs\LayoutParagraphsLayoutTempstoreRepository + */ + protected $tempstore; + + /** + * {@inheritDoc} + */ + public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, $label, $view_mode, array $third_party_settings, LoggerChannelFactoryInterface $logger_factory, EntityDisplayRepositoryInterface $entity_display_repository, LayoutParagraphsLayoutTempstoreRepository $tempstore) { + parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $label, $view_mode, $third_party_settings, $logger_factory, $entity_display_repository); + $this->tempstore = $tempstore; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $plugin_id, + $plugin_definition, + $configuration['field_definition'], + $configuration['settings'], + $configuration['label'], + $configuration['view_mode'], + $configuration['third_party_settings'], + $container->get('logger.factory'), + $container->get('entity_display.repository'), + $container->get('layout_paragraphs.tempstore_repository') + ); + } + + /** + * {@inheritDoc} + */ + public function view(FieldItemListInterface $items, $langcode = NULL) { + + $elements = [ + '#theme' => 'layout_paragraphs_builder_formatter', + '#root_components' => parent::view($items, $langcode), + ]; + $entity = $items->getEntity(); + if (!$entity->access('update')) { + return $elements['#root_components']; + } + + /** @var \Drupal\Core\Entity\EntityDefintion $definition */ + $definition = $items->getFieldDefinition(); + $field_name = $definition->get('field_name'); + + $layout = new LayoutParagraphsLayout($items, $this->getSettings()); + $this->tempstore->set($layout); + $layout = $this->tempstore->get($layout); + + $root_components = $layout->getRootComponents(); + $elements['#link_text'] = $this->t('Edit @field_name', ['@field_name' => $field_name]); + $elements['#link_url'] = Url::fromRoute('layout_paragraphs.builder.formatter', [ + 'layout_paragraphs_layout' => $layout->id(), + ]); + $elements['#field_label'] = $definition->label(); + $elements['#type'] = 'container'; + $elements['#is_empty'] = count($layout->getRootComponents()) == 0; + $elements['#attributes'] = [ + 'class' => [ + 'lpb-formatter', + ], + 'data-lpb-id' => $layout->id(), + ]; + $elements['#attached']['library'][] = 'layout_paragraphs/layout_paragraphs_builder'; + return $elements; + } + + /** + * Replicates settings from Layout Paragraphs Widget. + * + * @see \Drupal\layout_paragraphs\Plugin\Field\FieldWidget\LayoutParagraphsWidget + * + * {@inheritdoc} + */ + public function settingsForm(array $form, FormStateInterface $form_state) { + $widget = $this->widgetInstance(); + return $widget->settingsForm($form, $form_state); + } + + /** + * Replicates settings from Layout Paragraphs Widget. + * + * @see \Drupal\layout_paragraphs\Plugin\Field\FieldWidget\LayoutParagraphsWidget + * + * {@inheritdoc} + */ + public function settingsSummary() { + $widget = $this->widgetInstance(); + return $widget->settingsSummary(); + } + + /** + * Replicates settings from Layout Paragraphs Widget. + * + * @see \Drupal\layout_paragraphs\Plugin\Field\FieldWidget\LayoutParagraphsWidget + * + * @return array + * The default settings array. + */ + public static function defaultSettings() { + $defaults = parent::defaultSettings(); + return LayoutParagraphsWidget::defaultSettings() + $defaults; + } + + /** + * Returns a layout paragraphs field widget with correct settings applied. + * + * @return \Drupal\Core\Field\WidgetInterface + * The widget instance. + */ + protected function widgetInstance() { + $plugin_manager = \Drupal::service('plugin.manager.field.widget'); + $widget = $plugin_manager->getInstance([ + 'field_definition' => $this->fieldDefinition, + 'form_mode' => 'layout_paragraphs_editor', + 'prepare' => TRUE, + 'configuration' => [ + 'type' => 'layout_paragraphs', + 'settings' => $this->getSettings(), + 'third_party_settings' => [], + ], + ]); + return $widget; + } + +} diff --git a/templates/layout-paragraphs-builder-formatter.html.twig b/templates/layout-paragraphs-builder-formatter.html.twig new file mode 100644 index 0000000000000000000000000000000000000000..8eea35d9d2232269f94dbb5b3ce3022f4c93d325 --- /dev/null +++ b/templates/layout-paragraphs-builder-formatter.html.twig @@ -0,0 +1,17 @@ +{% if is_empty %} +<div class="lpb-enable__empty-message__wrapper"> + <div class="lpb-enable__empty-message"> + <p> + You haven't created any {{ field_label }} yet. + <a class="lpb-enable-button use-ajax" href="{{link_url}}">{% trans %}Start creating {{ field_label }}.{% endtrans %}</a> + </p> + </div> +</div> +{% else %} +<div class="lpb-enable__wrapper"> + <div class="lpb-enable"> + <div class="lpb-enable__button"><a class="lpb-enable-button use-ajax" href="{{link_url}}"><span>{{link_text}}</span></a></div> + </div> +</div> +{{ root_components }} +{% endif %} \ No newline at end of file diff --git a/templates/layout-paragraphs-builder.html.twig b/templates/layout-paragraphs-builder.html.twig index a592292dd5a36cf7a6c3cfdfed376fcbcdd42a03..5ebf84779720e043e29e0d9c9cee5b5940044500 100644 --- a/templates/layout-paragraphs-builder.html.twig +++ b/templates/layout-paragraphs-builder.html.twig @@ -8,7 +8,7 @@ </div> </div> {% else %} - <div class="lpb-components"> + <div class="lpb-component-list"> {{ root_components }} </div> {% endif %}