diff --git a/src/app/core/services/filters.service.ts b/src/app/core/services/filters.service.ts index de2e2d538..583d0ea31 100644 --- a/src/app/core/services/filters.service.ts +++ b/src/app/core/services/filters.service.ts @@ -11,6 +11,7 @@ export type Filter = { labels: string[]; milestones: string[]; hiddenLabels: Set; + deselectedLabels: Set; }; export const DEFAULT_FILTER: Filter = { @@ -20,7 +21,8 @@ export const DEFAULT_FILTER: Filter = { sort: { active: 'id', direction: 'asc' }, labels: [], milestones: [], - hiddenLabels: new Set() + hiddenLabels: new Set(), + deselectedLabels: new Set() }; @Injectable({ diff --git a/src/app/shared/filter-bar/label-filter-bar/label-filter-bar.component.css b/src/app/shared/filter-bar/label-filter-bar/label-filter-bar.component.css index 03e92e4be..69d9e392f 100644 --- a/src/app/shared/filter-bar/label-filter-bar/label-filter-bar.component.css +++ b/src/app/shared/filter-bar/label-filter-bar/label-filter-bar.component.css @@ -88,6 +88,26 @@ flex-direction: row; justify-content: flex-start; align-items: center; + border-radius: 10px; + height: 40px; + padding: 0px 12px; + margin: 8px 4px; + box-sizing: border-box; + position: relative; +} + +.flexbox-container:hover { + background-color: rgba(0, 0, 0, 0.04); +} + +.flexbox-container-strikethrough { + position: absolute; + top: 50%; + width: 90%; + left: 50%; + transform: translate(-50%, -50%); + height: 2px; + background-color: black; } .input-field { @@ -95,10 +115,6 @@ padding: 0 15px; } -.list-option { - width: 100%; -} - .mat-chip { height: auto; padding: 5.5px 7px; @@ -109,7 +125,6 @@ min-height: 16px; max-height: 42px; margin: 0px; - top: 50%; } .mat-stroked-button { diff --git a/src/app/shared/filter-bar/label-filter-bar/label-filter-bar.component.html b/src/app/shared/filter-bar/label-filter-bar/label-filter-bar.component.html index ae5b5ef32..ad12b066b 100644 --- a/src/app/shared/filter-bar/label-filter-bar/label-filter-bar.component.html +++ b/src/app/shared/filter-bar/label-filter-bar/label-filter-bar.component.html @@ -1,5 +1,5 @@ + + -
- - - - {{ label.name }} - -
- - + {{ label.name }} +
+
+ diff --git a/src/app/shared/filter-bar/label-filter-bar/label-filter-bar.component.ts b/src/app/shared/filter-bar/label-filter-bar/label-filter-bar.component.ts index 6d4326277..38adeec95 100644 --- a/src/app/shared/filter-bar/label-filter-bar/label-filter-bar.component.ts +++ b/src/app/shared/filter-bar/label-filter-bar/label-filter-bar.component.ts @@ -1,5 +1,4 @@ import { AfterViewInit, Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; -import { MatListOption, MatSelectionList } from '@angular/material/list'; import { Observable, Subscription } from 'rxjs'; import { SimpleLabel } from '../../../core/models/label.model'; import { FiltersService } from '../../../core/services/filters.service'; @@ -12,11 +11,14 @@ import { LoggingService } from '../../../core/services/logging.service'; styleUrls: ['./label-filter-bar.component.css'] }) export class LabelFilterBarComponent implements OnInit, AfterViewInit, OnDestroy { - @ViewChild(MatSelectionList) matSelectionList; + private static readonly DEFAULT_LABEL_COLOR: string = 'transparent'; + private static readonly DESELECTED_LABEL_COLOR: string = '#b00020'; + private static readonly SELECTED_LABEL_COLOR: string = '#41c300'; labels$: Observable; allLabels: SimpleLabel[]; - selectedLabelNames: string[] = []; + selectedLabelNames: Set = new Set(); + deselectedLabelNames: Set = new Set(); hiddenLabelNames: Set = new Set(); loaded = false; @@ -35,7 +37,7 @@ export class LabelFilterBarComponent implements OnInit, AfterViewInit, OnDestroy this.labels$.subscribe((labels) => { this.allLabels = labels; this.filtersService.sanitizeLabels(this.allLabels); - this.selectedLabelNames = this.filtersService.filter$.value.labels; + this.selectedLabelNames = new Set(this.filtersService.filter$.value.labels); this.hiddenLabelNames = this.filtersService.filter$.value.hiddenLabels; }); }); @@ -64,16 +66,34 @@ export class LabelFilterBarComponent implements OnInit, AfterViewInit, OnDestroy } /** - * chip as of the current project version consumes click events - * this method is used as an workaround the issue. - * https://github.com/angular/components/issues/19759 + * Change label to the next state. + * Label has the following state rotation: default -> selected -> deselected. + * @param label The label to change state */ - simulateClick(el: MatListOption): void { - if (el.disabled) { - return; + changeLabelState(label: SimpleLabel) { + if (this.selectedLabelNames.has(label.name)) { + this.selectedLabelNames.delete(label.name); + this.deselectedLabelNames.add(label.name); + } else if (this.deselectedLabelNames.has(label.name)) { + this.deselectedLabelNames.delete(label.name); + } else { + this.selectedLabelNames.add(label.name); + } + this.updateSelection(); + } + + /** + * Returns the border color of the label. + * The border color represents the state of the label. + */ + getColor(label: SimpleLabel): string { + if (this.selectedLabelNames.has(label.name)) { + return LabelFilterBarComponent.SELECTED_LABEL_COLOR; + } else if (this.deselectedLabelNames.has(label.name)) { + return LabelFilterBarComponent.DESELECTED_LABEL_COLOR; + } else { + return LabelFilterBarComponent.DEFAULT_LABEL_COLOR; } - el.toggle(); - this.updateSelection([el]); } /** loads in the labels in the repository */ @@ -101,22 +121,16 @@ export class LabelFilterBarComponent implements OnInit, AfterViewInit, OnDestroy return this.allLabels.some((label) => !this.filter(filter, label.name)); } - updateSelection(options: MatListOption[]): void { - options.forEach((option) => { - if (option.selected && !this.selectedLabelNames.includes(option.value)) { - this.selectedLabelNames.push(option.value); - } - if (!option.selected && this.selectedLabelNames.includes(option.value)) { - const index = this.selectedLabelNames.indexOf(option.value); - this.selectedLabelNames.splice(index, 1); - } + updateSelection(): void { + this.filtersService.updateFilters({ + labels: Array.from(this.selectedLabelNames), + deselectedLabels: this.deselectedLabelNames }); - this.filtersService.updateFilters({ labels: this.selectedLabelNames }); } removeAllSelection(): void { - this.matSelectionList.deselectAll(); - this.selectedLabelNames = []; - this.filtersService.updateFilters({ labels: this.selectedLabelNames }); + this.selectedLabelNames = new Set(); + this.deselectedLabelNames = new Set(); + this.updateSelection(); } } diff --git a/src/app/shared/issue-tables/dropdownfilter.ts b/src/app/shared/issue-tables/dropdownfilter.ts index 0b9e8ebef..e1f122f77 100644 --- a/src/app/shared/issue-tables/dropdownfilter.ts +++ b/src/app/shared/issue-tables/dropdownfilter.ts @@ -27,7 +27,7 @@ export function applyDropdownFilter(filter: Filter, data: Issue[]): Issue[] { } ret = ret && filter.milestones.some((milestone) => issue.milestone.title === milestone); - + ret = ret && issue.labels.every((label) => !filter.deselectedLabels.has(label)); return ret && filter.labels.every((label) => issue.labels.includes(label)); }); return filteredData; diff --git a/tests/app/shared/filter-bar/label-filter-bar/label-filter-bar.component.spec.ts b/tests/app/shared/filter-bar/label-filter-bar/label-filter-bar.component.spec.ts index 9ee9bfd31..d0d85a591 100644 --- a/tests/app/shared/filter-bar/label-filter-bar/label-filter-bar.component.spec.ts +++ b/tests/app/shared/filter-bar/label-filter-bar/label-filter-bar.component.spec.ts @@ -119,23 +119,19 @@ describe('LabelFilterBarComponent', () => { describe('updateSelection', () => { it('should update filters service with selected labels', () => { const selectedLabels = [LABEL_NAME_SEVERITY_HIGH, LABEL_NAME_SEVERITY_LOW]; - component.selectedLabelNames = selectedLabels; + component.selectedLabelNames = new Set(selectedLabels); - component.updateSelection([]); + component.updateSelection(); - expect(filtersServiceSpy.updateFilters).toHaveBeenCalledWith({ labels: selectedLabels }); + expect(filtersServiceSpy.updateFilters).toHaveBeenCalledWith({ labels: selectedLabels, deselectedLabels: new Set() }); }); }); describe('removeAllSelection', () => { it('should deselect all labels and update the filter', () => { - const matSelectionListSpy = jasmine.createSpyObj('MatSelectionList', ['deselectAll']); - component.matSelectionList = matSelectionListSpy; - component.removeAllSelection(); - - expect(matSelectionListSpy.deselectAll).toHaveBeenCalled(); - expect(filtersServiceSpy.updateFilters).toHaveBeenCalledWith({ labels: [] }); + expect(component.selectedLabelNames).toEqual(new Set()); + expect(component.deselectedLabelNames).toEqual(new Set()); }); }); });