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

View File

@@ -5,7 +5,10 @@ import {
MindmapElementModel, MindmapElementModel,
NoteBlockModel, NoteBlockModel,
} from '@blocksuite/affine-model'; } 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 { Bound } from '@blocksuite/global/utils';
import chunk from 'lodash.chunk'; import chunk from 'lodash.chunk';
@@ -15,6 +18,7 @@ const ALIGN_PADDING = 20;
import type { Command } from '@blocksuite/block-std'; import type { Command } from '@blocksuite/block-std';
import type { BlockModel, BlockProps } from '@blocksuite/store'; import type { BlockModel, BlockProps } from '@blocksuite/store';
import { EdgelessCRUDIdentifier } from '../extensions/crud-extension.js';
import { updateXYWH } from '../utils/update-xywh.js'; import { updateXYWH } from '../utils/update-xywh.js';
/** /**
@@ -25,11 +29,10 @@ export const autoArrangeElementsCommand: Command<never, never, {}> = (
next next
) => { ) => {
const { updateBlock } = ctx.std.doc; const { updateBlock } = ctx.std.doc;
const rootService = ctx.std.getService('affine:page'); const gfx = ctx.std.get(GfxControllerIdentifier);
// @ts-expect-error TODO: fix after edgeless refactor
const elements = rootService?.selection.selectedElements; const elements = gfx.selection.selectedElements;
// @ts-expect-error TODO: fix after edgeless refactor const { updateElement } = ctx.std.get(EdgelessCRUDIdentifier);
const updateElement = rootService?.updateElement;
if (elements && updateElement) { if (elements && updateElement) {
autoArrangeElements(elements, updateElement, updateBlock); autoArrangeElements(elements, updateElement, updateBlock);
} }
@@ -44,11 +47,10 @@ export const autoResizeElementsCommand: Command<never, never, {}> = (
next next
) => { ) => {
const { updateBlock } = ctx.std.doc; const { updateBlock } = ctx.std.doc;
const rootService = ctx.std.getService('affine:page'); const gfx = ctx.std.get(GfxControllerIdentifier);
// @ts-expect-error TODO: fix after edgeless refactor
const elements = rootService?.selection.selectedElements; const elements = gfx.selection.selectedElements;
// @ts-expect-error TODO: fix after edgeless refactor const { updateElement } = ctx.std.get(EdgelessCRUDIdentifier);
const updateElement = rootService?.updateElement;
if (elements && updateElement) { if (elements && updateElement) {
autoResizeElements(elements, updateElement, updateBlock); autoResizeElements(elements, updateElement, updateBlock);
} }

View File

@@ -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);
}
};
}

View File

@@ -0,0 +1 @@
export * from './crud-extension';

View File

@@ -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';
}

View File

@@ -88,7 +88,11 @@ import {
} from '@blocksuite/global/utils'; } from '@blocksuite/global/utils';
import { generateKeyBetween } from 'fractional-indexing'; import { generateKeyBetween } from 'fractional-indexing';
import { generateElementId, normalizeWheelDeltaY } from './utils/index.js'; import {
generateElementId,
getLastPropsKey,
normalizeWheelDeltaY,
} from './utils';
import { import {
addTree, addTree,
containsNode, containsNode,
@@ -98,9 +102,11 @@ import {
hideNodeConnector, hideNodeConnector,
moveNode, moveNode,
tryMoveNode, tryMoveNode,
} from './utils/mindmap/utils.js'; } from './utils/mindmap/utils';
export type { Options } from './utils/rough/core.js'; export * from './extensions';
export { sortIndex } from './utils/sort.js'; 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 { updateXYWH } from './utils/update-xywh.js';
export const ConnectorUtils = { export const ConnectorUtils = {
@@ -129,6 +135,7 @@ export const CommonUtils = {
getPointFromBoundsWithRotation, getPointFromBoundsWithRotation,
getStroke, getStroke,
getSvgPathFromStroke, getSvgPathFromStroke,
getLastPropsKey,
intersects, intersects,
isOverlap, isOverlap,
isPointIn, isPointIn,

View File

@@ -12,6 +12,7 @@ import {
SurfaceBlockAdapterExtensions, SurfaceBlockAdapterExtensions,
} from './adapters/extension.js'; } from './adapters/extension.js';
import { commands } from './commands/index.js'; import { commands } from './commands/index.js';
import { EdgelessCRUDExtension } from './extensions/crud-extension.js';
import { SurfaceBlockService } from './surface-service.js'; import { SurfaceBlockService } from './surface-service.js';
import { MindMapView } from './view/mindmap.js'; import { MindMapView } from './view/mindmap.js';
@@ -21,6 +22,7 @@ const CommonSurfaceBlockSpec: ExtensionType[] = [
CommandExtension(commands), CommandExtension(commands),
HighlightSelectionExtension, HighlightSelectionExtension,
MindMapView, MindMapView,
EdgelessCRUDExtension,
]; ];
export const PageSurfaceBlockSpec: ExtensionType[] = [ export const PageSurfaceBlockSpec: ExtensionType[] = [

View File

@@ -32,3 +32,5 @@ export function normalizeWheelDeltaY(delta: number, zoom = 1) {
Math.min(1, abs / 20); Math.min(1, abs / 20);
return newZoom; return newZoom;
} }
export { getLastPropsKey } from './get-last-props-key';

View File

@@ -1,3 +1,4 @@
import { EdgelessCRUDIdentifier } from '@blocksuite/affine-block-surface';
import { toast } from '@blocksuite/affine-components/toast'; import { toast } from '@blocksuite/affine-components/toast';
import type { EmbedCardStyle } from '@blocksuite/affine-model'; import type { EmbedCardStyle } from '@blocksuite/affine-model';
import { import {
@@ -71,6 +72,7 @@ export class EmbedCardCreateModal extends WithDisposable(ShadowlessElement) {
} }
const gfx = this.host.std.get(GfxControllerIdentifier); const gfx = this.host.std.get(GfxControllerIdentifier);
const crud = this.host.std.get(EdgelessCRUDIdentifier);
const viewport = gfx.viewport; const viewport = gfx.viewport;
const surfaceModel = gfx.surface; const surfaceModel = gfx.surface;
@@ -79,7 +81,7 @@ export class EmbedCardCreateModal extends WithDisposable(ShadowlessElement) {
} }
const center = Vec.toVec(viewport.center); const center = Vec.toVec(viewport.center);
edgelessRoot.service.addBlock( crud.addBlock(
flavour, flavour,
{ {
url, url,

View File

@@ -1,8 +1,9 @@
import { EdgelessCRUDIdentifier } from '@blocksuite/affine-block-surface';
import { focusTextModel } from '@blocksuite/affine-components/rich-text'; import { focusTextModel } from '@blocksuite/affine-components/rich-text';
import type { Command } from '@blocksuite/block-std'; import type { Command } from '@blocksuite/block-std';
import { GfxControllerIdentifier } from '@blocksuite/block-std/gfx';
import { Bound } from '@blocksuite/global/utils'; 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 { getSurfaceBlock } from '../../surface-ref-block/utils.js';
import { import {
EDGELESS_TEXT_BLOCK_MIN_HEIGHT, EDGELESS_TEXT_BLOCK_MIN_HEIGHT,
@@ -21,15 +22,16 @@ export const insertEdgelessTextCommand: Command<
const { std, x, y } = ctx; const { std, x, y } = ctx;
const host = std.host; const host = std.host;
const doc = host.doc; const doc = host.doc;
const edgelessService = std.getService('affine:page');
const surface = getSurfaceBlock(doc); const surface = getSurfaceBlock(doc);
if (!(edgelessService instanceof EdgelessRootService) || !surface) { if (!surface) {
next(); next();
return; return;
} }
const gfx = std.get(GfxControllerIdentifier);
const zoom = gfx.viewport.zoom;
const selection = gfx.selection;
const zoom = edgelessService.zoom; const textId = std.get(EdgelessCRUDIdentifier).addBlock(
const textId = edgelessService.addBlock(
'affine:edgeless-text', 'affine:edgeless-text',
{ {
xywh: new Bound( xywh: new Bound(
@@ -45,13 +47,13 @@ export const insertEdgelessTextCommand: Command<
const blockId = doc.addBlock('affine:paragraph', { type: 'text' }, textId); const blockId = doc.addBlock('affine:paragraph', { type: 'text' }, textId);
host.updateComplete host.updateComplete
.then(() => { .then(() => {
edgelessService.selection.set({ selection.set({
elements: [textId], elements: [textId],
editing: true, editing: true,
}); });
const disposable = edgelessService.selection.slots.updated.on(() => { const disposable = selection.slots.updated.on(() => {
const editing = edgelessService.selection.editing; const editing = selection.editing;
const id = edgelessService.selection.selectedIds[0]; const id = selection.selectedIds[0];
if (!editing || id !== textId) { if (!editing || id !== textId) {
const textBlock = host.view.getBlock(textId); const textBlock = host.view.getBlock(textId);
if (textBlock instanceof EdgelessTextBlockComponent) { if (textBlock instanceof EdgelessTextBlockComponent) {

View File

@@ -2,6 +2,7 @@ import { addAttachments } from '@blocksuite/affine-block-attachment';
import { addImages } from '@blocksuite/affine-block-image'; import { addImages } from '@blocksuite/affine-block-image';
import { import {
CanvasElementType, CanvasElementType,
EdgelessCRUDIdentifier,
SurfaceGroupLikeModel, SurfaceGroupLikeModel,
TextUtils, TextUtils,
} from '@blocksuite/affine-block-surface'; } from '@blocksuite/affine-block-surface';
@@ -329,11 +330,7 @@ export class EdgelessClipboardController extends PageClipboard {
).serialize(); ).serialize();
options.style = style; options.style = style;
const id = this.host.service.addBlock( const id = this.crud.addBlock(flavour, options, this.surface.model.id);
flavour,
options,
this.surface.model.id
);
this.std.getOptional(TelemetryProvider)?.track('CanvasElementAdded', { this.std.getOptional(TelemetryProvider)?.track('CanvasElementAdded', {
control: 'canvas:paste', control: 'canvas:paste',
@@ -418,6 +415,10 @@ export class EdgelessClipboardController extends PageClipboard {
return this.host.surface; return this.host.surface;
} }
private get crud() {
return this.std.get(EdgelessCRUDIdentifier);
}
private get toolManager() { private get toolManager() {
return this.host.gfx.tool; return this.host.gfx.tool;
} }
@@ -470,7 +471,7 @@ export class EdgelessClipboardController extends PageClipboard {
if (!(await this.host.std.collection.blobSync.get(sourceId as string))) { if (!(await this.host.std.collection.blobSync.get(sourceId as string))) {
return null; return null;
} }
const attachmentId = this.host.service.addBlock( const attachmentId = this.crud.addBlock(
'affine:attachment', 'affine:attachment',
{ {
xywh, xywh,
@@ -491,7 +492,7 @@ export class EdgelessClipboardController extends PageClipboard {
const { xywh, style, url, caption, description, icon, image, title } = const { xywh, style, url, caption, description, icon, image, title } =
bookmark.props; bookmark.props;
const bookmarkId = this.host.service.addBlock( const bookmarkId = this.crud.addBlock(
'affine:bookmark', 'affine:bookmark',
{ {
xywh, xywh,
@@ -581,10 +582,13 @@ export class EdgelessClipboardController extends PageClipboard {
clipboardData.lockedBySelf = false; clipboardData.lockedBySelf = false;
const id = this.host.service.addElement( const id = this.crud.addElement(
clipboardData.type as CanvasElementType, clipboardData.type as CanvasElementType,
clipboardData clipboardData
); );
if (!id) {
return null;
}
this.std.getOptional(TelemetryProvider)?.track('CanvasElementAdded', { this.std.getOptional(TelemetryProvider)?.track('CanvasElementAdded', {
control: 'canvas:paste', control: 'canvas:paste',
page: 'whiteboard editor', page: 'whiteboard editor',
@@ -624,7 +628,7 @@ export class EdgelessClipboardController extends PageClipboard {
private _createFigmaEmbedBlock(figmaEmbed: BlockSnapshot) { private _createFigmaEmbedBlock(figmaEmbed: BlockSnapshot) {
const { xywh, style, url, caption, title, description } = figmaEmbed.props; const { xywh, style, url, caption, title, description } = figmaEmbed.props;
const embedFigmaId = this.host.service.addBlock( const embedFigmaId = this.crud.addBlock(
'affine:embed-figma', 'affine:embed-figma',
{ {
xywh, xywh,
@@ -654,7 +658,7 @@ export class EdgelessClipboardController extends PageClipboard {
}); });
} }
const frameId = this.host.service.addBlock( const frameId = this.crud.addBlock(
'affine:frame', 'affine:frame',
{ {
xywh, xywh,
@@ -687,7 +691,7 @@ export class EdgelessClipboardController extends PageClipboard {
assignees, assignees,
} = githubEmbed.props; } = githubEmbed.props;
const embedGithubId = this.host.service.addBlock( const embedGithubId = this.crud.addBlock(
'affine:embed-github', 'affine:embed-github',
{ {
xywh, xywh,
@@ -714,7 +718,7 @@ export class EdgelessClipboardController extends PageClipboard {
private _createHtmlEmbedBlock(htmlEmbed: BlockSnapshot) { private _createHtmlEmbedBlock(htmlEmbed: BlockSnapshot) {
const { xywh, style, caption, html, design } = htmlEmbed.props; const { xywh, style, caption, html, design } = htmlEmbed.props;
const embedHtmlId = this.host.service.addBlock( const embedHtmlId = this.crud.addBlock(
'affine:embed-html', 'affine:embed-html',
{ {
xywh, xywh,
@@ -735,7 +739,7 @@ export class EdgelessClipboardController extends PageClipboard {
if (!(await this.host.std.collection.blobSync.get(sourceId as string))) { if (!(await this.host.std.collection.blobSync.get(sourceId as string))) {
return null; return null;
} }
return this.host.service.addBlock( return this.crud.addBlock(
'affine:image', 'affine:image',
{ {
caption, caption,
@@ -760,7 +764,7 @@ export class EdgelessClipboardController extends PageClipboard {
description, description,
}); });
return this.host.service.addBlock( return this.crud.addBlock(
'affine:embed-linked-doc', 'affine:embed-linked-doc',
{ {
xywh, xywh,
@@ -776,7 +780,7 @@ export class EdgelessClipboardController extends PageClipboard {
const { xywh, style, url, caption, videoId, image, title, description } = const { xywh, style, url, caption, videoId, image, title, description } =
loomEmbed.props; loomEmbed.props;
const embedLoomId = this.host.service.addBlock( const embedLoomId = this.crud.addBlock(
'affine:embed-loom', 'affine:embed-loom',
{ {
xywh, xywh,
@@ -820,7 +824,7 @@ export class EdgelessClipboardController extends PageClipboard {
syncedDocEmbed.props; syncedDocEmbed.props;
const referenceInfo = ReferenceInfoSchema.parse({ pageId, params }); const referenceInfo = ReferenceInfoSchema.parse({ pageId, params });
return this.host.service.addBlock( return this.crud.addBlock(
'affine:embed-synced-doc', 'affine:embed-synced-doc',
{ {
xywh, xywh,
@@ -848,7 +852,7 @@ export class EdgelessClipboardController extends PageClipboard {
creatorImage, creatorImage,
} = youtubeEmbed.props; } = youtubeEmbed.props;
const embedYoutubeId = this.host.service.addBlock( const embedYoutubeId = this.crud.addBlock(
'affine:embed-youtube', 'affine:embed-youtube',
{ {
xywh, xywh,
@@ -1085,7 +1089,7 @@ export class EdgelessClipboardController extends PageClipboard {
).serialize(), ).serialize(),
}; };
const noteId = edgeless.service.addBlock( const noteId = this.crud.addBlock(
'affine:note', 'affine:note',
noteProps, noteProps,
this.doc.root!.id this.doc.root!.id
@@ -1101,7 +1105,7 @@ export class EdgelessClipboardController extends PageClipboard {
if (typeof content === 'string') { if (typeof content === 'string') {
TextUtils.splitIntoLines(content).forEach((line, idx) => { TextUtils.splitIntoLines(content).forEach((line, idx) => {
edgeless.service.addBlock( this.crud.addBlock(
'affine:paragraph', 'affine:paragraph',
{ text: new DocCollection.Y.Text(line) }, { text: new DocCollection.Y.Text(line) },
noteId, noteId,
@@ -1181,7 +1185,7 @@ export class EdgelessClipboardController extends PageClipboard {
sortedElements.forEach(ele => { sortedElements.forEach(ele => {
const newIndex = idxGenerator(); const newIndex = idxGenerator();
this.edgeless.service.updateElement(ele.id, { this.crud.updateElement(ele.id, {
index: newIndex, index: newIndex,
}); });
}); });
@@ -1323,6 +1327,8 @@ export class EdgelessClipboardController extends PageClipboard {
getNewXYWH(data.xywh) getNewXYWH(data.xywh)
); );
if (!element) continue;
canvasElements.push(element); canvasElements.push(element);
allElements.push(element); allElements.push(element);

View File

@@ -1,6 +1,7 @@
import { import {
CanvasElementType, CanvasElementType,
CommonUtils, CommonUtils,
EdgelessCRUDIdentifier,
} from '@blocksuite/affine-block-surface'; } from '@blocksuite/affine-block-surface';
import { import {
FontFamilyIcon, FontFamilyIcon,
@@ -137,6 +138,10 @@ export class EdgelessAutoCompletePanel extends WithDisposable(LitElement) {
this.connector = connector; this.connector = connector;
} }
get crud() {
return this.std.get(EdgelessCRUDIdentifier);
}
private _addFrame() { private _addFrame() {
const bound = this._generateTarget(this.connector)?.nextBound; const bound = this._generateTarget(this.connector)?.nextBound;
if (!bound) return; if (!bound) return;
@@ -152,7 +157,7 @@ export class EdgelessAutoCompletePanel extends WithDisposable(LitElement) {
const { service, surfaceBlockModel } = edgeless; const { service, surfaceBlockModel } = edgeless;
const frameMgr = service.frame; const frameMgr = service.frame;
const frameIndex = service.frames.length + 1; const frameIndex = service.frames.length + 1;
const id = service.addBlock( const id = this.crud.addBlock(
'affine:frame', 'affine:frame',
{ {
title: new DocCollection.Y.Text(`Frame ${frameIndex}`), title: new DocCollection.Y.Text(`Frame ${frameIndex}`),
@@ -178,7 +183,6 @@ export class EdgelessAutoCompletePanel extends WithDisposable(LitElement) {
private _addNote() { private _addNote() {
const { doc } = this.edgeless; const { doc } = this.edgeless;
const service = this.edgeless.service;
const target = this._getTargetXYWH( const target = this._getTargetXYWH(
DEFAULT_NOTE_WIDTH, DEFAULT_NOTE_WIDTH,
DEFAULT_NOTE_OVERLAY_HEIGHT DEFAULT_NOTE_OVERLAY_HEIGHT
@@ -186,7 +190,7 @@ export class EdgelessAutoCompletePanel extends WithDisposable(LitElement) {
if (!target) return; if (!target) return;
const { xywh, position } = target; const { xywh, position } = target;
const id = service.addBlock( const id = this.crud.addBlock(
'affine:note', 'affine:note',
{ {
xywh: serializeXYWH(...xywh), xywh: serializeXYWH(...xywh),
@@ -205,7 +209,7 @@ export class EdgelessAutoCompletePanel extends WithDisposable(LitElement) {
id, id,
position: position as [number, number], position: position as [number, number],
}; };
service.updateElement(this.connector.id, { this.crud.updateElement(this.connector.id, {
target: { id, position }, target: { id, position },
}); });
this.edgeless.service.selection.set({ this.edgeless.service.selection.set({
@@ -223,9 +227,10 @@ export class EdgelessAutoCompletePanel extends WithDisposable(LitElement) {
const { nextBound, position } = result; const { nextBound, position } = result;
const { service } = edgeless; const { service } = edgeless;
const id = createShapeElement(edgeless, currentSource, targetType); const id = createShapeElement(edgeless, currentSource, targetType);
if (!id) return;
service.updateElement(id, { xywh: nextBound.serialize() }); this.crud.updateElement(id, { xywh: nextBound.serialize() });
service.updateElement(this.connector.id, { this.crud.updateElement(this.connector.id, {
target: { id, position }, target: { id, position },
}); });
@@ -260,7 +265,7 @@ export class EdgelessAutoCompletePanel extends WithDisposable(LitElement) {
const textElement = edgelessService.getElementById(textId); const textElement = edgelessService.getElementById(textId);
if (!textElement) return; if (!textElement) return;
edgelessService.updateElement(this.connector.id, { this.crud.updateElement(this.connector.id, {
target: { id: textId, position }, target: { id: textId, position },
}); });
if (this.currentSource.group instanceof GroupElementModel) { if (this.currentSource.group instanceof GroupElementModel) {
@@ -273,7 +278,7 @@ export class EdgelessAutoCompletePanel extends WithDisposable(LitElement) {
}); });
this.edgeless.doc.captureSync(); this.edgeless.doc.captureSync();
} else { } else {
const textId = edgelessService.addElement(CanvasElementType.TEXT, { const textId = this.crud.addElement(CanvasElementType.TEXT, {
xywh: bound.serialize(), xywh: bound.serialize(),
text: new DocCollection.Y.Text(), text: new DocCollection.Y.Text(),
textAlign: 'left', textAlign: 'left',
@@ -283,10 +288,11 @@ export class EdgelessAutoCompletePanel extends WithDisposable(LitElement) {
fontWeight: FontWeight.Regular, fontWeight: FontWeight.Regular,
fontStyle: FontStyle.Normal, fontStyle: FontStyle.Normal,
}); });
if (!textId) return;
const textElement = edgelessService.getElementById(textId); const textElement = edgelessService.getElementById(textId);
assertInstanceOf(textElement, TextElementModel); assertInstanceOf(textElement, TextElementModel);
edgelessService.updateElement(this.connector.id, { this.crud.updateElement(this.connector.id, {
target: { id: textId, position }, target: { id: textId, position },
}); });
if (this.currentSource.group instanceof GroupElementModel) { if (this.currentSource.group instanceof GroupElementModel) {

View File

@@ -2,6 +2,7 @@ import {
CanvasElementType, CanvasElementType,
type ConnectionOverlay, type ConnectionOverlay,
ConnectorPathGenerator, ConnectorPathGenerator,
EdgelessCRUDIdentifier,
Overlay, Overlay,
OverlayIdentifier, OverlayIdentifier,
type RoughCanvas, type RoughCanvas,
@@ -232,12 +233,17 @@ export class EdgelessAutoComplete extends WithDisposable(LitElement) {
return this.std.get(OverlayIdentifier('connection')) as ConnectionOverlay; return this.std.get(OverlayIdentifier('connection')) as ConnectionOverlay;
} }
get crud() {
return this.std.get(EdgelessCRUDIdentifier);
}
private _addConnector(source: Connection, target: Connection) { private _addConnector(source: Connection, target: Connection) {
const { edgeless } = this; const { edgeless } = this;
const id = edgeless.service.addElement(CanvasElementType.CONNECTOR, { const id = this.crud.addElement(CanvasElementType.CONNECTOR, {
source, source,
target, target,
}); });
if (!id) return null;
return edgeless.service.getElementById(id) as ConnectorElementModel; return edgeless.service.getElementById(id) as ConnectorElementModel;
} }
@@ -354,6 +360,7 @@ export class EdgelessAutoComplete extends WithDisposable(LitElement) {
const { doc, service } = this.edgeless; const { doc, service } = this.edgeless;
const bound = this._computeNextBound(type); const bound = this._computeNextBound(type);
const id = createEdgelessElement(this.edgeless, this.current, bound); const id = createEdgelessElement(this.edgeless, this.current, bound);
if (!id) return;
if (isShape(this.current)) { if (isShape(this.current)) {
const { startPosition, endPosition } = getPosition(type); const { startPosition, endPosition } = getPosition(type);
this._addConnector( this._addConnector(

View File

@@ -281,11 +281,12 @@ export function createEdgelessElement(
let element: GfxModel | null = null; let element: GfxModel | null = null;
if (isShape(current)) { if (isShape(current)) {
id = service.addElement(current.type, { id = service.crud.addElement(current.type, {
...current.serialize(), ...current.serialize(),
text: new DocCollection.Y.Text(), text: new DocCollection.Y.Text(),
xywh: bound.serialize(), xywh: bound.serialize(),
}); });
if (!id) return null;
element = service.getElementById(id); element = service.getElementById(id);
} else { } else {
const { doc } = edgeless; const { doc } = edgeless;
@@ -335,11 +336,12 @@ export function createShapeElement(
targetType: TARGET_SHAPE_TYPE targetType: TARGET_SHAPE_TYPE
) { ) {
const service = edgeless.service; const service = edgeless.service;
const id = service.addElement('shape', { const id = service.crud.addElement('shape', {
shapeType: getShapeType(targetType), shapeType: getShapeType(targetType),
radius: getShapeRadius(targetType), radius: getShapeRadius(targetType),
text: new DocCollection.Y.Text(), text: new DocCollection.Y.Text(),
}); });
if (!id) return null;
const element = service.getElementById(id); const element = service.getElementById(id);
const group = current.group; const group = current.group;
if (group instanceof GroupElementModel && element) { if (group instanceof GroupElementModel && element) {

View File

@@ -1,4 +1,7 @@
import { TextUtils } from '@blocksuite/affine-block-surface'; import {
EdgelessCRUDIdentifier,
TextUtils,
} from '@blocksuite/affine-block-surface';
import type { RichText } from '@blocksuite/affine-components/rich-text'; import type { RichText } from '@blocksuite/affine-components/rich-text';
import type { ConnectorElementModel } from '@blocksuite/affine-model'; import type { ConnectorElementModel } from '@blocksuite/affine-model';
import { ThemeProvider } from '@blocksuite/affine-shared/services'; import { ThemeProvider } from '@blocksuite/affine-shared/services';
@@ -60,6 +63,10 @@ export class EdgelessConnectorLabelEditor extends WithDisposable(
} }
`; `;
get crud() {
return this.edgeless.std.get(EdgelessCRUDIdentifier);
}
private _isComposition = false; private _isComposition = false;
private _keeping = false; private _keeping = false;
@@ -84,7 +91,7 @@ export class EdgelessConnectorLabelEditor extends WithDisposable(
!connector.labelXYWH || !connector.labelXYWH ||
labelXYWH.some((p, i) => !almostEqual(p, connector.labelXYWH![i])) labelXYWH.some((p, i) => !almostEqual(p, connector.labelXYWH![i]))
) { ) {
edgeless.service.updateElement(connector.id, { this.crud.updateElement(connector.id, {
labelXYWH, labelXYWH,
}); });
} }
@@ -172,13 +179,13 @@ export class EdgelessConnectorLabelEditor extends WithDisposable(
const len = trimed.length; const len = trimed.length;
if (len === 0) { if (len === 0) {
// reset // reset
edgeless.service.updateElement(connector.id, { this.crud.updateElement(connector.id, {
text: undefined, text: undefined,
labelXYWH: undefined, labelXYWH: undefined,
labelOffset: undefined, labelOffset: undefined,
}); });
} else if (len < text.length) { } else if (len < text.length) {
edgeless.service.updateElement(connector.id, { this.crud.updateElement(connector.id, {
// @TODO: trim in Y.Text? // @TODO: trim in Y.Text?
text: new DocCollection.Y.Text(trimed), text: new DocCollection.Y.Text(trimed),
}); });

View File

@@ -1,4 +1,8 @@
import { CommonUtils, TextUtils } from '@blocksuite/affine-block-surface'; import {
CommonUtils,
EdgelessCRUDIdentifier,
TextUtils,
} from '@blocksuite/affine-block-surface';
import type { RichText } from '@blocksuite/affine-components/rich-text'; import type { RichText } from '@blocksuite/affine-components/rich-text';
import type { ShapeElementModel } from '@blocksuite/affine-model'; import type { ShapeElementModel } from '@blocksuite/affine-model';
import { MindmapElementModel, TextResizing } from '@blocksuite/affine-model'; import { MindmapElementModel, TextResizing } from '@blocksuite/affine-model';
@@ -24,6 +28,10 @@ import { getSelectedRect } from '../../utils/query.js';
const { toRadian } = CommonUtils; const { toRadian } = CommonUtils;
export class EdgelessShapeTextEditor extends WithDisposable(ShadowlessElement) { export class EdgelessShapeTextEditor extends WithDisposable(ShadowlessElement) {
get crud() {
return this.edgeless.std.get(EdgelessCRUDIdentifier);
}
private _keeping = false; private _keeping = false;
private _lastXYWH = ''; private _lastXYWH = '';
@@ -137,7 +145,7 @@ export class EdgelessShapeTextEditor extends WithDisposable(ShadowlessElement) {
const [modelLeftTopX, modelLeftTopY] = const [modelLeftTopX, modelLeftTopY] =
this.edgeless.service.viewport.toModelCoord(leftTopX, leftTopY); this.edgeless.service.viewport.toModelCoord(leftTopX, leftTopY);
this.edgeless.service.updateElement(this.element.id, { this.crud.updateElement(this.element.id, {
xywh: new Bound( xywh: new Bound(
modelLeftTopX, modelLeftTopX,
modelLeftTopY, modelLeftTopY,

View File

@@ -1,4 +1,8 @@
import { CommonUtils, TextUtils } from '@blocksuite/affine-block-surface'; import {
CommonUtils,
EdgelessCRUDIdentifier,
TextUtils,
} from '@blocksuite/affine-block-surface';
import type { RichText } from '@blocksuite/affine-components/rich-text'; import type { RichText } from '@blocksuite/affine-components/rich-text';
import type { TextElementModel } from '@blocksuite/affine-model'; import type { TextElementModel } from '@blocksuite/affine-model';
import { ThemeProvider } from '@blocksuite/affine-shared/services'; import { ThemeProvider } from '@blocksuite/affine-shared/services';
@@ -23,6 +27,10 @@ import { getSelectedRect } from '../../utils/query.js';
const { toRadian } = CommonUtils; const { toRadian } = CommonUtils;
export class EdgelessTextEditor extends WithDisposable(ShadowlessElement) { export class EdgelessTextEditor extends WithDisposable(ShadowlessElement) {
get crud() {
return this.edgeless.std.get(EdgelessCRUDIdentifier);
}
static BORDER_WIDTH = 1; static BORDER_WIDTH = 1;
static PADDING_HORIZONTAL = 10; static PADDING_HORIZONTAL = 10;
@@ -137,7 +145,7 @@ export class EdgelessTextEditor extends WithDisposable(ShadowlessElement) {
break; break;
} }
edgeless.service.updateElement(element.id, { this.crud.updateElement(element.id, {
xywh: bound.serialize(), xywh: bound.serialize(),
}); });
}; };

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { CanvasElementType } from '@blocksuite/affine-block-surface'; import { CanvasElementType } from '@blocksuite/affine-block-surface';
import { type MindmapStyle, TextElementModel } from '@blocksuite/affine-model'; import { type MindmapStyle, TextElementModel } from '@blocksuite/affine-model';
import { TelemetryProvider } from '@blocksuite/affine-shared/services'; import { TelemetryProvider } from '@blocksuite/affine-shared/services';
@@ -78,7 +77,7 @@ export const getMindmapRender =
}); });
} }
const mindmapId = edgelessService.addElement('mindmap', { const mindmapId = edgelessService.crud.addElement('mindmap', {
style: mindmapStyle, style: mindmapStyle,
children: root, children: root,
}) as string; }) as string;
@@ -113,10 +112,10 @@ export const textRender: DraggableTool['render'] = (
}); });
id = textId!; id = textId!;
} else { } else {
id = service.addElement(CanvasElementType.TEXT, { id = service.crud.addElement(CanvasElementType.TEXT, {
xywh: new Bound(bound.x, vCenter - h / 2, w, h).serialize(), xywh: new Bound(bound.x, vCenter - h / 2, w, h).serialize(),
text: new DocCollection.Y.Text(), text: new DocCollection.Y.Text(),
}); }) as string;
edgeless.doc.captureSync(); edgeless.doc.captureSync();
const textElement = edgeless.service.getElementById(id); const textElement = edgeless.service.getElementById(id);

View File

@@ -1,3 +1,4 @@
import { EdgelessCRUDIdentifier } from '@blocksuite/affine-block-surface';
import type { import type {
MindmapElementModel, MindmapElementModel,
MindmapStyle, MindmapStyle,
@@ -162,6 +163,10 @@ export class EdgelessMindmapToolButton extends EdgelessToolbarToolMixin(
return getMindMaps(this.theme); return getMindMaps(this.theme);
} }
get crud() {
return this.edgeless.std.get(EdgelessCRUDIdentifier);
}
private _toggleMenu() { private _toggleMenu() {
if (this.tryDisposePopper()) return; if (this.tryDisposePopper()) return;
this.setEdgelessTool({ type: 'default' }); this.setEdgelessTool({ type: 'default' });
@@ -176,10 +181,11 @@ export class EdgelessMindmapToolButton extends EdgelessToolbarToolMixin(
}, },
onImportMindMap: (bound: Bound) => { onImportMindMap: (bound: Bound) => {
return importMindmap(bound).then(mindmap => { return importMindmap(bound).then(mindmap => {
const id = this.edgeless.service.addElement('mindmap', { const id = this.crud.addElement('mindmap', {
children: mindmap, children: mindmap,
layoutType: mindmap?.layoutType === 'left' ? 1 : 0, layoutType: mindmap?.layoutType === 'left' ? 1 : 0,
}); });
if (!id) return;
const element = this.edgeless.service.getElementById( const element = this.edgeless.service.getElementById(
id id
) as MindmapElementModel; ) as MindmapElementModel;

View File

@@ -1,3 +1,4 @@
import { EdgelessCRUDIdentifier } from '@blocksuite/affine-block-surface';
import { generateKeyBetweenV2 } from '@blocksuite/block-std/gfx'; import { generateKeyBetweenV2 } from '@blocksuite/block-std/gfx';
import { import {
DisposableGroup, DisposableGroup,
@@ -98,6 +99,10 @@ export class EdgelessFrameOrderMenu extends SignalWatcher(
} }
`; `;
get crud() {
return this.edgeless.std.get(EdgelessCRUDIdentifier);
}
private get _frames() { private get _frames() {
return this.edgeless.service.frames; return this.edgeless.service.frames;
} }
@@ -182,7 +187,7 @@ export class EdgelessFrameOrderMenu extends SignalWatcher(
const frame = this._frames[index]; const frame = this._frames[index];
this.edgeless.service.updateElement(frame.id, { this.crud.updateElement(frame.id, {
presentationIndex: generateKeyBetweenV2(before, after), presentationIndex: generateKeyBetweenV2(before, after),
}); });
this.edgeless.doc.captureSync(); this.edgeless.doc.captureSync();

View File

@@ -1,4 +1,7 @@
import { CanvasElementType } from '@blocksuite/affine-block-surface'; import {
CanvasElementType,
EdgelessCRUDIdentifier,
} from '@blocksuite/affine-block-surface';
import { import {
ellipseSvg, ellipseSvg,
roundedSvg, roundedSvg,
@@ -137,6 +140,10 @@ export class EdgelessToolbarShapeDraggable extends EdgelessToolbarToolMixin(
override type = 'shape' as const; override type = 'shape' as const;
get crud() {
return this.edgeless.std.get(EdgelessCRUDIdentifier);
}
get shapeShadow() { get shapeShadow() {
return this.theme === 'dark' return this.theme === 'dark'
? '0 0 7px rgba(0, 0, 0, .22)' ? '0 0 7px rgba(0, 0, 0, .22)'
@@ -179,12 +186,12 @@ export class EdgelessToolbarShapeDraggable extends EdgelessToolbarToolMixin(
onDrop: (el, bound) => { onDrop: (el, bound) => {
const xywh = bound.serialize(); const xywh = bound.serialize();
const shape = el.data; const shape = el.data;
const id = this.edgeless.service.addElement(CanvasElementType.SHAPE, { const id = this.crud.addElement(CanvasElementType.SHAPE, {
shapeType: getShapeType(shape.name), shapeType: getShapeType(shape.name),
xywh, xywh,
radius: getShapeRadius(shape.name), radius: getShapeRadius(shape.name),
}); });
if (!id) return;
this.edgeless.std this.edgeless.std
.getOptional(TelemetryProvider) .getOptional(TelemetryProvider)
?.track('CanvasElementAdded', { ?.track('CanvasElementAdded', {

View File

@@ -1,4 +1,7 @@
import { CanvasElementType } from '@blocksuite/affine-block-surface'; import {
CanvasElementType,
EdgelessCRUDIdentifier,
} from '@blocksuite/affine-block-surface';
import { import {
getShapeRadius, getShapeRadius,
getShapeType, getShapeType,
@@ -67,6 +70,10 @@ export class EdgelessShapeToolElement extends WithDisposable(LitElement) {
} }
`; `;
get crud() {
return this.edgeless.std.get(EdgelessCRUDIdentifier);
}
private readonly _addShape = (coord: Coord, padding: Coord) => { private readonly _addShape = (coord: Coord, padding: Coord) => {
const width = 100; const width = 100;
const height = 100; const height = 100;
@@ -78,7 +85,7 @@ export class EdgelessShapeToolElement extends WithDisposable(LitElement) {
coord.y - edgelessY - height * padding.y * zoom coord.y - edgelessY - height * padding.y * zoom
); );
const xywh = new Bound(modelX, modelY, width, height).serialize(); const xywh = new Bound(modelX, modelY, width, height).serialize();
this.edgeless.service.addElement(CanvasElementType.SHAPE, { this.crud.addElement(CanvasElementType.SHAPE, {
shapeType: getShapeType(this.shape.name), shapeType: getShapeType(this.shape.name),
xywh: xywh, xywh: xywh,
radius: getShapeRadius(this.shape.name), radius: getShapeRadius(this.shape.name),

View File

@@ -1,4 +1,5 @@
import { import {
EdgelessCRUDIdentifier,
type ElementRenderer, type ElementRenderer,
elementRenderers, elementRenderers,
type SurfaceBlockModel, type SurfaceBlockModel,
@@ -11,7 +12,6 @@ import {
MindmapElementModel, MindmapElementModel,
RootBlockSchema, RootBlockSchema,
} from '@blocksuite/affine-model'; } from '@blocksuite/affine-model';
import { EditPropsStore } from '@blocksuite/affine-shared/services';
import { clamp } from '@blocksuite/affine-shared/utils'; import { clamp } from '@blocksuite/affine-shared/utils';
import type { BlockStdScope } from '@blocksuite/block-std'; import type { BlockStdScope } from '@blocksuite/block-std';
import type { import type {
@@ -28,7 +28,7 @@ import {
} from '@blocksuite/block-std/gfx'; } from '@blocksuite/block-std/gfx';
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions'; import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
import { Bound, getCommonBound } from '@blocksuite/global/utils'; import { Bound, getCommonBound } from '@blocksuite/global/utils';
import { type BlockModel, Slot } from '@blocksuite/store'; import { Slot } from '@blocksuite/store';
import { effect } from '@preact/signals-core'; import { effect } from '@preact/signals-core';
import { getSurfaceBlock } from '../../surface-ref-block/utils.js'; import { getSurfaceBlock } from '../../surface-ref-block/utils.js';
@@ -43,7 +43,6 @@ import {
replaceIdMiddleware, replaceIdMiddleware,
} from './services/template-middlewares.js'; } from './services/template-middlewares.js';
import { FIT_TO_SCREEN_PADDING } from './utils/consts.js'; import { FIT_TO_SCREEN_PADDING } from './utils/consts.js';
import { getLastPropsKey } from './utils/get-last-props-key.js';
import { getCursorMode } from './utils/query.js'; import { getCursorMode } from './utils/query.js';
import { import {
ZOOM_INITIAL, ZOOM_INITIAL,
@@ -85,29 +84,6 @@ export class EdgelessRootService extends RootService implements SurfaceContext {
TemplateJob = TemplateJob; TemplateJob = TemplateJob;
updateElement = (id: string, props: Record<string, unknown>) => {
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.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.doc.updateBlock(block, props);
}
};
get blocks(): GfxBlockModel[] { get blocks(): GfxBlockModel[] {
return this.layer.blocks; return this.layer.blocks;
} }
@@ -179,6 +155,10 @@ export class EdgelessRootService extends RootService implements SurfaceContext {
return this.viewport.zoom; return this.viewport.zoom;
} }
get crud() {
return this.std.get(EdgelessCRUDIdentifier);
}
constructor(std: BlockStdScope, flavourProvider: { flavour: string }) { constructor(std: BlockStdScope, flavourProvider: { flavour: string }) {
super(std, flavourProvider); super(std, flavourProvider);
const surface = getSurfaceBlock(this.doc); const surface = getSurfaceBlock(this.doc);
@@ -216,44 +196,11 @@ export class EdgelessRootService extends RootService implements SurfaceContext {
); );
} }
addBlock(
flavour: string,
props: Record<string, unknown>,
parent?: string | BlockModel,
parentIndex?: number
) {
const key = getLastPropsKey(flavour as BlockSuite.EdgelessModelKeys, props);
if (key) {
props = this.std.get(EditPropsStore).applyLastProps(key, props);
}
const nProps = {
...props,
index: this.generateIndex(),
};
return this.doc.addBlock(flavour as never, nProps, parent, parentIndex);
}
addElement<T extends Record<string, unknown>>(type: string, props: T) {
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 ?? this.generateIndex(),
};
const id = this._surface.addElement(nProps);
return id;
}
createGroup(elements: BlockSuite.EdgelessModel[] | string[]) { createGroup(elements: BlockSuite.EdgelessModel[] | string[]) {
const groups = this.elements.filter( const groups = this.elements.filter(
el => el.type === 'group' el => el.type === 'group'
) as GroupElementModel[]; ) as GroupElementModel[];
const groupId = this.addElement('group', { const groupId = this.crud.addElement('group', {
children: elements.reduce( children: elements.reduce(
(pre, el) => { (pre, el) => {
const id = typeof el === 'string' ? el : el.id; const id = typeof el === 'string' ? el : el.id;
@@ -290,12 +237,15 @@ export class EdgelessRootService extends RootService implements SurfaceContext {
if (parent !== null) { if (parent !== null) {
selection.selectedElements.forEach(element => { selection.selectedElements.forEach(element => {
// eslint-disable-next-line unicorn/prefer-dom-node-remove // oxlint-disable-next-line unicorn/prefer-dom-node-remove
parent.removeChild(element); parent.removeChild(element);
}); });
} }
const groupId = this.createGroup(selection.selectedElements); const groupId = this.createGroup(selection.selectedElements);
if (!groupId) {
return;
}
const group = this.surface.getElementById(groupId); const group = this.surface.getElementById(groupId);
if (parent !== null && group) { if (parent !== null && group) {
@@ -487,12 +437,12 @@ export class EdgelessRootService extends RootService implements SurfaceContext {
} }
if (parent !== null) { if (parent !== null) {
// eslint-disable-next-line unicorn/prefer-dom-node-remove // oxlint-disable-next-line unicorn/prefer-dom-node-remove
parent.removeChild(group); parent.removeChild(group);
} }
elements.forEach(element => { elements.forEach(element => {
// eslint-disable-next-line unicorn/prefer-dom-node-remove // oxlint-disable-next-line unicorn/prefer-dom-node-remove
group.removeChild(element); group.removeChild(element);
}); });

View File

@@ -1,5 +1,6 @@
import { import {
CommonUtils, CommonUtils,
EdgelessCRUDIdentifier,
Overlay, Overlay,
type SurfaceBlockComponent, type SurfaceBlockComponent,
} from '@blocksuite/affine-block-surface'; } from '@blocksuite/affine-block-surface';
@@ -7,7 +8,6 @@ import type { PointerEventState } from '@blocksuite/block-std';
import { BaseTool } from '@blocksuite/block-std/gfx'; import { BaseTool } from '@blocksuite/block-std/gfx';
import { Bound, type IVec } from '@blocksuite/global/utils'; import { Bound, type IVec } from '@blocksuite/global/utils';
import { deleteElementsV2 } from '../utils/crud.js';
import { isTopLevelBlock } from '../utils/query.js'; import { isTopLevelBlock } from '../utils/query.js';
const { getSvgPathFromStroke, getStroke, linePolygonIntersects } = CommonUtils; const { getSvgPathFromStroke, getStroke, linePolygonIntersects } = CommonUtils;
@@ -99,7 +99,9 @@ export class EraserTool extends BaseTool {
} }
override dragEnd(_: PointerEventState): void { override dragEnd(_: PointerEventState): void {
deleteElementsV2(this.gfx, Array.from(this._eraseTargets)); this.gfx.std
.get(EdgelessCRUDIdentifier)
.deleteElements(Array.from(this._eraseTargets));
this._reset(); this._reset();
this.doc.captureSync(); this.doc.captureSync();
} }

View File

@@ -1,11 +1,10 @@
import { getLastPropsKey } from '@blocksuite/affine-block-surface';
import { EditPropsStore } from '@blocksuite/affine-shared/services'; import { EditPropsStore } from '@blocksuite/affine-shared/services';
import { import {
type SurfaceMiddleware, type SurfaceMiddleware,
SurfaceMiddlewareBuilder, SurfaceMiddlewareBuilder,
} from '@blocksuite/block-std/gfx'; } from '@blocksuite/block-std/gfx';
import { getLastPropsKey } from '../utils/get-last-props-key.js';
export class EditPropsMiddlewareBuilder extends SurfaceMiddlewareBuilder { export class EditPropsMiddlewareBuilder extends SurfaceMiddlewareBuilder {
static override key = 'editProps'; static override key = 'editProps';

View File

@@ -1,3 +1,4 @@
import { EdgelessCRUDIdentifier } from '@blocksuite/affine-block-surface';
import { focusTextModel } from '@blocksuite/affine-components/rich-text'; import { focusTextModel } from '@blocksuite/affine-components/rich-text';
import { import {
DEFAULT_NOTE_HEIGHT, DEFAULT_NOTE_HEIGHT,
@@ -18,7 +19,6 @@ import {
} from '@blocksuite/global/utils'; } from '@blocksuite/global/utils';
import { DEFAULT_NOTE_OFFSET_X, DEFAULT_NOTE_OFFSET_Y } from './consts.js'; import { DEFAULT_NOTE_OFFSET_X, DEFAULT_NOTE_OFFSET_Y } from './consts.js';
import { addBlock } from './crud.js';
export function addNoteAtPoint( export function addNoteAtPoint(
std: BlockStdScope, std: BlockStdScope,
@@ -37,6 +37,7 @@ export function addNoteAtPoint(
} = {} } = {}
) { ) {
const gfx = std.get(GfxControllerIdentifier); const gfx = std.get(GfxControllerIdentifier);
const crud = std.get(EdgelessCRUDIdentifier);
const { const {
width = DEFAULT_NOTE_WIDTH, width = DEFAULT_NOTE_WIDTH,
height = DEFAULT_NOTE_HEIGHT, height = DEFAULT_NOTE_HEIGHT,
@@ -47,8 +48,7 @@ export function addNoteAtPoint(
scale = 1, scale = 1,
} = options; } = options;
const [x, y] = gfx.viewport.toModelCoord(point.x, point.y); const [x, y] = gfx.viewport.toModelCoord(point.x, point.y);
const blockId = addBlock( const blockId = crud.addBlock(
std,
'affine:note', 'affine:note',
{ {
xywh: serializeXYWH( xywh: serializeXYWH(
@@ -63,7 +63,7 @@ export function addNoteAtPoint(
noteIndex noteIndex
); );
gfx.std.getOptional(TelemetryProvider)?.track('CanvasElementAdded', { std.getOptional(TelemetryProvider)?.track('CanvasElementAdded', {
control: 'canvas:draw', control: 'canvas:draw',
page: 'whiteboard editor', page: 'whiteboard editor',
module: 'toolbar', module: 'toolbar',

View File

@@ -1,3 +1,5 @@
import { EdgelessCRUDIdentifier } from '@blocksuite/affine-block-surface';
import type { EdgelessRootService } from '../edgeless-root-service.js'; import type { EdgelessRootService } from '../edgeless-root-service.js';
/** /**
@@ -12,14 +14,15 @@ export function moveConnectors(
service: EdgelessRootService service: EdgelessRootService
) { ) {
const connectors = service.surface.getConnectors(originId); const connectors = service.surface.getConnectors(originId);
const crud = service.std.get(EdgelessCRUDIdentifier);
connectors.forEach(connector => { connectors.forEach(connector => {
if (connector.source.id === originId) { if (connector.source.id === originId) {
service.updateElement(connector.id, { crud.updateElement(connector.id, {
source: { ...connector.source, id: targetId }, source: { ...connector.source, id: targetId },
}); });
} }
if (connector.target.id === originId) { if (connector.target.id === originId) {
service.updateElement(connector.id, { crud.updateElement(connector.id, {
target: { ...connector.target, id: targetId }, target: { ...connector.target, id: targetId },
}); });
} }

View File

@@ -1,15 +1,5 @@
import type { SurfaceBlockModel } from '@blocksuite/affine-block-surface';
import { EditPropsStore } from '@blocksuite/affine-shared/services';
import type { BlockStdScope } from '@blocksuite/block-std';
import {
type GfxController,
GfxControllerIdentifier,
} from '@blocksuite/block-std/gfx';
import type { BlockModel } from '@blocksuite/store';
import type { Connectable } from '../../../_common/utils/index.js'; import type { Connectable } from '../../../_common/utils/index.js';
import type { EdgelessRootBlockComponent } from '../index.js'; import type { EdgelessRootBlockComponent } from '../index.js';
import { getLastPropsKey } from './get-last-props-key.js';
import { isConnectable, isNoteBlock } from './query.js'; import { isConnectable, isNoteBlock } from './query.js';
/** /**
@@ -42,51 +32,3 @@ export function deleteElements(
} }
}); });
} }
export function deleteElementsV2(
gfx: GfxController,
elements: BlockSuite.EdgelessModel[]
) {
const set = new Set(elements);
elements.forEach(element => {
if (isConnectable(element)) {
const connectors = (gfx.surface as SurfaceBlockModel).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);
}
});
}
export function addBlock(
std: BlockStdScope,
flavour: BlockSuite.EdgelessModelKeys,
props: Record<string, unknown>,
parentId?: string | BlockModel,
parentIndex?: number
) {
const gfx = std.get(GfxControllerIdentifier);
const key = getLastPropsKey(flavour as BlockSuite.EdgelessModelKeys, props);
if (key) {
props = std.get(EditPropsStore).applyLastProps(key, props);
}
const nProps = {
...props,
index: gfx.layer.generateIndex(),
};
return std.doc.addBlock(flavour as never, nProps, parentId, parentIndex);
}

View File

@@ -1,5 +1,6 @@
import { import {
CanvasElementType, CanvasElementType,
EdgelessCRUDIdentifier,
type IModelCoord, type IModelCoord,
TextUtils, TextUtils,
} from '@blocksuite/affine-block-surface'; } from '@blocksuite/affine-block-surface';
@@ -12,11 +13,7 @@ import { ShapeElementModel, TextElementModel } from '@blocksuite/affine-model';
import type { PointerEventState } from '@blocksuite/block-std'; import type { PointerEventState } from '@blocksuite/block-std';
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions'; import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
import type { IVec } from '@blocksuite/global/utils'; import type { IVec } from '@blocksuite/global/utils';
import { import { assertInstanceOf, Bound } from '@blocksuite/global/utils';
assertExists,
assertInstanceOf,
Bound,
} from '@blocksuite/global/utils';
import { DocCollection } from '@blocksuite/store'; import { DocCollection } from '@blocksuite/store';
import { EdgelessConnectorLabelEditor } from '../components/text/edgeless-connector-label-editor.js'; import { EdgelessConnectorLabelEditor } from '../components/text/edgeless-connector-label-editor.js';
@@ -77,7 +74,9 @@ export function mountShapeTextEditor(
if (!shapeElement.text) { if (!shapeElement.text) {
const text = new DocCollection.Y.Text(); const text = new DocCollection.Y.Text();
edgeless.service.updateElement(shapeElement.id, { text }); edgeless.std
.get(EdgelessCRUDIdentifier)
.updateElement(shapeElement.id, { text });
} }
const updatedElement = edgeless.service.getElementById(shapeElement.id); const updatedElement = edgeless.service.getElementById(shapeElement.id);
@@ -164,13 +163,16 @@ export function addText(
event.x, event.x,
event.y event.y
); );
const id = edgeless.service.addElement(CanvasElementType.TEXT, { const id = edgeless.std
.get(EdgelessCRUDIdentifier)
.addElement(CanvasElementType.TEXT, {
xywh: new Bound(modelX, modelY, 32, 32).serialize(), xywh: new Bound(modelX, modelY, 32, 32).serialize(),
text: new DocCollection.Y.Text(), text: new DocCollection.Y.Text(),
}); });
if (!id) return;
edgeless.doc.captureSync(); edgeless.doc.captureSync();
const textElement = edgeless.service.getElementById(id); const textElement = edgeless.service.getElementById(id);
assertExists(textElement); if (!textElement) return;
if (textElement instanceof TextElementModel) { if (textElement instanceof TextElementModel) {
mountTextElementEditor(textElement, edgeless); mountTextElementEditor(textElement, edgeless);
} }
@@ -203,7 +205,7 @@ export function mountConnectorLabelEditor(
labelXYWH = bounds.toXYWH(); labelXYWH = bounds.toXYWH();
} }
edgeless.service.updateElement(connector.id, { edgeless.std.get(EdgelessCRUDIdentifier).updateElement(connector.id, {
text, text,
labelXYWH, labelXYWH,
labelOffset: { ...labelOffset }, labelOffset: { ...labelOffset },

View File

@@ -1,4 +1,7 @@
import { updateXYWH } from '@blocksuite/affine-block-surface'; import {
EdgelessCRUDIdentifier,
updateXYWH,
} from '@blocksuite/affine-block-surface';
import { import {
AlignBottomIcon, AlignBottomIcon,
AlignDistributeHorizontallyIcon, AlignDistributeHorizontallyIcon,
@@ -267,7 +270,7 @@ export class EdgelessAlignButton extends WithDisposable(LitElement) {
} }
private _updateXYWH(ele: BlockSuite.EdgelessModel, bound: Bound) { private _updateXYWH(ele: BlockSuite.EdgelessModel, bound: Bound) {
const { updateElement } = this.edgeless.service; const { updateElement } = this.edgeless.std.get(EdgelessCRUDIdentifier);
const { updateBlock } = this.edgeless.doc; const { updateBlock } = this.edgeless.doc;
updateXYWH(ele, bound, updateElement, updateBlock); updateXYWH(ele, bound, updateElement, updateBlock);
} }

View File

@@ -1,3 +1,4 @@
import { EdgelessCRUDIdentifier } from '@blocksuite/affine-block-surface';
import type { import type {
BrushElementModel, BrushElementModel,
BrushProps, BrushProps,
@@ -57,10 +58,7 @@ export class EdgelessChangeBrushButton extends WithDisposable(LitElement) {
pickColor = (event: PickColorEvent) => { pickColor = (event: PickColorEvent) => {
if (event.type === 'pick') { if (event.type === 'pick') {
this.elements.forEach(ele => this.elements.forEach(ele =>
this.service.updateElement( this.crud.updateElement(ele.id, packColor('color', { ...event.detail }))
ele.id,
packColor('color', { ...event.detail })
)
); );
return; return;
} }
@@ -93,6 +91,10 @@ export class EdgelessChangeBrushButton extends WithDisposable(LitElement) {
return this.edgeless.surface; return this.edgeless.surface;
} }
get crud() {
return this.edgeless.std.get(EdgelessCRUDIdentifier);
}
private _setBrushProp<K extends keyof BrushProps>( private _setBrushProp<K extends keyof BrushProps>(
key: K, key: K,
value: BrushProps[K] value: BrushProps[K]
@@ -101,7 +103,7 @@ export class EdgelessChangeBrushButton extends WithDisposable(LitElement) {
this.elements this.elements
.filter(notEqual(key, value)) .filter(notEqual(key, value))
.forEach(element => .forEach(element =>
this.service.updateElement(element.id, { [key]: value }) this.crud.updateElement(element.id, { [key]: value })
); );
} }

View File

@@ -1,3 +1,4 @@
import { EdgelessCRUDIdentifier } from '@blocksuite/affine-block-surface';
import { import {
AddTextIcon, AddTextIcon,
ConnectorCWithArrowIcon, ConnectorCWithArrowIcon,
@@ -222,10 +223,14 @@ const MODE_CHOOSE: [ConnectorMode, () => TemplateResult<1>][] = [
] as const; ] as const;
export class EdgelessChangeConnectorButton extends WithDisposable(LitElement) { export class EdgelessChangeConnectorButton extends WithDisposable(LitElement) {
get crud() {
return this.edgeless.std.get(EdgelessCRUDIdentifier);
}
pickColor = (event: PickColorEvent) => { pickColor = (event: PickColorEvent) => {
if (event.type === 'pick') { if (event.type === 'pick') {
this.elements.forEach(ele => this.elements.forEach(ele =>
this.service.updateElement( this.crud.updateElement(
ele.id, ele.id,
packColor('stroke', { ...event.detail }) packColor('stroke', { ...event.detail })
) )
@@ -257,7 +262,7 @@ export class EdgelessChangeConnectorButton extends WithDisposable(LitElement) {
if (frontEndpointStyle === rearEndpointStyle) return; if (frontEndpointStyle === rearEndpointStyle) return;
this.elements.forEach(element => this.elements.forEach(element =>
this.service.updateElement(element.id, { this.crud.updateElement(element.id, {
frontEndpointStyle: rearEndpointStyle, frontEndpointStyle: rearEndpointStyle,
rearEndpointStyle: frontEndpointStyle, rearEndpointStyle: frontEndpointStyle,
}) })
@@ -286,7 +291,7 @@ export class EdgelessChangeConnectorButton extends WithDisposable(LitElement) {
: 'rearEndpointStyle']: style, : 'rearEndpointStyle']: style,
}; };
this.elements.forEach(element => this.elements.forEach(element =>
this.service.updateElement(element.id, { ...props }) this.crud.updateElement(element.id, { ...props })
); );
} }
@@ -297,7 +302,7 @@ export class EdgelessChangeConnectorButton extends WithDisposable(LitElement) {
this.elements this.elements
.filter(notEqual(key, value)) .filter(notEqual(key, value))
.forEach(element => .forEach(element =>
this.service.updateElement(element.id, { [key]: value }) this.crud.updateElement(element.id, { [key]: value })
); );
} }

View File

@@ -2,6 +2,7 @@ import {
getDocContentWithMaxLength, getDocContentWithMaxLength,
getEmbedCardIcons, getEmbedCardIcons,
} from '@blocksuite/affine-block-embed'; } from '@blocksuite/affine-block-embed';
import { EdgelessCRUDIdentifier } from '@blocksuite/affine-block-surface';
import { import {
CaptionIcon, CaptionIcon,
CenterPeekIcon, CenterPeekIcon,
@@ -114,6 +115,10 @@ export class EdgelessChangeEmbedCardButton extends WithDisposable(LitElement) {
} }
`; `;
get crud() {
return this.edgeless.std.get(EdgelessCRUDIdentifier);
}
private readonly _convertToCardView = () => { private readonly _convertToCardView = () => {
if (this._isCardView) { if (this._isCardView) {
return; return;
@@ -146,7 +151,7 @@ export class EdgelessChangeEmbedCardButton extends WithDisposable(LitElement) {
bound.w = EMBED_CARD_WIDTH[targetStyle]; bound.w = EMBED_CARD_WIDTH[targetStyle];
bound.h = EMBED_CARD_HEIGHT[targetStyle]; bound.h = EMBED_CARD_HEIGHT[targetStyle];
const newId = this.edgeless.service.addBlock( const newId = this.crud.addBlock(
targetFlavour, targetFlavour,
{ url, xywh: bound.serialize(), style: targetStyle, caption }, { url, xywh: bound.serialize(), style: targetStyle, caption },
this.edgeless.surface.model this.edgeless.surface.model
@@ -197,7 +202,7 @@ export class EdgelessChangeEmbedCardButton extends WithDisposable(LitElement) {
bound.w = EMBED_CARD_WIDTH[targetStyle]; bound.w = EMBED_CARD_WIDTH[targetStyle];
bound.h = EMBED_CARD_HEIGHT[targetStyle]; bound.h = EMBED_CARD_HEIGHT[targetStyle];
const newId = this.edgeless.service.addBlock( const newId = this.crud.addBlock(
flavour, flavour,
{ {
url, url,
@@ -206,6 +211,7 @@ export class EdgelessChangeEmbedCardButton extends WithDisposable(LitElement) {
}, },
this.edgeless.surface.model this.edgeless.surface.model
); );
if (!newId) return;
this.std.command.exec('reassociateConnectors', { this.std.command.exec('reassociateConnectors', {
oldId: id, oldId: id,

View File

@@ -1,3 +1,4 @@
import { EdgelessCRUDIdentifier } from '@blocksuite/affine-block-surface';
import { import {
NoteIcon, NoteIcon,
RenameIcon, RenameIcon,
@@ -51,10 +52,14 @@ function getMostCommonColor(
} }
export class EdgelessChangeFrameButton extends WithDisposable(LitElement) { export class EdgelessChangeFrameButton extends WithDisposable(LitElement) {
get crud() {
return this.edgeless.std.get(EdgelessCRUDIdentifier);
}
pickColor = (event: PickColorEvent) => { pickColor = (event: PickColorEvent) => {
if (event.type === 'pick') { if (event.type === 'pick') {
this.frames.forEach(ele => this.frames.forEach(ele =>
this.service.updateElement( this.crud.updateElement(
ele.id, ele.id,
packColor('background', { ...event.detail }) packColor('background', { ...event.detail })
) )
@@ -116,7 +121,7 @@ export class EdgelessChangeFrameButton extends WithDisposable(LitElement) {
private _setFrameBackground(color: string) { private _setFrameBackground(color: string) {
this.frames.forEach(frame => { this.frames.forEach(frame => {
this.service.updateElement(frame.id, { background: color }); this.crud.updateElement(frame.id, { background: color });
}); });
} }

View File

@@ -1,3 +1,4 @@
import { EdgelessCRUDIdentifier } from '@blocksuite/affine-block-surface';
import { import {
ExpandIcon, ExpandIcon,
LineStyleIcon, LineStyleIcon,
@@ -78,6 +79,10 @@ function getMostCommonBackground(
} }
export class EdgelessChangeNoteButton extends WithDisposable(LitElement) { export class EdgelessChangeNoteButton extends WithDisposable(LitElement) {
get crud() {
return this.edgeless.std.get(EdgelessCRUDIdentifier);
}
private readonly _setBorderRadius = (borderRadius: number) => { private readonly _setBorderRadius = (borderRadius: number) => {
this.notes.forEach(note => { this.notes.forEach(note => {
const props = { const props = {
@@ -88,7 +93,7 @@ export class EdgelessChangeNoteButton extends WithDisposable(LitElement) {
}, },
}, },
}; };
this.edgeless.service.updateElement(note.id, props); this.crud.updateElement(note.id, props);
}); });
}; };
@@ -111,7 +116,7 @@ export class EdgelessChangeNoteButton extends WithDisposable(LitElement) {
if (event.type === 'pick') { if (event.type === 'pick') {
this.notes.forEach(element => { this.notes.forEach(element => {
const props = packColor('background', { ...event.detail }); const props = packColor('background', { ...event.detail });
this.edgeless.service.updateElement(element.id, props); this.crud.updateElement(element.id, props);
}); });
return; return;
} }
@@ -142,7 +147,7 @@ export class EdgelessChangeNoteButton extends WithDisposable(LitElement) {
private _setBackground(background: string) { private _setBackground(background: string) {
this.notes.forEach(element => { this.notes.forEach(element => {
this.edgeless.service.updateElement(element.id, { background }); this.crud.updateElement(element.id, { background });
}); });
} }
@@ -173,7 +178,7 @@ export class EdgelessChangeNoteButton extends WithDisposable(LitElement) {
return; return;
} }
this.edgeless.service.updateElement(note.id, { displayMode: newMode }); this.crud.updateElement(note.id, { displayMode: newMode });
const noteParent = this.doc.getParent(note); const noteParent = this.doc.getParent(note);
assertExists(noteParent); assertExists(noteParent);
@@ -208,7 +213,7 @@ export class EdgelessChangeNoteButton extends WithDisposable(LitElement) {
}, },
}, },
}; };
this.edgeless.service.updateElement(note.id, props); this.crud.updateElement(note.id, props);
}); });
} }
@@ -222,7 +227,7 @@ export class EdgelessChangeNoteButton extends WithDisposable(LitElement) {
}, },
}, },
}; };
this.edgeless.service.updateElement(note.id, props); this.crud.updateElement(note.id, props);
}); });
} }
@@ -236,7 +241,7 @@ export class EdgelessChangeNoteButton extends WithDisposable(LitElement) {
}, },
}, },
}; };
this.edgeless.service.updateElement(note.id, props); this.crud.updateElement(note.id, props);
}); });
} }

View File

@@ -1,3 +1,4 @@
import { EdgelessCRUDIdentifier } from '@blocksuite/affine-block-surface';
import { import {
AddTextIcon, AddTextIcon,
ChangeShapeIcon, ChangeShapeIcon,
@@ -155,6 +156,10 @@ export class EdgelessChangeShapeButton extends WithDisposable(LitElement) {
return this.edgeless.service; return this.edgeless.service;
} }
get crud() {
return this.edgeless.std.get(EdgelessCRUDIdentifier);
}
#pickColor<K extends keyof Pick<ShapeProps, 'fillColor' | 'strokeColor'>>( #pickColor<K extends keyof Pick<ShapeProps, 'fillColor' | 'strokeColor'>>(
key: K key: K
) { ) {
@@ -166,7 +171,7 @@ export class EdgelessChangeShapeButton extends WithDisposable(LitElement) {
if (key === 'fillColor' && !ele.filled) { if (key === 'fillColor' && !ele.filled) {
Object.assign(props, { filled: true }); Object.assign(props, { filled: true });
} }
this.service.updateElement(ele.id, props); this.crud.updateElement(ele.id, props);
}); });
return; return;
} }
@@ -199,25 +204,25 @@ export class EdgelessChangeShapeButton extends WithDisposable(LitElement) {
const filled = !isTransparent(fillColor); const filled = !isTransparent(fillColor);
const color = this._getTextColor(fillColor); const color = this._getTextColor(fillColor);
this.elements.forEach(ele => this.elements.forEach(ele =>
this.service.updateElement(ele.id, { filled, fillColor, color }) this.crud.updateElement(ele.id, { filled, fillColor, color })
); );
} }
private _setShapeStrokeColor(strokeColor: string) { private _setShapeStrokeColor(strokeColor: string) {
this.elements.forEach(ele => this.elements.forEach(ele =>
this.service.updateElement(ele.id, { strokeColor }) this.crud.updateElement(ele.id, { strokeColor })
); );
} }
private _setShapeStrokeStyle(strokeStyle: StrokeStyle) { private _setShapeStrokeStyle(strokeStyle: StrokeStyle) {
this.elements.forEach(ele => this.elements.forEach(ele =>
this.service.updateElement(ele.id, { strokeStyle }) this.crud.updateElement(ele.id, { strokeStyle })
); );
} }
private _setShapeStrokeWidth(strokeWidth: number) { private _setShapeStrokeWidth(strokeWidth: number) {
this.elements.forEach(ele => this.elements.forEach(ele =>
this.service.updateElement(ele.id, { strokeWidth }) this.crud.updateElement(ele.id, { strokeWidth })
); );
} }
@@ -226,7 +231,7 @@ export class EdgelessChangeShapeButton extends WithDisposable(LitElement) {
shapeStyle === ShapeStyle.General ? FontFamily.Inter : FontFamily.Kalam; shapeStyle === ShapeStyle.General ? FontFamily.Inter : FontFamily.Kalam;
this.elements.forEach(ele => { this.elements.forEach(ele => {
this.service.updateElement(ele.id, { shapeStyle, fontFamily }); this.crud.updateElement(ele.id, { shapeStyle, fontFamily });
}); });
} }
@@ -257,7 +262,7 @@ export class EdgelessChangeShapeButton extends WithDisposable(LitElement) {
this._shapePanel.slots.select.on(shapeName => { this._shapePanel.slots.select.on(shapeName => {
this.edgeless.doc.captureSync(); this.edgeless.doc.captureSync();
this.elements.forEach(element => { this.elements.forEach(element => {
this.service.updateElement(element.id, { this.crud.updateElement(element.id, {
shapeType: getShapeType(shapeName), shapeType: getShapeType(shapeName),
radius: getShapeRadius(shapeName), radius: getShapeRadius(shapeName),
}); });

View File

@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { import {
ConnectorUtils, ConnectorUtils,
EdgelessCRUDIdentifier,
normalizeShapeBound, normalizeShapeBound,
TextUtils, TextUtils,
} from '@blocksuite/affine-block-surface'; } from '@blocksuite/affine-block-surface';
@@ -176,6 +176,10 @@ export class EdgelessChangeTextMenu extends WithDisposable(LitElement) {
} }
`; `;
get crud() {
return this.edgeless.std.get(EdgelessCRUDIdentifier);
}
private readonly _setFontFamily = (fontFamily: FontFamily) => { private readonly _setFontFamily = (fontFamily: FontFamily) => {
const currentFontWeight = getMostCommonFontWeight(this.elements); const currentFontWeight = getMostCommonFontWeight(this.elements);
const fontWeight = TextUtils.isFontWeightSupported( const fontWeight = TextUtils.isFontWeightSupported(
@@ -194,7 +198,7 @@ export class EdgelessChangeTextMenu extends WithDisposable(LitElement) {
const props = { fontFamily, fontWeight, fontStyle }; const props = { fontFamily, fontWeight, fontStyle };
this.elements.forEach(element => { this.elements.forEach(element => {
this.service.updateElement(element.id, buildProps(element, props)); this.crud.updateElement(element.id, buildProps(element, props));
this._updateElementBound(element); this._updateElementBound(element);
}); });
}; };
@@ -202,7 +206,7 @@ export class EdgelessChangeTextMenu extends WithDisposable(LitElement) {
private readonly _setFontSize = (fontSize: number) => { private readonly _setFontSize = (fontSize: number) => {
const props = { fontSize }; const props = { fontSize };
this.elements.forEach(element => { this.elements.forEach(element => {
this.service.updateElement(element.id, buildProps(element, props)); this.crud.updateElement(element.id, buildProps(element, props));
this._updateElementBound(element); this._updateElementBound(element);
}); });
}; };
@@ -213,7 +217,7 @@ export class EdgelessChangeTextMenu extends WithDisposable(LitElement) {
) => { ) => {
const props = { fontWeight, fontStyle }; const props = { fontWeight, fontStyle };
this.elements.forEach(element => { this.elements.forEach(element => {
this.service.updateElement(element.id, buildProps(element, props)); this.crud.updateElement(element.id, buildProps(element, props));
this._updateElementBound(element); this._updateElementBound(element);
}); });
}; };
@@ -221,14 +225,14 @@ export class EdgelessChangeTextMenu extends WithDisposable(LitElement) {
private readonly _setTextAlign = (textAlign: TextAlign) => { private readonly _setTextAlign = (textAlign: TextAlign) => {
const props = { textAlign }; const props = { textAlign };
this.elements.forEach(element => { this.elements.forEach(element => {
this.service.updateElement(element.id, buildProps(element, props)); this.crud.updateElement(element.id, buildProps(element, props));
}); });
}; };
private readonly _setTextColor = ({ detail: color }: ColorEvent) => { private readonly _setTextColor = ({ detail: color }: ColorEvent) => {
const props = { color }; const props = { color };
this.elements.forEach(element => { this.elements.forEach(element => {
this.service.updateElement(element.id, buildProps(element, props)); this.crud.updateElement(element.id, buildProps(element, props));
}); });
}; };
@@ -257,7 +261,7 @@ export class EdgelessChangeTextMenu extends WithDisposable(LitElement) {
}, },
Bound.fromXYWH(element.deserializedXYWH) Bound.fromXYWH(element.deserializedXYWH)
); );
this.service.updateElement(element.id, { this.crud.updateElement(element.id, {
xywh: newBound.serialize(), xywh: newBound.serialize(),
}); });
} else if ( } else if (
@@ -285,7 +289,7 @@ export class EdgelessChangeTextMenu extends WithDisposable(LitElement) {
prevBounds prevBounds
); );
bounds.center = center; bounds.center = center;
this.service.updateElement(element.id, { this.crud.updateElement(element.id, {
labelXYWH: bounds.toXYWH(), labelXYWH: bounds.toXYWH(),
}); });
} else if ( } else if (
@@ -296,7 +300,7 @@ export class EdgelessChangeTextMenu extends WithDisposable(LitElement) {
element, element,
Bound.fromXYWH(element.deserializedXYWH) Bound.fromXYWH(element.deserializedXYWH)
); );
this.service.updateElement(element.id, { this.crud.updateElement(element.id, {
xywh: newBound.serialize(), xywh: newBound.serialize(),
}); });
} }
@@ -307,7 +311,7 @@ export class EdgelessChangeTextMenu extends WithDisposable(LitElement) {
if (event.type === 'pick') { if (event.type === 'pick') {
this.elements.forEach(element => { this.elements.forEach(element => {
const props = packColor('color', { ...event.detail }); const props = packColor('color', { ...event.detail });
this.service.updateElement(element.id, buildProps(element, props)); this.crud.updateElement(element.id, buildProps(element, props));
this._updateElementBound(element); this._updateElementBound(element);
}); });
return; return;

View File

@@ -9,6 +9,7 @@ import {
promptDocTitle, promptDocTitle,
} from '@blocksuite/affine-block-embed'; } from '@blocksuite/affine-block-embed';
import type { ImageBlockComponent } from '@blocksuite/affine-block-image'; import type { ImageBlockComponent } from '@blocksuite/affine-block-image';
import { EdgelessCRUDIdentifier } from '@blocksuite/affine-block-surface';
import { isPeekable, peek } from '@blocksuite/affine-components/peek'; import { isPeekable, peek } from '@blocksuite/affine-components/peek';
import type { MenuItemGroup } from '@blocksuite/affine-components/toolbar'; import type { MenuItemGroup } from '@blocksuite/affine-components/toolbar';
import { TelemetryProvider } from '@blocksuite/affine-shared/services'; import { TelemetryProvider } from '@blocksuite/affine-shared/services';
@@ -253,8 +254,9 @@ export const conversionsGroup: MenuItemGroup<ElementToolbarMoreMenuContext> = {
if (title === null) return; if (title === null) return;
const linkedDoc = createLinkedDocFromNote(doc, element, title); const linkedDoc = createLinkedDocFromNote(doc, element, title);
const crud = std.get(EdgelessCRUDIdentifier);
// insert linked doc card // insert linked doc card
const cardId = service.addBlock( const cardId = crud.addBlock(
'affine:embed-synced-doc', 'affine:embed-synced-doc',
{ {
xywh: element.xywh, xywh: element.xywh,
@@ -300,15 +302,7 @@ export const conversionsGroup: MenuItemGroup<ElementToolbarMoreMenuContext> = {
icon: LinkedPageIcon({ width: '20', height: '20' }), icon: LinkedPageIcon({ width: '20', height: '20' }),
label: 'Create linked doc', label: 'Create linked doc',
type: 'create-linked-doc', type: 'create-linked-doc',
action: async ({ action: async ({ doc, selection, surface, edgeless, host, std }) => {
doc,
selection,
service,
surface,
edgeless,
host,
std,
}) => {
const title = await promptDocTitle(std); const title = await promptDocTitle(std);
if (title === null) return; if (title === null) return;
@@ -318,6 +312,7 @@ export const conversionsGroup: MenuItemGroup<ElementToolbarMoreMenuContext> = {
elements, elements,
title title
); );
const crud = std.get(EdgelessCRUDIdentifier);
// delete selected elements // delete selected elements
doc.transact(() => { doc.transact(() => {
deleteElements(edgeless, elements); deleteElements(edgeless, elements);
@@ -326,7 +321,7 @@ export const conversionsGroup: MenuItemGroup<ElementToolbarMoreMenuContext> = {
const width = 364; const width = 364;
const height = 390; const height = 390;
const bound = getCommonBoundWithRotation(elements); const bound = getCommonBoundWithRotation(elements);
const cardId = service.addBlock( const cardId = crud.addBlock(
'affine:embed-linked-doc', 'affine:embed-linked-doc',
{ {
xywh: `[${bound.center[0] - width / 2}, ${bound.center[1] - height / 2}, ${width}, ${height}]`, xywh: `[${bound.center[0] - width / 2}, ${bound.center[1] - height / 2}, ${width}, ${height}]`,

View File

@@ -5,6 +5,7 @@ import {
LayoutType, LayoutType,
NoteDisplayMode, NoteDisplayMode,
} from '@blocksuite/blocks'; } from '@blocksuite/blocks';
import { assertExists } from '@blocksuite/global/utils';
import { DocCollection } from '@blocksuite/store'; import { DocCollection } from '@blocksuite/store';
import { beforeEach, describe, expect, test } from 'vitest'; import { beforeEach, describe, expect, test } from 'vitest';
@@ -26,9 +27,9 @@ describe('group', () => {
const map = new DocCollection.Y.Map<boolean>(); const map = new DocCollection.Y.Map<boolean>();
const ids = Array.from({ length: 2 }) const ids = Array.from({ length: 2 })
.map(() => { .map(() => {
const id = service.addElement('shape', { const id = service.crud.addElement('shape', {
shapeType: 'rect', shapeType: 'rect',
}); })!;
map.set(id, true); map.set(id, true);
return id; return id;
@@ -40,7 +41,7 @@ describe('group', () => {
return id; return id;
}) })
); );
service.addElement('group', { children: map }); service.crud.addElement('group', { children: map });
doc.captureSync(); doc.captureSync();
expect(service.elements.length).toBe(3); expect(service.elements.length).toBe(3);
@@ -64,14 +65,15 @@ describe('group', () => {
const map = new DocCollection.Y.Map<boolean>(); const map = new DocCollection.Y.Map<boolean>();
const doc = service.doc; const doc = service.doc;
const noteId = addNote(doc); const noteId = addNote(doc);
const shapeId = service.addElement('shape', { const shapeId = service.crud.addElement('shape', {
shapeType: 'rect', shapeType: 'rect',
}); });
assertExists(shapeId);
map.set(noteId, true); map.set(noteId, true);
map.set(shapeId, true); map.set(shapeId, true);
const groupId = service.addElement('group', { children: map }); const groupId = service.crud.addElement('group', { children: map });
assertExists(groupId);
expect(service.elements.length).toBe(2); expect(service.elements.length).toBe(2);
expect(doc.getBlock(noteId)).toBeDefined(); expect(doc.getBlock(noteId)).toBeDefined();
doc.captureSync(); doc.captureSync();
@@ -86,14 +88,16 @@ describe('group', () => {
}); });
test("group's xywh should update automatically when children change", async () => { test("group's xywh should update automatically when children change", async () => {
const shape1 = service.addElement('shape', { const shape1 = service.crud.addElement('shape', {
shapeType: 'rect', shapeType: 'rect',
xywh: '[0,0,100,100]', xywh: '[0,0,100,100]',
}); });
const shape2 = service.addElement('shape', { assertExists(shape1);
const shape2 = service.crud.addElement('shape', {
shapeType: 'rect', shapeType: 'rect',
xywh: '[100,100,100,100]', xywh: '[100,100,100,100]',
}); });
assertExists(shape2);
const note1 = addNote(doc, { const note1 = addNote(doc, {
displayMode: NoteDisplayMode.DocAndEdgeless, displayMode: NoteDisplayMode.DocAndEdgeless,
xywh: '[200,200,800,100]', xywh: '[200,200,800,100]',
@@ -114,7 +118,9 @@ describe('group', () => {
children.set(shape2, true); children.set(shape2, true);
children.set(note1, true); children.set(note1, true);
const groupId = service.addElement('group', { children }); const groupId = service.crud.addElement('group', { children });
assertExists(groupId);
const group = service.getElementById(groupId) as GroupElementModel; const group = service.getElementById(groupId) as GroupElementModel;
const assertInitial = () => { const assertInitial = () => {
expect(group.x).toBe(0); expect(group.x).toBe(0);
@@ -139,7 +145,7 @@ describe('group', () => {
await wait(); await wait();
assertInitial(); assertInitial();
service.updateElement(note1, { service.crud.updateElement(note1, {
xywh: '[300,300,800,100]', xywh: '[300,300,800,100]',
}); });
await wait(); await wait();
@@ -165,7 +171,7 @@ describe('group', () => {
await wait(); await wait();
assertInitial(); assertInitial();
service.updateElement(shape1, { service.crud.updateElement(shape1, {
xywh: '[100,100,100,100]', xywh: '[100,100,100,100]',
}); });
await wait(); await wait();
@@ -182,7 +188,8 @@ describe('group', () => {
test('empty group should have all zero xywh', () => { test('empty group should have all zero xywh', () => {
const map = new DocCollection.Y.Map<boolean>(); const map = new DocCollection.Y.Map<boolean>();
const groupId = service.addElement('group', { children: map }); const groupId = service.crud.addElement('group', { children: map });
assertExists(groupId);
const group = service.getElementById(groupId) as GroupElementModel; const group = service.getElementById(groupId) as GroupElementModel;
expect(group.x).toBe(0); expect(group.x).toBe(0);
@@ -193,9 +200,9 @@ describe('group', () => {
test('descendant of group should not contain itself', () => { test('descendant of group should not contain itself', () => {
const groupIds = [1, 2, 3].map(_ => { const groupIds = [1, 2, 3].map(_ => {
return service.addElement('group', { return service.crud.addElement('group', {
children: new DocCollection.Y.Map<boolean>(), children: new DocCollection.Y.Map<boolean>(),
}); }) as string;
}); });
const groups = groupIds.map( const groups = groupIds.map(
id => service.getElementById(id) as GroupElementModel id => service.getElementById(id) as GroupElementModel
@@ -245,7 +252,8 @@ describe('mindmap', () => {
}, },
], ],
}; };
const mindmapId = service.addElement('mindmap', { children: tree }); const mindmapId = service.crud.addElement('mindmap', { children: tree });
assertExists(mindmapId);
const mindmap = () => const mindmap = () =>
service.getElementById(mindmapId) as MindmapElementModel; service.getElementById(mindmapId) as MindmapElementModel;
@@ -292,10 +300,11 @@ describe('mindmap', () => {
}, },
], ],
}; };
const mindmapId = service.addElement('mindmap', { const mindmapId = service.crud.addElement('mindmap', {
type: LayoutType.RIGHT, type: LayoutType.RIGHT,
children: tree, children: tree,
}); });
assertExists(mindmapId);
const mindmap = () => const mindmap = () =>
service.getElementById(mindmapId) as MindmapElementModel; service.getElementById(mindmapId) as MindmapElementModel;
@@ -336,10 +345,11 @@ describe('mindmap', () => {
}, },
], ],
}; };
const mindmapId = service.addElement('mindmap', { const mindmapId = service.crud.addElement('mindmap', {
type: LayoutType.RIGHT, type: LayoutType.RIGHT,
children: tree, children: tree,
}); });
assertExists(mindmapId);
const mindmap = () => const mindmap = () =>
service.getElementById(mindmapId) as MindmapElementModel; service.getElementById(mindmapId) as MindmapElementModel;

View File

@@ -24,6 +24,7 @@ import {
ShapeType, ShapeType,
type TextElementModel, type TextElementModel,
} from '@blocksuite/blocks'; } from '@blocksuite/blocks';
import { assertExists } from '@blocksuite/global/utils';
import { beforeEach, describe, expect, test } from 'vitest'; import { beforeEach, describe, expect, test } from 'vitest';
import { getDocRootBlock } from '../utils/edgeless.js'; import { getDocRootBlock } from '../utils/edgeless.js';
@@ -45,10 +46,13 @@ describe('apply last props', () => {
test('shapes', () => { test('shapes', () => {
// rect shape // rect shape
const rectId = service.addElement('shape', { shapeType: ShapeType.Rect }); const rectId = service.crud.addElement('shape', {
shapeType: ShapeType.Rect,
});
assertExists(rectId);
const rectShape = service.getElementById(rectId) as ShapeElementModel; const rectShape = service.getElementById(rectId) as ShapeElementModel;
expect(rectShape.fillColor).toBe(ShapeFillColor.Yellow); expect(rectShape.fillColor).toBe(ShapeFillColor.Yellow);
service.updateElement(rectId, { service.crud.updateElement(rectId, {
fillColor: ShapeFillColor.Orange, fillColor: ShapeFillColor.Orange,
}); });
expect( expect(
@@ -57,12 +61,13 @@ describe('apply last props', () => {
).toBe(ShapeFillColor.Orange); ).toBe(ShapeFillColor.Orange);
// diamond shape // diamond shape
const diamondId = service.addElement('shape', { const diamondId = service.crud.addElement('shape', {
shapeType: ShapeType.Diamond, shapeType: ShapeType.Diamond,
}); });
assertExists(diamondId);
const diamondShape = service.getElementById(diamondId) as ShapeElementModel; const diamondShape = service.getElementById(diamondId) as ShapeElementModel;
expect(diamondShape.fillColor).toBe(ShapeFillColor.Yellow); expect(diamondShape.fillColor).toBe(ShapeFillColor.Yellow);
service.updateElement(diamondId, { service.crud.updateElement(diamondId, {
fillColor: ShapeFillColor.Blue, fillColor: ShapeFillColor.Blue,
}); });
expect( expect(
@@ -71,15 +76,16 @@ describe('apply last props', () => {
).toBe(ShapeFillColor.Blue); ).toBe(ShapeFillColor.Blue);
// rounded rect shape // rounded rect shape
const roundedRectId = service.addElement('shape', { const roundedRectId = service.crud.addElement('shape', {
shapeType: ShapeType.Rect, shapeType: ShapeType.Rect,
radius: 0.1, radius: 0.1,
}); });
assertExists(roundedRectId);
const roundedRectShape = service.getElementById( const roundedRectShape = service.getElementById(
roundedRectId roundedRectId
) as ShapeElementModel; ) as ShapeElementModel;
expect(roundedRectShape.fillColor).toBe(ShapeFillColor.Yellow); expect(roundedRectShape.fillColor).toBe(ShapeFillColor.Yellow);
service.updateElement(roundedRectId, { service.crud.updateElement(roundedRectId, {
fillColor: ShapeFillColor.Green, fillColor: ShapeFillColor.Green,
}); });
expect( expect(
@@ -87,22 +93,27 @@ describe('apply last props', () => {
).toBe(ShapeFillColor.Green); ).toBe(ShapeFillColor.Green);
// apply last props // apply last props
const rectId2 = service.addElement('shape', { shapeType: ShapeType.Rect }); const rectId2 = service.crud.addElement('shape', {
shapeType: ShapeType.Rect,
});
assertExists(rectId2);
const rectShape2 = service.getElementById(rectId2) as ShapeElementModel; const rectShape2 = service.getElementById(rectId2) as ShapeElementModel;
expect(rectShape2.fillColor).toBe(ShapeFillColor.Orange); expect(rectShape2.fillColor).toBe(ShapeFillColor.Orange);
const diamondId2 = service.addElement('shape', { const diamondId2 = service.crud.addElement('shape', {
shapeType: ShapeType.Diamond, shapeType: ShapeType.Diamond,
}); });
assertExists(diamondId2);
const diamondShape2 = service.getElementById( const diamondShape2 = service.getElementById(
diamondId2 diamondId2
) as ShapeElementModel; ) as ShapeElementModel;
expect(diamondShape2.fillColor).toBe(ShapeFillColor.Blue); expect(diamondShape2.fillColor).toBe(ShapeFillColor.Blue);
const roundedRectId2 = service.addElement('shape', { const roundedRectId2 = service.crud.addElement('shape', {
shapeType: ShapeType.Rect, shapeType: ShapeType.Rect,
radius: 0.1, radius: 0.1,
}); });
assertExists(roundedRectId2);
const roundedRectShape2 = service.getElementById( const roundedRectShape2 = service.getElementById(
roundedRectId2 roundedRectId2
) as ShapeElementModel; ) as ShapeElementModel;
@@ -110,26 +121,29 @@ describe('apply last props', () => {
}); });
test('connector', () => { test('connector', () => {
const id = service.addElement('connector', { mode: 0 }); const id = service.crud.addElement('connector', { mode: 0 });
assertExists(id);
const connector = service.getElementById(id) as ConnectorElementModel; const connector = service.getElementById(id) as ConnectorElementModel;
expect(connector.stroke).toBe(LineColor.Grey); expect(connector.stroke).toBe(LineColor.Grey);
expect(connector.strokeWidth).toBe(2); expect(connector.strokeWidth).toBe(2);
expect(connector.strokeStyle).toBe('solid'); expect(connector.strokeStyle).toBe('solid');
expect(connector.frontEndpointStyle).toBe('None'); expect(connector.frontEndpointStyle).toBe('None');
expect(connector.rearEndpointStyle).toBe('Arrow'); expect(connector.rearEndpointStyle).toBe('Arrow');
service.updateElement(id, { strokeWidth: 10 }); service.crud.updateElement(id, { strokeWidth: 10 });
const id2 = service.addElement('connector', { mode: 1 }); const id2 = service.crud.addElement('connector', { mode: 1 });
assertExists(id2);
const connector2 = service.getElementById(id2) as ConnectorElementModel; const connector2 = service.getElementById(id2) as ConnectorElementModel;
expect(connector2.strokeWidth).toBe(10); expect(connector2.strokeWidth).toBe(10);
service.updateElement(id2, { service.crud.updateElement(id2, {
labelStyle: { labelStyle: {
color: LineColor.Magenta, color: LineColor.Magenta,
fontFamily: FontFamily.Kalam, fontFamily: FontFamily.Kalam,
}, },
}); });
const id3 = service.addElement('connector', { mode: 1 }); const id3 = service.crud.addElement('connector', { mode: 1 });
assertExists(id3);
const connector3 = service.getElementById(id3) as ConnectorElementModel; const connector3 = service.getElementById(id3) as ConnectorElementModel;
expect(connector3.strokeWidth).toBe(10); expect(connector3.strokeWidth).toBe(10);
expect(connector3.labelStyle.color).toBe(LineColor.Magenta); expect(connector3.labelStyle.color).toBe(LineColor.Magenta);
@@ -137,42 +151,46 @@ describe('apply last props', () => {
}); });
test('brush', () => { test('brush', () => {
const id = service.addElement('brush', {}); const id = service.crud.addElement('brush', {});
assertExists(id);
const brush = service.getElementById(id) as BrushElementModel; const brush = service.getElementById(id) as BrushElementModel;
expect(brush.color).toEqual({ expect(brush.color).toEqual({
dark: LineColor.White, dark: LineColor.White,
light: LineColor.Black, light: LineColor.Black,
}); });
expect(brush.lineWidth).toBe(4); expect(brush.lineWidth).toBe(4);
service.updateElement(id, { lineWidth: 10 }); service.crud.updateElement(id, { lineWidth: 10 });
const secondBrush = service.getElementById( const secondBrush = service.getElementById(
service.addElement('brush', {}) service.crud.addElement('brush', {}) as string
) as BrushElementModel; ) as BrushElementModel;
expect(secondBrush.lineWidth).toBe(10); expect(secondBrush.lineWidth).toBe(10);
}); });
test('text', () => { test('text', () => {
const id = service.addElement('text', {}); const id = service.crud.addElement('text', {});
assertExists(id);
const text = service.getElementById(id) as TextElementModel; const text = service.getElementById(id) as TextElementModel;
expect(text.fontSize).toBe(24); expect(text.fontSize).toBe(24);
service.updateElement(id, { fontSize: 36 }); service.crud.updateElement(id, { fontSize: 36 });
const secondText = service.getElementById( const secondText = service.getElementById(
service.addElement('text', {}) service.crud.addElement('text', {}) as string
) as TextElementModel; ) as TextElementModel;
expect(secondText.fontSize).toBe(36); expect(secondText.fontSize).toBe(36);
}); });
test('mindmap', () => { test('mindmap', () => {
const id = service.addElement('mindmap', {}); const id = service.crud.addElement('mindmap', {});
assertExists(id);
const mindmap = service.getElementById(id) as MindmapElementModel; const mindmap = service.getElementById(id) as MindmapElementModel;
expect(mindmap.layoutType).toBe(LayoutType.RIGHT); expect(mindmap.layoutType).toBe(LayoutType.RIGHT);
expect(mindmap.style).toBe(MindmapStyle.ONE); expect(mindmap.style).toBe(MindmapStyle.ONE);
service.updateElement(id, { service.crud.updateElement(id, {
layoutType: LayoutType.BALANCE, layoutType: LayoutType.BALANCE,
style: MindmapStyle.THREE, style: MindmapStyle.THREE,
}); });
const id2 = service.addElement('mindmap', {}); const id2 = service.crud.addElement('mindmap', {});
assertExists(id2);
const mindmap2 = service.getElementById(id2) as MindmapElementModel; const mindmap2 = service.getElementById(id2) as MindmapElementModel;
expect(mindmap2.layoutType).toBe(LayoutType.BALANCE); expect(mindmap2.layoutType).toBe(LayoutType.BALANCE);
expect(mindmap2.style).toBe(MindmapStyle.THREE); expect(mindmap2.style).toBe(MindmapStyle.THREE);
@@ -180,27 +198,30 @@ describe('apply last props', () => {
test('edgeless-text', () => { test('edgeless-text', () => {
const surface = getSurfaceBlock(doc); const surface = getSurfaceBlock(doc);
const id = service.addBlock('affine:edgeless-text', {}, surface!.id); const id = service.crud.addBlock('affine:edgeless-text', {}, surface!.id);
assertExists(id);
const text = service.getElementById(id) as EdgelessTextBlockModel; const text = service.getElementById(id) as EdgelessTextBlockModel;
expect(text.color).toBe(DEFAULT_TEXT_COLOR); expect(text.color).toBe(DEFAULT_TEXT_COLOR);
expect(text.fontFamily).toBe(FontFamily.Inter); expect(text.fontFamily).toBe(FontFamily.Inter);
service.updateElement(id, { service.crud.updateElement(id, {
color: LineColor.Green, color: LineColor.Green,
fontFamily: FontFamily.OrelegaOne, fontFamily: FontFamily.OrelegaOne,
}); });
const id2 = service.addBlock('affine:edgeless-text', {}, surface!.id); const id2 = service.crud.addBlock('affine:edgeless-text', {}, surface!.id);
assertExists(id2);
const text2 = service.getElementById(id2) as EdgelessTextBlockModel; const text2 = service.getElementById(id2) as EdgelessTextBlockModel;
expect(text2.color).toBe(LineColor.Green); expect(text2.color).toBe(LineColor.Green);
expect(text2.fontFamily).toBe(FontFamily.OrelegaOne); expect(text2.fontFamily).toBe(FontFamily.OrelegaOne);
}); });
test('note', () => { test('note', () => {
const id = service.addBlock('affine:note', {}, doc.root!.id); const id = service.crud.addBlock('affine:note', {}, doc.root!.id);
assertExists(id);
const note = service.getElementById(id) as NoteBlockModel; const note = service.getElementById(id) as NoteBlockModel;
expect(note.background).toBe(DEFAULT_NOTE_BACKGROUND_COLOR); expect(note.background).toBe(DEFAULT_NOTE_BACKGROUND_COLOR);
expect(note.edgeless.style.shadowType).toBe(DEFAULT_NOTE_SHADOW); expect(note.edgeless.style.shadowType).toBe(DEFAULT_NOTE_SHADOW);
service.updateElement(id, { service.crud.updateElement(id, {
background: NoteBackgroundColor.Purple, background: NoteBackgroundColor.Purple,
edgeless: { edgeless: {
style: { style: {
@@ -209,7 +230,8 @@ describe('apply last props', () => {
}, },
}); });
const id2 = service.addBlock('affine:note', {}, doc.root!.id); const id2 = service.crud.addBlock('affine:note', {}, doc.root!.id);
assertExists(id2);
const note2 = service.getElementById(id2) as NoteBlockModel; const note2 = service.getElementById(id2) as NoteBlockModel;
expect(note2.background).toBe(NoteBackgroundColor.Purple); expect(note2.background).toBe(NoteBackgroundColor.Purple);
expect(note2.edgeless.style.shadowType).toBe(NoteShadow.Film); expect(note2.edgeless.style.shadowType).toBe(NoteShadow.Film);
@@ -217,28 +239,32 @@ describe('apply last props', () => {
test('frame', () => { test('frame', () => {
const surface = getSurfaceBlock(doc); const surface = getSurfaceBlock(doc);
const id = service.addBlock('affine:frame', {}, surface!.id); const id = service.crud.addBlock('affine:frame', {}, surface!.id);
assertExists(id);
const note = service.getElementById(id) as FrameBlockModel; const note = service.getElementById(id) as FrameBlockModel;
expect(note.background).toBe('--affine-palette-transparent'); expect(note.background).toBe('--affine-palette-transparent');
service.updateElement(id, { service.crud.updateElement(id, {
background: FrameBackgroundColor.Purple, background: FrameBackgroundColor.Purple,
}); });
const id2 = service.addBlock('affine:frame', {}, surface!.id); const id2 = service.crud.addBlock('affine:frame', {}, surface!.id);
assertExists(id2);
const frame2 = service.getElementById(id2) as FrameBlockModel; const frame2 = service.getElementById(id2) as FrameBlockModel;
expect(frame2.background).toBe(FrameBackgroundColor.Purple); expect(frame2.background).toBe(FrameBackgroundColor.Purple);
service.updateElement(id, { service.crud.updateElement(id2, {
background: { normal: '#def4e740' }, background: { normal: '#def4e740' },
}); });
const id3 = service.addBlock('affine:frame', {}, surface!.id); const id3 = service.crud.addBlock('affine:frame', {}, surface!.id);
assertExists(id3);
const frame3 = service.getElementById(id3) as FrameBlockModel; const frame3 = service.getElementById(id3) as FrameBlockModel;
expect(frame3.background).toEqual({ normal: '#def4e740' }); expect(frame3.background).toEqual({ normal: '#def4e740' });
service.updateElement(id, { service.crud.updateElement(id3, {
background: { light: '#a381aa23', dark: '#6e907452' }, background: { light: '#a381aa23', dark: '#6e907452' },
}); });
const id4 = service.addBlock('affine:frame', {}, surface!.id); const id4 = service.crud.addBlock('affine:frame', {}, surface!.id);
assertExists(id4);
const frame4 = service.getElementById(id4) as FrameBlockModel; const frame4 = service.getElementById(id4) as FrameBlockModel;
expect(frame4.background).toEqual({ expect(frame4.background).toEqual({
light: '#a381aa23', light: '#a381aa23',

View File

@@ -45,7 +45,7 @@ describe('add new edgeless blocks or canvas elements should update layer automat
test('add note, note, shape sequentially', async () => { test('add note, note, shape sequentially', async () => {
addNote(doc); addNote(doc);
addNote(doc); addNote(doc);
service.addElement('shape', { service.crud.addElement('shape', {
shapeType: 'rect', shapeType: 'rect',
}); });
@@ -56,7 +56,7 @@ describe('add new edgeless blocks or canvas elements should update layer automat
test('add note, shape, note sequentially', async () => { test('add note, shape, note sequentially', async () => {
addNote(doc); addNote(doc);
service.addElement('shape', { service.crud.addElement('shape', {
shapeType: 'rect', shapeType: 'rect',
}); });
addNote(doc); addNote(doc);
@@ -68,7 +68,7 @@ describe('add new edgeless blocks or canvas elements should update layer automat
test('delete element should update layer automatically', () => { test('delete element should update layer automatically', () => {
const id = addNote(doc); const id = addNote(doc);
const canvasElId = service.addElement('shape', { const canvasElId = service.crud.addElement('shape', {
shapeType: 'rect', shapeType: 'rect',
}); });
@@ -76,20 +76,20 @@ test('delete element should update layer automatically', () => {
expect(service.layer.layers.length).toBe(1); expect(service.layer.layers.length).toBe(1);
service.removeElement(canvasElId); service.removeElement(canvasElId!);
expect(service.layer.layers.length).toBe(0); expect(service.layer.layers.length).toBe(0);
}); });
test('change element should update layer automatically', async () => { test('change element should update layer automatically', async () => {
const id = addNote(doc); const id = addNote(doc);
const canvasElId = service.addElement('shape', { const canvasElId = service.crud.addElement('shape', {
shapeType: 'rect', shapeType: 'rect',
}); });
await wait(); await wait();
service.updateElement(id, { service.crud.updateElement(id, {
index: service.layer.getReorderedIndex( index: service.layer.getReorderedIndex(
service.getElementById(id)!, service.getElementById(id)!,
'forward' 'forward'
@@ -99,9 +99,9 @@ test('change element should update layer automatically', async () => {
'block' 'block'
); );
service.updateElement(canvasElId, { service.crud.updateElement(canvasElId!, {
index: service.layer.getReorderedIndex( index: service.layer.getReorderedIndex(
service.getElementById(canvasElId)!, service.getElementById(canvasElId!)!,
'forward' 'forward'
), ),
}); });
@@ -112,7 +112,7 @@ test('change element should update layer automatically', async () => {
test('new added canvas elements should be placed in the topmost canvas layer', async () => { test('new added canvas elements should be placed in the topmost canvas layer', async () => {
addNote(doc); addNote(doc);
service.addElement('shape', { service.crud.addElement('shape', {
shapeType: 'rect', shapeType: 'rect',
}); });
@@ -130,11 +130,11 @@ test("there should be at lease one layer in canvasLayers property even there's n
test('if the topmost layer is canvas layer, the length of canvasLayers array should equal to the counts of canvas layers', () => { test('if the topmost layer is canvas layer, the length of canvasLayers array should equal to the counts of canvas layers', () => {
addNote(doc); addNote(doc);
service.addElement('shape', { service.crud.addElement('shape', {
shapeType: 'rect', shapeType: 'rect',
}); });
addNote(doc); addNote(doc);
service.addElement('shape', { service.crud.addElement('shape', {
shapeType: 'rect', shapeType: 'rect',
}); });
@@ -145,11 +145,11 @@ test('if the topmost layer is canvas layer, the length of canvasLayers array sho
}); });
test('a new layer should be created in canvasLayers prop when the topmost layer is not canvas layer', () => { test('a new layer should be created in canvasLayers prop when the topmost layer is not canvas layer', () => {
service.addElement('shape', { service.crud.addElement('shape', {
shapeType: 'rect', shapeType: 'rect',
}); });
addNote(doc); addNote(doc);
service.addElement('shape', { service.crud.addElement('shape', {
shapeType: 'rect', shapeType: 'rect',
}); });
addNote(doc); addNote(doc);
@@ -162,13 +162,13 @@ test('layer zindex should update correctly when elements changed', async () => {
const noteId = addNote(doc); const noteId = addNote(doc);
const note = service.getElementById(noteId); const note = service.getElementById(noteId);
addNote(doc); addNote(doc);
service.addElement('shape', { service.crud.addElement('shape', {
shapeType: 'rect', shapeType: 'rect',
}); });
const topShapeId = service.addElement('shape', { const topShapeId = service.crud.addElement('shape', {
shapeType: 'rect', shapeType: 'rect',
}); });
const topShape = service.getElementById(topShapeId); const topShape = service.getElementById(topShapeId!)!;
await wait(); await wait();
@@ -183,7 +183,7 @@ test('layer zindex should update correctly when elements changed', async () => {
service.doc.captureSync(); service.doc.captureSync();
service.updateElement(noteId, { service.crud.updateElement(noteId, {
index: service.layer.getReorderedIndex(note!, 'front'), index: service.layer.getReorderedIndex(note!, 'front'),
}); });
await wait(); await wait();
@@ -198,7 +198,7 @@ test('layer zindex should update correctly when elements changed', async () => {
}; };
assert2StepState(); assert2StepState();
service.updateElement(topShapeId, { service.crud.updateElement(topShapeId!, {
index: service.layer.getReorderedIndex(topShape!, 'front'), index: service.layer.getReorderedIndex(topShape!, 'front'),
}); });
await wait(); await wait();
@@ -235,7 +235,7 @@ test('blocks should rerender when their z-index changed', async () => {
await wait(); await wait();
assertBlocksContent(); assertBlocksContent();
service.addElement('shape', { service.crud.addElement('shape', {
shapeType: 'rect', shapeType: 'rect',
index: CommonUtils.generateKeyBetween( index: CommonUtils.generateKeyBetween(
service.getElementById(blocks[1])!.index, service.getElementById(blocks[1])!.index,
@@ -252,19 +252,19 @@ describe('layer reorder functionality', () => {
beforeEach(() => { beforeEach(() => {
ids = [ ids = [
service.addElement('shape', { service.crud.addElement('shape', {
shapeType: 'rect', shapeType: 'rect',
}), })!,
addNote(doc), addNote(doc),
service.addElement('shape', { service.crud.addElement('shape', {
shapeType: 'rect', shapeType: 'rect',
}), })!,
addNote(doc), addNote(doc),
]; ];
}); });
test('forward', async () => { test('forward', async () => {
service.updateElement(ids[0], { service.crud.updateElement(ids[0], {
index: service.layer.getReorderedIndex( index: service.layer.getReorderedIndex(
service.getElementById(ids[0])!, service.getElementById(ids[0])!,
'forward' 'forward'
@@ -284,7 +284,7 @@ describe('layer reorder functionality', () => {
await wait(); await wait();
service.updateElement(ids[1], { service.crud.updateElement(ids[1], {
index: service.layer.getReorderedIndex( index: service.layer.getReorderedIndex(
service.getElementById(ids[1])!, service.getElementById(ids[1])!,
'forward' 'forward'
@@ -304,7 +304,7 @@ describe('layer reorder functionality', () => {
}); });
test('front', async () => { test('front', async () => {
service.updateElement(ids[0], { service.crud.updateElement(ids[0], {
index: service.layer.getReorderedIndex( index: service.layer.getReorderedIndex(
service.getElementById(ids[0])!, service.getElementById(ids[0])!,
'front' 'front'
@@ -319,7 +319,7 @@ describe('layer reorder functionality', () => {
) )
).toBe(3); ).toBe(3);
service.updateElement(ids[1], { service.crud.updateElement(ids[1], {
index: service.layer.getReorderedIndex( index: service.layer.getReorderedIndex(
service.getElementById(ids[1])!, service.getElementById(ids[1])!,
'front' 'front'
@@ -334,7 +334,7 @@ describe('layer reorder functionality', () => {
}); });
test('backward', async () => { test('backward', async () => {
service.updateElement(ids[3], { service.crud.updateElement(ids[3], {
index: service.layer.getReorderedIndex( index: service.layer.getReorderedIndex(
service.getElementById(ids[3])!, service.getElementById(ids[3])!,
'backward' 'backward'
@@ -354,7 +354,7 @@ describe('layer reorder functionality', () => {
await wait(); await wait();
service.updateElement(ids[2], { service.crud.updateElement(ids[2], {
index: service.layer.getReorderedIndex( index: service.layer.getReorderedIndex(
service.getElementById(ids[2])!, service.getElementById(ids[2])!,
'backward' 'backward'
@@ -374,7 +374,7 @@ describe('layer reorder functionality', () => {
}); });
test('back', async () => { test('back', async () => {
service.updateElement(ids[3], { service.crud.updateElement(ids[3], {
index: service.layer.getReorderedIndex( index: service.layer.getReorderedIndex(
service.getElementById(ids[3])!, service.getElementById(ids[3])!,
'back' 'back'
@@ -389,7 +389,7 @@ describe('layer reorder functionality', () => {
await wait(); await wait();
service.updateElement(ids[2], { service.crud.updateElement(ids[2], {
index: service.layer.getReorderedIndex( index: service.layer.getReorderedIndex(
service.getElementById(ids[2])!, service.getElementById(ids[2])!,
'back' 'back'
@@ -412,7 +412,7 @@ describe('group related functionality', () => {
const children = new DocCollection.Y.Map<boolean>(); const children = new DocCollection.Y.Map<boolean>();
childIds.forEach(id => children.set(id, true)); childIds.forEach(id => children.set(id, true));
return service.addElement('group', { return service.crud.addElement('group', {
children, children,
}); });
}; };
@@ -420,17 +420,17 @@ describe('group related functionality', () => {
test("new added group should effect it children's layer", async () => { test("new added group should effect it children's layer", async () => {
const edgeless = getDocRootBlock(doc, editor, 'edgeless'); const edgeless = getDocRootBlock(doc, editor, 'edgeless');
const elements = [ const elements = [
service.addElement('shape', { service.crud.addElement('shape', {
shapeType: 'rect', shapeType: 'rect',
}), })!,
addNote(doc), addNote(doc),
service.addElement('shape', { service.crud.addElement('shape', {
shapeType: 'rect', shapeType: 'rect',
}), })!,
addNote(doc), addNote(doc),
service.addElement('shape', { service.crud.addElement('shape', {
shapeType: 'rect', shapeType: 'rect',
}), })!,
]; ];
await wait(0); await wait(0);
@@ -490,23 +490,23 @@ describe('group related functionality', () => {
test("change group index should update its children's layer", () => { test("change group index should update its children's layer", () => {
const elements = [ const elements = [
service.addElement('shape', { service.crud.addElement('shape', {
shapeType: 'rect', shapeType: 'rect',
}), })!,
addNote(doc), addNote(doc),
service.addElement('shape', { service.crud.addElement('shape', {
shapeType: 'rect', shapeType: 'rect',
}), })!,
addNote(doc), addNote(doc),
service.addElement('shape', { service.crud.addElement('shape', {
shapeType: 'rect', shapeType: 'rect',
}), })!,
]; ];
const groupId = createGroup( const groupId = createGroup(
service, service,
elements.filter((_, idx) => idx !== 1 && idx !== 3) elements.filter((_, idx) => idx !== 1 && idx !== 3)
); )!;
const group = service.getElementById(groupId)!; const group = service.getElementById(groupId)!;
expect(service.layer.layers.length).toBe(2); expect(service.layer.layers.length).toBe(2);
@@ -525,17 +525,17 @@ describe('group related functionality', () => {
test('should keep relative index order of elements after group, ungroup, undo, redo', () => { test('should keep relative index order of elements after group, ungroup, undo, redo', () => {
const edgeless = getDocRootBlock(doc, editor, 'edgeless'); const edgeless = getDocRootBlock(doc, editor, 'edgeless');
const elementIds = [ const elementIds = [
service.addElement('shape', { service.crud.addElement('shape', {
shapeType: 'rect', shapeType: 'rect',
}), })!,
addNote(doc), addNote(doc),
service.addElement('shape', { service.crud.addElement('shape', {
shapeType: 'rect', shapeType: 'rect',
}), })!,
addNote(doc), addNote(doc),
service.addElement('shape', { service.crud.addElement('shape', {
shapeType: 'rect', shapeType: 'rect',
}), })!,
]; ];
service.doc.captureSync(); service.doc.captureSync();
const elements = elementIds.map(id => service.getElementById(id)!); const elements = elementIds.map(id => service.getElementById(id)!);
@@ -549,7 +549,7 @@ describe('group related functionality', () => {
expect(isKeptRelativeOrder()).toBeTruthy(); expect(isKeptRelativeOrder()).toBeTruthy();
const groupId = createGroup(edgeless.service, elementIds); const groupId = createGroup(edgeless.service, elementIds)!;
expect(isKeptRelativeOrder()).toBeTruthy(); expect(isKeptRelativeOrder()).toBeTruthy();
service.ungroup(service.getElementById(groupId) as GroupElementModel); service.ungroup(service.getElementById(groupId) as GroupElementModel);
@@ -577,19 +577,19 @@ describe('compare function', () => {
const children = new DocCollection.Y.Map<boolean>(); const children = new DocCollection.Y.Map<boolean>();
childIds.forEach(id => children.set(id, true)); childIds.forEach(id => children.set(id, true));
return service.addElement('group', { return service.crud.addElement('group', {
children, children,
}); });
}; };
test('compare same element', () => { test('compare same element', () => {
const shapeId = service.addElement('shape', { const shapeId = service.crud.addElement('shape', {
shapeType: 'rect', shapeType: 'rect',
}); })!;
const shapeEl = service.getElementById(shapeId)!; const shapeEl = service.getElementById(shapeId)!;
expect(service.layer.compare(shapeEl, shapeEl)).toBe(SORT_ORDER.SAME); expect(service.layer.compare(shapeEl, shapeEl)).toBe(SORT_ORDER.SAME);
const groupId = createGroup(service, [shapeId]); const groupId = createGroup(service, [shapeId])!;
const groupEl = service.getElementById(groupId)!; const groupEl = service.getElementById(groupId)!;
expect(service.layer.compare(groupEl, groupEl)).toBe(SORT_ORDER.SAME); expect(service.layer.compare(groupEl, groupEl)).toBe(SORT_ORDER.SAME);
@@ -599,13 +599,13 @@ describe('compare function', () => {
}); });
test('compare a group and its child', () => { test('compare a group and its child', () => {
const shapeId = service.addElement('shape', { const shapeId = service.crud.addElement('shape', {
shapeType: 'rect', shapeType: 'rect',
}); })!;
const shapeEl = service.getElementById(shapeId)!; const shapeEl = service.getElementById(shapeId)!;
const noteId = addNote(doc); const noteId = addNote(doc);
const note = service.getElementById(noteId)! as NoteBlockModel; const note = service.getElementById(noteId)! as NoteBlockModel;
const groupId = createGroup(service, [shapeId, noteId]); const groupId = createGroup(service, [shapeId, noteId])!;
const groupEl = service.getElementById(groupId)!; const groupEl = service.getElementById(groupId)!;
expect(service.layer.compare(groupEl, shapeEl)).toBe(SORT_ORDER.BEFORE); expect(service.layer.compare(groupEl, shapeEl)).toBe(SORT_ORDER.BEFORE);
@@ -615,13 +615,13 @@ describe('compare function', () => {
}); });
test('compare two different elements', () => { test('compare two different elements', () => {
const shape1Id = service.addElement('shape', { const shape1Id = service.crud.addElement('shape', {
shapeType: 'rect', shapeType: 'rect',
}); })!;
const shape1 = service.getElementById(shape1Id)!; const shape1 = service.getElementById(shape1Id)!;
const shape2Id = service.addElement('shape', { const shape2Id = service.crud.addElement('shape', {
shapeType: 'rect', shapeType: 'rect',
}); })!;
const shape2 = service.getElementById(shape2Id)!; const shape2 = service.getElementById(shape2Id)!;
const note1Id = addNote(doc); const note1Id = addNote(doc);
@@ -643,12 +643,12 @@ describe('compare function', () => {
}); });
test('compare nested elements', () => { test('compare nested elements', () => {
const shape1Id = service.addElement('shape', { const shape1Id = service.crud.addElement('shape', {
shapeType: 'rect', shapeType: 'rect',
}); })!;
const shape2Id = service.addElement('shape', { const shape2Id = service.crud.addElement('shape', {
shapeType: 'rect', shapeType: 'rect',
}); })!;
const note1Id = addNote(doc); const note1Id = addNote(doc);
const note2Id = addNote(doc); const note2Id = addNote(doc);
const group1Id = createGroup(service, [ const group1Id = createGroup(service, [
@@ -656,8 +656,8 @@ describe('compare function', () => {
shape2Id, shape2Id,
note1Id, note1Id,
note2Id, note2Id,
]); ])!;
const group2Id = createGroup(service, [group1Id]); const group2Id = createGroup(service, [group1Id])!;
const shape1 = service.getElementById(shape1Id)!; const shape1 = service.getElementById(shape1Id)!;
const shape2 = service.getElementById(shape2Id)!; const shape2 = service.getElementById(shape2Id)!;
@@ -688,24 +688,24 @@ describe('compare function', () => {
}); });
test('compare two nested elements', () => { test('compare two nested elements', () => {
const groupAShapeId = service.addElement('shape', { const groupAShapeId = service.crud.addElement('shape', {
shapeType: 'rect', shapeType: 'rect',
}); })!;
const groupANoteId = addNote(doc); const groupANoteId = addNote(doc);
const groupAId = createGroup(service, [ const groupAId = createGroup(service, [
createGroup(service, [groupAShapeId, groupANoteId]), createGroup(service, [groupAShapeId, groupANoteId])!,
]); ])!;
const groupAShape = service.getElementById(groupAShapeId)!; const groupAShape = service.getElementById(groupAShapeId)!;
const groupANote = service.getElementById(groupANoteId)!; const groupANote = service.getElementById(groupANoteId)!;
const groupA = service.getElementById(groupAId)!; const groupA = service.getElementById(groupAId)!;
const groupBShapeId = service.addElement('shape', { const groupBShapeId = service.crud.addElement('shape', {
shapeType: 'rect', shapeType: 'rect',
}); })!;
const groupBNoteId = addNote(doc); const groupBNoteId = addNote(doc);
const groupBId = createGroup(service, [ const groupBId = createGroup(service, [
createGroup(service, [groupBShapeId, groupBNoteId]), createGroup(service, [groupBShapeId, groupBNoteId])!,
]); ])!;
const groupBShape = service.getElementById(groupBShapeId)!; const groupBShape = service.getElementById(groupBShapeId)!;
const groupBNote = service.getElementById(groupBNoteId)!; const groupBNote = service.getElementById(groupBNoteId)!;
const groupB = service.getElementById(groupBId)!; const groupB = service.getElementById(groupBId)!;
@@ -759,17 +759,17 @@ describe('compare function', () => {
test('indexed canvas should be inserted into edgeless portal when switch to edgeless mode', async () => { test('indexed canvas should be inserted into edgeless portal when switch to edgeless mode', async () => {
let surface = getSurface(doc, editor); let surface = getSurface(doc, editor);
service.addElement('shape', { service.crud.addElement('shape', {
shapeType: 'rect', shapeType: 'rect',
}); })!;
addNote(doc); addNote(doc);
await wait(); await wait();
service.addElement('shape', { service.crud.addElement('shape', {
shapeType: 'rect', shapeType: 'rect',
}); })!;
editor.mode = 'page'; editor.mode = 'page';
await wait(); await wait();
@@ -855,9 +855,9 @@ describe('index generator', () => {
let preinsertedNote: NoteBlockModel; let preinsertedNote: NoteBlockModel;
beforeEach(() => { beforeEach(() => {
const shapeId = service.addElement('shape', { const shapeId = service.crud.addElement('shape', {
shapeType: 'rect', shapeType: 'rect',
}); })!;
const noteId = addNote(doc); const noteId = addNote(doc);
preinsertedShape = service.getElementById( preinsertedShape = service.getElementById(

View File

@@ -29,20 +29,20 @@ describe('basic', () => {
noteAId = addNote(doc, { noteAId = addNote(doc, {
index: service.generateIndex(), index: service.generateIndex(),
}); });
shapeAId = service.addElement('shape', { shapeAId = service.crud.addElement('shape', {
type: 'rect', type: 'rect',
xywh: '[0, 0, 100, 100]', xywh: '[0, 0, 100, 100]',
index: service.generateIndex(), index: service.generateIndex(),
}); })!;
noteBId = addNote(doc, { noteBId = addNote(doc, {
index: service.generateIndex(), index: service.generateIndex(),
}); });
shapeBId = service.addElement('shape', { shapeBId = service.crud.addElement('shape', {
type: 'rect', type: 'rect',
xywh: '[100, 0, 100, 100]', xywh: '[100, 0, 100, 100]',
index: service.generateIndex(), index: service.generateIndex(),
}); })!;
frameId = service.addBlock( frameId = service.crud.addBlock(
'affine:frame', 'affine:frame',
{ {
xywh: '[0, 0, 800, 200]', xywh: '[0, 0, 800, 200]',
@@ -122,7 +122,7 @@ describe('basic', () => {
}); });
test('content in group should be rendered in the correct order', async () => { test('content in group should be rendered in the correct order', async () => {
const groupId = service.addElement('group', { const groupId = service.crud.addElement('group', {
children: { children: {
[shapeAId]: true, [shapeAId]: true,
[shapeBId]: true, [shapeBId]: true,
@@ -186,14 +186,14 @@ describe('basic', () => {
}); });
test('group should be rendered in surface-ref viewport', async () => { test('group should be rendered in surface-ref viewport', async () => {
const groupId = service.addElement('group', { const groupId = service.crud.addElement('group', {
children: { children: {
[shapeAId]: true, [shapeAId]: true,
[shapeBId]: true, [shapeBId]: true,
[noteAId]: true, [noteAId]: true,
[noteBId]: true, [noteBId]: true,
}, },
}); })!;
const surfaceRefId = doc.addBlock( const surfaceRefId = doc.addBlock(
'affine:surface-ref', 'affine:surface-ref',
{ {
@@ -247,7 +247,7 @@ describe('basic', () => {
}); });
test('view in edgeless mode button', async () => { test('view in edgeless mode button', async () => {
const groupId = service.addElement('group', { const groupId = service.crud.addElement('group', {
children: { children: {
[shapeAId]: true, [shapeAId]: true,
[shapeBId]: true, [shapeBId]: true,

View File

@@ -26,7 +26,7 @@ describe('default tool', () => {
}); });
test('element click selection', async () => { test('element click selection', async () => {
const id = service.addElement('shape', { const id = service.crud.addElement('shape', {
shapeType: 'rect', shapeType: 'rect',
xywh: '[0,0,100,100]', xywh: '[0,0,100,100]',
fillColor: 'red', fillColor: 'red',
@@ -47,7 +47,7 @@ describe('default tool', () => {
}); });
test('element drag moving', async () => { test('element drag moving', async () => {
const id = edgeless.service.addElement('shape', { const id = edgeless.service.crud.addElement('shape', {
shapeType: 'rect', shapeType: 'rect',
xywh: '[0,0,100,100]', xywh: '[0,0,100,100]',
fillColor: 'red', fillColor: 'red',
@@ -64,7 +64,7 @@ describe('default tool', () => {
drag(edgeless.host, { x: 0, y: 50 }, { x: 0, y: 150 }); drag(edgeless.host, { x: 0, y: 50 }, { x: 0, y: 150 });
await wait(); await wait();
const element = service.getElementById(id)!; const element = service.getElementById(id!)!;
expect(element.xywh).toEqual(`[0,100,100,100]`); expect(element.xywh).toEqual(`[0,100,100,100]`);
}); });

View File

@@ -508,7 +508,7 @@ const CREATE_AS_LINKED_DOC = {
y = viewportCenter.y - height / 2; y = viewportCenter.y - height / 2;
} }
service.addBlock( service.crud.addBlock(
'affine:embed-linked-doc', 'affine:embed-linked-doc',
{ {
xywh: `[${x}, ${y}, ${width}, ${height}]`, xywh: `[${x}, ${y}, ${width}, ${height}]`,

View File

@@ -174,7 +174,7 @@ export class AIChatBlockPeekView extends LitElement {
const edgelessService = this._rootService as EdgelessRootService; const edgelessService = this._rootService as EdgelessRootService;
const bound = calcChildBound(this.parentModel, edgelessService); const bound = calcChildBound(this.parentModel, edgelessService);
const aiChatBlockId = edgelessService.addBlock( const aiChatBlockId = edgelessService.crud.addBlock(
'affine:embed-ai-chat' as keyof BlockSuite.BlockModels, 'affine:embed-ai-chat' as keyof BlockSuite.BlockModels,
{ {
xywh: bound.serialize(), xywh: bound.serialize(),
@@ -193,7 +193,7 @@ export class AIChatBlockPeekView extends LitElement {
this.updateContext({ currentChatBlockId: aiChatBlockId }); this.updateContext({ currentChatBlockId: aiChatBlockId });
// Connect the parent chat block to the AI chat block // Connect the parent chat block to the AI chat block
edgelessService.addElement(CanvasElementType.CONNECTOR, { edgelessService.crud.addElement(CanvasElementType.CONNECTOR, {
mode: ConnectorMode.Curve, mode: ConnectorMode.Curve,
controllers: [], controllers: [],
source: { id: this.parentChatBlockId }, source: { id: this.parentChatBlockId },

View File

@@ -530,7 +530,7 @@ export function patchEdgelessClipboard() {
rootDocId, rootDocId,
rootWorkspaceId, rootWorkspaceId,
} = block.props; } = block.props;
const blockId = component.service.addBlock( const blockId = component.service.crud.addBlock(
AIChatBlockFlavour, AIChatBlockFlavour,
{ {
xywh, xywh,

View File

@@ -361,7 +361,7 @@ export const ShapeSettings = () => {
const { shapeType, radius } = shape; const { shapeType, radius } = shape;
const shapeName = getShapeName(shapeType, radius); const shapeName = getShapeName(shapeType, radius);
const props = editorSetting.get(`shape:${shapeName}`); const props = editorSetting.get(`shape:${shapeName}`);
edgelessService.updateElement(shape.id, props); edgelessService.crud.updateElement(shape.id, props);
}); });
doc.awarenessStore.setReadonly(doc.blockCollection, true); doc.awarenessStore.setReadonly(doc.blockCollection, true);
}, },

View File

@@ -79,7 +79,7 @@ export const EdgelessSnapshot = (props: Props) => {
const props = editorSetting.get(keyName) as any; const props = editorSetting.get(keyName) as any;
doc.awarenessStore.setReadonly(doc.blockCollection, false); doc.awarenessStore.setReadonly(doc.blockCollection, false);
elements.forEach(element => { elements.forEach(element => {
edgelessService.updateElement(element.id, props); edgelessService.crud.updateElement(element.id, props);
}); });
doc.awarenessStore.setReadonly(doc.blockCollection, true); doc.awarenessStore.setReadonly(doc.blockCollection, true);
}, [editorSetting, getElements, keyName]); }, [editorSetting, getElements, keyName]);