1
0
Fork 0
mirror of https://github.com/overleaf/overleaf.git synced 2025-04-11 03:27:19 +00:00

Display cursor name labels

This commit is contained in:
James Allen 2014-06-26 11:19:05 +01:00
parent 70f64ee20c
commit afb953a489
6 changed files with 205 additions and 57 deletions
services/web
app/coffee/Features/Editor
public
coffee/app/ide
stylesheets/app
test/UnitTests/coffee/Editor

View file

@ -112,12 +112,18 @@ module.exports = EditorController =
return callback(error) if error?
client.get "last_name", (error, last_name) ->
return callback(error) if error?
cursorData.id = client.id
if first_name? and last_name?
cursorData.name = first_name + " " + last_name
else
cursorData.name = "Anonymous"
EditorRealTimeController.emitToRoom(project_id, "clientTracking.clientUpdated", cursorData)
client.get "email", (error, email) ->
return callback(error) if error?
client.get "user_id", (error, user_id) ->
return callback(error) if error?
cursorData.id = client.id
cursorData.user_id = user_id if user_id?
cursorData.email = email if email?
if first_name? and last_name?
cursorData.name = first_name + " " + last_name
else
cursorData.name = "Anonymous"
EditorRealTimeController.emitToRoom(project_id, "clientTracking.clientUpdated", cursorData)
addUserToProject: (project_id, email, privileges, callback = (error, collaborator_added)->)->
email = email.toLowerCase()

View file

