⚡️(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:
@@ -15,6 +15,7 @@ and this project adheres to
|
|||||||
|
|
||||||
- ⚡️(sw) stop to cache external resources likes videos #1655
|
- ⚡️(sw) stop to cache external resources likes videos #1655
|
||||||
- 💥(frontend) upgrade to ui-kit v2
|
- 💥(frontend) upgrade to ui-kit v2
|
||||||
|
- ⚡️(frontend) improve perf on upload and table of contents #1662
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
|||||||
@@ -75,27 +75,31 @@ const UploadLoaderBlockComponent = ({
|
|||||||
|
|
||||||
loopCheckDocMediaStatus(url)
|
loopCheckDocMediaStatus(url)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
// Replace the loading block with the resource block (image, audio, video, pdf ...)
|
// Add random delay to reduce collision probability during collaboration
|
||||||
try {
|
const randomDelay = Math.random() * 800;
|
||||||
editor.replaceBlocks(
|
setTimeout(() => {
|
||||||
[block.id],
|
// Replace the loading block with the resource block (image, audio, video, pdf ...)
|
||||||
[
|
try {
|
||||||
{
|
editor.replaceBlocks(
|
||||||
type: block.props.blockUploadType,
|
[block.id],
|
||||||
props: {
|
[
|
||||||
url: `${mediaUrl}${response.file}`,
|
{
|
||||||
showPreview: block.props.blockUploadShowPreview,
|
type: block.props.blockUploadType,
|
||||||
name: block.props.blockUploadName,
|
props: {
|
||||||
caption: '',
|
url: `${mediaUrl}${response.file}`,
|
||||||
backgroundColor: 'default',
|
showPreview: block.props.blockUploadShowPreview,
|
||||||
textAlignment: 'left',
|
name: block.props.blockUploadName,
|
||||||
},
|
caption: '',
|
||||||
} as never,
|
backgroundColor: 'default',
|
||||||
],
|
textAlignment: 'left',
|
||||||
);
|
},
|
||||||
} catch {
|
} as never,
|
||||||
/* During collaboration, another user might have updated the block */
|
],
|
||||||
}
|
);
|
||||||
|
} catch {
|
||||||
|
/* During collaboration, another user might have updated the block */
|
||||||
|
}
|
||||||
|
}, randomDelay);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Error analyzing file:', error);
|
console.error('Error analyzing file:', error);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Block } from '@blocknote/core';
|
||||||
import { captureException } from '@sentry/nextjs';
|
import { captureException } from '@sentry/nextjs';
|
||||||
import { useCallback, useEffect } from 'react';
|
import { useCallback, useEffect } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
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) => {
|
export const useUploadStatus = (editor: DocsBlockNoteEditor) => {
|
||||||
const ANALYZE_URL = 'media-check';
|
const ANALYZE_URL = 'media-check';
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
useEffect(() => {
|
/**
|
||||||
const unsubscribe = editor.onChange((_, context) => {
|
* Replace the resource block by a uploadLoader block to show analyzing status
|
||||||
const blocksChanges = context.getChanges();
|
*/
|
||||||
|
const replaceBlockWithUploadLoader = useCallback(
|
||||||
if (!blocksChanges.length) {
|
(block: Block) => {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const blockChanges = blocksChanges[0];
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
blockChanges.source.type !== 'local' ||
|
!block ||
|
||||||
blockChanges.type !== 'update' ||
|
!('url' in block.props) ||
|
||||||
!('url' in blockChanges.block.props) ||
|
('url' in block.props && !block.props.url.includes(ANALYZE_URL))
|
||||||
('url' in blockChanges.block.props &&
|
|
||||||
!blockChanges.block.props.url.includes(ANALYZE_URL))
|
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const blockUploadUrl = blockChanges.block.props.url;
|
const blockUploadUrl = block.props.url;
|
||||||
const blockUploadType = blockChanges.block.type;
|
const blockUploadType = block.type;
|
||||||
const blockUploadName = blockChanges.block.props.name;
|
const blockUploadName = block.props.name;
|
||||||
const blockUploadShowPreview =
|
const blockUploadShowPreview =
|
||||||
('showPreview' in blockChanges.block.props &&
|
('showPreview' in block.props && block.props.showPreview) || false;
|
||||||
blockChanges.block.props.showPreview) ||
|
|
||||||
false;
|
|
||||||
|
|
||||||
const timeoutId = setTimeout(() => {
|
try {
|
||||||
// Replace the resource block by a uploadLoader block
|
editor.replaceBlocks(
|
||||||
// to show analyzing status
|
[block.id],
|
||||||
try {
|
[
|
||||||
editor.replaceBlocks(
|
{
|
||||||
[blockChanges.block.id],
|
type: 'uploadLoader',
|
||||||
[
|
props: {
|
||||||
{
|
information: t('Analyzing file...'),
|
||||||
type: 'uploadLoader',
|
type: 'loading',
|
||||||
props: {
|
blockUploadName,
|
||||||
information: t('Analyzing file...'),
|
blockUploadType,
|
||||||
type: 'loading',
|
blockUploadUrl,
|
||||||
blockUploadName,
|
blockUploadShowPreview,
|
||||||
blockUploadType,
|
|
||||||
blockUploadUrl,
|
|
||||||
blockUploadShowPreview,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
],
|
},
|
||||||
);
|
],
|
||||||
} catch (error) {
|
);
|
||||||
captureException(error, {
|
} catch (error) {
|
||||||
extra: { info: 'Error replacing block for upload loader' },
|
captureException(error, {
|
||||||
});
|
extra: { info: 'Error replacing block for upload loader' },
|
||||||
}
|
});
|
||||||
}, 250);
|
}
|
||||||
|
},
|
||||||
|
[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 () => {
|
return () => {
|
||||||
clearTimeout(timeoutId);
|
clearTimeout(innerTimeoutId);
|
||||||
unsubscribe();
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
}, [editor, replaceBlockWithUploadLoader]);
|
||||||
return () => {
|
|
||||||
unsubscribe();
|
|
||||||
};
|
|
||||||
}, [editor, t]);
|
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user