UNPKG

@covalent/code-editor

Version:

Teradata UI Platform Code Editor Module

658 lines (652 loc) 25.7 kB
import * as i0 from '@angular/core'; import { EventEmitter, PLATFORM_ID, forwardRef, Component, Inject, Input, Output, ViewChild, NgModule } from '@angular/core'; import { isPlatformServer } from '@angular/common'; import { NG_VALUE_ACCESSOR } from '@angular/forms'; import { Subject, of, merge, fromEvent, timer } from 'rxjs'; import { debounceTime, distinctUntilChanged, takeUntil } from 'rxjs/operators'; import { editor, languages } from 'monaco-editor/esm/vs/editor/editor.api'; import { mixinControlValueAccessor, mixinDisabled } from '@covalent/core/common'; import * as tokens from '@covalent/tokens'; const covalentThemeName = 'tdaa-lite'; const covalentThemeConf = { base: 'vs', inherit: true, rules: [ { token: '', foreground: '212121', background: 'f5f5f5' }, { token: 'invalid', foreground: 'b71c1c' }, { token: 'keyword', foreground: '3b78e7' }, { token: 'keyword.json', foreground: 'ec407a', fontStyle: 'bold italic' }, { token: 'comment', foreground: '0d904f' }, { token: 'string.sql', foreground: '0d904f' }, { token: 'number', foreground: 'E74C3C' }, { token: 'key', foreground: '863B00' }, { token: 'string.key.json', foreground: '0d904f' }, { token: 'string.value.json', foreground: '0d904f' }, { token: 'predefined', foreground: 'ec407a' }, { token: 'predefined.sql', foreground: 'ec407a' }, { token: 'delimiter', foreground: '000000' }, { token: 'delimiter.html', foreground: '383838' }, { token: 'delimiter.xml', foreground: '3b78e7' }, { token: 'tag', foreground: '3b78e7' }, { token: 'tag.id.jade', foreground: '4F76AC' }, { token: 'tag.class.jade', foreground: '4F76AC' }, { token: 'meta.scss', foreground: '800000' }, { token: 'metatag', foreground: 'e00000' }, { token: 'metatag.content.html', foreground: 'FF0000' }, { token: 'metatag.html', foreground: '808080' }, { token: 'metatag.xml', foreground: '808080' }, { token: 'metatag.php', fontStyle: 'bold' }, { token: 'attribute.name', foreground: '9c27b0' }, { token: 'attribute.value', foreground: '0451A5' }, { token: 'attribute.value.number', foreground: '09885A' }, { token: 'attribute.value.unit', foreground: '09885A' }, { token: 'attribute.value.html', foreground: '0000FF' }, { token: 'attribute.value.xml', foreground: '0d904f' }, { token: 'arbitration-variable', foreground: '006D90' }, { token: 'arbitration-variable.invalid', foreground: 'b71c1c' }, { token: 'operator.scss', foreground: '666666' }, { token: 'operator', foreground: '778899' }, ], colors: { comment: '388e3c', 'editor.foreground': 'ff0000', 'editor.background': '#f5f5f5', 'editorLineNumber.activeForeground': '#707070', 'editorLineNumber.foreground': '#b7b7b7', 'editor.lineHighlightBackground': '#ebebeb', 'inputValidation.errorBackground': '#b71c1c', 'inputValidation.errorBorder': 'rgba(229, 115, 115, 0.1)', }, }; const getTheme = (theme) => { return { base: theme === 'Light' ? 'vs' : 'vs-dark', inherit: true, rules: [ { token: '', foreground: tokens[`Cv${theme}CodeSnippetColor`], background: tokens[`Cv${theme}Surface`], }, { token: 'arbitration-variable', foreground: tokens[`Cv${theme}CodeSnippetVariable`], }, { token: 'arbitration-variable.invalid', foreground: tokens[`Cv${theme}Negative`], }, { token: 'attribute.name', foreground: tokens[`Cv${theme}CodeSnippetVariable`], }, { token: 'attribute.value', foreground: tokens[`Cv${theme}CodeSnippetVariable`], }, { token: 'attribute.value.number', foreground: tokens[`Cv${theme}CodeSnippetVariable`], }, { token: 'attribute.value.unit', foreground: tokens[`Cv${theme}CodeSnippetVariable`], }, { token: 'attribute.value.html', foreground: tokens[`Cv${theme}CodeSnippetString`], }, { token: 'attribute.value.xml', foreground: tokens[`Cv${theme}CodeSnippetLiteral`], }, { token: 'builtins', foreground: tokens[`Cv${theme}CodeSnippetClass`], }, { token: 'class', foreground: tokens[`Cv${theme}CodeSnippetClass`], }, { token: 'comment', foreground: tokens[`Cv${theme}CodeSnippetComment`], fontStyle: 'italic', }, { token: 'constant', foreground: tokens[`Cv${theme}CodeSnippetLiteral`], }, { token: 'delimiter', foreground: tokens[`CvTheme${theme}ColorsOnSurface`], }, { token: 'delimiter.html', foreground: tokens[`Cv${theme}CodeSnippetSelector`], }, { token: 'delimiter.xml', foreground: tokens[`Cv${theme}CodeSnippetSelector`], }, { token: 'doctag', foreground: tokens[`Cv${theme}CodeSnippetKeyword`], }, { token: 'emphasis', fontStyle: 'italic', }, { token: 'formula', foreground: tokens[`Cv${theme}CodeSnippetKeyword`], }, { token: 'function', foreground: tokens[`Cv${theme}CodeSnippetTitle`], }, { token: 'invalid', foreground: tokens[`Cv${theme}Negative`], }, { token: 'key', foreground: tokens[`Cv${theme}CodeSnippetString`] }, { token: 'keyword', foreground: tokens[`Cv${theme}CodeSnippetKeyword`], }, { token: 'keyword.json', foreground: tokens[`Cv${theme}CodeSnippetTitle`], fontStyle: 'bold italic', }, { token: 'link', foreground: tokens[`Cv${theme}CodeSnippetTitle`], fontStyle: 'underline', }, { token: 'literal', foreground: tokens[`Cv${theme}CodeSnippetLiteral`], }, { token: 'meta', foreground: tokens[`Cv${theme}CodeSnippetTitle`], }, { token: 'number', foreground: tokens[`Cv${theme}CodeSnippetVariable`], }, { token: 'operator', foreground: tokens[`Cv${theme}CodeSnippetLiteral`], }, { token: 'predefined', foreground: tokens[`Cv${theme}CodeSnippetTitle`] }, { token: 'predefined.sql', foreground: tokens[`Cv${theme}CodeSnippetTitle`], }, { token: 'predefined.python', foreground: tokens[`Cv${theme}CodeSnippetClass`], }, { token: 'punctuation', foreground: tokens[`Cv${theme}CodeSnippetColor`], }, { token: 'string', foreground: tokens[`Cv${theme}CodeSnippetString`], }, { token: 'string.sql', foreground: tokens[`Cv${theme}CodeSnippetString`], }, { token: 'string.key.json', foreground: tokens[`Cv${theme}CodeSnippetString`], }, { token: 'string.value.json', foreground: tokens[`Cv${theme}CodeSnippetString`], }, { token: 'strong', fontStyle: 'bold', }, { token: 'tag', foreground: tokens[`Cv${theme}CodeSnippetSelector`], }, { token: 'tag.id.jade', foreground: tokens[`Cv${theme}CodeSnippetClass`], }, { token: 'tag.class.jade', foreground: tokens[`Cv${theme}CodeSnippetClass`], }, { token: 'type', foreground: tokens[`Cv${theme}CodeSnippetClass`], }, { token: 'variable', foreground: tokens[`Cv${theme}CodeSnippetVariable`], }, ], colors: { comment: tokens[`Cv${theme}CodeSnippetComment`], 'editor.background': tokens[`Cv${theme}Surface`], 'editor.foreground': tokens[`CvTheme${theme}ColorsOnSurface`], 'editorCursor.foreground': tokens[`Cv${theme}TextSecondaryOnBackground`], 'editorLineNumber.activeForeground': tokens[`CvTheme${theme}ColorsOnSurfaceVariant`], 'editorLineNumber.foreground': tokens[`CvTheme${theme}ColorsOnSurfaceVariant`], 'editor.lineHighlightBackground': tokens[`CvTheme${theme}ColorsSurfaceContainerLow`], 'inputValidation.errorBackground': tokens[`Cv${theme}Negative`], 'inputValidation.errorBorder': tokens[`CvTheme${theme}PalettesNegative95`], }, }; }; // Any changes to these themes should also be reflected in the components/code-editor theme. const cvEditorDarkTheme = getTheme('Dark'); const cvEditorLightTheme = getTheme('Light'); const _c0 = ["editorContainer"]; const noop = () => { // empty method }; // counter for ids to allow for multiple editors on one page let uniqueCounter = 0; class TdCodeEditorBase { _changeDetectorRef; constructor(_changeDetectorRef) { this._changeDetectorRef = _changeDetectorRef; } } const _TdCodeEditorMixinBase = mixinControlValueAccessor(mixinDisabled(TdCodeEditorBase), []); class TdCodeEditorComponent extends _TdCodeEditorMixinBase { _elementRef; _ngZone; platformId; _destroy = new Subject(); _widthSubject = new Subject(); _heightSubject = new Subject(); _editorStyle = 'width:100%;height:100%;'; _value = ''; _theme = 'cv-light'; _language = 'javascript'; _subject = new Subject(); _editorInnerContainer = 'editorInnerContainer' + uniqueCounter++; _editor; _fromEditor = false; _componentInitialized = false; _editorOptions = {}; _isFullScreen = false; _keycode; _registeredLanguagesStyles = []; _disposables = []; _editorContainer; /** * editorInitialized: function($event) * Event emitted when editor is first initialized */ editorInitialized = new EventEmitter(); /** * editorConfigurationChanged: function($event) * Event emitted when editor's configuration changes */ editorConfigurationChanged = new EventEmitter(); /** * editorLanguageChanged: function($event) * Event emitted when editor's Language changes */ editorLanguageChanged = new EventEmitter(); /** * editorValueChange: function($event) * Event emitted any time something changes the editor value */ editorValueChange = new EventEmitter(); propagateChange = (_) => noop; /** * value?: string */ set value(value) { this._value = value; if (this._componentInitialized) { this.applyValue(); } } get value() { return this._value; } applyValue() { if (!this._fromEditor) { this._editor.setValue(this._value || ''); } this._fromEditor = false; this.propagateChange(this._value); this.editorValueChange.emit(); } registerOnChange(fn) { this.propagateChange = fn; } registerOnTouched(fn) { this.onTouched = fn; } /** * getEditorContent?: function * Returns the content within the editor */ getValue() { if (!this._componentInitialized) { return of(''); } setTimeout(() => { this._subject.next(this._value); this._subject.complete(); this._subject = new Subject(); }); return this._subject.asObservable(); } /** * language?: string * language used in editor */ set language(language) { this._language = language; if (this._componentInitialized) { this.applyLanguage(); } } get language() { return this._language; } applyLanguage() { if (this._language) { editor.setModelLanguage(this._editor.getModel(), this._language); this.editorLanguageChanged.emit(); } } /** * registerLanguage?: function * Registers a custom Language within the editor */ registerLanguage(language) { if (this._componentInitialized) { languages.register({ id: language.id }); this._disposables.push(languages.setMonarchTokensProvider(language.id, { tokenizer: { root: language.monarchTokensProvider, }, })); // Define a new theme that constains only rules that match this language editor.defineTheme(language.customTheme.id, language.customTheme.theme); this._theme = language.customTheme.id; this._disposables.push(languages.registerCompletionItemProvider(language.id, { provideCompletionItems: () => { return language.completionItemProvider; }, })); const css = document.createElement('style'); css.type = 'text/css'; css.innerHTML = language.monarchTokensProviderCSS; document.body.appendChild(css); this.editorConfigurationChanged.emit(); this._registeredLanguagesStyles = [ ...this._registeredLanguagesStyles, css, ]; } } /** * style?: string * css style of the editor on the page */ set editorStyle(editorStyle) { this._editorStyle = editorStyle; if (this._componentInitialized) { this.applyStyle(); } } get editorStyle() { return this._editorStyle; } applyStyle() { if (this._editorStyle) { const containerDiv = this._editorContainer.nativeElement; containerDiv.setAttribute('style', this._editorStyle); } } /** * theme?: string * Theme to be applied to editor */ set theme(theme) { this._theme = theme; if (this._componentInitialized) { this._editor.updateOptions({ theme }); this.editorConfigurationChanged.emit(); } } get theme() { return this._theme; } /** * fullScreenKeyBinding?: number * See here for key bindings https://microsoft.github.io/monaco-editor/api/enums/keycode.html * Sets the KeyCode for shortcutting to Fullscreen mode */ set fullScreenKeyBinding(keycode) { this._keycode = keycode; } get fullScreenKeyBinding() { return this._keycode; } /** * editorOptions?: object * Options used on editor instantiation. Available options listed here: * https://microsoft.github.io/monaco-editor/api/interfaces/editor.ieditoroptions.html */ set editorOptions(editorOptions) { this._editorOptions = editorOptions; if (this._componentInitialized) { this._editor.updateOptions(editorOptions); this.editorConfigurationChanged.emit(); } } get editorOptions() { return this._editorOptions; } /** * layout method that calls layout method of editor and instructs the editor to remeasure its container */ layout() { if (this._componentInitialized) { this._editor.layout(); } } /** * Returns if in Full Screen Mode or not */ get isFullScreen() { return this._isFullScreen; } // tslint:disable-next-line:member-ordering constructor(_changeDetectorRef, _elementRef, _ngZone, platformId) { super(_changeDetectorRef); this._elementRef = _elementRef; this._ngZone = _ngZone; this.platformId = platformId; } ngOnInit() { if (isPlatformServer(this.platformId)) { return; } const containerDiv = this._editorContainer.nativeElement; containerDiv.id = this._editorInnerContainer; // Add teradata branded themes editor.defineTheme('cv-light', cvEditorLightTheme); editor.defineTheme('cv-dark', cvEditorDarkTheme); this._editor = editor.create(containerDiv, Object.assign({ value: this._value, language: this.language, theme: this._theme, }, this.editorOptions)); this._componentInitialized = true; setTimeout(() => { this.applyLanguage(); this._fromEditor = true; this.applyValue(); this.applyStyle(); this.editorInitialized.emit(this._editor); this.editorConfigurationChanged.emit(); }); // The `onDidChangeContent` returns a disposable object (an object with `dispose()` method) which will cleanup // the listener. The callback, that we pass to `onDidChangeContent`, captures `this`. This leads to a circular reference // (`td-code-editor -> monaco -> td-code-editor`) and prevents the `td-code-editor` from being GC'd. this._disposables.push(this._editor.getModel().onDidChangeContent(() => { this._fromEditor = true; this.writeValue(this._editor.getValue()); this.layout(); })); this.addFullScreenModeCommand(); this._ngZone.runOutsideAngular(() => merge(fromEvent(window, 'resize').pipe(debounceTime(100)), this._widthSubject.asObservable().pipe(distinctUntilChanged()), this._heightSubject.asObservable().pipe(distinctUntilChanged())) .pipe(debounceTime(100), takeUntil(this._destroy)) .subscribe(() => { // Note: this is being called outside of the Angular zone since we don't have to // run change detection whenever the editor resizes itself. this.layout(); })); this._ngZone.runOutsideAngular(() => timer(500, 250) .pipe(takeUntil(this._destroy)) .subscribe(() => { const { width, height } = this._elementRef.nativeElement.getBoundingClientRect(); this._widthSubject.next(width); this._heightSubject.next(height); })); } ngOnDestroy() { this._changeDetectorRef.detach(); this._registeredLanguagesStyles.forEach((style) => style.remove()); while (this._disposables.length) { this._disposables.pop()?.dispose(); } if (this._editor) { this._editor.dispose(); } this._destroy.next(true); this._destroy.unsubscribe(); } /** * showFullScreenEditor request for full screen of Code Editor based on its browser type. */ showFullScreenEditor() { if (this._componentInitialized) { const codeEditorElement = this._editorContainer .nativeElement; codeEditorElement.requestFullscreen(); } this._isFullScreen = true; } /** * exitFullScreenEditor request to exit full screen of Code Editor based on its browser type. */ exitFullScreenEditor() { if (this._componentInitialized) { document.exitFullscreen(); } this._isFullScreen = false; } /** * addFullScreenModeCommand used to add the fullscreen option to the context menu */ addFullScreenModeCommand() { this._disposables.push(this._editor.addAction({ // An unique identifier of the contributed action. id: 'fullScreen', // A label of the action that will be presented to the user. label: 'Full Screen', // An optional array of keybindings for the action. contextMenuGroupId: 'navigation', keybindings: this._keycode, contextMenuOrder: 1.5, // Method that will be executed when the action is triggered. run: () => { this.showFullScreenEditor(); }, })); } static ɵfac = function TdCodeEditorComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || TdCodeEditorComponent)(i0.ɵɵdirectiveInject(i0.ChangeDetectorRef), i0.ɵɵdirectiveInject(i0.ElementRef), i0.ɵɵdirectiveInject(i0.NgZone), i0.ɵɵdirectiveInject(PLATFORM_ID)); }; static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: TdCodeEditorComponent, selectors: [["td-code-editor"]], viewQuery: function TdCodeEditorComponent_Query(rf, ctx) { if (rf & 1) { i0.ɵɵviewQuery(_c0, 7); } if (rf & 2) { let _t; i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx._editorContainer = _t.first); } }, inputs: { value: "value", language: "language", editorStyle: "editorStyle", theme: "theme", fullScreenKeyBinding: "fullScreenKeyBinding", editorOptions: "editorOptions" }, outputs: { editorInitialized: "editorInitialized", editorConfigurationChanged: "editorConfigurationChanged", editorLanguageChanged: "editorLanguageChanged", editorValueChange: "editorValueChange" }, features: [i0.ɵɵProvidersFeature([ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => TdCodeEditorComponent), multi: true, }, ]), i0.ɵɵInheritDefinitionFeature], decls: 2, vars: 0, consts: [["editorContainer", ""], [1, "editor-container"]], template: function TdCodeEditorComponent_Template(rf, ctx) { if (rf & 1) { i0.ɵɵelement(0, "div", 1, 0); } }, styles: ["[_nghost-%COMP%]{display:block;position:relative}[_nghost-%COMP%] .editor-container[_ngcontent-%COMP%]{position:absolute;inset:0} .monaco-aria-container{display:none}"] }); } (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(TdCodeEditorComponent, [{ type: Component, args: [{ selector: 'td-code-editor', providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => TdCodeEditorComponent), multi: true, }, ], template: "<div class=\"editor-container\" #editorContainer></div>\n", styles: [":host{display:block;position:relative}:host .editor-container{position:absolute;inset:0}::ng-deep .monaco-aria-container{display:none}\n"] }] }], () => [{ type: i0.ChangeDetectorRef }, { type: i0.ElementRef }, { type: i0.NgZone }, { type: undefined, decorators: [{ type: Inject, args: [PLATFORM_ID] }] }], { _editorContainer: [{ type: ViewChild, args: ['editorContainer', { static: true }] }], editorInitialized: [{ type: Output }], editorConfigurationChanged: [{ type: Output }], editorLanguageChanged: [{ type: Output }], editorValueChange: [{ type: Output }], value: [{ type: Input }], language: [{ type: Input }], editorStyle: [{ type: Input }], theme: [{ type: Input }], fullScreenKeyBinding: [{ type: Input }], editorOptions: [{ type: Input }] }); })(); (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(TdCodeEditorComponent, { className: "TdCodeEditorComponent", filePath: "lib/code-editor.component.ts", lineNumber: 63 }); })(); /** * @deprecated This module is deprecated and will be removed in future versions. * Please migrate to using standalone components as soon as possible. */ class CovalentCodeEditorModule { static ɵfac = function CovalentCodeEditorModule_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || CovalentCodeEditorModule)(); }; static ɵmod = /*@__PURE__*/ i0.ɵɵdefineNgModule({ type: CovalentCodeEditorModule }); static ɵinj = /*@__PURE__*/ i0.ɵɵdefineInjector({}); } (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(CovalentCodeEditorModule, [{ type: NgModule, args: [{ imports: [TdCodeEditorComponent], exports: [TdCodeEditorComponent], }] }], null, null); })(); (function () { (typeof ngJitMode === "undefined" || ngJitMode) && i0.ɵɵsetNgModuleScope(CovalentCodeEditorModule, { imports: [TdCodeEditorComponent], exports: [TdCodeEditorComponent] }); })(); /** * Generated bundle index. Do not edit. */ export { CovalentCodeEditorModule, TdCodeEditorComponent }; //# sourceMappingURL=covalent-code-editor.mjs.map