Skip to content

fix: Loading indicator when dropping/pasting files #1069

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 32 additions & 11 deletions packages/core/src/api/parsers/handleFileInsertion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,25 +106,24 @@ export async function handleFileInsertion<

const file = items[i].getAsFile();
if (file) {
const updateData = await editor.uploadFile(file);
const fileBlock = {
type: fileBlockType,
props: {
name: file.name,
loading: true,
},
} as PartialBlock<BSchema, I, S>;

const fileBlock =
typeof updateData === "string"
? ({
type: fileBlockType,
props: {
name: file.name,
url: updateData,
},
} as PartialBlock<BSchema, I, S>)
: { type: fileBlockType, ...updateData };
let insertedBlockId: string | undefined = undefined;

if (event.type === "paste") {
editor.insertBlocks(
[fileBlock],
editor.getTextCursorPosition().block,
"after"
);

insertedBlockId = editor.getTextCursorPosition().nextBlock!.id;
}

if (event.type === "drop") {
Expand All @@ -144,7 +143,29 @@ export async function handleFileInsertion<
);

editor.insertBlocks([fileBlock], blockInfo.id, "after");

insertedBlockId = editor._tiptapEditor.state.doc
.resolve(blockInfo.endPos + 2)
.node().attrs.id;
}

if (!insertedBlockId) {
return;
}

const updateData = await editor.uploadFile(file);

const updatedFileBlock =
typeof updateData === "string"
? ({
props: {
url: updateData,
loading: false,
},
} as PartialBlock<BSchema, I, S>)
: { ...updateData, props: { ...updateData.props, loading: false } };

editor.updateBlock(insertedBlockId, updatedFileBlock);
}
}
}
20 changes: 20 additions & 0 deletions packages/core/src/blocks/AudioBlockContent/AudioBlockContent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ export const audioPropSchema = {
default: "" as const,
},

