diff --git a/services/web/app/views/project/editor/editor.jade b/services/web/app/views/project/editor/editor.jade index 47bf67b4ee..4fd79ab6d0 100644 --- a/services/web/app/views/project/editor/editor.jade +++ b/services/web/app/views/project/editor/editor.jade @@ -1,5 +1,5 @@ div.full-size(ng-show="ui.view == 'editor'") - .loading(ng-show="!editor.sharejs_doc || editor.opening") + .loading-panel(ng-show="!editor.sharejs_doc || editor.opening") i.fa.fa-spin.fa-refresh |   Loading... diff --git a/services/web/app/views/project/editor/track-changes.jade b/services/web/app/views/project/editor/track-changes.jade index 60eaf78755..682c8a4e45 100644 --- a/services/web/app/views/project/editor/track-changes.jade +++ b/services/web/app/views/project/editor/track-changes.jade @@ -52,4 +52,20 @@ div#trackChanges(ng-show="ui.view == 'track-changes'") span.doc {{ doc.entity.name }} div.users span.user(ng-repeat="user in update.meta.users") - | {{user.first_name}} {{user.last_name}} \ No newline at end of file + | {{user.first_name}} {{user.last_name}} + + .diff.full-size + .diff-editor( + ace-editor, + ng-show="!!trackChanges.diff && !trackChanges.diff.loading", + theme="settings.theme", + font-size="settings.fontSize", + text="trackChanges.diff.text", + annotations="trackChanges.diff.annotations", + read-only="true" + ) + .loading-panel(ng-show="trackChanges.diff.loading") + i.fa.fa-spin.fa-refresh + |   Loading... + .error-panel(ng-show="trackChanges.diff.error") + .alert.alert-danger Sorry, something went wrong :( \ No newline at end of file diff --git a/services/web/public/coffee/app/ide/editor/EditorManager.coffee b/services/web/public/coffee/app/ide/editor/EditorManager.coffee index b20d9e718b..91ab8a7212 100644 --- a/services/web/public/coffee/app/ide/editor/EditorManager.coffee +++ b/services/web/public/coffee/app/ide/editor/EditorManager.coffee @@ -13,7 +13,7 @@ define [ } @$scope.$on "entity:selected", (event, entity) => - if (entity.type == "doc") + if (@$scope.ui.view == "editor" and entity.type == "doc") @openDoc(entity) initialized = false diff --git a/services/web/public/coffee/app/ide/editor/annotations/AnnotationsManager.coffee b/services/web/public/coffee/app/ide/editor/annotations/AnnotationsManager.coffee index 2d10114edb..bbfb9ded2c 100644 --- a/services/web/public/coffee/app/ide/editor/annotations/AnnotationsManager.coffee +++ b/services/web/public/coffee/app/ide/editor/annotations/AnnotationsManager.coffee @@ -47,6 +47,26 @@ define [ snapToStartOfRange: true } @_drawCursor(annotation, colorScheme) + else if annotation.highlight? + @labels.push { + text: annotation.label + range: new Range( + annotation.highlight.start.row, annotation.highlight.start.column, + annotation.highlight.end.row, annotation.highlight.end.column + ) + colorScheme: colorScheme + } + @_drawHighlight(annotation, colorScheme) + else if annotation.strikeThrough? + @labels.push { + text: annotation.label + range: new Range( + annotation.strikeThrough.start.row, annotation.strikeThrough.start.column, + annotation.strikeThrough.end.row, annotation.strikeThrough.end.column + ) + colorScheme: colorScheme + } + @_drawStrikeThrough(annotation, colorScheme) showAnnotationLabels: (position) -> labelToShow = null @@ -104,7 +124,7 @@ define [ @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) -> + ), "annotation remote-cursor", (html, range, left, top, config) -> div = """
+ @_addMarkerWithCustomStyle( + new Range( + annotation.highlight.start.row, annotation.highlight.start.column, + annotation.highlight.end.row, annotation.highlight.end.column + 1 + ), + "annotation highlight", + false, + "background-color: #{colorScheme.highlightBackgroundColor}" + ) + + _drawStrikeThrough: (annotation, colorScheme) -> + lineHeight = @editor.renderer.lineHeight + @_addMarkerWithCustomStyle( + new Range( + annotation.strikeThrough.start.row, annotation.strikeThrough.start.column, + annotation.strikeThrough.end.row, annotation.strikeThrough.end.column + 1 + ), + "annotation strike-through-background", + false, + "background-color: #{colorScheme.strikeThroughBackgroundColor}" + ) + @_addMarkerWithCustomStyle( + new Range( + annotation.strikeThrough.start.row, annotation.strikeThrough.start.column, + annotation.strikeThrough.end.row, annotation.strikeThrough.end.column + 1 + ), + "annotation strike-through-foreground", + true, + """ + height: #{Math.round(lineHeight/2) + 2}px; + border-bottom: 2px solid #{colorScheme.strikeThroughForegroundColor}; + """ + ) + + _addMarkerWithCustomStyle: (range, klass, foreground, style) -> + if foreground? + markerLayer = @editor.renderer.$markerBack + else + markerLayer = @editor.renderer.$markerFront + + @markerIds.push @editor.getSession().addMarker range, klass, (html, range, left, top, config) -> + if range.isMultiLine() + markerLayer.drawTextMarker(html, range, klass, config, style) + else + markerLayer.drawSingleLineMarker(html, range, "#{klass} ace_start", config, 0, style) + , foreground + _getColorScheme: (hue) -> if @_isDarkTheme() return { cursor: "hsl(#{hue}, 100%, 50%)" labelBackgroundColor: "hsl(#{hue}, 100%, 50%)" + highlightBackgroundColor: "hsl(#{hue}, 100%, 28%);" + strikeThroughBackgroundColor: "hsl(#{hue}, 100%, 20%);" + strikeThroughForegroundColor: "hsl(#{hue}, 100%, 60%);" } else return { cursor: "hsl(#{hue}, 100%, 50%)" labelBackgroundColor: "hsl(#{hue}, 100%, 50%)" + highlightBackgroundColor: "hsl(#{hue}, 70%, 85%);" + strikeThroughBackgroundColor: "hsl(#{hue}, 70%, 95%);" + strikeThroughForegroundColor: "hsl(#{hue}, 70%, 40%);" } _isDarkTheme: () -> diff --git a/services/web/public/coffee/app/ide/editor/directives/aceEditor.coffee b/services/web/public/coffee/app/ide/editor/directives/aceEditor.coffee index 3c3c601d91..246a6574f6 100644 --- a/services/web/public/coffee/app/ide/editor/directives/aceEditor.coffee +++ b/services/web/public/coffee/app/ide/editor/directives/aceEditor.coffee @@ -27,6 +27,8 @@ define [ spellCheckLanguage: "=" cursorPosition: "=" annotations: "=" + text: "=" + readOnly: "=" } link: (scope, element, attrs) -> # Don't freak out if we're already in an apply callback @@ -59,7 +61,8 @@ define [ editor.on "changeSelection", () -> cursor = editor.getCursorPosition() scope.$apply () -> - scope.cursorPosition = cursor + if scope.cursorPosition? + scope.cursorPosition = cursor scope.$watch "theme", (value) -> editor.setTheme("ace/theme/#{value}") @@ -85,6 +88,13 @@ define [ if sharejs_doc? attachToAce(sharejs_doc) + scope.$watch "text", (text) -> + if text? + editor.setValue(text, -1) + + scope.$watch "readOnly", (value) -> + editor.setReadOnly !!value + attachToAce = (sharejs_doc) -> lines = sharejs_doc.getSnapshot().split("\n") editor.setSession(new EditSession(lines)) diff --git a/services/web/public/coffee/app/ide/online-users/OnlineUsersManager.coffee b/services/web/public/coffee/app/ide/online-users/OnlineUsersManager.coffee index 4bb3a4cafa..1ff4b0b3dd 100644 --- a/services/web/public/coffee/app/ide/online-users/OnlineUsersManager.coffee +++ b/services/web/public/coffee/app/ide/online-users/OnlineUsersManager.coffee @@ -29,7 +29,7 @@ define [ continue if !doc_id? @$scope.onlineUserCursorAnnotations[doc_id] ||= [] @$scope.onlineUserCursorAnnotations[doc_id].push { - text: client.name + label: client.name cursor: row: client.row column: client.column diff --git a/services/web/public/coffee/app/ide/track-changes/TrackChangesListController.coffee b/services/web/public/coffee/app/ide/track-changes/TrackChangesListController.coffee index e5a849a919..5823553624 100644 --- a/services/web/public/coffee/app/ide/track-changes/TrackChangesListController.coffee +++ b/services/web/public/coffee/app/ide/track-changes/TrackChangesListController.coffee @@ -7,7 +7,7 @@ define [ $scope.recalculateSelectedUpdates = () -> beforeSelection = true afterSelection = false - inSelection = false + $scope.trackChanges.selection.updates = [] for update in $scope.trackChanges.updates if update.selectedTo inSelection = true @@ -17,6 +17,9 @@ define [ update.inSelection = inSelection update.afterSelection = afterSelection + if inSelection + $scope.trackChanges.selection.updates.push update + if update.selectedFrom inSelection = false afterSelection = true @@ -31,8 +34,6 @@ define [ if update.hoverSelectedTo hoverSelectedTo = true - console.log "RECALCULATING HOVER", hoverSelectedFrom, hoverSelectedTo - if hoverSelectedFrom # We want to 'hover select' everything between hoverSelectedFrom and selectedTo inHoverSelection = false @@ -62,19 +63,19 @@ define [ ] App.controller "TrackChangesListItemController", ["$scope", ($scope) -> - $scope.$watch "update.selectedFrom", (selectedFrom) -> - if selectedFrom? - if selectedFrom - for update in $scope.trackChanges.updates - update.selectedFrom = false unless update == $scope.update - $scope.recalculateSelectedUpdates() + $scope.$watch "update.selectedFrom", (selectedFrom, oldSelectedFrom) -> + if selectedFrom + for update in $scope.trackChanges.updates + update.selectedFrom = false unless update == $scope.update + if selectedFrom != oldSelectedFrom + $scope.recalculateSelectedUpdates() - $scope.$watch "update.selectedTo", (selectedTo) -> - if selectedTo? - if selectedTo - for update in $scope.trackChanges.updates - update.selectedTo = false unless update == $scope.update - $scope.recalculateSelectedUpdates() + $scope.$watch "update.selectedTo", (selectedTo, oldSelectedTo) -> + if selectedTo + for update in $scope.trackChanges.updates + update.selectedTo = false unless update == $scope.update + if selectedTo != oldSelectedTo + $scope.recalculateSelectedUpdates() $scope.select = () -> $scope.update.selectedTo = true diff --git a/services/web/public/coffee/app/ide/track-changes/TrackChangesManager.coffee b/services/web/public/coffee/app/ide/track-changes/TrackChangesManager.coffee index 218e243132..1bf5962587 100644 --- a/services/web/public/coffee/app/ide/track-changes/TrackChangesManager.coffee +++ b/services/web/public/coffee/app/ide/track-changes/TrackChangesManager.coffee @@ -7,6 +7,17 @@ define [ updates: [] nextBeforeTimestamp: null atEnd: false + selection: { + updates: [] + doc: null + range: { + fromV: null + toV: null + start_ts: null + end_ts: null + } + } + diff: null } @$scope.toggleTrackChanges = () => @@ -18,6 +29,16 @@ define [ @$scope.$on "file-tree:initialized", () => @fetchNextBatchOfChanges() + @$scope.$watch "trackChanges.selection.updates", () => + @$scope.trackChanges.selection.range = @_calculateRangeFromSelection() + @reloadDiff() + + @$scope.$on "entity:selected", (event, entity) => + if (@$scope.ui.view == "track-changes") and (entity.type == "doc") + @$scope.trackChanges.selection.doc = entity + @$scope.trackChanges.selection.range = @_calculateRangeFromSelection() + @reloadDiff() + BATCH_SIZE: 4 fetchNextBatchOfChanges: () -> url = "/project/#{@ide.project_id}/updates?min_count=#{@BATCH_SIZE}" @@ -31,6 +52,90 @@ define [ if !data.nextBeforeTimestamp? @$scope.trackChanges.atEnd = true + reloadDiff: () -> + console.log "Checking if diff has changed" + + diff = @$scope.trackChanges.diff + {updates, doc} = @$scope.trackChanges.selection + {fromV, toV} = @$scope.trackChanges.selection.range + + return if !doc? + + return if diff? and + diff.doc == doc and + diff.fromV == fromV and + diff.toV == toV + + console.log "Loading diff", fromV, toV, doc?.id + + @$scope.trackChanges.diff = diff = { + fromV: fromV + toV: toV + doc: doc + loading: true + error: false + } + + url = "/project/#{@$scope.project_id}/doc/#{diff.doc.id}/diff" + if diff.fromV? and diff.toV? + url += "?from=#{diff.fromV}&to=#{diff.toV}" + + @ide.$http + .get(url) + .success (data) => + diff.loading = false + {text, annotations} = @_parseDiff(data) + diff.text = text + diff.annotations = annotations + .error () -> + diff.loading = false + diff.error = true + + _parseDiff: (diff) -> + row = 0 + column = 0 + annotations = [] + text = "" + for entry, i in diff.diff or [] + content = entry.u or entry.i or entry.d + content ||= "" + text += content + lines = content.split("\n") + startRow = row + startColumn = column + if lines.length > 1 + endRow = startRow + lines.length - 1 + endColumn = lines[lines.length - 1].length + else + endRow = startRow + endColumn = startColumn + lines[0].length + row = endRow + column = endColumn + + range = { + start: + row: startRow + column: startColumn + end: + row: endRow + column: endColumn + } + + if entry.i? + annotations.push { + label: entry.meta.user.first_name + highlight: range + hue: @ide.onlineUsersManager.getHueForUserId(entry.meta.user.id) + } + else if entry.d? + annotations.push { + label: entry.meta.user.first_name + strikeThrough: range + hue: @ide.onlineUsersManager.getHueForUserId(entry.meta.user.id) + } + + return {text, annotations} + _loadUpdates: (updates = []) -> previousUpdate = @$scope.trackChanges.updates[@$scope.trackChanges.updates.length - 1] @@ -52,3 +157,28 @@ define [ @$scope.trackChanges.updates = @$scope.trackChanges.updates.concat(updates) + + _calculateRangeFromSelection: () -> + fromV = toV = start_ts = end_ts = null + + selected_doc_id = @$scope.trackChanges.selection.doc?.id + + for update in @$scope.trackChanges.selection.updates or [] + console.log "Checking update", update + for doc_id, doc of update.docs + console.log "Checking doc", doc_id, selected_doc_id, doc.fromV, doc.toV + if doc_id == selected_doc_id + console.log "Doc matches" + if fromV? and toV? + fromV = Math.min(fromV, doc.fromV) + toV = Math.max(toV, doc.toV) + start_ts = Math.min(start_ts, update.meta.start_ts) + end_ts = Math.max(end_ts, update.meta.end_ts) + else + fromV = doc.fromV + toV = doc.toV + start_ts = update.meta.start_ts + end_ts = update.meta.end_ts + break + + return {fromV, toV, start_ts, end_ts} diff --git a/services/web/public/stylesheets/app/editor.less b/services/web/public/stylesheets/app/editor.less index 20827b8724..aeaefac6da 100644 --- a/services/web/public/stylesheets/app/editor.less +++ b/services/web/public/stylesheets/app/editor.less @@ -29,7 +29,7 @@ .full-size; } -.loading { +.loading-panel { .full-size; padding-top: 10rem; font-family: @font-family-serif; @@ -37,6 +37,16 @@ background-color: #fafafa; } +.error-panel { + .full-size; + padding: @line-height-computed; + background-color: #fafafa; + .alert { + max-width: 400px; + margin: auto; + } +} + // The internal components of the aceEditor directive .ace-editor-wrapper { .full-size; @@ -58,8 +68,6 @@ background-position: bottom left; } .remote-cursor { - position: absolute; - z-index: 2; border-left: 2px solid transparent; .nubbin { height: 5px; @@ -76,6 +84,10 @@ color: white; font-weight: 700; } + .annotation { + position: absolute; + z-index: 2; + } } .ui-layout-resizer { diff --git a/services/web/public/stylesheets/app/editor/track-changes.less b/services/web/public/stylesheets/app/editor/track-changes.less index c917170c3e..b0f7616540 100644 --- a/services/web/public/stylesheets/app/editor/track-changes.less +++ b/services/web/public/stylesheets/app/editor/track-changes.less @@ -121,6 +121,9 @@ // .border-radius(3px); // } + .diff { + margin-right: @changesListWidth; + } aside.change-list { border-left: 1px solid @toolbar-border-color;