From dc50a05d78566a9af8ae6a64b6f19dc80551289f Mon Sep 17 00:00:00 2001 From: ZHUO Xu Date: Wed, 9 Oct 2024 14:54:15 +0800 Subject: [PATCH] feat: support uploading binary file (#64) --- packages/secretnote-lite/package.json | 2 +- .../src/modules/file/service.ts | 13 +++++-- .../secretnote-lite/src/modules/file/view.tsx | 36 ++++++++++++++----- packages/secretnote-lite/src/utils/file.ts | 25 ++++++++++--- 4 files changed, 59 insertions(+), 17 deletions(-) diff --git a/packages/secretnote-lite/package.json b/packages/secretnote-lite/package.json index 9161a1e0..f9ff756a 100644 --- a/packages/secretnote-lite/package.json +++ b/packages/secretnote-lite/package.json @@ -1,6 +1,6 @@ { "name": "@alipay/secretnote-lite", - "version": "0.0.44", + "version": "0.0.45", "license": "Apache-2.0", "author": "vectorse@126.com", "repository": "https://github.com/secretflow/secretnote/tree/main/packages/secretnote", diff --git a/packages/secretnote-lite/src/modules/file/service.ts b/packages/secretnote-lite/src/modules/file/service.ts index 0258a3b2..1e735926 100644 --- a/packages/secretnote-lite/src/modules/file/service.ts +++ b/packages/secretnote-lite/src/modules/file/service.ts @@ -93,7 +93,15 @@ export class FileService { return list.content.some((file: any) => file.name === name); } - async uploadFile(nodeData: DataNode, name: string, content: string) { + /** + * Upload file via Jupyter Server. For binary files, use `format='base64'` and base64-encoded `content`. + */ + async uploadFile( + nodeData: DataNode, + name: string, + content: string, + format: 'text' | 'base64' = 'text', + ) { const serverId = nodeData.key as string; const server = await this.serverManager.getServerDetail(serverId); if (server) { @@ -105,7 +113,8 @@ export class FileService { name, path, type: 'file', - format: 'text', + format, + ...(format === 'base64' ? { mimetype: 'application/octet-stream' } : {}), }); } } diff --git a/packages/secretnote-lite/src/modules/file/view.tsx b/packages/secretnote-lite/src/modules/file/view.tsx index 3d53e72e..3ce1ffc3 100644 --- a/packages/secretnote-lite/src/modules/file/view.tsx +++ b/packages/secretnote-lite/src/modules/file/view.tsx @@ -81,11 +81,18 @@ export const FileComponent = () => { } }; - const uploadFile = async (nodeData: DataNode, file: File) => { + /** + * Handle the upload file action. + */ + const uploadFile = async ( + nodeData: DataNode, + file: File, + format: 'text' | 'base64' = 'text', + ) => { setIsUploading(true); - const content = await readFile(file); + const content = await readFile(file, format); try { - await fileService.uploadFile(nodeData, file.name, content); + await fileService.uploadFile(nodeData, file.name, content, format); await fileService.getFileTree(); message.success(l10n.t('文件上传成功')); } catch (e) { @@ -95,7 +102,7 @@ export const FileComponent = () => { } }; - const uploadRender = (nodeData: DataNode) => { + const uploadRender = (nodeData: DataNode, format: 'text' | 'base64' = 'text') => { const props: UploadProps = { beforeUpload: async (file) => { const isExisted = await fileService.isFileExist(nodeData, file.name); @@ -110,19 +117,25 @@ export const FileComponent = () => { cancelText: l10n.t('取消'), okType: 'danger', async onOk(close) { - await uploadFile(nodeData, file); + await uploadFile(nodeData, file, format); return close(Promise.resolve); }, }); } else { - uploadFile(nodeData, file); + uploadFile(nodeData, file, format); } return false; }, fileList: [], }; - return {l10n.t('上传到文件夹')}; + return ( + + {l10n.t( + format === 'text' ? '上传到文件夹 (文本文件)' : '上传到文件夹 (二进制文件)', + )} + + ); }; const getFileIcon = (nodeData: DataNode) => { @@ -142,8 +155,13 @@ export const FileComponent = () => { const folderMenuItems: Menu[] = [ { - key: 'upload', - label: uploadRender(nodeData), + key: 'uploadText', + label: uploadRender(nodeData, 'text'), + icon: , + }, + { + key: 'uploadBinary', + label: uploadRender(nodeData, 'base64'), icon: , }, ]; diff --git a/packages/secretnote-lite/src/utils/file.ts b/packages/secretnote-lite/src/utils/file.ts index 5e46ddaa..7e12de79 100644 --- a/packages/secretnote-lite/src/utils/file.ts +++ b/packages/secretnote-lite/src/utils/file.ts @@ -39,14 +39,29 @@ const saveAs = (blob: Blob, filename: string) => { } }; -export async function readFile(file: File): Promise { +/** + * Read file as text or base64 string. + */ +export async function readFile( + file: File, + format: 'text' | 'base64' = 'text', +): Promise { return await new Promise((resolve, reject) => { try { const reader = new FileReader(); - reader.addEventListener('loadend', () => { - resolve(reader.result?.toString() || ''); - }); - reader.readAsText(file); + if (format === 'text') { + reader.addEventListener('loadend', () => { + resolve(reader.result?.toString() || ''); + }); + reader.readAsText(file); + } else if (format === 'base64') { + reader.addEventListener('loadend', () => { + // remove base64 prelude added by the browser + const base64 = (reader.result as string).replace(/data:.*base64,/, ''); + resolve(base64 || ''); + }); + reader.readAsDataURL(file); + } } catch (e) { reject(e); }