UNPKG

open-collaboration-monaco

Version:

Connect a single Monaco Editor to an Open Collaboration Tools session

134 lines (119 loc) 4.63 kB
// ****************************************************************************** // Copyright 2024 TypeFox GmbH // This program and the accompanying materials are made available under the // terms of the MIT License, which is available in the project root. // ****************************************************************************** import * as types from 'open-collaboration-protocol'; import * as awarenessProtocol from 'y-protocols/awareness'; type PeerDecorationOptions = { selectionClassName: string; cursorClassName: string; cursorInvertedClassName: string; }; export class DisposablePeer { readonly peer: types.Peer; color: string | undefined; private yjsAwareness: awarenessProtocol.Awareness; readonly decoration: PeerDecorationOptions; get clientId(): number | undefined { const states = this.yjsAwareness.getStates() as Map<number, types.ClientAwareness>; for (const [clientID, state] of states.entries()) { if (state.peer === this.peer.id) { return clientID; } } return undefined; } get lastUpdated(): number | undefined { const clientId = this.clientId; if (clientId !== undefined) { const meta = this.yjsAwareness.meta.get(clientId); if (meta) { return meta.lastUpdated; } } return undefined; } constructor(yAwareness: awarenessProtocol.Awareness, peer: types.Peer) { this.peer = peer; this.yjsAwareness = yAwareness; this.decoration = this.createDecorations(); } private createDecorations(): PeerDecorationOptions { const color = createColor(); const colorCss = typeof color === 'string' ? color : `rgb(${color[0]}, ${color[1]}, ${color[2]})`; this.color = colorCss; const className = `peer-${this.peer.id}`; const cursorClassName = `${className}-cursor`; const cursorInvertedClassName = `${className}-cursor-inverted`; const selectionClassName = `${className}-selection`; const cursorCss = `.${cursorClassName} { background-color: ${colorCss} !important; border-color: ${colorCss} !important; position: absolute; border-right: solid 2px; border-top: solid 2px; border-bottom: solid 2px; height: 100%; box-sizing: border-box; }`; generateCSS(cursorCss); const cursorAfterCss = `.${cursorClassName}::after { content: "${this.peer.name}"; position: absolute; transform: translateY(-100%); padding: 0 4px; border-radius: 4px 4px 4px 0px; background-color: ${colorCss}; }`; generateCSS(cursorAfterCss); const cursorAfterInvertedCss = `.${cursorClassName}.${cursorInvertedClassName}::after { transform: translateY(100%); margin-top: -2px; border-radius: 0px 4px 4px 4px; z-index: 1; }`; generateCSS(cursorAfterInvertedCss); const selectionCss = `.${selectionClassName} { background: ${colorCss} !important; opacity: 0.25; }`; generateCSS(selectionCss); return { cursorClassName, cursorInvertedClassName, selectionClassName }; } } let colorIndex = 0; const defaultColors: Array<[number, number, number] | string> = [ 'yellow', // Yellow 'green', // Green 'magenta', // Magenta 'lightGreen', // Light green [255, 178, 123], // Light orange [255, 157, 242], // Light magenta [92, 45, 145], // Purple [0, 178, 148], // Light teal [255, 241, 0], // Light yellow [180, 160, 255] // Light purple ]; const knownColors = new Set<string>(); function createColor(): [number, number, number] | string { if (colorIndex < defaultColors.length) { return defaultColors[colorIndex++]; } const o = Math.round, r = Math.random, s = 255; let color: [number, number, number]; do { color = [o(r() * s), o(r() * s), o(r() * s)]; } while (knownColors.has(JSON.stringify(color))); knownColors.add(JSON.stringify(color)); return color; } function generateCSS(cssText: string) { const style: HTMLStyleElement = document.createElement('style'); style.textContent = cssText; document.head.appendChild(style); }