Browse Source

refactored, started random player

Bachir Soussi Chiadmi 6 years ago
parent
commit
d317b85ef3

+ 71 - 96
sites/all/modules/figli/edlp_corpus/assets/dist/scripts/corpus.min.js

@@ -17,7 +17,7 @@
   *
   */
 
-(function($) {
+(function($, Drupal, drupalSettings) {
 
   EdlpCorpus = function(){
     var _activated = true;
@@ -42,6 +42,7 @@
     var _nodes_centered = [];
     var _nodes_centered_attractions = [];
 
+    var _playlist = [];
 
     // var _particules = [];
     // var _base_radius = 3; // nodes radius (real radius, not diametre)
@@ -75,15 +76,6 @@
     //  _/ // / / / / /_(__  )
     // /___/_/ /_/_/\__/____/
     function init(){
-      console.log("EdlpCorpus init()");
-      // console.log("document", document);
-      initMap();
-
-    };
-
-    function initMap(){
-      console.log("EdlpCorpus initMap()");
-      // console.log('_physics',_physics);
       initCanvas();
       if(_activated){
         loadCorpus();
@@ -92,17 +84,15 @@
       }
     };
 
-    //    ______
-    //   / ____/___ _____ _   ______ ______
-    //  / /   / __ `/ __ \ | / / __ `/ ___/
-    // / /___/ /_/ / / / / |/ / /_/ (__  )
-    // \____/\__,_/_/ /_/|___/\__,_/____/
+    //   ___
+    //  / __|__ _ _ ___ ____ _ ___
+    // | (__/ _` | ' \ V / _` (_-<
+    //  \___\__,_|_||_\_/\__,_/__/
     function initCanvas(){
       // resize the canvas to fill browser window dynamically
       window.addEventListener('resize', onResizeCanvas, false);
       onResizeCanvas();
     };
-
     function onResizeCanvas() {
       _canvas.width = window.innerWidth;
       _canvas.height = window.innerHeight;
@@ -113,12 +103,11 @@
       }
     };
 
-    //     ___      _
-    //    /   |    (_)___ __  __
-    //   / /| |   / / __ `/ |/_/
-    //  / ___ |  / / /_/ />  <
-    // /_/  |_|_/ /\__,_/_/|_|
-    //       /___/
+    //   ___
+    //  / __|___ _ _ _ __ _  _ ___
+    // | (__/ _ \ '_| '_ \ || (_-<
+    //  \___\___/_| | .__/\_,_/__/
+    //              |_|
     function loadCorpus(){
       // console.log('drupalSettings',drupalSettings);
       // console.log('window.location', window.location);
@@ -131,24 +120,21 @@
           onCorpusLoadError(jqxhr, textStatus, error);
         });
     };
-
     function onCorpusLoadError(jqxhr, textStatus, error){
       console.warn('corpus load failed', jqxhr.responseText);
     };
-
     function onCorpusLoaded(data){
       console.log('corpus loaded : data', data);
       // console.log('first node', data.nodes[0]);
       // buildParticles(data.nodes);
-      initNodePopup();
-      initArtilesLink();
       initPhysics();
       buildNodes(data.nodes);
+      initArtilesLink();
+      initNodePopup();
       initEvents();
       startAnime();
     };
 
-
     //  ___ _           _
     // | _ \ |_ _  _ __(_)__ ___
     // |  _/ ' \ || (_-< / _(_-<
@@ -195,11 +181,10 @@
     };
 
 
-    //     _   __          __
-    //    / | / /___  ____/ /__  _____
-    //   /  |/ / __ \/ __  / _ \/ ___/
-    //  / /|  / /_/ / /_/ /  __(__  )
-    // /_/ |_/\____/\__,_/\___/____/
+    //  _  _         _
+    // | \| |___  __| |___ ___
+    // | .` / _ \/ _` / -_|_-<
+    // |_|\_\___/\__,_\___/__/
     function buildNodes(nodes){
       console.log("buildNodes", nodes);
       var d;
@@ -207,6 +192,12 @@
         d = i < 1 ? true : false;
         // _nodes.push(new Node(i,nodes[i],d));
         new Node(i,nodes[i],d);
+
+        _playlist.push({
+          'id':i,
+          'nid':nodes[i].nid,
+          'audio_url':nodes[i].audio_url
+        });
       }
       console.log('_nodes',_nodes);
       console.log('_articles_nodes',_articles_nodes);
@@ -518,7 +509,8 @@
 
       this.init();
     };
-    // TODO: we may convert a lot of computation into a web worker https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers
+    // TODO: we may convert a lot of computation into a web worker
+    // https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers
     function checkParticulesCollisions(){
       // pre create vars to save memory;
       var d, full_rad, margin,
@@ -597,43 +589,18 @@
         }
       }
     };
-
     // show opened audio node
     function openNode(id){
       closeNode();
       _node_opened_id = id;
       _nodes[id].open();
     }
-
     function closeNode(){
       if(_node_opened_id != -1){
         _nodes[_node_opened_id].close();
         _node_opened_id = -1;
       }
     }
-
-    //  ___     _
-    // | __|_ _| |_ _ _ ___ ___ ___
-    // | _|| ' \  _| '_/ -_) -_|_-<
-    // |___|_||_\__|_| \___\___/__/
-    function filterEntree(t){
-      // shuydown articles if active
-      if(_$articles_link.is('.is-active'))
-        _$articles_link.trigger('click');
-
-      _nodes_centered = [];
-      for (var n = 0; n < _nodes.length; n++) {
-        if(_nodes[n].entrees.indexOf(t) == -1){
-          _nodes[n].setAside();
-        }else{
-          _nodes[n].setCenteredOnEntree(t);
-          // record centered nodes for inter node repulsions
-          _nodes_centered.push(_nodes[n]);
-        }
-      }
-      createNodesRepulsions();
-    };
-
     function createNodesRepulsions(){
       // how to delete all these attractions before creates new once
       console.log('_nodes_centered', _nodes_centered);
@@ -644,7 +611,6 @@
         }
       }
     };
