From 3f71c826c32193e46f69740e6adc547ea80876c2 Mon Sep 17 00:00:00 2001 From: bhuvaneshdhakshinmaoorthy Date: Tue, 6 Oct 2026 15:18:40 +0530 Subject: [PATCH 1/2] 1055046: Prepared samples for javascript client samples for collaborative editing --- README.md | 523 ++++-- .../spreadsheet-editor.component.ts | 2 +- client/javascript/.gitignore | 6 + client/javascript/README.md | 79 + client/javascript/index.html | 12 + client/javascript/package-lock.json | 1663 +++++++++++++++++ client/javascript/package.json | 20 + client/javascript/src/index.js | 92 + .../src/spreadsheet-editor-adapter.js | 66 + client/javascript/src/style.css | 11 + client/react/README.md | 2 +- client/react/src/App.tsx | 2 +- client/typescript/src/index.ts | 2 +- client/vue/src/App.vue | 2 +- 14 files changed, 2286 insertions(+), 196 deletions(-) create mode 100644 client/javascript/.gitignore create mode 100644 client/javascript/README.md create mode 100644 client/javascript/index.html create mode 100644 client/javascript/package-lock.json create mode 100644 client/javascript/package.json create mode 100644 client/javascript/src/index.js create mode 100644 client/javascript/src/spreadsheet-editor-adapter.js create mode 100644 client/javascript/src/style.css diff --git a/README.md b/README.md index 7d39919..9615dee 100644 --- a/README.md +++ b/README.md @@ -1,204 +1,345 @@ # Spreadsheet Collaborative Editing -This repository showcases real-time collaborative editing using the Syncfusion® Spreadsheet Editor, allowing multiple users to edit the same workbook simultaneously. The project demonstrates how to implement collaborative features across multiple frontend frameworks (Angular, React, Vue, and TypeScript) with an ASP.NET Core backend. +This repository demonstrates real-time collaborative editing in the Syncfusion® Spreadsheet. Multiple users can open the same workbook, edit it simultaneously, view other participants, and receive synchronized changes in real time. + +The repository includes client-side samples for Angular, JavaScript, React, TypeScript, and Vue. Each client connects to a Collaboration Server through SignalR and uses Redis to maintain collaboration sessions and synchronize workbook actions. ## Features -- Real-time collaborative editing of spreadsheets -- Support for multiple frontend frameworks: - - Angular - - React - - Vue - - TypeScript (Vanilla) -- ASP.NET Core backend with SignalR for real-time communication -- Redis integration for collaboration state management -- Spreadsheet operations synchronization across multiple clients -- Selection highlighting for other users' cursor positions +- Real-time multi-user Spreadsheet editing +- Synchronization of supported Spreadsheet actions across connected clients +- Participant presence and selection indicators +- Unique collaboration rooms identified through the `id` URL query parameter +- SignalR-based real-time communication +- Redis-backed collaboration state management +- Client examples for Angular, JavaScript, React, TypeScript, and Vue +- Reusable collaboration adapter implementation for each platform ## Prerequisites -### Backend -- .NET 10.0 SDK -- Redis server (for collaboration state management) -- Syncfusion license key - -### Frontend -- Node.js (v18 or later) -- npm or yarn - -## Project Structure - -``` -├── client/ -│ ├── angular/ # Angular implementation -│ ├── react/ # React implementation -│ ├── typescript/ # TypeScript/Vanilla implementation -│ └── vue/ # Vue implementation -├── server/ -│ └── aspnet-core/ # ASP.NET Core backend with SignalR -└── README.md -``` - -## Setup - -### Backend Setup - -1. Navigate to the server directory: - ```bash - cd server/aspnet-core - ``` - -2. Configure your Syncfusion license: - - Add your license key to `SyncfusionLicense.txt` file - - Or set the `SYNCFUSION_LICENSE_KEY` environment variable - -3. Configure Redis connection: - - Update the Redis connection string in `appsettings.json`: - ```json - { - "ConnectionStrings": { - "Redis": "your_redis_connection_string" - } - } - ``` - -4. Run the server: - ```bash - dotnet run - ``` - - The server will start on `https://localhost:5001` by default. - -### Frontend Setup - -Choose one of the client implementations: - -#### Angular - -1. Navigate to the Angular client: - ```bash - cd client/angular - ``` - -2. Install dependencies: - ```bash - npm install - ``` - -3. Update the service URL in `src/components/spreadsheet-editor/spreadsheet-editor.component.ts`: - ```typescript - const serviceUrl: string = 'https://localhost:5001/'; - ``` - -4. Run the application: - ```bash - npm start - ``` - - The application will be available at `http://localhost:4200`. - -#### React - -1. Navigate to the React client: - ```bash - cd client/react - ``` - -2. Install dependencies: - ```bash - npm install - ``` - -3. Update the service URL in `src/SpreadsheetEditorAdapter.ts`: - ```typescript - const serviceUrl: string = 'https://localhost:5001/'; - ``` - -4. Run the application: - ```bash - npm run dev - ``` - - The application will be available at `http://localhost:5173`. - -#### Vue - -1. Navigate to the Vue client: - ```bash - cd client/vue - ``` - -2. Install dependencies: - ```bash - npm install - ``` - -3. Update the service URL in `src/SpreadsheetEditorAdapter.ts`: - ```typescript - const serviceUrl: string = 'https://localhost:5001/'; - ``` - -4. Run the application: - ```bash - npm run dev - ``` - - The application will be available at `http://localhost:5173`. - -#### TypeScript (Vanilla) - -1. Navigate to the TypeScript client: - ```bash - cd client/typescript - ``` - -2. Install dependencies: - ```bash - npm install - ``` - -3. Update the service URL in `src/spreadsheet-editor-adapter.ts`: - ```typescript - const serviceUrl: string = 'https://localhost:5001/'; - ``` - -4. Run the application: - ```bash - npm run dev - ``` - - The application will be available at `http://localhost:5173`. +Before running the samples, ensure that the following software and services are available. -## Usage - -1. Start the backend server -2. Start one of the frontend clients -3. Open the application in multiple browser windows or devices -4. Each user will be assigned a unique room ID automatically -5. To collaborate in the same room, share the URL with the room ID parameter -6. All users in the same room will see real-time updates as others edit the spreadsheet - -## API Endpoints - -The backend provides the following API endpoints: - -- `POST /api/CollaborativeEditing/ImportFile` - Import a file for collaborative editing - -## Contributing - -Contributions are welcome! Please follow these steps: - -1. Fork the repository -2. Create a feature branch -3. Commit your changes -4. Push to the branch -5. Create a pull request +### Client applications -## License +- Node.js +- npm +- A supported modern web browser + +### Collaboration Server + +- ASP.NET Core Collaboration Server +- Redis Server or a hosted Redis service +- A valid Syncfusion license key + +## Real-time communication + +The client applications connect to the Collaboration Server through SignalR. The real-time connection is used to join a collaboration room and receive actions, participant updates, and selection changes from other connected users. + +Each client uses the following configuration: + +```ts +const client = new CollaborationClient(adapter, { + serviceUrl, + connectionType: 'signalr', + currentUser +}); +``` + +The client and Collaboration Server must use the same transport configuration. + +## Redis backing system + +Redis is used by the Collaboration Server to store collaboration operations, maintain room state, and coordinate updates between connected users and server instances. + +Configure the Redis connection in the Collaboration Server settings: + +```json +{ + "ConnectionStrings": { + "Redis": "YOUR_REDIS_CONNECTION_STRING" + } +} +``` + +Replace `YOUR_REDIS_CONNECTION_STRING` with the connection string of the running Redis service. + +## How collaborative editing works + +### Client side + +1. The client obtains the room ID from the `id` query parameter. +2. If the URL does not contain a room ID, the client generates one and updates the browser URL. +3. The client requests the latest synchronized workbook and room version from the Collaboration Server. +4. The Spreadsheet initializes its collaboration room, server version, service endpoint, and local user. +5. The `CollaborationClient` establishes a SignalR connection and joins the room. +6. Supported local Spreadsheet actions are sent to the Collaboration Server from the `actionComplete` event. +7. Remote actions received from other participants are applied to the Spreadsheet through the collaboration adapter. + +### Server side + +1. The Collaboration Server receives actions from connected clients. +2. Actions are ordered and stored using Redis. +3. The server processes the actions against the current room version. +4. The latest room and workbook state is maintained for subsequent users. +5. Processed actions are broadcast to the other clients in the same collaboration room. +6. Connected clients apply the remote actions and remain synchronized. + +## Repository structure + +```text +Spreadsheet-Collaborative-Editing/ +|-- client/ +| |-- angular/ +| |-- javascript/ +| |-- react/ +| |-- typescript/ +| `-- vue/ +|-- server/ +| `-- aspnet-core/ +|-- README.md +`-- license +``` + +## Client samples + +The following client implementations are available: + +- [Angular](./client/angular) +- [JavaScript](./client/javascript) +- [React](./client/react) +- [TypeScript](./client/typescript) +- [Vue](./client/vue) + +Each client folder contains its own installation instructions and platform-specific source files. + +## Configure the Collaboration Server URL + +Before starting a client application, replace the placeholder Collaboration Server URL in the client source code: + +```ts +const serviceUrl: string = 'YOUR_COLLABORATION_SERVER_URL'; +``` + +For JavaScript: + +```js +const serviceUrl = 'YOUR_COLLABORATION_SERVER_URL'; +``` + +Replace `YOUR_COLLABORATION_SERVER_URL` with the URL of the running Collaboration Server. For example: + +```text +https://localhost:5001/ +``` + +The adapter automatically appends a trailing slash when the configured URL does not contain one. + +## Run the Collaboration Server + +Navigate to the ASP.NET Core Collaboration Server folder: + +```bash +cd server/aspnet-core +``` + +Update the Redis connection string and configure the Syncfusion license key as required by the server project. + +Restore the server dependencies: + +```bash +dotnet restore +``` + +Run the Collaboration Server: + +```bash +dotnet run +``` + +Use the server URL displayed in the terminal as `YOUR_COLLABORATION_SERVER_URL` in the client applications. + +## Run a client application + +Choose one client implementation and navigate to its folder. + +### Angular + +```bash +cd client/angular +npm install +npm start +``` + +The Angular application normally runs at: + +```text +http://localhost:4200/ +``` + +### React + +```bash +cd client/react +npm install +npm run dev +``` + +### Vue + +```bash +cd client/vue +npm install +npm run dev +``` -This project uses Syncfusion Essential JS 2 components, which are licensed under either the Syncfusion Community License Program or the Syncfusion commercial license. +### TypeScript + +```bash +cd client/typescript +npm install +npm run dev +``` + +### JavaScript + +```bash +cd client/javascript +npm install +npm run dev +``` + +The Vite-based applications normally run at: + +```text +http://localhost:5173/ +``` + +Use the actual URL displayed in the terminal if a different port is selected. + +## Test collaborative editing + +1. Start Redis. +2. Start the Collaboration Server. +3. Start one of the client applications. +4. Open the client URL in a browser. +5. Confirm that the client adds an `id` query parameter to the URL. +6. Copy the complete URL, including the room ID. +7. Open the copied URL in another browser tab, browser window, or browser profile. +8. Edit a cell in the first client. +9. Confirm that the same change appears in the second client. +10. Make another supported change in the second client and confirm that it appears in the first client. + +Example collaboration URL: + +```text +http://localhost:5173/?id=sample-room +``` + +All users who open the same URL and connect to the same Collaboration Server join the same workbook session. + +## Client-side packages + +The samples use the following collaboration packages: + +```text +@syncfusion/ej2-collaborator +@microsoft/signalr +``` + +The platform-specific Spreadsheet package is also required: + +```text +@syncfusion/ej2-angular-spreadsheet +@syncfusion/ej2-react-spreadsheet +@syncfusion/ej2-vue-spreadsheet +@syncfusion/ej2-spreadsheet +``` + +The exact package list is available in the `package.json` file of each client sample. + +## Build the client applications + +For React, Vue, TypeScript, and JavaScript: + +```bash +npm run build +``` + +For Angular: + +```bash +npm run build +``` + +The generated production files are written to the platform's configured output directory. + +## Troubleshooting + +### The collaborator package cannot be resolved + +Ensure that `@syncfusion/ej2-collaborator` is listed in the client `package.json`, and then run: + +```bash +npm install +``` + +### The SignalR package cannot be resolved + +Ensure that `@microsoft/signalr` is listed in the client `package.json`, and then run: + +```bash +npm install +``` + +### The Spreadsheet is not styled correctly + +Confirm that the Syncfusion theme is imported by the client application. The Vite samples use: + +```css +@import '@syncfusion/ej2-tailwind3-theme/styles/tailwind3.css'; +``` + +### The workbook does not load + +Verify that: + +- The Collaboration Server is running. +- The configured `serviceUrl` is correct. +- The `ImportFile` endpoint is reachable. +- The source workbook required by the server is available. +- The browser console does not contain CORS or network errors. + +### Changes are not synchronized + +Verify that: + +- Redis is running and the configured connection string is valid. +- All clients connect to the same Collaboration Server. +- All clients use the same room ID. +- The SignalR connection is established successfully. +- The browser console does not contain connection or action-processing errors. + +### The second browser opens a different workbook session + +Copy and open the complete URL containing the `id` query parameter. Opening only the base URL generates a new room ID and creates a different collaboration session. + +## Documentation + +- [Spreadsheet collaborative editing overview](https://help.syncfusion.com/document-processing/excel/spreadsheet/javascript-es5/collaborative-editing/overview) +- [Collaboration Client](https://help.syncfusion.com/document-processing/collaborator/collaboration-client) +- [Collaboration Server](https://help.syncfusion.com/document-processing/collaborator/collaboration-server) +- [ASP.NET Core Redis Collaboration Server](https://help.syncfusion.com/document-processing/excel/spreadsheet/javascript-es5/collaborative-editing/aspnet-core-redis) + +Platform-specific integration documentation: + +- [Angular integration](https://help.syncfusion.com/document-processing/excel/spreadsheet/angular/collaborative-editing/integration) +- [JavaScript integration](https://help.syncfusion.com/document-processing/excel/spreadsheet/javascript-es5/collaborative-editing/integration) +- [React integration](https://help.syncfusion.com/document-processing/excel/spreadsheet/react/collaborative-editing/integration) +- [TypeScript integration](https://help.syncfusion.com/document-processing/excel/spreadsheet/typescript/collaborative-editing/integration) +- [Vue integration](https://help.syncfusion.com/document-processing/excel/spreadsheet/vue/collaborative-editing/integration) + +## License -Please refer to the [license](license) file for more information. +This project is licensed under the terms specified in the repository's license file. -To use this product, you must agree to and abide by Syncfusion's license containing all terms and conditions, which can be found at: -https://www.syncfusion.com/content/downloads/syncfusion_license.pdf +Syncfusion® controls and services are subject to the applicable Syncfusion license terms. Refer to the Syncfusion licensing documentation before using this example in a production application. diff --git a/client/angular/src/components/spreadsheet-editor/spreadsheet-editor.component.ts b/client/angular/src/components/spreadsheet-editor/spreadsheet-editor.component.ts index b074c2b..b10add1 100644 --- a/client/angular/src/components/spreadsheet-editor/spreadsheet-editor.component.ts +++ b/client/angular/src/components/spreadsheet-editor/spreadsheet-editor.component.ts @@ -8,7 +8,7 @@ import { CollaborationClient } from '@syncfusion/ej2-collaborator'; import { SpreadsheetEditorAdapter } from './spreadsheet-editor-adapter'; const serviceUrl: string = - 'https://appservice-267100-e9c8cxaab6a2dfeu.centralindia-01.azurewebsites.net/'; + ''YOUR_COLLABORATION_SERVER_URL''; const currentUser: string = 'John'; function getRoomName(): string { diff --git a/client/javascript/.gitignore b/client/javascript/.gitignore new file mode 100644 index 0000000..cc9689e --- /dev/null +++ b/client/javascript/.gitignore @@ -0,0 +1,6 @@ +node_modules +dist +.vscode/* +!.vscode/extensions.json +.DS_Store +*.local diff --git a/client/javascript/README.md b/client/javascript/README.md new file mode 100644 index 0000000..0d35784 --- /dev/null +++ b/client/javascript/README.md @@ -0,0 +1,79 @@ +# JavaScript Spreadsheet Collaborative Editing + +This example demonstrates real-time collaborative editing in the Syncfusion Essential JS 2 JavaScript Spreadsheet. Multiple users can edit the same workbook, and supported actions are synchronized through the Collaboration Server. + +## Prerequisites + +- Node.js +- Redis Server +- ASP.NET Core Redis Collaboration Server, or access to the configured hosted Collaboration Server + +## Install dependencies + +```bash +npm install +``` + +## Configure the Collaboration Server + +The Collaboration Server URL is defined in `src/index.js`. Update `serviceUrl` when a different server endpoint is used. + +## Run the application + +```bash +npm run dev +``` + +Open the URL displayed by Vite, normally: + +```text +http://localhost:5173/ +``` + +When the page opens without an `id` query parameter, the client generates a room ID and updates the URL. Open the complete URL in another browser tab or window to join the same collaboration room. + +Example: + +```text +http://localhost:5173/?id=sample-room +``` + +## Project structure + +```text +src/ +|-- index.js +|-- spreadsheet-editor-adapter.js +`-- style.css +``` + +## How it works + +1. `CollaborativeEditingHandler` is injected into the Spreadsheet. +2. The Spreadsheet starts with collaborative editing enabled. +3. The client obtains or generates a room ID from the URL. +4. The adapter imports the latest workbook and room version from the server. +5. `CollaborationClient` connects through SignalR and joins the room. +6. Local Spreadsheet actions are sent from `actionComplete`. +7. Remote actions are applied through `SpreadsheetEditorAdapter.applyRemoteAction`. + +## Build + +```bash +npm run build +``` + +## Important packages + +- `@syncfusion/ej2-spreadsheet` +- `@syncfusion/ej2-collaborator` +- `@microsoft/signalr` +- `@syncfusion/ej2-tailwind3-theme` + +## Troubleshooting + +If clients do not synchronize, verify that the Collaboration Server is reachable, Redis is running, all clients use the same room ID, and the browser console has no SignalR or CORS errors. + +## License + +Refer to the license file in the parent repository. diff --git a/client/javascript/index.html b/client/javascript/index.html new file mode 100644 index 0000000..0e768e8 --- /dev/null +++ b/client/javascript/index.html @@ -0,0 +1,12 @@ + + + + + + JavaScript Spreadsheet Collaborative Editing + + +
+ + + diff --git a/client/javascript/package-lock.json b/client/javascript/package-lock.json new file mode 100644 index 0000000..65f9841 --- /dev/null +++ b/client/javascript/package-lock.json @@ -0,0 +1,1663 @@ +{ + "name": "javascript-spreadsheet-collaborative-editing", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "javascript-spreadsheet-collaborative-editing", + "version": "1.0.0", + "dependencies": { + "@microsoft/signalr": "^9.0.0", + "@syncfusion/ej2-collaborator": "35.1.37", + "@syncfusion/ej2-spreadsheet": "35.1.37", + "@syncfusion/ej2-tailwind3-theme": "35.1.37" + }, + "devDependencies": { + "vite": "^7.1.9" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@microsoft/signalr": { + "version": "9.0.19", + "resolved": "https://registry.npmjs.org/@microsoft/signalr/-/signalr-9.0.19.tgz", + "integrity": "sha512-hKDxt4nRXvkhhy+Etlhbg0UDQdhk8PsZKlQTnHyCn+bb+Q6gM8RCZLI43WsLH1IQCa7l/G8tX09em1F1LH/C1w==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "eventsource": "^2.0.2", + "fetch-cookie": "^2.0.3", + "node-fetch": "^2.6.7", + "ws": "^7.5.10" + } + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.64.0.tgz", + "integrity": "sha512-FLLpX941CD/7aBvX3GqLGBM9OvY5PGXTryPJkG5r5/OuBpZpV5k45Uo3RxAvqd35tsyc3hH1bhtz921IKzoIzg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.64.0.tgz", + "integrity": "sha512-iI32XM/jnDYJsvx5ai/za/7gWO9XoSeFFtU06/ZCaRgyilfJOjaopD3nOpk2WrTCGOyeDt4hleUiGOAnd5tHNg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.64.0.tgz", + "integrity": "sha512-dRBZlbt2sEtF8Mn4MODThze9fIPdDoOhVSlXMAKy5dQbOYK9pOOoPY4Fat3OTtr0+8zbJo5TTZwlsfxY/kDNJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.64.0.tgz", + "integrity": "sha512-Y3whrJOMucfkhgNkvcM93M6j8C53iEAMmPMYD99C6G1kUy4ELigLWk9ar2/4aCBSRgsql0zdlOemLgW2m5GZgQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.64.0.tgz", + "integrity": "sha512-S6S1GEzPjFF6IONYJX3AKt/799+8RgBgCcwRnUo+Ep1IvBxUbw5M51iU0TDdK2NWWVnSvCQ8yi6Em0fGASMOng==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.64.0.tgz", + "integrity": "sha512-Ed1DPoFENkXj4IGzSB12SesV40iP6LxCCrbTIkSGIQBpOOxpBM+0kj62lmaeIHfMWTDoddqTOtqc7s8LYfn+Pw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.64.0.tgz", + "integrity": "sha512-J/6xhutJPtFyIkmHdf4olOdHHKH5kOJNumojdWn+Elv5ICEYTVOa362Vw27TxsK0BLzb1le3EDlO3UbvFK1lnQ==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.64.0.tgz", + "integrity": "sha512-G4L/Nnx59d2LvDoV/P4S5lZ7oVLT6hodOHMacqjiq2LL2gts86KbkEBTYL53+fBt0odhf7XPIbIATGoYiJctxg==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.64.0.tgz", + "integrity": "sha512-zF16pvJDEUksGYRbfnXne7pdklAyj2XDmJeYKgP93oq0cUyX9Y8O7lUo1R+IY1GagP/YBvCEDCe7eEcAKpw//g==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.64.0.tgz", + "integrity": "sha512-nIggWn8n+w12+R5lsV+PYYoUY8vuObxPAQqJI7WES+9v0qspYuAHoZMfYM0yLxlipasT5BY9ht5W8t5c/rSCkg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.64.0.tgz", + "integrity": "sha512-WLOTiHPzszP0dxIMsHpFeDSOSB5I49Mj8K4+yKE3H1X/D0JRG5XZ5jrctJLMjnUc/LUzaKiwq6EHWNJ8pup8PA==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.64.0.tgz", + "integrity": "sha512-35RK9aRhh7g7Pmeu5gQ01aCvrhXzj9LoBE6xtUcEH1ns+nyTaSzxSpADmBQINu4QiTDGvmQYwo1j5hlg1rRXFw==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.64.0.tgz", + "integrity": "sha512-RqTfnpoTjuWfPAmqK5p2edt2AMCDJWm6oTn7sKfmyx+6tRga0ip05SJ977o6udTcIiDeF/CJMUfkaN4gtWt6Aw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.64.0.tgz", + "integrity": "sha512-RQjZPigjxFvFZDijG6rjlSXxhRaTf9zLYVpGK+lVcga64oMqP1GbgVqCb46eHTTceSIgp89Ja0y5aPe3GFBYHg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.64.0.tgz", + "integrity": "sha512-tjL58mgQcTRnDRpeky+Sth1MKQeYi2Ct6r3Luq9E5aTSjnM/i/JbozqZkKqp9thM5+oDSZxo+05f9kdQedfTGA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.64.0.tgz", + "integrity": "sha512-ictoRyVaoZ8C67myHef+8hhCa6IplXJJ/xUCkVCxziV4zxMZh+jvhkhGsPBLqj1S72fnqD0QH5zL4lcaa4X7DQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.64.0.tgz", + "integrity": "sha512-8aVvZ2hfr9RLFzBQkQNmYeHLDja8fLeQAmLt757zdKN/B+8L809KlLJKctQjS22wZotptV8G6o7fVPuAs8S1tQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.64.0.tgz", + "integrity": "sha512-2dEF8GAcDKwshUfydD+GhosppNyBzhVUHkdFF3CXd3FpOkzj/RZzF5aoEtzklujklT5qFVRU2GK/cFeLaTNKVg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.64.0.tgz", + "integrity": "sha512-3XHTvoo2hKkh0MiOfS7SkhHjMaaWF7P4F3drocZZaho/Z8w7h/jpnuBHm91BOQLfvrKHs1yR+zir4E8RI3XzTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.64.0.tgz", + "integrity": "sha512-HKtkzodL4a3KFWDXDoO61cRynAWTR3LnAE0oGtfG9lcNFpNuVYUFOByxDgNqEvic9rvaQbAj+SFwTEWDptRaIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.64.0.tgz", + "integrity": "sha512-JBsxqiY16uyyCujtoC3s7zL+SrYyNElptD7K4V+KUoJ2V8/XG3hPWpheK+SuPwNh1cikJvIZmIdsrYXvD6PLJw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.64.0.tgz", + "integrity": "sha512-S5Qrijh37qhnUYQ0ay8WN3LuWeKjow7wTUyCOPMZtnXiBXYXdsZETI6ZLfYnQAKovltckPLBFdNeD61P2rJc7g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.64.0.tgz", + "integrity": "sha512-zbZ6iMmLFEsCtTdc2/y1F/cDFVNNCPF7Ae8PCT/wdPE//hGwYp6r+MyCLMFX+kOeIe09Pz6ASfnnfpSTN1FJQQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.64.0.tgz", + "integrity": "sha512-L6Hw69oNpuYaOr1zCG30v0/DqKYCSRwodPGwIcVZ/6f8SOzq9rxtL/xNVI5MO4wjli+Ko0ErK9uHJL3mNPt8wQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.64.0.tgz", + "integrity": "sha512-nl+CTWIgOGszQmUz/kanfgrfFZ4qiwxKIfT/ryl3N3Ewk5tqizmYtNXiqb7Bu/fDtNgu1kyxdFiAUJNur3iysg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@syncfusion/ej2-base": { + "version": "35.1.37", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-base/-/ej2-base-35.1.37.tgz", + "integrity": "sha512-pg4WMFGbZJ9XoGr7ecBQ4+afWeVh1s0nckwqa1fFe1PK48Szg/gV5rEFXygFsHfratparBVAJogAd8WrWv3pYg==", + "license": "SEE LICENSE IN license", + "dependencies": { + "@syncfusion/ej2-icons": "~35.1.37" + }, + "bin": { + "syncfusion-license": "bin/syncfusion-license.js" + } + }, + "node_modules/@syncfusion/ej2-buttons": { + "version": "35.1.37", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-buttons/-/ej2-buttons-35.1.37.tgz", + "integrity": "sha512-tLYpbTqfPPNBqVZ8SmwM9yw+gAkBRhD0Rcl+1YcrG/8z43tVqWJdemtOlOItV/GGEQXXhDbX7PKgJs9m3CDkrg==", + "license": "SEE LICENSE IN license", + "dependencies": { + "@syncfusion/ej2-base": "~35.1.37" + } + }, + "node_modules/@syncfusion/ej2-calculate": { + "version": "35.1.37", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-calculate/-/ej2-calculate-35.1.37.tgz", + "integrity": "sha512-q6Ogf+3vUcDZFOs7cdhFtXwrBmz555dSKnXNPe1QRdOhnZpOYIDYWcy0WlJ9E6zVk8SdvS3hMNKTmVP8DE1lxA==", + "license": "SEE LICENSE IN license", + "dependencies": { + "@syncfusion/ej2-base": "~35.1.37" + } + }, + "node_modules/@syncfusion/ej2-calendars": { + "version": "35.1.37", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-calendars/-/ej2-calendars-35.1.37.tgz", + "integrity": "sha512-KLYnPjinjjpHU4I4hZwJKWuMm8AYPWnnT7R/kVYQ0fMz4Vt1XrNCcOyByTvSINTDvb4sZeF6a5VTRNL6/i7Xgw==", + "license": "SEE LICENSE IN license", + "dependencies": { + "@syncfusion/ej2-base": "~35.1.37", + "@syncfusion/ej2-buttons": "~35.1.37", + "@syncfusion/ej2-inputs": "~35.1.37", + "@syncfusion/ej2-lists": "~35.1.37", + "@syncfusion/ej2-popups": "~35.1.37" + } + }, + "node_modules/@syncfusion/ej2-charts": { + "version": "35.1.37", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-charts/-/ej2-charts-35.1.37.tgz", + "integrity": "sha512-q9ShJ4K7ozYRPqSpENay95eoe4ZBftHogveYTRsJTAKXCrh7tbjxJ9doeKBN+pcdqIBqcK0ecXDw4vPyxRvEtA==", + "license": "SEE LICENSE IN license", + "dependencies": { + "@syncfusion/ej2-base": "~35.1.37", + "@syncfusion/ej2-calendars": "~35.1.37", + "@syncfusion/ej2-data": "~35.1.37", + "@syncfusion/ej2-excel-export": "~35.1.37", + "@syncfusion/ej2-navigations": "~35.1.37", + "@syncfusion/ej2-pdf-export": "~35.1.37", + "@syncfusion/ej2-svg-base": "~35.1.37" + } + }, + "node_modules/@syncfusion/ej2-collaborator": { + "version": "35.1.37", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-collaborator/-/ej2-collaborator-35.1.37.tgz", + "integrity": "sha512-Ff72o4M1yIw8VtjaiKyicHv7e5jr0jcL3Mak0yrvOCZoLyp4ucu5Zi5f+WHAy9qj4Tgu2WCuhzqRML8tx8G5gA==", + "license": "SEE LICENSE IN license", + "dependencies": { + "@syncfusion/ej2-base": "~35.1.37" + } + }, + "node_modules/@syncfusion/ej2-compression": { + "version": "35.1.37", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-compression/-/ej2-compression-35.1.37.tgz", + "integrity": "sha512-Lz+5N1iahYLtmyznJ9O+xTq34Mfc7PXFJna28h7PGJ7YNkZxJDLi1KijypJHj+LZfi8NAQsn7MRMnTFfbHxBmQ==", + "license": "SEE LICENSE IN license", + "dependencies": { + "@syncfusion/ej2-file-utils": "~35.1.37" + } + }, + "node_modules/@syncfusion/ej2-data": { + "version": "35.1.37", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-data/-/ej2-data-35.1.37.tgz", + "integrity": "sha512-rjHvWvwOYWCvWhptPe9ldenOKZXPXFer0v2UrbJhSfJ1oC9NxCm+HX1MrfY46SStOz93s3X8XorF2LgQlXRyRA==", + "license": "SEE LICENSE IN license", + "dependencies": { + "@syncfusion/ej2-base": "~35.1.37" + } + }, + "node_modules/@syncfusion/ej2-dropdowns": { + "version": "35.1.37", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-dropdowns/-/ej2-dropdowns-35.1.37.tgz", + "integrity": "sha512-StvzQPhuFZIQd53PAIWg7gsBiEKNMJuRueDgCNKTUoJo45fuHphShhb4RIwjnM1D6BJqQ1a/7oxeWQYxdaH/CQ==", + "license": "SEE LICENSE IN license", + "dependencies": { + "@syncfusion/ej2-base": "~35.1.37", + "@syncfusion/ej2-data": "~35.1.37", + "@syncfusion/ej2-inputs": "~35.1.37", + "@syncfusion/ej2-lists": "~35.1.37", + "@syncfusion/ej2-navigations": "~35.1.37", + "@syncfusion/ej2-notifications": "~35.1.37", + "@syncfusion/ej2-popups": "~35.1.37" + } + }, + "node_modules/@syncfusion/ej2-excel-export": { + "version": "35.1.37", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-excel-export/-/ej2-excel-export-35.1.37.tgz", + "integrity": "sha512-oTjGCkiTpsx06EVbvwrwaTjORucMizuCb/AnfbjGoHVPGfZj74cchHQY+8rmY2uSRmz9nLQIGhc4Ip1JNcWDZg==", + "license": "SEE LICENSE IN license", + "dependencies": { + "@syncfusion/ej2-base": "~35.1.37", + "@syncfusion/ej2-compression": "~35.1.37" + } + }, + "node_modules/@syncfusion/ej2-file-utils": { + "version": "35.1.37", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-file-utils/-/ej2-file-utils-35.1.37.tgz", + "integrity": "sha512-+Psm/DeB0b8DJeo63OU997bSSSL8Lj6s7tnY0CFUb1nXHmJ6pxxGLapf6MbBJYFSD+as1IWk2kFHkmWI1hu5Xg==", + "license": "SEE LICENSE IN license" + }, + "node_modules/@syncfusion/ej2-grids": { + "version": "35.1.37", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-grids/-/ej2-grids-35.1.37.tgz", + "integrity": "sha512-4t3o3VsUoYCes+gIVsYTyb2jbADfMMbGmGbJdeg9D4/Vl7sfvMdM7cWAxOX87oGwxhua29hQw7+8DLZ2Wm1Agg==", + "license": "SEE LICENSE IN license", + "dependencies": { + "@syncfusion/ej2-base": "~35.1.37", + "@syncfusion/ej2-buttons": "~35.1.37", + "@syncfusion/ej2-calendars": "~35.1.37", + "@syncfusion/ej2-compression": "~35.1.37", + "@syncfusion/ej2-data": "~35.1.37", + "@syncfusion/ej2-dropdowns": "~35.1.37", + "@syncfusion/ej2-excel-export": "~35.1.37", + "@syncfusion/ej2-file-utils": "~35.1.37", + "@syncfusion/ej2-inputs": "~35.1.37", + "@syncfusion/ej2-lists": "~35.1.37", + "@syncfusion/ej2-navigations": "~35.1.37", + "@syncfusion/ej2-notifications": "~35.1.37", + "@syncfusion/ej2-pdf-export": "~35.1.37", + "@syncfusion/ej2-popups": "~35.1.37", + "@syncfusion/ej2-querybuilder": "~35.1.37", + "@syncfusion/ej2-splitbuttons": "~35.1.37" + } + }, + "node_modules/@syncfusion/ej2-icons": { + "version": "35.1.37", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-icons/-/ej2-icons-35.1.37.tgz", + "integrity": "sha512-D7LC3O2OI2Ugc/DLShHvhoEPBEZCT9hj49PjV30CxfJ8ZyVR+EWl/GC3b1q1xMKLiB7ZhDrXBoZgq5mJaeVZuw==", + "license": "SEE LICENSE IN license" + }, + "node_modules/@syncfusion/ej2-inputs": { + "version": "35.1.37", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-inputs/-/ej2-inputs-35.1.37.tgz", + "integrity": "sha512-P4NQI/MQrfAqnpMNuRsxB0GMIXXZ5EIPPIt/oZnq1gAzGbDj7HM4yz4rg6xtiae8t2nMr0QI4cggL2dDKzpfHQ==", + "license": "SEE LICENSE IN license", + "dependencies": { + "@syncfusion/ej2-base": "~35.1.37", + "@syncfusion/ej2-buttons": "~35.1.37", + "@syncfusion/ej2-popups": "~35.1.37", + "@syncfusion/ej2-splitbuttons": "~35.1.37" + } + }, + "node_modules/@syncfusion/ej2-interactive-chat": { + "version": "35.1.37", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-interactive-chat/-/ej2-interactive-chat-35.1.37.tgz", + "integrity": "sha512-oq+h77v2DlbQBblBAfT25DKoLUMtBKiFxrlkgpDq2QqTLIhcD0XlrOXO5FKlUsLTMpmV71pZjpyNWyWc4rH+/g==", + "license": "SEE LICENSE IN license", + "dependencies": { + "@syncfusion/ej2-base": "~35.1.37", + "@syncfusion/ej2-dropdowns": "~35.1.37", + "@syncfusion/ej2-inputs": "~35.1.37", + "@syncfusion/ej2-layouts": "~35.1.37", + "@syncfusion/ej2-markdown-converter": "~35.1.37", + "@syncfusion/ej2-navigations": "~35.1.37", + "@syncfusion/ej2-notifications": "~35.1.37", + "@syncfusion/ej2-popups": "~35.1.37", + "@syncfusion/ej2-splitbuttons": "~35.1.37" + } + }, + "node_modules/@syncfusion/ej2-layouts": { + "version": "35.1.37", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-layouts/-/ej2-layouts-35.1.37.tgz", + "integrity": "sha512-5yj54DPYAKLjuIA+pO3IaZryGWbqAfXRZZuyOb35cjOFHoDDeAoQ9x0ocigxUYFv/IOx/6JW3M79Fb3hx7XA8g==", + "license": "SEE LICENSE IN license", + "dependencies": { + "@syncfusion/ej2-base": "~35.1.37" + } + }, + "node_modules/@syncfusion/ej2-lists": { + "version": "35.1.37", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-lists/-/ej2-lists-35.1.37.tgz", + "integrity": "sha512-GA5cnNXA+Kpme2nDAxld7x88T1YnoMDvHidTk2CJj/Dp3mwvB2/FScaGxYjidES1Hev0XWvJtgnGPdhI95gE2Q==", + "license": "SEE LICENSE IN license", + "dependencies": { + "@syncfusion/ej2-base": "~35.1.37", + "@syncfusion/ej2-buttons": "~35.1.37", + "@syncfusion/ej2-data": "~35.1.37", + "@syncfusion/ej2-popups": "~35.1.37" + } + }, + "node_modules/@syncfusion/ej2-markdown-converter": { + "version": "35.1.37", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-markdown-converter/-/ej2-markdown-converter-35.1.37.tgz", + "integrity": "sha512-fBtN3+1ACdXIYbzAX35gDcOTtR5VCdjW2QSWaLpO3jhh+NntOrhfM82ntxI+TOdyZYOvtNxt/pQjIp2K/UOGBA==", + "license": "SEE LICENSE IN license", + "dependencies": { + "@syncfusion/ej2-base": "~35.1.37" + } + }, + "node_modules/@syncfusion/ej2-navigations": { + "version": "35.1.37", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-navigations/-/ej2-navigations-35.1.37.tgz", + "integrity": "sha512-jqpVXZimfqfIPzCf9I7KPB47hVdiouYDnmEHIpayekQlSdMq2qZQJy59sqb8genYL4OR/KnEFJlcIp1mdhJ8Kw==", + "license": "SEE LICENSE IN license", + "dependencies": { + "@syncfusion/ej2-base": "~35.1.37", + "@syncfusion/ej2-buttons": "~35.1.37", + "@syncfusion/ej2-data": "~35.1.37", + "@syncfusion/ej2-inputs": "~35.1.37", + "@syncfusion/ej2-lists": "~35.1.37", + "@syncfusion/ej2-notifications": "~35.1.37", + "@syncfusion/ej2-popups": "~35.1.37" + } + }, + "node_modules/@syncfusion/ej2-notifications": { + "version": "35.1.37", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-notifications/-/ej2-notifications-35.1.37.tgz", + "integrity": "sha512-UyjQ2Ykx8N1YrikYxrMaK9F7x/u4Ea4ZdCO8rL07binfF+lDEFlPpMoLbiQYatwb8gN++d5UbcyQXs/sYLIYfg==", + "license": "SEE LICENSE IN license", + "dependencies": { + "@syncfusion/ej2-base": "~35.1.37", + "@syncfusion/ej2-buttons": "~35.1.37", + "@syncfusion/ej2-popups": "~35.1.37" + } + }, + "node_modules/@syncfusion/ej2-pdf-export": { + "version": "35.1.37", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-pdf-export/-/ej2-pdf-export-35.1.37.tgz", + "integrity": "sha512-9+2GJBe0vjs0CuIBgQQd7GcQ2CaxCh//OqNlnbGvPO4dfagEj0TUoKpbhIK9Qs5+l1KHvpSgPhQ80LuZBfyHhw==", + "license": "SEE LICENSE IN license", + "dependencies": { + "@syncfusion/ej2-compression": "~35.1.37" + } + }, + "node_modules/@syncfusion/ej2-popups": { + "version": "35.1.37", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-popups/-/ej2-popups-35.1.37.tgz", + "integrity": "sha512-HC3x/sR07YDDc/eS/iZrN0SyJkuAXDS1uRABeN6UdPdkp2o7IOkSOCAyAURMY/dHqpCDblhsnE0LXZ6HwlcQaQ==", + "license": "SEE LICENSE IN license", + "dependencies": { + "@syncfusion/ej2-base": "~35.1.37", + "@syncfusion/ej2-buttons": "~35.1.37" + } + }, + "node_modules/@syncfusion/ej2-querybuilder": { + "version": "35.1.37", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-querybuilder/-/ej2-querybuilder-35.1.37.tgz", + "integrity": "sha512-h2xUbOt+I88ICtWRhcMrmizfX4fXU9ZfSc2cR/Umn5vSoMJE52AEAQ9iamRiMTJUFAwOyINpdfabr1pO7xngKg==", + "license": "SEE LICENSE IN license", + "dependencies": { + "@syncfusion/ej2-base": "~35.1.37", + "@syncfusion/ej2-buttons": "~35.1.37", + "@syncfusion/ej2-calendars": "~35.1.37", + "@syncfusion/ej2-dropdowns": "~35.1.37", + "@syncfusion/ej2-inputs": "~35.1.37", + "@syncfusion/ej2-splitbuttons": "~35.1.37" + } + }, + "node_modules/@syncfusion/ej2-splitbuttons": { + "version": "35.1.37", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-splitbuttons/-/ej2-splitbuttons-35.1.37.tgz", + "integrity": "sha512-whjKUf7p9grTtDxssx4s2u9gYDysiRHBn658dj6oRMKoYAECdaw5H/WEIcqKoxA6B1hJyUHZ8GE1SbWdV7eSVw==", + "license": "SEE LICENSE IN license", + "dependencies": { + "@syncfusion/ej2-base": "~35.1.37", + "@syncfusion/ej2-popups": "~35.1.37" + } + }, + "node_modules/@syncfusion/ej2-spreadsheet": { + "version": "35.1.37", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-spreadsheet/-/ej2-spreadsheet-35.1.37.tgz", + "integrity": "sha512-TEM28i5E17uFG37HpYLaRhpjiyq/k4KxQG6MJMTn3JroZtM2+BUvki6l847xhCdPt1Jrvg8slc0uKI28Cox3XA==", + "license": "SEE LICENSE IN license", + "dependencies": { + "@syncfusion/ej2-base": "~35.1.37", + "@syncfusion/ej2-calculate": "~35.1.37", + "@syncfusion/ej2-charts": "~35.1.37", + "@syncfusion/ej2-dropdowns": "~35.1.37", + "@syncfusion/ej2-grids": "~35.1.37", + "@syncfusion/ej2-interactive-chat": "~35.1.37", + "@syncfusion/ej2-navigations": "~35.1.37" + } + }, + "node_modules/@syncfusion/ej2-svg-base": { + "version": "35.1.37", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-svg-base/-/ej2-svg-base-35.1.37.tgz", + "integrity": "sha512-jgTjelQSmDHETQfXaecRL92tw/i63ijnACZEwp97gDl+bbY8Nc/PjTvaHNjECjuOL2Nt83q4z77OnGjE2zmNJQ==", + "license": "SEE LICENSE IN license", + "dependencies": { + "@syncfusion/ej2-base": "~35.1.37" + } + }, + "node_modules/@syncfusion/ej2-tailwind3-theme": { + "version": "35.1.37", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-tailwind3-theme/-/ej2-tailwind3-theme-35.1.37.tgz", + "integrity": "sha512-1Qujjxkeo4pH58CcBHytkqwTuZZRlFeoEfdzyfkvZGjtcg9dH2sOMYVwz6GVs4dRrtm/KJkqeBXMToCMR6bT2g==", + "license": "SEE LICENSE IN license" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/esbuild": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/eventsource": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-2.0.2.tgz", + "integrity": "sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fetch-cookie": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/fetch-cookie/-/fetch-cookie-2.2.0.tgz", + "integrity": "sha512-h9AgfjURuCgA2+2ISl8GbavpUdR+WGAM2McW/ovn4tVccegp8ZqCKWSBR8uRdM8dDNlx5WdKRWxBYUwteLDCNQ==", + "license": "Unlicense", + "dependencies": { + "set-cookie-parser": "^2.4.8", + "tough-cookie": "^4.0.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.29", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.29.tgz", + "integrity": "sha512-49cGhUbXj8Qenv0iTMxA1cFBzxXoctpC9Ujd77t1WcbJIr6nF/eI7g/8MgxrYldFRuAXvja7xQRwavoW7kgrxQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.19", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/psl": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/lupomontero" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "license": "MIT" + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.64.0.tgz", + "integrity": "sha512-gjA/kFeDfffII8COoPsvLPvE16hWDTEc1+0QKQNKUkuV8iJjqsC4xXs8/FzuL0v9EV39kuxTbAgcFoTRyy2TGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.64.0", + "@rollup/rollup-android-arm64": "4.64.0", + "@rollup/rollup-darwin-arm64": "4.64.0", + "@rollup/rollup-darwin-x64": "4.64.0", + "@rollup/rollup-freebsd-arm64": "4.64.0", + "@rollup/rollup-freebsd-x64": "4.64.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.64.0", + "@rollup/rollup-linux-arm-musleabihf": "4.64.0", + "@rollup/rollup-linux-arm64-gnu": "4.64.0", + "@rollup/rollup-linux-arm64-musl": "4.64.0", + "@rollup/rollup-linux-loong64-gnu": "4.64.0", + "@rollup/rollup-linux-loong64-musl": "4.64.0", + "@rollup/rollup-linux-ppc64-gnu": "4.64.0", + "@rollup/rollup-linux-ppc64-musl": "4.64.0", + "@rollup/rollup-linux-riscv64-gnu": "4.64.0", + "@rollup/rollup-linux-riscv64-musl": "4.64.0", + "@rollup/rollup-linux-s390x-gnu": "4.64.0", + "@rollup/rollup-linux-x64-gnu": "4.64.0", + "@rollup/rollup-linux-x64-musl": "4.64.0", + "@rollup/rollup-openbsd-x64": "4.64.0", + "@rollup/rollup-openharmony-arm64": "4.64.0", + "@rollup/rollup-win32-arm64-msvc": "4.64.0", + "@rollup/rollup-win32-ia32-msvc": "4.64.0", + "@rollup/rollup-win32-x64-gnu": "4.64.0", + "@rollup/rollup-win32-x64-msvc": "4.64.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/source-map-js": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "license": "MIT", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/vite": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.7.tgz", + "integrity": "sha512-AHEzTORMbi7/7aXp7w7KZ5+DCUdSeLsJz0grY8xHpc9DHdYb0vQu6DqJfm2tOw3gDExNgPgnJTXxM6syYFHaCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0 || ^0.28.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/ws": { + "version": "7.5.13", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.13.tgz", + "integrity": "sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA==", + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/client/javascript/package.json b/client/javascript/package.json new file mode 100644 index 0000000..3b56e90 --- /dev/null +++ b/client/javascript/package.json @@ -0,0 +1,20 @@ +{ + "name": "javascript-spreadsheet-collaborative-editing", + "private": true, + "version": "1.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "@microsoft/signalr": "^9.0.0", + "@syncfusion/ej2-collaborator": "35.1.37", + "@syncfusion/ej2-spreadsheet": "35.1.37", + "@syncfusion/ej2-tailwind3-theme": "35.1.37" + }, + "devDependencies": { + "vite": "^7.1.9" + } +} diff --git a/client/javascript/src/index.js b/client/javascript/src/index.js new file mode 100644 index 0000000..8b4670a --- /dev/null +++ b/client/javascript/src/index.js @@ -0,0 +1,92 @@ +import { + CollaborativeEditingHandler, + Spreadsheet +} from '@syncfusion/ej2-spreadsheet'; +import { CollaborationClient } from '@syncfusion/ej2-collaborator'; +import { SpreadsheetEditorAdapter } from './spreadsheet-editor-adapter'; +import './style.css'; + +const serviceUrl = + ''YOUR_COLLABORATION_SERVER_URL''; +const currentUser = 'John'; + +Spreadsheet.Inject(CollaborativeEditingHandler); + +function getRoomName() { + const currentUrl = new URL(window.location.href); + let roomName = ( + currentUrl.searchParams.get('id') || '' + ).trim(); + + if (!roomName) { + roomName = Math.random().toString(32).slice(2); + currentUrl.searchParams.set('id', roomName); + window.history.replaceState( + window.history.state, + '', + currentUrl.pathname + currentUrl.search + currentUrl.hash + ); + } + + return roomName; +} + +let adapter = null; +let collaborationClient = null; +let initialized = false; + +const spreadsheet = new Spreadsheet({ + height: '550px', + enableCollaborativeEditing: true, + created: async () => { + if (initialized) { + return; + } + + initialized = true; + const roomName = getRoomName(); + const spreadsheetAdapter = new SpreadsheetEditorAdapter( + spreadsheet, + serviceUrl, + currentUser + ); + + try { + await spreadsheetAdapter.loadFromServer( + 'Sample', + roomName + ); + + const client = new CollaborationClient( + spreadsheetAdapter, + { + serviceUrl, + connectionType: 'signalr', + currentUser + } + ); + + adapter = spreadsheetAdapter; + collaborationClient = client; + await client.joinRoomAsync(roomName); + + console.log( + '[Collaborative Editing] Joined room', + roomName + ); + } catch (error) { + initialized = false; + console.error( + '[Collaborative Editing] Failed to join the room.', + error + ); + } + }, + actionComplete: (args) => { + adapter?.sendActionToServer(args); + } +}); + +spreadsheet.appendTo('#spreadsheet'); + +void collaborationClient; diff --git a/client/javascript/src/spreadsheet-editor-adapter.js b/client/javascript/src/spreadsheet-editor-adapter.js new file mode 100644 index 0000000..e142f54 --- /dev/null +++ b/client/javascript/src/spreadsheet-editor-adapter.js @@ -0,0 +1,66 @@ +export class SpreadsheetEditorAdapter { + constructor(spreadsheet, serviceUrl, currentUser) { + this.spreadsheet = spreadsheet; + this.serviceUrl = serviceUrl.endsWith('/') + ? serviceUrl + : serviceUrl + '/'; + this.currentUser = currentUser; + this.currentRoomName = ''; + } + + async loadFromServer(fileName, roomName) { + 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 the workbook.'); + } + + const data = JSON.parse(await response.text()); + + this.currentRoomName = roomName; + this.spreadsheet.collaborativeEditingModule.updateRoomInfo( + roomName, + data.version, + this.serviceUrl + 'api/CollaborativeEditing/' + ); + this.spreadsheet.collaborativeEditingModule.setLocalUser( + this.currentUser + ); + this.spreadsheet.openFromJson({ + file: data.sfdt + }); + } + + sendActionToServer(action) { + if (!action) { + return; + } + + this.spreadsheet.collaborativeEditingModule.sendActionToServer( + action + ); + } + + applyRemoteAction(action, data) { + if (!data) { + return; + } + + this.spreadsheet.collaborativeEditingModule.applyRemoteAction( + action, + data.payload + ); + } +} diff --git a/client/javascript/src/style.css b/client/javascript/src/style.css new file mode 100644 index 0000000..8a9a433 --- /dev/null +++ b/client/javascript/src/style.css @@ -0,0 +1,11 @@ +@import '@syncfusion/ej2-tailwind3-theme/styles/tailwind3.css'; + +html, +body { + height: 100%; + margin: 0; +} + +#spreadsheet { + width: 100%; +} diff --git a/client/react/README.md b/client/react/README.md index cc6a6e0..9d9bd24 100644 --- a/client/react/README.md +++ b/client/react/README.md @@ -36,7 +36,7 @@ Open `src/App.tsx` and update the following URL when required: ```ts const serviceUrl: string = - 'https://appservice-267100-e9c8cxaab6a2dfeu.centralindia-01.azurewebsites.net/'; + ''YOUR_COLLABORATION_SERVER_URL''; ``` The URL must point to the running ASP.NET Core Collaboration Server. diff --git a/client/react/src/App.tsx b/client/react/src/App.tsx index e32f1e6..a0a47ef 100644 --- a/client/react/src/App.tsx +++ b/client/react/src/App.tsx @@ -9,7 +9,7 @@ import { SpreadsheetEditorAdapter } from './SpreadsheetEditorAdapter'; import './App.css'; const serviceUrl: string = - 'https://appservice-267100-e9c8cxaab6a2dfeu.centralindia-01.azurewebsites.net/'; + ''YOUR_COLLABORATION_SERVER_URL''; const currentUser: string = 'John'; diff --git a/client/typescript/src/index.ts b/client/typescript/src/index.ts index 84b604d..26d1cc4 100644 --- a/client/typescript/src/index.ts +++ b/client/typescript/src/index.ts @@ -7,7 +7,7 @@ import { SpreadsheetEditorAdapter } from './spreadsheet-editor-adapter'; import './style.css'; const serviceUrl: string = - 'https://appservice-267100-e9c8cxaab6a2dfeu.centralindia-01.azurewebsites.net/'; + ''YOUR_COLLABORATION_SERVER_URL''; const currentUser: string = 'John'; Spreadsheet.Inject(CollaborativeEditingHandler); diff --git a/client/vue/src/App.vue b/client/vue/src/App.vue index 6c04bbf..344fbaa 100644 --- a/client/vue/src/App.vue +++ b/client/vue/src/App.vue @@ -11,7 +11,7 @@ import { CollaborationClient } from '@syncfusion/ej2-collaborator'; import { SpreadsheetEditorAdapter } from './SpreadsheetEditorAdapter'; const serviceUrl: string = - 'https://appservice-267100-e9c8cxaab6a2dfeu.centralindia-01.azurewebsites.net/'; + ''YOUR_COLLABORATION_SERVER_URL''; const currentUser: string = 'John'; provide('spreadsheet', [CollaborativeEditingHandler]); From 05c3694ef5f65c5f0b39e99482a163b3a3c3a742 Mon Sep 17 00:00:00 2001 From: bhuvaneshdhakshinmaoorthy Date: Tue, 6 Oct 2026 16:37:39 +0530 Subject: [PATCH 2/2] 1055046: Made corrections and added readme files --- README.md | 307 ++---------------- .../spreadsheet-editor.component.ts | 2 +- client/javascript/src/index.js | 2 +- client/react/README.md | 2 +- client/react/src/App.tsx | 2 +- client/typescript/src/index.ts | 2 +- client/vue/src/App.vue | 2 +- server/aspnet-core/README.md | 225 +++++++++++++ 8 files changed, 257 insertions(+), 287 deletions(-) create mode 100644 server/aspnet-core/README.md diff --git a/README.md b/README.md index 9615dee..a23eb64 100644 --- a/README.md +++ b/README.md @@ -1,89 +1,23 @@ # Spreadsheet Collaborative Editing -This repository demonstrates real-time collaborative editing in the Syncfusion® Spreadsheet. Multiple users can open the same workbook, edit it simultaneously, view other participants, and receive synchronized changes in real time. - -The repository includes client-side samples for Angular, JavaScript, React, TypeScript, and Vue. Each client connects to a Collaboration Server through SignalR and uses Redis to maintain collaboration sessions and synchronize workbook actions. +This repository demonstrates real-time collaborative editing in the Syncfusion® Spreadsheet. Multiple users can edit the same workbook simultaneously, and supported changes are synchronized across all users connected to the same collaboration room. ## Features - Real-time multi-user Spreadsheet editing -- Synchronization of supported Spreadsheet actions across connected clients +- Synchronization of supported Spreadsheet actions - Participant presence and selection indicators -- Unique collaboration rooms identified through the `id` URL query parameter - SignalR-based real-time communication - Redis-backed collaboration state management -- Client examples for Angular, JavaScript, React, TypeScript, and Vue -- Reusable collaboration adapter implementation for each platform +- Client samples for Angular, JavaScript, React, TypeScript, and Vue ## Prerequisites -Before running the samples, ensure that the following software and services are available. - -### Client applications - -- Node.js -- npm -- A supported modern web browser - -### Collaboration Server - +- Node.js and npm - ASP.NET Core Collaboration Server - Redis Server or a hosted Redis service - A valid Syncfusion license key -## Real-time communication - -The client applications connect to the Collaboration Server through SignalR. The real-time connection is used to join a collaboration room and receive actions, participant updates, and selection changes from other connected users. - -Each client uses the following configuration: - -```ts -const client = new CollaborationClient(adapter, { - serviceUrl, - connectionType: 'signalr', - currentUser -}); -``` - -The client and Collaboration Server must use the same transport configuration. - -## Redis backing system - -Redis is used by the Collaboration Server to store collaboration operations, maintain room state, and coordinate updates between connected users and server instances. - -Configure the Redis connection in the Collaboration Server settings: - -```json -{ - "ConnectionStrings": { - "Redis": "YOUR_REDIS_CONNECTION_STRING" - } -} -``` - -Replace `YOUR_REDIS_CONNECTION_STRING` with the connection string of the running Redis service. - -## How collaborative editing works - -### Client side - -1. The client obtains the room ID from the `id` query parameter. -2. If the URL does not contain a room ID, the client generates one and updates the browser URL. -3. The client requests the latest synchronized workbook and room version from the Collaboration Server. -4. The Spreadsheet initializes its collaboration room, server version, service endpoint, and local user. -5. The `CollaborationClient` establishes a SignalR connection and joins the room. -6. Supported local Spreadsheet actions are sent to the Collaboration Server from the `actionComplete` event. -7. Remote actions received from other participants are applied to the Spreadsheet through the collaboration adapter. - -### Server side - -1. The Collaboration Server receives actions from connected clients. -2. Actions are ordered and stored using Redis. -3. The server processes the actions against the current room version. -4. The latest room and workbook state is maintained for subsequent users. -5. Processed actions are broadcast to the other clients in the same collaboration room. -6. Connected clients apply the remote actions and remain synchronized. - ## Repository structure ```text @@ -102,244 +36,55 @@ Spreadsheet-Collaborative-Editing/ ## Client samples -The following client implementations are available: - - [Angular](./client/angular) - [JavaScript](./client/javascript) - [React](./client/react) - [TypeScript](./client/typescript) - [Vue](./client/vue) -Each client folder contains its own installation instructions and platform-specific source files. - -## Configure the Collaboration Server URL - -Before starting a client application, replace the placeholder Collaboration Server URL in the client source code: - -```ts -const serviceUrl: string = 'YOUR_COLLABORATION_SERVER_URL'; -``` - -For JavaScript: - -```js -const serviceUrl = 'YOUR_COLLABORATION_SERVER_URL'; -``` - -Replace `YOUR_COLLABORATION_SERVER_URL` with the URL of the running Collaboration Server. For example: - -```text -https://localhost:5001/ -``` - -The adapter automatically appends a trailing slash when the configured URL does not contain one. - -## Run the Collaboration Server - -Navigate to the ASP.NET Core Collaboration Server folder: - -```bash -cd server/aspnet-core -``` - -Update the Redis connection string and configure the Syncfusion license key as required by the server project. - -Restore the server dependencies: - -```bash -dotnet restore -``` - -Run the Collaboration Server: - -```bash -dotnet run -``` - -Use the server URL displayed in the terminal as `YOUR_COLLABORATION_SERVER_URL` in the client applications. - -## Run a client application - -Choose one client implementation and navigate to its folder. - -### Angular - -```bash -cd client/angular -npm install -npm start -``` - -The Angular application normally runs at: - -```text -http://localhost:4200/ -``` - -### React - -```bash -cd client/react -npm install -npm run dev -``` +Refer to the README file inside each client folder for platform-specific installation, configuration, and run instructions. -### Vue +## Collaboration Server -```bash -cd client/vue -npm install -npm run dev -``` +The Collaboration Server uses SignalR for real-time communication and Redis to maintain collaboration sessions and synchronize workbook actions. -### TypeScript +Configure the Redis connection string in the server settings: -```bash -cd client/typescript -npm install -npm run dev +```json +{ + "ConnectionStrings": { + "Redis": "YOUR_REDIS_CONNECTION_STRING" + } +} ``` -### JavaScript +Refer to the README file inside the server folder for setup and run instructions. -```bash -cd client/javascript -npm install -npm run dev -``` +## Configure the server URL -The Vite-based applications normally run at: +In each client sample, replace the Collaboration Server URL placeholder with the URL of the running server: ```text -http://localhost:5173/ +YOUR_COLLABORATION_SERVER_URL ``` -Use the actual URL displayed in the terminal if a different port is selected. +Users who connect to the same server and open a URL containing the same `id` query parameter join the same collaboration room. ## Test collaborative editing -1. Start Redis. -2. Start the Collaboration Server. -3. Start one of the client applications. -4. Open the client URL in a browser. -5. Confirm that the client adds an `id` query parameter to the URL. -6. Copy the complete URL, including the room ID. -7. Open the copied URL in another browser tab, browser window, or browser profile. -8. Edit a cell in the first client. -9. Confirm that the same change appears in the second client. -10. Make another supported change in the second client and confirm that it appears in the first client. - -Example collaboration URL: - -```text -http://localhost:5173/?id=sample-room -``` - -All users who open the same URL and connect to the same Collaboration Server join the same workbook session. - -## Client-side packages - -The samples use the following collaboration packages: - -```text -@syncfusion/ej2-collaborator -@microsoft/signalr -``` - -The platform-specific Spreadsheet package is also required: - -```text -@syncfusion/ej2-angular-spreadsheet -@syncfusion/ej2-react-spreadsheet -@syncfusion/ej2-vue-spreadsheet -@syncfusion/ej2-spreadsheet -``` - -The exact package list is available in the `package.json` file of each client sample. - -## Build the client applications - -For React, Vue, TypeScript, and JavaScript: - -```bash -npm run build -``` - -For Angular: - -```bash -npm run build -``` - -The generated production files are written to the platform's configured output directory. - -## Troubleshooting - -### The collaborator package cannot be resolved - -Ensure that `@syncfusion/ej2-collaborator` is listed in the client `package.json`, and then run: - -```bash -npm install -``` - -### The SignalR package cannot be resolved - -Ensure that `@microsoft/signalr` is listed in the client `package.json`, and then run: - -```bash -npm install -``` - -### The Spreadsheet is not styled correctly - -Confirm that the Syncfusion theme is imported by the client application. The Vite samples use: - -```css -@import '@syncfusion/ej2-tailwind3-theme/styles/tailwind3.css'; -``` - -### The workbook does not load - -Verify that: - -- The Collaboration Server is running. -- The configured `serviceUrl` is correct. -- The `ImportFile` endpoint is reachable. -- The source workbook required by the server is available. -- The browser console does not contain CORS or network errors. - -### Changes are not synchronized - -Verify that: - -- Redis is running and the configured connection string is valid. -- All clients connect to the same Collaboration Server. -- All clients use the same room ID. -- The SignalR connection is established successfully. -- The browser console does not contain connection or action-processing errors. - -### The second browser opens a different workbook session - -Copy and open the complete URL containing the `id` query parameter. Opening only the base URL generates a new room ID and creates a different collaboration session. +1. Start Redis and the Collaboration Server. +2. Start any client sample. +3. Open the application in a browser. +4. Copy the complete URL containing the `id` query parameter. +5. Open the copied URL in another browser tab or window. +6. Edit the workbook and verify that supported changes are synchronized between both clients. ## Documentation -- [Spreadsheet collaborative editing overview](https://help.syncfusion.com/document-processing/excel/spreadsheet/javascript-es5/collaborative-editing/overview) +- [Collaborative editing overview](https://help.syncfusion.com/document-processing/excel/spreadsheet/react/collaborative-editing/overview) - [Collaboration Client](https://help.syncfusion.com/document-processing/collaborator/collaboration-client) - [Collaboration Server](https://help.syncfusion.com/document-processing/collaborator/collaboration-server) -- [ASP.NET Core Redis Collaboration Server](https://help.syncfusion.com/document-processing/excel/spreadsheet/javascript-es5/collaborative-editing/aspnet-core-redis) - -Platform-specific integration documentation: - -- [Angular integration](https://help.syncfusion.com/document-processing/excel/spreadsheet/angular/collaborative-editing/integration) -- [JavaScript integration](https://help.syncfusion.com/document-processing/excel/spreadsheet/javascript-es5/collaborative-editing/integration) -- [React integration](https://help.syncfusion.com/document-processing/excel/spreadsheet/react/collaborative-editing/integration) -- [TypeScript integration](https://help.syncfusion.com/document-processing/excel/spreadsheet/typescript/collaborative-editing/integration) -- [Vue integration](https://help.syncfusion.com/document-processing/excel/spreadsheet/vue/collaborative-editing/integration) ## License This project is licensed under the terms specified in the repository's license file. - -Syncfusion® controls and services are subject to the applicable Syncfusion license terms. Refer to the Syncfusion licensing documentation before using this example in a production application. diff --git a/client/angular/src/components/spreadsheet-editor/spreadsheet-editor.component.ts b/client/angular/src/components/spreadsheet-editor/spreadsheet-editor.component.ts index b10add1..4ab96aa 100644 --- a/client/angular/src/components/spreadsheet-editor/spreadsheet-editor.component.ts +++ b/client/angular/src/components/spreadsheet-editor/spreadsheet-editor.component.ts @@ -9,7 +9,7 @@ import { SpreadsheetEditorAdapter } from './spreadsheet-editor-adapter'; const serviceUrl: string = ''YOUR_COLLABORATION_SERVER_URL''; -const currentUser: string = 'John'; +const currentUser: string = 'John Adams'; function getRoomName(): string { const currentUrl: URL = new URL(window.location.href); diff --git a/client/javascript/src/index.js b/client/javascript/src/index.js index 8b4670a..fab4ac7 100644 --- a/client/javascript/src/index.js +++ b/client/javascript/src/index.js @@ -8,7 +8,7 @@ import './style.css'; const serviceUrl = ''YOUR_COLLABORATION_SERVER_URL''; -const currentUser = 'John'; +const currentUser = 'John Adams'; Spreadsheet.Inject(CollaborativeEditingHandler); diff --git a/client/react/README.md b/client/react/README.md index 9d9bd24..ec1c7ba 100644 --- a/client/react/README.md +++ b/client/react/README.md @@ -70,7 +70,7 @@ Copy the complete URL and open it in another browser window or tab. Clients usin The sample uses the following participant name: ```ts -const currentUser: string = 'John'; +const currentUser: string = 'John Adams'; ``` ## How the sample works diff --git a/client/react/src/App.tsx b/client/react/src/App.tsx index a0a47ef..d2ecabf 100644 --- a/client/react/src/App.tsx +++ b/client/react/src/App.tsx @@ -11,7 +11,7 @@ import './App.css'; const serviceUrl: string = ''YOUR_COLLABORATION_SERVER_URL''; -const currentUser: string = 'John'; +const currentUser: string = 'John Adams'; function getRoomName(): string { const currentUrl: URL = new URL(window.location.href); diff --git a/client/typescript/src/index.ts b/client/typescript/src/index.ts index 26d1cc4..2335257 100644 --- a/client/typescript/src/index.ts +++ b/client/typescript/src/index.ts @@ -8,7 +8,7 @@ import './style.css'; const serviceUrl: string = ''YOUR_COLLABORATION_SERVER_URL''; -const currentUser: string = 'John'; +const currentUser: string = 'John Adams'; Spreadsheet.Inject(CollaborativeEditingHandler); diff --git a/client/vue/src/App.vue b/client/vue/src/App.vue index 344fbaa..bb6b0fc 100644 --- a/client/vue/src/App.vue +++ b/client/vue/src/App.vue @@ -12,7 +12,7 @@ import { SpreadsheetEditorAdapter } from './SpreadsheetEditorAdapter'; const serviceUrl: string = ''YOUR_COLLABORATION_SERVER_URL''; -const currentUser: string = 'John'; +const currentUser: string = 'John Adams'; provide('spreadsheet', [CollaborativeEditingHandler]); diff --git a/server/aspnet-core/README.md b/server/aspnet-core/README.md new file mode 100644 index 0000000..4db6e8c --- /dev/null +++ b/server/aspnet-core/README.md @@ -0,0 +1,225 @@ +# ASP.NET Core Spreadsheet Collaboration Server + +This project provides the ASP.NET Core Collaboration Server for real-time collaborative editing in the Syncfusion® Spreadsheet. It uses SignalR for real-time communication and Redis to maintain collaboration operations and room state. + +## Prerequisites + +Before running the server, ensure that the following are available: + +- .NET 10.0 SDK +- Redis Server or a hosted Redis service +- The `wwwroot/Files/Sample.xlsx` workbook + +## Configure the Redis connection + +Open `appsettings.json` and replace the Redis connection-string placeholder: + +```json +{ + "ConnectionStrings": { + "Redis": "Your_Connection_string" + } +} +``` + +For a local Redis instance, the value can be similar to: + +```json +{ + "ConnectionStrings": { + "Redis": "localhost:6379" + } +} +``` + +## Register the Syncfusion license key + +The server reads the Syncfusion license key from either a local license file or an environment variable. + +### Option 1: License file + +Create a `SyncfusionLicense.txt` file in the project root and add the license key as its content. + +```text +YOUR_SYNCFUSION_LICENSE_KEY +``` + +### Option 2: Environment variable + +Set the `SYNCFUSION_LICENSE_KEY` environment variable before running the server. + +Windows Command Prompt: + +```cmd +set SYNCFUSION_LICENSE_KEY=YOUR_SYNCFUSION_LICENSE_KEY +``` + +Windows PowerShell: + +```powershell +$env:SYNCFUSION_LICENSE_KEY="YOUR_SYNCFUSION_LICENSE_KEY" +``` + +Linux or macOS: + +```bash +export SYNCFUSION_LICENSE_KEY=YOUR_SYNCFUSION_LICENSE_KEY +``` + +## Source workbook + +The collaboration sample loads the source workbook from: + +```text +wwwroot/Files/Sample.xlsx +``` + +Ensure that `Sample.xlsx` exists at this location before starting the server. + +## Run the server + +Navigate to the ASP.NET Core server folder: + +```bash +cd server/aspnet-core +``` + +Restore the required packages: + +```bash +dotnet restore +``` + +Run the server: + +```bash +dotnet run +``` + +The development profile uses the following URLs: + +```text +https://localhost:7002 +http://localhost:5209 +``` + +Use one of these URLs as the Collaboration Server URL in the client applications. + +For example: + +```ts +const serviceUrl: string = 'https://localhost:7002/'; +``` + +## Collaboration endpoints + +The server exposes the following Spreadsheet collaboration endpoints under: + +```text +api/CollaborativeEditing +``` + +- `POST /ImportFile` loads the source workbook, applies pending room operations, and returns the synchronized workbook and version. +- `POST /UpdateAction` stores and broadcasts a Spreadsheet action. +- `POST /UpdateSelection` updates and broadcasts a participant's selection. +- `GET /GetRoomSelections/{roomName}` returns the active selections for a collaboration room. +- `POST /RemoveUserSelection` removes a disconnected participant's selection. +- `POST /GetActionsFromServer` returns actions newer than the client's last synchronized version. + +The collaboration transport endpoint is registered through `MapCollaborationServer` and uses SignalR. + +## Spreadsheet endpoints + +The server also provides the following standard Spreadsheet endpoints: + +```text +POST /api/Spreadsheet/Open +POST /api/Spreadsheet/Save +``` + +These endpoints support opening and saving Spreadsheet files. + +## CORS + +The server uses the `AllowAllOrigins` CORS policy so that the client samples can access the collaboration and Spreadsheet endpoints during development. + +## Test collaborative editing + +1. Start Redis. +2. Run the ASP.NET Core server using `dotnet run`. +3. Configure a client sample with the displayed server URL. +4. Start the client application. +5. Open the complete client URL, including the `id` query parameter, in two browser tabs or windows. +6. Edit a cell and verify that the supported change is synchronized between both clients. + +## Project structure + +```text +server/aspnet-core/ +|-- Adapters/ +| `-- SpreadsheetCollaborativeAdaptor.cs +|-- Controllers/ +| |-- CollaborativeEditingController.cs +| |-- SpreadsheetController.cs +| `-- TestController.cs +|-- Properties/ +| `-- launchSettings.json +|-- wwwroot/ +| `-- Files/ +| `-- Sample.xlsx +|-- appsettings.Development.json +|-- appsettings.json +|-- ej2-spreadsheet-server.csproj +|-- ej2-spreadsheet-server.sln +|-- NuGet.Config +|-- Program.cs +|-- SyncfusionLicense.txt +|-- web.config +`-- README.md +``` + +## Troubleshooting + +### Redis connection failure + +Verify that Redis is running and that the `Redis` value in `appsettings.json` contains a valid connection string. + +### Workbook does not load + +Verify that the following file exists: + +```text +wwwroot/Files/Sample.xlsx +``` + +Also confirm that the browser Network panel does not show an error for: + +```text +api/CollaborativeEditing/ImportFile +``` + +### Client cannot connect to the collaboration room + +Verify that: + +- The ASP.NET Core server is running. +- The client uses the correct server URL. +- The client and server use SignalR. +- The browser accepts the local HTTPS development certificate. +- The browser console does not contain SignalR or network errors. + +### Syncfusion license warning + +Ensure that a valid license key is available through `SyncfusionLicense.txt` or the `SYNCFUSION_LICENSE_KEY` environment variable. + +### Package restore failure + +Confirm that the package sources configured in `NuGet.Config` are accessible, and then run: + +```bash +dotnet restore +``` + +## License + +This project is licensed under the terms specified in the repository's license file. Syncfusion® packages are subject to the applicable Syncfusion license terms.