You are here

Builder.php in GridStack 8.2

File

src/Plugin/gridstack/stylizer/Builder.php
View source
<?php

namespace Drupal\gridstack\Plugin\gridstack\stylizer;

use Drupal\Component\Serialization\Json;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Url;
use Drupal\Core\Render\Element;
use Drupal\gridstack\GridStackDefault;
use Drupal\gridstack\Entity\GridStack;
use Drupal\gridstack\Entity\GridStackVariant;

/**
 * Provides Layout Builder integration for editor previews.
 *
 * @GridStackStylizer(
 *   id = "builder",
 *   label = @Translation("Builder")
 * )
 */
class Builder extends Style {

  /**
   * Static cache for the variant options.
   *
   * @var array
   */
  private $variantOptions;

  /**
   * Provides gridstack skins and libraries.
   */
  public function attach(array &$load, array $attach = []) {
    parent::attach($load, $attach);

    // Admin assets.
    if (!empty($attach['_ipe'])) {

      // @todo remove modal later.
      $load['library'][] = 'gridstack/admin_modal';
      $load['library'][] = 'gridstack/admin_layout';

      // The CSS framework grid library for admin pages.
      if (!empty($attach['library'])) {
        $library = $attach['library'];
        if (strpos($library, ',') !== FALSE) {
          $items = array_map('trim', explode(',', $library));
          foreach ($items as $item) {
            $load['library'][] = $item;
          }
        }
        else {
          $load['library'][] = $library;
        }
      }
    }
  }

  /**
   * Provides Layout Builder attributes if available.
   *
   * GridStackLayout has no knowledge of IPE, and IPE expects region keys which
   * are not provided by GridStack, hence rebuild needed attributes.
   * Must use $content_attributes for draggable to work, not $attributes.
   *
   * @param array $box
   *   The box being modified.
   * @param array $content_attributes
   *   The content attributes being modified.
   * @param array $settings
   *   The settings.
   * @param array $regions
   *   The region attributes provided by Layout Builder for admin.
   */
  public function adminAttributes(array &$box, array &$content_attributes, array $settings, array $regions = []) {
    $rid = isset($settings['rid']) ? $settings['rid'] : -1;
    $region = isset($regions[$rid]) ? $regions[$rid] : [];

    // Layout Builder integration.
    if (!empty($region['#attributes']) && is_array($region['#attributes'])) {
      $this
        ->lb($box, $content_attributes, $settings, $region);
    }

    // Panels IPE integration.
    if (!empty($settings['_panels']) && !empty($region['#prefix'])) {
      $this
        ->panels($box, $settings, $region);
    }
  }

  /**
   * Provides inline styles specific for admin pages.
   */
  public function rootAttributes(array &$attributes, array $styles) {
    if ($rules = $this
      ->parseStyles($styles, FALSE)) {
      $attributes['data-gs-inline-styles'] = Json::encode(reset($rules));
    }
  }

  /**
   * Modifies inline styles for admin previews, not front-end.
   */
  public function inlineAttributes(array &$attributes, array $data) {
    $rules = $data['rules'];
    $selector = $data['selector'];
    if (!empty($rules[$selector])) {
      $this
        ->inlineStyle($attributes, $rules[$selector]);
    }
  }

  /**
   * Returns admin regions.
   */
  public function regions(array $element) {
    $regions = [];
    foreach (Element::children($element) as $child) {
      if ($child == 'items') {
        continue;
      }
      $regions[$child] = $element[$child];
    }
    return $regions;
  }

