1+ import { Component , Input , forwardRef , OnInit , OnDestroy , ViewEncapsulation , signal , effect , ViewChild , ElementRef , inject , PLATFORM_ID } from '@angular/core' ;
2+ import { ControlValueAccessor , NG_VALUE_ACCESSOR , FormsModule } from '@angular/forms' ;
3+ import { isPlatformBrowser } from '@angular/common' ;
4+
5+ interface MonacoEditor {
6+ getValue ( ) : string ;
7+ setValue ( value : string ) : void ;
8+ dispose ( ) : void ;
9+ onDidChangeModelContent ( callback : ( ) => void ) : void ;
10+ updateOptions ( options : MonacoEditorOptions ) : void ;
11+ layout ( ) : void ;
12+ }
13+
14+ interface MonacoEditorOptions {
15+ value ?: string ;
16+ language ?: string ;
17+ theme ?: string ;
18+ minimap ?: { enabled : boolean } ;
19+ automaticLayout ?: boolean ;
20+ scrollBeyondLastLine ?: boolean ;
21+ fontSize ?: number ;
22+ wordWrap ?: string ;
23+ lineNumbers ?: string ;
24+ glyphMargin ?: boolean ;
25+ folding ?: boolean ;
26+ lineDecorationsWidth ?: number ;
27+ lineNumbersMinChars ?: number ;
28+ renderLineHighlight ?: string ;
29+ contextmenu ?: boolean ;
30+ mouseWheelZoom ?: boolean ;
31+ readOnly ?: boolean ;
32+ }
33+
34+ interface WindowRequire {
35+ config ( config : { paths : { vs : string } } ) : void ;
36+ ( modules : string [ ] , callback : ( ) => void ) : void ;
37+ }
38+
39+ declare const monaco : {
40+ editor : {
41+ create ( element : HTMLElement , options : MonacoEditorOptions ) : MonacoEditor ;
42+ } ;
43+ } ;
44+
45+ declare global {
46+ interface Window {
47+ require : WindowRequire ;
48+ }
49+ }
50+
51+ @Component ( {
52+ selector : 'app-monaco-editor' ,
53+ template : `<div #editorContainer style="height: 100%;"></div>` ,
54+ styleUrls : [ ] ,
55+ encapsulation : ViewEncapsulation . None ,
56+ providers : [
57+ {
58+ provide : NG_VALUE_ACCESSOR ,
59+ useExisting : forwardRef ( ( ) => MonacoEditorComponent ) ,
60+ multi : true
61+ }
62+ ] ,
63+ imports : [ FormsModule ] ,
64+ standalone : true
65+ } )
66+ export class MonacoEditorComponent implements OnInit , OnDestroy , ControlValueAccessor {
67+ @Input ( ) language = 'rust' ;
68+ @Input ( ) theme = 'vs-dark' ;
69+ @Input ( ) height = '500px' ;
70+ @Input ( ) options : MonacoEditorOptions = { } ;
71+
72+ @ViewChild ( 'editorContainer' , { static : true } ) editorContainer ! : ElementRef < HTMLDivElement > ;
73+
74+ private editor : MonacoEditor | null = null ;
75+ private currentValue = signal ( '' ) ;
76+
77+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
78+ private onChange = ( _value : string ) => { /* no-op */ } ;
79+ private onTouched = ( ) => { } ;
80+
81+ private platformId = inject ( PLATFORM_ID ) ;
82+
83+ constructor ( ) {
84+ effect ( ( ) => {
85+ if ( this . editor && this . currentValue ( ) !== this . editor . getValue ( ) ) {
86+ this . editor . setValue ( this . currentValue ( ) ) ;
87+ }
88+ } ) ;
89+ }
90+
91+ ngOnInit ( ) {
92+ this . loadMonacoEditor ( ) ;
93+ }
94+
95+ ngOnDestroy ( ) {
96+ if ( this . editor ) {
97+ this . editor . dispose ( ) ;
98+ }
99+ }
100+
101+ private loadMonacoEditor ( ) {
102+ // Only load Monaco Editor in the browser
103+ if ( ! isPlatformBrowser ( this . platformId ) ) {
104+ return ;
105+ }
106+
107+ // Load Monaco Editor
108+ if ( typeof monaco === 'undefined' ) {
109+ const script = document . createElement ( 'script' ) ;
110+ script . type = 'text/javascript' ;
111+ script . src = '/assets/monaco-editor/min/vs/loader.js' ;
112+ script . onload = ( ) => {
113+ window . require . config ( {
114+ paths : { vs : '/assets/monaco-editor/min/vs' }
115+ } ) ;
116+ window . require ( [ 'vs/editor/editor.main' ] , ( ) => {
117+ this . initEditor ( ) ;
118+ } ) ;
119+ } ;
120+ script . onerror = ( ) => {
121+ this . loadFromCDN ( ) ;
122+ } ;
123+ document . getElementsByTagName ( 'head' ) [ 0 ] . appendChild ( script ) ;
124+ } else {
125+ this . initEditor ( ) ;
126+ }
127+ }
128+
129+ private loadFromCDN ( ) {
130+ if ( ! isPlatformBrowser ( this . platformId ) ) {
131+ return ;
132+ }
133+
134+ const script = document . createElement ( 'script' ) ;
135+ script . src = 'https://unpkg.com/monaco-editor@latest/min/vs/loader.js' ;
136+ script . onload = ( ) => {
137+ window . require . config ( {
138+ paths : { vs : 'https://unpkg.com/monaco-editor@latest/min/vs' }
139+ } ) ;
140+ window . require ( [ 'vs/editor/editor.main' ] , ( ) => {
141+ this . initEditor ( ) ;
142+ } ) ;
143+ } ;
144+ document . head . appendChild ( script ) ;
145+ }
146+
147+ private initEditor ( ) {
148+ const editorElement = this . editorContainer . nativeElement ;
149+ if ( ! editorElement ) {
150+ setTimeout ( ( ) => this . initEditor ( ) , 100 ) ;
151+ return ;
152+ }
153+
154+ const defaultOptions : MonacoEditorOptions = {
155+ value : this . currentValue ( ) ,
156+ language : this . language ,
157+ theme : this . theme ,
158+ minimap : { enabled : false } ,
159+ automaticLayout : true ,
160+ scrollBeyondLastLine : false ,
161+ fontSize : 14 ,
162+ wordWrap : 'on' ,
163+ lineNumbers : 'on' ,
164+ glyphMargin : false ,
165+ folding : true ,
166+ lineDecorationsWidth : 20 ,
167+ lineNumbersMinChars : 3 ,
168+ renderLineHighlight : 'line' ,
169+ contextmenu : true ,
170+ mouseWheelZoom : true ,
171+ ...this . options
172+ } ;
173+
174+ this . editor = monaco . editor . create ( editorElement , defaultOptions ) ;
175+
176+ // Set up change listener
177+ this . editor . onDidChangeModelContent ( ( ) => {
178+ const value = this . editor ?. getValue ( ) || '' ;
179+ this . currentValue . set ( value ) ;
180+ this . onChange ( value ) ;
181+ this . onTouched ( ) ;
182+ } ) ;
183+
184+ // Set height
185+ editorElement . style . height = this . height ;
186+ this . editor . layout ( ) ;
187+ }
188+
189+ // ControlValueAccessor implementation
190+ writeValue ( value : string ) : void {
191+ this . currentValue . set ( value || '' ) ;
192+ }
193+
194+ registerOnChange ( fn : ( value : string ) => void ) : void {
195+ this . onChange = fn ;
196+ }
197+
198+ registerOnTouched ( fn : ( ) => void ) : void {
199+ this . onTouched = fn ;
200+ }
201+
202+ setDisabledState ( isDisabled : boolean ) : void {
203+ if ( this . editor ) {
204+ this . editor . updateOptions ( { readOnly : isDisabled } ) ;
205+ }
206+ }
207+ }
0 commit comments