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:
Félix Malfait
2025-02-18 10:18:59 +01:00
committed by GitHub
parent 270744eca6
commit 103dff4bd0
13 changed files with 573 additions and 21 deletions

View File

@ -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",

View File

@ -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

View File

@ -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>
)}
</>
);
};

View File

@ -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>

View File

@ -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>
);
};

View File

@ -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 <></>;

View File

@ -30,6 +30,7 @@ export const GET_CLIENT_CONFIG = gql`
frontDomain
debugMode
analyticsEnabled
isAttachmentPreviewEnabled
support {
supportDriver
supportFrontChatId

View File

@ -0,0 +1,6 @@
import { createState } from '@ui/utilities/state/utils/createState';
export const isAttachmentPreviewEnabledState = createState<boolean>({
key: 'isAttachmentPreviewEnabled',
defaultValue: false,
});

View File

@ -57,4 +57,5 @@ export const mockedClientConfig: ClientConfig = {
isMicrosoftCalendarEnabled: true,
isGoogleMessagingEnabled: true,
isGoogleCalendarEnabled: true,
isAttachmentPreviewEnabled: true,
};

View File

@ -110,6 +110,9 @@ export class ClientConfig {
@Field(() => Support)
support: Support;
@Field(() => Boolean)
isAttachmentPreviewEnabled: boolean;
@Field(() => Sentry)
sentry: Sentry;

View File

@ -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') ===

View File

@ -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 = (

242
yarn.lock
View File

@ -4273,6 +4273,25 @@ __metadata:
languageName: node
linkType: hard
"@cyntler/react-doc-viewer@npm:^1.17.0":
version: 1.17.0
resolution: "@cyntler/react-doc-viewer@npm:1.17.0"
dependencies:
"@types/mustache": "npm:^4.2.5"
"@types/papaparse": "npm:^5.3.14"
ajv: "npm:^7.2.4"
core-js: "npm:^3.37.1"
mustache: "npm:^4.2.0"
papaparse: "npm:^5.4.1"
react-pdf: "npm:^9.0.0"
styled-components: "npm:^6.1.11"
peerDependencies:
react: ">=17.0.0"
react-dom: ">=17.0.0"
checksum: 10c0/940e16894564384861bb56c4fa2896653e2ab2db48f9c7e60503468a300b06d7571677bcf62e2a1f934171b84a843669879233ad3492d23812265796e9315c28
languageName: node
linkType: hard
"@dagrejs/dagre@npm:^1.1.2":
version: 1.1.3
resolution: "@dagrejs/dagre@npm:1.1.3"
@ -4384,6 +4403,15 @@ __metadata:
languageName: node
linkType: hard
"@emotion/is-prop-valid@npm:1.2.2":
version: 1.2.2
resolution: "@emotion/is-prop-valid@npm:1.2.2"
dependencies:
"@emotion/memoize": "npm:^0.8.1"
checksum: 10c0/bb1530dcb4e0e5a4fabb219279f2d0bc35796baf66f6241f98b0d03db1985c890a8cafbea268e0edefd5eeda143dbd5c09a54b5fba74cee8c69b98b13194af50
languageName: node
linkType: hard
"@emotion/is-prop-valid@npm:^0.8.2":
version: 0.8.8
resolution: "@emotion/is-prop-valid@npm:0.8.8"
@ -4409,6 +4437,13 @@ __metadata:
languageName: node
linkType: hard
"@emotion/memoize@npm:^0.8.1":
version: 0.8.1
resolution: "@emotion/memoize@npm:0.8.1"
checksum: 10c0/dffed372fc3b9fa2ba411e76af22b6bb686fb0cb07694fdfaa6dd2baeb0d5e4968c1a7caa472bfcf06a5997d5e7c7d16b90e993f9a6ffae79a2c3dbdc76dfe78
languageName: node
linkType: hard
"@emotion/memoize@npm:^0.9.0":
version: 0.9.0
resolution: "@emotion/memoize@npm:0.9.0"
@ -4477,6 +4512,13 @@ __metadata:
languageName: node
linkType: hard
"@emotion/unitless@npm:0.8.1":
version: 0.8.1
resolution: "@emotion/unitless@npm:0.8.1"
checksum: 10c0/a1ed508628288f40bfe6dd17d431ed899c067a899fa293a13afe3aed1d70fac0412b8a215fafab0b42829360db687fecd763e5f01a64ddc4a4b58ec3112ff548
languageName: node
linkType: hard
"@emotion/unitless@npm:^0.9.0":
version: 0.9.0
resolution: "@emotion/unitless@npm:0.9.0"
@ -18173,6 +18215,13 @@ __metadata:
languageName: node
linkType: hard
"@types/mustache@npm:^4.2.5":
version: 4.2.5
resolution: "@types/mustache@npm:4.2.5"
checksum: 10c0/624975c39068d47407eadb89628aaff5ef60f3b7a71eef92a254310896a4e90518a01dcf71d95779ab2c986034a6ca5403d22fea237c67ff87f2e2b3fb794ea6
languageName: node
linkType: hard
"@types/mute-stream@npm:^0.0.4":
version: 0.0.4
resolution: "@types/mute-stream@npm:0.0.4"
@ -18294,6 +18343,15 @@ __metadata:
languageName: node
linkType: hard
"@types/papaparse@npm:^5.3.14":
version: 5.3.15
resolution: "@types/papaparse@npm:5.3.15"
dependencies:
"@types/node": "npm:*"
checksum: 10c0/9333e980b9ed4102f80b1791b6dfefb23fb4d27252b2b6ee6084b2cd847cbe802cfb838d46b4f6b4cb035f9225e0323034c956ee21bbcbcb4cbdb663302d5eeb
languageName: node
linkType: hard
"@types/parse-json@npm:^4.0.0":
version: 4.0.2
resolution: "@types/parse-json@npm:4.0.2"
@ -18585,6 +18643,13 @@ __metadata:
languageName: node
linkType: hard
"@types/stylis@npm:4.2.5":
version: 4.2.5
resolution: "@types/stylis@npm:4.2.5"
checksum: 10c0/23f5b35a3a04f6bb31a29d404fa1bc8e0035fcaff2356b4047743a057e0c37b2eba7efe14d57dd2b95b398cea3bac294d9c6cd93ed307d8c0b7f5d282224b469
languageName: node
linkType: hard
"@types/superagent@npm:*":
version: 8.1.8
resolution: "@types/superagent@npm:8.1.8"
@ -20145,6 +20210,18 @@ __metadata:
languageName: node
linkType: hard
"ajv@npm:^7.2.4":
version: 7.2.4
resolution: "ajv@npm:7.2.4"
dependencies:
fast-deep-equal: "npm:^3.1.1"
json-schema-traverse: "npm:^1.0.0"
require-from-string: "npm:^2.0.2"
uri-js: "npm:^4.2.2"
checksum: 10c0/420b72d7f6af89c7e63e00856b894e16a8a2ea107b2b2a76ff36d8dd05f6d708ab120a8c7cc627983ad1f75c6990363eda7c68f45943635f084de62353ca1c56
languageName: node
linkType: hard
"ajv@npm:^8.0.0":
version: 8.17.1
resolution: "ajv@npm:8.17.1"
@ -23149,6 +23226,13 @@ __metadata:
languageName: node
linkType: hard
"camelize@npm:^1.0.0":
version: 1.0.1
resolution: "camelize@npm:1.0.1"
checksum: 10c0/4c9ac55efd356d37ac483bad3093758236ab686192751d1c9daa43188cc5a07b09bd431eb7458a4efd9ca22424bba23253e7b353feb35d7c749ba040de2385fb
languageName: node
linkType: hard
"can-bind-to-host@npm:^1.1.1":
version: 1.1.2
resolution: "can-bind-to-host@npm:1.1.2"
@ -23172,6 +23256,17 @@ __metadata:
languageName: node
linkType: hard
"canvas@npm:^3.0.0-rc2":
version: 3.1.0
resolution: "canvas@npm:3.1.0"
dependencies:
node-addon-api: "npm:^7.0.0"
node-gyp: "npm:latest"
prebuild-install: "npm:^7.1.1"
checksum: 10c0/28da5184c1d7e97049ba6a24f10690b9ed4b303bbd25517d95c892fa3a6331417791657a3a7467068e40af0dda2dcc9120d062f7426a3d796131e69a30e3cbf1
languageName: node
linkType: hard
"capital-case@npm:^1.0.4":
version: 1.0.4
resolution: "capital-case@npm:1.0.4"
@ -24655,6 +24750,13 @@ __metadata:
languageName: node
linkType: hard
"core-js@npm:^3.37.1":
version: 3.40.0
resolution: "core-js@npm:3.40.0"
checksum: 10c0/db7946ada881e845d8b157061945b1187618fa45cf162f392a151e8a497962aed2da688c982eaa1d444c864be97a70f8be4d73385294b515d224dd164d19f1d4
languageName: node
linkType: hard
"core-js@npm:^3.8.2":
version: 3.38.0
resolution: "core-js@npm:3.38.0"
@ -24988,6 +25090,13 @@ __metadata:
languageName: node
linkType: hard
"css-color-keywords@npm:^1.0.0":
version: 1.0.0
resolution: "css-color-keywords@npm:1.0.0"
checksum: 10c0/af205a86c68e0051846ed91eb3e30b4517e1904aac040013ff1d742019b3f9369ba5658ba40901dbbc121186fc4bf0e75a814321cc3e3182fbb2feb81c6d9cb7
languageName: node
linkType: hard
"css-in-js-utils@npm:^3.1.0":
version: 3.1.0
resolution: "css-in-js-utils@npm:3.1.0"
@ -25048,6 +25157,17 @@ __metadata:
languageName: node
linkType: hard
"css-to-react-native@npm:3.2.0":
version: 3.2.0
resolution: "css-to-react-native@npm:3.2.0"
dependencies:
camelize: "npm:^1.0.0"
css-color-keywords: "npm:^1.0.0"
postcss-value-parser: "npm:^4.0.2"
checksum: 10c0/fde850a511d5d3d7c55a1e9b8ed26b69a8ad4868b3487e36ebfbfc0b96fc34bc977d9cd1d61a289d0c74d3f9a662d8cee297da53d4433bf2e27d6acdff8e1003
languageName: node
linkType: hard
"css-tree@npm:^1.1.2":
version: 1.1.3
resolution: "css-tree@npm:1.1.3"
@ -25149,7 +25269,7 @@ __metadata:
languageName: node
linkType: hard
"csstype@npm:^3.0.2, csstype@npm:^3.1.2":
"csstype@npm:3.1.3, csstype@npm:^3.0.2, csstype@npm:^3.1.2":
version: 3.1.3
resolution: "csstype@npm:3.1.3"
checksum: 10c0/80c089d6f7e0c5b2bd83cf0539ab41474198579584fa10d86d0cafe0642202343cbc119e076a0b1aece191989477081415d66c9fefbf3c957fc2fc4b7009f248
@ -35603,6 +35723,13 @@ __metadata:
languageName: node
linkType: hard
"make-cancellable-promise@npm:^1.3.1":
version: 1.3.2
resolution: "make-cancellable-promise@npm:1.3.2"
checksum: 10c0/10aa0450c743dcf20b55414c433ca45926b775b22eb6d25fa386fc499a8f3fc64c70eb575d99bdd16667d300068f51702822c293bc4e72da7ff4f82d0ea48184
languageName: node
linkType: hard
"make-dir@npm:^2.0.0, make-dir@npm:^2.1.0":
version: 2.1.0
resolution: "make-dir@npm:2.1.0"
@ -35638,6 +35765,13 @@ __metadata:
languageName: node
linkType: hard
"make-event-props@npm:^1.6.0":
version: 1.6.2
resolution: "make-event-props@npm:1.6.2"
checksum: 10c0/ecf0b742e43a392c07e2267baca2397e750d38cc14ef3cb72ef8bfe4a8c8b0fd99a03a2eeab84a26c2b204f7c231da6af31fa26321fbfd413ded43ba1825e867
languageName: node
linkType: hard
"make-fetch-happen@npm:^10.0.3":
version: 10.2.1
resolution: "make-fetch-happen@npm:10.2.1"
@ -36395,6 +36529,18 @@ __metadata:
languageName: node
linkType: hard
"merge-refs@npm:^1.3.0":
version: 1.3.0
resolution: "merge-refs@npm:1.3.0"
peerDependencies:
"@types/react": ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
peerDependenciesMeta:
"@types/react":
optional: true
checksum: 10c0/403d20d283a595565a6bef813415df509dad12a5ad157f0ae04861b3aee4a3691971ccae7079e20497d9f367a478ad60e5b63a2ca9ffb2cc3d511284b49b4bd6
languageName: node
linkType: hard
"merge-source-map@npm:^1.1.0":
version: 1.1.0
resolution: "merge-source-map@npm:1.1.0"
@ -39471,6 +39617,13 @@ __metadata:
languageName: node
linkType: hard
"papaparse@npm:^5.4.1":
version: 5.5.2
resolution: "papaparse@npm:5.5.2"
checksum: 10c0/83b8c0cf570395581a42331cd9231194dbba43bc8c608026739f5180827506575993dc788def039a9666bc103e2a96075de8732ea8a63e507b74c02aa757bcd5
languageName: node
linkType: hard
"param-case@npm:^3.0.4":
version: 3.0.4
resolution: "param-case@npm:3.0.4"
@ -39962,6 +40115,13 @@ __metadata:
languageName: node
linkType: hard
"path2d@npm:^0.2.1":
version: 0.2.2
resolution: "path2d@npm:0.2.2"
checksum: 10c0/1bb76c7f275d07f1bc7ca12171d828e91bf8a12596f0765a52e9d4d47fe1a428455dc1dd4c9002924a9bc554f6ac25e09a6c22eaecf32e5e33fba2985b5168f8
languageName: node
linkType: hard
"pathe@npm:^1.1.0, pathe@npm:^1.1.1, pathe@npm:^1.1.2":
version: 1.1.2
resolution: "pathe@npm:1.1.2"
@ -39996,6 +40156,21 @@ __metadata:
languageName: node
linkType: hard
"pdfjs-dist@npm:4.8.69":
version: 4.8.69
resolution: "pdfjs-dist@npm:4.8.69"
dependencies:
canvas: "npm:^3.0.0-rc2"
path2d: "npm:^0.2.1"
dependenciesMeta:
canvas:
optional: true
path2d:
optional: true
checksum: 10c0/dc297f2a36aa36834a2892cb78c3cafc7ac01753a2e7c4316a1f6e8c1d337a52a3bfbf7fdff7aaba615893b53f2d06a0efc2176525592b4d7b51021279c101be
languageName: node
linkType: hard
"peberminta@npm:^0.9.0":
version: 0.9.0
resolution: "peberminta@npm:0.9.0"
@ -40511,7 +40686,7 @@ __metadata:
languageName: node
linkType: hard
"postcss-value-parser@npm:^4.1.0, postcss-value-parser@npm:^4.2.0":
"postcss-value-parser@npm:^4.0.2, postcss-value-parser@npm:^4.1.0, postcss-value-parser@npm:^4.2.0":
version: 4.2.0
resolution: "postcss-value-parser@npm:4.2.0"
checksum: 10c0/f4142a4f56565f77c1831168e04e3effd9ffcc5aebaf0f538eee4b2d465adfd4b85a44257bb48418202a63806a7da7fe9f56c330aebb3cac898e46b4cbf49161
@ -40529,6 +40704,17 @@ __metadata:
languageName: node
linkType: hard
"postcss@npm:8.4.49":
version: 8.4.49
resolution: "postcss@npm:8.4.49"
dependencies:
nanoid: "npm:^3.3.7"
picocolors: "npm:^1.1.1"
source-map-js: "npm:^1.2.1"
checksum: 10c0/f1b3f17aaf36d136f59ec373459f18129908235e65dbdc3aee5eef8eba0756106f52de5ec4682e29a2eab53eb25170e7e871b3e4b52a8f1de3d344a514306be3
languageName: node
linkType: hard
"postcss@npm:^8.4.33":
version: 8.4.47
resolution: "postcss@npm:8.4.47"
@ -41899,6 +42085,29 @@ __metadata:
languageName: node
linkType: hard
"react-pdf@npm:^9.0.0":
version: 9.2.1
resolution: "react-pdf@npm:9.2.1"
dependencies:
clsx: "npm:^2.0.0"
dequal: "npm:^2.0.3"
make-cancellable-promise: "npm:^1.3.1"
make-event-props: "npm:^1.6.0"
merge-refs: "npm:^1.3.0"
pdfjs-dist: "npm:4.8.69"
tiny-invariant: "npm:^1.0.0"
warning: "npm:^4.0.0"
peerDependencies:
"@types/react": ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
peerDependenciesMeta:
"@types/react":
optional: true
checksum: 10c0/69b5456b3941ea08f03319a94b155db782232dee4b3e03513c4a4c10cc3d81d129fc3284136990b51d5dcf766192abc64d71e1d258ca7e0eb4e6592343fea6a4
languageName: node
linkType: hard
"react-phone-number-input@npm:^3.3.4":
version: 3.4.5
resolution: "react-phone-number-input@npm:3.4.5"
@ -44004,7 +44213,7 @@ __metadata:
languageName: node
linkType: hard
"shallowequal@npm:^1.1.0":
"shallowequal@npm:1.1.0, shallowequal@npm:^1.1.0":
version: 1.1.0
resolution: "shallowequal@npm:1.1.0"
checksum: 10c0/b926efb51cd0f47aa9bc061add788a4a650550bbe50647962113a4579b60af2abe7b62f9b02314acc6f97151d4cf87033a2b15fc20852fae306d1a095215396c
@ -45396,6 +45605,26 @@ __metadata:
languageName: node
linkType: hard
"styled-components@npm:^6.1.11":
version: 6.1.15
resolution: "styled-components@npm:6.1.15"
dependencies:
"@emotion/is-prop-valid": "npm:1.2.2"
"@emotion/unitless": "npm:0.8.1"
"@types/stylis": "npm:4.2.5"
css-to-react-native: "npm:3.2.0"
csstype: "npm:3.1.3"
postcss: "npm:8.4.49"
shallowequal: "npm:1.1.0"
stylis: "npm:4.3.2"
tslib: "npm:2.6.2"
peerDependencies:
react: ">= 16.8.0"
react-dom: ">= 16.8.0"
checksum: 10c0/7c29db75af722599e10962ef74edd86423275385a3bf6baeb76783dfacc3de7608d1cc07b0d5866986e5263d60f0801b0d1f5b3b63be1af23bed68fdca8eaab6
languageName: node
linkType: hard
"styled-jsx@npm:5.1.1":
version: 5.1.1
resolution: "styled-jsx@npm:5.1.1"
@ -45419,7 +45648,7 @@ __metadata:
languageName: node
linkType: hard
"stylis@npm:^4.3.0":
"stylis@npm:4.3.2, stylis@npm:^4.3.0":
version: 4.3.2
resolution: "stylis@npm:4.3.2"
checksum: 10c0/0410e1404cbeee3388a9e17587875211ce2f014c8379af0d1e24ca55878867c9f1ccc7b0ce9a156ca53f5d6e301391a82b0645522a604674a378b3189a4a1994
@ -45926,7 +46155,7 @@ __metadata:
languageName: node
linkType: hard
"tiny-invariant@npm:^1.0.2, tiny-invariant@npm:^1.0.6, tiny-invariant@npm:^1.1.0, tiny-invariant@npm:^1.3.1, tiny-invariant@npm:^1.3.3":
"tiny-invariant@npm:^1.0.0, tiny-invariant@npm:^1.0.2, tiny-invariant@npm:^1.0.6, tiny-invariant@npm:^1.1.0, tiny-invariant@npm:^1.3.1, tiny-invariant@npm:^1.3.3":
version: 1.3.3
resolution: "tiny-invariant@npm:1.3.3"
checksum: 10c0/65af4a07324b591a059b35269cd696aba21bef2107f29b9f5894d83cc143159a204b299553435b03874ebb5b94d019afa8b8eff241c8a4cfee95872c2e1c1c4a
@ -46708,6 +46937,7 @@ __metadata:
dependencies:
"@blocknote/xl-docx-exporter": "npm:^0.22.0"
"@blocknote/xl-pdf-exporter": "npm:^0.22.0"
"@cyntler/react-doc-viewer": "npm:^1.17.0"
"@lingui/detect-locale": "npm:^5.2.0"
"@nivo/calendar": "npm:^0.87.0"
"@nivo/core": "npm:^0.87.0"
@ -49115,7 +49345,7 @@ __metadata:
languageName: node
linkType: hard
"warning@npm:^4.0.3":
"warning@npm:^4.0.0, warning@npm:^4.0.3":
version: 4.0.3
resolution: "warning@npm:4.0.3"
dependencies: