File previewer (#10260)
Add a file previewer for pdf, image, doc, xls <img width="991" alt="Screenshot 2025-02-17 at 15 03 10" src="https://github.com/user-attachments/assets/7516c13d-d6cb-4a10-b10f-b422268d223b" />
This commit is contained in:
@ -32,6 +32,7 @@
|
||||
"dependencies": {
|
||||
"@blocknote/xl-docx-exporter": "^0.22.0",
|
||||
"@blocknote/xl-pdf-exporter": "^0.22.0",
|
||||
"@cyntler/react-doc-viewer": "^1.17.0",
|
||||
"@lingui/detect-locale": "^5.2.0",
|
||||
"@nivo/calendar": "^0.87.0",
|
||||
"@nivo/core": "^0.87.0",
|
||||
|
||||
@ -249,6 +249,7 @@ export type ClientConfig = {
|
||||
debugMode: Scalars['Boolean'];
|
||||
defaultSubdomain?: Maybe<Scalars['String']>;
|
||||
frontDomain: Scalars['String'];
|
||||
isAttachmentPreviewEnabled: Scalars['Boolean'];
|
||||
isEmailVerificationRequired: Scalars['Boolean'];
|
||||
isGoogleCalendarEnabled: Scalars['Boolean'];
|
||||
isGoogleMessagingEnabled: Scalars['Boolean'];
|
||||
@ -2221,7 +2222,7 @@ export type UpdateBillingSubscriptionMutation = { __typename?: 'Mutation', updat
|
||||
export type GetClientConfigQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type GetClientConfigQuery = { __typename?: 'Query', clientConfig: { __typename?: 'ClientConfig', signInPrefilled: boolean, isMultiWorkspaceEnabled: boolean, isEmailVerificationRequired: boolean, defaultSubdomain?: string | null, frontDomain: string, debugMode: boolean, analyticsEnabled: boolean, chromeExtensionId?: string | null, canManageFeatureFlags: boolean, isMicrosoftMessagingEnabled: boolean, isMicrosoftCalendarEnabled: boolean, isGoogleMessagingEnabled: boolean, isGoogleCalendarEnabled: boolean, billing: { __typename?: 'Billing', isBillingEnabled: boolean, billingUrl?: string | null, trialPeriods: Array<{ __typename?: 'BillingTrialPeriodDTO', duration: number, isCreditCardRequired: boolean }> }, authProviders: { __typename?: 'AuthProviders', google: boolean, password: boolean, microsoft: boolean, sso: Array<{ __typename?: 'SSOIdentityProvider', id: string, name: string, type: IdentityProviderType, status: SsoIdentityProviderStatus, issuer: string }> }, support: { __typename?: 'Support', supportDriver: string, supportFrontChatId?: string | null }, sentry: { __typename?: 'Sentry', dsn?: string | null, environment?: string | null, release?: string | null }, captcha: { __typename?: 'Captcha', provider?: CaptchaDriverType | null, siteKey?: string | null }, api: { __typename?: 'ApiConfig', mutationMaximumAffectedRecords: number }, publicFeatureFlags: Array<{ __typename?: 'PublicFeatureFlag', key: FeatureFlagKey, metadata: { __typename?: 'PublicFeatureFlagMetadata', label: string, description: string, imagePath: string } }> } };
|
||||
export type GetClientConfigQuery = { __typename?: 'Query', clientConfig: { __typename?: 'ClientConfig', signInPrefilled: boolean, isMultiWorkspaceEnabled: boolean, isEmailVerificationRequired: boolean, defaultSubdomain?: string | null, frontDomain: string, debugMode: boolean, analyticsEnabled: boolean, isAttachmentPreviewEnabled: boolean, chromeExtensionId?: string | null, canManageFeatureFlags: boolean, isMicrosoftMessagingEnabled: boolean, isMicrosoftCalendarEnabled: boolean, isGoogleMessagingEnabled: boolean, isGoogleCalendarEnabled: boolean, billing: { __typename?: 'Billing', isBillingEnabled: boolean, billingUrl?: string | null, trialPeriods: Array<{ __typename?: 'BillingTrialPeriodDTO', duration: number, isCreditCardRequired: boolean }> }, authProviders: { __typename?: 'AuthProviders', google: boolean, password: boolean, microsoft: boolean, sso: Array<{ __typename?: 'SSOIdentityProvider', id: string, name: string, type: IdentityProviderType, status: SsoIdentityProviderStatus, issuer: string }> }, support: { __typename?: 'Support', supportDriver: string, supportFrontChatId?: string | null }, sentry: { __typename?: 'Sentry', dsn?: string | null, environment?: string | null, release?: string | null }, captcha: { __typename?: 'Captcha', provider?: CaptchaDriverType | null, siteKey?: string | null }, api: { __typename?: 'ApiConfig', mutationMaximumAffectedRecords: number }, publicFeatureFlags: Array<{ __typename?: 'PublicFeatureFlag', key: FeatureFlagKey, metadata: { __typename?: 'PublicFeatureFlagMetadata', label: string, description: string, imagePath: string } }> } };
|
||||
|
||||
export type SkipSyncEmailOnboardingStepMutationVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
@ -3730,6 +3731,7 @@ export const GetClientConfigDocument = gql`
|
||||
frontDomain
|
||||
debugMode
|
||||
analyticsEnabled
|
||||
isAttachmentPreviewEnabled
|
||||
support {
|
||||
supportDriver
|
||||
supportFrontChatId
|
||||
|
||||
@ -1,14 +1,25 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { ReactElement, useState } from 'react';
|
||||
import { lazy, ReactElement, Suspense, useState } from 'react';
|
||||
import { IconButton, IconDownload, IconX } from 'twenty-ui';
|
||||
|
||||
import { DropZone } from '@/activities/files/components/DropZone';
|
||||
import { useUploadAttachmentFile } from '@/activities/files/hooks/useUploadAttachmentFile';
|
||||
import { Attachment } from '@/activities/files/types/Attachment';
|
||||
import { downloadFile } from '@/activities/files/utils/downloadFile';
|
||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||
import { isAttachmentPreviewEnabledState } from '@/client-config/states/isAttachmentPreviewEnabledState';
|
||||
import { Modal } from '@/ui/layout/modal/components/Modal';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { ActivityList } from '@/activities/components/ActivityList';
|
||||
import { AttachmentRow } from './AttachmentRow';
|
||||
|
||||
const DocumentViewer = lazy(() =>
|
||||
import('@/activities/files/components/DocumentViewer').then((module) => ({
|
||||
default: module.DocumentViewer,
|
||||
})),
|
||||
);
|
||||
|
||||
type AttachmentListProps = {
|
||||
targetableObject: ActivityTargetableObject;
|
||||
title: string;
|
||||
@ -23,9 +34,7 @@ const StyledContainer = styled.div`
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: ${({ theme }) => theme.spacing(2, 6, 6)};
|
||||
|
||||
width: calc(100% - ${({ theme }) => theme.spacing(12)});
|
||||
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
@ -51,10 +60,50 @@ const StyledCount = styled.span`
|
||||
const StyledDropZoneContainer = styled.div`
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
overflow: auto;
|
||||
`;
|
||||
|
||||
const StyledLoadingContainer = styled.div`
|
||||
align-items: center;
|
||||
background: ${({ theme }) => theme.background.primary};
|
||||
display: flex;
|
||||
height: 80vh;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledLoadingText = styled.div`
|
||||
color: ${({ theme }) => theme.font.color.secondary};
|
||||
font-size: ${({ theme }) => theme.font.size.lg};
|
||||
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||
`;
|
||||
|
||||
const StyledHeader = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledModalTitle = styled.span`
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
`;
|
||||
|
||||
const StyledModalHeader = styled(Modal.Header)`
|
||||
padding: 0;
|
||||
`;
|
||||
|
||||
const StyledModalContent = styled(Modal.Content)`
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
`;
|
||||
|
||||
const StyledButtonContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
export const AttachmentList = ({
|
||||
targetableObject,
|
||||
title,
|
||||
@ -63,11 +112,30 @@ export const AttachmentList = ({
|
||||
}: AttachmentListProps) => {
|
||||
const { uploadAttachmentFile } = useUploadAttachmentFile();
|
||||
const [isDraggingFile, setIsDraggingFile] = useState(false);
|
||||
const [previewedAttachment, setPreviewedAttachment] =
|
||||
useState<Attachment | null>(null);
|
||||
const isAttachmentPreviewEnabled = useRecoilValue(
|
||||
isAttachmentPreviewEnabledState,
|
||||
);
|
||||
|
||||
const onUploadFile = async (file: File) => {
|
||||
await uploadAttachmentFile(file, targetableObject);
|
||||
};
|
||||
|
||||
const handlePreview = (attachment: Attachment) => {
|
||||
if (!isAttachmentPreviewEnabled) return;
|
||||
setPreviewedAttachment(attachment);
|
||||
};
|
||||
|
||||
const handleClosePreview = () => {
|
||||
setPreviewedAttachment(null);
|
||||
};
|
||||
|
||||
const handleDownload = () => {
|
||||
if (!previewedAttachment) return;
|
||||
downloadFile(previewedAttachment.fullPath, previewedAttachment.name);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{attachments && attachments.length > 0 && (
|
||||
@ -87,13 +155,56 @@ export const AttachmentList = ({
|
||||
) : (
|
||||
<ActivityList>
|
||||
{attachments.map((attachment) => (
|
||||
<AttachmentRow key={attachment.id} attachment={attachment} />
|
||||
<AttachmentRow
|
||||
key={attachment.id}
|
||||
attachment={attachment}
|
||||
onPreview={
|
||||
isAttachmentPreviewEnabled ? handlePreview : undefined
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</ActivityList>
|
||||
)}
|
||||
</StyledDropZoneContainer>
|
||||
</StyledContainer>
|
||||
)}
|
||||
{previewedAttachment && isAttachmentPreviewEnabled && (
|
||||
<Modal size="large" isClosable onClose={handleClosePreview}>
|
||||
<StyledModalHeader>
|
||||
<StyledHeader>
|
||||
<StyledModalTitle>{previewedAttachment.name}</StyledModalTitle>
|
||||
<StyledButtonContainer>
|
||||
<IconButton
|
||||
Icon={IconDownload}
|
||||
onClick={handleDownload}
|
||||
size="small"
|
||||
/>
|
||||
<IconButton
|
||||
Icon={IconX}
|
||||
onClick={handleClosePreview}
|
||||
size="small"
|
||||
/>
|
||||
</StyledButtonContainer>
|
||||
</StyledHeader>
|
||||
</StyledModalHeader>
|
||||
<StyledModalContent>
|
||||
<Suspense
|
||||
fallback={
|
||||
<StyledLoadingContainer>
|
||||
<StyledLoadingText>
|
||||
Loading document viewer...
|
||||
</StyledLoadingText>
|
||||
</StyledLoadingContainer>
|
||||
}
|
||||
>
|
||||
<DocumentViewer
|
||||
documentName={previewedAttachment.name}
|
||||
documentUrl={previewedAttachment.fullPath}
|
||||
/>
|
||||
</Suspense>
|
||||
</StyledModalContent>
|
||||
</Modal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { ActivityRow } from '@/activities/components/ActivityRow';
|
||||
import { AttachmentDropdown } from '@/activities/files/components/AttachmentDropdown';
|
||||
import { AttachmentIcon } from '@/activities/files/components/AttachmentIcon';
|
||||
import { PREVIEWABLE_EXTENSIONS } from '@/activities/files/components/DocumentViewer';
|
||||
import { Attachment } from '@/activities/files/types/Attachment';
|
||||
import { downloadFile } from '@/activities/files/utils/downloadFile';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
@ -14,6 +15,7 @@ import { TextInput } from '@/ui/input/components/TextInput';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
import { IconCalendar, OverflowingTextWithTooltip } from 'twenty-ui';
|
||||
|
||||
import { formatToHumanReadableDate } from '~/utils/date-utils';
|
||||
@ -43,10 +45,17 @@ const StyledCalendarIconContainer = styled.div`
|
||||
|
||||
const StyledLink = styled.a`
|
||||
align-items: center;
|
||||
appearance: none;
|
||||
background: none;
|
||||
border: none;
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
padding: 0;
|
||||
text-align: left;
|
||||
text-decoration: none;
|
||||
|
||||
width: 100%;
|
||||
|
||||
:hover {
|
||||
@ -59,13 +68,25 @@ const StyledLinkContainer = styled.div`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const AttachmentRow = ({ attachment }: { attachment: Attachment }) => {
|
||||
type AttachmentRowProps = {
|
||||
attachment: Attachment;
|
||||
onPreview?: (attachment: Attachment) => void;
|
||||
};
|
||||
|
||||
export const AttachmentRow = ({
|
||||
attachment,
|
||||
onPreview,
|
||||
}: AttachmentRowProps) => {
|
||||
const theme = useTheme();
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
|
||||
const { name: originalFileName, extension: attachmentFileExtension } =
|
||||
getFileNameAndExtension(attachment.name);
|
||||
|
||||
const fileExtension =
|
||||
attachmentFileExtension?.toLowerCase().replace('.', '') ?? '';
|
||||
const isPreviewable = PREVIEWABLE_EXTENSIONS.includes(fileExtension);
|
||||
|
||||
const [attachmentFileName, setAttachmentFileName] =
|
||||
useState(originalFileName);
|
||||
|
||||
@ -122,6 +143,19 @@ export const AttachmentRow = ({ attachment }: { attachment: Attachment }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const handleOpenDocument = (e: React.MouseEvent) => {
|
||||
// Cmd/Ctrl+click opens new tab, right click opens context menu
|
||||
if (e.metaKey || e.ctrlKey || e.button === 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only prevent default and use preview if onPreview is provided
|
||||
if (isDefined(onPreview)) {
|
||||
e.preventDefault();
|
||||
onPreview(attachment);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<FieldContext.Provider value={fieldContext as GenericFieldContextType}>
|
||||
<ActivityRow disabled>
|
||||
@ -137,13 +171,22 @@ export const AttachmentRow = ({ attachment }: { attachment: Attachment }) => {
|
||||
/>
|
||||
) : (
|
||||
<StyledLinkContainer>
|
||||
<StyledLink
|
||||
href={attachment.fullPath}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<OverflowingTextWithTooltip text={attachment.name} />
|
||||
</StyledLink>
|
||||
{isPreviewable ? (
|
||||
<StyledLink
|
||||
onClick={handleOpenDocument}
|
||||
href={attachment.fullPath}
|
||||
>
|
||||
<OverflowingTextWithTooltip text={attachment.name} />
|
||||
</StyledLink>
|
||||
) : (
|
||||
<StyledLink
|
||||
href={attachment.fullPath}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<OverflowingTextWithTooltip text={attachment.name} />
|
||||
</StyledLink>
|
||||
)}
|
||||
</StyledLinkContainer>
|
||||
)}
|
||||
</StyledLeftContent>
|
||||
|
||||
@ -0,0 +1,133 @@
|
||||
import DocViewer, { DocViewerRenderers } from '@cyntler/react-doc-viewer';
|
||||
import '@cyntler/react-doc-viewer/dist/index.css';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { getFileNameAndExtension } from '~/utils/file/getFileNameAndExtension';
|
||||
|
||||
const StyledDocumentViewerContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - 200px);
|
||||
min-height: 500px;
|
||||
width: 100%;
|
||||
background: ${({ theme }) => theme.background.secondary};
|
||||
|
||||
.react-doc-viewer {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
background: none;
|
||||
}
|
||||
|
||||
#react-doc-viewer #header-bar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#react-doc-viewer #pdf-controls {
|
||||
display: none !important;
|
||||
}
|
||||
`;
|
||||
|
||||
type DocumentViewerProps = {
|
||||
documentName: string;
|
||||
documentUrl: string;
|
||||
};
|
||||
|
||||
export const PREVIEWABLE_EXTENSIONS = [
|
||||
'bmp',
|
||||
'csv',
|
||||
'odt',
|
||||
'doc',
|
||||
'docx',
|
||||
'gif',
|
||||
'htm',
|
||||
'html',
|
||||
'jpg',
|
||||
'jpeg',
|
||||
'pdf',
|
||||
'png',
|
||||
'ppt',
|
||||
'pptx',
|
||||
'tiff',
|
||||
'txt',
|
||||
'xls',
|
||||
'xlsx',
|
||||
'mp4',
|
||||
'webp',
|
||||
];
|
||||
|
||||
const MIME_TYPE_MAPPING: Record<
|
||||
(typeof PREVIEWABLE_EXTENSIONS)[number],
|
||||
string
|
||||
> = {
|
||||
bmp: 'image/bmp',
|
||||
csv: 'text/csv',
|
||||
odt: 'application/vnd.oasis.opendocument.text',
|
||||
doc: 'application/msword',
|
||||
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
gif: 'image/gif',
|
||||
htm: 'text/html',
|
||||
html: 'text/html',
|
||||
jpg: 'image/jpeg',
|
||||
jpeg: 'image/jpeg',
|
||||
pdf: 'application/pdf',
|
||||
png: 'image/png',
|
||||
ppt: 'application/vnd.ms-powerpoint',
|
||||
pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||||
tiff: 'image/tiff',
|
||||
txt: 'text/plain',
|
||||
xls: 'application/vnd.ms-excel',
|
||||
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
mp4: 'video/mp4',
|
||||
webp: 'image/webp',
|
||||
};
|
||||
|
||||
export const DocumentViewer = ({
|
||||
documentName,
|
||||
documentUrl,
|
||||
}: DocumentViewerProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const { extension } = getFileNameAndExtension(documentName);
|
||||
const fileExtension = extension?.toLowerCase().replace('.', '') ?? '';
|
||||
const mimeType = PREVIEWABLE_EXTENSIONS.includes(fileExtension)
|
||||
? MIME_TYPE_MAPPING[fileExtension]
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<StyledDocumentViewerContainer>
|
||||
<DocViewer
|
||||
documents={[
|
||||
{
|
||||
uri: documentUrl,
|
||||
fileName: documentName,
|
||||
fileType: mimeType,
|
||||
},
|
||||
]}
|
||||
pluginRenderers={DocViewerRenderers}
|
||||
style={{ height: '100%' }}
|
||||
config={{
|
||||
header: {
|
||||
disableHeader: true,
|
||||
disableFileName: true,
|
||||
retainURLParams: false,
|
||||
},
|
||||
pdfVerticalScrollByDefault: true,
|
||||
pdfZoom: {
|
||||
defaultZoom: 1,
|
||||
zoomJump: 0.1,
|
||||
},
|
||||
}}
|
||||
theme={{
|
||||
primary: theme.background.primary,
|
||||
secondary: theme.background.secondary,
|
||||
tertiary: theme.background.tertiary,
|
||||
textPrimary: theme.font.color.primary,
|
||||
textSecondary: theme.font.color.secondary,
|
||||
textTertiary: theme.font.color.tertiary,
|
||||
disableThemeScrollbar: true,
|
||||
}}
|
||||
/>
|
||||
</StyledDocumentViewerContainer>
|
||||
);
|
||||
};
|
||||
@ -6,6 +6,7 @@ import { captchaState } from '@/client-config/states/captchaState';
|
||||
import { chromeExtensionIdState } from '@/client-config/states/chromeExtensionIdState';
|
||||
import { clientConfigApiStatusState } from '@/client-config/states/clientConfigApiStatusState';
|
||||
import { isAnalyticsEnabledState } from '@/client-config/states/isAnalyticsEnabledState';
|
||||
import { isAttachmentPreviewEnabledState } from '@/client-config/states/isAttachmentPreviewEnabledState';
|
||||
import { isDebugModeState } from '@/client-config/states/isDebugModeState';
|
||||
import { isDeveloperDefaultSignInPrefilledState } from '@/client-config/states/isDeveloperDefaultSignInPrefilledState';
|
||||
import { isEmailVerificationRequiredState } from '@/client-config/states/isEmailVerificationRequiredState';
|
||||
@ -77,6 +78,10 @@ export const ClientConfigProviderEffect = () => {
|
||||
isGoogleCalendarEnabledState,
|
||||
);
|
||||
|
||||
const setIsAttachmentPreviewEnabled = useSetRecoilState(
|
||||
isAttachmentPreviewEnabledState,
|
||||
);
|
||||
|
||||
const { data, loading, error } = useGetClientConfigQuery({
|
||||
skip: clientConfigApiStatus.isLoaded,
|
||||
});
|
||||
@ -149,6 +154,9 @@ export const ClientConfigProviderEffect = () => {
|
||||
setMicrosoftCalendarEnabled(data?.clientConfig?.isMicrosoftCalendarEnabled);
|
||||
setGoogleMessagingEnabled(data?.clientConfig?.isGoogleMessagingEnabled);
|
||||
setGoogleCalendarEnabled(data?.clientConfig?.isGoogleCalendarEnabled);
|
||||
setIsAttachmentPreviewEnabled(
|
||||
data?.clientConfig?.isAttachmentPreviewEnabled,
|
||||
);
|
||||
}, [
|
||||
data,
|
||||
setIsDebugMode,
|
||||
@ -173,6 +181,7 @@ export const ClientConfigProviderEffect = () => {
|
||||
setMicrosoftCalendarEnabled,
|
||||
setGoogleMessagingEnabled,
|
||||
setGoogleCalendarEnabled,
|
||||
setIsAttachmentPreviewEnabled,
|
||||
]);
|
||||
|
||||
return <></>;
|
||||
|
||||
@ -30,6 +30,7 @@ export const GET_CLIENT_CONFIG = gql`
|
||||
frontDomain
|
||||
debugMode
|
||||
analyticsEnabled
|
||||
isAttachmentPreviewEnabled
|
||||
support {
|
||||
supportDriver
|
||||
supportFrontChatId
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
import { createState } from '@ui/utilities/state/utils/createState';
|
||||
|
||||
export const isAttachmentPreviewEnabledState = createState<boolean>({
|
||||
key: 'isAttachmentPreviewEnabled',
|
||||
defaultValue: false,
|
||||
});
|
||||
@ -57,4 +57,5 @@ export const mockedClientConfig: ClientConfig = {
|
||||
isMicrosoftCalendarEnabled: true,
|
||||
isGoogleMessagingEnabled: true,
|
||||
isGoogleCalendarEnabled: true,
|
||||
isAttachmentPreviewEnabled: true,
|
||||
};
|
||||
|
||||
@ -110,6 +110,9 @@ export class ClientConfig {
|
||||
@Field(() => Support)
|
||||
support: Support;
|
||||
|
||||
@Field(() => Boolean)
|
||||
isAttachmentPreviewEnabled: boolean;
|
||||
|
||||
@Field(() => Sentry)
|
||||
sentry: Sentry;
|
||||
|
||||
|
||||
@ -75,6 +75,9 @@ export class ClientConfigResolver {
|
||||
'MUTATION_MAXIMUM_AFFECTED_RECORDS',
|
||||
),
|
||||
},
|
||||
isAttachmentPreviewEnabled: this.environmentService.get(
|
||||
'IS_ATTACHMENT_PREVIEW_ENABLED',
|
||||
),
|
||||
analyticsEnabled: this.environmentService.get('ANALYTICS_ENABLED'),
|
||||
canManageFeatureFlags:
|
||||
this.environmentService.get('NODE_ENV') ===
|
||||
|
||||
@ -970,6 +970,15 @@ export class EnvironmentVariables {
|
||||
@CastToPositiveNumber()
|
||||
@IsOptional()
|
||||
HEALTH_MONITORING_TIME_WINDOW_IN_MINUTES = 5;
|
||||
|
||||
@EnvironmentVariablesMetadata({
|
||||
group: EnvironmentVariablesGroup.Other,
|
||||
description: 'Enable or disable the attachment preview feature',
|
||||
})
|
||||
@CastToBoolean()
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
IS_ATTACHMENT_PREVIEW_ENABLED = true;
|
||||
}
|
||||
|
||||
export const validate = (
|
||||
|
||||
Reference in New Issue
Block a user