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(blocks): add page block service #3232

Merged
merged 5 commits into from
Jun 28, 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
18 changes: 11 additions & 7 deletions packages/block-std/src/event/pointer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { assertExists } from '@blocksuite/global/utils';

import { UIEventStateContext } from './base.js';
import { UIEventState, UIEventStateContext } from './base.js';
import type { UIEventDispatcher } from './dispatcher.js';
import { PointerEventState } from './state.js';
import { isFarEnough } from './utils.js';
Expand Down Expand Up @@ -45,6 +45,10 @@ export class PointerControl {
this._dragging = false;
};

private _createContext(event: Event, pointerState: PointerEventState) {
return UIEventStateContext.from(new UIEventState(event), pointerState);
}

private _down = (event: PointerEvent) => {
if (
this._lastPointerDownEvent &&
Expand Down Expand Up @@ -72,7 +76,7 @@ export class PointerControl {

this._dispatcher.run(
'pointerDown',
UIEventStateContext.from(pointerEventState)
this._createContext(event, pointerEventState)
);

this._dispatcher.disposables.addFromEvent(
Expand All @@ -91,7 +95,7 @@ export class PointerControl {
startY: this._startY,
last: this._lastDragState,
});
const context = UIEventStateContext.from(pointerEventState);
const context = this._createContext(event, pointerEventState);

const run = () => {
if (this._dragging) {
Expand Down Expand Up @@ -132,12 +136,12 @@ export class PointerControl {
this._dragging = true;
this._dispatcher.run(
'dragStart',
UIEventStateContext.from(this._startDragState)
this._createContext(event, this._startDragState)
);
}

if (this._dragging) {
this._dispatcher.run('dragMove', UIEventStateContext.from(state));
this._dispatcher.run('dragMove', this._createContext(event, state));
}
};

Expand All @@ -150,7 +154,7 @@ export class PointerControl {
last: this._lastDragState,
});

this._dispatcher.run('pointerMove', UIEventStateContext.from(state));
this._dispatcher.run('pointerMove', this._createContext(event, state));
};

private _out = (event: PointerEvent) => {
Expand All @@ -162,6 +166,6 @@ export class PointerControl {
last: null,
});

this._dispatcher.run('pointerOut', UIEventStateContext.from(state));
this._dispatcher.run('pointerOut', this._createContext(event, state));
};
}
17 changes: 16 additions & 1 deletion packages/block-std/src/service/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { BaseBlockModel } from '@blocksuite/store';
import { DisposableGroup } from '@blocksuite/store';

import type { UIEventDispatcher } from '../event/index.js';
import type { EventName, UIEventHandler } from '../event/index.js';

export interface BlockServiceOptions {
// TODO: add these
Expand All @@ -19,11 +20,25 @@ export class BlockService<Model extends BaseBlockModel = BaseBlockModel> {
this.uiEventDispatcher = options.uiEventDispatcher;
}

// life cycle start
dispose() {
this.disposables.dispose();
}

// TODO: life cycle methods
mounted() {
// do nothing
}

unmounted() {
// do nothing
}
// life cycle end

// event handlers start
handleEvent(name: EventName, fn: UIEventHandler) {
this.disposables.add(this.uiEventDispatcher.add(name, fn));
}
// event handlers end
}

