From f43e543e844a931df2a7882445c3e01268ed7d3a Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Thu, 23 Mar 2023 14:21:31 +0200 Subject: [PATCH] UI: color picker --- ui-ngx/package.json | 1 + .../color-picker-input.component.html | 18 +++ .../color-picker-input.component.ts | 70 +++++++++++ .../color-picker/color-picker.component.html | 37 ++++++ .../color-picker/color-picker.component.scss | 110 ++++++++++++++++++ .../color-picker/color-picker.component.ts | 106 +++++++++++++++++ .../dialog/color-picker-dialog.component.html | 13 ++- .../dialog/color-picker-dialog.component.ts | 18 ++- ui-ngx/src/app/shared/shared.module.ts | 11 +- ui-ngx/yarn.lock | 7 ++ 10 files changed, 380 insertions(+), 11 deletions(-) create mode 100644 ui-ngx/src/app/shared/components/color-picker/color-picker-input.component.html create mode 100644 ui-ngx/src/app/shared/components/color-picker/color-picker-input.component.ts create mode 100644 ui-ngx/src/app/shared/components/color-picker/color-picker.component.html create mode 100644 ui-ngx/src/app/shared/components/color-picker/color-picker.component.scss create mode 100644 ui-ngx/src/app/shared/components/color-picker/color-picker.component.ts diff --git a/ui-ngx/package.json b/ui-ngx/package.json index a8abb6fbeb..96577444fd 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -31,6 +31,7 @@ "@flowjs/flow.js": "^2.14.1", "@flowjs/ngx-flow": "~0.6.0", "@geoman-io/leaflet-geoman-free": "^2.13.0", + "@iplab/ngx-color-picker": "^15.0.2", "@juggle/resize-observer": "^3.3.1", "@mat-datetimepicker/core": "~11.0.3", "@material-ui/core": "4.12.3", diff --git a/ui-ngx/src/app/shared/components/color-picker/color-picker-input.component.html b/ui-ngx/src/app/shared/components/color-picker/color-picker-input.component.html new file mode 100644 index 0000000000..564f1d64f2 --- /dev/null +++ b/ui-ngx/src/app/shared/components/color-picker/color-picker-input.component.html @@ -0,0 +1,18 @@ +
+
+ + + + + + + + + +
+ + + + +
+ diff --git a/ui-ngx/src/app/shared/components/color-picker/color-picker-input.component.ts b/ui-ngx/src/app/shared/components/color-picker/color-picker-input.component.ts new file mode 100644 index 0000000000..22ce2c0849 --- /dev/null +++ b/ui-ngx/src/app/shared/components/color-picker/color-picker-input.component.ts @@ -0,0 +1,70 @@ +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + EventEmitter, + Input, + OnInit, + Output +} from '@angular/core'; +import { Color } from '@iplab/ngx-color-picker'; +import { ColorType } from '@shared/components/color-picker/color-picker.component'; + +@Component({ + selector: `tb-color-picker-input-component`, + templateUrl: `./color-picker-input.component.html`, + styleUrls: [], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ColorPickerInputComponent { + + @Input() + public hue: Color; + + @Output() + public hueChange = new EventEmitter(false); + + @Input() + public color: Color; + + @Output() + public colorChange = new EventEmitter(false); + + public labelVisible: boolean; + + @Input() + public set label(value) { + this.labelVisible = true; + } + + @Input() + public colorFormat: string; + + public isAlphaVisible = true; + + @Input() + public set alpha(isVisible: boolean) { + this.isAlphaVisible = isVisible; + } + + public get value() { + return this.color ? this.color.getRgba() : null; + } + + constructor() { + } + + public onInputChange(newValue, color: 'R' | 'G' | 'B' | 'A') { + const value = this.value; + const red = color === 'R' ? newValue : value.red; + const green = color === 'G' ? newValue : value.green; + const blue = color === 'B' ? newValue : value.blue; + const alpha = color === 'A' ? newValue : value.alpha; + + const newColor = new Color().setRgba(red, green, blue, alpha); + const hue = new Color().setHsva(newColor.getHsva().hue); + + this.hueChange.emit(hue); + this.colorChange.emit(newColor); + } +} diff --git a/ui-ngx/src/app/shared/components/color-picker/color-picker.component.html b/ui-ngx/src/app/shared/components/color-picker/color-picker.component.html new file mode 100644 index 0000000000..dba4605736 --- /dev/null +++ b/ui-ngx/src/app/shared/components/color-picker/color-picker.component.html @@ -0,0 +1,37 @@ + + +
+
+
+ +
+
+ + +
+
+ + + + + {{type}} + + + + + + + + + + + + + + + + + +
+ + diff --git a/ui-ngx/src/app/shared/components/color-picker/color-picker.component.scss b/ui-ngx/src/app/shared/components/color-picker/color-picker.component.scss new file mode 100644 index 0000000000..39a0c7444c --- /dev/null +++ b/ui-ngx/src/app/shared/components/color-picker/color-picker.component.scss @@ -0,0 +1,110 @@ +:host { + display: block; + width: 100%; + max-width: 320px; + min-width: 100%; + border-radius: 2px; + background: #fff; + + ::ng-deep { + .saturation-component { + height: 200px; + border-radius: 8px; + div.pointer { + border-width: 2px; + width: 16px; + height: 16px; + } + } + + indicator-component { + svg { + vertical-align: -70% !important; + } + } + + .hue-alpha-range { + alpha-component, hue-component { + height: 18px; + border: 1px solid rgba(0, 0, 0, 0.1); + .pointer { + height: 18px; + width: 18px; + background: none; + border: 2px solid #fff; + } + .gradient-color { + border-radius: 24px; + } + } + } + } +} + + + +.controls { + padding: 15px 0; +} + +.controls-row { + display: table; + width: 100%; +} + +.column { + display: table-cell; + vertical-align: middle; +} + +.hue-alpha .column:first-child { + width: 42px; + padding: 0 10px 0 0; +} + +indicator-component { + height: 48px; + width: 48px; + border-radius: 8px; + background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAMUlEQVQ4T2NkYGAQYcAP3uCTZhw1gGGYhAGBZIA/nYDCgBDAm9BGDWAAJyRCgLaBCAAgXwixzAS0pgAAAABJRU5ErkJggg==') repeat; +} + +.hue-alpha-range { + hue-component, alpha-component { + border-radius: 24px; + } +} + +hue-component { + margin-bottom: 16px; +} + +color-presets-component { + border-top: 1px solid #d0d0d0; + padding: 12px; + + ::ng-deep .presets-row { + padding: 12px 0 0; + } +} + +.type-btn { + display: inline-block; + height: 20px; + width: 20px; + background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAAgCAYAAAAffCjxAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAACewAAAnsB01CO3AAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAIASURBVEiJ7ZY9axRRFIafsxMStrLQJpAgpBFhi+C9w1YSo00I6RZ/g9vZpBf/QOr4GyRgkSKNSrAadsZqQGwCkuAWyRZJsySwvhZ7N/vhzrgbLH3Ld8597jlzz50zJokyxXH8DqDVar0qi6v8BbItqSGpEcfxdlmsFWXkvX8AfAVWg3UKPEnT9GKujMzsAFgZsVaCN1VTQd77XUnrgE1kv+6935268WRpzrnHZvYRWC7YvC3pRZZl3wozqtVqiyH9IgjAspkd1Gq1xUJQtVrdB9ZKIAOthdg/Qc65LUk7wNIMoCVJO865rYFhkqjX6/d7vV4GPJwBMqofURS5JEk6FYBer/eeYb/Mo9WwFnPOvQbeAvfuAAK4BN4sAJtAG/gJIElmNuiJyba3EGNmZiPeZuEVmVell/Y/6N+CzDn3AXhEOOo7Hv/3BeAz8IzQkMPnJbuPx1wC+yYJ7/0nYIP5S/0FHKdp+rwCEEXRS/rf5Hl1Gtb2M0iSpCOpCZzPATmX1EySpHMLAsiy7MjMDoHrGSDXZnaYZdnRwBh7J91utwmczAA6CbG3GgPleX4jqUH/a1CktqRGnuc3hSCAMB32gKspkCtgb3KCQMmkjeP4WNJThrNNZval1WptTIsv7JtQ4tmIdRa8qSoEpWl6YWZNoAN0zKxZNPehpLSBZv2t+Q0CJ9lLnARQLAAAAABJRU5ErkJggg==') no-repeat center; + background-size: 6px 12px; + + &:hover { + background-color: #eee; + } +} + +.type-column { + width: 25px; + text-align: right; +} + +.presentation { + padding: 12px 0 0; +} diff --git a/ui-ngx/src/app/shared/components/color-picker/color-picker.component.ts b/ui-ngx/src/app/shared/components/color-picker/color-picker.component.ts new file mode 100644 index 0000000000..e11d11f880 --- /dev/null +++ b/ui-ngx/src/app/shared/components/color-picker/color-picker.component.ts @@ -0,0 +1,106 @@ +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + EventEmitter, + Input, + OnChanges, + OnDestroy, + OnInit, + Output, SimpleChanges +} from '@angular/core'; +import { Color, ColorPickerControl } from '@iplab/ngx-color-picker'; +import { Subscription } from 'rxjs'; +export enum ColorType { + hex = 'hex', + hexa = 'hexa', + rgba = 'rgba', + rgb = 'rgb', + hsla = 'hsla', + hsl = 'hsl', + cmyk = 'cmyk' +} + +@Component({ + selector: `tb-color-picker`, + templateUrl: `./color-picker.component.html`, + styleUrls: [`./color-picker.component.scss`], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ColorPickerComponent implements OnInit, OnChanges, OnDestroy { + + public selectedPresentation = 0; + public presentations = [ColorType.hex, ColorType.hexa, ColorType.rgb, ColorType.rgba, ColorType.hsl, ColorType.hsla]; + + @Input() + public color: string; + + @Input() + public control: ColorPickerControl; + + @Output() + public colorChange: EventEmitter = new EventEmitter(false); + + private subscriptions: Array = []; + + constructor(private readonly cdr: ChangeDetectorRef) { + } + + public ngOnInit(): void { + if (!this.control) { + this.control = new ColorPickerControl(); + } + + if (this.color) { + this.control.setValueFrom(this.color); + } + + this.subscriptions.push( + this.control.valueChanges.subscribe((value) => { + this.cdr.markForCheck(); + this.colorChange.emit(this.getValueByType(value, this.control.initType)); + }) + ); + } + + changeColorFormat(event: Event) { + this.colorChange.emit(this.getValueByType(this.control.value, this.control.initType)); + } + + public ngOnDestroy(): void { + this.cdr.detach(); + this.subscriptions.forEach((subscription) => subscription.unsubscribe()); + this.subscriptions.length = 0; + } + + public ngOnChanges(changes: SimpleChanges): void { + if (this.color && this.control && this.getValueByType(this.control.value, this.control.initType) !== this.color) { + this.control.setValueFrom(this.color); + } + } + + public changePresentation(): void { + this.selectedPresentation = + this.selectedPresentation === this.presentations.length - 1 ? 0 : this.selectedPresentation + 1; + } + + getValueByType(color: Color, type: ColorType): string { + switch (type) { + case ColorType.hex: + return color.toHexString(); + case ColorType.hexa: + return color.toHexString(true); + case ColorType.rgb: + return color.toRgbString(); + case ColorType.rgba: + return color.toRgbaString(); + case ColorType.hsl: + return color.toHslString(); + case ColorType.hsla: + return color.toHslaString(); + default: + return color.toRgbaString(); + } + } + +} diff --git a/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.html b/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.html index a7d4835547..4ea0a5f8c5 100644 --- a/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.html +++ b/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.html @@ -17,12 +17,13 @@ -->
- - + + + + + + +
diff --git a/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.ts b/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.ts index e986ddc331..784da8da25 100644 --- a/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.ts +++ b/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.ts @@ -19,9 +19,19 @@ import { ErrorStateMatcher } from '@angular/material/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms'; +import { + FormGroupDirective, + NgForm, + UntypedFormBuilder, + UntypedFormControl, + UntypedFormGroup, + Validators +} from '@angular/forms'; import { Router } from '@angular/router'; import { DialogComponent } from '@shared/components/dialog.component'; +import { ColorPickerControl } from '@iplab/ngx-color-picker'; +import { ColorType } from '@shared/components/color-picker/color-picker.component'; +import * as tinycolor_ from 'tinycolor2'; export interface ColorPickerDialogData { color: string; @@ -40,12 +50,16 @@ export class ColorPickerDialogComponent extends DialogComponent, protected router: Router, @Inject(MAT_DIALOG_DATA) public data: ColorPickerDialogData, @SkipSelf() private errorStateMatcher: ErrorStateMatcher, public dialogRef: MatDialogRef, - public fb: UntypedFormBuilder) { + public fb: UntypedFormBuilder,) { super(store, router, dialogRef); } diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index 950532adab..10dbbef4aa 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -62,7 +62,7 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { RouterModule } from '@angular/router'; import { ShareModule as ShareButtonsModule } from 'ngx-sharebuttons'; import { HotkeyModule } from 'angular2-hotkeys'; -import { ColorPickerModule } from 'ngx-color-picker'; +import { ColorPickerModule } from '@iplab/ngx-color-picker'; import { NgxHmCarouselModule } from 'ngx-hm-carousel'; import { UserMenuComponent } from '@shared/components/user-menu.component'; import { NospacePipe } from '@shared/pipe/nospace.pipe'; @@ -169,6 +169,8 @@ import { CustomDateAdapter } from '@shared/adapter/custom-datatime-adapter'; import { CustomPaginatorIntl } from '@shared/services/custom-paginator-intl'; import { TbScriptLangComponent } from '@shared/components/script-lang.component'; import { DateAgoPipe } from '@shared/pipe/date-ago.pipe'; +import { ColorPickerComponent } from '@shared/components/color-picker/color-picker.component'; +import { ColorPickerInputComponent } from '@shared/components/color-picker/color-picker-input.component'; export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) { return markedOptionsService; @@ -309,7 +311,9 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) BranchAutocompleteComponent, PhoneInputComponent, TbScriptLangComponent, - DateAgoPipe + DateAgoPipe, + ColorPickerComponent, + ColorPickerInputComponent ], imports: [ CommonModule, @@ -514,7 +518,8 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) BranchAutocompleteComponent, PhoneInputComponent, TbScriptLangComponent, - DateAgoPipe + DateAgoPipe, + ColorPickerComponent ] }) export class SharedModule { } diff --git a/ui-ngx/yarn.lock b/ui-ngx/yarn.lock index b939f898f1..e4c90c98f3 100644 --- a/ui-ngx/yarn.lock +++ b/ui-ngx/yarn.lock @@ -1565,6 +1565,13 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== +"@iplab/ngx-color-picker@^15.0.2": + version "15.0.2" + resolved "https://registry.yarnpkg.com/@iplab/ngx-color-picker/-/ngx-color-picker-15.0.2.tgz#856bf2571378e792e5e42b566ac1aa79a9262763" + integrity sha512-wum0Hg4Ky/6mhvzolEpFpjGckOjN8L2nkXvy3mWUVclFHP9MZqqgpJaxghtax7/tMdEWQnwQI3PYsNyKfF15ug== + dependencies: + tslib "^2.3.0" + "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced"