You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

270 lines
10 KiB

  1. /**********************************************************************************
  2. *
  3. * Copyright (c) 2017-today MuK IT GmbH.
  4. *
  5. * This file is part of MuK Grid Snippets
  6. * (see https://mukit.at).
  7. *
  8. * This program is free software: you can redistribute it and/or modify
  9. * it under the terms of the GNU Lesser General Public License as published by
  10. * the Free Software Foundation, either version 3 of the License, or
  11. * (at your option) any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU Lesser General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU Lesser General Public License
  19. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  20. *
  21. **********************************************************************************/
  22. odoo.define('muk_web_theme.KanbanRenderer', function (require) {
  23. "use strict";
  24. const config = require('web.config');
  25. const core = require('web.core');
  26. const KanbanRenderer = require('web.KanbanRenderer');
  27. const _t = core._t;
  28. const qweb = core.qweb;
  29. if (!config.device.isMobile) {
  30. return;
  31. }
  32. KanbanRenderer.include({
  33. custom_events: _.extend({}, KanbanRenderer.prototype.custom_events || {}, {
  34. quick_create_column_created: '_onColumnAdded',
  35. }),
  36. events: _.extend({}, KanbanRenderer.prototype.events, {
  37. 'click .o_kanban_mobile_tab': '_onMobileTabClicked',
  38. 'click .o_kanban_mobile_add_column': '_onMobileQuickCreateClicked',
  39. }),
  40. ANIMATE: true,
  41. init() {
  42. this._super.apply(this, arguments);
  43. this.activeColumnIndex = 0;
  44. this._scrollPosition = null;
  45. },
  46. on_attach_callback() {
  47. if (this._scrollPosition && this.state.groupedBy.length && this.widgets.length) {
  48. var $column = this.widgets[this.activeColumnIndex].$el;
  49. $column.scrollLeft(this._scrollPosition.left);
  50. $column.scrollTop(this._scrollPosition.top);
  51. }
  52. this._computeTabPosition();
  53. this._super.apply(this, arguments);
  54. },
  55. on_detach_callback() {
  56. if (this.state.groupedBy.length && this.widgets.length) {
  57. var $column = this.widgets[this.activeColumnIndex].$el;
  58. this._scrollPosition = {
  59. left: $column.scrollLeft(),
  60. top: $column.scrollTop(),
  61. };
  62. } else {
  63. this._scrollPosition = null;
  64. }
  65. this._super.apply(this, arguments);
  66. },
  67. addQuickCreate() {
  68. if(this._canCreateColumn() && !this.quickCreate.folded) {
  69. this._onMobileQuickCreateClicked();
  70. }
  71. return this.widgets[this.activeColumnIndex].addQuickCreate();
  72. },
  73. updateColumn(localID) {
  74. var index = _.findIndex(this.widgets, {db_id: localID});
  75. var $column = this.widgets[index].$el;
  76. var scrollTop = $column.scrollTop();
  77. return this._super.apply(this, arguments)
  78. .then(() => this._layoutUpdate(false))
  79. .then(() => $column.scrollTop(scrollTop));
  80. },
  81. _canCreateColumn: function() {
  82. return this.quickCreateEnabled && this.quickCreate && this.widgets.length;
  83. },
  84. _computeColumnPosition(animate) {
  85. if (this.widgets.length) {
  86. const rtl = _t.database.parameters.direction === 'rtl';
  87. this.$('.o_kanban_group').show();
  88. const $columnAfter = this._toNode(this.widgets.filter((widget, index) => index > this.activeColumnIndex));
  89. const promiseAfter = this._updateColumnCss($columnAfter, rtl ? {right: '100%'} : {left: '100%'}, animate);
  90. const $columnBefore = this._toNode(this.widgets.filter((widget, index) => index < this.activeColumnIndex));
  91. const promiseBefore = this._updateColumnCss($columnBefore, rtl ? {right: '-100%'} : {left: '-100%'}, animate);
  92. const $columnCurrent = this._toNode(this.widgets.filter((widget, index) => index === this.activeColumnIndex));
  93. const promiseCurrent = this._updateColumnCss($columnCurrent, rtl ? {right: '0%'} : {left: '0%'}, animate);
  94. promiseAfter
  95. .then(promiseBefore)
  96. .then(promiseCurrent)
  97. .then(() => {
  98. $columnAfter.hide();
  99. $columnBefore.hide();
  100. });
  101. }
  102. },
  103. _computeCurrentColumn() {
  104. if (this.widgets.length) {
  105. var column = this.widgets[this.activeColumnIndex];
  106. if (!column) {
  107. return;
  108. }
  109. var columnID = column.id || column.db_id;
  110. this.$('.o_kanban_mobile_tab.o_current, .o_kanban_group.o_current')
  111. .removeClass('o_current');
  112. this.$('.o_kanban_group[data-id="' + columnID + '"], ' +
  113. '.o_kanban_mobile_tab[data-id="' + columnID + '"]')
  114. .addClass('o_current');
  115. }
  116. },
  117. _computeTabPosition() {
  118. this._computeTabJustification();
  119. this._computeTabScrollPosition();
  120. },
  121. _computeTabScrollPosition() {
  122. if (this.widgets.length) {
  123. var lastItemIndex = this.widgets.length - 1;
  124. var moveToIndex = this.activeColumnIndex;
  125. var scrollToLeft = 0;
  126. for (var i = 0; i < moveToIndex; i++) {
  127. var columnWidth = this._getTabWidth(this.widgets[i]);
  128. if (moveToIndex !== lastItemIndex && i === moveToIndex - 1) {
  129. var partialWidth = 0.75;
  130. scrollToLeft += columnWidth * partialWidth;
  131. } else {
  132. scrollToLeft += columnWidth;
  133. }
  134. }
  135. this.$('.o_kanban_mobile_tabs').scrollLeft(scrollToLeft);
  136. }
  137. },
  138. _computeTabJustification() {
  139. if (this.widgets.length) {
  140. var self = this;
  141. var widthChilds = this.widgets.reduce(function (total, column) {
  142. return total + self._getTabWidth(column);
  143. }, 0);
  144. var $tabs = this.$('.o_kanban_mobile_tabs');
  145. $tabs.toggleClass('justify-content-between', $tabs.outerWidth() >= widthChilds);
  146. }
  147. },
  148. _enableSwipe() {
  149. var self = this;
  150. var step = _t.database.parameters.direction === 'rtl' ? -1 : 1;
  151. this.$el.swipe({
  152. excludedElements: ".o_kanban_mobile_tabs",
  153. swipeLeft() {
  154. var moveToIndex = self.activeColumnIndex + step;
  155. if (moveToIndex < self.widgets.length) {
  156. self._moveToGroup(moveToIndex, self.ANIMATE);
  157. }
  158. },
  159. swipeRight() {
  160. var moveToIndex = self.activeColumnIndex - step;
  161. if (moveToIndex > -1) {
  162. self._moveToGroup(moveToIndex, self.ANIMATE);
  163. }
  164. }
  165. });
  166. },
  167. _getTabWidth (column) {
  168. var columnID = column.id || column.db_id;
  169. return this.$('.o_kanban_mobile_tab[data-id="' + columnID + '"]').outerWidth();
  170. },
  171. _layoutUpdate (animate) {
  172. this._computeCurrentColumn();
  173. this._computeTabPosition();
  174. this._computeColumnPosition(animate);
  175. },
  176. _moveToGroup(moveToIndex, animate) {
  177. if (moveToIndex < 0 || moveToIndex >= this.widgets.length) {
  178. this._layoutUpdate(animate);
  179. return Promise.resolve();
  180. }
  181. this.activeColumnIndex = moveToIndex;
  182. var column = this.widgets[this.activeColumnIndex];
  183. if (column.data.isOpen) {
  184. this._layoutUpdate(animate);
  185. } else {
  186. this.trigger_up('column_toggle_fold', {
  187. db_id: column.db_id,
  188. onSuccess: () => this._layoutUpdate(animate)
  189. });
  190. }
  191. this._enableSwipe();
  192. return Promise.resolve();
  193. },
  194. _renderGrouped(fragment) {
  195. var self = this;
  196. var newFragment = document.createDocumentFragment();
  197. this._super.apply(this, [newFragment]);
  198. this.defs.push(Promise.all(this.defs).then(function () {
  199. var data = [];
  200. _.each(self.state.data, function (group) {
  201. if (!group.value) {
  202. group = _.extend({}, group, {value: _t('Undefined')});
  203. data.unshift(group);
  204. } else {
  205. data.push(group);
  206. }
  207. });
  208. var kanbanColumnContainer = document.createElement('div');
  209. kanbanColumnContainer.classList.add('o_kanban_columns_content');
  210. kanbanColumnContainer.appendChild(newFragment);
  211. fragment.appendChild(kanbanColumnContainer);
  212. $(qweb.render('KanbanView.MobileTabs', {
  213. data: data,
  214. quickCreateEnabled: self._canCreateColumn()
  215. })).prependTo(fragment);
  216. }));
  217. },
  218. _renderView() {
  219. var self = this;
  220. return this._super.apply(this, arguments).then(function () {
  221. if (self.state.groupedBy.length) {
  222. return self._moveToGroup(0);
  223. } else {
  224. if(self._canCreateColumn()) {
  225. self._onMobileQuickCreateClicked();
  226. }
  227. return Promise.resolve();
  228. }
  229. });
  230. },
  231. _toNode(widgets) {
  232. const selectorCss = widgets
  233. .map(widget => '.o_kanban_group[data-id="' + (widget.id || widget.db_id) + '"]')
  234. .join(', ');
  235. return this.$(selectorCss);
  236. },
  237. _updateColumnCss($column, cssProperties, animate) {
  238. if (animate) {
  239. return new Promise(resolve => $column.animate(cssProperties, 'fast', resolve));
  240. } else {
  241. $column.css(cssProperties);
  242. return Promise.resolve();
  243. }
  244. },
  245. _onColumnAdded() {
  246. this._computeTabPosition();
  247. if(this._canCreateColumn() && !this.quickCreate.folded) {
  248. this.quickCreate.toggleFold();
  249. }
  250. },
  251. _onMobileQuickCreateClicked: function() {
  252. this.$('.o_kanban_group').toggle();
  253. this.quickCreate.toggleFold();
  254. },
  255. _onMobileTabClicked(event) {
  256. if(this._canCreateColumn() && !this.quickCreate.folded) {
  257. this.quickCreate.toggleFold();
  258. }
  259. this._moveToGroup($(event.currentTarget).index(), true);
  260. },
  261. _renderExampleBackground() {},
  262. });
  263. });