-
     function purgeNodesRepulsions(){
       // console.log('_physics.attractions.length', _physics.attractions.length);
       for (var a = _physics.attractions.length-1; a >= 0; a--) {
@@ -659,6 +625,37 @@
       // console.log('_physics.attractions.length', _physics.attractions.length);
     };
 
+    //  ___     _
+    // | __|_ _| |_ _ _ ___ ___ ___
+    // | _|| ' \  _| '_/ -_) -_|_-<
+    // |___|_||_\__|_| \___\___/__/
+    function filterEntree(t){
+      // shuydown articles if active
+      if(_$articles_link.is('.is-active'))
+        _$articles_link.trigger('click');
+
+      _nodes_centered = [];
+      for (var n = 0; n < _nodes.length; n++) {
+        if(_nodes[n].entrees.indexOf(t) == -1){
+          _nodes[n].setAside();
+        }else{
+          _nodes[n].setCenteredOnEntree(t);
+          // record centered nodes for inter node repulsions
+          _nodes_centered.push(_nodes[n]);
+        }
+      }
+      createNodesRepulsions();
+    };
+    function highlightEntries(){
+      _$entrees_block_termlinks.removeClass('highlighted');
+      var entree;
+      for (var i = 0; i < _nodes[_node_hover_id].entrees.length; i++) {
+        entree = _nodes[_node_hover_id].entrees[i];
+        _$entrees_block_termlinks.filter(function(){
+          return $(this).attr('tid') == entree;
+        }).addClass('highlighted');
+      }
+    };
 
     //  ___                    _    _ _
     // / __| __ _ _ __ _ _ __ | |__| (_)_ _  __ _
@@ -672,7 +669,6 @@
       // setTimeout(stopScrambling, 2000);
       stopScrambling();
     };
-
     function stopScrambling(){
       for (var i = 0; i < _nodes.length; i++) {
         setTimeout(function(n){
@@ -680,7 +676,6 @@
         }.bind(this, _nodes[i]), 1000 + Math.random()*4000);
       }
     };
-
     function closeAllEntries(){
       _$entrees_block_termlinks.each(function(index, el) {
         if($(this).parents('li').is('.opened')){
@@ -708,7 +703,6 @@
          )
        );
     };
-
     function onCLickedOnArticles(e){
       e.preventDefault();
       $(this).toggleClass('is-active');
@@ -723,14 +717,12 @@
       // });
       return false;
     };
-
     function filterArticles(){
       console.log('filterArticles');
       for (var i = 0; i < _no_articles_nodes.length; i++) {
         _no_articles_nodes[i].fade();
       }
     };
-
     function resetArticlesFilter(){
       console.log('resetArticlesFilter');
       for (var i = 0; i < _no_articles_nodes.length; i++) {
@@ -738,12 +730,10 @@
       }
     };
 
-
-    //     ______                 __
-    //    / ____/   _____  ____  / /______
-    //   / __/ | | / / _ \/ __ \/ __/ ___/
-    //  / /___ | |/ /  __/ / / / /_(__  )
-    // /_____/ |___/\___/_/ /_/\__/____/
+    //  ___             _
+    // | __|_ _____ _ _| |_ ___
+    // | _|\ V / -_) ' \  _(_-<
+    // |___|\_/\___|_||_\__/__/
     function initEvents(){
       _$canvas
         .on('mousemove', function(event) {
@@ -771,10 +761,11 @@
                 'target_node':{
                   'id':_node_hover_id,
                   'nid':_nodes[_node_hover_id].nid,
-                  'audio_url':_nodes[_node_hover_id].son_url
+                  'audio_url':_nodes[_node_hover_id].audio_url
                 },
               };
               _$canvas.trigger(event);
+              // TODO: instead of directly opening the doc, create an event listener (e.g. : audio played from random)
               openNode(_node_hover_id);
             }else{
               // console.log('corpus : click on map');
@@ -899,11 +890,10 @@
       }
     }
 
-    //     ____                 __
-    //    / __ \___  ____  ____/ /__  _____
-    //   / /_/ / _ \/ __ \/ __  / _ \/ ___/
-    //  / _, _/  __/ / / / /_/ /  __/ /
-    // /_/ |_|\___/_/ /_/\__,_/\___/_/
+    //  ___             _
+    // | _ \___ _ _  __| |___ _ _
+    // |   / -_) ' \/ _` / -_) '_|
+    // |_|_\___|_||_\__,_\___|_|
     function render(){
       _ctx.clearRect(0, 0, _canvas.width, _canvas.height);
 
@@ -926,24 +916,6 @@
       }
     };
 
-
-    //  _  _ _      _    _   _      _   _   ___     _       _
-    // | || (_)__ _| |_ | |_(_)__ _| |_| |_| __|_ _| |_ _ _(_)___ ___
-    // | __ | / _` | ' \| / / / _` | ' \  _| _|| ' \  _| '_| / -_|_-<
-    // |_||_|_\__, |_||_|_\_\_\__, |_||_\__|___|_||_\__|_| |_\___/__/
-    //        |___/           |___/
-    function highlightEntries(){
-      _$entrees_block_termlinks.removeClass('highlighted');
-      var entree;
-      for (var i = 0; i < _nodes[_node_hover_id].entrees.length; i++) {
-        entree = _nodes[_node_hover_id].entrees[i];
-        _$entrees_block_termlinks.filter(function(){
-          return $(this).attr('tid') == entree;
-        }).addClass('highlighted');
-      }
-    }
-
-
     //  ___ _            _     _        _
     // / __| |_ __ _ _ _| |_  /_\  _ _ (_)_ __  ___
     // \__ \  _/ _` | '_|  _|/ _ \| ' \| | '  \/ -_)
@@ -951,7 +923,10 @@
     function startAnime(){
       _physics.onUpdate(render);
       _physics.play()
-      $('body').trigger('corpus-map-ready');
+      $('body').trigger({
+        'type':'corpus-map-ready',
+        'playlist':_playlist
+      });
     };
 
     init();
@@ -963,4 +938,4 @@
   });
 
 
-})(jQuery);
+})(jQuery, Drupal, drupalSettings);

+ 71 - 96
sites/all/modules/figli/edlp_corpus/assets/scripts/corpus.js

@@ -17,7 +17,7 @@
   *
   */
 
