diff --git a/.gitignore b/.gitignore
index 86d943a9..7079776f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -44,3 +44,5 @@ testem.log
# System Files
.DS_Store
Thumbs.db
+
+.angular/
\ No newline at end of file
diff --git a/src/app/base/base.module.ts b/src/app/base/base.module.ts
index e16e05ce..0470c7b4 100644
--- a/src/app/base/base.module.ts
+++ b/src/app/base/base.module.ts
@@ -16,6 +16,7 @@ import { SidebarPmComponent } from './components/sidebar-pm/sidebar-pm.component
import { DropdownDirective } from './directives/dropdown.directive';
import { PercentRoundPipe } from './pipes/decimal-round.pipe';
import { HeaderComponent } from './components/header/header.component';
+import { DropdownComponent } from './components/dropdown/dropdown.component';
import { HeuristicStatusesComponent } from './components/heuristic-statuses/heuristic-statuses.component';
@NgModule({
@@ -28,6 +29,7 @@ import { HeuristicStatusesComponent } from './components/heuristic-statuses/heur
DropdownDirective,
PercentRoundPipe,
HeaderComponent,
+ DropdownComponent,
HeuristicStatusesComponent
],
imports: [CommonModule, AppRoutingModule, HighlightModule],
@@ -50,6 +52,7 @@ import { HeuristicStatusesComponent } from './components/heuristic-statuses/heur
DropdownDirective,
PercentRoundPipe,
HeaderComponent,
+ DropdownComponent,
HeuristicStatusesComponent
],
providers: [
diff --git a/src/app/base/components/dropdown/dropdown-helper.ts b/src/app/base/components/dropdown/dropdown-helper.ts
new file mode 100644
index 00000000..fb596128
--- /dev/null
+++ b/src/app/base/components/dropdown/dropdown-helper.ts
@@ -0,0 +1,50 @@
+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)
+ * @buttonVersion {string, optional} - defaults to 'default' (button with a caption text), '...', 'userIcon'
+ * @avatarUri {string, optional} - link to the avatar image for logged user
+ * @prefix {string, optional} - prefix to the main name in the option
+ * @postfix {string, optional} - postfix to the main name in the option
+ * @buttonBgColor {string, optional} - background color for the button
+ * @buttonTextColor {string, optional} - text color for the button
+ * @optionDescriptions {string, optional} - array with optional descriptions to the options
+ * @hoverColor {string, optional} - background color on hover for the dropdown options
+ * @textColor {string, optional} - text color for the dropdown options
+ * @textHoverColor {string, optional} - text color on hover for the dropdown options
+ * @textSize {string, optional} - text size for the dropdown options
+ * @isButtonSampleProjects {boolean, optional} - checks if the button is the specific one for sample project
+ */
+export type DropdownOptions = {
+ optionArray: string[] | FormArray[] | any[];
+ buttonCaption?: string;
+ valuePropertyPath?: string;
+ keepDropdownOpen?: boolean;
+ buttonTooltip?: string;
+ isDisabled?: boolean;
+ isOptionDisabled?: boolean[];
+ optionIcons?: string[];
+ hasCheckboxes?: boolean;
+ buttonVersion?: string;
+ avatarUri?: string;
+ prefix?: string;
+ postfix?: string;
+ buttonBgColor?: string;
+ buttonTextColor?: string;
+ optionDescriptions?: string[];
+ hoverColor?: string;
+ textColor?: string;
+ textHoverColor?: string;
+ textSize?: string;
+ isButtonSampleProjects?: boolean;
+};
+
diff --git a/src/app/base/components/dropdown/dropdown.component.html b/src/app/base/components/dropdown/dropdown.component.html
new file mode 100644
index 00000000..6731f163
--- /dev/null
+++ b/src/app/base/components/dropdown/dropdown.component.html
@@ -0,0 +1,196 @@
+
+
+ {{ dropdownOptions.hasCheckboxes?'':dropdownOptions.buttonCaption }}
+
+ {{getDropdownDisplayText(dropdownOptions.optionArray,"EMPTY")}}
+ {{getDropdownDisplayText(dropdownOptions.optionArray,"NOT_NEGATED")}}
+ {{getDropdownDisplayText(dropdownOptions.optionArray,"NEGATED")}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/app/base/components/dropdown/dropdown.component.scss b/src/app/base/components/dropdown/dropdown.component.scss
new file mode 100644
index 00000000..452f95d2
--- /dev/null
+++ b/src/app/base/components/dropdown/dropdown.component.scss
@@ -0,0 +1,11 @@
+.tooltip::before {
+ z-index: 50;
+}
+
+.width-icon-menues {
+ width: 128px;
+}
+
+.width-sample-projects {
+ width: 384px !important;
+}
\ No newline at end of file
diff --git a/src/app/base/components/dropdown/dropdown.component.spec.ts b/src/app/base/components/dropdown/dropdown.component.spec.ts
new file mode 100644
index 00000000..9b7c789b
--- /dev/null
+++ b/src/app/base/components/dropdown/dropdown.component.spec.ts
@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { DropdownComponent } from './dropdown.component';
+
+describe('DropdownItComponent', () => {
+ let component: DropdownComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [DropdownComponent]
+ })
+ .compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(DropdownComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/base/components/dropdown/dropdown.component.ts b/src/app/base/components/dropdown/dropdown.component.ts
new file mode 100644
index 00000000..bc3e8036
--- /dev/null
+++ b/src/app/base/components/dropdown/dropdown.component.ts
@@ -0,0 +1,212 @@
+import { Component, ElementRef, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges, ViewChild } from '@angular/core';
+import { AbstractControl, FormGroup } from '@angular/forms';
+import { DropdownOptions } from './dropdown-helper';
+
+@Component({
+ selector: 'kern-dropdown',
+ templateUrl: './dropdown.component.html',
+ styleUrls: ['./dropdown.component.scss']
+})
+export class DropdownComponent implements OnChanges {
+
+ @Input() dropdownOptions: DropdownOptions;
+ @Output() optionClicked = new EventEmitter();
+ @Output() isInitialProject = new EventEmitter<{ flagInitial: boolean, value: string }>();
+
+ @ViewChild("dropdownOpenButton") dropdownOpenButton: ElementRef;
+ @ViewChild("dropdownOptionsDiv") dropdownOptionsDiv: ElementRef;
+
+ hasInputErrors: string;
+ buttonClassList: string;
+ dropdownClassList: string;
+ dropdownOptionCaptions: string[];
+ useValueAsCaption: boolean = false;
+ static colorWithoutNumber: string[] = ['kernindigo', 'black', 'white']
+
+ constructor() { }
+ ngOnChanges(changes: SimpleChanges): void {
+ this.dropdownOptionCaptions = this.addPrePostFix(this.getTextArray(this.dropdownOptions.optionArray));
+ this.runInputChecks();
+ this.buildHelperValues();
+ }
+
+ private addPrePostFix(arr: string[]): string[] {
+ if (this.dropdownOptions.prefix) arr = arr.map(el => this.dropdownOptions.prefix + el);
+ if (this.dropdownOptions.postfix) arr = arr.map(el => el + this.dropdownOptions.postfix);
+ return arr;
+ }
+
+ private getTextArray(arr: string[] | any[]): string[] {
+ if (!arr) return [];
+ if (arr.length == 0) return [];
+ if (typeof arr[0] == 'string') return arr as string[];
+ if (typeof arr[0] == 'number') return arr.map(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.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 ' : '';
+ this.dropdownClassList += this.dropdownOptions.buttonVersion != 'default' ? 'right-0 width-icon-menues' : '';
+ this.buttonClassList += this.dropdownOptions.isButtonSampleProjects ? 'py-2' : 'border-gray-300 py-1.5';
+ 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.optionDescriptions && this.dropdownOptions.optionArray.length != this.dropdownOptions.optionDescriptions.length) this.hasInputErrors = "array options != optionDescriptions length\n";
+ if (this.dropdownOptions.optionIcons && this.dropdownOptions.optionIcons.length != this.dropdownOptions.optionIcons.length) this.hasInputErrors = "array options != optionIcons length\n";
+ if (!this.dropdownOptions.buttonVersion) this.dropdownOptions.buttonVersion = "default";
+
+ this.buttonClassList = "";
+ if (!this.dropdownOptions.buttonBgColor) this.dropdownOptions.buttonBgColor = "bg-white ";
+ else this.dropdownOptions.buttonBgColor = "bg-" + this.reduceColorProperty(this.dropdownOptions.buttonBgColor, '700');
+ this.buttonClassList += this.dropdownOptions.buttonBgColor + " ";
+ if (!this.dropdownOptions.buttonTextColor) this.dropdownOptions.buttonTextColor = "text-gray-700 ";
+ else this.dropdownOptions.buttonTextColor = "text-" + this.reduceColorProperty(this.dropdownOptions.buttonTextColor, '700');
+ this.buttonClassList += this.dropdownOptions.buttonTextColor;
+
+ // Dropdown properties
+ this.dropdownOptions.hoverColor = "hover:bg-" + (!this.dropdownOptions.hoverColor ? "gray-700" : this.reduceColorProperty(this.dropdownOptions.hoverColor, '700'));
+ this.dropdownOptions.textColor = "text-" + (!this.dropdownOptions.textColor ? "gray-700" : this.reduceColorProperty(this.dropdownOptions.textColor, '700'));
+ this.dropdownOptions.textHoverColor = "hover:text-" + (!this.dropdownOptions.textHoverColor ? "white" : this.reduceColorProperty(this.dropdownOptions.textHoverColor, '700'));
+ this.dropdownOptions.textSize = !this.dropdownOptions.textSize ? "text-xs" : this.dropdownOptions.textSize;
+ if (this.hasInputErrors) console.log(this.hasInputErrors);
+
+ }
+
+ private reduceColorProperty(property: string, defaultShade: string): string {
+ let splitted = property.split(":");
+ if (splitted.length > 1) property = splitted[splitted.length - 1];
+
+ splitted = property.split("-");
+ if (['bg', 'text'].includes(splitted[0])) splitted = splitted.slice(1);
+
+ if (splitted.length == 1) {
+ if (DropdownComponent.colorWithoutNumber.includes(splitted[0])) return splitted[0] + " ";
+ return splitted[0] + "-" + defaultShade;
+ }
+ return splitted.join("-");
+ }
+
+ 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.optionArray.length > 0 && typeof this.dropdownOptions.optionArray[0] != 'string') {
+ if (typeof this.dropdownOptions.optionArray[0] == 'number') {
+ this.optionClicked.emit(this.dropdownOptions.optionArray[clickIndex]);
+ return;
+ }
+ }
+ if (this.dropdownOptions.isButtonSampleProjects && clickIndex % 2 != 0) this.isInitialProject.emit({ flagInitial: true, value: this.dropdownOptionCaptions[clickIndex - 1] });
+
+ 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/base/components/header/header.component.html b/src/app/base/components/header/header.component.html
index b9b7ffdb..4b80017c 100644
--- a/src/app/base/components/header/header.component.html
+++ b/src/app/base/components/header/header.component.html
@@ -1,4 +1,4 @@
-
+
diff --git a/src/app/base/components/header/header.component.ts b/src/app/base/components/header/header.component.ts
index 0cfd38eb..2ca700ff 100644
--- a/src/app/base/components/header/header.component.ts
+++ b/src/app/base/components/header/header.component.ts
@@ -1,4 +1,4 @@
-import { Component, Input, OnDestroy, OnInit } from '@angular/core';
+import { Component, Input, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { Project } from 'aws-sdk/clients/codebuild';
import { Subscription, timer } from 'rxjs';
@@ -10,7 +10,7 @@ import { ConfigManager } from '../../services/config-service';
templateUrl: './header.component.html',
styleUrls: ['./header.component.scss']
})
-export class HeaderComponent implements OnInit, OnDestroy {
+export class HeaderComponent implements OnInit {
@Input() organizationName: string;
@Input() user: any;
@@ -18,13 +18,12 @@ export class HeaderComponent implements OnInit, OnDestroy {
@Input() page: string;
@Input() project: Project;
@Input() avatarUri: string;
- subscriptions$: Subscription[] = [];
showConfigSettings: boolean = false;
+ subscriptions$: Subscription[] = [];
hideLogout: boolean;
- constructor(private auth: AuthApiService,
- private router: Router) { }
+ constructor(private router: Router, private auth: AuthApiService,) { }
ngOnInit(): void {
this.setShowConfig();
@@ -38,19 +37,17 @@ export class HeaderComponent implements OnInit, OnDestroy {
this.showConfigSettings = !ConfigManager.getIsManaged();
}
- ngOnDestroy(): void {
- this.subscriptions$.forEach((subscription) => subscription.unsubscribe());
- }
-
- toggleVisible(isVisible: boolean, menuButton: HTMLDivElement): void {
- if (isVisible) {
- menuButton.classList.remove('hidden');
- menuButton.classList.add('block');
- menuButton.classList.add('z-10');
- } else {
- menuButton.classList.remove('z-10');
- menuButton.classList.remove('block');
- menuButton.classList.add('hidden');
+ executeOption(optionSelected: string) {
+ switch(optionSelected) {
+ case 'Account Settings':
+ window.open('/auth/settings', '_blank');
+ break;
+ case 'Change config':
+ this.router.navigate(['config'])
+ break;
+ case 'Logout':
+ this.logout();
+ break;
}
}
@@ -62,10 +59,4 @@ export class HeaderComponent implements OnInit, OnDestroy {
);
}
- settings() {
- window.open('/auth/settings', '_blank');
- }
- changeConfig() {
- this.router.navigate(['config'])
- }
}
diff --git a/src/app/base/services/knowledge-bases/knowledge-bases-apollo.service.ts b/src/app/base/services/knowledge-bases/knowledge-bases-apollo.service.ts
index 735d9fda..6ccfffa6 100644
--- a/src/app/base/services/knowledge-bases/knowledge-bases-apollo.service.ts
+++ b/src/app/base/services/knowledge-bases/knowledge-bases-apollo.service.ts
@@ -55,7 +55,7 @@ export class KnowledgeBasesApolloService {
});
}
- blacklistTerm(projectId: string, knowledgeBaseId: string, termId: string) {
+ toggleBlacklistTerm(projectId: string, knowledgeBaseId: string, termId: string) {
return this.apollo.mutate({
mutation: mutations.BLACKLIST_TERM,
variables: {
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 102ff990..f0b807b7 100644
--- a/src/app/data/components/data-browser/data-browser.component.html
+++ b/src/app/data/components/data-browser/data-browser.component.html
@@ -332,47 +332,18 @@
-
-
- No
- Labels
- associated with this
- task
-
-
+
+
-
-
-
- {{getDropdownDisplayText(groupItem.get('manualLabels').controls,"EMPTY")}}
- {{getDropdownDisplayText(groupItem.get('manualLabels').controls,"NOT_NEGATED")}}
- {{getDropdownDisplayText(groupItem.get('manualLabels').controls,"NEGATED")}}
-
-
-
+
+
@@ -381,48 +352,20 @@
-
-
- No
- Labels associated with this
- task
-
-
+
+
-
-
-
- {{getDropdownDisplayText(groupItem.get('weakSupervisionLabels').controls,"EMPTY")}}
- {{getDropdownDisplayText(groupItem.get('weakSupervisionLabels').controls,"NOT_NEGATED")}}
- {{getDropdownDisplayText(groupItem.get('weakSupervisionLabels').controls,"NEGATED")}}
-
-
-
-
+
+
@@ -580,7 +523,8 @@
style="grid-template-columns: max-content max-content max-content max-content max-content;">