Skip to content
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Add multi-select cursor sharing
 - add handler for idle, status and url updates
  • Loading branch information
triglian committed Dec 7, 2013
commit c713e4a3b7798c448e7c58d35eb30a65a271eb26
102 changes: 66 additions & 36 deletions togetherjs/forms.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,12 +103,17 @@ define(["jquery", "util", "session", "elementFinder", "eventMaker", "templating"
assert(this.element.hasClass("ace_editor"));
this.cursorMarkers= {};
this._change = this._change.bind(this);
this._changeCursor = this._changeCursor.bind(this);
this._editor().document.on("change", this._change);
this._editor().document.getSelection().on("changeCursor", this._changeCursor);
this._changeSelection = this._changeSelection.bind(this);
this._syncCursor = ace.require("ace/lib/lang").delayedCall(this._changeSelection);
this._delayedChangeSelection = this._delayedChangeSelection.bind(this);
this._editor().editor.on("changeSelection", this._delayedChangeSelection);
//changeSelection doesn't fire when a user presses Esc to leave the multi-select
//mode, so we need this extra event listener
this._editor().editor.selection.on("singleSelect", this._delayedChangeSelection);

this.peerUpdate = this.peerUpdate.bind(this);

peers.on("new-peer identity-updated status-updated", this.peerUpdate);
peers.on("new-peer identity-updated status-updated url-updated idle-updated", this.peerUpdate);
},

tracked: function (el) {
Expand All @@ -117,64 +122,81 @@ define(["jquery", "util", "session", "elementFinder", "eventMaker", "templating"

destroy: function (el) {
this._editor().document.removeListener("change", this._change);
this._editor()
.document
.getSelection()
.removeListener("change", this._changeCursor);
this._editor().editor.off("changeSelection", this._changeSelection);
peers.off("new-peer identity-updated status-updated url-updated idle-updated", this.peerUpdate);
},

update: function (msg) {
if (msg.value) {
this.init(msg);
return;
}

if(msg.hasOwnProperty('delta')) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing a space after if here and below

this._editor().document.getDocument().applyDeltas([msg.delta]);
}

if(msg.hasOwnProperty('cursor')) {
if(msg.hasOwnProperty('cursors')) {
this.updateCursorMarker(msg);
}
},

peerUpdate: function (peer) {
//status-updated
if (peer.status=='bye') {
this.peerBye(peer);
return;
}
//new-peer, identity-updated, url-updated, idle-updated
if( this.cursorMarkers.hasOwnProperty(peer.id)) {
var msg = {
peer: peer,
cursor: this.cursorMarkers[peer.id].cursor
cursors: this.cursorMarkers[peer.id].cursors
};
this.updateCursorMarker(msg);
}
},

peerBye: function (peer) {
if ( this.cursorMarkers.hasOwnProperty(peer.id)) {
var oldMarkers = this.cursorMarkers[peer.id].markers;
oldMarkers.forEach(function(markerId){
this._editor()
.document
.removeMarker(markerId);
}, this);
delete this.cursorMarkers[peer.id];
}
},

updateCursorMarker: function (msg) {
if ("undefined" == typeof msg) {
if (msg === undefined) {
return;
}

if( this.cursorMarkers.hasOwnProperty(msg.peer.id)) {
this._editor()
.document
.removeMarker(this.cursorMarkers[msg.peer.id].markerId);
if ( this.cursorMarkers.hasOwnProperty(msg.peer.id)) {
var oldMarkers = this.cursorMarkers[msg.peer.id].markers;
oldMarkers.forEach(function(markerId){
this._editor()
.document
.removeMarker(markerId);
}, this);
}
var markers = msg.cursors.map(function(cursor) {
var Range = ace.require('ace/range').Range;
var range = new Range(cursor.row, cursor.column,
cursor.row, cursor.column + 1);

var Range = ace.require('ace/range').Range;
var range = new Range(msg.cursor.row, msg.cursor.column,
msg.cursor.row, msg.cursor.column + 1);

var markerId = this._editor()
var markerId = this._editor()
.document
.addMarker(range, "ace_selection", drawMarker, false);

this.cursorMarkers[msg.clientId] = {
markerId:markerId,
cursor:msg.cursor
return markerId;
}, this);
this.cursorMarkers[msg.peer.id] = {
markers:markers,
cursors:msg.cursors
};

function drawMarker(stringBuilder, range, left, top, config) {
var color = "background-color:"+ msg.peer.color;
stringBuilder.push("<div class='ace_selection' style='", "left:", left, "px;", "top:", top + 2, "px;", "height:", config.lineHeight - 2, "px;", "width:", 2, "px;", color || "", "'></div>", "<div class='ace_selection' style='", "left:", left - 2, "px;", "top:", top, "px;", "height:", 5, "px;", "width:", 6, "px;", color || "", "'></div>");
var opacity = (msg.peer.url != session.currentUrl() || msg.peer.idle=="inactive") ? 0.5 : 1;
stringBuilder.push("<div class='ace_selection' style='", "opacity:", opacity, ";", "left:", left, "px;", "top:", top + 5, "px;", "height:", config.lineHeight - 5, "px;", "width:", 2, "px;", color || "", "'></div>", "<div class='ace_selection' style='", "opacity:", opacity, ";", "left:", left - 2, "px;", "top:", top, "px;", "height:", 5, "px;", "width:", 6, "px;", color || "", "'></div>");
}
},

Expand All @@ -194,7 +216,7 @@ define(["jquery", "util", "session", "elementFinder", "eventMaker", "templating"
return this.element[0].env;
},

_change: function (e) {
_change: function (e, document) {
// FIXME: I should have an internal .send() function that automatically
// asserts !inRemoteUpdate, among other things
if (inRemoteUpdate) {
Expand All @@ -206,25 +228,33 @@ define(["jquery", "util", "session", "elementFinder", "eventMaker", "templating"
type: "form-update",
tracker: this.trackerName,
element: elementFinder.elementLocation(this.element),
delta: JSON.parse(JSON.stringify(e.data)),
cursor: this._editor().document.getSelection().getCursor()
delta: JSON.parse(JSON.stringify(e.data))
});
},

_changeCursor: function (e) {
_changeSelection: function () {
// FIXME: I should have an internal .send() function that automatically
// asserts !inRemoteUpdate, among other things
if (inRemoteUpdate) {
return;
}
// FIXME: I want to use a more normalized version of replace instead of
// ACE's native delta
var selection = this._editor().document.getSelection();
var cursors = [];
if (selection.inMultiSelectMode) {
cursors = selection.getAllRanges().map(function(range) {return range.cursor; });
} else {
cursors.push(selection.getCursor());
}
session.send({
type: "form-update",
tracker: this.trackerName,
element: elementFinder.elementLocation(this.element),
cursor: this._editor().document.getSelection().getCursor()
cursors: cursors
});
},

_delayedChangeSelection: function(){
this._syncCursor.delay(50);
}
});

Expand Down