diff -r 67f3a24a7c01 -r fa8035885814 client/js/renderer/scene.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/js/renderer/scene.js Mon May 05 17:43:37 2014 +0200 @@ -0,0 +1,1204 @@ +"use strict"; + +define(['jquery', 'underscore', 'requtils', 'renderer/miniframe'], function ($, _, requtils, MiniFrame) { + + var Utils = requtils.getUtils(); + + /* Scene Begin */ + + var Scene = function(_renkan) { + this.renkan = _renkan; + this.$ = $(".Rk-Render"); + this.representations = []; + this.$.html(this.template(_renkan)); + this.onStatusChange(); + this.canvas_$ = this.$.find(".Rk-Canvas"); + this.labels_$ = this.$.find(".Rk-Labels"); + this.editor_$ = this.$.find(".Rk-Editor"); + this.notif_$ = this.$.find(".Rk-Notifications"); + paper.setup(this.canvas_$[0]); + this.scale = 1; + this.initialScale = 1; + this.offset = paper.view.center; + this.totalScroll = 0; + this.mouse_down = false; + this.click_target = null; + this.selected_target = null; + this.edge_layer = new paper.Layer(); + this.node_layer = new paper.Layer(); + this.buttons_layer = new paper.Layer(); + this.delete_list = []; + + if (_renkan.options.show_minimap) { + this.minimap = { + background_layer: new paper.Layer(), + edge_layer: new paper.Layer(), + node_layer: new paper.Layer(), + node_group: new paper.Group(), + size: new paper.Size( _renkan.options.minimap_width, _renkan.options.minimap_height ) + }; + + this.minimap.background_layer.activate(); + this.minimap.topleft = paper.view.bounds.bottomRight.subtract(this.minimap.size); + this.minimap.rectangle = new paper.Path.Rectangle(this.minimap.topleft.subtract([2,2]), this.minimap.size.add([4,4])); + this.minimap.rectangle.fillColor = _renkan.options.minimap_background_color; + this.minimap.rectangle.strokeColor = _renkan.options.minimap_border_color; + this.minimap.rectangle.strokeWidth = 4; + this.minimap.offset = new paper.Point(this.minimap.size.divide(2)); + this.minimap.scale = .1; + + this.minimap.node_layer.activate(); + this.minimap.cliprectangle = new paper.Path.Rectangle(this.minimap.topleft, this.minimap.size); + this.minimap.node_group.addChild(this.minimap.cliprectangle); + this.minimap.node_group.clipped = true; + this.minimap.miniframe = new paper.Path.Rectangle(this.minimap.topleft, this.minimap.size); + this.minimap.node_group.addChild(this.minimap.miniframe); + this.minimap.miniframe.fillColor = '#c0c0ff'; + this.minimap.miniframe.opacity = .3; + this.minimap.miniframe.strokeColor = '#000080'; + this.minimap.miniframe.strokeWidth = 3; + this.minimap.miniframe.__representation = new MiniFrame(this, null); + } + + this.throttledPaperDraw = _(function() { + paper.view.draw(); + }).throttle(100); + + this.bundles = []; + this.click_mode = false; + + var _this = this, + _allowScroll = true, + _originalScale = 1, + _zooming = false, + _lastTapX = 0, + _lastTapY = 0; + + this.image_cache = {}; + this.icon_cache = {}; + + ['edit', 'remove', 'link', 'enlarge', 'shrink', 'revert' ].forEach(function(imgname) { + var img = new Image(); + img.src = _renkan.options.static_url + 'img/' + imgname + '.png'; + _this.icon_cache[imgname] = img; + }); + + var throttledMouseMove = _.throttle(function(_event, _isTouch) { + _this.onMouseMove(_event, _isTouch); + }, Utils._MOUSEMOVE_RATE); + + this.canvas_$.on({ + mousedown: function(_event) { + _event.preventDefault(); + _this.onMouseDown(_event, false); + }, + mousemove: function(_event) { + _event.preventDefault(); + throttledMouseMove(_event, false); + }, + mouseup: function(_event) { + _event.preventDefault(); + _this.onMouseUp(_event, false); + }, + mousewheel: function(_event, _delta) { + if(_renkan.options.zoom_on_scroll) { + _event.preventDefault(); + if (_allowScroll) { + _this.onScroll(_event, _delta); + } + } + }, + touchstart: function(_event) { + _event.preventDefault(); + var _touches = _event.originalEvent.touches[0]; + if ( + _renkan.options.allow_double_click + && new Date() - _lastTap < Utils._DOUBLETAP_DELAY + && ( Math.pow(_lastTapX - _touches.pageX, 2) + Math.pow(_lastTapY - _touches.pageY, 2) < Utils._DOUBLETAP_DISTANCE ) + ) { + _lastTap = 0; + _this.onDoubleClick(_touches); + } else { + _lastTap = new Date(); + _lastTapX = _touches.pageX; + _lastTapY = _touches.pageY; + _originalScale = _this.scale; + _zooming = false; + _this.onMouseDown(_touches, true); + } + }, + touchmove: function(_event) { + _event.preventDefault(); + _lastTap = 0; + if (_event.originalEvent.touches.length == 1) { + _this.onMouseMove(_event.originalEvent.touches[0], true); + } else { + if (!_zooming) { + _this.onMouseUp(_event.originalEvent.touches[0], true); + _this.click_target = null; + _this.is_dragging = false; + _zooming = true; + } + if (_event.originalEvent.scale === "undefined") { + return; + } + var _newScale = _event.originalEvent.scale * _originalScale, + _scaleRatio = _newScale / _this.scale, + _newOffset = new paper.Point([ + _this.canvas_$.width(), + _this.canvas_$.height() + ]).multiply( .5 * ( 1 - _scaleRatio ) ).add(_this.offset.multiply( _scaleRatio )); + _this.setScale(_newScale, _newOffset); + } + }, + touchend: function(_event) { + _event.preventDefault(); + _this.onMouseUp(_event.originalEvent.changedTouches[0], true); + }, + dblclick: function(_event) { + _event.preventDefault(); + if (_renkan.options.allow_double_click) { + _this.onDoubleClick(_event); + } + }, + mouseleave: function(_event) { + _event.preventDefault(); + _this.onMouseUp(_event, false); + _this.click_target = null; + _this.is_dragging = false; + }, + dragover: function(_event) { + _event.preventDefault(); + }, + dragenter: function(_event) { + _event.preventDefault(); + _allowScroll = false; + }, + dragleave: function(_event) { + _event.preventDefault(); + _allowScroll = true; + }, + drop: function(_event) { + _event.preventDefault(); + _allowScroll = true; + var res = {}; + _(_event.originalEvent.dataTransfer.types).each(function(t) { + try { + res[t] = _event.originalEvent.dataTransfer.getData(t); + } catch(e) {} + }); + var text = _event.originalEvent.dataTransfer.getData("Text"); + if (typeof text === "string") { + switch(text[0]) { + case "{": + case "[": + try { + var data = JSON.parse(text); + _(res).extend(data); + } + catch(e) { + if (!res["text/plain"]) { + res["text/plain"] = text; + } + } + break; + case "<": + if (!res["text/html"]) { + res["text/html"] = text; + } + break; + default: + if (!res["text/plain"]) { + res["text/plain"] = text; + } + } + } + var url = _event.originalEvent.dataTransfer.getData("URL"); + if (url && !res["text/uri-list"]) { + res["text/uri-list"] = url; + } + _this.dropData(res, _event.originalEvent); + } + }); + + var bindClick = function(selector, fname) { + _this.$.find(selector).click(function(evt) { + _this[fname](evt); + return false; + }); + }; + + bindClick(".Rk-ZoomOut", "zoomOut"); + bindClick(".Rk-ZoomIn", "zoomIn"); + bindClick(".Rk-ZoomFit", "autoScale"); + this.$.find(".Rk-ZoomSave").click( function() { + // Save scale and offset point + _this.renkan.project.addView( { zoom_level:_this.scale, offset_x:_this.offset.x, offset_y:_this.offset.y } ); + }); + this.$.find(".Rk-ZoomSetSaved").click( function() { + var view = _this.renkan.project.get("views").last(); + if(view){ + _this.setScale(view.get("zoom_level"), new paper.Point(view.get("offset_x"), view.get("offset_y"))); + } + }); + if(this.renkan.read_only && !isNaN(parseInt(this.renkan.options.default_view))){ + this.$.find(".Rk-ZoomSetSaved").show(); + } + this.$.find(".Rk-CurrentUser").mouseenter( + function() { _this.$.find(".Rk-UserList").slideDown(); } + ); + this.$.find(".Rk-Users").mouseleave( + function() { _this.$.find(".Rk-UserList").slideUp(); } + ); + bindClick(".Rk-FullScreen-Button", "fullScreen"); + bindClick(".Rk-AddNode-Button", "addNodeBtn"); + bindClick(".Rk-AddEdge-Button", "addEdgeBtn"); + bindClick(".Rk-Save-Button", "save"); + bindClick(".Rk-Open-Button", "open"); + this.$.find(".Rk-Bookmarklet-Button") + .attr("href","javascript:" + Utils._BOOKMARKLET_CODE(_renkan)) + .click(function(){ + _this.notif_$ + .text(_renkan.translate("Drag this button to your bookmark bar. When on a third-party website, click it to enable drag-and-drop from the website to Renkan.")) + .fadeIn() + .delay(5000) + .fadeOut(); + return false; + }); + this.$.find(".Rk-TopBar-Button").mouseover(function() { + $(this).find(".Rk-TopBar-Tooltip").show(); + }).mouseout(function() { + $(this).find(".Rk-TopBar-Tooltip").hide(); + }); + bindClick(".Rk-Fold-Bins", "foldBins"); + + paper.view.onResize = function(_event) { + // Because of paper bug which does not calculate the good height (and width a fortiori) + // We have to update manually the canvas's height + paper.view._viewSize.height = _event.size.height = _this.canvas_$.parent().height(); + + if (_this.minimap) { + _this.minimap.topleft = paper.view.bounds.bottomRight.subtract(_this.minimap.size); + _this.minimap.rectangle.fitBounds(_this.minimap.topleft.subtract([2,2]), _this.minimap.size.add([4,4])); + _this.minimap.cliprectangle.fitBounds(_this.minimap.topleft, _this.minimap.size); + } + _this.redraw(); + }; + + var _thRedraw = _.throttle(function() { + _this.redraw(); + },50); + + this.addRepresentations("Node", this.renkan.project.get("nodes")); + this.addRepresentations("Edge", this.renkan.project.get("edges")); + this.renkan.project.on("change:title", function() { + _this.$.find(".Rk-PadTitle").val(_renkan.project.get("title")); + }); + + this.$.find(".Rk-PadTitle").on("keyup input paste", function() { + _renkan.project.set({"title": $(this).val()}); + }); + + var _thRedrawUsers = _.throttle(function() { + _this.redrawUsers(); + }, 100); + + _thRedrawUsers(); + + // register model events + this.renkan.project.on("add:users remove:users", _thRedrawUsers); + + this.renkan.project.on("add:views remove:views", function(_node) { + if(_this.renkan.project.get('views').length > 0) { + _this.$.find(".Rk-ZoomSetSaved").show(); + } + else { + _this.$.find(".Rk-ZoomSetSaved").hide(); + } + }); + + this.renkan.project.on("add:nodes", function(_node) { + _this.addRepresentation("Node", _node); + _thRedraw(); + }); + this.renkan.project.on("add:edges", function(_edge) { + _this.addRepresentation("Edge", _edge); + _thRedraw(); + }); + this.renkan.project.on("change:title", function(_model, _title) { + var el = _this.$.find(".Rk-PadTitle"); + if (el.is("input")) { + if (el.val() !== _title) { + el.val(_title); + } + } else { + el.text(_title); + } + }); + + if (_renkan.options.size_bug_fix) { + var _delay = ( + typeof _renkan.options.size_bug_fix === "number" + ? _renkan.options.size_bug_fix + : 500 + ); + window.setTimeout( + function() { + _this.fixSize(true); + }, + _delay + ); + } + + if (_renkan.options.force_resize) { + $(window).resize(function() { + _this.fixSize(false); + }); + } + + if (_renkan.options.show_user_list && _renkan.options.user_color_editable) { + var $cpwrapper = this.$.find(".Rk-Users .Rk-Edit-ColorPicker-Wrapper"), + $cplist = this.$.find(".Rk-Users .Rk-Edit-ColorPicker"); + + $cpwrapper.hover( + function(_e) { + if (_this.isEditable()) { + _e.preventDefault(); + $cplist.show(); + } + }, + function(_e) { + _e.preventDefault(); + $cplist.hide(); + } + ); + + $cplist.find("li").mouseenter( + function(_e) { + if (_this.isEditable()) { + _e.preventDefault(); + _this.$.find(".Rk-CurrentUser-Color").css("background", $(this).attr("data-color")); + } + } + ); + } + + if (_renkan.options.show_search_field) { + + var lastval = ''; + + this.$.find(".Rk-GraphSearch-Field").on("keyup change paste input", function() { + var $this = $(this), + val = $this.val(); + if (val === lastval) { + return; + } + lastval = val; + if (val.length < 2) { + _renkan.project.get("nodes").each(function(n) { + _this.getRepresentationByModel(n).unhighlight(); + }); + } else { + var rxs = Utils.regexpFromTextOrArray(val); + _renkan.project.get("nodes").each(function(n) { + if (rxs.test(n.get("title")) || rxs.test(n.get("description"))) { + _this.getRepresentationByModel(n).highlight(rxs); + } else { + _this.getRepresentationByModel(n).unhighlight(); + } + }); + } + }); + } + + this.redraw(); + + window.setInterval(function() { + var _now = new Date().valueOf(); + _this.delete_list.forEach(function(d) { + if (_now >= d.time) { + var el = _renkan.project.get("nodes").findWhere({"delete_scheduled":d.id}); + if (el) { + project.removeNode(el); + } + el = _renkan.project.get("edges").findWhere({"delete_scheduled":d.id}); + if (el) { + project.removeEdge(el); + } + } + }); + _this.delete_list = _this.delete_list.filter(function(d) { + return _renkan.project.get("nodes").findWhere({"delete_scheduled":d.id}) || _renkan.project.get("edges").findWhere({"delete_scheduled":d.id}); + }); + }, 500); + + if (this.minimap) { + window.setInterval(function() { + _this.rescaleMinimap(); + }, 2000); + } + + }; + + _(Scene.prototype).extend({ + template: _.template( + '<% if (options.show_top_bar) { %>
<% if (!options.editor_mode) { %>

<%- project.get("title") || translate("Untitled project")%>

' + + '<% } else { %>" placeholder="<%-translate("Untitled project")%>" /><% } %>' + + '<% if (options.show_user_list) { %>
<% if (options.user_color_editable) { %><% } %>' + + '<% if (options.user_color_editable) { print(colorPicker) } %>
<unknown user>
<% } %>' + + '<% if (options.home_button_url) {%>
' + + '<%- translate(options.home_button_title) %>
<% } %>' + + '<% if (options.show_fullscreen_button) { %>
<%-translate("Full Screen")%>
<% } %>' + + '<% if (options.editor_mode) { %>' + + '<% if (options.show_addnode_button) { %>
' + + '
<%-translate("Add Node")%>
<% } %>' + + '<% if (options.show_addedge_button) { %>
' + + '
<%-translate("Add Edge")%>
<% } %>' + + '<% if (options.show_save_button) { %>
<% } %>' + + '<% if (options.show_open_button) { %>
<%-translate("Open Project")%>
<% } %>' + + '<% if (options.show_bookmarklet) { %>
' + + '<%-translate("Renkan \'Drag-to-Add\' bookmarklet")%>
<% } %>' + + '
<% }; if (options.show_search_field) { %>' + + '
" />
<% } %>
<% } %>' + + '
' + + '
' + + '<% if (options.show_bins) { %>
«
<% } %>' + + '
">
">
">
' + + '<% if (options.editor_mode) { %>
">
<% } %>' + + '<% if (options.editor_mode || !isNaN(parseInt(options.default_view))) { %>
">
<% } %>
' + + '
' + ), + fixSize: function(_autoscale) { + var w = this.$.width(), + h = this.$.height(); + if (this.renkan.options.show_top_bar) { + h -= this.$.find(".Rk-TopBar").height(); + } + this.canvas_$.attr({ + width: w, + height: h + }); + + paper.view.viewSize = new paper.Size([w, h]); + + if (_autoscale) { + // If _autoscale, we get the initial view (zoom+offset) set in the project datas. + if(this.renkan.read_only && !isNaN(parseInt(this.renkan.options.default_view))){ + this.autoScale(this.renkan.project.get("views")[parseInt(this.renkan.options.default_view)]); + } + else{ + this.autoScale(); + } + } + }, + drawSector: function(_repr, _inR, _outR, _startAngle, _endAngle, _padding, _imgname, _caption) { + var _options = this.renkan.options, + _startRads = _startAngle * Math.PI / 180, + _endRads = _endAngle * Math.PI / 180, + _img = this.icon_cache[_imgname], + _startdx = - Math.sin(_startRads), + _startdy = Math.cos(_startRads), + _startXIn = Math.cos(_startRads) * _inR + _padding * _startdx, + _startYIn = Math.sin(_startRads) * _inR + _padding * _startdy, + _startXOut = Math.cos(_startRads) * _outR + _padding * _startdx, + _startYOut = Math.sin(_startRads) * _outR + _padding * _startdy, + _enddx = - Math.sin(_endRads), + _enddy = Math.cos(_endRads), + _endXIn = Math.cos(_endRads) * _inR - _padding * _enddx, + _endYIn = Math.sin(_endRads) * _inR - _padding * _enddy, + _endXOut = Math.cos(_endRads) * _outR - _padding * _enddx, + _endYOut = Math.sin(_endRads) * _outR - _padding * _enddy, + _centerR = (_inR + _outR)/2, + _centerRads = (_startRads + _endRads) / 2, + _centerX = Math.cos(_centerRads) * _centerR, + _centerY = Math.sin(_centerRads) * _centerR, + _centerXIn = Math.cos(_centerRads) * _inR, + _centerXOut = Math.cos(_centerRads) * _outR, + _centerYIn = Math.sin(_centerRads) * _inR, + _centerYOut = Math.sin(_centerRads) * _outR, + _textX = Math.cos(_centerRads) * (_outR + 3), + _textY = Math.sin(_centerRads) * (_outR + _options.buttons_label_font_size) + _options.buttons_label_font_size / 2; + this.buttons_layer.activate(); + var _path = new paper.Path(); + _path.add([_startXIn, _startYIn]); + _path.arcTo([_centerXIn, _centerYIn], [_endXIn, _endYIn]); + _path.lineTo([_endXOut, _endYOut]); + _path.arcTo([_centerXOut, _centerYOut], [_startXOut, _startYOut]); + _path.fillColor = _options.buttons_background; + _path.opacity = .5; + _path.closed = true; + _path.__representation = _repr; + var _text = new paper.PointText(_textX,_textY); + _text.characterStyle = { + fontSize: _options.buttons_label_font_size, + fillColor: _options.buttons_label_color + }; + if (_textX > 2) { + _text.paragraphStyle.justification = 'left'; + } else if (_textX < -2) { + _text.paragraphStyle.justification = 'right'; + } else { + _text.paragraphStyle.justification = 'center'; + } + _text.visible = false; + var _visible = false, + _restPos = new paper.Point(-200, -200), + _grp = new paper.Group([_path, _text]), + _delta = _grp.position, + _imgdelta = new paper.Point([_centerX, _centerY]), + _currentPos = new paper.Point(0,0); + _text.content = _caption; + _grp.visible = false; + _grp.position = _restPos; + var _res = { + show: function() { + _visible = true; + _grp.position = _currentPos.add(_delta); + _grp.visible = true; + }, + moveTo: function(_point) { + _currentPos = _point; + if (_visible) { + _grp.position = _point.add(_delta); + } + }, + hide: function() { + _visible = false; + _grp.visible = false; + _grp.position = _restPos; + }, + select: function() { + _path.opacity = .8; + _text.visible = true; + }, + unselect: function() { + _path.opacity = .5; + _text.visible = false; + }, + destroy: function() { + _grp.remove(); + } + }; + var showImage = function() { + var _raster = new paper.Raster(_img); + _raster.position = _imgdelta.add(_grp.position).subtract(_delta); + _raster.locked = true; // Disable mouse events on icon + _grp.addChild(_raster); + }; + if (_img.width) { + showImage(); + } else { + $(_img).on("load",showImage); + } + + return _res; + }, + addToBundles: function(_edgeRepr) { + var _bundle = _(this.bundles).find(function(_bundle) { + return ( + ( _bundle.from === _edgeRepr.from_representation && _bundle.to === _edgeRepr.to_representation ) + || ( _bundle.from === _edgeRepr.to_representation && _bundle.to === _edgeRepr.from_representation ) + ); + }); + if (typeof _bundle !== "undefined") { + _bundle.edges.push(_edgeRepr); + } else { + _bundle = { + from: _edgeRepr.from_representation, + to: _edgeRepr.to_representation, + edges: [ _edgeRepr ], + getPosition: function(_er) { + var _dir = (_er.from_representation === this.from) ? 1 : -1; + return _dir * ( _(this.edges).indexOf(_er) - (this.edges.length - 1) / 2 ); + } + }; + this.bundles.push(_bundle); + } + return _bundle; + }, + isEditable: function() { + return (this.renkan.options.editor_mode && !this.renkan.read_only); + }, + onStatusChange: function() { + var savebtn = this.$.find(".Rk-Save-Button"), + tip = savebtn.find(".Rk-TopBar-Tooltip-Contents"); + if (this.renkan.read_only) { + savebtn.removeClass("disabled Rk-Save-Online").addClass("Rk-Save-ReadOnly"); + tip.text(this.renkan.translate("Connection lost")); + } else { + if (this.renkan.options.snapshot_mode) { + savebtn.removeClass("Rk-Save-ReadOnly Rk-Save-Online"); + tip.text(this.renkan.translate("Save Project")); + } else { + savebtn.removeClass("disabled Rk-Save-ReadOnly").addClass("Rk-Save-Online"); + tip.text(this.renkan.translate("Auto-save enabled")); + } + } + this.redrawUsers(); + }, + setScale: function(_newScale, _offset) { + if ((_newScale/this.initialScale) > Utils._MIN_SCALE && (_newScale/this.initialScale) < Utils._MAX_SCALE) { + this.scale = _newScale; + if (_offset) { + this.offset = _offset; + } + this.redraw(); + } + }, + autoScale: function(force_view) { + var nodes = this.renkan.project.get("nodes"); + if (nodes.length > 1) { + var _xx = nodes.map(function(_node) { return _node.get("position").x; }), + _yy = nodes.map(function(_node) { return _node.get("position").y; }), + _minx = Math.min.apply(Math, _xx), + _miny = Math.min.apply(Math, _yy), + _maxx = Math.max.apply(Math, _xx), + _maxy = Math.max.apply(Math, _yy); + var _scale = Math.min( (paper.view.size.width - 2 * this.renkan.options.autoscale_padding) / (_maxx - _minx), (paper.view.size.height - 2 * this.renkan.options.autoscale_padding) / (_maxy - _miny)); + this.initialScale = _scale; + // Override calculated scale if asked + if((typeof force_view !== "undefined") && parseFloat(force_view.zoom_level)>0 && parseFloat(force_view.offset_x)>0 && parseFloat(force_view.offset_y)>0){ + this.setScale(parseFloat(force_view.zoom_level), new paper.Point(parseFloat(force_view.offset_x), parseFloat(force_view.offset_y))); + } + else{ + this.setScale(_scale, paper.view.center.subtract(new paper.Point([(_maxx + _minx) / 2, (_maxy + _miny) / 2]).multiply(_scale))); + } + } + if (nodes.length === 1) { + this.setScale(1, paper.view.center.subtract(new paper.Point([nodes.at(0).get("position").x, nodes.at(0).get("position").y]))); + } + }, + redrawMiniframe: function() { + var topleft = this.toMinimapCoords(this.toModelCoords(new paper.Point([0,0]))), + bottomright = this.toMinimapCoords(this.toModelCoords(paper.view.bounds.bottomRight)); + this.minimap.miniframe.fitBounds(topleft, bottomright); + }, + rescaleMinimap: function() { + var nodes = this.renkan.project.get("nodes"); + if (nodes.length > 1) { + var _xx = nodes.map(function(_node) { return _node.get("position").x; }), + _yy = nodes.map(function(_node) { return _node.get("position").y; }), + _minx = Math.min.apply(Math, _xx), + _miny = Math.min.apply(Math, _yy), + _maxx = Math.max.apply(Math, _xx), + _maxy = Math.max.apply(Math, _yy); + var _scale = Math.min( + this.scale * .8 * this.renkan.options.minimap_width / paper.view.bounds.width, + this.scale * .8 * this.renkan.options.minimap_height / paper.view.bounds.height, + ( this.renkan.options.minimap_width - 2 * this.renkan.options.minimap_padding ) / (_maxx - _minx), + ( this.renkan.options.minimap_height - 2 * this.renkan.options.minimap_padding ) / (_maxy - _miny) + ); + this.minimap.offset = this.minimap.size.divide(2).subtract(new paper.Point([(_maxx + _minx) / 2, (_maxy + _miny) / 2]).multiply(_scale)); + this.minimap.scale = _scale; + } + if (nodes.length === 1) { + this.minimap.scale = .1; + this.minimap.offset = this.minimap.size.divide(2).subtract(new paper.Point([nodes.at(0).get("position").x, nodes.at(0).get("position").y]).multiply(this.minimap.scale)); + } + this.redraw(); + }, + toPaperCoords: function(_point) { + return _point.multiply(this.scale).add(this.offset); + }, + toMinimapCoords: function(_point) { + return _point.multiply(this.minimap.scale).add(this.minimap.offset).add(this.minimap.topleft); + }, + toModelCoords: function(_point) { + return _point.subtract(this.offset).divide(this.scale); + }, + addRepresentation: function(_type, _model) { + var r = requtils.getRenderer()[_type]; + var _repr = new r(this, _model); + this.representations.push(_repr); + return _repr; + }, + addRepresentations: function(_type, _collection) { + var _this = this; + _collection.forEach(function(_model) { + _this.addRepresentation(_type, _model); + }); + }, + userTemplate: _.template( + '
  • <%=name%>
  • ' + ), + redrawUsers: function() { + if (!this.renkan.options.show_user_list) { + return; + } + var allUsers = [].concat((this.renkan.project.current_user_list || {}).models || [], (this.renkan.project.get("users") || {}).models || []), + ulistHtml = '', + $userpanel = this.$.find(".Rk-Users"), + $name = $userpanel.find(".Rk-CurrentUser-Name"), + $cpitems = $userpanel.find(".Rk-Edit-ColorPicker li"), + $colorsquare = $userpanel.find(".Rk-CurrentUser-Color"), + _this = this; + $name.off("click").text(this.renkan.translate("")); + $cpitems.off("mouseleave click"); + allUsers.forEach(function(_user) { + if (_user.get("_id") === _this.renkan.current_user) { + $name.text(_user.get("title")); + $colorsquare.css("background", _user.get("color")); + if (_this.isEditable()) { + + if (_this.renkan.options.user_name_editable) { + $name.click(function() { + var $this = $(this), + $input = $('').val(_user.get("title")).blur(function() { + _user.set("title", $(this).val()); + _this.redrawUsers(); + _this.redraw(); + }); + $this.empty().html($input); + $input.select(); + }); + } + + if (_this.renkan.options.user_color_editable) { + $cpitems.click( + function(_e) { + _e.preventDefault(); + if (_this.isEditable()) { + _user.set("color", $(this).attr("data-color")); + } + $(this).parent().hide(); + } + ).mouseleave(function() { + $colorsquare.css("background", _user.get("color")); + }); + } + } + + } else { + ulistHtml += _this.userTemplate({ + name: _user.get("title"), + background: _user.get("color") + }); + } + }); + $userpanel.find(".Rk-UserList").html(ulistHtml); + }, + removeRepresentation: function(_representation) { + _representation.destroy(); + this.representations = _(this.representations).reject( + function(_repr) { + return _repr == _representation; + } + ); + }, + getRepresentationByModel: function(_model) { + if (!_model) { + return undefined; + } + return _(this.representations).find(function(_repr) { + return _repr.model === _model; + }); + }, + removeRepresentationsOfType: function(_type) { + var _representations = _(this.representations).filter(function(_repr) { + return _repr.type == _type; + }), + _this = this; + _(_representations).each(function(_repr) { + _this.removeRepresentation(_repr); + }); + }, + highlightModel: function(_model) { + var _repr = this.getRepresentationByModel(_model); + if (_repr) { + _repr.highlight(); + } + }, + unhighlightAll: function(_model) { + _(this.representations).each(function(_repr) { + _repr.unhighlight(); + }); + }, + unselectAll: function(_model) { + _(this.representations).each(function(_repr) { + _repr.unselect(); + }); + }, + redraw: function() { + _(this.representations).each(function(_representation) { + _representation.redraw(true); + }); + if (this.minimap) { + this.redrawMiniframe(); + } + paper.view.draw(); + }, + addTempEdge: function(_from, _point) { + var _tmpEdge = this.addRepresentation("TempEdge",null); + _tmpEdge.end_pos = _point; + _tmpEdge.from_representation = _from; + _tmpEdge.redraw(); + this.click_target = _tmpEdge; + }, + findTarget: function(_hitResult) { + if (_hitResult && typeof _hitResult.item.__representation !== "undefined") { + var _newTarget = _hitResult.item.__representation; + if (this.selected_target !== _hitResult.item.__representation) { + if (this.selected_target) { + this.selected_target.unselect(_newTarget); + } + _newTarget.select(this.selected_target); + this.selected_target = _newTarget; + } + } else { + if (this.selected_target) { + this.selected_target.unselect(); + } + this.selected_target = null; + } + }, + paperShift: function(_delta) { + this.offset = this.offset.add(_delta); + this.redraw(); + }, + onMouseMove: function(_event) { + var _off = this.canvas_$.offset(), + _point = new paper.Point([ + _event.pageX - _off.left, + _event.pageY - _off.top + ]), + _delta = _point.subtract(this.last_point); + this.last_point = _point; + if (!this.is_dragging && this.mouse_down && _delta.length > Utils._MIN_DRAG_DISTANCE) { + this.is_dragging = true; + } + var _hitResult = paper.project.hitTest(_point); + if (this.is_dragging) { + if (this.click_target && typeof this.click_target.paperShift === "function") { + this.click_target.paperShift(_delta); + } else { + this.paperShift(_delta); + } + } else { + this.findTarget(_hitResult); + } + paper.view.draw(); + }, + onMouseDown: function(_event, _isTouch) { + var _off = this.canvas_$.offset(), + _point = new paper.Point([ + _event.pageX - _off.left, + _event.pageY - _off.top + ]); + this.last_point = _point; + this.mouse_down = true; + if (!this.click_target || this.click_target.type !== "Temp-edge") { + this.removeRepresentationsOfType("editor"); + this.is_dragging = false; + var _hitResult = paper.project.hitTest(_point); + if (_hitResult && typeof _hitResult.item.__representation !== "undefined") { + this.click_target = _hitResult.item.__representation; + this.click_target.mousedown(_event, _isTouch); + } else { + this.click_target = null; + if (this.isEditable() && this.click_mode === Utils._CLICKMODE_ADDNODE) { + var _coords = this.toModelCoords(_point), + _data = { + id: Utils.getUID('node'), + created_by: this.renkan.current_user, + position: { + x: _coords.x, + y: _coords.y + } + }; + _node = this.renkan.project.addNode(_data); + this.getRepresentationByModel(_node).openEditor(); + } + } + } + if (this.click_mode) { + if (this.isEditable() && this.click_mode === Utils._CLICKMODE_STARTEDGE && this.click_target && this.click_target.type === "Node") { + this.removeRepresentationsOfType("editor"); + this.addTempEdge(this.click_target, _point); + this.click_mode = Utils._CLICKMODE_ENDEDGE; + this.notif_$.fadeOut(function() { + $(this).html(this.renkan.translate("Click on a second node to complete the edge")).fadeIn(); + }); + } else { + this.notif_$.hide(); + this.click_mode = false; + } + } + paper.view.draw(); + }, + onMouseUp: function(_event, _isTouch) { + this.mouse_down = false; + if (this.click_target) { + var _off = this.canvas_$.offset(); + this.click_target.mouseup( + { + point: new paper.Point([ + _event.pageX - _off.left, + _event.pageY - _off.top + ]) + }, + _isTouch + ); + } else { + this.click_target = null; + this.is_dragging = false; + if (_isTouch) { + this.unselectAll(); + } + } + paper.view.draw(); + }, + onScroll: function(_event, _scrolldelta) { + this.totalScroll += _scrolldelta; + if (Math.abs(this.totalScroll) >= 1) { + var _off = this.canvas_$.offset(), + _delta = new paper.Point([ + _event.pageX - _off.left, + _event.pageY - _off.top + ]).subtract(this.offset).multiply( Math.SQRT2 - 1 ); + if (this.totalScroll > 0) { + this.setScale( this.scale * Math.SQRT2, this.offset.subtract(_delta) ); + } else { + this.setScale( this.scale * Math.SQRT1_2, this.offset.add(_delta.divide(Math.SQRT2))); + } + this.totalScroll = 0; + } + }, + onDoubleClick: function(_event) { + if (!this.isEditable()) { + return; + } + var _off = this.canvas_$.offset(), + _point = new paper.Point([ + _event.pageX - _off.left, + _event.pageY - _off.top + ]); + var _hitResult = paper.project.hitTest(_point); + if (this.isEditable() && (!_hitResult || typeof _hitResult.item.__representation === "undefined")) { + var _coords = this.toModelCoords(_point), + _data = { + id: Utils.getUID('node'), + created_by: this.renkan.current_user, + position: { + x: _coords.x, + y: _coords.y + } + }, + _node = this.renkan.project.addNode(_data); + this.getRepresentationByModel(_node).openEditor(); + } + paper.view.draw(); + }, + defaultDropHandler: function(_data) { + var newNode = {}; + switch(_data["text/x-iri-specific-site"]) { + case "twitter": + var snippet = $('
    ').html(_data["text/x-iri-selected-html"]), + tweetdiv = snippet.find(".tweet"); + newNode.title = this.renkan.translate("Tweet by ") + tweetdiv.attr("data-name"); + newNode.uri = "http://twitter.com/" + tweetdiv.attr("data-screen-name") + "/status/" + tweetdiv.attr("data-tweet-id"); + newNode.image = tweetdiv.find(".avatar").attr("src"); + newNode.description = tweetdiv.find(".js-tweet-text:first").text(); + break; + case "google": + var snippet = $('
    ').html(_data["text/x-iri-selected-html"]); + newNode.title = snippet.find("h3:first").text().trim(); + newNode.uri = snippet.find("h3 a").attr("href"); + newNode.description = snippet.find(".st:first").text().trim(); + break; + case undefined: + default: + if (_data["text/x-iri-source-uri"]) { + newNode.uri = _data["text/x-iri-source-uri"]; + } + if (_data["text/plain"] || _data["text/x-iri-selected-text"]) { + newNode.description = (_data["text/plain"] || _data["text/x-iri-selected-text"]).replace(/[\s\n]+/gm,' ').trim(); + } + if (_data["text/html"] || _data["text/x-iri-selected-html"]) { + var snippet = $('
    ').html(_data["text/html"] || _data["text/x-iri-selected-html"]); + var _svgimgs = snippet.find("image"); + if (_svgimgs.length) { + newNode.image = _svgimgs.attr("xlink:href"); + } + var _svgpaths = snippet.find("path"); + if (_svgpaths.length) { + newNode.clipPath = _svgpaths.attr("d"); + } + var _imgs = snippet.find("img"); + if (_imgs.length) { + newNode.image = _imgs[0].src; + } + var _as = snippet.find("a"); + if (_as.length) { + newNode.uri = _as[0].href; + } + newNode.title = snippet.find("[title]").attr("title") || newNode.title; + newNode.description = snippet.text().replace(/[\s\n]+/gm,' ').trim(); + } + if (_data["text/uri-list"]) { + newNode.uri = _data["text/uri-list"]; + } + if (_data["text/x-moz-url"] && !newNode.title) { + newNode.title = (_data["text/x-moz-url"].split("\n")[1] || "").trim(); + if (newNode.title === newNode.uri) { + newNode.title = false; + } + } + if (_data["text/x-iri-source-title"] && !newNode.title) { + newNode.title = _data["text/x-iri-source-title"]; + } + if (_data["text/html"] || _data["text/x-iri-selected-html"]) { + var snippet = $('
    ').html(_data["text/html"] || _data["text/x-iri-selected-html"]); + newNode.image = snippet.find("[data-image]").attr("data-image") || newNode.image; + newNode.uri = snippet.find("[data-uri]").attr("data-uri") || newNode.uri; + newNode.title = snippet.find("[data-title]").attr("data-title") || newNode.title; + newNode.description = snippet.find("[data-description]").attr("data-description") || newNode.description; + newNode.clipPath = snippet.find("[data-clip-path]").attr("data-clip-path") || newNode.clipPath; + } + } + + if(typeof this.renkan.options.drop_enhancer === "function"){ + newNode = this.renkan.options.drop_enhancer(newNode, _data); + } + return newNode; + + }, + dropData: function(_data, _event) { + if (!this.isEditable()) { + return; + } + if (_data["text/json"] || _data["application/json"]) { + try { + var jsondata = JSON.parse(_data["text/json"] || _data["application/json"]); + _(_data).extend(jsondata); + } + catch(e) {} + } + + var newNode = (typeof this.renkan.options.drop_handler === "undefined")?this.defaultDropHandler(_data):this.renkan.options.drop_handler(_data); + + if (!newNode.title) { + newNode.title = this.renkan.translate("Dragged resource"); + } + var fields = ["title", "description", "uri", "image"]; + for (var i = 0; i < fields.length; i++) { + var f = fields[i]; + if (_data["text/x-iri-" + f] || _data[f]) { + newNode[f] = _data["text/x-iri-" + f] || _data[f]; + } + if (newNode[f] === "none" || newNode[f] === "null") { + newNode[f] = undefined; + } + } + var _off = this.canvas_$.offset(), + _point = new paper.Point([ + _event.pageX - _off.left, + _event.pageY - _off.top + ]), + _coords = this.toModelCoords(_point), + _nodedata = { + id: Utils.getUID('node'), + created_by: this.renkan.current_user, + uri: newNode.uri || "", + title: newNode.title || "", + description: newNode.description || "", + image: newNode.image || "", + color: newNode.color || undefined, + clip_path: newNode.clipPath || undefined, + position: { + x: _coords.x, + y: _coords.y + } + }; + var _node = this.renkan.project.addNode(_nodedata), + _repr = this.getRepresentationByModel(_node); + if (_event.type === "drop") { + _repr.openEditor(); + } + }, + fullScreen: function() { + var _isFull = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen, + _el = this.renkan.$[0], + _requestMethods = ["requestFullScreen","mozRequestFullScreen","webkitRequestFullScreen"], + _cancelMethods = ["cancelFullScreen","mozCancelFullScreen","webkitCancelFullScreen"]; + if (_isFull) { + for (var i = 0; i < _cancelMethods.length; i++) { + if (typeof document[_cancelMethods[i]] === "function") { + document[_cancelMethods[i]](); + break; + } + } + } else { + for (var i = 0; i < _requestMethods.length; i++) { + if (typeof _el[_requestMethods[i]] === "function") { + _el[_requestMethods[i]](); + break; + } + } + } + }, + zoomOut: function() { + var _newScale = this.scale * Math.SQRT1_2, + _offset = new paper.Point([ + this.canvas_$.width(), + this.canvas_$.height() + ]).multiply( .5 * ( 1 - Math.SQRT1_2 ) ).add(this.offset.multiply( Math.SQRT1_2 )); + this.setScale( _newScale, _offset ); + }, + zoomIn: function() { + var _newScale = this.scale * Math.SQRT2, + _offset = new paper.Point([ + this.canvas_$.width(), + this.canvas_$.height() + ]).multiply( .5 * ( 1 - Math.SQRT2 ) ).add(this.offset.multiply( Math.SQRT2 )); + this.setScale( _newScale, _offset ); + }, + addNodeBtn: function() { + if (this.click_mode === Utils._CLICKMODE_ADDNODE) { + this.click_mode = false; + this.notif_$.hide(); + } else { + this.click_mode = Utils._CLICKMODE_ADDNODE; + this.notif_$.text(this.renkan.translate("Click on the background canvas to add a node")).fadeIn(); + } + return false; + }, + addEdgeBtn: function() { + if (this.click_mode === Utils._CLICKMODE_STARTEDGE || this.click_mode === Utils._CLICKMODE_ENDEDGE) { + this.click_mode = false; + this.notif_$.hide(); + } else { + this.click_mode = Utils._CLICKMODE_STARTEDGE; + this.notif_$.text(this.renkan.translate("Click on a first node to start the edge")).fadeIn(); + } + return false; + }, + foldBins: function() { + var foldBinsButton = this.$.find(".Rk-Fold-Bins"), + bins = this.renkan.$.find(".Rk-Bins"); + if (bins.offset().left < 0) { + bins.animate({left: 0},250); + var _this = this; + this.$.animate({left: 300},250,function() { + var w = _this.$.width(); + paper.view.viewSize = new paper.Size([w, _this.canvas_$.height()]); + }); + foldBinsButton.html("«"); + } else { + bins.animate({left: -300},250); + var _this = this; + this.$.animate({left: 0},250,function() { + var w = _this.$.width(); + paper.view.viewSize = new paper.Size([w, _this.canvas_$.height()]); + }); + foldBinsButton.html("»"); + } + }, + save: function() { }, + open: function() { } + }); + + /* Scene End */ + + return Scene; + +});