Merge pull request #1626 from sharelatex/pr-history-drag-and-drop-selection

History drag and drop selection

GitOrigin-RevId: d2bc0dfc2e2634553224b7bef2ac5d926d42bf01
This commit is contained in:
Paulo Jorge Reis 2019-04-09 10:56:45 +01:00 committed by sharelatex
parent ca9e6044c8
commit 9c5c6922b9
20 changed files with 843 additions and 472 deletions

View file

@ -162,6 +162,9 @@ block requirejs
"ace/keybinding-vim": {
"deps": ["ace/ace"]
},
"libs/jquery.ui.touch-punch": {
"deps": [ "libs/#{lib('jquery-layout')}" ]
}
},
"config":{
"moment":{

View file

@ -1,11 +1,13 @@
aside.change-list(
ng-if="history.isV2 && history.viewMode === HistoryViewModes.POINT_IN_TIME"
ng-if="history.isV2"
ng-controller="HistoryV2ListController"
)
history-entries-list(
ng-if="!history.showOnlyLabels && !history.error"
entries="history.updates"
range-selection-enabled="history.viewMode === HistoryViewModes.COMPARE"
selected-history-version="history.selection.range.toV"
selected-history-range="history.selection.range"
current-user="user"
current-user-is-owner="project.owner._id === user.id"
users="projectUsers"
@ -14,232 +16,23 @@ aside.change-list(
load-initialize="ui.view == 'history'"
is-loading="history.loading"
free-history-limit-hit="history.freeHistoryLimitHit"
on-entry-select="handleEntrySelect(selectedEntry)"
on-version-select="handleVersionSelect(version)"
on-range-select="handleRangeSelect(selectedToV, selectedFromV)"
on-label-delete="handleLabelDelete(label)"
)
history-labels-list(
ng-if="history.showOnlyLabels && !history.error"
labels="history.labels"
range-selection-enabled="history.viewMode === HistoryViewModes.COMPARE"
selected-history-version="history.selection.range.toV"
selected-history-range="history.selection.range"
current-user="user"
users="projectUsers"
is-loading="history.loading"
selected-label="history.selection.label"
on-label-select="handleLabelSelect(label)"
on-version-select="handleVersionSelect(version)"
on-range-select="handleRangeSelect(selectedToV, selectedFromV)"
on-label-delete="handleLabelDelete(label)"
)
aside.change-list.change-list-compare(
ng-if="history.isV2 && history.viewMode === HistoryViewModes.COMPARE"
ng-controller="HistoryCompareController"
)
div(
ng-if="!history.showOnlyLabels && !history.error"
)
aside.change-list(
infinite-scroll="loadMore()"
infinite-scroll-disabled="history.loading || history.atEnd"
infinite-scroll-initialize="ui.view == 'history' && history.viewMode === HistoryViewModes.COMPARE"
)
.infinite-scroll-inner
ul.list-unstyled(
ng-class="{\
'hover-state': history.hoveringOverListSelectors\
}"
)
li.change(
ng-repeat="update in history.updates track by update.fromV"
ng-class="{\
'first-in-day': update.meta.first_in_day,\
'selected': update.toV <= history.selection.range.toV && update.fromV >= history.selection.range.fromV,\
'selected-to': update.toV === history.selection.range.toV,\
'selected-from': update.fromV === history.selection.range.fromV,\
'hover-selected': update.toV <= history.selection.hoveredRange.toV && update.fromV >= history.selection.hoveredRange.fromV,\
'hover-selected-to': update.toV === history.selection.hoveredRange.toV,\
'hover-selected-from': update.fromV === history.selection.hoveredRange.fromV,\
}"
)
div.day(ng-if="::update.meta.first_in_day") {{ ::update.meta.end_ts | relativeDate }}
div.selectors
div.range
form
input.selector-from(
type="radio"
name="fromVersion"
ng-model="history.selection.range.fromV"
ng-value="update.fromV"
ng-mouseover="setHoverFrom(update.fromV)"
ng-mouseout="resetHover()"
ng-show="update.fromV < history.selection.range.fromV || update.toV <= history.selection.range.toV && update.fromV >= history.selection.range.fromV"
)
form
input.selector-to(
type="radio"
name="toVersion"
ng-model="history.selection.range.toV"
ng-value="update.toV"
ng-mouseover="setHoverTo(update.toV)"
ng-mouseout="resetHover()"
ng-show="update.toV > history.selection.range.toV || update.toV <= history.selection.range.toV && update.fromV >= history.selection.range.fromV"
)
div.description(ng-click="select(update.toV, update.fromV)")
history-label(
ng-repeat="label in update.labels track by label.id"
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")}
div.docs(ng-repeat="pathname in ::update.pathnames")
.doc {{ ::pathname }}
div.docs(ng-repeat="project_op in ::update.project_ops")
div(ng-if="::project_op.rename")
.action #{translate("file_action_renamed")}
.doc {{ ::project_op.rename.pathname }} &rarr; {{ ::project_op.rename.newPathname }}
div(ng-if="::project_op.add")
.action #{translate("file_action_created")}
.doc {{ ::project_op.add.pathname }}
div(ng-if="::project_op.remove")
.action #{translate("file_action_deleted")}
.doc {{ ::project_op.remove.pathname }}
div.users
div.user(ng-repeat="update_user in ::update.meta.users")
.color-square(ng-if="::update_user != null", ng-style="::{'background-color': 'hsl({{ update_user.hue }}, 70%, 50%)'}")
.color-square(ng-if="::update_user == null", ng-style="::{'background-color': 'hsl(100, 70%, 50%)'}")
.name(ng-if="::update_user && update_user.id != user.id" ng-bind="getDisplayNameForUser(update_user)")
.name(ng-if="::update_user && update_user.id == user.id") You
.name(ng-if="::update_user == null") #{translate("anonymous")}
div.user(ng-if="::update.meta.users.length == 0")
.color-square(style="background-color: hsl(100, 100%, 50%)")
span #{translate("anonymous")}
.loading(ng-show="history.loading")
i.fa.fa-spin.fa-refresh
| &nbsp;&nbsp; #{translate("loading")}...
.history-entries-list-upgrade-prompt(
ng-if="history.freeHistoryLimitHit && project.owner._id === user.id"
ng-controller="FreeTrialModalController"
)
p #{translate("currently_seeing_only_24_hrs_history")}
p: strong #{translate("upgrade_to_get_feature", {feature:"full Project History"})}
ul.list-unstyled
li
i.fa.fa-check &nbsp;
| #{translate("unlimited_projects")}
li
i.fa.fa-check &nbsp;
| #{translate("collabs_per_proj", {collabcount:'Multiple'})}
li
i.fa.fa-check &nbsp;
| #{translate("full_doc_history")}
li
i.fa.fa-check &nbsp;
| #{translate("sync_to_dropbox")}
li
i.fa.fa-check &nbsp;
| #{translate("sync_to_github")}
li
i.fa.fa-check &nbsp;
|#{translate("compile_larger_projects")}
p.text-center
a.btn.btn-success(
href
ng-class="buttonClass"
ng-click="startFreeTrial('history')"
) #{translate("start_free_trial")}
p.small(ng-show="startedFreeTrial") #{translate("refresh_page_after_starting_free_trial")}
.history-entries-list-upgrade-prompt(
ng-if="history.freeHistoryLimitHit && !project.owner._id === user.id"
)
p #{translate("currently_seeing_only_24_hrs_history")}
strong #{translate("ask_proj_owner_to_upgrade_for_full_history")}
.history-labels-list-compare(
ng-if="history.showOnlyLabels && !history.error"
)
ul.list-unstyled(
ng-class="{\
'hover-state': history.hoveringOverListSelectors\
}"
)
li.change(
ng-repeat="versionWithLabel in versionsWithLabels | orderBy:'-version' track by versionWithLabel.version"
ng-class="{\
'selected': versionWithLabel.version <= history.selection.range.toV && versionWithLabel.version >= history.selection.range.fromV,\
'selected-to': versionWithLabel.version === history.selection.range.toV,\
'selected-from': versionWithLabel.version === history.selection.range.fromV,\
'hover-selected': versionWithLabel.version <= history.selection.hoveredRange.toV && versionWithLabel.version >= history.selection.hoveredRange.fromV,\
'hover-selected-to': versionWithLabel.version === history.selection.hoveredRange.toV,\
'hover-selected-from': versionWithLabel.version === history.selection.hoveredRange.fromV,\
}"
)
div.selectors
div.range
form
input.selector-from(
type="radio"
name="fromVersionForLabel"
ng-model="history.selection.range.fromV"
ng-value="versionWithLabel.version"
ng-mouseover="setHoverFrom(versionWithLabel.version)"
ng-mouseout="resetHover()"
ng-show="versionWithLabel.version < history.selection.range.fromV || versionWithLabel.version <= history.selection.range.toV && versionWithLabel.version >= history.selection.range.fromV"
)
form
input.selector-to(
type="radio"
name="toVersionForLabel"
ng-model="history.selection.range.toV"
ng-value="versionWithLabel.version"
ng-mouseover="setHoverTo(versionWithLabel.version)"
ng-mouseout="resetHover()"
ng-show="versionWithLabel.version > history.selection.range.toV || versionWithLabel.version <= history.selection.range.toV && versionWithLabel.version >= history.selection.range.fromV"
)
.description(ng-click="addLabelVersionToSelection(versionWithLabel.version)")
div(
ng-repeat="label in versionWithLabel.labels track by label.id"
)
history-label(
show-tooltip="false"
label-text="label.comment"
is-owned-by-current-user="label.user_id === user.id"
is-pseudo-current-state-label="label.isPseudoCurrentStateLabel"
on-label-delete="deleteLabel(label)"
)
.history-entry-label-metadata
.history-entry-label-metadata-user(
ng-if="!label.isPseudoCurrentStateLabel"
ng-init="labelOwner = getUserById(label.user_id)"
)
| Saved by
span.name(
ng-if="::labelOwner && labelOwner._id !== user.id"
ng-style="::{'color': 'hsl({{ hueForUser(label.user_id) }}, 70%, 50%)'}"
) {{ ::getDisplayNameById(label.user_id) }}
span.name(
ng-if="labelOwner && labelOwner._id == user.id"
ng-style="::{'color': 'hsl({{ hueForUser(label.user_id) }}, 70%, 50%)'}"
) You
span.name(
ng-if="::labelOwner == null"
ng-style="::{'color': 'hsl(100, 70%, 50%)'}"
) #{translate("anonymous")}
time.history-entry-label-metadata-time {{ ::label.created_at | formatDate }}
.loading(ng-show="history.loading")
i.fa.fa-spin.fa-refresh
| &nbsp;&nbsp; #{translate("loading")}...
script(type="text/ng-template", id="historyEntriesListTpl")
.history-entries(
@ -250,11 +43,15 @@ script(type="text/ng-template", id="historyEntriesListTpl")
.infinite-scroll-inner
history-entry(
ng-repeat="entry in $ctrl.entries"
range-selection-enabled="$ctrl.rangeSelectionEnabled"
is-dragging="$ctrl.isDragging"
selected-history-version="$ctrl.selectedHistoryVersion"
selected-history-range="$ctrl.selectedHistoryRange"
hovered-history-range="$ctrl.hoveredHistoryRange"
entry="entry"
current-user="$ctrl.currentUser"
users="$ctrl.users"
on-select="$ctrl.onEntrySelect({ selectedEntry: selectedEntry })"
on-select="$ctrl.handleEntrySelect(selectedEntry)"
on-label-delete="$ctrl.onLabelDelete({ label: label })"
)
.loading(ng-show="$ctrl.isLoading")
@ -307,13 +104,30 @@ script(type="text/ng-template", id="historyEntryTpl")
.history-entry(
ng-class="{\
'history-entry-first-in-day': $ctrl.entry.meta.first_in_day,\
'history-entry-selected': $ctrl.entry.toV === $ctrl.selectedHistoryVersion,\
'history-entry-selected': !$ctrl.isDragging && $ctrl.isEntrySelected(),\
'history-entry-selected-to': $ctrl.rangeSelectionEnabled && !$ctrl.isDragging && $ctrl.selectedHistoryRange.toV === $ctrl.entry.toV,\
'history-entry-selected-from': $ctrl.rangeSelectionEnabled && !$ctrl.isDragging && $ctrl.selectedHistoryRange.fromV === $ctrl.entry.fromV,\
'history-entry-hover-selected': $ctrl.rangeSelectionEnabled && $ctrl.isDragging && $ctrl.isEntryHoverSelected(),\
'history-entry-hover-selected-to': $ctrl.rangeSelectionEnabled && $ctrl.isDragging && $ctrl.hoveredHistoryRange.toV === $ctrl.entry.toV,\
'history-entry-hover-selected-from': $ctrl.rangeSelectionEnabled && $ctrl.isDragging && $ctrl.hoveredHistoryRange.fromV === $ctrl.entry.fromV,\
}"
history-droppable-area
history-droppable-area-on-drop="$ctrl.onDrop(boundary)"
history-droppable-area-on-over="$ctrl.onOver(boundary)"
)
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-entry-details(
ng-click="$ctrl.onSelect({ selectedEntry: $ctrl.entry })"
)
.history-entry-toV-handle(
ng-show="$ctrl.rangeSelectionEnabled && $ctrl.selectedHistoryRange && ((!$ctrl.isDragging && $ctrl.selectedHistoryRange.toV === $ctrl.entry.toV) || ($ctrl.isDragging && $ctrl.hoveredHistoryRange.toV === $ctrl.entry.toV))"
history-draggable-boundary="toV"
history-draggable-boundary-on-drag-start="$ctrl.onDraggingStart()"
history-draggable-boundary-on-drag-stop="$ctrl.onDraggingStop(isValidDrop, boundary)"
)
history-label(
ng-repeat="label in $ctrl.entry.labels | orderBy : '-created_at'"
label-text="label.comment"
@ -342,7 +156,6 @@ 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
@ -368,39 +181,72 @@ script(type="text/ng-template", id="historyEntryTpl")
ng-style="$ctrl.getUserCSSStyle();"
) #{translate("anonymous")}
.history-entry-fromV-handle(
ng-show="$ctrl.rangeSelectionEnabled && $ctrl.selectedHistoryRange && ((!$ctrl.isDragging && $ctrl.selectedHistoryRange.fromV === $ctrl.entry.fromV) || ($ctrl.isDragging && $ctrl.hoveredHistoryRange.fromV === $ctrl.entry.fromV))"
history-draggable-boundary="fromV"
history-draggable-boundary-on-drag-start="$ctrl.onDraggingStart()"
history-draggable-boundary-on-drag-stop="$ctrl.onDraggingStop(isValidDrop, boundary)"
)
script(type="text/ng-template", id="historyLabelsListTpl")
.history-labels-list
.history-entry-label(
ng-repeat="label in $ctrl.labels track by label.id"
ng-click="$ctrl.onLabelSelect({ label: label })"
ng-class="{ 'history-entry-label-selected': label.id === $ctrl.selectedLabel.id }"
.history-version-with-label(
ng-repeat="versionWithLabel in $ctrl.versionsWithLabels | orderBy:'-version' track by versionWithLabel.version"
ng-class="{\
'history-version-with-label-selected': !$ctrl.isDragging && $ctrl.isVersionSelected(versionWithLabel.version),\
'history-version-with-label-selected-to': !$ctrl.isDragging && $ctrl.selectedHistoryRange.toV === versionWithLabel.version,\
'history-version-with-label-selected-from': !$ctrl.isDragging && $ctrl.selectedHistoryRange.fromV === versionWithLabel.version,\
'history-version-with-label-hover-selected': $ctrl.isDragging && $ctrl.isVersionHoverSelected(versionWithLabel.version),\
'history-version-with-label-hover-selected-to': $ctrl.isDragging && $ctrl.hoveredHistoryRange.toV === versionWithLabel.version,\
'history-version-with-label-hover-selected-from': $ctrl.isDragging && $ctrl.hoveredHistoryRange.fromV === versionWithLabel.version,\
}"
ng-click="$ctrl.handleVersionSelect(versionWithLabel)"
history-droppable-area
history-droppable-area-on-drop="$ctrl.onDrop(boundary, versionWithLabel)"
history-droppable-area-on-over="$ctrl.onOver(boundary, versionWithLabel)"
)
history-label(
show-tooltip="false"
label-text="label.comment"
is-owned-by-current-user="label.user_id === $ctrl.currentUser.id"
on-label-delete="$ctrl.onLabelDelete({ label: label })"
is-pseudo-current-state-label="label.isPseudoCurrentStateLabel"
.history-entry-toV-handle(
ng-show="$ctrl.rangeSelectionEnabled && $ctrl.selectedHistoryRange && ((!$ctrl.isDragging && $ctrl.selectedHistoryRange.toV === versionWithLabel.version) || ($ctrl.isDragging && $ctrl.hoveredHistoryRange.toV === versionWithLabel.version))"
history-draggable-boundary="toV"
history-draggable-boundary-on-drag-start="$ctrl.onDraggingStart()"
history-draggable-boundary-on-drag-stop="$ctrl.onDraggingStop(isValidDrop, boundary)"
)
.history-entry-label-metadata
.history-entry-label-metadata-user(
ng-if="!label.isPseudoCurrentStateLabel"
ng-init="user = $ctrl.getUserById(label.user_id)"
div(
ng-repeat="label in versionWithLabel.labels track by label.id"
)
history-label(
show-tooltip="false"
label-text="label.comment"
is-owned-by-current-user="label.user_id === $ctrl.currentUser.id"
on-label-delete="$ctrl.onLabelDelete({ label: label })"
is-pseudo-current-state-label="label.isPseudoCurrentStateLabel"
)
| Saved by
span.name(
ng-if="user && user._id !== $ctrl.currentUser.id"
ng-style="$ctrl.getUserCSSStyle(user, label);"
) {{ ::$ctrl.displayName(user) }}
span.name(
ng-if="user && user._id == $ctrl.currentUser.id"
ng-style="$ctrl.getUserCSSStyle(user, label);"
) You
span.name(
ng-if="user == null"
ng-style="$ctrl.getUserCSSStyle(user, label);"
) #{translate("anonymous")}
time.history-entry-label-metadata-time {{ ::label.created_at | formatDate }}
.history-entry-label-metadata
.history-entry-label-metadata-user(
ng-if="!label.isPseudoCurrentStateLabel"
ng-init="user = $ctrl.getUserById(label.user_id)"
)
| Saved by
span.name(
ng-if="user && user._id !== $ctrl.currentUser.id"
ng-style="$ctrl.getUserCSSStyle(user, versionWithLabel);"
) {{ ::$ctrl.displayName(user) }}
span.name(
ng-if="user && user._id == $ctrl.currentUser.id"
ng-style="$ctrl.getUserCSSStyle(user, versionWithLabel);"
) You
span.name(
ng-if="user == null"
ng-style="$ctrl.getUserCSSStyle(user, versionWithLabel);"
) #{translate("anonymous")}
time.history-entry-label-metadata-time {{ ::label.created_at | formatDate }}
.history-entry-fromV-handle(
ng-show="$ctrl.rangeSelectionEnabled && $ctrl.selectedHistoryRange && ((!$ctrl.isDragging && $ctrl.selectedHistoryRange.fromV === versionWithLabel.version) || ($ctrl.isDragging && $ctrl.hoveredHistoryRange.fromV === versionWithLabel.version))"
history-draggable-boundary="fromV"
history-draggable-boundary-on-drag-start="$ctrl.onDraggingStart()"
history-draggable-boundary-on-drag-stop="$ctrl.onDraggingStop(isValidDrop, boundary)"
)
.loading(ng-show="$ctrl.isLoading")
i.fa.fa-spin.fa-refresh
| &nbsp;&nbsp; #{translate("loading")}...

View file

@ -2,7 +2,7 @@
ng-if="history.isV2 && history.viewMode === HistoryViewModes.COMPARE"
)
.diff(
ng-if="!!history.selection.diff && !history.selection.diff.loading && !history.selection.diff.error",
ng-if="!!history.selection.diff && !isHistoryLoading() && !history.selection.diff.error",
ng-class="{ 'diff-binary': history.selection.diff.binary }"
)
.diff-editor-v2.hide-ace-cursor(
@ -19,10 +19,10 @@
.alert.alert-info(ng-if="history.selection.diff.binary")
| We're still working on showing image and binary changes, sorry. Stay tuned!
.loading-panel(ng-show="history.selection.diff.loading")
.loading-panel(ng-show="isHistoryLoading()")
i.fa.fa-spin.fa-refresh
| &nbsp;&nbsp;#{translate("loading")}...
.error-panel(ng-show="history.selection.diff.error")
.error-panel(ng-show="history.selection.diff.error && !isHistoryLoading()")
.alert.alert-danger #{translate("generic_something_went_wrong")}
.point-in-time-panel.full-size(
@ -42,7 +42,7 @@
)
.alert.alert-info(ng-if="history.selection.file.binary")
| We're still working on showing image and binary changes, sorry. Stay tuned!
.loading-panel(ng-show="history.selection.file.loading")
.loading-panel(ng-show="isHistoryLoading()")
i.fa.fa-spin.fa-refresh
| &nbsp;&nbsp;#{translate("loading")}...
.error-panel(ng-show="history.error")

View file

@ -2,28 +2,32 @@
ng-controller="HistoryV2ToolbarController"
ng-if="ui.view == 'history' && history.isV2"
)
span.history-toolbar-selected-version(ng-show="history.loadingFileTree || history.selection.diff.loading")
span.history-toolbar-selected-version(ng-show="history.loadingFileTree")
i.fa.fa-spin.fa-refresh
| &nbsp;&nbsp; #{translate("loading")}...
//- point-in-time mode info
span.history-toolbar-selected-version(
ng-show="!history.loadingFileTree && !history.showOnlyLabels && history.selection.update && !history.error"
ng-show="!history.loadingFileTree && history.viewMode === HistoryViewModes.POINT_IN_TIME && !history.showOnlyLabels && currentUpdate && !history.error"
) #{translate("browsing_project_as_of")}&nbsp;
time.history-toolbar-time {{ history.selection.update.meta.end_ts | formatDate:'Do MMM YYYY, h:mm a' }}
time.history-toolbar-time {{ currentUpdate.meta.end_ts | formatDate:'Do MMM YYYY, h:mm a' }}
span.history-toolbar-selected-version(
ng-show="!history.loadingFileTree && history.showOnlyLabels && history.selection.label && !history.error"
ng-show="!history.loadingFileTree && history.viewMode === HistoryViewModes.POINT_IN_TIME && history.showOnlyLabels && currentUpdate && !history.error"
)
span(ng-if="history.selection.label.comment && !history.selection.label.isPseudoCurrentStateLabel")
span(ng-if="currentUpdate.labels.length > 0")
| #{translate("browsing_project_labelled")}&nbsp;
span.history-toolbar-selected-label "{{ history.selection.label.comment }}"
span.history-toolbar-selected-label(ng-if="history.selection.label.isPseudoCurrentStateLabel")
span.history-toolbar-selected-label(
ng-repeat="label in currentUpdate.labels"
)
| {{ label.comment }}
span(ng-if="!$last") ,
span.history-toolbar-selected-label(ng-if="currentUpdate.labels.length === 0 && history.labels[0].isPseudoCurrentStateLabel && currentUpdate.toV === history.labels[0].version")
| #{translate("browsing_project_latest_for_pseudo_label")}
//- compare mode info
span.history-toolbar-selected-version(ng-show="history.viewMode === HistoryViewModes.COMPARE && history.selection.diff && !history.selection.diff.binary && !history.selection.diff.loading && !history.selection.diff.error && !history.loadingFileTree")
span.history-toolbar-selected-version(ng-if="history.viewMode === HistoryViewModes.COMPARE && history.selection.diff && !history.selection.diff.binary && !history.selection.diff.loading && !history.selection.diff.error && !history.loadingFileTree")
| <strong>{{history.selection.diff.highlights.length}} </strong>
ng-pluralize(
count="history.selection.diff.highlights.length",
@ -41,13 +45,13 @@
button.history-toolbar-btn(
ng-click="showAddLabelDialog();"
ng-if="!history.showOnlyLabels"
ng-disabled="history.loadingFileTree || history.selection.range.toV == null || history.selection.range.fromV == null"
ng-disabled="isHistoryLoading() || history.selection.range.toV == null || history.selection.range.fromV == null"
)
i.fa.fa-tag
| &nbsp;#{translate("history_label_this_version")}
button.history-toolbar-btn(
ng-click="toggleHistoryViewMode();"
ng-disabled="history.loadingFileTree"
ng-disabled="isHistoryLoading()"
)
i.fa.fa-exchange
| &nbsp;#{translate("compare_to_another_version")}
@ -68,6 +72,7 @@
)
button.history-toolbar-btn(
ng-click="toggleHistoryViewMode();"
ng-disabled="isHistoryLoading()"
)
i.fa
| #{translate("view_single_version")}

View file

@ -0,0 +1,180 @@
/*!
* jQuery UI Touch Punch 0.2.3
*
* Copyright 20112014, Dave Furfero
* Dual licensed under the MIT or GPL Version 2 licenses.
*
* Depends:
* jquery.ui.widget.js
* jquery.ui.mouse.js
*/
(function ($) {
// Detect touch support
$.support.touch = 'ontouchend' in document;
// Ignore browsers without touch support
if (!$.support.touch) {
return;
}
var mouseProto = $.ui.mouse.prototype,
_mouseInit = mouseProto._mouseInit,
_mouseDestroy = mouseProto._mouseDestroy,
touchHandled;
/**
* Simulate a mouse event based on a corresponding touch event
* @param {Object} event A touch event
* @param {String} simulatedType The corresponding mouse event
*/
function simulateMouseEvent (event, simulatedType) {
// Ignore multi-touch events
if (event.originalEvent.touches.length > 1) {
return;
}
event.preventDefault();
var touch = event.originalEvent.changedTouches[0],
simulatedEvent = document.createEvent('MouseEvents');
// Initialize the simulated mouse event using the touch event's coordinates
simulatedEvent.initMouseEvent(
simulatedType, // type
true, // bubbles
true, // cancelable
window, // view
1, // detail
touch.screenX, // screenX
touch.screenY, // screenY
touch.clientX, // clientX
touch.clientY, // clientY
false, // ctrlKey
false, // altKey
false, // shiftKey
false, // metaKey
0, // button
null // relatedTarget
);
// Dispatch the simulated event to the target element
event.target.dispatchEvent(simulatedEvent);
}
/**
* Handle the jQuery UI widget's touchstart events
* @param {Object} event The widget element's touchstart event
*/
mouseProto._touchStart = function (event) {
var self = this;
// Ignore the event if another widget is already being handled
if (touchHandled || !self._mouseCapture(event.originalEvent.changedTouches[0])) {
return;
}
// Set the flag to prevent other widgets from inheriting the touch event
touchHandled = true;
// Track movement to determine if interaction was a click
self._touchMoved = false;
// Simulate the mouseover event
simulateMouseEvent(event, 'mouseover');
// Simulate the mousemove event
simulateMouseEvent(event, 'mousemove');
// Simulate the mousedown event
simulateMouseEvent(event, 'mousedown');
};
/**
* Handle the jQuery UI widget's touchmove events
* @param {Object} event The document's touchmove event
*/
mouseProto._touchMove = function (event) {
// Ignore event if not handled
if (!touchHandled) {
return;
}
// Interaction was not a click
this._touchMoved = true;
// Simulate the mousemove event
simulateMouseEvent(event, 'mousemove');
};
/**
* Handle the jQuery UI widget's touchend events
* @param {Object} event The document's touchend event
*/
mouseProto._touchEnd = function (event) {
// Ignore event if not handled
if (!touchHandled) {
return;
}
// Simulate the mouseup event
simulateMouseEvent(event, 'mouseup');
// Simulate the mouseout event
simulateMouseEvent(event, 'mouseout');
// If the touch interaction did not move, it should trigger a click
if (!this._touchMoved) {
// Simulate the click event
simulateMouseEvent(event, 'click');
}
// Unset the flag to allow other widgets to inherit the touch event
touchHandled = false;
};
/**
* A duck punch of the $.ui.mouse _mouseInit method to support touch events.
* This method extends the widget with bound touch event handlers that
* translate touch events to mouse events and pass them to the widget's
* original mouse event handling methods.
*/
mouseProto._mouseInit = function () {
var self = this;
// Delegate the touch handlers to the widget's element
self.element.bind({
touchstart: $.proxy(self, '_touchStart'),
touchmove: $.proxy(self, '_touchMove'),
touchend: $.proxy(self, '_touchEnd')
});
// Call the original $.ui.mouse init method
_mouseInit.call(self);
};
/**
* Remove the touch event handlers
*/
mouseProto._mouseDestroy = function () {
var self = this;
// Delegate the touch handlers to the widget's element
self.element.unbind({
touchstart: $.proxy(self, '_touchStart'),
touchmove: $.proxy(self, '_touchMove'),
touchend: $.proxy(self, '_touchEnd')
});
// Call the original $.ui.mouse destroy method
_mouseDestroy.call(self);
};
})(jQuery);

View file

@ -13,7 +13,7 @@
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
define(['base', 'libs/jquery-layout'], App =>
define(['base', 'libs/jquery-layout', 'libs/jquery.ui.touch-punch'], App =>
App.directive('layout', [
'$parse',
'$compile',

View file

@ -19,7 +19,6 @@ define([
'moment',
'ide/colors/ColorManager',
'ide/history/util/displayNameForUser',
'ide/history/controllers/HistoryCompareController',
'ide/history/controllers/HistoryListController',
'ide/history/controllers/HistoryDiffController',
'ide/history/directives/infiniteScroll'

View file

@ -28,6 +28,8 @@ define([
'ide/history/controllers/HistoryV2AddLabelModalController',
'ide/history/controllers/HistoryV2DeleteLabelModalController',
'ide/history/directives/infiniteScroll',
'ide/history/directives/historyDraggableBoundary',
'ide/history/directives/historyDroppableArea',
'ide/history/components/historyEntriesList',
'ide/history/components/historyEntry',
'ide/history/components/historyLabelsList',
@ -57,6 +59,7 @@ define([
$scope.project_id
}`
this._previouslySelectedPathname = null
this._loadFileTreeRequestCanceller = null
this.hardReset()
this.$scope.toggleHistory = () => {
@ -71,23 +74,18 @@ define([
}, 0)
}
this.$scope.$watchGroup(
['history.selection.range.toV', 'history.selection.range.fromV'],
(newRange, prevRange) => {
if (this.$scope.history.viewMode === HistoryViewModes.COMPARE) {
let [newTo, newFrom] = newRange
let [prevTo, prevFrom] = prevRange
if (
newTo != null &&
newFrom != null &&
newTo !== prevTo &&
newFrom !== prevFrom
) {
this.loadFileTreeDiff(newTo, newFrom)
}
}
}
)
this.$scope.isHistoryLoading = () => {
let selection = this.$scope.history.selection
return (
this.$scope.history.loadingFileTree ||
(this.$scope.history.viewMode === HistoryViewModes.POINT_IN_TIME &&
selection.file &&
selection.file.loading) ||
(this.$scope.history.viewMode === HistoryViewModes.COMPARE &&
selection.diff &&
selection.diff.loading)
)
}
}
show() {
@ -151,8 +149,6 @@ define([
},
diff: null,
files: [],
update: null,
label: null,
file: null
},
error: null,
@ -186,8 +182,6 @@ define([
},
diff: null, // When history.viewMode == HistoryViewModes.COMPARE
files: [], // When history.viewMode == HistoryViewModes.COMPARE
update: null, // When history.viewMode == HistoryViewModes.POINT_IN_TIME
label: null, // When history.viewMode == HistoryViewModes.POINT_IN_TIME
file: null
}
this.$scope.history.error = null
@ -221,9 +215,9 @@ define([
} else {
// Point-in-time mode
if (this.$scope.history.showOnlyLabels) {
this.selectLabelFromUpdatesSelection()
this.autoSelectLabelForPointInTime()
} else {
this.autoSelectLastVersionForPointInTime()
this.autoSelectVersionForPointInTime()
}
}
}
@ -288,14 +282,23 @@ define([
let selection = this.$scope.history.selection
const query = [`from=${fromV}`, `to=${toV}`]
url += `?${query.join('&')}`
this.$scope.history.loadingFileTree = true
this.$scope.$applyAsync(
() => (this.$scope.history.loadingFileTree = true)
)
selection.file = null
selection.pathname = null
if (selection.diff) {
selection.diff.loading = true
// If `this._loadFileTreeRequestCanceller` is not null, then we have a request inflight
if (this._loadFileTreeRequestCanceller != null) {
// Resolving it will cancel the inflight request (or, rather, ignore its result)
this._loadFileTreeRequestCanceller.resolve()
}
this._loadFileTreeRequestCanceller = this.ide.$q.defer()
return this.ide.$http
.get(url)
.get(url, { timeout: this._loadFileTreeRequestCanceller.promise })
.then(response => {
this.$scope.history.selection.files = response.data.diff
for (let file of this.$scope.history.selection.files) {
@ -305,15 +308,15 @@ define([
delete file.newPathname
}
}
this._loadFileTreeRequestCanceller = null
this.$scope.history.loadingFileTree = false
this.autoSelectFile()
})
.catch(err => {
console.error(err)
})
.finally(() => {
this.$scope.history.loadingFileTree = false
if (selection.diff) {
selection.diff.loading = true
if (err.status !== -1) {
this._loadFileTreeRequestCanceller = null
} else {
this.$scope.history.loadingFileTree = false
}
})
}
@ -380,7 +383,8 @@ define([
return
}
this.$scope.history.selection.range.toV = this.$scope.history.updates[0].toV
let toV = this.$scope.history.updates[0].toV
let fromV = null
let indexOfLastUpdateNotByMe = 0
for (let i = 0; i < this.$scope.history.updates.length; i++) {
@ -394,19 +398,24 @@ define([
indexOfLastUpdateNotByMe = i
}
this.$scope.history.selection.range.fromV = this.$scope.history.updates[
indexOfLastUpdateNotByMe
].fromV
fromV = this.$scope.history.updates[indexOfLastUpdateNotByMe].fromV
this.selectVersionsForCompare(toV, fromV)
}
autoSelectLastVersionForPointInTime() {
this.$scope.history.selection.label = null
autoSelectVersionForPointInTime() {
if (this.$scope.history.updates.length === 0) {
return
}
return this.selectVersionForPointInTime(
this.$scope.history.updates[0].toV
)
let versionToSelect = this.$scope.history.updates[0].toV
let range = this.$scope.history.selection.range
if (
range.toV != null &&
range.fromV != null &&
range.toV === range.fromV
) {
versionToSelect = range.toV
}
this.selectVersionForPointInTime(versionToSelect)
}
autoSelectLastLabel() {
@ -429,14 +438,27 @@ define([
selectVersionForPointInTime(version) {
let selection = this.$scope.history.selection
selection.range.toV = version
selection.range.fromV = version
selection.update = this._getUpdateForVersion(version)
this.loadFileTreeForVersion(version)
if (
selection.range.toV !== version &&
selection.range.fromV !== version
) {
selection.range.toV = version
selection.range.fromV = version
this.loadFileTreeForVersion(version)
}
}
selectLabelFromUpdatesSelection() {
const selectedUpdate = this._getUpdateForVersion(
selectVersionsForCompare(toV, fromV) {
let range = this.$scope.history.selection.range
if (range.toV !== toV || range.fromV !== fromV) {
range.toV = toV
range.fromV = fromV
this.loadFileTreeDiff(toV, fromV)
}
}
autoSelectLabelForPointInTime() {
const selectedUpdate = this.getUpdateForVersion(
this.$scope.history.selection.range.toV
)
let nSelectedLabels = 0
@ -450,9 +472,7 @@ define([
this.autoSelectLastLabel()
// If the update has one label, select it
} else if (nSelectedLabels === 1) {
this.selectLabelForPointInTime(
this.$scope.history.selection.update.labels[0]
)
this.selectLabelForPointInTime(selectedUpdate.labels[0])
// If there are multiple labels for the update, select the latest
} else if (nSelectedLabels > 1) {
const sortedLabels = this.ide.$filter('orderBy')(
@ -479,19 +499,17 @@ define([
}
}
this.$scope.history.selection.label = labelToSelect
if (updateToSelect != null) {
this.selectVersionForPointInTime(updateToSelect.toV)
} else {
let selection = this.$scope.history.selection
selection.range.toV = labelToSelect.version
selection.range.fromV = labelToSelect.version
selection.update = null
this.loadFileTreeForVersion(labelToSelect.version)
}
}
_getUpdateForVersion(version) {
getUpdateForVersion(version) {
for (let update of this.$scope.history.updates) {
if (update.toV === version) {
return update
@ -501,17 +519,16 @@ define([
autoSelectLabelsForComparison() {
let labels = this.$scope.history.labels
let selection = this.$scope.history.selection
let nLabels = 0
if (Array.isArray(labels)) {
nLabels = labels.length
}
if (nLabels === 1) {
selection.range.toV = labels[0].version
selection.range.fromV = labels[0].version
if (nLabels === 0) {
// TODO better handling
} else if (nLabels === 1) {
this.selectVersionsForCompare(labels[0].version, labels[0].version)
} else if (nLabels > 1) {
selection.range.toV = labels[0].version
selection.range.fromV = labels[1].version
this.selectVersionsForCompare(labels[0].version, labels[1].version)
}
}
@ -573,26 +590,27 @@ define([
_loadLabels(labels, lastUpdateToV) {
let sortedLabels = this._sortLabelsByVersionAndDate(labels)
let nLabels = sortedLabels.length
let hasPseudoCurrentStateLabel = false
let needsPseudoCurrentStateLabel = false
if (sortedLabels.length > 0 && lastUpdateToV) {
hasPseudoCurrentStateLabel = sortedLabels[0].isPseudoCurrentStateLabel
if (lastUpdateToV) {
hasPseudoCurrentStateLabel =
nLabels > 0 ? sortedLabels[0].isPseudoCurrentStateLabel : false
if (hasPseudoCurrentStateLabel) {
needsPseudoCurrentStateLabel =
sortedLabels.length > 1
? sortedLabels[1].version !== lastUpdateToV
: false
nLabels > 1 ? sortedLabels[1].version !== lastUpdateToV : false
} else {
needsPseudoCurrentStateLabel =
sortedLabels[0].version !== lastUpdateToV
nLabels > 0 ? sortedLabels[0].version !== lastUpdateToV : true
}
if (needsPseudoCurrentStateLabel && !hasPseudoCurrentStateLabel) {
sortedLabels.unshift({
let pseudoCurrentStateLabel = {
id: '1',
isPseudoCurrentStateLabel: true,
version: lastUpdateToV,
created_at: new Date().toISOString()
})
}
sortedLabels.unshift(pseudoCurrentStateLabel)
} else if (
!needsPseudoCurrentStateLabel &&
hasPseudoCurrentStateLabel
@ -638,8 +656,6 @@ define([
reloadDiff() {
let { diff } = this.$scope.history.selection
// const { updates } = this.$scope.history.selection
// const { fromV, toV, pathname } = this._calculateDiffDataFromSelection()
const { range, pathname } = this.$scope.history.selection
const { fromV, toV } = range
@ -654,7 +670,7 @@ define([
diff.fromV === fromV &&
diff.toV === toV
) {
return this.ide.$q.when(true)
return
}
this.$scope.history.selection.diff = diff = {
@ -732,8 +748,11 @@ define([
}
_isLabelSelected(label) {
if (this.$scope.history.selection.label) {
return label.id === this.$scope.history.selection.label.id
if (label) {
return (
label.version <= this.$scope.history.selection.range.toV &&
label.version >= this.$scope.history.selection.range.fromV
)
} else {
return false
}

View file

@ -11,15 +11,18 @@
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
define(['base'], function(App) {
const historyEntriesListController = function($scope, $element, $attrs) {
const historyEntriesListController = function($scope, $element, $attrs, _) {
const ctrl = this
ctrl.$entryListViewportEl = null
ctrl.isDragging = false
const _isEntryElVisible = function($entryEl) {
const entryElTop = $entryEl.offset().top
const entryElBottom = entryElTop + $entryEl.outerHeight()
const entryListViewportElTop = ctrl.$entryListViewportEl.offset().top
const entryListViewportElBottom =
entryListViewportElTop + ctrl.$entryListViewportEl.height()
return (
entryElTop >= entryListViewportElTop &&
entryElBottom <= entryListViewportElBottom
@ -31,18 +34,86 @@ define(['base'], function(App) {
}
ctrl.onEntryLinked = function(entry, $entryEl) {
if (
entry.toV === ctrl.selectedHistoryVersion &&
!_isEntryElVisible($entryEl)
!ctrl.rangeSelectionEnabled &&
entry.toV === ctrl.selectedHistoryVersion
) {
return $scope.$applyAsync(() =>
ctrl.$entryListViewportEl.scrollTop(
_getScrollTopPosForEntry($entryEl)
)
)
$scope.$applyAsync(() => {
if (!_isEntryElVisible($entryEl)) {
ctrl.$entryListViewportEl.scrollTop(
_getScrollTopPosForEntry($entryEl)
)
}
})
}
}
ctrl.$onInit = () =>
(ctrl.$entryListViewportEl = $element.find('> .history-entries'))
ctrl.handleEntrySelect = entry => {
if (ctrl.rangeSelectionEnabled) {
ctrl.onRangeSelect({
selectedToV: entry.toV,
selectedFromV: entry.fromV
})
} else {
ctrl.onVersionSelect({ version: entry.toV })
}
}
ctrl.setRangeToV = toV => {
if (toV > ctrl.selectedHistoryRange.fromV) {
ctrl.onRangeSelect({
selectedToV: toV,
selectedFromV: ctrl.selectedHistoryRange.fromV
})
}
}
ctrl.setRangeFromV = fromV => {
if (fromV < ctrl.selectedHistoryRange.toV) {
ctrl.onRangeSelect({
selectedToV: ctrl.selectedHistoryRange.toV,
selectedFromV: fromV
})
}
}
ctrl.initHoveredRange = () => {
ctrl.hoveredHistoryRange = {
toV: ctrl.selectedHistoryRange.toV,
fromV: ctrl.selectedHistoryRange.fromV
}
}
ctrl.resetHoveredRange = () => {
ctrl.hoveredHistoryRange = { toV: null, fromV: null }
}
ctrl.setHoveredRangeToV = toV => {
if (toV > ctrl.hoveredHistoryRange.fromV) {
$scope.$applyAsync(() => (ctrl.hoveredHistoryRange.toV = toV))
}
}
ctrl.setHoveredRangeFromV = fromV => {
if (fromV < ctrl.hoveredHistoryRange.toV) {
$scope.$applyAsync(() => (ctrl.hoveredHistoryRange.fromV = fromV))
}
}
ctrl.onDraggingStart = () => {
$scope.$applyAsync(() => {
ctrl.isDragging = true
ctrl.initHoveredRange()
})
}
ctrl.onDraggingStop = (isValidDrop, boundary) => {
$scope.$applyAsync(() => {
ctrl.isDragging = false
if (!isValidDrop) {
if (boundary === 'toV') {
ctrl.setRangeToV(ctrl.hoveredHistoryRange.toV)
} else if (boundary === 'fromV') {
ctrl.setRangeFromV(ctrl.hoveredHistoryRange.fromV)
}
}
ctrl.resetHoveredRange()
})
}
ctrl.$onInit = () => {
ctrl.$entryListViewportEl = $element.find('> .history-entries')
ctrl.resetHoveredRange()
}
}
return App.component('historyEntriesList', {
@ -56,8 +127,11 @@ define(['base'], function(App) {
currentUser: '<',
freeHistoryLimitHit: '<',
currentUserIsOwner: '<',
selectedHistoryVersion: '<',
onEntrySelect: '&',
rangeSelectionEnabled: '<',
selectedHistoryVersion: '<?',
selectedHistoryRange: '<?',
onVersionSelect: '&',
onRangeSelect: '&',
onLabelDelete: '&'
},
controller: historyEntriesListController,

View file

@ -43,17 +43,67 @@ define([
(user != null ? user._id : undefined) ||
(user != null ? user.id : undefined)
const hue = ColorManager.getHueForUserId(curUserId) || 100
if (ctrl.entry.toV === ctrl.selectedHistoryVersion) {
if (ctrl.isEntrySelected() || ctrl.isEntryHoverSelected()) {
return { color: '#FFF' }
} else {
return { color: `hsl(${hue}, 70%, 50%)` }
}
}
ctrl.$onInit = () =>
ctrl.historyEntriesList.onEntryLinked(
ctrl.entry,
$element.find('> .history-entry')
ctrl.isEntrySelected = function() {
if (ctrl.rangeSelectionEnabled) {
return (
ctrl.entry.toV <= ctrl.selectedHistoryRange.toV &&
ctrl.entry.fromV >= ctrl.selectedHistoryRange.fromV
)
} else {
return ctrl.entry.toV === ctrl.selectedHistoryVersion
}
}
ctrl.isEntryHoverSelected = function() {
return (
ctrl.rangeSelectionEnabled &&
ctrl.entry.toV <= ctrl.hoveredHistoryRange.toV &&
ctrl.entry.fromV >= ctrl.hoveredHistoryRange.fromV
)
}
ctrl.onDraggingStart = () => {
ctrl.historyEntriesList.onDraggingStart()
}
ctrl.onDraggingStop = (isValidDrop, boundary) =>
ctrl.historyEntriesList.onDraggingStop(isValidDrop, boundary)
ctrl.onDrop = boundary => {
if (boundary === 'toV') {
$scope.$applyAsync(() =>
ctrl.historyEntriesList.setRangeToV(ctrl.entry.toV)
)
} else if (boundary === 'fromV') {
$scope.$applyAsync(() =>
ctrl.historyEntriesList.setRangeFromV(ctrl.entry.fromV)
)
}
}
ctrl.onOver = boundary => {
if (boundary === 'toV') {
$scope.$applyAsync(() =>
ctrl.historyEntriesList.setHoveredRangeToV(ctrl.entry.toV)
)
} else if (boundary === 'fromV') {
$scope.$applyAsync(() =>
ctrl.historyEntriesList.setHoveredRangeFromV(ctrl.entry.fromV)
)
}
}
ctrl.$onInit = () => {
ctrl.$entryEl = $element.find('> .history-entry')
ctrl.$entryDetailsEl = $element.find('.history-entry-details')
ctrl.$toVHandleEl = $element.find('.history-entry-toV-handle')
ctrl.$fromVHandleEl = $element.find('.history-entry-fromV-handle')
ctrl.historyEntriesList.onEntryLinked(ctrl.entry, ctrl.$entryEl)
}
}
return App.component('historyEntry', {
@ -61,7 +111,11 @@ define([
entry: '<',
currentUser: '<',
users: '<',
selectedHistoryVersion: '<',
rangeSelectionEnabled: '<',
isDragging: '<',
selectedHistoryVersion: '<?',
selectedHistoryRange: '<?',
hoveredHistoryRange: '<?',
onSelect: '&',
onLabelDelete: '&'
},

View file

@ -15,8 +15,125 @@ define([
'ide/colors/ColorManager',
'ide/history/util/displayNameForUser'
], function(App, ColorManager, displayNameForUser) {
const historyLabelsListController = function($scope, $element, $attrs) {
const historyLabelsListController = function($scope, $element, $attrs, _) {
const ctrl = this
ctrl.isDragging = false
ctrl.versionsWithLabels = []
$scope.$watchCollection('$ctrl.labels', function(labels) {
if (labels != null && labels.length > 0) {
const groupedLabelsHash = _.groupBy(labels, 'version')
ctrl.versionsWithLabels = _.map(
groupedLabelsHash,
(labels, version) => {
return {
version: parseInt(version, 10),
labels
}
}
)
}
})
ctrl.initHoveredRange = () => {
ctrl.hoveredHistoryRange = {
toV: ctrl.selectedHistoryRange.toV,
fromV: ctrl.selectedHistoryRange.fromV
}
}
ctrl.resetHoveredRange = () => {
ctrl.hoveredHistoryRange = { toV: null, fromV: null }
}
ctrl.setHoveredRangeToV = toV => {
if (toV >= ctrl.hoveredHistoryRange.fromV) {
ctrl.hoveredHistoryRange.toV = toV
}
}
ctrl.setHoveredRangeFromV = fromV => {
if (fromV <= ctrl.hoveredHistoryRange.toV) {
ctrl.hoveredHistoryRange.fromV = fromV
}
}
ctrl.isVersionSelected = function(version) {
if (ctrl.rangeSelectionEnabled) {
return (
version <= ctrl.selectedHistoryRange.toV &&
version >= ctrl.selectedHistoryRange.fromV
)
} else {
return version === ctrl.selectedHistoryVersion
}
}
ctrl.isVersionHoverSelected = function(version) {
return (
ctrl.rangeSelectionEnabled &&
version <= ctrl.hoveredHistoryRange.toV &&
version >= ctrl.hoveredHistoryRange.fromV
)
}
ctrl.onDraggingStart = () => {
$scope.$applyAsync(() => {
ctrl.isDragging = true
ctrl.initHoveredRange()
})
}
ctrl.onDraggingStop = (isValidDrop, boundary) => {
$scope.$applyAsync(() => {
if (!isValidDrop) {
if (boundary === 'toV') {
ctrl.setRangeToV(ctrl.hoveredHistoryRange.toV)
} else if (boundary === 'fromV') {
ctrl.setRangeFromV(ctrl.hoveredHistoryRange.fromV)
}
}
ctrl.isDragging = false
ctrl.resetHoveredRange()
})
}
ctrl.onDrop = (boundary, versionWithLabel) => {
if (boundary === 'toV') {
$scope.$applyAsync(() => ctrl.setRangeToV(versionWithLabel.version))
} else if (boundary === 'fromV') {
$scope.$applyAsync(() => ctrl.setRangeFromV(versionWithLabel.version))
}
}
ctrl.onOver = (boundary, versionWithLabel) => {
if (boundary === 'toV') {
$scope.$applyAsync(() =>
ctrl.setHoveredRangeToV(versionWithLabel.version)
)
} else if (boundary === 'fromV') {
$scope.$applyAsync(() =>
ctrl.setHoveredRangeFromV(versionWithLabel.version)
)
}
}
ctrl.handleVersionSelect = versionWithLabel => {
if (ctrl.rangeSelectionEnabled) {
// TODO
ctrl.onRangeSelect({
selectedToV: versionWithLabel.version,
selectedFromV: versionWithLabel.version
})
} else {
ctrl.onVersionSelect({ version: versionWithLabel.version })
}
}
ctrl.setRangeToV = version => {
if (version >= ctrl.selectedHistoryRange.fromV) {
ctrl.onRangeSelect({
selectedToV: version,
selectedFromV: ctrl.selectedHistoryRange.fromV
})
}
}
ctrl.setRangeFromV = version => {
if (version <= ctrl.selectedHistoryRange.toV) {
ctrl.onRangeSelect({
selectedToV: ctrl.selectedHistoryRange.toV,
selectedFromV: version
})
}
}
// 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.
@ -28,30 +145,37 @@ define([
return curUserId === id
})
ctrl.displayName = displayNameForUser
ctrl.getUserCSSStyle = function(user, label) {
ctrl.getUserCSSStyle = function(user, versionWithLabel) {
const curUserId =
(user != null ? user._id : undefined) ||
(user != null ? user.id : undefined)
const hue = ColorManager.getHueForUserId(curUserId) || 100
if (
label.id ===
(ctrl.selectedLabel != null ? ctrl.selectedLabel.id : undefined)
ctrl.isVersionSelected(versionWithLabel.version) ||
ctrl.isVersionHoverSelected(versionWithLabel.version)
) {
return { color: '#FFF' }
} else {
return { color: `hsl(${hue}, 70%, 50%)` }
}
}
ctrl.$onInit = () => {
ctrl.resetHoveredRange()
}
}
return App.component('historyLabelsList', {
bindings: {
labels: '<',
rangeSelectionEnabled: '<',
users: '<',
currentUser: '<',
isLoading: '<',
selectedLabel: '<',
onLabelSelect: '&',
selectedHistoryVersion: '<?',
selectedHistoryRange: '<?',
onVersionSelect: '&',
onRangeSelect: '&',
onLabelDelete: '&'
},
controller: historyLabelsListController,

View file

@ -1,87 +0,0 @@
/* eslint-disable
camelcase,
max-len,
no-return-assign,
no-undef,
*/
define(['base', 'ide/history/util/displayNameForUser'], function(
App,
displayNameForUser
) {
App.controller('HistoryCompareController', [
'$scope',
'$modal',
'ide',
'_',
function($scope, $modal, ide, _) {
$scope.projectUsers = []
$scope.versionsWithLabels = []
$scope.$watch('project.members', function(newVal) {
if (newVal != null) {
$scope.projectUsers = newVal.concat($scope.project.owner)
}
})
$scope.$watchCollection('history.labels', function(labels) {
if (labels != null && labels.length > 0) {
const groupedLabelsHash = _.groupBy(labels, 'version')
$scope.versionsWithLabels = _.map(
groupedLabelsHash,
(labels, version) => {
return {
version: parseInt(version, 10),
labels
}
}
)
}
})
$scope.loadMore = () => ide.historyManager.fetchNextBatchOfUpdates()
$scope.setHoverFrom = fromV => ide.historyManager.setHoverFrom(fromV)
$scope.setHoverTo = toV => ide.historyManager.setHoverTo(toV)
$scope.resetHover = () => ide.historyManager.resetHover()
$scope.select = (toV, fromV) => {
$scope.history.selection.range.toV = toV
$scope.history.selection.range.fromV = fromV
}
$scope.addLabelVersionToSelection = version => {
ide.historyManager.expandSelectionToVersion(version)
}
// 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.
$scope.getUserById = id =>
_.find($scope.projectUsers, function(user) {
let curUserId
if (user) {
curUserId = user._id || user.id
}
return curUserId === id
})
$scope.getDisplayNameById = id =>
displayNameForUser($scope.getUserById(id))
$scope.getDisplayNameForUser = user => displayNameForUser(user)
$scope.deleteLabel = labelDetails =>
$modal.open({
templateUrl: 'historyV2DeleteLabelModalTemplate',
controller: 'HistoryV2DeleteLabelModalController',
resolve: {
labelDetails() {
return labelDetails
}
}
})
}
])
})

View file

@ -35,11 +35,18 @@ define(['base', 'ide/history/util/displayNameForUser'], (
return ide.historyManager.fetchNextBatchOfUpdates()
}
$scope.handleEntrySelect = entry =>
ide.historyManager.selectVersionForPointInTime(entry.toV)
$scope.handleVersionSelect = version =>
$scope.$applyAsync(() =>
ide.historyManager.selectVersionForPointInTime(version)
)
$scope.handleLabelSelect = label =>
ide.historyManager.selectLabelForPointInTime(label)
$scope.handleRangeSelect = (selectedToV, selectedFromV) =>
$scope.$applyAsync(() =>
ide.historyManager.selectVersionsForCompare(
selectedToV,
selectedFromV
)
)
return ($scope.handleLabelDelete = labelDetails =>
$modal.open({

View file

@ -21,6 +21,9 @@ define(['base'], App =>
($scope, $modal, ide, event_tracking, waitFor) => {
let openEntity
$scope.currentUpdate = null
$scope.currentLabel = null
$scope.restoreState = {
inflight: false,
error: false
@ -50,6 +53,22 @@ define(['base'], App =>
}
})
$scope.$watch('history.viewMode', (newVal, oldVal) => {
if (newVal != null && newVal !== oldVal) {
$scope.currentUpdate = ide.historyManager.getUpdateForVersion(newVal)
}
})
$scope.$watch('history.selection.range.toV', (newVal, oldVal) => {
if (
newVal != null &&
newVal !== oldVal &&
$scope.history.viewMode === $scope.HistoryViewModes.POINT_IN_TIME
) {
$scope.currentUpdate = ide.historyManager.getUpdateForVersion(newVal)
}
})
$scope.toggleHistoryViewMode = () => {
ide.historyManager.toggleHistoryViewMode()
}

View file

@ -0,0 +1,32 @@
define(['base'], App =>
App.directive('historyDraggableBoundary', () => ({
scope: {
historyDraggableBoundary: '@',
historyDraggableBoundaryOnDragStart: '&',
historyDraggableBoundaryOnDragStop: '&'
},
restrict: 'A',
link(scope, element, attrs) {
element.data('selectionBoundary', {
boundary: scope.historyDraggableBoundary
})
element.draggable({
axis: 'y',
opacity: false,
helper: 'clone',
revert: true,
scroll: true,
cursor: 'row-resize',
start(e, ui) {
ui.helper.data('wasProperlyDropped', false)
scope.historyDraggableBoundaryOnDragStart()
},
stop(e, ui) {
scope.historyDraggableBoundaryOnDragStop({
isValidDrop: ui.helper.data('wasProperlyDropped'),
boundary: scope.historyDraggableBoundary
})
}
})
}
})))

View file

@ -0,0 +1,25 @@
define(['base'], App =>
App.directive('historyDroppableArea', () => ({
scope: {
historyDroppableAreaOnDrop: '&',
historyDroppableAreaOnOver: '&',
historyDroppableAreaOnOut: '&'
},
restrict: 'A',
link(scope, element, attrs) {
element.droppable({
accept: e => '.history-entry-toV-handle, .history-entry-fromV-handle',
drop: (e, ui) => {
const draggedBoundary = ui.draggable.data('selectionBoundary')
.boundary
ui.helper.data('wasProperlyDropped', true)
scope.historyDroppableAreaOnDrop({ boundary: draggedBoundary })
},
over: (e, ui) => {
const draggedBoundary = ui.draggable.data('selectionBoundary')
.boundary
scope.historyDroppableAreaOnOver({ boundary: draggedBoundary })
}
})
}
})))

View file

@ -69,6 +69,10 @@
color: @history-base-color;
height: 100%;
background-color: @history-base-bg;
position: relative;
&.history-entries-dragging {
cursor: row-resize;
}
}
.history-entry-day {
@ -79,18 +83,82 @@
line-height: 1;
}
.history-entry-toV-handle,
.history-entry-fromV-handle {
position: absolute;
background-color: @history-entry-handle-bg;
height: @history-entry-handle-height;
top: 0;
left: 0;
right: 0;
z-index: 2;
cursor: row-resize;
&.ui-draggable-dragging {
opacity: 0;
}
&::after {
content: '\00b7\00b7\00b7\00b7';
position: absolute;
text-align: center;
-webkit-font-smoothing: antialiased;
width: 100%;
font-size: 20px;
color: #FFF;
height: @history-entry-handle-height;
line-height: @history-entry-handle-height / 2;
}
}
.history-entry-fromV-handle {
top: auto;
bottom: 0;
}
.history-entry-details {
position: relative;
background-color: #FFF;
margin-bottom: 2px;
border-bottom: solid 2px @history-base-bg;
padding: 5px 10px;
cursor: pointer;
}
.history-entry-selected &,
.history-entry-label-selected & {
.history-version-with-label {
.history-entry-details;
padding: 7px 10px;
}
.history-entry-selected .history-entry-details,
.history-version-with-label-selected & {
background-color: @history-entry-selected-bg;
color: #FFF;
}
}
.history-entry-hover-selected .history-entry-details,
.history-entry-hover-selected.history-entry-selected .history-entry-details,
.history-version-with-label-hover-selected &,
.history-version-with-label-hover-selected.history-entry-selected &, {
background-color: tint(@history-entry-selected-bg, 20%);
color: #FFF;
}
.history-entry-selected-to .history-entry-details,
.history-entry-hover-selected-to .history-entry-details,
.history-version-with-label-selected-to &,
.history-version-with-label-hover-selected-to & {
padding-top: @history-entry-handle-height + 5px;
}
.history-entry-selected-from .history-entry-details,
.history-entry-hover-selected-from .history-entry-details,
.history-version-with-label-selected-from &,
.history-version-with-label-hover-selected-from & {
padding-bottom: @history-entry-handle-height + 5px;
}
.history-label {
display: inline-block;
color: @history-entry-label-color;
@ -99,12 +167,16 @@
margin-right: 10px;
white-space: nowrap;
.history-entry-selected &,
.history-entry-label-selected & {
.history-entry-hover-selected &,
.history-version-with-label-selected &,
.history-version-with-label-hover-selected & {
color: @history-entry-selected-label-color;
}
&.history-label-pseudo-current-state {
.history-entry-selected &,
.history-entry-label-selected & {
.history-entry-hover-selected &,
.history-version-with-label-selected &,
.history-version-with-label-hover-selected & {
color: @history-entry-selected-pseudo-label-color;
}
}
@ -119,7 +191,9 @@
}
.history-entry-selected &,
.history-entry-label-selected & {
.history-entry-hover-selected &,
.history-version-with-label-selected &,
.history-version-with-label-hover-selected & {
background-color: @history-entry-selected-label-bg-color;
}
}
@ -142,7 +216,9 @@
&:hover {
background-color: darken(@history-entry-label-bg-color, 8%);
.history-entry-selected &,
.history-entry-label-selected & {
.history-entry-hover-selected &,
.history-version-with-label-selected &,
.history-version-with-label-hover-selected & {
background-color: darken(@history-entry-selected-label-bg-color, 8%);
}
}
@ -181,7 +257,8 @@
font-weight: bold;
word-break: break-all;
.history-entry-selected &,
.history-entry-label-selected & {
.history-entry-hover-selected &,
.history-version-with-label-selected & {
color: #FFF;
}
}
@ -223,14 +300,6 @@
.history-labels-list-compare {
background-color: transparent;
}
.history-entry-label {
.history-entry-details;
padding: 7px 10px;
&.history-entry-label-selected {
background-color: @history-entry-selected-bg;
color: #FFF;
}
}
.history-file-tree-inner {
.full-size;

View file

@ -1030,6 +1030,8 @@
@history-entry-selected-pseudo-label-color: @green;
@history-entry-day-bg : @gray;
@history-entry-selected-bg : @red;
@history-entry-handle-bg : darken(@history-entry-selected-bg, 10%);
@history-entry-handle-height : 8px;
@history-base-color : @gray-light;
@history-highlight-color : @gray;
@history-toolbar-bg-color : @toolbar-alt-bg-color;

View file

@ -346,6 +346,8 @@
@history-entry-selected-pseudo-label-color: @ol-green;
@history-entry-day-bg : @ol-blue-gray-2;
@history-entry-selected-bg : @ol-green;
@history-entry-handle-bg : darken(@ol-green, 10%);
@history-entry-handle-height : 8px;
@history-base-color : @ol-blue-gray-2;
@history-highlight-color : @ol-type-color;
@history-toolbar-bg-color : @editor-toolbar-bg;

View file

@ -34,8 +34,6 @@ define(['ide/history/HistoryV2Manager'], HistoryV2Manager =>
},
diff: null,
files: [],
update: null,
label: null,
file: null
},
error: null,