/**
Singleton Screen Layout editor is used to create / edit an existing screen division layout (aka: template > viewers) and when done
editing, create new ScreenTemplates via ScreenTemplateFactory for both Thumb and main Timeline UIs
@class ScreenLayoutEditorView
@constructor
@return {object} instantiated ScreenLayoutEditorView
**/
define(['jquery', 'backbone', 'StackView', 'ScreenTemplateFactory'], function ($, Backbone, StackView, ScreenTemplateFactory) {
/**
Custom event fired when a screen division / viewer has been removed
@event VIEWER_REMOVED
@param {This} caller
@param {Self} context caller
@param {Event} rss link
@static
@final
**/
BB.EVENTS.VIEWER_REMOVED = 'VIEWER_REMOVED';
BB.SERVICES.SCREEN_LAYOUT_EDITOR_VIEW = 'ScreenLayoutEditorView';
var ScreenLayoutEditorView = BB.View.extend({
/**
Constructor
@method initialize
**/
initialize: function () {
var self = this;
BB.comBroker.setService(BB.SERVICES['SCREEN_LAYOUT_EDITOR_VIEW'], self);
self.RATIO = 4;
self.m_canvas = undefined;
self.m_canvasID = undefined;
self.m_selectedViewerID = undefined;
self.m_dimensionProps = undefined;
this.m_property = BB.comBroker.getService(BB.SERVICES['PROPERTIES_VIEW']);
self.m_property.initPanel(Elements.VIEWER_EDIT_PROPERTIES);
$(this.el).find('#prev').on('click', function (e) {
self._deSelectView();
BB.comBroker.fire(BB.EVENTS.CAMPAIGN_TIMELINE_CHANGED, self);
return false;
});
self._listenAddDivision();
self._listenRemoveDivision();
self._listenPushToTopDivision();
self._listenPushToBottomDivision();
self._listenSelectNextDivision();
self.listenTo(self.options.stackView, BB.EVENTS.SELECTED_STACK_VIEW, function (e) {
if (e == self) {
if (self.m_dimensionProps == undefined) {
require(['DimensionProps'], function (DimensionProps) {
self.m_dimensionProps = new DimensionProps({
appendTo: Elements.VIEWER_DIMENSIONS,
showAngle: false
});
$(self.m_dimensionProps).on('changed', function (e) {
var props = e.target.getValues();
self._updateDimensionsInDB(self.m_canvas.getActiveObject(), props);
self._moveViewer(props);
});
self._render();
});
} else {
self._render();
}
}
});
},
/**
Load the editor into DOM using the StackView using animation slider
@method selectView
**/
selectView: function (i_campaign_timeline_id, i_campaign_timeline_board_template_id) {
var self = this;
self.m_campaign_timeline_id = i_campaign_timeline_id;
self.m_campaign_timeline_board_template_id = i_campaign_timeline_board_template_id;
self.m_global_board_template_id = pepper.getGlobalTemplateIdOfTimeline(i_campaign_timeline_board_template_id);
self.m_screenProps = pepper.getTemplateViewersScreenProps(self.m_campaign_timeline_id, self.m_campaign_timeline_board_template_id);
self.m_orientation = BB.comBroker.getService(BB.SERVICES['ORIENTATION_SELECTOR_VIEW']).getOrientation();
self.m_resolution = BB.comBroker.getService(BB.SERVICES['RESOLUTION_SELECTOR_VIEW']).getResolution();
self.options.stackView.slideToPage(self, 'right');
require(['fabric'], function () {
var w = parseInt(self.m_resolution.split('x')[0]) / self.RATIO;
var h = parseInt(self.m_resolution.split('x')[1]) / self.RATIO;
self._canvasFactory(w, h);
self._listenObjectChanged();
self._listenObjectsOverlap();
self._listenBackgroundSelected();
})
},
/**
On render load default dashboard properties
@method _render
**/
_render: function () {
var self = this;
self.m_property.resetPropertiesView();
},
/**
Listen to the addition of a new viewer
@method (totalViews - i)
**/
_listenAddDivision: function () {
var self = this;
$(Elements.LAYOUT_EDITOR_ADD_NEW, self.$el).on('click', function () {
var props = {
x: 0,
y: 0,
w: 100,
h: 100
}
var board_viewer_id = pepper.createViewer(self.m_global_board_template_id, props);
var campaign_timeline_chanel_id = pepper.createTimelineChannel(self.m_campaign_timeline_id);
pepper.assignViewerToTimelineChannel(self.m_campaign_timeline_board_template_id, board_viewer_id, campaign_timeline_chanel_id);
var viewer = new fabric.Rect({
left: 0,
top: 0,
fill: '#ececec',
id: board_viewer_id,
hasRotatingPoint: false,
borderColor: '#5d5d5d',
stroke: 'black',
strokeWidth: 1,
borderScaleFactor: 0,
lineWidth: 1,
width: 100,
height: 100,
cornerColor: 'black',
cornerSize: 5,
lockRotation: true,
transparentCorners: false
});
self.m_canvas.add(viewer);
var props = {
x: 0,
y: 0,
w: viewer.get('width') * self.RATIO,
h: viewer.get('height') * self.RATIO
}
self._updateDimensionsInDB(viewer, props);
});
},
/**
Listen to the removal of an existing screen division
@method _listenRemoveDivision
**/
_listenRemoveDivision: function () {
var self = this;
$(Elements.LAYOUT_EDITOR_REMOVE, self.$el).on('click', function () {
if(_.isUndefined(self.m_canvas))
return;
var totalViews = self.m_canvas.getObjects().length;
if (totalViews < 2) {
bootbox.alert($(Elements.MSG_BOOTBOX_AT_LEAST_ONE_DIV).text());
return;
}
var campaign_timeline_chanel_id = pepper.removeTimelineBoardViewerChannel(self.m_selectedViewerID);
pepper.removeBoardTemplateViewer(self.m_campaign_timeline_board_template_id, self.m_selectedViewerID);
pepper.removeChannelFromTimeline(campaign_timeline_chanel_id);
pepper.removeBlocksFromTimelineChannel(campaign_timeline_chanel_id);
self.m_canvas.remove(self.m_canvas.getActiveObject());
self.m_canvas.renderAll();
/*var viewer = self.m_canvas.item(0);
var props = {
y: viewer.get('top'),
x: viewer.get('left'),
w: viewer.get('width') * self.RATIO,
h: viewer.get('height') * self.RATIO
};
self._updateDimensionsInDB(viewer, props);*/
BB.comBroker.fire(BB.EVENTS.VIEWER_REMOVED, this, this, {
campaign_timeline_chanel_id: campaign_timeline_chanel_id
});
pepper.announceTemplateViewerEdited(self.m_campaign_timeline_board_template_id);
});
},
/**
Listen to re-order of screen division, putting selected on top
@method _listenPushToTopDivision
**/
_listenPushToTopDivision: function () {
var self = this;
$(Elements.LAYOUT_EDITOR_PUSH_TOP, self.$el).on('click', function () {
var viewer = self.m_canvas.getActiveObject();
if (!viewer)
return;
self.m_canvas.bringToFront(viewer);
self._updateZorder();
});
},
/**
Listen to re-order of screen division, putting selected at bottom
@method _listenPushToBottomDivision
**/
_listenPushToBottomDivision: function () {
var self = this;
$(Elements.LAYOUT_EDITOR_PUSH_BOTTOM, self.$el).on('click', function () {
var viewer = self.m_canvas.getActiveObject();
if (!viewer)
return;
self.m_canvas.sendToBack(viewer);
self._updateZorder();
});
},
/**
Change the z-order of viewers in pepper
@method _updateZorder
**/
_updateZorder: function () {
var self = this;
var viewers = self.m_canvas.getObjects();
for (var i in viewers){
// log(viewers[i].get('id') + ' ' + i);
pepper.updateTemplateViewerOrder(viewers[i].get('id'), i);
}
var active = self.m_canvas.getActiveObject();
self.m_canvas.setActiveObject(active);
},
/**
Listen to selection of next viewer
@method _listenSelectNextDivision
**/
_listenSelectNextDivision: function () {
var self = this;
$(Elements.LAYOUT_EDITOR_NEXT, self.$el).on('click', function () {
var viewer = self.m_canvas.getActiveObject();
var viewIndex = self.m_canvas.getObjects().indexOf(viewer);
var totalViews = self.m_canvas.getObjects().length;
if (viewIndex == totalViews - 1) {
self.m_canvas.setActiveObject(self.m_canvas.item(0));
} else {
self.m_canvas.setActiveObject(self.m_canvas.item(viewIndex + 1));
}
});
},
/**
Unload the editor from DOM using the StackView animated slider
@method selectView
**/
_deSelectView: function () {
var self = this;
self._destroy();
self.m_property.resetPropertiesView();
self.options.stackView.slideToPage(self.options.from, 'left');
},
/**
Create the canvas to render the screen division
@method _canvasFactory
@param {Number} i_width
@param {Number} i_height
**/
_canvasFactory: function (i_width, i_height) {
var self = this;
var offsetH = i_height / 2;
var offsetW = (i_width / 2) + 30;
self.m_canvasID = _.uniqueId('screenLayoutEditorCanvas');
$('#screenLayoutEditorCanvasWrap').append('' +
'<div>' +
'<span align="center">' + self.m_resolution.split('x')[0] + 'px </span>' +
'<canvas id="' + self.m_canvasID + '" width="' + i_width + 'px" height="' + i_height + 'px" style="border: 1px solid rgb(170, 170, 170);"></canvas>' +
'<span style="position: relative; top: -' + offsetH + 'px; left: -' + offsetW + 'px;">' + self.m_resolution.split('x')[1] + 'px</span>' +
'</div>');
self.m_canvas = new fabric.Canvas(self.m_canvasID);
self.m_canvas.selection = false;
var screenTemplateData = {
orientation: self.m_orientation,
resolution: self.m_resolution,
screenProps: self.m_screenProps,
scale: self.RATIO
};
var screenTemplate = new ScreenTemplateFactory({
i_screenTemplateData: screenTemplateData,
i_selfDestruct: true,
i_owner: this});
var rects = screenTemplate.getDivisions();
for (var i = 0; i < rects.length; i++) {
var rectProperties = rects[i];
var rect = new fabric.Rect({
left: rectProperties.x.baseVal.value,
top: rectProperties.y.baseVal.value,
fill: '#ececec',
id: $(rectProperties).data('campaign_timeline_board_viewer_id'),
hasRotatingPoint: false,
borderColor: '#5d5d5d',
stroke: 'black',
strokeWidth: 1,
borderScaleFactor: 0,
lineWidth: 1,
width: rectProperties.width.baseVal.value,
height: rectProperties.height.baseVal.value,
cornerColor: 'black',
cornerSize: 5,
lockRotation: true,
transparentCorners: false
});
self.m_canvas.add(rect);
//rect.on('selected', function () {
// log('object selected a rectangle');
//});
}
//self.m_canvas.on('object:moving', function (e) {
// log('savings: ' + self.m_global_board_template_id);
//});
setTimeout(function () {
if (!self.m_canvas)
return;
self.m_canvas.setHeight(i_height);
self.m_canvas.setWidth(i_width);
self.m_canvas.renderAll();
}, 500);
},
/**
Listen to changes on selecting the background canvas
@method _listenBackgroundSelected
**/
_listenBackgroundSelected: function () {
var self = this;
self.m_bgSelectedHandler = function (e) {
self.m_property.resetPropertiesView();
};
self.m_canvas.on('selection:cleared', self.m_bgSelectedHandler);
},
/**
Listen to changes in viewer overlaps
@method _listenObjectsOverlap
**/
_listenObjectsOverlap: function () {
var self = this;
self.m_onOverlap = function (options) {
options.target.setCoords();
self.m_canvas.forEachObject(function (obj) {
if (obj === options.target) return;
obj.setOpacity(options.target.intersectsWithObject(obj) ? 0.5 : 1);
});
}
self.m_canvas.on({
'object:moving': self.m_onOverlap,
'object:scaling': self.m_onOverlap,
'object:rotating': self.m_onOverlap
});
},
/**
Make sure that at least one screen division is visible within the canvas
@method _enforceViewerVisible
**/
_enforceViewerVisible: function () {
var self = this;
var pass = 0;
var viewer;
self.m_canvas.forEachObject(function (o) {
viewer = o;
if (pass)
return;
if (o.get('left') < (0 - o.get('width')) + 20) {
} else if (o.get('left') > self.m_canvas.getWidth() - 20) {
} else if (o.get('top') < (0 - o.get('height') + 20)) {
} else if (o.get('top') > self.m_canvas.getHeight() - 20) {
} else {
pass = 1;
}
});
if (!pass && viewer) {
viewer.set({left: 0, top: 0}).setCoords();
viewer.setCoords();
self.m_canvas.renderAll();
bootbox.alert({
message: $(Elements.MSG_BOOTBOX_AT_LEAST_ONE_DIV).text(),
title: $(Elements.MSG_BOOTBOX_SCREEN_DIV_POS_RESET).text()
});
var props = {
x: viewer.get('top'),
y: viewer.get('left'),
w: viewer.get('width') * self.RATIO,
h: viewer.get('height') * self.RATIO
}
self._updateDimensionsInDB(viewer, props);
}
},
/**
Enforce minimum x y w h props
@method self._enforceViewerMinimums(i_viewer);
@param {Object} i_rect
**/
_enforceViewerMinimums: function (i_viewer) {
var self = this;
var MIN_SIZE = 50;
if ((i_viewer.width * self.RATIO) < MIN_SIZE || (i_viewer.height * self.RATIO) < MIN_SIZE) {
i_viewer.width = MIN_SIZE / self.RATIO;
i_viewer.height = MIN_SIZE / self.RATIO;
var props = {
x: i_viewer.get('top'),
y: i_viewer.get('left'),
w: MIN_SIZE,
h: MIN_SIZE
}
self._updateDimensionsInDB(i_viewer, props);
}
},
/**
Listen to changes in a viewer changes in cords and update pepper
@method i_props
**/
_listenObjectChanged: function () {
var self = this;
self.m_objectMovingHandler = _.debounce(function (e) {
var o = e.target;
if (o.width != o.currentWidth || o.height != o.currentHeight) {
o.width = o.currentWidth;
o.height = o.currentHeight;
o.scaleX = 1;
o.scaleY = 1;
}
self._enforceViewerMinimums(o);
self._enforceViewerVisible();
var x = BB.lib.parseToFloatDouble(o.left) * self.RATIO;
var y = BB.lib.parseToFloatDouble(o.top) * self.RATIO;
var w = BB.lib.parseToFloatDouble(o.currentWidth) * self.RATIO;
var h = BB.lib.parseToFloatDouble(o.currentHeight) * self.RATIO;
// var a = o.get('angle');
var props = {
w: w,
h: h,
x: x,
y: y
};
self.m_property.viewPanel(Elements.VIEWER_EDIT_PROPERTIES);
self.m_dimensionProps.setValues(props);
self.m_selectedViewerID = o.id;
self._updateDimensionsInDB(o, props);
}, 200);
self.m_canvas.on({
'object:moving': self.m_objectMovingHandler,
'object:scaling': self.m_objectMovingHandler,
'object:selected': self.m_objectMovingHandler,
'object:modified': self.m_objectMovingHandler
});
},
/**
Move the object / viewer to new set of coords
@method _moveViewer
@param {Object} i_props
**/
_moveViewer: function (i_props) {
var self = this;
// log('moving viewer');
var viewer = self.m_canvas.getActiveObject();
if (viewer) {
viewer.setWidth(i_props.w / self.RATIO);
viewer.setHeight(i_props.h / self.RATIO);
viewer.set('left', i_props.x / self.RATIO);
viewer.set('top', i_props.y / self.RATIO);
self._enforceViewerMinimums(viewer);
self._enforceViewerVisible();
viewer.setCoords();
self.m_canvas.renderAll();
}
},
/**
Update Pepper with latest object dimensions
@method _updateDimensionsInDB
@param {Object} i_props
**/
_updateDimensionsInDB: function (i_viewer, i_props) {
var self = this;
log('Pepper ' +i_viewer.get('id') + ' ' + JSON.stringify(i_props));
pepper.setBoardTemplateViewer(self.m_campaign_timeline_board_template_id, i_viewer.get('id'), i_props);
i_viewer.setCoords();
self.m_canvas.renderAll();
},
/**
One exit UI destroy all members
@method _destroy
**/
_destroy: function () {
var self = this;
if (!_.isUndefined(self.m_canvas)){
self.m_canvas.off('selection:cleared', self.m_bgSelectedHandler);
self.m_canvas.off({
'object:moving': self.m_objectMovingHandler,
'object:scaling': self.m_objectMovingHandler,
'object:selected': self.m_objectMovingHandler,
'object:modified': self.m_objectMovingHandler
});
self.m_canvas.off({
'object:moving': self.m_onOverlap,
'object:scaling': self.m_onOverlap,
'object:rotating': self.m_onOverlap
});
self.m_canvas.clear().renderAll();
}
$('#screenLayoutEditorCanvasWrap').empty()
self.m_canvasID = undefined;
self.m_canvas = undefined;
self.m_campaign_timeline_id = undefined;
self.m_campaign_timeline_board_template_id = undefined;
self.m_screenProps = undefined;
self.m_orientation = undefined;
self.m_resolution = undefined;
self.m_global_board_template_id = undefined;
self.m_selectedViewerID = undefined;
}
});
return ScreenLayoutEditorView;
});