file = $file; $this->parameters = $parameters; $this->width = (isset($parameters['width'])) ? $parameters['width'] : '800'; $this->height = (isset($parameters['height'])) ? $parameters['height'] : (int)($this->width * .75); $this->url = $file->getFullUrl(); $this->thumb = isset($parameters['isFilePageThumb']); } /** * Fetch HTML for this transform output * * @access public * @param array $options Associative array of options. Boolean options * should be indicated with a value of true for true, and false or * absent for false. * alt Alternate text or caption * desc-link Boolean, show a description link * file-link Boolean, show a file download link * custom-url-link Custom URL to link to * custom-title-link Custom Title object to link to * valign vertical-align property, if the output is an inline element * img-class Class applied to the "" tag, if there is such a tag * * @return string HTML */ public function toHtml($options = []) { if ($this->thumb) { return Html::rawElement('span',['class' => 'fake-thumbnail'],'Gltf model thumbnail'); } //return Html::rawElement( 'div',[ 'class' => 'my-model-class'], $this->url . ' is a ' . $this->parameters['type'] . ' 3D model!' ); if (isset($options['img-class'])) { $this->parameters['class'] = $options['img-class']; } if (isset($options['preview']) && $options['preview']) { $this->parameters['preview'] = $options['preview']; } return self::buildViewer($this->file->getDescriptionText(),$this->url,$this->parameters); } /** * Build model-viewer and child elements * * This takes in the metadata text from the model page (or the current editor) * and produces the html string for the model-viewer and all relevant child * elements. * * @param string $inText The metadata text which must include a json formatted string inside a pre tag * @param string $srcUrl The full url pointing to the model file * @param array $frameParams The additional user defined parameters for the viewer such as hotspot and view classes * @return string Html string of the complete model-viewer element inside a div container */ private function buildViewer($inText, $srcUrl, $viewParams) { //Gather basic data preg_match('/
([\S\s]*?)<\/pre>/',$inText,$modelDescript);
    $metadata = json_decode($modelDescript[1], true);
    $viewClass = self::extractClassParams('view',$viewParams['class']);
    $hsSetClass = self::extractClassParams('hsset',$viewParams['class']);

    //Handle annotations and annotation sets
    if (isset($metadata['annotations'])) {
      $hotspots = [];
      $annotations = [];
      if ($hsSetClass != 'default' && isset($metadata['annotationSets'])) {
        foreach($metadata['annotationSets'][$hsSetClass] as $includeAn) {
          if (isset($metadata['annotations'][$includeAn])) {
            $annotations[$includeAn] = $metadata['annotations'][$includeAn];
          }
        }
      } else {
        $annotations = $metadata['annotations'];
      }
        foreach($annotations as $label => $an) {
        $elAnnot = Html::rawElement('div',['class' => 'HotspotAnnotation HiddenAnnotation'],$label);
        $hsDefault = array(
          'class' => 'Hotspot',
          'slot' => 'hotspot-'.(count($hotspots) +1),
          'ontouchstart' => 'event.stopPropagation()',
          'onclick' => 'onAnnotation(event)'
        );
        if (isset($viewParams['preview'])) {
            $hsDefault['onmousedown'] = 'grabAnnotation(event)';
            $hsDefault['onmouseup'] = 'releaseAnnotation(event)';
        }
        $attrHotspot = array_merge($hsDefault, $an);
        $elHotspot = Html::rawElement('button',$attrHotspot,$elAnnot.(count($hotspots) +1));
        array_push($hotspots, $elHotspot);
      }
    }

    //Set viewer configurations or use basic default if none defined
    if (isset($metadata['viewerConfig']) && isset($metadata['viewerConfig'][$viewClass])) {
      $attrModelView = $metadata['viewerConfig'][$viewClass];
    } else {
      $attrModelView = array('camera-controls' => true);
    }

    //Add important additional attributes and render model-viewer with hotspots
    $attrModelView = array_merge(['src' => $srcUrl, 'class' => 'mv-model', 'interpolation-decay' => '100', 'interaction-prompt' => 'none'], $attrModelView);
    $attrModelView['style'] = 'width: 100%; height: 100%;';
    $hotspotHtml = (isset($hotspots)) ? implode($hotspots) : '';
 
    $elModel = Html::rawElement('model-viewer', $attrModelView, $hotspotHtml);
    $elMenu = self::buildViewMenu();

    //Render and return container element with model-viewer
    $attrContainer = array(
        'class' => 'glmv-container',
        'style' => 'width: ' .  $this->width . 'px; height: ' . $this->height . 'px;',
        'onmousedown' => 'clearAnnotations()',
        'ontouchstart' => 'clearAnnotations()'
    );
    return Html::rawElement('div', $attrContainer, $elModel . $elMenu);
  }

  /**
   * Detect and return model user params from link classes
   * 
   * The best way to send information in a file link is by adding classes to the
   * link. This function parses the array of those classes and returns the information
   * or 'default'
   * 
   * @param string $paramType the type of parameter to be extracted 'view'|'hsset'
   * @param string $classList a string of class names
   * @return string parameter name or 'default'
   */
  private static function extractClassParams(string $paramType, ? string $classList) {
    if (!in_array($paramType, ['view', 'hsset'])) {
      return 'default';
    }
    
    if (!isset($classList)) {
      return 'default';
    }

    $searchString = '/' . $paramType . '-(\S*)/';
    preg_match($searchString,$classList,$extractInfo);

    return ($extractInfo[1]) ? $extractInfo[1] : 'default';
  }

  /**
   * Build the button menu used for viewer actions
   * 
   * @return string
   */
  private static function buildViewMenu() {
    $attrMenu = array(
        'class' => 'glmv-menu'
    );
    $mainConfig = ConfigFactory::getDefaultInstance()->makeConfig( 'main' );
    $menuUrl = $mainConfig->get( 'ExtensionAssetsPath' ) . '/GlModelViewer/resources/menu_arrow.svg';
    $attrMenuImg = array (
        'class' => 'glmv-menu-image',
        'src' => $menuUrl
  );
    $menuImg = Html::rawElement('img', $attrMenuImg, '');

    $gotoUrl = $mainConfig->get( 'ExtensionAssetsPath' ) . '/GlModelViewer/resources/goto_hs.svg';
    $attrMenuButtonPrev = array (
        'class' => 'glmv-menu-button prev-hs',
        'onclick' => 'prevAnnotation(event.target.closest(".glmv-container").querySelector("model-viewer"))',
        'onmousedown' => 'event.stopPropagation()',
        'ontouchstart' => 'event.stopPropagation()'
    );
    $attrMenuButtonNext = array (
        'class' => 'glmv-menu-button next-hs',
        'onclick' => 'nextAnnotation(event.target.closest(".glmv-container").querySelector("model-viewer"))',
        'onmousedown' => 'event.stopPropagation()',
        'ontouchstart' => 'event.stopPropagation()'
    );

    $slideUrl = $mainConfig->get( 'ExtensionAssetsPath' ) . '/GlModelViewer/resources/hs_slideshow.svg';
    $attrMenuButtonSlides = array (
        'class' => 'glmv-menu-button',
        'onclick' => 'event.target.toggleAttribute("toggled"); slideshowAnnotations(event.target.closest(".glmv-container").querySelector("model-viewer"))',
        'onmousedown' => 'event.stopPropagation()',
        'ontouchstart' => 'event.stopPropagation()'
    );

    $hideUrl = $mainConfig->get( 'ExtensionAssetsPath' ) . '/GlModelViewer/resources/hs_hide.svg';
    $attrMenuButtonHide = array (
        'class' => 'glmv-menu-button',
        'onclick' => 'event.target.toggleAttribute("toggled"); toggleAnnotations(event.target.closest(".glmv-container").querySelector("model-viewer"))',
        'onmousedown' => 'event.stopPropagation()',
        'ontouchstart' => 'event.stopPropagation()'
    );
    $menuButtons = array(
        Html::rawElement('div', $attrMenuButtonPrev, ''),
        Html::rawElement('div', $attrMenuButtonSlides, ''),
        Html::rawElement('div', $attrMenuButtonNext, ''),
        Html::rawElement('div', $attrMenuButtonHide, '')
    );

    return Html::rawElement('div', $attrMenu, $menuImg . implode($menuButtons));
  }

  /**
   * Small helper function to display information on the browser console
   * 
   * Usage:
   * echo '';
   * 
   * @param $data information to display
   * @param bool $add_script_tags true to put information is inside complete script tag
   */
  public static function console_log($data, $add_script_tags = false) {
    $command = 'console.log('. json_encode($data, JSON_HEX_TAG).');';
    if ($add_script_tags) {
        $command = '';
    }
    echo $command;
  }
}