UNPKG

@kephas/angular-ace

Version:

Provides the integration of the Ace editor with Kephas and Angular.

392 lines (386 loc) 11.6 kB
import { ValueEditorBase, provideWidget, provideValueAccessor } from '@kephas/angular'; import { Component, ElementRef, ViewContainerRef, Input, NgModule } from '@angular/core'; import { edit } from 'brace'; import 'brace/theme/monokai'; import 'brace/mode/abap'; import 'brace/mode/lua'; import 'brace/mode/sh'; import 'brace/mode/sql'; import 'brace/mode/powershell'; import 'brace/mode/text'; import 'brace/mode/json'; import { BrowserModule } from '@angular/platform-browser'; /** * Value editor based on Ace. * * @export * @class AceComponent * @extends {(ValueEditorBase<string | {} | null>)} */ class AceComponent extends ValueEditorBase { /** * Creates an instance of AceComponent. * * @param {ElementRef} elementRef The element reference. * @param {ViewContainerRef} viewContainerRef The view container reference. * @memberof AceComponent */ constructor(elementRef, viewContainerRef) { super(elementRef, viewContainerRef); this._theme = 'monokai'; this._editorType = 'json'; this._valueIsObject = false; } /** * Gets or sets the editor type. * * @type {string} * @memberof AceComponent */ get editorType() { return this._editorType; } set editorType(value) { if (this._editorType === value) { return; } this._editorType = value; this.setEditorType(value); } /** * Gets or sets the editor options. * * @memberof AceComponent */ set options(value) { if (this.editor) { this.editor.setOptions(value || {}); } } get options() { return this.editor && this.editor.getOptions(); } /** * Gets or sets the editor theme. * * @type {string} * @memberof AceComponent */ get theme() { return this._theme; } set theme(value) { if (this._theme === value) { return; } this._theme = value; this.setEditorTheme(value); } /** * Gets or sets a value indicating whether the bound value is an object or not. * * @readonly * @type {boolean} * @memberof AceComponent */ get valueIsObject() { return this._valueIsObject; } set valueIsObject(value) { this._valueIsObject = value; } /** * A callback method that is invoked immediately after the * default change detector has checked the directive's * data-bound properties for the first time, * and before any of the view or content children have been checked. * It is invoked only once when the directive is instantiated. * * @memberof AceComponent */ ngOnInit() { super.ngOnInit(); const hostElement = this.elementRef.nativeElement.children[0]; this.editor = edit(hostElement); this.editor.setShowPrintMargin(false); this.setEditorTheme(this._theme); this.setEditorType(this._editorType); this.setEditorReadOnly(this.readonly); this.setEditorValue(this.value); this.editor.on('change', e => this.onEditorChange(e)); this.editor.on('paste', e => this.onEditorChange(e)); this.editor.commands.addCommand({ name: 'formatDocumentCommand', bindKey: { win: 'Ctrl-Shift-F', mac: 'Command-Shift-F' }, exec: (e) => { this.formatDocument(); } }); } /** * A callback method that is invoked immediately after * Angular has completed initialization of a component's view. * It is invoked only once when the view is instantiated. * * @memberof AceComponent */ ngAfterViewInit() { super.ngAfterViewInit(); if (!this.observeVisibilityOf) { return; } this.observeVisibility(this.observeVisibilityOf); } /** * A callback method that performs custom clean-up, invoked immediately * after a directive, pipe, or service instance is destroyed. * * @memberof AceComponent */ ngOnDestroy() { super.ngOnDestroy(); if (this._observer) { this._observer.disconnect(); } } /** * Observes the visibility of the elements in the query string, * and if they are visible, it will trigger an editor resize. * This is required to fix a problem in the ACE editor when * setValue doesn't work if the editor is hidden. * * See also https://github.com/ajaxorg/ace/issues/3070. * See also https://github.com/Starcounter/Content/commit/91a757d8750523431fc1637fdf57409d0fcb13db. * * @param {string} queryString * @memberof ScriptEditorComponent */ observeVisibility(queryStrings) { if (this._observer) { this._observer.disconnect(); } if (typeof queryStrings === 'string') { queryStrings = [queryStrings]; } this._observer = new MutationObserver(mutations => { const changedTargets = []; mutations.forEach(mutationRecord => { if (changedTargets.findIndex(e => e === mutationRecord.target) < 0) { changedTargets.push(mutationRecord.target); } }); changedTargets.forEach(t => { if (this._isAceVisible(t) && this.editor) { this.editor.resize(true); } }); }); for (const queryString of queryStrings) { const query = top.document.querySelectorAll(queryString); for (let i = 0; i < query.length; i++) { this._observer.observe(query[i], { attributes: true, attributeFilter: ['style'] }); } } } /** * Formats the JSON document inside the editor. * * @protected * @returns * @memberof AceComponent */ formatDocument() { if (!this.editor) { return; } if (this.editorType.toLowerCase() === 'json') { let stringValue = this.editor.getValue(); try { const objectValue = JSON.parse(stringValue); stringValue = this._getFormattedJson(objectValue); this.editor.setValue(stringValue); } catch (error) { // too bad we could not format this.notification.notifyWarning(error); } } } /** * Sets the value of the underlying editor. * * @protected * @param {string | {} | null} value * @memberof ValueEditorBase */ setEditorValue(value) { if (!this.editor) { return; } let stringValue = ''; if (typeof value === 'object') { this._valueIsObject = true; stringValue = this._getFormattedJson(value); } else if (typeof value === 'string') { if (value) { this._valueIsObject = false; } stringValue = value; } else { stringValue = value && value.toString(); } this.editor.setValue(stringValue || ''); this.editor.clearSelection(); } /** * Gets the underlying editor's value. * * @protected * @returns {string | {} | null} The widget value. * @memberof ValueEditorBase */ getEditorValue() { if (!this.editor) { return null; } const stringValue = this.editor.getValue(); if (this._valueIsObject) { if (!stringValue) { return stringValue; } try { const objectValue = JSON.parse(stringValue); return objectValue; } catch (error) { return stringValue; } } return stringValue; } /** * When overridden in a derived class, this method is called when the read only state changes. * * @protected * @param {boolean} oldValue The old value. * @param {boolean} newValue The new value. * * @memberof EditorBase */ onReadOnlyChanged(oldValue, newValue) { if (this.editor) { this.setEditorReadOnly(newValue); } } /** * Sets the readonly state of the editor. * * @protected * @param {boolean} value * @memberof AceComponent */ setEditorReadOnly(value) { if (this.editor) { this.editor.setReadOnly(value); } } /** * Sets the editor theme. * * @protected * @param {string} theme * @memberof AceComponent */ setEditorTheme(theme) { if (this.editor && theme) { this.editor.setTheme(`ace/theme/${theme}`); } } /** * Sets the editor type. * * @protected * @param {string} editorType * @memberof AceComponent */ setEditorType(editorType) { if (this.editor && editorType) { const mode = this.getEditorMode(editorType); this.editor.getSession().setMode(`ace/mode/${mode}`); this.editor.$blockScrolling = Infinity; } } /** * Gets the editor mode based on the provided editor type. * * @protected * @param {string} editorType The editor type. * @returns {string} * @memberof AceComponent */ getEditorMode(editorType) { return editorType; } /** * Updates the underlying editor with the provided value. * * @protected * @param {TValue} value The value to be set in the underlying component. * @returns {boolean} * @memberof AceComponent */ updateEditor(value) { value = value || ''; return super.updateEditor(value); } _getFormattedJson(obj) { return JSON.stringify(obj, null, 4); } _isAceVisible(element) { const style = window.getComputedStyle(element); return !(style.display === 'none'); } } AceComponent.decorators = [ { type: Component, args: [{ selector: 'ace', template: `<div class="form-control ace"></div>`, providers: [provideWidget(AceComponent), provideValueAccessor(AceComponent)] },] } ]; AceComponent.ctorParameters = () => [ { type: ElementRef }, { type: ViewContainerRef } ]; AceComponent.propDecorators = { observeVisibilityOf: [{ type: Input }], editorType: [{ type: Input }], options: [{ type: Input }], theme: [{ type: Input }], valueIsObject: [{ type: Input }] }; class AceModule { } AceModule.decorators = [ { type: NgModule, args: [{ declarations: [ AceComponent, ], exports: [ AceComponent, ], imports: [ BrowserModule, ], },] } ]; /* * Public API Surface of angular-ace */ /** * Generated bundle index. Do not edit. */ export { AceComponent, AceModule }; //# sourceMappingURL=kephas-angular-ace.js.map