var _ = require('underscore'); var Backbone = require('backbone'); var Collections = require('../models/collections'); var CommitCollection = Collections.CommitCollection; var BranchCollection = Collections.BranchCollection; var TagCollection = Collections.TagCollection; var EventBaton = require('../util/eventBaton').EventBaton; var GitVisuals = require('../visuals').GitVisuals; var Visualization = Backbone.View.extend({ initialize: function(options) { options = options || {}; this.options = options; this.customEvents = _.clone(Backbone.Events); this.containerElement = options.containerElement; var _this = this; // we want to add our canvas somewhere var container = options.containerElement || $('#canvasHolder')[0]; new Raphael(container, 200, 200, function() { // raphael calls with paper as this for some inane reason... var paper = this; // use process.nextTick to go from sync to async process.nextTick(function() { _this.paperInitialize(paper, options); }); }); }, paperInitialize: function(paper, options) { this.treeString = options.treeString; this.paper = paper; var Main = require('../app'); // if we dont want to receive keyoard input (directly), // make a new event baton so git engine steals something that no one // is broadcasting to this.eventBaton = (options.noKeyboardInput) ? new EventBaton({noInput: true}) : Main.getEventBaton(); this.commitCollection = new CommitCollection(); this.branchCollection = new BranchCollection(); this.tagCollection = new TagCollection(); this.gitVisuals = new GitVisuals({ commitCollection: this.commitCollection, branchCollection: this.branchCollection, tagCollection: this.tagCollection, paper: this.paper, noClick: this.options.noClick, isGoalVis: this.options.isGoalVis, smallCanvas: this.options.smallCanvas, visualization: this }); var GitEngine = require('../git').GitEngine; this.gitEngine = new GitEngine({ collection: this.commitCollection, branches: this.branchCollection, tags: this.tagCollection, gitVisuals: this.gitVisuals, eventBaton: this.eventBaton }); this.gitEngine.init(); this.gitVisuals.assignGitEngine(this.gitEngine); this.myResize(); $(window).on('resize', function() { this.myResize(); }.bind(this)); // If the visualization is within a draggable container, we need to update the // position whenever the container is moved. this.$el.parents('.ui-draggable').on('drag', function(event, ui) { this.customEvents.trigger('drag', event, ui); this.myResize(); }.bind(this)); this.gitVisuals.drawTreeFirstTime(); if (this.treeString) { this.gitEngine.loadTreeFromString(this.treeString); } if (this.options.zIndex) { this.setTreeIndex(this.options.zIndex); } this.shown = false; this.setTreeOpacity(0); // reflow needed process.nextTick(this.fadeTreeIn.bind(this)); this.customEvents.trigger('gitEngineReady'); this.customEvents.trigger('paperReady'); }, clearOrigin: function() { delete this.originVis; }.bind(this), makeOrigin: function(options) { // oh god, here we go. We basically do a bizarre form of composition here, // where this visualization actually contains another one of itself. this.originVis = new Visualization(_.extend( {}, // copy all of our options over, except... this.options, { // never accept keyboard input or clicks noKeyboardInput: true, noClick: true, treeString: options.treeString } )); // if the z index is set on ours, carry that over this.originVis.customEvents.on('paperReady', function() { var value = $(this.paper.canvas).css('z-index'); this.originVis.setTreeIndex(value); }.bind(this)); // return the newly created visualization which will soon have a git engine return this.originVis; }, originToo: function(methodToCall, args) { if (!this.originVis) { return; } var callMethod = function() { this.originVis[methodToCall].apply(this.originVis, args); }.bind(this); if (this.originVis.paper) { callMethod(); return; } // this is tricky -- sometimes we already have paper initialized but // our origin vis does not (since we kill that on every reset). // in this case lets bind to the custom event on paper ready this.originVis.customEvents.on('paperReady', callMethod); }, setTreeIndex: function(level) { $(this.paper.canvas).css('z-index', level); this.originToo('setTreeIndex', arguments); }, setTreeOpacity: function(level) { if (level === 0) { this.shown = false; } $(this.paper.canvas).css('opacity', level); this.originToo('setTreeOpacity', arguments); }, getAnimationTime: function() { return 300; }, fadeTreeIn: function() { this.shown = true; if (!this.paper) { return; } $(this.paper.canvas).animate({opacity: 1}, this.getAnimationTime()); this.originToo('fadeTreeIn', arguments); }, fadeTreeOut: function() { this.shown = false; $(this.paper.canvas).animate({opacity: 0}, this.getAnimationTime()); this.originToo('fadeTreeOut', arguments); }, hide: function() { this.fadeTreeOut(); // remove click handlers by toggling visibility setTimeout(function() { $(this.paper.canvas).css('visibility', 'hidden'); }.bind(this), this.getAnimationTime()); this.originToo('hide', arguments); }, show: function() { $(this.paper.canvas).css('visibility', 'visible'); setTimeout(this.fadeTreeIn.bind(this), 10); this.originToo('show', arguments); this.myResize(); }, showHarsh: function() { $(this.paper.canvas).css('visibility', 'visible'); this.setTreeOpacity(1); this.originToo('showHarsh', arguments); this.myResize(); }.bind(this), resetFromThisTreeNow: function(treeString) { this.treeString = treeString; // do the same but for origin tree string var oTree = this.getOriginInTreeString(treeString); if (oTree) { var oTreeString = this.gitEngine.printTree(oTree); this.originToo('resetFromThisTreeNow', [oTreeString]); } }, getOriginInTreeString: function(treeString) { var tree = JSON.parse(unescape(treeString)); return tree.originTree; }, reset: function(tree) { var treeString = tree || this.treeString; this.setTreeOpacity(0); if (treeString) { this.gitEngine.loadTreeFromString(treeString); } else { this.gitEngine.defaultInit(); } this.fadeTreeIn(); if (this.originVis) { if (treeString) { var oTree = this.getOriginInTreeString(treeString); this.originToo('reset', [JSON.stringify(oTree)]); } else { // easy this.originToo('reset', arguments); } } }, tearDown: function(options) { options = options || {}; this.gitEngine.tearDown(); this.gitVisuals.tearDown(); delete this.paper; this.originToo('tearDown', arguments); }, die: function() { this.fadeTreeOut(); setTimeout(function() { if (!this.shown) { this.tearDown({fromDie: true}); } }.bind(this), this.getAnimationTime()); this.originToo('die', arguments); }, myResize: function() { if (!this.paper) { return; } var smaller = 1; var el = this.el; var width = el.clientWidth - smaller; var height = el.clientHeight - smaller; // if we don't have a container, we need to set our // position absolutely to whatever we are tracking if (!this.containerElement) { var left = this.$el.offset().left; var top = this.$el.offset().top; $(this.paper.canvas).css({ position: 'absolute', left: left + 'px', top: top + 'px' }); } else { // set position to absolute so we all stack nicely $(this.paper.canvas).css({ position: 'absolute' }); } this.paper.setSize(width, height); this.gitVisuals.canvasResize(width, height); this.originToo('myResize', arguments); } }); exports.Visualization = Visualization;