diff --git a/services/web/app/views/project/editor/header.pug b/services/web/app/views/project/editor/header.pug index 2817efa814..fe3732de97 100644 --- a/services/web/app/views/project/editor/header.pug +++ b/services/web/app/views/project/editor/header.pug @@ -93,7 +93,7 @@ header.toolbar.toolbar-header.toolbar-with-labels( ) i.review-icon p.toolbar-label - | Review + | #{translate("review")} span(style="vertical-align: 20%; margin-left: 4px; padding: 2px 4px;").beta-feature-badge a.btn.btn-full-height( href, diff --git a/services/web/app/views/project/editor/review-panel.pug b/services/web/app/views/project/editor/review-panel.pug index 93ff664eed..fe5ada3596 100644 --- a/services/web/app/views/project/editor/review-panel.pug +++ b/services/web/app/views/project/editor/review-panel.pug @@ -4,9 +4,7 @@ ng-if="editor.wantTrackChanges" ng-click="toggleReviewPanel();" ng-class="{ 'rp-track-changes-indicator-on-dark' : darkTheme }" - ) Track changes is - strong on - + ) !{translate("track_changes_is_on")} .review-panel-toolbar resolved-comments-dropdown( class="rp-flex-block" @@ -21,10 +19,8 @@ permissions="permissions" ) span.review-panel-toolbar-label(ng-if="permissions.write") - span(ng-click="toggleTrackChanges(true)", ng-if="editor.wantTrackChanges === false") Track Changes is - strong off - span(ng-click="toggleTrackChanges(false)", ng-if="editor.wantTrackChanges === true") Track Changes is - strong on + span(ng-click="toggleTrackChanges(true)", ng-if="editor.wantTrackChanges === false") !{translate("track_changes_is_off")} + span(ng-click="toggleTrackChanges(false)", ng-if="editor.wantTrackChanges === true") !{translate("track_changes_is_on")} review-panel-toggle( ng-if="editor.wantTrackChanges == editor.trackChanges" ng-model="editor.wantTrackChanges" @@ -33,10 +29,8 @@ on-disabled-click="openTrackChangesUpgradeModal" ) span.review-panel-toolbar-label.review-panel-toolbar-label-disabled(ng-if="!permissions.write") - span(ng-if="editor.wantTrackChanges === false") Track Changes is - strong off - span(ng-if="editor.wantTrackChanges === true") Track Changes is - strong on + span(ng-if="editor.wantTrackChanges === false") !{translate("track_changes_is_off")} + span(ng-if="editor.wantTrackChanges === true") !{translate("track_changes_is_on")} span.review-panel-toolbar-spinner(ng-if="editor.wantTrackChanges != editor.trackChanges") i.fa.fa-spin.fa-spinner @@ -47,6 +41,7 @@ .rp-entry-list-inner .rp-entry-wrapper( ng-repeat="(entry_id, entry) in reviewPanel.entries[editor.open_doc_id]" + ng-if="entry.visible" ) div(ng-if="entry.type === 'insert' || entry.type === 'delete'") change-entry( @@ -92,33 +87,45 @@ ng-if="!reviewPanel.overview.loading" ) .rp-overview-file-header( - ng-if="reviewPanel.entries[doc.doc.id] | notEmpty" + ng-if="(reviewPanel.entries[doc.doc.id] != null) && (reviewPanel.entries[doc.doc.id] | notEmpty)" + ng-click="reviewPanel.overview.docsCollapsedState[doc.doc.id] = ! reviewPanel.overview.docsCollapsedState[doc.doc.id]" ) + span.rp-overview-file-header-collapse( + ng-class="{ 'rp-overview-file-header-collapse-on': reviewPanel.overview.docsCollapsedState[doc.doc.id] }" + ) + i.fa.fa-angle-down | {{ doc.path }} - .rp-entry-wrapper( - ng-repeat="(entry_id, entry) in reviewPanel.entries[doc.doc.id] | orderOverviewEntries" - ng-if="!(entry.type === 'comment' && reviewPanel.commentThreads[entry.thread_id].resolved === true)" - ) - div(ng-if="entry.type === 'insert' || entry.type === 'delete'") - change-entry( - entry="entry" - user="users[entry.metadata.user_id]" - on-indicator-click="toggleReviewPanel();" - ng-click="gotoEntry(doc.doc.id, entry)" - permissions="permissions" - ) + span.rp-overview-file-num-entries( + ng-show="reviewPanel.overview.docsCollapsedState[doc.doc.id]" + )  ({{ reviewPanel.entries[doc.doc.id] | numKeys }}) - div(ng-if="entry.type === 'comment'") - comment-entry( - entry="entry" - threads="reviewPanel.commentThreads" - on-reply="submitReply(entry, entry_id);" - on-save-edit="saveEdit(entry.thread_id, comment)" - on-delete="deleteComment(entry.thread_id, comment)" - on-indicator-click="toggleReviewPanel();" - ng-click="gotoEntry(doc.doc.id, entry)" - permissions="permissions" - ) + .rp-overview-file-entries( + review-panel-collapse-height="reviewPanel.overview.docsCollapsedState[doc.doc.id]" + ) + .rp-entry-wrapper( + ng-repeat="(entry_id, entry) in reviewPanel.entries[doc.doc.id] | orderOverviewEntries" + ng-if="!(entry.type === 'comment' && reviewPanel.commentThreads[entry.thread_id].resolved === true)" + ) + div(ng-if="entry.type === 'insert' || entry.type === 'delete'") + change-entry( + entry="entry" + user="users[entry.metadata.user_id]" + on-indicator-click="toggleReviewPanel();" + ng-click="gotoEntry(doc.doc.id, entry)" + permissions="permissions" + ) + + div(ng-if="entry.type === 'comment'") + comment-entry( + entry="entry" + threads="reviewPanel.commentThreads" + on-reply="submitReply(entry, entry_id);" + on-save-edit="saveEdit(entry.thread_id, comment)" + on-delete="deleteComment(entry.thread_id, comment)" + on-indicator-click="toggleReviewPanel();" + ng-click="gotoEntry(doc.doc.id, entry)" + permissions="permissions" + ) .rp-nav a.rp-nav-item( @@ -127,14 +134,14 @@ ng-class="{ 'rp-nav-item-active' : reviewPanel.subView === SubViews.CUR_FILE }" ) i.fa.fa-file-text-o - span.rp-nav-label Current file + span.rp-nav-label #{translate("current_file")} a.rp-nav-item( href ng-click="setSubView(SubViews.OVERVIEW);" ng-class="{ 'rp-nav-item-active' : reviewPanel.subView === SubViews.OVERVIEW }" ) i.fa.fa-list - span.rp-nav-label Overview + span.rp-nav-label #{translate("overview")} script(type='text/ng-template', id='changeEntryTemplate') @@ -158,30 +165,30 @@ script(type='text/ng-template', id='changeEntryTemplate') i.rp-icon-delete(ng-switch-when="delete") .rp-entry-details .rp-entry-description(ng-switch="entry.type") - span(ng-switch-when="insert") Added  + span(ng-switch-when="insert") #{translate("tracked_change_added")}  ins.rp-content-highlight {{ entry.content | limitTo:(isCollapsed ? contentLimit : entry.content.length) }} a.rp-collapse-toggle( href ng-if="needsCollapsing" ng-click="toggleCollapse();" - ) {{ isCollapsed ? '... (show all)' : ' (show less)' }} - span(ng-switch-when="delete") Deleted  + ) {{ isCollapsed ? '... (#{translate("show_all")})' : ' (#{translate("show_less")})' }} + span(ng-switch-when="delete") #{translate("tracked_change_deleted")}  del.rp-content-highlight {{ entry.content | limitTo:(isCollapsed ? contentLimit : entry.content.length) }} a.rp-collapse-toggle( href ng-if="needsCollapsing" ng-click="toggleCollapse();" - ) {{ isCollapsed ? '... (show all)' : ' (show less)' }} + ) {{ isCollapsed ? '... (#{translate("show_all")})' : ' (#{translate("show_less")})' }} .rp-entry-metadata | {{ entry.metadata.ts | date : 'MMM d, y h:mm a' }} •  span.rp-entry-user(style="color: hsl({{ user.hue }}, 70%, 40%);") {{ user.name }} .rp-entry-actions(ng-if="permissions.write") a.rp-entry-button(href, ng-click="onReject();") i.fa.fa-times - |  Reject + |  #{translate("reject")} a.rp-entry-button(href, ng-click="onAccept();") i.fa.fa-check - |  Accept + |  #{translate("accept")} script(type='text/ng-template', id='commentEntryTemplate') .rp-comment-wrapper( @@ -198,7 +205,7 @@ script(type='text/ng-template', id='commentEntryTemplate') ) .rp-loading(ng-if="!threads[entry.thread_id].submitting && (!threads[entry.thread_id] || threads[entry.thread_id].messages.length == 0)") - | No comments + | #{translate("no_comments")} .rp-comment-loaded .rp-comment( ng-repeat="comment in threads[entry.thread_id].messages track by comment.id" @@ -222,16 +229,16 @@ script(type='text/ng-template', id='commentEntryTemplate') span(ng-if="!comment.deleting") {{ comment.timestamp | date : 'MMM d, y h:mm a' }} span.rp-comment-actions(ng-if="comment.user.isSelf && !comment.deleting") |  •  - a(href, ng-click="startEditing(comment)") Edit + a(href, ng-click="startEditing(comment)") #{translate("edit")} span(ng-if="threads[entry.thread_id].messages.length > 1") |  •  - a(href, ng-click="confirmDelete(comment)") Delete + a(href, ng-click="confirmDelete(comment)") #{translate("delete")} span.rp-confim-delete(ng-if="comment.user.isSelf && comment.deleting") - | Are you sure? + | #{translate("are_you_sure")} | •  - a(href, ng-click="doDelete(comment)") Delete + a(href, ng-click="doDelete(comment)") #{translate("delete")} |  •  - a(href, ng-click="cancelDelete(comment)") Cancel + a(href, ng-click="cancelDelete(comment)") #{translate("cancel")} .rp-loading(ng-if="threads[entry.thread_id].submitting") i.fa.fa-spinner.fa-spin @@ -241,7 +248,7 @@ script(type='text/ng-template', id='commentEntryTemplate') ng-model="entry.replyContent" ng-keypress="handleCommentReplyKeyPress($event);" stop-propagation="click" - placeholder="{{ 'Hit \"Enter\" to reply' + (entry.resolved ? ' and re-open' : '') }}" + placeholder=translate("hit_enter_to_reply") ) .rp-entry-actions button.rp-entry-button( @@ -249,20 +256,20 @@ script(type='text/ng-template', id='commentEntryTemplate') ng-if="permissions.comment && permissions.write" ) i.fa.fa-inbox - |  Resolve + |  #{translate("resolve")} button.rp-entry-button( ng-click="onReply();" ng-if="permissions.comment" ng-disabled="!entry.replyContent.length" ) i.fa.fa-reply - |  Reply + |  #{translate("reply")} script(type='text/ng-template', id='resolvedCommentEntryTemplate') .rp-resolved-comment div .rp-resolved-comment-context - | Quoted text on  + | #{translate("quoted_text_in")}  span.rp-resolved-comment-context-file {{ thread.docName }} p.rp-resolved-comment-context-quote span {{ thread.content | limitTo:(isCollapsed ? contentLimit : thread.content.length)}} @@ -270,7 +277,7 @@ script(type='text/ng-template', id='resolvedCommentEntryTemplate') href ng-if="needsCollapsing" ng-click="toggleCollapse();" - )  {{ isCollapsed ? '... (show all)' : ' (show less)' }} + )  {{ isCollapsed ? '... (#{translate("show_all")})' : ' (#{translate("show_less")})' }} .rp-comment( ng-repeat="comment in thread.messages track by comment.id" ) @@ -287,7 +294,7 @@ script(type='text/ng-template', id='resolvedCommentEntryTemplate') span.rp-entry-user( style="color: hsl({{ thread.resolved_by_user.hue }}, 70%, 40%);" ) {{ thread.resolved_by_user.name }}:  - | Marked as resolved. + | #{translate("mark_as_resolved")}. .rp-entry-metadata | {{ thread.resolved_at | date : 'MMM d, y h:mm a' }} @@ -296,12 +303,12 @@ script(type='text/ng-template', id='resolvedCommentEntryTemplate') href ng-click="onUnresolve({ 'threadId': thread.threadId });" ) - |  Re-open + |  #{translate("reopen")} a.rp-entry-button( href ng-click="onDelete({ 'entryId': thread.entryId, 'docId': thread.docId, 'threadId': thread.threadId });" ) - |  Delete + |  #{translate("delete")} script(type='text/ng-template', id='addCommentEntryTemplate') @@ -310,7 +317,7 @@ script(type='text/ng-template', id='addCommentEntryTemplate') .rp-entry-indicator( ng-if="!commentState.adding" ng-click="startNewComment(); onIndicatorClick();" - tooltip="Add a comment" + tooltip=translate("add_comment") tooltip-placement="{{ layoutToLeft ? 'left' : 'right' }}" tooltip-append-to-body="true" ) @@ -324,14 +331,14 @@ script(type='text/ng-template', id='addCommentEntryTemplate') ng-click="startNewComment();" ) i.fa.fa-comment - |  Add comment + |  #{translate("add_comment")} div(ng-if="state.isAdding") .rp-new-comment textarea.rp-comment-input( expandable-text-area ng-model="state.content" ng-keypress="handleCommentKeyPress($event);" - placeholder="Add your comment here" + placeholder=translate("add_your_comment_here") focus-on="comment:new:open" ng-blur="submitNewComment()" ) @@ -340,13 +347,13 @@ script(type='text/ng-template', id='addCommentEntryTemplate') ng-click="cancelNewComment();" ) i.fa.fa-times - |  Cancel + |  #{translate("cancel")} button.rp-entry-button( ng-click="submitNewComment()" ng-disabled="!state.content.length" ) i.fa.fa-comment - |  Comment + |  #{translate("comment")} script(type='text/ng-template', id='resolvedCommentsDropdownTemplate') .resolved-comments @@ -357,7 +364,7 @@ script(type='text/ng-template', id='resolvedCommentsDropdownTemplate') a.resolved-comments-toggle( href ng-click="toggleOpenState();" - tooltip="Resolved Comments" + tooltip=translate("resolved_comments") tooltip-placement="bottom" tooltip-append-to-body="true" ) @@ -378,7 +385,7 @@ script(type='text/ng-template', id='resolvedCommentsDropdownTemplate') permissions="permissions" ) .rp-loading(ng-if="!resolvedComments.length") - | No resolved threads. + | #{translate("no_resolved_threads")}. script(type="text/ng-template", id="trackChangesUpgradeModalTemplate") .modal-header @@ -387,14 +394,14 @@ script(type="text/ng-template", id="trackChangesUpgradeModalTemplate") data-dismiss="modal" ng-click="cancel()" ) × - h3 Upgrade to Track Changes + h3 #{translate("upgrade_to_track_changes")} .modal-body .teaser-video-container video.teaser-video(autoplay, loop) source(src="/img/teasers/track-changes/teaser-track-changes.mp4", type="video/mp4") img(src="/img/teasers/track-changes/teaser-track-changes.gif") - h4.teaser-title See changes in your documents, live + h4.teaser-title #{translate("see_changes_in_your_documents_live")} p.small(ng-show="startedFreeTrial") | #{translate("refresh_page_after_starting_free_trial")} @@ -404,15 +411,15 @@ script(type="text/ng-template", id="trackChangesUpgradeModalTemplate") ul.list-unstyled li i.fa.fa-check   - | Track any change, in real-time + | #{translate("track_any_change_in_real_time")} li i.fa.fa-check   - | Review your peers' work + | #{translate("review_your_peers_work")} li i.fa.fa-check   - | Accept or reject each change individually + | #{translate("accept_or_reject_each_changes_individually")} .row.text-center(ng-controller="FreeTrialModalController") @@ -420,8 +427,8 @@ script(type="text/ng-template", id="trackChangesUpgradeModalTemplate") href ng-click="startFreeTrial('real-time-track-changes')" ng-show="project.owner._id == user.id" - ) Try it for free - p(ng-show="project.owner._id != user.id"): strong Please ask the project owner to upgrade to use track changes + ) #{translate("try_it_for_free")} + p(ng-show="project.owner._id != user.id"): strong #{translate("please_ask_the_project_owner_to_upgrade_to_track_changes")} .modal-footer() button.btn.btn-default( diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor.coffee index 9205cb7b87..faa1159e00 100644 --- a/services/web/public/coffee/ide/editor/directives/aceEditor.coffee +++ b/services/web/public/coffee/ide/editor/directives/aceEditor.coffee @@ -184,6 +184,28 @@ define [ session = editor.getSession() session.setScrollTop(session.getScrollTop() + newScreenPosition - previousScreenPosition) + scope.$on "#{scope.name}:set-scroll-size", (e, size) -> + # Make sure that the editor has enough scroll margin above and below + # to scroll the review panel with the given size + marginTop = size.overflowTop + maxHeight = editor.renderer.layerConfig.maxHeight + marginBottom = Math.max(size.height - maxHeight, 0) + setScrollMargins(marginTop, marginBottom) + + setScrollMargins = (marginTop, marginBottom) -> + marginChanged = false + if editor.renderer.scrollMargin.top != marginTop + editor.renderer.scrollMargin.top = marginTop + marginChanged = true + if editor.renderer.scrollMargin.bottom != marginBottom + editor.renderer.scrollMargin.bottom = marginBottom + marginChanged = true + if marginChanged + editor.renderer.updateFull() + + resetScrollMargins = () -> + setScrollMargins(0,0) + scope.$watch "theme", (value) -> editor.setTheme("ace/theme/#{value}") @@ -308,6 +330,8 @@ define [ sharejs_doc.attachToAce(editor) editor.initing = false + resetScrollMargins() + # need to set annotations after attaching because attaching # deletes and then inserts document content session.setAnnotations scope.annotations diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor/track-changes/TrackChangesManager.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor/track-changes/TrackChangesManager.coffee index b75cb07dba..38b3e5678c 100644 --- a/services/web/public/coffee/ide/editor/directives/aceEditor/track-changes/TrackChangesManager.coffee +++ b/services/web/public/coffee/ide/editor/directives/aceEditor/track-changes/TrackChangesManager.coffee @@ -60,6 +60,18 @@ define [ onChangeSession = (e) => @clearAnnotations() @redrawAnnotations() + @editor.session.on "changeScrollTop", onChangeScroll + + _scrollTimeout = null + onChangeScroll = () => + if _scrollTimeout? + return + else + _scrollTimeout = setTimeout () => + @recalculateVisibleEntries() + @$scope.$apply() + _scrollTimeout = null + , 200 bindToAce = () => @editor.on "changeSelection", onChangeSelection @@ -282,6 +294,7 @@ define [ recalculateReviewEntriesScreenPositions: () -> session = @editor.getSession() renderer = @editor.renderer + {firstRow, lastRow} = renderer.layerConfig entries = @_getCurrentDocEntries() for entry_id, entry of entries or {} doc_position = @_shareJsOffsetToAcePosition(entry.offset) @@ -290,9 +303,24 @@ define [ entry.screenPos ?= {} entry.screenPos.y = y entry.docPos = doc_position - + @recalculateVisibleEntries() @$scope.$apply() - + + recalculateVisibleEntries: () -> + OFFSCREEN_ROWS = 20 + CULL_AFTER = 100 # With less than this number of entries, don't bother culling to avoid little UI jumps when scrolling. + {firstRow, lastRow} = @editor.renderer.layerConfig + entries = @_getCurrentDocEntries() or {} + entriesLength = Object.keys(entries).length + changed = false + for entry_id, entry of entries + old = entry.visible + entry.visible = (entriesLength < CULL_AFTER) or (firstRow - OFFSCREEN_ROWS <= entry.docPos.row <= lastRow + OFFSCREEN_ROWS) + if (entry.visible != old) + changed = true + if changed + @$scope.$emit "editor:track-changes:visibility_changed" + _getCurrentDocEntries: () -> doc_id = @$scope.docId entries = @$scope.reviewPanel.entries[doc_id] ?= {} diff --git a/services/web/public/coffee/ide/review-panel/ReviewPanelManager.coffee b/services/web/public/coffee/ide/review-panel/ReviewPanelManager.coffee index 1565d6db73..9b622ffa05 100644 --- a/services/web/public/coffee/ide/review-panel/ReviewPanelManager.coffee +++ b/services/web/public/coffee/ide/review-panel/ReviewPanelManager.coffee @@ -8,6 +8,8 @@ define [ "ide/review-panel/directives/addCommentEntry" "ide/review-panel/directives/resolvedCommentEntry" "ide/review-panel/directives/resolvedCommentsDropdown" + "ide/review-panel/directives/reviewPanelCollapseHeight" "ide/review-panel/filters/notEmpty" + "ide/review-panel/filters/numKeys" "ide/review-panel/filters/orderOverviewEntries" ], () -> \ No newline at end of file diff --git a/services/web/public/coffee/ide/review-panel/controllers/ReviewPanelController.coffee b/services/web/public/coffee/ide/review-panel/controllers/ReviewPanelController.coffee index 88a3233934..5067799571 100644 --- a/services/web/public/coffee/ide/review-panel/controllers/ReviewPanelController.coffee +++ b/services/web/public/coffee/ide/review-panel/controllers/ReviewPanelController.coffee @@ -4,7 +4,7 @@ define [ "ide/colors/ColorManager" "ide/review-panel/RangesTracker" ], (App, EventEmitter, ColorManager, RangesTracker) -> - App.controller "ReviewPanelController", ($scope, $element, ide, $timeout, $http, $modal, event_tracking) -> + App.controller "ReviewPanelController", ($scope, $element, ide, $timeout, $http, $modal, event_tracking, localStorage) -> $reviewPanelEl = $element.find "#review-panel" $scope.SubViews = @@ -19,6 +19,7 @@ define [ openSubView: $scope.SubViews.CUR_FILE overview: loading: false + docsCollapsedState: JSON.parse(localStorage("docs_collapsed_state:#{$scope.project_id}")) or {} dropdown: loading: false commentThreads: {} @@ -27,6 +28,14 @@ define [ rendererData: {} loadingThreads: false + window.addEventListener "beforeunload", () -> + collapsedStates = {} + for doc, state of $scope.reviewPanel.overview.docsCollapsedState + if state + collapsedStates[doc] = state + valToStore = if Object.keys(collapsedStates).length > 0 then JSON.stringify(collapsedStates) else null + localStorage("docs_collapsed_state:#{$scope.project_id}", valToStore) + $scope.$on "layout:pdf:linked", (event, state) -> $scope.reviewPanel.layoutToLeft = (state.east?.size < 220 || state.east?.initClosed) $scope.$broadcast "review-panel:layout" @@ -39,6 +48,9 @@ define [ $timeout () -> $scope.$broadcast "review-panel:layout" + $scope.$on "review-panel:sizes", (e, sizes) -> + $scope.$broadcast "editor:set-scroll-size", sizes + $scope.$watch "ui.pdfLayout", (layout) -> $scope.reviewPanel.layoutToLeft = (layout == "flat") @@ -171,6 +183,8 @@ define [ $http.get "/project/#{$scope.project_id}/ranges" .success (docs) -> for doc in docs + if !$scope.reviewPanel.overview.docsCollapsedState[doc.id]? + $scope.reviewPanel.overview.docsCollapsedState[doc.id] = false if doc.id != $scope.editor.open_doc_id # this is kept up to date in real-time, don't overwrite rangesTracker = getChangeTracker(doc.id) rangesTracker.comments = doc.ranges?.comments or [] @@ -263,7 +277,11 @@ define [ updateEntries(doc_id) $scope.$broadcast "review-panel:recalculate-screen-positions" $scope.$broadcast "review-panel:layout" - + + $scope.$on "editor:track-changes:visibility_changed", () -> + $timeout () -> + $scope.$broadcast "review-panel:layout", false + $scope.$on "editor:focus:changed", (e, selection_offset_start, selection_offset_end, selection) -> doc_id = $scope.editor.open_doc_id entries = getDocEntries(doc_id) diff --git a/services/web/public/coffee/ide/review-panel/directives/reviewPanelCollapseHeight.coffee b/services/web/public/coffee/ide/review-panel/directives/reviewPanelCollapseHeight.coffee new file mode 100644 index 0000000000..aef8a91a3b --- /dev/null +++ b/services/web/public/coffee/ide/review-panel/directives/reviewPanelCollapseHeight.coffee @@ -0,0 +1,18 @@ +define [ + "base" +], (App) -> + App.directive "reviewPanelCollapseHeight", ($parse) -> + return { + restrict: "A", + link: (scope, element, attrs) -> + scope.$watch (() -> $parse(attrs.reviewPanelCollapseHeight)(scope)), (shouldCollapse) -> + neededHeight = element.prop("scrollHeight") + if neededHeight > 0 + if shouldCollapse + element.animate { height: 0 }, 150 + else + element.animate { height: neededHeight }, 150 + else + if shouldCollapse + element.height 0 + } \ No newline at end of file diff --git a/services/web/public/coffee/ide/review-panel/directives/reviewPanelSorted.coffee b/services/web/public/coffee/ide/review-panel/directives/reviewPanelSorted.coffee index 4028406713..b76f891e7f 100644 --- a/services/web/public/coffee/ide/review-panel/directives/reviewPanelSorted.coffee +++ b/services/web/public/coffee/ide/review-panel/directives/reviewPanelSorted.coffee @@ -15,9 +15,11 @@ define [ if scope.ui.reviewPanelOpen PADDING = 8 TOOLBAR_HEIGHT = 38 + OVERVIEW_TOGGLE_HEIGHT = 57 else PADDING = 4 TOOLBAR_HEIGHT = 4 + OVERVIEW_TOGGLE_HEIGHT = 0 entries = [] for el in element.find(".rp-entry-wrapper") @@ -31,6 +33,7 @@ define [ entry.$layout_el = entry.$box_el else entry.$layout_el = entry.$indicator_el + entry.height = entry.$layout_el.height() # Do all of our DOM reads first for perfomance, see http://wilsonpage.co.uk/preventing-layout-thrashing/ entries.push entry entries.sort (a,b) -> a.scope.entry.offset - b.scope.entry.offset @@ -50,19 +53,6 @@ define [ sl_console.log "focused_entry_index", focused_entry_index - # As we go backwards, we run the risk of pushing things off the top of the editor. - # If we go through the entries before and assume they are as pushed together as they - # could be, we can work out the 'ceiling' that each one can't go through. I.e. the first - # on can't go beyond the toolbar height, the next one can't go beyond the bottom of the first - # one at this minimum height, etc. - heights = (entry.$layout_el.height() for entry in entries_before) - previousMinTop = TOOLBAR_HEIGHT - min_tops = [] - for height in heights - min_tops.push previousMinTop - previousMinTop += PADDING + height - min_tops.reverse() - positionLayoutEl = ($callout_el, original_top, top) -> if original_top <= top $callout_el.removeClass("rp-entry-callout-inverted") @@ -72,7 +62,7 @@ define [ $callout_el.css(top: top + line_height, height: original_top - top) # Put the focused entry as close to where it wants to be as possible - focused_entry_top = Math.max(previousMinTop, focused_entry.scope.entry.screenPos.y) + focused_entry_top = Math.max(focused_entry.scope.entry.screenPos.y, TOOLBAR_HEIGHT) focused_entry.$box_el.css(top: focused_entry_top) focused_entry.$indicator_el.css(top: focused_entry_top) positionLayoutEl(focused_entry.$callout_el, focused_entry.scope.entry.screenPos.y, focused_entry_top) @@ -80,27 +70,39 @@ define [ previousBottom = focused_entry_top + focused_entry.$layout_el.height() for entry in entries_after original_top = entry.scope.entry.screenPos.y - height = entry.$layout_el.height() + height = entry.height top = Math.max(original_top, previousBottom + PADDING) previousBottom = top + height entry.$box_el.css(top: top) entry.$indicator_el.css(top: top) positionLayoutEl(entry.$callout_el, original_top, top) sl_console.log "ENTRY", {entry: entry.scope.entry, top} + + lastBottom = previousBottom previousTop = focused_entry_top entries_before.reverse() # Work through backwards, starting with the one just above for entry, i in entries_before original_top = entry.scope.entry.screenPos.y - height = entry.$layout_el.height() + height = entry.height original_bottom = original_top + height bottom = Math.min(original_bottom, previousTop - PADDING) - top = Math.max(bottom - height, min_tops[i]) + top = bottom - height previousTop = top entry.$box_el.css(top: top) entry.$indicator_el.css(top: top) positionLayoutEl(entry.$callout_el, original_top, top) sl_console.log "ENTRY", {entry: entry.scope.entry, top} + + lastTop = top + if lastTop < TOOLBAR_HEIGHT + overflowTop = -lastTop + TOOLBAR_HEIGHT + else + overflowTop = 0 + scope.$emit "review-panel:sizes", { + overflowTop: overflowTop, + height: previousBottom + OVERVIEW_TOGGLE_HEIGHT + } scope.$applyAsync () -> layout() diff --git a/services/web/public/coffee/ide/review-panel/filters/numKeys.coffee b/services/web/public/coffee/ide/review-panel/filters/numKeys.coffee new file mode 100644 index 0000000000..c1822d02c9 --- /dev/null +++ b/services/web/public/coffee/ide/review-panel/filters/numKeys.coffee @@ -0,0 +1,9 @@ +define [ + "base" +], (App) -> + app.filter "numKeys", () -> + (object) -> + if object? + return Object.keys(object).length + else + return 0 diff --git a/services/web/public/img/review-icon-sprite@2x.png b/services/web/public/img/review-icon-sprite@2x.png new file mode 100644 index 0000000000..7a76326d63 Binary files /dev/null and b/services/web/public/img/review-icon-sprite@2x.png differ diff --git a/services/web/public/stylesheets/app/editor/review-panel.less b/services/web/public/stylesheets/app/editor/review-panel.less index 1d1900c90c..71cff511d4 100644 --- a/services/web/public/stylesheets/app/editor/review-panel.less +++ b/services/web/public/stylesheets/app/editor/review-panel.less @@ -29,6 +29,8 @@ @rp-toolbar-height : 32px; +@rp-entry-animation-speed: 0.3s; + .rp-button() { @@ -49,11 +51,11 @@ } &[disabled] { - opacity: 0.5; + background-color: tint(@rp-highlight-blue, 50%); &:hover, &:focus { - background-color: @rp-highlight-blue; + background-color: tint(@rp-highlight-blue, 50%); } } } @@ -158,6 +160,7 @@ position: absolute; top: 0; bottom: 0; + padding-bottom: 52px; } .rp-state-overview & { @@ -183,7 +186,7 @@ border-radius: 3px; color: #FFF; cursor: pointer; - transition: top 0.3s, left 0.1s, right 0.1s; + transition: top @rp-entry-animation-speed, left 0.1s, right 0.1s; .no-animate & { transition: none; } @@ -279,7 +282,7 @@ border-left: solid @rp-entry-ribbon-width transparent; border-radius: 3px; background-color: #FFF; - transition: top 0.3s, left 0.1s, right 0.1s; + transition: top @rp-entry-animation-speed, left 0.1s, right 0.1s; .no-animate & { transition: none; } @@ -461,6 +464,7 @@ margin-top: 3px; overflow-x: hidden; min-height: 3em; + max-height: 400px; } .rp-icon-delete { @@ -496,6 +500,7 @@ } .rp-entry-callout { + transition: top @rp-entry-animation-speed, height @rp-entry-animation-speed; .rp-state-current-file & { position: absolute; border-top: 1px solid grey; @@ -567,8 +572,28 @@ margin-top: 10px; font-weight: @rp-semibold-weight; text-align: center; + cursor: pointer; } + .rp-overview-file-num-entries { + font-weight: normal; + font-size: 0.9em; + } + .rp-overview-file-header-collapse { + display: inline-block; + float: left; + transform: rotateZ(0deg); + transition: transform 0.15s ease + } + + .rp-overview-file-header-collapse-on { + transform: rotateZ(-90deg); + } + + .rp-overview-file-entries { + overflow: hidden; + } + .rp-comment-wrapper { transition: .35s opacity ease-out .2s; @@ -762,6 +787,10 @@ .toolbar .btn-full-height:active & { background-position-y: -60px; } + + @media (min-device-pixel-ratio: 1.5), (min-resolution: 144dpi) { + background-image: url('/img/review-icon-sprite@2x.png'); + } } .resolved-comments-toggle {