countdown-8.x-1.8/src/Element/CountdownPreview.php

src/Element/CountdownPreview.php
<?php

namespace Drupal\countdown\Element;

/**
 * @file
 * Contains the CountdownPreview render element.
 */

use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element\RenderElement;

/**
 * Provides a countdown preview render element.
 *
 * This element creates a live preview of the selected countdown library with
 * all its configurations and extensions. It handles all preview logic
 * internally without needing external adapters or renderers.
 *
 * @RenderElement("countdown_preview")
 */
class CountdownPreview extends RenderElement {

  /**
   * {@inheritdoc}
   */
  public function getInfo() {
    $class = get_class($this);
    return [
      '#pre_render' => [
        [$class, 'preRenderCountdownPreview'],
      ],
      '#theme' => 'countdown_preview',
      '#attributes' => [],
      '#form_state' => NULL,
    ];
  }

  /**
   * Pre-render callback for the countdown preview element.
   *
   * @param array $element
   *   The render element.
   *
   * @return array
   *   The modified render element.
   */
  public static function preRenderCountdownPreview(array $element) {
    // Ensure ID is set.
    if (empty($element['#attributes']['id'])) {
      $element['#attributes']['id'] = 'countdown-preview-wrapper';
    }

    // Get form state if available.
    $form_state = $element['#form_state'] ?? NULL;
    if (!$form_state) {
      $element['#countdown_markup'] = [
        '#markup' => '<div class="countdown-preview-empty">' . t('No preview available') . '</div>',
      ];
      return $element;
    }

    // Get configuration and plugin manager.
    $config = \Drupal::config('countdown.settings');
    $plugin_manager = \Drupal::service('plugin.manager.countdown_library');

    // Extract current values from form state.
    $library = $form_state->getValue('library');
    if (!$library) {
      $library = $config->get('library');
    }

    // If no library or "none", show placeholder.
    if (!$library || $library === 'none') {
      $element['#countdown_markup'] = [
        '#markup' => '<div class="countdown-preview-placeholder">' . t('Select a countdown library to see the preview.') . '</div>',
      ];
      return $element;
    }

    // Get the plugin instance.
    try {
      $plugin = $plugin_manager->createInstance($library);
    }
    catch (\Exception $e) {
      $element['#countdown_markup'] = [
        '#markup' => '<div class="countdown-preview-error">' . t('Selected library plugin was not found.') . '</div>',
      ];
      return $element;
    }

    // Build preview configuration from form state.
    $preview_config = self::buildPreviewConfig($form_state, $config, $plugin);

    // Build the countdown markup.
    $target_date = date('Y-m-d H:i:s', strtotime('+1 day'));
    $data_attributes = [
      'class' => ['countdown-timer'],
      'data-countdown-target' => $target_date,
      'data-countdown-format' => 'dhms',
    ];

    // Add library-specific data attributes.
    if ($library === 'tick' && !empty($preview_config['extensions'])) {
      // Find the active view extension.
      $view = 'text';
      foreach ($preview_config['extensions'] as $ext) {
        if (strpos($ext, 'view_') === 0) {
          $view = substr($ext, 5);
        }
      }
      $data_attributes['data-tick-view'] = $view;
    }

    $element['#countdown_markup'] = [
      '#type' => 'html_tag',
      '#tag' => 'div',
      '#attributes' => $data_attributes,
    ];

    $element['#countdown_loading'] = [
      '#markup' => '<div class="countdown-preview-loading">' . t('Loading preview...') . '</div>',
      '#weight' => -1,
    ];

    // Build attachments using the plugin's buildAttachments method.
    $attachments = $plugin->buildAttachments($preview_config);

    // Apply the attachments to the element.
    if (!empty($attachments['#attached'])) {
      $element['#attached'] = $attachments['#attached'];
    }

    // For plugins with extensions, ensure extension libraries are loaded.
    if ($plugin->hasExtensions() && !empty($preview_config['extensions'])) {
      self::attachExtensionLibraries($element, $plugin, $preview_config);
    }

    // Add admin styles.
    $element['#attached']['library'][] = 'countdown/admin';

    // Add cleanup behavior.
    $element['#attached']['library'][] = 'countdown/preview-cleanup';

    // Add debug info if enabled.
    if ($preview_config['debug']) {
      $element['#debug_info'] = [
        'library' => $library,
        'method' => $preview_config['method'],
        'extensions' => $preview_config['extensions'] ?? [],
        'timestamp' => time(),
      ];

      \Drupal::logger('countdown')->debug('Preview attached for @lib via @method with extensions: @ext', [
        '@lib' => $library,
        '@method' => $preview_config['method'],
        '@ext' => implode(', ', $preview_config['extensions'] ?? []),
      ]);
    }

    return $element;
  }

  /**
   * Builds preview configuration from form state.
   *
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   * @param \Drupal\Core\Config\ImmutableConfig $config
   *   The configuration object.
   * @param \Drupal\countdown\Plugin\CountdownLibraryPluginInterface $plugin
   *   The plugin instance.
   *
   * @return array
   *   The preview configuration array.
   */
  protected static function buildPreviewConfig(FormStateInterface $form_state, $config, $plugin): array {
    // Extract variant value and convert to boolean.
    $variant_value = $form_state->getValue('variant');
    if ($variant_value === NULL) {
      $variant_value = $config->get('build.variant');
    }

    // Build the configuration array.
    $preview_config = [
      'method' => $form_state->getValue('method') ?: $config->get('method') ?: 'local',
      'variant' => (bool) $variant_value,
      'cdn_provider' => $form_state->getValue('cdn_provider') ?: $config->get('cdn.provider') ?: 'jsdelivr',
      'rtl' => (bool) ($form_state->getValue('rtl') ?? $config->get('rtl')),
      'debug' => (bool) ($form_state->getValue('debug_mode') ?? $config->get('debug_mode')),
      'extensions' => [],
    ];

    // Get extensions if the plugin supports them.
    if ($plugin->hasExtensions()) {
      $preview_config['extensions'] = self::resolveActiveExtensions($form_state, $plugin, $config);
    }

    return $preview_config;
  }

