feat(editor): add edgeless crud extension (#9335)

This commit is contained in:
Saul-Mirone
2024-12-26 08:58:06 +00:00
parent 0de4f7abbb
commit 6afa1d542f
48 changed files with 629 additions and 423 deletions
@@ -5,7 +5,10 @@ import {
MindmapElementModel,
NoteBlockModel,
} from '@blocksuite/affine-model';
import type { GfxModel } from '@blocksuite/block-std/gfx';
import {
GfxControllerIdentifier,
type GfxModel,
} from '@blocksuite/block-std/gfx';
import { Bound } from '@blocksuite/global/utils';
import chunk from 'lodash.chunk';
@@ -15,6 +18,7 @@ const ALIGN_PADDING = 20;
import type { Command } from '@blocksuite/block-std';
import type { BlockModel, BlockProps } from '@blocksuite/store';
import { EdgelessCRUDIdentifier } from '../extensions/crud-extension.js';
import { updateXYWH } from '../utils/update-xywh.js';
/**
@@ -25,11 +29,10 @@ export const autoArrangeElementsCommand: Command<never, never, {}> = (
next
) => {
const { updateBlock } = ctx.std.doc;
const rootService = ctx.std.getService('affine:page');
// @ts-expect-error TODO: fix after edgeless refactor
const elements = rootService?.selection.selectedElements;
// @ts-expect-error TODO: fix after edgeless refactor
const updateElement = rootService?.updateElement;
const gfx = ctx.std.get(GfxControllerIdentifier);
const elements = gfx.selection.selectedElements;
const { updateElement } = ctx.std.get(EdgelessCRUDIdentifier);
if (elements && updateElement) {
autoArrangeElements(elements, updateElement, updateBlock);
}
@@ -44,11 +47,10 @@ export const autoResizeElementsCommand: Command<never, never, {}> = (
next
) => {
const { updateBlock } = ctx.std.doc;
const rootService = ctx.std.getService('affine:page');
// @ts-expect-error TODO: fix after edgeless refactor
const elements = rootService?.selection.selectedElements;
// @ts-expect-error TODO: fix after edgeless refactor
const updateElement = rootService?.updateElement;
const gfx = ctx.std.get(GfxControllerIdentifier);
const elements = gfx.selection.selectedElements;
const { updateElement } = ctx.std.get(EdgelessCRUDIdentifier);
if (elements && updateElement) {
autoResizeElements(elements, updateElement, updateBlock);
}
@@ -0,0 +1,140 @@
import { EditPropsStore } from '@blocksuite/affine-shared/services';
import {
type BlockStdScope,
Extension,
StdIdentifier,
} from '@blocksuite/block-std';
import { GfxControllerIdentifier } from '@blocksuite/block-std/gfx';
import { type Container, createIdentifier } from '@blocksuite/global/di';
import type { BlockModel } from '@blocksuite/store';
import type { SurfaceBlockModel } from '../surface-model';
import { getLastPropsKey } from '../utils/get-last-props-key';
import { isConnectable, isNoteBlock } from './query';
export const EdgelessCRUDIdentifier = createIdentifier<EdgelessCRUDExtension>(
'AffineEdgelessCrudService'
);
export class EdgelessCRUDExtension extends Extension {
constructor(readonly std: BlockStdScope) {
super();
}
static override setup(di: Container) {
di.add(this, [StdIdentifier]);
di.addImpl(EdgelessCRUDIdentifier, provider => provider.get(this));
}
private get _gfx() {
return this.std.get(GfxControllerIdentifier);
}
private get _surface() {
return this._gfx.surface as SurfaceBlockModel | null;
}
deleteElements(elements: BlockSuite.EdgelessModel[]) {
const surface = this._surface;
if (!surface) {
console.error('surface is not initialized');
return;
}
const gfx = this.std.get(GfxControllerIdentifier);
const set = new Set(elements);
elements.forEach(element => {
if (isConnectable(element)) {
const connectors = surface.getConnectors(element.id);
connectors.forEach(connector => set.add(connector));
}
});
set.forEach(element => {
if (isNoteBlock(element)) {
const children = gfx.doc.root?.children ?? [];
if (children.length > 1) {
gfx.doc.deleteBlock(element);
}
} else {
gfx.deleteElement(element.id);
}
});
}
addBlock(
flavour: BlockSuite.EdgelessModelKeys | string,
props: Record<string, unknown>,
parentId?: string | BlockModel,
parentIndex?: number
) {
const gfx = this.std.get(GfxControllerIdentifier);
const key = getLastPropsKey(flavour as BlockSuite.EdgelessModelKeys, props);
if (key) {
props = this.std.get(EditPropsStore).applyLastProps(key, props);
}
const nProps = {
...props,
index: gfx.layer.generateIndex(),
};
return this.std.doc.addBlock(
flavour as never,
nProps,
parentId,
parentIndex
);
}
addElement<T extends Record<string, unknown>>(type: string, props: T) {
const surface = this._surface;
if (!surface) {
console.error('surface is not initialized');
return;
}
const gfx = this.std.get(GfxControllerIdentifier);
const key = getLastPropsKey(type as BlockSuite.EdgelessModelKeys, props);
if (key) {
props = this.std.get(EditPropsStore).applyLastProps(key, props) as T;
}
const nProps = {
...props,
type,
index: props.index ?? gfx.layer.generateIndex(),
};
return surface.addElement(nProps);
}
updateElement = (id: string, props: Record<string, unknown>) => {
const surface = this._surface;
if (!surface) {
console.error('surface is not initialized');
return;
}
const element = this._surface.getElementById(id);
if (element) {
const key = getLastPropsKey(
element.type as BlockSuite.EdgelessModelKeys,
{ ...element.yMap.toJSON(), ...props }
);
key && this.std.get(EditPropsStore).recordLastProps(key, props);
this._surface.updateElement(id, props);
return;
}
const block = this.std.doc.getBlockById(id);
if (block) {
const key = getLastPropsKey(
block.flavour as BlockSuite.EdgelessModelKeys,
{ ...block.yBlock.toJSON(), ...props }
);
key && this.std.get(EditPropsStore).recordLastProps(key, props);
this.std.doc.updateBlock(block, props);
}
};
}
@@ -0,0 +1 @@
export * from './crud-extension';
@@ -0,0 +1,16 @@
import type { NoteBlockModel } from '@blocksuite/affine-model';
import type { BlockModel } from '@blocksuite/store';
import type { Connectable } from '../managers/connector-manager';
export function isConnectable(
element: BlockSuite.EdgelessModel | null
): element is Connectable {
return !!element && element.connectable;
}
export function isNoteBlock(
element: BlockModel | BlockSuite.EdgelessModel | null
): element is NoteBlockModel {
return !!element && 'flavour' in element && element.flavour === 'affine:note';
}
+11 -4
View File
@@ -88,7 +88,11 @@ import {
} from '@blocksuite/global/utils';
import { generateKeyBetween } from 'fractional-indexing';
import { generateElementId, normalizeWheelDeltaY } from './utils/index.js';
import {
generateElementId,
getLastPropsKey,
normalizeWheelDeltaY,
} from './utils';
import {
addTree,
containsNode,
@@ -98,9 +102,11 @@ import {
hideNodeConnector,
moveNode,
tryMoveNode,
} from './utils/mindmap/utils.js';
export type { Options } from './utils/rough/core.js';
export { sortIndex } from './utils/sort.js';
} from './utils/mindmap/utils';
export * from './extensions';
export { getLastPropsKey } from './utils/get-last-props-key';
export type { Options } from './utils/rough/core';
export { sortIndex } from './utils/sort';
export { updateXYWH } from './utils/update-xywh.js';
export const ConnectorUtils = {
@@ -129,6 +135,7 @@ export const CommonUtils = {
getPointFromBoundsWithRotation,
getStroke,
getSvgPathFromStroke,
getLastPropsKey,
intersects,
isOverlap,
isPointIn,
@@ -12,6 +12,7 @@ import {
SurfaceBlockAdapterExtensions,
} from './adapters/extension.js';
import { commands } from './commands/index.js';
import { EdgelessCRUDExtension } from './extensions/crud-extension.js';
import { SurfaceBlockService } from './surface-service.js';
import { MindMapView } from './view/mindmap.js';
@@ -21,6 +22,7 @@ const CommonSurfaceBlockSpec: ExtensionType[] = [
CommandExtension(commands),
HighlightSelectionExtension,
MindMapView,
EdgelessCRUDExtension,
];
export const PageSurfaceBlockSpec: ExtensionType[] = [
@@ -0,0 +1,29 @@
import { getShapeName, type ShapeProps } from '@blocksuite/affine-model';
import type {
LastProps,
LastPropsKey,
} from '@blocksuite/affine-shared/services';
import { NodePropsSchema } from '@blocksuite/affine-shared/utils';
const LastPropsSchema = NodePropsSchema;
export function getLastPropsKey(
modelType: BlockSuite.EdgelessModelKeys,
modelProps: Partial<LastProps[LastPropsKey]>
): LastPropsKey | null {
if (modelType === 'shape') {
const { shapeType, radius } = modelProps as ShapeProps;
const shapeName = getShapeName(shapeType, radius);
return `${modelType}:${shapeName}`;
}
if (isLastPropsKey(modelType)) {
return modelType;
}
return null;
}
function isLastPropsKey(key: string): key is LastPropsKey {
return Object.keys(LastPropsSchema.shape).includes(key);
}
@@ -32,3 +32,5 @@ export function normalizeWheelDeltaY(delta: number, zoom = 1) {
Math.min(1, abs / 20);
return newZoom;
}
export { getLastPropsKey } from './get-last-props-key';