mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-19 19:16:29 +08:00
feat(editor): replace slot with rxjs subject (#10768)
This commit is contained in:
@@ -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';
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
+29
-23
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user