@covalent/code-editor
Version:
Teradata UI Platform Code Editor Module
658 lines (652 loc) • 25.7 kB
JavaScript
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