mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
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:
parent
5d0ce8c4cd
commit
c870bd5f53
12 changed files with 136 additions and 99 deletions
|
@ -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": "",
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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({
|
||||||
|
|
|
@ -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
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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",
|
||||||
|
|
Loading…
Reference in a new issue