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'); } 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 value 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;
    }
}