launcher.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  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. },
  57. stop: function(oEvent, oUi) {
  58. oUi.item.removeClass('bx-std-widget-dragging');
  59. $this.reorder(oUi.item);
  60. }
  61. });
  62. });
  63. //--- Escape leaves edit mode; the search field and popups handle their own Escape first.
  64. window.addEventListener('keydown', function(oEvent) {
  65. if(oEvent.key !== 'Escape' || !$this.bJitterMode || $(oEvent.target).is('input, textarea, select') || $('.bx-popup-applied:visible').length)
  66. return;
  67. $this.disableJitter();
  68. });
  69. };
  70. BxDolStudioLauncher.prototype.browser = function(oLink, sType) {
  71. var $this = this;
  72. var oDate = new Date();
  73. if(sType == undefined)
  74. sType = '';
  75. var oBrowser = $('#bx-std-launcher-browser');
  76. if(oBrowser.length > 0) {
  77. var fBrowserShow = function() {
  78. oBrowser.dolPopup({
  79. closeOnOuterClick: true,
  80. pointer: {
  81. el: '.bx-menu-breadcrumb .bx-menu-bc-' + (sType.length > 0 ? 'type' : 'home'),
  82. align: 'left',
  83. offset: '-16 0'
  84. },
  85. onBeforeShow: function(oPopup) {
  86. $this.browserChangeType(oLink, sType);
  87. }
  88. });
  89. }
  90. if(oBrowser.is(':visible'))
  91. oBrowser.dolPopupHide({
  92. onHide: function() {
  93. if(oBrowser.find('.bx-std-lb-menu .bx-std-pmen-item.bx-std-pmen-item-' + sType + '.bx-menu-tab-active').length == 0)
  94. fBrowserShow();
  95. }
  96. });
  97. else
  98. fBrowserShow();
  99. return false;
  100. }
  101. $.get(
  102. this.sActionsUrl,
  103. {
  104. action: 'launcher-browser',
  105. type: sType,
  106. _t: oDate.getTime()
  107. },
  108. function(oData) {
  109. processJsonData(oData);
  110. },
  111. 'json'
  112. );
  113. return false;
  114. };
  115. BxDolStudioLauncher.prototype.browserChangeType = function(oLink, sType) {
  116. var sMenuActive = 'bx-menu-tab-active';
  117. var sContentActive = 'bx-std-lbw-active';
  118. var oMenuActive = $('.bx-std-launcher-browser .bx-std-lb-menu .bx-std-pmen-item.' + sMenuActive);
  119. if(oMenuActive.hasClass('bx-std-pmen-item-' + sType))
  120. return;
  121. oMenuActive.removeClass(sMenuActive).siblings('.bx-std-pmen-item-' + sType).addClass(sMenuActive);
  122. $('.bx-std-launcher-browser .bx-std-lb-content .bx-std-lb-widgets.' + sContentActive).removeClass(sContentActive).siblings('.bx-std-lbw-' + sType).addClass(sContentActive);
  123. };
  124. BxDolStudioLauncher.prototype.updateCache = function() {
  125. var oDate = new Date();
  126. $.get(
  127. this.sActionsUrl,
  128. {
  129. action: 'launcher-update-cache',
  130. _t:oDate.getTime()
  131. }
  132. );
  133. };
  134. BxDolStudioLauncher.prototype.reorder = function(oDraggable) {
  135. var oDate = new Date();
  136. $.post(
  137. this.sActionsUrl + '?' + oDraggable.parent('.bx-std-widgets').sortable('serialize', {key: 'items[]'}),
  138. {
  139. action: 'launcher-reorder',
  140. page: this.sScrollCurrent,
  141. _t:oDate.getTime()
  142. },
  143. function(oData) {
  144. if(oData.code != 0) {
  145. bx_alert(oData.message);
  146. return;
  147. }
  148. },
  149. 'json'
  150. );
  151. return true;
  152. };
  153. BxDolStudioLauncher.prototype.featured = function(sPageName, oLink) {
  154. var $this = this;
  155. var oDate = new Date();
  156. $.get(
  157. this.sActionsUrl,
  158. {
  159. action: 'page-featured',
  160. page: sPageName,
  161. _t: oDate.getTime()
  162. },
  163. function(oData) {
  164. $('.bx-popup-applied:visible').dolPopupHide();
  165. if(oData.code != 0) {
  166. bx_alert(oData.message);
  167. return;
  168. }
  169. var oSettings = $(oLink).parents('.bx-mod-popup-settings:first');
  170. if(oSettings.length > 0 && oData.widget_id != undefined && oData.widget.length > 0) {
  171. $('#bx-std-widget-' + oData.widget_id).replaceWith(oData.widget);
  172. if($this.bInit)
  173. oBxDolStudioLauncher.enableJitter();
  174. }
  175. var sClassStatic = 'bx-menu-item-static';
  176. var sClassDivider = 'bx-menu-item-divider';
  177. var oItem = $('#bx-menu-item-' + sPageName).toggleClass(sClassStatic + ' bx-menu-item-dynamic'); // the acted-on app's dock item, not necessarily this page's
  178. if(oItem.hasClass(sClassStatic))
  179. $(oItem).insertBefore(oItem.siblings('.' + sClassDivider));
  180. else
  181. $(oItem).insertAfter(oItem.siblings(':last'));
  182. },
  183. 'json'
  184. );
  185. return true;
  186. };
  187. BxDolStudioLauncher.prototype.bookmark = function(sPageName, oLink) {
  188. var oDate = new Date();
  189. $.get(
  190. this.sActionsUrl,
  191. {
  192. action: 'page-bookmark',
  193. page: sPageName,
  194. _t: oDate.getTime()
  195. },
  196. function(oData) {
  197. $('.bx-popup-applied:visible').dolPopupHide();
  198. if(oData.code != 0) {
  199. bx_alert(oData.message);
  200. return;
  201. }
  202. var sClassStatic = 'bx-menu-item-static';
  203. var sClassDivider = 'bx-menu-item-divider';
  204. var oItem = $('#bx-menu-item-' + sPageName).toggleClass(sClassStatic + ' bx-menu-item-dynamic'); // the acted-on app's dock item, not necessarily this page's
  205. if(oItem.hasClass(sClassStatic))
  206. $(oItem).insertBefore(oItem.siblings('.' + sClassDivider));
  207. else
  208. $(oItem).insertAfter(oItem.siblings(':last'));
  209. },
  210. 'json'
  211. );
  212. return true;
  213. };
  214. BxDolStudioLauncher.prototype.rearrange = function(iWidgetId, oSelect) {
  215. var oDate = new Date();
  216. var oSelect = $(oSelect);
  217. var oPopup = oSelect.parents('.bx-popup');
  218. bx_loading(oPopup, true);
  219. $.get(
  220. this.sActionsUrl,
  221. {
  222. action: 'widget-rearrange',
  223. widget_id: iWidgetId,
  224. type: oSelect.val(),
  225. _t: oDate.getTime()
  226. },
  227. function(oData) {
  228. bx_loading(oPopup, false);
  229. processJsonData(oData);
  230. },
  231. 'json'
  232. );
  233. return true;
  234. };
  235. BxDolStudioLauncher.prototype.enableJitter = function() {
  236. $(this.aJitterConf.elements).fadeIn('fast');
  237. $(this.aJitterConf.item).removeClass('bx-std-widget-icon-trans');
  238. $(this.aSortingConf.parent).addClass('bx-std-jitter').sortable('option', 'disabled', false);
  239. this.bJitterMode = true;
  240. this.markJitter();
  241. };
  242. BxDolStudioLauncher.prototype.disableJitter = function() {
  243. var oActive = $(document.activeElement);
  244. var oWidget = oActive.closest(this.aSortingConf.item);
  245. $(this.aJitterConf.elements).fadeOut('fast');
  246. $(this.aJitterConf.item).addClass('bx-std-widget-icon-trans');
  247. $(this.aSortingConf.parent).removeClass('bx-std-jitter').sortable('option', 'disabled', true);
  248. this.bJitterMode = false;
  249. this.markJitter();
  250. //--- Keyboard focus was on a control that just went away: keep it on the same tile, or on the first one.
  251. if(oWidget.length && oActive.closest('.bx-std-widget-actions').length)
  252. oWidget.find('.bx-std-widget-icon .bx-std-widget-link').trigger('focus');
  253. else if(oActive.closest('#bx-std-launcher-edit').length)
  254. $(this.aSortingConf.parent + ' > ' + this.aSortingConf.item).filter(':visible').first().find('.bx-std-widget-icon .bx-std-widget-link').trigger('focus');
  255. };
  256. /**
  257. * Reflect edit mode on the Done bar and on the Manage Apps toggle in the account menu.
  258. */
  259. BxDolStudioLauncher.prototype.markJitter = function() {
  260. var bOn = this.bJitterMode;
  261. $('#bx-std-launcher-edit').prop('hidden', !bOn);
  262. $('.bx-menu-account li.edit').toggleClass('bx-menu-tab-active', bOn).find('[aria-pressed]').attr('aria-pressed', bOn ? 'true' : 'false');
  263. };
  264. /**
  265. * Non-drag reordering for edit mode: shift one app past its visible neighbour, save, and announce the new position.
  266. */
  267. BxDolStudioLauncher.prototype.move = function(oButton, iDirection) {
  268. var sItem = this.aSortingConf.item + ':visible';
  269. var oWidget = $(oButton).closest(this.aSortingConf.item);
  270. var oSibling = iDirection < 0 ? oWidget.prevAll(sItem).first() : oWidget.nextAll(sItem).first();
  271. if(!oSibling.length)
  272. return false;
  273. if(iDirection < 0)
  274. oWidget.insertBefore(oSibling);
  275. else
  276. oWidget.insertAfter(oSibling);
  277. $(oButton).trigger('focus');
  278. var oWidgets = oWidget.parent().children(sItem);
  279. this.announce(this.sTxtMoved.replace('{0}', oWidget.find('.bx-std-widget-caption').text().trim()).replace('{1}', oWidgets.index(oWidget) + 1).replace('{2}', oWidgets.length));
  280. return this.reorder(oWidget);
  281. };
  282. /**
  283. * Search feedback: the empty message when nothing matches, and the match count for screen readers.
  284. */
  285. BxDolStudioLauncher.prototype.searchResult = function(sSearch, iCount) {
  286. $('#bx-std-launcher-empty').prop('hidden', !sSearch.length || iCount > 0);
  287. if(!sSearch.length)
  288. $('#bx-std-launcher-status').text('');
  289. else
  290. this.announce((iCount == 1 ? this.sTxtMatchesOne : this.sTxtMatches).replace('{0}', iCount));
  291. };
  292. /**
  293. * Polite live region of the launcher; cleared first so the same text is announced again.
  294. */
  295. BxDolStudioLauncher.prototype.announce = function(sText) {
  296. var oStatus = $('#bx-std-launcher-status');
  297. if(!oStatus.length)
  298. return;
  299. oStatus.text('');
  300. clearTimeout(this.iAnnounceTimer);
  301. this.iAnnounceTimer = setTimeout(function() {
  302. oStatus.text(sText);
  303. }, 50);
  304. };
  305. /** @} */