loading: {
default: false,
},
showPreview: {
default: true,
},
Expand All @@ -50,6 +53,15 @@ export const audioRender = (
block: BlockFromConfig<typeof audioBlockConfig, any, any>,
editor: BlockNoteEditor<any, any, any>
) => {
if (block.props.loading) {
const loading = document.createElement("div");
loading.className = "bn-file-loading-preview";
loading.textContent = "Loading...";
return {
dom: loading,
};
}

const wrapper = document.createElement("div");
wrapper.className = "bn-file-block-content-wrapper";

Expand Down Expand Up @@ -124,6 +136,14 @@ export const audioParse = (
export const audioToExternalHTML = (
block: BlockFromConfig<typeof audioBlockConfig, any, any>
) => {
if (block.props.loading) {
const loading = document.createElement("div");
loading.textContent = "Loading...";
return {
dom: loading,
};
}

if (!block.props.url) {
const div = document.createElement("p");
div.textContent = "Add audio";
Expand Down
21 changes: 21 additions & 0 deletions packages/core/src/blocks/FileBlockContent/FileBlockContent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ export const filePropSchema = {
caption: {
default: "" as const,
},

loading: {
default: false,
},
} satisfies PropSchema;

export const fileBlockConfig = {
Expand All @@ -42,6 +46,15 @@ export const fileRender = (
block: BlockFromConfig<typeof fileBlockConfig, any, any>,
editor: BlockNoteEditor<any, any, any>
) => {
if (block.props.loading) {
const loading = document.createElement("div");
loading.className = "bn-file-loading-preview";
loading.textContent = "Loading...";
return {
dom: loading,
};
}

// Wrapper element to set the file alignment, contains both file/file
// upload dashboard and caption.
const wrapper = document.createElement("div");
Expand Down Expand Up @@ -91,6 +104,14 @@ export const fileParse = (element: HTMLElement) => {
export const fileToExternalHTML = (
block: BlockFromConfig<typeof fileBlockConfig, any, any>
) => {
if (block.props.loading) {
const loading = document.createElement("div");
loading.textContent = "Loading...";
return {
dom: loading,
};
}

if (!block.props.url) {
const div = document.createElement("p");
div.textContent = "Add file";
Expand Down
20 changes: 20 additions & 0 deletions packages/core/src/blocks/ImageBlockContent/ImageBlockContent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ export const imagePropSchema = {
default: "" as const,
},

loading: {
default: false,
},
showPreview: {
default: true,
},
Expand All @@ -56,6 +59,15 @@ export const imageRender = (
block: BlockFromConfig<typeof imageBlockConfig, any, any>,
editor: BlockNoteEditor<any, any, any>
) => {
if (block.props.loading) {
const loading = document.createElement("div");
loading.className = "bn-file-loading-preview";
loading.textContent = "Loading...";
return {
dom: loading,
};
}

const wrapper = document.createElement("div");
wrapper.className = "bn-file-block-content-wrapper";

Expand Down Expand Up @@ -143,6 +155,14 @@ export const imageParse = (
export const imageToExternalHTML = (
block: BlockFromConfig<typeof imageBlockConfig, any, any>
) => {
if (block.props.loading) {
const loading = document.createElement("div");
loading.textContent = "Loading...";
return {
dom: loading,
};
}

if (!block.props.url) {
const div = document.createElement("p");
div.textContent = "Add image";
Expand Down
20 changes: 20 additions & 0 deletions packages/core/src/blocks/VideoBlockContent/VideoBlockContent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ export const videoPropSchema = {
default: "" as const,
},

loading: {
default: false,
},
showPreview: {
default: true,
},
Expand All @@ -56,6 +59,15 @@ export const videoRender = (
block: BlockFromConfig<typeof videoBlockConfig, any, any>,
editor: BlockNoteEditor<any, any, any>
) => {
if (block.props.loading) {
const loading = document.createElement("div");
loading.className = "bn-file-loading-preview";
loading.textContent = "Loading...";
return {
dom: loading,
};
}

const wrapper = document.createElement("div");
wrapper.className = "bn-file-block-content-wrapper";

Expand Down Expand Up @@ -141,6 +153,14 @@ export const videoParse = (
export const videoToExternalHTML = (
block: BlockFromConfig<typeof videoBlockConfig, any, any>
) => {
if (block.props.loading) {
const loading = document.createElement("div");
loading.textContent = "Loading...";
return {
dom: loading,
};
}

if (!block.props.url) {
const div = document.createElement("p");
div.textContent = "Add video";
Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/schema/blocks/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ export type FileBlockConfig = {
default: "";
};

// Whether the file is currently being loaded
loading?: {
default: false;
};
// Whether to show the file preview or the name only.
// This is useful for some file blocks, but not all
// (e.g.: not relevant for default "file" block which doesn;'t show previews)
Expand Down
51 changes: 30 additions & 21 deletions packages/react/src/blocks/AudioBlockContent/AudioBlockContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,29 +71,38 @@ export const AudioToExternalHTML = (
};

export const ReactAudioBlock = createReactBlockSpec(audioBlockConfig, {
render: (props) => (
<div className={"bn-file-block-content-wrapper"}>
{props.block.props.url === "" ? (
<AddFileButton
{...props}
editor={props.editor as any}
buttonText={props.editor.dictionary.file_blocks.audio.add_button_text}
buttonIcon={<RiVolumeUpFill size={24} />}
/>
) : !props.block.props.showPreview ? (
<FileAndCaptionWrapper block={props.block} editor={props.editor as any}>
<DefaultFilePreview
block={props.block}
render: (props) =>
props.block.props.loading ? (
<div>Loading...</div>
) : (
<div className={"bn-file-block-content-wrapper"}>
{props.block.props.url === "" ? (
<AddFileButton
{...props}
editor={props.editor as any}
buttonText={
props.editor.dictionary.file_blocks.audio.add_button_text
}
buttonIcon={<RiVolumeUpFill size={24} />}
/>
</FileAndCaptionWrapper>
) : (
<FileAndCaptionWrapper block={props.block} editor={props.editor as any}>
<AudioPreview block={props.block} editor={props.editor as any} />
</FileAndCaptionWrapper>
)}
</div>
),
) : !props.block.props.showPreview ? (
<FileAndCaptionWrapper
block={props.block}
editor={props.editor as any}>
<DefaultFilePreview
block={props.block}
editor={props.editor as any}
/>
</FileAndCaptionWrapper>
) : (
<FileAndCaptionWrapper
block={props.block}
editor={props.editor as any}>
<AudioPreview block={props.block} editor={props.editor as any} />
</FileAndCaptionWrapper>
)}
</div>
),
parse: audioParse,
toExternalHTML: (props) => <AudioToExternalHTML {...props} />,
});
39 changes: 23 additions & 16 deletions packages/react/src/blocks/FileBlockContent/FileBlockContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,25 +40,32 @@ export const FileToExternalHTML = (
};

export const ReactFileBlock = createReactBlockSpec(fileBlockConfig, {
render: (props) => (
<div className={"bn-file-block-content-wrapper"}>
{props.block.props.url === "" ? (
<AddFileButton
block={props.block}
editor={props.editor as any}
buttonIcon={<RiFile2Line size={24} />}
buttonText={props.editor.dictionary.file_blocks.file.add_button_text}
/>
) : (
<FileAndCaptionWrapper block={props.block} editor={props.editor as any}>
<DefaultFilePreview
render: (props) =>
props.block.props.loading ? (
<div>Loading...</div>
) : (
<div className={"bn-file-block-content-wrapper"}>
{props.block.props.url === "" ? (
<AddFileButton
block={props.block}
editor={props.editor as any}
buttonIcon={<RiFile2Line size={24} />}
buttonText={
props.editor.dictionary.file_blocks.file.add_button_text
}
/>
</FileAndCaptionWrapper>
)}
</div>
),
) : (
<FileAndCaptionWrapper
block={props.block}
editor={props.editor as any}>
<DefaultFilePreview
block={props.block}
editor={props.editor as any}
/>
</FileAndCaptionWrapper>
)}
</div>
),
parse: fileParse,
toExternalHTML: (props) => <FileToExternalHTML {...props} />,
});
Loading
Loading