diff --git a/blocksuite/affine/all/package.json b/blocksuite/affine/all/package.json index 9ebc581a19..7864565304 100644 --- a/blocksuite/affine/all/package.json +++ b/blocksuite/affine/all/package.json @@ -34,6 +34,7 @@ "@blocksuite/affine-fragment-frame-panel": "workspace:*", "@blocksuite/affine-fragment-outline": "workspace:*", "@blocksuite/affine-gfx-connector": "workspace:*", + "@blocksuite/affine-gfx-group": "workspace:*", "@blocksuite/affine-gfx-mindmap": "workspace:*", "@blocksuite/affine-gfx-note": "workspace:*", "@blocksuite/affine-gfx-shape": "workspace:*", @@ -124,6 +125,7 @@ "./gfx/note": "./src/gfx/note.ts", "./gfx/mindmap": "./src/gfx/mindmap.ts", "./gfx/connector": "./src/gfx/connector.ts", + "./gfx/group": "./src/gfx/group.ts", "./gfx/turbo-renderer": "./src/gfx/turbo-renderer.ts", "./components/block-selection": "./src/components/block-selection.ts", "./components/block-zero-width": "./src/components/block-zero-width.ts", diff --git a/blocksuite/affine/all/src/gfx/group.ts b/blocksuite/affine/all/src/gfx/group.ts new file mode 100644 index 0000000000..6a65959da6 --- /dev/null +++ b/blocksuite/affine/all/src/gfx/group.ts @@ -0,0 +1 @@ +export * from '@blocksuite/affine-gfx-group'; diff --git a/blocksuite/affine/all/tsconfig.json b/blocksuite/affine/all/tsconfig.json index d1920e6738..89fc97d120 100644 --- a/blocksuite/affine/all/tsconfig.json +++ b/blocksuite/affine/all/tsconfig.json @@ -31,6 +31,7 @@ { "path": "../fragments/fragment-frame-panel" }, { "path": "../fragments/fragment-outline" }, { "path": "../gfx/connector" }, + { "path": "../gfx/group" }, { "path": "../gfx/mindmap" }, { "path": "../gfx/note" }, { "path": "../gfx/shape" }, diff --git a/blocksuite/affine/blocks/block-root/package.json b/blocksuite/affine/blocks/block-root/package.json index 8e063a72f3..8c8048a66c 100644 --- a/blocksuite/affine/blocks/block-root/package.json +++ b/blocksuite/affine/blocks/block-root/package.json @@ -28,6 +28,7 @@ "@blocksuite/affine-components": "workspace:*", "@blocksuite/affine-fragment-doc-title": "workspace:*", "@blocksuite/affine-gfx-connector": "workspace:*", + "@blocksuite/affine-gfx-group": "workspace:*", "@blocksuite/affine-gfx-mindmap": "workspace:*", "@blocksuite/affine-gfx-note": "workspace:*", "@blocksuite/affine-gfx-shape": "workspace:*", diff --git a/blocksuite/affine/blocks/block-root/src/edgeless/configs/toolbar/index.ts b/blocksuite/affine/blocks/block-root/src/edgeless/configs/toolbar/index.ts index 26afc93a74..d1b9375d89 100644 --- a/blocksuite/affine/blocks/block-root/src/edgeless/configs/toolbar/index.ts +++ b/blocksuite/affine/blocks/block-root/src/edgeless/configs/toolbar/index.ts @@ -1,6 +1,7 @@ import { edgelessTextToolbarExtension } from '@blocksuite/affine-block-edgeless-text'; import { frameToolbarExtension } from '@blocksuite/affine-block-frame'; import { connectorToolbarExtension } from '@blocksuite/affine-gfx-connector'; +import { groupToolbarExtension } from '@blocksuite/affine-gfx-group'; import { mindmapToolbarExtension } from '@blocksuite/affine-gfx-mindmap'; import { shapeToolbarExtension } from '@blocksuite/affine-gfx-shape'; import { textToolbarExtension } from '@blocksuite/affine-gfx-text'; @@ -9,16 +10,12 @@ import { BlockFlavourIdentifier } from '@blocksuite/block-std'; import type { ExtensionType } from '@blocksuite/store'; import { builtinBrushToolbarConfig } from './brush'; -import { builtinGroupToolbarConfig } from './group'; import { builtinLockedToolbarConfig, builtinMiscToolbarConfig } from './misc'; export const EdgelessElementToolbarExtension: ExtensionType[] = [ frameToolbarExtension, - ToolbarModuleExtension({ - id: BlockFlavourIdentifier('affine:surface:group'), - config: builtinGroupToolbarConfig, - }), + groupToolbarExtension, ToolbarModuleExtension({ id: BlockFlavourIdentifier('affine:surface:brush'), diff --git a/blocksuite/affine/blocks/block-root/src/edgeless/configs/toolbar/misc.ts b/blocksuite/affine/blocks/block-root/src/edgeless/configs/toolbar/misc.ts index a58470821e..179485bac3 100644 --- a/blocksuite/affine/blocks/block-root/src/edgeless/configs/toolbar/misc.ts +++ b/blocksuite/affine/blocks/block-root/src/edgeless/configs/toolbar/misc.ts @@ -3,6 +3,11 @@ import { EdgelessCRUDIdentifier, getSurfaceComponent, } from '@blocksuite/affine-block-surface'; +import { + createGroupCommand, + createGroupFromSelectedCommand, + ungroupCommand, +} from '@blocksuite/affine-gfx-group'; import { ConnectorElementModel, DEFAULT_CONNECTOR_MODE, @@ -29,7 +34,6 @@ import { } from '@blocksuite/icons/lit'; import { html } from 'lit'; -import { EdgelessRootService } from '../../edgeless-root-service'; import { renderAlignmentMenu } from './alignment'; import { moreActions } from './more'; @@ -137,10 +141,8 @@ export const builtinMiscToolbarConfig = { const models = ctx.getSurfaceModels(); if (models.length < 2) return; - const service = ctx.std.get(EdgelessRootService); - // TODO(@fundon): should be a command - service.createGroupFromSelected(); + ctx.command.exec(createGroupFromSelectedCommand); }, }, { @@ -268,8 +270,9 @@ export const builtinMiscToolbarConfig = { return; } - const service = ctx.std.get(EdgelessRootService); - const groupId = service.createGroup([topElement, ...otherElements]); + const [_, { groupId }] = ctx.command.exec(createGroupCommand, { + elements: [topElement, ...otherElements], + }); if (groupId) { const element = ctx.std @@ -335,11 +338,9 @@ export const builtinLockedToolbarConfig = { ctx.store.captureSync(); - const service = ctx.std.get(EdgelessRootService); - for (const element of elements) { if (element instanceof GroupElementModel) { - service.ungroup(element); + ctx.command.exec(ungroupCommand, { group: element }); } else { element.lockedBySelf = false; } diff --git a/blocksuite/affine/blocks/block-root/src/edgeless/configs/toolbar/more.ts b/blocksuite/affine/blocks/block-root/src/edgeless/configs/toolbar/more.ts index f201ef254c..47f44cf6f8 100644 --- a/blocksuite/affine/blocks/block-root/src/edgeless/configs/toolbar/more.ts +++ b/blocksuite/affine/blocks/block-root/src/edgeless/configs/toolbar/more.ts @@ -11,6 +11,7 @@ import { EdgelessCRUDIdentifier, getSurfaceComponent, } from '@blocksuite/affine-block-surface'; +import { createGroupFromSelectedCommand } from '@blocksuite/affine-gfx-group'; import { AttachmentBlockModel, BookmarkBlockModel, @@ -45,7 +46,6 @@ import { ResetIcon, } from '@blocksuite/icons/lit'; -import { EdgelessRootService } from '../../edgeless-root-service'; import { duplicate } from '../../utils/clipboard-utils'; import { getSortedCloneElements } from '../../utils/clone-utils'; import { moveConnectors } from '../../utils/connector'; @@ -92,8 +92,7 @@ export const moreActions = [ return !models.some(model => ctx.matchModel(model, FrameBlockModel)); }, run(ctx) { - const service = ctx.std.get(EdgelessRootService); - service.createGroupFromSelected(); + ctx.command.exec(createGroupFromSelectedCommand); }, }, ], diff --git a/blocksuite/affine/blocks/block-root/src/edgeless/edgeless-keyboard.ts b/blocksuite/affine/blocks/block-root/src/edgeless/edgeless-keyboard.ts index 102a220ddb..e9fd7ef45c 100644 --- a/blocksuite/affine/blocks/block-root/src/edgeless/edgeless-keyboard.ts +++ b/blocksuite/affine/blocks/block-root/src/edgeless/edgeless-keyboard.ts @@ -3,6 +3,10 @@ import { EdgelessTextBlockComponent } from '@blocksuite/affine-block-edgeless-te import { isNoteBlock } from '@blocksuite/affine-block-surface'; import { toast } from '@blocksuite/affine-components/toast'; import { mountConnectorLabelEditor } from '@blocksuite/affine-gfx-connector'; +import { + createGroupFromSelectedCommand, + ungroupCommand, +} from '@blocksuite/affine-gfx-group'; import { getNearestTranslation, isElementOutsideViewport, @@ -227,7 +231,7 @@ export class EdgelessPageKeyboardManager extends PageKeyboardManager { !this.rootComponent.service.selection.editing ) { ctx.get('keyboardState').event.preventDefault(); - rootComponent.service.createGroupFromSelected(); + rootComponent.std.command.exec(createGroupFromSelectedCommand); } }, 'Shift-Mod-g': ctx => { @@ -239,7 +243,9 @@ export class EdgelessPageKeyboardManager extends PageKeyboardManager { !selection.firstElement.isLocked() ) { ctx.get('keyboardState').event.preventDefault(); - rootComponent.service.ungroup(selection.firstElement); + rootComponent.std.command.exec(ungroupCommand, { + group: selection.firstElement, + }); } }, 'Mod-a': ctx => { diff --git a/blocksuite/affine/blocks/block-root/src/edgeless/edgeless-root-service.ts b/blocksuite/affine/blocks/block-root/src/edgeless/edgeless-root-service.ts index d41c109536..2c71dc2f0f 100644 --- a/blocksuite/affine/blocks/block-root/src/edgeless/edgeless-root-service.ts +++ b/blocksuite/affine/blocks/block-root/src/edgeless/edgeless-root-service.ts @@ -10,8 +10,6 @@ import { } from '@blocksuite/affine-block-surface'; import { type ConnectorElementModel, - type GroupElementModel, - MindmapElementModel, RootBlockSchema, } from '@blocksuite/affine-model'; import type { BlockStdScope } from '@blocksuite/block-std'; @@ -166,70 +164,6 @@ export class EdgelessRootService extends RootService implements SurfaceContext { ); } - createGroup(elements: GfxModel[] | string[]) { - const groups = this.elements.filter( - el => el.type === 'group' - ) as GroupElementModel[]; - const groupId = this.crud.addElement('group', { - children: elements.reduce( - (pre, el) => { - const id = typeof el === 'string' ? el : el.id; - pre[id] = true; - return pre; - }, - {} as Record - ), - title: `Group ${groups.length + 1}`, - }); - - return groupId; - } - - /** - * Create a group from selected elements, if the selected elements are in the same group - * @returns the id of the created group - */ - createGroupFromSelected() { - const { selection } = this; - - if ( - selection.selectedElements.length === 0 || - !selection.selectedElements.every( - element => - element.group === selection.firstElement.group && - !(element.group instanceof MindmapElementModel) - ) - ) { - return; - } - - const parent = selection.firstElement.group as GroupElementModel; - - if (parent !== null) { - selection.selectedElements.forEach(element => { - // oxlint-disable-next-line unicorn/prefer-dom-node-remove - parent.removeChild(element); - }); - } - - const groupId = this.createGroup(selection.selectedElements); - if (!groupId) { - return; - } - const group = this.surface.getElementById(groupId); - - if (parent !== null && group) { - parent.addChild(group); - } - - selection.set({ - editing: false, - elements: [groupId], - }); - - return groupId; - } - createTemplateJob( type: 'template' | 'sticker', center?: { x: number; y: number } @@ -353,46 +287,6 @@ export class EdgelessRootService extends RootService implements SurfaceContext { this.viewport.smoothZoom(clamp(this.zoom + step, ZOOM_MIN, ZOOM_MAX)); } - ungroup(group: GroupElementModel) { - const { selection } = this; - const elements = group.childElements; - const parent = group.group as GroupElementModel; - - if (group instanceof MindmapElementModel) { - return; - } - - if (parent !== null) { - // oxlint-disable-next-line unicorn/prefer-dom-node-remove - parent.removeChild(group); - } - - elements.forEach(element => { - // oxlint-disable-next-line unicorn/prefer-dom-node-remove - group.removeChild(element); - }); - - // keep relative index order of group children after ungroup - elements - .sort((a, b) => this.layer.compare(a, b)) - .forEach(element => { - this.doc.transact(() => { - element.index = this.layer.generateIndex(); - }); - }); - - if (parent !== null) { - elements.forEach(element => { - parent.addChild(element); - }); - } - - selection.set({ - editing: false, - elements: elements.map(ele => ele.id), - }); - } - override unmounted() { super.unmounted(); diff --git a/blocksuite/affine/blocks/block-root/src/edgeless/gfx-tool/default-tool.ts b/blocksuite/affine/blocks/block-root/src/edgeless/gfx-tool/default-tool.ts index 65e890c717..68ee299ba3 100644 --- a/blocksuite/affine/blocks/block-root/src/edgeless/gfx-tool/default-tool.ts +++ b/blocksuite/affine/blocks/block-root/src/edgeless/gfx-tool/default-tool.ts @@ -9,6 +9,7 @@ import { OverlayIdentifier, } from '@blocksuite/affine-block-surface'; import { mountConnectorLabelEditor } from '@blocksuite/affine-gfx-connector'; +import { mountGroupTitleEditor } from '@blocksuite/affine-gfx-group'; import { mountShapeTextEditor } from '@blocksuite/affine-gfx-shape'; import { addText, mountTextElementEditor } from '@blocksuite/affine-gfx-text'; import type { @@ -52,7 +53,6 @@ import type { EdgelessRootBlockComponent } from '../index.js'; import { prepareCloneData } from '../utils/clone-utils.js'; import { calPanDelta } from '../utils/panning-utils.js'; import { isCanvasElement, isEdgelessTextBlock } from '../utils/query.js'; -import { mountGroupTitleEditor } from '../utils/text.js'; import { DefaultModeDragType } from './default-tool-ext/ext.js'; export class DefaultTool extends BaseTool { diff --git a/blocksuite/affine/blocks/block-root/src/effects.ts b/blocksuite/affine/blocks/block-root/src/effects.ts index 018f3cc4ae..ec16771216 100644 --- a/blocksuite/affine/blocks/block-root/src/effects.ts +++ b/blocksuite/affine/blocks/block-root/src/effects.ts @@ -1,4 +1,5 @@ import { effects as gfxConnectorEffects } from '@blocksuite/affine-gfx-connector/effects'; +import { effects as gfxGroupEffects } from '@blocksuite/affine-gfx-group/effects'; import { effects as gfxMindmapEffects } from '@blocksuite/affine-gfx-mindmap/effects'; import { effects as gfxNoteEffects } from '@blocksuite/affine-gfx-note/effects'; import { effects as gfxShapeEffects } from '@blocksuite/affine-gfx-shape/effects'; @@ -19,7 +20,6 @@ import { EDGELESS_SELECTED_RECT_WIDGET, EdgelessSelectedRectWidget, } from './edgeless/components/rects/edgeless-selected-rect.js'; -import { EdgelessGroupTitleEditor } from './edgeless/components/text/edgeless-group-title-editor.js'; import { EdgelessBrushMenu } from './edgeless/components/toolbar/brush/brush-menu.js'; import { EdgelessBrushToolButton } from './edgeless/components/toolbar/brush/brush-tool-button.js'; import { EdgelessSlideMenu } from './edgeless/components/toolbar/common/slide-menu.js'; @@ -81,7 +81,6 @@ export function effects() { registerGfxEffects(); registerWidgets(); registerEdgelessToolbarComponents(); - registerEdgelessEditorComponents(); registerMiscComponents(); } @@ -101,6 +100,7 @@ function registerGfxEffects() { gfxNoteEffects(); gfxConnectorEffects(); gfxMindmapEffects(); + gfxGroupEffects(); } function registerWidgets() { @@ -144,13 +144,6 @@ function registerEdgelessToolbarComponents() { customElements.define('toolbar-arrow-up-icon', ToolbarArrowUpIcon); } -function registerEdgelessEditorComponents() { - customElements.define( - 'edgeless-group-title-editor', - EdgelessGroupTitleEditor - ); -} - function registerMiscComponents() { // Modal and menu components customElements.define('affine-custom-modal', AffineCustomModal); @@ -203,7 +196,6 @@ declare global { 'edgeless-navigator-black-background': EdgelessNavigatorBlackBackgroundWidget; 'edgeless-dragging-area-rect': EdgelessDraggingAreaRectWidget; 'edgeless-selected-rect': EdgelessSelectedRectWidget; - 'edgeless-group-title-editor': EdgelessGroupTitleEditor; 'edgeless-brush-menu': EdgelessBrushMenu; 'edgeless-brush-tool-button': EdgelessBrushToolButton; 'edgeless-slide-menu': EdgelessSlideMenu; diff --git a/blocksuite/affine/blocks/block-root/tsconfig.json b/blocksuite/affine/blocks/block-root/tsconfig.json index fcb5db3fef..c99693331b 100644 --- a/blocksuite/affine/blocks/block-root/tsconfig.json +++ b/blocksuite/affine/blocks/block-root/tsconfig.json @@ -25,6 +25,7 @@ { "path": "../../components" }, { "path": "../../fragments/fragment-doc-title" }, { "path": "../../gfx/connector" }, + { "path": "../../gfx/group" }, { "path": "../../gfx/mindmap" }, { "path": "../../gfx/note" }, { "path": "../../gfx/shape" }, diff --git a/blocksuite/affine/gfx/group/package.json b/blocksuite/affine/gfx/group/package.json new file mode 100644 index 0000000000..fe79c68c94 --- /dev/null +++ b/blocksuite/affine/gfx/group/package.json @@ -0,0 +1,45 @@ +{ + "name": "@blocksuite/affine-gfx-group", + "description": "Gfx group for BlockSuite.", + "type": "module", + "scripts": { + "build": "tsc" + }, + "sideEffects": false, + "keywords": [], + "author": "toeverything", + "license": "MIT", + "dependencies": { + "@blocksuite/affine-block-surface": "workspace:*", + "@blocksuite/affine-components": "workspace:*", + "@blocksuite/affine-model": "workspace:*", + "@blocksuite/affine-rich-text": "workspace:*", + "@blocksuite/affine-shared": "workspace:*", + "@blocksuite/affine-widget-edgeless-toolbar": "workspace:*", + "@blocksuite/block-std": "workspace:*", + "@blocksuite/global": "workspace:*", + "@blocksuite/icons": "^2.2.6", + "@blocksuite/store": "workspace:*", + "@lit/context": "^1.1.2", + "@preact/signals-core": "^1.8.0", + "@toeverything/theme": "^1.1.12", + "@types/lodash-es": "^4.17.12", + "lit": "^3.2.0", + "lodash-es": "^4.17.21", + "minimatch": "^10.0.1", + "rxjs": "^7.8.1", + "yjs": "^13.6.21", + "zod": "^3.23.8" + }, + "exports": { + ".": "./src/index.ts", + "./effects": "./src/effects.ts" + }, + "files": [ + "src", + "dist", + "!src/__tests__", + "!dist/__tests__" + ], + "version": "0.20.0" +} diff --git a/blocksuite/affine/gfx/group/src/command/group-api.ts b/blocksuite/affine/gfx/group/src/command/group-api.ts new file mode 100644 index 0000000000..abb711c25f --- /dev/null +++ b/blocksuite/affine/gfx/group/src/command/group-api.ts @@ -0,0 +1,137 @@ +import { EdgelessCRUDIdentifier } from '@blocksuite/affine-block-surface'; +import { + type GroupElementModel, + MindmapElementModel, +} from '@blocksuite/affine-model'; +import type { Command } from '@blocksuite/block-std'; +import { + GfxControllerIdentifier, + type GfxModel, +} from '@blocksuite/block-std/gfx'; + +export const createGroupCommand: Command< + { elements: GfxModel[] | string[] }, + { groupId: string } +> = (ctx, next) => { + const { std, elements } = ctx; + const gfx = std.get(GfxControllerIdentifier); + const crud = std.get(EdgelessCRUDIdentifier); + + const groups = gfx.layer.canvasElements.filter( + el => el.type === 'group' + ) as GroupElementModel[]; + const groupId = crud.addElement('group', { + children: elements.reduce( + (pre, el) => { + const id = typeof el === 'string' ? el : el.id; + pre[id] = true; + return pre; + }, + {} as Record + ), + title: `Group ${groups.length + 1}`, + }); + if (!groupId) { + return; + } + + next({ groupId }); +}; + +export const createGroupFromSelectedCommand: Command< + {}, + { groupId: string } +> = (ctx, next) => { + const { std } = ctx; + const gfx = std.get(GfxControllerIdentifier); + const { selection, surface } = gfx; + + if (!surface) { + return; + } + + if ( + selection.selectedElements.length === 0 || + !selection.selectedElements.every( + element => + element.group === selection.firstElement.group && + !(element.group instanceof MindmapElementModel) + ) + ) { + return; + } + + const parent = selection.firstElement.group as GroupElementModel; + + if (parent !== null) { + selection.selectedElements.forEach(element => { + // oxlint-disable-next-line unicorn/prefer-dom-node-remove + parent.removeChild(element); + }); + } + + const [_, result] = std.command.exec(createGroupCommand, { + elements: selection.selectedElements, + }); + if (!result.groupId) { + return; + } + const group = surface.getElementById(result.groupId); + + if (parent !== null && group) { + parent.addChild(group); + } + + selection.set({ + editing: false, + elements: [result.groupId], + }); + + next({ groupId: result.groupId }); +}; + +export const ungroupCommand: Command<{ group: GroupElementModel }, {}> = ( + ctx, + next +) => { + const { std, group } = ctx; + const gfx = std.get(GfxControllerIdentifier); + const { selection } = gfx; + const parent = group.group as GroupElementModel; + const elements = group.childElements; + + if (group instanceof MindmapElementModel) { + return; + } + + if (parent !== null) { + // oxlint-disable-next-line unicorn/prefer-dom-node-remove + parent.removeChild(group); + } + + elements.forEach(element => { + // oxlint-disable-next-line unicorn/prefer-dom-node-remove + group.removeChild(element); + }); + + // keep relative index order of group children after ungroup + elements + .sort((a, b) => gfx.layer.compare(a, b)) + .forEach(element => { + std.store.transact(() => { + element.index = gfx.layer.generateIndex(); + }); + }); + + if (parent !== null) { + elements.forEach(element => { + parent.addChild(element); + }); + } + + selection.set({ + editing: false, + elements: elements.map(ele => ele.id), + }); + next(); +}; diff --git a/blocksuite/affine/gfx/group/src/command/index.ts b/blocksuite/affine/gfx/group/src/command/index.ts new file mode 100644 index 0000000000..ca75ba2c3f --- /dev/null +++ b/blocksuite/affine/gfx/group/src/command/index.ts @@ -0,0 +1 @@ +export * from './group-api'; diff --git a/blocksuite/affine/gfx/group/src/effects.ts b/blocksuite/affine/gfx/group/src/effects.ts new file mode 100644 index 0000000000..af289b93f3 --- /dev/null +++ b/blocksuite/affine/gfx/group/src/effects.ts @@ -0,0 +1,14 @@ +import { EdgelessGroupTitleEditor } from './text/edgeless-group-title-editor'; + +export function effects() { + customElements.define( + 'edgeless-group-title-editor', + EdgelessGroupTitleEditor + ); +} + +declare global { + interface HTMLElementTagNameMap { + 'edgeless-group-title-editor': EdgelessGroupTitleEditor; + } +} diff --git a/blocksuite/affine/gfx/group/src/index.ts b/blocksuite/affine/gfx/group/src/index.ts new file mode 100644 index 0000000000..be0f635062 --- /dev/null +++ b/blocksuite/affine/gfx/group/src/index.ts @@ -0,0 +1,3 @@ +export * from './command'; +export * from './text/text'; +export * from './toolbar/config'; diff --git a/blocksuite/affine/blocks/block-root/src/edgeless/components/text/edgeless-group-title-editor.ts b/blocksuite/affine/gfx/group/src/text/edgeless-group-title-editor.ts similarity index 100% rename from blocksuite/affine/blocks/block-root/src/edgeless/components/text/edgeless-group-title-editor.ts rename to blocksuite/affine/gfx/group/src/text/edgeless-group-title-editor.ts diff --git a/blocksuite/affine/blocks/block-root/src/edgeless/utils/text.ts b/blocksuite/affine/gfx/group/src/text/text.ts similarity index 86% rename from blocksuite/affine/blocks/block-root/src/edgeless/utils/text.ts rename to blocksuite/affine/gfx/group/src/text/text.ts index b66162b266..da2ca1a6af 100644 --- a/blocksuite/affine/blocks/block-root/src/edgeless/utils/text.ts +++ b/blocksuite/affine/gfx/group/src/text/text.ts @@ -3,7 +3,7 @@ import type { BlockComponent } from '@blocksuite/block-std'; import { GfxControllerIdentifier } from '@blocksuite/block-std/gfx'; import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions'; -import { EdgelessGroupTitleEditor } from '../components/text/edgeless-group-title-editor.js'; +import { EdgelessGroupTitleEditor } from './edgeless-group-title-editor'; export function mountGroupTitleEditor( group: GroupElementModel, @@ -19,6 +19,7 @@ export function mountGroupTitleEditor( const gfx = edgeless.std.get(GfxControllerIdentifier); + // @ts-expect-error FIXME: resolve after gfx tool refactor gfx.tool.setTool('default'); gfx.selection.set({ elements: [group.id], diff --git a/blocksuite/affine/blocks/block-root/src/edgeless/configs/toolbar/group.ts b/blocksuite/affine/gfx/group/src/toolbar/config.ts similarity index 83% rename from blocksuite/affine/blocks/block-root/src/edgeless/configs/toolbar/group.ts rename to blocksuite/affine/gfx/group/src/toolbar/config.ts index 35074e71a3..a4eaa041c7 100644 --- a/blocksuite/affine/blocks/block-root/src/edgeless/configs/toolbar/group.ts +++ b/blocksuite/affine/gfx/group/src/toolbar/config.ts @@ -7,16 +7,20 @@ import { NoteDisplayMode, SurfaceRefBlockSchema, } from '@blocksuite/affine-model'; -import { type ToolbarModuleConfig } from '@blocksuite/affine-shared/services'; +import { + type ToolbarModuleConfig, + ToolbarModuleExtension, +} from '@blocksuite/affine-shared/services'; import { matchModels } from '@blocksuite/affine-shared/utils'; import { getRootBlock } from '@blocksuite/affine-widget-edgeless-toolbar'; +import { BlockFlavourIdentifier } from '@blocksuite/block-std'; import { Bound } from '@blocksuite/global/gfx'; import { EditIcon, PageIcon, UngroupIcon } from '@blocksuite/icons/lit'; -import { EdgelessRootService } from '../../edgeless-root-service'; -import { mountGroupTitleEditor } from '../../utils/text'; +import { ungroupCommand } from '../command'; +import { mountGroupTitleEditor } from '../text/text'; -export const builtinGroupToolbarConfig = { +export const groupToolbarConfig = { actions: [ { id: 'a.insert-into-page', @@ -85,10 +89,8 @@ export const builtinGroupToolbarConfig = { const models = ctx.getSurfaceModelsByType(GroupElementModel); if (!models.length) return; - const edgelessService = ctx.std.get(EdgelessRootService); - for (const model of models) { - edgelessService.ungroup(model); + ctx.command.exec(ungroupCommand, { group: model }); } }, }, @@ -96,3 +98,8 @@ export const builtinGroupToolbarConfig = { when: ctx => ctx.getSurfaceModelsByType(GroupElementModel).length > 0, } as const satisfies ToolbarModuleConfig; + +export const groupToolbarExtension = ToolbarModuleExtension({ + id: BlockFlavourIdentifier('affine:surface:group'), + config: groupToolbarConfig, +}); diff --git a/blocksuite/affine/gfx/group/tsconfig.json b/blocksuite/affine/gfx/group/tsconfig.json new file mode 100644 index 0000000000..5b280fe89a --- /dev/null +++ b/blocksuite/affine/gfx/group/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./dist", + "tsBuildInfoFile": "./dist/tsconfig.tsbuildinfo" + }, + "include": ["./src"], + "references": [ + { "path": "../../blocks/block-surface" }, + { "path": "../../components" }, + { "path": "../../model" }, + { "path": "../../rich-text" }, + { "path": "../../shared" }, + { "path": "../../widgets/widget-edgeless-toolbar" }, + { "path": "../../../framework/block-std" }, + { "path": "../../../framework/global" }, + { "path": "../../../framework/store" } + ] +} diff --git a/blocksuite/integration-test/src/__tests__/edgeless/layer.spec.ts b/blocksuite/integration-test/src/__tests__/edgeless/layer.spec.ts index 0021125b7f..29e651b06d 100644 --- a/blocksuite/integration-test/src/__tests__/edgeless/layer.spec.ts +++ b/blocksuite/integration-test/src/__tests__/edgeless/layer.spec.ts @@ -1,11 +1,12 @@ +import { generateKeyBetween } from '@blocksuite/affine/block-std/gfx'; import type { EdgelessRootBlockComponent } from '@blocksuite/affine/blocks/root'; import type { SurfaceElementModel } from '@blocksuite/affine/blocks/surface'; +import { ungroupCommand } from '@blocksuite/affine/gfx/group'; import type { GroupElementModel, NoteBlockModel, } from '@blocksuite/affine/model'; import type { BlockComponent } from '@blocksuite/block-std'; -import { generateKeyBetween } from '@blocksuite/block-std/gfx'; import type { BlockModel, Store } from '@blocksuite/store'; import { beforeEach, describe, expect, test } from 'vitest'; import * as Y from 'yjs'; @@ -554,7 +555,9 @@ describe('group related functionality', () => { const groupId = createGroup(edgeless.service, elementIds)!; expect(isKeptRelativeOrder()).toBeTruthy(); - service.ungroup(service.crud.getElementById(groupId) as GroupElementModel); + service.std.command.exec(ungroupCommand, { + group: service.crud.getElementById(groupId) as GroupElementModel, + }); expect(isKeptRelativeOrder()).toBeTruthy(); service.doc.undo(); diff --git a/tools/utils/src/workspace.gen.ts b/tools/utils/src/workspace.gen.ts index dd2d81485d..bac0a463d8 100644 --- a/tools/utils/src/workspace.gen.ts +++ b/tools/utils/src/workspace.gen.ts @@ -29,6 +29,7 @@ export const PackageList = [ 'blocksuite/affine/fragments/fragment-frame-panel', 'blocksuite/affine/fragments/fragment-outline', 'blocksuite/affine/gfx/connector', + 'blocksuite/affine/gfx/group', 'blocksuite/affine/gfx/mindmap', 'blocksuite/affine/gfx/note', 'blocksuite/affine/gfx/shape', @@ -312,6 +313,7 @@ export const PackageList = [ 'blocksuite/affine/components', 'blocksuite/affine/fragments/fragment-doc-title', 'blocksuite/affine/gfx/connector', + 'blocksuite/affine/gfx/group', 'blocksuite/affine/gfx/mindmap', 'blocksuite/affine/gfx/note', 'blocksuite/affine/gfx/shape', @@ -463,6 +465,21 @@ export const PackageList = [ 'blocksuite/framework/store', ], }, + { + location: 'blocksuite/affine/gfx/group', + name: '@blocksuite/affine-gfx-group', + workspaceDependencies: [ + 'blocksuite/affine/blocks/block-surface', + 'blocksuite/affine/components', + 'blocksuite/affine/model', + 'blocksuite/affine/rich-text', + 'blocksuite/affine/shared', + 'blocksuite/affine/widgets/widget-edgeless-toolbar', + 'blocksuite/framework/block-std', + 'blocksuite/framework/global', + 'blocksuite/framework/store', + ], + }, { location: 'blocksuite/affine/gfx/mindmap', name: '@blocksuite/affine-gfx-mindmap', @@ -1129,6 +1146,7 @@ export type PackageName = | '@blocksuite/affine-fragment-frame-panel' | '@blocksuite/affine-fragment-outline' | '@blocksuite/affine-gfx-connector' + | '@blocksuite/affine-gfx-group' | '@blocksuite/affine-gfx-mindmap' | '@blocksuite/affine-gfx-note' | '@blocksuite/affine-gfx-shape' diff --git a/tsconfig.json b/tsconfig.json index 1a0930d39e..6eb4508579 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -76,6 +76,7 @@ { "path": "./blocksuite/affine/fragments/fragment-frame-panel" }, { "path": "./blocksuite/affine/fragments/fragment-outline" }, { "path": "./blocksuite/affine/gfx/connector" }, + { "path": "./blocksuite/affine/gfx/group" }, { "path": "./blocksuite/affine/gfx/mindmap" }, { "path": "./blocksuite/affine/gfx/note" }, { "path": "./blocksuite/affine/gfx/shape" }, diff --git a/yarn.lock b/yarn.lock index d31e3692eb..5d315832ae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2734,6 +2734,7 @@ __metadata: "@blocksuite/affine-components": "workspace:*" "@blocksuite/affine-fragment-doc-title": "workspace:*" "@blocksuite/affine-gfx-connector": "workspace:*" + "@blocksuite/affine-gfx-group": "workspace:*" "@blocksuite/affine-gfx-mindmap": "workspace:*" "@blocksuite/affine-gfx-note": "workspace:*" "@blocksuite/affine-gfx-shape": "workspace:*" @@ -2998,6 +2999,33 @@ __metadata: languageName: unknown linkType: soft +"@blocksuite/affine-gfx-group@workspace:*, @blocksuite/affine-gfx-group@workspace:blocksuite/affine/gfx/group": + version: 0.0.0-use.local + resolution: "@blocksuite/affine-gfx-group@workspace:blocksuite/affine/gfx/group" + dependencies: + "@blocksuite/affine-block-surface": "workspace:*" + "@blocksuite/affine-components": "workspace:*" + "@blocksuite/affine-model": "workspace:*" + "@blocksuite/affine-rich-text": "workspace:*" + "@blocksuite/affine-shared": "workspace:*" + "@blocksuite/affine-widget-edgeless-toolbar": "workspace:*" + "@blocksuite/block-std": "workspace:*" + "@blocksuite/global": "workspace:*" + "@blocksuite/icons": "npm:^2.2.6" + "@blocksuite/store": "workspace:*" + "@lit/context": "npm:^1.1.2" + "@preact/signals-core": "npm:^1.8.0" + "@toeverything/theme": "npm:^1.1.12" + "@types/lodash-es": "npm:^4.17.12" + lit: "npm:^3.2.0" + lodash-es: "npm:^4.17.21" + minimatch: "npm:^10.0.1" + rxjs: "npm:^7.8.1" + yjs: "npm:^13.6.21" + zod: "npm:^3.23.8" + languageName: unknown + linkType: soft + "@blocksuite/affine-gfx-mindmap@workspace:*, @blocksuite/affine-gfx-mindmap@workspace:blocksuite/affine/gfx/mindmap": version: 0.0.0-use.local resolution: "@blocksuite/affine-gfx-mindmap@workspace:blocksuite/affine/gfx/mindmap" @@ -3602,6 +3630,7 @@ __metadata: "@blocksuite/affine-fragment-frame-panel": "workspace:*" "@blocksuite/affine-fragment-outline": "workspace:*" "@blocksuite/affine-gfx-connector": "workspace:*" + "@blocksuite/affine-gfx-group": "workspace:*" "@blocksuite/affine-gfx-mindmap": "workspace:*" "@blocksuite/affine-gfx-note": "workspace:*" "@blocksuite/affine-gfx-shape": "workspace:*" @@ -3713,7 +3742,7 @@ __metadata: languageName: unknown linkType: soft -"@blocksuite/icons@npm:^2.2.8": +"@blocksuite/icons@npm:^2.2.6, @blocksuite/icons@npm:^2.2.8": version: 2.2.8 resolution: "@blocksuite/icons@npm:2.2.8" peerDependencies: