|
@@ -1,3 +1,15 @@
|
|
|
|
|
+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 HistoryVersionDetails from './history-version-details'
|
|
import HistoryVersionDetails from './history-version-details'
|
|
|
import TagTooltip from './tag-tooltip'
|
|
import TagTooltip from './tag-tooltip'
|
|
|
import Changes from './changes'
|
|
import Changes from './changes'
|
|
@@ -13,10 +25,11 @@ import {
|
|
|
ItemSelectionState,
|
|
ItemSelectionState,
|
|
|
} from '../../utils/history-details'
|
|
} from '../../utils/history-details'
|
|
|
import { ActiveDropdown } from '../../hooks/use-dropdown-active-item'
|
|
import { ActiveDropdown } from '../../hooks/use-dropdown-active-item'
|
|
|
-import { memo, useCallback } from 'react'
|
|
|
|
|
|
|
+import useAsync from '../../../../shared/hooks/use-async'
|
|
|
import { HistoryContextValue } from '../../context/types/history-context-value'
|
|
import { HistoryContextValue } from '../../context/types/history-context-value'
|
|
|
import VersionDropdownContent from './dropdown/version-dropdown-content'
|
|
import VersionDropdownContent from './dropdown/version-dropdown-content'
|
|
|
import CompareItems from './dropdown/menu-item/compare-items'
|
|
import CompareItems from './dropdown/menu-item/compare-items'
|
|
|
|
|
+import { completeHistoryTutorial } from '../../services/api'
|
|
|
import CompareVersionDropdown from './dropdown/compare-version-dropdown'
|
|
import CompareVersionDropdown from './dropdown/compare-version-dropdown'
|
|
|
import { CompareVersionDropdownContentAllHistory } from './dropdown/compare-version-dropdown-content'
|
|
import { CompareVersionDropdownContentAllHistory } from './dropdown/compare-version-dropdown-content'
|
|
|
|
|
|
|
@@ -35,6 +48,8 @@ type HistoryVersionProps = {
|
|
|
compareDropdownActive: boolean
|
|
compareDropdownActive: boolean
|
|
|
setActiveDropdownItem: ActiveDropdown['setActiveDropdownItem']
|
|
setActiveDropdownItem: ActiveDropdown['setActiveDropdownItem']
|
|
|
closeDropdownForItem: ActiveDropdown['closeDropdownForItem']
|
|
closeDropdownForItem: ActiveDropdown['closeDropdownForItem']
|
|
|
|
|
+ hasTutorialOverlay?: boolean
|
|
|
|
|
+ setShowTutorial: (show: boolean) => void
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function HistoryVersion({
|
|
function HistoryVersion({
|
|
@@ -52,8 +67,97 @@ function HistoryVersion({
|
|
|
compareDropdownActive,
|
|
compareDropdownActive,
|
|
|
setActiveDropdownItem,
|
|
setActiveDropdownItem,
|
|
|
closeDropdownForItem,
|
|
closeDropdownForItem,
|
|
|
|
|
+ hasTutorialOverlay = false,
|
|
|
|
|
+ setShowTutorial,
|
|
|
}: HistoryVersionProps) {
|
|
}: HistoryVersionProps) {
|
|
|
const orderedLabels = orderBy(update.labels, ['created_at'], ['desc'])
|
|
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)
|
|
|
|
|
+
|
|
|
|
|
+ useEffect(() => {
|
|
|
|
|
+ if (iconRef.current && hasTutorialOverlay && layoutSettled) {
|
|
|
|
|
+ const dismissModal = () => {
|
|
|
|
|
+ setShowTutorial(false)
|
|
|
|
|
+ runAsync(completeHistoryTutorial()).catch(console.error)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const compareIcon = (
|
|
|
|
|
+ <MaterialIcon
|
|
|
|
|
+ type="align_end"
|
|
|
|
|
+ className="material-symbols-rounded history-dropdown-icon-inverted"
|
|
|
|
|
+ />
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ setPopover(
|
|
|
|
|
+ <Overlay
|
|
|
|
|
+ placement="left"
|
|
|
|
|
+ show={hasTutorialOverlay}
|
|
|
|
|
+ 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]} // eslint-disable-line react/jsx-key
|
|
|
|
|
+ />
|
|
|
|
|
+ <a href="https://www.overleaf.com/learn/latex/Using_the_History_feature">
|
|
|
|
|
+ {' '}
|
|
|
|
|
+ {t('react_history_tutorial_learn_more')}
|
|
|
|
|
+ </a>
|
|
|
|
|
+ </Popover>
|
|
|
|
|
+ </Overlay>
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+ }, [hasTutorialOverlay, runAsync, setShowTutorial, t, layoutSettled])
|
|
|
|
|
+
|
|
|
|
|
+ // 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 {
|
|
|
|
|
+ setShowTutorial(false)
|
|
|
|
|
+ }
|
|
|
|
|
+ timer = window.setTimeout(() => {
|
|
|
|
|
+ timer = null
|
|
|
|
|
+ setShowTutorial(true)
|
|
|
|
|
+ }, 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, setShowTutorial])
|
|
|
|
|
|
|
|
const closeDropdown = useCallback(() => {
|
|
const closeDropdown = useCallback(() => {
|
|
|
closeDropdownForItem(update, 'moreOptions')
|
|
closeDropdownForItem(update, 'moreOptions')
|
|
@@ -63,6 +167,7 @@ function HistoryVersion({
|
|
|
|
|
|
|
|
return (
|
|
return (
|
|
|
<>
|
|
<>
|
|
|
|
|
+ {hasTutorialOverlay && popover}
|
|
|
{showDivider ? (
|
|
{showDivider ? (
|
|
|
<div
|
|
<div
|
|
|
className={classNames({
|
|
className={classNames({
|
|
@@ -121,7 +226,11 @@ function HistoryVersion({
|
|
|
)}
|
|
)}
|
|
|
|
|
|
|
|
{selected !== 'selected' ? (
|
|
{selected !== 'selected' ? (
|
|
|
- <div data-testid="compare-icon-version" className="pull-right">
|
|
|
|
|
|
|
+ <div
|
|
|
|
|
+ data-testid="compare-icon-version"
|
|
|
|
|
+ className="pull-right"
|
|
|
|
|
+ ref={iconRef}
|
|
|
|
|
+ >
|
|
|
{selected !== 'withinSelected' ? (
|
|
{selected !== 'withinSelected' ? (
|
|
|
<CompareItems
|
|
<CompareItems
|
|
|
updateRange={updateRange}
|
|
updateRange={updateRange}
|