Merge pull request #15211 from overleaf/em-remove-sl-history-frontend

Remove SL history frontend

GitOrigin-RevId: f6f98db7792b47f365b46da14fc823ee58787cdb
This commit is contained in:
Eric Mc Sween
2023-10-13 08:03:51 +00:00
committed by Copybot
parent 3a3ec856c2
commit 5b08d76817
23 changed files with 5 additions and 1272 deletions
@@ -18,10 +18,7 @@
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
import './directives/fileEntity'
import './controllers/FileTreeController'
import './controllers/FileTreeEntityController'
import './controllers/FileTreeFolderController'
import '../../features/file-tree/controllers/file-tree-controller'
import { debugConsole } from '@/utils/debugging'
let FileTreeManager
@@ -144,22 +141,6 @@ export default FileTreeManager = class FileTreeManager {
return (entity.selected = true)
}
toggleMultiSelectEntity(entity) {
entity.multiSelected = !entity.multiSelected
this.$scope.multiSelectedCount = this.multiSelectedCount()
this.$scope.editor.multiSelectedCount = this.$scope.multiSelectedCount
}
multiSelectedCount() {
let count = 0
this.forEachEntity(function (entity) {
if (entity.multiSelected) {
return count++
}
})
return count
}
getMultiSelectedEntities() {
const entities = []
this.forEachEntity(function (e) {
@@ -223,14 +204,6 @@ export default FileTreeManager = class FileTreeManager {
return (this.$scope.multiSelectedCount = 0)
}
multiSelectSelectedEntity() {
const entity = this.findSelectedEntity()
if (entity) {
entity.multiSelected = true
}
this.$scope.multiSelectedCount = this.multiSelectedCount()
}
existsInFolder(folder_id, name) {
const folder = this.findEntityById(folder_id)
if (folder == null) {
@@ -1,52 +0,0 @@
import App from '../../../base'
import iconTypeFromName from '../util/iconTypeFromName'
App.controller(
'FileTreeEntityController',
function ($scope, ide, $modal, $element) {
$scope.MAX_DEPTH = 8
$scope.select = function (e) {
if (e.ctrlKey || e.metaKey) {
e.stopPropagation()
const initialMultiSelectCount = ide.fileTreeManager.multiSelectedCount()
ide.fileTreeManager.toggleMultiSelectEntity($scope.entity)
if (initialMultiSelectCount === 0) {
// On first multi selection, also include the current active/open file.
return ide.fileTreeManager.multiSelectSelectedEntity()
}
} else {
ide.fileTreeManager.selectEntity($scope.entity)
return $scope.$emit('entity:selected', $scope.entity)
}
}
if ($scope.entity.type === 'doc') {
$scope.$watch('entity.selected', function (isSelected) {
if (isSelected) {
$scope.$emit('entity-file:selected', $scope.entity)
if (!_isEntryElVisible($element)) {
$scope.$applyAsync(function () {
$element[0].scrollIntoView()
})
}
}
})
}
function _isEntryElVisible($entryEl) {
const viewportEl = $('.file-tree-list')
const entryElTop = $entryEl.offset().top
const entryElBottom = entryElTop + $entryEl.outerHeight()
const entryListViewportElTop = viewportEl.offset().top
const entryListViewportElBottom =
entryListViewportElTop + viewportEl.height()
return (
entryElTop >= entryListViewportElTop &&
entryElBottom <= entryListViewportElBottom
)
}
$scope.iconTypeFromName = iconTypeFromName
}
)
@@ -1,23 +0,0 @@
import App from '../../../base'
export default App.controller(
'FileTreeFolderController',
function ($scope, ide, $modal, localStorage) {
$scope.expanded =
localStorage(`folder.${$scope.entity.id}.expanded`) || false
$scope.toggleExpanded = function () {
$scope.expanded = !$scope.expanded
$scope._storeCurrentStateInLocalStorage()
}
$scope.$on('entity-file:selected', function () {
$scope.expanded = true
$scope._storeCurrentStateInLocalStorage()
})
$scope._storeCurrentStateInLocalStorage = function () {
localStorage(`folder.${$scope.entity.id}.expanded`, $scope.expanded)
}
}
)
@@ -1,43 +0,0 @@
/* eslint-disable
max-len,
no-return-assign,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
import App from '../../../base'
export default App.directive('fileEntity', RecursionHelper => ({
restrict: 'E',
scope: {
entity: '=',
permissions: '=',
depth: '=?',
},
templateUrl: 'entityListItemTemplate',
compile(element) {
return RecursionHelper.compile(
element,
function (scope, element, attrs, ctrl) {
// Don't freak out if we're already in an apply callback
scope.$originalApply = scope.$apply
return (scope.$apply = function (fn) {
if (fn == null) {
fn = function () {}
}
const phase = this.$root.$$phase
if (phase === '$apply' || phase === '$digest') {
return fn()
} else {
return this.$originalApply(fn)
}
})
}
)
},
}))
@@ -1,385 +0,0 @@
/* eslint-disable
camelcase,
max-len,
no-return-assign,
no-unused-vars,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* DS206: Consider reworking classes to avoid initClass
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
import moment from 'moment'
import ColorManager from '../colors/ColorManager'
import displayNameForUser from './util/displayNameForUser'
import './controllers/HistoryListController'
import './controllers/HistoryDiffController'
import './directives/infiniteScroll'
let HistoryManager
export default HistoryManager = (function () {
HistoryManager = class HistoryManager {
static initClass() {
this.prototype.BATCH_SIZE = 10
}
constructor(ide, $scope) {
this.ide = ide
this.$scope = $scope
this.reset()
this.$scope.toggleHistory = () => {
if (this.$scope.ui.view === 'history') {
return this.hide()
} else {
return this.show()
}
}
this.$scope.$watch('history.selection.updates', updates => {
if (updates != null && updates.length > 0) {
this._selectDocFromUpdates()
return this.reloadDiff()
}
})
this.$scope.$on('entity:selected', (event, entity) => {
if (this.$scope.ui.view === 'history' && entity.type === 'doc') {
this.$scope.history.selection.doc = entity
return this.reloadDiff()
}
})
}
show() {
this.$scope.ui.view = 'history'
return this.reset()
}
hide() {
this.$scope.ui.view = 'editor'
// Make sure we run the 'open' logic for whatever is currently selected
return this.$scope.$emit(
'entity:selected',
this.ide.fileTreeManager.findSelectedEntity()
)
}
reset() {
return (this.$scope.history = {
updates: [],
nextBeforeTimestamp: null,
atEnd: false,
selection: {
updates: [],
doc: null,
range: {
fromV: null,
toV: null,
start_ts: null,
end_ts: null,
},
},
diff: null,
})
}
autoSelectRecentUpdates() {
if (this.$scope.history.updates.length === 0) {
return
}
this.$scope.history.updates[0].selectedTo = true
let indexOfLastUpdateNotByMe = 0
for (let i = 0; i < this.$scope.history.updates.length; i++) {
const update = this.$scope.history.updates[i]
if (this._updateContainsUserId(update, this.$scope.user.id)) {
break
}
indexOfLastUpdateNotByMe = i
}
return (this.$scope.history.updates[
indexOfLastUpdateNotByMe
].selectedFrom = true)
}
fetchNextBatchOfUpdates() {
let url = `/project/${this.ide.project_id}/updates?min_count=${this.BATCH_SIZE}`
if (this.$scope.history.nextBeforeTimestamp != null) {
url += `&before=${this.$scope.history.nextBeforeTimestamp}`
}
this.$scope.history.loading = true
return this.ide.$http.get(url).then(response => {
const { data } = response
this._loadUpdates(data.updates)
this.$scope.history.nextBeforeTimestamp = data.nextBeforeTimestamp
if (data.nextBeforeTimestamp == null) {
this.$scope.history.atEnd = true
}
return (this.$scope.history.loading = false)
})
}
reloadDiff() {
let { diff } = this.$scope.history
const { updates, doc } = this.$scope.history.selection
const { fromV, toV, start_ts, end_ts } =
this._calculateRangeFromSelection()
if (doc == null) {
return
}
if (
diff != null &&
diff.doc === doc &&
diff.fromV === fromV &&
diff.toV === toV
) {
return
}
this.$scope.history.diff = diff = {
fromV,
toV,
start_ts,
end_ts,
doc,
error: false,
pathname: doc.name,
}
if (!doc.deleted) {
diff.loading = true
let url = `/project/${this.$scope.project_id}/doc/${diff.doc.id}/diff`
if (diff.fromV != null && diff.toV != null) {
url += `?from=${diff.fromV}&to=${diff.toV}`
}
return this.ide.$http
.get(url)
.then(response => {
const { data } = response
diff.loading = false
const { text, highlights } = this._parseDiff(data)
diff.text = text
return (diff.highlights = highlights)
})
.catch(function () {
diff.loading = false
return (diff.error = true)
})
} else {
diff.deleted = true
diff.restoreInProgress = false
diff.restoreDeletedSuccess = false
return (diff.restoredDocNewId = null)
}
}
restoreDeletedDoc(doc) {
const url = `/project/${this.$scope.project_id}/doc/${doc.id}/restore`
return this.ide.$http.post(url, {
name: doc.name,
_csrf: window.csrfToken,
})
}
restoreDiff(diff) {
const url = `/project/${this.$scope.project_id}/doc/${diff.doc.id}/version/${diff.fromV}/restore`
return this.ide.$http.post(url, { _csrf: window.csrfToken })
}
_parseDiff(diff) {
let row = 0
let column = 0
const highlights = []
let text = ''
const iterable = diff.diff || []
for (let i = 0; i < iterable.length; i++) {
let endColumn, endRow
const entry = iterable[i]
let content = entry.u || entry.i || entry.d
if (!content) {
content = ''
}
text += content
const lines = content.split('\n')
const startRow = row
const startColumn = column
if (lines.length > 1) {
endRow = startRow + lines.length - 1
endColumn = lines[lines.length - 1].length
} else {
endRow = startRow
endColumn = startColumn + lines[0].length
}
row = endRow
column = endColumn
const range = {
start: {
row: startRow,
column: startColumn,
},
end: {
row: endRow,
column: endColumn,
},
}
if (entry.i != null || entry.d != null) {
const name = displayNameForUser(entry.meta.user)
const date = moment(entry.meta.end_ts).format('Do MMM YYYY, h:mm a')
if (entry.i != null) {
highlights.push({
label: `Added by ${name} on ${date}`,
highlight: range,
hue: ColorManager.getHueForUserId(
entry.meta.user != null ? entry.meta.user.id : undefined
),
})
} else if (entry.d != null) {
highlights.push({
label: `Deleted by ${name} on ${date}`,
strikeThrough: range,
hue: ColorManager.getHueForUserId(
entry.meta.user != null ? entry.meta.user.id : undefined
),
})
}
}
}
return { text, highlights }
}
_loadUpdates(updates) {
if (updates == null) {
updates = []
}
let previousUpdate =
this.$scope.history.updates[this.$scope.history.updates.length - 1]
for (const update of Array.from(updates)) {
update.pathnames = [] // Used for display
const object = update.docs || {}
for (const doc_id in object) {
const doc = object[doc_id]
doc.entity = this.ide.fileTreeManager.findEntityById(doc_id, {
includeDeleted: true,
})
update.pathnames.push(doc.entity.name)
}
for (const user of Array.from(update.meta.users || [])) {
if (user != null) {
user.hue = ColorManager.getHueForUserId(user.id)
}
}
if (
previousUpdate == null ||
!moment(previousUpdate.meta.end_ts).isSame(update.meta.end_ts, 'day')
) {
update.meta.first_in_day = true
}
update.selectedFrom = false
update.selectedTo = false
update.inSelection = false
previousUpdate = update
}
const firstLoad = this.$scope.history.updates.length === 0
this.$scope.history.updates = this.$scope.history.updates.concat(updates)
if (firstLoad) {
return this.autoSelectRecentUpdates()
}
}
_calculateRangeFromSelection() {
let end_ts, start_ts, toV
let fromV = (toV = start_ts = end_ts = null)
const selected_doc_id =
this.$scope.history.selection.doc != null
? this.$scope.history.selection.doc.id
: undefined
for (const update of Array.from(
this.$scope.history.selection.updates || []
)) {
for (const doc_id in update.docs) {
const doc = update.docs[doc_id]
if (doc_id === selected_doc_id) {
if (fromV != null && toV != null) {
fromV = Math.min(fromV, doc.fromV)
toV = Math.max(toV, doc.toV)
start_ts = Math.min(start_ts, update.meta.start_ts)
end_ts = Math.max(end_ts, update.meta.end_ts)
} else {
;({ fromV } = doc)
;({ toV } = doc)
;({ start_ts } = update.meta)
;({ end_ts } = update.meta)
}
break
}
}
}
return { fromV, toV, start_ts, end_ts }
}
// Set the track changes selected doc to one of the docs in the range
// of currently selected updates. If we already have a selected doc
// then prefer this one if present.
_selectDocFromUpdates() {
let doc, doc_id
const affected_docs = {}
for (const update of Array.from(this.$scope.history.selection.updates)) {
for (doc_id in update.docs) {
doc = update.docs[doc_id]
affected_docs[doc_id] = doc.entity
}
}
let selected_doc = this.$scope.history.selection.doc
if (selected_doc != null && affected_docs[selected_doc.id] != null) {
// Selected doc is already open
} else {
const doc_ids = Object.keys(affected_docs)
if (doc_ids.length > 0) {
const doc_id = doc_ids[0]
doc = affected_docs[doc_id]
selected_doc = doc
}
}
this.$scope.history.selection.doc = selected_doc
return this.ide.fileTreeManager.selectEntity(selected_doc)
}
_updateContainsUserId(update, user_id) {
for (const user of Array.from(update.meta.users)) {
if ((user != null ? user.id : undefined) === user_id) {
return true
}
}
return false
}
}
HistoryManager.initClass()
return HistoryManager
})()
@@ -133,7 +133,6 @@ export default HistoryManager = (function () {
hardReset() {
this.$scope.history = {
isV2: true,
updates: [],
viewMode: this._getViewModeUserPref(),
nextBeforeTimestamp: null,
@@ -1,67 +0,0 @@
/* eslint-disable
camelcase,
max-len,
no-return-assign,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
import App from '../../../base'
App.controller(
'HistoryDiffController',
function ($scope, $modal, ide, eventTracking) {
$scope.restoreDeletedDoc = function () {
eventTracking.sendMB('history-restore-deleted')
$scope.history.diff.restoreInProgress = true
return ide.historyManager
.restoreDeletedDoc($scope.history.diff.doc)
.then(function (response) {
const { data } = response
$scope.history.diff.restoredDocNewId = data.doc_id
$scope.history.diff.restoreInProgress = false
return ($scope.history.diff.restoreDeletedSuccess = true)
})
}
$scope.openRestoreDiffModal = function () {
eventTracking.sendMB('history-restore-modal')
return $modal.open({
templateUrl: 'historyRestoreDiffModalTemplate',
controller: 'HistoryRestoreDiffModalController',
resolve: {
diff() {
return $scope.history.diff
},
},
})
}
return ($scope.backToEditorAfterRestore = () =>
ide.editorManager.openDoc({ id: $scope.history.diff.restoredDocNewId }))
}
)
export default App.controller(
'HistoryRestoreDiffModalController',
function ($scope, $modalInstance, diff, ide, eventTracking) {
$scope.state = { inflight: false }
$scope.diff = diff
$scope.restore = function () {
eventTracking.sendMB('history-restored')
$scope.state.inflight = true
return ide.historyManager.restoreDiff(diff).then(function () {
$scope.state.inflight = false
$modalInstance.close()
return ide.editorManager.openDoc(diff.doc)
})
}
return ($scope.cancel = () => $modalInstance.dismiss())
}
)
@@ -1,218 +0,0 @@
import _ from 'lodash'
/* eslint-disable
camelcase,
max-len,
no-return-assign,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* DS205: Consider reworking code to avoid use of IIFEs
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
import App from '../../../base'
import displayNameForUser from '../util/displayNameForUser'
App.controller('HistoryListController', function ($scope, $modal, ide) {
$scope.hoveringOverListSelectors = false
$scope.projectUsers = []
$scope.$watch('project.members', function (newVal) {
if (newVal != null) {
return ($scope.projectUsers = newVal.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.
const _getUserById = id =>
_.find($scope.projectUsers, function (user) {
const curUserId =
(user != null ? user._id : undefined) ||
(user != null ? user.id : undefined)
return curUserId === id
})
$scope.getDisplayNameById = id => displayNameForUser(_getUserById(id))
$scope.deleteLabel = labelDetails =>
$modal.open({
templateUrl: 'historyV2DeleteLabelModalTemplate',
controller: 'HistoryV2DeleteLabelModalController',
resolve: {
labelDetails() {
return labelDetails
},
},
})
$scope.loadMore = () => {
return ide.historyManager.fetchNextBatchOfUpdates()
}
$scope.recalculateSelectedUpdates = function () {
let beforeSelection = true
let afterSelection = false
$scope.history.selection.updates = []
return (() => {
const result = []
for (const update of Array.from($scope.history.updates)) {
// replacing this declaration with `let` introduces a bug in history point selection:
// https://github.com/overleaf/overleaf/issues/1035
// eslint-disable-next-line no-var
var inSelection
if (update.selectedTo) {
inSelection = true
beforeSelection = false
}
update.beforeSelection = beforeSelection
update.inSelection = inSelection
update.afterSelection = afterSelection
if (inSelection) {
$scope.history.selection.updates.push(update)
}
if (update.selectedFrom) {
inSelection = false
result.push((afterSelection = true))
} else {
result.push(undefined)
}
}
return result
})()
}
$scope.recalculateHoveredUpdates = function () {
let inHoverSelection
let hoverSelectedFrom = false
let hoverSelectedTo = false
for (const update of Array.from($scope.history.updates)) {
// Figure out whether the to or from selector is hovered over
if (update.hoverSelectedFrom) {
hoverSelectedFrom = true
}
if (update.hoverSelectedTo) {
hoverSelectedTo = true
}
}
if (hoverSelectedFrom) {
// We want to 'hover select' everything between hoverSelectedFrom and selectedTo
inHoverSelection = false
for (const update of Array.from($scope.history.updates)) {
if (update.selectedTo) {
update.hoverSelectedTo = true
inHoverSelection = true
}
update.inHoverSelection = inHoverSelection
if (update.hoverSelectedFrom) {
inHoverSelection = false
}
}
}
if (hoverSelectedTo) {
// We want to 'hover select' everything between hoverSelectedTo and selectedFrom
inHoverSelection = false
return (() => {
const result = []
for (const update of Array.from($scope.history.updates)) {
if (update.hoverSelectedTo) {
inHoverSelection = true
}
update.inHoverSelection = inHoverSelection
if (update.selectedFrom) {
update.hoverSelectedFrom = true
result.push((inHoverSelection = false))
} else {
result.push(undefined)
}
}
return result
})()
}
}
$scope.resetHoverState = () =>
(() => {
const result = []
for (const update of Array.from($scope.history.updates)) {
delete update.hoverSelectedFrom
delete update.hoverSelectedTo
result.push(delete update.inHoverSelection)
}
return result
})()
return $scope.$watch('history.updates.length', () =>
$scope.recalculateSelectedUpdates()
)
})
export default App.controller(
'HistoryListItemController',
function ($scope, eventTracking) {
$scope.$watch(
'update.selectedFrom',
function (selectedFrom, oldSelectedFrom) {
if (selectedFrom) {
for (const update of Array.from($scope.history.updates)) {
if (update !== $scope.update) {
update.selectedFrom = false
}
}
return $scope.recalculateSelectedUpdates()
}
}
)
$scope.$watch('update.selectedTo', function (selectedTo, oldSelectedTo) {
if (selectedTo) {
for (const update of Array.from($scope.history.updates)) {
if (update !== $scope.update) {
update.selectedTo = false
}
}
return $scope.recalculateSelectedUpdates()
}
})
$scope.select = function () {
eventTracking.sendMB('history-view-change')
$scope.update.selectedTo = true
return ($scope.update.selectedFrom = true)
}
$scope.mouseOverSelectedFrom = function () {
$scope.history.hoveringOverListSelectors = true
$scope.update.hoverSelectedFrom = true
return $scope.recalculateHoveredUpdates()
}
$scope.mouseOutSelectedFrom = function () {
$scope.history.hoveringOverListSelectors = false
return $scope.resetHoverState()
}
$scope.mouseOverSelectedTo = function () {
$scope.history.hoveringOverListSelectors = true
$scope.update.hoverSelectedTo = true
return $scope.recalculateHoveredUpdates()
}
$scope.mouseOutSelectedTo = function () {
$scope.history.hoveringOverListSelectors = false
return $scope.resetHoverState()
}
return ($scope.displayName = displayNameForUser)
}
)