main.js 41 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241
  1. (function($, Drupal, drupalSettings) {
  2. EdlpTheme = function(){
  3. var _ajax_settings = drupalSettings.edlp_ajax;
  4. var _$body = $('body');
  5. var _is_front = _$body.is('.path-frontpage');
  6. var _corpus_ready = false;
  7. var _$corpus_canvas;
  8. var _$row = $('main[role="main"]>.layout-content>.row');
  9. var _$ajaxLinks;
  10. var _audioPlayer;
  11. var _randomPlayer;
  12. var _compoPlayer;
  13. // ___ _ _
  14. // |_ _|_ _ (_) |_
  15. // | || ' \| | _|
  16. // |___|_||_|_|\__|
  17. function init(){
  18. console.log("EdlpTheme init()");
  19. initAjaxLinks();
  20. initHistory();
  21. if(!drupalSettings.path.isFront)
  22. return;
  23. initEvents();
  24. _audioPlayer = new AudioPlayer();
  25. _compoPlayer = new CompoPlayer();
  26. };
  27. // ___ _
  28. // | __|_ _____ _ _| |_ ___
  29. // | _|\ V / -_) ' \ _(_-<
  30. // |___|\_/\___|_||_\__/__/
  31. function initEvents(){
  32. _$body
  33. .on('corpus-map-ready', onCorpusMapReady)
  34. .on('on-studio-chutier-updated', initAjaxLinks)
  35. .on('studio-initialized', function(e){
  36. _compoPlayer.newCompo();
  37. })
  38. .on('studio-not-active', function(e){
  39. _compoPlayer.deactivate();
  40. })
  41. .on('on-studio-compo-updated', function(e){
  42. initAjaxLinks();
  43. _compoPlayer.refresh();
  44. })
  45. .on('on-studio-compo-opened', function(e){
  46. initAjaxLinks();
  47. _compoPlayer.newCompo();
  48. })
  49. .on('search-results-loaded', initAjaxLinks)
  50. // do not close index or notice modale on entree click
  51. .on('open_entree', function(e){
  52. console.log('on_open_entree : e', e);
  53. closeAllModals();
  54. // add body class for currently loaded content
  55. // var body_classes = [
  56. // 'path-'+sys_path.replace(/\//g, '-'),
  57. // 'entity-type-'+data.entity_type,
  58. // 'bundle-'+data.bundle,
  59. // 'view-mode-'+data.view_mode
  60. // ];
  61. _$body.removeClass();//.addClass(body_classes.join(' '));
  62. // record new history state
  63. if(typeof e.url != 'undefined'){
  64. // var state = {
  65. // ajax_path:null,
  66. // sys_path:null,
  67. // entree_tid:e.tid
  68. // };
  69. var state = getSysPathState(e.sys_path);
  70. history.pushState(state, null, e.url);
  71. }
  72. })
  73. .on('close_entree', backToFrontPage);
  74. }
  75. // ___ _ _ ___
  76. // / __| __ _ _ ___| | | _ ) __ _ _ _ ___
  77. // \__ \/ _| '_/ _ \ | | _ \/ _` | '_(_-<
  78. // |___/\__|_| \___/_|_|___/\__,_|_| /__/
  79. function initScrollbars(){
  80. // console.log("initScrollbars");
  81. // TODO: find a better js scroll than overlayScrollbars which does not handle well max-height + overflow-y:auto;
  82. // $('.os-scroll').overlayScrollbars({
  83. // overflowBehavior:{
  84. // x:'h',
  85. // y:'scroll',
  86. // clipAlways:false
  87. // }
  88. // });
  89. };
  90. // _ _
  91. // /_\ (_)__ ___ __
  92. // / _ \ | / _` \ \ /
  93. // /_/ \_\/ \__,_/_\_\
  94. // |__/
  95. function getSysPathState(sys_path, view_mode){
  96. // console.log('Theme : getSysPathState', sys_path);
  97. var state = {
  98. 'sys_path':sys_path,
  99. 'ajax_path': sys_path
  100. };
  101. // convert node link to edlp_ajax_node module links
  102. var node_match = state.ajax_path.match(/^\/?(node\/(\d+))$/i);
  103. console.log('node_match', node_match);
  104. var term_match = state.ajax_path.match(/^\/?(taxonomy\/term\/(\d+))$/i);
  105. console.log('term_match', term_match);
  106. if(node_match){
  107. state.ajax_path = _ajax_settings.entityjson_path+'/'+node_match[1];
  108. state.node_nid = node_match[2];
  109. // check for viewmode attribute
  110. if(view_mode){
  111. state.ajax_path += '/'+view_mode;
  112. }
  113. }else if(term_match){
  114. // terms are always entrees, there's no other vocabulary links in front
  115. state.ajax_path = _ajax_settings.entityjson_path+'/'+term_match[1];
  116. state.ajax_path = state.ajax_path.replace(/taxonomy\/term/, 'taxonomy_term');
  117. state.entree_tid = term_match[2];
  118. // check for viewmode attribute
  119. if(view_mode){
  120. state.ajax_path += '/'+view_mode;
  121. state.view_mode = view_mode;
  122. }else{
  123. state.ajax_path = null;
  124. }
  125. }else{
  126. // convert other link to ajax
  127. // TODO: we assume that other links (no node, no term) are all from own modules (e.g. productions) !! may not be true !!
  128. state.ajax_path += '/ajax'
  129. }
  130. return state;
  131. };
  132. function ajaxLoadContent(state){
  133. console.log('ajaxLoadContent : url', state.url);
  134. _$body.addClass('ajax-loading');
  135. var path = window.location.origin + Drupal.url(state.ajax_path);
  136. $.getJSON(path, {})
  137. .done(function(data){
  138. onAjaxLoaded(data, state);
  139. })
  140. .fail(function(jqxhr, textStatus, error){
  141. onAjaxLoadError(jqxhr, textStatus, error, state.sys_path);
  142. });
  143. };
  144. function onAjaxLoadError(jqxhr, textStatus, error, sys_path){
  145. console.warn('ajaxlink load failed for '+sys_path+' : '+error, jqxhr.responseText);
  146. $('.ajax-loading').removeClass('ajax-loading');
  147. _$body.removeClass('ajax-loading');
  148. };
  149. function onAjaxLoaded(data, state){
  150. console.log('ajax loaded url:'+state.url+' ajax_path:'+state.ajax_path+' sys_path:'+state.sys_path);
  151. // console.log(data);
  152. // reset all style may been added by other pages (like masonry for productions)
  153. // and replace all content with newly loaded
  154. // TODO: build a system to replace or append contents (like studio + search)
  155. _$row.removeAttr('style').html(data.rendered);
  156. // add body class for currently loaded content
  157. var body_classes = [
  158. 'path-'+state.sys_path.replace(/\//g, '-'),
  159. 'entity-type-'+data.entity_type,
  160. 'bundle-'+data.bundle,
  161. 'view-mode-'+data.view_mode
  162. ];
  163. _$body.removeClass().addClass(body_classes.join(' '));
  164. // id node add a generic path-node class to body
  165. // m = state.sys_path.match(/^\/?(node\/\d+)$/g);
  166. // if(m)
  167. if(state.node_nid)
  168. _$body.addClass('path-edlp-node');
  169. // handle clicked link classes
  170. $('.ajax-loading').removeClass('ajax-loading');
  171. $('.is-active').removeClass('is-active');
  172. $('.is-active-trail').removeClass('is-active-trail');
  173. if(typeof state.selector != 'undefined'){
  174. // in case of entree link (actualy, selector is used only for entries links)
  175. console.log('selector', state.selector);
  176. $('a[selector="'+state.selector+'"]').addClass('is-active');
  177. }else{
  178. $('a[data-drupal-link-system-path="'+state.sys_path+'"]').addClass('is-active');
  179. // as new content is not related to entree, we trigger close entree
  180. _$body.trigger({'type':'new-content-not-entree-ajax-loaded'});
  181. }
  182. // if bundle page (productions) activate production links
  183. if (typeof data.bundle != 'undefined' && data.bundle == "page") {
  184. $('a[data-drupal-link-system-path="productions"]').addClass('is-active-trail');
  185. }
  186. // if node is in production menu tree, set first level of tree active, e.g. pieces sonores
  187. if (typeof data.menu_parents != 'undefined') {
  188. for (var i = 0; i < data.menu_parents.length; i++) {
  189. var menu_sys_path = data.menu_parents[i];
  190. $('a[data-drupal-link-system-path="'+menu_sys_path+'"]').addClass('is-active-trail');
  191. }
  192. }
  193. // if block attached (eg : from edlp_productions module)
  194. // not used anymore as production block is always present (but not visible)
  195. if(typeof data.block != 'undefined'){
  196. // if block not already added
  197. if(!$('#'+data.block.id, '.region-'+data.block.region).length){
  198. $('.region-'+data.block.region).append(data.block.rendered);
  199. }
  200. }
  201. // initScrollbars();
  202. if(state.sys_path == "productions"){
  203. initProductions();
  204. }else{
  205. addCloseModalBtnToCols();
  206. }
  207. // update the language switcher block if it comes in the response
  208. if(typeof data.translations_links != 'undefined'){
  209. console.log('state',state);
  210. var lang_code = drupalSettings.path.currentLanguage;
  211. var $links = $(data.translations_links);
  212. // set active link
  213. $links.find('li[hreflang="'+lang_code+'"]').addClass('is-active').find('a').addClass('is-active');
  214. if(state.view_mode){
  215. $links.find('a').each(function(i,e){
  216. var $a = $(this);
  217. $a.attr('href', $a.attr('href')+'#'+state.view_mode);
  218. });
  219. }
  220. // if(state.selector){
  221. // $links.find('a').attr('selector', state.selector);
  222. // }
  223. $('ul','.block.language-switcher-language-url').replaceWith($links);
  224. }
  225. initAjaxLinks();
  226. // trigger other modules behaviours
  227. _$body.trigger({'type':'new-content-ajax-loaded'});
  228. // and call druapl behaviours
  229. Drupal.attachBehaviors(_$row[0]);
  230. _$body.attr('booted', 'booted');
  231. _$body.removeClass('ajax-loading');
  232. // url is null means that we are loading content on popState event
  233. // so we don't record the state again
  234. if(state.url){
  235. // var state = {
  236. // ajax_path:ajax_path,
  237. // sys_path:sys_path,
  238. // };
  239. // console.log('url:'+url+' ; state',state);
  240. // console.log(window.location);
  241. // we can not pushestate with absolute url
  242. history.pushState(state, null, state.url);
  243. }
  244. };
  245. function addCloseModalBtnToCols(){
  246. $('.col', _$row).each(function(index, el) {
  247. if($('span.close-col-btn', this).length)
  248. return true;
  249. $(this).children('.wrapper').prepend($('<span>')
  250. .addClass('close-col-btn')
  251. .on('click', function(e){
  252. // check for theme attribute and emmit event
  253. var $col = $(this).parents('.col');
  254. var theme = $col.attr('theme');
  255. if(theme != ''){
  256. _$body.trigger({'type':theme+'-col-closed'});
  257. }
  258. // remove the col
  259. $col.remove();
  260. // if row is empty and we are not in productions call closeAllModals()
  261. if(!$('.col', _$row).length
  262. && !_$body.is('.entity-type-node.bundle-page')){
  263. backToFrontPage();
  264. }
  265. })
  266. );
  267. });
  268. };
  269. // _ _ ___ _ _
  270. // /_\ (_)__ ___ __ | _ ) |___ __| |__ ___
  271. // / _ \ | / _` \ \ / | _ \ / _ \/ _| / /(_-<
  272. // /_/ \_\/ \__,_/_\_\ |___/_\___/\__|_\_\/__/
  273. // |__/
  274. // NOT USED (YET)
  275. function refreshAllBlocks(){
  276. var path = window.location.origin + Drupal.url(_ajax_settings.blocksjson_path);
  277. $.getJSON(path, {})
  278. .done(function(data){
  279. onAjaxBlockLoaded(data);
  280. })
  281. .fail(function(jqxhr, textStatus, error){
  282. onAjaxBlockLoadError(jqxhr, textStatus, error);
  283. });
  284. };
  285. function onAjaxBlockLoadError(jqxhr, textStatus, error){
  286. console.warn('ajax block load failed: '+error, jqxhr.responseText);
  287. };
  288. function onAjaxBlockLoaded(data){
  289. console.log('onAjaxBlockLoaded', data);
  290. // TODO: update each blocks (exepted language switcher)
  291. for (var blockname in data.blocks) {
  292. var block = data.blocks[blockname];
  293. console.log(blockname, block);
  294. $(block.id).replaceWith(block.rendered);
  295. }
  296. };
  297. // _ _ _ _
  298. // | || (_)__| |_ ___ _ _ _ _
  299. // | __ | (_-< _/ _ \ '_| || |
  300. // |_||_|_/__/\__\___/_| \_, |
  301. // |__/
  302. function initHistory(){
  303. initFirstLoad();
  304. window.addEventListener('popstate', onHistoryPopState);
  305. };
  306. function initFirstLoad(){
  307. console.log('theme : initFirstLoad()');
  308. var origin_sys_path = window.localStorage.getItem('edlp_origin_path');
  309. if(origin_sys_path){
  310. var origin_url = window.localStorage.getItem('edlp_origin_url');
  311. // origin_hash is used as viewmode for taxonomy term entrees load (index or notice)
  312. var origin_hash = window.localStorage.getItem('edlp_origin_hash');
  313. var view_mode = origin_hash.replace('#', '');
  314. if(view_mode){
  315. // TODO first load with index or notice do not activate the right link (activate all)
  316. var $link = $('[href="'+origin_url+'"][viewmode="'+view_mode+'"]');
  317. var selector = $link.attr('selector') || null;
  318. if(selector){
  319. // in case of entree link (actualy, selector is used only for entries links)
  320. if(_corpus_ready){
  321. _$corpus_canvas.trigger({
  322. type:'open-entree',
  323. tid:$link.attr('tid')
  324. });
  325. }else{
  326. // else : EdlpCorpus will check when ready if entry item (notice or index) is already .is-active
  327. // .is-active class is added by onAjaxLoaded() (when content is loaded)
  328. // but what if corpus ready before onAjaxLoaded
  329. $('li.entree[tid="'+$link.attr('tid')+'"] a.term-link').addClass('is-active');
  330. }
  331. }
  332. }
  333. // create history state
  334. var state = getSysPathState(origin_sys_path, view_mode);
  335. // only if not entree path
  336. if(state.ajax_path){
  337. // load content through ajax
  338. // ajaxLoadContent(null, state.sys_path, state.ajax_path, selector);
  339. ajaxLoadContent(state);
  340. }
  341. // TODO what about entree alone (without notice or index)
  342. if(state.entree_tid){
  343. openEntree(state.entree_tid);
  344. }
  345. // record history state
  346. history.replaceState(state, null, origin_url+origin_hash);
  347. // reset the storage
  348. window.localStorage.removeItem("edlp_origin_path");
  349. window.localStorage.removeItem("edlp_origin_url");
  350. }else{
  351. history.replaceState({home:true}, null, window.location.pathname);
  352. _$body.attr('booted', 'booted');
  353. }
  354. };
  355. function onHistoryPopState(e){
  356. console.log('onPopState',e.state);
  357. if(e.state.home){
  358. backToFrontPage(true);
  359. }else{
  360. if(e.state.entree_tid){
  361. openEntree(e.state.entree_tid);
  362. }
  363. if(e.state.ajax_path){
  364. e.state.url = null;
  365. // ajaxLoadContent(null, e.state.sys_path, e.state.ajax_path)
  366. ajaxLoadContent(e.state);
  367. }
  368. }
  369. };
  370. // _ _ _ _ _
  371. // /_\ (_)__ ___ _| | (_)_ _ | |__ ___
  372. // / _ \ | / _` \ \ / |__| | ' \| / /(_-<
  373. // /_/ \_\/ \__,_/_\_\____|_|_||_|_\_\/__/
  374. // |__/
  375. function initAjaxLinks(){
  376. // console.log('initAjaxLinks');
  377. $('a.site-name', '#block-edlptheme-branding')
  378. .add('a', '#block-mainnavigation')
  379. // .add('a', '.block.language-switcher-language-url')
  380. .add('a', '#block-footer.menu--footer')
  381. .add('a', '#block-productions')
  382. .add('a', 'article.node:not(.node--type-enregistrement) h2.node-title')
  383. .add('a', '.productions-subtree')
  384. .add('a', '.productions-parent')
  385. // .add('a.index-link, a.notice-link', '#block-edlpentreesblock')
  386. .addClass('ajax-link');
  387. _$ajaxLinks = $('.ajax-link:not(.ajax-enabled)')
  388. .each(function(i,e){
  389. var $this = $(this);
  390. // avoid already ajaxified links
  391. if($this.is('.ajax-enable')) return;
  392. if($this.attr('data-drupal-link-system-path')){
  393. $this.on('click', onClickAjaxLink).addClass('ajax-enable');
  394. }
  395. });
  396. };
  397. function onClickAjaxLink(e){
  398. e.preventDefault();
  399. var $link = $(this);
  400. if($link.is('.is-active'))
  401. return false;
  402. // Audio links
  403. // launch audio player and stop here
  404. if($link.is('.audio-link')){
  405. _audioPlayer
  406. .emmit('stop-shuffle')
  407. .openDocument({
  408. nid:$link.attr('nid'),
  409. audio_url:$link.attr('audio_url')
  410. });
  411. return false;
  412. }
  413. // NOT USED (YET)
  414. // if($link.is('.language-link')){
  415. // TODO: change global site current language
  416. // load all blocks translated (exepted language switcher) ... :/
  417. // refreshAllBlocks();
  418. // TODO: use a promise to initAjaxLinks again after blocks updated
  419. // then let load the content
  420. // }
  421. // other links
  422. var sys_path = $(this).attr('data-drupal-link-system-path');
  423. // front page
  424. // just remove contents and stop here
  425. if(sys_path == '<front>'){
  426. backToFrontPage();
  427. return false;
  428. }
  429. var view_mode = $link.attr('viewmode');
  430. var state = getSysPathState(sys_path, view_mode);
  431. state.url = $(this).attr('href');
  432. if(view_mode){
  433. state.url += "#"+view_mode;
  434. }
  435. if($link.is('[selector]')){
  436. state.selector = $link.attr('selector');
  437. }
  438. $link.addClass('ajax-loading');
  439. // ajaxLoadContent(url, sys_path, state.ajax_path, selector);
  440. ajaxLoadContent(state);
  441. return false;
  442. };
  443. // ___
  444. // / __|___ _ _ _ __ _ _ ___
  445. // | (__/ _ \ '_| '_ \ || (_-<
  446. // \___\___/_| | .__/\_,_/__/
  447. // |_|
  448. function onCorpusMapReady(e){
  449. //console.log('theme : onCorpusReady', e);
  450. _corpus_ready = true;
  451. _$corpus_canvas = $('canvas#corpus-map');
  452. _$corpus_canvas
  453. .on('corpus-cliked-on-map', function(e) {
  454. //console.log('theme : corpus-cliked-on-map');
  455. backToFrontPage();
  456. })
  457. .on('corpus-cliked-on-node', function(e) {
  458. //console.log('theme : corpus-cliked-on-node', e);
  459. _audioPlayer
  460. .emmit('stop-shuffle')
  461. .openDocument(e.target_node);
  462. });
  463. _randomPlayer = new RandomPlayer(e.playlist);
  464. _$body.attr('corpus-map', 'ready');
  465. }
  466. function openEntree(tid){
  467. if(tid){
  468. closeAllModals();
  469. _$body.removeClass();//.addClass(body_classes.join(' '));
  470. // open entree
  471. if(_corpus_ready){
  472. _$corpus_canvas.trigger({
  473. type:'open-entree',
  474. tid:tid
  475. });
  476. }else{
  477. // else : EdlpCorpus will check when ready if entry item (notice or index) is already .is-active
  478. $('li.entree[tid="'+tid+'"] a.term-link').addClass('is-active');
  479. }
  480. }
  481. };
  482. // _ _ _
  483. // /_\ _ _ __| (_)___
  484. // / _ \ || / _` | / _ \
  485. // /_/ \_\_,_\__,_|_\___/
  486. //
  487. // https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement
  488. // https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/samples/gg589528%28v%3dvs.85%29
  489. // https://www.binarytides.com/using-html5-audio-element-javascript/
  490. function AudioPlayer(){
  491. var that = this;
  492. this.fid;
  493. this.audio = new Audio();
  494. // audio events
  495. this.audio_events = ["loadedmetadata","canplay","playing","pause","timeupdate","ended"];
  496. // UI dom objects
  497. this.$container = $('<div id="audio-player">');
  498. // btns
  499. this.$btns = $('<div>').addClass('btns').appendTo(this.$container);
  500. this.$previous = $('<div>').addClass('previous').appendTo(this.$btns);
  501. this.$playpause = $('<div>').addClass('play-pause').appendTo(this.$btns);
  502. this.$next = $('<div>').addClass('next').appendTo(this.$btns);
  503. // timeline
  504. this.$timelinecont= $('<div>').addClass('time-line-container').appendTo(this.$container);
  505. this.$timeline = $('<div>').addClass('time-line').appendTo(this.$timelinecont);
  506. this.$loader = $('<div>').addClass('loader').appendTo(this.$timeline);
  507. this.$cursor = $('<div>').addClass('cursor').appendTo(this.$timeline);
  508. // time
  509. this.$time = $('<div>').addClass('time').appendTo(this.$container);
  510. this.$currentTime = $('<div>').addClass('current-time').html('00:00').appendTo(this.$time);
  511. this.$duration = $('<div>').addClass('duration').html('00:00').appendTo(this.$time);
  512. // favoris
  513. this.$fav = $('<div>').addClass('favoris').appendTo(this.$container);
  514. // cartel
  515. this.$cartel = $('<div>').addClass('cartel').appendTo(this.$container);
  516. // hiding
  517. this.hideTimer = false;
  518. this.hideTimeMS = 10000;
  519. // history
  520. this.currentHistoricIndex = null;
  521. this.historic = [];
  522. this.shuffle_is_active = false;
  523. // object events
  524. this.event_handlers = {
  525. 'audio-open-document':[],
  526. 'audio-play':[],
  527. 'audio-pause':[],
  528. 'audio-play-next':[],
  529. 'audio-ended':[],
  530. 'stop-shuffle':[]
  531. };
  532. this.init();
  533. };
  534. AudioPlayer.prototype = {
  535. init(){
  536. // append ui to document
  537. this.$container.appendTo('header[role="banner"] .region-header');
  538. // record timeline width
  539. this.timeline_w = parseInt(this.$timeline.width());
  540. // init audio events
  541. var fn = '';
  542. for (var i = 0; i < this.audio_events.length; i++) {
  543. fn = this.audio_events[i];
  544. // capitalize first letter of event (only cosmetic :p )
  545. fn = 'on'+fn.charAt(0).toUpperCase()+fn.slice(1);
  546. this.audio.addEventListener(
  547. this.audio_events[i],
  548. this[fn].bind(this),
  549. true);
  550. }
  551. // init btns events
  552. this.$previous.on('click', this.playPrevious.bind(this));
  553. this.$playpause.on('click', this.togglePlayPause.bind(this));
  554. this.$next.on('click', this.playNext.bind(this));
  555. },
  556. openDocument(node, caller){
  557. // console.log('AudioPlayer openDocument', node);
  558. if(typeof node == 'undefined'
  559. || typeof node.nid == 'undefined'
  560. || typeof node.audio_url == 'undfined'){
  561. console.warn('AudioPlayer openDocument() node is malformed', node);
  562. return false;
  563. }
  564. this.historic.push(node);
  565. this.currentHistoricIndex = this.historic.length-1;
  566. // this.shuffle_mode = shuffle_mode || false;
  567. // TODO: add an hash tag to be able to share and play audio from any where
  568. this.emmit('audio-open-document', {caller:caller});
  569. this.launch();
  570. },
  571. launch(){
  572. this.clearTimeOutToHide();
  573. this.setSRC(this.historic[this.currentHistoricIndex].audio_url);
  574. this.loadNode(this.historic[this.currentHistoricIndex].nid);
  575. // emmit new playing doc (e.g.: corpus map nowing that audio played from RandomPlayer)
  576. try {
  577. _$corpus_canvas.trigger({
  578. 'type':'audio-node-opened',
  579. 'nid':this.historic[this.currentHistoricIndex].nid
  580. });
  581. } catch (e) {
  582. console.info('AudioPlayer : _$corpus_canvas does not exists');
  583. }
  584. this.showHidePreviousBtn();
  585. this.showHideNextBtn();
  586. this.show();
  587. },
  588. // audio functions
  589. setSRC(url){
  590. // console.log('AudioPlayer setSRC : url', url);
  591. this.audio.src = url;
  592. },
  593. onLoadedmetadata(){
  594. var rem = parseInt(this.audio.duration, 10),
  595. mins = Math.floor(rem/60,10),
  596. secs = rem - mins*60;
  597. this.$duration.html('<span>'+(mins<10 ? '0':'')+mins+':'+(secs<10 ? '0':'')+secs+'</span>');
  598. this.updateLoadingBar();
  599. },
  600. updateLoadingBar(){
  601. this.$loader.css({
  602. 'width':parseInt((100 * this.audio.buffered.end(0) / this.audio.duration), 10)+'%'
  603. });
  604. if( this.audio.buffered.end(0) < this.audio.duration ){
  605. // loop through this function until file is fully loaded
  606. var that = this;
  607. window.requestAnimationFrame(that.updateLoadingBar.bind(that));
  608. }else{
  609. //console.log('Audio fully loaded');
  610. }
  611. },
  612. onCanplay(){
  613. this.play();
  614. },
  615. play(){
  616. this.clearTimeOutToHide();
  617. this.audio.play();
  618. },
  619. playPrevious(){
  620. if(this.currentHistoricIndex > 0){
  621. this.currentHistoricIndex -= 1;
  622. this.launch();
  623. }
  624. },
  625. playNext(){
  626. if(this.currentHistoricIndex < this.historic.length-1){
  627. this.currentHistoricIndex += 1;
  628. this.launch();
  629. }else{
  630. this.emmit('audio-play-next');
  631. }
  632. },
  633. togglePlayPause(e){
  634. if(this.audio.paused){
  635. this.audio.play();
  636. }else{
  637. this.audio.pause();
  638. }
  639. },
  640. stop(){
  641. // console.log('AudioPlayer stop()');
  642. this.audio.pause();
  643. this.timeOutToHide();
  644. },
  645. // audio events
  646. onPlaying(){
  647. this.$btns.addClass('is-playing');
  648. this.emmit('audio-play');
  649. },
  650. onPause(){
  651. this.$btns.removeClass('is-playing');
  652. this.emmit('audio-pause');
  653. },
  654. onTimeupdate(){
  655. // move cursor
  656. this.$cursor.css({
  657. 'left':(this.audio.currentTime/this.audio.duration * this.timeline_w)+"px"
  658. });
  659. // update time text display
  660. var rem = parseInt(this.audio.currentTime, 10),
  661. mins = Math.floor(rem/60,10),
  662. secs = rem - mins*60;
  663. this.$currentTime.html('<span>'+(mins<10 ? '0':'')+mins+':'+(secs<10 ? '0':'')+secs+'</span>');
  664. },
  665. onEnded(){
  666. this.emmit('audio-ended');
  667. this.stop();
  668. },
  669. // cartel functions
  670. loadNode(nid){
  671. this.$cartel.addClass('loading');
  672. var vm = 'player_cartel';
  673. var ajax_path = _ajax_settings.entityjson_path+'/node/'+nid+'/'+vm;
  674. var path = window.location.origin + Drupal.url(ajax_path);
  675. $.getJSON(path, {})
  676. .done(this.onNodeLoaded.bind(this))
  677. .fail(this.onNodeLoadFail.bind(this));
  678. },
  679. onNodeLoaded(data){
  680. // console.log('AudioPlayer node loaded');
  681. this.$cartel.html(data.rendered).removeClass('loading');
  682. _$body.trigger({'type':'new-audio-cartel-loaded'});
  683. initAjaxLinks();
  684. },
  685. onNodeLoadFail(jqxhr, textStatus, error){
  686. console.warn('AudioPlayer node load failed', jqxhr.responseText);
  687. this.$cartel.removeClass('loading').html('');
  688. },
  689. // global
  690. show(){
  691. this.$container.addClass('visible');
  692. },
  693. showHidePreviousBtn(){
  694. if(this.historic.length > 1 && this.currentHistoricIndex > 0){
  695. this.$previous.addClass('is-active');
  696. }else{
  697. this.$previous.removeClass('is-active');
  698. }
  699. },
  700. showHideNextBtn(){
  701. if(this.currentHistoricIndex < this.historic.length-1 || this.shuffle_is_active){
  702. this.$next.addClass('is-active');
  703. }else{
  704. this.$next.removeClass('is-active');
  705. }
  706. },
  707. timeOutToHide(){
  708. // console.log('AudioPlayer timeOutToHide()');
  709. this.clearTimeOutToHide();
  710. this.hideTimer = setTimeout(this.hide.bind(this), this.hideTimeMS);
  711. },
  712. clearTimeOutToHide(){
  713. // console.log('AudioPlayer clearTimeOutToHide()',this.hideTimer);
  714. if(this.hideTimer){
  715. clearTimeout(this.hideTimer);
  716. this.hideTimer = false;
  717. }
  718. },
  719. hide(){
  720. // console.log('AudioPlayer hide()');
  721. this.$container.removeClass('visible');
  722. // trigger highlighted node remove on corpus map
  723. try {
  724. _$corpus_canvas.trigger('audio-node-closed');
  725. } catch (e) {
  726. console.info('AudioPlayer hide() : _$corpus_canvas does not exists');
  727. }
  728. },
  729. // object events
  730. on(event_name, handler){
  731. if(typeof this.event_handlers[event_name] == 'undefined'){
  732. console.warn('AudioPlayer : event '+event_name+' does not exists');
  733. }
  734. this.event_handlers[event_name].push(handler);
  735. return this;
  736. },
  737. emmit(event_name, args){
  738. // console.log('AudioPlayer emmit() event_name', event_name);
  739. // console.log('AudioPlayer emmit() handlers', this.event_handlers[event_name]);
  740. var handler;
  741. var args = args || {};
  742. for (var i = this.event_handlers[event_name].length-1; i >= 0 ; i--) {
  743. handler = this.event_handlers[event_name][i];
  744. // console.log('AudioPlayer emmit() loop handler', handler);
  745. setTimeout(function(){
  746. // console.log('AudioPlayer emmit() timeout handler', handler);
  747. handler(args);
  748. }, 0);
  749. }
  750. return this;
  751. },
  752. }
  753. // ___ _ ___ _
  754. // | _ \__ _ _ _ __| |___ _ __ | _ \ |__ _ _ _ ___ _ _
  755. // | / _` | ' \/ _` / _ \ ' \| _/ / _` | || / -_) '_|
  756. // |_|_\__,_|_||_\__,_\___/_|_|_|_| |_\__,_|\_, \___|_|
  757. // |__/
  758. function RandomPlayer(playlist){
  759. this.active = false;
  760. this.playlist = playlist;
  761. this.$btn = $('<a>').html('Shuffle').addClass('random-player-btn');
  762. this.init();
  763. };
  764. RandomPlayer.prototype = {
  765. init(){
  766. // this.shuffle();
  767. $('<div>')
  768. .addClass('block random-player')
  769. .append(this.$btn)
  770. // .insertAfter('#block-userlogin, #block-studiolinkblock');
  771. .prependTo('.region-footer-right');
  772. // events
  773. this.$btn.on('click', this.toggleActive.bind(this));
  774. // attach an event on AudioPlayer
  775. _audioPlayer
  776. .on('audio-ended', this.onAudioPlayerEnded.bind(this))
  777. .on('audio-play-next', this.onAudioPlayNext.bind(this))
  778. .on('stop-shuffle', this.stop.bind(this));
  779. },
  780. shuffle(){
  781. var tempPLaylist = [];
  782. for (var i = this.playlist.length-1; i >= 0 ; i--) {
  783. tempPLaylist.push(this.playlist[i]);
  784. }
  785. this.shuffledPlaylist = [];
  786. while(tempPLaylist.length > 0){
  787. var r = Math.floor(Math.random() * tempPLaylist.length);
  788. this.shuffledPlaylist.push(tempPLaylist.splice(r,1)[0]);
  789. }
  790. //console.log('RandomPlayer, this.shuffledPlaylist', this.shuffledPlaylist);
  791. },
  792. toggleActive(e){
  793. if (this.active) {
  794. this.stop();
  795. }else{
  796. this.start();
  797. }
  798. },
  799. start(){
  800. this.active = _audioPlayer.shuffle_is_active = true;
  801. this.$btn.addClass('is-active');
  802. this.shuffle();
  803. this.next();
  804. },
  805. stop(){
  806. this.active = _audioPlayer.shuffle_is_active = false;
  807. this.$btn.removeClass('is-active');
  808. // stop audio player
  809. // _audioPlayer.stop();
  810. },
  811. next(){
  812. if(this.active && this.shuffledPlaylist.length > 0)
  813. _audioPlayer.openDocument(this.shuffledPlaylist.splice(0,1)[0]);
  814. },
  815. onAudioPlayNext(){
  816. //console.log('RandomPlayer : onAudioPlayNext()');
  817. this.next();
  818. },
  819. onAudioPlayerEnded(){
  820. //console.log('RandomPlayer : onAudioPlayerEnded()');
  821. this.next();
  822. }
  823. };
  824. // ___ ___ _
  825. // / __|___ _ __ _ __ ___| _ \ |__ _ _ _ ___ _ _
  826. // | (__/ _ \ ' \| '_ \/ _ \ _/ / _` | || / -_) '_|
  827. // \___\___/_|_|_| .__/\___/_| |_\__,_|\_, \___|_|
  828. // |_| |__/
  829. function CompoPlayer(){
  830. this.active = false;
  831. this.playing = false;
  832. this.paused = false;
  833. this.playlist = [];
  834. this.current_index = 0;
  835. this.$composer = null;
  836. this.$compo = null;
  837. this.$controls = null;
  838. this.init();
  839. };
  840. CompoPlayer.prototype = {
  841. init(){
  842. // console.log('CompoPlayer init()');
  843. // attach an event on AudioPlayer
  844. _audioPlayer
  845. .on('audio-open-document', this.onAudioOpenDocument.bind(this))
  846. .on('audio-play', this.onAudioPlayerPlay.bind(this))
  847. .on('audio-pause', this.onAudioPlayerPause.bind(this))
  848. .on('audio-ended', this.onAudioPlayerEnded.bind(this));
  849. // .on('audio-play-next', this.onAudioPlayNext.bind(this));
  850. // this.newCompo();
  851. },
  852. newCompo(){
  853. //console.log('CompoPlayer newCompo()');
  854. // this.$compo = $('.composition_ui .composer .composition');
  855. this.initControls();
  856. },
  857. initControls(){
  858. //console.log('CompoPlayer initControls()');
  859. this.$composer = $('.composition_ui .composer');
  860. this.$compo = $('.composition_ui .composer .composition');
  861. this.$controls = $('.composition_ui .composer .compo-player-controls');
  862. if(!this.$controls.is('.ready') && this.$compo){
  863. this.$previous = $('<div>').addClass('previous')
  864. .on('click', this.prev.bind(this))
  865. .appendTo(this.$controls);
  866. this.$playpause = $('<div>').addClass('play-pause')
  867. .on('click', this.togglePlayPause.bind(this))
  868. .appendTo(this.$controls);
  869. this.$next = $('<div>').addClass('next')
  870. .on('click', this.next.bind(this))
  871. .appendTo(this.$controls);
  872. this.$controls.addClass('ready');
  873. this.refresh();
  874. this.active = true;
  875. // this.newCompo();
  876. }
  877. },
  878. refresh(){
  879. // console.log('CompoPlayer refresh(), this', this);
  880. this.stop();
  881. // load new playlist
  882. this.playlist = [];
  883. var that = this;
  884. $('.field--name-documents .field__item',this.$compo).each(function(i,el){
  885. var $link = $('a.audio-link',this);
  886. that.playlist.push({
  887. item:$(this),
  888. audio_url:$link.attr("audio_url"),
  889. nid:$link.attr("nid"),
  890. });
  891. });
  892. this.showHideControls();
  893. },
  894. togglePlayPause(){
  895. // console.log('CompoPlayer togglePlayPause');
  896. if (this.playing && !this.paused) {
  897. this.pause();
  898. }else{
  899. if(this.playing && this.paused){
  900. this.play();
  901. }else{
  902. this.start();
  903. }
  904. }
  905. },
  906. start(){
  907. //console.log('start');
  908. // console.log('CompoPlayer start()');
  909. this.playing = true;
  910. this.play();
  911. },
  912. play(){
  913. // console.log('play');
  914. if(this.paused){
  915. this.paused = false;
  916. _audioPlayer.play();
  917. }else{
  918. _audioPlayer.openDocument(this.playlist[this.current_index], this);
  919. }
  920. this.setActiveItem().showHideControls();
  921. },
  922. pause(){
  923. //console.log('pause');
  924. this.paused = true;
  925. this.showHideControls();
  926. _audioPlayer.stop();
  927. },
  928. next(){
  929. // console.log('CompoPlayer next()');
  930. if(this.playing){
  931. this.current_index += 1;
  932. if(this.current_index < this.playlist.length){
  933. this.play();
  934. }else{
  935. this.stop();
  936. }
  937. }
  938. },
  939. prev(){
  940. // console.log('CompoPlayer prev()');
  941. if(this.playing){
  942. this.current_index -= 1;
  943. if(this.current_index >= 0){
  944. this.play();
  945. }else{
  946. this.stop();
  947. }
  948. }
  949. },
  950. stop(){
  951. _audioPlayer.stop();
  952. this.reset();
  953. },
  954. reset(){
  955. this.playing = false;
  956. this.paused = false;
  957. this.resetIndex();
  958. },
  959. resetIndex(){
  960. this.current_index = 0;
  961. this.showHideControls().resetActiveItems();
  962. },
  963. setActiveItem(){
  964. this.resetActiveItems();
  965. if(this.playing && this.current_index >= 0){
  966. this.playlist[this.current_index].item.addClass('is-active');
  967. }
  968. // this call shoud not be here
  969. this.showHideControls();
  970. return this;
  971. },
  972. resetActiveItems(){
  973. for (var n = 0; n < this.playlist.length; n++) {
  974. // console.log('node',node);
  975. this.playlist[n].item.removeClass('is-active');
  976. }
  977. return this;
  978. },
  979. showHideControls(){
  980. // console.log('CompoPlayer showHideNextBtn(), playing:'+this.playing+', paused:'+this.paused);
  981. // global playing
  982. if(this.$controls){
  983. if(this.playing && !this.paused){
  984. this.$controls.addClass('is-playing');
  985. }else{
  986. this.$controls.removeClass('is-playing');
  987. }
  988. }
  989. // playpause
  990. if(this.$playpause){
  991. if(this.playlist.length > 0){
  992. this.$playpause.addClass('is-active');
  993. }else{
  994. this.$playpause.removeClass('is-active');
  995. }
  996. }
  997. // next
  998. if(this.$next){
  999. if(this.playing && this.playlist.length > 1 && this.current_index < this.playlist.length -1){
  1000. this.$next.addClass('is-active');
  1001. }else{
  1002. this.$next.removeClass('is-active');
  1003. }
  1004. }
  1005. // previous
  1006. if(this.$previous){
  1007. if(this.playing && this.playlist.length > 1 && this.current_index > 0){
  1008. this.$previous.addClass('is-active');
  1009. }else{
  1010. this.$previous.removeClass('is-active');
  1011. }
  1012. }
  1013. return this;
  1014. },
  1015. deactivate(){
  1016. this.stop();
  1017. this.active = false;
  1018. },
  1019. // _audioPlayer events
  1020. onAudioOpenDocument(args){
  1021. if(args.caller !== this){
  1022. // console.log('CompoPlayer onAudioOpenDocument() called by other');
  1023. this.reset();
  1024. }
  1025. // else{
  1026. // // console.log('CompoPlayer onAudioOpenDocument() self calling');
  1027. // }
  1028. },
  1029. onAudioPlayerPlay(){
  1030. if(this.playing && this.paused){
  1031. this.paused = false;
  1032. this.showHideControls();
  1033. }
  1034. },
  1035. onAudioPlayerPause(){
  1036. if(this.playing && !this.paused){
  1037. this.paused = true;
  1038. this.showHideControls();
  1039. }
  1040. },
  1041. onAudioPlayerEnded(){
  1042. this.next();
  1043. },
  1044. // onAudioPlayNext(){
  1045. // this.next();
  1046. // }
  1047. };
  1048. // ___ _ ___
  1049. // | __| _ ___ _ _| |_| _ \__ _ __ _ ___
  1050. // | _| '_/ _ \ ' \ _| _/ _` / _` / -_)
  1051. // |_||_| \___/_||_\__|_| \__,_\__, \___|
  1052. // |___/
  1053. function backToFrontPage(pop_state){
  1054. closeAllModals();
  1055. // assume we are going back to front page
  1056. $('body').removeClass().addClass('path-frontpage');
  1057. $('a[data-drupal-link-system-path="<front>"]').addClass('is-active');
  1058. // close entrees
  1059. _$corpus_canvas.trigger({'type':'close-all-entree'});
  1060. if(!pop_state){
  1061. history.pushState({home:true}, null, window.location.origin);
  1062. }
  1063. }
  1064. function initHome(){
  1065. addCloseModalBtnToCols();
  1066. // console.log('theme : initHome');
  1067. // console.log('theme : initProductions');
  1068. var $grid = $('.grid',_$row).masonry({
  1069. itemSelector:'.col',
  1070. columnWidth:'.col-2',
  1071. horizontalOrder: true,
  1072. containerStyle: null,
  1073. // disable initial layout
  1074. // initLayout: false,
  1075. });
  1076. // bind event
  1077. // $grid.masonry( 'on', 'layoutComplete', function() {
  1078. // console.log('layout is complete');
  1079. // });
  1080. // layout Masonry after each image loads
  1081. $grid.imagesLoaded().progress( function() {
  1082. $grid.masonry('layout');
  1083. });
  1084. $grid.imagesLoaded(function(){
  1085. $grid.masonry('layout');
  1086. });
  1087. }
  1088. // ___ _ _ _
  1089. // | _ \_ _ ___ __| |_ _ __| |_(_)___ _ _ ___
  1090. // | _/ '_/ _ \/ _` | || / _| _| / _ \ ' \(_-<
  1091. // |_| |_| \___/\__,_|\_,_\__|\__|_\___/_||_/__/
  1092. function initProductions(){
  1093. // console.log('theme : initProductions');
  1094. var $grid = $('.grid',_$row).masonry({
  1095. itemSelector:'.col',
  1096. columnWidth:'.col-2',
  1097. horizontalOrder: true,
  1098. containerStyle: null,
  1099. // disable initial layout
  1100. // initLayout: false,
  1101. });
  1102. // bind event
  1103. // $grid.masonry( 'on', 'layoutComplete', function() {
  1104. // console.log('layout is complete');
  1105. // });
  1106. // layout Masonry after each image loads
  1107. $grid.imagesLoaded().progress( function() {
  1108. $grid.masonry('layout');
  1109. });
  1110. $grid.imagesLoaded(function(){
  1111. $grid.masonry('layout');
  1112. });
  1113. };
  1114. // __ __ _ _
  1115. // | \/ |___ __| |__ _| |___
  1116. // | |\/| / _ \/ _` / _` | (_-<
  1117. // |_| |_\___/\__,_\__,_|_/__/
  1118. function closeAllModals(){
  1119. //console.log('theme : closeAllModals');
  1120. // TODO: animate the remove
  1121. _$row.html('');
  1122. _$ajaxLinks.removeClass('is-active');
  1123. _$body.trigger({'type':'all-modal-closed'});
  1124. };
  1125. // _ _ _
  1126. // | || |___| |_ __ ___ _ _ ___
  1127. // | __ / -_) | '_ \/ -_) '_(_-<
  1128. // |_||_\___|_| .__/\___|_| /__/
  1129. // |_|
  1130. // https://plainjs.com/javascript/utilities/set-cookie-get-cookie-and-delete-cookie-5/
  1131. // function getCookie(name) {
  1132. // var v = document.cookie.match('(^|;) ?' + name + '=([^;]*)(;|$)');
  1133. // return v ? v[2] : null;
  1134. // }
  1135. // function setCookie(name, value, days) {
  1136. // var d = new Date;
  1137. // d.setTime(d.getTime() + 24*60*60*1000*days);
  1138. // document.cookie = name + "=" + value + ";path=/;expires=" + d.toGMTString();
  1139. // }
  1140. // function deleteCookie(name) { setCookie(name, '', -1); }
  1141. init();
  1142. } // end EdlpTheme()
  1143. $(document).ready(function($) {
  1144. var edlptheme = new EdlpTheme();
  1145. });
  1146. })(jQuery, Drupal, drupalSettings);