timeline/js/timeline.js
changeset 66 37492d1ce841
parent 65 03bbfd9cd3c6
child 67 5d1ac260d3ee
--- a/timeline/js/timeline.js	Tue May 22 14:55:44 2012 +0200
+++ b/timeline/js/timeline.js	Wed May 23 18:16:27 2012 +0200
@@ -104,7 +104,7 @@
         end_date_format : '{{0hours}}:{{0minutes}}'
     }, {
         label : "1 Heure",
-        span : 80 * 60 * 1000,
+        span : 60 * 60 * 1000,
         grid_interval : 15 * 60 * 1000,
         grid_date_format : '{{0hours}}:{{0minutes}}',
         start_date_format : '{{dayOfMonth}} {{shortMonthName}} {{0hours}}:{{0minutes}}',
@@ -114,7 +114,8 @@
     central_time: 0,
     sync_now: true,
     url_occurrences: '',
-    occurrences: {}
+    occurrences: [],
+    cluster_spacing: 9
 }
 
 for (var _i = 0; _i < Tlns.Defaults.Timeline.timescales.length; _i++) {
@@ -127,9 +128,12 @@
     + '<div class="Tl-TopBar-Button Tl-Border-Right"><div class="Tl-TopBar-PreviousButton"></div></div><div class="Tl-TopBar-TimeSpan Tl-TopBar-TextBtn Tl-Border-Right">--/--</div>'
     + '<div class="Tl-TopBar-Button Tl-Border-Right"><div class="Tl-TopBar-SyncButton"></div></div><div class="Tl-TopBar-Button Tl-Border-Right"><div class="Tl-TopBar-NextButton"></div></div><div class="Tl-TopBar-Spacer Tl-Border-Right"></div>'
     + '<div class="Tl-TopBar-Timescales">{{#timescales}}<div class="Tl-TopBar-Button Tl-TopBar-TextBtn Tl-Border-Right" data-level="{{level}}">{{label}}</div>{{/timescales}}</div></div>'
-    + '<div class="Tl-BottomPart"><ul class="Tl-UniversLabels"></ul><div class="Tl-MainPart"><div class="Tl-Grid"></div></div></div>';
+    + '<div class="Tl-BottomPart"><ul class="Tl-UniversLabels"></ul><div class="Tl-MainPart"><div class="Tl-Layer Tl-Grid"></div><div class="Tl-Layer Tl-Occurrences"></div></div>'
+    + '<div class="Tl-Overlay-Container"><div class="Tl-Overlay-Box"><div class="Tl-Overlay"><div class="Tl-Overlay-Main"></div><div class="Tl-Overlay-Tip"></div></div></div></div></div>';
 
-Tlns.Templates.Univers = '<span class="Tl-UniversText">{{title}}</span>'
+Tlns.Templates.Univers = '<span class="Tl-UniversText">{{title}}</span>';
+
+Tlns.Templates.Occurrence = '{{#clusters}}<div class="Tl-Cluster Tl-Occ{{type}}" style="left: {{x}}px; top: {{y}}px;" cluster-contents="{{#occurrences}}{{type}}|{{id}},{{/occurrences}}"><div class="Tl-ClusterCount">{{occurrences.length}}</div></div>{{/clusters}}{{#occurrences}}<div class="Tl-Occurrence Tl-Occ{{type}}" occurrence-type="{{type}}" occurrence-id="{{id}}" style="left: {{x}}px; top: {{univers.y}}px;"></div>{{/occurrences}}';
 
 /* Classes */
 
@@ -150,6 +154,7 @@
     this.main_height = this.height - this.$.find('.Tl-TopBar').outerHeight();
     this.$.find('.Tl-BottomPart').css("height", this.main_height + "px");
     this.$.find('.Tl-MainPart').css("width", this.main_width + "px");
+    this.$.find('.Tl-Overlay-Container').css("left", Math.floor(this.$.find('.Tl-BottomPart').outerWidth() - this.main_width / 2) + "px");
     var _o = this.$.find('.Tl-MainPart').offset();
     this.dragging_bounds = {
         left: _o.left,
@@ -193,6 +198,12 @@
         return false;
     });
     
