file = $file; $this->parameters = $parameters; $this->width = $parameters['width']; $this->height = $parameters['height']; $this->view = (isset($parameters['view'])) ? $parameters['view'] : 'default'; $this->hsset = (isset($parameters['hsset'])) ? $parameters['hsset'] : 'all'; $this->url = $file->getFullUrl(); $this->thumb = isset($parameters['isFilePageThumb']) && $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 * preview Boolean, render is being called from a preview page * * @return string HTML */ public function toHtml($options = []) { if ($this->thumb) { $mainConfig = ConfigFactory::getDefaultInstance()->makeConfig( 'main' ); $thumbUrl = $mainConfig->get( 'ExtensionAssetsPath' ) . '/GlModelViewer/resources/model_thumb.svg'; $attrMenuImg = array ( 'src' => $thumbUrl, 'style' => 'width: ' . $this->width . 'px; height: ' . $this->height . 'px;' ); return Html::rawElement('img', $attrMenuImg, ''); } 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);
        $hsSet = (isset($metadata['annotationSets']) && isset($metadata['annotationSets'][$this->hsset])) ? $this->hsset : 'default';
        $view = (isset($metadata['viewerConfig']) && isset($metadata['viewerConfig'][$this->view])) ? $this->view : 'default';

        //Handle annotations and annotation sets
        if (isset($metadata['annotations'])) {
            $hotspots = [];
            $annotations = [];
            if ($hsSet != 'default') {
                foreach($metadata['annotationSets'][$hsSet] 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 awaiting-model',
                'style' => 'display: none;',
                '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
        $interactButton = '';
        if (isset($metadata['viewerConfig']) && isset($metadata['viewerConfig'][$view])) {
            $attrModelView = $metadata['viewerConfig'][$view];
            if (isset($attrModelView['poster'])) {
                $posterFile = MediaWikiServices::getInstance()->getRepoGroup()->findFile(`File:` . $attrModelView['poster']);
                if ($posterFile) {
                    $attrModelView['poster'] = $posterFile->getFullUrl();
                    $attrModelView['reveal'] = 'manual';
                    $mainConfig = ConfigFactory::getDefaultInstance()->makeConfig( 'main' );
                    $interactUrl = $mainConfig->get( 'ExtensionAssetsPath' ) . '/GlModelViewer/resources/interact.svg';
                    $attrInteractButton = array (
                        'class' => 'glmv-act-button',
                        'onclick' => 'this.style.display = "none"; this.parentElement.querySelector("model-viewer").dismissPoster()',
                    );
                    $interactButton = Html::rawElement('div', $attrInteractButton, '');
                }
            }
        } 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%;';
        $attrModelView['onload'] = 'modelLoaded()';
        $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()',
            'onfullscreenchange' => 'toggleFullScreen(event)'
        );

        return Html::rawElement('div', $attrContainer, $elModel . $elMenu . $interactButton);
    }

    /**
     * Build the button menu used for viewer actions
     * 
     * @return string
     */
    private static function buildViewMenu() {
        $attrMenu = array(
            'class' => 'glmv-menu awaiting-model',
            'style' => 'display: none;'
        );
        $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 disable-on-hide',
            'onclick' => 'prevAnnotation(event.target.closest(".glmv-container").querySelector("model-viewer"))',
            'onmousedown' => 'event.stopPropagation()',
            'ontouchstart' => 'event.stopPropagation()'
        );
        $attrMenuButtonNext = array (
            'class' => 'glmv-menu-button next-hs disable-on-hide',
            '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 disable-on-hide',
            '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()'
        );

        $screenDownUrl = $mainConfig->get( 'ExtensionAssetsPath' ) . '/GlModelViewer/resources/full_close.svg';
        $screenUpUrl = $mainConfig->get( 'ExtensionAssetsPath' ) . '/GlModelViewer/resources/full_open.svg';
        $attrMenuButtonScreen = array (
            'class' => 'glmv-menu-button',
            'onclick' => 'toggleFullScreen(this.parentNode.parentNode)',
            'onmousedown' => 'event.stopPropagation()',
            'ontouchstart' => 'event.stopPropagation()'
        );

        $menuButtons = array(
            Html::rawElement('div', $attrMenuButtonPrev, ''),
            Html::rawElement('div', $attrMenuButtonSlides, ''),
            Html::rawElement('div', $attrMenuButtonNext, ''),
            Html::rawElement('div', $attrMenuButtonHide, ''),
            Html::rawElement('div', $attrMenuButtonScreen, '')
        );

        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;
    }
}