Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Client side with dotnet/Angular/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@
"@angular/platform-browser": "19.2.21",
"@angular/platform-browser-dynamic": "19.2.21",
"@angular/router": "19.2.21",
"@microsoft/signalr": "^8.0.0",
"@syncfusion/ej2-collaborator": "*",
"@syncfusion/ej2-angular-documenteditor": "*",
"rxjs": "~7.8.1",
"tslib": "^2.6.0",
"zone.js": "~0.15.0"
},
"devDependencies": {
"@angular-devkit/build-angular": "^21.2.7",
"@angular/cli": "^21.2.7",
"@angular-devkit/build-angular": "^19.2.0",
"@angular/cli": "^19.2.0",
"@angular/compiler-cli": "19.2.21",
"@types/jasmine": "~5.1.0",
"jasmine-core": "~5.1.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import {
DocumentEditor,
DocumentEditorContainer,
Operation,ContentChangeEventArgs
} from "@syncfusion/ej2-documenteditor";

import { ICollaborationProvider, ICollaborationActionData } from "@syncfusion/ej2-collaborator";

export class DocumentEditorAdapter implements ICollaborationProvider {

constructor(
private container: DocumentEditorContainer,
private serviceUrl: string,
) { }

public async loadFromServer(fileName: string): Promise<string> {
const roomName: string = this.getRoomName(fileName);
const response: Response = await fetch(
this.serviceUrl +'api/CollaborativeEditing/ImportFile',
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ fileName, roomName })
}
);
if (!response.ok) {
throw new Error('Failed to load document');
}
const responseText: string = await response.text();
await this.open(responseText, roomName);
return roomName;
}

public async open(responseText: string, roomName: string): Promise<void> {
const data: any = JSON.parse(responseText);
this.container?.documentEditor.collaborativeEditingHandlerModule?.updateRoomInfo(roomName, data.version, this.serviceUrl + 'api/CollaborativeEditing/');
this.container.documentEditor.open(data.sfdt);
this.container.documentEditor.contentChange = (args: ContentChangeEventArgs) => {
console.log('[SENT]',new Date().toISOString());
this.container.documentEditor.collaborativeEditingHandlerModule?.sendActionToServer(args.operations as Operation[]);
}

}

public applyRemoteAction( action: string,data:ICollaborationActionData): void {

this.container.documentEditor.collaborativeEditingHandlerModule?.applyRemoteAction( action,data.payload);
}

private getRoomName(fileName: string): string {
const queryString: string = window.location.search;
const urlParams: URLSearchParams = new URLSearchParams(queryString);
let roomId: string | null = urlParams.get('id');

if (!roomId) {
roomId = Math.random().toString(32).slice(2);
window.history.replaceState({}, '', '?id=' + roomId);
}

return roomId;
}
}
2 changes: 1 addition & 1 deletion Client side with dotnet/Angular/src/app/app.component.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<div>
<div id="documenteditor_titlebar" class="e-de-ctn-title" style="height:35px;"></div>
<ejs-documenteditorcontainer #documenteditor_default [enableToolbar]="true" (created)="onCreated()"
(contentChange)="onContentChange($event)" height="600px" style="display: block;"></ejs-documenteditorcontainer>
height="600px" style="display: block;"></ejs-documenteditorcontainer>
</div>


Expand Down
134 changes: 24 additions & 110 deletions Client side with dotnet/Angular/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { TitleBar } from "./title-bar"
import { HubConnectionBuilder, HttpTransportType, HubConnectionState, HubConnection } from '@microsoft/signalr';
import { hideSpinner, showSpinner } from '@syncfusion/ej2-popups';
import { isNullOrUndefined } from '@syncfusion/ej2-base';
import { CollaborationClient, UserInfo } from "@syncfusion/ej2-collaborator";
import { DocumentEditorAdapter } from "./Collaborator/DocumentEditorAdapter";