-(function($) {
+(function($, Drupal, drupalSettings) {
 
   EdlpCorpus = function(){
     var _activated = true;
@@ -42,6 +42,7 @@
     var _nodes_centered = [];
     var _nodes_centered_attractions = [];
 
+    var _playlist = [];
 
     // var _particules = [];
     // var _base_radius = 3; // nodes radius (real radius, not diametre)
@@ -75,15 +76,6 @@
     //  _/ // / / / / /_(__  )
     // /___/_/ /_/_/\__/____/
     function init(){
-      console.log("EdlpCorpus init()");
-      // console.log("document", document);
-      initMap();
-
-    };
-
-    function initMap(){
-      console.log("EdlpCorpus initMap()");
-      // console.log('_physics',_physics);
       initCanvas();
       if(_activated){
         loadCorpus();
@@ -92,17 +84,15 @@
       }
     };
 
-    //    ______
-    //   / ____/___ _____ _   ______ ______
-    //  / /   / __ `/ __ \ | / / __ `/ ___/
-    // / /___/ /_/ / / / / |/ / /_/ (__  )
-    // \____/\__,_/_/ /_/|___/\__,_/____/
+    //   ___
+    //  / __|__ _ _ ___ ____ _ ___
+    // | (__/ _` | ' \ V / _` (_-<
+    //  \___\__,_|_||_\_/\__,_/__/
     function initCanvas(){
       // resize the canvas to fill browser window dynamically
       window.addEventListener('resize', onResizeCanvas, false);
       onResizeCanvas();
     };
-
     function onResizeCanvas() {
       _canvas.width = window.innerWidth;
       _canvas.height = window.innerHeight;
@@ -113,12 +103,11 @@
       }
     };
 
-    //     ___      _
-    //    /   |    (_)___ __  __
-    //   / /| |   / / __ `/ |/_/
-    //  / ___ |  / / /_/ />  <
-    // /_/  |_|_/ /\__,_/_/|_|
-    //       /___/
+    //   ___
+    //  / __|___ _ _ _ __ _  _ ___
+    // | (__/ _ \ '_| '_ \ || (_-<
+    //  \___\___/_| | .__/\_,_/__/
+    //              |_|
     function loadCorpus(){
       // console.log('drupalSettings',drupalSettings);
       // console.log('window.location', window.location);
@@ -131,24 +120,21 @@
           onCorpusLoadError(jqxhr, textStatus, error);
         });
     };
-
     function onCorpusLoadError(jqxhr, textStatus, error){
       console.warn('corpus load failed', jqxhr.responseText);
     };
-
     function onCorpusLoaded(data){
       console.log('corpus loaded : data', data);
       // console.log('first node', data.nodes[0]);
       // buildParticles(data.nodes);
-      initNodePopup();
-      initArtilesLink();
       initPhysics();
       buildNodes(data.nodes);
+      initArtilesLink();
+      initNodePopup();
       initEvents();
       startAnime();
     };
 
-
     //  ___ _           _
     // | _ \ |_ _  _ __(_)__ ___
     // |  _/ ' \ || (_-< / _(_-<
@@ -195,11 +181,10 @@
     };
 
 
-    //     _   __          __
-    //    / | / /___  ____/ /__  _____
-    //   /  |/ / __ \/ __  / _ \/ ___/
-    //  / /|  / /_/ / /_/ /  __(__  )
-    // /_/ |_/\____/\__,_/\___/____/
+    //  _  _         _
+    // | \| |___  __| |___ ___
+    // | .` / _ \/ _` / -_|_-<
+    // |_|\_\___/\__,_\___/__/
     function buildNodes(nodes){
       console.log("buildNodes", nodes);
       var d;
@@ -207,6 +192,12 @@
         d = i < 1 ? true : false;
         // _nodes.push(new Node(i,nodes[i],d));
         new Node(i,nodes[i],d);
+
+        _playlist.push({
+          'id':i,
+          'nid':nodes[i].nid,
+          'audio_url':nodes[i].audio_url
+        });
       }
       console.log('_nodes',_nodes);
       console.log('_articles_nodes',_articles_nodes);
@@ -518,7 +509,8 @@
 
       this.init();
     };
-    // TODO: we may convert a lot of computation into a web worker https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers
+    // TODO: we may convert a lot of computation into a web worker
+    // https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers
     function checkParticulesCollisions(){
       // pre create vars to save memory;
       var d, full_rad, margin,
@@ -597,43 +589,18 @@
         }
       }
     };
-
     // show opened audio node
     function openNode(id){
       closeNode();
       _node_opened_id = id;
       _nodes[id].open();
     }
-
     function closeNode(){
       if(_node_opened_id != -1){
         _nodes[_node_opened_id].close();
         _node_opened_id = -1;
       }
     }
-
-    //  ___     _
-    // | __|_ _| |_ _ _ ___ ___ ___
-    // | _|| ' \  _| '_/ -_) -_|_-<
-    // |___|_||_\__|_| \___\___/__/
-    function filterEntree(t){
-      // shuydown articles if active
-      if(_$articles_link.is('.is-active'))
-        _$articles_link.trigger('click');
-
-      _nodes_centered = [];
-      for (var n = 0; n < _nodes.length; n++) {
-        if(_nodes[n].entrees.indexOf(t) == -1){
-          _nodes[n].setAside();
-        }else{
-          _nodes[n].setCenteredOnEntree(t);
-          // record centered nodes for inter node repulsions
-          _nodes_centered.push(_nodes[n]);
-        }
-      }
-      createNodesRepulsions();
-    };
-
     function createNodesRepulsions(){
       // how to delete all these attractions before creates new once
       console.log('_nodes_centered', _nodes_centered);
@@ -644,7 +611,6 @@
         }
       }
     };
