From 103dff4bd0c66e19792a3e702bba7fbff7bcb4ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Malfait?= Date: Tue, 18 Feb 2025 10:18:59 +0100 Subject: [PATCH] File previewer (#10260) Add a file previewer for pdf, image, doc, xls Screenshot 2025-02-17 at 15 03 10 --- packages/twenty-front/package.json | 1 + .../twenty-front/src/generated/graphql.tsx | 4 +- .../files/components/AttachmentList.tsx | 121 ++++++++- .../files/components/AttachmentRow.tsx | 61 ++++- .../files/components/DocumentViewer.tsx | 133 ++++++++++ .../components/ClientConfigProviderEffect.tsx | 9 + .../graphql/queries/getClientConfig.ts | 1 + .../states/isAttachmentPreviewEnabledState.ts | 6 + .../src/testing/mock-data/config.ts | 1 + .../client-config/client-config.entity.ts | 3 + .../client-config/client-config.resolver.ts | 3 + .../environment/environment-variables.ts | 9 + yarn.lock | 242 +++++++++++++++++- 13 files changed, 573 insertions(+), 21 deletions(-) create mode 100644 packages/twenty-front/src/modules/activities/files/components/DocumentViewer.tsx create mode 100644 packages/twenty-front/src/modules/client-config/states/isAttachmentPreviewEnabledState.ts diff --git a/packages/twenty-front/package.json b/packages/twenty-front/package.json index a5b8be382..019cc8f57 100644 --- a/packages/twenty-front/package.json +++ b/packages/twenty-front/package.json @@ -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", diff --git a/packages/twenty-front/src/generated/graphql.tsx b/packages/twenty-front/src/generated/graphql.tsx index 98b07b530..cb2c72dae 100644 --- a/packages/twenty-front/src/generated/graphql.tsx +++ b/packages/twenty-front/src/generated/graphql.tsx @@ -249,6 +249,7 @@ export type ClientConfig = { debugMode: Scalars['Boolean']; defaultSubdomain?: Maybe; 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 diff --git a/packages/twenty-front/src/modules/activities/files/components/AttachmentList.tsx b/packages/twenty-front/src/modules/activities/files/components/AttachmentList.tsx index 903924fa9..7b8defff9 100644 --- a/packages/twenty-front/src/modules/activities/files/components/AttachmentList.tsx +++ b/packages/twenty-front/src/modules/activities/files/components/AttachmentList.tsx @@ -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(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 = ({ ) : ( {attachments.map((attachment) => ( - + ))} )} )} + {previewedAttachment && isAttachmentPreviewEnabled && ( + + + + {previewedAttachment.name} + + + + + + + + + + Loading document viewer... + + + } + > + + + + + )} ); }; diff --git a/packages/twenty-front/src/modules/activities/files/components/AttachmentRow.tsx b/packages/twenty-front/src/modules/activities/files/components/AttachmentRow.tsx index 18f59b8ce..4637ac272 100644 --- a/packages/twenty-front/src/modules/activities/files/components/AttachmentRow.tsx +++ b/packages/twenty-front/src/modules/activities/files/components/AttachmentRow.tsx @@ -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 ( @@ -137,13 +171,22 @@ export const AttachmentRow = ({ attachment }: { attachment: Attachment }) => { /> ) : ( - - - + {isPreviewable ? ( + + + + ) : ( + + + + )} )} diff --git a/packages/twenty-front/src/modules/activities/files/components/DocumentViewer.tsx b/packages/twenty-front/src/modules/activities/files/components/DocumentViewer.tsx new file mode 100644 index 000000000..e55a358a7 --- /dev/null +++ b/packages/twenty-front/src/modules/activities/files/components/DocumentViewer.tsx @@ -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 ( + + + + ); +}; diff --git a/packages/twenty-front/src/modules/client-config/components/ClientConfigProviderEffect.tsx b/packages/twenty-front/src/modules/client-config/components/ClientConfigProviderEffect.tsx index 99d0c9a86..610bbf7d1 100644 --- a/packages/twenty-front/src/modules/client-config/components/ClientConfigProviderEffect.tsx +++ b/packages/twenty-front/src/modules/client-config/components/ClientConfigProviderEffect.tsx @@ -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 <>; diff --git a/packages/twenty-front/src/modules/client-config/graphql/queries/getClientConfig.ts b/packages/twenty-front/src/modules/client-config/graphql/queries/getClientConfig.ts index 67ed1ef60..949dc281c 100644 --- a/packages/twenty-front/src/modules/client-config/graphql/queries/getClientConfig.ts +++ b/packages/twenty-front/src/modules/client-config/graphql/queries/getClientConfig.ts @@ -30,6 +30,7 @@ export const GET_CLIENT_CONFIG = gql` frontDomain debugMode analyticsEnabled + isAttachmentPreviewEnabled support { supportDriver supportFrontChatId diff --git a/packages/twenty-front/src/modules/client-config/states/isAttachmentPreviewEnabledState.ts b/packages/twenty-front/src/modules/client-config/states/isAttachmentPreviewEnabledState.ts new file mode 100644 index 000000000..6ea94cc84 --- /dev/null +++ b/packages/twenty-front/src/modules/client-config/states/isAttachmentPreviewEnabledState.ts @@ -0,0 +1,6 @@ +import { createState } from '@ui/utilities/state/utils/createState'; + +export const isAttachmentPreviewEnabledState = createState({ + key: 'isAttachmentPreviewEnabled', + defaultValue: false, +}); diff --git a/packages/twenty-front/src/testing/mock-data/config.ts b/packages/twenty-front/src/testing/mock-data/config.ts index 2cd789cc6..e6964ebcf 100644 --- a/packages/twenty-front/src/testing/mock-data/config.ts +++ b/packages/twenty-front/src/testing/mock-data/config.ts @@ -57,4 +57,5 @@ export const mockedClientConfig: ClientConfig = { isMicrosoftCalendarEnabled: true, isGoogleMessagingEnabled: true, isGoogleCalendarEnabled: true, + isAttachmentPreviewEnabled: true, }; diff --git a/packages/twenty-server/src/engine/core-modules/client-config/client-config.entity.ts b/packages/twenty-server/src/engine/core-modules/client-config/client-config.entity.ts index 42060fee9..8262b2bd7 100644 --- a/packages/twenty-server/src/engine/core-modules/client-config/client-config.entity.ts +++ b/packages/twenty-server/src/engine/core-modules/client-config/client-config.entity.ts @@ -110,6 +110,9 @@ export class ClientConfig { @Field(() => Support) support: Support; + @Field(() => Boolean) + isAttachmentPreviewEnabled: boolean; + @Field(() => Sentry) sentry: Sentry; diff --git a/packages/twenty-server/src/engine/core-modules/client-config/client-config.resolver.ts b/packages/twenty-server/src/engine/core-modules/client-config/client-config.resolver.ts index 4cd073fd0..30fc67cfb 100644 --- a/packages/twenty-server/src/engine/core-modules/client-config/client-config.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/client-config/client-config.resolver.ts @@ -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') === diff --git a/packages/twenty-server/src/engine/core-modules/environment/environment-variables.ts b/packages/twenty-server/src/engine/core-modules/environment/environment-variables.ts index 709d8bfde..86e871c1c 100644 --- a/packages/twenty-server/src/engine/core-modules/environment/environment-variables.ts +++ b/packages/twenty-server/src/engine/core-modules/environment/environment-variables.ts @@ -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 = ( diff --git a/yarn.lock b/yarn.lock index dc87b5f32..27367dac3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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: