From 7fd4139a2b16603895ca0f406cdf5e8815c45df3 Mon Sep 17 00:00:00 2001 From: Yuye Zhu Date: Mon, 21 Aug 2023 12:30:29 +0800 Subject: [PATCH] refactor: in left menu, move recently viewed from top to middle (#87) * refactor recently visited links to category Signed-off-by: yuye-aws * bring back external link logic Signed-off-by: yuye-aws * add no recently visited items when empty Signed-off-by: yuye-aws * change annotation Signed-off-by: yuye-aws * refactor with type RecentNavLink Signed-off-by: yuye-aws * rename navlink type from ChromeOrRecentNavLink to CollapsibleNavLink Signed-off-by: yuye-aws --------- Signed-off-by: yuye-aws --- src/core/public/chrome/nav_links/nav_link.ts | 4 +- .../chrome/ui/header/collapsible_nav.tsx | 83 +++++-------------- src/core/public/chrome/ui/header/nav_link.tsx | 55 ++++++------ 3 files changed, 53 insertions(+), 89 deletions(-) diff --git a/src/core/public/chrome/nav_links/nav_link.ts b/src/core/public/chrome/nav_links/nav_link.ts index 8479c8468b74..19e2fd2eddab 100644 --- a/src/core/public/chrome/nav_links/nav_link.ts +++ b/src/core/public/chrome/nav_links/nav_link.ts @@ -93,8 +93,10 @@ export interface ChromeNavLink { * Disables a link from being clickable. * * @internalRemarks - * This is only used by the ML and Graph plugins currently. They use this field + * This is used by the ML and Graph plugins. They use this field * to disable the nav link when the license is expired. + * This is also used by recently visited category in left menu + * to disable "No recently visited items". */ readonly disabled?: boolean; diff --git a/src/core/public/chrome/ui/header/collapsible_nav.tsx b/src/core/public/chrome/ui/header/collapsible_nav.tsx index e119cc6a05b0..eb58a982140c 100644 --- a/src/core/public/chrome/ui/header/collapsible_nav.tsx +++ b/src/core/public/chrome/ui/header/collapsible_nav.tsx @@ -36,7 +36,6 @@ import { EuiListGroup, EuiListGroupItem, EuiShowFor, - EuiText, } from '@elastic/eui'; import { i18n } from '@osd/i18n'; import { groupBy, sortBy } from 'lodash'; @@ -49,11 +48,16 @@ import { AppCategory } from '../../../../types'; import { InternalApplicationStart } from '../../../application'; import { HttpStart } from '../../../http'; import { OnIsLockedUpdate } from './'; -import { createEuiListItem, isModifiedOrPrevented, createRecentNavLink } from './nav_link'; +import { + createEuiListItem, + createRecentChromeNavLink, + emptyRecentlyVisited, + CollapsibleNavLink, +} from './nav_link'; import { ChromeBranding } from '../../chrome_service'; import { CollapsibleNavHeader } from './collapsible_nav_header'; -function getAllCategories(allCategorizedLinks: Record) { +function getAllCategories(allCategorizedLinks: Record) { const allCategories = {} as Record; for (const [key, value] of Object.entries(allCategorizedLinks)) { @@ -64,7 +68,7 @@ function getAllCategories(allCategorizedLinks: Record) } function getOrderedCategories( - mainCategories: Record, + mainCategories: Record, categoryDictionary: ReturnType ) { return sortBy( @@ -75,9 +79,9 @@ function getOrderedCategories( function getMergedNavLinks( orderedCategories: string[], - uncategorizedLinks: ChromeNavLink[], + uncategorizedLinks: CollapsibleNavLink[], categoryDictionary: ReturnType -): Array { +): Array { const uncategorizedLinksWithOrder = sortBy( uncategorizedLinks.filter((link) => link.order !== null), 'order' @@ -149,9 +153,17 @@ export function CollapsibleNav({ }: Props) { const navLinks = useObservable(observables.navLinks$, []).filter((link) => !link.hidden); const recentlyAccessed = useObservable(observables.recentlyAccessed$, []); + const allNavLinks: CollapsibleNavLink[] = [...navLinks]; + if (recentlyAccessed.length) { + allNavLinks.push( + ...recentlyAccessed.map((link) => createRecentChromeNavLink(link, navLinks, basePath)) + ); + } else { + allNavLinks.push(emptyRecentlyVisited); + } const appId = useObservable(observables.appId$, ''); const lockRef = useRef(null); - const groupedNavLinks = groupBy(navLinks, (link) => link?.category?.id); + const groupedNavLinks = groupBy(allNavLinks, (link) => link?.category?.id); const { undefined: uncategorizedLinks = [], ...allCategorizedLinks } = groupedNavLinks; const categoryDictionary = getAllCategories(allCategorizedLinks); const orderedCategories = getOrderedCategories(allCategorizedLinks, categoryDictionary); @@ -161,7 +173,7 @@ export function CollapsibleNav({ categoryDictionary ); - const readyForEUI = (link: ChromeNavLink, needsIcon: boolean = false) => { + const readyForEUI = (link: CollapsibleNavLink, needsIcon: boolean = false) => { return createEuiListItem({ link, appId, @@ -227,61 +239,6 @@ export function CollapsibleNav({ basePath={basePath} /> - {/* Recently viewed */} - - setIsCategoryOpen('recentlyViewed', isCategoryOpen, storage) - } - data-test-subj="collapsibleNavGroup-recentlyViewed" - > - {recentlyAccessed.length > 0 ? ( - { - // TODO #64541 - // Can remove icon from recent links completely - const { iconType, onClick, ...hydratedLink } = createRecentNavLink( - link, - navLinks, - basePath, - navigateToUrl - ); - - return { - ...hydratedLink, - 'data-test-subj': 'collapsibleNavAppLink--recent', - onClick: (event) => { - if (!isModifiedOrPrevented(event)) { - closeNav(); - onClick(event); - } - }, - }; - })} - maxWidth="none" - color="subdued" - gutterSize="none" - size="s" - className="osdCollapsibleNav__recentsListGroup" - /> - ) : ( - -

- {i18n.translate('core.ui.EmptyRecentlyViewed', { - defaultMessage: 'No recently viewed items', - })} -

-
- )} -
- {/* merged NavLinks */} {mergedNavLinks.map((item, i) => { if (typeof item === 'string') { diff --git a/src/core/public/chrome/ui/header/nav_link.tsx b/src/core/public/chrome/ui/header/nav_link.tsx index ea35e192e7bd..e8b335db1015 100644 --- a/src/core/public/chrome/ui/header/nav_link.tsx +++ b/src/core/public/chrome/ui/header/nav_link.tsx @@ -31,22 +31,21 @@ import { EuiIcon } from '@elastic/eui'; import { i18n } from '@osd/i18n'; import React from 'react'; -import { ChromeNavLink, ChromeRecentlyAccessedHistoryItem, CoreStart } from '../../..'; +import { AppCategory, ChromeNavLink, ChromeRecentlyAccessedHistoryItem, CoreStart } from '../../..'; import { HttpStart } from '../../../http'; -import { InternalApplicationStart } from '../../../application/types'; import { relativeToAbsolute } from '../../nav_links/to_nav_link'; export const isModifiedOrPrevented = (event: React.MouseEvent) => event.metaKey || event.altKey || event.ctrlKey || event.shiftKey || event.defaultPrevented; +export type CollapsibleNavLink = ChromeNavLink | RecentNavLink; interface Props { - link: ChromeNavLink; + link: ChromeNavLink | RecentNavLink; appId?: string; basePath?: HttpStart['basePath']; dataTestSubj: string; onClick?: Function; navigateToApp: CoreStart['application']['navigateToApp']; - externalLink?: boolean; } // TODO #64541 @@ -73,6 +72,7 @@ export function createEuiListItem({ } if ( + !link.externalLink && // ignore external links event.button === 0 && // ignore everything but left clicks !isModifiedOrPrevented(event) ) { @@ -91,14 +91,16 @@ export function createEuiListItem({ }; } -export interface RecentNavLink { - href: string; - label: string; - title: string; - 'aria-label': string; - iconType?: string; - onClick: React.MouseEventHandler; -} +export type RecentNavLink = Omit; + +const recentlyVisitedCategory: AppCategory = { + id: 'recentlyVisited', + label: i18n.translate('core.ui.recentlyVisited.label', { + defaultMessage: 'Recently Visited', + }), + order: 0, + euiIconType: 'clock', +}; /** * Add saved object type info to recently links @@ -110,11 +112,10 @@ export interface RecentNavLink { * @param navLinks * @param basePath */ -export function createRecentNavLink( +export function createRecentChromeNavLink( recentLink: ChromeRecentlyAccessedHistoryItem, navLinks: ChromeNavLink[], - basePath: HttpStart['basePath'], - navigateToUrl: InternalApplicationStart['navigateToUrl'] + basePath: HttpStart['basePath'] ): RecentNavLink { const { link, label } = recentLink; const href = relativeToAbsolute(basePath.prepend(link)); @@ -133,16 +134,20 @@ export function createRecentNavLink( return { href, - label, + id: recentLink.id, + externalLink: true, + category: recentlyVisitedCategory, title: titleAndAriaLabel, - 'aria-label': titleAndAriaLabel, - iconType: navLink?.euiIconType, - /* Use href and onClick to support "open in new tab" and SPA navigation in the same link */ - onClick(event: React.MouseEvent) { - if (event.button === 0 && !isModifiedOrPrevented(event)) { - event.preventDefault(); - navigateToUrl(href); - } - }, }; } + +// As emptyRecentlyVisited is disabled, values for id, href and baseUrl does not affect +export const emptyRecentlyVisited: RecentNavLink = { + id: '', + href: '', + disabled: true, + category: recentlyVisitedCategory, + title: i18n.translate('core.ui.EmptyRecentlyVisited', { + defaultMessage: 'No recently visited items', + }), +};