mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #12644 from overleaf/td-history-compare
Initial implementation of comparing history versions GitOrigin-RevId: 890e270d6e41856a79689ab41ccfbde25c4703ba
This commit is contained in:
parent
df58f6720e
commit
99e1ff0804
16 changed files with 147 additions and 119 deletions
|
@ -19,7 +19,7 @@ function ChangeList() {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{!error && (
|
{!error && (
|
||||||
<div className="version-list-container">
|
<div className="history-version-list-container">
|
||||||
{labelsOnly ? <LabelsList /> : <AllHistoryList />}
|
{labelsOnly ? <LabelsList /> : <AllHistoryList />}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -6,6 +6,9 @@ import { useUserContext } from '../../../../shared/context/user-context'
|
||||||
import { relativeDate, formatTime } from '../../../utils/format-date'
|
import { relativeDate, formatTime } from '../../../utils/format-date'
|
||||||
import { orderBy } from 'lodash'
|
import { orderBy } from 'lodash'
|
||||||
import { LoadedUpdate } from '../../services/types/update'
|
import { LoadedUpdate } from '../../services/types/update'
|
||||||
|
import { useHistoryContext } from '../../context/history-context'
|
||||||
|
import classNames from 'classnames'
|
||||||
|
import { updateIsSelected } from '../../utils/history-details'
|
||||||
|
|
||||||
type HistoryEntryProps = {
|
type HistoryEntryProps = {
|
||||||
update: LoadedUpdate
|
update: LoadedUpdate
|
||||||
|
@ -15,16 +18,46 @@ function HistoryVersion({ update }: HistoryEntryProps) {
|
||||||
const { id: currentUserId } = useUserContext()
|
const { id: currentUserId } = useUserContext()
|
||||||
const orderedLabels = orderBy(update.labels, ['created_at'], ['desc'])
|
const orderedLabels = orderBy(update.labels, ['created_at'], ['desc'])
|
||||||
|
|
||||||
|
const { selection, setSelection } = useHistoryContext()
|
||||||
|
|
||||||
|
const selected = updateIsSelected(update, selection)
|
||||||
|
|
||||||
|
function compare() {
|
||||||
|
const { updateRange } = selection
|
||||||
|
if (!updateRange) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const fromV = Math.min(update.fromV, updateRange.fromV)
|
||||||
|
const toV = Math.max(update.toV, updateRange.toV)
|
||||||
|
|
||||||
|
setSelection({
|
||||||
|
updateRange: { fromV, toV },
|
||||||
|
comparing: true,
|
||||||
|
files: [],
|
||||||
|
pathname: null,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className={classNames({ 'history-version-selected': selected })}>
|
||||||
{update.meta.first_in_day && (
|
{update.meta.first_in_day && (
|
||||||
<time className="history-version-day">
|
<time className="history-version-day">
|
||||||
{relativeDate(update.meta.end_ts)}
|
{relativeDate(update.meta.end_ts)}
|
||||||
</time>
|
</time>
|
||||||
)}
|
)}
|
||||||
|
{/* TODO: Sort out accessibility for this */}
|
||||||
|
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
|
||||||
<div
|
<div
|
||||||
className="history-version-details"
|
className="history-version-details"
|
||||||
data-testid="history-version-details"
|
data-testid="history-version-details"
|
||||||
|
onClick={() =>
|
||||||
|
setSelection({
|
||||||
|
updateRange: update,
|
||||||
|
comparing: false,
|
||||||
|
files: [],
|
||||||
|
pathname: null,
|
||||||
|
})
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<time className="history-version-metadata-time">
|
<time className="history-version-metadata-time">
|
||||||
<b>{formatTime(update.meta.end_ts, 'Do MMMM, h:mm a')}</b>
|
<b>{formatTime(update.meta.end_ts, 'Do MMMM, h:mm a')}</b>
|
||||||
|
@ -44,6 +77,18 @@ function HistoryVersion({ update }: HistoryEntryProps) {
|
||||||
currentUserId={currentUserId}
|
currentUserId={currentUserId}
|
||||||
/>
|
/>
|
||||||
<Origin origin={update.meta.origin} />
|
<Origin origin={update.meta.origin} />
|
||||||
|
{selection.comparing ? null : (
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
onClick={event => {
|
||||||
|
event.stopPropagation()
|
||||||
|
compare()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Compare
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -17,7 +17,7 @@ type Diff = {
|
||||||
|
|
||||||
function Main() {
|
function Main() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { projectId, updateSelection, fileSelection } = useHistoryContext()
|
const { projectId, selection } = useHistoryContext()
|
||||||
const { isLoading, runAsync, data } = useAsync<DocDiffResponse>()
|
const { isLoading, runAsync, data } = useAsync<DocDiffResponse>()
|
||||||
let diff: Diff | undefined
|
let diff: Diff | undefined
|
||||||
if (data?.diff) {
|
if (data?.diff) {
|
||||||
|
@ -28,16 +28,18 @@ function Main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { updateRange, pathname } = selection
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!updateSelection || !fileSelection || !fileSelection.pathname) {
|
if (!updateRange || !pathname) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const { fromV, toV } = updateSelection.update
|
const { fromV, toV } = updateRange
|
||||||
|
|
||||||
// TODO: Error handling
|
// TODO: Error handling
|
||||||
runAsync(diffDoc(projectId, fromV, toV, fileSelection.pathname))
|
runAsync(diffDoc(projectId, fromV, toV, pathname))
|
||||||
}, [fileSelection, projectId, runAsync, updateSelection])
|
}, [projectId, runAsync, pathname, updateRange])
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -7,13 +7,9 @@ import {
|
||||||
import HistoryFileTreeFolderList from './file-tree/history-file-tree-folder-list'
|
import HistoryFileTreeFolderList from './file-tree/history-file-tree-folder-list'
|
||||||
|
|
||||||
export default function HistoryFileTree() {
|
export default function HistoryFileTree() {
|
||||||
const { fileSelection } = useHistoryContext()
|
const { files } = useHistoryContext().selection
|
||||||
|
|
||||||
if (!fileSelection) {
|
const fileTree = _.reduce(files, reducePathsToTree, [])
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const fileTree = _.reduce(fileSelection.files, reducePathsToTree, [])
|
|
||||||
|
|
||||||
const mappedFileTree = fileTreeDiffToFileTreeData(fileTree)
|
const mappedFileTree = fileTreeDiffToFileTreeData(fileTree)
|
||||||
|
|
||||||
|
|
|
@ -19,9 +19,9 @@ import ColorManager from '../../../ide/colors/ColorManager'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import * as eventTracking from '../../../infrastructure/event-tracking'
|
import * as eventTracking from '../../../infrastructure/event-tracking'
|
||||||
import { cloneDeep } from 'lodash'
|
import { cloneDeep } from 'lodash'
|
||||||
import { LoadedUpdate, Update, UpdateSelection } from '../services/types/update'
|
import { LoadedUpdate, Update } from '../services/types/update'
|
||||||
import { FileSelection } from '../services/types/file'
|
|
||||||
import { Nullable } from '../../../../../types/utils'
|
import { Nullable } from '../../../../../types/utils'
|
||||||
|
import { Selection } from '../services/types/selection'
|
||||||
|
|
||||||
function useHistory() {
|
function useHistory() {
|
||||||
const { view } = useLayoutContext()
|
const { view } = useLayoutContext()
|
||||||
|
@ -30,9 +30,14 @@ function useHistory() {
|
||||||
const userId = user.id
|
const userId = user.id
|
||||||
const projectId = project._id
|
const projectId = project._id
|
||||||
const projectOwnerId = project.owner?._id
|
const projectOwnerId = project.owner?._id
|
||||||
const [updateSelection, setUpdateSelection] =
|
|
||||||
useState<UpdateSelection | null>(null)
|
const [selection, setSelection] = useState<Selection>({
|
||||||
const [fileSelection, setFileSelection] = useState<FileSelection | null>(null)
|
updateRange: null,
|
||||||
|
comparing: false,
|
||||||
|
files: [],
|
||||||
|
pathname: null,
|
||||||
|
})
|
||||||
|
|
||||||
const [updates, setUpdates] = useState<LoadedUpdate[]>([])
|
const [updates, setUpdates] = useState<LoadedUpdate[]>([])
|
||||||
const [loadingFileTree, setLoadingFileTree] =
|
const [loadingFileTree, setLoadingFileTree] =
|
||||||
useState<HistoryContextValue['loadingFileTree']>(true)
|
useState<HistoryContextValue['loadingFileTree']>(true)
|
||||||
|
@ -158,15 +163,17 @@ function useHistory() {
|
||||||
}
|
}
|
||||||
}, [view, fetchNextBatchOfUpdates])
|
}, [view, fetchNextBatchOfUpdates])
|
||||||
|
|
||||||
|
const { updateRange, comparing } = selection
|
||||||
|
|
||||||
// Load files when the update selection changes
|
// Load files when the update selection changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!updateSelection) {
|
if (!updateRange) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const { fromV, toV } = updateSelection.update
|
const { fromV, toV } = updateRange
|
||||||
|
|
||||||
diffFiles(projectId, fromV, toV).then(({ diff: files }) => {
|
diffFiles(projectId, fromV, toV).then(({ diff: files }) => {
|
||||||
const pathname = autoSelectFile(files, updateSelection, updates)
|
const pathname = autoSelectFile(files, updateRange, comparing, updates)
|
||||||
const newFiles = files.map(file => {
|
const newFiles = files.map(file => {
|
||||||
if (isFileRenamed(file) && file.newPathname) {
|
if (isFileRenamed(file) && file.newPathname) {
|
||||||
return renamePathnameKey(file)
|
return renamePathnameKey(file)
|
||||||
|
@ -174,19 +181,21 @@ function useHistory() {
|
||||||
|
|
||||||
return file
|
return file
|
||||||
})
|
})
|
||||||
setFileSelection({ files: newFiles, pathname })
|
setSelection({ updateRange, comparing, files: newFiles, pathname })
|
||||||
})
|
})
|
||||||
}, [updateSelection, projectId, updates])
|
}, [updateRange, projectId, updates, comparing])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Set update selection if there isn't one
|
// Set update selection if there isn't one
|
||||||
if (updates.length && !updateSelection) {
|
if (updates.length && !updateRange) {
|
||||||
setUpdateSelection({
|
setSelection({
|
||||||
update: updates[0],
|
updateRange: updates[0],
|
||||||
comparing: false,
|
comparing: false,
|
||||||
|
files: [],
|
||||||
|
pathname: null,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}, [setUpdateSelection, updateSelection, updates])
|
}, [updateRange, updates])
|
||||||
|
|
||||||
const value = useMemo<HistoryContextValue>(
|
const value = useMemo<HistoryContextValue>(
|
||||||
() => ({
|
() => ({
|
||||||
|
@ -202,10 +211,8 @@ function useHistory() {
|
||||||
setUpdates,
|
setUpdates,
|
||||||
userHasFullFeature,
|
userHasFullFeature,
|
||||||
projectId,
|
projectId,
|
||||||
fileSelection,
|
selection,
|
||||||
setFileSelection,
|
setSelection,
|
||||||
updateSelection,
|
|
||||||
setUpdateSelection,
|
|
||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
atEnd,
|
atEnd,
|
||||||
|
@ -220,10 +227,8 @@ function useHistory() {
|
||||||
setUpdates,
|
setUpdates,
|
||||||
userHasFullFeature,
|
userHasFullFeature,
|
||||||
projectId,
|
projectId,
|
||||||
fileSelection,
|
selection,
|
||||||
setFileSelection,
|
setSelection,
|
||||||
updateSelection,
|
|
||||||
setUpdateSelection,
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,25 +1,19 @@
|
||||||
import { useCallback, useMemo } from 'react'
|
import { useCallback } from 'react'
|
||||||
import { useHistoryContext } from '../history-context'
|
import { useHistoryContext } from '../history-context'
|
||||||
|
|
||||||
export function useFileTreeItemSelection(pathname: string) {
|
export function useFileTreeItemSelection(pathname: string) {
|
||||||
const { fileSelection, setFileSelection } = useHistoryContext()
|
const { selection, setSelection } = useHistoryContext()
|
||||||
|
|
||||||
const handleClick = useCallback(() => {
|
const handleClick = useCallback(() => {
|
||||||
if (!fileSelection) {
|
if (pathname !== selection.pathname) {
|
||||||
return
|
setSelection({
|
||||||
}
|
...selection,
|
||||||
if (pathname !== fileSelection.pathname) {
|
|
||||||
setFileSelection({
|
|
||||||
files: fileSelection.files,
|
|
||||||
pathname,
|
pathname,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}, [fileSelection, pathname, setFileSelection])
|
}, [pathname, selection, setSelection])
|
||||||
|
|
||||||
const isSelected = useMemo(
|
const isSelected = selection.pathname === pathname
|
||||||
() => fileSelection?.pathname === pathname,
|
|
||||||
[fileSelection, pathname]
|
|
||||||
)
|
|
||||||
|
|
||||||
return { isSelected, onClick: handleClick }
|
return { isSelected, onClick: handleClick }
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Nullable } from '../../../../../../types/utils'
|
import { Nullable } from '../../../../../../types/utils'
|
||||||
import { LoadedUpdate, UpdateSelection } from '../../services/types/update'
|
import { LoadedUpdate } from '../../services/types/update'
|
||||||
import { LoadedLabel } from '../../services/types/label'
|
import { LoadedLabel } from '../../services/types/label'
|
||||||
import { FileSelection } from '../../services/types/file'
|
import { Selection } from '../../services/types/selection'
|
||||||
|
|
||||||
export type HistoryContextValue = {
|
export type HistoryContextValue = {
|
||||||
updates: LoadedUpdate[]
|
updates: LoadedUpdate[]
|
||||||
|
@ -18,8 +18,6 @@ export type HistoryContextValue = {
|
||||||
setLabels: React.Dispatch<React.SetStateAction<HistoryContextValue['labels']>>
|
setLabels: React.Dispatch<React.SetStateAction<HistoryContextValue['labels']>>
|
||||||
loadingFileTree: boolean
|
loadingFileTree: boolean
|
||||||
projectId: string
|
projectId: string
|
||||||
fileSelection: FileSelection | null
|
selection: Selection
|
||||||
setFileSelection: (fileSelection: FileSelection) => void
|
setSelection: (selection: Selection) => void
|
||||||
updateSelection: UpdateSelection | null
|
|
||||||
setUpdateSelection: (updateSelection: UpdateSelection) => void
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,11 +73,6 @@ const plugin = ViewPlugin.fromClass(
|
||||||
const oldLocations = this.view.state.field(highlightLocationsField)
|
const oldLocations = this.view.state.field(highlightLocationsField)
|
||||||
const newLocations = calculateHighlightLocations(this.view)
|
const newLocations = calculateHighlightLocations(this.view)
|
||||||
|
|
||||||
console.log(
|
|
||||||
'dispatchIfChanged, changed is',
|
|
||||||
!isEqual(oldLocations, newLocations)
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!isEqual(oldLocations, newLocations)) {
|
if (!isEqual(oldLocations, newLocations)) {
|
||||||
this.view.dispatch({
|
this.view.dispatch({
|
||||||
effects: setHighlightLocationsEffect.of(newLocations),
|
effects: setHighlightLocationsEffect.of(newLocations),
|
||||||
|
|
|
@ -17,9 +17,10 @@ export function fetchUpdates(projectId: string, before?: number) {
|
||||||
|
|
||||||
const queryParamsSerialized = new URLSearchParams(queryParams).toString()
|
const queryParamsSerialized = new URLSearchParams(queryParams).toString()
|
||||||
const updatesURL = `/project/${projectId}/updates?${queryParamsSerialized}`
|
const updatesURL = `/project/${projectId}/updates?${queryParamsSerialized}`
|
||||||
return getJSON<{ updates: Update[]; nextBeforeTimestamp?: number }>(
|
return getJSON<{
|
||||||
updatesURL
|
updates: Update[]
|
||||||
)
|
nextBeforeTimestamp?: number
|
||||||
|
}>(updatesURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchLabels(projectId: string) {
|
export function fetchLabels(projectId: string) {
|
||||||
|
|
|
@ -30,8 +30,3 @@ export type FileDiff =
|
||||||
| FileEdited
|
| FileEdited
|
||||||
| FileRenamed
|
| FileRenamed
|
||||||
| FileUnchanged
|
| FileUnchanged
|
||||||
|
|
||||||
export interface FileSelection {
|
|
||||||
files: FileDiff[]
|
|
||||||
pathname: string | null
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { FileDiff } from './file'
|
||||||
|
import { UpdateRange } from './update'
|
||||||
|
|
||||||
|
export interface Selection {
|
||||||
|
updateRange: UpdateRange | null
|
||||||
|
comparing: boolean
|
||||||
|
files: FileDiff[]
|
||||||
|
pathname: string | null
|
||||||
|
}
|
|
@ -9,9 +9,12 @@ export interface ProjectOp {
|
||||||
atV: number
|
atV: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Update {
|
export interface UpdateRange {
|
||||||
fromV: number
|
fromV: number
|
||||||
toV: number
|
toV: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Update extends UpdateRange {
|
||||||
meta: Meta
|
meta: Meta
|
||||||
labels: Label[]
|
labels: Label[]
|
||||||
pathnames: string[]
|
pathnames: string[]
|
||||||
|
@ -32,8 +35,3 @@ interface LoadedUpdateMeta extends Meta {
|
||||||
export interface LoadedUpdate extends Update {
|
export interface LoadedUpdate extends Update {
|
||||||
meta: LoadedUpdateMeta
|
meta: LoadedUpdateMeta
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateSelection {
|
|
||||||
update: Update
|
|
||||||
comparing: boolean
|
|
||||||
}
|
|
||||||
|
|
|
@ -3,12 +3,12 @@ import type { Nullable } from '../../../../../types/utils'
|
||||||
import type { HistoryContextValue } from '../context/types/history-context-value'
|
import type { HistoryContextValue } from '../context/types/history-context-value'
|
||||||
import type { FileDiff } from '../services/types/file'
|
import type { FileDiff } from '../services/types/file'
|
||||||
import type { DiffOperation } from '../services/types/diff-operation'
|
import type { DiffOperation } from '../services/types/diff-operation'
|
||||||
import type { Update } from '../services/types/update'
|
import type { LoadedUpdate, UpdateRange } from '../services/types/update'
|
||||||
|
|
||||||
function getUpdateForVersion(
|
function getUpdateForVersion(
|
||||||
version: Update['toV'],
|
version: LoadedUpdate['toV'],
|
||||||
updates: HistoryContextValue['updates']
|
updates: HistoryContextValue['updates']
|
||||||
): Nullable<Update> {
|
): Nullable<LoadedUpdate> {
|
||||||
return updates.filter(update => update.toV === version)?.[0] ?? null
|
return updates.filter(update => update.toV === version)?.[0] ?? null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,18 +19,13 @@ type FileWithOps = {
|
||||||
|
|
||||||
function getFilesWithOps(
|
function getFilesWithOps(
|
||||||
files: FileDiff[],
|
files: FileDiff[],
|
||||||
updateSelection: HistoryContextValue['updateSelection'],
|
updateRange: UpdateRange,
|
||||||
|
comparing: boolean,
|
||||||
updates: HistoryContextValue['updates']
|
updates: HistoryContextValue['updates']
|
||||||
): FileWithOps[] {
|
): FileWithOps[] {
|
||||||
if (!updateSelection) {
|
if (updateRange.toV && !comparing) {
|
||||||
return []
|
|
||||||
}
|
|
||||||
if (updateSelection.update.toV && !updateSelection.comparing) {
|
|
||||||
const filesWithOps: FileWithOps[] = []
|
const filesWithOps: FileWithOps[] = []
|
||||||
const currentUpdate = getUpdateForVersion(
|
const currentUpdate = getUpdateForVersion(updateRange.toV, updates)
|
||||||
updateSelection.update.toV,
|
|
||||||
updates
|
|
||||||
)
|
|
||||||
|
|
||||||
if (currentUpdate !== null) {
|
if (currentUpdate !== null) {
|
||||||
for (const pathname of currentUpdate.pathnames) {
|
for (const pathname of currentUpdate.pathnames) {
|
||||||
|
@ -95,12 +90,13 @@ const orderedOpTypes: DiffOperation[] = [
|
||||||
|
|
||||||
export function autoSelectFile(
|
export function autoSelectFile(
|
||||||
files: FileDiff[],
|
files: FileDiff[],
|
||||||
updateSelection: HistoryContextValue['updateSelection'],
|
updateRange: UpdateRange,
|
||||||
|
comparing: boolean,
|
||||||
updates: HistoryContextValue['updates']
|
updates: HistoryContextValue['updates']
|
||||||
) {
|
) {
|
||||||
let fileToSelect: Nullable<FileDiff> = null
|
let fileToSelect: Nullable<FileDiff> = null
|
||||||
|
|
||||||
const filesWithOps = getFilesWithOps(files, updateSelection, updates)
|
const filesWithOps = getFilesWithOps(files, updateRange, comparing, updates)
|
||||||
for (const opType of orderedOpTypes) {
|
for (const opType of orderedOpTypes) {
|
||||||
const fileWithMatchingOpType = _.find(filesWithOps, {
|
const fileWithMatchingOpType = _.find(filesWithOps, {
|
||||||
operation: opType,
|
operation: opType,
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import ColorManager from '../../../ide/colors/ColorManager'
|
import ColorManager from '../../../ide/colors/ColorManager'
|
||||||
import { Nullable } from '../../../../../types/utils'
|
import { Nullable } from '../../../../../types/utils'
|
||||||
import { User } from '../services/types/shared'
|
import { User } from '../services/types/shared'
|
||||||
import { ProjectOp } from '../services/types/update'
|
import { ProjectOp, UpdateRange } from '../services/types/update'
|
||||||
|
import { Selection } from '../services/types/selection'
|
||||||
|
|
||||||
export const getUserColor = (user?: Nullable<{ id: string }>) => {
|
export const getUserColor = (user?: Nullable<{ id: string }>) => {
|
||||||
const hue = ColorManager.getHueForUserId(user?.id) || 100
|
const hue = ColorManager.getHueForUserId(user?.id) || 100
|
||||||
|
@ -35,3 +36,11 @@ export const getProjectOpDoc = (projectOp: ProjectOp) => {
|
||||||
}
|
}
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const updateIsSelected = (update: UpdateRange, selection: Selection) => {
|
||||||
|
return (
|
||||||
|
selection.updateRange &&
|
||||||
|
update.fromV >= selection.updateRange.fromV &&
|
||||||
|
update.toV <= selection.updateRange.toV
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -39,6 +39,8 @@ history-root {
|
||||||
}
|
}
|
||||||
|
|
||||||
.change-list {
|
.change-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
width: @versions-list-width;
|
width: @versions-list-width;
|
||||||
font-size: @font-size-small;
|
font-size: @font-size-small;
|
||||||
border-left: 1px solid @history-react-separator-color;
|
border-left: 1px solid @history-react-separator-color;
|
||||||
|
@ -53,6 +55,11 @@ history-root {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.history-version-list-container {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.history-toggle-switch-container,
|
.history-toggle-switch-container,
|
||||||
.history-version-day,
|
.history-version-day,
|
||||||
.history-version-details {
|
.history-version-details {
|
||||||
|
@ -79,6 +86,10 @@ history-root {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.history-version-selected {
|
||||||
|
background-color: @green-10;
|
||||||
|
}
|
||||||
|
|
||||||
.history-version-metadata-time {
|
.history-version-metadata-time {
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
|
|
|
@ -3,7 +3,6 @@ import type { HistoryContextValue } from '../../../../../frontend/js/features/hi
|
||||||
import type { FileDiff } from '../../../../../frontend/js/features/history/services/types/file'
|
import type { FileDiff } from '../../../../../frontend/js/features/history/services/types/file'
|
||||||
import { autoSelectFile } from '../../../../../frontend/js/features/history/utils/auto-select-file'
|
import { autoSelectFile } from '../../../../../frontend/js/features/history/utils/auto-select-file'
|
||||||
import type { User } from '../../../../../frontend/js/features/history/services/types/shared'
|
import type { User } from '../../../../../frontend/js/features/history/services/types/shared'
|
||||||
import { UpdateSelection } from '../../../../../frontend/js/features/history/services/types/update'
|
|
||||||
|
|
||||||
describe('autoSelectFile', function () {
|
describe('autoSelectFile', function () {
|
||||||
const historyUsers: User[] = [
|
const historyUsers: User[] = [
|
||||||
|
@ -260,12 +259,7 @@ describe('autoSelectFile', function () {
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const updateSelection: UpdateSelection = {
|
const pathname = autoSelectFile(files, updates[0], comparing, updates)
|
||||||
update: updates[0],
|
|
||||||
comparing,
|
|
||||||
}
|
|
||||||
|
|
||||||
const pathname = autoSelectFile(files, updateSelection, updates)
|
|
||||||
|
|
||||||
expect(pathname).to.equal('newfolder1/newfile10.tex')
|
expect(pathname).to.equal('newfolder1/newfile10.tex')
|
||||||
})
|
})
|
||||||
|
@ -330,12 +324,7 @@ describe('autoSelectFile', function () {
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const updateSelection: UpdateSelection = {
|
const pathname = autoSelectFile(files, updates[0], comparing, updates)
|
||||||
update: updates[0],
|
|
||||||
comparing,
|
|
||||||
}
|
|
||||||
|
|
||||||
const pathname = autoSelectFile(files, updateSelection, updates)
|
|
||||||
|
|
||||||
expect(pathname).to.equal('newfile1.tex')
|
expect(pathname).to.equal('newfile1.tex')
|
||||||
})
|
})
|
||||||
|
@ -431,12 +420,7 @@ describe('autoSelectFile', function () {
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const updateSelection: UpdateSelection = {
|
const pathname = autoSelectFile(files, updates[0], comparing, updates)
|
||||||
update: updates[0],
|
|
||||||
comparing,
|
|
||||||
}
|
|
||||||
|
|
||||||
const pathname = autoSelectFile(files, updateSelection, updates)
|
|
||||||
|
|
||||||
expect(pathname).to.equal('main3.tex')
|
expect(pathname).to.equal('main3.tex')
|
||||||
})
|
})
|
||||||
|
@ -602,12 +586,7 @@ describe('autoSelectFile', function () {
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const updateSelection: UpdateSelection = {
|
const pathname = autoSelectFile(files, updates[0], comparing, updates)
|
||||||
update: updates[0],
|
|
||||||
comparing,
|
|
||||||
}
|
|
||||||
|
|
||||||
const pathname = autoSelectFile(files, updateSelection, updates)
|
|
||||||
|
|
||||||
expect(pathname).to.equal('main.tex')
|
expect(pathname).to.equal('main.tex')
|
||||||
})
|
})
|
||||||
|
@ -710,12 +689,7 @@ describe('autoSelectFile', function () {
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const updateSelection: UpdateSelection = {
|
const pathname = autoSelectFile(files, updates[0], comparing, updates)
|
||||||
update: updates[0],
|
|
||||||
comparing,
|
|
||||||
}
|
|
||||||
|
|
||||||
const pathname = autoSelectFile(files, updateSelection, updates)
|
|
||||||
|
|
||||||
expect(pathname).to.equal('certainly_not_main.tex')
|
expect(pathname).to.equal('certainly_not_main.tex')
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue