Просмотр исходного кода

History ui - Adding the compare dropdown on the withinSelected versions

GitOrigin-RevId: 728979b11918c9bc1e535024040d90053536251d
Davinder Singh 3 лет назад
Родитель
Сommit
d8c4399c01
17 измененных файлов с 310 добавлено и 35 удалено
  1. 1 0
      services/web/frontend/extracted-translations.json
  2. 10 1
      services/web/frontend/js/features/history/components/change-list/all-history-list.tsx
  3. 7 6
      services/web/frontend/js/features/history/components/change-list/dropdown/actions-dropdown.tsx
  4. 112 0
      services/web/frontend/js/features/history/components/change-list/dropdown/compare-version-dropdown-content.tsx
  5. 39 0
      services/web/frontend/js/features/history/components/change-list/dropdown/compare-version-dropdown.tsx
  6. 7 0
      services/web/frontend/js/features/history/components/change-list/dropdown/history-dropdown.tsx
  7. 1 1
      services/web/frontend/js/features/history/components/change-list/dropdown/label-dropdown-content.tsx
  8. 4 0
      services/web/frontend/js/features/history/components/change-list/dropdown/menu-item/compare-items.tsx
  9. 3 0
      services/web/frontend/js/features/history/components/change-list/dropdown/menu-item/compare.tsx
  10. 1 1
      services/web/frontend/js/features/history/components/change-list/dropdown/version-dropdown-content.tsx
  11. 34 5
      services/web/frontend/js/features/history/components/change-list/history-version.tsx
  12. 44 14
      services/web/frontend/js/features/history/components/change-list/label-list-item.tsx
  13. 10 1
      services/web/frontend/js/features/history/components/change-list/labels-list.tsx
  14. 9 2
      services/web/frontend/js/features/history/hooks/use-dropdown-active-item.tsx
  15. 7 4
      services/web/frontend/stylesheets/app/editor/history-react.less
  16. 1 0
      services/web/locales/en.json
  17. 20 0
      services/web/test/frontend/features/history/components/change-list.spec.tsx

+ 1 - 0
services/web/frontend/extracted-translations.json

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

+ 10 - 1
services/web/frontend/js/features/history/components/change-list/all-history-list.tsx

@@ -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}
             />
           )

+ 7 - 6
services/web/frontend/js/features/history/components/change-list/dropdown/actions-dropdown.tsx

@@ -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"

+ 112 - 0
services/web/frontend/js/features/history/components/change-list/dropdown/compare-version-dropdown-content.tsx

@@ -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,
+}

+ 39 - 0
services/web/frontend/js/features/history/components/change-list/dropdown/compare-version-dropdown.tsx

@@ -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

+ 7 - 0
services/web/frontend/js/features/history/components/change-list/dropdown/history-dropdown.tsx

@@ -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}

+ 1 - 1
services/web/frontend/js/features/history/components/change-list/dropdown/label-dropdown-content.tsx

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

+ 4 - 0
services/web/frontend/js/features/history/components/change-list/dropdown/menu-item/compare-items.tsx

@@ -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"

+ 3 - 0
services/web/frontend/js/features/history/components/change-list/dropdown/menu-item/compare.tsx

@@ -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>
   )

+ 1 - 1
services/web/frontend/js/features/history/components/change-list/dropdown/version-dropdown-content.tsx

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

+ 34 - 5
services/web/frontend/js/features/history/components/change-list/history-version.tsx

@@ -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">

+ 44 - 14
services/web/frontend/js/features/history/components/change-list/label-list-item.tsx

@@ -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">

+ 10 - 1
services/web/frontend/js/features/history/components/change-list/labels-list.tsx

@@ -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}
           />

+ 9 - 2
services/web/frontend/js/features/history/hooks/use-dropdown-active-item.tsx

@@ -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]
   )
 

+ 7 - 4
services/web/frontend/stylesheets/app/editor/history-react.less

@@ -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 {

+ 1 - 0
services/web/locales/en.json

@@ -258,6 +258,7 @@
   "commons_plan_tooltip": "You’re 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",

+ 20 - 0
services/web/test/frontend/features/history/components/change-list.spec.tsx

@@ -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 () {