Skip to content

Commit 76ecfa6

Browse files
v-tarasevich-blitz-brainchriscollins3456purnimagarg1
authored
feat(uploadFiles): bring changes from SaaS (#15166)
Co-authored-by: Chris Collins <[email protected]> Co-authored-by: Purnima Garg <[email protected]>
1 parent 3e5e552 commit 76ecfa6

File tree

15 files changed

+612
-42
lines changed

15 files changed

+612
-42
lines changed

datahub-web-react/src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { GlobalStyles } from '@components/components/GlobalStyles';
1212

1313
import { Routes } from '@app/Routes';
1414
import { isLoggedInVar } from '@app/auth/checkAuthStatus';
15+
import { FilesUploadingDownloadingLatencyTracker } from '@app/shared/FilesUploadingDownloadingLatencyTracker';
1516
import { ErrorCodes } from '@app/shared/constants';
1617
import { PageRoutes } from '@conf/Global';
1718
import CustomThemeProvider from '@src/CustomThemeProvider';
@@ -88,6 +89,7 @@ export const InnerApp: React.VFC = () => {
8889
<HelmetProvider>
8990
<CustomThemeProvider>
9091
<GlobalStyles />
92+
<FilesUploadingDownloadingLatencyTracker />
9193

9294
<Helmet>
9395
<title>{useCustomTheme().theme?.content?.title}</title>

datahub-web-react/src/alchemy-components/components/Editor/Editor.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
CodeExtension,
1111
DropCursorExtension,
1212
FontSizeExtension,
13+
GapCursorExtension,
1314
HardBreakExtension,
1415
HeadingExtension,
1516
HistoryExtension,
@@ -37,6 +38,7 @@ import { FloatingToolbar } from '@components/components/Editor/toolbar/FloatingT
3738
import { TableCellMenu } from '@components/components/Editor/toolbar/TableCellMenu';
3839
import { Toolbar } from '@components/components/Editor/toolbar/Toolbar';
3940
import { EditorProps } from '@components/components/Editor/types';
41+
import { colors } from '@components/theme';
4042

4143
import { notEmpty } from '@app/entityV2/shared/utils';
4244

@@ -66,7 +68,10 @@ export const Editor = forwardRef((props: EditorProps, ref) => {
6668
new CodeBlockExtension({ syntaxTheme: 'base16_ateliersulphurpool_light' }),
6769
new CodeExtension(),
6870
new DataHubMentionsExtension({}),
69-
new DropCursorExtension({}),
71+
new DropCursorExtension({
72+
color: colors.primary[100],
73+
width: 2,
74+
}),
7075
new HardBreakExtension(),
7176
new HeadingExtension({}),
7277
new HistoryExtension({}),
@@ -78,6 +83,7 @@ export const Editor = forwardRef((props: EditorProps, ref) => {
7883
onFileUploadSucceeded,
7984
onFileDownloadView,
8085
}),
86+
new GapCursorExtension(), // required to allow cursor placement next to non-editable inline elements
8187
new ImageExtension({ enableResizing: !readOnly }),
8288
new ItalicExtension(),
8389
new LinkExtension({ autoLink: true, defaultTarget: '_blank' }),

datahub-web-react/src/alchemy-components/components/Editor/EditorTheme.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ export const EditorContainer = styled.div<{ $readOnly?: boolean; $hideBorder?: b
7171
flex: 1 1 100%;
7272
display: flex;
7373
flex-direction: column;
74+
width: 100%;
7475
}
7576
7677
.remirror-editor.ProseMirror {

datahub-web-react/src/alchemy-components/components/Editor/extensions/fileDragDrop/FileDragDropExtension.tsx

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ class FileDragDropExtension extends NodeExtension<FileDragDropOptions> {
5757
}
5858

5959
createTags() {
60-
return [ExtensionTag.Block, ExtensionTag.Behavior, ExtensionTag.FormattingNode];
60+
return [ExtensionTag.InlineNode, ExtensionTag.Behavior];
6161
}
6262

6363
get defaultPriority() {
@@ -74,7 +74,13 @@ class FileDragDropExtension extends NodeExtension<FileDragDropOptions> {
7474
props: {
7575
handleDOMEvents: {
7676
drop: (view: EditorView, event: DragEvent) => {
77-
return this.handleDrop(view, event);
77+
const data = event.dataTransfer;
78+
if (data && data.files && data.files.length > 0) {
79+
// External file drop
80+
return this.handleDrop(view, event);
81+
}
82+
// Moving nodes internally
83+
return false;
7884
},
7985
dragover: (view: EditorView, event: DragEvent) => {
8086
if (event.dataTransfer?.types.includes('Files')) {
@@ -97,6 +103,23 @@ class FileDragDropExtension extends NodeExtension<FileDragDropOptions> {
97103
dragleave: (_view: EditorView, _event: DragEvent) => {
98104
return false;
99105
},
106+
dragstart: (view: EditorView, event: DragEvent) => {
107+
const pos = view.posAtCoords({ left: event.clientX, top: event.clientY });
108+
if (!pos) return false;
109+
110+
const node = view.state.doc.nodeAt(pos.pos);
111+
if (!node || node.type !== this.type) return false;
112+
113+
const data = event.dataTransfer;
114+
if (data) {
115+
data.setData(
116+
'application/x-prosemirror-node',
117+
JSON.stringify({ id: node.attrs.id, type: node.type.name }),
118+
);
119+
data.effectAllowed = 'move';
120+
}
121+
return false; // Allow default handling in ProseMirror
122+
},
100123
},
101124
},
102125
}),
@@ -178,6 +201,17 @@ class FileDragDropExtension extends NodeExtension<FileDragDropOptions> {
178201
description: 'Something went wrong',
179202
});
180203
}
204+
} else {
205+
this.options.onFileUploadFailed?.(
206+
file.type,
207+
file.size,
208+
'drag-and-drop',
209+
FileUploadFailureType.UPLOADING_NOT_SUPPORTED,
210+
);
211+
this.removeNode(view, placeholderAttrs.id);
212+
notification.error({
213+
message: 'Uploading files in this context is not currently supported',
214+
});
181215
}
182216
} catch (error) {
183217
console.error(error);
@@ -198,7 +232,7 @@ class FileDragDropExtension extends NodeExtension<FileDragDropOptions> {
198232
public updateNodeWithUrl(view: EditorView, nodeId: string, url: string): void {
199233
const { nodePos, nodeToUpdate } = this.findNodeById(view.state, nodeId);
200234

201-
if (!nodePos || !nodeToUpdate) return;
235+
if (nodePos === null || !nodeToUpdate) return;
202236

203237
const { name, type } = nodeToUpdate.attrs;
204238

@@ -211,7 +245,7 @@ class FileDragDropExtension extends NodeExtension<FileDragDropOptions> {
211245

212246
public removeNode(view: EditorView, nodeId: string) {
213247
const { nodePos, nodeToUpdate } = this.findNodeById(view.state, nodeId);
214-
if (!nodePos || !nodeToUpdate) return;
248+
if (nodePos === null || !nodeToUpdate) return;
215249

216250
const updatedTransaction = view.state.tr.delete(nodePos, nodePos + nodeToUpdate.nodeSize);
217251
view.dispatch(updatedTransaction);
@@ -298,12 +332,11 @@ class FileDragDropExtension extends NodeExtension<FileDragDropOptions> {
298332

299333
createNodeSpec(extra: ApplySchemaAttributes, override: Partial<NodeSpecOverride>): NodeExtensionSpec {
300334
return {
301-
inline: false,
302-
group: 'block',
303-
marks: '',
304-
selectable: true,
305-
draggable: true,
335+
inline: true,
336+
group: 'inline',
306337
atom: true,
338+
selectable: true,
339+
draggable: (state) => state.editable,
307340
...override,
308341
attrs: {
309342
...extra.defaults(),
@@ -315,7 +348,7 @@ class FileDragDropExtension extends NodeExtension<FileDragDropOptions> {
315348
},
316349
parseDOM: [
317350
{
318-
tag: `div[${FILE_ATTRS.name}]`,
351+
tag: `span[${FILE_ATTRS.name}]`,
319352
getAttrs: (node: string | Node) => this.parseFileNode(node, extra),
320353
},
321354
{
@@ -335,9 +368,10 @@ class FileDragDropExtension extends NodeExtension<FileDragDropOptions> {
335368
[FILE_ATTRS.type]: type,
336369
[FILE_ATTRS.size]: size.toString(),
337370
[FILE_ATTRS.id]: id,
371+
contenteditable: 'false',
338372
};
339373

340-
return ['div', attrs, name];
374+
return ['span', attrs, name];
341375
},
342376
};
343377
}

datahub-web-react/src/alchemy-components/components/Editor/extensions/fileDragDrop/FileNodeView.tsx

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,49 @@
11
import { NodeViewComponentProps } from '@remirror/react';
2+
import { Typography } from 'antd';
23
import React from 'react';
34
import styled from 'styled-components';
45

56
import {
67
FILE_ATTRS,
78
FileNodeAttributes,
9+
getExtensionFromFileName,
10+
getFileIconFromExtension,
811
handleFileDownload,
912
} from '@components/components/Editor/extensions/fileDragDrop/fileUtils';
1013
import { Icon } from '@components/components/Icon';
14+
import { colors } from '@components/theme';
1115

1216
import Loading from '@app/shared/Loading';
1317

14-
const FileContainer = styled.div`
15-
max-width: 100%;
18+
const FileContainer = styled.span`
19+
width: fit-content;
20+
display: inline-block;
21+
padding: 4px;
22+
cursor: pointer;
23+
color: ${({ theme }) => theme.styles['primary-color']};
24+
25+
:hover {
26+
border-radius: 8px;
27+
background-color: ${colors.gray[1500]};
28+
}
29+
30+
.ProseMirror-selectednode > & {
31+
border-radius: 8px;
32+
background-color: ${colors.gray[1500]};
33+
}
1634
`;
1735

18-
const FileDetails = styled.div`
19-
min-width: 0; // Allows text truncation
20-
cursor: pointer;
36+
const FileDetails = styled.span`
37+
width: fit-content;
2138
display: flex;
2239
gap: 4px;
2340
align-items: center;
24-
color: ${({ theme }) => theme.styles['primary-color']};
2541
font-weight: 600;
42+
max-width: 350px;
2643
`;
2744

28-
const FileName = styled.span`
45+
const FileName = styled(Typography.Text)`
2946
color: ${({ theme }) => theme.styles['primary-color']};
30-
display: block;
3147
white-space: nowrap;
3248
overflow: hidden;
3349
text-overflow: ellipsis;
@@ -66,6 +82,9 @@ export const FileNodeView: React.FC<FileNodeViewProps> = ({ node, onFileDownload
6682
);
6783
}
6884

85+
const extension = getExtensionFromFileName(name);
86+
const icon = getFileIconFromExtension(extension || '');
87+
6988
return (
7089
<FileContainer {...containerProps}>
7190
<FileDetails
@@ -76,8 +95,8 @@ export const FileNodeView: React.FC<FileNodeViewProps> = ({ node, onFileDownload
7695
handleFileDownload(url, name);
7796
}}
7897
>
79-
<Icon icon="FileArrowDown" size="lg" source="phosphor" />
80-
<FileName>{name}</FileName>
98+
<Icon icon={icon} size="lg" source="phosphor" />
99+
<FileName ellipsis={{ tooltip: name }}>{name}</FileName>
81100
</FileDetails>
82101
</FileContainer>
83102
);

datahub-web-react/src/alchemy-components/components/Editor/extensions/fileDragDrop/__tests__/fileUtils.test.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
createFileNodeAttributes,
55
generateFileId,
66
getExtensionFromFileName,
7+
getFileIconFromExtension,
78
getFileTypeFromFilename,
89
getFileTypeFromUrl,
910
handleFileDownload,
@@ -294,4 +295,68 @@ describe('fileUtils', () => {
294295
expect(getExtensionFromFileName('file.TXT')).toBe('txt');
295296
});
296297
});
298+
299+
describe('getFileIconFromExtension', () => {
300+
it('should return FilePdf for pdf extension', () => {
301+
expect(getFileIconFromExtension('pdf')).toBe('FilePdf');
302+
expect(getFileIconFromExtension('PDF')).toBe('FilePdf'); // case-insensitive
303+
});
304+
305+
it('should return FileWord for doc and docx extensions', () => {
306+
expect(getFileIconFromExtension('doc')).toBe('FileWord');
307+
expect(getFileIconFromExtension('DOCX')).toBe('FileWord');
308+
});
309+
310+
it('should return FileText for txt, md, rtf extensions', () => {
311+
expect(getFileIconFromExtension('txt')).toBe('FileText');
312+
expect(getFileIconFromExtension('md')).toBe('FileText');
313+
expect(getFileIconFromExtension('RTF')).toBe('FileText');
314+
});
315+
316+
it('should return FileXls for xls and xlsx extensions', () => {
317+
expect(getFileIconFromExtension('xls')).toBe('FileXls');
318+
expect(getFileIconFromExtension('XLSX')).toBe('FileXls');
319+
});
320+
321+
it('should return FilePpt for ppt and pptx extensions', () => {
322+
expect(getFileIconFromExtension('ppt')).toBe('FilePpt');
323+
expect(getFileIconFromExtension('PPTX')).toBe('FilePpt');
324+
});
325+
326+
it('should return FileImage for image extensions', () => {
327+
['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'tiff'].forEach((ext) => {
328+
expect(getFileIconFromExtension(ext)).toBe('FileImage');
329+
expect(getFileIconFromExtension(ext.toUpperCase())).toBe('FileImage');
330+
});
331+
});
332+
333+
it('should return FileVideo for video extensions', () => {
334+
['mp4', 'wmv', 'mov'].forEach((ext) => {
335+
expect(getFileIconFromExtension(ext)).toBe('FileVideo');
336+
expect(getFileIconFromExtension(ext.toUpperCase())).toBe('FileVideo');
337+
});
338+
});
339+
340+
it('should return FileAudio for mp3 extension', () => {
341+
expect(getFileIconFromExtension('mp3')).toBe('FileAudio');
342+
expect(getFileIconFromExtension('MP3')).toBe('FileAudio');
343+
});
344+
345+
it('should return FileZip for archive extensions', () => {
346+
['zip', 'rar', 'gz'].forEach((ext) => {
347+
expect(getFileIconFromExtension(ext)).toBe('FileZip');
348+
expect(getFileIconFromExtension(ext.toUpperCase())).toBe('FileZip');
349+
});
350+
});
351+
352+
it('should return FileCsv for csv extension', () => {
353+
expect(getFileIconFromExtension('csv')).toBe('FileCsv');
354+
expect(getFileIconFromExtension('CSV')).toBe('FileCsv');
355+
});
356+
357+
it('should return FileArrowDown for unknown extensions', () => {
358+
expect(getFileIconFromExtension('unknown')).toBe('FileArrowDown');
359+
expect(getFileIconFromExtension('')).toBe('FileArrowDown');
360+
});
361+
});
297362
});

datahub-web-react/src/alchemy-components/components/Editor/extensions/fileDragDrop/fileUtils.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,3 +241,50 @@ export const getFileTypeFromUrl = (url: string): string => {
241241
export const getFileTypeFromFilename = (filename: string): string => {
242242
return getFileTypeFromUrl(filename);
243243
};
244+
245+
/**
246+
* Get icon to show based on file extension
247+
* @param extension - the extension of the file
248+
* @returns string depicting the phosphor icon name
249+
*/
250+
export const getFileIconFromExtension = (extension: string) => {
251+
switch (extension.toLowerCase()) {
252+
case 'pdf':
253+
return 'FilePdf';
254+
case 'doc':
255+
case 'docx':
256+
return 'FileWord';
257+
case 'txt':
258+
case 'md':
259+
case 'rtf':
260+
return 'FileText';
261+
case 'xls':
262+
case 'xlsx':
263+
return 'FileXls';
264+
case 'ppt':
265+
case 'pptx':
266+
return 'FilePpt';
267+
case 'jpg':
268+
case 'jpeg':
269+
case 'png':
270+
case 'gif':
271+
case 'webp':
272+
case 'bmp':
273+
case 'tiff':
274+
return 'FileImage';
275+
case 'mp4':
276+
case 'wmv':
277+
case 'mov':
278+
return 'FileVideo';
279+
case 'mp3':
280+
return 'FileAudio';
281+
case 'zip':
282+
case 'rar':
283+
case 'gz':
284+
return 'FileZip';
285+
case 'csv':
286+
return 'FileCsv';
287+
default:
288+
return 'FileArrowDown';
289+
}
290+
};

datahub-web-react/src/alchemy-components/components/Editor/extensions/markdownToHtml.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ marked.use({
1414

1515
/* Check if this is a file link (URL points to our file storage) */
1616
if (href && isFileUrl(href)) {
17-
return `<div class="file-node" ${FILE_ATTRS.url}="${href}" ${FILE_ATTRS.name}="${text}"></div>`;
17+
return `<span class="file-node" ${FILE_ATTRS.url}="${href}" ${FILE_ATTRS.name}="${text}"></span>`;
1818
}
1919

2020
/* Returning false allows marked to use the default link parser */

0 commit comments

Comments
 (0)