@theia/collaboration 1.53.0-next.55

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +33 -0
  2. package/lib/browser/collaboration-color-service.d.ts +27 -0
  3. package/lib/browser/collaboration-color-service.d.ts.map +1 -0
  4. package/lib/browser/collaboration-color-service.js +76 -0
  5. package/lib/browser/collaboration-color-service.js.map +1 -0
  6. package/lib/browser/collaboration-file-system-provider.d.ts +37 -0
  7. package/lib/browser/collaboration-file-system-provider.d.ts.map +1 -0
  8. package/lib/browser/collaboration-file-system-provider.js +114 -0
  9. package/lib/browser/collaboration-file-system-provider.js.map +1 -0
  10. package/lib/browser/collaboration-frontend-contribution.d.ts +43 -0
  11. package/lib/browser/collaboration-frontend-contribution.d.ts.map +1 -0
  12. package/lib/browser/collaboration-frontend-contribution.js +334 -0
  13. package/lib/browser/collaboration-frontend-contribution.js.map +1 -0
  14. package/lib/browser/collaboration-frontend-module.d.ts +4 -0
  15. package/lib/browser/collaboration-frontend-module.d.ts.map +1 -0
  16. package/lib/browser/collaboration-frontend-module.js +38 -0
  17. package/lib/browser/collaboration-frontend-module.js.map +1 -0
  18. package/lib/browser/collaboration-instance.d.ts +94 -0
  19. package/lib/browser/collaboration-instance.d.ts.map +1 -0
  20. package/lib/browser/collaboration-instance.js +806 -0
  21. package/lib/browser/collaboration-instance.js.map +1 -0
  22. package/lib/browser/collaboration-utils.d.ts +8 -0
  23. package/lib/browser/collaboration-utils.d.ts.map +1 -0
  24. package/lib/browser/collaboration-utils.js +63 -0
  25. package/lib/browser/collaboration-utils.js.map +1 -0
  26. package/lib/browser/collaboration-workspace-service.d.ts +12 -0
  27. package/lib/browser/collaboration-workspace-service.d.ts.map +1 -0
  28. package/lib/browser/collaboration-workspace-service.js +67 -0
  29. package/lib/browser/collaboration-workspace-service.js.map +1 -0
  30. package/lib/package.spec.d.ts +1 -0
  31. package/lib/package.spec.d.ts.map +1 -0
  32. package/lib/package.spec.js +26 -0
  33. package/lib/package.spec.js.map +1 -0
  34. package/package.json +58 -0
  35. package/src/browser/collaboration-color-service.ts +77 -0
  36. package/src/browser/collaboration-file-system-provider.ts +119 -0
  37. package/src/browser/collaboration-frontend-contribution.ts +327 -0
  38. package/src/browser/collaboration-frontend-module.ts +37 -0
  39. package/src/browser/collaboration-instance.ts +819 -0
  40. package/src/browser/collaboration-utils.ts +59 -0
  41. package/src/browser/collaboration-workspace-service.ts +69 -0
  42. package/src/browser/style/index.css +22 -0
  43. package/src/package.spec.ts +28 -0
