countdown-8.x-1.8/js/integrations/countdown.tick.integration.js

js/integrations/countdown.tick.integration.js
/**
 * @file
 * Integration for PQINA Tick countdown library.
 *
 * This file provides the integration layer between Drupal and the Tick
 * countdown library, handling initialization, configuration, and view management.
 */

(function (Drupal) {
  'use strict';

  /**
   * Initialize a PQINA Tick timer.
   *
   * @param {Element} element
   *   The DOM element to initialize as a timer.
   * @param {Object} settings
   *   The settings object from drupalSettings.
   */
  function initializeTick(element, settings) {
    // Validate library availability.
    if (typeof Tick === 'undefined' || !Tick || !Tick.DOM) {
      Drupal.countdown.utils.handleError(element, 'Tick library not loaded or DOM not available', 'tick');
      return;
    }

    // Resolve settings using shared utility.
    const config = Drupal.countdown.utils.resolveCountdownSettings(element, settings, 'tick');

    // Extract target date and timezone.
    const targetDate = config.target_date || element.dataset.countdownTarget;
    const timezone = config.timezone || element.dataset.countdownTimezone || 'UTC';

    if (!targetDate) {
      Drupal.countdown.utils.handleError(element, 'No target date specified', 'tick');
      return;
    }

    // Check if already expired.
    if (Drupal.countdown.utils.isExpired(targetDate, timezone)) {
      Drupal.countdown.utils.showExpiredMessage(element, config, 'tick');
      return;
    }

    // Normalize boolean settings using shared utility.
    config.showLabels = Drupal.countdown.utils.normalizeBoolean(config.showLabels);
    config.showCredits = Drupal.countdown.utils.normalizeBoolean(config.showCredits);
    config.cascade = Drupal.countdown.utils.normalizeBoolean(config.cascade);
    config.server_sync = Drupal.countdown.utils.normalizeBoolean(config.server_sync);
    config.autostart = Drupal.countdown.utils.normalizeBoolean(config.autostart);
    config.line_flip = Drupal.countdown.utils.normalizeBoolean(config.line_flip);
    config.enable_transforms = Drupal.countdown.utils.normalizeBoolean(config.enable_transforms);
    config.debug_mode = Drupal.countdown.utils.normalizeBoolean(config.debug_mode);

    // Normalize numeric settings using shared utility.
    config.update_interval = Drupal.countdown.utils.normalizeNumber(config.update_interval, 1000);
    config.interval = Drupal.countdown.utils.normalizeNumber(config.interval, 1000);
    config.volume = Drupal.countdown.utils.normalizeNumber(config.volume, 1);
    config.dot_update_delay = Drupal.countdown.utils.normalizeNumber(config.dot_update_delay, 0);

    // Determine the view type to use.
    const viewType = config.view_type || config.view || 'text';

    // Determine format from preset or custom configuration.
    let format = processTickFormat(config);

    // Build Tick markup structure based on view type.
    const markup = buildTickMarkup(viewType, format, config, settings);
    element.innerHTML = markup;

    // Find the tick root element.
    const tickRoot = element.querySelector('.tick');
    if (!tickRoot) {
      Drupal.countdown.utils.handleError(element, 'Failed to create Tick markup', 'tick');
      return;
    }

    // Apply custom CSS class if provided.
    if (config.custom_class) {
      tickRoot.classList.add(config.custom_class);
    }

    // Apply layout class.
    if (config.layout) {
      tickRoot.classList.add('tick-layout-' + config.layout);
    }

    // Apply size class if specified.
    if (config.size) {
      tickRoot.classList.add('tick-size-' + config.size);
    }

    // Set up the init handler for Tick-specific operations.
    window.handleTickInit = function (tick) {
      // Remove credits if configured.
      if (config.showCredits === false) {
        const creditsEl = tick.root.querySelector('.tick-credits');
        if (creditsEl) {
          creditsEl.remove();
        }
      }

      // Store reference for debugging.
      if (config.debug_mode === true) {
        console.log('Tick initialized via callback for:', tick.root.parentElement);
      }
    };

    // Initialize Tick DOM.
    let tickDom;
    try {
      tickDom = Tick.DOM.create(tickRoot);
    }
    catch (error) {
      // Fall back to text view if requested view fails.
      console.warn('Countdown: Tick view "' + viewType + '" not available, falling back to text.', error);

      // Rebuild with text view.
      const textMarkup = buildTickMarkup('text', format, config, settings);
      element.innerHTML = textMarkup;
      const newTickRoot = element.querySelector('.tick');

      try {
        tickDom = Tick.DOM.create(newTickRoot);
      }
      catch (fallbackError) {
        Drupal.countdown.utils.handleError(element, 'Failed to initialize Tick: ' + fallbackError.message, 'tick');
        return;
      }
    }

    // Create countdown configuration.
    const countConfig = {
      format: format,
      interval: config.interval || config.update_interval || 1000,
      cascade: config.cascade !== false,
      server: config.server_sync === true
    };

    // Determine countdown direction.
    const direction = config.direction || 'countdown';

    // Create countdown counter.
    const counter = direction === 'countdown'
      ? Tick.count.down(targetDate, countConfig)
      : Tick.count.up(targetDate, countConfig);

    // Handle counter updates.
    counter.onupdate = function (value) {
      tickDom.value = value;

      // Dispatch tick event.
      Drupal.countdown.utils.dispatchEvent(element, 'tick', {
        element: element,
        library: 'tick',
        value: value,
        format: format,
        view: viewType
      });
    };

    // Handle countdown completion.
    counter.onended = function () {
      Drupal.countdown.utils.showExpiredMessage(element, config, 'tick');

      // Execute callback if provided.
      if (typeof config.onComplete === 'function') {
        config.onComplete.call(this, element);
      }
    };

    // Start the counter if autostart is enabled.
    if (config.autostart !== false) {
      if (counter.timer && typeof counter.timer.start === 'function') {
        counter.timer.start();
      }
    }

    // Store instance for cleanup.
    Drupal.countdown.storeInstance(element, {
      counter: counter,
      dom: tickDom,
      view: viewType,
      settings: config,
      stop: function () {
        if (counter && counter.stop) {
          counter.stop();
        }
        if (tickDom && tickDom.destroy) {
          tickDom.destroy();
        }
      }
    });

    // Mark as initialized.
    element.classList.add('countdown-initialized');
    element.classList.add('countdown-tick');
    element.classList.add('countdown-tick-' + viewType);
    element.setAttribute('data-tick-view-active', viewType);

    // Debug output if enabled.
    if (config.debug_mode === true) {
      console.log('Tick countdown initialized:', {
        element: element,
        view: viewType,
        format: format,
        settings: config
      });
    }

    // Dispatch initialization event.
    Drupal.countdown.utils.dispatchEvent(element, 'initialized', {
      library: 'tick',
      element: element,
      settings: config,
      view: viewType
    });
  }

  /**
   * Process Tick format configuration.
   *
   * Converts various format inputs to array format for Tick library.
   *
   * @param {Object} config
   *   The configuration object.
   *
   * @return {Array}
   *   The processed format array.
   */
  function processTickFormat(config) {
    let format = config.format || ['d', 'h', 'm', 's'];

    // Handle preset formats.
    if (config.preset && config.preset !== 'custom') {
      format = getPresetFormat(config.preset);
    }

    // Handle format from checkboxes (object format).
    if (typeof format === 'object' && !Array.isArray(format)) {
      const selectedFormats = [];
      const possibleFormats = ['y', 'M', 'w', 'd', 'h', 'm', 's'];
      possibleFormats.forEach(function (key) {
        if (format[key]) {
          selectedFormats.push(key);
        }
      });
      format = selectedFormats.length > 0 ? selectedFormats : ['d', 'h', 'm', 's'];
    }

    // Ensure format is an array.
    if (typeof format === 'string') {
      format = format.match(/[yMwdhms]/g) || ['d', 'h', 'm', 's'];
    }

    if (!Array.isArray(format)) {
      format = ['d', 'h', 'm', 's'];
    }

    return format;
  }

  /**
   * Build Tick markup structure based on view type and format.
   *
   * @param {string} viewType
   *   The view type (text, flip, line, dots, boom, swap, etc.).
   * @param {Array} format
   *   The format array.
   * @param {Object} config
   *   The configuration object.
   * @param {Object} settings
   *   The full drupalSettings object.
   *
   * @return {string}
   *   The HTML markup string.
   */
  function buildTickMarkup(viewType, format, config, settings) {
    const formatString = format.join(', ');

    // Start building markup.
    let markup = '<div class="tick"';

    // Add data attributes.
    markup += ' data-did-init="handleTickInit"';

    // Add credits configuration.
    if (config.show_credits === true || config.showCredits === true) {
      markup += ' data-credits="true"';
    } else {
      markup += ' data-credits="false"';
    }

    markup += '>';
    markup += '<div data-repeat="true"';
    markup += ' data-layout="horizontal center fit"';

    // Add transform configuration.
    let transform = 'preset(' + formatString + ')';
    if (config.enable_transforms === true && config.transform_chain) {
      transform = config.transform_chain;
    }
    transform += ' -> delay';
    markup += ' data-transform="' + transform + '">';

    markup += '<div class="tick-group">';
    markup += '<div data-key="value" data-repeat="true"';
    markup += ' data-transform="pad(00) -> split -> delay">';

    // Add the view element based on view type.
    if (viewType === 'boom' && config.sample_url) {
      // Boom view with custom audio.
      markup += '<span data-view="boom"';
      markup += ' data-style="sample: url(' + config.sample_url + ');';
      if (config.volume !== undefined) {
        markup += ' volume: ' + config.volume + ';';
      }
      markup += '"></span>';
    }
    else if (viewType === 'boom') {
      // Boom view with default audio.
      const modulePath = settings.countdown ? settings.countdown.modulePath : '';
      markup += '<span data-view="boom"';
      markup += ' data-style="sample: url(' + modulePath + '/media/bell.m4a), ';
      markup += 'url(' + modulePath + '/media/bell.ogg);"></span>';
    }
    else if (viewType === 'dots') {
      // Dots view with configuration.
      markup += '<span data-view="dots"';
      if (config.dot_color || config.dot_shape || config.dot_update_delay) {
        markup += ' data-style="';
        if (config.dot_color && config.dot_color !== 'auto') {
          markup += 'color: ' + config.dot_color + '; ';
        }
        if (config.dot_shape && config.dot_shape !== 'auto') {
          markup += 'shape: ' + config.dot_shape + '; ';
        }
        if (config.dot_update_delay) {
          markup += 'dotUpdateDelay: ' + config.dot_update_delay + '; ';
        }
        markup += '"';
      }
      markup += '></span>';
    }
    else if (viewType === 'line') {
      // Line view with configuration.
      markup += '<span data-view="line"';
      if (config.line_orientation || config.line_flip !== undefined ||
        config.fill_color || config.rail_color) {
        markup += ' data-style="';
        if (config.line_orientation) {
          markup += 'orientation: ' + config.line_orientation + '; ';
        }
        if (config.line_flip === true) {
          markup += 'flip: true; ';
        }
        if (config.fill_color) {
          markup += 'fillColor: ' + config.fill_color + '; ';
        }
        if (config.rail_color) {
          markup += 'railColor: ' + config.rail_color + '; ';
        }
        markup += '"';
      }
      markup += '></span>';
    }
    else if (viewType === 'swap') {
      // Swap view with configuration.
      markup += '<span data-view="swap"';
      if (config.transition_direction) {
        markup += ' data-style="transitionDirection: ' + config.transition_direction + ';"';
      }
      markup += '></span>';
    }
    else if (viewType === 'flip') {
      // Flip view for compatibility.
      markup += '<span data-view="flip"></span>';
    }
    else {
      // Default text view.
      markup += '<span data-view="text"></span>';
    }

    markup += '</div>';

    // Add labels if configured.
    if (config.showLabels === true) {
      markup += '<span data-key="label" data-view="text" class="tick-label"></span>';
    }

    markup += '</div>';
    markup += '</div>';
    markup += '</div>';

    return markup;
  }

  /**
   * Get preset format configuration.
   *
   * @param {string} preset
   *   The preset identifier.
   *
   * @return {Array}
   *   The format array.
   */
  function getPresetFormat(preset) {
    const formats = {
      'full': ['y', 'M', 'd', 'h', 'm', 's'],
      'extended': ['d', 'h', 'm', 's'],
      'simple': ['h', 'm', 's'],
      'minimal': ['m', 's'],
      'days_only': ['d'],
      'hours_only': ['h']
    };

    return formats[preset] || ['d', 'h', 'm', 's'];
  }

  // Register the loader with the main countdown system.
  Drupal.countdown.registerLoader('tick', initializeTick);

})(Drupal);

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

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