refactor: image preview plugin (#3457)

This commit is contained in:
Alex Yang 2023-07-29 00:18:28 -07:00 committed by GitHub
parent be3909370e
commit 52809a2783
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 129 additions and 45 deletions

View File

@ -81,6 +81,9 @@ export const createGlobalThis = () => {
TextDecoder: globalThis.TextDecoder,
Request: globalThis.Request,
Error: globalThis.Error,
// bookmark uses these
Blob: globalThis.Blob,
ClipboardItem: globalThis.ClipboardItem,
// fixme: use our own db api
indexedDB: globalThis.indexedDB,

View File

@ -37,6 +37,7 @@ const builtinPluginUrl = new Set([
'/plugins/bookmark',
'/plugins/copilot',
'/plugins/hello-world',
'/plugins/image-preview',
]);
const logger = new DebugLogger('register-plugins');

View File

@ -1,11 +1,13 @@
import { BlockHubWrapper } from '@affine/component/block-hub';
import { BlockSuiteEditor } from '@affine/component/block-suite-editor';
import { ImagePreviewModal } from '@affine/component/image-preview-modal';
import { initEmptyPage } from '@affine/env/blocksuite';
import { WorkspaceFlavour } from '@affine/env/workspace';
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
import { ImagePreviewModal } from '@affine/image-preview-plugin/src/component';
import { rootBlockHubAtom } from '@affine/workspace/atom';
import { getOrCreateWorkspace } from '@affine/workspace/manager';
import type { Meta } from '@storybook/react';
import { createPortal } from 'react-dom';
export default {
title: 'Component/ImagePreviewModal',
@ -52,6 +54,10 @@ export const Default = () => {
}}
>
<BlockSuiteEditor mode="page" page={page} onInit={initEmptyPage} />
{createPortal(
<ImagePreviewModal pageId={page.id} workspace={page.workspace} />,
document.body
)}
</div>
<BlockHubWrapper
style={{

View File

@ -18,6 +18,9 @@
{
"path": "../../packages/workspace"
},
{
"path": "../../plugins/image-preview"
},
{
"path": "./tsconfig.node.json"
}

View File

@ -45,7 +45,8 @@ const external = [
/^@toeverything/,
// react
/^react/,
'react',
/^react\//,
/^react-dom/,
// store
@ -72,11 +73,11 @@ const json: z.infer<typeof packageJsonInputSchema> = await readFile(
)
.then(text => JSON.parse(text))
.then(async json => {
const { success } = await packageJsonInputSchema.safeParseAsync(json);
if (success) {
const result = await packageJsonInputSchema.safeParseAsync(json);
if (result.success) {
return json;
} else {
throw new Error('invalid package.json');
throw new Error('invalid package.json', result.error);
}
});

View File

@ -8,8 +8,7 @@ import { Skeleton } from '@mui/material';
import { use } from 'foxact/use';
import { useAtomValue } from 'jotai';
import type { CSSProperties, ReactElement } from 'react';
import { lazy, memo, Suspense, useCallback, useEffect, useRef } from 'react';
import { createPortal } from 'react-dom';
import { memo, Suspense, useCallback, useEffect, useRef } from 'react';
import type { FallbackProps } from 'react-error-boundary';
import { ErrorBoundary } from 'react-error-boundary';
@ -41,12 +40,6 @@ declare global {
var currentEditor: EditorContainer | undefined;
}
const ImagePreviewModal = lazy(() =>
import('../image-preview-modal').then(module => ({
default: module.ImagePreviewModal,
}))
);
const BlockSuiteEditorImpl = (props: EditorProps): ReactElement => {
const { onLoad, page, mode, style } = props;
if (!page.loaded) {
@ -191,17 +184,6 @@ export const BlockSuiteEditor = memo(function BlockSuiteEditor(
<Suspense fallback={<BlockSuiteFallback />}>
<BlockSuiteEditorImpl {...props} />
</Suspense>
{props.page && (
<Suspense fallback={null}>
{createPortal(
<ImagePreviewModal
workspace={props.page.workspace}
pageId={props.page.id}
/>,
document.body
)}
</Suspense>
)}
</ErrorBoundary>
);
});

View File

@ -0,0 +1,20 @@
{
"name": "@affine/image-preview-plugin",
"version": "0.8.0-canary.0",
"description": "Image preview plugin",
"affinePlugin": {
"release": true,
"entry": {
"core": "./src/index.ts"
}
},
"dependencies": {
"@affine/component": "workspace:*",
"@blocksuite/icons": "^2.1.27",
"@toeverything/plugin-infra": "workspace:*",
"@toeverything/theme": "^0.7.9",
"clsx": "^2.0.0",
"react-error-boundary": "^4.0.10",
"swr": "2.1.5"
}
}

View File

@ -0,0 +1,11 @@
import type { Page } from '@blocksuite/store';
import { ImagePreviewModal } from './component';
export type AppProps = {
page: Page;
};
export const App = ({ page }: AppProps) => {
return <ImagePreviewModal pageId={page.id} workspace={page.workspace} />;
};

View File

@ -5,8 +5,8 @@ export const hasAnimationPlayedAtom = atom<boolean | null>(true);
previewBlockIdAtom.onMount = set => {
const callback = (event: MouseEvent) => {
const target = event.target;
if (target instanceof HTMLImageElement) {
const target = event.target as HTMLElement | null;
if (target?.tagName === 'IMG') {
const imageBlock = target.closest('affine-image');
if (imageBlock) {
const blockId = imageBlock.getAttribute('data-block-id');

View File

@ -1,5 +1,4 @@
import '@blocksuite/blocks';
import { Button, IconButton, Tooltip } from '@affine/component';
import type { ImageBlockModel } from '@blocksuite/blocks';
import { assertExists } from '@blocksuite/global/utils';
import {
@ -23,8 +22,6 @@ import type { FallbackProps } from 'react-error-boundary';
import { ErrorBoundary } from 'react-error-boundary';
import useSWR from 'swr';
import { Button, IconButton } from '../../ui/button';
import { Tooltip } from '../../ui/tooltip';
import { useZoomControls } from './hooks/use-zoom';
import {
buttonStyle,

View File

@ -0,0 +1,18 @@
import type { PluginContext } from '@toeverything/plugin-infra/entry';
import { createElement } from 'react';
import { createRoot } from 'react-dom/client';
import { App } from './app';
export const entry = (context: PluginContext) => {
context.register('editor', (div, editor) => {
const root = createRoot(div);
root.render(createElement(App, { page: editor.page }));
return () => {
root.unmount();
};
});
return () => {
// do nothing
};
};

View File

@ -0,0 +1,17 @@
{
"extends": "../../tsconfig.json",
"include": ["./src"],
"compilerOptions": {
"noEmit": false,
"outDir": "lib",
"jsx": "preserve"
},
"references": [
{
"path": "../../packages/plugin-infra"
},
{
"path": "../../packages/component"
}
]
}

View File

@ -1,16 +1,15 @@
import { spawnSync } from 'node:child_process';
import { spawn } from 'node:child_process';
spawnSync('yarn', ['-T', 'run', 'dev-plugin', '--plugin', 'bookmark'], {
const builtInPlugins = ['bookmark', 'hello-world', 'copilot', 'image-preview'];
for (const plugin of builtInPlugins) {
const cp = spawn('yarn', ['-T', 'run', 'dev-plugin', '--plugin', plugin], {
stdio: 'inherit',
shell: true,
});
spawnSync('yarn', ['-T', 'run', 'dev-plugin', '--plugin', 'hello-world'], {
stdio: 'inherit',
shell: true,
});
spawnSync('yarn', ['-T', 'run', 'dev-plugin', '--plugin', 'copilot'], {
stdio: 'inherit',
shell: true,
});
});
cp.on('exit', code => {
if (code !== 0) {
process.exit(code);
}
});
}

View File

@ -7,7 +7,7 @@ test('plugin should exist', async ({ page }) => {
await openHomePage(page);
await waitEditorLoad(page);
await page.route('**/plugins/**/package.json', route => route.fetch(), {
times: 3,
times: 4,
});
await page.waitForTimeout(50);
const packageJson = await page.evaluate(() => {
@ -17,14 +17,26 @@ test('plugin should exist', async ({ page }) => {
expect(packageJson).toEqual([
{
name: '@affine/bookmark-plugin',
version: expect.any(String),
description: expect.any(String),
affinePlugin: expect.anything(),
},
{
name: '@affine/copilot-plugin',
version: expect.any(String),
description: expect.any(String),
affinePlugin: expect.anything(),
},
{
name: '@affine/hello-world-plugin',
version: expect.any(String),
description: expect.any(String),
affinePlugin: expect.anything(),
},
{
name: '@affine/image-preview-plugin',
version: expect.any(String),
description: expect.any(String),
affinePlugin: expect.anything(),
},
]);

View File

@ -403,6 +403,20 @@ __metadata:
languageName: unknown
linkType: soft
"@affine/image-preview-plugin@workspace:plugins/image-preview":
version: 0.0.0-use.local
resolution: "@affine/image-preview-plugin@workspace:plugins/image-preview"
dependencies:
"@affine/component": "workspace:*"
"@blocksuite/icons": ^2.1.27
"@toeverything/plugin-infra": "workspace:*"
"@toeverything/theme": ^0.7.9
clsx: ^2.0.0
react-error-boundary: ^4.0.10
swr: 2.1.5
languageName: unknown
linkType: soft
"@affine/jotai@workspace:*, @affine/jotai@workspace:packages/jotai":
version: 0.0.0-use.local
resolution: "@affine/jotai@workspace:packages/jotai"