mirror of
https://github.com/overleaf/overleaf.git
synced 2025-01-24 05:31:17 +00:00
Add infinite scrolling to history versions list (#12752)
* Add infinite scrolling to change list, add spinner while doing initial updates load, refactor updates state * Update type in tests * Update types * Using LoadingSpinner component * Remove redundant imports GitOrigin-RevId: 98c7eae8edbc4d10d7107d825045edfc4159494f
This commit is contained in:
parent
bf076a8c97
commit
afd1195902
11 changed files with 223 additions and 110 deletions
|
@ -1,21 +1,79 @@
|
|||
import { Fragment } from 'react'
|
||||
import { Fragment, useEffect, useRef, useState } from 'react'
|
||||
import { useHistoryContext } from '../../context/history-context'
|
||||
import HistoryVersion from './history-version'
|
||||
import LoadingSpinner from '../../../../shared/components/loading-spinner'
|
||||
|
||||
function AllHistoryList() {
|
||||
const { updates } = useHistoryContext()
|
||||
const { updatesInfo, loadingState, fetchNextBatchOfUpdates } =
|
||||
useHistoryContext()
|
||||
const { updates, atEnd } = updatesInfo
|
||||
const scrollerRef = useRef<HTMLDivElement>(null)
|
||||
const bottomRef = useRef<HTMLDivElement>(null)
|
||||
const intersectionObserverRef = useRef<IntersectionObserver | null>(null)
|
||||
const [bottomVisible, setBottomVisible] = useState(false)
|
||||
|
||||
// Create an intersection observer that watches for any part of an element
|
||||
// positioned at the bottom of the list to be visible
|
||||
useEffect(() => {
|
||||
if (loadingState === 'ready' && !intersectionObserverRef.current) {
|
||||
const scroller = scrollerRef.current
|
||||
const bottom = bottomRef.current
|
||||
|
||||
if (scroller && bottom) {
|
||||
intersectionObserverRef.current = new IntersectionObserver(
|
||||
entries => {
|
||||
setBottomVisible(entries[0].isIntersecting)
|
||||
},
|
||||
{ root: scroller }
|
||||
)
|
||||
|
||||
intersectionObserverRef.current.observe(bottom)
|
||||
|
||||
return () => {
|
||||
if (intersectionObserverRef.current) {
|
||||
intersectionObserverRef.current.disconnect()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [loadingState])
|
||||
|
||||
useEffect(() => {
|
||||
if (!atEnd && loadingState === 'ready' && bottomVisible) {
|
||||
fetchNextBatchOfUpdates()
|
||||
}
|
||||
}, [atEnd, bottomVisible, fetchNextBatchOfUpdates, loadingState])
|
||||
|
||||
// While updates are loading, remove the intersection observer and set
|
||||
// bottomVisible to false. This is to avoid loading more updates immediately
|
||||
// after rendering the pending updates, which would happen otherwise, because
|
||||
// the intersection observer is asynchronous and won't have noticed that the
|
||||
// bottom is no longer visible
|
||||
useEffect(() => {
|
||||
if (loadingState !== 'ready' && intersectionObserverRef.current) {
|
||||
setBottomVisible(false)
|
||||
if (intersectionObserverRef.current) {
|
||||
intersectionObserverRef.current.disconnect()
|
||||
intersectionObserverRef.current = null
|
||||
}
|
||||
}
|
||||
}, [loadingState])
|
||||
|
||||
return (
|
||||
<>
|
||||
{updates.map((update, index) => (
|
||||
<Fragment key={`${update.fromV}_${update.toV}`}>
|
||||
{update.meta.first_in_day && index > 0 && (
|
||||
<hr className="history-version-divider" />
|
||||
)}
|
||||
<HistoryVersion update={update} />
|
||||
</Fragment>
|
||||
))}
|
||||
</>
|
||||
<div ref={scrollerRef} className="history-all-versions-scroller">
|
||||
<div className="history-all-versions-container">
|
||||
<div ref={bottomRef} className="history-versions-bottom" />
|
||||
{updates.map((update, index) => (
|
||||
<Fragment key={`${update.fromV}_${update.toV}`}>
|
||||
{update.meta.first_in_day && index > 0 && (
|
||||
<hr className="history-version-divider" />
|
||||
)}
|
||||
<HistoryVersion update={update} />
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
{loadingState === 'ready' ? null : <LoadingSpinner />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ type TagProps = {
|
|||
function Tag({ label, currentUserId, ...props }: TagProps) {
|
||||
const { t } = useTranslation()
|
||||
const [showDeleteModal, setShowDeleteModal] = useState(false)
|
||||
const { projectId, updates, setUpdates, labels, setLabels } =
|
||||
const { projectId, updatesInfo, setUpdatesInfo, labels, setLabels } =
|
||||
useHistoryContext()
|
||||
const { isLoading, isSuccess, isError, error, runAsync } = useAsync()
|
||||
const isPseudoCurrentStateLabel = isPseudoLabel(label)
|
||||
|
@ -36,7 +36,7 @@ function Tag({ label, currentUserId, ...props }: TagProps) {
|
|||
const handleModalExited = () => {
|
||||
if (!isSuccess) return
|
||||
|
||||
const tempUpdates = [...updates]
|
||||
const tempUpdates = [...updatesInfo.updates]
|
||||
for (const [i, update] of tempUpdates.entries()) {
|
||||
if (update.toV === label.version) {
|
||||
tempUpdates[i] = {
|
||||
|
@ -47,7 +47,7 @@ function Tag({ label, currentUserId, ...props }: TagProps) {
|
|||
}
|
||||
}
|
||||
|
||||
setUpdates(tempUpdates)
|
||||
setUpdatesInfo({ ...updatesInfo, updates: tempUpdates })
|
||||
|
||||
if (labels) {
|
||||
const nonPseudoLabels = labels.filter(isLabel)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Nullable } from '../../../../../../types/utils'
|
||||
import { Diff } from '../../services/types/doc'
|
||||
import DocumentDiffViewer from './document-diff-viewer'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import LoadingSpinner from '../../../../shared/components/loading-spinner'
|
||||
|
||||
type MainProps = {
|
||||
diff: Nullable<Diff>
|
||||
|
@ -9,16 +9,8 @@ type MainProps = {
|
|||
}
|
||||
|
||||
function Main({ diff, isLoading }: MainProps) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="history-loading-panel">
|
||||
<i className="fa fa-spin fa-refresh" />
|
||||
|
||||
{t('loading')}…
|
||||
</div>
|
||||
)
|
||||
return <LoadingSpinner />
|
||||
}
|
||||
|
||||
if (!diff) {
|
||||
|
|
|
@ -3,15 +3,12 @@ import DiffView from './diff-view/diff-view'
|
|||
import { HistoryProvider, useHistoryContext } from '../context/history-context'
|
||||
import { createPortal } from 'react-dom'
|
||||
import HistoryFileTree from './history-file-tree'
|
||||
import LoadingSpinner from '../../../shared/components/loading-spinner'
|
||||
|
||||
const fileTreeContainer = document.getElementById('history-file-tree')
|
||||
|
||||
function Main() {
|
||||
const { updates } = useHistoryContext()
|
||||
|
||||
if (updates.length === 0) {
|
||||
return null
|
||||
}
|
||||
const { loadingState } = useHistoryContext()
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -19,8 +16,14 @@ function Main() {
|
|||
? createPortal(<HistoryFileTree />, fileTreeContainer)
|
||||
: null}
|
||||
<div className="history-react">
|
||||
<DiffView />
|
||||
<ChangeList />
|
||||
{loadingState === 'loadingInitial' ? (
|
||||
<LoadingSpinner />
|
||||
) : (
|
||||
<>
|
||||
<DiffView />
|
||||
<ChangeList />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
|
|
@ -19,10 +19,44 @@ import ColorManager from '../../../ide/colors/ColorManager'
|
|||
import moment from 'moment'
|
||||
import * as eventTracking from '../../../infrastructure/event-tracking'
|
||||
import { cloneDeep } from 'lodash'
|
||||
import { LoadedUpdate, Update } from '../services/types/update'
|
||||
import {
|
||||
FetchUpdatesResponse,
|
||||
LoadedUpdate,
|
||||
Update,
|
||||
} from '../services/types/update'
|
||||
import { Nullable } from '../../../../../types/utils'
|
||||
import { Selection } from '../services/types/selection'
|
||||
|
||||
// Allow testing of infinite scrolling by providing query string parameters to
|
||||
// limit the number of updates returned in a batch and apply a delay
|
||||
function limitUpdates(
|
||||
promise: Promise<FetchUpdatesResponse>
|
||||
): Promise<FetchUpdatesResponse> {
|
||||
const queryParams = new URLSearchParams(window.location.search)
|
||||
const maxBatchSizeParam = queryParams.get('history-max-updates')
|
||||
const delayParam = queryParams.get('history-updates-delay')
|
||||
if (delayParam === null && maxBatchSizeParam === null) {
|
||||
return promise
|
||||
}
|
||||
return promise.then(response => {
|
||||
let { updates, nextBeforeTimestamp } = response
|
||||
const maxBatchSize = maxBatchSizeParam ? parseInt(maxBatchSizeParam, 10) : 0
|
||||
const delay = delayParam ? parseInt(delayParam, 10) : 0
|
||||
if (maxBatchSize > 0 && updates) {
|
||||
updates = updates.slice(0, maxBatchSize)
|
||||
nextBeforeTimestamp = updates[updates.length - 1].fromV
|
||||
}
|
||||
const limitedResponse = { updates, nextBeforeTimestamp }
|
||||
if (delay > 0) {
|
||||
return new Promise(resolve => {
|
||||
window.setTimeout(() => resolve(limitedResponse), delay)
|
||||
})
|
||||
} else {
|
||||
return limitedResponse
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function useHistory() {
|
||||
const { view } = useLayoutContext()
|
||||
const user = useUserContext()
|
||||
|
@ -38,17 +72,17 @@ function useHistory() {
|
|||
pathname: null,
|
||||
})
|
||||
|
||||
const [updates, setUpdates] = useState<LoadedUpdate[]>([])
|
||||
const [loadingFileTree, setLoadingFileTree] =
|
||||
useState<HistoryContextValue['loadingFileTree']>(true)
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const [nextBeforeTimestamp, setNextBeforeTimestamp] =
|
||||
useState<HistoryContextValue['nextBeforeTimestamp']>()
|
||||
const [atEnd, setAtEnd] = useState<HistoryContextValue['atEnd']>(false)
|
||||
const [freeHistoryLimitHit, setFreeHistoryLimitHit] =
|
||||
useState<HistoryContextValue['freeHistoryLimitHit']>(false)
|
||||
const [updatesInfo, setUpdatesInfo] = useState<
|
||||
HistoryContextValue['updatesInfo']
|
||||
>({
|
||||
updates: [],
|
||||
atEnd: false,
|
||||
freeHistoryLimitHit: false,
|
||||
nextBeforeTimestamp: undefined,
|
||||
})
|
||||
const [labels, setLabels] = useState<HistoryContextValue['labels']>(null)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [loadingState, setLoadingState] =
|
||||
useState<HistoryContextValue['loadingState']>('loadingInitial')
|
||||
const [error, setError] = useState(null)
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const [userHasFullFeature, setUserHasFullFeature] =
|
||||
|
@ -58,6 +92,7 @@ function useHistory() {
|
|||
const loadUpdates = (updatesData: Update[]) => {
|
||||
const dateTimeNow = new Date()
|
||||
const timestamp24hoursAgo = dateTimeNow.setDate(dateTimeNow.getDate() - 1)
|
||||
let { updates, freeHistoryLimitHit } = updatesInfo
|
||||
let previousUpdate = updates[updates.length - 1]
|
||||
let cutOffIndex: Nullable<number> = null
|
||||
|
||||
|
@ -79,7 +114,7 @@ function useHistory() {
|
|||
|
||||
if (userHasFullFeature && update.meta.end_ts < timestamp24hoursAgo) {
|
||||
cutOffIndex = index || 1 // Make sure that we show at least one entry (to allow labelling).
|
||||
setFreeHistoryLimitHit(true)
|
||||
freeHistoryLimitHit = true
|
||||
if (projectOwnerId === userId) {
|
||||
eventTracking.send(
|
||||
'subscription-funnel',
|
||||
|
@ -98,20 +133,19 @@ function useHistory() {
|
|||
loadedUpdates = loadedUpdates.slice(0, cutOffIndex)
|
||||
}
|
||||
|
||||
setUpdates(updates.concat(loadedUpdates))
|
||||
|
||||
// TODO first load
|
||||
// if (firstLoad) {
|
||||
// _handleHistoryUIStateChange()
|
||||
// }
|
||||
return { updates: updates.concat(loadedUpdates), freeHistoryLimitHit }
|
||||
}
|
||||
|
||||
if (atEnd) return
|
||||
if (updatesInfo.atEnd || loadingState === 'loadingUpdates') return
|
||||
|
||||
const updatesPromise = fetchUpdates(projectId, nextBeforeTimestamp)
|
||||
const updatesPromise = limitUpdates(
|
||||
fetchUpdates(projectId, updatesInfo.nextBeforeTimestamp)
|
||||
)
|
||||
const labelsPromise = labels == null ? fetchLabels(projectId) : null
|
||||
|
||||
setIsLoading(true)
|
||||
setLoadingState(
|
||||
loadingState === 'ready' ? 'loadingUpdates' : 'loadingInitial'
|
||||
)
|
||||
Promise.all([updatesPromise, labelsPromise])
|
||||
.then(([{ updates: updatesData, nextBeforeTimestamp }, labels]) => {
|
||||
const lastUpdateToV = updatesData.length ? updatesData[0].toV : null
|
||||
|
@ -120,37 +154,33 @@ function useHistory() {
|
|||
setLabels(loadLabels(labels, lastUpdateToV))
|
||||
}
|
||||
|
||||
loadUpdates(updatesData)
|
||||
setNextBeforeTimestamp(nextBeforeTimestamp)
|
||||
if (
|
||||
nextBeforeTimestamp == null ||
|
||||
freeHistoryLimitHit ||
|
||||
!updates.length
|
||||
) {
|
||||
setAtEnd(true)
|
||||
}
|
||||
if (!updates.length) {
|
||||
setLoadingFileTree(false)
|
||||
}
|
||||
const { updates, freeHistoryLimitHit } = loadUpdates(updatesData)
|
||||
|
||||
const atEnd =
|
||||
nextBeforeTimestamp == null || freeHistoryLimitHit || !updates.length
|
||||
|
||||
setUpdatesInfo({
|
||||
updates,
|
||||
freeHistoryLimitHit,
|
||||
atEnd,
|
||||
nextBeforeTimestamp,
|
||||
})
|
||||
})
|
||||
.catch(error => {
|
||||
setError(error)
|
||||
setAtEnd(true)
|
||||
setLoadingFileTree(false)
|
||||
setUpdatesInfo({ ...updatesInfo, atEnd: true })
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoading(false)
|
||||
setLoadingState('ready')
|
||||
})
|
||||
}, [
|
||||
atEnd,
|
||||
freeHistoryLimitHit,
|
||||
loadingState,
|
||||
labels,
|
||||
nextBeforeTimestamp,
|
||||
projectId,
|
||||
projectOwnerId,
|
||||
userId,
|
||||
userHasFullFeature,
|
||||
updates,
|
||||
updatesInfo,
|
||||
])
|
||||
|
||||
// Initial load when the History tab is active
|
||||
|
@ -163,6 +193,7 @@ function useHistory() {
|
|||
}, [view, fetchNextBatchOfUpdates])
|
||||
|
||||
const { updateRange, comparing } = selection
|
||||
const { updates } = updatesInfo
|
||||
|
||||
// Load files when the update selection changes
|
||||
useEffect(() => {
|
||||
|
@ -190,7 +221,7 @@ function useHistory() {
|
|||
}, [updateRange, projectId, updates, comparing])
|
||||
|
||||
useEffect(() => {
|
||||
// Set update selection if there isn't one
|
||||
// Set update range if there isn't one and updates have loaded
|
||||
if (updates.length && !updateRange) {
|
||||
setSelection({
|
||||
updateRange: {
|
||||
|
@ -208,36 +239,30 @@ function useHistory() {
|
|||
|
||||
const value = useMemo<HistoryContextValue>(
|
||||
() => ({
|
||||
atEnd,
|
||||
error,
|
||||
isLoading,
|
||||
freeHistoryLimitHit,
|
||||
loadingState,
|
||||
updatesInfo,
|
||||
setUpdatesInfo,
|
||||
labels,
|
||||
setLabels,
|
||||
loadingFileTree,
|
||||
nextBeforeTimestamp,
|
||||
updates,
|
||||
setUpdates,
|
||||
userHasFullFeature,
|
||||
projectId,
|
||||
selection,
|
||||
setSelection,
|
||||
fetchNextBatchOfUpdates,
|
||||
}),
|
||||
[
|
||||
atEnd,
|
||||
error,
|
||||
isLoading,
|
||||
freeHistoryLimitHit,
|
||||
loadingState,
|
||||
updatesInfo,
|
||||
setUpdatesInfo,
|
||||
labels,
|
||||
setLabels,
|
||||
loadingFileTree,
|
||||
nextBeforeTimestamp,
|
||||
updates,
|
||||
setUpdates,
|
||||
userHasFullFeature,
|
||||
projectId,
|
||||
selection,
|
||||
setSelection,
|
||||
fetchNextBatchOfUpdates,
|
||||
]
|
||||
)
|
||||
|
||||
|
|
|
@ -4,20 +4,22 @@ import { LoadedLabel } from '../../services/types/label'
|
|||
import { Selection } from '../../services/types/selection'
|
||||
|
||||
export type HistoryContextValue = {
|
||||
updates: LoadedUpdate[]
|
||||
setUpdates: React.Dispatch<
|
||||
React.SetStateAction<HistoryContextValue['updates']>
|
||||
updatesInfo: {
|
||||
updates: LoadedUpdate[]
|
||||
atEnd: boolean
|
||||
nextBeforeTimestamp: number | undefined
|
||||
freeHistoryLimitHit: boolean
|
||||
}
|
||||
setUpdatesInfo: React.Dispatch<
|
||||
React.SetStateAction<HistoryContextValue['updatesInfo']>
|
||||
>
|
||||
nextBeforeTimestamp: number | undefined
|
||||
atEnd: boolean
|
||||
userHasFullFeature: boolean | undefined
|
||||
freeHistoryLimitHit: boolean
|
||||
isLoading: boolean
|
||||
loadingState: 'loadingInitial' | 'loadingUpdates' | 'ready'
|
||||
error: Nullable<unknown>
|
||||
labels: Nullable<LoadedLabel[]>
|
||||
setLabels: React.Dispatch<React.SetStateAction<HistoryContextValue['labels']>>
|
||||
loadingFileTree: boolean
|
||||
projectId: string
|
||||
selection: Selection
|
||||
setSelection: (selection: Selection) => void
|
||||
fetchNextBatchOfUpdates: () => void
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { getJSON } from '../../../infrastructure/fetch-json'
|
||||
import { FileDiff } from './types/file'
|
||||
import { Update } from './types/update'
|
||||
import { FetchUpdatesResponse } from './types/update'
|
||||
import { Label } from './types/label'
|
||||
import { DocDiffResponse } from './types/doc'
|
||||
|
||||
|
@ -17,10 +17,7 @@ export function fetchUpdates(projectId: string, before?: number) {
|
|||
|
||||
const queryParamsSerialized = new URLSearchParams(queryParams).toString()
|
||||
const updatesURL = `/project/${projectId}/updates?${queryParamsSerialized}`
|
||||
return getJSON<{
|
||||
updates: Update[]
|
||||
nextBeforeTimestamp?: number
|
||||
}>(updatesURL)
|
||||
return getJSON<FetchUpdatesResponse>(updatesURL)
|
||||
}
|
||||
|
||||
export function fetchLabels(projectId: string) {
|
||||
|
|
|
@ -41,3 +41,8 @@ interface LoadedUpdateMeta extends Meta {
|
|||
export interface LoadedUpdate extends Update {
|
||||
meta: LoadedUpdateMeta
|
||||
}
|
||||
|
||||
export type FetchUpdatesResponse = {
|
||||
updates: Update[]
|
||||
nextBeforeTimestamp?: number
|
||||
}
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
import _ from 'lodash'
|
||||
import type { Nullable } from '../../../../../types/utils'
|
||||
import type { HistoryContextValue } from '../context/types/history-context-value'
|
||||
import type { FileDiff } from '../services/types/file'
|
||||
import type { DiffOperation } from '../services/types/diff-operation'
|
||||
import type { LoadedUpdate, Version } from '../services/types/update'
|
||||
|
||||
function getUpdateForVersion(
|
||||
version: Version,
|
||||
updates: HistoryContextValue['updates']
|
||||
updates: LoadedUpdate[]
|
||||
): Nullable<LoadedUpdate> {
|
||||
return updates.filter(update => update.toV === version)?.[0] ?? null
|
||||
}
|
||||
|
@ -21,7 +20,7 @@ function getFilesWithOps(
|
|||
files: FileDiff[],
|
||||
toV: Version,
|
||||
comparing: boolean,
|
||||
updates: HistoryContextValue['updates']
|
||||
updates: LoadedUpdate[]
|
||||
): FileWithOps[] {
|
||||
if (toV && !comparing) {
|
||||
const filesWithOps: FileWithOps[] = []
|
||||
|
@ -92,7 +91,7 @@ export function autoSelectFile(
|
|||
files: FileDiff[],
|
||||
toV: Version,
|
||||
comparing: boolean,
|
||||
updates: HistoryContextValue['updates']
|
||||
updates: LoadedUpdate[]
|
||||
) {
|
||||
let fileToSelect: Nullable<FileDiff> = null
|
||||
|
||||
|
|
|
@ -60,6 +60,21 @@ history-root {
|
|||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.history-all-versions-scroller {
|
||||
overflow-y: auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.history-all-versions-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.history-versions-bottom {
|
||||
position: absolute;
|
||||
height: 8em;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.history-toggle-switch-container,
|
||||
.history-version-day,
|
||||
.history-version-details {
|
||||
|
@ -166,6 +181,23 @@ history-root {
|
|||
}
|
||||
}
|
||||
|
||||
.loading {
|
||||
padding-top: 10rem;
|
||||
font-family: @font-family-serif;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
& > .loading {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.history-all-versions-scroller .loading {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
padding: @line-height-computed / 2 0;
|
||||
background-color: @gray-lightest;
|
||||
}
|
||||
|
||||
.history-loading-panel {
|
||||
padding-top: 10rem;
|
||||
font-family: @font-family-serif;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { expect } from 'chai'
|
||||
import type { HistoryContextValue } from '../../../../../frontend/js/features/history/context/types/history-context-value'
|
||||
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 { LoadedUpdate } from '../../../../../frontend/js/features/history/services/types/update'
|
||||
|
||||
describe('autoSelectFile', function () {
|
||||
const historyUsers: User[] = [
|
||||
|
@ -40,7 +40,7 @@ describe('autoSelectFile', function () {
|
|||
},
|
||||
]
|
||||
|
||||
const updates: HistoryContextValue['updates'] = [
|
||||
const updates: LoadedUpdate[] = [
|
||||
{
|
||||
fromV: 25,
|
||||
toV: 26,
|
||||
|
@ -284,7 +284,7 @@ describe('autoSelectFile', function () {
|
|||
},
|
||||
]
|
||||
|
||||
const updates: HistoryContextValue['updates'] = [
|
||||
const updates: LoadedUpdate[] = [
|
||||
{
|
||||
fromV: 0,
|
||||
toV: 4,
|
||||
|
@ -349,7 +349,7 @@ describe('autoSelectFile', function () {
|
|||
},
|
||||
]
|
||||
|
||||
const updates: HistoryContextValue['updates'] = [
|
||||
const updates: LoadedUpdate[] = [
|
||||
{
|
||||
fromV: 4,
|
||||
toV: 7,
|
||||
|
@ -444,7 +444,7 @@ describe('autoSelectFile', function () {
|
|||
},
|
||||
]
|
||||
|
||||
const updates: HistoryContextValue['updates'] = [
|
||||
const updates: LoadedUpdate[] = [
|
||||
{
|
||||
fromV: 9,
|
||||
toV: 11,
|
||||
|
@ -604,7 +604,7 @@ describe('autoSelectFile', function () {
|
|||
},
|
||||
]
|
||||
|
||||
const updates: HistoryContextValue['updates'] = [
|
||||
const updates: LoadedUpdate[] = [
|
||||
{
|
||||
fromV: 7,
|
||||
toV: 8,
|
||||
|
|
Loading…
Reference in a new issue