Skip to content

Commit

Permalink
refactor(editor): add native clipboard extension
Browse files Browse the repository at this point in the history
  • Loading branch information
fundon committed Jan 3, 2025
1 parent ee5f13c commit 14bc7cd
Show file tree
Hide file tree
Showing 7 changed files with 61 additions and 23 deletions.
29 changes: 15 additions & 14 deletions blocksuite/affine/block-image/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type {
ImageBlockModel,
ImageBlockProps,
} from '@blocksuite/affine-model';
import { NativeClipboardProvider } from '@blocksuite/affine-shared/services';
import {
downloadBlob,
humanFileSize,
Expand All @@ -12,7 +13,6 @@ import {
} from '@blocksuite/affine-shared/utils';
import type { BlockStdScope, EditorHost } from '@blocksuite/block-std';
import { GfxControllerIdentifier } from '@blocksuite/block-std/gfx';
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
import { Bound, type IVec, Point, Vec } from '@blocksuite/global/utils';
import type { BlockModel } from '@blocksuite/store';

Expand Down Expand Up @@ -200,7 +200,7 @@ export async function resetImageSize(
});
}

function convertToString(blob: Blob): Promise<string | null> {
function convertToDataURL(blob: Blob): Promise<string | null> {
return new Promise(resolve => {
const reader = new FileReader();
reader.addEventListener('load', _ => resolve(reader.result as string));
Expand Down Expand Up @@ -234,25 +234,26 @@ function convertToPng(blob: Blob): Promise<Blob | null> {
export async function copyImageBlob(
block: ImageBlockComponent | ImageEdgelessBlockComponent
) {
const { host, model } = block;
const { host, model, std } = block;
let blob = await getImageBlob(model);
if (!blob) {
console.error('Failed to get image blob');
return;
}

let copied = false;

try {
// @ts-expect-error FIXME: BS-2239
if (window.apis?.clipboard?.copyAsImageFromString) {
const dataURL = await convertToString(blob);
if (!dataURL)
throw new BlockSuiteError(
ErrorCode.DefaultRuntimeError,
'Cant convert a blob to data URL.'
);
// @ts-expect-error FIXME: BS-2239
await window.apis.clipboard?.copyAsImageFromString(dataURL);
} else {
// Copies the image directly without converting it to png in Electron.
const copyAsImage = std.getOptional(NativeClipboardProvider)?.copyAsImage;
if (copyAsImage) {
const dataURL = await convertToDataURL(blob);
if (dataURL) {
copied = await copyAsImage(dataURL);
}
}

if (!copied) {
// DOMException: Type image/jpeg not supported on write.
if (blob.type !== 'image/png') {
const pngBlob = await convertToPng(blob);
Expand Down
1 change: 1 addition & 0 deletions blocksuite/affine/shared/src/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export * from './editor-setting-service';
export * from './embed-option-service';
export * from './font-loader';
export * from './generate-url-service';
export * from './native-clipboard-service';
export * from './notification-service';
export * from './page-viewport-service';
export * from './parse-url-service';
Expand Down
26 changes: 26 additions & 0 deletions blocksuite/affine/shared/src/services/native-clipboard-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { ExtensionType } from '@blocksuite/block-std';
import { createIdentifier } from '@blocksuite/global/di';

/**
* Copies the image directly without converting it to png in Electron.
*
* In the web, it can only be converted to png before it can be written to the clipboard,
* or it will throw an exception: `DOMException: Type image/jpeg not supported on write.`
*/
export interface NativeClipboardService {
copyAsImage(dataURL: string): Promise<boolean>;
}

export const NativeClipboardProvider = createIdentifier<NativeClipboardService>(
'NativeClipboardService'
);

export function NativeClipboardExtension(
nativeClipboardProvider: NativeClipboardService
): ExtensionType {
return {
setup: di => {
di.addImpl(NativeClipboardProvider, nativeClipboardProvider);
},
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,5 @@ export const edgelessToBlob = async (
};

export const writeImageBlobToClipboard = async (blob: Blob) => {
// @ts-expect-error FIXME: BS-2239
if (window.apis?.clipboard?.copyAsImageFromString) {
// @ts-expect-error FIXME: BS-2239
await window.apis.clipboard?.copyAsImageFromString(blob);
} else {
await navigator.clipboard.write([new ClipboardItem({ [blob.type]: blob })]);
}
await navigator.clipboard.write([new ClipboardItem({ [blob.type]: blob })]);
};
7 changes: 5 additions & 2 deletions packages/frontend/apps/electron/src/main/clipboard/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import { clipboard, nativeImage } from 'electron';
import type { NamespaceHandlers } from '../type';

export const clipboardHandlers = {
copyAsImageFromString: async (_: IpcMainInvokeEvent, dataURL: string) => {
clipboard.writeImage(nativeImage.createFromDataURL(dataURL));
copyAsImage: async (_: IpcMainInvokeEvent, dataURL: string) => {
const image = nativeImage.createFromDataURL(dataURL);
if (image.isEmpty()) return false;
clipboard.writeImage(image);
return true;
},
} satisfies NamespaceHandlers;
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import {
patchEdgelessClipboard,
patchEmbedLinkedDocBlockConfig,
patchForAttachmentEmbedViews,
patchForClipboardInElectron,
patchForMobile,
patchForSharedPage,
patchGenerateDocUrlExtension,
Expand Down Expand Up @@ -170,6 +171,9 @@ const usePatchSpecs = (shared: boolean, mode: DocMode) => {
if (BUILD_CONFIG.isMobileEdition) {
patched = patched.concat(patchForMobile());
}
if (BUILD_CONFIG.isElectron) {
patched = patched.concat(patchForClipboardInElectron(framework));
}
patched = patched.concat(
patchDocModeService(docService, docsService, editorService)
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
} from '@affine/component';
import { AIChatBlockSchema } from '@affine/core/blocksuite/blocks';
import { WorkspaceServerService } from '@affine/core/modules/cloud';
import { DesktopApiService } from '@affine/core/modules/desktop-api';
import { type DocService, DocsService } from '@affine/core/modules/doc';
import type { EditorService } from '@affine/core/modules/editor';
import { EditorSettingService } from '@affine/core/modules/editor-setting';
Expand Down Expand Up @@ -53,6 +54,7 @@ import {
EmbedLinkedDocBlockConfigExtension,
GenerateDocUrlExtension,
MobileSpecsPatches,
NativeClipboardExtension,
NotificationExtension,
ParseDocUrlExtension,
PeekViewExtension,
Expand Down Expand Up @@ -618,3 +620,10 @@ export function patchForAttachmentEmbedViews(
},
};
}

export function patchForClipboardInElectron(framework: FrameworkProvider) {
const desktopApi = framework.get(DesktopApiService);
return NativeClipboardExtension({
copyAsImage: desktopApi.handler.clipboard.copyAsImage,
});
}

0 comments on commit 14bc7cd

Please sign in to comment.