rework the bulk editor

This commit is contained in:
jonaswinkler
2020-12-28 12:36:26 +01:00
parent 641e66f1e1
commit 91afd39a0e
4 changed files with 168 additions and 214 deletions

View File

@@ -1,12 +1,12 @@
<div class="btn-group" ngbDropdown role="group" (openChange)="dropdownOpenChange($event)" #dropdown="ngbDropdown">
<button class="btn btn-sm" id="dropdown{{title}}" ngbDropdownToggle [ngClass]="type !== types.Editing && selectionModel.selectionSize() > 0 ? 'btn-primary' : 'btn-outline-primary'">
<button class="btn btn-sm" id="dropdown{{title}}" ngbDropdownToggle [ngClass]="!editing && selectionModel.selectionSize() > 0 ? 'btn-primary' : 'btn-outline-primary'">
<div class="d-none d-md-inline">{{title}}</div>
<div class="d-inline-block d-md-none">
<svg class="toolbaricon" fill="currentColor">
<use attr.xlink:href="assets/bootstrap-icons.svg#{{icon}}" />
</svg>
</div>
<ng-container *ngIf="type !== types.Editing && selectionModel.selectionSize() > 0">
<ng-container *ngIf="!editing && selectionModel.selectionSize() > 0">
<div class="badge bg-secondary text-light rounded-pill badge-corner">
{{selectionModel.selectionSize()}}
</div>
@@ -24,8 +24,8 @@
<app-toggleable-dropdown-button [item]="item" [state]="selectionModel.get(item.id)" (toggle)="selectionModel.toggle(item.id)"></app-toggleable-dropdown-button>
</ng-container>
</div>
<button *ngIf="type == types.Editing" class="list-group-item list-group-item-action bg-light" (click)="dropdown.close()" [disabled]="!hasBeenToggled || (toggleableItems | filter: filterText).length == 0">
<small class="ml-1" [ngClass]="{'font-weight-bold': hasBeenToggled && (toggleableItems | filter: filterText).length > 0}">Apply</small>
<button *ngIf="editing" class="list-group-item list-group-item-action bg-light" (click)="applyClicked()" [disabled]="!selectionModel.isDirty()">
<small class="ml-1" [ngClass]="{'font-weight-bold': selectionModel.isDirty()}">Apply</small>
<svg width="1.5em" height="1em" viewBox="0 0 16 16" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#arrow-right" />
</svg>

View File

