Added Drag+Drop from a tweet list
authorveltr
Fri, 17 Aug 2012 18:36:12 +0200
changeset 21 b43dd87f7ffa
parent 20 bd58970ffd16
child 22 948283342245
Added Drag+Drop from a tweet list
client/css/renkan.css
client/js/main.js
client/js/paper-renderer.js
client/js/random-data.js
client/js/twitter-bin.js
client/render-test.html
--- a/client/css/renkan.css	Fri Aug 17 12:50:00 2012 +0200
+++ b/client/css/renkan.css	Fri Aug 17 18:36:12 2012 +0200
@@ -46,11 +46,15 @@
 }
 
 .Rk-Bins {
-    background: #F0F0F0; position: absolute; left: 0; top: 0; width: 200px; bottom: 0;
+    background: #F0F0F0; position: absolute; left: 0; top: 0; width: 300px; bottom: 0; overflow: hidden;
 }
 
 .Rk-Render {
-    position: absolute; left: 200px; top: 0; right: 0; bottom: 0;
+    position: absolute; left: 300px; top: 0; right: 0; bottom: 0;
+}
+
+.Rk-Canvas {
+    position: absolute; left: 0; top: 0; right: 0; bottom: 0;
 }
 
 .Rk-Editor {
@@ -105,13 +109,47 @@
     background-position: -21px -20px;
 }
 
