feat(editor): group gfx extension (#11950)

Closes: BS-3208

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
  - Introduced a new group view extension, enhancing how group elements are handled and displayed.
  - Added new store and view modules for group-related features, improving modularity and integration.

- **Refactor**
  - Updated the group element architecture to use extension-based registration for views, toolbars, and effects.
  - Simplified and reorganized exports for group and text modules.

- **Chores**
  - Updated dependencies and project references to improve build consistency and compatibility.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Saul-Mirone
2025-04-24 03:21:53 +00:00
parent 2d3130eac9
commit 9d90899344
22 changed files with 101 additions and 50 deletions

View File

@@ -188,7 +188,9 @@
"./gfx/connector": "./src/gfx/connector/index.ts", "./gfx/connector": "./src/gfx/connector/index.ts",
"./gfx/connector/store": "./src/gfx/connector/store.ts", "./gfx/connector/store": "./src/gfx/connector/store.ts",
"./gfx/connector/view": "./src/gfx/connector/view.ts", "./gfx/connector/view": "./src/gfx/connector/view.ts",
"./gfx/group": "./src/gfx/group.ts", "./gfx/group": "./src/gfx/group/index.ts",
"./gfx/group/store": "./src/gfx/group/store.ts",
"./gfx/group/view": "./src/gfx/group/view.ts",
"./gfx/template": "./src/gfx/template/index.ts", "./gfx/template": "./src/gfx/template/index.ts",
"./gfx/template/view": "./src/gfx/template/view.ts", "./gfx/template/view": "./src/gfx/template/view.ts",
"./gfx/turbo-renderer": "./src/gfx/turbo-renderer.ts", "./gfx/turbo-renderer": "./src/gfx/turbo-renderer.ts",

View File

@@ -17,6 +17,7 @@ import { SurfaceRefStoreExtension } from '@blocksuite/affine-block-surface-ref/s
import { TableStoreExtension } from '@blocksuite/affine-block-table/store'; import { TableStoreExtension } from '@blocksuite/affine-block-table/store';
import { BrushStoreExtension } from '@blocksuite/affine-gfx-brush/store'; import { BrushStoreExtension } from '@blocksuite/affine-gfx-brush/store';
import { ConnectorStoreExtension } from '@blocksuite/affine-gfx-connector/store'; import { ConnectorStoreExtension } from '@blocksuite/affine-gfx-connector/store';
import { GroupStoreExtension } from '@blocksuite/affine-gfx-group/store';
import { MindmapStoreExtension } from '@blocksuite/affine-gfx-mindmap/store'; import { MindmapStoreExtension } from '@blocksuite/affine-gfx-mindmap/store';
import { ShapeStoreExtension } from '@blocksuite/affine-gfx-shape/store'; import { ShapeStoreExtension } from '@blocksuite/affine-gfx-shape/store';
import { FootnoteStoreExtension } from '@blocksuite/affine-inline-footnote/store'; import { FootnoteStoreExtension } from '@blocksuite/affine-inline-footnote/store';
@@ -57,6 +58,7 @@ export function getInternalStoreExtensions() {
ShapeStoreExtension, ShapeStoreExtension,
MindmapStoreExtension, MindmapStoreExtension,
ConnectorStoreExtension, ConnectorStoreExtension,
GroupStoreExtension,
MigratingStoreExtension, MigratingStoreExtension,
]; ];

View File

