Skip to content

Commit

Permalink
refactor: in left menu, move recently viewed from top to middle (open…
Browse files Browse the repository at this point in the history
…search-project#87)

* refactor recently visited links to category

Signed-off-by: yuye-aws <yuyezhu@amazon.com>

* bring back external link logic

Signed-off-by: yuye-aws <yuyezhu@amazon.com>

* add no recently visited items when empty

Signed-off-by: yuye-aws <yuyezhu@amazon.com>

* change annotation

Signed-off-by: yuye-aws <yuyezhu@amazon.com>

* refactor with type RecentNavLink

Signed-off-by: yuye-aws <yuyezhu@amazon.com>

* rename navlink type from ChromeOrRecentNavLink to CollapsibleNavLink

Signed-off-by: yuye-aws <yuyezhu@amazon.com>

---------

Signed-off-by: yuye-aws <yuyezhu@amazon.com>
  • Loading branch information
yuye-aws authored and ruanyl committed Sep 15, 2023
1 parent 99a3922 commit 19ccc14
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 89 deletions.
4 changes: 3 additions & 1 deletion src/core/public/chrome/nav_links/nav_link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
83 changes: 20 additions & 63 deletions src/core/public/chrome/ui/header/collapsible_nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ import {
EuiListGroup,
EuiListGroupItem,
EuiShowFor,
EuiText,
} from '@elastic/eui';
import { i18n } from '@osd/i18n';
import { groupBy, sortBy } from 'lodash';
Expand All @@ -50,10 +49,15 @@ import { InternalApplicationStart } from '../../../application';
import { HttpStart } from '../../../http';
import { OnIsLockedUpdate } from './';
import type { Logos } from '../../../../common/types';
import { createEuiListItem, isModifiedOrPrevented, createRecentNavLink } from './nav_link';
import {
createEuiListItem,
createRecentChromeNavLink,
emptyRecentlyVisited,
CollapsibleNavLink,
} from './nav_link';
import { CollapsibleNavHeader } from './collapsible_nav_header';

function getAllCategories(allCategorizedLinks: Record<string, ChromeNavLink[]>) {
function getAllCategories(allCategorizedLinks: Record<string, CollapsibleNavLink[]>) {
const allCategories = {} as Record<string, AppCategory | undefined>;

for (const [key, value] of Object.entries(allCategorizedLinks)) {
Expand All @@ -64,7 +68,7 @@ function getAllCategories(allCategorizedLinks: Record<string, ChromeNavLink[]>)
}

function getOrderedCategories(
mainCategories: Record<string, ChromeNavLink[]>,
mainCategories: Record<string, CollapsibleNavLink[]>,
categoryDictionary: ReturnType<typeof getAllCategories>
) {
return sortBy(
Expand All @@ -75,9 +79,9 @@ function getOrderedCategories(

function getMergedNavLinks(
orderedCategories: string[],
uncategorizedLinks: ChromeNavLink[],
uncategorizedLinks: CollapsibleNavLink[],
categoryDictionary: ReturnType<typeof getAllCategories>
): Array<string | ChromeNavLink> {
): Array<string | CollapsibleNavLink> {
const uncategorizedLinksWithOrder = sortBy(
uncategorizedLinks.filter((link) => link.order !== null),
'order'
Expand Down Expand Up @@ -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<HTMLButtonElement>(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);
Expand All @@ -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,
Expand Down Expand Up @@ -191,61 +203,6 @@ export function CollapsibleNav({
basePath={basePath}
/>

{/* Recently viewed */}
<EuiCollapsibleNavGroup
key="recentlyViewed"
background="light"
title={i18n.translate('core.ui.recentlyViewed', { defaultMessage: 'Recently viewed' })}
isCollapsible={true}
initialIsOpen={getIsCategoryOpen('recentlyViewed', storage)}
onToggle={(isCategoryOpen) =>
setIsCategoryOpen('recentlyViewed', isCategoryOpen, storage)
}
data-test-subj="collapsibleNavGroup-recentlyViewed"
>
{recentlyAccessed.length > 0 ? (
<EuiListGroup
aria-label={i18n.translate('core.ui.recentlyViewedAriaLabel', {
defaultMessage: 'Recently viewed links',
})}
listItems={recentlyAccessed.map((link) => {
// 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"
/>
) : (
<EuiText size="s" color="subdued" style={{ padding: '0 8px 8px' }}>
<p>
{i18n.translate('core.ui.EmptyRecentlyViewed', {
defaultMessage: 'No recently viewed items',
})}
</p>
</EuiText>
)}
</EuiCollapsibleNavGroup>

{/* merged NavLinks */}
{mergedNavLinks.map((item, i) => {
if (typeof item === 'string') {
Expand Down
55 changes: 30 additions & 25 deletions src/core/public/chrome/ui/header/nav_link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<HTMLButtonElement, 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
Expand All @@ -73,6 +72,7 @@ export function createEuiListItem({
}

if (
!link.externalLink && // ignore external links
event.button === 0 && // ignore everything but left clicks
!isModifiedOrPrevented(event)
) {
Expand All @@ -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<ChromeNavLink, 'baseUrl'>;

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
Expand All @@ -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));
Expand All @@ -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<HTMLButtonElement, 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',
}),
};

0 comments on commit 19ccc14

Please sign in to comment.