  /**
   * Provides Layout Builder attributes.
   */
  private function lb(array &$box, array &$content_attributes, array $settings, array $region = []) {
    $rid = isset($settings['rid']) ? $settings['rid'] : -1;
    $content_attributes = NestedArray::mergeDeep($content_attributes, $region['#attributes']);

    // Provides add block and contextual links.
    if (isset($region['layout_builder_add_block'])) {
      $link = $region['layout_builder_add_block'];
      $link['#attributes']['class'][] = 'gridstack__action';
      $link['#attributes']['data-gs-region'] = $rid;
      $link['#weight'] = 100;
      if (isset($link['link'], $link['link']['#url'])) {
        $label = 'data-layout-content-preview-placeholder-label';
        foreach (Element::children($box) as $uuid) {
          $params = $link['link']['#url']
            ->getRouteParameters() + [
            'uuid' => $uuid,
          ];
          $fallback = isset($box[$uuid]['#attributes'][$label]) ? $box[$uuid]['#attributes'][$label] : '';
          $box[$uuid]['#attributes']['class'][] = 'js-layout-builder-block layout-builder-block';
          $box[$uuid]['#attributes']['data-layout-block-uuid'] = $uuid;
          $box[$uuid]['#attributes']['data-layout-builder-highlight-id'] = $uuid;
          if (!empty($settings['_lbux'])) {
            assert(isset($box[$uuid]['content']));
            $box[$uuid]['content'] = [
              'actions' => $this
                ->lbux($params, $fallback),
              'content' => $box[$uuid]['content'],
            ];
          }
          else {
            $box[$uuid]['#contextual_links'] = $this
              ->contextualLinks($params);
          }
        }
      }
      if (isset($region['region_label'])) {
        $box['region_label'] = $region['region_label'];
      }
      $box['add_block'] = $link;
    }
  }

  /**
   * Provides Panels IPE attributes.
   */
  private function panels(array &$box, array $settings, array $region = []) {
    $box['#prefix'] = $region['#prefix'];
    $box['#suffix'] = $region['#suffix'];
    foreach (Element::children($box) as $bid) {
      if (isset($region[$bid]['#attributes']['data-block-id'])) {
        $box[$bid]['#attributes']['data-block-id'] = $region[$bid]['#attributes']['data-block-id'];
      }
    }
  }

  /**
   * Returns regular contextual links.
   */
  private function contextualLinks(array $params) {
    return [
      'layout_builder_block' => [
        'route_parameters' => $params,
        'metadata' => [
          'operations' => 'move:update:remove',
        ],
      ],
    ];
  }

  /**
   * Returns a clone of LB UX links.
   */
  private function lbux(array $params, $fallback) {
    $links = [
      '#type' => 'container',
      '#attributes' => [
        'class' => [
          'layout-builder__actions',
          'layout-builder__actions__block',
          // In case changed to match the rest BEM, we are prepared.
          'layout-builder__actions--block',
        ],
        'tabindex' => 0,
      ],
      'label' => [
        '#type' => 'html_tag',
        '#tag' => 'span',
        '#attributes' => [
          'class' => [
            'layout-builder__block-label',
          ],
        ],
        'content' => [
          '#markup' => $fallback,
        ],
      ],
      'move' => [
        '#url' => Url::fromRoute('layout_builder.move_block_form', $params),
      ],
      'configure' => [
        '#url' => Url::fromRoute('layout_builder.update_block', $params),
      ],
      'remove' => [
        '#url' => Url::fromRoute('layout_builder.remove_block', $params),
      ],
    ];
    foreach ([
      'move',
      'configure',
      'remove',
    ] as $key) {
      $title = $this
        ->t('@title @block', [
        '@title' => ucwords($key),
        '@block' => $fallback,
      ]);
      $links[$key]['#type'] = 'link';
      $links[$key]['#title'] = [
        '#markup' => '<span class="visually-hidden">' . $title . '</span>',
        '#allowed_tags' => [
          'span',
        ],
      ];
      $links[$key]['#attributes'] = [
        'class' => [
          'use-ajax',
          'layout-builder__link',
          'layout-builder__link--' . $key,
        ],
        'data-dialog-type' => 'dialog',
        'data-dialog-renderer' => 'off_canvas',
      ];
    }
    return $links;
  }

