From b0ed78622037f4b350ac9c9c1afc6c7d5c9f0146 Mon Sep 17 00:00:00 2001 From: yuye-aws Date: Wed, 16 Aug 2023 17:53:17 +0800 Subject: [PATCH 1/6] refactor recently visited links to category Signed-off-by: yuye-aws --- .../chrome/ui/header/collapsible_nav.tsx | 61 ++----------------- src/core/public/chrome/ui/header/nav_link.tsx | 41 +++++-------- 2 files changed, 20 insertions(+), 82 deletions(-) diff --git a/src/core/public/chrome/ui/header/collapsible_nav.tsx b/src/core/public/chrome/ui/header/collapsible_nav.tsx index e119cc6a05b0..9a4c978b0109 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,7 +48,7 @@ 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 } from './nav_link'; import { ChromeBranding } from '../../chrome_service'; import { CollapsibleNavHeader } from './collapsible_nav_header'; @@ -149,6 +148,9 @@ export function CollapsibleNav({ }: Props) { const navLinks = useObservable(observables.navLinks$, []).filter((link) => !link.hidden); const recentlyAccessed = useObservable(observables.recentlyAccessed$, []); + navLinks.push( + ...recentlyAccessed.map((link) => createRecentChromeNavLink(link, navLinks, basePath)) + ); const appId = useObservable(observables.appId$, ''); const lockRef = useRef(null); const groupedNavLinks = groupBy(navLinks, (link) => link?.category?.id); @@ -227,61 +229,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..c1bc40aaacc7 100644 --- a/src/core/public/chrome/ui/header/nav_link.tsx +++ b/src/core/public/chrome/ui/header/nav_link.tsx @@ -31,9 +31,8 @@ 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) => @@ -46,7 +45,6 @@ interface Props { dataTestSubj: string; onClick?: Function; navigateToApp: CoreStart['application']['navigateToApp']; - externalLink?: boolean; } // TODO #64541 @@ -91,14 +89,14 @@ export function createEuiListItem({ }; } -export interface RecentNavLink { - href: string; - label: string; - title: string; - 'aria-label': string; - iconType?: string; - onClick: React.MouseEventHandler; -} +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,12 +108,11 @@ export interface RecentNavLink { * @param navLinks * @param basePath */ -export function createRecentNavLink( +export function createRecentChromeNavLink( recentLink: ChromeRecentlyAccessedHistoryItem, navLinks: ChromeNavLink[], - basePath: HttpStart['basePath'], - navigateToUrl: InternalApplicationStart['navigateToUrl'] -): RecentNavLink { + basePath: HttpStart['basePath'] +): ChromeNavLink { const { link, label } = recentLink; const href = relativeToAbsolute(basePath.prepend(link)); const navLink = navLinks.find((nl) => href.startsWith(nl.baseUrl)); @@ -133,16 +130,10 @@ export function createRecentNavLink( return { href, - label, + baseUrl: href, + 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); - } - }, }; } From 750a3b9799262afd9271c3e84b171467e41b0238 Mon Sep 17 00:00:00 2001 From: yuye-aws Date: Thu, 17 Aug 2023 08:18:56 +0800 Subject: [PATCH 2/6] bring back external link logic Signed-off-by: yuye-aws --- src/core/public/chrome/ui/header/nav_link.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/public/chrome/ui/header/nav_link.tsx b/src/core/public/chrome/ui/header/nav_link.tsx index c1bc40aaacc7..b9b46f68452d 100644 --- a/src/core/public/chrome/ui/header/nav_link.tsx +++ b/src/core/public/chrome/ui/header/nav_link.tsx @@ -71,6 +71,7 @@ export function createEuiListItem({ } if ( + !link.externalLink && // ignore external links event.button === 0 && // ignore everything but left clicks !isModifiedOrPrevented(event) ) { From 0569e80e33f15ee540e514d8ec94dc51e10942db Mon Sep 17 00:00:00 2001 From: yuye-aws Date: Fri, 18 Aug 2023 12:17:53 +0800 Subject: [PATCH 3/6] add no recently visited items when empty Signed-off-by: yuye-aws --- src/core/public/chrome/nav_links/nav_link.ts | 4 +++- .../public/chrome/ui/header/collapsible_nav.tsx | 12 ++++++++---- src/core/public/chrome/ui/header/nav_link.tsx | 13 +++++++++++++ 3 files changed, 24 insertions(+), 5 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 9a4c978b0109..1533914aee4e 100644 --- a/src/core/public/chrome/ui/header/collapsible_nav.tsx +++ b/src/core/public/chrome/ui/header/collapsible_nav.tsx @@ -48,7 +48,7 @@ import { AppCategory } from '../../../../types'; import { InternalApplicationStart } from '../../../application'; import { HttpStart } from '../../../http'; import { OnIsLockedUpdate } from './'; -import { createEuiListItem, createRecentChromeNavLink } from './nav_link'; +import { createEuiListItem, createRecentChromeNavLink, emptyRecentlyVisited } from './nav_link'; import { ChromeBranding } from '../../chrome_service'; import { CollapsibleNavHeader } from './collapsible_nav_header'; @@ -148,9 +148,13 @@ export function CollapsibleNav({ }: Props) { const navLinks = useObservable(observables.navLinks$, []).filter((link) => !link.hidden); const recentlyAccessed = useObservable(observables.recentlyAccessed$, []); - navLinks.push( - ...recentlyAccessed.map((link) => createRecentChromeNavLink(link, navLinks, basePath)) - ); + if (recentlyAccessed.length) { + navLinks.push( + ...recentlyAccessed.map((link) => createRecentChromeNavLink(link, navLinks, basePath)) + ); + } else { + navLinks.push(emptyRecentlyVisited); + } const appId = useObservable(observables.appId$, ''); const lockRef = useRef(null); const groupedNavLinks = groupBy(navLinks, (link) => link?.category?.id); diff --git a/src/core/public/chrome/ui/header/nav_link.tsx b/src/core/public/chrome/ui/header/nav_link.tsx index b9b46f68452d..058291c24cd1 100644 --- a/src/core/public/chrome/ui/header/nav_link.tsx +++ b/src/core/public/chrome/ui/header/nav_link.tsx @@ -129,6 +129,7 @@ export function createRecentChromeNavLink( }); } + // As RecentChromeNavLink is only used in function createEuiListItem, value for baseUrl does not affect return { href, baseUrl: href, @@ -138,3 +139,15 @@ export function createRecentChromeNavLink( title: titleAndAriaLabel, }; } + +// As this link is disabled, values for id, href and baseUrl does not affect +export const emptyRecentlyVisited: ChromeNavLink = { + href: '', + baseUrl: '', + id: '', + disabled: true, + category: recentlyVisitedCategory, + title: i18n.translate('core.ui.EmptyRecentlyVisitied', { + defaultMessage: 'No recently visited items', + }), +}; From 5ed0c86061f663c3532e155ea40adce05b43ab67 Mon Sep 17 00:00:00 2001 From: yuye-aws Date: Fri, 18 Aug 2023 12:18:42 +0800 Subject: [PATCH 4/6] change annotation Signed-off-by: yuye-aws --- src/core/public/chrome/ui/header/nav_link.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/public/chrome/ui/header/nav_link.tsx b/src/core/public/chrome/ui/header/nav_link.tsx index 058291c24cd1..f0b0745d141a 100644 --- a/src/core/public/chrome/ui/header/nav_link.tsx +++ b/src/core/public/chrome/ui/header/nav_link.tsx @@ -140,7 +140,7 @@ export function createRecentChromeNavLink( }; } -// As this link is disabled, values for id, href and baseUrl does not affect +// As emptyRecentlyVisited is disabled, values for id, href and baseUrl does not affect export const emptyRecentlyVisited: ChromeNavLink = { href: '', baseUrl: '', From 32615102ef85108e1920137f082be115494b5c56 Mon Sep 17 00:00:00 2001 From: yuye-aws Date: Mon, 21 Aug 2023 11:30:38 +0800 Subject: [PATCH 5/6] refactor with type RecentNavLink Signed-off-by: yuye-aws --- .../chrome/ui/header/collapsible_nav.tsx | 24 ++++++++++++------- src/core/public/chrome/ui/header/nav_link.tsx | 16 ++++++------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/src/core/public/chrome/ui/header/collapsible_nav.tsx b/src/core/public/chrome/ui/header/collapsible_nav.tsx index 1533914aee4e..cf6d479b58c5 100644 --- a/src/core/public/chrome/ui/header/collapsible_nav.tsx +++ b/src/core/public/chrome/ui/header/collapsible_nav.tsx @@ -48,11 +48,16 @@ import { AppCategory } from '../../../../types'; import { InternalApplicationStart } from '../../../application'; import { HttpStart } from '../../../http'; import { OnIsLockedUpdate } from './'; -import { createEuiListItem, createRecentChromeNavLink, emptyRecentlyVisited } from './nav_link'; +import { + createEuiListItem, + createRecentChromeNavLink, + emptyRecentlyVisited, + ChromeOrRecentNavLink, +} 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)) { @@ -63,7 +68,7 @@ function getAllCategories(allCategorizedLinks: Record) } function getOrderedCategories( - mainCategories: Record, + mainCategories: Record, categoryDictionary: ReturnType ) { return sortBy( @@ -74,9 +79,9 @@ function getOrderedCategories( function getMergedNavLinks( orderedCategories: string[], - uncategorizedLinks: ChromeNavLink[], + uncategorizedLinks: ChromeOrRecentNavLink[], categoryDictionary: ReturnType -): Array { +): Array { const uncategorizedLinksWithOrder = sortBy( uncategorizedLinks.filter((link) => link.order !== null), 'order' @@ -148,16 +153,17 @@ export function CollapsibleNav({ }: Props) { const navLinks = useObservable(observables.navLinks$, []).filter((link) => !link.hidden); const recentlyAccessed = useObservable(observables.recentlyAccessed$, []); + const allNavLinks: ChromeOrRecentNavLink[] = [...navLinks]; if (recentlyAccessed.length) { - navLinks.push( + allNavLinks.push( ...recentlyAccessed.map((link) => createRecentChromeNavLink(link, navLinks, basePath)) ); } else { - navLinks.push(emptyRecentlyVisited); + 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); @@ -167,7 +173,7 @@ export function CollapsibleNav({ categoryDictionary ); - const readyForEUI = (link: ChromeNavLink, needsIcon: boolean = false) => { + const readyForEUI = (link: ChromeOrRecentNavLink, needsIcon: boolean = false) => { return createEuiListItem({ link, appId, diff --git a/src/core/public/chrome/ui/header/nav_link.tsx b/src/core/public/chrome/ui/header/nav_link.tsx index f0b0745d141a..34b0a24b348e 100644 --- a/src/core/public/chrome/ui/header/nav_link.tsx +++ b/src/core/public/chrome/ui/header/nav_link.tsx @@ -38,8 +38,9 @@ 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 ChromeOrRecentNavLink = ChromeNavLink | RecentNavLink; interface Props { - link: ChromeNavLink; + link: ChromeNavLink | RecentNavLink; appId?: string; basePath?: HttpStart['basePath']; dataTestSubj: string; @@ -90,6 +91,8 @@ export function createEuiListItem({ }; } +export type RecentNavLink = Omit; + const recentlyVisitedCategory: AppCategory = { id: 'recentlyVisited', label: i18n.translate('core.ui.recentlyVisited.label', { @@ -113,7 +116,7 @@ export function createRecentChromeNavLink( recentLink: ChromeRecentlyAccessedHistoryItem, navLinks: ChromeNavLink[], basePath: HttpStart['basePath'] -): ChromeNavLink { +): RecentNavLink { const { link, label } = recentLink; const href = relativeToAbsolute(basePath.prepend(link)); const navLink = navLinks.find((nl) => href.startsWith(nl.baseUrl)); @@ -129,10 +132,8 @@ export function createRecentChromeNavLink( }); } - // As RecentChromeNavLink is only used in function createEuiListItem, value for baseUrl does not affect return { href, - baseUrl: href, id: recentLink.id, externalLink: true, category: recentlyVisitedCategory, @@ -141,13 +142,12 @@ export function createRecentChromeNavLink( } // As emptyRecentlyVisited is disabled, values for id, href and baseUrl does not affect -export const emptyRecentlyVisited: ChromeNavLink = { - href: '', - baseUrl: '', +export const emptyRecentlyVisited: RecentNavLink = { id: '', + href: '', disabled: true, category: recentlyVisitedCategory, - title: i18n.translate('core.ui.EmptyRecentlyVisitied', { + title: i18n.translate('core.ui.EmptyRecentlyVisited', { defaultMessage: 'No recently visited items', }), }; From 40421c9505b92dfd8645217e19dc3415882e5d25 Mon Sep 17 00:00:00 2001 From: yuye-aws Date: Mon, 21 Aug 2023 11:49:03 +0800 Subject: [PATCH 6/6] rename navlink type from ChromeOrRecentNavLink to CollapsibleNavLink Signed-off-by: yuye-aws --- .../public/chrome/ui/header/collapsible_nav.tsx | 14 +++++++------- src/core/public/chrome/ui/header/nav_link.tsx | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/core/public/chrome/ui/header/collapsible_nav.tsx b/src/core/public/chrome/ui/header/collapsible_nav.tsx index cf6d479b58c5..eb58a982140c 100644 --- a/src/core/public/chrome/ui/header/collapsible_nav.tsx +++ b/src/core/public/chrome/ui/header/collapsible_nav.tsx @@ -52,12 +52,12 @@ import { createEuiListItem, createRecentChromeNavLink, emptyRecentlyVisited, - ChromeOrRecentNavLink, + 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)) { @@ -68,7 +68,7 @@ function getAllCategories(allCategorizedLinks: Record, + mainCategories: Record, categoryDictionary: ReturnType ) { return sortBy( @@ -79,9 +79,9 @@ function getOrderedCategories( function getMergedNavLinks( orderedCategories: string[], - uncategorizedLinks: ChromeOrRecentNavLink[], + uncategorizedLinks: CollapsibleNavLink[], categoryDictionary: ReturnType -): Array { +): Array { const uncategorizedLinksWithOrder = sortBy( uncategorizedLinks.filter((link) => link.order !== null), 'order' @@ -153,7 +153,7 @@ export function CollapsibleNav({ }: Props) { const navLinks = useObservable(observables.navLinks$, []).filter((link) => !link.hidden); const recentlyAccessed = useObservable(observables.recentlyAccessed$, []); - const allNavLinks: ChromeOrRecentNavLink[] = [...navLinks]; + const allNavLinks: CollapsibleNavLink[] = [...navLinks]; if (recentlyAccessed.length) { allNavLinks.push( ...recentlyAccessed.map((link) => createRecentChromeNavLink(link, navLinks, basePath)) @@ -173,7 +173,7 @@ export function CollapsibleNav({ categoryDictionary ); - const readyForEUI = (link: ChromeOrRecentNavLink, needsIcon: boolean = false) => { + const readyForEUI = (link: CollapsibleNavLink, needsIcon: boolean = false) => { return createEuiListItem({ link, appId, diff --git a/src/core/public/chrome/ui/header/nav_link.tsx b/src/core/public/chrome/ui/header/nav_link.tsx index 34b0a24b348e..e8b335db1015 100644 --- a/src/core/public/chrome/ui/header/nav_link.tsx +++ b/src/core/public/chrome/ui/header/nav_link.tsx @@ -38,7 +38,7 @@ 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 ChromeOrRecentNavLink = ChromeNavLink | RecentNavLink; +export type CollapsibleNavLink = ChromeNavLink | RecentNavLink; interface Props { link: ChromeNavLink | RecentNavLink; appId?: string;