Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(edgeless): support consistent dragging behavior #5613

Merged
merged 3 commits into from
Dec 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions packages/blocks/src/_common/components/file-drop-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ export type onDropProps = {

export type FileDropOptions = {
flavour: string;
maxFileSize?: number;
onDrop?: ({ files, targetModel, place, point }: onDropProps) => void;
};

Expand Down Expand Up @@ -81,10 +80,6 @@ export class FileDropManager {
return targetModel;
}

get maxFileSize(): number {
return this._fileDropOptions.maxFileSize ?? 10 * 1000 * 1000; // default to 10MB
}

onDragOver = (event: DragEvent) => {
event.preventDefault();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ import {
focusBlockByModel,
focusTitle,
} from '../../../_common/utils/selection.js';
import { type ListBlockModel, type PageBlockModel } from '../../../models.js';
import type { ListBlockModel } from '../../../list-block/index.js';
import type { PageBlockModel } from '../../../page-block/index.js';
import type { ExtendedModel } from '../../types.js';

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,39 @@
import type { BlockService } from '@blocksuite/block-std';
import { assertExists } from '@blocksuite/global/utils';
import { BlockElement } from '@blocksuite/lit';
import type { BaseBlockModel } from '@blocksuite/store';
import type { TemplateResult } from 'lit';
import { html } from 'lit';
import { html, render } from 'lit';
import { styleMap } from 'lit/directives/style-map.js';

import type { DragHandleOption } from '../../page-block/widgets/drag-handle/config.js';
import { AffineDragHandleWidget } from '../../page-block/widgets/drag-handle/drag-handle.js';
import {
captureEventTarget,
convertDragPreviewDocToEdgeless,
convertDragPreviewEdgelessToDoc,
} from '../../page-block/widgets/drag-handle/utils.js';
import { Bound } from '../../surface-block/index.js';
import type { EdgelessSelectableProps } from '../edgeless/mixin/index.js';
import { type BlockModels, matchFlavours } from '../utils/index.js';

export class EmbedBlockElement<
Model extends
BaseBlockModel<EdgelessSelectableProps> = BaseBlockModel<EdgelessSelectableProps>,
Service extends BlockService = BlockService,
WidgetName extends string = string,
> extends BlockElement<Model, Service, WidgetName> {
protected get _isInSurface() {
const parent = this.host.querySelector('affine-edgeless-page');
return !!parent;
protected _isInSurface = false;

protected _width = 400;
protected _height = 200;

get isInSurface() {
return this._isInSurface;
}

get surface() {
if (!this._isInSurface) return null;

return this.host.querySelector('affine-surface');
}

Expand All @@ -48,4 +60,98 @@ export class EmbedBlockElement<
</div>
`;
};

private _dragHandleOption: DragHandleOption = {
flavour: /affine:embed-*/,
edgeless: true,
onDragStart: ({ state, startDragging, anchorBlockPath }) => {
if (!anchorBlockPath) return false;
const anchorComponent = this.std.view.viewFromPath(
'block',
anchorBlockPath
);
if (
!anchorComponent ||
!matchFlavours(anchorComponent.model, [
this.flavour as keyof BlockModels,
])
)
return false;

const blockComponent = anchorComponent as this;
const isInSurface = blockComponent.isInSurface;
if (!isInSurface) {
this.host.selection.setGroup('block', [
this.host.selection.getInstance('block', {
path: blockComponent.path,
}),
]);
startDragging([blockComponent], state);
return true;
}

const element = captureEventTarget(state.raw.target);
const insideDragHandle = !!element?.closest('affine-drag-handle-widget');
if (!insideDragHandle) return false;

const embedPortal = blockComponent.closest(
'.edgeless-block-portal-embed'
);
assertExists(embedPortal);
const dragPreviewEl = embedPortal.cloneNode() as HTMLElement;
dragPreviewEl.style.transform = '';
render(
blockComponent.host.renderModel(blockComponent.model),
dragPreviewEl
);

startDragging([blockComponent], state, dragPreviewEl);
return true;
},
onDragEnd: props => {
const { state, draggingElements } = props;
if (
draggingElements.length !== 1 ||
!matchFlavours(draggingElements[0].model, [
this.flavour as keyof BlockModels,
])
)
return false;

const blockComponent = draggingElements[0] as this;
const isInSurface = blockComponent.isInSurface;
const target = captureEventTarget(state.raw.target);
const isTargetEdgelessContainer =
target?.classList.contains('edgeless') &&
target?.classList.contains('affine-block-children-container');

if (isInSurface) {
return convertDragPreviewEdgelessToDoc({
blockComponent,
...props,
});
} else if (isTargetEdgelessContainer) {
return convertDragPreviewDocToEdgeless({
blockComponent,
cssSelector: '.embed-block-container',
width: this._width,
height: this._height,
...props,
});
}

return false;
},
};

override connectedCallback() {
super.connectedCallback();

const parent = this.host.page.getParent(this.model);
this._isInSurface = parent?.flavour === 'affine:surface';

this.disposables.add(
AffineDragHandleWidget.registerOption(this._dragHandleOption)
);
}
}
14 changes: 14 additions & 0 deletions packages/blocks/src/_common/utils/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,10 @@ function isEdgelessChildNote({ classList }: Element) {
return classList.contains('edgeless-block-portal-note');
}

function isEdgelessChildImage({ classList }: Element) {
return classList.contains('edgeless-block-portal-image');
}

/**
* Returns the closest block element by a point in the rect.
*
Expand Down Expand Up @@ -701,6 +705,16 @@ export function getHoveringNote(point: Point) {
);
}

/**
* Get hovering top level image with given a point in edgeless mode.
*/
export function getHoveringImage(point: Point) {
return (
document.elementsFromPoint(point.x, point.y).find(isEdgelessChildImage) ||
null
);
}

/**
* Gets the table of the database.
*/
Expand Down
2 changes: 1 addition & 1 deletion packages/blocks/src/_legacy/clipboard/utils/commons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ async function generateClipboardInfo(
childrenJson.push(json);
}

const service = await getService(model.flavour);
const service = getService(model.flavour);

const html = await service.block2html(model, {
childText: childrenHtml.join(''),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export class BookmarkBlockService extends BaseService<BookmarkBlockModel> {
</figure>
`;
}

override block2Text(block: BookmarkBlockModel): string {
return block.url;
}
Expand Down
4 changes: 2 additions & 2 deletions packages/blocks/src/attachment-block/attachment-block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,8 @@ export class AttachmentBlockComponent extends BlockElement<AttachmentBlockModel>
this._disposables.add(
AffineDragHandleWidget.registerOption({
flavour: AttachmentBlockSchema.model.flavour,
onDragStart: (state, startDragging) => {
// Check if start dragging from the image block
onDragStart: ({ state, startDragging }) => {
// Check if start dragging from the attachment block
const target = captureEventTarget(state.raw.target);
const attachmentBlock = target?.closest('affine-attachment');
if (!attachmentBlock) return false;
Expand Down
19 changes: 1 addition & 18 deletions packages/blocks/src/attachment-block/attachment-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import {
FileDropManager,
type FileDropOptions,
} from '../_common/components/file-drop-manager.js';
import { toast } from '../_common/components/toast.js';
import { humanFileSize } from '../_common/utils/math.js';
import { matchFlavours } from '../_common/utils/model.js';
import type { AttachmentBlockModel } from './attachment-model.js';
import { addSiblingAttachmentBlock } from './utils.js';
Expand All @@ -15,7 +13,6 @@ export class AttachmentService extends BlockService<AttachmentBlockModel> {

private _fileDropOptions: FileDropOptions = {
flavour: this.flavour,
maxFileSize: this.maxFileSize,
onDrop: async ({ files, targetModel, place }) => {
if (!files.length || !targetModel) return false;
if (matchFlavours(targetModel, ['affine:surface'])) return false;
Expand All @@ -25,22 +22,8 @@ export class AttachmentService extends BlockService<AttachmentBlockModel> {
file => !file.type.startsWith('image/')
);

const isSizeExceeded = attachmentFiles.some(
file => file.size > this.maxFileSize
);
if (isSizeExceeded) {
toast(
`You can only upload files less than ${humanFileSize(
this.maxFileSize,
true,
0
)}`
);
return true;
}

attachmentFiles.forEach(file =>
addSiblingAttachmentBlock(file, targetModel, this.maxFileSize, place)
addSiblingAttachmentBlock(file, this.maxFileSize, targetModel, place)
);
return true;
},
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 @@ -169,8 +169,8 @@ export async function uploadBlobForAttachment(
*/
export async function addSiblingAttachmentBlock(
file: File,
model: BaseBlockModel,
maxFileSize: number,
model: BaseBlockModel,
place: 'before' | 'after' = 'after'
): Promise<string | null> {
if (file.size > maxFileSize) {
Expand Down
Loading
Loading