@ -4,34 +4,139 @@ define [
Range = require("ace/range").Range
class AnnotationsManager
constructor: (@$scope, @editor) ->
constructor: (@$scope, @editor, @element) ->
@markerIds = []
@labels = []
@$scope.annotationLabel = {
show: false
right: "auto"
left: "auto"
top: "auto"
bottom: "auto"
backgroundColor: "black"
text: ""
}
@$scope.$watch "annotations", (value) =>
if value?
@redrawAnnotations()
@redrawAnnotations()
@$scope.$watch "theme", (value) =>
@redrawAnnotations()
@editor.on "mousemove", (e) =>
position = @editor.renderer.screenToTextCoordinates(e.clientX, e.clientY)
e.position = position
@showAnnotationLabels(position)
redrawAnnotations: () ->
console.log "REDRAWING ANNOTATIONS"
@_clearMarkers()
@_clearLabels()
for annotation in @$scope.annotations or []
do (annotation) =>
colorScheme = @_getColorScheme(annotation.hue)
console.log "DRAWING ANNOTATION", annotation, colorScheme
if annotation.cursor?
@labels.push {
text: annotation.text
range: new Range(
annotation.cursor.row, annotation.cursor.column,
annotation.cursor.row, annotation.cursor.column + 1
)
colorScheme: colorScheme
snapToStartOfRange: true
}
@_drawCursor(annotation, colorScheme)
showAnnotationLabels: (position) ->
labelToShow = null
for label in @labels or []
if label.range.contains(position.row, position.column)
labelToShow = label
@$scope.$apply () =>
if !labelToShow?
@$scope.annotationLabel.show = false
else
$ace = $(@editor.renderer.container).find(".ace_scroller")
# Move the label into the Ace content area so that offsets and positions are easy to calculate.
$ace.append(@element.find(".annotation-label"))
if labelToShow.snapToStartOfRange
coords = @editor.renderer.textToScreenCoordinates(labelToShow.range.start.row, labelToShow.range.start.column)
else
coords = @editor.renderer.textToScreenCoordinates(position.row, position.column)
offset = $ace.offset()
height = $ace.height()
coords.pageX = coords.pageX - offset.left
coords.pageY = coords.pageY - offset.top
if coords.pageY > 100
console.log "middle of page", height - coords.pageY
top = "auto"
bottom = height - coords.pageY
else
console.log "top of page", coords.pageY
top = coords.pageY + @editor.renderer.lineHeight
bottom = "auto"
left = coords.pageX
console.log "TOP BOTTOM", top, bottom
@$scope.annotationLabel = {
show: true
left: left
bottom: bottom
top: top
backgroundColor: labelToShow.colorScheme.labelBackgroundColor
text: labelToShow.text
}
_clearMarkers: () ->
for marker_id in @markerIds
@editor.getSession().removeMarker(marker_id)
@markerIds = []
for annotation in @$scope.annotations or []
do (annotation) =>
console.log "DRAWING ANNOTATION", annotation
@markerIds.push @editor.getSession().addMarker new Range(
annotation.cursor.row, annotation.cursor.column,
annotation.cursor.row, annotation.cursor.column + 1
), "remote-cursor", (html, range, left, top, config) ->
div = """
<div
class='remote-cursor custom ace_start'
style='height: #{config.lineHeight}px; top:#{top}px; left:#{left}px;'
>
<div class="nubbin" style="bottom: #{config.lineHeight - 2}px"></div>
<div class="name" style="display: none; bottom: #{config.lineHeight - 2}px">#{$('<div/>').text(annotation.text).html()}</div>
</div>
"""
html.push div
, true
_clearLabels: () ->
@labels = []
_drawCursor: (annotation, colorScheme) ->
@markerIds.push @editor.getSession().addMarker new Range(
annotation.cursor.row, annotation.cursor.column,
annotation.cursor.row, annotation.cursor.column + 1
), "remote-cursor", (html, range, left, top, config) ->
div = """
<div
class='remote-cursor custom ace_start'
style='height: #{config.lineHeight}px; top:#{top}px; left:#{left}px; border-color: #{colorScheme.cursor};'
>
<div class="nubbin" style="bottom: #{config.lineHeight}px; background-color: #{colorScheme.cursor};"></div>
</div>
"""
html.push div
, true
_getColorScheme: (hue) ->
if @_isDarkTheme()
return {
cursor: "hsl(#{hue}, 100%, 50%)"
labelBackgroundColor: "hsl(#{hue}, 100%, 50%)"
}
else
return {
cursor: "hsl(#{hue}, 100%, 50%)"
labelBackgroundColor: "hsl(#{hue}, 100%, 50%)"
}
_isDarkTheme: () ->
rgb = @element.find(".ace_editor").css("background-color");
[m, r, g, b] = rgb.match(/rgb\(([0-9]+), ([0-9]+), ([0-9]+)\)/)
r = parseInt(r, 10)
g = parseInt(g, 10)
b = parseInt(b, 10)
return r + g + b < 3 * 128

View file

@ -39,10 +39,10 @@ define [
editor = Ace.edit(element.find(".ace-editor-body")[0])
autoCompleteManager = new AutoCompleteManager(scope, editor)
spellCheckManager = new SpellCheckManager(scope, editor, element)
undoManager = new UndoManager(scope, editor)
annotationsManagaer = new AnnotationsManager(scope, editor)
autoCompleteManager = new AutoCompleteManager(scope, editor, element)
spellCheckManager = new SpellCheckManager(scope, editor, element)
undoManager = new UndoManager(scope, editor, element)
annotationsManagaer = new AnnotationsManager(scope, editor, element)
# Prevert Ctrl|Cmd-S from triggering save dialog
editor.commands.addCommand
@ -91,6 +91,7 @@ define [
session.setMode(new LatexMode())
autoCompleteManager.bindToSession(session)
annotationsManagaer.redrawAnnotations()
doc = session.getDocument()
doc.on "change", () ->
@ -138,6 +139,20 @@ define [
</li>
</ul>
</div>
<div
class="annotation-label"
ng-show="annotationLabel.show"
ng-style="{
position: 'absolute',
left: annotationLabel.left,
right: annotationLabel.right,
bottom: annotationLabel.bottom,
top: annotationLabel.top,
'background-color': annotationLabel.backgroundColor
}"
>
{{ annotationLabel.text }}
</div>
</div>
"""
}

View file

@ -1,4 +1,6 @@
define [], () ->
define [
"../../../libs/md5"
], () ->
class OnlineUsersManager
constructor: (@ide, @$scope) ->
@$scope.onlineUsers = {}
@ -17,6 +19,7 @@ define [], () ->
@updateCursorHighlights()
@ide.socket.on "clientTracking.clientDisconnected", (client_id) =>
console.log "CLIENT DISCONNECTED", client_id
@$scope.$apply () =>
delete @$scope.onlineUsers[client_id]
@updateCursorHighlights()
@ -26,12 +29,14 @@ define [], () ->
@$scope.onlineUserCursorAnnotations = {}
for client_id, client of @$scope.onlineUsers
doc_id = client.doc_id
continue if !doc_id?
@$scope.onlineUserCursorAnnotations[doc_id] ||= []
@$scope.onlineUserCursorAnnotations[doc_id].push {
text: client.name
cursor:
row: client.row
column: client.column
hue: @getHueForUserId(client.user_id)
}
UPDATE_INTERVAL: 500
@ -52,5 +57,20 @@ define [], () ->
, @UPDATE_INTERVAL
else
console.log "NOT UPDATING"
OWN_HUE: 200 # We will always appear as this color to ourselves
ANONYMOUS_HUE: 100
getHueForUserId: (user_id) ->
if !user_id?
return @ANONYMOUS_HUE
if window.user.id == user_id
return @OWN_HUE
hash = CryptoJS.MD5(user_id)
hue = parseInt(hash.toString().slice(0,8), 16) % 320
# Avoid 20 degrees either side of the personal hue
if hue > @OWNER_HUE - 20
hue = hue + 40
return hue

View file

@ -193,28 +193,25 @@
background-repeat: repeat-x;
background-position: bottom left;
}
@cursor-color: rgb(14, 158, 0);
.remote-cursor {
position: absolute;
z-index: 2;
border-left: 2px solid @cursor-color;
.name {
font-size: 0.8em;
background-color: @cursor-color;
color: white;
padding: 2px 6px;
border-radius: 3px 3px 3px 0;
position: absolute;
left: -4px;
}
border-left: 2px solid transparent;
.nubbin {
height: 6px;
width: 6px;
background-color: @cursor-color;
height: 5px;
width: 5px;
position: absolute;
left: -4px;
left: -2px;
}
}
.annotation-label {
padding: (@line-height-computed / 4) (@line-height-computed / 2);
font-size: 0.8rem;
z-index: 100;
font-family: @font-family-sans-serif;
color: white;
font-weight: 700;
}
}
.ui-layout-resizer {

View file

@ -246,15 +246,16 @@ describe "EditorController", ->
row: @row = 42
column: @column = 37
}
@clientParams = {
project_id: @project_id
first_name: @first_name = "Douglas"
last_name: @last_name = "Adams"
}
@client.get = (param, callback) => callback null, @clientParams[param]
describe "with a logged in user", ->
beforeEach ->
@clientParams = {
project_id: @project_id
first_name: @first_name = "Douglas"
last_name: @last_name = "Adams"
email: @email = "joe@example.com"
user_id: @user_id = "user-id-123"
}
@client.get = (param, callback) => callback null, @clientParams[param]
@EditorController.updateClientPosition @client, @update
it "should send the update to the project room with the user's name", ->
@ -265,13 +266,17 @@ describe "EditorController", ->
name: "#{@first_name} #{@last_name}"
row: @row
column: @column
email: @email
user_id: @user_id
})
.should.equal true
describe "with an anonymous user", ->
beforeEach ->
@clientParams.first_name = null
@clientParams.last_name = null
@clientParams = {
project_id: @project_id
}
@client.get = (param, callback) => callback null, @clientParams[param]
@EditorController.updateClientPosition @client, @update
it "should send the update to the project room with an anonymous name", ->