diff --git a/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.html b/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.html index fc74eb4e9..3cc116171 100644 --- a/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.html +++ b/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.html @@ -12,6 +12,16 @@ diff --git a/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.scss b/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.scss index 40c93838f..e728ca16b 100644 --- a/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.scss +++ b/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.scss @@ -1,3 +1,5 @@ +@import "/src/theme"; + .badge-corner { position: absolute; top: -8px; @@ -12,3 +14,43 @@ overflow-y: scroll; } } + +.btn-group-xs { + > .btn { + padding: 0.2rem 0.25rem; + font-size: 0.675rem; + line-height: 1.2; + border-radius: 0.15rem; + } + + > .btn:not(:first-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } + + > .btn:not(:last-child) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } +} + +.btn-group > label.disabled { + filter: brightness(0.5); + + &.active { + background-color: lighten($primary, 30%); + } +} + + +small > svg { + margin-top: -2px; +} + +.list-group-item-note { + line-height: 1; + + small { + font-size: 65%; + } +} diff --git a/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.ts b/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.ts index 6c526faee..413f2fdb6 100644 --- a/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.ts +++ b/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.ts @@ -15,6 +15,8 @@ export class FilterableDropdownSelectionModel { changed = new Subject() multiple = false + private _logicalOperator = 'and' + temporaryLogicalOperator = this._logicalOperator items: MatchingModel[] = [] @@ -43,6 +45,10 @@ export class FilterableDropdownSelectionModel { return this.items.filter(i => this.temporarySelectionStates.get(i.id) == ToggleableItemState.Selected) } + getExcludedItems() { + return this.items.filter(i => this.temporarySelectionStates.get(i.id) == ToggleableItemState.Excluded) + } + set(id: number, state: ToggleableItemState, fireEvent = true) { if (state == ToggleableItemState.NotSelected) { this.temporarySelectionStates.delete(id) @@ -56,9 +62,9 @@ export class FilterableDropdownSelectionModel { toggle(id: number, fireEvent = true) { let state = this.temporarySelectionStates.get(id) - if (state == null || state != ToggleableItemState.Selected) { + if (state == null || (state != ToggleableItemState.Selected && state != ToggleableItemState.Excluded)) { this.temporarySelectionStates.set(id, ToggleableItemState.Selected) - } else if (state == ToggleableItemState.Selected) { + } else if (state == ToggleableItemState.Selected || state == ToggleableItemState.Excluded) { this.temporarySelectionStates.delete(id) } @@ -83,13 +89,46 @@ export class FilterableDropdownSelectionModel { if (fireEvent) { this.changed.next(this) } + } + exclude(id: number, fireEvent:boolean = true) { + let state = this.temporarySelectionStates.get(id) + if (state == null || state != ToggleableItemState.Excluded) { + this.temporarySelectionStates.set(id, ToggleableItemState.Excluded) + this.temporaryLogicalOperator = this._logicalOperator = 'and' + } else if (state == ToggleableItemState.Excluded) { + this.temporarySelectionStates.delete(id) + } + + if (!this.multiple) { + for (let key of this.temporarySelectionStates.keys()) { + if (key != id) { + this.temporarySelectionStates.delete(key) + } + } + } + + if (fireEvent) { + this.changed.next(this) + } } private getNonTemporary(id: number) { return this.selectionStates.get(id) || ToggleableItemState.NotSelected } + get logicalOperator(): string { + return this.temporaryLogicalOperator + } + + set logicalOperator(operator: string) { + this.temporaryLogicalOperator = operator + } + + toggleOperator() { + this.changed.next(this) + } + get(id: number) { return this.temporarySelectionStates.get(id) || ToggleableItemState.NotSelected } @@ -100,6 +139,7 @@ export class FilterableDropdownSelectionModel { clear(fireEvent = true) { this.temporarySelectionStates.clear() + this.temporaryLogicalOperator = this._logicalOperator = 'and' if (fireEvent) { this.changed.next(this) } @@ -110,6 +150,8 @@ export class FilterableDropdownSelectionModel { return true } else if (!Array.from(this.selectionStates.keys()).every(id => this.selectionStates.get(id) == this.temporarySelectionStates.get(id))) { return true + } else if (this.temporaryLogicalOperator !== this._logicalOperator) { + return true } else { return false } @@ -129,6 +171,7 @@ export class FilterableDropdownSelectionModel { this.temporarySelectionStates.forEach((value, key) => { this.selectionStates.set(key, value) }) + this._logicalOperator = this.temporaryLogicalOperator } reset() { @@ -228,6 +271,10 @@ export class FilterableDropdownComponent { @Output() open = new EventEmitter() + get operatorToggleEnabled(): boolean { + return this.selectionModel.selectionSize() > 1 && this.selectionModel.getExcludedItems().length == 0 + } + constructor(private filterPipe: FilterPipe) { this.selectionModel = new FilterableDropdownSelectionModel() } @@ -269,4 +316,12 @@ export class FilterableDropdownComponent { } } } + + excludeClicked(itemID: number) { + if (this.editing) { + this.selectionModel.toggle(itemID) + } else { + this.selectionModel.exclude(itemID) + } + } } diff --git a/src-ui/src/app/components/common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component.html b/src-ui/src/app/components/common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component.html index 8f74297d3..b020a3c34 100644 --- a/src-ui/src/app/components/common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component.html +++ b/src-ui/src/app/components/common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component.html @@ -1,4 +1,4 @@ -