fix(editor): remote selection refresh rate (#9543)
Closes: [BS-2307](https://linear.app/affine-design/issue/BS-2307/%E7%90%86%E8%AE%BA%E4%B8%8A%E5%85%89%E6%A0%87%E5%BA%94%E8%AF%A5%E4%B8%8D%E6%B6%88%E5%A4%B1%EF%BC%8C%E4%B8%80%E7%9B%B4%E6%98%BE%E7%A4%BA)
This commit is contained in:
@@ -5,10 +5,11 @@ import {
|
|||||||
TextSelection,
|
TextSelection,
|
||||||
WidgetComponent,
|
WidgetComponent,
|
||||||
} from '@blocksuite/block-std';
|
} from '@blocksuite/block-std';
|
||||||
import { assertExists } from '@blocksuite/global/utils';
|
import { throttle } from '@blocksuite/global/utils';
|
||||||
import type { UserInfo } from '@blocksuite/store';
|
import type { UserInfo } from '@blocksuite/store';
|
||||||
import { computed } from '@preact/signals-core';
|
import { computed, effect } from '@preact/signals-core';
|
||||||
import { css, html, nothing } from 'lit';
|
import { css, html, nothing, type PropertyValues } from 'lit';
|
||||||
|
import { state } from 'lit/decorators.js';
|
||||||
import { styleMap } from 'lit/directives/style-map.js';
|
import { styleMap } from 'lit/directives/style-map.js';
|
||||||
|
|
||||||
import { RemoteColorManager } from '../manager/remote-color-manager';
|
import { RemoteColorManager } from '../manager/remote-color-manager';
|
||||||
@@ -34,6 +35,14 @@ export class AffineDocRemoteSelectionWidget extends WidgetComponent {
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
private accessor _selections: Array<{
|
||||||
|
id: number;
|
||||||
|
selections: BaseSelection[];
|
||||||
|
rects: SelectionRect[];
|
||||||
|
user?: UserInfo;
|
||||||
|
}> = [];
|
||||||
|
|
||||||
private readonly _abortController = new AbortController();
|
private readonly _abortController = new AbortController();
|
||||||
|
|
||||||
private _remoteColorManager: RemoteColorManager | null = null;
|
private _remoteColorManager: RemoteColorManager | null = null;
|
||||||
@@ -84,6 +93,47 @@ export class AffineDocRemoteSelectionWidget extends WidgetComponent {
|
|||||||
return this.host.selection;
|
return this.host.selection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _getTextRange(textSelection: TextSelection): Range | null {
|
||||||
|
const toBlockId = textSelection.to
|
||||||
|
? textSelection.to.blockId
|
||||||
|
: textSelection.from.blockId;
|
||||||
|
|
||||||
|
let range = this.std.range.textSelectionToRange(
|
||||||
|
this._selectionManager.create(TextSelection, {
|
||||||
|
from: {
|
||||||
|
blockId: toBlockId,
|
||||||
|
index: textSelection.to
|
||||||
|
? textSelection.to.index + textSelection.to.length
|
||||||
|
: textSelection.from.index + textSelection.from.length,
|
||||||
|
length: 0,
|
||||||
|
},
|
||||||
|
to: null,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!range) {
|
||||||
|
// If no range, maybe the block is not updated yet
|
||||||
|
// We just set the range to the end of the block
|
||||||
|
const block = this.std.view.getBlock(toBlockId);
|
||||||
|
if (!block) return null;
|
||||||
|
|
||||||
|
range = this.std.range.textSelectionToRange(
|
||||||
|
this._selectionManager.create(TextSelection, {
|
||||||
|
from: {
|
||||||
|
blockId: toBlockId,
|
||||||
|
index: block.model.text?.length ?? 0,
|
||||||
|
length: 0,
|
||||||
|
},
|
||||||
|
to: null,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!range) return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return range;
|
||||||
|
}
|
||||||
|
|
||||||
private _getCursorRect(selections: BaseSelection[]): SelectionRect | null {
|
private _getCursorRect(selections: BaseSelection[]): SelectionRect | null {
|
||||||
if (this.block.model.flavour !== 'affine:page') {
|
if (this.block.model.flavour !== 'affine:page') {
|
||||||
console.error('remote selection widget must be used in page component');
|
console.error('remote selection widget must be used in page component');
|
||||||
@@ -100,24 +150,8 @@ export class AffineDocRemoteSelectionWidget extends WidgetComponent {
|
|||||||
const containerRect = this._containerRect;
|
const containerRect = this._containerRect;
|
||||||
|
|
||||||
if (textSelection) {
|
if (textSelection) {
|
||||||
const range = this.std.range.textSelectionToRange(
|
const range = this._getTextRange(textSelection);
|
||||||
this._selectionManager.create(TextSelection, {
|
if (!range) return null;
|
||||||
from: {
|
|
||||||
blockId: textSelection.to
|
|
||||||
? textSelection.to.blockId
|
|
||||||
: textSelection.from.blockId,
|
|
||||||
index: textSelection.to
|
|
||||||
? textSelection.to.index + textSelection.to.length
|
|
||||||
: textSelection.from.index + textSelection.from.length,
|
|
||||||
length: 0,
|
|
||||||
},
|
|
||||||
to: null,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!range) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const container = this._container;
|
const container = this._container;
|
||||||
const containerRect = this._containerRect;
|
const containerRect = this._containerRect;
|
||||||
@@ -162,7 +196,9 @@ export class AffineDocRemoteSelectionWidget extends WidgetComponent {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getSelectionRect(selections: BaseSelection[]): SelectionRect[] {
|
private readonly _getSelectionRect = (
|
||||||
|
selections: BaseSelection[]
|
||||||
|
): SelectionRect[] => {
|
||||||
if (this.block.model.flavour !== 'affine:page') {
|
if (this.block.model.flavour !== 'affine:page') {
|
||||||
console.error('remote selection widget must be used in page component');
|
console.error('remote selection widget must be used in page component');
|
||||||
return [];
|
return [];
|
||||||
@@ -199,7 +235,7 @@ export class AffineDocRemoteSelectionWidget extends WidgetComponent {
|
|||||||
transparent: isTransparent,
|
transparent: isTransparent,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
override connectedCallback() {
|
override connectedCallback() {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
@@ -221,18 +257,10 @@ export class AffineDocRemoteSelectionWidget extends WidgetComponent {
|
|||||||
this._abortController.abort();
|
this._abortController.abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
override render() {
|
private readonly _updateSelections = throttle(
|
||||||
if (this._remoteSelections.value.length === 0) {
|
(selections: typeof this._remoteSelections.value) => {
|
||||||
return nothing;
|
|
||||||
}
|
|
||||||
|
|
||||||
const remoteUsers = new Set<number>();
|
const remoteUsers = new Set<number>();
|
||||||
const selections: Array<{
|
this._selections = selections.flatMap(({ selections, id, user }) => {
|
||||||
id: number;
|
|
||||||
selections: BaseSelection[];
|
|
||||||
rects: SelectionRect[];
|
|
||||||
user?: UserInfo;
|
|
||||||
}> = this._remoteSelections.value.flatMap(({ selections, id, user }) => {
|
|
||||||
if (remoteUsers.has(id)) {
|
if (remoteUsers.has(id)) {
|
||||||
return [];
|
return [];
|
||||||
} else {
|
} else {
|
||||||
@@ -246,11 +274,34 @@ export class AffineDocRemoteSelectionWidget extends WidgetComponent {
|
|||||||
user,
|
user,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
60
|
||||||
|
);
|
||||||
|
|
||||||
|
protected override firstUpdated(_changedProperties: PropertyValues): void {
|
||||||
|
this.disposables.add(
|
||||||
|
effect(() => {
|
||||||
|
const selections = this._remoteSelections.value;
|
||||||
|
this._updateSelections(selections);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
this.disposables.add(
|
||||||
|
this.std.doc.slots.blockUpdated.on(() => {
|
||||||
|
this._updateSelections(this._remoteSelections.peek());
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
override render() {
|
||||||
|
if (this._selections.length === 0) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
|
||||||
const remoteColorManager = this._remoteColorManager;
|
const remoteColorManager = this._remoteColorManager;
|
||||||
assertExists(remoteColorManager);
|
if (!remoteColorManager) return nothing;
|
||||||
return html`<div>
|
return html`<div>
|
||||||
${selections.flatMap(selection => {
|
${this._selections.map(selection => {
|
||||||
const color = remoteColorManager.get(selection.id);
|
const color = remoteColorManager.get(selection.id);
|
||||||
if (!color) return [];
|
if (!color) return [];
|
||||||
const cursorRect = this._getCursorRect(selection.selections);
|
const cursorRect = this._getCursorRect(selection.selections);
|
||||||
|
|||||||
Reference in New Issue
Block a user