From 0346e329065fd2850a4a178214cefa97c36846a9 Mon Sep 17 00:00:00 2001 From: June Kelly Date: Tue, 16 May 2023 09:18:06 +0100 Subject: [PATCH] Merge pull request #12836 from overleaf/td-history-performance History migration: Improve performance of selecting or comparing versions GitOrigin-RevId: 2a18a93c246fb94ed8d8b9770449be83364177ea --- .../change-list/all-history-list.tsx | 72 +++++--- .../change-list/dropdown/actions-dropdown.tsx | 13 +- .../change-list/dropdown/history-dropdown.tsx | 23 +++ .../dropdown/history-version-dropdown.tsx | 43 ----- .../dropdown/label-dropdown-content.tsx | 48 ++++++ .../change-list/dropdown/label-dropdown.tsx | 40 ----- .../dropdown/menu-item/add-label.tsx | 15 +- .../dropdown/menu-item/compare.tsx | 38 +++-- .../dropdown/menu-item/download.tsx | 9 +- .../dropdown/version-dropdown-content.tsx | 52 ++++++ .../change-list/history-version-details.tsx | 21 ++- .../change-list/history-version.tsx | 156 +++++++++++------- .../change-list/label-list-item.tsx | 121 ++++++++++++++ .../components/change-list/labels-list.tsx | 80 +++------ .../history/components/history-root.tsx | 2 +- .../hooks/use-dropdown-active-item.tsx | 36 ++++ .../features/history/utils/history-details.ts | 12 ++ 17 files changed, 523 insertions(+), 258 deletions(-) create mode 100644 services/web/frontend/js/features/history/components/change-list/dropdown/history-dropdown.tsx delete mode 100644 services/web/frontend/js/features/history/components/change-list/dropdown/history-version-dropdown.tsx create mode 100644 services/web/frontend/js/features/history/components/change-list/dropdown/label-dropdown-content.tsx delete mode 100644 services/web/frontend/js/features/history/components/change-list/dropdown/label-dropdown.tsx create mode 100644 services/web/frontend/js/features/history/components/change-list/dropdown/version-dropdown-content.tsx create mode 100644 services/web/frontend/js/features/history/components/change-list/label-list-item.tsx create mode 100644 services/web/frontend/js/features/history/hooks/use-dropdown-active-item.tsx diff --git a/services/web/frontend/js/features/history/components/change-list/all-history-list.tsx b/services/web/frontend/js/features/history/components/change-list/all-history-list.tsx index d40a180dfb..d031b608e5 100644 --- a/services/web/frontend/js/features/history/components/change-list/all-history-list.tsx +++ b/services/web/frontend/js/features/history/components/change-list/all-history-list.tsx @@ -1,20 +1,35 @@ -import { Fragment, useEffect, useRef, useState } from 'react' -import { useHistoryContext } from '../../context/history-context' +import { useEffect, useRef, useState } from 'react' import HistoryVersion from './history-version' import LoadingSpinner from '../../../../shared/components/loading-spinner' import { OwnerPaywallPrompt } from './owner-paywall-prompt' import { NonOwnerPaywallPrompt } from './non-owner-paywall-prompt' -import { relativeDate } from '../../../utils/format-date' +import { isVersionSelected } from '../../utils/history-details' +import { useUserContext } from '../../../../shared/context/user-context' +import useDropdownActiveItem from '../../hooks/use-dropdown-active-item' +import { useHistoryContext } from '../../context/history-context' function AllHistoryList() { - const { updatesInfo, fetchNextBatchOfUpdates, currentUserIsOwner } = - useHistoryContext() - const updatesLoadingState = updatesInfo.loadingState - const { visibleUpdateCount, updates, atEnd } = updatesInfo + const { id: currentUserId } = useUserContext() + const { + projectId, + updatesInfo, + fetchNextBatchOfUpdates, + currentUserIsOwner, + selection, + setSelection, + } = useHistoryContext() + const { + visibleUpdateCount, + updates, + atEnd, + loadingState: updatesLoadingState, + } = updatesInfo const scrollerRef = useRef(null) const bottomRef = useRef(null) const intersectionObserverRef = useRef(null) const [bottomVisible, setBottomVisible] = useState(false) + const { activeDropdownItem, setActiveDropdownItem, closeDropdownForItem } = + useDropdownActiveItem() const showPaywall = updatesLoadingState === 'ready' && updatesInfo.freeHistoryLimitHit const showOwnerPaywall = showPaywall && currentUserIsOwner @@ -73,25 +88,36 @@ function AllHistoryList() {
- {visibleUpdates.map((update, index) => ( - - {update.meta.first_in_day && index > 0 && ( -
- )} - {update.meta.first_in_day && ( - - )} + {visibleUpdates.map((update, index) => { + const selected = isVersionSelected( + selection, + update.fromV, + update.toV + ) + const dropdownActive = update === activeDropdownItem.item + const showDivider = Boolean(update.meta.first_in_day && index > 0) + const faded = + updatesInfo.freeHistoryLimitHit && + index === visibleUpdates.length - 1 + + return ( -
- ))} + ) + })}
{showOwnerPaywall ? : null} {showNonOwnerPaywall ? : null} diff --git a/services/web/frontend/js/features/history/components/change-list/dropdown/actions-dropdown.tsx b/services/web/frontend/js/features/history/components/change-list/dropdown/actions-dropdown.tsx index 48aee26017..99e1f8d4fd 100644 --- a/services/web/frontend/js/features/history/components/change-list/dropdown/actions-dropdown.tsx +++ b/services/web/frontend/js/features/history/components/change-list/dropdown/actions-dropdown.tsx @@ -1,4 +1,4 @@ -import { useState, useRef, useEffect } from 'react' +import { useRef, useEffect } from 'react' import { useTranslation } from 'react-i18next' import { Dropdown } from 'react-bootstrap' import DropdownToggleWithTooltip from '../../../../../shared/components/dropdown/dropdown-toggle-with-tooltip' @@ -9,11 +9,18 @@ type DropdownMenuProps = { id: string children: React.ReactNode parentSelector?: string + isOpened: boolean + setIsOpened: (isOpened: boolean) => void } -function ActionsDropdown({ id, children, parentSelector }: DropdownMenuProps) { +function ActionsDropdown({ + id, + children, + parentSelector, + isOpened, + setIsOpened, +}: DropdownMenuProps) { const { t } = useTranslation() - const [isOpened, setIsOpened] = useState(false) const menuRef = useRef() // handle the placement of the dropdown above or below the toggle button diff --git a/services/web/frontend/js/features/history/components/change-list/dropdown/history-dropdown.tsx b/services/web/frontend/js/features/history/components/change-list/dropdown/history-dropdown.tsx new file mode 100644 index 0000000000..5448aa2fab --- /dev/null +++ b/services/web/frontend/js/features/history/components/change-list/dropdown/history-dropdown.tsx @@ -0,0 +1,23 @@ +import ActionsDropdown from './actions-dropdown' + +type HistoryDropdownProps = { + children: React.ReactNode + id: string + isOpened: boolean + setIsOpened: (isOpened: boolean) => void +} + +function HistoryDropdown({ + children, + id, + isOpened, + setIsOpened, +}: HistoryDropdownProps) { + return ( + + {children} + + ) +} + +export default HistoryDropdown diff --git a/services/web/frontend/js/features/history/components/change-list/dropdown/history-version-dropdown.tsx b/services/web/frontend/js/features/history/components/change-list/dropdown/history-version-dropdown.tsx deleted file mode 100644 index 2c4849201d..0000000000 --- a/services/web/frontend/js/features/history/components/change-list/dropdown/history-version-dropdown.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import ActionsDropdown from './actions-dropdown' -import AddLabel from './menu-item/add-label' -import Download from './menu-item/download' -import Compare from './menu-item/compare' -import { UpdateRange } from '../../../services/types/update' - -type HistoryVersionDropdownProps = { - id: string - projectId: string - isComparing: boolean - isSelected: boolean - updateMetaEndTimestamp: number -} & Pick - -function HistoryVersionDropdown({ - id, - projectId, - isComparing, - isSelected, - fromV, - toV, - updateMetaEndTimestamp, -}: HistoryVersionDropdownProps) { - return ( - - - - {!isComparing && !isSelected && ( - - )} - - ) -} - -export default HistoryVersionDropdown diff --git a/services/web/frontend/js/features/history/components/change-list/dropdown/label-dropdown-content.tsx b/services/web/frontend/js/features/history/components/change-list/dropdown/label-dropdown-content.tsx new file mode 100644 index 0000000000..8ef2a5581d --- /dev/null +++ b/services/web/frontend/js/features/history/components/change-list/dropdown/label-dropdown-content.tsx @@ -0,0 +1,48 @@ +import Download from './menu-item/download' +import Compare from './menu-item/compare' +import { Version } from '../../../services/types/update' +import { ActiveDropdown } from '../../../hooks/use-dropdown-active-item' +import { useCallback } from 'react' + +type LabelDropdownContentProps = { + projectId: string + version: Version + updateMetaEndTimestamp: number + selected: boolean + comparing: boolean + closeDropdownForItem: ActiveDropdown['closeDropdownForItem'] +} + +function LabelDropdownContent({ + projectId, + version, + updateMetaEndTimestamp, + selected, + comparing, + closeDropdownForItem, +}: LabelDropdownContentProps) { + const closeDropdown = useCallback(() => { + closeDropdownForItem(version) + }, [closeDropdownForItem, version]) + + return ( + <> + + {!comparing && !selected && ( + + )} + + ) +} + +export default LabelDropdownContent diff --git a/services/web/frontend/js/features/history/components/change-list/dropdown/label-dropdown.tsx b/services/web/frontend/js/features/history/components/change-list/dropdown/label-dropdown.tsx deleted file mode 100644 index 573db42d69..0000000000 --- a/services/web/frontend/js/features/history/components/change-list/dropdown/label-dropdown.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import ActionsDropdown from './actions-dropdown' -import Download from './menu-item/download' -import Compare from './menu-item/compare' - -type LabelDropdownProps = { - id: string - projectId: string - isComparing: boolean - isSelected: boolean - version: number - updateMetaEndTimestamp: number -} - -function LabelDropdown({ - id, - projectId, - isComparing, - isSelected, - version, - updateMetaEndTimestamp, -}: LabelDropdownProps) { - return ( - - - {!isComparing && !isSelected && ( - - )} - - ) -} - -export default LabelDropdown diff --git a/services/web/frontend/js/features/history/components/change-list/dropdown/menu-item/add-label.tsx b/services/web/frontend/js/features/history/components/change-list/dropdown/menu-item/add-label.tsx index 29504a3f7e..8bc4f9228d 100644 --- a/services/web/frontend/js/features/history/components/change-list/dropdown/menu-item/add-label.tsx +++ b/services/web/frontend/js/features/history/components/change-list/dropdown/menu-item/add-label.tsx @@ -7,15 +7,26 @@ import AddLabelModal from '../../add-label-modal' type DownloadProps = { projectId: string version: number + closeDropdown: () => void } -function AddLabel({ version, projectId, ...props }: DownloadProps) { +function AddLabel({ + version, + projectId, + closeDropdown, + ...props +}: DownloadProps) { const { t } = useTranslation() const [showModal, setShowModal] = useState(false) + const handleClick = () => { + closeDropdown() + setShowModal(true) + } + return ( <> - setShowModal(true)} {...props}> + {t('history_label_this_version')} void } & Pick function Compare({ @@ -15,29 +16,36 @@ function Compare({ fromV, toV, updateMetaEndTimestamp, + closeDropdown, ...props }: CompareProps) { const { t } = useTranslation() - const { selection, setSelection } = useHistoryContext() + const { setSelection } = useHistoryContext() const handleCompareVersion = (e: React.MouseEvent) => { e.stopPropagation() + closeDropdown() - const { updateRange } = selection - if (updateRange) { - const range = computeUpdateRange( - updateRange, - fromV, - toV, - updateMetaEndTimestamp - ) + setSelection(prevSelection => { + const { updateRange } = prevSelection - setSelection({ - updateRange: range, - comparing: true, - files: [], - }) - } + if (updateRange) { + const range = computeUpdateRange( + updateRange, + fromV, + toV, + updateMetaEndTimestamp + ) + + return { + updateRange: range, + comparing: true, + files: [], + } + } + + return prevSelection + }) } return ( diff --git a/services/web/frontend/js/features/history/components/change-list/dropdown/menu-item/download.tsx b/services/web/frontend/js/features/history/components/change-list/dropdown/menu-item/download.tsx index e40a15076c..456bd142ca 100644 --- a/services/web/frontend/js/features/history/components/change-list/dropdown/menu-item/download.tsx +++ b/services/web/frontend/js/features/history/components/change-list/dropdown/menu-item/download.tsx @@ -6,13 +6,20 @@ import * as location from '../../../../../../shared/components/location' type DownloadProps = { projectId: string version: number + closeDropdown: () => void } -function Download({ version, projectId, ...props }: DownloadProps) { +function Download({ + version, + projectId, + closeDropdown, + ...props +}: DownloadProps) { const { t } = useTranslation() const handleDownloadVersion = (e: React.MouseEvent) => { e.preventDefault() + closeDropdown() const event = e as typeof e & { target: HTMLAnchorElement } location.assign(event.target.href) } diff --git a/services/web/frontend/js/features/history/components/change-list/dropdown/version-dropdown-content.tsx b/services/web/frontend/js/features/history/components/change-list/dropdown/version-dropdown-content.tsx new file mode 100644 index 0000000000..72cd929f7c --- /dev/null +++ b/services/web/frontend/js/features/history/components/change-list/dropdown/version-dropdown-content.tsx @@ -0,0 +1,52 @@ +import AddLabel from './menu-item/add-label' +import Download from './menu-item/download' +import Compare from './menu-item/compare' +import { LoadedUpdate } from '../../../services/types/update' +import { useCallback } from 'react' +import { ActiveDropdown } from '../../../hooks/use-dropdown-active-item' + +type VersionDropdownContentProps = { + projectId: string + update: LoadedUpdate + selected: boolean + comparing: boolean + closeDropdownForItem: ActiveDropdown['closeDropdownForItem'] +} + +function VersionDropdownContent({ + projectId, + update, + selected, + comparing, + closeDropdownForItem, +}: VersionDropdownContentProps) { + const closeDropdown = useCallback(() => { + closeDropdownForItem(update) + }, [closeDropdownForItem, update]) + + return ( + <> + + + {!comparing && !selected && ( + + )} + + ) +} + +export default VersionDropdownContent diff --git a/services/web/frontend/js/features/history/components/change-list/history-version-details.tsx b/services/web/frontend/js/features/history/components/change-list/history-version-details.tsx index ab3dc6b1c3..da8cf48384 100644 --- a/services/web/frontend/js/features/history/components/change-list/history-version-details.tsx +++ b/services/web/frontend/js/features/history/components/change-list/history-version-details.tsx @@ -1,29 +1,28 @@ -import { useHistoryContext } from '../../context/history-context' import classnames from 'classnames' +import { HistoryContextValue } from '../../context/types/history-context-value' import { UpdateRange } from '../../services/types/update' +import { ReactNode, MouseEvent } from 'react' type HistoryVersionDetailsProps = { - children: React.ReactNode + children: ReactNode + updateRange: UpdateRange selected: boolean selectable: boolean -} & UpdateRange + setSelection: HistoryContextValue['setSelection'] +} function HistoryVersionDetails({ children, selected, + updateRange, selectable, - fromV, - toV, - fromVTimestamp, - toVTimestamp, + setSelection, }: HistoryVersionDetailsProps) { - const { setSelection } = useHistoryContext() - - const handleSelect = (e: React.MouseEvent) => { + const handleSelect = (e: MouseEvent) => { const target = e.target as HTMLElement if (!target.closest('.dropdown') && e.currentTarget.contains(target)) { setSelection({ - updateRange: { fromV, toV, fromVTimestamp, toVTimestamp }, + updateRange, comparing: false, files: [], }) diff --git a/services/web/frontend/js/features/history/components/change-list/history-version.tsx b/services/web/frontend/js/features/history/components/change-list/history-version.tsx index 6e0efc086b..5d330383f8 100644 --- a/services/web/frontend/js/features/history/components/change-list/history-version.tsx +++ b/services/web/frontend/js/features/history/components/change-list/history-version.tsx @@ -3,79 +3,115 @@ import TagTooltip from './tag-tooltip' import Changes from './changes' import MetadataUsersList from './metadata-users-list' import Origin from './origin' -import HistoryVersionDropdown from './dropdown/history-version-dropdown' -import { useUserContext } from '../../../../shared/context/user-context' -import { useHistoryContext } from '../../context/history-context' -import { isVersionSelected } from '../../utils/history-details' -import { formatTime } from '../../../utils/format-date' +import HistoryDropdown from './dropdown/history-dropdown' +import { formatTime, relativeDate } from '../../../utils/format-date' import { orderBy } from 'lodash' import { LoadedUpdate } from '../../services/types/update' import classNames from 'classnames' +import { updateRangeForUpdate } from '../../utils/history-details' +import { ActiveDropdown } from '../../hooks/use-dropdown-active-item' +import { memo } from 'react' +import { HistoryContextValue } from '../../context/types/history-context-value' +import VersionDropdownContent from './dropdown/version-dropdown-content' -type HistoryEntryProps = { +type HistoryVersionProps = { update: LoadedUpdate + currentUserId: string + projectId: string + comparing: boolean faded: boolean + showDivider: boolean + selected: boolean + setSelection: HistoryContextValue['setSelection'] + dropdownOpen: boolean + dropdownActive: boolean + setActiveDropdownItem: ActiveDropdown['setActiveDropdownItem'] + closeDropdownForItem: ActiveDropdown['closeDropdownForItem'] } -function HistoryVersion({ update, faded }: HistoryEntryProps) { - const { id: currentUserId } = useUserContext() - const { projectId, selection } = useHistoryContext() - +function HistoryVersion({ + update, + currentUserId, + projectId, + comparing, + faded, + showDivider, + selected, + setSelection, + dropdownOpen, + dropdownActive, + setActiveDropdownItem, + closeDropdownForItem, +}: HistoryVersionProps) { const orderedLabels = orderBy(update.labels, ['created_at'], ['desc']) - const selected = isVersionSelected(selection, update.fromV, update.toV) + const selectable = !faded && (comparing || !selected) return ( -
- + {showDivider ?
: null} + {update.meta.first_in_day ? ( + + ) : null} +
-
- - {orderedLabels.map(label => ( - +
+ + {orderedLabels.map(label => ( + + ))} + - ))} - - - -
- {faded ? null : ( - - )} - -
+ + +
+ {faded ? null : ( + + setActiveDropdownItem({ item: update, isOpened }) + } + > + {dropdownActive ? ( + + ) : null} + + )} +
+
+ ) } -export default HistoryVersion +export default memo(HistoryVersion) diff --git a/services/web/frontend/js/features/history/components/change-list/label-list-item.tsx b/services/web/frontend/js/features/history/components/change-list/label-list-item.tsx new file mode 100644 index 0000000000..446a48c3d3 --- /dev/null +++ b/services/web/frontend/js/features/history/components/change-list/label-list-item.tsx @@ -0,0 +1,121 @@ +import { memo, useCallback } from 'react' +import { UpdateRange, Version } from '../../services/types/update' +import TagTooltip from './tag-tooltip' +import { formatTime, isoToUnix } from '../../../utils/format-date' +import { isPseudoLabel } from '../../utils/label' +import UserNameWithColoredBadge from './user-name-with-colored-badge' +import HistoryDropdown from './dropdown/history-dropdown' +import HistoryVersionDetails from './history-version-details' +import { LoadedLabel } from '../../services/types/label' +import { useTranslation } from 'react-i18next' +import { ActiveDropdown } from '../../hooks/use-dropdown-active-item' +import { HistoryContextValue } from '../../context/types/history-context-value' +import LabelDropdownContent from './dropdown/label-dropdown-content' + +type LabelListItemProps = { + version: Version + labels: LoadedLabel[] + currentUserId: string + projectId: string + comparing: boolean + selected: boolean + selectable: boolean + setSelection: HistoryContextValue['setSelection'] + dropdownOpen: boolean + dropdownActive: boolean + setActiveDropdownItem: ActiveDropdown['setActiveDropdownItem'] + closeDropdownForItem: ActiveDropdown['closeDropdownForItem'] +} + +function LabelListItem({ + version, + labels, + currentUserId, + projectId, + comparing, + selected, + selectable, + setSelection, + dropdownOpen, + dropdownActive, + setActiveDropdownItem, + closeDropdownForItem, +}: LabelListItemProps) { + const { t } = useTranslation() + + // first label + const fromVTimestamp = isoToUnix(labels[labels.length - 1].created_at) + // most recent label + const toVTimestamp = isoToUnix(labels[0].created_at) + + const updateRange: UpdateRange = { + fromV: version, + toV: version, + fromVTimestamp, + toVTimestamp, + } + + const setIsOpened = useCallback( + (isOpened: boolean) => { + setActiveDropdownItem({ item: version, isOpened }) + }, + [setActiveDropdownItem, version] + ) + + return ( + +
+ {labels.map(label => ( +
+ + + {!isPseudoLabel(label) && ( +
+ + {t('saved_by')} + + +
+ )} +
+ ))} +
+ + {dropdownActive ? ( + + ) : null} + +
+ ) +} + +export default memo(LabelListItem) diff --git a/services/web/frontend/js/features/history/components/change-list/labels-list.tsx b/services/web/frontend/js/features/history/components/change-list/labels-list.tsx index 3d684c8ffd..7b16034370 100644 --- a/services/web/frontend/js/features/history/components/change-list/labels-list.tsx +++ b/services/web/frontend/js/features/history/components/change-list/labels-list.tsx @@ -1,20 +1,17 @@ -import { useMemo } from 'react' -import { useTranslation } from 'react-i18next' -import HistoryVersionDetails from './history-version-details' -import TagTooltip from './tag-tooltip' -import UserNameWithColoredBadge from './user-name-with-colored-badge' -import LabelDropdown from './dropdown/label-dropdown' -import { useHistoryContext } from '../../context/history-context' import { useUserContext } from '../../../../shared/context/user-context' import { isVersionSelected } from '../../utils/history-details' -import { getVersionWithLabels, isPseudoLabel } from '../../utils/label' -import { formatTime, isoToUnix } from '../../../utils/format-date' +import { useMemo } from 'react' import { Version } from '../../services/types/update' +import LabelListItem from './label-list-item' +import useDropdownActiveItem from '../../hooks/use-dropdown-active-item' +import { getVersionWithLabels } from '../../utils/label' +import { useHistoryContext } from '../../context/history-context' function LabelsList() { - const { t } = useTranslation() - const { labels, projectId, selection } = useHistoryContext() const { id: currentUserId } = useUserContext() + const { projectId, labels, selection, setSelection } = useHistoryContext() + const { activeDropdownItem, setActiveDropdownItem, closeDropdownForItem } = + useDropdownActiveItem() const versionWithLabels = useMemo( () => getVersionWithLabels(labels), @@ -33,59 +30,24 @@ function LabelsList() { <> {versionWithLabels.map(({ version, labels }) => { const selected = selectedVersions.has(version) - - // first label - const fromVTimestamp = isoToUnix(labels[labels.length - 1].created_at) - // most recent label - const toVTimestamp = isoToUnix(labels[0].created_at) + const dropdownActive = version === activeDropdownItem.item return ( - -
- {labels.map(label => ( -
- - - {!isPseudoLabel(label) && ( -
- - {t('saved_by')} - - -
- )} -
- ))} -
- -
+ setSelection={setSelection} + dropdownOpen={activeDropdownItem.isOpened && dropdownActive} + dropdownActive={dropdownActive} + setActiveDropdownItem={setActiveDropdownItem} + closeDropdownForItem={closeDropdownForItem} + /> ) })} diff --git a/services/web/frontend/js/features/history/components/history-root.tsx b/services/web/frontend/js/features/history/components/history-root.tsx index 345139c7c7..94aaea1e2d 100644 --- a/services/web/frontend/js/features/history/components/history-root.tsx +++ b/services/web/frontend/js/features/history/components/history-root.tsx @@ -11,7 +11,7 @@ const fileTreeContainer = document.getElementById('history-file-tree') function Main() { const { updatesInfo, error } = useHistoryContext() - let content = null + let content if (updatesInfo.loadingState === 'loadingInitial') { content = } else if (error) { diff --git a/services/web/frontend/js/features/history/hooks/use-dropdown-active-item.tsx b/services/web/frontend/js/features/history/hooks/use-dropdown-active-item.tsx new file mode 100644 index 0000000000..16d7c2ec2f --- /dev/null +++ b/services/web/frontend/js/features/history/hooks/use-dropdown-active-item.tsx @@ -0,0 +1,36 @@ +import { Dispatch, SetStateAction, useCallback, useState } from 'react' +import { LoadedUpdate, Version } from '../services/types/update' + +type DropdownItem = LoadedUpdate | Version + +export type ActiveDropdownValue = { + item: DropdownItem | null + isOpened: boolean +} + +export type ActiveDropdown = { + activeDropdownItem: ActiveDropdownValue + setActiveDropdownItem: Dispatch> + closeDropdownForItem: (item: DropdownItem) => void +} + +function useDropdownActiveItem(): ActiveDropdown { + const [activeDropdownItem, setActiveDropdownItem] = + useState({ + item: null, + isOpened: false, + }) + + const closeDropdownForItem = useCallback( + (item: DropdownItem) => setActiveDropdownItem({ item, isOpened: false }), + [setActiveDropdownItem] + ) + + return { + activeDropdownItem, + setActiveDropdownItem, + closeDropdownForItem, + } +} + +export default useDropdownActiveItem diff --git a/services/web/frontend/js/features/history/utils/history-details.ts b/services/web/frontend/js/features/history/utils/history-details.ts index ce7f3b8740..4ce57e78bc 100644 --- a/services/web/frontend/js/features/history/utils/history-details.ts +++ b/services/web/frontend/js/features/history/utils/history-details.ts @@ -71,3 +71,15 @@ export function isVersionSelected( export const getUpdateForVersion = (version: number, updates: LoadedUpdate[]) => updates.find(update => update.toV === version) + +export const updateRangeForUpdate = (update: LoadedUpdate) => { + const { fromV, toV, meta } = update + const fromVTimestamp = meta.end_ts + + return { + fromV, + toV, + fromVTimestamp, + toVTimestamp: fromVTimestamp, + } +}