diff --git a/src/Explorer/Explorer.ts b/src/Explorer/Explorer.ts index 0369765f9..38c398881 100644 --- a/src/Explorer/Explorer.ts +++ b/src/Explorer/Explorer.ts @@ -87,6 +87,7 @@ import { ContextualPaneBase } from "./Panes/ContextualPaneBase"; import TabsBase from "./Tabs/TabsBase"; import { CommandButtonComponentProps } from "./Controls/CommandButton/CommandButtonComponent"; import { updateUserContext, userContext } from "../UserContext"; +import { stringToBlob } from "../Utils/BlobUtils"; BindingHandlersRegisterer.registerBindingHandlers(); // Hold a reference to ComponentRegisterer to prevent transpiler to ignore import @@ -2621,9 +2622,11 @@ export default class Explorer { throw new Error(error); } + const clearMessage = NotificationConsoleUtils.logConsoleProgress(`Downloading ${notebookFile.path}`); + return this.notebookManager?.notebookContentClient.readFileContent(notebookFile.path).then( (content: string) => { - const blob = new Blob([content], { type: "octet/stream" }); + const blob = stringToBlob(content, "text/plain"); if (navigator.msSaveBlob) { // for IE and Edge navigator.msSaveBlob(blob, notebookFile.name); @@ -2640,12 +2643,16 @@ export default class Explorer { downloadLink.click(); downloadLink.remove(); } + + clearMessage(); }, (error: any) => { NotificationConsoleUtils.logConsoleMessage( ConsoleDataType.Error, `Could not download notebook ${JSON.stringify(error)}` ); + + clearMessage(); } ); } diff --git a/src/Explorer/Notebook/NotebookContentClient.ts b/src/Explorer/Notebook/NotebookContentClient.ts index a3e52327e..d745c7e4f 100644 --- a/src/Explorer/Notebook/NotebookContentClient.ts +++ b/src/Explorer/Notebook/NotebookContentClient.ts @@ -194,18 +194,24 @@ export class NotebookContentClient { }); } - public readFileContent(filePath: string): Promise { - const fileType = NotebookUtil.isNotebookFile(filePath) ? "notebook" : "file"; - return this.contentProvider - .get(this.getServerConfig(), filePath, { type: fileType, format: "text", content: 1 }) - .toPromise() - .then(xhr => { - const content = (xhr.response as any).content; - if (!content) { - throw new Error("No content read"); - } + public async readFileContent(filePath: string): Promise { + const xhr = await this.contentProvider.get(this.getServerConfig(), filePath, { content: 1 }).toPromise(); + const content = (xhr.response as any).content; + if (!content) { + throw new Error("No content read"); + } + + const format = (xhr.response as any).format; + switch (format) { + case "text": + return content; + case "base64": + return atob(content); + case "json": return stringifyNotebook(content); - }); + default: + throw new Error(`Unsupported content format ${format}`); + } } private deleteNotebookFile(path: string): Promise { diff --git a/src/Utils/BlobUtils.ts b/src/Utils/BlobUtils.ts new file mode 100644 index 000000000..a08391a4d --- /dev/null +++ b/src/Utils/BlobUtils.ts @@ -0,0 +1,17 @@ +export const stringToBlob = (data: string, contentType: string, sliceSize = 512): Blob => { + const byteArrays = []; + + for (let offset = 0; offset < data.length; offset += sliceSize) { + const slice = data.slice(offset, offset + sliceSize); + + const byteNumbers = new Array(slice.length); + for (let i = 0; i < slice.length; i++) { + byteNumbers[i] = slice.charCodeAt(i); + } + + const byteArray = new Uint8Array(byteNumbers); + byteArrays.push(byteArray); + } + + return new Blob(byteArrays, { type: contentType }); +};