DocumentEditor.Inject(CollaborativeEditingHandler);
@Component({
Expand All @@ -33,127 +35,39 @@ export class AppComponent {
this.container.documentEditor.documentName = 'Getting Started';
//Enable collaborative editing in Document Editor.
this.container.documentEditor.enableCollaborativeEditing = true;

//Title bar implementation
this.titleBar = new TitleBar(document.getElementById('documenteditor_titlebar') as HTMLElement, this.container.documentEditor, true);
this.titleBar.updateDocumentTitle();
this.initializeSignalR();
this.loadDocumentFromServer();
}

onContentChange = (args: ContainerContentChangeEventArgs) => {
//Send the editing action to server
this.container.documentEditor.collaborativeEditingHandlerModule.sendActionToServer(args.operations as Operation[]);
}

initializeSignalR = (): void => {
// SignalR connection
this.connection = new HubConnectionBuilder().withUrl(this.serviceUrl + 'documenteditorhub', {
skipNegotiation: true,
transport: HttpTransportType.WebSockets
}).withAutomaticReconnect().build();
//Event handler for signalR connection
this.connection.on('dataReceived', this.onDataRecived.bind(this));

this.connection.onclose(async () => {
if (this.connection && this.connection.state === HubConnectionState.Disconnected) {
alert('Connection lost. Please relod the browser to continue.');
}
});

this.connection.onreconnected(() => {
if (this.connection && this.currentRoomName != null) {
this.connection.send('JoinGroup', { roomName: this.currentRoomName, currentUser: this.currentUser });
const adapter = new DocumentEditorAdapter(this.container, 'http://localhost:5212/');
const client = new CollaborationClient(adapter, {
serviceUrl: "http://localhost:5212",
currentUser: this.currentUser,
connectionType: "signalr",
onUserJoined: (user: UserInfo) => {
console.log("User Joined", user);
this.titleBar?.addUser(user);
},
onUserLeft: (user: UserInfo) => {
console.log("User Left", user);
this.titleBar?.removeUser(user);
}
console.log('server reconnected!!!');
});
}

onDataRecived(action: string, data: any) {
if (this.container.documentEditor.collaborativeEditingHandlerModule) {
if (action == 'connectionId') {
//Update the current connection id to track other users
this.connectionId = data;
} else if (this.connectionId != data.connectionId) {
if (this.titleBar) {
if (action == 'action' || action == 'addUser') {
//Add the user to title bar when user joins the room
this.titleBar.addUser(data);
} else if (action == 'removeUser') {
//Remove the user from title bar when user leaves the room
this.titleBar.removeUser(data);
}
}
}
//Apply the remote action in DocumentEditor
this.container.documentEditor.collaborativeEditingHandlerModule.applyRemoteAction(action, data);
}
}
(async () => {

openDocument(responseText: string, roomName: string): void {
showSpinner(document.getElementById('container') as HTMLElement);
const roomName =
await adapter.loadFromServer(
"Giant Panda.docx"
);

let data = JSON.parse(responseText);
if (this.container) {
//Update the room and version information to collaborative editing handler.
this.container.documentEditor.collaborativeEditingHandlerModule.updateRoomInfo(roomName, data.version, this.serviceUrl + 'api/CollaborativeEditing/');
await client.joinRoomAsync(
roomName
);

//Open the document
this.container.documentEditor.open(data.sfdt);
})();

setTimeout(() => {
if (this.container) {
// connect to server using signalR
this.connectToRoom({ action: 'connect', roomName: roomName, currentUser: this.container.currentUser });
}
});
}
hideSpinner(document.getElementById('container') as HTMLElement);
}

loadDocumentFromServer() {
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
let roomId = urlParams.get('id');
if (roomId == null) {
roomId = Math.random().toString(32).slice(2)
window.history.replaceState({}, "", `?id=` + roomId);
}
var httpRequest = new XMLHttpRequest();
httpRequest.open('Post', this.serviceUrl + 'api/CollaborativeEditing/ImportFile', true);
httpRequest.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
httpRequest.onreadystatechange = () => {
if (httpRequest.readyState === 4) {
if (httpRequest.status === 200 || httpRequest.status === 304) {
this.openDocument(httpRequest.responseText, roomId as string);
}
else {
hideSpinner(document.getElementById('container') as HTMLElement);
alert('Fail to load the document');
}
}
};
httpRequest.send(JSON.stringify({ "fileName": "Giant Panda.docx", "roomName": roomId }));
}

public connectToRoom(data: any) {
try {
this.currentRoomName = data.roomName;
if (this.connection) {
// start the connection.
this.connection.start().then(() => {
// Join the room.
if (this.connection) {
this.connection.send('JoinGroup', { roomName: data.roomName, currentUser: data.currentUser });
}
console.log('server connected!!!');
});
}
} catch (err) {
console.log(err);
//Attempting to reconnect in 5 seconds
setTimeout(this.connectToRoom, 5000);
}
};

}
2 changes: 1 addition & 1 deletion Client side with dotnet/Angular/src/app/title-bar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ export class TitleBar {
}
}

public removeUser(conectionId: string): void {
public removeUser(conectionId: any): void {
if (this.userMap[conectionId]) {
if (this.userList) {
this.userList.removeChild(this.userMap[conectionId]);
Expand Down
7 changes: 4 additions & 3 deletions Client side with dotnet/Javascript/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"license": "SEE LICENSE IN license",
"devDependencies": {
"css-loader": "^6.8.1",
"glob": "^13.0.6",
"html-webpack-plugin": "^5.5.3",
"mini-css-extract-plugin": "^2.7.6",
"sass": "^1.63.6",
Expand All @@ -21,9 +22,9 @@
"webpack-dev-server": "^5.2.2"
},
"dependencies": {
"@microsoft/signalr": "^8.0.0",
"@syncfusion/ej2-buttons": "^*",
"@syncfusion/ej2-collaborator": "*",
"@syncfusion/ej2-documenteditor": "^*",
"@syncfusion/ej2-splitbuttons": "^*",
"@syncfusion/ej2-buttons": "^*"
"@syncfusion/ej2-splitbuttons": "^*"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
export class DocumentEditorAdapter {
constructor(container, serviceUrl) {
this.container = container;
this.serviceUrl = serviceUrl;
}

// The only ICollaborationProvider method — applied for every remote action.
applyRemoteAction(action, data) {
this.container.documentEditor.collaborativeEditingHandlerModule?.applyRemoteAction(action, data.payload);
}

// Fetch the document from the product's REST API and return the room name.
async loadFromServer(fileName) {
const roomName = this.getRoomName(fileName);
const response = await fetch(
this.serviceUrl + 'api/CollaborativeEditing/ImportFile',
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ fileName, roomName })
}
);
if (!response.ok) {
throw new Error('Failed to load document');
}
const responseText = await response.text();
await this.open(responseText, roomName);
return roomName;
}

// Seed the editor and bridge local edits to the editor's sender.
async open(responseText, roomName) {
const data = JSON.parse(responseText);
this.container?.documentEditor.collaborativeEditingHandlerModule?.updateRoomInfo(roomName, data.version, this.serviceUrl + 'api/CollaborativeEditing/');
this.container.documentEditor.open(data.sfdt);
this.container.contentChange = (args) => {
console.log('[SENT]', new Date().toISOString());
this.container.documentEditor.collaborativeEditingHandlerModule?.sendActionToServer(args.operations);
}
}

getRoomName(fileName) {
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
let roomId = urlParams.get('id');

if (!roomId) {
roomId = Math.random().toString(32).slice(2);
window.history.replaceState({}, '', '?id=' + roomId);
}

return roomId;
}
}
Loading
Loading