From b293c7014cdf4888ff8f8d7fe57929d32db32b41 Mon Sep 17 00:00:00 2001 From: Davinder Singh Date: Thu, 21 Sep 2023 10:01:52 +0100 Subject: [PATCH] History UI - Moving pop over out from the version (#14770) Moving popover out of versions list GitOrigin-RevId: d1739f4c17b66a0e39c8bb46a0fac5b2069d9171 --- .../change-list/all-history-list.tsx | 127 +++++++++++++---- .../change-list/history-version.tsx | 128 +----------------- .../stylesheets/app/editor/history-react.less | 8 ++ 3 files changed, 114 insertions(+), 149 deletions(-) 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 c2ce26dfdf..15ab7c08ca 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 @@ -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 = ( + + + {t('react_history_tutorial_title')}{' '} + dismissModal()} /> + + } + className="dark-themed history-popover" + > + , + , // eslint-disable-line jsx-a11y/anchor-has-content, react/jsx-key + ]} + /> + + ) - 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 (
+ {popover}
{visibleUpdates.map((update, index) => { @@ -148,9 +234,6 @@ function AllHistoryList() { selectionState === 'aboveSelected' || selectionState === 'belowSelected') - const hasTutorialOverlay = - index === firstUnselectedIndex && showTutorial - return ( ) })} 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 f3170ea68f..2b7826d324 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 @@ -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(null) - - const { runAsync } = useAsync() - - const { t } = useTranslation() - - const [popover, setPopover] = useState(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 = ( - - ) - - setPopover( - - - {t('react_history_tutorial_title')}{' '} - dismissModal()} /> - - } - className="dark-themed" - > - , // eslint-disable-line jsx-a11y/anchor-has-content, react/jsx-key - ]} - /> - - - ) - } 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 ? (
+
{selectionState !== 'withinSelected' ? (