Skip to content

Commit

Permalink
feat(edgeless): support top-level image block inside surface (toevery…
Browse files Browse the repository at this point in the history
…thing#4894)

Co-authored-by: Yifeng Wang <doodlewind@toeverything.info>
  • Loading branch information
regischen and doodlewind authored Oct 11, 2023
1 parent b940b9e commit 763786d
Show file tree
Hide file tree
Showing 54 changed files with 1,005 additions and 694 deletions.
53 changes: 44 additions & 9 deletions packages/blocks/src/__internal__/clipboard/edgeless-clipboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,20 @@ import { assertExists, groupBy } from '@blocksuite/global/utils';
import { type Page, Workspace } from '@blocksuite/store';

import type { FrameBlockModel } from '../../frame-block/index.js';
import type { NoteBlockModel } from '../../index.js';
import type { ImageBlockModel } from '../../image-block/index.js';
import type { NoteBlockModel } from '../../note-block/index.js';
import type { EdgelessPageBlockComponent } from '../../page-block/edgeless/edgeless-page-block.js';
import type { Selectable } from '../../page-block/edgeless/services/tools-manager.js';
import { deleteElements } from '../../page-block/edgeless/utils/crud.js';
import {
isFrameBlock,
isImageBlock,
isNoteBlock,
isPhasorElementWithText,
isTopLevelBlock,
} from '../../page-block/edgeless/utils/query.js';
import { getSelectedContentModels } from '../../page-block/utils/selection.js';
import { EdgelessBlockType } from '../../surface-block/edgeless-types.js';
import {
Bound,
compare,
Expand All @@ -35,6 +38,7 @@ import {
import { getService } from '../service/index.js';
import { addSerializedBlocks } from '../service/json2block.js';
import type { FrameBlockService } from '../service/legacy-services/frame-service.js';
import type { ImageBlockService } from '../service/legacy-services/image-service.js';
import type { Clipboard } from './type.js';
import {
clipboardData2Blocks,
Expand Down Expand Up @@ -93,6 +97,9 @@ async function prepareClipboardData(selectedAll: Selectable[]) {
} else if (isFrameBlock(selected)) {
const service = getService(selected.flavour) as FrameBlockService;
return { ...service.block2Json(selected) };
} else if (isImageBlock(selected)) {
const service = getService(selected.flavour) as ImageBlockService;
return { ...service.block2Json(selected, []) };
} else if (selected instanceof ConnectorElement) {
return prepareConnectorClipboardData(selected, selectedAll);
} else {
Expand Down Expand Up @@ -299,19 +306,20 @@ export class EdgelessClipboard implements Clipboard {
notes: SerializedBlock[],
oldToNewIdMap: Map<string, string>
) {
const { surface } = this;
const noteIds = await Promise.all(
notes.map(async ({ id, xywh, children, background }) => {
assertExists(xywh);

const noteId = this._page.addBlock(
'affine:note',
const noteId = this.surface.addElement(
EdgelessBlockType.NOTE,
{
xywh,
background,
},
this._page.root?.id
);
const note = this._page.getBlockById(noteId);
const note = surface.pickById(noteId) as NoteBlockModel;
if (id) oldToNewIdMap.set(id, noteId);
assertExists(note);

Expand All @@ -325,8 +333,8 @@ export class EdgelessClipboard implements Clipboard {
private async _createFrameBlocks(frames: SerializedBlock[]) {
const frameIds = await Promise.all(
frames.map(async ({ xywh, title, background }) => {
const frameId = this._page.addBlock(
'affine:frame',
const frameId = this.surface.addElement(
EdgelessBlockType.FRAME,
{
xywh,
background,
Expand All @@ -340,6 +348,24 @@ export class EdgelessClipboard implements Clipboard {
return frameIds;
}

private async _createImageBlocks(images: SerializedBlock[]) {
const imageIds = await Promise.all(
images.map(async ({ xywh, sourceId, rotate }) => {
const imageId = this.surface.addElement(
EdgelessBlockType.IMAGE,
{
xywh,
sourceId,
rotate,
},
this.surface.model.id
);
return imageId;
})
);
return imageIds;
}

private _getOldCommonBound(
phasorElements: PhasorElement[],
blocks: TopLevelBlockModel[]
Expand Down Expand Up @@ -391,10 +417,13 @@ export class EdgelessClipboard implements Clipboard {
? 'notes'
: isFrameBlock(data as unknown as Selectable)
? 'frames'
: isImageBlock(data as unknown as Selectable)
? 'images'
: 'elements'
) as unknown as {
frames: SerializedBlock[];
notes?: SerializedBlock[];
images?: SerializedBlock[];
elements?: { type: PhasorElement['type'] }[];
};

Expand All @@ -407,6 +436,7 @@ export class EdgelessClipboard implements Clipboard {
oldIdToNewIdMap
);
const frameIds = await this._createFrameBlocks(groupedByType.frames ?? []);
const imageIds = await this._createImageBlocks(groupedByType.images ?? []);

const notes = noteIds.map(id =>
this._page.getBlockById(id)
Expand All @@ -416,6 +446,10 @@ export class EdgelessClipboard implements Clipboard {
this._page.getBlockById(id)
) as FrameBlockModel[];

const images = imageIds.map(id =>
this.surface.pickById(id)
) as ImageBlockModel[];

const elements = this._createPhasorElements(
groupedByType.elements || [],
oldIdToNewIdMap
Expand All @@ -429,6 +463,7 @@ export class EdgelessClipboard implements Clipboard {
const oldCommonBound = this._getOldCommonBound(elements, [
...notes,
...frames,
...images,
]);
const pasteX = modelX - oldCommonBound.w / 2;
const pasteY = modelY - oldCommonBound.h / 2;
Expand All @@ -450,22 +485,22 @@ export class EdgelessClipboard implements Clipboard {
}
});

[...notes, ...frames].forEach(block => {
[...notes, ...frames, ...images].forEach(block => {
const [x, y, w, h] = deserializeXYWH(block.xywh);
const newBound = new Bound(
pasteX + x - oldCommonBound.x,
pasteY + y - oldCommonBound.y,
w,
h
);
this._page.updateBlock(block, {
this.surface.updateElement(block.id, {
xywh: serializeXYWH(newBound.x, newBound.y, newBound.w, newBound.h),
});
});

this._emitSelectionChangeAfterPaste(
elements.map(ele => ele.id),
[...noteIds, ...frameIds]
[...noteIds, ...frameIds, ...imageIds]
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ export class ImageBlockService extends BaseService<ImageBlockModel> {
height: block.height,
caption: block.caption,
flavour: block.flavour,
xywh: block.xywh,
rotate: block.rotate,
children,
};
}
Expand Down
2 changes: 1 addition & 1 deletion packages/blocks/src/__internal__/utils/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,7 @@ function isDatabase({ tagName }: Element) {
* Returns `true` if element is edgeless child note.
*/
export function isEdgelessChildNote({ classList }: Element) {
return classList.contains('affine-edgeless-child-note');
return classList.contains('edgeless-block-portal-note');
}

/**
Expand Down
11 changes: 8 additions & 3 deletions packages/blocks/src/__internal__/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { DataViewDataType } from '../../database-block/common/data-view.js'
import type { Cell } from '../../database-block/index.js';
import type { Column } from '../../database-block/table/types.js';
import type { FrameBlockModel } from '../../frame-block/index.js';
import type { ImageBlockModel } from '../../image-block/index.js';
import type { NoteBlockModel } from '../../note-block/index.js';
import { type ShapeStyle } from '../../surface-block/consts.js';
import {
Expand Down Expand Up @@ -123,16 +124,19 @@ export type AbstractEditor = {
export type ExtendedModel = BaseBlockModel & Record<string, any>;

// blocks that would only appear under the edgeless container root
export type TopLevelBlockModel = NoteBlockModel | FrameBlockModel;
export type TopLevelBlockModel =
| NoteBlockModel
| FrameBlockModel
| ImageBlockModel;

export type EdgelessElement = NoteBlockModel | FrameBlockModel | PhasorElement;
export type EdgelessElement = TopLevelBlockModel | PhasorElement;

export type Alignable = EdgelessElement;

export type Erasable = EdgelessElement;

export type Connectable =
| NoteBlockModel
| Exclude<TopLevelBlockModel, FrameBlockModel>
| Exclude<PhasorElement, ConnectorElement | BrushElement>;

export type DefaultTool = {
Expand Down Expand Up @@ -267,6 +271,7 @@ export type SerializedBlock = {
crawled?: boolean;
background?: string;
bookmarkTitle?: string;
rotate?: number;
};

export type EmbedBlockDoubleClickData = {
Expand Down
2 changes: 1 addition & 1 deletion packages/blocks/src/attachment-block/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export async function turnIntoEmbedView(model: AttachmentBlockModel) {
const imageConvertData = model.sourceId
? getImageData(model.sourceId)
: undefined;
const imageProp: ImageBlockProps = {
const imageProp: Partial<ImageBlockProps> = {
sourceId,
caption: model.caption,
...imageConvertData,
Expand Down
21 changes: 8 additions & 13 deletions packages/blocks/src/components/file-drop-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ import {
matchFlavours,
Point,
} from '../__internal__/index.js';
import type { ImageBlockModel } from '../image-block/index.js';
import type {
DocPageBlockComponent,
EdgelessPageBlockComponent,
ImageBlockModel,
} from '../index.js';
} from '../page-block/index.js';
import { Vec } from '../surface-block/index.js';
import type { DragIndicator } from './index.js';

export type GetPageInfo = () => {
Expand Down Expand Up @@ -147,7 +148,7 @@ export class FileDropManager {
type = 'after';
}

let noteId: string | undefined;
let selectedId: string | undefined;
let focusId: string | undefined;

page.captureSync();
Expand All @@ -172,21 +173,15 @@ export class FileDropManager {
for (; i < len; i++) {
const model = models[i];
if (model.flavour === 'affine:image') {
const note = (edgelessBlockEle as EdgelessPageBlockComponent).addImage(
selectedId = (edgelessBlockEle as EdgelessPageBlockComponent).addImage(
model as ImageBlockModel,
point
Vec.toVec(point)
);
noteId = note.noteId;
}
}
if (!noteId || !focusId) return;
if (!selectedId || !focusId) return;

(edgelessBlockEle as EdgelessPageBlockComponent).setSelection(
noteId,
true,
focusId,
point
);
edgelessBlockEle.setSelection(selectedId, true, focusId, point);
}

findFileHandler(file: File): ImportHandler | undefined {
Expand Down
16 changes: 14 additions & 2 deletions packages/blocks/src/frame-block/frame-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import { BaseBlockModel, defineBlockSchema } from '@blocksuite/store';

import { BLOCK_ID_ATTR } from '../__internal__/consts.js';
import type { SurfaceBlockComponent } from '../index.js';
import { FRAME_BATCH } from '../surface-block/batch.js';
import type { EdgelessBlockType } from '../surface-block/edgeless-types.js';
import type {
EdgelessElementUtils,
HitTestOptions,
IEdgelessElement,
} from '../surface-block/elements/edgeless-element.js';
import {
Bound,
Expand Down Expand Up @@ -44,12 +46,22 @@ type Props = {

export class FrameBlockModel
extends BaseBlockModel<Props>
implements EdgelessElementUtils
implements IEdgelessElement
{
override flavour!: EdgelessBlockType.FRAME;

get connectable() {
return false;
}

get batch() {
return FRAME_BATCH;
}

get rotate() {
return 0;
}

containedByBounds(bound: Bound): boolean {
return bound.contains(Bound.deserialize(this.xywh));
}
Expand Down
Loading

1 comment on commit 763786d

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Size Report

Bundles

Entry Size Gzip Brotli
examples/basic 11 MB (+31.4 kB) 2.18 MB (+4.7 kB) 1.35 MB (+3.28 kB)

Packages

Name Size Gzip Brotli
blocks 1.51 MB (+4.15 kB) 378 kB (+823 B) 286 kB (+611 B)
editor 8.85 kB 3.33 kB 2.92 kB
store 60.3 kB (-67 B) 17.4 kB (-9 B) 15.6 kB (+10 B)
virgo 31.3 kB 8.8 kB 7.88 kB

Please sign in to comment.