Merge pull request #13104 from overleaf/td-history-compare-to-from

History migration: Add "compare to" and "compare from" options

GitOrigin-RevId: f550ad06b1e812011ecb32e6772354eb0abc2163
This commit is contained in:
Tim Down 2023-05-17 11:52:49 +01:00 committed by Copybot
parent 5d0ce8c4cd
commit c870bd5f53
12 changed files with 136 additions and 99 deletions

View file

@ -401,7 +401,9 @@
"history_add_label": "", "history_add_label": "",
"history_adding_label": "", "history_adding_label": "",
"history_are_you_sure_delete_label": "", "history_are_you_sure_delete_label": "",
"history_compare_with_this_version": "", "history_compare_from_this_version": "",
"history_compare_to_selected_version": "",
"history_compare_to_this_version": "",
"history_delete_label": "", "history_delete_label": "",
"history_deleting_label": "", "history_deleting_label": "",
"history_download_this_version": "", "history_download_this_version": "",

View file

@ -99,6 +99,7 @@ function AllHistoryList() {
const faded = const faded =
updatesInfo.freeHistoryLimitHit && updatesInfo.freeHistoryLimitHit &&
index === visibleUpdates.length - 1 index === visibleUpdates.length - 1
const selectable = !faded && (selection.comparing || !selected)
return ( return (
<HistoryVersion <HistoryVersion
@ -109,7 +110,7 @@ function AllHistoryList() {
setSelection={setSelection} setSelection={setSelection}
selected={selected} selected={selected}
currentUserId={currentUserId} currentUserId={currentUserId}
comparing={selection.comparing} selectable={selectable}
projectId={projectId} projectId={projectId}
setActiveDropdownItem={setActiveDropdownItem} setActiveDropdownItem={setActiveDropdownItem}
closeDropdownForItem={closeDropdownForItem} closeDropdownForItem={closeDropdownForItem}

View file

@ -1,24 +1,22 @@
import Download from './menu-item/download' import Download from './menu-item/download'
import Compare from './menu-item/compare'
import { Version } from '../../../services/types/update' import { Version } from '../../../services/types/update'
import { ActiveDropdown } from '../../../hooks/use-dropdown-active-item' import { ActiveDropdown } from '../../../hooks/use-dropdown-active-item'
import { useCallback } from 'react' import { useCallback } from 'react'
import CompareItems from './menu-item/compare-items'
type LabelDropdownContentProps = { type LabelDropdownContentProps = {
projectId: string projectId: string
version: Version version: Version
updateMetaEndTimestamp: number versionTimestamp: number
selected: boolean selected: boolean
comparing: boolean
closeDropdownForItem: ActiveDropdown['closeDropdownForItem'] closeDropdownForItem: ActiveDropdown['closeDropdownForItem']
} }
function LabelDropdownContent({ function LabelDropdownContent({
projectId, projectId,
version, version,
updateMetaEndTimestamp, versionTimestamp,
selected, selected,
comparing,
closeDropdownForItem, closeDropdownForItem,
}: LabelDropdownContentProps) { }: LabelDropdownContentProps) {
const closeDropdown = useCallback(() => { const closeDropdown = useCallback(() => {
@ -32,15 +30,16 @@ function LabelDropdownContent({
version={version} version={version}
closeDropdown={closeDropdown} closeDropdown={closeDropdown}
/> />
{!comparing && !selected && ( <CompareItems
<Compare updateRange={{
projectId={projectId} fromV: version,
fromV={version} toV: version,
toV={version} fromVTimestamp: versionTimestamp,
updateMetaEndTimestamp={updateMetaEndTimestamp} toVTimestamp: versionTimestamp,
}}
selected={selected}
closeDropdown={closeDropdown} closeDropdown={closeDropdown}
/> />
)}
</> </>
) )
} }

View file

@ -0,0 +1,69 @@
import { useTranslation } from 'react-i18next'
import { useHistoryContext } from '../../../../context/history-context'
import { UpdateRange } from '../../../../services/types/update'
import Compare from './compare'
import { updateRangeUnion } from '../../../../utils/range'
type CompareItemsProps = {
updateRange: UpdateRange
selected: boolean
closeDropdown: () => void
}
function CompareItems({
updateRange,
selected,
closeDropdown,
}: CompareItemsProps) {
const { t } = useTranslation()
const { selection } = useHistoryContext()
const { updateRange: selRange, comparing } = selection
const notASelectionBoundary =
!!selRange &&
comparing &&
updateRange.toV !== selRange.toV &&
updateRange.fromV !== selRange.fromV
const showCompareWithSelected = !comparing && !!selRange && !selected
const showCompareToThis =
notASelectionBoundary && updateRange.toV > selRange.fromV
const showCompareFromThis =
notASelectionBoundary && updateRange.fromV < selRange.toV
return (
<>
{showCompareWithSelected ? (
<Compare
comparisonRange={updateRangeUnion(updateRange, selRange)}
closeDropdown={closeDropdown}
text={t('history_compare_to_selected_version')}
/>
) : null}
{showCompareToThis ? (
<Compare
comparisonRange={{
fromV: selRange.fromV,
toV: updateRange.toV,
fromVTimestamp: selRange.fromVTimestamp,
toVTimestamp: updateRange.toVTimestamp,
}}
closeDropdown={closeDropdown}
text={t('history_compare_to_this_version')}
/>
) : null}
{showCompareFromThis ? (
<Compare
comparisonRange={{
fromV: updateRange.fromV,
toV: selRange.toV,
fromVTimestamp: updateRange.fromVTimestamp,
toVTimestamp: selRange.toVTimestamp,
}}
closeDropdown={closeDropdown}
text={t('history_compare_from_this_version')}
/>
) : null}
</>
)
}
export default CompareItems

View file

@ -1,56 +1,36 @@
import { useTranslation } from 'react-i18next'
import { MenuItem, MenuItemProps } from 'react-bootstrap' import { MenuItem, MenuItemProps } from 'react-bootstrap'
import Icon from '../../../../../../shared/components/icon' import Icon from '../../../../../../shared/components/icon'
import { useHistoryContext } from '../../../../context/history-context' import { useHistoryContext } from '../../../../context/history-context'
import { computeUpdateRange } from '../../../../utils/range'
import { UpdateRange } from '../../../../services/types/update' import { UpdateRange } from '../../../../services/types/update'
type CompareProps = { type CompareProps = {
projectId: string comparisonRange: UpdateRange
updateMetaEndTimestamp: number text: string
closeDropdown: () => void closeDropdown: () => void
} & Pick<UpdateRange, 'fromV' | 'toV'> }
function Compare({ function Compare({
projectId, comparisonRange,
fromV, text,
toV,
updateMetaEndTimestamp,
closeDropdown, closeDropdown,
...props ...props
}: CompareProps) { }: CompareProps) {
const { t } = useTranslation()
const { setSelection } = useHistoryContext() const { setSelection } = useHistoryContext()
const handleCompareVersion = (e: React.MouseEvent<MenuItemProps>) => { const handleCompareVersion = (e: React.MouseEvent<MenuItemProps>) => {
e.stopPropagation() e.stopPropagation()
closeDropdown() closeDropdown()
setSelection(prevSelection => { setSelection({
const { updateRange } = prevSelection updateRange: comparisonRange,
if (updateRange) {
const range = computeUpdateRange(
updateRange,
fromV,
toV,
updateMetaEndTimestamp
)
return {
updateRange: range,
comparing: true, comparing: true,
files: [], files: [],
}
}
return prevSelection
}) })
} }
return ( return (
<MenuItem onClick={handleCompareVersion} {...props}> <MenuItem onClick={handleCompareVersion} {...props}>
<Icon type="exchange" fw /> {t('history_compare_with_this_version')} <Icon type="exchange" fw /> {text}
</MenuItem> </MenuItem>
) )
} }

View file

@ -1,15 +1,15 @@
import AddLabel from './menu-item/add-label' import AddLabel from './menu-item/add-label'
import Download from './menu-item/download' import Download from './menu-item/download'
import Compare from './menu-item/compare'
import { LoadedUpdate } from '../../../services/types/update' import { LoadedUpdate } from '../../../services/types/update'
import { useCallback } from 'react' import { useCallback } from 'react'
import { ActiveDropdown } from '../../../hooks/use-dropdown-active-item' import { ActiveDropdown } from '../../../hooks/use-dropdown-active-item'
import CompareItems from './menu-item/compare-items'
import { updateRangeForUpdate } from '../../../utils/history-details'
type VersionDropdownContentProps = { type VersionDropdownContentProps = {
projectId: string projectId: string
update: LoadedUpdate update: LoadedUpdate
selected: boolean selected: boolean
comparing: boolean
closeDropdownForItem: ActiveDropdown['closeDropdownForItem'] closeDropdownForItem: ActiveDropdown['closeDropdownForItem']
} }
@ -17,9 +17,10 @@ function VersionDropdownContent({
projectId, projectId,
update, update,
selected, selected,
comparing,
closeDropdownForItem, closeDropdownForItem,
}: VersionDropdownContentProps) { }: VersionDropdownContentProps) {
const updateRange = updateRangeForUpdate(update)
const closeDropdown = useCallback(() => { const closeDropdown = useCallback(() => {
closeDropdownForItem(update) closeDropdownForItem(update)
}, [closeDropdownForItem, update]) }, [closeDropdownForItem, update])
@ -36,15 +37,11 @@ function VersionDropdownContent({
version={update.toV} version={update.toV}
closeDropdown={closeDropdown} closeDropdown={closeDropdown}
/> />
{!comparing && !selected && ( <CompareItems
<Compare updateRange={updateRange}
projectId={projectId} selected={selected}
fromV={update.fromV}
toV={update.toV}
updateMetaEndTimestamp={update.meta.end_ts}
closeDropdown={closeDropdown} closeDropdown={closeDropdown}
/> />
)}
</> </>
) )
} }

View file

@ -18,7 +18,7 @@ type HistoryVersionProps = {
update: LoadedUpdate update: LoadedUpdate
currentUserId: string currentUserId: string
projectId: string projectId: string
comparing: boolean selectable: boolean
faded: boolean faded: boolean
showDivider: boolean showDivider: boolean
selected: boolean selected: boolean
@ -33,7 +33,7 @@ function HistoryVersion({
update, update,
currentUserId, currentUserId,
projectId, projectId,
comparing, selectable,
faded, faded,
showDivider, showDivider,
selected, selected,
@ -44,7 +44,6 @@ function HistoryVersion({
closeDropdownForItem, closeDropdownForItem,
}: HistoryVersionProps) { }: HistoryVersionProps) {
const orderedLabels = orderBy(update.labels, ['created_at'], ['desc']) const orderedLabels = orderBy(update.labels, ['created_at'], ['desc'])
const selectable = !faded && (comparing || !selected)
return ( return (
<> <>
@ -99,7 +98,6 @@ function HistoryVersion({
> >
{dropdownActive ? ( {dropdownActive ? (
<VersionDropdownContent <VersionDropdownContent
comparing={comparing}
selected={selected} selected={selected}
update={update} update={update}
projectId={projectId} projectId={projectId}

View file

@ -17,7 +17,6 @@ type LabelListItemProps = {
labels: LoadedLabel[] labels: LoadedLabel[]
currentUserId: string currentUserId: string
projectId: string projectId: string
comparing: boolean
selected: boolean selected: boolean
selectable: boolean selectable: boolean
setSelection: HistoryContextValue['setSelection'] setSelection: HistoryContextValue['setSelection']
@ -32,7 +31,6 @@ function LabelListItem({
labels, labels,
currentUserId, currentUserId,
projectId, projectId,
comparing,
selected, selected,
selectable, selectable,
setSelection, setSelection,
@ -105,10 +103,9 @@ function LabelListItem({
> >
{dropdownActive ? ( {dropdownActive ? (
<LabelDropdownContent <LabelDropdownContent
comparing={comparing}
selected={selected} selected={selected}
version={version} version={version}
updateMetaEndTimestamp={toVTimestamp} versionTimestamp={toVTimestamp}
projectId={projectId} projectId={projectId}
closeDropdownForItem={closeDropdownForItem} closeDropdownForItem={closeDropdownForItem}
/> />

View file

@ -39,7 +39,6 @@ function LabelsList() {
version={version} version={version}
currentUserId={currentUserId} currentUserId={currentUserId}
projectId={projectId} projectId={projectId}
comparing={selection.comparing}
selected={selected} selected={selected}
selectable={!(singleVersionSelected && selected)} selectable={!(singleVersionSelected && selected)}
setSelection={setSelection} setSelection={setSelection}

View file

@ -1,9 +1,12 @@
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useHistoryContext } from '../../context/history-context' import { useHistoryContext } from '../../context/history-context'
import { getUpdateForVersion } from '../../utils/history-details' import {
import { computeUpdateRange } from '../../utils/range' getUpdateForVersion,
updateRangeForUpdate,
} from '../../utils/history-details'
import { isAnyVersionMatchingSelection } from '../../utils/label' import { isAnyVersionMatchingSelection } from '../../utils/label'
import { HistoryContextValue } from '../../context/types/history-context-value' import { HistoryContextValue } from '../../context/types/history-context-value'
import { updateRangeUnion } from '../../utils/range'
type ToggleSwitchProps = Pick< type ToggleSwitchProps = Pick<
HistoryContextValue, HistoryContextValue,
@ -28,22 +31,19 @@ function ToggleSwitch({ labelsOnly, setLabelsOnly }: ToggleSwitchProps) {
// in labels only mode the `fromV` is equal to `toV` value // in labels only mode the `fromV` is equal to `toV` value
// switching to all history mode and triggering immediate comparison with // switching to all history mode and triggering immediate comparison with
// an older version would cause a bug if the computation below is skipped. // an older version would cause a bug if the computation below is skipped.
const update = selection.updateRange?.toV
? getUpdateForVersion(selection.updateRange.toV, updatesInfo.updates)
: null
const { updateRange } = selection const { updateRange } = selection
const update = updateRange?.toV
? getUpdateForVersion(updateRange.toV, updatesInfo.updates)
: null
if ( if (
updateRange && updateRange &&
update && update &&
(update.fromV !== updateRange.fromV || update.toV !== updateRange.toV) (update.fromV !== updateRange.fromV || update.toV !== updateRange.toV)
) { ) {
const range = computeUpdateRange( const range = updateRangeUnion(
updateRange, updateRangeForUpdate(update),
update.fromV, updateRange
update.toV,
update.meta.end_ts
) )
setSelection({ setSelection({

View file

@ -1,26 +1,19 @@
import { UpdateRange } from '../services/types/update' import { UpdateRange } from '../services/types/update'
export const computeUpdateRange = ( export const updateRangeUnion = (
updateRange: UpdateRange, updateRange1: UpdateRange,
fromV: number, updateRange2: UpdateRange
toV: number,
updateMetaEndTimestamp: number
) => { ) => {
const fromVersion = Math.min(fromV, updateRange.fromV)
const toVersion = Math.max(toV, updateRange.toV)
const fromVTimestamp = Math.min(
updateMetaEndTimestamp,
updateRange.fromVTimestamp
)
const toVTimestamp = Math.max(
updateMetaEndTimestamp,
updateRange.toVTimestamp
)
return { return {
fromV: fromVersion, fromV: Math.min(updateRange1.fromV, updateRange2.fromV),
toV: toVersion, toV: Math.max(updateRange1.toV, updateRange2.toV),
fromVTimestamp, fromVTimestamp: Math.min(
toVTimestamp, updateRange1.fromVTimestamp,
updateRange2.fromVTimestamp
),
toVTimestamp: Math.max(
updateRange1.toVTimestamp,
updateRange2.toVTimestamp
),
} }
} }

View file

@ -682,7 +682,9 @@
"history_add_label": "Add label", "history_add_label": "Add label",
"history_adding_label": "Adding label", "history_adding_label": "Adding label",
"history_are_you_sure_delete_label": "Are you sure you want to delete the following label", "history_are_you_sure_delete_label": "Are you sure you want to delete the following label",
"history_compare_with_this_version": "Compare with this version", "history_compare_from_this_version": "Compare from this version",
"history_compare_to_selected_version": "Compare to selected version",
"history_compare_to_this_version": "Compare to this version",
"history_delete_label": "Delete label", "history_delete_label": "Delete label",
"history_deleting_label": "Deleting label", "history_deleting_label": "Deleting label",
"history_download_this_version": "Download this version", "history_download_this_version": "Download this version",