Skip to content

Commit

Permalink
feat(blocks): add page block service (toeverything#3232)
Browse files Browse the repository at this point in the history
  • Loading branch information
Saul-Mirone authored Jun 28, 2023
1 parent 2f544a1 commit 85013b8
Show file tree
Hide file tree
Showing 18 changed files with 229 additions and 76 deletions.
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

0 comments on commit 85013b8

Please sign in to comment.