  /**
   * Resolves active extensions from form state.
   *
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   * @param \Drupal\countdown\Plugin\CountdownLibraryPluginInterface $plugin
   *   The plugin instance.
   * @param \Drupal\Core\Config\ImmutableConfig $config
   *   The configuration object.
   *
   * @return array
   *   Array of active extension IDs.
   */
  protected static function resolveActiveExtensions(FormStateInterface $form_state, $plugin, $config): array {
    $library_id = $plugin->getPluginId();
    $active_extensions = [];

    // Check triggering element for extension selection.
    $trigger = $form_state->getTriggeringElement();
    if ($trigger && !empty($trigger['#parents'])) {
      $last = end($trigger['#parents']);

      // If it's an extension that was triggered, handle it.
      $available = $plugin->getAvailableExtensions();
      if (isset($available[$last])) {
        // For view extensions, only keep one active.
        if (strpos($last, 'view_') === 0) {
          // Clear other view extensions.
          foreach ($available as $ext_id => $ext_info) {
            if (strpos($ext_id, 'view_') === 0 && $ext_id !== $last) {
              // Skip this view extension.
              continue;
            }
            $input = $form_state->getUserInput();
            if (!empty($input[$ext_id])) {
              $active_extensions[] = $ext_id;
            }
          }
        }
        else {
          // For non-view extensions, just check all inputs.
          $input = $form_state->getUserInput();
          foreach ($available as $ext_id => $ext_info) {
            if (!empty($input[$ext_id])) {
              $active_extensions[] = $ext_id;
            }
          }
        }
      }
    }

    // If no extensions from triggering element, check all user input.
    if (empty($active_extensions)) {
      $input = $form_state->getUserInput();
      if (is_array($input)) {
        $available = $plugin->getAvailableExtensions();
        $view_found = FALSE;

        foreach ($available as $ext_id => $ext_info) {
          if (!empty($input[$ext_id])) {
            // For view extensions, only keep the last one.
            if (strpos($ext_id, 'view_') === 0) {
              if (!$view_found) {
                $active_extensions[] = $ext_id;
                $view_found = TRUE;
              }
            }
            else {
              // Non-view extensions can all be active.
              $active_extensions[] = $ext_id;
            }
          }
        }
      }
    }

    // Fall back to saved extensions if nothing selected.
    if (empty($active_extensions)) {
      $saved_extensions = $config->get("file_assets.extensions.{$library_id}") ?: [];
      if (!empty($saved_extensions)) {
        $active_extensions = $saved_extensions;
      }
    }

    return $active_extensions;
  }

  /**
   * Attaches extension libraries for plugins that support them.
   *
   * @param array &$element
   *   The render element to attach libraries to.
   * @param \Drupal\countdown\Plugin\CountdownLibraryPluginInterface $plugin
   *   The plugin instance.
   * @param array $preview_config
   *   The preview configuration.
   */
  protected static function attachExtensionLibraries(array &$element, $plugin, array $preview_config): void {
    $library_id = $plugin->getPluginId();

    // Map extension IDs to library names based on plugin conventions.
    foreach ($preview_config['extensions'] as $ext_id) {
      // Build the library name for this extension.
      $lib_name = 'countdown/' . $library_id . '_';

      // Handle different extension naming patterns.
      if (strpos($ext_id, 'view_') === 0) {
        // View extensions: view_dots -> tick_dots.
        $lib_name .= substr($ext_id, 5);
      }
      elseif (strpos($ext_id, 'font_') === 0) {
        // Font extensions: font_highres -> tick_font_highres.
        $lib_name .= 'font_' . substr($ext_id, 5);
      }
      else {
        // Direct mapping for other extensions.
        $lib_name .= $ext_id;
      }

      // Add CDN suffix if using CDN method.
      if ($preview_config['method'] === 'cdn') {
        $lib_name .= '_cdn';
      }

      // Add minified suffix if using production variant.
      if ($preview_config['variant']) {
        $lib_name .= '.min';
      }

      // Check if library exists before attaching.
      $library_discovery = \Drupal::service('library.discovery');
      $library_parts = explode('/', $lib_name);
      if (count($library_parts) === 2) {
        $library_definition = $library_discovery->getLibraryByName($library_parts[0], $library_parts[1]);
        if ($library_definition) {
          $element['#attached']['library'][] = $lib_name;
        }
      }
    }

    // Special handling for specific library requirements.
    if ($library_id === 'tick') {
      // Dots view requires font_highres.
      if (in_array('view_dots', $preview_config['extensions'])) {
        $font_lib = 'countdown/tick_font_highres';
        if ($preview_config['method'] === 'cdn') {
          $font_lib .= '_cdn';
        }
        if ($preview_config['variant']) {
          $font_lib .= '.min';
        }
        $element['#attached']['library'][] = $font_lib;
      }
    }

    // Ensure extensions are passed to drupalSettings.
    if (!isset($element['#attached']['drupalSettings'])) {
      $element['#attached']['drupalSettings'] = [];
    }
    if (!isset($element['#attached']['drupalSettings']['countdown'])) {
      $element['#attached']['drupalSettings']['countdown'] = [];
    }

    // Pass extensions to the library namespace.
    $element['#attached']['drupalSettings']['countdown'][$library_id] = [
      'extensions' => $preview_config['extensions'],
      'debug' => $preview_config['debug'],
    ];
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc