Merge pull request #796 from sharelatex/pr-history-labels-ui

History labels UI
This commit is contained in:
James Allen
2018-08-09 10:50:28 +01:00
committed by GitHub
16 changed files with 385 additions and 33 deletions

View File

@@ -67,3 +67,52 @@ script(type="text/ng-template", id="historyRestoreDiffModalTemplate")
)
span(ng-show="!state.inflight") #{translate("restore")}
span(ng-show="state.inflight") #{translate("restoring")} ...
script(type="text/ng-template", id="historyLabelTpl")
.history-entry-label(
ng-class="{ 'history-entry-label-own' : $ctrl.isOwnedByCurrentUser }"
)
span.history-entry-label-comment(
tooltip-append-to-body="true"
tooltip-template="'historyLabelTooltipTpl'"
tooltip-placement="left"
)
i.fa.fa-tag
|  {{ $ctrl.labelText }}
button.history-entry-label-delete-btn(
ng-if="$ctrl.isOwnedByCurrentUser"
stop-propagation="click"
ng-click="$ctrl.onLabelDelete()"
) ×
script(type="text/ng-template", id="historyLabelTooltipTpl")
.history-label-tooltip
p.history-label-tooltip-title
i.fa.fa-tag
|  {{ $ctrl.labelText }}
p.history-label-tooltip-owner #{translate("history_label_created_by")} {{ $ctrl.labelOwnerName }}
time.history-label-tooltip-datetime {{ labelCreationDateTime | formatDate }}
script(type="text/ng-template", id="historyV2DeleteLabelModalTemplate")
.modal-header
h3 #{translate("history_delete_label")}
.modal-body
.alert.alert-danger(ng-show="state.error.message") {{ state.error.message}}
.alert.alert-danger(ng-show="state.error && !state.error.message") #{translate("generic_something_went_wrong")}
p.help-block(ng-if="labelDetails")
| #{translate("history_are_you_sure_delete_label")}
strong "{{ labelDetails.comment }}"
| ?
.modal-footer
button.btn.btn-default(
type="button"
ng-disabled="state.inflight"
ng-click="$dismiss()"
) #{translate("cancel")}
button.btn.btn-primary(
type="button"
ng-click="deleteLabel()"
ng-disabled="state.inflight"
) {{ state.inflight ? '#{translate("history_deleting_label")}' : '#{translate("history_delete_label")}' }}

View File

