Fix application icons (#20142)
fixes application chip (icon Name) in all setting tables ## After <img width="1200" height="896" alt="image" src="https://github.com/user-attachments/assets/bd377f47-1d52-4142-b904-f2ce90c1db78" /> <img width="1200" height="917" alt="image" src="https://github.com/user-attachments/assets/f49cc742-f11e-47e3-86ed-34beffe493c7" /> <img width="1234" height="878" alt="image" src="https://github.com/user-attachments/assets/2ab459de-5f9d-4d39-9490-eec4ed9ee432" /> <img width="1239" height="845" alt="image" src="https://github.com/user-attachments/assets/3c1bf258-285a-47b9-a60d-05ba1564334d" /> <img width="1183" height="907" alt="image" src="https://github.com/user-attachments/assets/715b2470-2d88-48e3-88ac-d3daf3451717" /> <img width="1300" height="912" alt="image" src="https://github.com/user-attachments/assets/d7c829fa-bf1d-4f19-82de-a8bf29e22bfa" />
This commit is contained in:
@@ -1,10 +1,19 @@
|
||||
import { useApplicationChipData } from '@/applications/hooks/useApplicationChipData';
|
||||
import { styled } from '@linaria/react';
|
||||
import { Avatar } from 'twenty-ui/display';
|
||||
import {
|
||||
Avatar,
|
||||
type AvatarSize,
|
||||
OverflowingTextWithTooltip,
|
||||
} from 'twenty-ui/display';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
type AppChipProps = {
|
||||
applicationId: string;
|
||||
size?: AvatarSize;
|
||||
applicationId?: string | null;
|
||||
fallbackApplicationData?: {
|
||||
logo?: string | null;
|
||||
name?: string | null;
|
||||
};
|
||||
className?: string;
|
||||
};
|
||||
|
||||
@@ -20,29 +29,30 @@ const StyledContainer = styled.div`
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
const StyledLabel = styled.span`
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
`;
|
||||
|
||||
export const AppChip = ({ applicationId, className }: AppChipProps) => {
|
||||
const { applicationChipData } = useApplicationChipData({ applicationId });
|
||||
export const AppChip = ({
|
||||
applicationId,
|
||||
size = 'sm',
|
||||
fallbackApplicationData,
|
||||
className,
|
||||
}: AppChipProps) => {
|
||||
const { applicationChipData } = useApplicationChipData({
|
||||
applicationId,
|
||||
fallbackApplicationData,
|
||||
});
|
||||
|
||||
return (
|
||||
<StyledContainer className={className}>
|
||||
<Avatar
|
||||
type="app"
|
||||
size="sm"
|
||||
size={size}
|
||||
avatarUrl={applicationChipData.logo}
|
||||
placeholder={applicationChipData.name}
|
||||
placeholderColorSeed={applicationChipData.seed}
|
||||
color={applicationChipData.colors?.color}
|
||||
backgroundColor={applicationChipData.colors?.backgroundColor}
|
||||
borderColor={applicationChipData.colors?.borderColor}
|
||||
/>
|
||||
<StyledLabel title={applicationChipData.name}>
|
||||
{applicationChipData.name}
|
||||
</StyledLabel>
|
||||
<OverflowingTextWithTooltip text={applicationChipData.name} />
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,41 +1,29 @@
|
||||
import { useApplicationAvatarColors } from '@/applications/hooks/useApplicationAvatarColors';
|
||||
import { Avatar, OverflowingTextWithTooltip } from 'twenty-ui/display';
|
||||
|
||||
type ApplicationDisplayData = {
|
||||
id?: string | null;
|
||||
name?: string | null;
|
||||
universalIdentifier?: string | null;
|
||||
logoUrl?: string | null;
|
||||
applicationRegistration?: {
|
||||
logoUrl?: string | null;
|
||||
} | null;
|
||||
};
|
||||
import { type ApplicationDisplayData } from '@/applications/types/applicationDisplayData.type';
|
||||
import { AppChip } from '@/applications/components/AppChip';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { styled } from '@linaria/react';
|
||||
|
||||
type ApplicationDisplayProps = {
|
||||
application: ApplicationDisplayData;
|
||||
application?: ApplicationDisplayData;
|
||||
};
|
||||
|
||||
const StyledAppChip = styled(AppChip)`
|
||||
color: ${themeCssVariables.font.color.primary};
|
||||
font-size: ${themeCssVariables.font.size.md};
|
||||
gap: ${themeCssVariables.spacing[2]};
|
||||
`;
|
||||
|
||||
export const ApplicationDisplay = ({
|
||||
application,
|
||||
}: ApplicationDisplayProps) => {
|
||||
const colors = useApplicationAvatarColors(application);
|
||||
const name = application.name ?? '';
|
||||
const logoUrl =
|
||||
application.logoUrl ?? application.applicationRegistration?.logoUrl;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Avatar
|
||||
type="app"
|
||||
size="md"
|
||||
avatarUrl={logoUrl ?? undefined}
|
||||
placeholder={name}
|
||||
placeholderColorSeed={application.universalIdentifier ?? name}
|
||||
color={colors?.color}
|
||||
backgroundColor={colors?.backgroundColor}
|
||||
borderColor={colors?.borderColor}
|
||||
/>
|
||||
<OverflowingTextWithTooltip text={name} />
|
||||
</>
|
||||
<StyledAppChip
|
||||
size="md"
|
||||
applicationId={application?.id}
|
||||
fallbackApplicationData={{
|
||||
logo: application?.logo,
|
||||
name: application?.name,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
+1
@@ -11,6 +11,7 @@ export const APPLICATION_FRAGMENT = gql`
|
||||
id
|
||||
name
|
||||
description
|
||||
logo
|
||||
version
|
||||
universalIdentifier
|
||||
applicationRegistrationId
|
||||
|
||||
@@ -6,6 +6,7 @@ export const FIND_MANY_APPLICATIONS = gql`
|
||||
id
|
||||
name
|
||||
description
|
||||
logo
|
||||
version
|
||||
universalIdentifier
|
||||
applicationRegistrationId
|
||||
|
||||
@@ -1,24 +1,30 @@
|
||||
import { CurrentApplicationContext } from '@/applications/contexts/CurrentApplicationContext';
|
||||
import {
|
||||
useApplicationAvatarColors,
|
||||
type ApplicationAvatarColors,
|
||||
useApplicationAvatarColors,
|
||||
} from '@/applications/hooks/useApplicationAvatarColors';
|
||||
import { isTwentyStandardApplication } from '@/applications/utils/isTwentyStandardApplication';
|
||||
import { isWorkspaceCustomApplication } from '@/applications/utils/isWorkspaceCustomApplication';
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useContext } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { buildApplicationLogoUrl } from '@/applications/utils/buildApplicationLogoUrl';
|
||||
import CustomLogo from '~/pages/settings/applications/assets/custom-illustrations/custom-logo.webp';
|
||||
import StandardLogo from '~/pages/settings/applications/assets/standard-illustrations/standard-logo.webp';
|
||||
|
||||
type UseApplicationChipDataArgs = {
|
||||
applicationId: string;
|
||||
applicationId?: string | null;
|
||||
fallbackApplicationData?: {
|
||||
logo?: string | null;
|
||||
name?: string | null;
|
||||
};
|
||||
};
|
||||
|
||||
type ApplicationChipData = {
|
||||
name: string;
|
||||
seed: string;
|
||||
colors?: ApplicationAvatarColors;
|
||||
logo?: string;
|
||||
};
|
||||
|
||||
type UseApplicationChipDataReturnType = {
|
||||
@@ -27,8 +33,8 @@ type UseApplicationChipDataReturnType = {
|
||||
|
||||
export const useApplicationChipData = ({
|
||||
applicationId,
|
||||
fallbackApplicationData,
|
||||
}: UseApplicationChipDataArgs): UseApplicationChipDataReturnType => {
|
||||
const currentApplicationId = useContext(CurrentApplicationContext);
|
||||
const currentWorkspace = useAtomStateValue(currentWorkspaceState);
|
||||
|
||||
const application = currentWorkspace?.installedApplications.find(
|
||||
@@ -40,28 +46,39 @@ export const useApplicationChipData = ({
|
||||
if (!isDefined(application)) {
|
||||
return {
|
||||
applicationChipData: {
|
||||
name: '',
|
||||
seed: applicationId,
|
||||
name: fallbackApplicationData?.name ?? '',
|
||||
logo: fallbackApplicationData?.logo ?? '',
|
||||
seed: fallbackApplicationData?.name ?? '',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const isCurrent =
|
||||
isDefined(currentApplicationId) && currentApplicationId === applicationId;
|
||||
const isStandard = isTwentyStandardApplication(application);
|
||||
|
||||
const displayName = isCurrent
|
||||
? t`This app`
|
||||
: isTwentyStandardApplication(application)
|
||||
? t`Standard`
|
||||
: isWorkspaceCustomApplication(application, currentWorkspace)
|
||||
? t`Custom`
|
||||
: application.name;
|
||||
const isCustom = isWorkspaceCustomApplication(application, currentWorkspace);
|
||||
|
||||
const displayName = isStandard
|
||||
? t`Standard`
|
||||
: isCustom
|
||||
? t`Custom`
|
||||
: application.name;
|
||||
|
||||
const logo = isStandard
|
||||
? new URL(StandardLogo, window.location.href).toString()
|
||||
: isCustom
|
||||
? new URL(CustomLogo, window.location.href).toString()
|
||||
: buildApplicationLogoUrl({
|
||||
applicationId: application.id,
|
||||
logo: application.logo,
|
||||
workspaceId: currentWorkspace?.id,
|
||||
});
|
||||
|
||||
return {
|
||||
applicationChipData: {
|
||||
name: displayName,
|
||||
seed: application.universalIdentifier ?? application.name,
|
||||
seed: application.name,
|
||||
colors,
|
||||
logo,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
export type ApplicationDisplayData = {
|
||||
id?: string | null;
|
||||
name?: string | null;
|
||||
universalIdentifier?: string | null;
|
||||
logo?: string | null;
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const buildApplicationLogoUrl = ({
|
||||
workspaceId,
|
||||
applicationId,
|
||||
logo,
|
||||
}: {
|
||||
workspaceId?: string | null;
|
||||
applicationId?: string | null;
|
||||
logo?: string | null;
|
||||
}): string | undefined => {
|
||||
if (
|
||||
!isDefined(logo) ||
|
||||
logo.startsWith('http://') ||
|
||||
logo.startsWith('https://')
|
||||
) {
|
||||
return logo ?? undefined;
|
||||
}
|
||||
|
||||
if (!isDefined(workspaceId) || !isDefined(applicationId)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return `${REACT_APP_SERVER_BASE_URL}/public-assets/${workspaceId}/${applicationId}/${logo}`;
|
||||
};
|
||||
Reference in New Issue
Block a user