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

Uses UI-lib breadcrumbs #1281

Merged
merged 4 commits into from
Jun 25, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
132 changes: 70 additions & 62 deletions frontend/src/components/layout/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,80 +9,88 @@
* by the Apache License, Version 2.0
*/

import { ChevronRightIcon } from '@primer/octicons-react';
import { observer } from 'mobx-react';
import { Link, useRouteMatch } from 'react-router-dom';
import { useRouteMatch } from 'react-router-dom';
import { isEmbedded } from '../../config';
import { uiState } from '../../state/uiState';
import { BreadcrumbEntry, uiState } from '../../state/uiState';
import { UserPreferencesButton } from '../misc/UserPreferences';
import DataRefreshButton from '../misc/buttons/data-refresh/Component';
import { IsDev } from '../../utils/env';
import { Box, Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbLinkProps, CopyButton, ColorModeSwitch, Flex } from '@redpanda-data/ui';
import { Box, Text, ColorModeSwitch, Flex, Breadcrumbs, CopyButton } from '@redpanda-data/ui';
import { computed } from 'mobx';

const AppPageHeader = observer(() => {
const showRefresh = useShouldShowRefresh();

return <Box> {/* we need to refactor out #mainLayout > div rule, for now I've added this box as a workaround */}
<Flex mb={5} alignItems="center" justifyContent="space-between">
<Breadcrumb spacing="8px" separator={<ChevronRightIcon/>}>
{!isEmbedded() && uiState.selectedClusterName &&
<BreadcrumbItem>
<BreadcrumbLink as={Link} to="/">
Cluster
</BreadcrumbLink>
</BreadcrumbItem>
}
{uiState.pageBreadcrumbs.filter((_, i, arr) => {
const isCurrentPage = arr.length - 1 === i;
return !isEmbedded() || isCurrentPage;
}).map((entry, i, arr) => {
const isCurrentPage = arr.length - 1 === i;
const currentBreadcrumbProps: BreadcrumbLinkProps = isCurrentPage ? {
as: 'span',
fontWeight: 700,
fontSize: 'xl',
} : {
};

return (
<BreadcrumbItem key={entry.linkTo} isCurrentPage={isCurrentPage}>
<BreadcrumbLink
to={entry.linkTo}
as={isCurrentPage ? 'span' : Link}
noOfLines={1}
{...currentBreadcrumbProps}
{...(entry.options?.canBeTruncated ? {
wordBreak: 'break-all',
whiteSpace: 'break-spaces',
} : {
whiteSpace: 'nowrap'
})}
>
{entry.title}
</BreadcrumbLink>

<Flex ml={2}>
{isCurrentPage && entry.options?.canBeCopied && <CopyButton content={entry.title} variant="ghost"/>}

{isCurrentPage && showRefresh && (
<Box minW={250}>
<DataRefreshButton/>
</Box>
)}
</Flex>

</BreadcrumbItem>
);
}
const breadcrumbItems = computed(() => {
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think a computed should be created within an observable component.
Are you sure it won't just always be recreated/recomputed on every render? I think it will...

Instead consider this (outside of a component):

const obj = observable({

    get breadcrumbItems() {
          // compute items here
         return items;
    }
});

const AppPageHeader = observer(() => {
       const items =  obj.breadcrumbItems;
       ...
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I though creating an computed with get() is OK as part of the observable. I think we use the same concept just class-based elsewhere. There is also an example on Mobx site https://mobx.js.org/computeds-with-args.html#2-close-over-the-arguments

But maybe I misunderstand something. I've tried your solution and it seems the number of renders is identical. I'm happpy to chat about this on Slack.

const items: BreadcrumbEntry[] = [...uiState.pageBreadcrumbs]

if (!isEmbedded() && uiState.selectedClusterName) {
items.unshift({
heading: '',
title: 'Cluster',
linkTo: '/'
});
}

return items
}).get()

const lastBreadcrumb = breadcrumbItems.pop()

return (
<Box>
{/* we need to refactor out #mainLayout > div rule, for now I've added this box as a workaround */}
<Flex mb={5} alignItems="center" justifyContent="space-between">
{!isEmbedded() && (
<Breadcrumbs
showHomeIcon={false}
items={breadcrumbItems.map((x) => ({
name: x.title,
heading: x.heading,
to: x.linkTo,
}))}
/>
)}
</Breadcrumb>
</Flex>

<Flex alignItems="center" gap={1}>
<UserPreferencesButton/>
{(IsDev && !isEmbedded()) && <ColorModeSwitch/>}
<Flex pb={2} alignItems="center" justifyContent="space-between">
<Flex alignItems="center">
{lastBreadcrumb && (
<Flex gap={2} alignItems="center">
<Text
fontWeight={700}
as="span"
fontSize="xl"
{...(lastBreadcrumb.options?.canBeTruncated
? {
wordBreak: 'break-all',
whiteSpace: 'break-spaces',
}
: {
whiteSpace: 'nowrap',
})}
>
{lastBreadcrumb.title}
</Text>
<Box>{lastBreadcrumb.options?.canBeCopied && <CopyButton content={lastBreadcrumb.title} variant="ghost" />}</Box>
</Flex>
)}
<Flex ml={2}>
{showRefresh && (
<Box minW={250}>
<DataRefreshButton />
</Box>
)}
</Flex>
</Flex>
<Flex alignItems="center" gap={1}>
<UserPreferencesButton />
{IsDev && !isEmbedded() && <ColorModeSwitch />}
</Flex>
</Flex>
</Flex>
</Box>;
</Box>
);
});

export default AppPageHeader;
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/pages/Page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export class PageInitHelper {
makeAutoObservable(this);
}
set title(title: string) { uiState.pageTitle = title; }
addBreadcrumb(title: string, to: string, options?: BreadcrumbOptions) { uiState.pageBreadcrumbs.push({ title: title, linkTo: to, options }) }
addBreadcrumb(title: string, to: string, heading?: string, options?: BreadcrumbOptions) { uiState.pageBreadcrumbs.push({ title: title, linkTo: to, heading, options }) }
}
export abstract class PageComponent<TRouteParams = Record<string, unknown>> extends React.Component<PageProps<TRouteParams>> {

Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/pages/connect/Connector.Details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -393,8 +393,8 @@ class KafkaConnectorDetails extends PageComponent<{ clusterName: string; connect
const connector = decodeURIComponent(this.props.connector);
p.title = connector;
p.addBreadcrumb('Connectors', '/connect-clusters');
p.addBreadcrumb(clusterName, `/connect-clusters/${encodeURIComponent(clusterName)}`);
p.addBreadcrumb(connector, `/connect-clusters/${encodeURIComponent(clusterName)}/${encodeURIComponent(connector)}`, {
p.addBreadcrumb(clusterName, `/connect-clusters/${encodeURIComponent(clusterName)}`, 'Cluster Name');
p.addBreadcrumb(connector, `/connect-clusters/${encodeURIComponent(clusterName)}/${encodeURIComponent(connector)}`, undefined, {
canBeTruncated: true,
canBeCopied: true
});
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/pages/consumers/Group.Details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class GroupDetails extends PageComponent<{ groupId: string }> {

p.title = this.props.groupId;
p.addBreadcrumb('Consumer Groups', '/groups');
if (group) p.addBreadcrumb(group, '/' + group, {
if (group) p.addBreadcrumb(group, '/' + group, undefined, {
canBeCopied: true,
canBeTruncated: true,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class EditSchemaCompatibilityPage extends PageComponent<{ subjectName: string }>
p.title = 'Edit Schema Compatibility';
p.addBreadcrumb('Schema Registry', '/schema-registry');
if(subjectName) {
p.addBreadcrumb(subjectName, `/schema-registry/subjects/${subjectName}`, {
p.addBreadcrumb(subjectName, `/schema-registry/subjects/${subjectName}`, undefined, {
canBeTruncated: true
})
p.addBreadcrumb('Edit Compatibility', `/schema-registry/subjects/${subjectName}/edit-compatibility`)
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/pages/schemas/Schema.Create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export class SchemaAddVersionPage extends PageComponent<{ subjectName: string }>
const subjectName = this.props.subjectName;
p.title = 'Add schema version';
p.addBreadcrumb('Schema Registry', '/schema-registry');
p.addBreadcrumb(subjectName, `/schema-registry/subjects/${subjectName}`, {
p.addBreadcrumb(subjectName, `/schema-registry/subjects/${subjectName}`, undefined, {
canBeTruncated: true
})
p.addBreadcrumb('Create schema', `/schema-registry/subjects/${subjectName}/add-version`);
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/pages/topics/Topic.Details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ class TopicDetails extends PageComponent<{ topicName: string }> {

p.title = topicName;
p.addBreadcrumb('Topics', '/topics');
p.addBreadcrumb(topicName, '/topics/' + topicName, {
p.addBreadcrumb(topicName, '/topics/' + topicName, undefined, {
canBeCopied: true,
canBeTruncated: true,
});
Expand Down
1 change: 1 addition & 0 deletions frontend/src/state/uiState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export interface BreadcrumbOptions {

export interface BreadcrumbEntry {
title: string;
heading?: string;
linkTo: string;
options?: BreadcrumbOptions;
}
Expand Down
Loading