Преглед изворни кода

History UI - Moving pop over out from the version (#14770)

Moving popover out of versions list

GitOrigin-RevId: d1739f4c17b66a0e39c8bb46a0fac5b2069d9171
Davinder Singh пре 2 година
родитељ
комит
74d64af950

+ 104 - 23
services/web/frontend/js/features/history/components/change-list/all-history-list.tsx

@@ -3,14 +3,17 @@ 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 {
-  isVersionSelected,
-  ItemSelectionState,
-} from '../../utils/history-details'
+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'
 import { useEditorContext } from '../../../../shared/context/editor-context'
+import { Overlay, Popover } from 'react-bootstrap'
+import Close from '@/shared/components/close'
+import { Trans, useTranslation } from 'react-i18next'
+import MaterialIcon from '@/shared/components/material-icon'
+import useAsync from '@/shared/hooks/use-async'
+import { completeHistoryTutorial } from '../../services/api'
 
 type CompletedTutorials = {
   'react-history-buttons-tutorial': Date
@@ -20,11 +23,6 @@ type EditorTutorials = {
   setCompletedTutorial: (key: string) => void
 }
 
-const unselectedStates: ItemSelectionState[] = [
-  'aboveSelected',
-  'belowSelected',
-]
-
 function AllHistoryList() {
   const { id: currentUserId } = useUserContext()
   const {
@@ -104,25 +102,113 @@ function AllHistoryList() {
   const { completedTutorials, setCompletedTutorial }: EditorTutorials =
     useEditorContext()
 
-  // only show tutorial popover if they havent dismissed ("completed") it yet
+  // only show tutorial popover if they haven't dismissed ("completed") it yet
   const showTutorial = !completedTutorials?.['react-history-buttons-tutorial']
 
   const completeTutorial = useCallback(() => {
     setCompletedTutorial('react-history-buttons-tutorial')
   }, [setCompletedTutorial])
 
-  // only show tutorial popover on the first icon
-  const firstUnselectedIndex = visibleUpdates.findIndex(update => {
-    const selectionState = isVersionSelected(
-      selection,
-      update.fromV,
-      update.toV
+  const { runAsync } = useAsync()
+
+  const { t } = useTranslation()
+
+  // wait for the layout to settle before showing popover, to avoid a flash/ instant move
+  const [layoutSettled, setLayoutSettled] = useState(false)
+  const [resizing, setResizing] = useState(false)
+
+  // When there is a paywall and only two version's to compare,
+  // they are not comparable because the one that has a paywall will not have the compare button
+  // so we should not display on-boarding popover in that case
+  const isPaywallAndNonComparable =
+    visibleUpdates.length === 2 && updatesInfo.freeHistoryLimitHit
+
+  const isMoreThanOneVersion = visibleUpdates.length > 1
+  let popover = null
+
+  if (
+    isMoreThanOneVersion &&
+    !isPaywallAndNonComparable &&
+    showTutorial &&
+    layoutSettled &&
+    !resizing
+  ) {
+    const dismissModal = () => {
+      completeTutorial()
+      runAsync(completeHistoryTutorial()).catch(console.error)
+    }
+
+    popover = (
+      <Overlay
+        placement="left"
+        show
+        // using scrollerRef to position the popover in the middle of the viewport
+        target={scrollerRef.current ?? undefined}
+        shouldUpdatePosition
+      >
+        <Popover
+          id="popover-toolbar-overflow"
+          arrowOffsetTop={10}
+          title={
+            <span>
+              {t('react_history_tutorial_title')}{' '}
+              <Close variant="dark" onDismiss={() => dismissModal()} />
+            </span>
+          }
+          className="dark-themed history-popover"
+        >
+          <Trans
+            i18nKey="react_history_tutorial_content"
+            components={[
+              // eslint-disable-next-line react/jsx-key
+              <MaterialIcon
+                type="align_end"
+                className="material-symbols-rounded history-dropdown-icon-inverted"
+              />,
+              <a href="https://www.overleaf.com/learn/latex/Using_the_History_feature" />, // eslint-disable-line jsx-a11y/anchor-has-content, react/jsx-key
+            ]}
+          />
+        </Popover>
+      </Overlay>
     )
-    return unselectedStates.includes(selectionState)
-  })
+  }
+
+  // give the components time to position before showing popover so we don't get an instant position change
+  useEffect(() => {
+    const timer = window.setTimeout(() => {
+      setLayoutSettled(true)
+    }, 500)
+
+    return () => clearTimeout(timer)
+  }, [setLayoutSettled])
+
+  useEffect(() => {
+    let timer: number | null = null
+
+    const handleResize = () => {
+      // Hide popover when a resize starts, then waiting for a gap of 500ms
+      // with no resizes before making it reappear
+      if (timer) {
+        window.clearTimeout(timer)
+      } else {
+        setResizing(true)
+      }
+      timer = window.setTimeout(() => {
+        timer = null
+        setResizing(false)
+      }, 500)
+    }
+
+    // only need a listener on the component that actually has the popover
+    if (showTutorial) {
+      window.addEventListener('resize', handleResize)
+      return () => window.removeEventListener('resize', handleResize)
+    }
+  }, [showTutorial])
 
   return (
     <div ref={scrollerRef} className="history-all-versions-scroller">
+      {popover}
       <div className="history-all-versions-container">
         <div ref={bottomRef} className="history-versions-bottom" />
         {visibleUpdates.map((update, index) => {
@@ -148,9 +234,6 @@ function AllHistoryList() {
               selectionState === 'aboveSelected' ||
               selectionState === 'belowSelected')
 
-          const hasTutorialOverlay =
-            index === firstUnselectedIndex && showTutorial
-
           return (
             <HistoryVersion
               key={`${update.fromV}_${update.toV}`}
@@ -170,8 +253,6 @@ function AllHistoryList() {
                 activeDropdownItem.isOpened && compareDropdownActive
               }
               dropdownActive={dropdownActive}
-              hasTutorialOverlay={hasTutorialOverlay}
-              completeTutorial={completeTutorial}
             />
           )
         })}

+ 2 - 126
services/web/frontend/js/features/history/components/change-list/history-version.tsx

@@ -1,15 +1,4 @@
-import {
-  useRef,
-  useCallback,
-  memo,
-  useEffect,
-  useState,
-  ReactNode,
-} from 'react'
-import { Popover, Overlay } from 'react-bootstrap'
-import { useTranslation, Trans } from 'react-i18next'
-import Close from '../../../../shared/components/close'
-import MaterialIcon from '../../../../shared/components/material-icon'
+import { useCallback, memo } from 'react'
 import HistoryVersionDetails from './history-version-details'
 import TagTooltip from './tag-tooltip'
 import Changes from './changes'
@@ -25,11 +14,9 @@ import {
   ItemSelectionState,
 } from '../../utils/history-details'
 import { ActiveDropdown } from '../../hooks/use-dropdown-active-item'
-import useAsync from '../../../../shared/hooks/use-async'
 import { HistoryContextValue } from '../../context/types/history-context-value'
 import VersionDropdownContent from './dropdown/version-dropdown-content'
 import CompareItems from './dropdown/menu-item/compare-items'
-import { completeHistoryTutorial } from '../../services/api'
 import CompareVersionDropdown from './dropdown/compare-version-dropdown'
 import { CompareVersionDropdownContentAllHistory } from './dropdown/compare-version-dropdown-content'
 
@@ -48,8 +35,6 @@ type HistoryVersionProps = {
   compareDropdownActive: boolean
   setActiveDropdownItem: ActiveDropdown['setActiveDropdownItem']
   closeDropdownForItem: ActiveDropdown['closeDropdownForItem']
-  hasTutorialOverlay?: boolean
-  completeTutorial: () => void
 }
 
 function HistoryVersion({
@@ -67,112 +52,8 @@ function HistoryVersion({
   compareDropdownActive,
   setActiveDropdownItem,
   closeDropdownForItem,
-  hasTutorialOverlay = false,
-  completeTutorial,
 }: HistoryVersionProps) {
   const orderedLabels = orderBy(update.labels, ['created_at'], ['desc'])
-  const iconRef = useRef<HTMLDivElement>(null)
-
-  const { runAsync } = useAsync()
-
-  const { t } = useTranslation()
-
-  const [popover, setPopover] = useState<ReactNode | null>(null)
-  // wait for the layout to settle before showing popover, to avoid a flash/ instant move
-  const [layoutSettled, setLayoutSettled] = useState(false)
-
-  const [resizing, setResizing] = useState(false)
-
-  // Determine whether the tutorial popover should be shown or not.
-  // This is a slightly unusual pattern, as in theory we could control this via
-  // the `show` prop. However we were concerned about the perf impact of every
-  // history version having a (hidden) popover that won't ever be triggered.
-  useEffect(() => {
-    if (iconRef.current && hasTutorialOverlay && layoutSettled && !resizing) {
-      const dismissModal = () => {
-        completeTutorial()
-        runAsync(completeHistoryTutorial()).catch(console.error)
-      }
-
-      const compareIcon = (
-        <MaterialIcon
-          type="align_end"
-          className="material-symbols-rounded history-dropdown-icon-inverted"
-        />
-      )
-
-      setPopover(
-        <Overlay
-          placement="left"
-          show
-          target={iconRef.current ?? undefined}
-          shouldUpdatePosition
-        >
-          <Popover
-            id="popover-toolbar-overflow"
-            title={
-              <span>
-                {t('react_history_tutorial_title')}{' '}
-                <Close variant="dark" onDismiss={() => dismissModal()} />
-              </span>
-            }
-            className="dark-themed"
-          >
-            <Trans
-              i18nKey="react_history_tutorial_content"
-              components={[
-                compareIcon,
-                <a href="https://www.overleaf.com/learn/latex/Using_the_History_feature" />, // eslint-disable-line jsx-a11y/anchor-has-content, react/jsx-key
-              ]}
-            />
-          </Popover>
-        </Overlay>
-      )
-    } else {
-      setPopover(null)
-    }
-  }, [
-    hasTutorialOverlay,
-    runAsync,
-    t,
-    layoutSettled,
-    completeTutorial,
-    resizing,
-  ])
-
-  // give the components time to position before showing popover so we dont get a instant position change
-  useEffect(() => {
-    const timer = window.setTimeout(() => {
-      setLayoutSettled(true)
-    }, 500)
-
-    return () => clearTimeout(timer)
-  }, [setLayoutSettled])
-
-  useEffect(() => {
-    let timer: number | null = null
-
-    const handleResize = () => {
-      // Hide popover when a resize starts, then waiting for a gap of 500ms
-      // with no resizes before making it reappear
-      if (timer) {
-        window.clearTimeout(timer)
-      } else {
-        setResizing(true)
-      }
-      timer = window.setTimeout(() => {
-        timer = null
-        setResizing(false)
-      }, 500)
-    }
-
-    // only need a listener on the component that actually has the popover
-    if (hasTutorialOverlay) {
-      window.addEventListener('resize', handleResize)
-      return () => window.removeEventListener('resize', handleResize)
-    }
-  }, [hasTutorialOverlay])
-
   const closeDropdown = useCallback(() => {
     closeDropdownForItem(update, 'moreOptions')
   }, [closeDropdownForItem, update])
@@ -180,7 +61,6 @@ function HistoryVersion({
   const updateRange = updateRangeForUpdate(update)
   return (
     <>
-      {popover}
       {showDivider ? (
         <div
           className={classNames({
@@ -241,11 +121,7 @@ function HistoryVersion({
           )}
 
           {selectionState !== 'selected' && !faded ? (
-            <div
-              data-testid="compare-icon-version"
-              className="pull-right"
-              ref={iconRef}
-            >
+            <div data-testid="compare-icon-version" className="pull-right">
               {selectionState !== 'withinSelected' ? (
                 <CompareItems
                   updateRange={updateRange}

+ 8 - 0
services/web/frontend/stylesheets/app/editor/history-react.less

@@ -7,6 +7,14 @@ history-root {
   display: block;
 }
 
+// Adding !important to override the styling of overlays and popovers
+.history-popover {
+  top: 90px !important;
+  .arrow {
+    top: 20px !important;
+  }
+}
+
 .history-react {
   display: flex;
   height: 100%;