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, TextDecoder: globalThis.TextDecoder,
Request: globalThis.Request, Request: globalThis.Request,
Error: globalThis.Error, Error: globalThis.Error,
// bookmark uses these
Blob: globalThis.Blob,
ClipboardItem: globalThis.ClipboardItem,
// fixme: use our own db api // fixme: use our own db api
indexedDB: globalThis.indexedDB, indexedDB: globalThis.indexedDB,

View File

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

View File

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

View File

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

View File

@ -45,7 +45,8 @@ const external = [
/^@toeverything/, /^@toeverything/,
// react // react
/^react/, 'react',
/^react\//,
/^react-dom/, /^react-dom/,
// store // store
@ -72,11 +73,11 @@ const json: z.infer<typeof packageJsonInputSchema> = await readFile(
) )
.then(text => JSON.parse(text)) .then(text => JSON.parse(text))
.then(async json => { .then(async json => {
const { success } = await packageJsonInputSchema.safeParseAsync(json); const result = await packageJsonInputSchema.safeParseAsync(json);
if (success) { if (result.success) {
return json; return json;
} else { } 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 { use } from 'foxact/use';
import { useAtomValue } from 'jotai'; import { useAtomValue } from 'jotai';
import type { CSSProperties, ReactElement } from 'react'; import type { CSSProperties, ReactElement } from 'react';
import { lazy, memo, Suspense, useCallback, useEffect, useRef } from 'react'; import { memo, Suspense, useCallback, useEffect, useRef } from 'react';
import { createPortal } from 'react-dom';
import type { FallbackProps } from 'react-error-boundary'; import type { FallbackProps } from 'react-error-boundary';
import { ErrorBoundary } from 'react-error-boundary'; import { ErrorBoundary } from 'react-error-boundary';
@ -41,12 +40,6 @@ declare global {
var currentEditor: EditorContainer | undefined; var currentEditor: EditorContainer | undefined;
} }
const ImagePreviewModal = lazy(() =>
import('../image-preview-modal').then(module => ({
default: module.ImagePreviewModal,
}))
);
const BlockSuiteEditorImpl = (props: EditorProps): ReactElement => { const BlockSuiteEditorImpl = (props: EditorProps): ReactElement => {
const { onLoad, page, mode, style } = props; const { onLoad, page, mode, style } = props;
if (!page.loaded) { if (!page.loaded) {
@ -191,17 +184,6 @@ export const BlockSuiteEditor = memo(function BlockSuiteEditor(
<Suspense fallback={<BlockSuiteFallback />}> <Suspense fallback={<BlockSuiteFallback />}>
<BlockSuiteEditorImpl {...props} /> <BlockSuiteEditorImpl {...props} />
</Suspense> </Suspense>
{props.page && (
<Suspense fallback={null}>
{createPortal(
<ImagePreviewModal
workspace={props.page.workspace}
pageId={props.page.id}
/>,
document.body
)}
</Suspense>
)}
</ErrorBoundary> </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 => { previewBlockIdAtom.onMount = set => {
const callback = (event: MouseEvent) => { const callback = (event: MouseEvent) => {
const target = event.target; const target = event.target as HTMLElement | null;
if (target instanceof HTMLImageElement) { if (target?.tagName === 'IMG') {
const imageBlock = target.closest('affine-image'); const imageBlock = target.closest('affine-image');
if (imageBlock) { if (imageBlock) {
const blockId = imageBlock.getAttribute('data-block-id'); 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 type { ImageBlockModel } from '@blocksuite/blocks';
import { assertExists } from '@blocksuite/global/utils'; import { assertExists } from '@blocksuite/global/utils';
import { import {
@ -23,8 +22,6 @@ import type { FallbackProps } from 'react-error-boundary';
import { ErrorBoundary } from 'react-error-boundary'; import { ErrorBoundary } from 'react-error-boundary';
import useSWR from 'swr'; import useSWR from 'swr';
import { Button, IconButton } from '../../ui/button';
import { Tooltip } from '../../ui/tooltip';
import { useZoomControls } from './hooks/use-zoom'; import { useZoomControls } from './hooks/use-zoom';
import { import {
buttonStyle, 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'];
stdio: 'inherit',
shell: true,
});
spawnSync('yarn', ['-T', 'run', 'dev-plugin', '--plugin', 'hello-world'], { for (const plugin of builtInPlugins) {
stdio: 'inherit', const cp = spawn('yarn', ['-T', 'run', 'dev-plugin', '--plugin', plugin], {
shell: true, stdio: 'inherit',
}); shell: true,
});
spawnSync('yarn', ['-T', 'run', 'dev-plugin', '--plugin', 'copilot'], { cp.on('exit', code => {
stdio: 'inherit', if (code !== 0) {
shell: true, process.exit(code);
}); }
});
}

View File

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

View File

@ -403,6 +403,20 @@ __metadata:
languageName: unknown languageName: unknown
linkType: soft 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": "@affine/jotai@workspace:*, @affine/jotai@workspace:packages/jotai":
version: 0.0.0-use.local version: 0.0.0-use.local
resolution: "@affine/jotai@workspace:packages/jotai" resolution: "@affine/jotai@workspace:packages/jotai"