History ui - Adding the compare dropdown on the withinSelected versions

GitOrigin-RevId: 728979b11918c9bc1e535024040d90053536251d
This commit is contained in:
Davinder Singh
2023-08-23 10:14:08 +01:00
committed by Copybot
parent 5ed07f28ec
commit c06ba69223
17 changed files with 310 additions and 35 deletions

View File

@@ -159,6 +159,7 @@
"commons_plan_tooltip": "",
"compact": "",
"company_name": "",
"compare": "",
"comparing_from_x_to_y": "",
"compile_error_entry_description": "",
"compile_error_handling": "",

View File

@@ -94,7 +94,12 @@ function AllHistoryList() {
update.fromV,
update.toV
)
const dropdownActive = update === activeDropdownItem.item
const dropdownActive =
update === activeDropdownItem.item &&
activeDropdownItem.whichDropDown === 'moreOptions'
const compareDropdownActive =
update === activeDropdownItem.item &&
activeDropdownItem.whichDropDown === 'compare'
const showDivider = Boolean(update.meta.first_in_day && index > 0)
const faded =
updatesInfo.freeHistoryLimitHit &&
@@ -119,6 +124,10 @@ function AllHistoryList() {
setActiveDropdownItem={setActiveDropdownItem}
closeDropdownForItem={closeDropdownForItem}
dropdownOpen={activeDropdownItem.isOpened && dropdownActive}
compareDropdownActive={compareDropdownActive}
compareDropdownOpen={
activeDropdownItem.isOpened && compareDropdownActive
}
dropdownActive={dropdownActive}
/>
)

View File

