chunk csv file before preview (#11886)
closes https://github.com/twentyhq/twenty/issues/10971
This commit is contained in:
@ -1,5 +1,6 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { IconLock } from 'twenty-ui/display';
|
||||
import { Card, CardContent } from 'twenty-ui/layout';
|
||||
|
||||
@ -29,7 +30,7 @@ export const CalendarEventNotSharedContent = () => {
|
||||
<StyledVisibilityCard>
|
||||
<StyledVisibilityCardContent>
|
||||
<IconLock size={theme.icon.size.sm} />
|
||||
Not shared
|
||||
<Trans>Not shared</Trans>
|
||||
</StyledVisibilityCardContent>
|
||||
</StyledVisibilityCard>
|
||||
);
|
||||
|
||||
@ -1,7 +1,11 @@
|
||||
import { fetchCsvPreview } from '@/activities/files/utils/fetchCsvPreview';
|
||||
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 { Trans } from '@lingui/react/macro';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { getFileNameAndExtension } from '~/utils/file/getFileNameAndExtension';
|
||||
|
||||
const StyledDocumentViewerContainer = styled.div`
|
||||
@ -12,13 +16,6 @@ const StyledDocumentViewerContainer = styled.div`
|
||||
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;
|
||||
}
|
||||
@ -26,6 +23,17 @@ const StyledDocumentViewerContainer = styled.div`
|
||||
#react-doc-viewer #pdf-controls {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
#react-doc-viewer,
|
||||
#proxy-renderer,
|
||||
#msdoc-renderer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
background: none;
|
||||
}
|
||||
`;
|
||||
|
||||
type DocumentViewerProps = {
|
||||
@ -87,6 +95,7 @@ export const DocumentViewer = ({
|
||||
documentUrl,
|
||||
}: DocumentViewerProps) => {
|
||||
const theme = useTheme();
|
||||
const [csvPreview, setCsvPreview] = useState<string | undefined>(undefined);
|
||||
|
||||
const { extension } = getFileNameAndExtension(documentName);
|
||||
const fileExtension = extension?.toLowerCase().replace('.', '') ?? '';
|
||||
@ -94,12 +103,32 @@ export const DocumentViewer = ({
|
||||
? MIME_TYPE_MAPPING[fileExtension]
|
||||
: undefined;
|
||||
|
||||
useEffect(() => {
|
||||
if (fileExtension === 'csv') {
|
||||
fetchCsvPreview(documentUrl).then((content) => {
|
||||
setCsvPreview(content);
|
||||
});
|
||||
}
|
||||
}, [documentUrl, fileExtension]);
|
||||
|
||||
if (fileExtension === 'csv' && !isDefined(csvPreview))
|
||||
return (
|
||||
<StyledDocumentViewerContainer>
|
||||
<Trans>Loading csv ... </Trans>
|
||||
</StyledDocumentViewerContainer>
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledDocumentViewerContainer>
|
||||
<DocViewer
|
||||
documents={[
|
||||
{
|
||||
uri: documentUrl,
|
||||
uri:
|
||||
fileExtension === 'csv' && isDefined(csvPreview)
|
||||
? window.URL.createObjectURL(
|
||||
new Blob([csvPreview], { type: 'text/csv' }),
|
||||
)
|
||||
: documentUrl,
|
||||
fileName: documentName,
|
||||
fileType: mimeType,
|
||||
},
|
||||
|
||||
@ -0,0 +1,22 @@
|
||||
import Papa from 'papaparse';
|
||||
|
||||
const DEFAULT_PREVIEW_ROWS = 50;
|
||||
|
||||
export const fetchCsvPreview = async (url: string): Promise<string> => {
|
||||
const response = await fetch(url);
|
||||
const text = await response.text();
|
||||
|
||||
const result = Papa.parse(text, {
|
||||
preview: DEFAULT_PREVIEW_ROWS,
|
||||
skipEmptyLines: true,
|
||||
header: true,
|
||||
});
|
||||
|
||||
const data = result.data as Record<string, string>[];
|
||||
|
||||
const csvContent = Papa.unparse(data, {
|
||||
header: true,
|
||||
});
|
||||
|
||||
return csvContent;
|
||||
};
|
||||
@ -6,6 +6,7 @@ import { EventCardMessageForbidden } from '@/activities/timeline-activities/rows
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
|
||||
import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED } from 'twenty-shared/constants';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { OverflowingTextWithTooltip } from 'twenty-ui/display';
|
||||
@ -94,14 +95,26 @@ export const EventCardMessage = ({
|
||||
);
|
||||
|
||||
if (shouldHandleNotFound) {
|
||||
return <div>Message not found</div>;
|
||||
return (
|
||||
<div>
|
||||
<Trans>Message not found</Trans>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <div>Error loading message</div>;
|
||||
return (
|
||||
<div>
|
||||
<Trans>Error loading message</Trans>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (loading || !isDefined(message)) {
|
||||
return <div>Loading...</div>;
|
||||
return (
|
||||
<div>
|
||||
<Trans>Loading...</Trans>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const messageParticipantHandles = message.messageParticipants
|
||||
@ -117,7 +130,7 @@ export const EventCardMessage = ({
|
||||
{message.subject !==
|
||||
FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED
|
||||
? message.subject
|
||||
: 'Subject not shared'}
|
||||
: `Subject not shared`}
|
||||
</StyledEmailTitle>
|
||||
<StyledEmailParticipants>
|
||||
<OverflowingTextWithTooltip text={messageParticipantHandles} />
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { IconLock } from 'twenty-ui/display';
|
||||
|
||||
const StyledEmailBodyNotSharedContainer = styled.div`
|
||||
@ -44,7 +45,9 @@ export const EventCardMessageBodyNotShared = ({
|
||||
<StyledEmailBodyNotSharedIconContainer>
|
||||
<IconLock />
|
||||
</StyledEmailBodyNotSharedIconContainer>
|
||||
<span>Not shared by {notSharedByFullName}</span>
|
||||
<span>
|
||||
<Trans>Not shared by {notSharedByFullName}</Trans>
|
||||
</span>
|
||||
</StyledEmailBodyNotShared>
|
||||
</StyledEmailBodyNotSharedContainer>
|
||||
);
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { EventCardMessageBodyNotShared } from '@/activities/timeline-activities/rows/message/components/EventCardMessageBodyNotShared';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
const StyledEventCardMessageContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -32,7 +32,7 @@ export const EventCardMessageForbidden = ({
|
||||
<StyledEventCardMessageContainer>
|
||||
<StyledEmailContent>
|
||||
<StyledEmailTitle>
|
||||
<span>Subject not shared</span>
|
||||
<Trans>Subject not shared</Trans>
|
||||
</StyledEmailTitle>
|
||||
<EventCardMessageBodyNotShared
|
||||
notSharedByFullName={notSharedByFullName}
|
||||
|
||||
@ -4,7 +4,9 @@ import { HttpResponse, graphql } from 'msw';
|
||||
|
||||
import { TimelineActivityContext } from '@/activities/timeline-activities/contexts/TimelineActivityContext';
|
||||
import { EventCardMessage } from '@/activities/timeline-activities/rows/message/components/EventCardMessage';
|
||||
import { FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED } from 'twenty-shared/constants';
|
||||
import { ComponentDecorator } from 'twenty-ui/testing';
|
||||
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
||||
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
|
||||
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
|
||||
|
||||
@ -12,6 +14,7 @@ const meta: Meta<typeof EventCardMessage> = {
|
||||
title: 'Modules/TimelineActivities/Rows/Message/EventCardMessage',
|
||||
component: EventCardMessage,
|
||||
decorators: [
|
||||
I18nFrontDecorator,
|
||||
ComponentDecorator,
|
||||
ObjectMetadataItemsDecorator,
|
||||
SnackBarDecorator,
|
||||
@ -66,21 +69,21 @@ export const NotShared: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
await canvas.findByText('Subject not shared');
|
||||
await canvas.findByText(`Subject not shared`);
|
||||
},
|
||||
parameters: {
|
||||
msw: {
|
||||
handlers: [
|
||||
graphql.query('FindOneMessage', () => {
|
||||
return HttpResponse.json({
|
||||
errors: [
|
||||
{
|
||||
message: 'Forbidden',
|
||||
extensions: {
|
||||
code: 'FORBIDDEN',
|
||||
},
|
||||
data: {
|
||||
message: {
|
||||
id: '1',
|
||||
subject: FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED,
|
||||
text: FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED,
|
||||
messageParticipants: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
}),
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user