diff --git a/package-lock.json b/package-lock.json
index 92c7231ec..e03cb63d5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2628,6 +2628,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/cors": {
+ "version": "2.8.17",
+ "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz",
+ "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
"node_modules/@types/emscripten": {
"version": "1.39.13",
"resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.39.13.tgz",
@@ -4475,6 +4485,19 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/cors": {
+ "version": "2.8.5",
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
+ "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
+ "license": "MIT",
+ "dependencies": {
+ "object-assign": "^4",
+ "vary": "^1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
"node_modules/corser": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz",
@@ -8177,6 +8200,15 @@
"url": "https://github.com/fb55/nth-check?sponsor=1"
}
},
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/object-inspect": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz",
@@ -11782,6 +11814,7 @@
"@codingame/monaco-vscode-typescript-basics-default-extension": "~10.1.1",
"@codingame/monaco-vscode-typescript-language-features-default-extension": "~10.1.1",
"@typefox/monaco-editor-react": "~6.0.0-next.5",
+ "cors": "^2.8.5",
"express": "~4.21.1",
"langium": "~3.2.0",
"monaco-editor": "npm:@codingame/monaco-vscode-editor-api@~10.1.1",
@@ -11801,6 +11834,7 @@
"wtd-core": "~4.0.1"
},
"devDependencies": {
+ "@types/cors": "^2.8.17",
"@types/emscripten": "~1.39.13",
"@types/express": "~5.0.0",
"@types/ws": "~8.5.12",
diff --git a/packages/examples/package.json b/packages/examples/package.json
index 186018acc..9e96a7ca9 100644
--- a/packages/examples/package.json
+++ b/packages/examples/package.json
@@ -74,6 +74,7 @@
"@codingame/monaco-vscode-typescript-basics-default-extension": "~10.1.1",
"@codingame/monaco-vscode-typescript-language-features-default-extension": "~10.1.1",
"@typefox/monaco-editor-react": "~6.0.0-next.5",
+ "cors": "^2.8.5",
"express": "~4.21.1",
"langium": "~3.2.0",
"monaco-editor": "npm:@codingame/monaco-vscode-editor-api@~10.1.1",
@@ -93,6 +94,7 @@
"wtd-core": "~4.0.1"
},
"devDependencies": {
+ "@types/cors": "^2.8.17",
"@types/express": "~5.0.0",
"@types/ws": "~8.5.12",
"@types/emscripten": "~1.39.13",
diff --git a/packages/examples/src/json/server/main.ts b/packages/examples/src/json/server/main.ts
index f233ad49e..b05b9ae53 100644
--- a/packages/examples/src/json/server/main.ts
+++ b/packages/examples/src/json/server/main.ts
@@ -4,9 +4,11 @@
* ------------------------------------------------------------------------------------------ */
import { resolve } from 'node:path';
+import cors from 'cors';
+
import { runLanguageServer } from '../../common/node/language-server-runner.js';
import { LanguageName } from '../../common/node/server-commons.js';
-
+import express from 'express';
export const runJsonServer = (baseDir: string, relativeDir: string) => {
const processRunPath = resolve(baseDir, relativeDir);
runLanguageServer({
@@ -23,4 +25,22 @@ export const runJsonServer = (baseDir: string, relativeDir: string) => {
perMessageDeflate: false
}
});
+
+ startMockHttpServerForSavingCodeFromEditor();
+};
+
+export const startMockHttpServerForSavingCodeFromEditor = () => {
+ const app = express();
+ app.use(cors());
+ app.use(express.json());
+ app.post('/save-code', (req, res) => {
+ const { code } = req.body;
+ console.log('Received code:', code);
+ res.json({ success: true, message: code});
+ });
+
+ const PORT = 3003;
+ app.listen(PORT, () => {
+ console.log(`JSON server running on port ${PORT}`);
+ });
};
diff --git a/verify/angular/src/app/app.component.css b/verify/angular/src/app/app.component.css
deleted file mode 100644
index 3a7e0476d..000000000
--- a/verify/angular/src/app/app.component.css
+++ /dev/null
@@ -1,3 +0,0 @@
-.monaco-editor {
- height: 50vh;
-}
diff --git a/verify/angular/src/app/app.component.html b/verify/angular/src/app/app.component.html
index a86adb4fd..c5c54137d 100644
--- a/verify/angular/src/app/app.component.html
+++ b/verify/angular/src/app/app.component.html
@@ -1,4 +1,11 @@
-
Monaco Language Client Angular Client Example
-
-
+
Angular Monaco Editor demo with saving code to a mock HTTP server
+
+
+
+
diff --git a/verify/angular/src/app/app.component.scss b/verify/angular/src/app/app.component.scss
new file mode 100644
index 000000000..e69de29bb
diff --git a/verify/angular/src/app/app.component.ts b/verify/angular/src/app/app.component.ts
index 77985edaf..0a0b510cf 100644
--- a/verify/angular/src/app/app.component.ts
+++ b/verify/angular/src/app/app.component.ts
@@ -3,30 +3,50 @@
* Licensed under the MIT License. See LICENSE in the package root for license information.
* ------------------------------------------------------------------------------------------ */
-import { AfterViewInit, Component } from '@angular/core';
-import { MonacoEditorLanguageClientWrapper } from 'monaco-editor-wrapper';
+import { AfterViewInit, Component, inject, signal } from '@angular/core';
+import { WrapperConfig } from 'monaco-editor-wrapper';
+import { MonacoAngularWrapperComponent } from '../monaco-angular-wrapper/monaco-angular-wrapper.component';
import { buildJsonClientUserConfig } from 'monaco-languageclient-examples/json-client';
-
+import { SaveCodeService } from '../save-code.service';
+import { firstValueFrom } from 'rxjs';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
- styleUrls: ['./app.component.css'],
- standalone: true
+ styleUrls: ['./app.component.scss'],
+ standalone: true,
+ imports: [MonacoAngularWrapperComponent],
})
-export class MonacoEditorComponent implements AfterViewInit {
- title = 'angular-client';
- initDone = false;
+export class AppComponent implements AfterViewInit {
+ private saveCodeService = inject(SaveCodeService);
+ wrapperConfig = signal
(undefined); // this can be updated at runtime
+
+ title = 'angular demo for saving code';
+ editorId = 'monaco-editor-root'; // this can be parameterized or it can be in a loop to support multiple editors
+ editorInlineStyle = signal('height: 50vh;');
+ readonly codeText = signal('');
async ngAfterViewInit(): Promise {
- const wrapper = new MonacoEditorLanguageClientWrapper();
+ const editorDom = document.getElementById(this.editorId);
+ if (editorDom) {
+ const config = buildJsonClientUserConfig({
+ htmlContainer: editorDom,
+ });
+ this.wrapperConfig.set(config);
+ }
+ }
- const config = buildJsonClientUserConfig({
- htmlContainer: document.getElementById('monaco-editor-root')!
- });
+ onTextChanged(text: string) {
+ this.codeText.set(text);
+ }
+
+ save = async () => {
try {
- await wrapper.initAndStart(config);
- } catch (e) {
- console.error(e);
+ const response = await firstValueFrom(
+ this.saveCodeService.saveCode(this.codeText())
+ );
+ alert('Code saved:' + JSON.stringify(response));
+ } catch (error) {
+ console.error('Error saving code:', error);
}
- }
+ };
}
diff --git a/verify/angular/src/main.ts b/verify/angular/src/main.ts
index 40ed6b012..0096b59db 100644
--- a/verify/angular/src/main.ts
+++ b/verify/angular/src/main.ts
@@ -4,5 +4,8 @@
* ------------------------------------------------------------------------------------------ */
import { bootstrapApplication } from '@angular/platform-browser';
-import { MonacoEditorComponent } from './app/app.component';
-bootstrapApplication(MonacoEditorComponent);
+import { provideHttpClient } from '@angular/common/http';
+import { AppComponent } from './app/app.component';
+bootstrapApplication(AppComponent, {
+ providers: [provideHttpClient()],
+});
diff --git a/verify/angular/src/monaco-angular-wrapper/monaco-angular-wrapper.component.html b/verify/angular/src/monaco-angular-wrapper/monaco-angular-wrapper.component.html
new file mode 100644
index 000000000..25a53b705
--- /dev/null
+++ b/verify/angular/src/monaco-angular-wrapper/monaco-angular-wrapper.component.html
@@ -0,0 +1,3 @@
+
diff --git a/verify/angular/src/monaco-angular-wrapper/monaco-angular-wrapper.component.scss b/verify/angular/src/monaco-angular-wrapper/monaco-angular-wrapper.component.scss
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/verify/angular/src/monaco-angular-wrapper/monaco-angular-wrapper.component.scss
@@ -0,0 +1 @@
+
diff --git a/verify/angular/src/monaco-angular-wrapper/monaco-angular-wrapper.component.ts b/verify/angular/src/monaco-angular-wrapper/monaco-angular-wrapper.component.ts
new file mode 100644
index 000000000..fd1b5a1d6
--- /dev/null
+++ b/verify/angular/src/monaco-angular-wrapper/monaco-angular-wrapper.component.ts
@@ -0,0 +1,95 @@
+/* --------------------------------------------------------------------------------------------
+ * Copyright (c) 2024 TypeFox and others.
+ * Licensed under the MIT License. See LICENSE in the package root for license information.
+ * ------------------------------------------------------------------------------------------ */
+
+import {
+ Component,
+ effect,
+ EventEmitter,
+ input,
+ OnDestroy,
+ Output,
+} from '@angular/core';
+
+import * as monaco from 'monaco-editor';
+import {
+ MonacoEditorLanguageClientWrapper,
+ TextChanges,
+ TextModels,
+ WrapperConfig , didModelContentChange
+} from 'monaco-editor-wrapper';
+
+@Component({
+ standalone: true,
+ selector: 'monaco-angular-wrapper',
+ templateUrl: './monaco-angular-wrapper.component.html',
+ styleUrls: ['./monaco-angular-wrapper.component.scss'],
+})
+export class MonacoAngularWrapperComponent implements OnDestroy {
+ @Output() onTextChanged = new EventEmitter();
+ wrapperConfig = input();
+ monacoEditorId = input();
+ editorInlineStyle = input();
+ private wrapper: MonacoEditorLanguageClientWrapper =
+ new MonacoEditorLanguageClientWrapper();
+ private _subscription: monaco.IDisposable | null = null;
+ private isRestarting?: Promise;
+
+ constructor() {
+ effect(async () => {
+ try {
+ if (this.wrapperConfig() !== undefined) {
+ await this.wrapper.initAndStart(
+ this.wrapperConfig() as WrapperConfig
+ );
+ this.handleOnTextChanged();
+ }
+ } catch (e) {
+ console.error(e);
+ }
+ });
+ }
+
+ protected async destroyMonaco(): Promise {
+ if (this.isRestarting) {
+ await this.isRestarting;
+ }
+ try {
+ await this.wrapper.dispose();
+ } catch {
+ // The language client may throw an error during disposal.
+ // This should not prevent us from continue working.
+ console.error('Error during disposal of the language client.');
+ }
+ if (this._subscription) {
+ this._subscription.dispose();
+ }
+ }
+
+ async ngOnDestroy() {
+ this.destroyMonaco();
+ }
+
+ handleOnTextChanged() {
+ const wrapperConfig = this.wrapperConfig();
+ const textModels = this.wrapper.getTextModels();
+ if (textModels?.text !== undefined && wrapperConfig !== undefined) {
+ const newSubscriptions: monaco.IDisposable[] = [];
+ this.emitCodeChange(textModels, wrapperConfig);
+ newSubscriptions.push(
+ textModels.text.onDidChangeContent(() => {
+ this.emitCodeChange(textModels, wrapperConfig);
+ })
+ );
+ }
+ }
+
+ emitCodeChange(textModels: TextModels , wrapperConfig: WrapperConfig ) {
+ const onTextChanged = (textChanges: TextChanges) => {
+ this.onTextChanged.emit(textChanges.text);
+ };
+ didModelContentChange(textModels, wrapperConfig.editorAppConfig.codeResources, onTextChanged);
+ }
+
+}
diff --git a/verify/angular/src/save-code.service.ts b/verify/angular/src/save-code.service.ts
new file mode 100644
index 000000000..7e2eaa91f
--- /dev/null
+++ b/verify/angular/src/save-code.service.ts
@@ -0,0 +1,14 @@
+/* --------------------------------------------------------------------------------------------
+ * Copyright (c) 2024 TypeFox and others.
+ * Licensed under the MIT License. See LICENSE in the package root for license information.
+ * ------------------------------------------------------------------------------------------ */
+
+import { inject, Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+@Injectable({ providedIn: 'root' })
+export class SaveCodeService {
+ private http = inject(HttpClient);
+ saveCode(codeText: string) {
+ return this.http.post('http://localhost:3003/save-code', { code: codeText });
+ }
+}