@@ -1,8 +1,6 @@
import { useRef, useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { useRef, useEffect, ReactNode } from 'react'
import { Dropdown } from 'react-bootstrap'
import DropdownToggleWithTooltip from '../../../../../shared/components/dropdown/dropdown-toggle-with-tooltip'
import Icon from '../../../../../shared/components/icon'
import DropdownMenuWithRef from '../../../../../shared/components/dropdown/dropdown-menu-with-ref'
type DropdownMenuProps = {
@@ -10,6 +8,8 @@ type DropdownMenuProps = {
children: React.ReactNode
parentSelector?: string
isOpened: boolean
iconTag: ReactNode
toolTipDescription: string
setIsOpened: (isOpened: boolean) => void
}
@@ -18,9 +18,10 @@ function ActionsDropdown({
children,
parentSelector,
isOpened,
iconTag,
setIsOpened,
toolTipDescription,
}: DropdownMenuProps) {
const { t } = useTranslation()
const menuRef = useRef<HTMLElement>()
// handle the placement of the dropdown above or below the toggle button
@@ -58,11 +59,11 @@ function ActionsDropdown({
className="history-version-dropdown-menu-btn"
tooltipProps={{
id,
description: t('more_actions'),
description: toolTipDescription,
overlayProps: { placement: 'bottom', trigger: ['hover'] },
}}
>
<Icon type="ellipsis-v" accessibilityLabel={t('more_actions')} />
{iconTag}
</DropdownToggleWithTooltip>
<DropdownMenuWithRef
bsRole="menu"

View File

@@ -0,0 +1,112 @@
import { LoadedUpdate, Version } from '../../../services/types/update'
import { ReactNode, useCallback } from 'react'
import { ActiveDropdown } from '../../../hooks/use-dropdown-active-item'
import { MenuItem } from 'react-bootstrap'
import { useTranslation } from 'react-i18next'
import CompareItems from './menu-item/compare-items'
import { updateRangeForUpdate } from '../../../utils/history-details'
type VersionDropdownContentAllHistoryProps = {
update: LoadedUpdate
closeDropdownForItem: ActiveDropdown['closeDropdownForItem']
}
function CompareVersionDropdownContentAllHistory({
update,
closeDropdownForItem,
}: VersionDropdownContentAllHistoryProps) {
const updateRange = updateRangeForUpdate(update)
const closeDropdown = useCallback(() => {
closeDropdownForItem(update, 'compare')
}, [closeDropdownForItem, update])
const { t } = useTranslation()
return (
<>
<DropdownOption>
<CompareItems
updateRange={updateRange}
selected="aboveSelected"
text={t('history_compare_up_to_this_version')}
closeDropdown={closeDropdown}
/>
</DropdownOption>
<DropdownOption>
<CompareItems
updateRange={updateRange}
selected="belowSelected"
text={t('history_compare_from_this_version')}
closeDropdown={closeDropdown}
/>
</DropdownOption>
</>
)
}
type VersionDropdownContentLabelsListProps = {
version: Version
versionTimestamp: number
closeDropdownForItem: ActiveDropdown['closeDropdownForItem']
}
function CompareVersionDropdownContentLabelsList({
version,
versionTimestamp,
closeDropdownForItem,
}: VersionDropdownContentLabelsListProps) {
const closeDropdownLabels = useCallback(() => {
closeDropdownForItem(version, 'compare')
}, [closeDropdownForItem, version])
const { t } = useTranslation()
return (
<>
<DropdownOption>
<CompareItems
updateRange={{
fromV: version,
toV: version,
fromVTimestamp: versionTimestamp,
toVTimestamp: versionTimestamp,
}}
selected="aboveSelected"
text={t('history_compare_up_to_this_version')}
closeDropdown={closeDropdownLabels}
/>
</DropdownOption>
<DropdownOption>
<CompareItems
updateRange={{
fromV: version,
toV: version,
fromVTimestamp: versionTimestamp,
toVTimestamp: versionTimestamp,
}}
selected="belowSelected"
text={t('history_compare_from_this_version')}
closeDropdown={closeDropdownLabels}
/>
</DropdownOption>
</>
)
}
type DropdownOptionProps = {
children: ReactNode
}
function DropdownOption({ children, ...props }: DropdownOptionProps) {
return (
<>
<MenuItem {...props}>{children}</MenuItem>
</>
)
}
export {
CompareVersionDropdownContentAllHistory,
CompareVersionDropdownContentLabelsList,
}

View File

@@ -0,0 +1,39 @@
import ActionsDropdown from './actions-dropdown'
import MaterialIcon from '../../../../../shared/components/material-icon'
import { useTranslation } from 'react-i18next'
type CompareVersionDropdownProps = {
children: React.ReactNode
id: string
isOpened: boolean
setIsOpened: (isOpened: boolean) => void
}
function CompareVersionDropdown({
children,
id,
isOpened,
setIsOpened,
}: CompareVersionDropdownProps) {
const { t } = useTranslation()
return (
<ActionsDropdown
id={id}
isOpened={isOpened}
setIsOpened={setIsOpened}
parentSelector="[data-history-version-list-container]"
toolTipDescription={t('compare')}
iconTag={
<MaterialIcon
type="align_space_even"
className="material-symbols-rounded history-dropdown-icon p-1"
accessibilityLabel="compare drop down"
/>
}
>
{children}
</ActionsDropdown>
)
}
export default CompareVersionDropdown

View File

@@ -1,4 +1,6 @@
import ActionsDropdown from './actions-dropdown'
import Icon from '../../../../../shared/components/icon'
import { useTranslation } from 'react-i18next'
type HistoryDropdownProps = {
children: React.ReactNode
@@ -13,11 +15,16 @@ function HistoryDropdown({
isOpened,
setIsOpened,
}: HistoryDropdownProps) {
const { t } = useTranslation()
return (
<ActionsDropdown
id={id}
isOpened={isOpened}
toolTipDescription={t('more_actions')}
setIsOpened={setIsOpened}
iconTag={
<Icon type="ellipsis-v" accessibilityLabel={t('more_actions')} />
}
parentSelector="[data-history-version-list-container]"
>
{children}

View File

@@ -15,7 +15,7 @@ function LabelDropdownContent({
closeDropdownForItem,
}: LabelDropdownContentProps) {
const closeDropdown = useCallback(() => {
closeDropdownForItem(version)
closeDropdownForItem(version, 'moreOptions')
}, [closeDropdownForItem, version])
return (

View File

@@ -8,12 +8,14 @@ import { ItemSelectionState } from '../../../../utils/history-details'
type CompareItemsProps = {
updateRange: UpdateRange
selected: ItemSelectionState
text?: string
closeDropdown: () => void
}
function CompareItems({
updateRange,
selected,
text,
closeDropdown,
}: CompareItemsProps) {
const { t } = useTranslation()
@@ -35,6 +37,7 @@ function CompareItems({
}}
closeDropdown={closeDropdown}
toolTipDescription={t('history_compare_from_this_version')}
text={text}
icon={
<MaterialIcon
type="align_end"
@@ -53,6 +56,7 @@ function CompareItems({
}}
closeDropdown={closeDropdown}
toolTipDescription={t('history_compare_up_to_this_version')}
text={text}
icon={
<MaterialIcon
type="align_start"

View File

@@ -8,12 +8,14 @@ import { Button } from 'react-bootstrap'
type CompareProps = {
comparisonRange: UpdateRange
icon?: ReactNode
text?: string
toolTipDescription?: string
closeDropdown: () => void
}
function Compare({
comparisonRange,
text,
closeDropdown,
toolTipDescription,
icon = <Icon type="exchange" fw />,
@@ -46,6 +48,7 @@ function Compare({
>
<span className="sr-only">{toolTipDescription}</span>
{icon}
{text ?? <span className="px-1">{text}</span>}
</Button>
</Tooltip>
)

View File

@@ -16,7 +16,7 @@ function VersionDropdownContent({
closeDropdownForItem,
}: VersionDropdownContentProps) {
const closeDropdown = useCallback(() => {
closeDropdownForItem(update)
closeDropdownForItem(update, 'moreOptions')
}, [closeDropdownForItem, update])
return (

View File

@@ -17,6 +17,8 @@ import { memo, useCallback } from 'react'
import { HistoryContextValue } from '../../context/types/history-context-value'
import VersionDropdownContent from './dropdown/version-dropdown-content'
import CompareItems from './dropdown/menu-item/compare-items'
import CompareVersionDropdown from './dropdown/compare-version-dropdown'
import { CompareVersionDropdownContentAllHistory } from './dropdown/compare-version-dropdown-content'
type HistoryVersionProps = {
update: LoadedUpdate
@@ -29,6 +31,8 @@ type HistoryVersionProps = {
setSelection: HistoryContextValue['setSelection']
dropdownOpen: boolean
dropdownActive: boolean
compareDropdownOpen: boolean
compareDropdownActive: boolean
setActiveDropdownItem: ActiveDropdown['setActiveDropdownItem']
closeDropdownForItem: ActiveDropdown['closeDropdownForItem']
}
@@ -44,13 +48,15 @@ function HistoryVersion({
setSelection,
dropdownOpen,
dropdownActive,
compareDropdownOpen,
compareDropdownActive,
setActiveDropdownItem,
closeDropdownForItem,
}: HistoryVersionProps) {
const orderedLabels = orderBy(update.labels, ['created_at'], ['desc'])
const closeDropdown = useCallback(() => {
closeDropdownForItem(update)
closeDropdownForItem(update, 'moreOptions')
}, [closeDropdownForItem, update])
const updateRange = updateRangeForUpdate(update)
@@ -97,7 +103,11 @@ function HistoryVersion({
id={`${update.fromV}_${update.toV}`}
isOpened={dropdownOpen}
setIsOpened={(isOpened: boolean) =>
setActiveDropdownItem({ item: update, isOpened })
setActiveDropdownItem({
item: update,
isOpened,
whichDropDown: 'moreOptions',
})
}
>
{dropdownActive ? (
@@ -111,15 +121,34 @@ function HistoryVersion({
)}
{selected !== 'selected' ? (
<span data-testid="compare-icon-version" className="pull-right">
<div data-testid="compare-icon-version" className="pull-right">
{selected !== 'withinSelected' ? (
<CompareItems
updateRange={updateRange}
selected={selected}
closeDropdown={closeDropdown}
/>
) : null}
</span>
) : (
<CompareVersionDropdown
id={`${update.fromV}_${update.toV}`}
isOpened={compareDropdownOpen}
setIsOpened={(isOpened: boolean) =>
setActiveDropdownItem({
item: update,
isOpened,
whichDropDown: 'compare',
})
}
>
{compareDropdownActive ? (
<CompareVersionDropdownContentAllHistory
update={update}
closeDropdownForItem={closeDropdownForItem}
/>
) : null}
</CompareVersionDropdown>
)}
</div>
) : null}
<div className="history-version-main-details">

View File

@@ -13,6 +13,8 @@ import { HistoryContextValue } from '../../context/types/history-context-value'
import LabelDropdownContent from './dropdown/label-dropdown-content'
import CompareItems from './dropdown/menu-item/compare-items'
import { ItemSelectionState } from '../../utils/history-details'
import CompareVersionDropdown from './dropdown/compare-version-dropdown'
import { CompareVersionDropdownContentLabelsList } from './dropdown/compare-version-dropdown-content'
type LabelListItemProps = {
version: Version
@@ -24,6 +26,8 @@ type LabelListItemProps = {
setSelection: HistoryContextValue['setSelection']
dropdownOpen: boolean
dropdownActive: boolean
compareDropdownOpen: boolean
compareDropdownActive: boolean
setActiveDropdownItem: ActiveDropdown['setActiveDropdownItem']
closeDropdownForItem: ActiveDropdown['closeDropdownForItem']
}
@@ -38,6 +42,8 @@ function LabelListItem({
setSelection,
dropdownOpen,
dropdownActive,
compareDropdownOpen,
compareDropdownActive,
setActiveDropdownItem,
closeDropdownForItem,
}: LabelListItemProps) {
@@ -57,12 +63,16 @@ function LabelListItem({
const setIsOpened = useCallback(
(isOpened: boolean) => {
setActiveDropdownItem({ item: version, isOpened })
setActiveDropdownItem({
item: version,
isOpened,
whichDropDown: 'moreOptions',
})
},
[setActiveDropdownItem, version]
)
const closeDropdown = useCallback(() => {
closeDropdownForItem(version)
closeDropdownForItem(version, 'moreOptions')
}, [closeDropdownForItem, version])
return (
@@ -86,18 +96,38 @@ function LabelListItem({
/>
) : null}
</HistoryDropdown>
<span className="pull-right">
<CompareItems
updateRange={{
fromV: version,
toV: version,
fromVTimestamp: toVTimestamp,
toVTimestamp,
}}
selected={selected}
closeDropdown={closeDropdown}
/>
</span>
{selected !== 'selected' ? (
<div data-testid="compare-icon-version" className="pull-right">
{selected !== 'withinSelected' ? (
<CompareItems
updateRange={updateRange}
selected={selected}
closeDropdown={closeDropdown}
/>
) : (
<CompareVersionDropdown
id={version.toString()}
isOpened={compareDropdownOpen}
setIsOpened={(isOpened: boolean) =>
setActiveDropdownItem({
item: version,
isOpened,
whichDropDown: 'compare',
})
}
>
{compareDropdownActive ? (
<CompareVersionDropdownContentLabelsList
version={version}
closeDropdownForItem={closeDropdownForItem}
versionTimestamp={toVTimestamp}
/>
) : null}
</CompareVersionDropdown>
)}
</div>
) : null}
<div className="history-version-main-details">
{labels.map(label => (
<div key={label.id} className="history-version-label">

View File

@@ -21,7 +21,12 @@ function LabelsList() {
<>
{versionWithLabels.map(({ version, labels }) => {
const selected = isVersionSelected(selection, version)
const dropdownActive = version === activeDropdownItem.item
const dropdownActive =
version === activeDropdownItem.item &&
activeDropdownItem.whichDropDown === 'moreOptions'
const compareDropdownActive =
version === activeDropdownItem.item &&
activeDropdownItem.whichDropDown === 'compare'
return (
<LabelListItem
@@ -35,6 +40,10 @@ function LabelsList() {
setSelection={setSelection}
dropdownOpen={activeDropdownItem.isOpened && dropdownActive}
dropdownActive={dropdownActive}
compareDropdownActive={compareDropdownActive}
compareDropdownOpen={
activeDropdownItem.isOpened && compareDropdownActive
}
setActiveDropdownItem={setActiveDropdownItem}
closeDropdownForItem={closeDropdownForItem}
/>

View File

@@ -2,16 +2,21 @@ import { Dispatch, SetStateAction, useCallback, useState } from 'react'
import { LoadedUpdate, Version } from '../services/types/update'
type DropdownItem = LoadedUpdate | Version
type WhichDropDownType = 'moreOptions' | 'compare' | null
export type ActiveDropdownValue = {
item: DropdownItem | null
isOpened: boolean
whichDropDown: WhichDropDownType
}
export type ActiveDropdown = {
activeDropdownItem: ActiveDropdownValue
setActiveDropdownItem: Dispatch<SetStateAction<ActiveDropdownValue>>
closeDropdownForItem: (item: DropdownItem) => void
closeDropdownForItem: (
item: DropdownItem,
whichDropDown: WhichDropDownType
) => void
}
function useDropdownActiveItem(): ActiveDropdown {
@@ -19,10 +24,12 @@ function useDropdownActiveItem(): ActiveDropdown {
useState<ActiveDropdownValue>({
item: null,
isOpened: false,
whichDropDown: null,
})
const closeDropdownForItem = useCallback(
(item: DropdownItem) => setActiveDropdownItem({ item, isOpened: false }),
(item: DropdownItem, whichDropDown: WhichDropDownType) =>
setActiveDropdownItem({ item, isOpened: false, whichDropDown }),
[setActiveDropdownItem]
)

View File

@@ -409,11 +409,14 @@ history-root {
.history-compare-btn {
line-height: 1;
padding: 0;
color: black;
display: flex;
justify-content: center;
align-items: center;
}
.history-compare-btn {
line-height: 1;
padding: 0;
.history-compare-btn:hover {
text-decoration: none;
color: black;
}
.history-file-tree {

View File

@@ -258,6 +258,7 @@
"commons_plan_tooltip": "Youre on the __plan__ plan because of your affiliation with __institution__. Click to find out how to make the most of your Overleaf premium features.",
"compact": "Compact",
"company_name": "Company Name",
"compare": "Compare",
"compare_to_another_version": "Compare to another version",
"comparing_from_x_to_y": "Comparing from <0>__startTime__</0> to <0>__endTime__</0>",
"compile_error_entry_description": "An error which prevented this project from compiling",

View File

@@ -349,6 +349,26 @@ describe('change list', function () {
).to.be.true
})
})
it('opens the compare drop down and compares with selected version', function () {
cy.findByLabelText(/all history/i).click({ force: true })
cy.findAllByTestId('history-version-details')
.eq(2)
.within(() => {
cy.findByRole('button', {
name: /compare from this version/i,
}).click()
})
cy.findByRole('button', { name: /compare drop down/i }).click()
cy.findByRole('button', { name: /compare up to this version/i }).click()
cy.findAllByTestId('history-version-details').should($versions => {
const [first, ...rest] = Array.from($versions)
expect(first).to.have.attr('data-selected', 'aboveSelected')
rest.forEach(version =>
expect(version).to.have.attr('data-selected', 'selectedEdge')
)
})
})
})
describe('compare mode', function () {