From c5624bfd132a632b0e1aadded9d5386efe3a3e3b Mon Sep 17 00:00:00 2001 From: donteatfriedrice Date: Wed, 26 Mar 2025 08:38:06 +0000 Subject: [PATCH] refactor(editor): embed iframe block surface toolbar extension (#11193) --- .../src/embed-iframe-block/configs/toolbar.ts | 196 +++++++++++++++++- .../embed-iframe-block/embed-iframe-spec.ts | 14 +- .../extensions/editor-config/toolbar/index.ts | 9 + 3 files changed, 203 insertions(+), 16 deletions(-) diff --git a/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/configs/toolbar.ts b/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/configs/toolbar.ts index 219d57d00b..c5e078b944 100644 --- a/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/configs/toolbar.ts +++ b/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/configs/toolbar.ts @@ -1,17 +1,24 @@ +import { reassociateConnectorsCommand } from '@blocksuite/affine-block-surface'; import { toast } from '@blocksuite/affine-components/toast'; import { BookmarkStyles, EmbedIframeBlockModel, } from '@blocksuite/affine-model'; +import { + EMBED_CARD_HEIGHT, + EMBED_CARD_WIDTH, +} from '@blocksuite/affine-shared/consts'; import { ActionPlacement, type ToolbarAction, type ToolbarActionGroup, type ToolbarContext, type ToolbarModuleConfig, + ToolbarModuleExtension, } from '@blocksuite/affine-shared/services'; import { getBlockProps } from '@blocksuite/affine-shared/utils'; -import { BlockSelection } from '@blocksuite/block-std'; +import { BlockFlavourIdentifier, BlockSelection } from '@blocksuite/block-std'; +import { Bound } from '@blocksuite/global/gfx'; import { CaptionIcon, CopyIcon, @@ -19,8 +26,8 @@ import { DuplicateIcon, ResetIcon, } from '@blocksuite/icons/lit'; -import { Slice, Text } from '@blocksuite/store'; -import { signal } from '@preact/signals-core'; +import { type ExtensionType, Slice, Text } from '@blocksuite/store'; +import { computed, signal } from '@preact/signals-core'; import { html } from 'lit'; import { keyed } from 'lit/directives/keyed.js'; import * as Y from 'yjs'; @@ -28,8 +35,7 @@ import * as Y from 'yjs'; import { EmbedIframeBlockComponent } from '../embed-iframe-block'; const trackBaseProps = { - category: 'bookmark', - type: 'card view', + category: 'embed iframe block', }; export const builtinToolbarConfig = { @@ -234,3 +240,183 @@ export const builtinToolbarConfig = { }, ], } as const satisfies ToolbarModuleConfig; + +export const builtinSurfaceToolbarConfig = { + actions: [ + { + id: 'b.conversions', + when: (ctx: ToolbarContext) => { + const model = ctx.getCurrentModelByType(EmbedIframeBlockModel); + if (!model) return false; + + return !!model.props.url; + }, + actions: [ + { + id: 'card', + label: 'Card view', + run(ctx) { + const model = ctx.getCurrentModelByType(EmbedIframeBlockModel); + if (!model) return; + + const { id: oldId, xywh, parent } = model; + const { url, caption } = model.props; + + if (!url) return; + + const style = + BookmarkStyles.find(s => s !== 'vertical' && s !== 'cube') ?? + BookmarkStyles[1]; + let flavour = 'affine:bookmark'; + + const bounds = Bound.deserialize(xywh); + bounds.w = EMBED_CARD_WIDTH[style]; + bounds.h = EMBED_CARD_HEIGHT[style]; + + const newId = ctx.store.addBlock( + flavour, + { url, caption, style, xywh: bounds.serialize() }, + parent + ); + + ctx.command.exec(reassociateConnectorsCommand, { oldId, newId }); + + ctx.store.deleteBlock(model); + + // Selects new block + ctx.gfx.selection.set({ editing: false, elements: [newId] }); + + ctx.track('SelectedView', { + ...trackBaseProps, + control: 'select view', + type: 'card view', + }); + }, + }, + { + id: 'embed', + label: 'Embed view', + disabled: true, + }, + ], + content(ctx) { + const model = ctx.getCurrentModelByType(EmbedIframeBlockModel); + if (!model) return null; + + const actions = this.actions.map(action => ({ ...action })); + const onToggle = (e: CustomEvent) => { + if (!e.detail) return; + + ctx.track('OpenedViewSelector', { + ...trackBaseProps, + control: 'switch view', + }); + }; + + return html`${keyed( + model, + html`` + )}`; + }, + } satisfies ToolbarActionGroup, + { + id: 'c.caption', + when: (ctx: ToolbarContext) => { + const model = ctx.getCurrentModelByType(EmbedIframeBlockModel); + if (!model) return false; + + return !!model.props.url; + }, + tooltip: 'Caption', + icon: CaptionIcon(), + run(ctx) { + const component = ctx.getCurrentBlockByType(EmbedIframeBlockComponent); + component?.captionEditor?.show(); + + ctx.track('OpenedCaptionEditor', { + ...trackBaseProps, + control: 'add caption', + }); + }, + }, + { + id: 'd.scale', + content(ctx) { + const model = ctx.getCurrentModelByType(EmbedIframeBlockModel); + if (!model) return null; + + const scale$ = computed(() => { + const scale = model.props.scale$.value ?? 1; + return Math.round(100 * scale); + }); + const onSelect = (e: CustomEvent) => { + e.stopPropagation(); + + const scale = e.detail / 100; + + const bounds = Bound.deserialize(model.xywh); + const oldScale = model.props.scale ?? 1; + const ratio = scale / oldScale; + bounds.w *= ratio; + bounds.h *= ratio; + const xywh = bounds.serialize(); + + ctx.store.updateBlock(model, () => { + model.xywh = xywh; + model.props.scale = scale; + }); + + ctx.track('SelectedCardScale', { + ...trackBaseProps, + control: 'select card scale', + }); + }; + const onToggle = (e: CustomEvent) => { + e.stopPropagation(); + + const opened = e.detail; + if (!opened) return; + + ctx.track('OpenedCardScaleSelector', { + ...trackBaseProps, + control: 'switch card scale', + }); + }; + const format = (value: number) => `${value}%`; + + return html`${keyed( + model, + html`` + )}`; + }, + }, + ], + when: ctx => ctx.getSurfaceModelsByType(EmbedIframeBlockModel).length > 0, +} as const satisfies ToolbarModuleConfig; + +export const createBuiltinToolbarConfigExtension = ( + flavour: string +): ExtensionType[] => { + const name = flavour.split(':').pop(); + + return [ + ToolbarModuleExtension({ + id: BlockFlavourIdentifier(flavour), + config: builtinToolbarConfig, + }), + ToolbarModuleExtension({ + id: BlockFlavourIdentifier(`affine:surface:${name}`), + config: builtinSurfaceToolbarConfig, + }), + ]; +}; diff --git a/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/embed-iframe-spec.ts b/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/embed-iframe-spec.ts index 5ca2d7967e..784cc4b096 100644 --- a/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/embed-iframe-spec.ts +++ b/blocksuite/affine/blocks/block-embed/src/embed-iframe-block/embed-iframe-spec.ts @@ -1,17 +1,12 @@ import { EmbedIframeBlockSchema } from '@blocksuite/affine-model'; -import { ToolbarModuleExtension } from '@blocksuite/affine-shared/services'; import { SlashMenuConfigExtension } from '@blocksuite/affine-widget-slash-menu'; -import { - BlockFlavourIdentifier, - BlockViewExtension, - FlavourExtension, -} from '@blocksuite/block-std'; +import { BlockViewExtension, FlavourExtension } from '@blocksuite/block-std'; import type { ExtensionType } from '@blocksuite/store'; import { literal } from 'lit/static-html.js'; import { EmbedIframeBlockAdapterExtensions } from './adapters'; import { embedIframeSlashMenuConfig } from './configs/slash-menu/slash-menu'; -import { builtinToolbarConfig } from './configs/toolbar'; +import { createBuiltinToolbarConfigExtension } from './configs/toolbar'; const flavour = EmbedIframeBlockSchema.model.flavour; @@ -23,9 +18,6 @@ export const EmbedIframeBlockSpec: ExtensionType[] = [ : literal`affine-embed-iframe-block`; }), EmbedIframeBlockAdapterExtensions, - ToolbarModuleExtension({ - id: BlockFlavourIdentifier(flavour), - config: builtinToolbarConfig, - }), + createBuiltinToolbarConfigExtension(flavour), SlashMenuConfigExtension(flavour, embedIframeSlashMenuConfig), ].flat(); diff --git a/packages/frontend/core/src/blocksuite/extensions/editor-config/toolbar/index.ts b/packages/frontend/core/src/blocksuite/extensions/editor-config/toolbar/index.ts index 44cb79c107..82e5738030 100644 --- a/packages/frontend/core/src/blocksuite/extensions/editor-config/toolbar/index.ts +++ b/packages/frontend/core/src/blocksuite/extensions/editor-config/toolbar/index.ts @@ -1081,5 +1081,14 @@ export const createCustomToolbarExtension = ( id: BlockFlavourIdentifier('custom:affine:embed-iframe'), config: embedIframeToolbarConfig, }), + + ToolbarModuleExtension({ + id: BlockFlavourIdentifier('custom:affine:surface:embed-iframe'), + config: { + actions: [embedIframeToolbarConfig.actions].flat(), + + when: ctx => ctx.getSurfaceModels().length === 1, + }, + }), ]; };