@@ -0,0 +1,327 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2024 TypeFox and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import '../../src/browser/style/index.css';
18
+
19
+ import {
20
+ CancellationToken, CancellationTokenSource, Command, CommandContribution, CommandRegistry, MessageService, nls, Progress, QuickInputService, QuickPickItem
21
+ } from '@theia/core';
22
+ import { inject, injectable, optional, postConstruct } from '@theia/core/shared/inversify';
23
+ import { ConnectionProvider, SocketIoTransportProvider } from 'open-collaboration-protocol';
24
+ import { WindowService } from '@theia/core/lib/browser/window/window-service';
25
+ import { CollaborationInstance, CollaborationInstanceFactory } from './collaboration-instance';
26
+ import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
27
+ import { Deferred } from '@theia/core/lib/common/promise-util';
28
+ import { CollaborationWorkspaceService } from './collaboration-workspace-service';
29
+ import { StatusBar, StatusBarAlignment, StatusBarEntry } from '@theia/core/lib/browser/status-bar';
30
+ import { codiconArray } from '@theia/core/lib/browser/widgets/widget';
31
+ import { FrontendApplicationConfigProvider } from '@theia/core/lib/browser/frontend-application-config-provider';
32
+
33
+ export const COLLABORATION_CATEGORY = 'Collaboration';
34
+
35
+ export namespace CollaborationCommands {
36
+ export const CREATE_ROOM: Command = {
37
+ id: 'collaboration.create-room'
38
+ };
39
+ export const JOIN_ROOM: Command = {
40
+ id: 'collaboration.join-room'
41
+ };
42
+ }
43
+
44
+ export const COLLABORATION_STATUS_BAR_ID = 'statusBar.collaboration';
45
+
46
+ export const COLLABORATION_AUTH_TOKEN = 'THEIA_COLLAB_AUTH_TOKEN';
47
+ export const COLLABORATION_SERVER_URL = 'COLLABORATION_SERVER_URL';
48
+ export const DEFAULT_COLLABORATION_SERVER_URL = 'https://api.open-collab.tools/';
49
+
50
+ @injectable()
51
+ export class CollaborationFrontendContribution implements CommandContribution {
52
+
53
+ protected readonly connectionProvider = new Deferred<ConnectionProvider>();
54
+
55
+ @inject(WindowService)
56
+ protected readonly windowService: WindowService;
57
+
58
+ @inject(QuickInputService) @optional()
59
+ protected readonly quickInputService?: QuickInputService;
60
+
61
+ @inject(EnvVariablesServer)
62
+ protected readonly envVariables: EnvVariablesServer;
63
+
64
+ @inject(CollaborationWorkspaceService)
65
+ protected readonly workspaceService: CollaborationWorkspaceService;
66
+
67
+ @inject(MessageService)
68
+ protected readonly messageService: MessageService;
69
+
70
+ @inject(CommandRegistry)
71
+ protected readonly commands: CommandRegistry;
72
+
73
+ @inject(StatusBar)
74
+ protected readonly statusBar: StatusBar;
75
+
76
+ @inject(CollaborationInstanceFactory)
77
+ protected readonly collaborationInstanceFactory: CollaborationInstanceFactory;
78
+
79
+ protected currentInstance?: CollaborationInstance;
80
+
81
+ @postConstruct()
82
+ protected init(): void {
83
+ this.setStatusBarEntryDefault();
84
+ this.getCollaborationServerUrl().then(serverUrl => {
85
+ const authHandler = new ConnectionProvider({
86
+ url: serverUrl,
87
+ client: FrontendApplicationConfigProvider.get().applicationName,
88
+ fetch: window.fetch.bind(window),
89
+ opener: url => this.windowService.openNewWindow(url, { external: true }),
90
+ transports: [SocketIoTransportProvider],
91
+ userToken: localStorage.getItem(COLLABORATION_AUTH_TOKEN) ?? undefined
92
+ });
93
+ this.connectionProvider.resolve(authHandler);
94
+ }, err => this.connectionProvider.reject(err));
95
+ }
96
+
97
+ protected async onStatusDefaultClick(): Promise<void> {
98
+ const items: QuickPickItem[] = [];
99
+ if (this.workspaceService.opened) {
100
+ items.push({
101
+ label: nls.localize('theia/collaboration/createRoom', 'Create New Collaboration Session'),
102
+ iconClasses: codiconArray('add'),
103
+ execute: () => this.commands.executeCommand(CollaborationCommands.CREATE_ROOM.id)
104
+ });
105
+ }
106
+ items.push({
107
+ label: nls.localize('theia/collaboration/joinRoom', 'Join Collaboration Session'),
108
+ iconClasses: codiconArray('vm-connect'),
109
+ execute: () => this.commands.executeCommand(CollaborationCommands.JOIN_ROOM.id)
110
+ });
111
+ await this.quickInputService?.showQuickPick(items, {
112
+ placeholder: nls.localize('theia/collaboration/selectCollaboration', 'Select collaboration option')
113
+ });
114
+ }
115
+
116
+ protected async onStatusSharedClick(code: string): Promise<void> {
117
+ const items: QuickPickItem[] = [{
118
+ label: nls.localize('theia/collaboration/invite', 'Invite Others'),
119
+ detail: nls.localize('theia/collaboration/inviteDetail', 'Copy the invitation code for sharing it with others to join the session.'),
120
+ iconClasses: codiconArray('clippy'),
121
+ execute: () => this.displayCopyNotification(code)
122
+ }];
123
+ if (this.currentInstance) {
124
+ // TODO: Implement readonly mode
125
+ // if (this.currentInstance.readonly) {
126
+ // items.push({
127
+ // label: nls.localize('theia/collaboration/enableEditing', 'Enable Workspace Editing'),
128
+ // detail: nls.localize('theia/collaboration/enableEditingDetail', 'Allow collaborators to modify content in your workspace.'),
129
+ // iconClasses: codiconArray('unlock'),
130
+ // execute: () => {
131
+ // if (this.currentInstance) {
132
+ // this.currentInstance.readonly = false;
133
+ // }
134
+ // }
135
+ // });
136
+ // } else {
137
+ // items.push({
138
+ // label: nls.localize('theia/collaboration/disableEditing', 'Disable Workspace Editing'),
139
+ // detail: nls.localize('theia/collaboration/disableEditingDetail', 'Restrict others from making changes to your workspace.'),
140
+ // iconClasses: codiconArray('lock'),
141
+ // execute: () => {
142
+ // if (this.currentInstance) {
143
+ // this.currentInstance.readonly = true;
144
+ // }
145
+ // }
146
+ // });
147
+ // }
148
+ }
149
+ items.push({
150
+ label: nls.localize('theia/collaboration/end', 'End Collaboration Session'),
151
+ detail: nls.localize('theia/collaboration/endDetail', 'Terminate the session, cease content sharing, and revoke access for others.'),
152
+ iconClasses: codiconArray('circle-slash'),
153
+ execute: () => this.currentInstance?.dispose()
154
+ });
155
+ await this.quickInputService?.showQuickPick(items, {
156
+ placeholder: nls.localize('theia/collaboration/whatToDo', 'What would you like to do with other collaborators?')
157
+ });
158
+ }
159
+
160
+ protected async onStatusConnectedClick(code: string): Promise<void> {
161
+ const items: QuickPickItem[] = [{
162
+ label: nls.localize('theia/collaboration/invite', 'Invite Others'),
163
+ detail: nls.localize('theia/collaboration/inviteDetail', 'Copy the invitation code for sharing it with others to join the session.'),
164
+ iconClasses: codiconArray('clippy'),
165
+ execute: () => this.displayCopyNotification(code)
166
+ }];
167
+ items.push({
168
+ label: nls.localize('theia/collaboration/leave', 'Leave Collaboration Session'),
169
+ detail: nls.localize('theia/collaboration/leaveDetail', 'Disconnect from the current collaboration session and close the workspace.'),
170
+ iconClasses: codiconArray('circle-slash'),
171
+ execute: () => this.currentInstance?.dispose()
172
+ });
173
+ await this.quickInputService?.showQuickPick(items, {
174
+ placeholder: nls.localize('theia/collaboration/whatToDo', 'What would you like to do with other collaborators?')
175
+ });
176
+ }
177
+
178
+ protected async setStatusBarEntryDefault(): Promise<void> {
179
+ await this.setStatusBarEntry({
180
+ text: '$(codicon-live-share) ' + nls.localize('theia/collaboration/collaborate', 'Collaborate'),
181
+ tooltip: nls.localize('theia/collaboration/startSession', 'Start or join collaboration session'),
182
+ onclick: () => this.onStatusDefaultClick()
183
+ });
184
+ }
185
+
186
+ protected async setStatusBarEntryShared(code: string): Promise<void> {
187
+ await this.setStatusBarEntry({
188
+ text: '$(codicon-broadcast) ' + nls.localizeByDefault('Shared'),
189
+ tooltip: nls.localize('theia/collaboration/sharedSession', 'Shared a collaboration session'),
190
+ onclick: () => this.onStatusSharedClick(code)
191
+ });
192
+ }
193
+
194
+ protected async setStatusBarEntryConnected(code: string): Promise<void> {
195
+ await this.setStatusBarEntry({
196
+ text: '$(codicon-broadcast) ' + nls.localize('theia/collaboration/connected', 'Connected'),
197
+ tooltip: nls.localize('theia/collaboration/connectedSession', 'Connected to a collaboration session'),
198
+ onclick: () => this.onStatusConnectedClick(code)
199
+ });
200
+ }
201
+
202
+ protected async setStatusBarEntry(entry: Omit<StatusBarEntry, 'alignment'>): Promise<void> {
203
+ await this.statusBar.setElement(COLLABORATION_STATUS_BAR_ID, {
204
+ ...entry,
205
+ alignment: StatusBarAlignment.LEFT,
206
+ priority: 5
207
+ });
208
+ }
209
+
210
+ protected async getCollaborationServerUrl(): Promise<string> {
211
+ const serverUrlVariable = await this.envVariables.getValue(COLLABORATION_SERVER_URL);
212
+ return serverUrlVariable?.value || DEFAULT_COLLABORATION_SERVER_URL;
213
+ }
214
+
215
+ registerCommands(commands: CommandRegistry): void {
216
+ commands.registerCommand(CollaborationCommands.CREATE_ROOM, {
217
+ execute: async () => {
218
+ const cancelTokenSource = new CancellationTokenSource();
219
+ const progress = await this.messageService.showProgress({
220
+ text: nls.localize('theia/collaboration/creatingRoom', 'Creating Session'),
221
+ options: {
222
+ cancelable: true
223
+ }
224
+ }, () => cancelTokenSource.cancel());
225
+ try {
226
+ const authHandler = await this.connectionProvider.promise;
227
+ const roomClaim = await authHandler.createRoom({
228
+ reporter: info => progress.report({ message: info.message }),
229
+ abortSignal: this.toAbortSignal(cancelTokenSource.token)
230
+ });
231
+ if (roomClaim.loginToken) {
232
+ localStorage.setItem(COLLABORATION_AUTH_TOKEN, roomClaim.loginToken);
233
+ }
234
+ this.currentInstance?.dispose();
235
+ const connection = await authHandler.connect(roomClaim.roomToken);
236
+ this.currentInstance = this.collaborationInstanceFactory({
237
+ role: 'host',
238
+ connection
239
+ });
240
+ this.currentInstance.onDidClose(() => {
241
+ this.setStatusBarEntryDefault();
242
+ });
243
+ const roomCode = roomClaim.roomId;
244
+ this.setStatusBarEntryShared(roomCode);
245
+ this.displayCopyNotification(roomCode, true);
246
+ } catch (err) {
247
+ await this.messageService.error(nls.localize('theia/collaboration/failedCreate', 'Failed to create room: {0}', err.message));
248
+ } finally {
249
+ progress.cancel();
250
+ }
251
+ }
252
+ });
253
+ commands.registerCommand(CollaborationCommands.JOIN_ROOM, {
254
+ execute: async () => {
255
+ let joinRoomProgress: Progress | undefined;
256
+ const cancelTokenSource = new CancellationTokenSource();
257
+ try {
258
+ const authHandler = await this.connectionProvider.promise;
259
+ const id = await this.quickInputService?.input({
260
+ placeHolder: nls.localize('theia/collaboration/enterCode', 'Enter collaboration session code')
261
+ });
262
+ if (!id) {
263
+ return;
264
+ }
265
+ joinRoomProgress = await this.messageService.showProgress({
266
+ text: nls.localize('theia/collaboration/joiningRoom', 'Joining Session'),
267
+ options: {
268
+ cancelable: true
269
+ }
270
+ }, () => cancelTokenSource.cancel());
271
+ const roomClaim = await authHandler.joinRoom({
272
+ roomId: id,
273
+ reporter: info => joinRoomProgress?.report({ message: info.message }),
274
+ abortSignal: this.toAbortSignal(cancelTokenSource.token)
275
+ });
276
+ joinRoomProgress.cancel();
277
+ if (roomClaim.loginToken) {
278
+ localStorage.setItem(COLLABORATION_AUTH_TOKEN, roomClaim.loginToken);
279
+ }
280
+ this.currentInstance?.dispose();
281
+ const connection = await authHandler.connect(roomClaim.roomToken, roomClaim.host);
282
+ this.currentInstance = this.collaborationInstanceFactory({
283
+ role: 'guest',
284
+ connection
285
+ });
286
+ this.currentInstance.onDidClose(() => {
287
+ this.setStatusBarEntryDefault();
288
+ });
289
+ this.setStatusBarEntryConnected(roomClaim.roomId);
290
+ } catch (err) {
291
+ joinRoomProgress?.cancel();
292
+ await this.messageService.error(nls.localize('theia/collaboration/failedJoin', 'Failed to join room: {0}', err.message));
293
+ }
294
+ }
295
+ });
296
+ }
297
+
298
+ protected toAbortSignal(...tokens: CancellationToken[]): AbortSignal {
299
+ const controller = new AbortController();
300
+ tokens.forEach(token => token.onCancellationRequested(() => controller.abort()));
301
+ return controller.signal;
302
+ }
303
+
304
+ protected async displayCopyNotification(code: string, firstTime = false): Promise<void> {
305
+ navigator.clipboard.writeText(code);
306
+ const notification = nls.localize('theia/collaboration/copiedInvitation', 'Invitation code copied to clipboard.');
307
+ if (firstTime) {
308
+ // const makeReadonly = nls.localize('theia/collaboration/makeReadonly', 'Make readonly');
309
+ const copyAgain = nls.localize('theia/collaboration/copyAgain', 'Copy Again');
310
+ const copyResult = await this.messageService.info(
311
+ notification,
312
+ // makeReadonly,
313
+ copyAgain
314
+ );
315
+ // if (copyResult === makeReadonly && this.currentInstance) {
316
+ // this.currentInstance.readonly = true;
317
+ // }
318
+ if (copyResult === copyAgain) {
319
+ navigator.clipboard.writeText(code);
320
+ }
321
+ } else {
322
+ await this.messageService.info(
323
+ notification
324
+ );
325
+ }
326
+ }
327
+ }
@@ -0,0 +1,37 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2024 TypeFox and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import { CommandContribution } from '@theia/core';
18
+ import { ContainerModule } from '@theia/core/shared/inversify';
19
+ import { WorkspaceService } from '@theia/workspace/lib/browser';
20
+ import { CollaborationColorService } from './collaboration-color-service';
21
+ import { CollaborationFrontendContribution } from './collaboration-frontend-contribution';
22
+ import { CollaborationInstance, CollaborationInstanceFactory, CollaborationInstanceOptions, createCollaborationInstanceContainer } from './collaboration-instance';
23
+ import { CollaborationUtils } from './collaboration-utils';
24
+ import { CollaborationWorkspaceService } from './collaboration-workspace-service';
25
+
26
+ export default new ContainerModule((bind, _, __, rebind) => {
27
+ bind(CollaborationWorkspaceService).toSelf().inSingletonScope();
28
+ rebind(WorkspaceService).toService(CollaborationWorkspaceService);
29
+ bind(CollaborationUtils).toSelf().inSingletonScope();
30
+ bind(CollaborationFrontendContribution).toSelf().inSingletonScope();
31
+ bind(CommandContribution).toService(CollaborationFrontendContribution);
32
+ bind(CollaborationInstanceFactory).toFactory(context => (options: CollaborationInstanceOptions) => {
33
+ const container = createCollaborationInstanceContainer(context.container, options);
34
+ return container.get(CollaborationInstance);
35
+ });
36
+ bind(CollaborationColorService).toSelf().inSingletonScope();
37
+ });