Skip to content

Commit

Permalink
Always keep session member view up-to-date (#40)
Browse files Browse the repository at this point in the history
  • Loading branch information
msujew authored Nov 19, 2024
1 parent d2ce847 commit 596ca0c
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 56 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import * as vscode from 'vscode';
import { inject, injectable } from 'inversify';
import { ConnectionProvider, SocketIoTransportProvider } from 'open-collaboration-protocol';
import { ExtensionContext } from './inversify';
import { version } from '../package.json';
import { packageVersion } from './utils/package';

export const OCT_USER_TOKEN = 'oct.userToken';

Expand All @@ -30,7 +30,7 @@ export class CollaborationConnectionProvider {
if (serverUrl) {
return new ConnectionProvider({
url: serverUrl,
client: 'OCT-VSCode@' + version,
client: `OCT_CODE_${vscode.env.appName.replace(/\s+/, '_')}@${packageVersion}`,
opener: (url) => vscode.env.openExternal(vscode.Uri.parse(url)),
transports: [SocketIoTransportProvider],
userToken,
Expand Down
57 changes: 25 additions & 32 deletions packages/open-collaboration-vscode/src/collaboration-instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ import { inject, injectable, postConstruct } from 'inversify';
import { removeWorkspaceFolders } from './utils/workspace';
import { Mutex } from 'async-mutex';
import { CollaborationUri } from './utils/uri';
import { userColors } from './utils/package';

export interface PeerWithColor extends types.Peer {
color?: string;
}

export class DisposablePeer implements vscode.Disposable {

Expand Down Expand Up @@ -55,8 +60,8 @@ export class DisposablePeer implements vscode.Disposable {
}

private createDecorationType(): ClientTextEditorDecorationType {
const color = createColor();
const colorCss = typeof color === 'string' ? `var(--vscode-${color.replaceAll('.', '-')})` : `rgb(${color[0]}, ${color[1]}, ${color[2]})`;
const color = nextColor();
const colorCss = `var(--vscode-${color.replaceAll('.', '-')})`;
const selection: vscode.DecorationRenderOptions = {
backgroundColor: `color-mix(in srgb, ${colorCss} 25%, transparent)`,
borderRadius: '0.1em'
Expand Down Expand Up @@ -110,31 +115,10 @@ export class DisposablePeer implements vscode.Disposable {
}

let colorIndex = 0;
const defaultColors: ([number, number, number] | string)[] = [
'oct.user.yellow', // Yellow
'oct.user.green', // Green
'oct.user.magenta', // Magenta
'oct.user.lightGreen', // Light green
'oct.user.lightOrange', // Light orange
'oct.user.lightMagenta', // 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 nextColor(): string {
colorIndex %= userColors.length;
return userColors[colorIndex++];
}

export class ClientTextEditorDecorationType implements vscode.Disposable {
Expand All @@ -146,7 +130,7 @@ export class ClientTextEditorDecorationType implements vscode.Disposable {
default: vscode.TextEditorDecorationType,
inverted: vscode.TextEditorDecorationType
},
readonly color: [number, number, number] | string
readonly color: string
) {
this.toDispose = vscode.Disposable.from(
before, after,
Expand All @@ -160,7 +144,7 @@ export class ClientTextEditorDecorationType implements vscode.Disposable {
}

getThemeColor(): vscode.ThemeColor | undefined {
return typeof this.color === 'string' ? new vscode.ThemeColor(this.color) : undefined;
return new vscode.ThemeColor(this.color);
}
}

Expand Down Expand Up @@ -205,8 +189,15 @@ export class CollaborationInstance implements vscode.Disposable {
private readonly onDidDisposeEmitter: vscode.EventEmitter<void> = new vscode.EventEmitter<void>();
readonly onDidDispose: vscode.Event<void> = this.onDidDisposeEmitter.event;

get connectedUsers(): DisposablePeer[] {
return Array.from(this.peers.values());
get connectedUsers(): Promise<PeerWithColor[]> {
return this.ownUserData.then(own => {
const all = Array.from(this.peers.values()).map(e => ({
...e.peer,
color: e.decoration.color
}) as PeerWithColor);
all.push(own);
return Array.from(all);
});
}

get ownUserData(): Promise<types.Peer> {
Expand Down Expand Up @@ -277,7 +268,6 @@ export class CollaborationInstance implements vscode.Disposable {
await this.initialize(initData);
});
connection.room.onJoin(async (_, peer) => {
this.peers.set(peer.id, new DisposablePeer(this.yjsAwareness, peer));
if (this.host) {
// Only initialize the user if we are the host
const roots = vscode.workspace.workspaceFolders ?? [];
Expand All @@ -294,6 +284,7 @@ export class CollaborationInstance implements vscode.Disposable {
};
connection.peer.init(peer.id, initData);
}
this.peers.set(peer.id, new DisposablePeer(this.yjsAwareness, peer));
this.onDidUsersChangeEmitter.fire();
});
connection.room.onLeave(async (_, peer) => {
Expand All @@ -315,6 +306,7 @@ export class CollaborationInstance implements vscode.Disposable {
connection.peer.onInfo((_, peer) => {
this.yjsAwareness.setLocalStateField('peer', peer.id);
this.identity.resolve(peer);
this.onDidUsersChangeEmitter.fire();
});

this.registerFileEvents();
Expand Down Expand Up @@ -863,5 +855,6 @@ export class CollaborationInstance implements vscode.Disposable {
}
this.fileSystem = new CollaborationFileSystemProvider(this.options.connection, this.yjs, data.host);
this.toDispose.push(vscode.workspace.registerFileSystemProvider('oct', this.fileSystem));
this.onDidUsersChangeEmitter.fire();
}
}
37 changes: 22 additions & 15 deletions packages/open-collaboration-vscode/src/collaboration-status-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,51 +5,58 @@
// ******************************************************************************

import * as vscode from 'vscode';
import { CollaborationInstance, DisposablePeer } from './collaboration-instance';
import { CollaborationInstance, PeerWithColor } from './collaboration-instance';
import { injectable } from 'inversify';

@injectable()
export class CollaborationStatusViewDataProvider implements vscode.TreeDataProvider<DisposablePeer> {
export class CollaborationStatusViewDataProvider implements vscode.TreeDataProvider<PeerWithColor> {

private onDidChangeTreeDataEmitter = new vscode.EventEmitter<DisposablePeer[] | undefined>();
private onDidChangeTreeDataEmitter = new vscode.EventEmitter<void>();
onDidChangeTreeData = this.onDidChangeTreeDataEmitter.event;

private instance: CollaborationInstance | undefined;

onConnection(instance: CollaborationInstance) {
this.instance = instance;
instance.onDidUsersChange(() => {
this.onDidChangeTreeDataEmitter.fire(undefined);
this.onDidChangeTreeDataEmitter.fire();
});
instance.onDidDispose(() => {
this.instance = undefined;
this.onDidChangeTreeDataEmitter.fire(undefined);
this.onDidChangeTreeDataEmitter.fire();
});
this.onDidChangeTreeDataEmitter.fire(undefined);
this.onDidChangeTreeDataEmitter.fire();
}

async getTreeItem(element: DisposablePeer): Promise<vscode.TreeItem> {
async getTreeItem(peer: PeerWithColor): Promise<vscode.TreeItem> {
const self = await this.instance?.ownUserData;
const you = vscode.l10n.t('You');
const treeItem = new vscode.TreeItem(element.peer.id === self?.id ? `${element.peer.name} (${you})` : element.peer.name);
treeItem.id = element.peer.id;
const treeItem = new vscode.TreeItem(peer.name);
const tags: string[] = [];
if (peer.id === self?.id) {
tags.push(vscode.l10n.t('You'));
}
if (peer.host) {
tags.push(vscode.l10n.t('Host'));
}
treeItem.description = tags.length ? ('(' + tags.join(' • ') + ')') : undefined;
treeItem.contextValue = 'self';
if (self?.id !== element.peer.id) {
treeItem.iconPath = new vscode.ThemeIcon('circle-filled', element.decoration.getThemeColor());
treeItem.contextValue = this.instance?.following === treeItem.id ? 'followedPeer' : 'peer';
if (self?.id !== peer.id) {
const themeColor = peer.color ? new vscode.ThemeColor(peer.color) : undefined;
treeItem.iconPath = new vscode.ThemeIcon('circle-filled', themeColor);
treeItem.contextValue = this.instance?.following === peer.id ? 'followedPeer' : 'peer';
}
return treeItem;
}

getChildren(element?: DisposablePeer): vscode.ProviderResult<DisposablePeer[]> {
getChildren(element?: PeerWithColor): vscode.ProviderResult<PeerWithColor[]> {
if (!element && this.instance) {
return this.instance.connectedUsers;
}
return [];
}

update() {
this.onDidChangeTreeDataEmitter.fire(undefined);
this.onDidChangeTreeDataEmitter.fire();
}

}
4 changes: 2 additions & 2 deletions packages/open-collaboration-vscode/src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import * as vscode from 'vscode';
import { inject, injectable } from 'inversify';
import { FollowService } from './follow-service';
import { CollaborationInstance, DisposablePeer } from './collaboration-instance';
import { CollaborationInstance, PeerWithColor } from './collaboration-instance';
import { ExtensionContext } from './inversify';
import { CollaborationConnectionProvider, OCT_USER_TOKEN } from './collaboration-connection-provider';
import { QuickPickItem, showQuickPick } from './utils/quick-pick';
Expand Down Expand Up @@ -40,7 +40,7 @@ export class Commands {

initialize(): void {
this.context.subscriptions.push(
vscode.commands.registerCommand('oct.followPeer', (peer?: DisposablePeer) => this.followService.followPeer(peer)),
vscode.commands.registerCommand('oct.followPeer', (peer?: PeerWithColor) => this.followService.followPeer(peer?.id)),
vscode.commands.registerCommand('oct.stopFollowPeer', () => this.followService.unfollowPeer()),
vscode.commands.registerCommand('oct.enter', async () => {
this.withConnectionProvider(async connectionProvider => {
Expand Down
10 changes: 5 additions & 5 deletions packages/open-collaboration-vscode/src/follow-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// ******************************************************************************

import { inject, injectable } from 'inversify';
import { CollaborationInstance, DisposablePeer } from './collaboration-instance';
import { CollaborationInstance } from './collaboration-instance';
import { showQuickPick } from './utils/quick-pick';
import { CollaborationStatusViewDataProvider } from './collaboration-status-view';
import { ContextKeyService } from './context-key-service';
Expand All @@ -19,22 +19,22 @@ export class FollowService {
@inject(ContextKeyService)
private contextKeyService: ContextKeyService;

async followPeer(peer?: DisposablePeer): Promise<void> {
async followPeer(peer?: string): Promise<void> {
if (!CollaborationInstance.Current) {
return;
}

if (!peer) {
const users = CollaborationInstance.Current.connectedUsers;
const items = users.map(user => ({ key: user, label: user.peer.name, detail: user.peer.id }));
const users = await CollaborationInstance.Current.connectedUsers;
const items = users.map(user => ({ label: user.name, detail: user.id, key: user.id }));
peer = await showQuickPick(items);
}

if (!peer) {
return;
}

CollaborationInstance.Current.followUser(peer.peer.id);
CollaborationInstance.Current.followUser(peer);
this.viewDataProvider.update();
this.contextKeyService.setFollowing(true);
}
Expand Down
13 changes: 13 additions & 0 deletions packages/open-collaboration-vscode/src/utils/package.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// ******************************************************************************
// 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 packageJson from '../../package.json';

export { packageJson };
export const packageVersion: string = packageJson.version;
export const userColors: string[] = packageJson.contributes.colors
.map((color: any) => color.id)
.filter((id: string) => id.startsWith('oct.user.'));

0 comments on commit 596ca0c

Please sign in to comment.