refactor(editor): edgeless frame toolbar config extension (#10769)

This commit is contained in:
fundon
2025-03-19 12:34:18 +00:00
parent e320552594
commit f87cc0f599
3 changed files with 200 additions and 12 deletions
@@ -1,11 +1,14 @@
import { FrameBlockSchema } from '@blocksuite/affine-model';
import { BlockViewExtension } from '@blocksuite/block-std';
import type { ExtensionType } from '@blocksuite/store';
import { literal } from 'lit/static-html.js';
import { EdgelessFrameManager, FrameOverlay } from './frame-manager';
const flavour = FrameBlockSchema.model.flavour;
export const FrameBlockSpec: ExtensionType[] = [
BlockViewExtension('affine:frame', literal`affine-frame`),
BlockViewExtension(flavour, literal`affine-frame`),
FrameOverlay,
EdgelessFrameManager,
];
@@ -1,11 +1,198 @@
import type { ToolbarModuleConfig } from '@blocksuite/affine-shared/services';
import { EdgelessFrameManagerIdentifier } from '@blocksuite/affine-block-frame';
import { EdgelessCRUDExtension } from '@blocksuite/affine-block-surface';
import {
packColor,
type PickColorEvent,
} from '@blocksuite/affine-components/color-picker';
import { toast } from '@blocksuite/affine-components/toast';
import {
DEFAULT_NOTE_HEIGHT,
DefaultTheme,
FrameBlockModel,
NoteBlockModel,
NoteBlockSchema,
NoteDisplayMode,
resolveColor,
SurfaceRefBlockSchema,
} from '@blocksuite/affine-model';
import {
FeatureFlagService,
type ToolbarModuleConfig,
ToolbarModuleExtension,
} from '@blocksuite/affine-shared/services';
import {
getMostCommonResolvedValue,
matchModels,
} from '@blocksuite/affine-shared/utils';
import { BlockFlavourIdentifier } from '@blocksuite/block-std';
import { Bound } from '@blocksuite/global/gfx';
import { EditIcon, PageIcon, UngroupIcon } from '@blocksuite/icons/lit';
import type { ExtensionType } from '@blocksuite/store';
import { html } from 'lit';
export const builtinFrameToolbarConfig = {
import { EdgelessRootBlockComponent } from '../..';
import { mountFrameTitleEditor } from '../../utils/text';
const builtinSurfaceToolbarConfig = {
actions: [
{
id: 'a.test',
label: 'Frame',
run() {},
id: 'a.insert-into-page',
label: 'Insert into Page',
tooltip: 'Insert into Page',
icon: PageIcon(),
when: ctx => ctx.getSurfaceModelsByType(FrameBlockModel).length === 1,
run(ctx) {
const model = ctx.getCurrentModelByType(FrameBlockModel);
if (!model) return;
const rootModel = ctx.store.root;
if (!rootModel) return;
const { id: frameId, xywh } = model;
let lastNoteId = rootModel.children
.filter(
note =>
matchModels(note, [NoteBlockModel]) &&
note.props.displayMode !== NoteDisplayMode.EdgelessOnly
)
.pop()?.id;
if (!lastNoteId) {
const bounds = Bound.deserialize(xywh);
bounds.y += bounds.h;
bounds.h = DEFAULT_NOTE_HEIGHT;
lastNoteId = ctx.store.addBlock(
NoteBlockSchema.model.flavour,
{ xywh: bounds.serialize() },
rootModel.id
);
}
ctx.store.addBlock(
SurfaceRefBlockSchema.model.flavour,
{ reference: frameId, refFlavour: NoteBlockSchema.model.flavour },
lastNoteId
);
toast(ctx.host, 'Frame has been inserted into doc');
},
},
{
id: 'b.rename',
tooltip: 'Rename',
icon: EditIcon(),
when: ctx => ctx.getSurfaceModelsByType(FrameBlockModel).length === 1,
run(ctx) {
const model = ctx.getCurrentModelByType(FrameBlockModel);
if (!model) return;
const rootModel = ctx.store.root;
if (!rootModel) return;
// TODO(@fundon): it should be simple
const edgeless = ctx.view.getBlock(rootModel.id);
if (!ctx.matchBlock(edgeless, EdgelessRootBlockComponent)) {
console.error('edgeless view is not found.');
return;
}
mountFrameTitleEditor(model, edgeless);
},
},
{
id: 'b.ungroup',
tooltip: 'Ungroup',
icon: UngroupIcon(),
run(ctx) {
const models = ctx.getSurfaceModelsByType(FrameBlockModel);
if (!models.length) return;
const rootModel = ctx.store.root;
if (!rootModel) return;
// TODO(@fundon): it should be simple
const edgeless = ctx.view.getBlock(rootModel.id);
if (!ctx.matchBlock(edgeless, EdgelessRootBlockComponent)) {
console.error('edgeless view is not found.');
return;
}
ctx.store.captureSync();
const frameManager = ctx.std.get(EdgelessFrameManagerIdentifier);
for (const model of models) {
frameManager.removeAllChildrenFromFrame(model);
}
for (const model of models) {
edgeless.service.removeElement(model);
}
edgeless.service.selection.clear();
},
},
{
id: 'c.color-picker',
content(ctx) {
const models = ctx.getSurfaceModelsByType(FrameBlockModel);
if (!models.length) return null;
const enableCustomColor = ctx.std
.get(FeatureFlagService)
.getFlag('enable_color_picker');
const theme = ctx.themeProvider.edgelessTheme;
const firstModel = models[0];
const background =
getMostCommonResolvedValue(
models.map(model => model.props),
'background',
background => resolveColor(background, theme)
) ?? DefaultTheme.transparent;
const onPick = (e: PickColorEvent) => {
const field = 'background';
if (e.type === 'pick') {
const color = e.detail.value;
for (const model of models) {
const props = packColor(field, color);
ctx.std.get(EdgelessCRUDExtension).updateElement(model.id, props);
}
return;
}
for (const model of models) {
model[e.type === 'start' ? 'stash' : 'pop'](field);
}
};
return html`
<edgeless-color-picker-button
class="background"
.label="${'Background'}"
.pick=${onPick}
.color=${background}
.theme=${theme}
.originalColor=${firstModel.props.background}
.enableCustomColor=${enableCustomColor}
>
</edgeless-color-picker-button>
`;
},
},
],
when: ctx => ctx.getSurfaceModelsByType(FrameBlockModel).length > 0,
} as const satisfies ToolbarModuleConfig;
export const createFrameToolbarConfig = (flavour: string): ExtensionType => {
const name = flavour.split(':').pop();
return ToolbarModuleExtension({
id: BlockFlavourIdentifier(`affine:surface:${name}`),
config: builtinSurfaceToolbarConfig,
});
};
@@ -1,10 +1,11 @@
import { FrameBlockSchema } from '@blocksuite/affine-model';
import { ToolbarModuleExtension } from '@blocksuite/affine-shared/services';
import { BlockFlavourIdentifier } from '@blocksuite/block-std';
import type { ExtensionType } from '@blocksuite/store';
import { builtinBrushToolbarConfig } from './brush';
import { builtinConnectorToolbarConfig } from './connector';
import { builtinFrameToolbarConfig } from './frame';
import { createFrameToolbarConfig } from './frame';
import { builtinGroupToolbarConfig } from './group';
import { builtinMindmapToolbarConfig } from './mindmap';
import { builtinMiscToolbarConfig } from './misc';
@@ -12,6 +13,8 @@ import { builtinShapeToolbarConfig } from './shape';
import { builtinTextToolbarConfig } from './text';
export const EdgelessElementToolbarExtension: ExtensionType[] = [
createFrameToolbarConfig(FrameBlockSchema.model.flavour),
ToolbarModuleExtension({
id: BlockFlavourIdentifier('affine:surface:brush'),
config: builtinBrushToolbarConfig,
@@ -22,11 +25,6 @@ export const EdgelessElementToolbarExtension: ExtensionType[] = [
config: builtinConnectorToolbarConfig,
}),
ToolbarModuleExtension({
id: BlockFlavourIdentifier('affine:surface:frame'),
config: builtinFrameToolbarConfig,
}),
ToolbarModuleExtension({
id: BlockFlavourIdentifier('affine:surface:group'),
config: builtinGroupToolbarConfig,