-
     function purgeNodesRepulsions(){
       // console.log('_physics.attractions.length', _physics.attractions.length);
       for (var a = _physics.attractions.length-1; a >= 0; a--) {
@@ -659,6 +625,37 @@
       // console.log('_physics.attractions.length', _physics.attractions.length);
     };
 
+    //  ___     _
+    // | __|_ _| |_ _ _ ___ ___ ___
+    // | _|| ' \  _| '_/ -_) -_|_-<
+    // |___|_||_\__|_| \___\___/__/
+    function filterEntree(t){
+      // shuydown articles if active
+      if(_$articles_link.is('.is-active'))
+        _$articles_link.trigger('click');
+
+      _nodes_centered = [];
+      for (var n = 0; n < _nodes.length; n++) {
+        if(_nodes[n].entrees.indexOf(t) == -1){
+          _nodes[n].setAside();
+        }else{
+          _nodes[n].setCenteredOnEntree(t);
+          // record centered nodes for inter node repulsions
+          _nodes_centered.push(_nodes[n]);
+        }
+      }
+      createNodesRepulsions();
+    };
+    function highlightEntries(){
+      _$entrees_block_termlinks.removeClass('highlighted');
+      var entree;
+      for (var i = 0; i < _nodes[_node_hover_id].entrees.length; i++) {
+        entree = _nodes[_node_hover_id].entrees[i];
+        _$entrees_block_termlinks.filter(function(){
+          return $(this).attr('tid') == entree;
+        }).addClass('highlighted');
+      }
+    };
 
     //  ___                    _    _ _
     // / __| __ _ _ __ _ _ __ | |__| (_)_ _  __ _
@@ -672,7 +669,6 @@
       // setTimeout(stopScrambling, 2000);
       stopScrambling();
     };
-
     function stopScrambling(){
       for (var i = 0; i < _nodes.length; i++) {
         setTimeout(function(n){
@@ -680,7 +676,6 @@
         }.bind(this, _nodes[i]), 1000 + Math.random()*4000);
       }
     };
-
     function closeAllEntries(){
       _$entrees_block_termlinks.each(function(index, el) {
         if($(this).parents('li').is('.opened')){
@@ -708,7 +703,6 @@
          )
        );
     };
-
     function onCLickedOnArticles(e){
       e.preventDefault();
       $(this).toggleClass('is-active');
@@ -723,14 +717,12 @@
       // });
       return false;
     };
-
     function filterArticles(){
       console.log('filterArticles');
       for (var i = 0; i < _no_articles_nodes.length; i++) {
         _no_articles_nodes[i].fade();
       }
     };
-
     function resetArticlesFilter(){
       console.log('resetArticlesFilter');
       for (var i = 0; i < _no_articles_nodes.length; i++) {
@@ -738,12 +730,10 @@
       }
     };
 
-
-    //     ______                 __
-    //    / ____/   _____  ____  / /______
-    //   / __/ | | / / _ \/ __ \/ __/ ___/
-    //  / /___ | |/ /  __/ / / / /_(__  )
-    // /_____/ |___/\___/_/ /_/\__/____/
+    //  ___             _
+    // | __|_ _____ _ _| |_ ___
+    // | _|\ V / -_) ' \  _(_-<
+    // |___|\_/\___|_||_\__/__/
     function initEvents(){
       _$canvas
         .on('mousemove', function(event) {
@@ -771,10 +761,11 @@
                 'target_node':{
                   'id':_node_hover_id,
                   'nid':_nodes[_node_hover_id].nid,
-                  'audio_url':_nodes[_node_hover_id].son_url
+                  'audio_url':_nodes[_node_hover_id].audio_url
                 },
               };
               _$canvas.trigger(event);
+              // TODO: instead of directly opening the doc, create an event listener (e.g. : audio played from random)
               openNode(_node_hover_id);
             }else{
               // console.log('corpus : click on map');
@@ -899,11 +890,10 @@
       }
     }
 
-    //     ____                 __
-    //    / __ \___  ____  ____/ /__  _____
-    //   / /_/ / _ \/ __ \/ __  / _ \/ ___/
-    //  / _, _/  __/ / / / /_/ /  __/ /
-    // /_/ |_|\___/_/ /_/\__,_/\___/_/
+    //  ___             _
+    // | _ \___ _ _  __| |___ _ _
+    // |   / -_) ' \/ _` / -_) '_|
+    // |_|_\___|_||_\__,_\___|_|
     function render(){
       _ctx.clearRect(0, 0, _canvas.width, _canvas.height);
 
@@ -926,24 +916,6 @@
       }
     };
 
-
-    //  _  _ _      _    _   _      _   _   ___     _       _
-    // | || (_)__ _| |_ | |_(_)__ _| |_| |_| __|_ _| |_ _ _(_)___ ___
-    // | __ | / _` | ' \| / / / _` | ' \  _| _|| ' \  _| '_| / -_|_-<
-    // |_||_|_\__, |_||_|_\_\_\__, |_||_\__|___|_||_\__|_| |_\___/__/
-    //        |___/           |___/
-    function highlightEntries(){
-      _$entrees_block_termlinks.removeClass('highlighted');
-      var entree;
-      for (var i = 0; i < _nodes[_node_hover_id].entrees.length; i++) {
-        entree = _nodes[_node_hover_id].entrees[i];
-        _$entrees_block_termlinks.filter(function(){
-          return $(this).attr('tid') == entree;
-        }).addClass('highlighted');
-      }
-    }
-
-
     //  ___ _            _     _        _
     // / __| |_ __ _ _ _| |_  /_\  _ _ (_)_ __  ___
     // \__ \  _/ _` | '_|  _|/ _ \| ' \| | '  \/ -_)
@@ -951,7 +923,10 @@
     function startAnime(){
       _physics.onUpdate(render);
       _physics.play()
-      $('body').trigger('corpus-map-ready');
+      $('body').trigger({
+        'type':'corpus-map-ready',
+        'playlist':_playlist
+      });
     };
 
     init();
@@ -963,4 +938,4 @@
   });
 
 
-})(jQuery);
+})(jQuery, Drupal, drupalSettings);

+ 8 - 8
sites/all/modules/figli/edlp_corpus/src/Controller/CorpusController.php