  /**
   * Returns the AJAX CRUD links for layout variants.
   */
  public function getVariantLinks(array $settings, $optionset, $reload = FALSE) {
    if (empty($settings['gid']) || empty($settings['optionset'])) {
      return [];
    }
    $links = [];
    $name = $settings['optionset'];
    $vid = isset($settings['vid']) ? $settings['vid'] : '';
    $dup = isset($settings['dup']) ? $settings['dup'] : '';
    $gid = GridStackDefault::gid($settings['gid']);
    $params = [
      'gridstack' => $name,
      'gid' => $gid,
    ];
    $options = [];
    $variant = $vid ? GridStackVariant::load($vid) : NULL;
    $pub = empty($settings['pub']) ? NULL : $settings['pub'];
    $params['gridstack_variant'] = $vid;
    if (!empty($settings['_variant']) && $vid) {
      $pub = $vid;
    }
    if ($dup) {
      $params['dup'] = $dup;
    }
    if ($pub) {
      $params['pub'] = $pub;
    }
    foreach ([
      'add',
      'cancel',
      'delete',
      'duplicate',
      'edit',
      'select',
    ] as $key) {
      $title = $key == 'select' ? 'variants' : $key . ' variant';
      $title = $key == 'cancel' ? $key : $title;
      $links[$key]['#type'] = 'link';
      $links[$key]['#title'] = $this
        ->t('@title', [
        '@title' => ucwords($title),
      ]);
      $links[$key]['#attributes']['class'] = [
        'use-ajax',
        'btn',
        'btn-primary',
        'btn--editor',
        'btn--editor-' . $key,
      ];
    }
    if (empty($variant)) {
      $links['cancel'] = [];
      $links['delete'] = [];
      $links['duplicate'] = [];
      $links['edit'] = [];
      if ($vid) {
        $links['add']['#url'] = new Url('entity.gridstack_variant.ajax_add_form', $params, $options);
      }
    }
    else {

      // @todo $links['add']['#url'] = new Url('entity.gridstack_variant.ajax_add_form', $params, $options);
      $options = [
        'language' => NULL,
        'entity_type' => 'gridstack_variant',
        'entity' => $variant,
      ];
      $params['gridstack_variant'] = $params['vid'] = $variant
        ->id();
      $links['duplicate']['#url'] = new Url('entity.gridstack_variant.ajax_edit_form', $params, $options);
      unset($params['dup'], $params['vid']);
      $links['delete']['#url'] = new Url('entity.gridstack_variant.ajax_delete_form', $params, $options);
      $links['edit']['#url'] = new Url('entity.gridstack_variant.ajax_edit_form', $params, $options);
      $params_cancel = $params;
      $params_cancel['vid'] = $vid;
      unset($params_cancel['gridstack_variant']);
      $links['cancel']['#url'] = new Url('entity.gridstack_variant.ajax_cancel_form', $params_cancel, $options);
    }
    if ($this
      ->getVariants($name, $reload)) {
      $options = [
        'language' => NULL,
        'entity_type' => 'gridstack',
        'entity' => GridStack::loadWithFallback($name),
      ];
      $params['vid'] = $vid;
      unset($params['gridstack_variant']);
      $links['select']['#url'] = new Url('entity.gridstack_variant.ajax_selection_form', $params, $options);
    }
    else {
      $links['select'] = [];
    }
    return $links;
  }

  /**
   * Returns the AJAX CRUD container for layout variants.
   */
  public function getVariantEditor(array $settings, $optionset, $reload = FALSE) {
    $exists = $this->manager
      ->getModuleHandler()
      ->moduleExists('gridstack_ui');
    $access = $this->currentUser
      ->hasPermission('modify gridstack variant');
    if (!$exists || !$access) {
      return [];
    }

    // Bail out if no unique ID per layout, to support multiple similar layouts.
    if (empty($settings['gid'])) {
      return [];
    }
    $links = $this
      ->getVariantLinks($settings, $optionset, $reload);
    $pos = $this
      ->config('editor_pos') == 'bottom' ? 'bottom' : 'top';
    return $links ? [
      '#type' => 'container',
      '#attributes' => [
        'class' => [
          'gridstack-editor',
          'gridstack-editor--' . $pos,
        ],
      ],
      '#attached' => [
        'library' => [
          'core/drupal.ajax',
          'gridstack/admin',
        ],
      ],
      'form' => [
        '#type' => 'container',
        '#attributes' => [
          'id' => GridStackDefault::variantWrapperId($settings['gid']),
          'class' => [
            'gridstack-editor__form',
            'clearfix',
          ],
          'data-gs-gid' => GridStackDefault::gid($settings['gid']),
        ],
        'add' => $links['add'],
        'edit' => $links['edit'],
        'select' => $links['select'],
      ],
    ] : [];
  }

  /**
   * Returns the available variants.
   */
  public function getVariants($source, $reload = FALSE) {
    if (!isset($this->variantOptions[$source]) || $reload) {
      $options = [];
      foreach ($this->manager
        ->entityLoadMultiple('gridstack_variant') as $key => $entity) {
        if ($entity
          ->source() != $source) {
          continue;
        }
        $options[$key] = Html::escape($entity
          ->label());
      }
      asort($options);
      $this->variantOptions[$source] = $options;
    }
    return $this->variantOptions[$source];
  }

}

Classes

Namesort descending Description
Builder Provides Layout Builder integration for editor previews.