@@ -17,6 +17,7 @@ import { SurfaceRefViewExtension } from '@blocksuite/affine-block-surface-ref/vi
import { TableViewExtension } from '@blocksuite/affine-block-table/view'; import { TableViewExtension } from '@blocksuite/affine-block-table/view';
import { BrushViewExtension } from '@blocksuite/affine-gfx-brush/view'; import { BrushViewExtension } from '@blocksuite/affine-gfx-brush/view';
import { ConnectorViewExtension } from '@blocksuite/affine-gfx-connector/view'; import { ConnectorViewExtension } from '@blocksuite/affine-gfx-connector/view';
import { GroupViewExtension } from '@blocksuite/affine-gfx-group/view';
import { MindmapViewExtension } from '@blocksuite/affine-gfx-mindmap/view'; import { MindmapViewExtension } from '@blocksuite/affine-gfx-mindmap/view';
import { NoteViewExtension as GfxNoteViewExtension } from '@blocksuite/affine-gfx-note/view'; import { NoteViewExtension as GfxNoteViewExtension } from '@blocksuite/affine-gfx-note/view';
import { ShapeViewExtension } from '@blocksuite/affine-gfx-shape/view'; import { ShapeViewExtension } from '@blocksuite/affine-gfx-shape/view';
@@ -32,11 +33,13 @@ import { MigratingViewExtension } from './migrating-view';
export function getInternalViewExtensions() { export function getInternalViewExtensions() {
return [ return [
// Gfx
GfxNoteViewExtension, GfxNoteViewExtension,
BrushViewExtension, BrushViewExtension,
ShapeViewExtension, ShapeViewExtension,
MindmapViewExtension, MindmapViewExtension,
ConnectorViewExtension, ConnectorViewExtension,
GroupViewExtension,
TemplateViewExtension, TemplateViewExtension,
// Block // Block

View File

@@ -0,0 +1 @@
export * from '@blocksuite/affine-gfx-group/store';

View File

@@ -0,0 +1 @@
export * from '@blocksuite/affine-gfx-group/view';

View File

@@ -1,12 +1,4 @@
import { FileDropExtension } from '@blocksuite/affine-components/drop-indicator'; import { FileDropExtension } from '@blocksuite/affine-components/drop-indicator';
import {
ConnectorElementRendererExtension,
ConnectorElementView,
} from '@blocksuite/affine-gfx-connector';
import {
GroupElementRendererExtension,
GroupElementView,
} from '@blocksuite/affine-gfx-group';
import { import {
TextElementRendererExtension, TextElementRendererExtension,
TextElementView, TextElementView,
@@ -42,16 +34,10 @@ import { viewportOverlayWidget } from './widgets';
* Because in some cases we need to create edgeless elements in page mode. * Because in some cases we need to create edgeless elements in page mode.
* And these view may contain some logic when elements initialize. * And these view may contain some logic when elements initialize.
*/ */
const EdgelessElementViews = [ const EdgelessElementViews = [TextElementView];
ConnectorElementView,
GroupElementView,
TextElementView,
];
export const EdgelessElementRendererExtension: ExtensionType[] = [ export const EdgelessElementRendererExtension: ExtensionType[] = [
TextElementRendererExtension, TextElementRendererExtension,
ConnectorElementRendererExtension,
GroupElementRendererExtension,
]; ];
export const CommonSpecs: ExtensionType[] = [ export const CommonSpecs: ExtensionType[] = [

View File

@@ -1,4 +1,3 @@
import { groupToolbarExtension } from '@blocksuite/affine-gfx-group';
import { textToolbarExtension } from '@blocksuite/affine-gfx-text'; import { textToolbarExtension } from '@blocksuite/affine-gfx-text';
import { ToolbarModuleExtension } from '@blocksuite/affine-shared/services'; import { ToolbarModuleExtension } from '@blocksuite/affine-shared/services';
import { BlockFlavourIdentifier } from '@blocksuite/std'; import { BlockFlavourIdentifier } from '@blocksuite/std';
@@ -7,8 +6,6 @@ import type { ExtensionType } from '@blocksuite/store';
import { builtinLockedToolbarConfig, builtinMiscToolbarConfig } from './misc'; import { builtinLockedToolbarConfig, builtinMiscToolbarConfig } from './misc';
export const EdgelessElementToolbarExtension: ExtensionType[] = [ export const EdgelessElementToolbarExtension: ExtensionType[] = [
groupToolbarExtension,
textToolbarExtension, textToolbarExtension,
ToolbarModuleExtension({ ToolbarModuleExtension({

View File

@@ -1,4 +1,3 @@
import { effects as gfxGroupEffects } from '@blocksuite/affine-gfx-group/effects';
import { effects as gfxCanvasTextEffects } from '@blocksuite/affine-gfx-text/effects'; import { effects as gfxCanvasTextEffects } from '@blocksuite/affine-gfx-text/effects';
import { effects as widgetEdgelessToolbarEffects } from '@blocksuite/affine-widget-edgeless-toolbar/effects'; import { effects as widgetEdgelessToolbarEffects } from '@blocksuite/affine-widget-edgeless-toolbar/effects';
import { effects as widgetMobileToolbarEffects } from '@blocksuite/affine-widget-keyboard-toolbar/effects'; import { effects as widgetMobileToolbarEffects } from '@blocksuite/affine-widget-keyboard-toolbar/effects';
@@ -73,7 +72,6 @@ function registerRootComponents() {
function registerGfxEffects() { function registerGfxEffects() {
gfxCanvasTextEffects(); gfxCanvasTextEffects();
gfxGroupEffects();
} }
function registerWidgets() { function registerWidgets() {

View File

@@ -11,7 +11,6 @@ import * as Y from 'yjs';
import { elementsCtorMap } from './element-model/index.js'; import { elementsCtorMap } from './element-model/index.js';
import { surfaceMiddlewareIdentifier } from './extensions/surface-middleware.js'; import { surfaceMiddlewareIdentifier } from './extensions/surface-middleware.js';
import { SurfaceBlockTransformer } from './surface-transformer.js'; import { SurfaceBlockTransformer } from './surface-transformer.js';
import { groupRelationWatcher } from './watchers/group.js';
export const SurfaceBlockSchema = defineBlockSchema({ export const SurfaceBlockSchema = defineBlockSchema({
flavour: 'affine:surface', flavour: 'affine:surface',
@@ -50,10 +49,6 @@ export class SurfaceBlockModel extends BaseSurfaceModel {
.forEach(({ middleware }) => { .forEach(({ middleware }) => {
this._disposables.add(middleware(this)); this._disposables.add(middleware(this));
}); });
[groupRelationWatcher(this)].forEach(disposable =>
this._disposables.add(disposable)
);
} }
getConnectors(id: string) { getConnectors(id: string) {

View File

@@ -6,9 +6,11 @@ import {
import { ConnectionOverlay } from './connector-manager'; import { ConnectionOverlay } from './connector-manager';
import { ConnectorTool } from './connector-tool'; import { ConnectorTool } from './connector-tool';
import { effects } from './effects'; import { effects } from './effects';
import { ConnectorElementRendererExtension } from './element-renderer';
import { ConnectorFilter } from './element-transform'; import { ConnectorFilter } from './element-transform';
import { connectorToolbarExtension } from './toolbar/config'; import { connectorToolbarExtension } from './toolbar/config';
import { connectorQuickTool } from './toolbar/quick-tool'; import { connectorQuickTool } from './toolbar/quick-tool';
import { ConnectorElementView } from './view/view';
export class ConnectorViewExtension extends ViewExtensionProvider { export class ConnectorViewExtension extends ViewExtensionProvider {
override name = 'affine-connector-gfx'; override name = 'affine-connector-gfx';
@@ -20,6 +22,8 @@ export class ConnectorViewExtension extends ViewExtensionProvider {
override setup(context: ViewExtensionContext) { override setup(context: ViewExtensionContext) {
super.setup(context); super.setup(context);
context.register(ConnectorElementView);
context.register(ConnectorElementRendererExtension);
if (this.isEdgeless(context.scope)) { if (this.isEdgeless(context.scope)) {
context.register(ConnectorTool); context.register(ConnectorTool);
context.register(ConnectorFilter); context.register(ConnectorFilter);

View File

@@ -12,6 +12,7 @@
"dependencies": { "dependencies": {
"@blocksuite/affine-block-surface": "workspace:*", "@blocksuite/affine-block-surface": "workspace:*",
"@blocksuite/affine-components": "workspace:*", "@blocksuite/affine-components": "workspace:*",
"@blocksuite/affine-ext-loader": "workspace:*",
"@blocksuite/affine-gfx-text": "workspace:*", "@blocksuite/affine-gfx-text": "workspace:*",
"@blocksuite/affine-model": "workspace:*", "@blocksuite/affine-model": "workspace:*",
"@blocksuite/affine-rich-text": "workspace:*", "@blocksuite/affine-rich-text": "workspace:*",
@@ -34,7 +35,9 @@
}, },
"exports": { "exports": {
".": "./src/index.ts", ".": "./src/index.ts",
"./effects": "./src/effects.ts" "./effects": "./src/effects.ts",
"./view": "./src/view.ts",
"./store": "./src/store.ts"
}, },
"files": [ "files": [
"src", "src",

View File

@@ -0,0 +1,24 @@
import type { GroupElementModel } from '@blocksuite/affine-model';
import { GfxElementModelView } from '@blocksuite/std/gfx';
import { mountGroupTitleEditor } from './text/edgeless-group-title-editor';
export class GroupElementView extends GfxElementModelView<GroupElementModel> {
static override type: string = 'group';
override onCreated(): void {
super.onCreated();
this._initDblClickToEdit();
}
private _initDblClickToEdit(): void {
this.on('dblclick', () => {
const edgeless = this.std.view.getBlock(this.std.store.root!.id);
if (edgeless && !this.model.isLocked()) {
mountGroupTitleEditor(this.model, edgeless);
}
});
}
}

View File

@@ -1,8 +1,11 @@
import { SurfaceGroupLikeModel } from '../element-model/base.js'; import {
import type { SurfaceMiddleware } from '../extensions/surface-middleware.js'; type SurfaceBlockModel,
import type { SurfaceBlockModel } from '../surface-model.js'; SurfaceGroupLikeModel,
type SurfaceMiddleware,
surfaceMiddlewareExtension,
} from '@blocksuite/affine-block-surface';
export const groupRelationWatcher: SurfaceMiddleware = ( const groupRelationWatcher: SurfaceMiddleware = (
surface: SurfaceBlockModel surface: SurfaceBlockModel
) => { ) => {
const disposables = [ const disposables = [
@@ -26,3 +29,8 @@ export const groupRelationWatcher: SurfaceMiddleware = (
disposables.forEach(d => d.unsubscribe()); disposables.forEach(d => d.unsubscribe());
}; };
}; };
export const groupRelationWatcherExtension = surfaceMiddlewareExtension(
'group-relation-watcher',
groupRelationWatcher
);

View File

@@ -1,6 +1,6 @@
export * from './adapter'; export * from './adapter';
export * from './command'; export * from './command';
export * from './element-renderer'; export * from './element-renderer';
export * from './element-view';
export * from './text/text'; export * from './text/text';
export * from './toolbar/config'; export * from './toolbar/config';
export * from './view';

View File

@@ -0,0 +1,21 @@
import {
type StoreExtensionContext,
StoreExtensionProvider,
} from '@blocksuite/affine-ext-loader';
import {
groupToMarkdownAdapterMatcher,
groupToPlainTextAdapterMatcher,
} from './adapter';
import { groupRelationWatcherExtension } from './group-watcher';
export class GroupStoreExtension extends StoreExtensionProvider {
override name = 'affine-group-gfx';
override setup(context: StoreExtensionContext) {
super.setup(context);
context.register(groupToPlainTextAdapterMatcher);
context.register(groupToMarkdownAdapterMatcher);
context.register(groupRelationWatcherExtension);
}
}

View File

@@ -1,24 +1,27 @@
import type { GroupElementModel } from '@blocksuite/affine-model'; import {
import { GfxElementModelView } from '@blocksuite/std/gfx'; type ViewExtensionContext,
ViewExtensionProvider,
} from '@blocksuite/affine-ext-loader';
import { mountGroupTitleEditor } from './text/edgeless-group-title-editor'; import { effects } from './effects';
import { GroupElementRendererExtension } from './element-renderer';
import { GroupElementView } from './element-view';
import { groupToolbarExtension } from './toolbar/config';
export class GroupElementView extends GfxElementModelView<GroupElementModel> { export class GroupViewExtension extends ViewExtensionProvider {
static override type: string = 'group'; override name = 'affine-group-gfx';
override onCreated(): void { override effect(): void {
super.onCreated(); super.effect();
effects();
this._initDblClickToEdit();
} }
private _initDblClickToEdit(): void { override setup(context: ViewExtensionContext) {
this.on('dblclick', () => { super.setup(context);
const edgeless = this.std.view.getBlock(this.std.store.root!.id); context.register(GroupElementRendererExtension);
context.register(GroupElementView);
if (edgeless && !this.model.isLocked()) { if (this.isEdgeless(context.scope)) {
mountGroupTitleEditor(this.model, edgeless); context.register(groupToolbarExtension);
} }
});
} }
} }

View File

@@ -9,6 +9,7 @@
"references": [ "references": [
{ "path": "../../blocks/surface" }, { "path": "../../blocks/surface" },
{ "path": "../../components" }, { "path": "../../components" },
{ "path": "../../ext-loader" },
{ "path": "../text" }, { "path": "../text" },
{ "path": "../../model" }, { "path": "../../model" },
{ "path": "../../rich-text" }, { "path": "../../rich-text" },

View File

@@ -2,6 +2,6 @@ export * from './adapter';
export * from './commands'; export * from './commands';
export * from './edgeless-text-editor'; export * from './edgeless-text-editor';
export * from './element-renderer'; export * from './element-renderer';
export * from './element-view';
export * from './tool'; export * from './tool';
export * from './toolbar'; export * from './toolbar';
export * from './view';

View File

@@ -530,6 +530,7 @@ export const PackageList = [
workspaceDependencies: [ workspaceDependencies: [
'blocksuite/affine/blocks/surface', 'blocksuite/affine/blocks/surface',
'blocksuite/affine/components', 'blocksuite/affine/components',
'blocksuite/affine/ext-loader',
'blocksuite/affine/gfx/text', 'blocksuite/affine/gfx/text',
'blocksuite/affine/model', 'blocksuite/affine/model',
'blocksuite/affine/rich-text', 'blocksuite/affine/rich-text',

View File

@@ -3159,6 +3159,7 @@ __metadata:
dependencies: dependencies:
"@blocksuite/affine-block-surface": "workspace:*" "@blocksuite/affine-block-surface": "workspace:*"
"@blocksuite/affine-components": "workspace:*" "@blocksuite/affine-components": "workspace:*"
"@blocksuite/affine-ext-loader": "workspace:*"
"@blocksuite/affine-gfx-text": "workspace:*" "@blocksuite/affine-gfx-text": "workspace:*"
"@blocksuite/affine-model": "workspace:*" "@blocksuite/affine-model": "workspace:*"
"@blocksuite/affine-rich-text": "workspace:*" "@blocksuite/affine-rich-text": "workspace:*"