From 52809a278310a167326757ed212399e86c7f636d Mon Sep 17 00:00:00 2001 From: Alex Yang Date: Sat, 29 Jul 2023 00:18:28 -0700 Subject: [PATCH] refactor: image preview plugin (#3457) --- apps/core/src/bootstrap/plugins/setup.ts | 3 +++ apps/core/src/bootstrap/register-plugins.ts | 1 + .../stories/image-preview-modal.stories.tsx | 8 +++++- apps/storybook/tsconfig.json | 3 +++ packages/cli/src/bin/dev-plugin.ts | 9 ++++--- .../components/block-suite-editor/index.tsx | 20 +------------- plugins/image-preview/package.json | 20 ++++++++++++++ plugins/image-preview/src/app.tsx | 11 ++++++++ .../src/component}/hooks/use-zoom.tsx | 0 .../image-preview/src/component}/index.css.ts | 0 .../src/component}/index.jotai.ts | 4 +-- .../image-preview/src/component}/index.tsx | 5 +--- .../image-preview/src/component}/toast.ts | 0 plugins/image-preview/src/index.ts | 18 +++++++++++++ plugins/image-preview/tsconfig.json | 17 ++++++++++++ scripts/build-plugins.mjs | 27 +++++++++---------- tests/affine-plugin/e2e/basic.spec.ts | 14 +++++++++- yarn.lock | 14 ++++++++++ 18 files changed, 129 insertions(+), 45 deletions(-) create mode 100644 plugins/image-preview/package.json create mode 100644 plugins/image-preview/src/app.tsx rename {packages/component/src/components/image-preview-modal => plugins/image-preview/src/component}/hooks/use-zoom.tsx (100%) rename {packages/component/src/components/image-preview-modal => plugins/image-preview/src/component}/index.css.ts (100%) rename {packages/component/src/components/image-preview-modal => plugins/image-preview/src/component}/index.jotai.ts (86%) rename {packages/component/src/components/image-preview-modal => plugins/image-preview/src/component}/index.tsx (99%) rename {packages/component/src/components/image-preview-modal => plugins/image-preview/src/component}/toast.ts (100%) create mode 100644 plugins/image-preview/src/index.ts create mode 100644 plugins/image-preview/tsconfig.json diff --git a/apps/core/src/bootstrap/plugins/setup.ts b/apps/core/src/bootstrap/plugins/setup.ts index dd49e41a75..6da43b396c 100644 --- a/apps/core/src/bootstrap/plugins/setup.ts +++ b/apps/core/src/bootstrap/plugins/setup.ts @@ -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, diff --git a/apps/core/src/bootstrap/register-plugins.ts b/apps/core/src/bootstrap/register-plugins.ts index 31713e56b1..d40970dc88 100644 --- a/apps/core/src/bootstrap/register-plugins.ts +++ b/apps/core/src/bootstrap/register-plugins.ts @@ -37,6 +37,7 @@ const builtinPluginUrl = new Set([ '/plugins/bookmark', '/plugins/copilot', '/plugins/hello-world', + '/plugins/image-preview', ]); const logger = new DebugLogger('register-plugins'); diff --git a/apps/storybook/src/stories/image-preview-modal.stories.tsx b/apps/storybook/src/stories/image-preview-modal.stories.tsx index 16835219d0..5bf5c0f936 100644 --- a/apps/storybook/src/stories/image-preview-modal.stories.tsx +++ b/apps/storybook/src/stories/image-preview-modal.stories.tsx @@ -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 = () => { }} > + {createPortal( + , + document.body + )} = 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); } }); diff --git a/packages/component/src/components/block-suite-editor/index.tsx b/packages/component/src/components/block-suite-editor/index.tsx index 0d4cf2352e..c097378661 100644 --- a/packages/component/src/components/block-suite-editor/index.tsx +++ b/packages/component/src/components/block-suite-editor/index.tsx @@ -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( }> - {props.page && ( - - {createPortal( - , - document.body - )} - - )} ); }); diff --git a/plugins/image-preview/package.json b/plugins/image-preview/package.json new file mode 100644 index 0000000000..fb4c091fe7 --- /dev/null +++ b/plugins/image-preview/package.json @@ -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" + } +} diff --git a/plugins/image-preview/src/app.tsx b/plugins/image-preview/src/app.tsx new file mode 100644 index 0000000000..e2fa1d9949 --- /dev/null +++ b/plugins/image-preview/src/app.tsx @@ -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 ; +}; diff --git a/packages/component/src/components/image-preview-modal/hooks/use-zoom.tsx b/plugins/image-preview/src/component/hooks/use-zoom.tsx similarity index 100% rename from packages/component/src/components/image-preview-modal/hooks/use-zoom.tsx rename to plugins/image-preview/src/component/hooks/use-zoom.tsx diff --git a/packages/component/src/components/image-preview-modal/index.css.ts b/plugins/image-preview/src/component/index.css.ts similarity index 100% rename from packages/component/src/components/image-preview-modal/index.css.ts rename to plugins/image-preview/src/component/index.css.ts diff --git a/packages/component/src/components/image-preview-modal/index.jotai.ts b/plugins/image-preview/src/component/index.jotai.ts similarity index 86% rename from packages/component/src/components/image-preview-modal/index.jotai.ts rename to plugins/image-preview/src/component/index.jotai.ts index 353d3dc5c1..67aa51d95d 100644 --- a/packages/component/src/components/image-preview-modal/index.jotai.ts +++ b/plugins/image-preview/src/component/index.jotai.ts @@ -5,8 +5,8 @@ export const hasAnimationPlayedAtom = atom(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'); diff --git a/packages/component/src/components/image-preview-modal/index.tsx b/plugins/image-preview/src/component/index.tsx similarity index 99% rename from packages/component/src/components/image-preview-modal/index.tsx rename to plugins/image-preview/src/component/index.tsx index 62892cc3f5..6ae7fb4656 100644 --- a/packages/component/src/components/image-preview-modal/index.tsx +++ b/plugins/image-preview/src/component/index.tsx @@ -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, diff --git a/packages/component/src/components/image-preview-modal/toast.ts b/plugins/image-preview/src/component/toast.ts similarity index 100% rename from packages/component/src/components/image-preview-modal/toast.ts rename to plugins/image-preview/src/component/toast.ts diff --git a/plugins/image-preview/src/index.ts b/plugins/image-preview/src/index.ts new file mode 100644 index 0000000000..5a95f4b96a --- /dev/null +++ b/plugins/image-preview/src/index.ts @@ -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 + }; +}; diff --git a/plugins/image-preview/tsconfig.json b/plugins/image-preview/tsconfig.json new file mode 100644 index 0000000000..f2db0bc490 --- /dev/null +++ b/plugins/image-preview/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../../tsconfig.json", + "include": ["./src"], + "compilerOptions": { + "noEmit": false, + "outDir": "lib", + "jsx": "preserve" + }, + "references": [ + { + "path": "../../packages/plugin-infra" + }, + { + "path": "../../packages/component" + } + ] +} diff --git a/scripts/build-plugins.mjs b/scripts/build-plugins.mjs index 9d0bc0dcf8..9549be72a7 100644 --- a/scripts/build-plugins.mjs +++ b/scripts/build-plugins.mjs @@ -1,16 +1,15 @@ -import { spawnSync } from 'node:child_process'; +import { spawn } from 'node:child_process'; -spawnSync('yarn', ['-T', 'run', 'dev-plugin', '--plugin', 'bookmark'], { - stdio: 'inherit', - shell: true, -}); +const builtInPlugins = ['bookmark', 'hello-world', 'copilot', 'image-preview']; -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, -}); +for (const plugin of builtInPlugins) { + const cp = spawn('yarn', ['-T', 'run', 'dev-plugin', '--plugin', plugin], { + stdio: 'inherit', + shell: true, + }); + cp.on('exit', code => { + if (code !== 0) { + process.exit(code); + } + }); +} diff --git a/tests/affine-plugin/e2e/basic.spec.ts b/tests/affine-plugin/e2e/basic.spec.ts index 98354a57f0..d299b50d9c 100644 --- a/tests/affine-plugin/e2e/basic.spec.ts +++ b/tests/affine-plugin/e2e/basic.spec.ts @@ -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(), }, ]); diff --git a/yarn.lock b/yarn.lock index dd78429798..47259dc262 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"