Skip to content

Commit

Permalink
feat(editor): add edgeless crud extension (#9335)
Browse files Browse the repository at this point in the history
  • Loading branch information
Saul-Mirone committed Dec 26, 2024
1 parent 0de4f7a commit 6afa1d5
Show file tree
Hide file tree
Showing 48 changed files with 629 additions and 423 deletions.
24 changes: 13 additions & 11 deletions blocksuite/affine/block-surface/src/commands/auto-align.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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';

/**
Expand All @@ -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);
}
Expand All @@ -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);
}
Expand Down
140 changes: 140 additions & 0 deletions blocksuite/affine/block-surface/src/extensions/crud-extension.ts
Original file line number Diff line number Diff line change
@@ -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);
}
};
}
1 change: 1 addition & 0 deletions blocksuite/affine/block-surface/src/extensions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './crud-extension';
16 changes: 16 additions & 0 deletions blocksuite/affine/block-surface/src/extensions/query.ts
Original file line number Diff line number Diff line change
@@ -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';
}
15 changes: 11 additions & 4 deletions blocksuite/affine/block-surface/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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 = {
Expand Down Expand Up @@ -129,6 +135,7 @@ export const CommonUtils = {
getPointFromBoundsWithRotation,
getStroke,
getSvgPathFromStroke,
getLastPropsKey,
intersects,
isOverlap,
isPointIn,
Expand Down
2 changes: 2 additions & 0 deletions blocksuite/affine/block-surface/src/surface-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -21,6 +22,7 @@ const CommonSurfaceBlockSpec: ExtensionType[] = [
CommandExtension(commands),
HighlightSelectionExtension,
MindMapView,
EdgelessCRUDExtension,
];

export const PageSurfaceBlockSpec: ExtensionType[] = [
Expand Down
2 changes: 2 additions & 0 deletions blocksuite/affine/block-surface/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { EdgelessCRUDIdentifier } from '@blocksuite/affine-block-surface';
import { toast } from '@blocksuite/affine-components/toast';
import type { EmbedCardStyle } from '@blocksuite/affine-model';
import {
Expand Down Expand Up @@ -71,6 +72,7 @@ export class EmbedCardCreateModal extends WithDisposable(ShadowlessElement) {
}

const gfx = this.host.std.get(GfxControllerIdentifier);
const crud = this.host.std.get(EdgelessCRUDIdentifier);

const viewport = gfx.viewport;
const surfaceModel = gfx.surface;
Expand All @@ -79,7 +81,7 @@ export class EmbedCardCreateModal extends WithDisposable(ShadowlessElement) {
}

const center = Vec.toVec(viewport.center);
edgelessRoot.service.addBlock(
crud.addBlock(
flavour,
{
url,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { EdgelessCRUDIdentifier } from '@blocksuite/affine-block-surface';
import { focusTextModel } from '@blocksuite/affine-components/rich-text';
import type { Command } from '@blocksuite/block-std';
import { GfxControllerIdentifier } from '@blocksuite/block-std/gfx';
import { Bound } from '@blocksuite/global/utils';

import { EdgelessRootService } from '../../root-block/edgeless/edgeless-root-service.js';
import { getSurfaceBlock } from '../../surface-ref-block/utils.js';
import {
EDGELESS_TEXT_BLOCK_MIN_HEIGHT,
Expand All @@ -21,15 +22,16 @@ export const insertEdgelessTextCommand: Command<
const { std, x, y } = ctx;
const host = std.host;
const doc = host.doc;
const edgelessService = std.getService('affine:page');
const surface = getSurfaceBlock(doc);
if (!(edgelessService instanceof EdgelessRootService) || !surface) {
if (!surface) {
next();
return;
}
const gfx = std.get(GfxControllerIdentifier);
const zoom = gfx.viewport.zoom;
const selection = gfx.selection;

const zoom = edgelessService.zoom;
const textId = edgelessService.addBlock(
const textId = std.get(EdgelessCRUDIdentifier).addBlock(
'affine:edgeless-text',
{
xywh: new Bound(
Expand All @@ -45,13 +47,13 @@ export const insertEdgelessTextCommand: Command<
const blockId = doc.addBlock('affine:paragraph', { type: 'text' }, textId);
host.updateComplete
.then(() => {
edgelessService.selection.set({
selection.set({
elements: [textId],
editing: true,
});
const disposable = edgelessService.selection.slots.updated.on(() => {
const editing = edgelessService.selection.editing;
const id = edgelessService.selection.selectedIds[0];
const disposable = selection.slots.updated.on(() => {
const editing = selection.editing;
const id = selection.selectedIds[0];
if (!editing || id !== textId) {
const textBlock = host.view.getBlock(textId);
if (textBlock instanceof EdgelessTextBlockComponent) {
Expand Down
Loading

0 comments on commit 6afa1d5

Please sign in to comment.