From 4f1ff3519427442dc290ac231a55a5610bbcf0e4 Mon Sep 17 00:00:00 2001 From: Justin Georgi Date: Tue, 29 Oct 2024 16:14:29 -0700 Subject: [PATCH] Tidy up PHP code Signed-off-by: Justin Georgi --- includes/GlModelHandler.php | 104 ++++----- includes/GlModelHooks.php | 222 ++++++++++--------- includes/GlModelTransformOutput.php | 333 ++++++++++++++-------------- 3 files changed, 332 insertions(+), 327 deletions(-) diff --git a/includes/GlModelHandler.php b/includes/GlModelHandler.php index 49b626d..472ef8a 100644 --- a/includes/GlModelHandler.php +++ b/includes/GlModelHandler.php @@ -5,19 +5,19 @@ use ImageHandler; use Html; class GlModelHandler extends ImageHandler { - /** - * Model cannot be displayed directly in a browser but can be rendered. - * - * @param File $file - * @return bool - */ - public function mustRender( $file ) { - return true; - } + /** + * Model cannot be displayed directly in a browser but can be rendered. + * + * @param File $file + * @return bool + */ + public function mustRender( $file ) { + return true; + } - /** - * Model can be rendered. - * + /** + * Model can be rendered. + * * @param File $file * @return bool */ @@ -25,53 +25,53 @@ class GlModelHandler extends ImageHandler { return true; } - /** - * Get a MediaTransformOutput object representing the transformed output. - * - * @param File $image - * @param string $dstPath Filesystem destination path - * @param string $dstUrl Destination URL to use in output HTML - * @param array $params Arbitrary set of parameters validated by $this->validateParam() - * @param int $flags A bitfield, may contain self::TRANSFORM_LATER - * @return MediaTransformOutput - */ - public function doTransform($image, $dstPath, $dstUrl, $params, $flags = 0) { - return new GlModelTransformOutput($image, $params); - } + /** + * Get a MediaTransformOutput object representing the transformed output. + * + * @param File $image + * @param string $dstPath Filesystem destination path + * @param string $dstUrl Destination URL to use in output HTML + * @param array $params Arbitrary set of parameters validated by $this->validateParam() + * @param int $flags A bitfield, may contain self::TRANSFORM_LATER + * @return MediaTransformOutput + */ + public function doTransform($image, $dstPath, $dstUrl, $params, $flags = 0) { + return new GlModelTransformOutput($image, $params); + } - /** - * Check the incoming media parameters - * + /** + * Check the incoming media parameters + * * @param string $name * @param string $value * @return bool */ public function validateParam( $name, $value ) { - if (in_array($name, ['width', 'height'])) { - return $value > 0; - } else if (in_array($name, ['view', 'hsset'])) { - return true; - } else { - return false; + if (in_array($name, ['width', 'height'])) { + return $value > 0; + } else if (in_array($name, ['view', 'hsset'])) { + return true; + } else { + return false; + } } - } - /** - * 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 = ''; + /** + * 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; } - echo $command; - } } \ No newline at end of file diff --git a/includes/GlModelHooks.php b/includes/GlModelHooks.php index 9c03f18..3df33c7 100644 --- a/includes/GlModelHooks.php +++ b/includes/GlModelHooks.php @@ -6,117 +6,119 @@ use Html; use ParserOutput; class GlModelHooks { - /** - * MWHook: Add gltf mime types to MimeMagic - * - * @see https://www.mediawiki.org/wiki/Manual:Hooks/MimeMagicInit - * - * @param MimeAnalyzer $mime Instance of MW MimeAnalyzer object - */ - public static function onMimeMagicInit($mime) { - $mime->addExtraTypes('model/gltf-binary glb gltf'); - $mime->addExtraInfo('model/gltf-binary [MULTIMEDIA]'); - } - - /** - * MWHook: Make sure that gltf files get the proper mime assignement on upload - * - * @see https://www.mediawiki.org/wiki/Manual:Hooks/MimeMagicImproveFromExtension - * - * @param MimeAnalyzer $mimeAnalyzer Instance of MW MimeAnalyzer object - * @param string $ext extention of upload file - * @param string &$mime current assigned mime type - */ - public static function onMimeMagicImproveFromExtension( $mimeAnalyzer, $ext, &$mime ) { - if ( $mime !== 'model/gltf-binary' && in_array( $ext, ['glb', 'gltf'] ) ) { - $mime = 'model/gltf-binary'; - } - } - - /** - * MWHook: Load the js and css modules if model-viewer element is found in the html output - * - * @see https://www.mediawiki.org/wiki/Manual:Hooks/BeforePageDisplay - * - * @param OutputPage $out compiled page html and manipulation methods - */ - public static function onBeforePageDisplay($out) { - preg_match('/(getHTML(),$findGltf); - if ($findGltf[0]) { - $mvScriptAttr = array( - 'src' => 'https://ajax.googleapis.com/ajax/libs/model-viewer/3.5.0/model-viewer.min.js', - 'type' => 'module' - ); - $out->addHeadItems(Html::rawElement('script',$mvScriptAttr)); - $out->addModules('ext.glmv'); - } - } - - /** - * MWHook: Display model on file page - * - * @see https://www.mediawiki.org/wiki/Manual:Hooks/ImageOpenShowImageInlineBefore - * - * @param ImagePage $imagepage information regarding the page for the image file - * @param OutputPage $out compiled page html and manipulation methods - */ - public static function onImageOpenShowImageInlineBefore( $imagepage, $out ){ - $file = $imagepage->getFile(); - if ($file->getMimeType() == 'model/gltf-binary') { - $out->clearHTML(); - $out->addWikiTextAsContent('[[' . $file->getTitle()->getFullText() . '|800x600px]]'); - } - } - - /** - * MWHook: Display model when model file page edits are previewed - * - * @see https://www.mediawiki.org/wiki/Manual:Hooks/AlternateEditPreview - * - * @param EditPage $editor access to information about the edit page itself - * @param Content $content access to the current content of the editor - * @param string &$previewHTML by reference access to the resultant compiled preview html - * @param ?ParserOutput &$parserOutput - * @return bool|void True to continue default processing or false to abort for custom processing - */ - public static function onAlternateEditPreview( $editor, $content, string &$previewHTML, ?ParserOutput &$parserOutput ) { - $file = MediaWikiServices::getInstance()->getRepoGroup()->findFile($editor->getTitle()); - if (!$file || $file->getMimeType() !== 'model/gltf-binary') { - return true; + /** + * MWHook: Add gltf mime types to MimeMagic + * + * @see https://www.mediawiki.org/wiki/Manual:Hooks/MimeMagicInit + * + * @param MimeAnalyzer $mime Instance of MW MimeAnalyzer object + */ + public static function onMimeMagicInit($mime) { + $mime->addExtraTypes('model/gltf-binary glb gltf'); + $mime->addExtraInfo('model/gltf-binary [MULTIMEDIA]'); } - $out = $editor->getContext()->getOutput(); - $out->addModules('ext.glmv'); - - $mvTransform = $file->transform([ 'width' => '800', 'hight' => '600']); - $previewViewer = $mvTransform->toHtml([ 'preview' => true]); - - $addButtonAttr = array( - 'class' => 'AddHotspot', - 'onclick' => 'readyAddHotspot()' - ); - $addHsButton = Html::rawElement('button',$addButtonAttr,'Add a new hotspot'); - - $previewHTML = Html::rawElement('div',NULL,$previewViewer.$addHsButton); - return false; - } - - /** - * 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 = ''; + /** + * MWHook: Make sure that gltf files get the proper mime assignement on upload + * + * @see https://www.mediawiki.org/wiki/Manual:Hooks/MimeMagicImproveFromExtension + * + * @param MimeAnalyzer $mimeAnalyzer Instance of MW MimeAnalyzer object + * @param string $ext extention of upload file + * @param string &$mime current assigned mime type + */ + public static function onMimeMagicImproveFromExtension( $mimeAnalyzer, $ext, &$mime ) { + if ( $mime !== 'model/gltf-binary' && in_array( $ext, ['glb', 'gltf'] ) ) { + $mime = 'model/gltf-binary'; + } + } + + /** + * MWHook: Load the js and css modules if model-viewer element is found in the html output + * + * @see https://www.mediawiki.org/wiki/Manual:Hooks/BeforePageDisplay + * + * @param OutputPage $out compiled page html and manipulation methods + */ + public static function onBeforePageDisplay($out) { + preg_match('/(getHTML(),$findGltf); + if ($findGltf[0]) { + $mvScriptAttr = array( + 'src' => 'https://ajax.googleapis.com/ajax/libs/model-viewer/3.5.0/model-viewer.min.js', + 'type' => 'module' + ); + $out->addHeadItems(Html::rawElement('script',$mvScriptAttr)); + $out->addModules('ext.glmv'); + } + } + + /** + * MWHook: Display model on file page + * + * @see https://www.mediawiki.org/wiki/Manual:Hooks/ImageOpenShowImageInlineBefore + * + * @param ImagePage $imagepage information regarding the page for the image file + * @param OutputPage $out compiled page html and manipulation methods + */ + public static function onImageOpenShowImageInlineBefore( $imagepage, $out ){ + $file = $imagepage->getFile(); + if ($file->getMimeType() == 'model/gltf-binary') { + $out->clearHTML(); + $out->addWikiTextAsContent('[[' . $file->getTitle()->getFullText() . '|800x600px]]'); + } + } + + /** + * MWHook: Display model when model file page edits are previewed + * + * @see https://www.mediawiki.org/wiki/Manual:Hooks/AlternateEditPreview + * + * @param EditPage $editor access to information about the edit page itself + * @param Content $content access to the current content of the editor + * @param string &$previewHTML by reference access to the resultant compiled preview html + * @param ?ParserOutput &$parserOutput + * @return bool|void True to continue default processing or false to abort for custom processing + */ + public static function onAlternateEditPreview( $editor, $content, string &$previewHTML, ?ParserOutput &$parserOutput ) { + $file = MediaWikiServices::getInstance()->getRepoGroup()->findFile($editor->getTitle()); + if (!$file || $file->getMimeType() !== 'model/gltf-binary') { + return true; + } + + $out = $editor->getContext()->getOutput(); + $out->addModules('ext.glmv'); + + $mvTransform = $file->transform([ 'width' => '800', 'hight' => '600']); + $previewViewer = $mvTransform->toHtml([ 'preview' => true]); + + $addButtonAttr = array( + 'class' => 'AddHotspot', + 'onclick' => 'readyAddHotspot()' + ); + $addHsButton = array( + Html::rawElement('button',$addButtonAttr,'Add a new hotspot') + ); + + $previewHTML = Html::rawElement('div',NULL,$previewViewer.implode($addHsButton)); + return false; + } + + /** + * 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; } - echo $command; - } } \ No newline at end of file diff --git a/includes/GlModelTransformOutput.php b/includes/GlModelTransformOutput.php index 437591a..33f74d9 100644 --- a/includes/GlModelTransformOutput.php +++ b/includes/GlModelTransformOutput.php @@ -45,189 +45,192 @@ class GlModelTransformOutput extends MediaTransformOutput { 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']);
+    /**
+     * 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];
-          }
+        //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);
+            }
         }
-      } 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)';
+
+        //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);
         }
-        $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';
-    }
+        //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) : '';
     
-    if (!isset($classList)) {
-      return 'default';
+        $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);
     }
 
-    $searchString = '/' . $paramType . '-(\S*)/';
-    preg_match($searchString,$classList,$extractInfo);
+    /**
+     * 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';
+        }
 
-    return ($extractInfo[1]) ? $extractInfo[1] : 'default';
-  }
+        if (!isset($classList)) {
+            return '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, '');
+        $searchString = '/' . $paramType . '-(\S*)/';
+        preg_match($searchString,$classList,$extractInfo);
 
-    $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 = '';
+        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;
     }
-    echo $command;
-  }
 }
\ No newline at end of file