Skip to content
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
1 change: 1 addition & 0 deletions web_ui/packages/ui/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ export { VirtualizedListLayout } from './src/virtualize-list-layout/virtualize-l
export { CornerIndicator } from './src/corner-indicator/corner-indicator.component';
export { VirtualizedHorizontalGrid } from './src/virtualized-horizontal-grid/virtualized-horizontal-grid';
export { ToggleButtons } from './src/toggle-buttons/toggle-buttons.component';
export { PhotoPlaceholder } from './src/photo-placeholder/photo-placeholder.component';

export {
ListBox as AriaComponentsListBox,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,30 @@
// Copyright (C) 2022-2025 Intel Corporation
// LIMITED EDGE SOFTWARE DISTRIBUTION LICENSE

import { Flex, Text, View, type ViewProps } from '@geti/ui';
import { FC } from 'react';

import { Flex, Text, View, ViewProps } from '@adobe/react-spectrum';
import { StyleProps } from '@react-types/shared';
import { isEmpty } from 'lodash-es';

import { getDistinctColorBasedOnHash } from '../../../pages/create-project/components/distinct-colors';
import { getForegroundColor, hexaToRGBA } from '../../../pages/utils';
import { getDistinctColorBasedOnHash, getForegroundColor, hexaToRGBA } from './utils';

interface PhotoPlaceholderProps {
export interface PhotoPlaceholderProps extends StyleProps {
name: string;
email: string;
width?: ViewProps<5>['height'];
width?: ViewProps<5>['width'];
height?: ViewProps<5>['height'];
borderRadius?: string;
}

export const PhotoPlaceholder = ({
export const PhotoPlaceholder: FC<PhotoPlaceholderProps> = ({
name,
email,
width = 'size-1600',
height = 'size-1600',
borderRadius = '50%',
}: PhotoPlaceholderProps): JSX.Element => {
...viewProps
}) => {
const backgroundColor = getDistinctColorBasedOnHash(email);
const letter = (isEmpty(name.trim()) ? email : name).charAt(0);

Expand All @@ -39,6 +42,7 @@ export const PhotoPlaceholder = ({
height={height}
UNSAFE_style={{ backgroundColor, color, borderRadius }}
data-testid={'placeholder-avatar-id'}
{...viewProps}
>
<Flex height={'100%'} width={'100%'} alignItems={'center'} justifyContent={'center'}>
<Text data-testid={'placeholder-letter-id'}>{letter.toUpperCase()}</Text>
Expand Down
75 changes: 75 additions & 0 deletions web_ui/packages/ui/src/photo-placeholder/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright (C) 2022-2025 Intel Corporation
// LIMITED EDGE SOFTWARE DISTRIBUTION LICENSE

import { isEmpty } from 'lodash-es';

export const getDistinctColorBasedOnHash = (value: string): string => {
const hash = Array.from(value).reduce((s, c) => (Math.imul(31, s) + c.charCodeAt(0)) | 0, 0);

const index = ((hash % DISTINCT_COLORS.length) + DISTINCT_COLORS.length) % DISTINCT_COLORS.length;

return DISTINCT_COLORS[index];
};

export const DISTINCT_COLORS = [
'#708541',
'#E96115',
'#EDB200',
'#FF5662',
'#CC94DA',
'#5B69FF',
'#548FAD',
//
'#25A18E',
'#9D3B1A',
'#C9E649',
'#F15B85',
'#81407B',
'#26518E',
'#076984',
//
'#00F5D4',
'#FF7D00',
'#F7DAB3',
'#80E9AF',
'#9B5DE5',
'#00A5CF',
'#D7BC5E',
];

type RGBArray = [number, number, number, number];

export const hexaToRGBA = (hex: string): RGBArray => {
if (isEmpty(hex)) {
return [0, 0, 0, 0];
}

if (hex.length == 9) {
return [
Number('0x' + hex[1] + hex[2]),
Number('0x' + hex[3] + hex[4]),
Number('0x' + hex[5] + hex[6]),
Number('0x' + hex[7] + hex[8]),
];
}

const alpha = Number('0x' + hex[4] + hex[4]);

return [
Number('0x' + hex[1] + hex[1]),
Number('0x' + hex[2] + hex[2]),
Number('0x' + hex[3] + hex[3]),
Number.isNaN(alpha) ? 1 : alpha,
Comment thread
ActiveChooN marked this conversation as resolved.
];
};

/**
* Determines the appropriate foreground color based on background color
* source https://css-tricks.com/css-variables-calc-rgb-enforcing-high-contrast-colors/
*/
export const getForegroundColor = (backgroundRgb: RGBArray, lowContrast: string, highContrast: string): string => {
const [r, g, b] = backgroundRgb;
const sum = Math.round((r * 299 + g * 587 + b * 114) / 1000);

return sum > 128 ? lowContrast : highContrast;
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,20 @@ import { FormEvent, useEffect, useState } from 'react';

import { paths } from '@geti/core';
import { useFeatureFlags } from '@geti/core/src/feature-flags/hooks/use-feature-flags.hook';
import { ActionGroup, Button, ButtonGroup, Divider, Flex, Form, Item, Key, Text, TextField, View } from '@geti/ui';
import {
ActionGroup,
Button,
ButtonGroup,
Divider,
Flex,
Form,
Item,
Key,
PhotoPlaceholder,
Text,
TextField,
View,
} from '@geti/ui';
import { CheckmarkCircleOutline, DeleteOutline, RemoveCircle } from '@geti/ui/icons';
import dayjs from 'dayjs';
import { isEmpty } from 'lodash-es';
Expand All @@ -14,7 +27,6 @@ import { useOverlayTriggerState } from 'react-stately';

import { AccountStatus, Organization } from '../../../core/organizations/organizations.interface';
import { DeleteDialog } from '../../../shared/components/delete-dialog/delete-dialog.component';
import { PhotoPlaceholder } from '../../../shared/components/photo-placeholder/photo-placeholder.component';
import { Header } from '../../shared/components/header/header.component';
import { useOrganization } from './hooks/organization.hook';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
// Copyright (C) 2022-2025 Intel Corporation
// LIMITED EDGE SOFTWARE DISTRIBUTION LICENSE

import { Flex, PressableElement, Tooltip, TooltipTrigger } from '@geti/ui';
import { Flex, PhotoPlaceholder, PressableElement, Tooltip, TooltipTrigger } from '@geti/ui';

import { PhotoPlaceholder } from '../../../../shared/components/photo-placeholder/photo-placeholder.component';
import { TruncatedText } from '../../../../shared/components/truncated-text/truncated-text.component';
import { OrganizationAdminsCopyText } from './organization-admins-copy-text.component';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,20 @@
import { FC, Key, useState } from 'react';

import { RESOURCE_TYPE, RoleResource, USER_ROLE } from '@geti/core/src/users/users.interface';
import { Button, ButtonGroup, Content, Dialog, DialogContainer, Divider, Flex, Heading, Item, Picker } from '@geti/ui';
import {
Button,
ButtonGroup,
Content,
Dialog,
DialogContainer,
Divider,
Flex,
Heading,
Item,
PhotoPlaceholder,
Picker,
} from '@geti/ui';

import { PhotoPlaceholder } from '../../../../shared/components/photo-placeholder/photo-placeholder.component';
import { TruncatedText } from '../../../../shared/components/truncated-text/truncated-text.component';
import { Membership } from '../mocked-memberships';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@

import { FC } from 'react';

import { Flex, Skeleton, View } from '@geti/ui';
import { Flex, PhotoPlaceholder, Skeleton, View } from '@geti/ui';

import { PhotoPlaceholder } from '../../../../shared/components/photo-placeholder/photo-placeholder.component';
import { TruncatedTextWithTooltip } from '../../../../shared/components/truncated-text/truncated-text.component';

import classes from './sidebar.module.scss';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useRef } from 'react';

import { getErrorMessage } from '@geti/core/src/services/utils';
import { useOnboardUserMutation } from '@geti/core/src/users/hook/use-onboard-user-mutation.hook';
import { CustomPopover, dimensionValue, Flex, Item, ListBox, Picker, View } from '@geti/ui';
import { CustomPopover, dimensionValue, Flex, Item, ListBox, PhotoPlaceholder, Picker, View } from '@geti/ui';
import { useOverlayTriggerState } from '@react-stately/overlays';
import { isNil } from 'lodash-es';

Expand All @@ -17,7 +17,6 @@ import {
isOrganizationVisible,
isUserInvitedInOrg,
} from '../../../../routes/organizations/util';
import { PhotoPlaceholder } from '../../../../shared/components/photo-placeholder/photo-placeholder.component';
import { QuietToggleButton } from '../../../../shared/components/quiet-button/quiet-toggle-button.component';
import { hasEqualId, isNonEmptyString } from '../../../../shared/utils';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { usePress } from 'react-aria';
import { isVideo } from '../../../../core/media/video.interface';
import { TestMediaItem } from '../../../../core/tests/test-media.interface';
import { TestScore } from '../../../../core/tests/tests.interface';
import { IndicatorWrapper } from '../../../../shared/components/indicator-wrapper/indicator-wrapper.component';
import { MediaItemView } from '../../../../shared/components/media-item-view/media-item-view.component';
import { getMediaId } from '../../../media/utils';
import { SCORE_FORMATTER_OPTIONS } from './utils';
Expand Down Expand Up @@ -58,9 +57,20 @@ export const TestMediaItemCard = ({
style={{ position: 'relative', width: 'inherit', height: 'inherit', cursor: 'pointer' }}
{...pressProps}
>
<IndicatorWrapper top={'size-50'} left={'size-50'} UNSAFE_className={classes.imageScore} id={scoreId}>
<View
top={'size-50'}
left={'size-50'}
zIndex={1}
padding={'size-50'}
position={'absolute'}
borderRadius={'small'}
height={'size-200'}
UNSAFE_className={classes.imageScore}
id={scoreId}
UNSAFE_style={{ backgroundColor: 'var(--spectrum-global-color-gray-50)' }}
>
{formatter.format(Number(labelScore?.value))}
</IndicatorWrapper>
</View>

<MediaItemView
mediaItem={mediaItem.media}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@

import { ComponentProps } from 'react';

import { ActionButton, View } from '@geti/ui';
import { ActionButton, PhotoPlaceholder, View } from '@geti/ui';
import { isNil } from 'lodash-es';
import { usePress } from 'react-aria';

import { PhotoPlaceholder } from '../../../../../shared/components/photo-placeholder/photo-placeholder.component';

import classes from './user-photo-placeholder.module.scss';

interface UserPhotoPlaceholderProps {
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import { isMediaPreprocessing } from '../../../core/media/utils/preprocessing.ut
import { isVideo, isVideoFrame } from '../../../core/media/video.interface';
import { AnnotationStateIndicator } from '../annotation-indicator/annotation-state-indicator.component';
import { VideoAnnotationIndicator } from '../annotation-indicator/video-annotation-indicator.component';
import { VideoFrameNumberIndicator } from '../video-indicator/video-frame-number-indicator.component';
import { VideoIndicator } from '../video-indicator/video-indicator.component';
import { VideoFrameNumberIndicator } from './video-frame-number-indicator.component';
import { VideoIndicator } from './video-indicator.component';

import classes from '../../shared.module.scss';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (C) 2022-2025 Intel Corporation
// LIMITED EDGE SOFTWARE DISTRIBUTION LICENSE

import { IndicatorWrapper } from '../indicator-wrapper/indicator-wrapper.component';
import { View } from '@geti/ui';

import classes from './video-indicator.module.scss';

Expand All @@ -11,14 +11,19 @@ interface VideoFrameIndicatorProps {

export const VideoFrameNumberIndicator = ({ frameNumber }: VideoFrameIndicatorProps): JSX.Element => {
return (
<IndicatorWrapper
<View
id={'video-frame-indicator-id'}
data-testid={'video-frame-indicator-id'}
top={'size-50'}
right={'size-50'}
zIndex={1}
padding={'size-50'}
position={'absolute'}
borderRadius={'small'}
height={'size-200'}
UNSAFE_className={classes.videoFrameText}
>
{frameNumber}F
</IndicatorWrapper>
</View>
);
};
Loading
Loading