-ul.Rk-TabButtons {
+.Rk-Search-Input, .Rk-Search-Select {
+    border-radius: 5px; margin: 2px; padding: 2px; font-size: 13px;
+}
+
+.Rk-Search-Input {
+    width: 180px;
+}
+
+.Rk-Search-Select {
+    width: 100px;
+}
+
+.Rk-Bin-Title {
+    border-top: 1px solid #cccccc; border-bottom: 1px solid #ffffff; background: #f0f0f0;
+    font-size: 14px; font-weight: bold; padding: 2px; cursor: pointer;
+}
+
+.Rk-Bin-Main {
+    overflow: auto;
 }
 
-li.Rk-TabButton {
-    float: left; padding: 2px; font-size: 13px; border-top-left-radius: 5px; border-top-right-radius: 5px; background: #CCCCCC;
+.Rk-Twitter-Tweet {
+    padding: 2px; background: #f8f8f8; border-bottom: 1px solid #cccccc; min-height: 55px;
+}
+
+.Rk-Twitter-TwImage {
+    float: left; width: 48px; height: 48px; margin: 2px 0;
+}
+
+.Rk-Twitter-TwTitle, .Rk-Twitter-TwDate, .Rk-Twitter-TwText, .Rk-Twitter-TwActions {
+    margin: 2px 0 2px 50px;
 }
 
-.Rk-TabDiv {
-    width: 100%; clear: both; overflow: auto; border-top: 1px solid #ccc;
+.Rk-Twitter-TwTitle {
+    font-size: 13px; font-weight: bold;
 }
+
+.Rk-Twitter-TwDate {
+    font-size: 11px; font-style: italic;
+}
+
+.Rk-Twitter-TwText {
+    font-size: 12px;
+}
\ No newline at end of file
--- a/client/js/main.js	Fri Aug 17 12:50:00 2012 +0200
+++ b/client/js/main.js	Fri Aug 17 18:36:12 2012 +0200
@@ -79,63 +79,102 @@
 
 Rkns.Bins = {}
 
-Rkns.Bins._Base = function(_project) {
-    if (typeof _project !== "undefined") {
-        this._project = _project;
+Rkns.Bins._Base = function(_renkan, _opts) {
+    if (typeof _renkan !== "undefined") {
+        this.renkan = _renkan;
+        this.renkan.$.find(".Rk-Bin-Main").hide();
+        this.$ = Rkns.$('<li>')
+            .addClass("Rk-Bin")
+            .appendTo(_renkan.$.find(".Rk-Bin-List"));
+        this.title_$ = Rkns.$('<h2>')
+            .addClass("Rk-Bin-Title")
+            .appendTo(this.$);
+        this.main_$ = Rkns.$('<div>')
+            .addClass("Rk-Bin-Main")
+            .appendTo(this.$);
+        this.renkan.resizeBins();
     }
 }
-
-Rkns.Bins._Base.prototype.baseTemplate = '';
-
-Rkns.Bins._Base.prototype.addTab = function(_title) {
-    var _tabDiv = Rkns.$('<div>');
-    _tabDiv.addClass('Rk-TabDiv')
-        .css("display","none")
-        .appendTo('.Rk-TabDivs');
-    var _tabButton = Rkns.$('<li>');
-    _tabButton.addClass('Rk-TabButton')
-        .html(_title)
-        .click(function() {
-            Rkns.$('.Rk-TabButton').removeClass("active");
-            Rkns.$(this).addClass("active");
-            Rkns.$('.Rk-TabDiv').hide();
-            _tabDiv.show();
-        })
-        .appendTo('.Rk-TabButtons');
-    return {
-        button: _tabButton,
-        contents: _tabDiv
-    }
-}
-
 /* Point of entry */
 
 Rkns.Renkan = function(_opts) {
-    if (typeof _opts.remotemodel == "undefined") {
-        _opts.remotemodel = "BasicJson";
+    if (typeof _opts.remotemodel !== "string") {
+        _opts.remotemodel = "FullJson";
     }
-    if (typeof _opts.language == "undefined" || typeof Rkns.i18n[_opts.language] == "undefined") {
+    if (typeof _opts.language !== "string" || typeof Rkns.i18n[_opts.language] == "undefined") {
         _opts.language = "en";
     }
-    if (typeof _opts.container == "undefined") {
+    if (typeof _opts.container !== "string") {
         _opts.container = "renkan";
     }
+    if (typeof _opts.search !== "object" || !_opts.search) {
+        _opts.search = [];
+    }
     this.project = new Rkns.ViewModel.Project();
     this.project.l10n = Rkns.i18n[_opts.language];
     if (typeof _opts.user === "object") {
         this.current_user = this.project.addUser(_opts.user)
     }
-    Rkns.$("#" + _opts.container).html(this.template());
-    this.project.remotemodel = new Rkns.RemoteModels[_opts.remotemodel](this.project, _opts);
-    this.project.renderer = new Rkns.Renderer.Scene(this.project, _opts);
+    this.$ = Rkns.$("#" + _opts.container);
+    this.$.html(this.template());
+    this.remotemodel = this.project.remotemodel = new Rkns.RemoteModels[_opts.remotemodel](this.project, _opts);
+    this.renderer = this.project.renderer = new Rkns.Renderer.Scene(this.project, _opts);
+    this.renderer.renkan = this;
+    this.tabs = [];
+    this.selected_bin_item = undefined;
+    this.mousedown = false;
     var _this = this;
-/*    if (typeof _opts.bins === "object") {
-        this.bins = Rkns._(_opts.bins).map(function(_type) {
-            return new Rkns.Bins[_type](_this);
+    this.$.mouseup(function() {
+        _this.selected_bin_item = undefined;
+    });
+    if (!_opts.search.length) {
+        this.$.find(".Rk-Search-Form").detach();
+    } else {
+        var _tmpl = Rkns._.template('<option value="<%= name %>"><%= name %></option>');
+        this.$.find(".Rk-Search-Select").html(
+            Rkns._(_opts.search).map(function(_name) {
+                return _tmpl({name:_name});
+            }).join("")
+        );
+    }
+    this.$.find(".Rk-Search-Form").submit(function() {
+        _this.tabs.push(
+            new Rkns.Bins[_this.$.find(".Rk-Search-Select").val()](
+                _this,
+                {
+                    search: _this.$.find(".Rk-Search-Input").val()
+                }
+            )
+        );
+        return false;
+    });
+    this.$.find(".Rk-Bins")
+        .click(function(_e) {
+            if (_e.target.className == "Rk-Bin-Title") {
+                var _mainDiv = Rkns.$(_e.target).siblings(".Rk-Bin-Main");
+                if (_mainDiv.is(":hidden")) {
+                    _this.$.find(".Rk-Bin-Main").slideUp();
+                    _mainDiv.slideDown();
+                }
+            }
+        }).mousedown(function(_e) {
+            var _t = Rkns.$(_e.target);
+            while (!_t.is(".Rk-Bins,.Rk-Bin-Item")) {
+                _t = _t.parent();
+            }
+            if (_t.is(".Rk-Bin-Item")) {
+                _this.selected_bin_item = {
+                    uri : $(_t).attr("data-uri"),
+                    title : $(_t).attr("data-title"),
+                    description : $(_t).attr("data-description")
+                }
+                return false;
+            }
         });
-        Rkns.$('.Rk-TabButton:last').addClass("active");
-        Rkns.$('.Rk-TabDiv:last').show();
-} */
+    Rkns.$(window).resize(function() {
+        _this.resizeBins();
+    })
+    
     this.project.remotemodel.onLoad(function() {
         if (typeof _this.project.current_user === "undefined") {
             _this.project.current_user = _this.project.users[0];
@@ -145,9 +184,20 @@
 }
 
 Rkns.Renkan.prototype.template = Rkns._.template(
-    '<div class="Rk-Bins"></div><div class="Rk-Render"></div>'
+    '<div class="Rk-Bins">'
+    + '<form class="Rk-Search-Form"><input class="Rk-Search-Input" type="search" placeholder="Search" /><select class="Rk-Search-Select"></select></form>'
+    + '<ul class="Rk-Bin-List"></ul></div><div class="Rk-Render"></div>'
 );
 
+
+Rkns.Renkan.prototype.resizeBins = function() {
+    var _titles = this.$.find(".Rk-Bin-Title"),
+        _d = _titles.length * _titles.outerHeight() + this.$.find(".Rk-Search-Form").outerHeight();
+    this.$.find(".Rk-Bin-Main").css({
+        height: this.$.find(".Rk-Bins").height() - _d
+    });
+}
+
 /* Utility functions */
 
 Rkns.Utils = {
--- a/client/js/paper-renderer.js	Fri Aug 17 12:50:00 2012 +0200
+++ b/client/js/paper-renderer.js	Fri Aug 17 18:36:12 2012 +0200
@@ -380,7 +380,8 @@
 
 Rkns.Renderer.TempEdge.prototype.paperShift = function(_delta) {
     this.end_pos = this.end_pos.add(_delta);
-    this._renderer.onMouseMove({point: this.end_pos});
+    var _hitResult = paper.project.hitTest(this.end_pos);
+    this._renderer.findTarget(_hitResult);
     this.redraw();
 }
 
@@ -779,15 +780,18 @@
     _tool.onMouseDrag = function(_event) {
         _this.onMouseDrag(_event);
     }
-    _tool.onMouseUp = function(_event) {
+    this.canvas_$.mouseup(function(_event) {
         _this.onMouseUp(_event);
-    }
+    });
     this.canvas_$.mousewheel(function(_event, _delta) {
         _this.onScroll(_event, _delta);
-    })
+    });
     this.canvas_$.dblclick(function(_event) {
         _this.onDoubleClick(_event);
     });
+    this.canvas_$.mouseenter(function(_event) {
+        _this.onMouseEnter(_event);
+    });
     this.editor_$.find(".Rk-ZoomOut").click(function() {
         _this.offset = new paper.Point([
             _this.canvas_$.width(),
@@ -882,8 +886,7 @@
     this.click_target = _tmpEdge;
 }
 
-Rkns.Renderer.Scene.prototype.onMouseMove = function(_event) {
-    var _hitResult = paper.project.hitTest(_event.point);
+Rkns.Renderer.Scene.prototype.findTarget = function(_hitResult) {
     if (_hitResult && typeof _hitResult.item.__controller !== "undefined") {
         var _newTarget = _hitResult.item.__controller;
         if (this.selected_target !== _hitResult.item.__controller) {
@@ -901,6 +904,20 @@
     }
 }
 
+Rkns.Renderer.Scene.prototype.onMouseMove = function(_event) {
+    var _hitResult = paper.project.hitTest(_event.point);
+    if (this.is_dragging) {
+        if (this.click_target && typeof this.click_target.paperShift === "function") {
+            this.click_target.paperShift(_event.delta);
+        } else {
+            this.offset = this.offset.add(_event.delta);
+            this.redraw();
+        }
+    } else {
+        this.findTarget(_hitResult);
+    }
+}
+
 Rkns.Renderer.Scene.prototype.onMouseDown = function(_event) {
     this.is_dragging = false;
     var _hitResult = paper.project.hitTest(_event.point);
@@ -919,17 +936,20 @@
 
 Rkns.Renderer.Scene.prototype.onMouseDrag = function(_event) {
     this.is_dragging = true;
-    if (this.click_target && typeof this.click_target.paperShift === "function") {
-        this.click_target.paperShift(_event.delta);
-    } else {
-        this.offset = this.offset.add(_event.delta);
-        this.redraw();
-    }
+    this.onMouseMove(_event);
 }
 
 Rkns.Renderer.Scene.prototype.onMouseUp = function(_event) {
     if (this.click_target) {
-        this.click_target.mouseup(_event);
+        var _off = this.canvas_$.offset();
+        this.click_target.mouseup(
+            {
+                point: new paper.Point([
+                    _event.pageX - _off.left,
+                    _event.pageY - _off.top
+                ])
+            }
+        );
     }
     this.is_dragging = false;
     this.click_target = null;
@@ -974,3 +994,26 @@
     }
     paper.view.draw();
 }
+
+Rkns.Renderer.Scene.prototype.onMouseEnter = function(_event) {
+    var _newEl = this.renkan.selected_bin_item;
+    if (_newEl) {
+        var _off = this.canvas_$.offset(),
+            _point = new paper.Point([
+                _event.pageX - _off.left,
+                _event.pageY - _off.top
+            ]),
+            _coords = this.toModelCoords(_point),
+            _node = this._project.addNode({
+                uri: _newEl.uri,
+                title: _newEl.title,
+                description: _newEl.description,
+                position: {
+                    x: _coords.x,
+                    y: _coords.y
+                }
+            }, Rkns._RENDER_AND_SAVE);
+        this.is_dragging = true;
+        this.click_target = _node.__controller;
+    }
+}
\ No newline at end of file
--- a/client/js/random-data.js	Fri Aug 17 12:50:00 2012 +0200
+++ b/client/js/random-data.js	Fri Aug 17 18:36:12 2012 +0200
@@ -2,8 +2,8 @@
 
 Rkns.RemoteModels.RandomData.prototype._init = function() {
     this._USER_COUNT = 5;
-    this._NODE_COUNT = 20;
-    this._EDGE_COUNT = 40;
+    this._NODE_COUNT = 50;
+    this._EDGE_COUNT = 100;
     this.user_colors = ["#1f77b4","#aec7e8","#ff7f0e","#ffbb78","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5","#8c564b","#c49c94","#e377c2","#f7b6d2","#7f7f7f","#c7c7c7","#bcbd22","#dbdb8d","#17becf","#9edae5"];
     this.load();
 }
@@ -24,10 +24,10 @@
             title: "Node #"+(1+i),
             created_by: "user-" + Math.floor(this._USER_COUNT*Math.random()),
             position: {
-//                x: 200 * Math.random(),
-//                y: 150 * Math.random()
-                x: 100 * Math.cos(2 * Math.PI * i / this._NODE_COUNT),
-                y: 100 * Math.sin(2 * Math.PI * i / this._NODE_COUNT)
+                x: 200 * Math.random(),
+                y: 150 * Math.random()
+//                x: 100 * Math.cos(2 * Math.PI * i / this._NODE_COUNT),
+//                y: 100 * Math.sin(2 * Math.PI * i / this._NODE_COUNT)
             }
         }));
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/js/twitter-bin.js	Fri Aug 17 18:36:12 2012 +0200
@@ -0,0 +1,89 @@
+Rkns.Bins.Twitter = Rkns.Utils.inherit(Rkns.Bins._Base);
+
+Rkns.Bins.Twitter.prototype.tweetTemplate = Rkns._.template(
+    '<li class="Rk-Twitter-Tweet Rk-Bin-Item" data-uri="http://twitter.com/<%=tweet.from_user%>/status/<%=tweet.id_str%>" '
+    + 'data-title="Tweet by @<%=tweet.from_user%>" data-description="<%-tweet.text%>">'
+    + '<img class="Rk-Twitter-TwImage" src="<%=tweet.profile_image_url%>" />'
+    + '<h4 class="Rk-Twitter-TwTitle"><a href="http://twitter.com/<%=tweet.from_user%>" target="_blank">@<%=tweet.from_user%></a> (<%=tweet.from_user_name%>)</h4>'
+    + '<p class="Rk-Twitter-TwDate"><%=date%></p>'
+    + '<p class="Rk-Twitter-TwText"><%=text%></p>'
+    + '<p class="Rk-Twitter-TwActions"><a class="Rk-Twitter-TwAction" href="http://twitter.com/<%=tweet.from_user%>/status/<%=tweet.id_str%>" target="_blank">show original</a> · '
+    + '<a href="http://twitter.com/intent/tweet?in_reply_to=<%=tweet.id_str%>" target="_blank">reply</a> · '
+    + '<a href="http://twitter.com/intent/retweet?tweet_id=<%=tweet.id_str%>" target="_blank">retweet</a> · '
+    + '<a href="http://twitter.com/intent/favorite?tweet_id=<%=tweet.id_str%>" target="_blank">favorite</a></p></li>'
+);
+
+Rkns.Bins.Twitter.prototype._init = function(_renkan, _opts) {
+    this.search = _opts.search;
+    this.title_$.html('Twitter: "' + this.search + '"')
+    var _this = this;
+    Rkns.$.getJSON(
+        "http://search.twitter.com/search.json?q=" + encodeURIComponent(this.search) + "&callback=?",
+        function(_data) {
+            var _html = Rkns._(_data.results).map(function(_result) {
+                var _entities = [],
+                    _users = _result.text.match(/@[\w_]+/gm),
+                    _lastpos = 0;
+                if (_users) {
+                    for (var _i = 0; _i < _users.length; _i++) {
+                        var _m = _users[_i],
+                            _start = _lastpos + _result.text.substr(_lastpos).search(_m),
+                            _lastpos = _start + _m.length;
+                        _entities.push({
+                            "text" : _m,
+                            "start" : _start,
+                            "end" : _lastpos,
+                            "link" :'<a href="http://twitter.com/' + _m.substr(1) + '" onclick="filtrerTexte(\'' + _m + '\'); return false;" target="_blank">',
+                        });
+                    }
+                }
+                var _hashes = _result.text.match(/([^&]|^)#[^\s,.!?=#@&;()]+/gm),
+                    _lastpos = 0;
+                if (_hashes) {
+                    for (var _i = 0; _i < _hashes.length; _i++) {
+                        var _m = _hashes[_i],
+                            _h = ( _m[0] == '#' ? _m : _m.substr(1) ),
+                            _start = _lastpos + _result.text.substr(_lastpos).search(_h),
+                            _lastpos = _start + _h.length;
+                        _entities.push({
+                            "text" : _h,
+                            "start" : _start,
+                            "end" : _lastpos,
+                            "link" :'<a href="http://twitter.com/search?q=' + encodeURIComponent(_h) + '" onclick="filtrerTexte(\'' + _.escape(_h) + '\'); return false;" target="_blank">',
+                        });
+                    }
+                }
+                
+                var _urls = _result.text.match(/(www\.|https?:\/\/)[\w./_\-]+/gim),
+                    _lastpos = 0;
+                if (_urls) {
+                    for (var _i = 0; _i < _urls.length; _i++) {
+                        var _m = _urls[_i],
+                            _start = _lastpos + _result.text.substr(_lastpos).search(_m),
+                            _lastpos = _start + _m.length;
+                        _entities.push({
+                            "text" : _m,
+                            "start" : _start,
+                            "end" : _lastpos,
+                            "link" :'<a href="' + _m + '" target="_blank">',
+                        });
+                    }
+                }
+                _entities = Rkns._(_entities).sortBy(function(a) { return a.start });
+                var _lastend = 0,
+                    _text = Rkns._(_entities).map(function(_e) {
+                        var _txt = _result.text.substring(_lastend, _e.start) + _e.link + _e.text + '</a>';
+                        _lastend = _e.end;
+                        return _txt;
+                    }).join("") + _result.text.substring(_lastend);
+                
+                return _this.tweetTemplate({
+                    tweet: _result,
+                    date: new Date(_result.created_at.replace(/(\+|-)/,'UTC$1')).toLocaleString(),
+                    text: _text
+                });
+            }).join("");
+            _this.main_$.html(_html);
+        }
+    );
+}
--- a/client/render-test.html	Fri Aug 17 12:50:00 2012 +0200
+++ b/client/render-test.html	Fri Aug 17 18:36:12 2012 +0200
@@ -14,6 +14,7 @@
         <script src="js/i18n.js"></script>
         <script src="js/view-model.js"></script>
         <script src="js/full-json.js"></script>
+        <script src="js/random-data.js"></script>
         <script src="js/twitter-bin.js"></script>
         <script src="js/paper-renderer.js"></script>
         <script type="text/javascript">
@@ -22,17 +23,20 @@
                 _proj = new Rkns.Renkan({
                     url: "data/simple-persist.php",
                     remotemodel: "FullJson",
+                    search: [
+                        "Twitter"
+                    ],
                     user: {
-                        id: "anonymous",
+                        id: "u-anonymous",
                         title: "anonymous",
-                        color: "#0000ff"
+                        color: "#dd00dd"
                     }
                 });
             });
         </script>
         <link rel="stylesheet" href="css/renkan.css" />
         <style type="text/css">
-            #topbar { position: absolute; left: 0; top: 0; right: 0; height: 40px; background: #f0f0f0; font-size: 20px; color: #a0a0a0; }
+            #topbar { position: absolute; left: 0; top: 0; right: 0; height: 40px; background: #f8f8f8; font-size: 20px; color: #a0a0a0; }
             #renkan {
                 position: absolute; left: 0; top: 40px; width: 200px; bottom: 0;
             }