+    this.$.find('.Tl-Overlay-Box').mouseover(function(_event) {
+        $(this).show();
+    }).mouseout(function(_event) {
+        $(this).hide();
+    })
+    
     this.throttledSetTime = _.throttle(function(_time) {
         _this.setTime(_time)
     }, 150);
@@ -206,36 +217,34 @@
 Tlns.Classes.Timeline.prototype.onMouseDown = function(_event) {
     this.mouse_down = true;
     this.is_dragging = false;
+    this.time_at_start = this.central_time;
     this.start_pos = {
         x: _event.pageX,
         y: _event.pageY
     };
-    var _target = $(_event.target);
-    while (!_target.hasClass("Tl-Main") && _target.length) {
-        if (_target.hasClass("Tl-MainPart")) {
-            this.dragging_type = "timeline";
-            this.time_at_start = this.central_time;
-            break;
-        }
-        _target = _target.parent();
+    if (typeof this.dragging_type === "undefined") {
+        this.dragging_type = "timeline";
     }
 }
 
 Tlns.Classes.Timeline.prototype.onMouseUp = function(_event) {
     this.mouse_down = false;
     this.is_dragging = false;
+    this.dragging_type = undefined;
 }
 
 Tlns.Classes.Timeline.prototype.onMouseMove = function(_event) {
     if (this.mouse_down) {
         this.is_dragging = true;
+        this.$.find('.Tl-Overlay-Box').hide();
         if (_event.pageX > this.dragging_bounds.left
             && _event.pageX < this.dragging_bounds.right
             && _event.pageY > this.dragging_bounds.top
-            && _event.pageY < this.dragging_bounds.bottom) {
+            && _event.pageY < this.dragging_bounds.bottom
+        ) {
+            var _newTime = Math.floor(this.time_at_start + ( this.start_pos.x - _event.pageX ) / this.current_scale);
             switch (this.dragging_type) {
                 case "timeline":
-                    var _newTime = Math.floor(this.time_at_start + ( this.start_pos.x - _event.pageX ) / this.current_scale);
                     this.throttledSetTime(_newTime);
                 break;
             }
@@ -253,6 +262,7 @@
     for(var _i = 0; _i < _data.length; _i++) {
         this.univers.push(new Tlns.Classes.Univers(_data[_i], this, _i));
     }
+    this.loadOccurrences();
 }
 
 Tlns.Classes.Timeline.prototype.offsetTime = function(_timeOffset) {
@@ -290,14 +300,14 @@
     }
     var _timescale = this.timescales[this.level],
         _offset = new Date().getTimezoneOffset() * 60000;
-    this.current_scale = this.main_width / (_timescale.span);
-    var _tmin = this.central_time - (_timescale.span / 2),
-        _tmax = this.central_time + (_timescale.span / 2),
-        _grid_width = Math.floor(_timescale.grid_interval * this.current_scale),
-        _roundstart = Math.floor((_tmin - _offset) / _timescale.grid_interval) * _timescale.grid_interval + _offset,
+    this.current_scale = this.main_width / (_timescale.span)
+    this.start_time = this.central_time - (_timescale.span / 2);
+    this.end_time = this.central_time + (_timescale.span / 2);
+    var _grid_width = Math.floor(_timescale.grid_interval * this.current_scale),
+        _roundstart = Math.floor((this.start_time - _offset) / _timescale.grid_interval) * _timescale.grid_interval + _offset,
         _html = '';
-    this.$.find('.Tl-TopBar-TimeSpan').html(Tlns.Utils.dateFormat(_tmin, _timescale.start_date_format) + ' - ' + Tlns.Utils.dateFormat(_tmax, _timescale.end_date_format));
-    for (var _t = _roundstart; _t < _tmax; _t += _timescale.grid_interval) {
+    this.$.find('.Tl-TopBar-TimeSpan').html(Tlns.Utils.dateFormat(this.start_time, _timescale.start_date_format) + ' - ' + Tlns.Utils.dateFormat(this.end_time, _timescale.end_date_format));
+    for (var _t = _roundstart; _t < this.end_time; _t += _timescale.grid_interval) {
         _html += '<div class="Tl-Grid-Column" style="width:' + _grid_width + 'px; left: ' + this.current_scale * (_t - this.central_time) + 'px">'
         + '<div class="Tl-Grid-Label">' + Tlns.Utils.dateFormat(_t, _timescale.grid_date_format) + '</div></div>';
     }
@@ -309,26 +319,165 @@
         + ( _isMajor ? '<div class="Tl-Grid-Label">' + Tlns.Utils.dateFormat(_t, _timescale.date_format) + '</div>' : '' ) + '</div>';
     }
 */
-    if (_tmin <= _now && _tmax >= _now) {
+    if (this.start_time <= _now && this.end_time >= _now) {
         _html += '<div class="Tl-Grid-Now" style="left: ' + this.current_scale * (_now - this.central_time) + 'px"></div>'
     }
     this.$.find('.Tl-Grid').html(_html);
+    this.drawOccurrences();
+}
+
+Tlns.Classes.Timeline.prototype.loadOccurrences = function() {
+    var _url = Mustache.to_html(this.url_occurrences, {
+            from_time: this.start_time,
+            to_time: this.end_time
+        }),
+        _this = this;
+    $.getJSON(_url, function(_data) {
+        _this.onOccurrencesLoaded(_data);
+    });
+}
+
+Tlns.Classes.Timeline.prototype.onOccurrencesLoaded = function(_data) {
+    if (typeof _data.occurrencesNarratives === "object" && _data.occurrencesNarratives !== null) {
+        for (var _i = 0; _i < _data.occurrencesNarratives.length; _i++) {
+            this.createOrUpdateOccurrence("narrative", _data.occurrencesNarratives[_i]);
+        }
+        for (var _i = 0; _i < _data.occurrencesProduction.length; _i++) {
+            this.createOrUpdateOccurrence("production", _data.occurrencesProduction[_i]);
+        }
+    }
+    if (!this.mouse_down) {
+        this.drawOccurrences();
+    }
+}
+
+Tlns.Classes.Timeline.prototype.getOccurrence = function(_type, _id) {
+    return _(this.occurrences).find(function(_occ) {
+        return (_occ.type == _type && _occ.id == _id);
+    });
+}
+
+Tlns.Classes.Timeline.prototype.createOrUpdateOccurrence = function(_type, _data) {
+    var _occurrence = this.getOccurrence(_type, _data.id);
+    if (typeof _occurrence === "undefined") {
+        _occurrence = new Tlns.Classes.Occurrence(this);
+        this.occurrences.push(_occurrence);
+    }
+    _occurrence.update(_type, _data);
+}
+
+Tlns.Classes.Timeline.prototype.showTooltip = function(_x, _y, _html) {
+    this.$.find('.Tl-Overlay-Box').show()
+        .css({
+            left: _x + "px",
+            top: _y + "px"
+        });
+    this.$.find('.Tl-Overlay-Main').html(_html);
+}
+
+Tlns.Classes.Timeline.prototype.hideTooltip = function() {
+    this.$.find('.Tl-Overlay-Box').hide();
 }
 
 Tlns.Classes.Timeline.prototype.drawOccurrences = function() {
+    var _this = this,
+        _visible = _(this.occurrences).filter(function(_occ) {
+        return (_occ.date >= _this.start_time && _occ.date <= _this.end_time && _occ.published);
+    });
+    _(_visible).each(function(_occ) {
+        _occ.x = _this.current_scale * (_occ.date - _this.central_time);
+        _occ.in_cluster = false;
+    });
     
+    var _moved = true;
+    while (_moved) {
+        _moved = false;
+        for (var _i = 0; _i < _visible.length; _i++) {
+            for (var _j = 0; _j < _i; _j++) {
+                if (_visible[_j].univers_id == _visible[_i].univers_id
+                    && _visible[_j].x != _visible[_i].x
+                    && Math.abs(_visible[_j].x-_visible[_i].x) < this.cluster_spacing
+                ) {
+                    _moved = true;
+                    _visible[_i].x = this.cluster_spacing * Math.round(_visible[_i].x / this.cluster_spacing);
+                    _visible[_j].x = this.cluster_spacing * Math.round(_visible[_j].x / this.cluster_spacing);
+                }
+            }
+        }
+    }
+    var _clusters = [];
+    for (var _i = 0; _i < _visible.length; _i++) {
+        for (var _j = 0; _j < _i; _j++) {
+            if (_visible[_j].univers_id == _visible[_i].univers_id && _visible[_j].x == _visible[_i].x) {
+                _visible[_j].in_cluster = true;
+                _visible[_i].in_cluster = true;
+                var _x = _visible[_j].x,
+                    _y = _visible[_j].univers.y;
+                    _cluster = _(_clusters).find(function(_c) { return _c.x == _x && _c.y == _y });
+                if (typeof _cluster === "undefined") {
+                    _cluster = { x: _x, y: _y, occurrences: [] };
+                    _clusters.push(_cluster);
+                }
+                if ("undefined" === typeof _(_cluster.occurrences).find(function(_o) {
+                    return _o.type == _visible[_j].type && _o.id == _visible[_j].id;
+                })) {
+                    _cluster.occurrences.push({type: _visible[_j].type, id: _visible[_j].id});
+                }
+                if ("undefined" === typeof _(_cluster.occurrences).find(function(_o) {
+                    return _o.type == _visible[_i].type && _o.id == _visible[_i].id;
+                })) {
+                    _cluster.occurrences.push({type: _visible[_i].type, id: _visible[_i].id});
+                }
+            }
+        }
+    }
+    _(_clusters).each(function(_cluster) {
+        _cluster.type = _cluster.occurrences[0].type;
+        for (var _i = 1; _i < _cluster.occurrences.length; _i++) {
+            if (_cluster.occurrences[_i].type !== _cluster.type) {
+                _cluster.type = "both";
+                break;
+            }
+        }
+    });
+    
+    var _html = Mustache.to_html(Tlns.Templates.Occurrence, {
+        occurrences:_(_visible).reject(function(_o) {return _o.in_cluster}),
+        clusters: _clusters
+    });
+    this.$.find('.Tl-Occurrences').html(_html);
+    this.$.find('.Tl-Occurrence').mousedown(function() {
+        _this.dragging_type = "occurrence"
+    }).mouseover(function() {
+        var _el = $(this),
+            _occurrence = _this.getOccurrence(_el.attr("occurrence-type"),_el.attr("occurrence-id"));
+        _this.showTooltip(_occurrence.x, _occurrence.univers.y, _occurrence.title);
+    }).mouseout(function() {
+        _this.hideTooltip();
+    });
 }
 
+Tlns.Classes.Timeline.prototype.getUnivers = function(_id) {
+    return _(this.univers).find(function(_univ) {
+        return (_univ.id == _id);
+    });
+}
+
+/*
+ * Univers
+ */
+
 Tlns.Classes.Univers = function(_data, _timeline, _index) {
     this.id = _data.id;
     this.index = _index;
     this.title = _data.nom;
     this.mainCharacter = _data.personnage;
+    this.y = (_timeline.univers_height * _index);
 
     this.$label = $('<li>').css({
         height : _timeline.univers_height + "px"
     }).html(Mustache.to_html(Tlns.Templates.Univers, this))
-    .addClass((_index % 2) ? 'Tl-Line-Odd' : 'Tl-Line-Even');
+      .addClass((_index % 2) ? 'Tl-Line-Odd' : 'Tl-Line-Even');
     
     _timeline.$.find('.Tl-UniversLabels').append(this.$label);
     var _txt = _data.nom,
@@ -339,3 +488,29 @@
         _span.html(_txt + '&hellip;');
     }
 }
+
+/*
+ * Occurrence
+ */
+
+Tlns.Classes.Occurrence = function(_timeline) {
+    this.timeline = _timeline;
+}
+
+Tlns.Classes.Occurrence.prototype.update = function(_type, _data) {
+    this.type = _type;
+    this.id = _data.id || _.uniqueId();
+    this.date = _data.date || _data.datePublication;
+    this.title = _data.titre || "<untitled>";
+    this.univers_id = _data.univers;
+    this.univers = this.timeline.getUnivers(this.univers_id);
+    this.status = _data.statut;
+    this.published = _data.publie || false;
+    this.locked = _data.verrouille || false;
+    this.characters = _data.personnagesSecondaires || [];
+    this.dependsOn = _data.dependDe || [];
+}
+
+Tlns.Classes.Occurrence.prototype.toString = function() {
+    return "Occurrence " + this.type + ': "' + this.title + '"';
+}