diff --git a/src/Squidex/app/features/content/module.ts b/src/Squidex/app/features/content/module.ts
index d47cb229a..6215501d9 100644
--- a/src/Squidex/app/features/content/module.ts
+++ b/src/Squidex/app/features/content/module.ts
@@ -8,7 +8,6 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { DndModule } from 'ng2-dnd';
-import { ColorPickerModule } from 'ngx-color-picker';
import {
CanDeactivateGuard,
@@ -100,7 +99,6 @@ const routes: Routes = [
@NgModule({
imports: [
- ColorPickerModule,
DndModule,
SqxFrameworkModule,
SqxSharedModule,
diff --git a/src/Squidex/app/features/content/shared/field-editor.component.html b/src/Squidex/app/features/content/shared/field-editor.component.html
index 12d8ca479..ae1cef477 100644
--- a/src/Squidex/app/features/content/shared/field-editor.component.html
+++ b/src/Squidex/app/features/content/shared/field-editor.component.html
@@ -73,11 +73,7 @@
-
+
diff --git a/src/Squidex/app/framework/angular/forms/code-editor.component.ts b/src/Squidex/app/framework/angular/forms/code-editor.component.ts
index 6d99cdef0..3bf6f7a04 100644
--- a/src/Squidex/app/framework/angular/forms/code-editor.component.ts
+++ b/src/Squidex/app/framework/angular/forms/code-editor.component.ts
@@ -18,7 +18,7 @@ import {
declare var ace: any;
-export const SQX_JSCRIPT_EDITOR_CONTROL_VALUE_ACCESSOR: any = {
+export const SQX_CODE_EDITOR_CONTROL_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => CodeEditorComponent), multi: true
};
@@ -26,7 +26,7 @@ export const SQX_JSCRIPT_EDITOR_CONTROL_VALUE_ACCESSOR: any = {
selector: 'sqx-code-editor',
styleUrls: ['./code-editor.component.scss'],
templateUrl: './code-editor.component.html',
- providers: [SQX_JSCRIPT_EDITOR_CONTROL_VALUE_ACCESSOR],
+ providers: [SQX_CODE_EDITOR_CONTROL_VALUE_ACCESSOR],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CodeEditorComponent extends ExternalControlComponent implements AfterViewInit {
diff --git a/src/Squidex/app/framework/angular/forms/color-picker.component.html b/src/Squidex/app/framework/angular/forms/color-picker.component.html
new file mode 100644
index 000000000..a891d3d98
--- /dev/null
+++ b/src/Squidex/app/framework/angular/forms/color-picker.component.html
@@ -0,0 +1,17 @@
+
+
+
\ No newline at end of file
diff --git a/src/Squidex/app/framework/angular/forms/color-picker.component.scss b/src/Squidex/app/framework/angular/forms/color-picker.component.scss
new file mode 100644
index 000000000..85b930ea9
--- /dev/null
+++ b/src/Squidex/app/framework/angular/forms/color-picker.component.scss
@@ -0,0 +1,18 @@
+@import '_mixins';
+@import '_vars';
+
+:host /deep/ {
+ .color-picker {
+ & {
+ border-color: $color-border;
+ }
+
+ .hex-text {
+ .box {
+ input {
+ border-color: $color-input;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Squidex/app/framework/angular/forms/color-picker.component.ts b/src/Squidex/app/framework/angular/forms/color-picker.component.ts
new file mode 100644
index 000000000..321b173db
--- /dev/null
+++ b/src/Squidex/app/framework/angular/forms/color-picker.component.ts
@@ -0,0 +1,59 @@
+/*
+ * Squidex Headless CMS
+ *
+ * @license
+ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
+ */
+
+import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, Input } from '@angular/core';
+import { NG_VALUE_ACCESSOR } from '@angular/forms';
+
+import {
+ MathHelper,
+ ModalModel,
+ StatefulControlComponent
+} from '@app/framework/internal';
+
+export const SQX_COLOR_PICKER_CONTROL_VALUE_ACCESSOR: any = {
+ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => ColorPickerComponent), multi: true
+};
+
+interface State {
+ value?: string;
+
+ foreground: string;
+}
+
+@Component({
+ selector: 'sqx-color-picker',
+ styleUrls: ['./color-picker.component.scss'],
+ templateUrl: './color-picker.component.html',
+ providers: [SQX_COLOR_PICKER_CONTROL_VALUE_ACCESSOR],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class ColorPickerComponent extends StatefulControlComponent {
+ @Input()
+ public placeholder = '';
+
+ public modal = new ModalModel();
+
+ constructor(changeDetector: ChangeDetectorRef) {
+ super(changeDetector, { foreground: 'black' });
+ }
+
+ public writeValue(obj: any) {
+ let foreground = 'black';
+
+ if (MathHelper.toLuminance(MathHelper.parseColor(obj)!) < .5) {
+ foreground = 'white';
+ }
+
+ this.next(s => ({ ...s, value: obj, foreground }));
+
+ this.callChange(obj);
+ }
+
+ public blur() {
+ this.callTouched();
+ }
+}
\ No newline at end of file
diff --git a/src/Squidex/app/framework/angular/modals/modal-target.directive.ts b/src/Squidex/app/framework/angular/modals/modal-target.directive.ts
index 428ae47c4..65692dd30 100644
--- a/src/Squidex/app/framework/angular/modals/modal-target.directive.ts
+++ b/src/Squidex/app/framework/angular/modals/modal-target.directive.ts
@@ -15,10 +15,10 @@ import { positionModal } from '@app/shared';
selector: '[sqxModalTarget]'
})
export class ModalTargetDirective extends ResourceOwner implements AfterViewInit, OnDestroy {
- private targetElement: any;
+ private targetElement: Element;
@Input('sqxModalTarget')
- public set target(element: any) {
+ public set target(element: Element) {
if (element !== this.targetElement) {
this.unsubscribeAll();
@@ -42,7 +42,7 @@ export class ModalTargetDirective extends ResourceOwner implements AfterViewInit
constructor(
private readonly renderer: Renderer2,
- private readonly element: ElementRef
+ private readonly element: ElementRef
) {
super();
}
@@ -78,7 +78,11 @@ export class ModalTargetDirective extends ResourceOwner implements AfterViewInit
const modalRef = this.element.nativeElement;
const modalRect = this.element.nativeElement.getBoundingClientRect();
- const targetRect: ClientRect = this.targetElement.getBoundingClientRect();
+ if (modalRect.width === 0 || modalRect.height === 0) {
+ return;
+ }
+
+ const targetRect = this.targetElement.getBoundingClientRect();
let y = 0;
let x = 0;
diff --git a/src/Squidex/app/framework/angular/modals/modal-view.directive.ts b/src/Squidex/app/framework/angular/modals/modal-view.directive.ts
index 68069bc2b..8a1cb1a28 100644
--- a/src/Squidex/app/framework/angular/modals/modal-view.directive.ts
+++ b/src/Squidex/app/framework/angular/modals/modal-view.directive.ts
@@ -87,7 +87,7 @@ export class ModalViewDirective implements OnChanges, OnDestroy {
setTimeout(() => {
this.startListening();
- });
+ }, 1000);
this.changeDetector.detectChanges();
} else if (!isOpen && this.renderedView) {
diff --git a/src/Squidex/app/framework/declarations.ts b/src/Squidex/app/framework/declarations.ts
index 5b6604ccf..ca3e47aa7 100644
--- a/src/Squidex/app/framework/declarations.ts
+++ b/src/Squidex/app/framework/declarations.ts
@@ -8,6 +8,7 @@
export * from './angular/forms/autocomplete.component';
export * from './angular/forms/checkbox-group.component';
export * from './angular/forms/code-editor.component';
+export * from './angular/forms/color-picker.component';
export * from './angular/forms/confirm-click.directive';
export * from './angular/forms/control-errors.component';
export * from './angular/forms/copy.directive';
diff --git a/src/Squidex/app/framework/module.ts b/src/Squidex/app/framework/module.ts
index 9f01654b1..816429552 100644
--- a/src/Squidex/app/framework/module.ts
+++ b/src/Squidex/app/framework/module.ts
@@ -9,6 +9,7 @@ import { CommonModule } from '@angular/common';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { ModuleWithProviders, NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { ColorPickerModule } from 'ngx-color-picker';
import {
AnalyticsService,
@@ -19,6 +20,7 @@ import {
ClipboardService,
CodeComponent,
CodeEditorComponent,
+ ColorPickerComponent,
ConfirmClickDirective,
ControlErrorsComponent,
CopyDirective,
@@ -89,11 +91,13 @@ import {
imports: [
FormsModule,
CommonModule,
- ReactiveFormsModule
+ ReactiveFormsModule,
+ ColorPickerModule
],
declarations: [
AutocompleteComponent,
CheckboxGroupComponent,
+ ColorPickerComponent,
ConfirmClickDirective,
ControlErrorsComponent,
CodeComponent,
@@ -158,6 +162,7 @@ import {
CodeEditorComponent,
CommonModule,
CodeComponent,
+ ColorPickerComponent,
ConfirmClickDirective,
ControlErrorsComponent,
CopyDirective,
diff --git a/src/Squidex/app/framework/utils/math-helper.spec.ts b/src/Squidex/app/framework/utils/math-helper.spec.ts
index af58f664c..8c4fbde93 100644
--- a/src/Squidex/app/framework/utils/math-helper.spec.ts
+++ b/src/Squidex/app/framework/utils/math-helper.spec.ts
@@ -71,4 +71,49 @@ describe('MathHelper', () => {
expect(MathHelper.roundToMultipleOfTwo(13)).toBe(14);
expect(MathHelper.roundToMultipleOfTwo(12.2)).toBe(12);
});
+
+ it('should create color from long string', () => {
+ const color = MathHelper.parseColor('#336699')!;
+
+ expect(color.r).toBe(0.2);
+ expect(color.g).toBe(0.4);
+ expect(color.b).toBe(0.6);
+ expect(color.a).toBe(1.0);
+ });
+
+ it('should create color from short string', () => {
+ const color = MathHelper.parseColor('#369')!;
+
+ expect(color.r).toBe(0.2);
+ expect(color.g).toBe(0.4);
+ expect(color.b).toBe(0.6);
+ expect(color.a).toBe(1.0);
+ });
+
+ it('should create color from rgb string', () => {
+ const color = MathHelper.parseColor('rgb(51, 102, 153)')!;
+
+ expect(color.r).toBe(0.2);
+ expect(color.g).toBe(0.4);
+ expect(color.b).toBe(0.6);
+ expect(color.a).toBe(1.0);
+ });
+
+ it('should create color from rgba string', () => {
+ const color = MathHelper.parseColor('rgba(51, 102, 153, 0.5)')!;
+
+ expect(color.r).toBe(0.2);
+ expect(color.g).toBe(0.4);
+ expect(color.b).toBe(0.6);
+ expect(color.a).toBe(0.5);
+ });
+
+ it('should convert to luminance', () => {
+ expect(MathHelper.toLuminance(undefined!)).toBe(1);
+
+ expect(MathHelper.toLuminance({ r: 0, g: 0, b: 0, a: 1 })).toBe(0);
+ expect(MathHelper.toLuminance({ r: 1, g: 1, b: 1, a: 1 })).toBe(1);
+
+ expect(MathHelper.toLuminance({ r: 0.5, g: 0.5, b: 0.5, a: 1 })).toBe(0.5);
+ });
});
diff --git a/src/Squidex/app/framework/utils/math-helper.ts b/src/Squidex/app/framework/utils/math-helper.ts
index 6a711380c..419464081 100644
--- a/src/Squidex/app/framework/utils/math-helper.ts
+++ b/src/Squidex/app/framework/utils/math-helper.ts
@@ -7,6 +7,65 @@
/* tslint:disable: no-bitwise */
+import { Types } from './types';
+
+interface IColorDefinition {
+ regex: RegExp;
+
+ process(bots: RegExpExecArray): Color;
+}
+
+export interface Color {
+ r: number;
+ g: number;
+ b: number;
+ a: number;
+}
+
+const ColorDefinitions: IColorDefinition[] = [
+ {
+ regex: /^rgba\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3}),\s*([\d\.]{1,})\)$/,
+ process: (bits) => {
+ return {
+ r: parseInt(bits[1], 10) / 255,
+ g: parseInt(bits[2], 10) / 255,
+ b: parseInt(bits[3], 10) / 255,
+ a: parseFloat(bits[4])
+ };
+ }
+ }, {
+ regex: /^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/,
+ process: (bits) => {
+ return {
+ r: parseInt(bits[1], 10) / 255,
+ g: parseInt(bits[2], 10) / 255,
+ b: parseInt(bits[3], 10) / 255,
+ a: 1
+ };
+ }
+ }, {
+ regex: /^(\w{2})(\w{2})(\w{2})$/,
+ process: (bits) => {
+ return {
+ r: parseInt(bits[1], 16) / 255,
+ g: parseInt(bits[2], 16) / 255,
+ b: parseInt(bits[3], 16) / 255,
+ a: 1
+ };
+ }
+ }, {
+ regex: /^(\w{1})(\w{1})(\w{1})$/,
+ process: (bits) => {
+ return {
+ r: parseInt(bits[1] + bits[1], 16) / 255,
+ g: parseInt(bits[2] + bits[2], 16) / 255,
+ b: parseInt(bits[3] + bits[3], 16) / 255,
+ a: 1
+ };
+ }
+ }
+];
+
export module MathHelper {
export const EMPTY_GUID = '00000000-0000-0000-0000-000000000000';
@@ -83,4 +142,34 @@ export module MathHelper {
return degree;
}
+
+ export function parseColor(value: string): Color | undefined {
+ if (!Types.isString(value)) {
+ return undefined;
+ }
+
+ if (value.charAt(0) === '#') {
+ value = value.substr(1, 6);
+ }
+
+ value = value.replace(/ /g, '').toLowerCase();
+
+ for (let colorDefinition of ColorDefinitions) {
+ const bits = colorDefinition.regex.exec(value);
+
+ if (bits) {
+ return colorDefinition.process(bits);
+ }
+ }
+
+ return undefined;
+ }
+
+ export function toLuminance(color: Color) {
+ if (!color) {
+ return 1;
+ }
+
+ return (0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b) / color.a;
+ }
}
\ No newline at end of file
diff --git a/src/Squidex/app/framework/utils/modal-positioner.ts b/src/Squidex/app/framework/utils/modal-positioner.ts
index bca4ae7c4..e81ba3edc 100644
--- a/src/Squidex/app/framework/utils/modal-positioner.ts
+++ b/src/Squidex/app/framework/utils/modal-positioner.ts
@@ -18,7 +18,7 @@ const POSITION_RIGHT_CENTER = 'right';
const POSITION_RIGHT_TOP = 'right-top';
const POSITION_RIGHT_BOTTOM = 'right-bottom';
-export function positionModal(targetRect: ClientRect, modalRect: ClientRect, relativePosition: string, offset: number, fix: boolean, viewportHeight: number, viewportWidth: number): { x: number, y: number } {
+export function positionModal(targetRect: ClientRect, modalRect: ClientRect, relativePosition: string, offset: number, fix: boolean, viewportWidth: number, viewportHeight: number): { x: number, y: number } {
let y = 0;
let x = 0;