@@ -5,11 +5,13 @@ aside.change-list(
history-entries-list(
entries="history.updates"
current-user="user"
users="projectUsers"
load-entries="loadMore()"
load-disabled="history.loading || history.atEnd"
load-initialize="ui.view == 'history'"
is-loading="history.loading"
on-entry-select="handleEntrySelect(selectedEntry)"
on-label-delete="handleLabelDelete(label)"
)
aside.change-list(
@@ -65,6 +67,14 @@ aside.change-list(
)
div.description(ng-click="select()")
history-label(
ng-repeat="label in update.labels"
label-text="label.comment"
label-owner-name="getDisplayNameById(label.user_id)"
label-creation-date-time="label.created_at"
is-owned-by-current-user="label.user_id === user.id"
on-label-delete="deleteLabel(label)"
)
div.time {{ update.meta.end_ts | formatDate:'h:mm a' }}
div.action.action-edited(ng-if="history.isV2 && update.pathnames.length > 0")
| #{translate("file_action_edited")}
@@ -106,7 +116,9 @@ script(type="text/ng-template", id="historyEntriesListTpl")
ng-repeat="entry in $ctrl.entries"
entry="entry"
current-user="$ctrl.currentUser"
users="$ctrl.users"
on-select="$ctrl.onEntrySelect({ selectedEntry: selectedEntry })"
on-label-delete="$ctrl.onLabelDelete({ label: label })"
ng-show="!$ctrl.isLoading"
)
.loading(ng-show="$ctrl.isLoading")
@@ -129,6 +141,15 @@ script(type="text/ng-template", id="historyEntryTpl")
time.history-entry-day(ng-if="::$ctrl.entry.meta.first_in_day") {{ ::$ctrl.entry.meta.end_ts | relativeDate }}
.history-entry-details(ng-click="$ctrl.onSelect({ selectedEntry: $ctrl.entry })")
history-label(
ng-repeat="label in $ctrl.entry.labels"
label-text="label.comment"
label-owner-name="$ctrl.displayNameById(label.user_id)"
label-creation-date-time="label.created_at"
is-owned-by-current-user="label.user_id === $ctrl.currentUser.id"
on-label-delete="$ctrl.onLabelDelete({ label: label })"
)
ol.history-entry-changes
li.history-entry-change(
ng-repeat="pathname in ::$ctrl.entry.pathnames"
@@ -148,6 +169,7 @@ script(type="text/ng-template", id="historyEntryTpl")
ng-if="::project_op.remove"
) #{translate("file_action_deleted")}
span.history-entry-change-doc {{ ::$ctrl.getProjectOpDoc(project_op) }}
.history-entry-metadata
time.history-entry-metadata-time {{ ::$ctrl.entry.meta.end_ts | formatDate:'h:mm a' }}
span

View File

@@ -1,4 +1,5 @@
.history-toolbar(
ng-controller="HistoryV2ToolbarController"
ng-if="ui.view == 'history' && history.isV2 && history.viewMode === HistoryViewModes.POINT_IN_TIME"
)
span(ng-show="history.loadingFileTree")
@@ -6,8 +7,49 @@
|    #{translate("loading")}...
span(ng-show="!history.loadingFileTree") #{translate("browsing_project_as_of")} 
time.history-toolbar-time {{ history.selection.updates[0].meta.end_ts | formatDate:'Do MMM YYYY, h:mm a' }}
.history-toolbar-btn(
ng-click="toggleHistoryViewMode();"
button.history-toolbar-btn(
ng-click="showAddLabelDialog();"
ng-disabled="history.loadingFileTree"
)
i.fa
| #{translate("compare_to_another_version")}
i.fa.fa-tag
|  #{translate("history_label_this_version")}
button.history-toolbar-btn(
ng-click="toggleHistoryViewMode();"
ng-disabled="history.loadingFileTree"
)
i.fa.fa-exchange
|  #{translate("compare_to_another_version")}
script(type="text/ng-template", id="historyV2AddLabelModalTemplate")
form(
name="addLabelModalForm"
ng-submit="addLabelModalFormSubmit();"
novalidate
)
.modal-header
h3 #{translate("history_add_label")}
.modal-body
.alert.alert-danger(ng-show="state.error.message") {{ state.error.message}}
.alert.alert-danger(ng-show="state.error && !state.error.message") #{translate("generic_something_went_wrong")}
.form-group
input.form-control(
type="text"
placeholder=translate("history_new_label_name")
ng-model="inputs.labelName"
focus-on="open"
required
)
p.help-block(ng-if="update")
| #{translate("history_new_label_added_at")}
strong {{ update.meta.end_ts | formatDate:'ddd Do MMM YYYY, h:mm a' }}
.modal-footer
button.btn.btn-default(
type="button"
ng-disabled="state.inflight"
ng-click="$dismiss()"
) #{translate("cancel")}
input.btn.btn-primary(
ng-disabled="addLabelModalForm.$invalid || state.inflight"
ng-value="state.inflight ? '" + translate("history_adding_label") + "' : '" + translate("history_add_label") + "'"
type="submit"
)

View File

@@ -6,9 +6,13 @@ define [
"ide/history/controllers/HistoryV2ListController"
"ide/history/controllers/HistoryV2DiffController"
"ide/history/controllers/HistoryV2FileTreeController"
"ide/history/controllers/HistoryV2ToolbarController"
"ide/history/controllers/HistoryV2AddLabelModalController"
"ide/history/controllers/HistoryV2DeleteLabelModalController"
"ide/history/directives/infiniteScroll"
"ide/history/components/historyEntriesList"
"ide/history/components/historyEntry"
"ide/history/components/historyLabel"
"ide/history/components/historyFileTree"
"ide/history/components/historyFileEntity"
], (moment, ColorManager, displayNameForUser, HistoryViewModes) ->
@@ -68,6 +72,7 @@ define [
toV: null
}
}
labels: null
files: []
diff: null # When history.viewMode == HistoryViewModes.COMPARE
selectedFile: null # When history.viewMode == HistoryViewModes.POINT_IN_TIME
@@ -126,18 +131,19 @@ define [
BATCH_SIZE: 10
fetchNextBatchOfUpdates: () ->
url = "/project/#{@ide.project_id}/updates?min_count=#{@BATCH_SIZE}"
updatesURL = "/project/#{@ide.project_id}/updates?min_count=#{@BATCH_SIZE}"
if @$scope.history.nextBeforeTimestamp?
url += "&before=#{@$scope.history.nextBeforeTimestamp}"
updatesURL += "&before=#{@$scope.history.nextBeforeTimestamp}"
@$scope.history.loading = true
@$scope.history.loadingFileTree = true
@ide.$http
.get(url)
@ide.$http.get updatesURL
.then (response) =>
{ data } = response
@_loadUpdates(data.updates)
@$scope.history.nextBeforeTimestamp = data.nextBeforeTimestamp
if !data.nextBeforeTimestamp?
updatesData = response.data
@_loadUpdates(updatesData.updates)
@$scope.history.nextBeforeTimestamp = updatesData.nextBeforeTimestamp
if !updatesData.nextBeforeTimestamp?
@$scope.history.atEnd = true
@$scope.history.loading = false
@@ -199,6 +205,27 @@ define [
diff.loading = false
diff.error = true
labelCurrentVersion: (labelComment) =>
@_labelVersion labelComment, @$scope.history.selection.updates[0].toV
deleteLabel: (labelId) =>
url = "/project/#{@$scope.project_id}/labels/#{labelId}"
@ide.$http({
url,
method: "DELETE"
headers:
"X-CSRF-Token": window.csrfToken
}).then (response) =>
@_deleteLabelFromLocalCollection @$scope.history.updates, labelId
@_deleteLabelFromLocalCollection @$scope.history.selection, labelId
_deleteLabelFromLocalCollection: (collection, labelId) ->
for update in collection
update.labels = _.filter update.labels, (label) ->
label.id != labelId
_parseDiff: (diff) ->
if diff.binary
return { binary: true }
@@ -278,6 +305,22 @@ define [
else
@autoSelectLastUpdate()
_labelVersion: (comment, version) ->
url = "/project/#{@$scope.project_id}/labels"
@ide.$http
.post url, {
comment,
version,
_csrf: window.csrfToken
}
.then (response) =>
@_addLabelToLocalUpdate response.data
_addLabelToLocalUpdate: (label) ->
localUpdate = _.find @$scope.history.updates, (update) -> update.toV == label.version
if localUpdate?
localUpdate.labels.push label
_perDocSummaryOfUpdates: (updates) ->
# Track current_pathname -> original_pathname
# create bare object for use as Map

View File

@@ -8,12 +8,14 @@ define [
App.component "historyEntriesList", {
bindings:
entries: "<"
users: "<"
loadEntries: "&"
loadDisabled: "<"
loadInitialize: "<"
isLoading: "<"
currentUser: "<"
onEntrySelect: "&"
onLabelDelete: "&"
controller: historyEntriesListController
templateUrl: "historyEntriesListTpl"
}

View File

@@ -2,9 +2,18 @@ define [
"base"
"ide/history/util/displayNameForUser"
], (App, displayNameForUser) ->
historyEntryController = ($scope, $element, $attrs) ->
historyEntryController = ($scope, $element, $attrs, _) ->
ctrl = @
# This method (and maybe the one below) will be removed soon. User details data will be
# injected into the history API responses, so we won't need to fetch user data from other
# local data structures.
_getUserById = (id) ->
_.find ctrl.users, (user) ->
curUserId = user?._id or user?.id
curUserId == id
ctrl.displayName = displayNameForUser
ctrl.displayNameById = (id) ->
displayNameForUser(_getUserById(id))
ctrl.getProjectOpDoc = (projectOp) ->
if projectOp.rename? then "#{ projectOp.rename.pathname}#{ projectOp.rename.newPathname }"
else if projectOp.add? then "#{ projectOp.add.pathname}"
@@ -21,7 +30,9 @@ define [
bindings:
entry: "<"
currentUser: "<"
users: "<"
onSelect: "&"
onLabelDelete: "&"
controller: historyEntryController
templateUrl: "historyEntryTpl"
}

View File

@@ -0,0 +1,17 @@
define [
"base"
], (App) ->
historyLabelController = ($scope, $element, $attrs, $filter, _) ->
ctrl = @
return
App.component "historyLabel", {
bindings:
labelText: "<"
labelOwnerName: "<"
labelCreationDateTime: "<"
isOwnedByCurrentUser: "<"
onLabelDelete: "&"
controller: historyLabelController
templateUrl: "historyLabelTpl"
}

View File

@@ -3,9 +3,30 @@ define [
"ide/history/util/displayNameForUser"
], (App, displayNameForUser) ->
App.controller "HistoryListController", ["$scope", "ide", ($scope, ide) ->
App.controller "HistoryListController", ["$scope", "$modal", "ide", ($scope, $modal, ide) ->
$scope.hoveringOverListSelectors = false
projectUsers = $scope.project.members.concat $scope.project.owner
# This method (and maybe the one below) will be removed soon. User details data will be
# injected into the history API responses, so we won't need to fetch user data from other
# local data structures.
_getUserById = (id) ->
_.find projectUsers, (user) ->
curUserId = user?._id or user?.id
curUserId == id
$scope.getDisplayNameById = (id) ->
displayNameForUser(_getUserById(id))
$scope.deleteLabel = (labelDetails) ->
$modal.open(
templateUrl: "historyV2DeleteLabelModalTemplate"
controller: "HistoryV2DeleteLabelModalController"
resolve:
labelDetails: () -> labelDetails
)
$scope.loadMore = () =>
ide.historyManager.fetchNextBatchOfUpdates()

View File

@@ -0,0 +1,29 @@
define [
"base",
], (App) ->
App.controller "HistoryV2AddLabelModalController", ["$scope", "$modalInstance", "ide", "update", ($scope, $modalInstance, ide, update) ->
$scope.update = update
$scope.inputs =
labelName: null
$scope.state =
inflight: false
error: false
$modalInstance.opened.then () ->
$scope.$applyAsync () ->
$scope.$broadcast "open"
$scope.addLabelModalFormSubmit = () ->
$scope.state.inflight = true
ide.historyManager.labelCurrentVersion $scope.inputs.labelName
.then (response) ->
$scope.state.inflight = false
$modalInstance.close()
.catch (response) ->
{ data, status } = response
$scope.state.inflight = false
if status == 400
$scope.state.error = { message: data }
else
$scope.state.error = true
]

View File

@@ -0,0 +1,23 @@
define [
"base",
], (App) ->
App.controller "HistoryV2DeleteLabelModalController", ["$scope", "$modalInstance", "ide", "labelDetails", ($scope, $modalInstance, ide, labelDetails) ->
$scope.labelDetails = labelDetails
$scope.state =
inflight: false
error: false
$scope.deleteLabel = () ->
$scope.state.inflight = true
ide.historyManager.deleteLabel labelDetails.id
.then (response) ->
$scope.state.inflight = false
$modalInstance.close()
.catch (response) ->
{ data, status } = response
$scope.state.inflight = false
if status == 400
$scope.state.error = { message: data }
else
$scope.state.error = true
]

View File

@@ -3,17 +3,27 @@ define [
"ide/history/util/displayNameForUser"
], (App, displayNameForUser) ->
App.controller "HistoryV2ListController", ["$scope", "ide", ($scope, ide) ->
App.controller "HistoryV2ListController", ["$scope", "$modal", "ide", ($scope, $modal, ide) ->
$scope.hoveringOverListSelectors = false
$scope.listConfig =
showOnlyLabelled: false
$scope.projectUsers = $scope.project.members.concat $scope.project.owner
$scope.loadMore = () =>
ide.historyManager.fetchNextBatchOfUpdates()
$scope.handleEntrySelect = (entry) ->
# $scope.$applyAsync () ->
ide.historyManager.selectUpdate(entry)
$scope.recalculateSelectedUpdates()
$scope.handleLabelDelete = (labelDetails) ->
$modal.open(
templateUrl: "historyV2DeleteLabelModalTemplate"
controller: "HistoryV2DeleteLabelModalController"
resolve:
labelDetails: () -> labelDetails
)
$scope.recalculateSelectedUpdates = () ->
beforeSelection = true
afterSelection = false

View File

@@ -0,0 +1,12 @@
define [
"base",
], (App) ->
App.controller "HistoryV2ToolbarController", ["$scope", "$modal", "ide", ($scope, $modal, ide) ->
$scope.showAddLabelDialog = () ->
$modal.open(
templateUrl: "historyV2AddLabelModalTemplate"
controller: "HistoryV2AddLabelModalController"
resolve:
update: () -> $scope.history.selection.updates[0]
)
]

View File

@@ -53,6 +53,68 @@
color: #FFF;
}
}
.history-entry-label {
display: inline-block;
color: @history-entry-label-color;
font-size: @font-size-small;
margin-bottom: 3px;
margin-right: 10px;
white-space: nowrap;
.history-entry-selected & {
color: @history-entry-selected-label-color;
}
}
.history-entry-label-comment,
.history-entry-label-delete-btn {
padding: 0 @padding-xs-horizontal 1px @padding-xs-horizontal;
border: 0;
background-color: @history-entry-label-bg-color;
.history-entry-selected & {
background-color: @history-entry-selected-label-bg-color;
}
}
.history-entry-label-comment {
display: block;
float: left;
border-radius: 9999px;
max-width: 190px;
overflow: hidden;
text-overflow: ellipsis;
.history-entry-label-own & {
padding-right: (@padding-xs-horizontal / 2);
border-radius: 9999px 0 0 9999px;
}
}
.history-entry-label-delete-btn {
padding-left: (@padding-xs-horizontal / 2);
padding-right: @padding-xs-horizontal;
border-radius: 0 9999px 9999px 0;
&:hover {
background-color: darken(@history-entry-label-bg-color, 8%);
.history-entry-selected & {
background-color: darken(@history-entry-selected-label-bg-color, 8%);
}
}
}
.history-label-tooltip {
white-space: normal;
padding: (@line-height-computed / 4);
text-align: left;
}
.history-label-tooltip-title,
.history-label-tooltip-owner,
.history-label-tooltip-datetime {
margin: 0 0 (@line-height-computed / 4) 0;
}
.history-label-tooltip-title {
font-weight: bold;
}
.history-label-tooltip-datetime {
margin-bottom: 0;
}
.history-entry-changes {
.list-unstyled;
margin-bottom: 3px;

View File

@@ -986,14 +986,18 @@
@sys-msg-border : 1px solid @common-border-color;
// v2 History
@history-base-font-size : @font-size-small;
@history-base-bg : @gray-lightest;
@history-entry-day-bg : @gray;
@history-entry-selected-bg : @red;
@history-base-color : @gray-light;
@history-highlight-color : @gray;
@history-toolbar-bg-color : @toolbar-alt-bg-color;
@history-toolbar-color : @text-color;
@history-base-font-size : @font-size-small;
@history-base-bg : @gray-lightest;
@history-entry-label-bg-color : @red;
@history-entry-label-color : #FFF;
@history-entry-selected-label-bg-color : #FFF;
@history-entry-selected-label-color : @red;
@history-entry-day-bg : @gray;
@history-entry-selected-bg : @red;
@history-base-color : @gray-light;
@history-highlight-color : @gray;
@history-toolbar-bg-color : @toolbar-alt-bg-color;
@history-toolbar-color : @text-color;
// Input suggestions
@input-suggestion-v-offset : 6px;

View File

@@ -275,14 +275,18 @@
// v2 History
@history-base-font-size : @font-size-small;
@history-base-bg : @ol-blue-gray-1;
@history-entry-day-bg : @ol-blue-gray-2;
@history-entry-selected-bg : @ol-green;
@history-base-color : @ol-blue-gray-2;
@history-highlight-color : @ol-type-color;
@history-toolbar-bg-color : @editor-toolbar-bg;
@history-toolbar-color : #FFF;
@history-base-font-size : @font-size-small;
@history-base-bg : @ol-blue-gray-1;
@history-entry-label-bg-color : @ol-blue;
@history-entry-label-color : #FFF;
@history-entry-selected-label-bg-color: #FFF;
@history-entry-selected-label-color : @ol-blue;
@history-entry-day-bg : @ol-blue-gray-2;
@history-entry-selected-bg : @ol-green;
@history-base-color : @ol-blue-gray-2;
@history-highlight-color : @ol-type-color;
@history-toolbar-bg-color : @editor-toolbar-bg;
@history-toolbar-color : #FFF;
// System messages
@sys-msg-background : @ol-blue;

View File

@@ -23,6 +23,7 @@ define ['ide/history/HistoryV2Manager'], (HistoryV2Manager) ->
toV: null
}
}
labels: null
diff: null
files: []
selectedFile: null