APIs

Show:
  1. /**
  2. Singleton Screen Layout editor is used to create / edit an existing screen division layout (aka: template > viewers) and when done
  3. editing, create new ScreenTemplates via ScreenTemplateFactory for both Thumb and main Timeline UIs
  4. @class ScreenLayoutEditorView
  5. @constructor
  6. @return {object} instantiated ScreenLayoutEditorView
  7. **/
  8. define(['jquery', 'backbone', 'StackView', 'ScreenTemplateFactory'], function ($, Backbone, StackView, ScreenTemplateFactory) {
  9.  
  10. /**
  11. Custom event fired when a screen division / viewer has been removed
  12. @event VIEWER_REMOVED
  13. @param {This} caller
  14. @param {Self} context caller
  15. @param {Event} rss link
  16. @static
  17. @final
  18. **/
  19. BB.EVENTS.VIEWER_REMOVED = 'VIEWER_REMOVED';
  20.  
  21. BB.SERVICES.SCREEN_LAYOUT_EDITOR_VIEW = 'ScreenLayoutEditorView';
  22.  
  23. var ScreenLayoutEditorView = BB.View.extend({
  24.  
  25. /**
  26. Constructor
  27. @method initialize
  28. **/
  29. initialize: function () {
  30. var self = this;
  31. BB.comBroker.setService(BB.SERVICES['SCREEN_LAYOUT_EDITOR_VIEW'], self);
  32. self.RATIO = 4;
  33. self.m_canvas = undefined;
  34. self.m_canvasID = undefined;
  35. self.m_selectedViewerID = undefined;
  36. self.m_dimensionProps = undefined;
  37.  
  38. this.m_property = BB.comBroker.getService(BB.SERVICES['PROPERTIES_VIEW']);
  39. self.m_property.initPanel(Elements.VIEWER_EDIT_PROPERTIES);
  40.  
  41. $(this.el).find('#prev').on('click', function (e) {
  42. self._deSelectView();
  43. BB.comBroker.fire(BB.EVENTS.CAMPAIGN_TIMELINE_CHANGED, self);
  44. return false;
  45. });
  46.  
  47. self._listenAddDivision();
  48. self._listenRemoveDivision();
  49. self._listenPushToTopDivision();
  50. self._listenPushToBottomDivision();
  51. self._listenSelectNextDivision();
  52.  
  53. self.listenTo(self.options.stackView, BB.EVENTS.SELECTED_STACK_VIEW, function (e) {
  54. if (e == self) {
  55. if (self.m_dimensionProps == undefined) {
  56. require(['DimensionProps'], function (DimensionProps) {
  57. self.m_dimensionProps = new DimensionProps({
  58. appendTo: Elements.VIEWER_DIMENSIONS,
  59. showAngle: false
  60. });
  61. $(self.m_dimensionProps).on('changed', function (e) {
  62. var props = e.target.getValues();
  63. self._updateDimensionsInDB(self.m_canvas.getActiveObject(), props);
  64. self._moveViewer(props);
  65. });
  66. self._render();
  67. });
  68. } else {
  69. self._render();
  70. }
  71. }
  72. });
  73. },
  74.  
  75. /**
  76. Load the editor into DOM using the StackView using animation slider
  77. @method selectView
  78. **/
  79. selectView: function (i_campaign_timeline_id, i_campaign_timeline_board_template_id) {
  80. var self = this;
  81. self.m_campaign_timeline_id = i_campaign_timeline_id;
  82. self.m_campaign_timeline_board_template_id = i_campaign_timeline_board_template_id;
  83. self.m_global_board_template_id = pepper.getGlobalTemplateIdOfTimeline(i_campaign_timeline_board_template_id);
  84. self.m_screenProps = pepper.getTemplateViewersScreenProps(self.m_campaign_timeline_id, self.m_campaign_timeline_board_template_id);
  85. self.m_orientation = BB.comBroker.getService(BB.SERVICES['ORIENTATION_SELECTOR_VIEW']).getOrientation();
  86. self.m_resolution = BB.comBroker.getService(BB.SERVICES['RESOLUTION_SELECTOR_VIEW']).getResolution();
  87.  
  88. self.options.stackView.slideToPage(self, 'right');
  89.  
  90. require(['fabric'], function () {
  91. var w = parseInt(self.m_resolution.split('x')[0]) / self.RATIO;
  92. var h = parseInt(self.m_resolution.split('x')[1]) / self.RATIO;
  93.  
  94. self._canvasFactory(w, h);
  95. self._listenObjectChanged();
  96. self._listenObjectsOverlap();
  97. self._listenBackgroundSelected();
  98. })
  99. },
  100.  
  101. /**
  102. On render load default dashboard properties
  103. @method _render
  104. **/
  105. _render: function () {
  106. var self = this;
  107. self.m_property.resetPropertiesView();
  108. },
  109.  
  110. /**
  111. Listen to the addition of a new viewer
  112. @method (totalViews - i)
  113. **/
  114. _listenAddDivision: function () {
  115. var self = this;
  116. $(Elements.LAYOUT_EDITOR_ADD_NEW, self.$el).on('click', function () {
  117. var props = {
  118. x: 0,
  119. y: 0,
  120. w: 100,
  121. h: 100
  122. }
  123. var board_viewer_id = pepper.createViewer(self.m_global_board_template_id, props);
  124. var campaign_timeline_chanel_id = pepper.createTimelineChannel(self.m_campaign_timeline_id);
  125. pepper.assignViewerToTimelineChannel(self.m_campaign_timeline_board_template_id, board_viewer_id, campaign_timeline_chanel_id);
  126.  
  127. var viewer = new fabric.Rect({
  128. left: 0,
  129. top: 0,
  130. fill: '#ececec',
  131. id: board_viewer_id,
  132. hasRotatingPoint: false,
  133. borderColor: '#5d5d5d',
  134. stroke: 'black',
  135. strokeWidth: 1,
  136. borderScaleFactor: 0,
  137. lineWidth: 1,
  138. width: 100,
  139. height: 100,
  140. cornerColor: 'black',
  141. cornerSize: 5,
  142. lockRotation: true,
  143. transparentCorners: false
  144. });
  145. self.m_canvas.add(viewer);
  146.  
  147. var props = {
  148. x: 0,
  149. y: 0,
  150. w: viewer.get('width') * self.RATIO,
  151. h: viewer.get('height') * self.RATIO
  152. }
  153. self._updateDimensionsInDB(viewer, props);
  154. });
  155. },
  156.  
  157. /**
  158. Listen to the removal of an existing screen division
  159. @method _listenRemoveDivision
  160. **/
  161. _listenRemoveDivision: function () {
  162. var self = this;
  163. $(Elements.LAYOUT_EDITOR_REMOVE, self.$el).on('click', function () {
  164. if(_.isUndefined(self.m_canvas))
  165. return;
  166. var totalViews = self.m_canvas.getObjects().length;
  167. if (totalViews < 2) {
  168. bootbox.alert($(Elements.MSG_BOOTBOX_AT_LEAST_ONE_DIV).text());
  169. return;
  170. }
  171. var campaign_timeline_chanel_id = pepper.removeTimelineBoardViewerChannel(self.m_selectedViewerID);
  172. pepper.removeBoardTemplateViewer(self.m_campaign_timeline_board_template_id, self.m_selectedViewerID);
  173. pepper.removeChannelFromTimeline(campaign_timeline_chanel_id);
  174. pepper.removeBlocksFromTimelineChannel(campaign_timeline_chanel_id);
  175. self.m_canvas.remove(self.m_canvas.getActiveObject());
  176. self.m_canvas.renderAll();
  177. /*var viewer = self.m_canvas.item(0);
  178. var props = {
  179. y: viewer.get('top'),
  180. x: viewer.get('left'),
  181. w: viewer.get('width') * self.RATIO,
  182. h: viewer.get('height') * self.RATIO
  183. };
  184. self._updateDimensionsInDB(viewer, props);*/
  185. BB.comBroker.fire(BB.EVENTS.VIEWER_REMOVED, this, this, {
  186. campaign_timeline_chanel_id: campaign_timeline_chanel_id
  187. });
  188. pepper.announceTemplateViewerEdited(self.m_campaign_timeline_board_template_id);
  189. });
  190. },
  191.  
  192. /**
  193. Listen to re-order of screen division, putting selected on top
  194. @method _listenPushToTopDivision
  195. **/
  196. _listenPushToTopDivision: function () {
  197. var self = this;
  198. $(Elements.LAYOUT_EDITOR_PUSH_TOP, self.$el).on('click', function () {
  199. var viewer = self.m_canvas.getActiveObject();
  200. if (!viewer)
  201. return;
  202. self.m_canvas.bringToFront(viewer);
  203. self._updateZorder();
  204. });
  205. },
  206.  
  207. /**
  208. Listen to re-order of screen division, putting selected at bottom
  209. @method _listenPushToBottomDivision
  210. **/
  211. _listenPushToBottomDivision: function () {
  212. var self = this;
  213. $(Elements.LAYOUT_EDITOR_PUSH_BOTTOM, self.$el).on('click', function () {
  214. var viewer = self.m_canvas.getActiveObject();
  215. if (!viewer)
  216. return;
  217. self.m_canvas.sendToBack(viewer);
  218. self._updateZorder();
  219. });
  220. },
  221.  
  222. /**
  223. Change the z-order of viewers in pepper
  224. @method _updateZorder
  225. **/
  226. _updateZorder: function () {
  227. var self = this;
  228. var viewers = self.m_canvas.getObjects();
  229. for (var i in viewers){
  230. // log(viewers[i].get('id') + ' ' + i);
  231. pepper.updateTemplateViewerOrder(viewers[i].get('id'), i);
  232. }
  233. var active = self.m_canvas.getActiveObject();
  234. self.m_canvas.setActiveObject(active);
  235. },
  236.  
  237. /**
  238. Listen to selection of next viewer
  239. @method _listenSelectNextDivision
  240. **/
  241. _listenSelectNextDivision: function () {
  242. var self = this;
  243. $(Elements.LAYOUT_EDITOR_NEXT, self.$el).on('click', function () {
  244. var viewer = self.m_canvas.getActiveObject();
  245. var viewIndex = self.m_canvas.getObjects().indexOf(viewer);
  246. var totalViews = self.m_canvas.getObjects().length;
  247. if (viewIndex == totalViews - 1) {
  248. self.m_canvas.setActiveObject(self.m_canvas.item(0));
  249. } else {
  250. self.m_canvas.setActiveObject(self.m_canvas.item(viewIndex + 1));
  251. }
  252. });
  253. },
  254.  
  255. /**
  256. Unload the editor from DOM using the StackView animated slider
  257. @method selectView
  258. **/
  259. _deSelectView: function () {
  260. var self = this;
  261. self._destroy();
  262. self.m_property.resetPropertiesView();
  263. self.options.stackView.slideToPage(self.options.from, 'left');
  264. },
  265.  
  266. /**
  267. Create the canvas to render the screen division
  268. @method _canvasFactory
  269. @param {Number} i_width
  270. @param {Number} i_height
  271. **/
  272. _canvasFactory: function (i_width, i_height) {
  273. var self = this;
  274.  
  275. var offsetH = i_height / 2;
  276. var offsetW = (i_width / 2) + 30;
  277. self.m_canvasID = _.uniqueId('screenLayoutEditorCanvas');
  278. $('#screenLayoutEditorCanvasWrap').append('' +
  279. '<div>' +
  280. '<span align="center">' + self.m_resolution.split('x')[0] + 'px </span>' +
  281. '<canvas id="' + self.m_canvasID + '" width="' + i_width + 'px" height="' + i_height + 'px" style="border: 1px solid rgb(170, 170, 170);"></canvas>' +
  282. '<span style="position: relative; top: -' + offsetH + 'px; left: -' + offsetW + 'px;">' + self.m_resolution.split('x')[1] + 'px</span>' +
  283. '</div>');
  284.  
  285. self.m_canvas = new fabric.Canvas(self.m_canvasID);
  286. self.m_canvas.selection = false;
  287.  
  288. var screenTemplateData = {
  289. orientation: self.m_orientation,
  290. resolution: self.m_resolution,
  291. screenProps: self.m_screenProps,
  292. scale: self.RATIO
  293. };
  294.  
  295. var screenTemplate = new ScreenTemplateFactory({
  296. i_screenTemplateData: screenTemplateData,
  297. i_selfDestruct: true,
  298. i_owner: this});
  299.  
  300. var rects = screenTemplate.getDivisions();
  301.  
  302. for (var i = 0; i < rects.length; i++) {
  303. var rectProperties = rects[i];
  304. var rect = new fabric.Rect({
  305. left: rectProperties.x.baseVal.value,
  306. top: rectProperties.y.baseVal.value,
  307. fill: '#ececec',
  308. id: $(rectProperties).data('campaign_timeline_board_viewer_id'),
  309. hasRotatingPoint: false,
  310. borderColor: '#5d5d5d',
  311. stroke: 'black',
  312. strokeWidth: 1,
  313. borderScaleFactor: 0,
  314. lineWidth: 1,
  315. width: rectProperties.width.baseVal.value,
  316. height: rectProperties.height.baseVal.value,
  317. cornerColor: 'black',
  318. cornerSize: 5,
  319. lockRotation: true,
  320. transparentCorners: false
  321. });
  322. self.m_canvas.add(rect);
  323.  
  324. //rect.on('selected', function () {
  325. // log('object selected a rectangle');
  326. //});
  327. }
  328.  
  329. //self.m_canvas.on('object:moving', function (e) {
  330. // log('savings: ' + self.m_global_board_template_id);
  331. //});
  332.  
  333. setTimeout(function () {
  334. if (!self.m_canvas)
  335. return;
  336. self.m_canvas.setHeight(i_height);
  337. self.m_canvas.setWidth(i_width);
  338. self.m_canvas.renderAll();
  339. }, 500);
  340. },
  341.  
  342. /**
  343. Listen to changes on selecting the background canvas
  344. @method _listenBackgroundSelected
  345. **/
  346. _listenBackgroundSelected: function () {
  347. var self = this;
  348. self.m_bgSelectedHandler = function (e) {
  349. self.m_property.resetPropertiesView();
  350. };
  351. self.m_canvas.on('selection:cleared', self.m_bgSelectedHandler);
  352. },
  353.  
  354. /**
  355. Listen to changes in viewer overlaps
  356. @method _listenObjectsOverlap
  357. **/
  358. _listenObjectsOverlap: function () {
  359. var self = this;
  360. self.m_onOverlap = function (options) {
  361. options.target.setCoords();
  362. self.m_canvas.forEachObject(function (obj) {
  363. if (obj === options.target) return;
  364. obj.setOpacity(options.target.intersectsWithObject(obj) ? 0.5 : 1);
  365. });
  366. }
  367.  
  368. self.m_canvas.on({
  369. 'object:moving': self.m_onOverlap,
  370. 'object:scaling': self.m_onOverlap,
  371. 'object:rotating': self.m_onOverlap
  372. });
  373. },
  374.  
  375. /**
  376. Make sure that at least one screen division is visible within the canvas
  377. @method _enforceViewerVisible
  378. **/
  379. _enforceViewerVisible: function () {
  380. var self = this;
  381. var pass = 0;
  382. var viewer;
  383. self.m_canvas.forEachObject(function (o) {
  384. viewer = o;
  385. if (pass)
  386. return;
  387. if (o.get('left') < (0 - o.get('width')) + 20) {
  388. } else if (o.get('left') > self.m_canvas.getWidth() - 20) {
  389. } else if (o.get('top') < (0 - o.get('height') + 20)) {
  390. } else if (o.get('top') > self.m_canvas.getHeight() - 20) {
  391. } else {
  392. pass = 1;
  393. }
  394. });
  395. if (!pass && viewer) {
  396. viewer.set({left: 0, top: 0}).setCoords();
  397. viewer.setCoords();
  398. self.m_canvas.renderAll();
  399. bootbox.alert({
  400. message: $(Elements.MSG_BOOTBOX_AT_LEAST_ONE_DIV).text(),
  401. title: $(Elements.MSG_BOOTBOX_SCREEN_DIV_POS_RESET).text()
  402. });
  403. var props = {
  404. x: viewer.get('top'),
  405. y: viewer.get('left'),
  406. w: viewer.get('width') * self.RATIO,
  407. h: viewer.get('height') * self.RATIO
  408. }
  409. self._updateDimensionsInDB(viewer, props);
  410. }
  411. },
  412.  
  413. /**
  414. Enforce minimum x y w h props
  415. @method self._enforceViewerMinimums(i_viewer);
  416. @param {Object} i_rect
  417. **/
  418. _enforceViewerMinimums: function (i_viewer) {
  419. var self = this;
  420. var MIN_SIZE = 50;
  421. if ((i_viewer.width * self.RATIO) < MIN_SIZE || (i_viewer.height * self.RATIO) < MIN_SIZE) {
  422. i_viewer.width = MIN_SIZE / self.RATIO;
  423. i_viewer.height = MIN_SIZE / self.RATIO;
  424. var props = {
  425. x: i_viewer.get('top'),
  426. y: i_viewer.get('left'),
  427. w: MIN_SIZE,
  428. h: MIN_SIZE
  429. }
  430. self._updateDimensionsInDB(i_viewer, props);
  431. }
  432. },
  433.  
  434. /**
  435. Listen to changes in a viewer changes in cords and update pepper
  436. @method i_props
  437. **/
  438. _listenObjectChanged: function () {
  439. var self = this;
  440. self.m_objectMovingHandler = _.debounce(function (e) {
  441. var o = e.target;
  442. if (o.width != o.currentWidth || o.height != o.currentHeight) {
  443. o.width = o.currentWidth;
  444. o.height = o.currentHeight;
  445. o.scaleX = 1;
  446. o.scaleY = 1;
  447. }
  448.  
  449. self._enforceViewerMinimums(o);
  450. self._enforceViewerVisible();
  451.  
  452. var x = BB.lib.parseToFloatDouble(o.left) * self.RATIO;
  453. var y = BB.lib.parseToFloatDouble(o.top) * self.RATIO;
  454. var w = BB.lib.parseToFloatDouble(o.currentWidth) * self.RATIO;
  455. var h = BB.lib.parseToFloatDouble(o.currentHeight) * self.RATIO;
  456. // var a = o.get('angle');
  457. var props = {
  458. w: w,
  459. h: h,
  460. x: x,
  461. y: y
  462. };
  463. self.m_property.viewPanel(Elements.VIEWER_EDIT_PROPERTIES);
  464. self.m_dimensionProps.setValues(props);
  465. self.m_selectedViewerID = o.id;
  466. self._updateDimensionsInDB(o, props);
  467.  
  468. }, 200);
  469.  
  470. self.m_canvas.on({
  471. 'object:moving': self.m_objectMovingHandler,
  472. 'object:scaling': self.m_objectMovingHandler,
  473. 'object:selected': self.m_objectMovingHandler,
  474. 'object:modified': self.m_objectMovingHandler
  475. });
  476. },
  477.  
  478. /**
  479. Move the object / viewer to new set of coords
  480. @method _moveViewer
  481. @param {Object} i_props
  482. **/
  483. _moveViewer: function (i_props) {
  484. var self = this;
  485. // log('moving viewer');
  486. var viewer = self.m_canvas.getActiveObject();
  487. if (viewer) {
  488. viewer.setWidth(i_props.w / self.RATIO);
  489. viewer.setHeight(i_props.h / self.RATIO);
  490. viewer.set('left', i_props.x / self.RATIO);
  491. viewer.set('top', i_props.y / self.RATIO);
  492.  
  493. self._enforceViewerMinimums(viewer);
  494. self._enforceViewerVisible();
  495.  
  496. viewer.setCoords();
  497. self.m_canvas.renderAll();
  498. }
  499. },
  500.  
  501. /**
  502. Update Pepper with latest object dimensions
  503. @method _updateDimensionsInDB
  504. @param {Object} i_props
  505. **/
  506. _updateDimensionsInDB: function (i_viewer, i_props) {
  507. var self = this;
  508. log('Pepper ' +i_viewer.get('id') + ' ' + JSON.stringify(i_props));
  509. pepper.setBoardTemplateViewer(self.m_campaign_timeline_board_template_id, i_viewer.get('id'), i_props);
  510. i_viewer.setCoords();
  511. self.m_canvas.renderAll();
  512. },
  513.  
  514. /**
  515. One exit UI destroy all members
  516. @method _destroy
  517. **/
  518. _destroy: function () {
  519. var self = this;
  520. if (!_.isUndefined(self.m_canvas)){
  521. self.m_canvas.off('selection:cleared', self.m_bgSelectedHandler);
  522. self.m_canvas.off({
  523. 'object:moving': self.m_objectMovingHandler,
  524. 'object:scaling': self.m_objectMovingHandler,
  525. 'object:selected': self.m_objectMovingHandler,
  526. 'object:modified': self.m_objectMovingHandler
  527. });
  528. self.m_canvas.off({
  529. 'object:moving': self.m_onOverlap,
  530. 'object:scaling': self.m_onOverlap,
  531. 'object:rotating': self.m_onOverlap
  532. });
  533. self.m_canvas.clear().renderAll();
  534. }
  535. $('#screenLayoutEditorCanvasWrap').empty()
  536. self.m_canvasID = undefined;
  537. self.m_canvas = undefined;
  538. self.m_campaign_timeline_id = undefined;
  539. self.m_campaign_timeline_board_template_id = undefined;
  540. self.m_screenProps = undefined;
  541. self.m_orientation = undefined;
  542. self.m_resolution = undefined;
  543. self.m_global_board_template_id = undefined;
  544. self.m_selectedViewerID = undefined;
  545. }
  546. });
  547.  
  548. return ScreenLayoutEditorView;
  549. });
  550.  
  551.