) => {
this.events = result.result;
+ this.selections = new SelectionModel(false, this.events);
this.pageInfo.totalPages = result.pagination.totalPages - 1; // ngx-datatable is 0 based, Kavita is 1 based
this.pageInfo.size = result.pagination.itemsPerPage;
@@ -143,4 +166,55 @@ export class UserScrobbleHistoryComponent implements OnInit {
this.toastr.info(translate('toasts.scrobble-gen-init'))
});
}
+
+ bulkDelete() {
+ if (!this.selections.hasAnySelected()) {
+ return;
+ }
+
+ const eventIds = this.selections.selected().map(e => e.id);
+
+ this.scrobblingService.bulkRemoveEvents(eventIds).subscribe({
+ next: () => {
+ this.events = this.events.filter(e => !eventIds.includes(e.id));
+ this.selectAll = false;
+ this.selections.clearSelected();
+ this.pageInfo.totalElements -= eventIds.length;
+ this.cdRef.markForCheck();
+ },
+ error: err => {
+ console.error(err);
+ }
+ });
+ }
+
+ toggleAll() {
+ this.selectAll = !this.selectAll;
+ this.events.forEach(e => this.selections.toggle(e, this.selectAll));
+ this.cdRef.markForCheck();
+ }
+
+ handleSelection(item: ScrobbleEvent, index: number) {
+ if (this.isShiftDown && this.lastSelectedIndex !== null) {
+ // Bulk select items between the last selected item and the current one
+ const start = Math.min(this.lastSelectedIndex, index);
+ const end = Math.max(this.lastSelectedIndex, index);
+
+ for (let i = start; i <= end; i++) {
+ const event = this.events[i];
+ if (!this.selections.isSelected(event, (e1, e2) => e1.id == e2.id)) {
+ this.selections.toggle(event, true);
+ }
+ }
+ } else {
+ this.selections.toggle(item);
+ }
+
+ this.lastSelectedIndex = index;
+
+
+ const numberOfSelected = this.selections.selected().length;
+ this.selectAll = numberOfSelected === this.events.length;
+ this.cdRef.markForCheck();
+ }
}
diff --git a/UI/Web/src/app/admin/manage-scrobble-errors/manage-scrobble-errors.component.html b/UI/Web/src/app/admin/manage-scrobble-errors/manage-scrobble-errors.component.html
index 78724272c..59a45873e 100644
--- a/UI/Web/src/app/admin/manage-scrobble-errors/manage-scrobble-errors.component.html
+++ b/UI/Web/src/app/admin/manage-scrobble-errors/manage-scrobble-errors.component.html
@@ -8,7 +8,7 @@
-
+
diff --git a/UI/Web/src/app/typeahead/_models/selection-model.ts b/UI/Web/src/app/typeahead/_models/selection-model.ts
index c4b2ab18a..8493a4eed 100644
--- a/UI/Web/src/app/typeahead/_models/selection-model.ts
+++ b/UI/Web/src/app/typeahead/_models/selection-model.ts
@@ -70,6 +70,28 @@ export class SelectionModel {
return (selectedCount !== this._data.length && selectedCount !== 0)
}
+ /**
+ * @return If at least one item is selected
+ */
+ hasAnySelected(): boolean {
+ for (const d of this._data) {
+ if (d.selected) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Marks every data entry has not selected
+ */
+ clearSelected() {
+ this._data = this._data.map(d => {
+ d.selected = false;
+ return d;
+ });
+ }
+
/**
*
* @returns All Selected items
diff --git a/UI/Web/src/assets/langs/en.json b/UI/Web/src/assets/langs/en.json
index b9ab24ae5..44aae59a1 100644
--- a/UI/Web/src/assets/langs/en.json
+++ b/UI/Web/src/assets/langs/en.json
@@ -42,6 +42,8 @@
"series-header": "Series",
"data-header": "Data",
"is-processed-header": "Is Processed",
+ "select-header": "Select all",
+ "delete-selected": "Delete selected",
"no-data": "{{common.no-data}}",
"volume-and-chapter-num": "Volume {{v}} Chapter {{n}}",
"volume-num": "Volume {{num}}",