feat(sheets-drawing-ui): add API to register custom URL image downloader (#6430)

Co-authored-by: weird94 <zhang9748@foxmail.com>
This commit is contained in:
WEI ZHANG
2026-01-07 17:19:01 +08:00
committed by GitHub
co-authored by weird94
parent e6dac104f9
commit 5bc20d9c01
2 changed files with 59 additions and 4 deletions
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import type { IDrawingSearch, Injector } from '@univerjs/core';
import type { IDisposable, IDrawingSearch, Injector } from '@univerjs/core';
import type { IEventBase } from '@univerjs/core/facade';
import type { ISheetFloatDom, ISheetImage } from '@univerjs/sheets-drawing';
import type { IDeleteDrawingCommandParams, IInsertDrawingCommandParams, ISetDrawingCommandParams } from '@univerjs/sheets-drawing-ui';
@@ -28,7 +28,7 @@ import type {
import { CanceledError, DrawingTypeEnum, ICommandService } from '@univerjs/core';
import { FUniver } from '@univerjs/core/facade';
import { IDrawingManagerService, SetDrawingSelectedOperation } from '@univerjs/drawing';
import { InsertSheetDrawingCommand, RemoveSheetDrawingCommand, SetSheetDrawingCommand } from '@univerjs/sheets-drawing-ui';
import { IBatchSaveImagesService, InsertSheetDrawingCommand, RemoveSheetDrawingCommand, SetSheetDrawingCommand } from '@univerjs/sheets-drawing-ui';
import { FOverGridImage } from './f-over-grid-image';
interface IBeforeOverGridImageInsertParam extends IEventBase {
@@ -476,6 +476,29 @@ export class FUniverDrawingMixin extends FUniver {
return this._injector.createInstance(FOverGridImage, drawing);
});
}
/**
* Register a custom image downloader for URL images
* @param downloader The downloader function that takes a URL and returns a base64 string
* @returns A disposable object to unregister the downloader
* @example
* ```ts
* const disposable = univerAPI.registerURLImageDownloader(async (url) => {
* const response = await fetch(url);
* const blob = await response.blob();
* const base64 = await new Promise<string>((resolve) => {
* const reader = new FileReader();
* reader.onloadend = () => resolve(reader.result as string);
* reader.readAsDataURL(blob);
* });
* return base64;
* });
* ```
*/
registerURLImageDownloader(downloader: (url: string) => Promise<string>): IDisposable {
const batchSaveImagesService = this._injector.get(IBatchSaveImagesService);
return batchSaveImagesService.registerURLImageDownloader(downloader);
}
}
FUniver.extend(FUniverDrawingMixin);
@@ -14,9 +14,9 @@
* limitations under the License.
*/
import type { ICellData, IRange, Nullable, Workbook } from '@univerjs/core';
import type { ICellData, IDisposable, IRange, Nullable, Workbook } from '@univerjs/core';
import type { IImageData } from '@univerjs/drawing';
import { createIdentifier, Disposable, IImageIoService, ImageSourceType, Inject, IUniverInstanceService, UniverInstanceType } from '@univerjs/core';
import { createIdentifier, Disposable, IImageIoService, ImageSourceType, Inject, IUniverInstanceService, toDisposable, UniverInstanceType } from '@univerjs/core';
import { SheetsSelectionsService } from '@univerjs/sheets';
declare global {
@@ -136,10 +136,20 @@ export interface IBatchSaveImagesService {
*/
getSelectionRowRange(): { startRow: number; endRow: number } | null;
/**
* Get all column indices that are within the current selection
*/
/**
* Get all column indices that are within the current selection
*/
getSelectionColumnIndices(): Set<number>;
/**
* Register a custom image downloader for URL images
* @param downloader The downloader function that takes a URL and returns a base64 string
* @returns A disposable object to unregister the downloader
*/
registerURLImageDownloader(downloader: (url: string) => Promise<string>): IDisposable;
}
export const IBatchSaveImagesService = createIdentifier<IBatchSaveImagesService>('sheets-drawing-ui.batch-save-images.service');
@@ -242,6 +252,8 @@ async function imageSourceToBlob(source: string, imageSourceType: ImageSourceTyp
}
export class BatchSaveImagesService extends Disposable implements IBatchSaveImagesService {
private _urlImageDownloader: Nullable<(url: string) => Promise<string>> = null;
constructor(
@IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService,
@Inject(SheetsSelectionsService) private readonly _selectionService: SheetsSelectionsService,
@@ -250,6 +262,15 @@ export class BatchSaveImagesService extends Disposable implements IBatchSaveImag
super();
}
registerURLImageDownloader(downloader: (url: string) => Promise<string>): IDisposable {
this._urlImageDownloader = downloader;
return toDisposable(() => {
if (this._urlImageDownloader === downloader) {
this._urlImageDownloader = null;
}
});
}
getCellImagesInSelection(): ICellImageInfo[] {
const workbook = this._univerInstanceService.getCurrentUnitForType<Workbook>(UniverInstanceType.UNIVER_SHEET);
if (!workbook) return [];
@@ -666,6 +687,17 @@ export class BatchSaveImagesService extends Disposable implements IBatchSaveImag
const imageUrl = await this._imageIoService.getImage(imageInfo.source);
return imageSourceToBlob(imageUrl, ImageSourceType.URL);
}
if (imageInfo.imageSourceType === ImageSourceType.URL && this._urlImageDownloader) {
try {
const base64 = await this._urlImageDownloader(imageInfo.source);
return imageSourceToBlob(base64, ImageSourceType.BASE64);
} catch (error) {
console.error(`Custom downloader failed for ${imageInfo.source}, falling back to default fetch:`, error);
// Fallback to default fetch if custom downloader fails
}
}
return imageSourceToBlob(imageInfo.source, imageInfo.imageSourceType);
}
}