mirror of
https://github.com/datahub-project/datahub.git
synced 2025-09-02 13:53:06 +00:00
fix(ui) Misc styling fixes (truncated filter values, updating tag color on clickaway) (#4246)
This commit is contained in:
parent
826abb882e
commit
5d0915ec64
@ -36,7 +36,8 @@ const PlatformInfo = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const TitleContainer = styled.div`
|
const TitleContainer = styled.div`
|
||||||
margin-bottom: 8px;
|
margin-bottom: 0px;
|
||||||
|
line-height: 30px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const PreviewImage = styled(Image)`
|
const PreviewImage = styled(Image)`
|
||||||
@ -49,7 +50,7 @@ const PreviewImage = styled(Image)`
|
|||||||
|
|
||||||
const EntityTitle = styled(Typography.Text)<{ $titleSizePx?: number }>`
|
const EntityTitle = styled(Typography.Text)<{ $titleSizePx?: number }>`
|
||||||
&&& {
|
&&& {
|
||||||
margin-bottom: 0;
|
margin-right 8px;
|
||||||
font-size: ${(props) => props.$titleSizePx || 16}px;
|
font-size: ${(props) => props.$titleSizePx || 16}px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
@ -91,7 +92,7 @@ const AvatarContainer = styled.div`
|
|||||||
|
|
||||||
const TagContainer = styled.div`
|
const TagContainer = styled.div`
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-left: 8px;
|
margin-left: 0px;
|
||||||
margin-top: -2px;
|
margin-top: -2px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { BookOutlined } from '@ant-design/icons';
|
import { BookOutlined } from '@ant-design/icons';
|
||||||
import { Tag } from 'antd';
|
import { Tag, Tooltip } from 'antd';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AggregationMetadata,
|
AggregationMetadata,
|
||||||
Domain,
|
Domain,
|
||||||
@ -53,24 +52,27 @@ export const SearchFilterLabel = ({ aggregation, field }: Props) => {
|
|||||||
|
|
||||||
if (aggregation.entity?.type === EntityType.Tag) {
|
if (aggregation.entity?.type === EntityType.Tag) {
|
||||||
const tag = aggregation.entity as TagType;
|
const tag = aggregation.entity as TagType;
|
||||||
|
const displayName = entityRegistry.getDisplayName(EntityType.Tag, tag);
|
||||||
|
const truncatedDisplayName = displayName.length > 25 ? `${displayName.slice(0, 25)}...` : displayName;
|
||||||
return (
|
return (
|
||||||
<>
|
<Tooltip title={displayName}>
|
||||||
<StyledTag $colorHash={tag?.urn} $color={tag?.properties?.colorHex}>
|
<StyledTag $colorHash={tag?.urn} $color={tag?.properties?.colorHex}>
|
||||||
{tag?.name}
|
{truncatedDisplayName}
|
||||||
</StyledTag>
|
</StyledTag>
|
||||||
({countText})
|
({countText})
|
||||||
</>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (aggregation.entity?.type === EntityType.CorpUser) {
|
if (aggregation.entity?.type === EntityType.CorpUser) {
|
||||||
const user = aggregation.entity as CorpUser;
|
const user = aggregation.entity as CorpUser;
|
||||||
const displayName = entityRegistry.getDisplayName(EntityType.CorpUser, user);
|
const displayName = entityRegistry.getDisplayName(EntityType.CorpUser, user);
|
||||||
|
const truncatedDisplayName = displayName.length > 25 ? `${displayName.slice(0, 25)}...` : displayName;
|
||||||
return (
|
return (
|
||||||
<>
|
<Tooltip title={displayName}>
|
||||||
<CustomAvatar
|
<CustomAvatar
|
||||||
size={18}
|
size={18}
|
||||||
name={displayName}
|
name={truncatedDisplayName}
|
||||||
photoUrl={user.editableProperties?.pictureLink || undefined}
|
photoUrl={user.editableProperties?.pictureLink || undefined}
|
||||||
useDefaultAvatar={false}
|
useDefaultAvatar={false}
|
||||||
style={{
|
style={{
|
||||||
@ -78,82 +80,92 @@ export const SearchFilterLabel = ({ aggregation, field }: Props) => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{displayName} ({countText})
|
{displayName} ({countText})
|
||||||
</>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (aggregation.entity?.type === EntityType.CorpGroup) {
|
if (aggregation.entity?.type === EntityType.CorpGroup) {
|
||||||
const group = aggregation.entity as CorpGroup;
|
const group = aggregation.entity as CorpGroup;
|
||||||
|
const displayName = entityRegistry.getDisplayName(EntityType.CorpGroup, group);
|
||||||
|
const truncatedDisplayName = displayName.length > 25 ? `${displayName.slice(0, 25)}...` : displayName;
|
||||||
return (
|
return (
|
||||||
<>
|
<Tooltip title={displayName}>
|
||||||
<span style={{ marginRight: 8 }}>
|
<span style={{ marginRight: 8 }}>
|
||||||
{entityRegistry.getIcon(EntityType.CorpGroup, 16, IconStyleType.ACCENT)}
|
{entityRegistry.getIcon(EntityType.CorpGroup, 16, IconStyleType.ACCENT)}
|
||||||
</span>
|
</span>
|
||||||
{entityRegistry.getDisplayName(EntityType.CorpGroup, group)} ({countText})
|
{truncatedDisplayName} ({countText})
|
||||||
</>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (aggregation.entity?.type === EntityType.GlossaryTerm) {
|
if (aggregation.entity?.type === EntityType.GlossaryTerm) {
|
||||||
const term = aggregation.entity as GlossaryTerm;
|
const term = aggregation.entity as GlossaryTerm;
|
||||||
|
const displayName = entityRegistry.getDisplayName(EntityType.GlossaryTerm, term);
|
||||||
|
const truncatedDisplayName = displayName.length > 25 ? `${displayName.slice(0, 25)}...` : displayName;
|
||||||
return (
|
return (
|
||||||
<>
|
<Tooltip title={displayName}>
|
||||||
<Tag closable={false}>
|
<Tag closable={false}>
|
||||||
<BookOutlined style={{ marginRight: '3%' }} />
|
<BookOutlined style={{ marginRight: '3%' }} />
|
||||||
{entityRegistry.getDisplayName(EntityType.GlossaryTerm, term)}
|
{truncatedDisplayName}
|
||||||
</Tag>
|
</Tag>
|
||||||
({countText})
|
({countText})
|
||||||
</>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (aggregation.entity?.type === EntityType.DataPlatform) {
|
if (aggregation.entity?.type === EntityType.DataPlatform) {
|
||||||
const platform = aggregation.entity as DataPlatform;
|
const platform = aggregation.entity as DataPlatform;
|
||||||
|
const displayName = platform.properties?.displayName || platform.info?.displayName || platform.name;
|
||||||
|
const truncatedDisplayName = displayName.length > 25 ? `${displayName.slice(0, 25)}...` : displayName;
|
||||||
return (
|
return (
|
||||||
<>
|
<Tooltip title={displayName}>
|
||||||
{!!platform.properties?.logoUrl && (
|
{!!platform.properties?.logoUrl && (
|
||||||
<PreviewImage src={platform.properties?.logoUrl} alt={platform.name} />
|
<PreviewImage src={platform.properties?.logoUrl} alt={platform.name} />
|
||||||
)}
|
)}
|
||||||
<span>
|
<span>
|
||||||
{platform.properties?.displayName || platform.name} ({countText})
|
{truncatedDisplayName} ({countText})
|
||||||
</span>
|
</span>
|
||||||
</>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (aggregation.entity?.type === EntityType.Container) {
|
if (aggregation.entity?.type === EntityType.Container) {
|
||||||
const container = aggregation.entity as Container;
|
const container = aggregation.entity as Container;
|
||||||
|
const displayName = entityRegistry.getDisplayName(EntityType.Container, container);
|
||||||
|
const truncatedDisplayName = displayName.length > 25 ? `${displayName.slice(0, 25)}...` : displayName;
|
||||||
return (
|
return (
|
||||||
<>
|
<Tooltip title={displayName}>
|
||||||
{!!container.platform?.properties?.logoUrl && (
|
{!!container.platform?.properties?.logoUrl && (
|
||||||
<PreviewImage src={container.platform?.properties?.logoUrl} alt={container.properties?.name} />
|
<PreviewImage src={container.platform?.properties?.logoUrl} alt={container.properties?.name} />
|
||||||
)}
|
)}
|
||||||
<span>
|
<span>
|
||||||
{container.properties?.name} ({countText})
|
{truncatedDisplayName} ({countText})
|
||||||
</span>
|
</span>
|
||||||
</>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (aggregation.entity?.type === EntityType.Domain) {
|
if (aggregation.entity?.type === EntityType.Domain) {
|
||||||
const domain = aggregation.entity as Domain;
|
const domain = aggregation.entity as Domain;
|
||||||
|
const displayName = entityRegistry.getDisplayName(EntityType.Domain, domain);
|
||||||
|
const truncatedDomainName = displayName.length > 25 ? `${displayName.slice(0, 25)}...` : displayName;
|
||||||
return (
|
return (
|
||||||
<>
|
<Tooltip title={displayName}>
|
||||||
<DomainLink urn={domain.urn} name={entityRegistry.getDisplayName(EntityType.Domain, domain)} />(
|
<DomainLink urn={domain.urn} name={truncatedDomainName} />({countText})
|
||||||
{countText})
|
</Tooltip>
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Warning: Special casing for Sub-Types
|
// Warning: Special casing for Sub-Types
|
||||||
if (field === 'typeNames') {
|
if (field === 'typeNames') {
|
||||||
const subTypeDisplayName = capitalizeFirstLetter(aggregation.value);
|
const displayName = capitalizeFirstLetter(aggregation.value) || '';
|
||||||
|
const truncatedDomainName = displayName.length > 25 ? `${displayName.slice(0, 25)}...` : displayName;
|
||||||
return (
|
return (
|
||||||
<>
|
<Tooltip title={displayName}>
|
||||||
<span>
|
<span>
|
||||||
{subTypeDisplayName} ({countText})
|
{truncatedDomainName} ({countText})
|
||||||
</span>
|
</span>
|
||||||
</>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { grey } from '@ant-design/colors';
|
import { grey } from '@ant-design/colors';
|
||||||
import { Alert, Button, Divider, message, Typography } from 'antd';
|
import { Alert, Button, Divider, message, Typography } from 'antd';
|
||||||
import { useHistory } from 'react-router';
|
import { useHistory } from 'react-router';
|
||||||
import { ApolloError } from '@apollo/client';
|
import { ApolloError } from '@apollo/client';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { ChromePicker } from 'react-color';
|
import { ChromePicker } from 'react-color';
|
||||||
|
import ColorHash from 'color-hash';
|
||||||
import { PlusOutlined } from '@ant-design/icons';
|
import { PlusOutlined } from '@ant-design/icons';
|
||||||
import { useGetTagQuery } from '../../graphql/tag.generated';
|
import { useGetTagQuery } from '../../graphql/tag.generated';
|
||||||
import { EntityType, FacetMetadata, Maybe, Scalars } from '../../types.generated';
|
import { EntityType, FacetMetadata, Maybe, Scalars } from '../../types.generated';
|
||||||
@ -61,7 +61,6 @@ const TitleText = styled(Typography.Text)`
|
|||||||
const ColorPicker = styled.div`
|
const ColorPicker = styled.div`
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-top: 1em;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ColorPickerButton = styled.div`
|
const ColorPickerButton = styled.div`
|
||||||
@ -141,6 +140,12 @@ const OwnerButtonTitle = styled.span`
|
|||||||
color: ${grey[10]};
|
color: ${grey[10]};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const TagName = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: left;
|
||||||
|
`;
|
||||||
|
|
||||||
const { Paragraph } = Typography;
|
const { Paragraph } = Typography;
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -152,6 +157,10 @@ type Props = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const generateColor = new ColorHash({
|
||||||
|
saturation: 0.9,
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Responsible for displaying metadata about a tag
|
* Responsible for displaying metadata about a tag
|
||||||
*/
|
*/
|
||||||
@ -166,7 +175,7 @@ export default function TagStyleEntity({ urn, useGetSearchResults = useWrappedSe
|
|||||||
|
|
||||||
const description = data?.tag?.properties?.description || '';
|
const description = data?.tag?.properties?.description || '';
|
||||||
const [updatedDescription, setUpdatedDescription] = useState('');
|
const [updatedDescription, setUpdatedDescription] = useState('');
|
||||||
const hexColor = data?.tag?.properties?.colorHex || '';
|
const hexColor = data?.tag?.properties?.colorHex || generateColor.hex(urn);
|
||||||
const [displayColorPicker, setDisplayColorPicker] = useState(false);
|
const [displayColorPicker, setDisplayColorPicker] = useState(false);
|
||||||
const [colorValue, setColorValue] = useState('');
|
const [colorValue, setColorValue] = useState('');
|
||||||
const ownersEmpty = !data?.tag?.ownership?.owners?.length;
|
const ownersEmpty = !data?.tag?.ownership?.owners?.length;
|
||||||
@ -195,9 +204,8 @@ export default function TagStyleEntity({ urn, useGetSearchResults = useWrappedSe
|
|||||||
const aggregations = facets && facets[0]?.aggregations;
|
const aggregations = facets && facets[0]?.aggregations;
|
||||||
|
|
||||||
// Save Color Change
|
// Save Color Change
|
||||||
const saveColor = async () => {
|
const saveColor = useCallback(async () => {
|
||||||
if (displayColorPicker) {
|
if (displayColorPicker) {
|
||||||
message.loading({ content: 'Saving...' });
|
|
||||||
try {
|
try {
|
||||||
await setTagColorMutation({
|
await setTagColorMutation({
|
||||||
variables: {
|
variables: {
|
||||||
@ -206,17 +214,42 @@ export default function TagStyleEntity({ urn, useGetSearchResults = useWrappedSe
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
message.destroy();
|
message.destroy();
|
||||||
message.success({ content: 'Color Updated', duration: 2 });
|
message.success({ content: 'Color Saved!', duration: 2 });
|
||||||
setDisplayColorPicker(false);
|
setDisplayColorPicker(false);
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
message.destroy();
|
message.destroy();
|
||||||
if (e instanceof Error) {
|
if (e instanceof Error) {
|
||||||
message.error({ content: `Failed to update Color: \n ${e.message || ''}`, duration: 2 });
|
message.error({ content: `Failed to save tag color: \n ${e.message || ''}`, duration: 2 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
refetch?.();
|
refetch?.();
|
||||||
}
|
}
|
||||||
|
}, [urn, colorValue, displayColorPicker, setTagColorMutation, setDisplayColorPicker, refetch]);
|
||||||
|
|
||||||
|
const colorPickerRef = useRef(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
/**
|
||||||
|
* Save Color if Clicked outside of the Color Picker
|
||||||
|
*/
|
||||||
|
function handleClickOutsideColorPicker(event) {
|
||||||
|
if (displayColorPicker) {
|
||||||
|
const { current }: any = colorPickerRef;
|
||||||
|
if (current) {
|
||||||
|
if (!current.contains(event.target)) {
|
||||||
|
setDisplayColorPicker(false);
|
||||||
|
saveColor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Bind the event listener
|
||||||
|
document.addEventListener('mousedown', handleClickOutsideColorPicker);
|
||||||
|
return () => {
|
||||||
|
// Unbind the event listener on clean up
|
||||||
|
document.removeEventListener('mousedown', handleClickOutsideColorPicker);
|
||||||
};
|
};
|
||||||
|
}, [colorPickerRef, displayColorPicker, saveColor]);
|
||||||
|
|
||||||
const handlePickerClick = () => {
|
const handlePickerClick = () => {
|
||||||
setDisplayColorPicker(!displayColorPicker);
|
setDisplayColorPicker(!displayColorPicker);
|
||||||
@ -271,15 +304,17 @@ export default function TagStyleEntity({ urn, useGetSearchResults = useWrappedSe
|
|||||||
{/* Tag Title */}
|
{/* Tag Title */}
|
||||||
<div>
|
<div>
|
||||||
<TitleLabel>Tag</TitleLabel>
|
<TitleLabel>Tag</TitleLabel>
|
||||||
|
<TagName>
|
||||||
<ColorPicker>
|
<ColorPicker>
|
||||||
<ColorPickerButton style={{ backgroundColor: colorValue }} onClick={handlePickerClick} />
|
<ColorPickerButton style={{ backgroundColor: colorValue }} onClick={handlePickerClick} />
|
||||||
</ColorPicker>
|
</ColorPicker>
|
||||||
|
<TitleText>{data?.tag?.properties?.name}</TitleText>
|
||||||
|
</TagName>
|
||||||
{displayColorPicker && (
|
{displayColorPicker && (
|
||||||
<ColorPickerPopOver>
|
<ColorPickerPopOver ref={colorPickerRef}>
|
||||||
<ChromePicker color={colorValue} onChange={handleColorChange} />
|
<ChromePicker color={colorValue} onChange={handleColorChange} />
|
||||||
</ColorPickerPopOver>
|
</ColorPickerPopOver>
|
||||||
)}
|
)}
|
||||||
<TitleText>{data?.tag?.properties?.name}</TitleText>
|
|
||||||
</div>
|
</div>
|
||||||
<Divider />
|
<Divider />
|
||||||
{/* Tag Description */}
|
{/* Tag Description */}
|
||||||
|
@ -16,13 +16,14 @@ export type Props = {
|
|||||||
name: string;
|
name: string;
|
||||||
closable?: boolean;
|
closable?: boolean;
|
||||||
onClose?: (e: any) => void;
|
onClose?: (e: any) => void;
|
||||||
|
tagStyle?: any | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DomainLink = ({ urn, name, closable, onClose }: Props): JSX.Element => {
|
export const DomainLink = ({ urn, name, closable, onClose, tagStyle }: Props): JSX.Element => {
|
||||||
const entityRegistry = useEntityRegistry();
|
const entityRegistry = useEntityRegistry();
|
||||||
return (
|
return (
|
||||||
<DomainLinkContainer to={entityRegistry.getEntityUrl(EntityType.Domain, urn)}>
|
<DomainLinkContainer to={entityRegistry.getEntityUrl(EntityType.Domain, urn)}>
|
||||||
<Tag closable={closable} onClose={onClose}>
|
<Tag style={tagStyle} closable={closable} onClose={onClose}>
|
||||||
<span style={{ paddingRight: '4px' }}>
|
<span style={{ paddingRight: '4px' }}>
|
||||||
{entityRegistry.getIcon(EntityType.Domain, 10, IconStyleType.ACCENT)}
|
{entityRegistry.getIcon(EntityType.Domain, 10, IconStyleType.ACCENT)}
|
||||||
</span>
|
</span>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user