@@ -61,14 +61,14 @@ class CorpusController extends ControllerBase {
       // dpm($description);
 
       $field_son_values = $node->get('field_son')->getValue();
-      $son_fid = count($field_son_values) ? $field_son_values[0]['target_id'] : "";
-      $son_file = \Drupal\file\Entity\File::load($son_fid);
-      $son_url = null;
+      $audio_fid = count($field_son_values) ? $field_son_values[0]['target_id'] : "";
+      $audio_file = \Drupal\file\Entity\File::load($audio_fid);
+      $audio_url = null;
       // if node don't have a sound file atteched, skip it
-      if(!$son_file) continue;
+      if(!$audio_file) continue;
 
-      $son_uri = $son_file->getFileUri();
-      $son_url = file_create_url($son_uri);
+      $son_uri = $audio_file->getFileUri();
+      $audio_url = file_create_url($son_uri);
 
       // has article ?
       $article_value = $node->body->getValue();
@@ -83,8 +83,8 @@ class CorpusController extends ControllerBase {
         "title" => $node->get('title')->getString(),
         "description" => $description,
         "entrees" => $entrees,
-        // "son_fid" => $son_fid,
-        "son_url" => $son_url,
+        // "son_fid" => $audio_fid,
+        "audio_url" => $audio_url,
         "has_article" => $has_article,
         "chutier_action" => 'add',
       );

+ 170 - 41
sites/all/themes/custom/edlptheme/assets/dist/scripts/main.min.js

@@ -1,21 +1,26 @@
-(function($) {
+(function($, Drupal, drupalSettings) {
 
   EdlpTheme = function(){
-
     var _$body = $('body');
     var _is_front = _$body.is('.path-frontpage');
     var _$corpus_canvas;
     var _$content_container = $('main[role="main"]>.layout-content');
     var _$ajaxLinks;
     var _audio_player;
+    var _randomPlayer;
 
+    //  ___      _ _
+    // |_ _|_ _ (_) |_
+    //  | || ' \| |  _|
+    // |___|_||_|_|\__|
     function init(){
       console.log("EdlpTheme init()");
 
       // TODO: redirect all no-front pages to front with write hash
 
       _$body.on('corpus-map-ready', onCorpusMapReady);
-      initAudioPlayer();
+
+      _audio_player = new AudioPlayer();
 
       initAjaxLinks();
 
@@ -23,12 +28,14 @@
         initProductions();
       }
 
-      initScrollbars();
-
+      // initScrollbars();
       initEvents();
     };
 
-
+    //  ___             _
+    // | __|_ _____ _ _| |_ ___
+    // | _|\ V / -_) ' \  _(_-<
+    // |___|\_/\___|_||_\__/__/
     function initEvents(){
       $('body')
         .on('on-studio-chutier-updated', function(e){
@@ -52,10 +59,6 @@
     // https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement
     // https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/samples/gg589528%28v%3dvs.85%29
     // https://www.binarytides.com/using-html5-audio-element-javascript/
-    //
-    function initAudioPlayer(){
-      _audio_player = new AudioPlayer();
-    };
     function AudioPlayer(){
       var that = this;
       this.fid;
@@ -88,6 +91,16 @@
       this.hideTimer = false;
       this.hideTimeMS = 10000;
 
+      // history
+      this.next_node = null;
+      this.currentHistoricIndex = null;
+      this.historic = [];
+
+      // object events
+      this.event_handlers = {
+        'audio-ended':[]
+      };
+
       this.init();
     };
     AudioPlayer.prototype = {
@@ -108,37 +121,31 @@
             true);
         }
         // init btns events
+        this.$previous.on('click', this.playPrevious.bind(this));
         this.$playpause.on('click', this.togglePlayPause.bind(this));
+        this.$next.on('click', this.playNext.bind(this));
         // TODO: previous and next btns
       },
-      openDocument(node){
+      openDocument(node, next_node){
         console.log('AudioPlayer openDocument', node);
-        this.setSRC(node.audio_url);
-        this.loadNode(node.nid);
+        this.historic.push(node);
+        this.currentHistoricIndex = this.historic.length-1;
+        this.next_node = next_node || null;
+        // TODO: emmit new playing doc (need for corpus map nowing that audio played from RandomPlayer)
+        this.launch();
       },
-      // cartel functions
-      loadNode(nid){
-        this.$cartel.addClass('loading');
-        $.getJSON('/edlp/ajax/json/node/'+nid+'/player_cartel', {})
-          .done(this.onNodeLoaded.bind(this))
-          .fail(this.onNodeLoadFail.bind(this));
-      },
-      onNodeLoaded(data){
-        console.log('AudioPlayer node loaded', data);
-        this.$cartel.html(data.rendered).removeClass('loading');
-        _$body.trigger({'type':'new-audio-cartel-loaded'});
-        initAjaxLinks();
-      },
-      onNodeLoadFail(jqxhr, textStatus, error){
-        console.warn('AudioPlayer node load failed', jqxhr.responseText);
-        this.$cartel.removeClass('loading').html('');
+      launch(){
+        this.setSRC(this.historic[this.currentHistoricIndex].audio_url);
+        this.loadNode(this.historic[this.currentHistoricIndex].nid);
+        this.showHidePreviousBtn();
+        this.showHideNextBtn();
+        this.show();
       },
       // audio functions
       setSRC(url){
         // console.log('AudioPlayer setSRC : url', url);
         this.clearTimeOutToHide();
         this.audio.src = url;
-        this.show();
       },
       onLoadedmetadata(){
         var rem = parseInt(this.audio.duration, 10),
@@ -165,7 +172,18 @@
       play(){
         this.audio.play();
       },
-      togglePlayPause(){
+      playPrevious(){
+        if(typeof this.historic[this.currentHistoricIndex-1] !== 'undefined'){
+          this.currentHistoricIndex -= 1;
+          this.launch();
+        }
+      },
+      playNext(){
+        if(this.next_node){
+          this.openDocument(this.next_node);
+        }
+      },
+      togglePlayPause(e){
         if(this.audio.paused){
           this.audio.play();
         }else{
@@ -190,13 +208,48 @@
         this.$currentTime.html('<span>'+(mins<10 ? '0':'')+mins+':'+(secs<10 ? '0':'')+secs+'</span>');
       },
       onEnded(){
+        this.emmit('audio-ended');
+        this.stop();
+      },
+      stop(){
         this.$container.removeClass('is-playing');
         this.timeOutToHide();
       },
+      // cartel functions
+      loadNode(nid){
+        this.$cartel.addClass('loading');
+        $.getJSON('/edlp/ajax/json/node/'+nid+'/player_cartel', {})
+          .done(this.onNodeLoaded.bind(this))
+          .fail(this.onNodeLoadFail.bind(this));
+      },
+      onNodeLoaded(data){
+        console.log('AudioPlayer node loaded', data);
+        this.$cartel.html(data.rendered).removeClass('loading');
+        _$body.trigger({'type':'new-audio-cartel-loaded'});
+        initAjaxLinks();
+      },
+      onNodeLoadFail(jqxhr, textStatus, error){
+        console.warn('AudioPlayer node load failed', jqxhr.responseText);
+        this.$cartel.removeClass('loading').html('');
+      },
       // global
       show(){
         this.$container.addClass('visible');
       },
+      showHidePreviousBtn(s){
+        if(this.historic.length > 1){
+          this.$previous.addClass('is-active');
+        }else{
+          this.$previous.removeClass('is-active');
+        }
+      },
+      showHideNextBtn(){
+        if(this.next_node){
+          this.$next.addClass('is-active');
+        }else{
+          this.$next.removeClass('is-active');
+        }
+      },
       timeOutToHide(){
         this.clearTimeOutToHide();
         this.hideTimer = setTimeout(this.hide.bind(this), this.hideTimeMS);
@@ -210,17 +263,29 @@
         this.$container.removeClass('visible');
         // trigger highlighted node remove on corpus map
         _$corpus_canvas.trigger('audio-node-closed');
-      }
+      },
+      // object events
+      on(event_name, handler){
+        if(typeof this.event_handlers[event_name] == 'undefined'){
+          console.warn('AudioPlayer : event '+event_name+' does not exists');
+        }
+        this.event_handlers[event_name].push(handler);
+      },
+      emmit(event_name){
+        var handler;
+        for (var i = this.event_handlers[event_name].length; i >= 0 ; i--) {
+          handler = this.event_handlers[event_name][i];
+          setTimeout(handler, 0);
+        }
+      },
     }
 
-
-
     //  ___             _ _ ___
     // / __| __ _ _ ___| | | _ ) __ _ _ _ ___
     // \__ \/ _| '_/ _ \ | | _ \/ _` | '_(_-<
     // |___/\__|_| \___/_|_|___/\__,_|_| /__/
     function initScrollbars(){
-      console.log("initScrollbars");
+      // console.log("initScrollbars");
       // TODO: find a better js scroll than overlayScrollbars which does not handle well max-height + overflow-y:auto;
       // $('.os-scroll').overlayScrollbars({
       //   overflowBehavior:{
@@ -261,7 +326,6 @@
           }
         });
     };
-
     function onClickAjaxLink(e){
       e.preventDefault();
       var $link = $(this);
@@ -326,13 +390,11 @@
 
       return false;
     };
-
     function onAjaxLinkLoadError(jqxhr, textStatus, error, $link, sys_path){
       console.warn('ajaxlink load failed for '+sys_path+' : '+error, jqxhr.responseText);
       $link.removeClass('ajax-loading');
       _$body.removeClass('ajax-loading');
     };
-
     function onAjaxLinkLoaded(data, $link, sys_path){
       console.log('ajax link loaded : data', data);
       _$body.removeClass('ajax-loading');
@@ -386,7 +448,7 @@
     //  \___\___/_| | .__/\_,_/__/
     //              |_|
     function onCorpusMapReady(e){
-      console.log('theme : onCorpusReady');
+      console.log('theme : onCorpusReady', e);
       _$corpus_canvas = $('canvas#corpus-map');
       _$corpus_canvas
         .on('corpus-cliked-on-map', function(e) {
@@ -398,9 +460,77 @@
           _audio_player.openDocument(e.target_node);
         });
 
+      _randomPlayer = new RandomPlayer(e.playlist);
+
       _$body.attr('corpus-map', 'ready');
     }
 
+    //  ___              _           ___ _
+    // | _ \__ _ _ _  __| |___ _ __ | _ \ |__ _ _  _ ___ _ _
+    // |   / _` | ' \/ _` / _ \ '  \|  _/ / _` | || / -_) '_|
+    // |_|_\__,_|_||_\__,_\___/_|_|_|_| |_\__,_|\_, \___|_|
+    //                                          |__/
+    function RandomPlayer(playlist){
+      this.active = false;
+      this.playlist = playlist;
+      this.$btn = $('<a>').html('&#x1f500;').addClass('random-player-btn');
+      this.init()
+    };
+    RandomPlayer.prototype = {
+      init(){
+        // this.shuffle();
+        $('<div>')
+          .addClass('block random-player')
+          .append(this.$btn)
+          .insertAfter('#block-userlogin');
+
+        // events
+        this.$btn.on('click', this.toggleActive.bind(this));
+
+        // attach an event on AudioPlayer
+        _audio_player.on('audio-ended', this.onAudioPlayerEnded.bind(this));
+      },
+      shuffle(){
+        var tempPLaylist = [];
+        for (var i = this.playlist.length-1; i >= 0 ; i--) {
+          tempPLaylist.push(this.playlist[i]);
+        }
+        this.shuffledPlaylist = [];
+        while(tempPLaylist.length > 0){
+          var r = Math.floor(Math.random() * tempPLaylist.length);
+          this.shuffledPlaylist.push(tempPLaylist.splice(r,1)[0]);
+        }
+        console.log('RandomPlayer, this.shuffledPlaylist', this.shuffledPlaylist);
+      },
+      toggleActive(e){
+        if (this.active) {
+          this.stop();
+        }else{
+          this.shuffle();
+          this.play();
+        }
+      },
+      play(){
+        this.active = true;
+        this.next();
+      },
+      stop(){
+        this.active = false;
+        // stop audio player
+        _audio_player.stop();
+      },
+      next(){
+        if(this.shuffledPlaylist.length > 0)
+          _audio_player.openDocument(this.shuffledPlaylist.splice(0,1)[0]);
+      },
+      onAudioPlayerEnded(){
+        console.log('RandomPlayer : onAudioPlayerEnded()');
+        if(this.active){
+          this.next();
+        }
+      }
+    };
+
     //  ___             _         _   _
     // | _ \_ _ ___  __| |_  _ __| |_(_)___ _ _  ___
     // |  _/ '_/ _ \/ _` | || / _|  _| / _ \ ' \(_-<
@@ -425,7 +555,6 @@
       // });
     };
 
-
     //  ___            _   ___
     // | __| _ ___ _ _| |_| _ \__ _ __ _ ___
     // | _| '_/ _ \ ' \  _|  _/ _` / _` / -_)
@@ -458,4 +587,4 @@
   });
 
 
-})(jQuery);
+})(jQuery, Drupal, drupalSettings);

