launcher.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. /**
  2. * Copyright (c) UNA, Inc - https://una.io
  3. * MIT License - https://opensource.org/licenses/MIT
  4. *
  5. * @defgroup UnaStudio UNA Studio
  6. * @{
  7. */
  8. function BxDolStudioLauncher(oOptions) {
  9. this.sActionsUrl = oOptions.sActionUrl;
  10. this.sObjName = oOptions.sObjName == undefined ? 'oBxDolStudioLauncher' : oOptions.sObjName;
  11. this.sAnimationEffect = oOptions.sAnimationEffect == undefined ? 'fade' : oOptions.sAnimationEffect;
  12. this.iAnimationSpeed = oOptions.iAnimationSpeed == undefined ? 'fast' : oOptions.iAnimationSpeed;
  13. this.bInit = oOptions.bInit == undefined ? true : oOptions.bInit;
  14. //--- Live region texts (BxBaseStudioLauncher::getPageJsCode): search results and moves in edit mode.
  15. this.sTxtMatches = oOptions.sTxtMatches == undefined ? '{0}' : oOptions.sTxtMatches;
  16. this.sTxtMatchesOne = oOptions.sTxtMatchesOne == undefined ? '{0}' : oOptions.sTxtMatchesOne;
  17. this.sTxtMoved = oOptions.sTxtMoved == undefined ? '{0}: {1}/{2}' : oOptions.sTxtMoved;
  18. //--- Jitter Settings ---//
  19. this.bJitterMode = false;
  20. this.aJitterConf = {
  21. item: '.bx-std-widget-icon',
  22. elements: '.bx-std-widget-actions,.bx-std-widget-icon-jitter,.bx-std-widget-caption-jitter'
  23. };
  24. this.aSortingConf = {
  25. parent: '.bx-std-widgets',
  26. item: '.bx-std-widget',
  27. placeholder: 'bx-std-widget bx-std-widget-empty'
  28. };
  29. if(this.bInit)
  30. this.init();
  31. }
  32. BxDolStudioLauncher.prototype.init = function() {
  33. var $this = this;
  34. $(document).ready(function() {
  35. var hammertime = new Hammer($('.bx-std-widgets').get(0));
  36. hammertime.get('press').set({time: 1000});
  37. hammertime.on('press', function(oEvent) {
  38. if(typeof oBxDolStudioContextMenu !== 'undefined' && oBxDolStudioContextMenu.justOpened())
  39. return; // the press opened a tile's context menu
  40. if(!$this.bJitterMode)
  41. $this.enableJitter();
  42. else
  43. $this.disableJitter();
  44. });
  45. //--- Desktop: put the cursor in the app search so typing filters straight away (no autofocus on touch, it would raise the keyboard; not while the intro tour holds focus).
  46. if(bx_is_mouse() && window.matchMedia('(min-width: 768px)').matches && !(typeof glTour !== 'undefined' && glTour.isActive()))
  47. $('li.bx-menu-tab-search:not(.bx-mt-compact) input[name="search"]').trigger('focus');
  48. //--- Enable Sorting for Page Edit mode ---//
  49. $($this.aSortingConf.parent).sortable({
  50. disabled: true,
  51. handle: '.bx-std-widget-icon-jitter',
  52. items: $this.aSortingConf.item,
  53. placeholder: $this.aSortingConf.placeholder,
  54. start: function(oEvent, oUi) {
  55. oUi.item.addClass('bx-std-widget-dragging');
  56. bx_sound('select'); // picked up
  57. },
  58. stop: function(oEvent, oUi) {
  59. oUi.item.removeClass('bx-std-widget-dragging');
  60. bx_sound('tap', {emphasis: 'strong'}); // dropped
  61. $this.reorder(oUi.item);
  62. }
  63. });
  64. });
  65. //--- Escape leaves edit mode; the search field and popups handle their own Escape first.
  66. window.addEventListener('keydown', function(oEvent) {
  67. if(oEvent.key !== 'Escape' || !$this.bJitterMode || $(oEvent.target).is('input, textarea, select') || $('.bx-popup-applied:visible').length)
  68. return;
  69. $this.disableJitter();
  70. });
  71. };
  72. BxDolStudioLauncher.prototype.browser = function(oLink, sType) {
  73. var $this = this;
  74. var oDate = new Date();
  75. if(sType == undefined)
  76. sType = '';
  77. var oBrowser = $('#bx-std-launcher-browser');
  78. if(oBrowser.length > 0) {
  79. var fBrowserShow = function() {
  80. oBrowser.dolPopup({
  81. closeOnOuterClick: true,
  82. pointer: {
  83. el: '.bx-menu-breadcrumb .bx-menu-bc-' + (sType.length > 0 ? 'type' : 'home'),
  84. align: 'left',
  85. offset: '-16 0'
  86. },
  87. onBeforeShow: function(oPopup) {
  88. $this.browserChangeType(oLink, sType);
  89. }
  90. });
  91. }
  92. if(oBrowser.is(':visible'))
  93. oBrowser.dolPopupHide({
  94. onHide: function() {
  95. if(oBrowser.find('.bx-std-lb-menu .bx-std-pmen-item.bx-std-pmen-item-' + sType + '.bx-menu-tab-active').length == 0)
  96. fBrowserShow();
  97. }
  98. });
  99. else
  100. fBrowserShow();
  101. return false;
  102. }
  103. $.get(
  104. this.sActionsUrl,
  105. {
  106. action: 'launcher-browser',
  107. type: sType,
  108. _t: oDate.getTime()
  109. },
  110. function(oData) {
  111. processJsonData(oData);
  112. },
  113. 'json'
  114. );
  115. return false;
  116. };
  117. BxDolStudioLauncher.prototype.browserChangeType = function(oLink, sType) {
  118. var sMenuActive = 'bx-menu-tab-active';
  119. var sContentActive = 'bx-std-lbw-active';
  120. var oMenuActive = $('.bx-std-launcher-browser .bx-std-lb-menu .bx-std-pmen-item.' + sMenuActive);
  121. if(oMenuActive.hasClass('bx-std-pmen-item-' + sType))
  122. return;
  123. oMenuActive.removeClass(sMenuActive).siblings('.bx-std-pmen-item-' + sType).addClass(sMenuActive);
  124. $('.bx-std-launcher-browser .bx-std-lb-content .bx-std-lb-widgets.' + sContentActive).removeClass(sContentActive).siblings('.bx-std-lbw-' + sType).addClass(sContentActive);
  125. };
  126. BxDolStudioLauncher.prototype.updateCache = function() {
  127. var oDate = new Date();
  128. $.get(
  129. this.sActionsUrl,
  130. {
  131. action: 'launcher-update-cache',
  132. _t:oDate.getTime()
  133. }
  134. );
  135. };
  136. BxDolStudioLauncher.prototype.reorder = function(oDraggable) {
  137. var oDate = new Date();
  138. $.post(
  139. this.sActionsUrl + '?' + oDraggable.parent('.bx-std-widgets').sortable('serialize', {key: 'items[]'}),
  140. {
  141. action: 'launcher-reorder',
  142. page: this.sScrollCurrent,
  143. _t:oDate.getTime()
  144. },
  145. function(oData) {
  146. if(oData.code != 0) {
  147. bx_sound('error');
  148. bx_alert(oData.message);
  149. return;
  150. }
  151. },
  152. 'json'
  153. );
  154. return true;
  155. };
  156. BxDolStudioLauncher.prototype.featured = function(sPageName, oLink) {
  157. var $this = this;
  158. var oDate = new Date();
  159. $.get(
  160. this.sActionsUrl,
  161. {
  162. action: 'page-featured',
  163. page: sPageName,
  164. _t: oDate.getTime()
  165. },
  166. function(oData) {
  167. $('.bx-popup-applied:visible').dolPopupHide();
  168. if(oData.code != 0) {
  169. bx_alert(oData.message);
  170. return;
  171. }
  172. var oSettings = $(oLink).parents('.bx-mod-popup-settings:first');
  173. if(oSettings.length > 0 && oData.widget_id != undefined && oData.widget.length > 0) {
  174. $('#bx-std-widget-' + oData.widget_id).replaceWith(oData.widget);
  175. if($this.bInit)
  176. oBxDolStudioLauncher.enableJitter();
  177. }
  178. var sClassStatic = 'bx-menu-item-static';
  179. var sClassDivider = 'bx-menu-item-divider';
  180. var oItem = $('#bx-menu-item-' + sPageName).toggleClass(sClassStatic + ' bx-menu-item-dynamic'); // the acted-on app's dock item, not necessarily this page's
  181. if(oItem.hasClass(sClassStatic))
  182. $(oItem).insertBefore(oItem.siblings('.' + sClassDivider));
  183. else
  184. $(oItem).insertAfter(oItem.siblings(':last'));
  185. },
  186. 'json'
  187. );
  188. return true;
  189. };
  190. BxDolStudioLauncher.prototype.bookmark = function(sPageName, oLink) {
  191. var oDate = new Date();
  192. $.get(
  193. this.sActionsUrl,
  194. {
  195. action: 'page-bookmark',
  196. page: sPageName,
  197. _t: oDate.getTime()
  198. },
  199. function(oData) {
  200. $('.bx-popup-applied:visible').dolPopupHide();
  201. if(oData.code != 0) {
  202. bx_alert(oData.message);
  203. return;
  204. }
  205. var sClassStatic = 'bx-menu-item-static';
  206. var sClassDivider = 'bx-menu-item-divider';
  207. var oItem = $('#bx-menu-item-' + sPageName).toggleClass(sClassStatic + ' bx-menu-item-dynamic'); // the acted-on app's dock item, not necessarily this page's
  208. if(oItem.hasClass(sClassStatic))
  209. $(oItem).insertBefore(oItem.siblings('.' + sClassDivider));
  210. else
  211. $(oItem).insertAfter(oItem.siblings(':last'));
  212. },
  213. 'json'
  214. );
  215. return true;
  216. };
  217. BxDolStudioLauncher.prototype.rearrange = function(iWidgetId, oSelect) {
  218. var oDate = new Date();
  219. var oSelect = $(oSelect);
  220. var oPopup = oSelect.parents('.bx-popup');
  221. bx_loading(oPopup, true);
  222. $.get(
  223. this.sActionsUrl,
  224. {
  225. action: 'widget-rearrange',
  226. widget_id: iWidgetId,
  227. type: oSelect.val(),
  228. _t: oDate.getTime()
  229. },
  230. function(oData) {
  231. bx_loading(oPopup, false);
  232. processJsonData(oData);
  233. },
  234. 'json'
  235. );
  236. return true;
  237. };
  238. BxDolStudioLauncher.prototype.enableJitter = function() {
  239. if(!this.bJitterMode)
  240. bx_sound('toggle');
  241. // While editing, a press on a tile starts a drag, not a visit: its cue sleeps until Done.
  242. $(this.aSortingConf.parent).find('[data-cuelume-navigate]').each(function() {
  243. $(this).attr('data-bx-cue-navigate', $(this).attr('data-cuelume-navigate')).removeAttr('data-cuelume-navigate');
  244. });
  245. $(this.aJitterConf.elements).fadeIn('fast');
  246. $(this.aJitterConf.item).removeClass('bx-std-widget-icon-trans');
  247. $(this.aSortingConf.parent).addClass('bx-std-jitter').sortable('option', 'disabled', false);
  248. this.bJitterMode = true;
  249. this.markJitter();
  250. };
  251. BxDolStudioLauncher.prototype.disableJitter = function() {
  252. var oActive = $(document.activeElement);
  253. var oWidget = oActive.closest(this.aSortingConf.item);
  254. if(this.bJitterMode)
  255. bx_sound('toggle', {direction: 'back'});
  256. $(this.aSortingConf.parent).find('[data-bx-cue-navigate]').each(function() {
  257. $(this).attr('data-cuelume-navigate', $(this).attr('data-bx-cue-navigate')).removeAttr('data-bx-cue-navigate');
  258. });
  259. $(this.aJitterConf.elements).fadeOut('fast');
  260. $(this.aJitterConf.item).addClass('bx-std-widget-icon-trans');
  261. $(this.aSortingConf.parent).removeClass('bx-std-jitter').sortable('option', 'disabled', true);
  262. this.bJitterMode = false;
  263. this.markJitter();
  264. //--- Keyboard focus was on a control that just went away: keep it on the same tile, or on the first one.
  265. if(oWidget.length && oActive.closest('.bx-std-widget-actions').length)
  266. oWidget.find('.bx-std-widget-icon .bx-std-widget-link').trigger('focus');
  267. else if(oActive.closest('#bx-std-launcher-edit').length)
  268. $(this.aSortingConf.parent + ' > ' + this.aSortingConf.item).filter(':visible').first().find('.bx-std-widget-icon .bx-std-widget-link').trigger('focus');
  269. };
  270. /**
  271. * Reflect edit mode on the Done bar and on the Manage Apps toggle in the account menu.
  272. */
  273. BxDolStudioLauncher.prototype.markJitter = function() {
  274. var bOn = this.bJitterMode;
  275. $('#bx-std-launcher-edit').prop('hidden', !bOn);
  276. $('.bx-menu-account li.edit').toggleClass('bx-menu-tab-active', bOn).find('[aria-pressed]').attr('aria-pressed', bOn ? 'true' : 'false');
  277. };
  278. /**
  279. * Non-drag reordering for edit mode: shift one app past its visible neighbour, save, and announce the new position.
  280. */
  281. BxDolStudioLauncher.prototype.move = function(oButton, iDirection) {
  282. var sItem = this.aSortingConf.item + ':visible';
  283. var oWidget = $(oButton).closest(this.aSortingConf.item);
  284. var oSibling = iDirection < 0 ? oWidget.prevAll(sItem).first() : oWidget.nextAll(sItem).first();
  285. if(!oSibling.length)
  286. return false;
  287. if(iDirection < 0)
  288. oWidget.insertBefore(oSibling);
  289. else
  290. oWidget.insertAfter(oSibling);
  291. bx_sound('select', {direction: iDirection < 0 ? 'back' : 'forward'}); // reordered
  292. $(oButton).trigger('focus');
  293. var oWidgets = oWidget.parent().children(sItem);
  294. this.announce(this.sTxtMoved.replace('{0}', oWidget.find('.bx-std-widget-caption').text().trim()).replace('{1}', oWidgets.index(oWidget) + 1).replace('{2}', oWidgets.length));
  295. return this.reorder(oWidget);
  296. };
  297. /**
  298. * Search feedback: the empty message when nothing matches, and the match count for screen readers.
  299. */
  300. BxDolStudioLauncher.prototype.searchResult = function(sSearch, iCount) {
  301. $('#bx-std-launcher-empty').prop('hidden', !sSearch.length || iCount > 0);
  302. if(!sSearch.length)
  303. $('#bx-std-launcher-status').text('');
  304. else
  305. this.announce((iCount == 1 ? this.sTxtMatchesOne : this.sTxtMatches).replace('{0}', iCount));
  306. };
  307. /**
  308. * Polite live region of the launcher; cleared first so the same text is announced again.
  309. */
  310. BxDolStudioLauncher.prototype.announce = function(sText) {
  311. var oStatus = $('#bx-std-launcher-status');
  312. if(!oStatus.length)
  313. return;
  314. oStatus.text('');
  315. clearTimeout(this.iAnnounceTimer);
  316. this.iAnnounceTimer = setTimeout(function() {
  317. oStatus.text(sText);
  318. }, 50);
  319. };
  320. /** @} */