feat(editor): replace slot with rxjs subject (#10768)

This commit is contained in:
Mirone
2025-03-12 11:29:24 +09:00
committed by GitHub
parent 19f978d9aa
commit cd63e0ed8b
302 changed files with 1405 additions and 1251 deletions
@@ -31,6 +31,7 @@
"lodash-es": "^4.17.21",
"lz-string": "^1.5.0",
"rehype-parse": "^9.0.0",
"rxjs": "^7.8.1",
"unified": "^11.0.5",
"w3c-keyname": "^2.2.8",
"yjs": "^13.6.21",
@@ -1,5 +1,5 @@
import { DisposableGroup } from '@blocksuite/global/disposable';
import { IS_ANDROID, IS_MAC } from '@blocksuite/global/env';
import { DisposableGroup } from '@blocksuite/global/slot';
import {
type UIEventHandler,
@@ -1,5 +1,5 @@
import { DisposableGroup } from '@blocksuite/global/disposable';
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
import { DisposableGroup } from '@blocksuite/global/slot';
import { LifeCycleWatcher } from '../extension/index.js';
import { KeymapIdentifier } from '../identifier.js';
@@ -1,4 +1,5 @@
import { DisposableGroup, Slot } from '@blocksuite/global/slot';
import { DisposableGroup } from '@blocksuite/global/disposable';
import { Subject } from 'rxjs';
import type { BlockStdScope } from '../scope/block-std-scope';
import { LifeCycleWatcher } from './lifecycle-watcher';
@@ -9,10 +10,10 @@ export class EditorLifeCycleExtension extends LifeCycleWatcher {
disposables = new DisposableGroup();
readonly slots = {
created: new Slot(),
mounted: new Slot(),
rendered: new Slot(),
unmounted: new Slot(),
created: new Subject<void>(),
mounted: new Subject<void>(),
rendered: new Subject<void>(),
unmounted: new Subject<void>(),
};
constructor(override readonly std: BlockStdScope) {
@@ -26,22 +27,22 @@ export class EditorLifeCycleExtension extends LifeCycleWatcher {
override created() {
super.created();
this.slots.created.emit();
this.slots.created.next();
}
override mounted() {
super.mounted();
this.slots.mounted.emit();
this.slots.mounted.next();
}
override rendered() {
super.rendered();
this.slots.rendered.emit();
this.slots.rendered.next();
}
override unmounted() {
super.unmounted();
this.slots.unmounted.emit();
this.slots.unmounted.next();
this.disposables.dispose();
}
@@ -1,6 +1,6 @@
import type { Container } from '@blocksuite/global/di';
import { DisposableGroup } from '@blocksuite/global/disposable';
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
import { DisposableGroup } from '@blocksuite/global/slot';
import { Extension } from '@blocksuite/store';
import type { EventName, UIEventHandler } from '../event/index.js';
@@ -109,12 +109,12 @@ export abstract class BlockService extends Extension {
// life cycle end
mounted() {
this.specSlots.mounted.emit({ service: this });
this.specSlots.mounted.next({ service: this });
}
unmounted() {
this.dispose();
this.specSlots.unmounted.emit({ service: this });
this.specSlots.unmounted.next({ service: this });
}
// event handlers end
}
@@ -1,10 +1,10 @@
import { DisposableGroup } from '@blocksuite/global/disposable';
import {
Bound,
getCommonBound,
getCommonBoundWithRotation,
type IBound,
} from '@blocksuite/global/gfx';
import { DisposableGroup } from '@blocksuite/global/slot';
import { assertType } from '@blocksuite/global/utils';
import type { BlockModel } from '@blocksuite/store';
import { Signal } from '@preact/signals-core';
@@ -1,10 +1,10 @@
import { DisposableGroup } from '@blocksuite/global/disposable';
import type { IBound } from '@blocksuite/global/gfx';
import {
Bound,
getBoundWithRotation,
intersects,
} from '@blocksuite/global/gfx';
import { DisposableGroup } from '@blocksuite/global/slot';
import type { BlockModel } from '@blocksuite/store';
import { compare } from '../utils/layer.js';
@@ -385,7 +385,7 @@ export class GridManager extends GfxExtension {
};
disposables.add(
store.slots.blockUpdated.on(payload => {
store.slots.blockUpdated.subscribe(payload => {
if (payload.type === 'add' && canBeRenderedAsGfxBlock(payload.model)) {
this.add(payload.model);
}
@@ -441,19 +441,19 @@ export class GridManager extends GfxExtension {
);
disposables.add(
surface.elementAdded.on(payload => {
surface.elementAdded.subscribe(payload => {
this.add(surface.getElementById(payload.id)!);
})
);
disposables.add(
surface.elementRemoved.on(payload => {
surface.elementRemoved.subscribe(payload => {
this.remove(payload.model);
})
);
disposables.add(
surface.elementUpdated.on(payload => {
surface.elementUpdated.subscribe(payload => {
if (
payload.props['xywh'] ||
payload.props['externalXYWH'] ||
@@ -465,13 +465,13 @@ export class GridManager extends GfxExtension {
);
disposables.add(
surface.localElementAdded.on(elm => {
surface.localElementAdded.subscribe(elm => {
this.add(elm);
})
);
disposables.add(
surface.localElementUpdated.on(payload => {
surface.localElementUpdated.subscribe(payload => {
if (payload.props['xywh'] || payload.props['responseExtension']) {
this.update(payload.model);
}
@@ -479,7 +479,7 @@ export class GridManager extends GfxExtension {
);
disposables.add(
surface.localElementDeleted.on(elm => {
surface.localElementDeleted.subscribe(elm => {
this.remove(elm);
})
);
@@ -1,4 +1,4 @@
import { DisposableGroup } from '@blocksuite/global/slot';
import { DisposableGroup } from '@blocksuite/global/disposable';
import { Signal } from '@preact/signals-core';
import type { BlockStdScope } from '../scope/block-std-scope.js';
+15 -14
View File
@@ -1,8 +1,9 @@
import { DisposableGroup } from '@blocksuite/global/disposable';
import { Bound } from '@blocksuite/global/gfx';
import { DisposableGroup, Slot } from '@blocksuite/global/slot';
import { assertType } from '@blocksuite/global/utils';
import { generateKeyBetween } from 'fractional-indexing';
import last from 'lodash-es/last';
import { Subject } from 'rxjs';
import {
compare,
@@ -101,7 +102,7 @@ export class LayerManager extends GfxExtension {
layers: Layer[] = [];
slots = {
layerUpdated: new Slot<{
layerUpdated: new Subject<{
type: 'delete' | 'add' | 'update';
initiatingElement: GfxModel | GfxLocalElementModel;
}>(),
@@ -588,7 +589,7 @@ export class LayerManager extends GfxExtension {
element.childElements.forEach(child => child && this._updateLayer(child));
}
this._buildCanvasLayers();
this.slots.layerUpdated.emit({
this.slots.layerUpdated.next({
type: 'add',
initiatingElement: element,
});
@@ -650,7 +651,7 @@ export class LayerManager extends GfxExtension {
if (isGroup) {
this._reset();
this.slots.layerUpdated.emit({
this.slots.layerUpdated.next({
type: 'delete',
initiatingElement: element as GfxModel,
});
@@ -673,14 +674,14 @@ export class LayerManager extends GfxExtension {
this._removeFromLayer(element, deleteType);
this._buildCanvasLayers();
this.slots.layerUpdated.emit({
this.slots.layerUpdated.next({
type: 'delete',
initiatingElement: element,
});
}
override unmounted() {
this.slots.layerUpdated.dispose();
this.slots.layerUpdated.complete();
this._disposable.dispose();
}
@@ -784,7 +785,7 @@ export class LayerManager extends GfxExtension {
) {
if (this._updateLayer(element, props)) {
this._buildCanvasLayers();
this.slots.layerUpdated.emit({
this.slots.layerUpdated.next({
type: 'update',
initiatingElement: element,
});
@@ -795,7 +796,7 @@ export class LayerManager extends GfxExtension {
const store = this._doc;
this._disposable.add(
store.slots.blockUpdated.on(payload => {
store.slots.blockUpdated.subscribe(payload => {
if (payload.type === 'add') {
const block = store.getBlockById(payload.id)!;
@@ -854,34 +855,34 @@ export class LayerManager extends GfxExtension {
);
this._disposable.add(
surface.elementAdded.on(payload =>
surface.elementAdded.subscribe(payload =>
this.add(surface.getElementById(payload.id)!)
)
);
this._disposable.add(
surface.elementUpdated.on(payload => {
surface.elementUpdated.subscribe(payload => {
if (payload.props['index'] || payload.props['childIds']) {
this.update(surface.getElementById(payload.id)!, payload.props);
}
})
);
this._disposable.add(
surface.elementRemoved.on(payload => this.delete(payload.model!))
surface.elementRemoved.subscribe(payload => this.delete(payload.model!))
);
this._disposable.add(
surface.localElementAdded.on(elm => {
surface.localElementAdded.subscribe(elm => {
this.add(elm);
})
);
this._disposable.add(
surface.localElementUpdated.on(payload => {
surface.localElementUpdated.subscribe(payload => {
if (payload.props['index'] || payload.props['groupId']) {
this.update(payload.model, payload.props);
}
})
);
this._disposable.add(
surface.localElementDeleted.on(elm => {
surface.localElementDeleted.subscribe(elm => {
this.delete(elm);
})
);
@@ -39,7 +39,7 @@ function startWatch(prop: string | symbol, receiver: GfxPrimitiveElementModel) {
if (!watchFn) return;
receiver['_disposable'].add(
receiver.surface.elementUpdated.on(payload => {
receiver.surface.elementUpdated.subscribe(payload => {
if (payload.id === receiver.id && prop in payload.props) {
watchFn(payload.oldValues[prop as string], receiver, payload.local);
}
@@ -1,3 +1,4 @@
import { DisposableGroup } from '@blocksuite/global/disposable';
import {
Bound,
deserializeXYWH,
@@ -13,9 +14,9 @@ import {
type SerializedXYWH,
type XYWH,
} from '@blocksuite/global/gfx';
import { DisposableGroup, Slot } from '@blocksuite/global/slot';
import { createMutex } from 'lib0/mutex';
import isEqual from 'lodash-es/isEqual';
import { Subject } from 'rxjs';
import * as Y from 'yjs';
import {
@@ -86,7 +87,7 @@ export abstract class GfxPrimitiveElementModel<
protected _stashed: Map<keyof Props | string, unknown>;
propsUpdated = new Slot<{ key: string }>();
propsUpdated = new Subject<{ key: string }>();
abstract rotate: number;
@@ -262,7 +263,7 @@ export abstract class GfxPrimitiveElementModel<
onDestroyed() {
this._disposable.dispose();
this.propsUpdated.dispose();
this.propsUpdated.complete();
}
pop(prop: keyof Props | string) {
@@ -142,7 +142,7 @@ export abstract class GfxLocalElementModel implements GfxCompatibleInterface {
if (surfaceModel.localElementModels.has(p)) {
this._mutex(() => {
surfaceModel.localElementUpdated.emit({
surfaceModel.localElementUpdated.next({
model: p,
props: {
[prop as string]: value,
@@ -1,8 +1,9 @@
import { DisposableGroup, Slot } from '@blocksuite/global/slot';
import { DisposableGroup } from '@blocksuite/global/disposable';
import { assertType, type Constructor } from '@blocksuite/global/utils';
import type { Boxed } from '@blocksuite/store';
import { BlockModel, nanoid } from '@blocksuite/store';
import { signal } from '@preact/signals-core';
import { Subject } from 'rxjs';
import * as Y from 'yjs';
import {
@@ -77,24 +78,24 @@ export class SurfaceBlockModel extends BlockModel<SurfaceBlockProps> {
protected _surfaceBlockModel = true;
elementAdded = new Slot<{ id: string; local: boolean }>();
protected localElements = new Set<GfxLocalElementModel>();
elementRemoved = new Slot<{
elementAdded = new Subject<{ id: string; local: boolean }>();
elementRemoved = new Subject<{
id: string;
type: string;
model: GfxPrimitiveElementModel;
local: boolean;
}>();
elementUpdated = new Slot<ElementUpdatedData>();
elementUpdated = new Subject<ElementUpdatedData>();
localElementAdded = new Slot<GfxLocalElementModel>();
localElementAdded = new Subject<GfxLocalElementModel>();
localElementDeleted = new Slot<GfxLocalElementModel>();
localElementDeleted = new Subject<GfxLocalElementModel>();
protected localElements = new Set<GfxLocalElementModel>();
localElementUpdated = new Slot<{
localElementUpdated = new Subject<{
model: GfxLocalElementModel;
props: Record<string, unknown>;
oldValues: Record<string, unknown>;
@@ -122,7 +123,10 @@ export class SurfaceBlockModel extends BlockModel<SurfaceBlockProps> {
constructor() {
super();
this.created.once(() => this._init());
const subscription = this.created.subscribe(() => {
this._init();
subscription.unsubscribe();
});
}
private _createElementFromProps(
@@ -296,9 +300,9 @@ export class SurfaceBlockModel extends BlockModel<SurfaceBlockProps> {
element,
{
onChange: payload => {
this.elementUpdated.emit(payload);
this.elementUpdated.next(payload);
Object.keys(payload.props).forEach(key => {
model.model.propsUpdated.emit({ key });
model.model.propsUpdated.next({ key });
});
},
skipFieldInit: true,
@@ -323,11 +327,11 @@ export class SurfaceBlockModel extends BlockModel<SurfaceBlockProps> {
addedElements.forEach(({ mount, model }) => {
mount();
this.elementAdded.emit({ id: model.id, local: transaction.local });
this.elementAdded.next({ id: model.id, local: transaction.local });
});
deletedElements.forEach(({ unmount, model }) => {
unmount();
this.elementRemoved.emit({
this.elementRemoved.next({
id: model.id,
type: model.type,
model,
@@ -343,9 +347,9 @@ export class SurfaceBlockModel extends BlockModel<SurfaceBlockProps> {
val,
{
onChange: payload => {
this.elementUpdated.emit(payload),
this.elementUpdated.next(payload),
Object.keys(payload.props).forEach(key => {
model.model.propsUpdated.emit({ key });
model.model.propsUpdated.next({ key });
});
},
skipFieldInit: true,
@@ -368,7 +372,7 @@ export class SurfaceBlockModel extends BlockModel<SurfaceBlockProps> {
elementsYMap.observe(onElementsMapChange);
const disposable = this.doc.slots.blockUpdated.on(payload => {
const subscription = this.doc.slots.blockUpdated.subscribe(payload => {
switch (payload.type) {
case 'add':
if (isGfxGroupCompatibleModel(payload.model)) {
@@ -392,9 +396,9 @@ export class SurfaceBlockModel extends BlockModel<SurfaceBlockProps> {
}
});
this.deleted.on(() => {
this.deleted.subscribe(() => {
elementsYMap.unobserve(onElementsMapChange);
disposable.dispose();
subscription.unsubscribe();
});
}
@@ -435,7 +439,7 @@ export class SurfaceBlockModel extends BlockModel<SurfaceBlockProps> {
): element is GfxGroupLikeElementModel =>
element instanceof GfxGroupLikeElementModel;
const disposable = this.elementUpdated.on(({ id, oldValues }) => {
const disposable = this.elementUpdated.subscribe(({ id, oldValues }) => {
const element = this.getElementById(id)!;
if (
@@ -446,8 +450,8 @@ export class SurfaceBlockModel extends BlockModel<SurfaceBlockProps> {
this.deleteElement(id);
}
});
this.deleted.on(() => {
disposable.dispose();
this.deleted.subscribe(() => {
disposable.unsubscribe();
});
}
@@ -458,14 +462,14 @@ export class SurfaceBlockModel extends BlockModel<SurfaceBlockProps> {
};
const disposables = new DisposableGroup();
disposables.add(this.elementAdded.on(updateIsEmpty));
disposables.add(this.elementRemoved.on(updateIsEmpty));
this.doc.slots.blockUpdated.on(payload => {
disposables.add(this.elementAdded.subscribe(updateIsEmpty));
disposables.add(this.elementRemoved.subscribe(updateIsEmpty));
this.doc.slots.blockUpdated.subscribe(payload => {
if (['add', 'delete'].includes(payload.type)) {
updateIsEmpty();
}
});
this.deleted.on(() => {
this.deleted.subscribe(() => {
disposables.dispose();
});
}
@@ -518,9 +522,9 @@ export class SurfaceBlockModel extends BlockModel<SurfaceBlockProps> {
const elementModel = this._createElementFromProps(props, {
onChange: payload => {
this.elementUpdated.emit(payload);
this.elementUpdated.next(payload);
Object.keys(payload.props).forEach(key => {
elementModel.model.propsUpdated.emit({ key });
elementModel.model.propsUpdated.next({ key });
});
},
});
@@ -536,7 +540,7 @@ export class SurfaceBlockModel extends BlockModel<SurfaceBlockProps> {
addLocalElement(elem: GfxLocalElementModel) {
this.localElements.add(elem);
this.localElementAdded.emit(elem);
this.localElementAdded.next(elem);
}
applyMiddlewares(middlewares: SurfaceMiddleware[]) {
@@ -575,16 +579,16 @@ export class SurfaceBlockModel extends BlockModel<SurfaceBlockProps> {
deleteLocalElement(elem: GfxLocalElementModel) {
if (this.localElements.delete(elem)) {
this.localElementDeleted.emit(elem);
this.localElementDeleted.next(elem);
}
}
override dispose(): void {
super.dispose();
this.elementAdded.dispose();
this.elementRemoved.dispose();
this.elementUpdated.dispose();
this.elementAdded.complete();
this.elementRemoved.complete();
this.elementUpdated.complete();
this._elementModels.forEach(({ unmount }) => unmount());
this._elementModels.clear();
@@ -1,10 +1,11 @@
import { DisposableGroup } from '@blocksuite/global/disposable';
import {
getCommonBoundWithRotation,
type IPoint,
} from '@blocksuite/global/gfx';
import { DisposableGroup, Slot } from '@blocksuite/global/slot';
import { assertType } from '@blocksuite/global/utils';
import groupBy from 'lodash-es/groupBy';
import { Subject } from 'rxjs';
import {
BlockSelection,
@@ -60,11 +61,11 @@ export class GfxSelectionManager extends GfxExtension {
disposable: DisposableGroup = new DisposableGroup();
readonly slots = {
updated: new Slot<SurfaceSelection[]>(),
remoteUpdated: new Slot(),
updated: new Subject<SurfaceSelection[]>(),
remoteUpdated: new Subject<void>(),
cursorUpdated: new Slot<CursorSelection>(),
remoteCursorUpdated: new Slot(),
cursorUpdated: new Subject<CursorSelection>(),
remoteCursorUpdated: new Subject<void>(),
};
get activeGroup() {
@@ -217,7 +218,7 @@ export class GfxSelectionManager extends GfxExtension {
override mounted() {
this.disposable.add(
this.stdSelection.slots.changed.on(selections => {
this.stdSelection.slots.changed.subscribe(selections => {
const { cursor = [], surface = [] } = groupBy(selections, sel => {
if (sel.is(SurfaceSelection)) {
return 'surface';
@@ -233,7 +234,7 @@ export class GfxSelectionManager extends GfxExtension {
if (cursor[0] && !this.cursorSelection?.equals(cursor[0])) {
this._cursorSelection = cursor[0];
this.slots.cursorUpdated.emit(cursor[0]);
this.slots.cursorUpdated.next(cursor[0]);
}
if ((surface.length === 0 && this.empty) || this.equals(surface)) {
@@ -250,12 +251,12 @@ export class GfxSelectionManager extends GfxExtension {
})
);
this.slots.updated.emit(this.surfaceSelections);
this.slots.updated.next(this.surfaceSelections);
})
);
this.disposable.add(
this.stdSelection.slots.remoteChanged.on(states => {
this.stdSelection.slots.remoteChanged.subscribe(states => {
const surfaceMap = new Map<number, SurfaceSelection[]>();
const cursorMap = new Map<number, CursorSelection>();
const selectedSet = new Set<string>();
@@ -299,8 +300,8 @@ export class GfxSelectionManager extends GfxExtension {
this._remoteSurfaceSelectionsMap = surfaceMap;
this._remoteSelectedSet = selectedSet;
this.slots.remoteUpdated.emit();
this.slots.remoteCursorUpdated.emit();
this.slots.remoteUpdated.next();
this.slots.remoteCursorUpdated.next();
})
);
}
@@ -1,8 +1,9 @@
import type { ServiceIdentifier } from '@blocksuite/global/di';
import { DisposableGroup } from '@blocksuite/global/disposable';
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
import type { IBound, IPoint } from '@blocksuite/global/gfx';
import { DisposableGroup, Slot } from '@blocksuite/global/slot';
import { Signal } from '@preact/signals-core';
import { Subject } from 'rxjs';
import type { PointerEventState } from '../../event/index.js';
import type { GfxController } from '../controller.js';
@@ -79,7 +80,7 @@ export const eventTarget = Symbol('eventTarget');
export class ToolController extends GfxExtension {
static override key = 'ToolController';
private readonly _builtInHookSlot = new Slot<BuiltInSlotContext>();
private readonly _builtInHookSlot = new Subject<BuiltInSlotContext>();
private readonly _disposableGroup = new DisposableGroup();
@@ -441,7 +442,7 @@ export class ToolController extends GfxExtension {
);
});
this._builtInHookSlot.on(evt => {
this._builtInHookSlot.subscribe(evt => {
hooks[evt.event]?.forEach(hook => hook(evt));
});
@@ -499,7 +500,7 @@ export class ToolController extends GfxExtension {
const beforeUpdateCtx = this._createBuiltInHookCtx('beforeToolUpdate', {
toolName: toolNameStr,
});
this._builtInHookSlot.emit(beforeUpdateCtx.slotCtx);
this._builtInHookSlot.next(beforeUpdateCtx.slotCtx);
if (beforeUpdateCtx.prevented) {
return;
@@ -528,7 +529,7 @@ export class ToolController extends GfxExtension {
const afterUpdateCtx = this._createBuiltInHookCtx('toolUpdate', {
toolName: toolNameStr,
});
this._builtInHookSlot.emit(afterUpdateCtx.slotCtx);
this._builtInHookSlot.next(afterUpdateCtx.slotCtx);
}
override unmounted(): void {
@@ -537,7 +538,7 @@ export class ToolController extends GfxExtension {
tool.unmounted();
tool['disposable'].dispose();
});
this._builtInHookSlot.dispose();
this._builtInHookSlot.complete();
}
}
@@ -1,6 +1,6 @@
import { type Container, createIdentifier } from '@blocksuite/global/di';
import { DisposableGroup } from '@blocksuite/global/disposable';
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
import { DisposableGroup } from '@blocksuite/global/slot';
import { Extension } from '@blocksuite/store';
import type { PointerEventState } from '../../event/index.js';
@@ -1,4 +1,4 @@
import { DisposableGroup } from '@blocksuite/global/slot';
import { DisposableGroup } from '@blocksuite/global/disposable';
import { onSurfaceAdded } from '../../utils/gfx.js';
import type { GfxController } from '../controller.js';
@@ -70,14 +70,14 @@ export class ViewManager extends GfxExtension {
};
this._disposable.add(
surface.elementAdded.on(payload => {
surface.elementAdded.subscribe(payload => {
const model = surface.getElementById(payload.id)!;
createView(model);
})
);
this._disposable.add(
surface.elementRemoved.on(elem => {
surface.elementRemoved.subscribe(elem => {
const view = this._viewMap.get(elem.id);
this._viewMap.delete(elem.id);
view?.onDestroyed();
@@ -85,13 +85,13 @@ export class ViewManager extends GfxExtension {
);
this._disposable.add(
surface.localElementAdded.on(model => {
surface.localElementAdded.subscribe(model => {
createView(model);
})
);
this._disposable.add(
surface.localElementDeleted.on(model => {
surface.localElementDeleted.subscribe(model => {
const view = this._viewMap.get(model.id);
this._viewMap.delete(model.id);
view?.onDestroyed();
@@ -1,7 +1,7 @@
import { type Container, createIdentifier } from '@blocksuite/global/di';
import { DisposableGroup } from '@blocksuite/global/disposable';
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
import type { Bound, IVec } from '@blocksuite/global/gfx';
import { DisposableGroup } from '@blocksuite/global/slot';
import type { Extension } from '@blocksuite/store';
import type { PointerEventState } from '../../event/index.js';
@@ -131,10 +131,10 @@ export class GfxViewportElement extends WithDisposable(ShadowlessElement) {
this._hideOutsideBlock();
this.disposables.add(
this.viewport.viewportUpdated.on(() => viewportUpdateCallback())
this.viewport.viewportUpdated.subscribe(() => viewportUpdateCallback())
);
this.disposables.add(
this.viewport.sizeUpdated.on(() => viewportUpdateCallback())
this.viewport.sizeUpdated.subscribe(() => viewportUpdateCallback())
);
}
@@ -5,9 +5,9 @@ import {
type IVec,
Vec,
} from '@blocksuite/global/gfx';
import { Slot } from '@blocksuite/global/slot';
import { signal } from '@preact/signals-core';
import debounce from 'lodash-es/debounce';
import { Subject } from 'rxjs';
import type { GfxViewportElement } from '.';
@@ -74,18 +74,18 @@ export class Viewport {
protected _zoom: number = 1.0;
elementReady = new Slot<GfxViewportElement>();
elementReady = new Subject<GfxViewportElement>();
sizeUpdated = new Slot<{
sizeUpdated = new Subject<{
width: number;
height: number;
left: number;
top: number;
}>();
viewportMoved = new Slot<IVec>();
viewportMoved = new Subject<IVec>();
viewportUpdated = new Slot<{
viewportUpdated = new Subject<{
zoom: number;
center: IVec;
}>();
@@ -106,7 +106,10 @@ export class Viewport {
}, 200);
constructor() {
this.elementReady.once(el => (this._element = el));
const subscription = this.elementReady.subscribe(el => {
this._element = el;
subscription.unsubscribe();
});
}
get boundingClientRect() {
@@ -233,9 +236,9 @@ export class Viewport {
dispose() {
this.clearViewportElement();
this.sizeUpdated.dispose();
this.viewportMoved.dispose();
this.viewportUpdated.dispose();
this.sizeUpdated.complete();
this.viewportMoved.complete();
this.viewportUpdated.complete();
this.zooming$.value = false;
this.panning$.value = false;
}
@@ -296,7 +299,7 @@ export class Viewport {
this._center.x = centerX;
this._center.y = centerY;
this.panning$.value = true;
this.viewportUpdated.emit({
this.viewportUpdated.next({
zoom: this.zoom,
center: Vec.toVec(this.center) as IVec,
});
@@ -306,7 +309,7 @@ export class Viewport {
setRect(left: number, top: number, width: number, height: number) {
this._left = left;
this._top = top;
this.sizeUpdated.emit({
this.sizeUpdated.next({
left,
top,
width,
@@ -420,7 +423,7 @@ export class Viewport {
this.zooming$.value = true;
}
this.setCenter(newCenter[0], newCenter[1]);
this.viewportUpdated.emit({
this.viewportUpdated.next({
zoom: this.zoom,
center: Vec.toVec(this.center) as IVec,
});
@@ -87,7 +87,7 @@ export const getInlineRangeProvider: (
const inlineRange$: InlineRangeProvider['inlineRange$'] = signal(null);
editorHost.disposables.add(
selectionManager.slots.changed.on(selections => {
selectionManager.slots.changed.subscribe(selections => {
if (!isActiveInEditor(editorHost)) return;
const textSelection = selections.find(s => s.type === 'text') as
@@ -313,7 +313,7 @@ export class RangeBinding {
constructor(public manager: RangeManager) {
this.host.disposables.add(
this.selectionManager.slots.changed.on(this._onStdSelectionChanged)
this.selectionManager.slots.changed.subscribe(this._onStdSelectionChanged)
);
this.host.disposables.addFromEvent(
@@ -1,15 +1,15 @@
import { Slot } from '@blocksuite/global/slot';
import { Subject } from 'rxjs';
import type { BlockService } from '../extension/service.js';
import type { BlockComponent, WidgetComponent } from '../view/index.js';
export type BlockSpecSlots<Service extends BlockService = BlockService> = {
mounted: Slot<{ service: Service }>;
unmounted: Slot<{ service: Service }>;
viewConnected: Slot<{ component: BlockComponent; service: Service }>;
viewDisconnected: Slot<{ component: BlockComponent; service: Service }>;
widgetConnected: Slot<{ component: WidgetComponent; service: Service }>;
widgetDisconnected: Slot<{
mounted: Subject<{ service: Service }>;
unmounted: Subject<{ service: Service }>;
viewConnected: Subject<{ component: BlockComponent; service: Service }>;
viewDisconnected: Subject<{ component: BlockComponent; service: Service }>;
widgetConnected: Subject<{ component: WidgetComponent; service: Service }>;
widgetDisconnected: Subject<{
component: WidgetComponent;
service: Service;
}>;
@@ -17,11 +17,11 @@ export type BlockSpecSlots<Service extends BlockService = BlockService> = {
export const getSlots = (): BlockSpecSlots => {
return {
mounted: new Slot(),
unmounted: new Slot(),
viewConnected: new Slot(),
viewDisconnected: new Slot(),
widgetConnected: new Slot(),
widgetDisconnected: new Slot(),
mounted: new Subject(),
unmounted: new Subject(),
viewConnected: new Subject(),
viewDisconnected: new Subject(),
widgetConnected: new Subject(),
widgetDisconnected: new Subject(),
};
};
@@ -214,21 +214,23 @@ export class BlockComponent<
this.std.view.setBlock(this);
const disposable = this.std.store.slots.blockUpdated.on(({ type, id }) => {
if (id === this.model.id && type === 'delete') {
this.std.view.deleteBlock(this);
disposable.dispose();
const disposable = this.std.store.slots.blockUpdated.subscribe(
({ type, id }) => {
if (id === this.model.id && type === 'delete') {
this.std.view.deleteBlock(this);
disposable.unsubscribe();
}
}
});
);
this._disposables.add(disposable);
this._disposables.add(
this.model.propsUpdated.on(() => {
this.model.propsUpdated.subscribe(() => {
this.requestUpdate();
})
);
this.service?.specSlots.viewConnected.emit({
this.service?.specSlots.viewConnected.next({
service: this.service,
component: this,
});
@@ -237,7 +239,7 @@ export class BlockComponent<
override disconnectedCallback() {
super.disconnectedCallback();
this.service?.specSlots.viewDisconnected.emit({
this.service?.specSlots.viewDisconnected.next({
service: this.service,
component: this,
});
@@ -30,13 +30,13 @@ function handleGfxConnection(instance: GfxBlockComponent) {
instance.style.position = 'absolute';
instance.disposables.add(
instance.gfx.viewport.viewportUpdated.on(() => {
instance.gfx.viewport.viewportUpdated.subscribe(() => {
updateTransform(instance);
})
);
instance.disposables.add(
instance.doc.slots.blockUpdated.on(({ type, id }) => {
instance.doc.slots.blockUpdated.subscribe(({ type, id }) => {
if (id === instance.model.id && type === 'update') {
updateTransform(instance);
}
@@ -74,7 +74,7 @@ export class WidgetComponent<
super.connectedCallback();
this.std.view.setWidget(this);
this.service?.specSlots.widgetConnected.emit({
this.service?.specSlots.widgetConnected.next({
service: this.service,
component: this,
});
@@ -83,7 +83,7 @@ export class WidgetComponent<
override disconnectedCallback() {
super.disconnectedCallback();
this.std?.view.deleteWidget(this);
this.service?.specSlots.widgetDisconnected.emit({
this.service?.specSlots.widgetDisconnected.next({
service: this.service,
component: this,
});
@@ -1,4 +1,4 @@
import { Slot } from '@blocksuite/global/slot';
import { Subject } from 'rxjs';
import { LifeCycleWatcher } from '../extension/index.js';
import type { BlockComponent, WidgetComponent } from './element/index.js';
@@ -24,7 +24,7 @@ export class ViewStore extends LifeCycleWatcher {
private readonly _blockMap = new Map<string, BlockComponent>();
viewUpdated: Slot<ViewUpdatePayload> = new Slot();
viewUpdated: Subject<ViewUpdatePayload> = new Subject();
get views() {
return Array.from(this._blockMap.values());
@@ -44,7 +44,7 @@ export class ViewStore extends LifeCycleWatcher {
deleteBlock = (node: BlockComponent) => {
this._blockMap.delete(node.model.id);
this.viewUpdated.emit({
this.viewUpdated.next({
id: node.model.id,
method: 'delete',
type: 'block',
@@ -56,7 +56,7 @@ export class ViewStore extends LifeCycleWatcher {
const id = node.dataset.widgetId as string;
const widgetIndex = `${node.model.id}|${id}`;
this._widgetMap.delete(widgetIndex);
this.viewUpdated.emit({
this.viewUpdated.next({
id: node.model.id,
method: 'delete',
type: 'widget',
@@ -81,7 +81,7 @@ export class ViewStore extends LifeCycleWatcher {
this.deleteBlock(node);
}
this._blockMap.set(node.model.id, node);
this.viewUpdated.emit({
this.viewUpdated.next({
id: node.model.id,
method: 'add',
type: 'block',
@@ -93,7 +93,7 @@ export class ViewStore extends LifeCycleWatcher {
const id = node.dataset.widgetId as string;
const widgetIndex = `${node.model.id}|${id}`;
this._widgetMap.set(widgetIndex, node);
this.viewUpdated.emit({
this.viewUpdated.next({
id: node.model.id,
method: 'add',
type: 'widget',
@@ -140,5 +140,6 @@ export class ViewStore extends LifeCycleWatcher {
override unmounted() {
this._blockMap.clear();
this._widgetMap.clear();
this.viewUpdated.complete();
}
}
+4 -3
View File
@@ -17,7 +17,7 @@
"./di": "./src/di/index.ts",
"./types": "./src/types/index.ts",
"./gfx": "./src/gfx/index.ts",
"./slot": "./src/slot/index.ts",
"./disposable": "./src/disposable/index.ts",
"./lit": "./src/lit/index.ts"
},
"typesVersions": {
@@ -40,8 +40,8 @@
"gfx": [
"dist/gfx/index.d.ts"
],
"slot": [
"dist/slot/index.d.ts"
"disposable": [
"dist/disposable/index.d.ts"
],
"lit": [
"dist/lit/index.d.ts"
@@ -61,6 +61,7 @@
"@preact/signals-core": "^1.8.0",
"lib0": "^0.2.97",
"lit": "^3.2.0",
"rxjs": "^7.8.1",
"zod": "^3.23.8"
},
"devDependencies": {
@@ -1,55 +0,0 @@
import { describe, expect, test, vi } from 'vitest';
import { Slot } from '../slot/slot.js';
describe('slot', () => {
test('init', () => {
const slot = new Slot();
expect(slot).is.toBeDefined();
});
test('emit', () => {
const slot = new Slot<void>();
const callback = vi.fn();
slot.on(callback);
slot.emit();
expect(callback).toBeCalled();
});
test('emit with value', () => {
const slot = new Slot<number>();
const callback = vi.fn(v => expect(v).toBe(5));
slot.on(callback);
slot.emit(5);
expect(callback).toBeCalled();
});
test('listen once', () => {
const slot = new Slot<number>();
const callback = vi.fn(v => expect(v).toBe(5));
slot.once(callback);
slot.emit(5);
slot.emit(6);
expect(callback).toBeCalledTimes(1);
});
test('listen once with dispose', () => {
const slot = new Slot<void>();
const callback = vi.fn(() => {
throw new Error('');
});
const disposable = slot.once(callback);
disposable.dispose();
slot.emit();
expect(callback).toBeCalledTimes(0);
});
test('cycle emit', () => {
const slot = new Slot<number>();
const callback = vi.fn(v => slot.emit(v + 1));
slot.on(callback);
slot.emit(0);
expect(callback).toBeCalledTimes(1);
expect(callback).toBeCalledWith(0);
});
});
@@ -1,15 +1,19 @@
import { Subject, Subscription } from 'rxjs';
type DisposeCallback = () => void;
export interface Disposable {
dispose: DisposeCallback;
}
export interface DisposableManager extends Disposable {
add(d: Disposable | DisposeCallback): void;
}
export type DisposableMember =
| Disposable
| Subscription
| Subject<any>
| DisposeCallback;
export class DisposableGroup implements DisposableManager {
private _disposables: Disposable[] = [];
export class DisposableGroup {
private _disposables: DisposableMember[] = [];
private _disposed = false;
@@ -21,14 +25,12 @@ export class DisposableGroup implements DisposableManager {
* Add to group to be disposed with others.
* This will be immediately disposed if this group has already been disposed.
*/
add(d: Disposable | DisposeCallback) {
if (typeof d === 'function') {
if (this._disposed) d();
else this._disposables.push({ dispose: d });
} else {
if (this._disposed) d.dispose();
else this._disposables.push(d);
add(d: DisposableMember) {
if (this._disposed) {
disposeMember(d);
return;
}
this._disposables.push(d);
}
addFromEvent<N extends keyof WindowEventMap>(
@@ -83,18 +85,22 @@ export class DisposableGroup implements DisposableManager {
}
}
export function flattenDisposables(disposables: Disposable[]): Disposable {
return {
dispose: () => disposeAll(disposables),
};
}
function disposeAll(disposables: Disposable[]) {
for (const disposable of disposables) {
try {
export function disposeMember(disposable: DisposableMember) {
try {
if (disposable instanceof Subscription) {
disposable.unsubscribe();
} else if (disposable instanceof Subject) {
disposable.complete();
} else if (typeof disposable === 'function') {
disposable();
} else {
disposable.dispose();
} catch (err) {
console.error(err);
}
} catch (e) {
console.error(e);
}
}
function disposeAll(disposables: DisposableMember[]) {
disposables.forEach(disposeMember);
}
@@ -1,6 +1,6 @@
import type { LitElement } from 'lit';
import { DisposableGroup } from '../slot/disposable.js';
import { DisposableGroup } from '../disposable/index.js';
import type { Constructor } from '../utils/types.js';
// See https://lit.dev/docs/composition/mixins/#mixins-in-typescript
@@ -1,2 +0,0 @@
export * from './disposable.js';
export * from './slot.js';
@@ -1,86 +0,0 @@
import { type Disposable, flattenDisposables } from './disposable.js';
export class Slot<T = void> implements Disposable {
private _callbacks: ((v: T) => unknown)[] = [];
private _disposables: Disposable[] = [];
private _emitting = false;
dispose() {
flattenDisposables(this._disposables).dispose();
this._callbacks = [];
this._disposables = [];
}
emit(v: T) {
// Prevent recursive emit calls
if (this._emitting) {
return;
}
const prevEmitting = this._emitting;
this._emitting = true;
try {
this._callbacks.forEach(f => {
try {
f(v);
} catch (err) {
console.error(err);
}
});
} finally {
this._emitting = prevEmitting;
}
}
on(callback: (v: T) => unknown): Disposable {
if (this._emitting) {
const newCallback = [...this._callbacks, callback];
this._callbacks = newCallback;
} else {
this._callbacks.push(callback);
}
return {
dispose: () => {
if (this._emitting) {
this._callbacks = this._callbacks.filter(v => v !== callback);
} else {
const index = this._callbacks.indexOf(callback);
if (index > -1) {
this._callbacks.splice(index, 1); // remove one item only
}
}
},
};
}
once(callback: (v: T) => unknown): Disposable {
let dispose: Disposable['dispose'] | undefined = undefined;
const handler = (v: T) => {
callback(v);
if (dispose) {
dispose();
}
};
const disposable = this.on(handler);
dispose = disposable.dispose;
return disposable;
}
filter(testFun: (v: T) => boolean): Slot<T> {
const result = new Slot<T>();
// if the original slot is disposed, dispose the filtered one
this._disposables.push({
dispose: () => result.dispose(),
});
this.on((v: T) => {
if (testFun(v)) {
result.emit(v);
}
});
return result;
}
}
+1
View File
@@ -28,6 +28,7 @@
"@blocksuite/global": "workspace:*",
"@preact/signals-core": "^1.8.0",
"lit": "^3.2.0",
"rxjs": "^7.8.1",
"yjs": "^13.6.21",
"zod": "^3.23.8"
},
@@ -1,6 +1,6 @@
import { DisposableGroup } from '@blocksuite/global/disposable';
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
import { SignalWatcher } from '@blocksuite/global/lit';
import { DisposableGroup } from '@blocksuite/global/slot';
import { effect, signal } from '@preact/signals-core';
import { html, LitElement } from 'lit';
import { property } from 'lit/decorators.js';
@@ -1,7 +1,8 @@
import { DisposableGroup } from '@blocksuite/global/disposable';
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
import { DisposableGroup, Slot } from '@blocksuite/global/slot';
import { type Signal, signal } from '@preact/signals-core';
import { nothing, render, type TemplateResult } from 'lit';
import { Subject } from 'rxjs';
import type * as Y from 'yjs';
import type { VLine } from './components/v-line.js';
@@ -160,19 +161,19 @@ export class InlineEditor<
};
readonly slots = {
mounted: new Slot(),
unmounted: new Slot(),
renderComplete: new Slot(),
textChange: new Slot(),
inlineRangeSync: new Slot<Range | null>(),
mounted: new Subject<void>(),
unmounted: new Subject<void>(),
renderComplete: new Subject<void>(),
textChange: new Subject<void>(),
inlineRangeSync: new Subject<Range | null>(),
/**
* Corresponding to the `compositionUpdate` and `beforeInput` events, and triggered only when the `inlineRange` is not null.
*/
inputting: new Slot(),
inputting: new Subject<void>(),
/**
* Triggered only when the `inlineRange` is not null.
*/
keydown: new Slot<KeyboardEvent>(),
keydown: new Subject<KeyboardEvent>(),
};
readonly vLineRenderer: ((vLine: VLine) => TemplateResult) | null;
@@ -252,7 +253,7 @@ export class InlineEditor<
this.renderService.mount();
this._mounted = true;
this.slots.mounted.emit();
this.slots.mounted.next();
this.render();
}
@@ -267,7 +268,7 @@ export class InlineEditor<
this._rootElement = null;
this._mounted = false;
this.disposables.dispose();
this.slots.unmounted.emit();
this.slots.unmounted.next();
}
setReadonly(isReadonly: boolean): void {
@@ -118,7 +118,7 @@ export class EventService<TextAttributes extends BaseTextAttributes> {
this.editor as InlineEditor
);
this.editor.slots.inputting.emit();
this.editor.slots.inputting.next();
};
private readonly _onClick = (event: MouseEvent) => {
@@ -180,7 +180,7 @@ export class EventService<TextAttributes extends BaseTextAttributes> {
});
}
this.editor.slots.inputting.emit();
this.editor.slots.inputting.next();
};
private readonly _onCompositionStart = () => {
@@ -215,14 +215,14 @@ export class EventService<TextAttributes extends BaseTextAttributes> {
)
return;
this.editor.slots.inputting.emit();
this.editor.slots.inputting.next();
};
private readonly _onKeyDown = (event: KeyboardEvent) => {
const inlineRange = this.editor.getInlineRange();
if (!inlineRange) return;
this.editor.slots.keydown.emit(event);
this.editor.slots.keydown.next(event);
if (
!event.shiftKey &&
@@ -257,7 +257,8 @@ export class RangeService<TextAttributes extends BaseTextAttributes> {
if (editor.inlineRangeProviderOverride) return;
if (this.editor.renderService.rendering) {
editor.slots.renderComplete.once(() => {
const subscription = editor.slots.renderComplete.subscribe(() => {
subscription.unsubscribe();
this.syncInlineRange(newInlineRange);
});
} else {
@@ -308,11 +309,14 @@ export class RangeService<TextAttributes extends BaseTextAttributes> {
selection.addRange(newRange);
this.editor.rootElement.focus();
this.editor.slots.inlineRangeSync.emit(newRange);
this.editor.slots.inlineRangeSync.next(newRange);
} else {
this.editor.slots.renderComplete.once(() => {
this.syncInlineRange(inlineRange);
});
const subscription = this.editor.slots.renderComplete.subscribe(
() => {
subscription.unsubscribe();
this.syncInlineRange(inlineRange);
}
);
}
} catch (error) {
console.error('failed to apply inline range');
@@ -322,7 +326,10 @@ export class RangeService<TextAttributes extends BaseTextAttributes> {
};
if (this.editor.renderService.rendering) {
this.editor.slots.renderComplete.once(handler);
const subscription = this.editor.slots.renderComplete.subscribe(() => {
subscription.unsubscribe();
handler();
});
} else {
handler();
}
@@ -14,7 +14,7 @@ export class RenderService<TextAttributes extends BaseTextAttributes> {
_: Y.YTextEvent,
transaction: Y.Transaction
) => {
this.editor.slots.textChange.emit();
this.editor.slots.textChange.next();
const yText = this.editor.yText;
@@ -153,7 +153,7 @@ export class RenderService<TextAttributes extends BaseTextAttributes> {
.waitForUpdate()
.then(() => {
this._rendering = false;
this.editor.slots.renderComplete.emit();
this.editor.slots.renderComplete.next();
this.editor.syncInlineRange();
})
.catch(console.error);
+1
View File
@@ -26,6 +26,7 @@
"lodash.merge": "^4.6.2",
"minimatch": "^10.0.1",
"nanoid": "^5.0.7",
"rxjs": "^7.8.1",
"y-protocols": "^1.0.6",
"yjs": "^13.6.21",
"zod": "^3.23.8"
@@ -1,4 +1,4 @@
import type { Slot } from '@blocksuite/global/slot';
import type { Subject } from 'rxjs';
import { assert, beforeEach, describe, expect, it, vi } from 'vitest';
import { applyUpdate, type Doc, encodeStateAsUpdate } from 'yjs';
@@ -36,8 +36,13 @@ function serializCollection(doc: Doc): Record<string, any> {
};
}
function waitOnce<T>(slot: Slot<T>) {
return new Promise<T>(resolve => slot.once(val => resolve(val)));
function waitOnce<T>(slot: Subject<T>) {
return new Promise<T>(resolve => {
const subscription = slot.subscribe(val => {
subscription.unsubscribe();
resolve(val);
});
});
}
function createRoot(doc: Store) {
@@ -150,8 +155,8 @@ describe('basic', () => {
const readyCallback = vi.fn();
const rootAddedCallback = vi.fn();
doc.slots.ready.on(readyCallback);
doc.slots.rootAdded.on(rootAddedCallback);
doc.slots.ready.subscribe(readyCallback);
doc.slots.rootAdded.subscribe(rootAddedCallback);
doc.load(() => {
const rootId = doc.addBlock('affine:page', {
@@ -428,7 +433,7 @@ describe('addBlock', () => {
);
let called = false;
collection.slots.docListUpdated.on(() => {
collection.slots.docListUpdated.subscribe(() => {
called = true;
});
@@ -41,7 +41,7 @@ test('trigger props updated', () => {
expect(rootModel).not.toBeNull();
const onPropsUpdated = vi.fn();
rootModel.propsUpdated.on(onPropsUpdated);
rootModel.propsUpdated.subscribe(onPropsUpdated);
const getColor = () =>
(rootModel.yBlock.get('prop:style') as Y.Map<string>).get('color');
@@ -101,7 +101,7 @@ test('stash and pop', () => {
expect(rootModel).not.toBeNull();
const onPropsUpdated = vi.fn();
rootModel.propsUpdated.on(onPropsUpdated);
rootModel.propsUpdated.subscribe(onPropsUpdated);
const getCount = () => rootModel.yBlock.get('prop:count');
const getColor = () =>
@@ -171,7 +171,7 @@ test('always get latest value in onChange', () => {
expect(rootModel).not.toBeNull();
let value: unknown;
rootModel.propsUpdated.on(({ key }) => {
rootModel.propsUpdated.subscribe(({ key }) => {
// @ts-expect-error ignore
value = rootModel[key];
});
@@ -1,4 +1,4 @@
import { Slot } from '@blocksuite/global/slot';
import { Subject } from 'rxjs';
import { describe, expect, test } from 'vitest';
import * as Y from 'yjs';
@@ -184,7 +184,7 @@ test('flat', () => {
map.set('prop:col.c.d', 3);
map.set('prop:col.c.e', 4);
const reactive = new ReactiveFlatYMap(map, new Slot());
const reactive = new ReactiveFlatYMap(map, new Subject());
const proxy = reactive.proxy as Record<string, any>;
proxy.col.c.d = 200;
expect(map.get('prop:col.c.d')).toBe(200);
@@ -1,6 +1,6 @@
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
import { Slot } from '@blocksuite/global/slot';
import { computed, signal } from '@preact/signals-core';
import { Subject } from 'rxjs';
import { nanoid } from '../../utils/id-generator';
import type { StackItem } from '../../yjs';
@@ -42,8 +42,8 @@ export class StoreSelectionExtension extends StoreExtension {
};
slots = {
changed: new Slot<BaseSelection[]>(),
remoteChanged: new Slot<Map<number, BaseSelection[]>>(),
changed: new Subject<BaseSelection[]>(),
remoteChanged: new Subject<Map<number, BaseSelection[]>>(),
};
override loaded() {
@@ -98,7 +98,7 @@ export class StoreSelectionExtension extends StoreExtension {
map.set(id, selections);
});
this._remoteSelections.value = map;
this.slots.remoteChanged.emit(map);
this.slots.remoteChanged.next(map);
}
}
);
@@ -162,7 +162,7 @@ export class StoreSelectionExtension extends StoreExtension {
this._id,
selections.map(s => s.toJSON())
);
this.slots.changed.emit(selections);
this.slots.changed.next(selections);
}
setGroup(group: string, selections: BaseSelection[]) {
@@ -1,5 +1,7 @@
import { type Disposable, Slot } from '@blocksuite/global/slot';
import type { Disposable } from '@blocksuite/global/disposable';
import { computed, type Signal, signal } from '@preact/signals-core';
import { Subject } from 'rxjs';
import { take } from 'rxjs/operators';
import type { Text } from '../../reactive/index.js';
import type { Store } from '../store/store.js';
@@ -57,9 +59,9 @@ export class BlockModel<
}, new Map<string, number>())
);
created = new Slot();
created = new Subject<void>();
deleted = new Slot();
deleted = new Subject<void>();
id!: string;
@@ -76,7 +78,7 @@ export class BlockModel<
pop!: (prop: keyof Props & string) => void;
propsUpdated = new Slot<{ key: string }>();
propsUpdated = new Subject<{ key: string }>();
stash!: (prop: keyof Props & string) => void;
@@ -124,26 +126,30 @@ export class BlockModel<
constructor() {
super();
this._onCreated = this.created.once(() => {
this._children.value = this.yBlock.get('sys:children').toArray();
this.yBlock.get('sys:children').observe(event => {
this._children.value = event.target.toArray();
});
this.yBlock.observe(event => {
if (event.keysChanged.has('sys:children')) {
this._children.value = this.yBlock.get('sys:children').toArray();
}
});
});
this._onDeleted = this.deleted.once(() => {
this._onCreated.dispose();
});
this._onCreated = {
dispose: this.created.pipe(take(1)).subscribe(() => {
this._children.value = this.yBlock.get('sys:children').toArray();
this.yBlock.get('sys:children').observe(event => {
this._children.value = event.target.toArray();
});
this.yBlock.observe(event => {
if (event.keysChanged.has('sys:children')) {
this._children.value = this.yBlock.get('sys:children').toArray();
}
});
}).unsubscribe,
};
this._onDeleted = {
dispose: this.deleted.pipe(take(1)).subscribe(() => {
this._onCreated.dispose();
}).unsubscribe,
};
}
dispose() {
this.created.dispose();
this.deleted.dispose();
this.propsUpdated.dispose();
this.created.complete();
this.deleted.complete();
this.propsUpdated.complete();
}
firstChild(): BlockModel | null {
@@ -142,7 +142,10 @@ export class SyncController {
this.model[key] = value;
});
});
model.deleted.once(dispose);
const subscription = model.deleted.subscribe(() => {
subscription.unsubscribe();
dispose();
});
return {
...acc,
[`${key}$`]: data,
+3 -3
View File
@@ -1,4 +1,4 @@
import type { Slot } from '@blocksuite/global/slot';
import type { Subject } from 'rxjs';
import type * as Y from 'yjs';
import type { AwarenessStore } from '../yjs/awareness.js';
@@ -24,8 +24,8 @@ export interface Doc {
dispose(): void;
slots: {
historyUpdated: Slot;
yBlockUpdated: Slot<
historyUpdated: Subject<void>;
yBlockUpdated: Subject<
| {
type: 'add';
id: string;
@@ -1,7 +1,8 @@
import { Container, type ServiceProvider } from '@blocksuite/global/di';
import { DisposableGroup } from '@blocksuite/global/disposable';
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
import { DisposableGroup, Slot } from '@blocksuite/global/slot';
import { computed, signal } from '@preact/signals-core';
import { Subject } from 'rxjs';
import type { ExtensionType } from '../../extension/extension.js';
import {
@@ -67,15 +68,15 @@ export class Store {
readonly slots: Doc['slots'] & {
/** This is always triggered after `doc.load` is called. */
ready: Slot;
ready: Subject<void>;
/**
* This fires when the root block is added via API call or has just been initialized from existing ydoc.
* useful for internal block UI components to start subscribing following up events.
* Note that at this moment, the whole block tree may not be fully initialized yet.
*/
rootAdded: Slot<string>;
rootDeleted: Slot<string>;
blockUpdated: Slot<
rootAdded: Subject<string>;
rootDeleted: Subject<string>;
blockUpdated: Subject<
| {
type: 'add';
id: string;
@@ -313,13 +314,15 @@ export class Store {
return this._doc.withoutTransact.bind(this._doc);
}
private _isDisposed = false;
constructor({ doc, readonly, query, provider, extensions }: StoreOptions) {
this._doc = doc;
this.slots = {
ready: new Slot(),
rootAdded: new Slot(),
rootDeleted: new Slot(),
blockUpdated: new Slot(),
ready: new Subject(),
rootAdded: new Subject(),
rootDeleted: new Subject(),
blockUpdated: new Subject(),
historyUpdated: this._doc.slots.historyUpdated,
yBlockUpdated: this._doc.slots.yBlockUpdated,
};
@@ -362,18 +365,31 @@ export class Store {
private readonly _subscribeToSlots = () => {
this.disposableGroup.add(
this._doc.slots.yBlockUpdated.on(({ type, id }) => {
switch (type) {
case 'add': {
this._onBlockAdded(id);
return;
}
case 'delete': {
this._onBlockRemoved(id);
return;
this._doc.slots.yBlockUpdated.subscribe(
({ type, id }: { type: string; id: string }) => {
switch (type) {
case 'add': {
this._onBlockAdded(id);
return;
}
case 'delete': {
this._onBlockRemoved(id);
return;
}
case 'update': {
const block = this.getBlock(id);
if (!block) return;
this.slots.blockUpdated.next({
type: 'update',
id,
flavour: block.flavour,
props: { key: 'content' },
});
return;
}
}
}
})
)
);
this.disposableGroup.add(this.slots.ready);
this.disposableGroup.add(this.slots.blockUpdated);
@@ -412,10 +428,10 @@ export class Store {
const options: BlockOptions = {
onChange: (block, key) => {
if (key) {
block.model.propsUpdated.emit({ key });
block.model.propsUpdated.next({ key });
}
this.slots.blockUpdated.emit({
this.slots.blockUpdated.next({
type: 'update',
id,
flavour: block.flavour,
@@ -431,13 +447,13 @@ export class Store {
...this._blocks.value,
[id]: block,
};
block.model.created.emit();
block.model.created.next();
if (block.model.role === 'root') {
this.slots.rootAdded.emit(id);
this.slots.rootAdded.next(id);
}
this.slots.blockUpdated.emit({
this.slots.blockUpdated.next({
type: 'add',
id,
init,
@@ -456,13 +472,13 @@ export class Store {
if (!block) return;
if (block.model.role === 'root') {
this.slots.rootDeleted.emit(id);
this.slots.rootDeleted.next(id);
}
this.slots.blockUpdated.emit({
this.slots.blockUpdated.next({
type: 'delete',
id,
flavour: block.model.flavour,
flavour: block.flavour,
parent: this.getParent(block.model)?.id ?? '',
model: block.model,
});
@@ -470,7 +486,7 @@ export class Store {
const { [id]: _, ...blocks } = this._blocks.peek();
this._blocks.value = blocks;
block.model.deleted.emit();
block.model.deleted.next();
block.model.dispose();
} catch (e) {
console.error('An error occurred while removing block:');
@@ -604,8 +620,12 @@ export class Store {
this._provider.getAll(StoreExtensionIdentifier).forEach(ext => {
ext.disposed();
});
this.slots.ready.complete();
this.slots.rootAdded.complete();
this.slots.rootDeleted.complete();
this.slots.blockUpdated.complete();
this.disposableGroup.dispose();
this._isDisposed = true;
}
getBlock(id: string): Block | undefined {
@@ -705,16 +725,18 @@ export class Store {
}
load(initFn?: () => void) {
if (this.disposableGroup.disposed) {
if (this._isDisposed) {
this.disposableGroup = new DisposableGroup();
this._subscribeToSlots();
this._isDisposed = false;
}
this._doc.load(initFn);
this._provider.getAll(StoreExtensionIdentifier).forEach(ext => {
ext.loaded();
});
this.slots.ready.emit();
this.slots.ready.next();
this.slots.rootAdded.next(this.root?.id ?? '');
return this;
}
@@ -1,4 +1,4 @@
import type { Slot } from '@blocksuite/global/slot';
import type { Subject } from 'rxjs';
export type Tag = {
id: string;
@@ -39,8 +39,8 @@ export interface WorkspaceMeta {
get docs(): unknown[] | undefined;
initialize(): void;
commonFieldsUpdated: Slot;
docMetaAdded: Slot<string>;
docMetaRemoved: Slot<string>;
docMetaUpdated: Slot;
commonFieldsUpdated: Subject<void>;
docMetaAdded: Subject<string>;
docMetaRemoved: Subject<string>;
docMetaUpdated: Subject<void>;
}
@@ -1,5 +1,5 @@
import type { Slot } from '@blocksuite/global/slot';
import type { BlobEngine } from '@blocksuite/sync';
import type { Subject } from 'rxjs';
import type { Awareness } from 'y-protocols/awareness.js';
import type * as Y from 'yjs';
@@ -20,9 +20,9 @@ export interface Workspace {
get docs(): Map<string, Doc>;
slots: {
docListUpdated: Slot;
docCreated: Slot<string>;
docRemoved: Slot<string>;
docListUpdated: Subject<void>;
docCreated: Subject<string>;
docRemoved: Subject<string>;
};
createDoc(options?: CreateBlocksOptions): Store;
@@ -1,6 +1,6 @@
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
import type { Slot } from '@blocksuite/global/slot';
import { signal } from '@preact/signals-core';
import type { Subject } from 'rxjs';
import {
Array as YArray,
Map as YMap,
@@ -29,7 +29,7 @@ type CreateProxyOptions = {
basePath?: string;
onChange?: OnChange;
transform: Transform;
onDispose: Slot;
onDispose: Subject<void>;
shouldByPassSignal: () => boolean;
shouldByPassYjs: () => boolean;
byPassSignalUpdate: (fn: () => void) => void;
@@ -113,17 +113,19 @@ function createProxy(
}
const signalData = signal(value);
root[signalKey] = signalData;
onDispose.once(
signalData.subscribe(next => {
if (!initialized()) {
return;
}
byPassSignalUpdate(() => {
proxy[p] = next;
onChange?.(firstKey);
});
})
);
const unsubscribe = signalData.subscribe(next => {
if (!initialized()) {
return;
}
byPassSignalUpdate(() => {
proxy[p] = next;
onChange?.(firstKey);
});
});
const subscription = onDispose.subscribe(() => {
subscription.unsubscribe();
unsubscribe();
});
return;
}
byPassSignalUpdate(() => {
@@ -464,7 +466,7 @@ export class ReactiveFlatYMap extends BaseReactiveYData<
constructor(
protected readonly _ySource: YMap<unknown>,
private readonly _onDispose: Slot,
private readonly _onDispose: Subject<void>,
private readonly _onChange?: OnChange
) {
super();
@@ -477,17 +479,19 @@ export class ReactiveFlatYMap extends BaseReactiveYData<
Object.entries(source).forEach(([key, value]) => {
const signalData = signal(value);
source[`${key}$`] = signalData;
_onDispose.once(
signalData.subscribe(next => {
if (!this._initialized) {
return;
}
this._updateWithSkip(() => {
proxy[key] = next;
this._onChange?.(key);
});
})
);
const unsubscribe = signalData.subscribe(next => {
if (!this._initialized) {
return;
}
this._updateWithSkip(() => {
proxy[key] = next;
this._onChange?.(key);
});
});
const subscription = _onDispose.subscribe(() => {
subscription.unsubscribe();
unsubscribe();
});
});
this._proxy = proxy;
+11 -11
View File
@@ -1,5 +1,5 @@
import { Slot } from '@blocksuite/global/slot';
import { signal } from '@preact/signals-core';
import { Subject } from 'rxjs';
import * as Y from 'yjs';
import type { YBlock } from '../model/block/types.js';
@@ -34,7 +34,7 @@ export class TestDoc implements Doc {
private readonly _historyObserver = () => {
this._updateCanUndoRedoSignals();
this.slots.historyUpdated.emit();
this.slots.historyUpdated.next();
};
private readonly _initSubDoc = () => {
@@ -45,7 +45,7 @@ export class TestDoc implements Doc {
});
this.rootDoc.getMap('spaces').set(this.id, subDoc);
this._loaded = true;
this._onLoadSlot.emit();
this._onLoadSlot.next();
} else {
this._loaded = false;
this.rootDoc.on('subdocs', this._onSubdocEvent);
@@ -56,7 +56,7 @@ export class TestDoc implements Doc {
private _loaded!: boolean;
private readonly _onLoadSlot = new Slot();
private readonly _onLoadSlot = new Subject<void>();
private readonly _onSubdocEvent = ({
loaded,
@@ -71,7 +71,7 @@ export class TestDoc implements Doc {
}
this.rootDoc.off('subdocs', this._onSubdocEvent);
this._loaded = true;
this._onLoadSlot.emit();
this._onLoadSlot.next();
};
/** Indicate whether the block tree is ready */
@@ -105,8 +105,8 @@ export class TestDoc implements Doc {
readonly rootDoc: Y.Doc;
readonly slots = {
historyUpdated: new Slot(),
yBlockUpdated: new Slot<
historyUpdated: new Subject<void>(),
yBlockUpdated: new Subject<
| {
type: 'add';
id: string;
@@ -186,11 +186,11 @@ export class TestDoc implements Doc {
}
private _handleYBlockAdd(id: string) {
this.slots.yBlockUpdated.emit({ type: 'add', id });
this.slots.yBlockUpdated.next({ type: 'add', id });
}
private _handleYBlockDelete(id: string) {
this.slots.yBlockUpdated.emit({ type: 'delete', id });
this.slots.yBlockUpdated.next({ type: 'delete', id });
}
private _handleYEvent(event: Y.YEvent<YBlock | Y.Text | Y.Array<unknown>>) {
@@ -244,12 +244,12 @@ export class TestDoc implements Doc {
private _destroy() {
this._ySpaceDoc.destroy();
this._onLoadSlot.dispose();
this._onLoadSlot.complete();
this._loaded = false;
}
dispose() {
this.slots.historyUpdated.dispose();
this.slots.historyUpdated.complete();
if (this.ready) {
this._yBlocks.unobserveDeep(this._handleYEvents);
@@ -1,4 +1,4 @@
import { Slot } from '@blocksuite/global/slot';
import { Subject } from 'rxjs';
import type * as Y from 'yjs';
import type {
@@ -45,15 +45,15 @@ export class TestMeta implements WorkspaceMeta {
DocCollectionMetaState[keyof DocCollectionMetaState]
>;
commonFieldsUpdated = new Slot();
commonFieldsUpdated = new Subject<void>();
readonly doc: Y.Doc;
docMetaAdded = new Slot<string>();
docMetaAdded = new Subject<string>();
docMetaRemoved = new Slot<string>();
docMetaRemoved = new Subject<string>();
docMetaUpdated = new Slot();
docMetaUpdated = new Subject<void>();
readonly id: string = 'meta';
@@ -103,7 +103,7 @@ export class TestMeta implements WorkspaceMeta {
}
private _handleCommonFieldsEvent() {
this.commonFieldsUpdated.emit();
this.commonFieldsUpdated.next();
}
private _handleDocMetaEvent() {
@@ -113,7 +113,7 @@ export class TestMeta implements WorkspaceMeta {
docMetas.forEach(docMeta => {
if (!_prevDocs.has(docMeta.id)) {
this.docMetaAdded.emit(docMeta.id);
this.docMetaAdded.next(docMeta.id);
}
newDocs.add(docMeta.id);
});
@@ -121,13 +121,13 @@ export class TestMeta implements WorkspaceMeta {
_prevDocs.forEach(prevDocId => {
const isRemoved = newDocs.has(prevDocId) === false;
if (isRemoved) {
this.docMetaRemoved.emit(prevDocId);
this.docMetaRemoved.next(prevDocId);
}
});
this._prevDocs = newDocs;
this.docMetaUpdated.emit();
this.docMetaUpdated.next();
}
addDocMeta(doc: DocMeta, index?: number) {
@@ -204,6 +204,6 @@ export class TestMeta implements WorkspaceMeta {
setProperties(meta: DocsPropertiesMeta) {
this._proxy.properties = meta;
this.docMetaUpdated.emit();
this.docMetaUpdated.next();
}
}
@@ -1,5 +1,4 @@
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
import { Slot } from '@blocksuite/global/slot';
import { NoopLogger } from '@blocksuite/global/utils';
import {
AwarenessEngine,
@@ -11,6 +10,7 @@ import {
MemoryBlobSource,
NoopDocSource,
} from '@blocksuite/sync';
import { Subject } from 'rxjs';
import { Awareness } from 'y-protocols/awareness.js';
import * as Y from 'yjs';
@@ -67,9 +67,9 @@ export class TestWorkspace implements Workspace {
meta: WorkspaceMeta;
slots = {
docListUpdated: new Slot(),
docRemoved: new Slot<string>(),
docCreated: new Slot<string>(),
docListUpdated: new Subject<void>(),
docRemoved: new Subject<string>(),
docCreated: new Subject<string>(),
};
get docs() {
@@ -116,7 +116,7 @@ export class TestWorkspace implements Workspace {
}
private _bindDocMetaEvents() {
this.meta.docMetaAdded.on(docId => {
this.meta.docMetaAdded.subscribe(docId => {
const doc = new TestDoc({
id: docId,
collection: this,
@@ -126,14 +126,14 @@ export class TestWorkspace implements Workspace {
this.blockCollections.set(doc.id, doc);
});
this.meta.docMetaUpdated.on(() => this.slots.docListUpdated.emit());
this.meta.docMetaUpdated.subscribe(() => this.slots.docListUpdated.next());
this.meta.docMetaRemoved.on(id => {
this.meta.docMetaRemoved.subscribe(id => {
const space = this.getBlockCollection(id);
if (!space) return;
this.blockCollections.delete(id);
space.remove();
this.slots.docRemoved.emit(id);
this.slots.docRemoved.next(id);
});
}
@@ -174,7 +174,7 @@ export class TestWorkspace implements Workspace {
createDate: Date.now(),
tags: [],
});
this.slots.docCreated.emit(docId);
this.slots.docCreated.next(docId);
return this.getDoc(docId, {
id: docId,
query,
@@ -1,4 +1,4 @@
import type { Slot } from '@blocksuite/global/slot';
import type { Subject } from 'rxjs';
import type { BlockModel, DraftModel, Store } from '../model/index.js';
import type { AssetsManager } from './assets.js';
@@ -95,10 +95,10 @@ export type AfterImportPayload =
};
export type TransformerSlots = {
beforeImport: Slot<BeforeImportPayload>;
afterImport: Slot<AfterImportPayload>;
beforeExport: Slot<BeforeExportPayload>;
afterExport: Slot<AfterExportPayload>;
beforeImport: Subject<BeforeImportPayload>;
afterImport: Subject<AfterImportPayload>;
beforeExport: Subject<BeforeExportPayload>;
afterExport: Subject<AfterExportPayload>;
};
type TransformerMiddlewareOptions = {
@@ -1,6 +1,6 @@
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
import { Slot } from '@blocksuite/global/slot';
import { nextTick } from '@blocksuite/global/utils';
import { Subject } from 'rxjs';
import {
BlockModel,
@@ -68,10 +68,10 @@ export class Transformer {
private readonly _docCRUD: DocCRUD;
private readonly _slots: TransformerSlots = {
beforeImport: new Slot<BeforeImportPayload>(),
afterImport: new Slot<AfterImportPayload>(),
beforeExport: new Slot<BeforeExportPayload>(),
afterExport: new Slot<AfterExportPayload>(),
beforeImport: new Subject<BeforeImportPayload>(),
afterImport: new Subject<AfterImportPayload>(),
beforeExport: new Subject<BeforeExportPayload>(),
afterExport: new Subject<AfterExportPayload>(),
};
blockToSnapshot = (
@@ -99,7 +99,7 @@ export class Transformer {
docToSnapshot = (doc: Store): DocSnapshot | undefined => {
try {
this._slots.beforeExport.emit({
this._slots.beforeExport.next({
type: 'page',
page: doc,
});
@@ -120,7 +120,7 @@ export class Transformer {
meta,
blocks,
};
this._slots.afterExport.emit({
this._slots.afterExport.next({
type: 'page',
page: doc,
snapshot: docSnapshot,
@@ -137,7 +137,7 @@ export class Transformer {
sliceToSnapshot = (slice: Slice): SliceSnapshot | undefined => {
try {
this._slots.beforeExport.emit({
this._slots.beforeExport.next({
type: 'slice',
slice,
});
@@ -156,7 +156,7 @@ export class Transformer {
pageId,
content: contentSnapshot,
};
this._slots.afterExport.emit({
this._slots.afterExport.next({
type: 'slice',
slice,
snapshot,
@@ -191,7 +191,7 @@ export class Transformer {
snapshotToDoc = async (snapshot: DocSnapshot): Promise<Store | undefined> => {
try {
this._slots.beforeImport.emit({
this._slots.beforeImport.next({
type: 'page',
snapshot,
});
@@ -200,7 +200,7 @@ export class Transformer {
const doc = this.docCRUD.create(meta.id);
doc.load();
await this.snapshotToBlock(blocks, doc);
this._slots.afterImport.emit({
this._slots.afterImport.next({
type: 'page',
snapshot,
page: doc,
@@ -247,7 +247,7 @@ export class Transformer {
): Promise<Slice | undefined> => {
SliceSnapshotSchema.parse(snapshot);
try {
this._slots.beforeImport.emit({
this._slots.beforeImport.next({
type: 'slice',
snapshot,
});
@@ -303,7 +303,7 @@ export class Transformer {
pageId,
});
this._slots.afterImport.emit({
this._slots.afterImport.next({
type: 'slice',
snapshot,
slice,
@@ -376,7 +376,7 @@ export class Transformer {
}
private _blockToSnapshot(model: DraftModel): BlockSnapshot | null {
this._slots.beforeExport.emit({
this._slots.beforeExport.next({
type: 'block',
model,
});
@@ -397,7 +397,7 @@ export class Transformer {
...snapshotLeaf,
children,
};
this._slots.afterExport.emit({
this._slots.afterExport.next({
type: 'block',
model,
snapshot,
@@ -539,7 +539,7 @@ export class Transformer {
);
}
this._slots.afterImport.emit({
this._slots.afterImport.next({
type: 'block',
model,
snapshot: node.snapshot,
@@ -627,7 +627,7 @@ export class Transformer {
parent?: string,
index?: number
) => {
this._slots.beforeImport.emit({
this._slots.beforeImport.next({
type: 'block',
snapshot: node,
parent: parent,
+1
View File
@@ -17,6 +17,7 @@
"idb": "^8.0.0",
"idb-keyval": "^6.2.1",
"lodash-es": "^4.17.21",
"rxjs": "^7.8.1",
"y-protocols": "^1.0.6"
},
"devDependencies": {
+10 -10
View File
@@ -1,5 +1,5 @@
import { Slot } from '@blocksuite/global/slot';
import type { Logger } from '@blocksuite/global/utils';
import { Subject } from 'rxjs';
import type { Doc } from 'yjs';
import { SharedPriorityTarget } from '../utils/async-queue.js';
@@ -49,7 +49,7 @@ export class DocEngine {
private _status: DocEngineStatus;
readonly onStatusChange = new Slot<DocEngineStatus>();
readonly onStatusChange = new Subject<DocEngineStatus>();
readonly priorityTarget = new SharedPriorityTarget();
@@ -79,7 +79,7 @@ export class DocEngine {
private setStatus(s: DocEngineStatus) {
this.logger.debug(`syne-engine:${this.rootDocId} status change`, s);
this._status = s;
this.onStatusChange.emit(s);
this.onStatusChange.next(s);
}
canGracefulStop() {
@@ -133,10 +133,10 @@ export class DocEngine {
);
cleanUp.push(
state.mainPeer.onStatusChange.on(() => {
state.mainPeer.onStatusChange.subscribe(() => {
if (!signal.aborted)
this.updateSyncingState(state.mainPeer, state.shadowPeers);
}).dispose
}).unsubscribe
);
this.updateSyncingState(state.mainPeer, state.shadowPeers);
@@ -153,10 +153,10 @@ export class DocEngine {
this.logger
);
cleanUp.push(
peer.onStatusChange.on(() => {
peer.onStatusChange.subscribe(() => {
if (!signal.aborted)
this.updateSyncingState(state.mainPeer, state.shadowPeers);
}).dispose
}).unsubscribe
);
return peer;
});
@@ -221,7 +221,7 @@ export class DocEngine {
});
}),
new Promise<void>(resolve => {
this.onStatusChange.on(() => {
this.onStatusChange.subscribe(() => {
if (this.canGracefulStop()) {
resolve();
}
@@ -243,7 +243,7 @@ export class DocEngine {
} else {
return Promise.race([
new Promise<void>(resolve => {
this.onStatusChange.on(status => {
this.onStatusChange.subscribe(status => {
if (isLoadedRootDoc(status)) {
resolve();
}
@@ -267,7 +267,7 @@ export class DocEngine {
} else {
return Promise.race([
new Promise<void>(resolve => {
this.onStatusChange.on(status => {
this.onStatusChange.subscribe(status => {
if (status.step === DocEngineStep.Synced) {
resolve();
}
+41 -41
View File
@@ -1,6 +1,6 @@
import { Slot } from '@blocksuite/global/slot';
import type { Logger } from '@blocksuite/global/utils';
import isEqual from 'lodash-es/isEqual';
import { Subject } from 'rxjs';
import type { Doc } from 'yjs';
import {
applyUpdate,
@@ -110,7 +110,7 @@ export class SyncPeer {
this.updateSyncStatus();
};
readonly onStatusChange = new Slot<DocPeerStatus>();
readonly onStatusChange = new Subject<DocPeerStatus>();
readonly state: {
connectedDocs: Map<string, Doc>;
@@ -142,7 +142,7 @@ export class SyncPeer {
if (!isEqual(s, this._status)) {
this.logger.debug(`doc-peer:${this.name} status change`, s);
this._status = s;
this.onStatusChange.emit(s);
this.onStatusChange.next(s);
}
}
@@ -402,50 +402,50 @@ export class SyncPeer {
}
async waitForLoaded(abort?: AbortSignal) {
if (this.status.step > DocPeerStep.Loaded) {
if (this.status.step >= DocPeerStep.LoadingSubDoc) {
return;
} else {
return Promise.race([
new Promise<void>(resolve => {
this.onStatusChange.on(status => {
if (status.step > DocPeerStep.Loaded) {
resolve();
}
});
}),
new Promise((_, reject) => {
if (abort?.aborted) {
reject(abort?.reason);
}
abort?.addEventListener('abort', () => {
reject(abort.reason);
});
}),
]);
}
await Promise.race([
new Promise<void>(resolve => {
this.onStatusChange.subscribe(status => {
if (status.step >= DocPeerStep.LoadingSubDoc) {
resolve();
}
});
}),
new Promise((_, reject) => {
if (abort?.aborted) {
reject(abort.reason);
}
abort?.addEventListener('abort', () => {
reject(abort.reason);
});
}),
]);
throwIfAborted(abort);
}
async waitForSynced(abort?: AbortSignal) {
if (this.status.step >= DocPeerStep.Synced) {
if (this.status.step === DocPeerStep.Synced) {
return;
} else {
return Promise.race([
new Promise<void>(resolve => {
this.onStatusChange.on(status => {
if (status.step >= DocPeerStep.Synced) {
resolve();
}
});
}),
new Promise((_, reject) => {
if (abort?.aborted) {
reject(abort?.reason);
}
abort?.addEventListener('abort', () => {
reject(abort.reason);
});
}),
]);
}
await Promise.race([
new Promise<void>(resolve => {
this.onStatusChange.subscribe(status => {
if (status.step === DocPeerStep.Synced) {
resolve();
}
});
}),
new Promise((_, reject) => {
if (abort?.aborted) {
reject(abort.reason);
}
abort?.addEventListener('abort', () => {
reject(abort.reason);
});
}),
]);
throwIfAborted(abort);
}
}