export type BlockServiceConstructor = new (
Expand Down
22 changes: 18 additions & 4 deletions packages/block-std/src/store/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export class BlockStore<ComponentType = unknown> {
dispose() {
this._services.forEach(service => {
service.dispose();
service.unmounted();
});
this._services.clear();
}
Expand All @@ -37,28 +38,41 @@ export class BlockStore<ComponentType = unknown> {
return spec.view;
}

getService(flavour: string) {
return this._services.get(flavour);
}

private _diffServices(
oldSpecs: Map<string, BlockSpec<ComponentType>>,
newSpecs: Map<string, BlockSpec<ComponentType>>
) {
oldSpecs.forEach((oldSpec, flavour) => {
if (newSpecs.has(flavour)) {
if (
newSpecs.has(flavour) &&
newSpecs.get(flavour)?.service === oldSpec.service
) {
return;
}

const service = this._services.get(oldSpec.schema.model.flavour);
const service = this._services.get(flavour);
if (service) {
service.dispose();
service.unmounted();
}
this._services.delete(oldSpec.schema.model.flavour);
this._services.delete(flavour);
});
newSpecs.forEach((newSpec, flavour) => {
if (oldSpecs.has(flavour) || !newSpec.service) {
if (this._services.has(flavour)) {
return;
}

if (!newSpec.service) {
return;
}

const service = new newSpec.service(this._serviceOptions);
this._services.set(flavour, service);
service.mounted();
});
}

Expand Down
3 changes: 3 additions & 0 deletions packages/blocks/src/__internal__/utils/block-range.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export function getCurrentBlockRange(page: Page) {
// check exist block selection
const pageBlock = getDefaultPage(page);
if (pageBlock) {
assertExists(pageBlock.selection);
const selectedBlocks = pageBlock.selection.state.selectedBlocks;
// Add embeds block to fix click image and delete case
const selectedEmbeds = pageBlock.selection.state.selectedEmbed;
Expand Down Expand Up @@ -193,6 +194,7 @@ export function restoreSelection(blockRange: BlockRange | ExtendBlockRange) {

// In the default mode
if (defaultPageBlock) {
assertExists(defaultPageBlock.selection);
defaultPageBlock.selection.state.clearBlockSelection();
defaultPageBlock.selection.state.type = 'native';
}
Expand All @@ -202,6 +204,7 @@ export function restoreSelection(blockRange: BlockRange | ExtendBlockRange) {
if (blockRange.type === 'Block') {
// In the default mode
if (defaultPageBlock) {
assertExists(defaultPageBlock.selection);
defaultPageBlock.selection.state.type = 'block';
defaultPageBlock.selection.refreshSelectedBlocksRectsByModels(
blockRange.models
Expand Down
4 changes: 3 additions & 1 deletion packages/blocks/src/__internal__/utils/selection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ export function focusBlockByModel(
'affine:bookmark',
])
) {
assertExists(pageBlock.selection);
pageBlock.selection.state.clearSelection();
const rect = getBlockElementByModel(model)?.getBoundingClientRect();
rect && pageBlock.slots.selectedRectsUpdated.emit([rect]);
Expand All @@ -196,6 +197,7 @@ export function focusBlockByModel(
const editableContainer = element?.querySelector('[contenteditable]');
if (editableContainer) {
if (isPageMode) {
assertExists(pageBlock.selection);
pageBlock.selection.state.clearSelection();
pageBlock.selection.setFocusedBlock(element, { type: 'UNKNOWN' });
}
Expand Down Expand Up @@ -258,7 +260,7 @@ export function resetNativeSelection(range: Range | null) {

export function clearSelection(page: Page) {
if (!page.root) return;
getPageBlock(page.root)?.selection.clear();
getPageBlock(page.root)?.selection?.clear();
}

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/blocks/src/list-block/list-block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export class ListBlockComponent extends BlockElement<ListBlockModel> {
const pageBlock = getPageBlock(this.model);
assertExists(pageBlock);
if (pageBlock instanceof DefaultPageBlockComponent) {
pageBlock.selection.selectOneBlock(this);
pageBlock.selection?.selectOneBlock(this);
}
}

Expand Down
54 changes: 30 additions & 24 deletions packages/blocks/src/page-block/default/default-page-block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import type { PageBlockModel } from '../page-model.js';
import { bindHotkeys, removeHotkeys } from '../utils/bind-hotkey.js';
import { tryUpdateNoteSize } from '../utils/index.js';
import { DraggingArea } from './components.js';
import { DefaultSelectionManager } from './selection-manager/index.js';
import type { DefaultPageService } from './default-page-service.js';
import { createDragHandle, getAllowSelectedBlocks } from './utils.js';

export interface DefaultSelectionSlots {
Expand All @@ -57,7 +57,7 @@ export interface DefaultSelectionSlots {

@customElement('affine-default-page')
export class DefaultPageBlockComponent
extends BlockElement<PageBlockModel>
extends BlockElement<PageBlockModel, DefaultPageService>
implements BlockHost
{
static override styles = css`
Expand Down Expand Up @@ -126,8 +126,6 @@ export class DefaultPageBlockComponent

clipboard = new PageClipboard(this);

selection!: DefaultSelectionManager;

getService = getService;

lastSelectionPosition: SelectionPosition = 'start';
Expand All @@ -141,6 +139,10 @@ export class DefaultPageBlockComponent

mouseRoot!: HTMLElement;

get selection() {
return this.service?.selection;
}

@state()
private _draggingArea: DOMRect | null = null;

Expand Down Expand Up @@ -180,6 +182,7 @@ export class DefaultPageBlockComponent

get innerRect() {
const { left, width } = this.pageBlockContainer.getBoundingClientRect();
assertExists(this.selection);
const { clientHeight, top } = this.selection.state.viewport;
return Rect.fromLWTH(
left,
Expand Down Expand Up @@ -290,6 +293,7 @@ export class DefaultPageBlockComponent

// TODO: disable it on scroll's threshold
private _onWheel = (e: WheelEvent) => {
assertExists(this.selection);
const { selection } = this;
const { state } = selection;
const { type, viewport } = state;
Expand Down Expand Up @@ -331,6 +335,7 @@ export class DefaultPageBlockComponent
};

private _onScroll = (e: Event) => {
assertExists(this.selection);
const { selection } = this;
const { type, viewport } = selection.state;
const { scrollLeft, scrollTop } = e.target as Element;
Expand Down Expand Up @@ -424,13 +429,19 @@ export class DefaultPageBlockComponent
private _initSlotEffects() {
const { slots } = this;

slots.draggingAreaUpdated.on(rect => {
this._draggingArea = rect;
});
slots.selectedRectsUpdated.on(rects => {
this._selectedRects = rects;
});
this.model.childrenUpdated.on(() => this.requestUpdate());
this._disposables.add(
slots.draggingAreaUpdated.on(rect => {
this._draggingArea = rect;
})
);
this._disposables.add(
slots.selectedRectsUpdated.on(rects => {
this._selectedRects = rects;
})
);
this._disposables.add(
this.model.childrenUpdated.on(() => this.requestUpdate())
);
}

private _initNoteSizeEffect() {
Expand All @@ -446,8 +457,8 @@ export class DefaultPageBlockComponent
(entries: ResizeObserverEntry[]) => {
for (const { target } of entries) {
if (target === this.viewportElement) {
this.selection.updateViewport();
this.selection.updateRects();
this.selection?.updateViewport();
this.selection?.updateRects();
break;
}
}
Expand All @@ -472,6 +483,7 @@ export class DefaultPageBlockComponent
);
this._disposables.add(() => hotkey.deleteScope(scope));
hotkey.withScope(scope, () => {
assertExists(selection);
bindHotkeys(page, selection);
});
hotkey.enableHotkey();
Expand All @@ -493,13 +505,7 @@ export class DefaultPageBlockComponent
this.clipboard.init(this.page);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
this.mouseRoot = this.parentElement!;
this.selection = new DefaultSelectionManager({
page: this.page,
mouseRoot: this.mouseRoot,
slots: this.slots,
container: this,
dispatcher: this.root.uiEventDispatcher,
});
this.service?.mountSelectionManager(this, this.slots);
}

override disconnectedCallback() {
Expand All @@ -509,23 +515,23 @@ export class DefaultPageBlockComponent
this.components.dragHandle?.remove();

removeHotkeys();
this.selection.clear();
this.selection.dispose();
this.service?.unmountSelectionManager();
if (this._resizeObserver) {
this._resizeObserver.disconnect();
this._resizeObserver = null;
}
this.mouseRoot.removeEventListener('wheel', this._onWheel);
this.viewportElement.removeEventListener('scroll', this._onScroll);
this._disposables.dispose();
}

override render() {
requestAnimationFrame(() => {
this.selection.refreshRemoteSelection();
this.service?.selection?.refreshRemoteSelection();
});

const { selection } = this;
const { viewportOffset } = selection.state;
const viewportOffset = selection?.state.viewportOffset;

const draggingArea = DraggingArea(this._draggingArea);
const isEmpty =
Expand Down
Loading