diff --git a/src/app/base/base.module.ts b/src/app/base/base.module.ts index 3576a1b0..587b2064 100644 --- a/src/app/base/base.module.ts +++ b/src/app/base/base.module.ts @@ -18,6 +18,7 @@ import { PercentRoundPipe } from './pipes/decimal-round.pipe'; import { HeaderComponent } from './components/header/header.component'; import { DropdownComponent } from './components/dropdown/dropdown.component'; import { DropdownIterativeComponent } from './components/dropdown-iterative/dropdown-iterative.component'; +import { DropdownItComponent } from './components/dropdown-it/dropdown-it.component'; @NgModule({ declarations: [ @@ -30,7 +31,8 @@ import { DropdownIterativeComponent } from './components/dropdown-iterative/drop PercentRoundPipe, HeaderComponent, DropdownComponent, - DropdownIterativeComponent + DropdownIterativeComponent, + DropdownItComponent ], imports: [CommonModule, AppRoutingModule, HighlightModule], exports: [ @@ -53,7 +55,8 @@ import { DropdownIterativeComponent } from './components/dropdown-iterative/drop PercentRoundPipe, HeaderComponent, DropdownComponent, - DropdownIterativeComponent + DropdownIterativeComponent, + DropdownItComponent ], providers: [ { diff --git a/src/app/base/components/dropdown-it/dropdown-it-helper.ts b/src/app/base/components/dropdown-it/dropdown-it-helper.ts new file mode 100644 index 00000000..7daa22c1 --- /dev/null +++ b/src/app/base/components/dropdown-it/dropdown-it-helper.ts @@ -0,0 +1,26 @@ +import { FormArray } from "@angular/forms"; + +/** + * Optionset for kern dropdown + * @optionArray {string[] | FormArray[] | any[]} - Can be any array. string array is just used, FormArray or any object array tries to use "name" property then "text" last first string property + * @buttonCaption {string, optional} - used as caption for the button, if not given the first / current value is used + * @valuePropertyPath {string, optional} - if undefined option text is returned, else (e.g. name.tmp.xyz) the path is split and used to access the object property + * @keepDropdownOpen {boolean, optional} - stops the event propagation of the click event and therfore keeps the menu open + * @buttonTooltip {string, optional} - adds a tooltip if defined + * @isDisabled {boolean, optional} - disables the dropdown + * @isOptionDisabled {boolean[], optional} - disables the dropdown option (needs to be the exact same length as the optionArray) + * @optionIcons {string[], optional} - displays a predfined icon if set for the index (needs to be the exact same length as the optionArray) + * @hasCheckboxes {boolean, optional} - helper for checkbox like dropdowns (e.g. data browser) + */ +export type DropdownOptions = { + optionArray: string[] | FormArray[] | any[]; + buttonCaption?: string; + valuePropertyPath?: string; + keepDropdownOpen?: boolean; + buttonTooltip?: string; + isDisabled?: boolean; + isOptionDisabled?: boolean[]; + optionIcons?: string[]; + hasCheckboxes?: boolean; +}; + diff --git a/src/app/base/components/dropdown-it/dropdown-it.component.html b/src/app/base/components/dropdown-it/dropdown-it.component.html new file mode 100644 index 00000000..dcd6bd9c --- /dev/null +++ b/src/app/base/components/dropdown-it/dropdown-it.component.html @@ -0,0 +1,144 @@ +
+ + +
+ + + + + X + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/app/base/components/dropdown-it/dropdown-it.component.scss b/src/app/base/components/dropdown-it/dropdown-it.component.scss new file mode 100644 index 00000000..b616bdc1 --- /dev/null +++ b/src/app/base/components/dropdown-it/dropdown-it.component.scss @@ -0,0 +1,3 @@ +.tooltip::before { + z-index: 50; +} diff --git a/src/app/base/components/dropdown-it/dropdown-it.component.spec.ts b/src/app/base/components/dropdown-it/dropdown-it.component.spec.ts new file mode 100644 index 00000000..de529faa --- /dev/null +++ b/src/app/base/components/dropdown-it/dropdown-it.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DropdownItComponent } from './dropdown-it.component'; + +describe('DropdownItComponent', () => { + let component: DropdownItComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [DropdownItComponent] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(DropdownItComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/base/components/dropdown-it/dropdown-it.component.ts b/src/app/base/components/dropdown-it/dropdown-it.component.ts new file mode 100644 index 00000000..a044f343 --- /dev/null +++ b/src/app/base/components/dropdown-it/dropdown-it.component.ts @@ -0,0 +1,162 @@ +import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core'; +import { AbstractControl, FormGroup } from '@angular/forms'; +import { DropdownOptions } from './dropdown-it-helper'; + +@Component({ + selector: 'kern-dropdown-it', + templateUrl: './dropdown-it.component.html', + styleUrls: ['./dropdown-it.component.scss'] +}) +export class DropdownItComponent implements OnChanges { + + @Input() dropdownOptions: DropdownOptions; + @Output() optionClicked = new EventEmitter(); + + hasInputErrors: string; + buttonClassList: string; + dropdownClassList: string; + dropdownOptionCaptions: string[]; + useValueAsCaption: boolean = false; + + constructor() { } + ngOnChanges(changes: SimpleChanges): void { + this.dropdownOptionCaptions = this.getTextArray(this.dropdownOptions.optionArray); + this.runInputChecks(); + this.buildHelperValues(); + } + + private getTextArray(arr: string[] | any[]): string[] { + if (!arr) return []; + if (arr.length == 0) return []; + if (typeof arr[0] == 'string') return arr as string[]; + let valueArray = arr; + if (arr[0].value && typeof arr[0].value == 'object') valueArray = arr.map(x => x.getRawValue()); + if (valueArray[0].name) return valueArray.map(a => a.name); + if (valueArray[0].text) return valueArray.map(a => a.text); + + let firstStringKey = ""; + + for (const key of Object.keys(valueArray[0])) { + if (typeof valueArray[0][key] == 'string') { + firstStringKey = key; + break; + } + } + if (!firstStringKey) throw new Error("Cant find text in given array - dropdown"); + return valueArray.map(a => a[firstStringKey]); + } + + private buildHelperValues() { + this.buttonClassList = ""; + this.buttonClassList += this.dropdownOptions.isDisabled ? 'opacity-50 cursor-not-allowed' : 'opacity-100 cursor-pointer'; + this.buttonClassList += this.dropdownOptions.buttonTooltip ? ' tooltip tooltip-right' : ''; + this.dropdownClassList = this.dropdownOptions.hasCheckboxes ? ' w-80' : ' w-auto'; + this.buttonClassList += this.dropdownClassList; + } + + private runInputChecks() { + this.hasInputErrors = ""; + if (!this.dropdownOptions) this.hasInputErrors = "no dropdown options provided\n"; + if (!this.dropdownOptions.optionArray || this.dropdownOptions.optionArray.length == 0) this.hasInputErrors = "no text provided\n"; + if (!this.dropdownOptions.buttonCaption && this.dropdownOptionCaptions.length > 0) { + this.dropdownOptions.buttonCaption = this.dropdownOptionCaptions[0]; + this.useValueAsCaption = true; + } + if (this.dropdownOptions.isOptionDisabled && this.dropdownOptions.isOptionDisabled.length != this.dropdownOptions.optionArray.length) this.hasInputErrors = "array options != isOptionDisabled length\n"; + if (this.dropdownOptions.optionIcons && this.dropdownOptions.optionIcons.length != this.dropdownOptions.optionIcons.length) this.hasInputErrors = "array options != optionIcons length\n"; + + + if (this.hasInputErrors) console.log(this.hasInputErrors); + + } + + toggleVisible(isVisible: boolean, dropdownOptions: HTMLDivElement): void { + if (isVisible) { + dropdownOptions.classList.remove('hidden'); + dropdownOptions.classList.add('block'); + dropdownOptions.classList.add('z-10'); + } else { + dropdownOptions.classList.remove('z-10'); + dropdownOptions.classList.remove('block'); + dropdownOptions.classList.add('hidden'); + } + } + + performActionOnOption(event: MouseEvent, clickIndex: number) { + if (this.dropdownOptions.isOptionDisabled?.length && this.dropdownOptions.isOptionDisabled[clickIndex]) return; + if (this.dropdownOptions.keepDropdownOpen) event.stopPropagation(); + + if (clickIndex >= this.dropdownOptions.optionArray.length) { + console.log("something is wrong in the click action of the dropdown component"); + return; + } + + if (!this.dropdownOptions.valuePropertyPath) { + if (this.useValueAsCaption) this.dropdownOptions.buttonCaption = this.dropdownOptionCaptions[clickIndex]; + if (this.dropdownOptions.hasCheckboxes) this.optionClicked.emit(this.dropdownOptions.optionArray[clickIndex]); + else this.optionClicked.emit(this.dropdownOptionCaptions[clickIndex]); + return; + } + + const splittedPath = this.dropdownOptions.valuePropertyPath.split("."); + + let tmp = this.dropdownOptions.optionArray[clickIndex]; + for (const key of splittedPath) { + tmp = tmp[key]; + } + if (typeof tmp != "string") { + console.log("something is wrong in the click action of the dropdown component - property path"); + return; + } + if (this.useValueAsCaption) this.dropdownOptions.buttonCaption = tmp; + this.optionClicked.emit(tmp); + + } + + getActiveNegateGroupColor(group: FormGroup) { + if (!group.get('active').value) return null; + if (group.contains('negate')) + return group.get('negate').value ? '#ef4444' : '#2563eb'; + return '#2563eb'; + } + + + getDropdownDisplayText( + formControls: AbstractControl[], + labelFor: string + ): string { + let text = ''; + let atLeastOneNegated: boolean = false; + for (let c of formControls) { + const hasNegate = Boolean(c.get('negate')); + if (labelFor == 'EMPTY' && c.get('active').value) return ''; + else if ( + labelFor == 'NOT_NEGATED' && + c.get('active').value && + (!hasNegate || (hasNegate && !c.get('negate').value)) + ) { + text += (text == '' ? '' : ', ') + c.get('name').value; + } else if ( + labelFor == 'NEGATED' && + c.get('active').value && + hasNegate && + c.get('negate').value + ) { + text += (text == '' ? '' : ', ') + c.get('name').value; + } + if ( + !atLeastOneNegated && + c.get('active').value && + hasNegate && + c.get('negate').value + ) + atLeastOneNegated = true; + } + if (labelFor == 'EMPTY') return 'None Selected'; + + if (labelFor == 'NOT_NEGATED' && atLeastOneNegated && text != '') + return text + ', '; + + return text; + } +} diff --git a/src/app/data/components/data-browser/data-browser.component.html b/src/app/data/components/data-browser/data-browser.component.html index 30e69136..4a7726af 100644 --- a/src/app/data/components/data-browser/data-browser.component.html +++ b/src/app/data/components/data-browser/data-browser.component.html @@ -332,14 +332,24 @@ - + + + (optionClicked)="setActiveNegateGroup($event)"> + + + + @@ -351,15 +361,17 @@ [ngIf]="groupItem.get('weakSupervisionLabels').controls.length == 0" formArrayName="manualLabels" [ngIfElse]="dropDownWS"> - + + + (optionClicked)="setActiveNegateGroup($event)"> + @@ -546,7 +558,8 @@
+ data-tooltip-target="tooltip-top" data-tooltip-placement="top" + [ngClass]="similarSearchHelper.recordsInDisplay || activeSlide ? 'cursor-not-allowed':'' ">