mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-09-07 01:09:54 +08:00
feat(editor): add edgeless crud extension (#9335)
This commit is contained in:
@@ -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';
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user