Improve performance of demo workspace - Rename getImageAbsoluteURIOrBase64 function (#6282)

### Description

1. This PR is a continuation of a previous PR:
https://github.com/twentyhq/twenty/pull/6201#pullrequestreview-2175601222

2. One test case was removed here:
`packages/twenty-front/src/utils/image/__tests__/getImageAbsoluteURI.test.ts`
because since we are not handling base64 images anymore, the result is
the same of the last test case. Would you rather we update the test
instead?


### Refs

- #3514
- https://github.com/twentyhq/twenty/pull/6201

### Demo


https://www.loom.com/share/4f32b535c77a4d418e319b095d09452c?sid=df34adf8-b013-44ef-b794-d54846f52d2d

Fixes #3514

---------

Co-authored-by: gitstart-twenty <gitstart-twenty@users.noreply.github.com>
This commit is contained in:
gitstart-app[bot] 2024-07-29 14:07:21 +02:00 committed by GitHub
parent 00fea17920
commit fed12ddfcd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 124 additions and 97 deletions

View File

@ -9,7 +9,7 @@ import { MainText } from 'src/components/MainText';
import { Title } from 'src/components/Title';
import { WhatIsTwenty } from 'src/components/WhatIsTwenty';
import { capitalize } from 'src/utils/capitalize';
import { getImageAbsoluteURIOrBase64 } from 'src/utils/getImageAbsoluteURIOrBase64';
import { getImageAbsoluteURI } from 'src/utils/getImageAbsoluteURI';
type SendInviteLinkEmailProps = {
link: string;
@ -27,7 +27,7 @@ export const SendInviteLinkEmail = ({
sender,
serverUrl,
}: SendInviteLinkEmailProps) => {
const workspaceLogo = getImageAbsoluteURIOrBase64(workspace.logo, serverUrl);
const workspaceLogo = getImageAbsoluteURI(workspace.logo, serverUrl);
return (
<BaseEmail width={333}>
<Title value="Join your team on Twenty" />

View File

@ -1,4 +1,4 @@
export const getImageAbsoluteURIOrBase64 = (
export const getImageAbsoluteURI = (
imageUrl?: string | null,
serverUrl?: string,
) => {
@ -6,7 +6,7 @@ export const getImageAbsoluteURIOrBase64 = (
return null;
}
if (imageUrl?.startsWith('data:') || imageUrl?.startsWith('https:')) {
if (imageUrl?.startsWith('https:')) {
return imageUrl;
}

View File

@ -16,7 +16,6 @@ import { Card } from '@/ui/layout/card/components/Card';
import { CardContent } from '@/ui/layout/card/components/CardContent';
import { TimelineCalendarEvent } from '~/generated-metadata/graphql';
import { CalendarChannelVisibility } from '~/generated/graphql';
import { getImageAbsoluteURIOrBase64 } from '~/utils/image/getImageAbsoluteURIOrBase64';
import { isDefined } from '~/utils/isDefined';
type CalendarEventRowProps = {
@ -163,7 +162,7 @@ export const CalendarEventRow = ({
key={[participant.workspaceMemberId, participant.displayName]
.filter(isDefined)
.join('-')}
avatarUrl={getImageAbsoluteURIOrBase64(participant.avatarUrl)}
avatarUrl={participant.avatarUrl}
placeholder={
participant.firstName && participant.lastName
? `${participant.firstName} ${participant.lastName}`

View File

@ -6,7 +6,6 @@ import {
beautifyExactDateTime,
beautifyPastDateRelativeToNow,
} from '~/utils/date-utils';
import { getImageAbsoluteURIOrBase64 } from '~/utils/image/getImageAbsoluteURIOrBase64';
const StyledContainer = styled.div`
align-items: center;
@ -60,7 +59,7 @@ export const CommentHeader = ({ comment, actionBar }: CommentHeaderProps) => {
<StyledContainer>
<StyledLeftContainer>
<Avatar
avatarUrl={getImageAbsoluteURIOrBase64(avatarUrl)}
avatarUrl={avatarUrl}
size="md"
placeholderColorSeed={author?.id}
placeholder={authorName}

View File

@ -4,7 +4,6 @@ import { Avatar } from 'twenty-ui';
import { getDisplayNameFromParticipant } from '@/activities/emails/utils/getDisplayNameFromParticipant';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { RecordChip } from '@/object-record/components/RecordChip';
import { getImageAbsoluteURIOrBase64 } from '~/utils/image/getImageAbsoluteURIOrBase64';
const StyledAvatar = styled(Avatar)`
margin-right: ${({ theme }) => theme.spacing(1)};
@ -74,7 +73,7 @@ export const ParticipantChip = ({
) : (
<StyledChip>
<StyledAvatar
avatarUrl={getImageAbsoluteURIOrBase64(avatarUrl)}
avatarUrl={avatarUrl}
type="rounded"
placeholder={displayName}
size="sm"

View File

@ -10,7 +10,6 @@ import { CardContent } from '@/ui/layout/card/components/CardContent';
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
import { MessageChannelVisibility, TimelineThread } from '~/generated/graphql';
import { formatToHumanReadableDate } from '~/utils/date-utils';
import { getImageAbsoluteURIOrBase64 } from '~/utils/image/getImageAbsoluteURIOrBase64';
const StyledCardContent = styled(CardContent)<{
visibility: MessageChannelVisibility;
@ -154,24 +153,20 @@ export const EmailThreadPreview = ({
<StyledHeading unread={!thread.read}>
<StyledParticipantsContainer>
<Avatar
avatarUrl={getImageAbsoluteURIOrBase64(
thread?.firstParticipant?.avatarUrl,
)}
avatarUrl={thread?.firstParticipant?.avatarUrl}
placeholder={thread.firstParticipant.displayName}
type="rounded"
/>
{thread?.lastTwoParticipants?.[0] && (
<StyledAvatar
avatarUrl={getImageAbsoluteURIOrBase64(
thread.lastTwoParticipants[0].avatarUrl,
)}
avatarUrl={thread.lastTwoParticipants[0].avatarUrl}
placeholder={thread.lastTwoParticipants[0].displayName}
type="rounded"
/>
)}
{finalDisplayedName && (
<StyledAvatar
avatarUrl={getImageAbsoluteURIOrBase64(finalAvatarUrl)}
avatarUrl={finalAvatarUrl}
placeholder={finalDisplayedName}
type="rounded"
color={isCountIcon ? GRAY_SCALE.gray50 : undefined}

View File

@ -11,7 +11,6 @@ import {
beautifyExactDateTime,
beautifyPastDateRelativeToNow,
} from '~/utils/date-utils';
import { getImageAbsoluteURIOrBase64 } from '~/utils/image/getImageAbsoluteURIOrBase64';
const StyledAvatarContainer = styled.div`
align-items: center;
@ -154,9 +153,7 @@ export const TimelineActivity = ({
<StyledTimelineItemContainer>
<StyledAvatarContainer>
<Avatar
avatarUrl={getImageAbsoluteURIOrBase64(
activityForTimeline.author?.avatarUrl,
)}
avatarUrl={activityForTimeline.author?.avatarUrl}
placeholder={activityForTimeline.author?.name.firstName ?? ''}
size="sm"
type="rounded"

View File

@ -1,6 +1,6 @@
import styled from '@emotion/styled';
import { getImageAbsoluteURIOrBase64 } from '~/utils/image/getImageAbsoluteURIOrBase64';
import { getImageAbsoluteURI } from '~/utils/image/getImageAbsoluteURI';
type LogoProps = {
workspaceLogo?: string | null;
@ -58,7 +58,7 @@ export const Logo = ({ workspaceLogo }: LogoProps) => {
return (
<StyledContainer>
<StyledMainLogo logo={getImageAbsoluteURIOrBase64(workspaceLogo)} />
<StyledMainLogo logo={getImageAbsoluteURI(workspaceLogo)} />
<StyledTwentyLogoContainer>
<StyledTwentyLogo src="/icons/android/android-launchericon-192-192.png" />
</StyledTwentyLogoContainer>

View File

@ -10,7 +10,6 @@ import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/componen
import { NavigationDrawerSection } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSection';
import { NavigationDrawerSectionTitle } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitle';
import { useNavigationSection } from '@/ui/navigation/navigation-drawer/hooks/useNavigationSection';
import { getImageAbsoluteURIOrBase64 } from '~/utils/image/getImageAbsoluteURIOrBase64';
import { useFavorites } from '../hooks/useFavorites';
@ -81,7 +80,7 @@ export const Favorites = () => {
Icon={() => (
<StyledAvatar
placeholderColorSeed={recordId}
avatarUrl={getImageAbsoluteURIOrBase64(avatarUrl)}
avatarUrl={avatarUrl}
type={avatarType}
placeholder={labelIdentifier}
className="fav-avatar"

View File

@ -11,7 +11,7 @@ import {
} from '@/ui/navigation/navigation-drawer/components/NavigationDrawer';
import { isNavigationDrawerOpenState } from '@/ui/navigation/states/isNavigationDrawerOpenState';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { getImageAbsoluteURIOrBase64 } from '~/utils/image/getImageAbsoluteURIOrBase64';
import { getImageAbsoluteURI } from '~/utils/image/getImageAbsoluteURI';
import { useIsSettingsPage } from '../hooks/useIsSettingsPage';
import { currentMobileNavigationDrawerState } from '../states/currentMobileNavigationDrawerState';
@ -49,7 +49,7 @@ export const AppNavigationDrawer = ({
: {
logo:
(currentWorkspace?.logo &&
getImageAbsoluteURIOrBase64(currentWorkspace.logo)) ??
getImageAbsoluteURI(currentWorkspace.logo)) ??
undefined,
title: currentWorkspace?.displayName ?? undefined,
children: <MainNavigationDrawerItems />,

View File

@ -2,7 +2,7 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { getLogoUrlFromDomainName } from '~/utils';
import { getImageAbsoluteURIOrBase64 } from '~/utils/image/getImageAbsoluteURIOrBase64';
import { getImageAbsoluteURI } from '~/utils/image/getImageAbsoluteURI';
import { isDefined } from '~/utils/isDefined';
import { getImageIdentifierFieldValue } from './getImageIdentifierFieldValue';
@ -21,7 +21,7 @@ export const getAvatarUrl = (
}
if (objectNameSingular === CoreObjectNameSingular.Person) {
return getImageAbsoluteURIOrBase64(record.avatarUrl) ?? '';
return getImageAbsoluteURI(record.avatarUrl) ?? '';
}
const imageIdentifierFieldValue = getImageIdentifierFieldValue(

View File

@ -1,7 +1,5 @@
import { AvatarChip, IconComponent } from 'twenty-ui';
import { getImageAbsoluteURIOrBase64 } from '~/utils/image/getImageAbsoluteURIOrBase64';
import { Filter } from '../types/Filter';
type GenericEntityFilterChipProps = {
@ -17,7 +15,7 @@ export const GenericEntityFilterChip = ({
placeholderColorSeed={filter.value}
name={filter.displayValue}
avatarType="rounded"
avatarUrl={getImageAbsoluteURIOrBase64(filter.displayAvatarUrl) || ''}
avatarUrl={filter.displayAvatarUrl}
LeftIcon={Icon}
/>
);

View File

@ -10,7 +10,6 @@ import { SelectableItem } from '@/ui/layout/selectable-list/components/Selectabl
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
import { MenuItemMultiSelectAvatar } from '@/ui/navigation/menu-item/components/MenuItemMultiSelectAvatar';
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
import { getImageAbsoluteURIOrBase64 } from '~/utils/image/getImageAbsoluteURIOrBase64';
import { isDefined } from '~/utils/isDefined';
export const StyledSelectableItem = styled(SelectableItem)`
@ -65,7 +64,7 @@ export const MultipleObjectRecordSelectItem = ({
selected={selected}
avatar={
<Avatar
avatarUrl={getImageAbsoluteURIOrBase64(recordIdentifier.avatarUrl)}
avatarUrl={recordIdentifier.avatarUrl}
placeholderColorSeed={objectRecordId}
placeholder={recordIdentifier.name}
size="md"

View File

@ -6,7 +6,6 @@ import { EntityForSelect } from '@/object-record/relation-picker/types/EntityFor
import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem';
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
import { MenuItemSelectAvatar } from '@/ui/navigation/menu-item/components/MenuItemSelectAvatar';
import { getImageAbsoluteURIOrBase64 } from '~/utils/image/getImageAbsoluteURIOrBase64';
type SelectableMenuItemSelectProps = {
entity: EntityForSelect;
@ -40,7 +39,7 @@ export const SelectableMenuItemSelect = ({
hovered={isSelectedItemId}
avatar={
<Avatar
avatarUrl={getImageAbsoluteURIOrBase64(entity.avatarUrl)}
avatarUrl={entity.avatarUrl}
placeholderColorSeed={entity.id}
placeholder={entity.name}
size="md"

View File

@ -6,7 +6,6 @@ import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
import { MenuItemMultiSelectAvatar } from '@/ui/navigation/menu-item/components/MenuItemMultiSelectAvatar';
import { getImageAbsoluteURIOrBase64 } from '~/utils/image/getImageAbsoluteURIOrBase64';
export const MultipleRecordSelectDropdown = ({
recordsToSelect,
@ -69,7 +68,7 @@ export const MultipleRecordSelectDropdown = ({
}
avatar={
<Avatar
avatarUrl={getImageAbsoluteURIOrBase64(record.avatarUrl)}
avatarUrl={record.avatarUrl}
placeholderColorSeed={record.id}
placeholder={record.name}
size="md"

View File

@ -6,7 +6,6 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { ImageInput } from '@/ui/input/components/ImageInput';
import { useUploadProfilePictureMutation } from '~/generated/graphql';
import { getImageAbsoluteURIOrBase64 } from '~/utils/image/getImageAbsoluteURIOrBase64';
import { isDefined } from '~/utils/isDefined';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
@ -101,7 +100,7 @@ export const ProfilePictureUploader = () => {
return (
<ImageInput
picture={getImageAbsoluteURIOrBase64(currentWorkspaceMember?.avatarUrl)}
picture={currentWorkspaceMember?.avatarUrl}
onUpload={handleUpload}
onRemove={handleRemove}
onAbort={handleAbort}

View File

@ -6,7 +6,6 @@ import {
useUpdateWorkspaceMutation,
useUploadWorkspaceLogoMutation,
} from '~/generated/graphql';
import { getImageAbsoluteURIOrBase64 } from '~/utils/image/getImageAbsoluteURIOrBase64';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
export const WorkspaceLogoUploader = () => {
@ -57,7 +56,7 @@ export const WorkspaceLogoUploader = () => {
return (
<ImageInput
picture={getImageAbsoluteURIOrBase64(currentWorkspace?.logo)}
picture={currentWorkspace?.logo}
onUpload={onUpload}
onRemove={onRemove}
/>

View File

@ -1,9 +1,10 @@
import React from 'react';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import React, { useMemo } from 'react';
import { IconFileUpload, IconTrash, IconUpload, IconX } from 'twenty-ui';
import { Button } from '@/ui/input/button/components/Button';
import { getImageAbsoluteURI } from '~/utils/image/getImageAbsoluteURI';
import { isDefined } from '~/utils/isDefined';
const StyledContainer = styled.div`
@ -105,16 +106,18 @@ export const ImageInput = ({
hiddenFileInput.current?.click();
};
const pictureURI = useMemo(() => getImageAbsoluteURI(picture), [picture]);
return (
<StyledContainer className={className}>
<StyledPicture
withPicture={!!picture}
withPicture={!!pictureURI}
disabled={disabled}
onClick={onUploadButtonClick}
>
{picture ? (
{pictureURI ? (
<img
src={picture || '/images/default-profile-picture.png'}
src={pictureURI || '/images/default-profile-picture.png'}
alt="profile"
/>
) : (
@ -139,7 +142,7 @@ export const ImageInput = ({
onClick={onAbort}
variant="secondary"
title="Abort"
disabled={!picture || disabled}
disabled={!pictureURI || disabled}
fullWidth
/>
) : (
@ -157,7 +160,7 @@ export const ImageInput = ({
onClick={onRemove}
variant="secondary"
title="Remove"
disabled={!picture || disabled}
disabled={!pictureURI || disabled}
fullWidth
/>
</StyledButtonContainer>

View File

@ -14,7 +14,7 @@ import { DEFAULT_WORKSPACE_LOGO } from '@/ui/navigation/navigation-drawer/consta
import { MULTI_WORKSPACE_DROPDOWN_ID } from '@/ui/navigation/navigation-drawer/constants/MulitWorkspaceDropdownId';
import { useWorkspaceSwitching } from '@/ui/navigation/navigation-drawer/hooks/useWorkspaceSwitching';
import { NavigationDrawerHotKeyScope } from '@/ui/navigation/navigation-drawer/types/NavigationDrawerHotKeyScope';
import { getImageAbsoluteURIOrBase64 } from '~/utils/image/getImageAbsoluteURIOrBase64';
import { getImageAbsoluteURI } from '~/utils/image/getImageAbsoluteURI';
const StyledLogo = styled.div<{ logo: string }>`
background: url(${({ logo }) => logo});
@ -88,7 +88,7 @@ export const MultiWorkspaceDropdownButton = ({
<StyledContainer>
<StyledLogo
logo={
getImageAbsoluteURIOrBase64(
getImageAbsoluteURI(
currentWorkspace?.logo === null
? DEFAULT_WORKSPACE_LOGO
: currentWorkspace?.logo,
@ -111,7 +111,7 @@ export const MultiWorkspaceDropdownButton = ({
avatar={
<StyledLogo
logo={
getImageAbsoluteURIOrBase64(
getImageAbsoluteURI(
workspace.logo === null
? DEFAULT_WORKSPACE_LOGO
: workspace.logo,

View File

@ -1,2 +1,2 @@
export const DEFAULT_WORKSPACE_LOGO =
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAA7EAAAOxAGVKw4bAAACb0lEQVR4nO2VO4taQRTHr3AblbjxEVlwCwVhg7BoqqCIjy/gAyyFWNlYBOxsfH0KuxgQGwXRUkGuL2S7i1barGAgiwbdW93SnGOc4BonPiKahf3DwXFmuP/fPM4ZlvmlTxAhCBdzHnEQWYiv7Mr4C3NeuVYhQYDPzOUUQgDLBQGcLHNhvQK8DACPx8PTxiqVyvISG43GbyaT6Qfpn06n0m63e/tPAPF4vJ1MJu8kEsnWTCkWi1yr1RKGw+GDRqPBOTfr44vFQvD7/Q/lcpmaaVQAr9fLp1IpO22c47hGOBz+MB6PH+Vy+VYDAL8qlUoGtVotzOfzq4MAgsHgE/6KojiQyWR/bKVSqbSszHFM8Pl8z1YK48JsNltCOBwOnrYLO+8AAIjb+nHbycoTiUQfDJ7tFq4YAHiVSmXBxcD41u8flQU8z7fhzO0r83atVns3Go3u9Xr9x0O/RQXo9/tsIBBg6vX606a52Wz+bZ7P5/WwG29gxSJzhKgA6XTaDoFNF+krFAocmC//4yWEcSf2wTm7mCO19xFgSsKOLI16vV7b7XY7mRNoLwA0JymJ5uQIzgIAuX5PzDElT2m+E8BqtQ4ymcx7Yq7T6a6ZE4sKgOadTucaCwkxp1UzlEKh0GDxIXOwDWHAdi6Xe3swQDQa/Q7mywoolUpvsaptymazDWKxmBHTlWXZm405BFZoNpuGgwEmk4mE2SGtVivii4f1AO7J3ZopkQCQj7Ar1FeRChCJRJzVapX6DKNIfSc1Ax+wtQWQ55h6bH8FWDfYV4fO3wlwDr0C/BcADYiTPCxHqIEA2QsCZAkAKnRGkMbKN/sTX5YHPQ1e7SkAAAAASUVORK5CYII=';
'https://twentyhq.github.io/placeholder-images/workspaces/twenty-logo.png';

View File

@ -1,7 +1,5 @@
import { AvatarChip } from 'twenty-ui';
import { getImageAbsoluteURIOrBase64 } from '~/utils/image/getImageAbsoluteURIOrBase64';
export type UserChipProps = {
id: string;
name: string;
@ -13,6 +11,6 @@ export const UserChip = ({ id, name, avatarUrl }: UserChipProps) => (
placeholderColorSeed={id}
name={name}
avatarType="rounded"
avatarUrl={getImageAbsoluteURIOrBase64(avatarUrl) || ''}
avatarUrl={avatarUrl}
/>
);

View File

@ -2,7 +2,6 @@ import styled from '@emotion/styled';
import { Avatar, OverflowingTextWithTooltip } from 'twenty-ui';
import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
import { getImageAbsoluteURIOrBase64 } from '~/utils/image/getImageAbsoluteURIOrBase64';
const StyledContainer = styled.div`
background: ${({ theme }) => theme.background.secondary};
@ -39,7 +38,7 @@ export const WorkspaceMemberCard = ({
}: WorkspaceMemberCardProps) => (
<StyledContainer>
<Avatar
avatarUrl={getImageAbsoluteURIOrBase64(workspaceMember.avatarUrl)}
avatarUrl={workspaceMember.avatarUrl}
placeholderColorSeed={workspaceMember.id}
placeholder={workspaceMember.name.firstName || ''}
type="squared"

View File

@ -0,0 +1,21 @@
import { getImageAbsoluteURI } from '../getImageAbsoluteURI';
describe('getImageAbsoluteURI', () => {
it('should return null if imageUrl is null', () => {
const imageUrl = null;
const result = getImageAbsoluteURI(imageUrl);
expect(result).toBeNull();
});
it('should return absolute url if the imageUrl is an absolute url', () => {
const imageUrl = 'https://XXX';
const result = getImageAbsoluteURI(imageUrl);
expect(result).toBe(imageUrl);
});
it('should return fully formed url if imageUrl is a relative url', () => {
const imageUrl = 'XXX';
const result = getImageAbsoluteURI(imageUrl);
expect(result).toBe('http://localhost:3000/files/XXX');
});
});

View File

@ -1,27 +0,0 @@
import { getImageAbsoluteURIOrBase64 } from '../getImageAbsoluteURIOrBase64';
describe('getImageAbsoluteURIOrBase64', () => {
it('should return null if imageUrl is null', () => {
const imageUrl = null;
const result = getImageAbsoluteURIOrBase64(imageUrl);
expect(result).toBeNull();
});
it('should return base64 encoded string if prefixed with data', () => {
const imageUrl = 'data:XXX';
const result = getImageAbsoluteURIOrBase64(imageUrl);
expect(result).toBe(imageUrl);
});
it('should return absolute url if the imageUrl is an absolute url', () => {
const imageUrl = 'https://XXX';
const result = getImageAbsoluteURIOrBase64(imageUrl);
expect(result).toBe(imageUrl);
});
it('should return fully formed url if imageUrl is a relative url', () => {
const imageUrl = 'XXX';
const result = getImageAbsoluteURIOrBase64(imageUrl);
expect(result).toBe('http://localhost:3000/files/XXX');
});
});

View File

@ -1,11 +1,11 @@
import { REACT_APP_SERVER_BASE_URL } from '~/config';
export const getImageAbsoluteURIOrBase64 = (imageUrl?: string | null) => {
export const getImageAbsoluteURI = (imageUrl?: string | null) => {
if (!imageUrl) {
return null;
}
if (imageUrl?.startsWith('data:') || imageUrl?.startsWith('https:')) {
if (imageUrl?.startsWith('https:')) {
return imageUrl;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
import { styled } from '@linaria/react';
import { isNonEmptyString, isUndefined } from '@sniptt/guards';
import { useContext } from 'react';
import { useContext, useMemo } from 'react';
import { useRecoilState } from 'recoil';
import { invalidAvatarUrlsState } from '@ui/display/avatar/components/states/isInvalidAvatarUrlState';
@ -8,7 +8,7 @@ import { AVATAR_PROPERTIES_BY_SIZE } from '@ui/display/avatar/constants/AvatarPr
import { AvatarSize } from '@ui/display/avatar/types/AvatarSize';
import { AvatarType } from '@ui/display/avatar/types/AvatarType';
import { ThemeContext } from '@ui/theme';
import { Nullable, stringToHslColor } from '@ui/utilities';
import { Nullable, getImageAbsoluteURI, stringToHslColor } from '@ui/utilities';
const StyledAvatar = styled.div<{
size: AvatarSize;
@ -73,15 +73,21 @@ export const Avatar = ({
invalidAvatarUrlsState,
);
const noAvatarUrl = !isNonEmptyString(avatarUrl);
const avatarImageURI = useMemo(
() => getImageAbsoluteURI(avatarUrl),
[avatarUrl],
);
const noAvatarUrl = !isNonEmptyString(avatarImageURI);
const placeholderChar = placeholder?.[0]?.toLocaleUpperCase();
const showPlaceholder = noAvatarUrl || invalidAvatarUrls.includes(avatarUrl);
const showPlaceholder =
noAvatarUrl || invalidAvatarUrls.includes(avatarImageURI);
const handleImageError = () => {
if (isNonEmptyString(avatarUrl)) {
setInvalidAvatarUrls((prev) => [...prev, avatarUrl]);
if (isNonEmptyString(avatarImageURI)) {
setInvalidAvatarUrls((prev) => [...prev, avatarImageURI]);
}
};
@ -105,7 +111,7 @@ export const Avatar = ({
{showPlaceholder ? (
placeholderChar
) : (
<StyledImage src={avatarUrl} onError={handleImageError} alt="" />
<StyledImage src={avatarImageURI} onError={handleImageError} alt="" />
)}
</StyledAvatar>
);

View File

@ -0,0 +1,30 @@
declare global {
interface Window {
_env_?: Record<string, string>;
__APOLLO_CLIENT__?: any;
}
}
const getDefaultUrl = () => {
if (
window.location.hostname === 'localhost' ||
window.location.hostname === '127.0.0.1'
) {
// In development environment front and backend usually run on separate ports
// we set the default value to localhost:3000.
// It dev context, we use env vars to overwrite it
return 'http://localhost:3000';
} else {
// Outside of localhost we assume that they run on the same port
// because the backend will serve the frontend
// It prod context, we use env-config.js + window var to ovewrite it
return `${window.location.protocol}//${window.location.hostname}${
window.location.port ? `:${window.location.port}` : ''
}`;
}
};
export const REACT_APP_SERVER_BASE_URL =
window._env_?.REACT_APP_SERVER_BASE_URL ||
process.env.REACT_APP_SERVER_BASE_URL ||
getDefaultUrl();

View File

@ -0,0 +1,15 @@
import { REACT_APP_SERVER_BASE_URL } from '@ui/utilities/config';
export const getImageAbsoluteURI = (imageUrl?: string | null) => {
if (!imageUrl) {
return null;
}
if (imageUrl?.startsWith('https:')) {
return imageUrl;
}
const serverFilesUrl = REACT_APP_SERVER_BASE_URL;
return `${serverFilesUrl}/files/${imageUrl}`;
};

View File

@ -1,4 +1,5 @@
export * from './color/utils/stringToHslColor';
export * from './image/getImageAbsoluteURI';
export * from './isDefined';
export * from './state/utils/createState';
export * from './types/Nullable';