⚡️(frontend) improve UploadFile process
We notices that `context.getChanges` was very greedy, on a large document with multiple users collaborating, it caused performance issues. We change the way that we track a upload by listening onUploadEnd event instead of tracking all changes in the document. When a doc opens, we check if there are any ongoing uploads and resume them. We fix as well a race condition that could happen when multiple collaborators were on a document during an upload.
This commit is contained in:
@@ -75,27 +75,31 @@ const UploadLoaderBlockComponent = ({
|
||||
|
||||
loopCheckDocMediaStatus(url)
|
||||
.then((response) => {
|
||||
// Replace the loading block with the resource block (image, audio, video, pdf ...)
|
||||
try {
|
||||
editor.replaceBlocks(
|
||||
[block.id],
|
||||
[
|
||||
{
|
||||
type: block.props.blockUploadType,
|
||||
props: {
|
||||
url: `${mediaUrl}${response.file}`,
|
||||
showPreview: block.props.blockUploadShowPreview,
|
||||
name: block.props.blockUploadName,
|
||||
caption: '',
|
||||
backgroundColor: 'default',
|
||||
textAlignment: 'left',
|
||||
},
|
||||
} as never,
|
||||
],
|
||||
);
|
||||
} catch {
|
||||
/* During collaboration, another user might have updated the block */
|
||||
}
|
||||
// Add random delay to reduce collision probability during collaboration
|
||||
const randomDelay = Math.random() * 800;
|
||||
setTimeout(() => {
|
||||
// Replace the loading block with the resource block (image, audio, video, pdf ...)
|
||||
try {
|
||||
editor.replaceBlocks(
|
||||
[block.id],
|
||||
[
|
||||
{
|
||||
type: block.props.blockUploadType,
|
||||
props: {
|
||||
url: `${mediaUrl}${response.file}`,
|
||||
showPreview: block.props.blockUploadShowPreview,
|
||||
name: block.props.blockUploadName,
|
||||
caption: '',
|
||||
backgroundColor: 'default',
|
||||
textAlignment: 'left',
|
||||
},
|
||||
} as never,
|
||||
],
|
||||
);
|
||||
} catch {
|
||||
/* During collaboration, another user might have updated the block */
|
||||
}
|
||||
}, randomDelay);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error analyzing file:', error);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Block } from '@blocknote/core';
|
||||
import { captureException } from '@sentry/nextjs';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -36,73 +37,93 @@ export const useUploadFile = (docId: string) => {
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* When we upload a file it can takes some time to analyze it (e.g. virus scan).
|
||||
* This hook listen to upload end and replace the uploaded block by a uploadLoader
|
||||
* block to show analyzing status.
|
||||
* The uploadLoader block will then handle the status display until the analysis is done
|
||||
* then replaced by the final block (e.g. image, pdf, etc.).
|
||||
* @param editor
|
||||
*/
|
||||
export const useUploadStatus = (editor: DocsBlockNoteEditor) => {
|
||||
const ANALYZE_URL = 'media-check';
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribe = editor.onChange((_, context) => {
|
||||
const blocksChanges = context.getChanges();
|
||||
|
||||
if (!blocksChanges.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const blockChanges = blocksChanges[0];
|
||||
|
||||
/**
|
||||
* Replace the resource block by a uploadLoader block to show analyzing status
|
||||
*/
|
||||
const replaceBlockWithUploadLoader = useCallback(
|
||||
(block: Block) => {
|
||||
if (
|
||||
blockChanges.source.type !== 'local' ||
|
||||
blockChanges.type !== 'update' ||
|
||||
!('url' in blockChanges.block.props) ||
|
||||
('url' in blockChanges.block.props &&
|
||||
!blockChanges.block.props.url.includes(ANALYZE_URL))
|
||||
!block ||
|
||||
!('url' in block.props) ||
|
||||
('url' in block.props && !block.props.url.includes(ANALYZE_URL))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const blockUploadUrl = blockChanges.block.props.url;
|
||||
const blockUploadType = blockChanges.block.type;
|
||||
const blockUploadName = blockChanges.block.props.name;
|
||||
const blockUploadUrl = block.props.url;
|
||||
const blockUploadType = block.type;
|
||||
const blockUploadName = block.props.name;
|
||||
const blockUploadShowPreview =
|
||||
('showPreview' in blockChanges.block.props &&
|
||||
blockChanges.block.props.showPreview) ||
|
||||
false;
|
||||
('showPreview' in block.props && block.props.showPreview) || false;
|
||||
|
||||
const timeoutId = setTimeout(() => {
|
||||
// Replace the resource block by a uploadLoader block
|
||||
// to show analyzing status
|
||||
try {
|
||||
editor.replaceBlocks(
|
||||
[blockChanges.block.id],
|
||||
[
|
||||
{
|
||||
type: 'uploadLoader',
|
||||
props: {
|
||||
information: t('Analyzing file...'),
|
||||
type: 'loading',
|
||||
blockUploadName,
|
||||
blockUploadType,
|
||||
blockUploadUrl,
|
||||
blockUploadShowPreview,
|
||||
},
|
||||
try {
|
||||
editor.replaceBlocks(
|
||||
[block.id],
|
||||
[
|
||||
{
|
||||
type: 'uploadLoader',
|
||||
props: {
|
||||
information: t('Analyzing file...'),
|
||||
type: 'loading',
|
||||
blockUploadName,
|
||||
blockUploadType,
|
||||
blockUploadUrl,
|
||||
blockUploadShowPreview,
|
||||
},
|
||||
],
|
||||
);
|
||||
} catch (error) {
|
||||
captureException(error, {
|
||||
extra: { info: 'Error replacing block for upload loader' },
|
||||
});
|
||||
}
|
||||
}, 250);
|
||||
},
|
||||
],
|
||||
);
|
||||
} catch (error) {
|
||||
captureException(error, {
|
||||
extra: { info: 'Error replacing block for upload loader' },
|
||||
});
|
||||
}
|
||||
},
|
||||
[editor, t],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const imagesBlocks = editor?.document.filter(
|
||||
(block) =>
|
||||
block.type === 'image' && block.props.url.includes(ANALYZE_URL),
|
||||
);
|
||||
|
||||
imagesBlocks.forEach((block) => {
|
||||
replaceBlockWithUploadLoader(block as Block);
|
||||
});
|
||||
}, [editor, replaceBlockWithUploadLoader]);
|
||||
|
||||
/**
|
||||
* Handle upload end to replace the upload block by a uploadLoader
|
||||
* block to show analyzing status
|
||||
*/
|
||||
useEffect(() => {
|
||||
editor.onUploadEnd((blockId) => {
|
||||
if (!blockId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const innerTimeoutId = setTimeout(() => {
|
||||
const block = editor.getBlock({ id: blockId });
|
||||
|
||||
replaceBlockWithUploadLoader(block as Block);
|
||||
}, 300);
|
||||
|
||||
return () => {
|
||||
clearTimeout(timeoutId);
|
||||
unsubscribe();
|
||||
clearTimeout(innerTimeoutId);
|
||||
};
|
||||
});
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
}, [editor, t]);
|
||||
}, [editor, replaceBlockWithUploadLoader]);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user