Browse Source

Color picker positioning improved.

pull/349/head
Sebastian Stehle 7 years ago
parent
commit
3cb57080b3
  1. 2
      src/Squidex/app/features/content/module.ts
  2. 6
      src/Squidex/app/features/content/shared/field-editor.component.html
  3. 4
      src/Squidex/app/framework/angular/forms/code-editor.component.ts
  4. 17
      src/Squidex/app/framework/angular/forms/color-picker.component.html
  5. 18
      src/Squidex/app/framework/angular/forms/color-picker.component.scss
  6. 59
      src/Squidex/app/framework/angular/forms/color-picker.component.ts
  7. 12
      src/Squidex/app/framework/angular/modals/modal-target.directive.ts
  8. 2
      src/Squidex/app/framework/angular/modals/modal-view.directive.ts
  9. 1
      src/Squidex/app/framework/declarations.ts
  10. 7
      src/Squidex/app/framework/module.ts
  11. 45
      src/Squidex/app/framework/utils/math-helper.spec.ts
  12. 89
      src/Squidex/app/framework/utils/math-helper.ts
  13. 2
      src/Squidex/app/framework/utils/modal-positioner.ts

2
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,

6
src/Squidex/app/features/content/shared/field-editor.component.html

@ -73,11 +73,7 @@
</div>
</ng-container>
<ng-container *ngSwitchCase="'Color'">
<input class="form-control" type="text" [style.background]="control.value" [formControl]="control" [placeholder]="field.displayPlaceholder"
cpPosition="bottom"
cpOutputFormat="hex"
[colorPicker]="control.value"
(colorPickerChange)="control.setValue($event)" />
<sqx-color-picker [formControl]="control" [placeholder]="field.displayPlaceholder"></sqx-color-picker>
</ng-container>
</ng-container>
</ng-container>

4
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<string> implements AfterViewInit {

17
src/Squidex/app/framework/angular/forms/color-picker.component.html

@ -0,0 +1,17 @@
<input class="form-control" type="text" #input
[style.background]="snapshot.value"
[style.color]="snapshot.foreground"
[placeholder]="placeholder"
[ngModel]="snapshot.value"
(ngModelChange)="writeValue($event)"
(focus)="modal.show()" (blur)="blur()" />
<div *sqxModalView="modal" [sqxModalTarget]="input" position="bottom-left">
<div [style.background]="snapshot.value"
[cpToggle]="true"
[cpDialogDisplay]="'inline'"
[cpCancelButton]="false"
[colorPicker]="snapshot.value"
(colorPickerChange)="writeValue($event)">
</div>
</div>

18
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;
}
}
}
}
}

59
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<State, string> {
@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();
}
}

12
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<Element>
) {
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;

2
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) {

1
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';

7
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,

45
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);
});
});

89
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;
}
}

2
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;

Loading…
Cancel
Save