Merge pull request #12636 from overleaf/td-history-autoselect-file

Simplify file autoselect in React history view

GitOrigin-RevId: 43f070b26ab0a75af13063712b5cf05778da4064
This commit is contained in:
Tim Down 2023-04-14 12:57:41 +02:00 committed by Copybot
parent 38998afa8e
commit 23b9c078b8
6 changed files with 64 additions and 176 deletions

View file

@ -37,8 +37,6 @@ function useHistory() {
const [loadingFileTree, setLoadingFileTree] =
useState<HistoryContextValue['loadingFileTree']>(true)
// eslint-disable-next-line no-unused-vars
const [viewMode, setViewMode] =
useState<HistoryContextValue['viewMode']>('point_in_time')
const [nextBeforeTimestamp, setNextBeforeTimestamp] =
useState<HistoryContextValue['nextBeforeTimestamp']>()
const [atEnd, setAtEnd] = useState<HistoryContextValue['atEnd']>(false)
@ -50,21 +48,6 @@ function useHistory() {
// eslint-disable-next-line no-unused-vars
const [userHasFullFeature, setUserHasFullFeature] =
useState<HistoryContextValue['userHasFullFeature']>(undefined)
const [selection, setSelection] = useState<HistoryContextValue['selection']>({
docs: {},
pathname: null,
range: {
fromV: null,
toV: null,
},
hoveredRange: {
fromV: null,
toV: null,
},
diff: null,
files: [],
file: null,
})
/* eslint-enable no-unused-vars */
const fetchNextBatchOfUpdates = useCallback(() => {
@ -180,14 +163,7 @@ function useHistory() {
const { fromV, toV } = updateSelection.update
diffFiles(projectId, fromV, toV).then(({ diff: files }) => {
const defaultSelection = autoSelectFile(
files,
selection,
viewMode,
updates
)
// TODO Infer default file sensibly
const pathname = defaultSelection.pathname
const pathname = autoSelectFile(files, updateSelection, updates)
const newFiles = files.map(file => {
if (isFileRenamed(file) && file.newPathname) {
return renamePathnameKey(file)
@ -197,33 +173,17 @@ function useHistory() {
})
setFileSelection({ files: newFiles, pathname })
})
}, [updateSelection, projectId, selection, updates, viewMode])
}, [updateSelection, projectId, updates])
useEffect(() => {
// Set update selection if there isn't one
if (updates.length && !updateSelection) {
setUpdateSelection({
update: {
...updates[0],
// Set fromV and toV for initial load as the latest version
// This is to mimic angular history behaviour when selecting default pathname on initial history load
fromV: updates[0].toV,
},
update: updates[0],
comparing: false,
})
}
// Set default selection if there isn't one
if (updates.length && selection.range.fromV === null) {
setSelection({
...selection,
range: {
fromV: updates[0].toV,
toV: updates[0].toV,
},
})
}
}, [setUpdateSelection, updateSelection, updates, selection])
}, [setUpdateSelection, updateSelection, updates])
const value = useMemo<HistoryContextValue>(
() => ({
@ -234,10 +194,8 @@ function useHistory() {
labels,
loadingFileTree,
nextBeforeTimestamp,
selection,
updates,
userHasFullFeature,
viewMode,
projectId,
fileSelection,
setFileSelection,
@ -252,10 +210,8 @@ function useHistory() {
labels,
loadingFileTree,
nextBeforeTimestamp,
selection,
updates,
userHasFullFeature,
viewMode,
projectId,
fileSelection,
setFileSelection,

View file

@ -2,16 +2,19 @@ import { useCallback, useMemo } from 'react'
import { useHistoryContext } from '../history-context'
export function useFileTreeItemSelection(pathname: string) {
const { fileSelection, setFileSelection, selection } = useHistoryContext()
const { fileSelection, setFileSelection } = useHistoryContext()
const handleClick = useCallback(() => {
if (pathname !== fileSelection?.pathname) {
if (!fileSelection) {
return
}
if (pathname !== fileSelection.pathname) {
setFileSelection({
files: fileSelection?.files || selection.files,
files: fileSelection.files,
pathname,
})
}
}, [fileSelection, pathname, selection, setFileSelection])
}, [fileSelection, pathname, setFileSelection])
const isSelected = useMemo(
() => fileSelection?.pathname === pathname,

View file

@ -1,18 +1,14 @@
import { Nullable } from '../../../../../../types/utils'
import { LoadedUpdate, UpdateSelection } from '../../services/types/update'
import { LoadedLabel } from '../../services/types/label'
import { Selection } from '../../../../../../types/history/selection'
import { FileSelection } from '../../services/types/file'
import { ViewMode } from '../../services/types/view-mode'
export type HistoryContextValue = {
updates: LoadedUpdate[]
viewMode: ViewMode
nextBeforeTimestamp: number | undefined
atEnd: boolean
userHasFullFeature: boolean | undefined
freeHistoryLimitHit: boolean
selection: Selection
isLoading: boolean
error: Nullable<unknown>
labels: Nullable<LoadedLabel[]>

View file

@ -1 +0,0 @@
export type ViewMode = 'compare' | 'point_in_time'

View file

@ -5,23 +5,6 @@ import type { FileDiff } from '../services/types/file'
import type { DiffOperation } from '../services/types/diff-operation'
import type { Update } from '../services/types/update'
function selectFile(
file: Nullable<FileDiff>,
selection: HistoryContextValue['selection']
) {
if (file === null) {
return selection
}
const newSelection = {
...selection,
pathname: file.pathname,
file,
}
return newSelection
}
function getUpdateForVersion(
version: Update['toV'],
updates: HistoryContextValue['updates']
@ -35,13 +18,19 @@ type FileWithOps = {
}
function getFilesWithOps(
viewMode: HistoryContextValue['viewMode'],
selection: HistoryContextValue['selection'],
files: FileDiff[],
updateSelection: HistoryContextValue['updateSelection'],
updates: HistoryContextValue['updates']
): FileWithOps[] {
if (selection.range.toV && viewMode === 'point_in_time') {
if (!updateSelection) {
return []
}
if (updateSelection.update.toV && !updateSelection.comparing) {
const filesWithOps: FileWithOps[] = []
const currentUpdate = getUpdateForVersion(selection.range.toV, updates)
const currentUpdate = getUpdateForVersion(
updateSelection.update.toV,
updates
)
if (currentUpdate !== null) {
for (const pathname of currentUpdate.pathnames) {
@ -80,7 +69,7 @@ function getFilesWithOps(
return filesWithOps
} else {
const filesWithOps = _.reduce(
selection.files,
files,
(curFilesWithOps, file) => {
if ('operation' in file) {
curFilesWithOps.push({
@ -106,13 +95,12 @@ const orderedOpTypes: DiffOperation[] = [
export function autoSelectFile(
files: FileDiff[],
selection: HistoryContextValue['selection'],
viewMode: HistoryContextValue['viewMode'],
updateSelection: HistoryContextValue['updateSelection'],
updates: HistoryContextValue['updates']
) {
let fileToSelect: Nullable<FileDiff> = null
const filesWithOps = getFilesWithOps(viewMode, selection, updates)
const filesWithOps = getFilesWithOps(files, updateSelection, updates)
for (const opType of orderedOpTypes) {
const fileWithMatchingOpType = _.find(filesWithOps, {
operation: opType,
@ -148,5 +136,5 @@ export function autoSelectFile(
}
}
return selectFile(fileToSelect, selection)
return fileToSelect.pathname
}

View file

@ -3,6 +3,7 @@ import type { HistoryContextValue } from '../../../../../frontend/js/features/hi
import type { FileDiff } from '../../../../../frontend/js/features/history/services/types/file'
import { autoSelectFile } from '../../../../../frontend/js/features/history/utils/auto-select-file'
import type { User } from '../../../../../frontend/js/features/history/services/types/shared'
import { UpdateSelection } from '../../../../../frontend/js/features/history/services/types/update'
describe('autoSelectFile', function () {
const historyUsers: User[] = [
@ -14,24 +15,8 @@ describe('autoSelectFile', function () {
},
]
const emptySelection: HistoryContextValue['selection'] = {
docs: {},
pathname: null,
range: {
fromV: null,
toV: null,
},
hoveredRange: {
fromV: null,
toV: null,
},
diff: null,
files: [],
file: null,
}
describe('for `point_in_time` view mode', function () {
const viewMode: HistoryContextValue['viewMode'] = 'point_in_time'
describe('for comparing version with previous', function () {
const comparing = false
it('return the file with `edited` as the last operation', function () {
const files: FileDiff[] = [
@ -56,14 +41,6 @@ describe('autoSelectFile', function () {
},
]
const selection: HistoryContextValue['selection'] = {
...emptySelection,
range: {
fromV: 26,
toV: 26,
},
}
const updates: HistoryContextValue['updates'] = [
{
fromV: 25,
@ -283,15 +260,16 @@ describe('autoSelectFile', function () {
},
]
const defaultSelection = autoSelectFile(
files,
selection,
viewMode,
updates
)
const updateSelection: UpdateSelection = {
update: updates[0],
comparing,
}
expect(defaultSelection.pathname).to.equal('newfolder1/newfile10.tex')
const pathname = autoSelectFile(files, updateSelection, updates)
expect(pathname).to.equal('newfolder1/newfile10.tex')
})
it('return file with `added` operation on highest `atV` value if no other operation is available on the latest `updates` entry', function () {
const files: FileDiff[] = [
{
@ -312,14 +290,6 @@ describe('autoSelectFile', function () {
},
]
const selection: HistoryContextValue['selection'] = {
...emptySelection,
range: {
fromV: 4,
toV: 4,
},
}
const updates: HistoryContextValue['updates'] = [
{
fromV: 0,
@ -360,14 +330,14 @@ describe('autoSelectFile', function () {
},
]
const defaultSelection = autoSelectFile(
files,
selection,
viewMode,
updates
)
const updateSelection: UpdateSelection = {
update: updates[0],
comparing,
}
expect(defaultSelection.pathname).to.equal('newfile1.tex')
const pathname = autoSelectFile(files, updateSelection, updates)
expect(pathname).to.equal('newfile1.tex')
})
it('return the last non-`removed` operation with the highest `atV` value', function () {
@ -390,14 +360,6 @@ describe('autoSelectFile', function () {
},
]
const selection: HistoryContextValue['selection'] = {
...emptySelection,
range: {
fromV: 7,
toV: 7,
},
}
const updates: HistoryContextValue['updates'] = [
{
fromV: 4,
@ -469,14 +431,14 @@ describe('autoSelectFile', function () {
},
]
const defaultSelection = autoSelectFile(
files,
selection,
viewMode,
updates
)
const updateSelection: UpdateSelection = {
update: updates[0],
comparing,
}
expect(defaultSelection.pathname).to.equal('main3.tex')
const pathname = autoSelectFile(files, updateSelection, updates)
expect(pathname).to.equal('main3.tex')
})
it('if `removed` is the last operation, and no other operation is available on the latest `updates` entry, with `main.tex` available as a file name somewhere in the file tree, return `main.tex`', function () {
@ -498,14 +460,6 @@ describe('autoSelectFile', function () {
},
]
const selection: HistoryContextValue['selection'] = {
...emptySelection,
range: {
fromV: 11,
toV: 11,
},
}
const updates: HistoryContextValue['updates'] = [
{
fromV: 9,
@ -648,14 +602,14 @@ describe('autoSelectFile', function () {
},
]
const defaultSelection = autoSelectFile(
files,
selection,
viewMode,
updates
)
const updateSelection: UpdateSelection = {
update: updates[0],
comparing,
}
expect(defaultSelection.pathname).to.equal('main.tex')
const pathname = autoSelectFile(files, updateSelection, updates)
expect(pathname).to.equal('main.tex')
})
it('if `removed` is the last operation, and no other operation is available on the latest `updates` entry, with `main.tex` is not available as a file name somewhere in the file tree, return any tex file based on ascending alphabetical order', function () {
@ -671,14 +625,6 @@ describe('autoSelectFile', function () {
},
]
const selection: HistoryContextValue['selection'] = {
...emptySelection,
range: {
fromV: 8,
toV: 8,
},
}
const updates: HistoryContextValue['updates'] = [
{
fromV: 7,
@ -764,14 +710,14 @@ describe('autoSelectFile', function () {
},
]
const defaultSelection = autoSelectFile(
files,
selection,
viewMode,
updates
)
const updateSelection: UpdateSelection = {
update: updates[0],
comparing,
}
expect(defaultSelection.pathname).to.equal('certainly_not_main.tex')
const pathname = autoSelectFile(files, updateSelection, updates)
expect(pathname).to.equal('certainly_not_main.tex')
})
})
})