@@ -1,14 +1,13 @@
import { Component, EventEmitter, Input, Output, ElementRef, ViewChild } from '@angular/core';
import { FilterPipe } from 'src/app/pipes/filter.pipe';
import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap'
import { ToggleableItem, ToggleableItemState } from './toggleable-dropdown-button/toggleable-dropdown-button.component';
import { ToggleableItemState } from './toggleable-dropdown-button/toggleable-dropdown-button.component';
import { MatchingModel } from 'src/app/data/matching-model';
import { Subject } from 'rxjs';
import { ThrowStmt } from '@angular/compiler';
export enum FilterableDropdownType {
Filtering = 'filtering',
Editing = 'editing'
export interface ChangedItems {
itemsToAdd: MatchingModel[],
itemsToRemove: MatchingModel[]
}
export class FilterableDropdownSelectionModel {
@@ -19,31 +18,37 @@ export class FilterableDropdownSelectionModel {
items: MatchingModel[] = []
selection = new Map<number, ToggleableItemState>()
private selectionStates = new Map<number, ToggleableItemState>()
private temporarySelectionStates = new Map<number, ToggleableItemState>()
getSelectedItems() {
return this.items.filter(i => this.selection.get(i.id) == ToggleableItemState.Selected)
return this.items.filter(i => this.temporarySelectionStates.get(i.id) == ToggleableItemState.Selected)
}
set(id: number, state: ToggleableItemState, fireEvent = true) {
this.selection.set(id, state)
if (state == ToggleableItemState.NotSelected) {
this.temporarySelectionStates.delete(id)
} else {
this.temporarySelectionStates.set(id, state)
}
if (fireEvent) {
this.changed.next(this)
}
}
toggle(id: number, fireEvent = true) {
let state = this.selection.get(id)
let state = this.temporarySelectionStates.get(id)
if (state == null || state != ToggleableItemState.Selected) {
this.selection.set(id, ToggleableItemState.Selected)
this.temporarySelectionStates.set(id, ToggleableItemState.Selected)
} else if (state == ToggleableItemState.Selected) {
this.selection.set(id, ToggleableItemState.NotSelected)
this.temporarySelectionStates.delete(id)
}
if (!this.multiple) {
for (let key of this.selection.keys()) {
for (let key of this.temporarySelectionStates.keys()) {
if (key != id) {
this.selection.set(key, ToggleableItemState.NotSelected)
this.temporarySelectionStates.delete(key)
}
}
}
@@ -55,7 +60,7 @@ export class FilterableDropdownSelectionModel {
}
get(id: number) {
return this.selection.get(id) || ToggleableItemState.NotSelected
return this.temporarySelectionStates.get(id) || ToggleableItemState.NotSelected
}
selectionSize() {
@@ -63,11 +68,47 @@ export class FilterableDropdownSelectionModel {
}
clear(fireEvent = true) {
this.selection.clear()
this.temporarySelectionStates.clear()
if (fireEvent) {
this.changed.next(this)
}
}
isDirty() {
if (!Array.from(this.temporarySelectionStates.keys()).every(id => this.temporarySelectionStates.get(id) == this.selectionStates.get(id))) {
return true
} else if (!Array.from(this.selectionStates.keys()).every(id => this.selectionStates.get(id) == this.temporarySelectionStates.get(id))) {
return true
} else {
return false
}
}
init(map) {
this.temporarySelectionStates = map
this.apply()
}
apply() {
this.selectionStates.clear()
this.temporarySelectionStates.forEach((value, key) => {
this.selectionStates.set(key, value)
})
}
reset() {
this.temporarySelectionStates.clear()
this.selectionStates.forEach((value, key) => {
this.temporarySelectionStates.set(key, value)
})
}
diff(): ChangedItems {
return {
itemsToAdd: this.items.filter(item => this.temporarySelectionStates.get(item.id) == ToggleableItemState.Selected && this.selectionStates.get(item.id) != ToggleableItemState.Selected),
itemsToRemove: this.items.filter(item => !this.temporarySelectionStates.has(item.id) && this.selectionStates.has(item.id)),
}
}
}
@Component({
@@ -131,35 +172,35 @@ export class FilterableDropdownComponent {
icon: string
@Input()
type: FilterableDropdownType = FilterableDropdownType.Filtering
editing = false
types = FilterableDropdownType
@Output()
apply = new EventEmitter<ChangedItems>()
hasBeenToggled:boolean = false
@Output()
open = new EventEmitter()
constructor(private filterPipe: FilterPipe) {
this.selectionModel = new FilterableDropdownSelectionModel()
}
toggleItem(toggleableItem: ToggleableItem): void {
// if (this.singular && toggleableItem.state == ToggleableItemState.Selected) {
// this.selectionModel.items.filter(ti => ti.item.id !== toggleableItem.item.id).forEach(ti => ti.state = ToggleableItemState.NotSelected)
// }
// this.hasBeenToggled = true
// this.toggle.emit(toggleableItem.item)
applyClicked() {
if (this.selectionModel.isDirty()) {
this.dropdown.close()
this.apply.emit(this.selectionModel.diff())
}
}
dropdownOpenChange(open: boolean): void {
// if (open) {
// setTimeout(() => {
// this.listFilterTextInput.nativeElement.focus();
// }, 0)
// this.hasBeenToggled = false
// this.open.next()
// } else {
// this.filterText = ''
// if (this.type == FilterableDropdownType.Editing) this.editingComplete.emit(this.toggleableItems)
// }
if (open) {
setTimeout(() => {
this.listFilterTextInput.nativeElement.focus();
}, 0)
this.selectionModel.reset()
this.open.next()
} else {
this.filterText = ''
}
}
listFilterEnter(): void {