mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-10-14 18:27:35 +00:00
* Fix #4954: UI: Breadcrumb layout breaks with long table names * Fixing code smells Co-authored-by: Vivek Ratnavel Subramanian <vivekratnavel90@gmail.com>
This commit is contained in:
parent
eeab12b0fc
commit
4886d9d4af
@ -25,6 +25,7 @@ import { TITLE_FOR_NON_ADMIN_ACTION } from '../../constants/constants';
|
||||
import { Glossary } from '../../generated/entity/data/glossary';
|
||||
import { GlossaryTerm } from '../../generated/entity/data/glossaryTerm';
|
||||
import { useAuth } from '../../hooks/authHooks';
|
||||
import { useAfterMount } from '../../hooks/useAfterMount';
|
||||
import { ModifiedGlossaryData } from '../../pages/GlossaryPage/GlossaryPageV1.component';
|
||||
import { getEntityDeleteMessage } from '../../utils/CommonUtils';
|
||||
import { generateTreeData } from '../../utils/GlossaryUtils';
|
||||
@ -110,6 +111,15 @@ const GlossaryV1 = ({
|
||||
>([]);
|
||||
const [showActions, setShowActions] = useState(false);
|
||||
const [isDelete, setIsDelete] = useState<boolean>(false);
|
||||
const [addTermButtonWidth, setAddTermButtonWidth] = useState(
|
||||
document.getElementById('add-term-button')?.offsetWidth || 0
|
||||
);
|
||||
const [manageButtonWidth, setManageButtonWidth] = useState(
|
||||
document.getElementById('manage-button')?.offsetWidth || 0
|
||||
);
|
||||
const [leftPanelWidth, setLeftPanelWidth] = useState(
|
||||
document.getElementById('glossary-left-panel')?.offsetWidth || 0
|
||||
);
|
||||
|
||||
/**
|
||||
* To create breadcrumb from the fqn
|
||||
@ -163,10 +173,23 @@ const GlossaryV1 = ({
|
||||
handleBreadcrum(selectedKey);
|
||||
}, [selectedKey]);
|
||||
|
||||
useAfterMount(() => {
|
||||
setLeftPanelWidth(
|
||||
document.getElementById('glossary-left-panel')?.offsetWidth || 0
|
||||
);
|
||||
setAddTermButtonWidth(
|
||||
document.getElementById('add-term-button')?.offsetWidth || 0
|
||||
);
|
||||
setManageButtonWidth(
|
||||
document.getElementById('manage-button')?.offsetWidth || 0
|
||||
);
|
||||
});
|
||||
|
||||
const manageButtonContent = () => {
|
||||
return (
|
||||
<div
|
||||
className="tw-flex tw-items-center tw-gap-5 tw-p-1.5 tw-cursor-pointer"
|
||||
id="manage-button"
|
||||
onClick={() => setIsDelete(true)}>
|
||||
<div>
|
||||
<SVGIcons
|
||||
@ -191,7 +214,7 @@ const GlossaryV1 = ({
|
||||
|
||||
const fetchLeftPanel = () => {
|
||||
return (
|
||||
<div className="tw-px-2">
|
||||
<div className="tw-px-2" id="glossary-left-panel">
|
||||
<div className="tw-bg-white tw-shadow-box tw-border tw-border-border-gray tw-rounded-md tw-min-h-full tw-h-80vh tw-py-2">
|
||||
<div className="tw-flex tw-justify-between tw-items-center tw-px-3">
|
||||
<h6 className="tw-heading tw-text-base">Glossary</h6>
|
||||
@ -256,9 +279,14 @@ const GlossaryV1 = ({
|
||||
<div
|
||||
className="tw-heading tw-text-link tw-text-base"
|
||||
data-testid="category-name">
|
||||
<TitleBreadcrumb titleLinks={breadcrumb} />
|
||||
<TitleBreadcrumb
|
||||
titleLinks={breadcrumb}
|
||||
widthDeductions={
|
||||
leftPanelWidth + addTermButtonWidth + manageButtonWidth + 20 // Additional deduction for margin on the right of leftPanel
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="tw-relative tw-mr-2 tw--mt-2">
|
||||
<div className="tw-relative tw-mr-2 tw--mt-2" id="add-term-button">
|
||||
<NonAdminAction position="bottom" title={TITLE_FOR_NON_ADMIN_ACTION}>
|
||||
<Button
|
||||
className={classNames('tw-h-8 tw-rounded tw-mb-1 tw-mr-2', {
|
||||
|
@ -23,6 +23,7 @@ import { SettledStatus } from '../../../enums/axios.enum';
|
||||
import { Operation } from '../../../generated/entity/policies/accessControl/rule';
|
||||
import { EntityReference } from '../../../generated/type/entityReference';
|
||||
import { LabelType, State, TagLabel } from '../../../generated/type/tagLabel';
|
||||
import { useAfterMount } from '../../../hooks/useAfterMount';
|
||||
import { getHtmlForNonAdminAction } from '../../../utils/CommonUtils';
|
||||
import { getEntityFeedLink, getInfoElements } from '../../../utils/EntityUtils';
|
||||
import {
|
||||
@ -96,6 +97,9 @@ const EntityPageInfo = ({
|
||||
const [tagList, setTagList] = useState<Array<TagOption>>([]);
|
||||
const [tagFetchFailed, setTagFetchFailed] = useState<boolean>(false);
|
||||
const [isTagLoading, setIsTagLoading] = useState<boolean>(false);
|
||||
const [versionFollowButtonWidth, setVersionFollowButtonWidth] = useState(
|
||||
document.getElementById('version-and-follow-section')?.offsetWidth
|
||||
);
|
||||
|
||||
const handleTagSelection = (selectedTags?: Array<EntityTags>) => {
|
||||
if (selectedTags) {
|
||||
@ -184,7 +188,7 @@ const EntityPageInfo = ({
|
||||
const getVersionButton = (version: string) => {
|
||||
return (
|
||||
<div
|
||||
className="tw-flex tw-h-6 tw-ml-2 tw-mt-2"
|
||||
className="tw-flex tw-h-6 tw-ml-2"
|
||||
data-testid="version"
|
||||
onClick={versionHandler}>
|
||||
<span
|
||||
@ -298,12 +302,23 @@ const EntityPageInfo = ({
|
||||
setEntityFollowers(followersList);
|
||||
}, [followersList]);
|
||||
|
||||
useAfterMount(() => {
|
||||
setVersionFollowButtonWidth(
|
||||
document.getElementById('version-and-follow-section')?.offsetWidth
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div data-testid="entity-page-info">
|
||||
<div className="tw-flex tw-flex-col">
|
||||
<div className="tw-flex tw-flex-initial tw-justify-between tw-items-start">
|
||||
<div className="tw-flex tw-items-center">
|
||||
<TitleBreadcrumb titleLinks={titleLinks} />
|
||||
<TitleBreadcrumb
|
||||
titleLinks={titleLinks}
|
||||
widthDeductions={
|
||||
(versionFollowButtonWidth ? versionFollowButtonWidth : 0) + 30
|
||||
}
|
||||
/>
|
||||
{deleted && (
|
||||
<>
|
||||
<div
|
||||
@ -318,7 +333,9 @@ const EntityPageInfo = ({
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="tw-flex tw-py-1">
|
||||
<div
|
||||
className="tw-flex tw-py-1 tw-mt-1"
|
||||
id="version-and-follow-section">
|
||||
{!isUndefined(version) ? (
|
||||
<>
|
||||
{!isUndefined(isVersionSelected) ? (
|
||||
@ -339,7 +356,7 @@ const EntityPageInfo = ({
|
||||
</>
|
||||
) : null}
|
||||
{!isUndefined(isFollowing) ? (
|
||||
<div className="tw-flex tw-h-6 tw-ml-2 tw-mt-2">
|
||||
<div className="tw-flex tw-h-6 tw-ml-2">
|
||||
<span
|
||||
className={classNames(
|
||||
'tw-flex tw-border tw-border-primary tw-rounded',
|
||||
|
@ -14,7 +14,13 @@
|
||||
import { faAngleRight } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import classNames from 'classnames';
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import React, {
|
||||
FunctionComponent,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { TitleBreadcrumbProps } from './title-breadcrumb.interface';
|
||||
|
||||
@ -22,16 +28,48 @@ const TitleBreadcrumb: FunctionComponent<TitleBreadcrumbProps> = ({
|
||||
titleLinks,
|
||||
className = '',
|
||||
noLink = false,
|
||||
widthDeductions,
|
||||
}: TitleBreadcrumbProps) => {
|
||||
const [screenWidth, setScreenWidth] = useState(window.innerWidth);
|
||||
|
||||
const finalWidthOfBreadcrumb = useMemo(() => {
|
||||
return (
|
||||
screenWidth -
|
||||
(widthDeductions ? widthDeductions : 0) - // Any extra deductions due to sibling elements of breadcrumb
|
||||
(titleLinks.length - 1) * 25 - // Deduction for every arrow between each titleLink name
|
||||
80 // Deduction due to margin of the container on both sides
|
||||
);
|
||||
}, [screenWidth, titleLinks, widthDeductions]);
|
||||
|
||||
const maxWidth = useMemo(() => {
|
||||
return finalWidthOfBreadcrumb / titleLinks.length;
|
||||
}, [finalWidthOfBreadcrumb, titleLinks.length]);
|
||||
|
||||
const changeWidth = useCallback(() => {
|
||||
setScreenWidth(window.innerWidth);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('resize', changeWidth);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', changeWidth);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<nav className={className} data-testid="breadcrumb">
|
||||
<ol className="list-reset tw-py-2 tw-rounded tw-flex">
|
||||
{titleLinks.map((link, index) => {
|
||||
const classes =
|
||||
'link-title' + (link.activeTitle ? ' tw-font-medium' : '');
|
||||
'link-title tw-truncate' +
|
||||
(link.activeTitle ? ' tw-font-medium' : '');
|
||||
|
||||
return (
|
||||
<li data-testid="breadcrumb-link" key={index}>
|
||||
<li
|
||||
className="tw-flex tw-items-center"
|
||||
data-testid="breadcrumb-link"
|
||||
key={index}>
|
||||
{link.imgSrc ? (
|
||||
<img
|
||||
alt=""
|
||||
@ -41,7 +79,12 @@ const TitleBreadcrumb: FunctionComponent<TitleBreadcrumbProps> = ({
|
||||
) : null}
|
||||
{index < titleLinks.length - 1 && !noLink ? (
|
||||
<>
|
||||
<Link className={classes} to={link.url}>
|
||||
<Link
|
||||
className={classes}
|
||||
style={{
|
||||
maxWidth,
|
||||
}}
|
||||
to={link.url}>
|
||||
{link.name}
|
||||
</Link>
|
||||
<span className="tw-px-2">
|
||||
@ -52,7 +95,12 @@ const TitleBreadcrumb: FunctionComponent<TitleBreadcrumbProps> = ({
|
||||
</span>
|
||||
</>
|
||||
) : link.url ? (
|
||||
<Link className={classes} to={link.url}>
|
||||
<Link
|
||||
className={classes}
|
||||
style={{
|
||||
maxWidth,
|
||||
}}
|
||||
to={link.url}>
|
||||
{link.name}
|
||||
</Link>
|
||||
) : (
|
||||
@ -62,7 +110,10 @@ const TitleBreadcrumb: FunctionComponent<TitleBreadcrumbProps> = ({
|
||||
classes,
|
||||
'tw-cursor-text hover:tw-text-primary hover:tw-no-underline'
|
||||
)}
|
||||
data-testid="inactive-link">
|
||||
data-testid="inactive-link"
|
||||
style={{
|
||||
maxWidth,
|
||||
}}>
|
||||
{link.name}
|
||||
</span>
|
||||
{noLink && index < titleLinks.length - 1 && (
|
||||
|
@ -20,4 +20,5 @@ export type TitleBreadcrumbProps = {
|
||||
}>;
|
||||
className?: string;
|
||||
noLink?: boolean;
|
||||
widthDeductions?: number;
|
||||
};
|
||||
|
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2022 Collate
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export const useAfterMount = (fnCallback: () => void) => {
|
||||
const [isMounting, setIsMounting] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isMounting) {
|
||||
fnCallback();
|
||||
}
|
||||
}, [isMounting]);
|
||||
|
||||
useEffect(() => {
|
||||
setIsMounting(false);
|
||||
}, []);
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user