mirror of
https://github.com/twentyhq/twenty.git
synced 2024-12-23 12:02:10 +03:00
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:
parent
00fea17920
commit
fed12ddfcd
@ -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" />
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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}`
|
||||
|
@ -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}
|
||||
|
@ -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"
|
||||
|
@ -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}
|
||||
|
@ -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"
|
||||
|
@ -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>
|
||||
|
@ -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"
|
||||
|
@ -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 />,
|
||||
|
@ -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(
|
||||
|
@ -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}
|
||||
/>
|
||||
);
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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}
|
||||
|
@ -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}
|
||||
/>
|
||||
|
@ -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>
|
||||
|
@ -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,
|
||||
|
@ -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';
|
||||
|
@ -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}
|
||||
/>
|
||||
);
|
||||
|
@ -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"
|
||||
|
@ -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');
|
||||
});
|
||||
});
|
@ -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');
|
||||
});
|
||||
});
|
@ -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
@ -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>
|
||||
);
|
||||
|
30
packages/twenty-ui/src/utilities/config/index.ts
Normal file
30
packages/twenty-ui/src/utilities/config/index.ts
Normal 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();
|
@ -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}`;
|
||||
};
|
@ -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';
|
||||
|
Loading…
Reference in New Issue
Block a user