From 4aea131966b527ca1f375f08c1aa404145ee4aab Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 23 Feb 2017 21:17:59 +0100 Subject: [PATCH] Json Editor --- .../angular/autocomplete.component.ts | 2 - .../angular/date-time-editor.component.ts | 1 - .../angular/json-editor.component.html | 1 + .../angular/json-editor.component.scss | 8 + .../angular/json-editor.component.ts | 137 ++++++++++++++++++ .../app/framework/angular/slider.component.ts | 1 - .../framework/angular/tag-editor.component.ts | 3 +- src/Squidex/app/framework/declarations.ts | 1 + src/Squidex/app/framework/module.ts | 3 + 9 files changed, 151 insertions(+), 6 deletions(-) create mode 100644 src/Squidex/app/framework/angular/json-editor.component.html create mode 100644 src/Squidex/app/framework/angular/json-editor.component.scss create mode 100644 src/Squidex/app/framework/angular/json-editor.component.ts diff --git a/src/Squidex/app/framework/angular/autocomplete.component.ts b/src/Squidex/app/framework/angular/autocomplete.component.ts index 8f88ed62f..2a9bc1062 100644 --- a/src/Squidex/app/framework/angular/autocomplete.component.ts +++ b/src/Squidex/app/framework/angular/autocomplete.component.ts @@ -26,9 +26,7 @@ export class AutocompleteItem { const KEY_ENTER = 13; const KEY_UP = 38; const KEY_DOWN = 40; - /* tslint:disable:no-empty */ - const NOOP = () => { }; export const SQX_AUTOCOMPLETE_CONTROL_VALUE_ACCESSOR: any = { diff --git a/src/Squidex/app/framework/angular/date-time-editor.component.ts b/src/Squidex/app/framework/angular/date-time-editor.component.ts index 2d5bc9e0a..6e601cd1c 100644 --- a/src/Squidex/app/framework/angular/date-time-editor.component.ts +++ b/src/Squidex/app/framework/angular/date-time-editor.component.ts @@ -12,7 +12,6 @@ import * as moment from 'moment'; let Pikaday = require('pikaday/pikaday'); /* tslint:disable:no-empty */ - const NOOP = () => { }; export const SQX_DATE_TIME_EDITOR_CONTROL_VALUE_ACCESSOR: any = { diff --git a/src/Squidex/app/framework/angular/json-editor.component.html b/src/Squidex/app/framework/angular/json-editor.component.html new file mode 100644 index 000000000..bb84a4048 --- /dev/null +++ b/src/Squidex/app/framework/angular/json-editor.component.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/src/Squidex/app/framework/angular/json-editor.component.scss b/src/Squidex/app/framework/angular/json-editor.component.scss new file mode 100644 index 000000000..58b58ff8c --- /dev/null +++ b/src/Squidex/app/framework/angular/json-editor.component.scss @@ -0,0 +1,8 @@ +@import '_mixins'; +@import '_vars'; + +.editor { + background: $color-accent-dark; + border: 1px solid $color-input; + height: 20rem; +} \ No newline at end of file diff --git a/src/Squidex/app/framework/angular/json-editor.component.ts b/src/Squidex/app/framework/angular/json-editor.component.ts new file mode 100644 index 000000000..6c6ae5629 --- /dev/null +++ b/src/Squidex/app/framework/angular/json-editor.component.ts @@ -0,0 +1,137 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Sebastian Stehle. All rights reserved + */ + +import { AfterViewInit, Component, forwardRef, ElementRef, ViewChild } from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { ReplaySubject } from 'rxjs'; + +declare var ace: any; + +/* tslint:disable:no-empty */ +const NOOP = () => { }; + +export const SQX_JSON_EDITOR_CONTROL_VALUE_ACCESSOR: any = { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => JsonEditorComponent), + multi: true +}; + +@Component({ + selector: 'sqx-json-editor', + styleUrls: ['./json-editor.component.scss'], + templateUrl: './json-editor.component.html', + providers: [SQX_JSON_EDITOR_CONTROL_VALUE_ACCESSOR] +}) +export class JsonEditorComponent implements ControlValueAccessor, AfterViewInit { + private static loaderCallback: ReplaySubject; + private static isLoaded: boolean; + + private changeCallback: (value: any) => void = NOOP; + private touchedCallback: () => void = NOOP; + private aceEditor: any; + private value: any; + private isDisabled = false; + + @ViewChild('editor') + public editor: ElementRef; + + public writeValue(value: any) { + this.value = value; + + if (this.aceEditor) { + this.setValue(value); + } + } + + public setDisabledState(isDisabled: boolean): void { + this.isDisabled = isDisabled; + + if (this.aceEditor) { + this.aceEditor.setReadOnly(isDisabled); + } + } + + public registerOnChange(fn: any) { + this.changeCallback = fn; + } + + public registerOnTouched(fn: any) { + this.touchedCallback = fn; + } + + public ngAfterViewInit() { + JsonEditorComponent.loadScript(() => { + this.aceEditor = ace.edit(this.editor.nativeElement); + + this.aceEditor.getSession().setMode('ace/mode/javascript'); + this.aceEditor.setReadOnly(this.isDisabled); + this.aceEditor.setFontSize(14); + + this.setValue(this.value); + + this.aceEditor.on('blur', () => { + const isValid = this.aceEditor.getSession().getAnnotations().length === 0; + + if (!isValid) { + this.changeCallback(null); + } else { + try { + const value = JSON.parse(this.aceEditor.getValue()); + + this.changeCallback(value); + } catch (e) { + this.changeCallback(null); + } + } + + this.touchedCallback(); + }); + }); + } + + private setValue(value: any) { + if (!value) { + value = {}; + } + + const jsonString = JSON.stringify(value, undefined, 4); + + this.aceEditor.setValue(jsonString); + } + + private static loadScript(callback: () => void) { + if (JsonEditorComponent.isLoaded) { + callback(); + + return; + } + + if (JsonEditorComponent.loaderCallback) { + JsonEditorComponent.loaderCallback.subscribe(callback); + + return; + } + + JsonEditorComponent.loaderCallback = new ReplaySubject(1); + JsonEditorComponent.loaderCallback.subscribe(callback); + + const url = 'https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.6/ace.js'; + + const script = document.createElement('script'); + script.src = url; + script.async = true; + script.onload = () => { + JsonEditorComponent.loaderCallback.next(null); + JsonEditorComponent.loaderCallback = null; + JsonEditorComponent.isLoaded = true; + }; + + const node = document.getElementsByTagName('script')[0]; + + node.parentNode.insertBefore(script, node); + } +} \ No newline at end of file diff --git a/src/Squidex/app/framework/angular/slider.component.ts b/src/Squidex/app/framework/angular/slider.component.ts index a9a625d6b..17bfc48a7 100644 --- a/src/Squidex/app/framework/angular/slider.component.ts +++ b/src/Squidex/app/framework/angular/slider.component.ts @@ -9,7 +9,6 @@ import { Component, ElementRef, forwardRef, Input, Renderer, ViewChild } from '@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; /* tslint:disable:no-empty */ - const NOOP = () => { }; export const SQX_SLIDER_CONTROL_VALUE_ACCESSOR: any = { diff --git a/src/Squidex/app/framework/angular/tag-editor.component.ts b/src/Squidex/app/framework/angular/tag-editor.component.ts index bc24b6c1c..73edc74d2 100644 --- a/src/Squidex/app/framework/angular/tag-editor.component.ts +++ b/src/Squidex/app/framework/angular/tag-editor.component.ts @@ -8,9 +8,8 @@ import { Component, forwardRef, Input } from '@angular/core'; import { ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR } from '@angular/forms'; -/* tslint:disable:no-empty */ - const KEY_ENTER = 13; +/* tslint:disable:no-empty */ const NOOP = () => { }; export interface Converter { diff --git a/src/Squidex/app/framework/declarations.ts b/src/Squidex/app/framework/declarations.ts index f64b76af1..079727ef5 100644 --- a/src/Squidex/app/framework/declarations.ts +++ b/src/Squidex/app/framework/declarations.ts @@ -16,6 +16,7 @@ export * from './angular/date-time.pipes'; export * from './angular/focus-on-change.directive'; export * from './angular/focus-on-init.directive'; export * from './angular/http-utils'; +export * from './angular/json-editor.component'; export * from './angular/modal-view.directive'; export * from './angular/money.pipe'; export * from './angular/name.pipe'; diff --git a/src/Squidex/app/framework/module.ts b/src/Squidex/app/framework/module.ts index cdff61d46..3bf93d8d6 100644 --- a/src/Squidex/app/framework/module.ts +++ b/src/Squidex/app/framework/module.ts @@ -25,6 +25,7 @@ import { FocusOnChangeDirective, FocusOnInitDirective, FromNowPipe, + JsonEditorComponent, LocalStoreService, MessageBus, ModalViewDirective, @@ -67,6 +68,7 @@ import { FocusOnChangeDirective, FocusOnInitDirective, FromNowPipe, + JsonEditorComponent, ModalViewDirective, MoneyPipe, MonthPipe, @@ -94,6 +96,7 @@ import { FocusOnChangeDirective, FocusOnInitDirective, FromNowPipe, + JsonEditorComponent, ModalViewDirective, MoneyPipe, MonthPipe,