+ 5 - 0
sites/all/themes/custom/edlptheme/assets/dist/styles/app.min.css

@@ -2031,6 +2031,11 @@ footer {
       text-indent: 40px;
       margin: 0;
       overflow: hidden; }
+  footer .block.random-player {
+    pointer-events: all; }
+    footer .block.random-player a {
+      background-color: inherit;
+      cursor: pointer; }
   footer #block-userlogin {
     pointer-events: all;
     position: relative;

+ 170 - 41
sites/all/themes/custom/edlptheme/assets/scripts/main.js

@@ -1,21 +1,26 @@
-(function($) {
+(function($, Drupal, drupalSettings) {
 
   EdlpTheme = function(){
-
     var _$body = $('body');
     var _is_front = _$body.is('.path-frontpage');
     var _$corpus_canvas;
     var _$content_container = $('main[role="main"]>.layout-content');
     var _$ajaxLinks;
     var _audio_player;
+    var _randomPlayer;
 
+    //  ___      _ _
+    // |_ _|_ _ (_) |_
+    //  | || ' \| |  _|
+    // |___|_||_|_|\__|
     function init(){
       console.log("EdlpTheme init()");
 
       // TODO: redirect all no-front pages to front with write hash
 
       _$body.on('corpus-map-ready', onCorpusMapReady);
-      initAudioPlayer();
+
+      _audio_player = new AudioPlayer();
 
       initAjaxLinks();
 
@@ -23,12 +28,14 @@
         initProductions();
       }
 
-      initScrollbars();
-
+      // initScrollbars();
       initEvents();
     };
 
-
+    //  ___             _
+    // | __|_ _____ _ _| |_ ___
+    // | _|\ V / -_) ' \  _(_-<
+    // |___|\_/\___|_||_\__/__/
     function initEvents(){
       $('body')
         .on('on-studio-chutier-updated', function(e){
@@ -52,10 +59,6 @@
     // https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement
     // https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/samples/gg589528%28v%3dvs.85%29
     // https://www.binarytides.com/using-html5-audio-element-javascript/
-    //
-    function initAudioPlayer(){
-      _audio_player = new AudioPlayer();
-    };
     function AudioPlayer(){
       var that = this;
       this.fid;
@@ -88,6 +91,16 @@
       this.hideTimer = false;
       this.hideTimeMS = 10000;
 
+      // history
+      this.next_node = null;
+      this.currentHistoricIndex = null;
+      this.historic = [];
+
+      // object events
+      this.event_handlers = {
+        'audio-ended':[]
+      };
+
       this.init();
     };
     AudioPlayer.prototype = {
@@ -108,37 +121,31 @@
             true);
         }
         // init btns events
+        this.$previous.on('click', this.playPrevious.bind(this));
         this.$playpause.on('click', this.togglePlayPause.bind(this));
+        this.$next.on('click', this.playNext.bind(this));
         // TODO: previous and next btns
       },
-      openDocument(node){
+      openDocument(node, next_node){
         console.log('AudioPlayer openDocument', node);
-        this.setSRC(node.audio_url);
-        this.loadNode(node.nid);
+        this.historic.push(node);
+        this.currentHistoricIndex = this.historic.length-1;
+        this.next_node = next_node || null;
+        // TODO: emmit new playing doc (need for corpus map nowing that audio played from RandomPlayer)
+        this.launch();
       },
-      // cartel functions
-      loadNode(nid){
-        this.$cartel.addClass('loading');
-        $.getJSON('/edlp/ajax/json/node/'+nid+'/player_cartel', {})
-          .done(this.onNodeLoaded.bind(this))
-          .fail(this.onNodeLoadFail.bind(this));
-      },
-      onNodeLoaded(data){
-        console.log('AudioPlayer node loaded', data);
-        this.$cartel.html(data.rendered).removeClass('loading');
-        _$body.trigger({'type':'new-audio-cartel-loaded'});
-        initAjaxLinks();
-      },
-      onNodeLoadFail(jqxhr, textStatus, error){
-        console.warn('AudioPlayer node load failed', jqxhr.responseText);
-        this.$cartel.removeClass('loading').html('');
+      launch(){
+        this.setSRC(this.historic[this.currentHistoricIndex].audio_url);
+        this.loadNode(this.historic[this.currentHistoricIndex].nid);
+        this.showHidePreviousBtn();
+        this.showHideNextBtn();
+        this.show();
       },
       // audio functions
       setSRC(url){
         // console.log('AudioPlayer setSRC : url', url);
         this.clearTimeOutToHide();
         this.audio.src = url;
-        this.show();
       },
       onLoadedmetadata(){
         var rem = parseInt(this.audio.duration, 10),
@@ -165,7 +172,18 @@
       play(){
         this.audio.play();
       },
-      togglePlayPause(){
+      playPrevious(){
+        if(typeof this.historic[this.currentHistoricIndex-1] !== 'undefined'){
+          this.currentHistoricIndex -= 1;
+          this.launch();
+        }
+      },
+      playNext(){
+        if(this.next_node){
+          this.openDocument(this.next_node);
+        }
+      },
+      togglePlayPause(e){
         if(this.audio.paused){
           this.audio.play();
         }else{
@@ -190,13 +208,48 @@
         this.$currentTime.html('<span>'+(mins<10 ? '0':'')+mins+':'+(secs<10 ? '0':'')+secs+'</span>');
       },
       onEnded(){
+        this.emmit('audio-ended');
+        this.stop();
+      },
+      stop(){
         this.$container.removeClass('is-playing');
         this.timeOutToHide();
       },
+      // cartel functions
+      loadNode(nid){
+        this.$cartel.addClass('loading');
+        $.getJSON('/edlp/ajax/json/node/'+nid+'/player_cartel', {})
+          .done(this.onNodeLoaded.bind(this))
+          .fail(this.onNodeLoadFail.bind(this));
+      },
+      onNodeLoaded(data){
+        console.log('AudioPlayer node loaded', data);
+        this.$cartel.html(data.rendered).removeClass('loading');
+        _$body.trigger({'type':'new-audio-cartel-loaded'});
+        initAjaxLinks();
+      },
+      onNodeLoadFail(jqxhr, textStatus, error){
+        console.warn('AudioPlayer node load failed', jqxhr.responseText);
+        this.$cartel.removeClass('loading').html('');
+      },
       // global
       show(){
         this.$container.addClass('visible');
       },
+      showHidePreviousBtn(s){
+        if(this.historic.length > 1){
+          this.$previous.addClass('is-active');
+        }else{
+          this.$previous.removeClass('is-active');
+        }
+      },
+      showHideNextBtn(){
+        if(this.next_node){
+          this.$next.addClass('is-active');
+        }else{
+          this.$next.removeClass('is-active');
+        }
+      },
       timeOutToHide(){
         this.clearTimeOutToHide();
         this.hideTimer = setTimeout(this.hide.bind(this), this.hideTimeMS);
@@ -210,17 +263,29 @@
         this.$container.removeClass('visible');
         // trigger highlighted node remove on corpus map
         _$corpus_canvas.trigger('audio-node-closed');
-      }
+      },
+      // object events
+      on(event_name, handler){
+        if(typeof this.event_handlers[event_name] == 'undefined'){
+          console.warn('AudioPlayer : event '+event_name+' does not exists');
+        }
+        this.event_handlers[event_name].push(handler);
+      },
+      emmit(event_name){
+        var handler;
+        for (var i = this.event_handlers[event_name].length; i >= 0 ; i--) {
+          handler = this.event_handlers[event_name][i];
+          setTimeout(handler, 0);
+        }
+      },
     }
 
-
-
     //  ___             _ _ ___
     // / __| __ _ _ ___| | | _ ) __ _ _ _ ___
     // \__ \/ _| '_/ _ \ | | _ \/ _` | '_(_-<
     // |___/\__|_| \___/_|_|___/\__,_|_| /__/
     function initScrollbars(){
-      console.log("initScrollbars");
+      // console.log("initScrollbars");
       // TODO: find a better js scroll than overlayScrollbars which does not handle well max-height + overflow-y:auto;
       // $('.os-scroll').overlayScrollbars({
       //   overflowBehavior:{
@@ -261,7 +326,6 @@
           }
         });
     };
-
     function onClickAjaxLink(e){
       e.preventDefault();
       var $link = $(this);
@@ -326,13 +390,11 @@
 
       return false;
     };
-
     function onAjaxLinkLoadError(jqxhr, textStatus, error, $link, sys_path){
       console.warn('ajaxlink load failed for '+sys_path+' : '+error, jqxhr.responseText);
       $link.removeClass('ajax-loading');
       _$body.removeClass('ajax-loading');
     };
-
     function onAjaxLinkLoaded(data, $link, sys_path){
       console.log('ajax link loaded : data', data);
       _$body.removeClass('ajax-loading');
@@ -386,7 +448,7 @@
     //  \___\___/_| | .__/\_,_/__/
     //              |_|
     function onCorpusMapReady(e){
-      console.log('theme : onCorpusReady');
+      console.log('theme : onCorpusReady', e);
       _$corpus_canvas = $('canvas#corpus-map');
       _$corpus_canvas
         .on('corpus-cliked-on-map', function(e) {
@@ -398,9 +460,77 @@
           _audio_player.openDocument(e.target_node);
         });
 
+      _randomPlayer = new RandomPlayer(e.playlist);
+
       _$body.attr('corpus-map', 'ready');
     }
 
+    //  ___              _           ___ _
+    // | _ \__ _ _ _  __| |___ _ __ | _ \ |__ _ _  _ ___ _ _
+    // |   / _` | ' \/ _` / _ \ '  \|  _/ / _` | || / -_) '_|
+    // |_|_\__,_|_||_\__,_\___/_|_|_|_| |_\__,_|\_, \___|_|
+    //                                          |__/
+    function RandomPlayer(playlist){
+      this.active = false;
+      this.playlist = playlist;
+      this.$btn = $('<a>').html('&#x1f500;').addClass('random-player-btn');
+      this.init()
+    };
+    RandomPlayer.prototype = {
+      init(){
+        // this.shuffle();
+        $('<div>')
+          .addClass('block random-player')
+          .append(this.$btn)
+          .insertAfter('#block-userlogin');
+
+        // events
+        this.$btn.on('click', this.toggleActive.bind(this));
+
+        // attach an event on AudioPlayer
+        _audio_player.on('audio-ended', this.onAudioPlayerEnded.bind(this));
+      },
+      shuffle(){
+        var tempPLaylist = [];
+        for (var i = this.playlist.length-1; i >= 0 ; i--) {
+          tempPLaylist.push(this.playlist[i]);
+        }
+        this.shuffledPlaylist = [];
+        while(tempPLaylist.length > 0){
+          var r = Math.floor(Math.random() * tempPLaylist.length);
+          this.shuffledPlaylist.push(tempPLaylist.splice(r,1)[0]);
+        }
+        console.log('RandomPlayer, this.shuffledPlaylist', this.shuffledPlaylist);
+      },
+      toggleActive(e){
+        if (this.active) {
+          this.stop();
+        }else{
+          this.shuffle();
+          this.play();
+        }
+      },
+      play(){
+        this.active = true;
+        this.next();
+      },
+      stop(){
+        this.active = false;
+        // stop audio player
+        _audio_player.stop();
+      },
+      next(){
+        if(this.shuffledPlaylist.length > 0)
+          _audio_player.openDocument(this.shuffledPlaylist.splice(0,1)[0]);
+      },
+      onAudioPlayerEnded(){
+        console.log('RandomPlayer : onAudioPlayerEnded()');
+        if(this.active){
+          this.next();
+        }
+      }
+    };
+
     //  ___             _         _   _
     // | _ \_ _ ___  __| |_  _ __| |_(_)___ _ _  ___
     // |  _/ '_/ _ \/ _` | || / _|  _| / _ \ ' \(_-<
@@ -425,7 +555,6 @@
       // });
     };
 
-
     //  ___            _   ___
     // | __| _ ___ _ _| |_| _ \__ _ __ _ ___
     // | _| '_/ _ \ ' \  _|  _/ _` / _` / -_)
@@ -458,4 +587,4 @@
   });
 
 
-})(jQuery);
+})(jQuery, Drupal, drupalSettings);

+ 9 - 4
sites/all/themes/custom/edlptheme/assets/styles/app.scss

@@ -1045,10 +1045,15 @@ footer{
       overflow: hidden;
     }
   }
-  // #block-fancylogin{
-  //   pointer-events: all;
-  //
-  // }
+
+  .block.random-player{
+    pointer-events: all;
+    a{
+      background-color: inherit;
+      cursor: pointer;
+    }
+  }
+
   #block-userlogin{
     pointer-events: all;
     // outline: 1px solid blue;