Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: in left menu, move recently viewed from top to middle #87

Merged
merged 6 commits into from
Aug 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -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<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 @@ -227,61 +239,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' }}>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will we still show No recently viewed items after this refactor?

Copy link
Collaborator Author

@yuye-aws yuye-aws Aug 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. This edge case for recently visited and favorites needs to be confirmed with UX.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just get to know that No recently viewed items need to show. I will add it later.

<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(
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still looking at the this, please don't merge for now, having a few doubts need to figure out

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',
}),
};
Loading