mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-01 01:29:31 +08:00
chore: merge blocksuite source code (#9213)
This commit is contained in:
@@ -0,0 +1,289 @@
|
||||
import { RemoteCursor } from '@blocksuite/affine-components/icons';
|
||||
import type { RootBlockModel } from '@blocksuite/affine-model';
|
||||
import { requestThrottledConnectedFrame } from '@blocksuite/affine-shared/utils';
|
||||
import { WidgetComponent } from '@blocksuite/block-std';
|
||||
import { assertExists, pickValues } from '@blocksuite/global/utils';
|
||||
import type { UserInfo } from '@blocksuite/store';
|
||||
import { css, html } from 'lit';
|
||||
import { state } from 'lit/decorators.js';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
|
||||
import type { EdgelessRootBlockComponent } from '../../../root-block/edgeless/edgeless-root-block.js';
|
||||
import {
|
||||
getSelectedRect,
|
||||
isTopLevelBlock,
|
||||
} from '../../../root-block/edgeless/utils/query.js';
|
||||
import { RemoteColorManager } from '../../../root-block/remote-color-manager/remote-color-manager.js';
|
||||
|
||||
export const AFFINE_EDGELESS_REMOTE_SELECTION_WIDGET =
|
||||
'affine-edgeless-remote-selection-widget';
|
||||
|
||||
export class EdgelessRemoteSelectionWidget extends WidgetComponent<
|
||||
RootBlockModel,
|
||||
EdgelessRootBlockComponent
|
||||
> {
|
||||
static override styles = css`
|
||||
:host {
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
transform-origin: left top;
|
||||
contain: size layout;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.remote-rect {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
border-width: 3px;
|
||||
z-index: 1;
|
||||
transform-origin: center center;
|
||||
}
|
||||
|
||||
.remote-cursor {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
transform-origin: left top;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.remote-cursor > svg {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.remote-username {
|
||||
margin-left: 22px;
|
||||
margin-top: -2px;
|
||||
|
||||
color: white;
|
||||
|
||||
max-width: 160px;
|
||||
padding: 0px 3px;
|
||||
border: 1px solid var(--affine-pure-black-20);
|
||||
|
||||
box-shadow: 0px 1px 6px 0px rgba(0, 0, 0, 0.16);
|
||||
border-radius: 4px;
|
||||
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
`;
|
||||
|
||||
private _remoteColorManager: RemoteColorManager | null = null;
|
||||
|
||||
private _updateOnElementChange = (element: string | { id: string }) => {
|
||||
const id = typeof element === 'string' ? element : element.id;
|
||||
|
||||
if (this.isConnected && this.selection.hasRemote(id))
|
||||
this._updateRemoteRects();
|
||||
};
|
||||
|
||||
private _updateRemoteCursor = () => {
|
||||
const remoteCursors: EdgelessRemoteSelectionWidget['_remoteCursors'] =
|
||||
new Map();
|
||||
const status = this.doc.awarenessStore.getStates();
|
||||
|
||||
this.selection.remoteCursorSelectionMap.forEach(
|
||||
(cursorSelection, clientId) => {
|
||||
remoteCursors.set(clientId, {
|
||||
x: cursorSelection.x,
|
||||
y: cursorSelection.y,
|
||||
user: status.get(clientId)?.user,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
this._remoteCursors = remoteCursors;
|
||||
};
|
||||
|
||||
private _updateRemoteRects = () => {
|
||||
const { selection, block } = this;
|
||||
const remoteSelectionsMap = selection.remoteSurfaceSelectionsMap;
|
||||
const remoteRects: EdgelessRemoteSelectionWidget['_remoteRects'] =
|
||||
new Map();
|
||||
|
||||
remoteSelectionsMap.forEach((selections, clientId) => {
|
||||
selections.forEach(selection => {
|
||||
if (selection.elements.length === 0) return;
|
||||
|
||||
const elements = selection.elements
|
||||
.map(id => block.service.getElementById(id))
|
||||
.filter(element => element) as BlockSuite.EdgelessModel[];
|
||||
const rect = getSelectedRect(elements);
|
||||
|
||||
if (rect.width === 0 || rect.height === 0) return;
|
||||
|
||||
const { left, top } = rect;
|
||||
const [width, height] = [rect.width, rect.height];
|
||||
|
||||
let rotate = 0;
|
||||
if (elements.length === 1) {
|
||||
const element = elements[0];
|
||||
if (!isTopLevelBlock(element)) {
|
||||
rotate = element.rotate ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
remoteRects.set(clientId, {
|
||||
width,
|
||||
height,
|
||||
borderStyle: 'solid',
|
||||
left,
|
||||
top,
|
||||
rotate,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
this._remoteRects = remoteRects;
|
||||
};
|
||||
|
||||
private _updateTransform = requestThrottledConnectedFrame(() => {
|
||||
const { translateX, translateY, zoom } = this.edgeless.service.viewport;
|
||||
|
||||
this.style.setProperty('--v-zoom', `${zoom}`);
|
||||
|
||||
this.style.setProperty(
|
||||
'transform',
|
||||
`translate(${translateX}px, ${translateY}px) scale(var(--v-zoom))`
|
||||
);
|
||||
}, this);
|
||||
|
||||
get edgeless() {
|
||||
return this.block;
|
||||
}
|
||||
|
||||
get selection() {
|
||||
return this.edgeless.service.selection;
|
||||
}
|
||||
|
||||
get surface() {
|
||||
return this.edgeless.surface;
|
||||
}
|
||||
|
||||
override connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
const { _disposables, doc, edgeless } = this;
|
||||
|
||||
pickValues(edgeless.service.surface, [
|
||||
'elementAdded',
|
||||
'elementRemoved',
|
||||
'elementUpdated',
|
||||
]).forEach(slot => {
|
||||
_disposables.add(slot.on(this._updateOnElementChange));
|
||||
});
|
||||
|
||||
_disposables.add(doc.slots.blockUpdated.on(this._updateOnElementChange));
|
||||
|
||||
_disposables.add(
|
||||
this.selection.slots.remoteUpdated.on(this._updateRemoteRects)
|
||||
);
|
||||
_disposables.add(
|
||||
this.selection.slots.remoteCursorUpdated.on(this._updateRemoteCursor)
|
||||
);
|
||||
|
||||
_disposables.add(
|
||||
edgeless.service.viewport.viewportUpdated.on(() => {
|
||||
this._updateTransform();
|
||||
})
|
||||
);
|
||||
|
||||
this._updateTransform();
|
||||
this._updateRemoteRects();
|
||||
|
||||
this._remoteColorManager = new RemoteColorManager(this.std);
|
||||
}
|
||||
|
||||
override render() {
|
||||
const { _remoteRects, _remoteCursors, _remoteColorManager } = this;
|
||||
assertExists(_remoteColorManager);
|
||||
|
||||
const rects = repeat(
|
||||
_remoteRects.entries(),
|
||||
value => value[0],
|
||||
([id, rect]) =>
|
||||
html`<div
|
||||
data-client-id=${id}
|
||||
class="remote-rect"
|
||||
style=${styleMap({
|
||||
pointerEvents: 'none',
|
||||
width: `${rect.width}px`,
|
||||
height: `${rect.height}px`,
|
||||
borderStyle: rect.borderStyle,
|
||||
borderColor: _remoteColorManager.get(id),
|
||||
transform: `translate(${rect.left}px, ${rect.top}px) rotate(${rect.rotate}deg)`,
|
||||
})}
|
||||
></div>`
|
||||
);
|
||||
|
||||
const cursors = repeat(
|
||||
_remoteCursors.entries(),
|
||||
value => value[0],
|
||||
([id, cursor]) => {
|
||||
return html`<div
|
||||
data-client-id=${id}
|
||||
class="remote-cursor"
|
||||
style=${styleMap({
|
||||
pointerEvents: 'none',
|
||||
transform: `translate(${cursor.x}px, ${cursor.y}px) scale(calc(1/var(--v-zoom)))`,
|
||||
color: _remoteColorManager.get(id),
|
||||
})}
|
||||
>
|
||||
${RemoteCursor}
|
||||
<div
|
||||
class="remote-username"
|
||||
style=${styleMap({
|
||||
backgroundColor: _remoteColorManager.get(id),
|
||||
})}
|
||||
>
|
||||
${cursor.user?.name ?? 'Unknown'}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
);
|
||||
|
||||
return html`
|
||||
<div class="affine-edgeless-remote-selection">${rects}${cursors}</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@state()
|
||||
private accessor _remoteCursors: Map<
|
||||
number,
|
||||
{
|
||||
x: number;
|
||||
y: number;
|
||||
user?: UserInfo | undefined;
|
||||
}
|
||||
> = new Map();
|
||||
|
||||
@state()
|
||||
private accessor _remoteRects: Map<
|
||||
number,
|
||||
{
|
||||
width: number;
|
||||
height: number;
|
||||
borderStyle: string;
|
||||
left: number;
|
||||
top: number;
|
||||
rotate: number;
|
||||
}
|
||||
> = new Map();
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
[AFFINE_EDGELESS_REMOTE_SELECTION_WIDGET]: EdgelessRemoteSelectionWidget;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user