From c7d76e31412a0b21124d188cfd967dee9967bd01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Mon, 19 Apr 2021 00:18:22 +0300 Subject: [PATCH 1/7] Base listing (filter, search, select) for project listing & overview --- .../project-listing-screen.component.html | 16 +- .../project-listing-screen.component.ts | 107 +++++------- .../project-overview-screen.component.html | 35 ++-- .../project-overview-screen.component.ts | 152 ++++++------------ .../shared/base/base-listing.component.ts | 132 +++++++++++++++ .../src/app/modules/shared/shared.module.ts | 2 + 6 files changed, 239 insertions(+), 205 deletions(-) create mode 100644 apps/red-ui/src/app/modules/shared/base/base-listing.component.ts diff --git a/apps/red-ui/src/app/modules/projects/screens/project-listing-screen/project-listing-screen.component.html b/apps/red-ui/src/app/modules/projects/screens/project-listing-screen/project-listing-screen.component.html index 13b48099b..580753ad9 100644 --- a/apps/red-ui/src/app/modules/projects/screens/project-listing-screen/project-listing-screen.component.html +++ b/apps/red-ui/src/app/modules/projects/screens/project-listing-screen/project-listing-screen.component.html @@ -48,7 +48,7 @@
- {{ 'project-listing.table-header.title' | translate: { length: displayedProjects.length || 0 } }} + {{ 'project-listing.table-header.title' | translate: { length: displayedEntities.length || 0 } }}
@@ -70,22 +70,18 @@
- +
- - diff --git a/apps/red-ui/src/app/modules/projects/screens/project-listing-screen/project-listing-screen.component.ts b/apps/red-ui/src/app/modules/projects/screens/project-listing-screen/project-listing-screen.component.ts index ba7d43e8f..cdeffd5a8 100644 --- a/apps/red-ui/src/app/modules/projects/screens/project-listing-screen/project-listing-screen.component.ts +++ b/apps/red-ui/src/app/modules/projects/screens/project-listing-screen/project-listing-screen.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectorRef, Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { Component, Injector, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { FileManagementControllerService, Project, RuleSetModel } from '@redaction/red-ui-http'; import { AppStateService } from '../../../../state/app-state.service'; import { UserService } from '../../../../services/user.service'; @@ -7,7 +7,6 @@ import { groupBy } from '../../../../utils/functions'; import { FilterModel } from '../../../shared/components/filter/model/filter.model'; import { annotationFilterChecker, - getFilteredEntities, processFilters, projectMemberChecker, projectStatusChecker, @@ -23,21 +22,19 @@ import { TranslateChartService } from '../../../../services/translate-chart.serv import { RedactionFilterSorter } from '../../../../utils/sorters/redaction-filter-sorter'; import { StatusSorter } from '../../../../utils/sorters/status-sorter'; import { Router } from '@angular/router'; -import { FormBuilder, FormGroup } from '@angular/forms'; -import { debounce } from '../../../../utils/debounce'; import { FilterComponent } from '../../../shared/components/filter/filter.component'; import { ProjectsDialogService } from '../../services/projects-dialog.service'; +import { BaseListingComponent } from '../../../shared/base/base-listing.component'; @Component({ selector: 'redaction-project-listing-screen', templateUrl: './project-listing-screen.component.html', styleUrls: ['./project-listing-screen.component.scss'] }) -export class ProjectListingScreenComponent implements OnInit, OnDestroy { +export class ProjectListingScreenComponent extends BaseListingComponent implements OnInit, OnDestroy { public projectsChartData: DoughnutChartConfig[] = []; public documentsChartData: DoughnutChartConfig[] = []; - public searchForm: FormGroup; public actionMenuOpen: boolean; public statusFilters: FilterModel[]; @@ -51,7 +48,6 @@ export class ProjectListingScreenComponent implements OnInit, OnDestroy { statusFilters: [] }; - public displayedProjects: ProjectWrapper[] = []; private projectAutoUpdateTimer: Subscription; @ViewChild('statusFilter') private _statusFilterComponent: FilterComponent; @@ -60,36 +56,33 @@ export class ProjectListingScreenComponent implements OnInit, OnDestroy { @ViewChild('ruleSetFilter') private _ruleSetFilterComponent: FilterComponent; constructor( - public readonly appStateService: AppStateService, + private readonly _appStateService: AppStateService, public readonly userService: UserService, public readonly permissionsService: PermissionsService, - private readonly _changeDetectorRef: ChangeDetectorRef, private readonly _dialogService: ProjectsDialogService, private readonly _translateService: TranslateService, private readonly _router: Router, public readonly sortingService: SortingService, public readonly translateChartService: TranslateChartService, - private readonly _formBuilder: FormBuilder, - private readonly _fileManagementControllerService: FileManagementControllerService + private readonly _fileManagementControllerService: FileManagementControllerService, + protected readonly _injector: Injector ) { - this.appStateService.reset(); - this.searchForm = this._formBuilder.group({ - query: [''] - }); - - this.searchForm.valueChanges.subscribe((value) => this._executeSearch(value)); + super(_injector); + this._appStateService.reset(); + this._loadEntitiesFromState(); } public ngOnInit(): void { this.projectAutoUpdateTimer = timer(0, 10000) .pipe( tap(async () => { - await this.appStateService.loadAllProjects(); + await this._appStateService.loadAllProjects(); + this._loadEntitiesFromState(); }) ) .subscribe(); this._calculateData(); - this.appStateService.fileChanged.subscribe(() => { + this._appStateService.fileChanged.subscribe(() => { this._calculateData(); }); } @@ -98,37 +91,30 @@ export class ProjectListingScreenComponent implements OnInit, OnDestroy { this.projectAutoUpdateTimer.unsubscribe(); } - public get hasActiveFilters() { - return ( - this._statusFilterComponent?.hasActiveFilters || - this._peopleFilterComponent?.hasActiveFilters || - this._needsWorkFilterComponent?.hasActiveFilters || - this._ruleSetFilterComponent?.hasActiveFilters || - this.searchForm.get('query').value - ); + private _loadEntitiesFromState() { + this.allEntities = this._appStateService.allProjects; + } + + protected get _searchKey() { + return 'name'; } public get noData() { - return this.appStateService.allProjects?.length === 0; + return this.allEntities.length === 0; } - public resetFilters() { - this._statusFilterComponent.deactivateAllFilters(); - this._peopleFilterComponent.deactivateAllFilters(); - this._needsWorkFilterComponent.deactivateAllFilters(); - this._ruleSetFilterComponent.deactivateAllFilters(); - this.filtersChanged(); - this.searchForm.reset({ query: '' }); + protected get filterComponents(): FilterComponent[] { + return [this._statusFilterComponent, this._peopleFilterComponent, this._needsWorkFilterComponent, this._ruleSetFilterComponent]; } private _calculateData() { this._computeAllFilters(); - this._filterProjects(); + this._filterEntities(); this.projectsChartData = [ - { value: this.activeProjects, color: 'ACTIVE', label: 'active' }, - { value: this.inactiveProjects, color: 'DELETED', label: 'archived' } + { value: this.activeProjectsCount, color: 'ACTIVE', label: 'active' }, + { value: this.inactiveProjectsCount, color: 'DELETED', label: 'archived' } ]; - const groups = groupBy(this.appStateService.aggregatedFiles, 'status'); + const groups = groupBy(this._appStateService.aggregatedFiles, 'status'); this.documentsChartData = []; for (const key of Object.keys(groups)) { this.documentsChartData.push({ @@ -150,12 +136,12 @@ export class ProjectListingScreenComponent implements OnInit, OnDestroy { return this.sortingService.getSortingOption('project-listing'); } - public get activeProjects() { - return this.appStateService.allProjects.reduce((i, p) => i + (p.project.status === Project.StatusEnum.ACTIVE ? 1 : 0), 0); + public get activeProjectsCount() { + return this.allEntities.filter((p) => p.project.status === Project.StatusEnum.ACTIVE).length; } - public get inactiveProjects() { - return this.appStateService.allProjects.length - this.activeProjects; + public get inactiveProjectsCount() { + return this.allEntities.length - this.activeProjectsCount; } public documentCount(project: ProjectWrapper) { @@ -171,7 +157,7 @@ export class ProjectListingScreenComponent implements OnInit, OnDestroy { } public getRuleSet(pw: ProjectWrapper): RuleSetModel { - return this.appStateService.getRuleSetById(pw.project.ruleSetId); + return this._appStateService.getRuleSetById(pw.project.ruleSetId); } public openAddProjectDialog(): void { @@ -193,7 +179,7 @@ export class ProjectListingScreenComponent implements OnInit, OnDestroy { const allDistinctPeople = new Set(); const allDistinctNeedsWork = new Set(); const allDistinctRuleSets = new Set(); - this.appStateService.allProjects.forEach((entry) => { + this.allEntities.forEach((entry) => { // all people entry.project.memberIds.forEach((memberId) => allDistinctPeople.add(memberId)); // file statuses @@ -248,25 +234,14 @@ export class ProjectListingScreenComponent implements OnInit, OnDestroy { allDistinctRuleSets.forEach((ruleSetId) => { ruleSetFilters.push({ key: ruleSetId, - label: this.appStateService.getRuleSetById(ruleSetId).name + label: this._appStateService.getRuleSetById(ruleSetId).name }); }); this.ruleSetFilters = processFilters(this.ruleSetFilters, ruleSetFilters); } - filtersChanged(filters?: { [key: string]: FilterModel[] }): void { - if (filters) { - for (const key of Object.keys(filters)) { - for (let idx = 0; idx < this[key].length; ++idx) { - this[key][idx] = filters[key][idx]; - } - } - } - this._filterProjects(); - } - - private get _filteredProjects(): ProjectWrapper[] { - const filters = [ + protected get filters(): { values: FilterModel[]; checker: Function; matchAll?: boolean; checkerArgs?: any }[] { + return [ { values: this.statusFilters, checker: projectStatusChecker }, { values: this.peopleFilters, checker: projectMemberChecker }, { @@ -277,25 +252,15 @@ export class ProjectListingScreenComponent implements OnInit, OnDestroy { }, { values: this.ruleSetFilters, checker: ruleSetChecker } ]; - return getFilteredEntities(this.appStateService.allProjects, filters); } - private _filterProjects() { + protected preFilter() { this.detailsContainerFilters = { statusFilters: this.statusFilters.map((f) => ({ ...f })) }; - this.displayedProjects = this._filteredProjects.filter((project) => - project.name.toLowerCase().includes(this.searchForm.get('query').value.toLowerCase()) - ); - this._changeDetectorRef.detectChanges(); } - @debounce(200) - private _executeSearch(value: { query: string }) { - this.displayedProjects = this._filteredProjects.filter((project) => project.name.toLowerCase().includes(value.query.toLowerCase())); - } - - actionPerformed(pw: ProjectWrapper) { + public actionPerformed(pw: ProjectWrapper) { this._calculateData(); } } diff --git a/apps/red-ui/src/app/modules/projects/screens/project-overview-screen/project-overview-screen.component.html b/apps/red-ui/src/app/modules/projects/screens/project-overview-screen/project-overview-screen.component.html index c4f6ba38c..f8c08808d 100644 --- a/apps/red-ui/src/app/modules/projects/screens/project-overview-screen/project-overview-screen.component.html +++ b/apps/red-ui/src/app/modules/projects/screens/project-overview-screen/project-overview-screen.component.html @@ -1,4 +1,4 @@ -
+
+ + diff --git a/apps/red-ui/src/app/modules/admin/screens/dictionary-listing/dictionary-listing-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/dictionary-listing/dictionary-listing-screen.component.ts index cf54c6347..04b7fdb21 100644 --- a/apps/red-ui/src/app/modules/admin/screens/dictionary-listing/dictionary-listing-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/dictionary-listing/dictionary-listing-screen.component.ts @@ -1,71 +1,58 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, Injector, OnInit } from '@angular/core'; import { DoughnutChartConfig } from '../../../shared/components/simple-doughnut-chart/simple-doughnut-chart.component'; import { DictionaryControllerService, TypeValue } from '@redaction/red-ui-http'; -import { SortingOption, SortingService } from '../../../../services/sorting.service'; import { AppStateService } from '../../../../state/app-state.service'; import { tap } from 'rxjs/operators'; import { forkJoin } from 'rxjs'; import { PermissionsService } from '../../../../services/permissions.service'; -import { FormBuilder, FormGroup } from '@angular/forms'; -import { debounce } from '../../../../utils/debounce'; import { ActivatedRoute } from '@angular/router'; import { AdminDialogService } from '../../services/admin-dialog.service'; +import { BaseListingComponent } from '../../../shared/base/base-listing.component'; @Component({ selector: 'redaction-dictionary-listing-screen', templateUrl: './dictionary-listing-screen.component.html', styleUrls: ['./dictionary-listing-screen.component.scss'] }) -export class DictionaryListingScreenComponent implements OnInit { +export class DictionaryListingScreenComponent extends BaseListingComponent implements OnInit { + protected readonly _searchKey = 'label'; + protected readonly _selectionKey = 'type'; + protected readonly _sortKey = 'dictionary-listing'; + + public viewReady = false; public chartData: DoughnutChartConfig[] = []; - public dictionaries: TypeValue[]; - public displayedDictionaries: TypeValue[]; - public selectedDictKeys: string[] = []; - public searchForm: FormGroup; + public allEntities: TypeValue[]; + public displayedEntities: TypeValue[]; constructor( private readonly _dialogService: AdminDialogService, - private readonly _sortingService: SortingService, - private readonly _formBuilder: FormBuilder, private readonly _dictionaryControllerService: DictionaryControllerService, private readonly _activatedRoute: ActivatedRoute, private readonly _appStateService: AppStateService, - public readonly permissionsService: PermissionsService + public readonly permissionsService: PermissionsService, + protected readonly _injector: Injector ) { + super(_injector); this._appStateService.activateRuleSet(_activatedRoute.snapshot.params.ruleSetId); - - this.searchForm = this._formBuilder.group({ - query: [''] - }); - - this.searchForm.valueChanges.subscribe((value) => this._executeSearch(value)); } ngOnInit(): void { this._loadDictionaryData(); - this._calculateData(); - } - - @debounce(200) - private _executeSearch(value: { query: string }) { - this.displayedDictionaries = this.dictionaries.filter((dict) => dict.label.toLowerCase().includes(value.query.toLowerCase())); } private _loadDictionaryData() { const appStateDictionaryData = this._appStateService.dictionaryData[this._appStateService.activeRuleSetId]; - this.dictionaries = Object.keys(appStateDictionaryData) + this.allEntities = Object.keys(appStateDictionaryData) .map((key) => appStateDictionaryData[key]) .filter((d) => !d.virtual || d.type === 'false_positive'); - this.displayedDictionaries = [...this.dictionaries]; - const dataObs = []; - this.dictionaries.forEach((item) => { - const observable = this._dictionaryControllerService.getDictionaryForType(item.type, this._appStateService.activeRuleSetId).pipe( + this.displayedEntities = [...this.allEntities]; + const dataObs = this.allEntities.map((dict) => + this._dictionaryControllerService.getDictionaryForType(dict.type, this._appStateService.activeRuleSetId).pipe( tap((values) => { - item.entries = values.entries ? values.entries : []; + dict.entries = values.entries ? values.entries : []; }) - ); - dataObs.push(observable); - }); + ) + ); forkJoin(dataObs).subscribe(() => { this._calculateData(); }); @@ -73,7 +60,7 @@ export class DictionaryListingScreenComponent implements OnInit { private _calculateData() { this.chartData = []; - for (const dict of this.dictionaries) { + for (const dict of this.allEntities) { this.chartData.push({ value: dict.entries ? dict.entries.length : 0, color: dict.hexColor, @@ -82,47 +69,11 @@ export class DictionaryListingScreenComponent implements OnInit { }); } this.chartData.sort((a, b) => (a.label < b.label ? -1 : 1)); + this.viewReady = true; } - public get sortingOption(): SortingOption { - return this._sortingService.getSortingOption('dictionary-listing'); - } - - public toggleSort($event) { - this._sortingService.toggleSort('dictionary-listing', $event); - } - - toggleDictSelected($event: MouseEvent, dict: TypeValue) { - $event.stopPropagation(); - const idx = this.selectedDictKeys.indexOf(dict.type); - if (idx === -1) { - this.selectedDictKeys.push(dict.type); - } else { - this.selectedDictKeys.splice(idx, 1); - } - } - - public toggleSelectAll() { - if (this.areSomeDictsSelected) { - this.selectedDictKeys = []; - } else { - this.selectedDictKeys = this.displayedDictionaries.map((dict) => dict.type); - } - } - - public get areAllDictsSelected() { - return this.displayedDictionaries.length !== 0 && this.selectedDictKeys.length === this.displayedDictionaries.length; - } - - public get areSomeDictsSelected() { - return this.selectedDictKeys.length > 0; - } - - public isDictSelected(dict: TypeValue) { - return this.selectedDictKeys.indexOf(dict.type) !== -1; - } - - openAddEditDictionaryDialog(dict?: TypeValue) { + openAddEditDictionaryDialog($event?: MouseEvent, dict?: TypeValue) { + $event?.stopPropagation(); this._dialogService.openAddEditDictionaryDialog(dict, this._appStateService.activeRuleSetId, async (newDictionary) => { if (newDictionary) { await this._appStateService.loadDictionaryData(); @@ -131,11 +82,6 @@ export class DictionaryListingScreenComponent implements OnInit { }); } - openEditDictionaryDialog($event: any, dict: TypeValue) { - $event.stopPropagation(); - this.openAddEditDictionaryDialog(dict); - } - openDeleteDictionaryDialog($event: any, dict: TypeValue) { this._dialogService.openDeleteDictionaryDialog($event, dict, this._appStateService.activeRuleSetId, async () => { await this._appStateService.loadDictionaryData(); From 6783c04c97650d24255edde2699dd62de00a1d58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Mon, 19 Apr 2021 22:18:12 +0300 Subject: [PATCH 4/7] Refactored default colors screen --- .../default-colors-screen.component.ts | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/apps/red-ui/src/app/modules/admin/screens/default-colors/default-colors-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/default-colors/default-colors-screen.component.ts index e4f1f461e..b645e21d1 100644 --- a/apps/red-ui/src/app/modules/admin/screens/default-colors/default-colors-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/default-colors/default-colors-screen.component.ts @@ -1,17 +1,19 @@ -import { Component } from '@angular/core'; +import { Component, Injector } from '@angular/core'; import { AppStateService } from '../../../../state/app-state.service'; import { Colors, DictionaryControllerService } from '@redaction/red-ui-http'; import { ActivatedRoute } from '@angular/router'; -import { SortingOption, SortingService } from '../../../../services/sorting.service'; import { PermissionsService } from '../../../../services/permissions.service'; import { AdminDialogService } from '../../services/admin-dialog.service'; +import { BaseListingComponent } from '../../../shared/base/base-listing.component'; @Component({ selector: 'redaction-default-colors-screen', templateUrl: './default-colors-screen.component.html', styleUrls: ['./default-colors-screen.component.scss'] }) -export class DefaultColorsScreenComponent { +export class DefaultColorsScreenComponent extends BaseListingComponent { + protected readonly _sortKey = 'default-colors'; + public viewReady = false; private _colorsObj: Colors; public colors: { key: string; value: string }[] = []; @@ -20,22 +22,15 @@ export class DefaultColorsScreenComponent { private readonly _appStateService: AppStateService, private readonly _activatedRoute: ActivatedRoute, private readonly _dictionaryControllerService: DictionaryControllerService, - private readonly _sortingService: SortingService, private readonly _dialogService: AdminDialogService, - public readonly permissionsService: PermissionsService + public readonly permissionsService: PermissionsService, + protected readonly _injector: Injector ) { + super(_injector); this._appStateService.activateRuleSet(_activatedRoute.snapshot.params.ruleSetId); this._loadColors(); } - public get sortingOption(): SortingOption { - return this._sortingService.getSortingOption('default-colors'); - } - - public toggleSort($event) { - this._sortingService.toggleSort('default-colors', $event); - } - public async loadRuleSetsData(): Promise { await this._appStateService.loadAllRuleSets(); } From 271e8b6501cb20a98768a414ef661b1cee591457 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Mon, 19 Apr 2021 22:37:22 +0300 Subject: [PATCH 5/7] Refactored file attributes listing --- ...e-attributes-listing-screen.component.html | 33 ++++---- ...ile-attributes-listing-screen.component.ts | 82 +++---------------- .../src/app/services/sorting.service.ts | 2 +- 3 files changed, 28 insertions(+), 89 deletions(-) diff --git a/apps/red-ui/src/app/modules/admin/screens/file-attributes-listing/file-attributes-listing-screen.component.html b/apps/red-ui/src/app/modules/admin/screens/file-attributes-listing/file-attributes-listing-screen.component.html index 1b9582206..003f33814 100644 --- a/apps/red-ui/src/app/modules/admin/screens/file-attributes-listing/file-attributes-listing-screen.component.html +++ b/apps/red-ui/src/app/modules/admin/screens/file-attributes-listing/file-attributes-listing-screen.component.html @@ -15,18 +15,13 @@
+ - - {{ 'file-attributes-listing.table-header.title' | translate: { length: displayedAttributes.length } }} + {{ 'file-attributes-listing.table-header.title' | translate: { length: displayedEntities.length } }}
-
+
@@ -89,20 +84,20 @@
- + -
-
-
- +
+
+
+
diff --git a/apps/red-ui/src/app/modules/admin/screens/file-attributes-listing/file-attributes-listing-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/file-attributes-listing/file-attributes-listing-screen.component.ts index 7c5ffafeb..e6da31163 100644 --- a/apps/red-ui/src/app/modules/admin/screens/file-attributes-listing/file-attributes-listing-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/file-attributes-listing/file-attributes-listing-screen.component.ts @@ -1,23 +1,22 @@ -import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'; +import { Component, ElementRef, Injector, OnInit, ViewChild } from '@angular/core'; import { PermissionsService } from '../../../../services/permissions.service'; -import { FormBuilder, FormGroup } from '@angular/forms'; import { FileAttributeConfig, FileAttributesControllerService } from '@redaction/red-ui-http'; import { AppStateService } from '../../../../state/app-state.service'; import { ActivatedRoute } from '@angular/router'; -import { debounce } from '../../../../utils/debounce'; -import { SortingOption, SortingService } from '../../../../services/sorting.service'; import { AdminDialogService } from '../../services/admin-dialog.service'; +import { BaseListingComponent } from '../../../shared/base/base-listing.component'; @Component({ selector: 'redaction-file-attributes-listing-screen', templateUrl: './file-attributes-listing-screen.component.html', styleUrls: ['./file-attributes-listing-screen.component.scss'] }) -export class FileAttributesListingScreenComponent implements OnInit { - public searchForm: FormGroup; - public attributes: FileAttributeConfig[] = []; - public displayedAttributes: FileAttributeConfig[] = []; - public selectedFileAttributeIds: string[] = []; +export class FileAttributesListingScreenComponent extends BaseListingComponent implements OnInit { + protected readonly _searchKey = 'label'; + protected readonly _selectionKey = 'id'; + protected readonly _sortKey = 'file-attributes-listing'; + + public allEntities: FileAttributeConfig[] = []; public viewReady = false; public loading = false; @@ -25,20 +24,14 @@ export class FileAttributesListingScreenComponent implements OnInit { constructor( public readonly permissionsService: PermissionsService, - public readonly _sortingService: SortingService, - private readonly _formBuilder: FormBuilder, private readonly _fileAttributesService: FileAttributesControllerService, private readonly _appStateService: AppStateService, private readonly _activatedRoute: ActivatedRoute, - private readonly _dialogService: AdminDialogService + private readonly _dialogService: AdminDialogService, + protected readonly _injector: Injector ) { + super(_injector); this._appStateService.activateRuleSet(_activatedRoute.snapshot.params.ruleSetId); - - this.searchForm = this._formBuilder.group({ - query: [''] - }); - - this.searchForm.valueChanges.subscribe((value) => this._executeSearch(value)); } async ngOnInit() { @@ -48,34 +41,15 @@ export class FileAttributesListingScreenComponent implements OnInit { private async _loadData() { try { const response = await this._fileAttributesService.getFileAttributesConfiguration(this._appStateService.activeRuleSetId).toPromise(); - this.attributes = response?.fileAttributeConfigs || []; + this.allEntities = response?.fileAttributeConfigs || []; } catch (e) { } finally { - // Remove potentially deleted items - this.selectedFileAttributeIds = this.selectedFileAttributeIds.filter((id) => !!this.attributes.find((attr) => attr.id === id)); - this._executeSearch(); this.viewReady = true; this.loading = false; } } - public get sortingOption(): SortingOption { - return this._sortingService.getSortingOption('file-attributes-listing'); - } - - public toggleSort($event) { - this._sortingService.toggleSort('file-attributes-listing', $event); - } - - @debounce(200) - private _executeSearch(value?: { query: string }) { - if (!value) { - value = { query: this.searchForm.get('query').value }; - } - this.displayedAttributes = this.attributes.filter((attribute) => attribute.label.toLowerCase().includes(value.query.toLowerCase())); - } - public openAddEditAttributeDialog($event: MouseEvent, fileAttribute?: FileAttributeConfig) { $event.stopPropagation(); this._dialogService.openAddEditFileAttributeDialog(fileAttribute, this._appStateService.activeRuleSetId, async (newValue: FileAttributeConfig) => { @@ -92,42 +66,12 @@ export class FileAttributesListingScreenComponent implements OnInit { if (!!fileAttribute) { await this._fileAttributesService.deleteFileAttribute(this._appStateService.activeRuleSetId, fileAttribute.id).toPromise(); } else { - await this._fileAttributesService.deleteFileAttributes(this.selectedFileAttributeIds, this._appStateService.activeRuleSetId).toPromise(); + await this._fileAttributesService.deleteFileAttributes(this.selectedEntitiesIds, this._appStateService.activeRuleSetId).toPromise(); } await this._loadData(); }); } - public toggleAttributeSelected($event: MouseEvent, attribute: FileAttributeConfig) { - $event.stopPropagation(); - const idx = this.selectedFileAttributeIds.indexOf(attribute.id); - if (idx === -1) { - this.selectedFileAttributeIds.push(attribute.id); - } else { - this.selectedFileAttributeIds.splice(idx, 1); - } - } - - public toggleSelectAll() { - if (this.areSomeAttributesSelected) { - this.selectedFileAttributeIds = []; - } else { - this.selectedFileAttributeIds = this.displayedAttributes.map((a) => a.id); - } - } - - public get areAllAttributesSelected() { - return this.displayedAttributes.length !== 0 && this.selectedFileAttributeIds.length === this.displayedAttributes.length; - } - - public get areSomeAttributesSelected() { - return this.selectedFileAttributeIds.length > 0; - } - - public isAttributeSelected(attribute: FileAttributeConfig) { - return this.selectedFileAttributeIds.indexOf(attribute.id) !== -1; - } - public importCSV(files: FileList | File[]) { const csvFile = files[0]; this._fileInput.nativeElement.value = null; diff --git a/apps/red-ui/src/app/services/sorting.service.ts b/apps/red-ui/src/app/services/sorting.service.ts index 58eeb969e..66ab9fb99 100644 --- a/apps/red-ui/src/app/services/sorting.service.ts +++ b/apps/red-ui/src/app/services/sorting.service.ts @@ -17,7 +17,7 @@ export class SortingService { 'dictionary-listing': { column: 'label', order: 'asc' }, 'rule-sets-listing': { column: 'name', order: 'asc' }, 'default-colors': { column: 'key', order: 'asc' }, - 'file-attributes-listing': { column: 'name', order: 'asc' } + 'file-attributes-listing': { column: 'label', order: 'asc' } }; constructor() {} From f3dca378e2e5822b82271c56b16f23c8708af5d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Mon, 19 Apr 2021 22:59:49 +0300 Subject: [PATCH 6/7] Refactor user listing --- .../default-colors-screen.component.html | 4 +- .../default-colors-screen.component.ts | 5 +- .../dictionary-listing-screen.component.ts | 4 +- ...ile-attributes-listing-screen.component.ts | 1 - .../rule-sets-listing-screen.component.ts | 3 +- .../user-listing-screen.component.html | 22 +++--- .../user-listing-screen.component.ts | 77 +++++-------------- .../shared/base/base-listing.component.ts | 10 ++- 8 files changed, 45 insertions(+), 81 deletions(-) diff --git a/apps/red-ui/src/app/modules/admin/screens/default-colors/default-colors-screen.component.html b/apps/red-ui/src/app/modules/admin/screens/default-colors/default-colors-screen.component.html index 95b6c0fa5..a91352feb 100644 --- a/apps/red-ui/src/app/modules/admin/screens/default-colors/default-colors-screen.component.html +++ b/apps/red-ui/src/app/modules/admin/screens/default-colors/default-colors-screen.component.html @@ -15,7 +15,7 @@
- {{ 'default-colors-screen.table-header.title' | translate: { length: colors.length } }} + {{ 'default-colors-screen.table-header.title' | translate: { length: allEntities.length } }}
@@ -36,7 +36,7 @@ -
+
diff --git a/apps/red-ui/src/app/modules/admin/screens/default-colors/default-colors-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/default-colors/default-colors-screen.component.ts index b645e21d1..b34fa75cf 100644 --- a/apps/red-ui/src/app/modules/admin/screens/default-colors/default-colors-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/default-colors/default-colors-screen.component.ts @@ -11,12 +11,11 @@ import { BaseListingComponent } from '../../../shared/base/base-listing.componen templateUrl: './default-colors-screen.component.html', styleUrls: ['./default-colors-screen.component.scss'] }) -export class DefaultColorsScreenComponent extends BaseListingComponent { +export class DefaultColorsScreenComponent extends BaseListingComponent<{ key: string; value: string }> { protected readonly _sortKey = 'default-colors'; public viewReady = false; private _colorsObj: Colors; - public colors: { key: string; value: string }[] = []; constructor( private readonly _appStateService: AppStateService, @@ -41,7 +40,7 @@ export class DefaultColorsScreenComponent extends BaseListingComponent { .toPromise() .then((data) => { this._colorsObj = data; - this.colors = Object.keys(data).map((key) => ({ + this.allEntities = Object.keys(data).map((key) => ({ key, value: data[key] })); diff --git a/apps/red-ui/src/app/modules/admin/screens/dictionary-listing/dictionary-listing-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/dictionary-listing/dictionary-listing-screen.component.ts index 04b7fdb21..71bdcc55b 100644 --- a/apps/red-ui/src/app/modules/admin/screens/dictionary-listing/dictionary-listing-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/dictionary-listing/dictionary-listing-screen.component.ts @@ -14,15 +14,13 @@ import { BaseListingComponent } from '../../../shared/base/base-listing.componen templateUrl: './dictionary-listing-screen.component.html', styleUrls: ['./dictionary-listing-screen.component.scss'] }) -export class DictionaryListingScreenComponent extends BaseListingComponent implements OnInit { +export class DictionaryListingScreenComponent extends BaseListingComponent implements OnInit { protected readonly _searchKey = 'label'; protected readonly _selectionKey = 'type'; protected readonly _sortKey = 'dictionary-listing'; public viewReady = false; public chartData: DoughnutChartConfig[] = []; - public allEntities: TypeValue[]; - public displayedEntities: TypeValue[]; constructor( private readonly _dialogService: AdminDialogService, diff --git a/apps/red-ui/src/app/modules/admin/screens/file-attributes-listing/file-attributes-listing-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/file-attributes-listing/file-attributes-listing-screen.component.ts index e6da31163..7fe68b944 100644 --- a/apps/red-ui/src/app/modules/admin/screens/file-attributes-listing/file-attributes-listing-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/file-attributes-listing/file-attributes-listing-screen.component.ts @@ -16,7 +16,6 @@ export class FileAttributesListingScreenComponent extends BaseListingComponent implements OnInit { protected readonly _searchKey = 'name'; protected readonly _selectionKey = 'ruleSetId'; protected readonly _sortKey = 'rule-sets-listing'; diff --git a/apps/red-ui/src/app/modules/admin/screens/user-listing/user-listing-screen.component.html b/apps/red-ui/src/app/modules/admin/screens/user-listing/user-listing-screen.component.html index bd27526a2..a3e32833a 100644 --- a/apps/red-ui/src/app/modules/admin/screens/user-listing/user-listing-screen.component.html +++ b/apps/red-ui/src/app/modules/admin/screens/user-listing/user-listing-screen.component.html @@ -28,23 +28,23 @@
- +
- {{ 'user-listing.table-header.title' | translate: { length: displayedUsers.length } }} + {{ 'user-listing.table-header.title' | translate: { length: displayedEntities.length } }} - +
- + -
-
-
- +
+
+
+
diff --git a/apps/red-ui/src/app/modules/admin/screens/user-listing/user-listing-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/user-listing/user-listing-screen.component.ts index 8c8253bb2..0af279818 100644 --- a/apps/red-ui/src/app/modules/admin/screens/user-listing/user-listing-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/user-listing/user-listing-screen.component.ts @@ -1,53 +1,44 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, Injector, OnInit } from '@angular/core'; import { PermissionsService } from '../../../../services/permissions.service'; import { UserService } from '../../../../services/user.service'; import { User, UserControllerService } from '@redaction/red-ui-http'; -import { FormBuilder, FormGroup } from '@angular/forms'; -import { debounce } from '../../../../utils/debounce'; import { AdminDialogService } from '../../services/admin-dialog.service'; import { TranslateService } from '@ngx-translate/core'; import { DoughnutChartConfig } from '../../../shared/components/simple-doughnut-chart/simple-doughnut-chart.component'; import { TranslateChartService } from '../../../../services/translate-chart.service'; +import { BaseListingComponent } from '../../../shared/base/base-listing.component'; @Component({ selector: 'redaction-user-listing-screen', templateUrl: './user-listing-screen.component.html', styleUrls: ['./user-listing-screen.component.scss'] }) -export class UserListingScreenComponent implements OnInit { +export class UserListingScreenComponent extends BaseListingComponent implements OnInit { + protected readonly _selectionKey = 'userId'; + public viewReady = false; public loading = false; public collapsedDetails = false; public chartData: DoughnutChartConfig[] = []; - public users: User[]; - public displayedUsers: User[] = []; - public searchForm: FormGroup; - public selectedUsersIds: string[] = []; constructor( public readonly permissionsService: PermissionsService, public readonly userService: UserService, - private readonly _formBuilder: FormBuilder, private readonly _translateService: TranslateService, private readonly _adminDialogService: AdminDialogService, private readonly _userControllerService: UserControllerService, - private readonly _translateChartService: TranslateChartService + private readonly _translateChartService: TranslateChartService, + protected readonly _injector: Injector ) { - this.searchForm = this._formBuilder.group({ - query: [''] - }); - - this.searchForm.valueChanges.subscribe(() => this._executeSearch()); + super(_injector); } public async ngOnInit() { await this._loadData(); } - @debounce(200) - private _executeSearch() { - const value = this.searchForm.get('query').value; - this.displayedUsers = this.users.filter((user) => this.userService.getName(user).toLowerCase().includes(value.toLowerCase())); + protected _searchField(user: any): string { + return this.userService.getName(user); } public openAddEditUserDialog($event: MouseEvent, user?: User) { @@ -77,7 +68,7 @@ export class UserListingScreenComponent implements OnInit { } private async _loadData() { - this.users = (await this._userControllerService.getAllUsers({ requestId: new Date().toISOString() }).toPromise()).users; + this.allEntities = (await this._userControllerService.getAllUsers({ requestId: new Date().toISOString() }).toPromise()).users; this._executeSearch(); this._computeStats(); this.viewReady = true; @@ -88,32 +79,32 @@ export class UserListingScreenComponent implements OnInit { this.chartData = this._translateChartService.translateRoles( [ { - value: this.users.filter((user) => !this.userService.isActive(user)).length, + value: this.allEntities.filter((user) => !this.userService.isActive(user)).length, color: 'INACTIVE', label: 'INACTIVE' }, { - value: this.users.filter((user) => user.roles.length === 1 && user.roles[0] === 'RED_USER').length, + value: this.allEntities.filter((user) => user.roles.length === 1 && user.roles[0] === 'RED_USER').length, color: 'REGULAR', label: 'REGULAR' }, { - value: this.users.filter((user) => this.userService.isManager(user) && !this.userService.isAdmin(user)).length, + value: this.allEntities.filter((user) => this.userService.isManager(user) && !this.userService.isAdmin(user)).length, color: 'MANAGER', label: 'RED_MANAGER' }, { - value: this.users.filter((user) => this.userService.isManager(user) && this.userService.isAdmin(user)).length, + value: this.allEntities.filter((user) => this.userService.isManager(user) && this.userService.isAdmin(user)).length, color: 'MANAGER_ADMIN', label: 'MANAGER_ADMIN' }, { - value: this.users.filter((user) => this.userService.isUserAdmin(user) && !this.userService.isAdmin(user)).length, + value: this.allEntities.filter((user) => this.userService.isUserAdmin(user) && !this.userService.isAdmin(user)).length, color: 'USER_ADMIN', label: 'RED_USER_ADMIN' }, { - value: this.users.filter((user) => this.userService.isAdmin(user) && !this.userService.isManager(user)).length, + value: this.allEntities.filter((user) => this.userService.isAdmin(user) && !this.userService.isManager(user)).length, color: 'ADMIN', label: 'RED_ADMIN' } @@ -136,41 +127,11 @@ export class UserListingScreenComponent implements OnInit { this.collapsedDetails = !this.collapsedDetails; } - toggleUserSelected($event: MouseEvent, user: User) { - $event.stopPropagation(); - const idx = this.selectedUsersIds.indexOf(user.userId); - if (idx === -1) { - this.selectedUsersIds.push(user.userId); - } else { - this.selectedUsersIds.splice(idx, 1); - } - } - - public toggleSelectAll() { - if (this.areSomeUsersSelected) { - this.selectedUsersIds = []; - } else { - this.selectedUsersIds = this.displayedUsers.map((user) => user.userId); - } - } - - public get areAllUsersSelected() { - return this.displayedUsers.length !== 0 && this.selectedUsersIds.length === this.displayedUsers.length; - } - - public get areSomeUsersSelected() { - return this.selectedUsersIds.length > 0; - } - - public isUserSelected(user: User) { - return this.selectedUsersIds.indexOf(user.userId) !== -1; - } - public async bulkDelete() { - this.openDeleteUserDialog(this.users.filter((u) => this.isUserSelected(u))); + this.openDeleteUserDialog(this.allEntities.filter((u) => this.isEntitySelected(u))); } public get canDeleteSelected(): boolean { - return this.selectedUsersIds.indexOf(this.userService.userId) === -1; + return this.selectedEntitiesIds.indexOf(this.userService.userId) === -1; } } diff --git a/apps/red-ui/src/app/modules/shared/base/base-listing.component.ts b/apps/red-ui/src/app/modules/shared/base/base-listing.component.ts index 631c2e268..e42e89b05 100644 --- a/apps/red-ui/src/app/modules/shared/base/base-listing.component.ts +++ b/apps/red-ui/src/app/modules/shared/base/base-listing.component.ts @@ -39,6 +39,10 @@ export class BaseListingComponent { protected get filterComponents(): FilterComponent[] { return []; } + + protected _searchField(entity: T): string { + return entity[this.searchKey]; + } // ---- constructor(protected readonly _injector: Injector) { @@ -82,9 +86,11 @@ export class BaseListingComponent { @debounce(200) protected _executeSearch() { this.displayedEntities = (this.filters.length ? this.filteredEntities : this.allEntities).filter((entity) => - entity[this.searchKey].toLowerCase().includes(this.searchForm.get('query').value.toLowerCase()) + this._searchField(entity).toLowerCase().includes(this.searchForm.get('query').value.toLowerCase()) ); - this.selectedEntitiesIds = this.displayedEntities.map((entity) => entity[this.selectionKey]).filter((id) => this.selectedEntitiesIds.includes(id)); + if (this._selectionKey) { + this.selectedEntitiesIds = this.displayedEntities.map((entity) => entity[this.selectionKey]).filter((id) => this.selectedEntitiesIds.includes(id)); + } } // Filter From dfb3787d42555add7ad8ea37992606ce89ee7296 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Tue, 20 Apr 2021 00:58:41 +0300 Subject: [PATCH 7/7] Refactor file attributes CSV import --- .../src/app/modules/admin/admin.module.ts | 8 +- .../active-fields-listing.component.html | 175 ++++++++++++++++ .../active-fields-listing.component.scss | 84 ++++++++ .../active-fields-listing.component.ts | 40 ++++ ...ttributes-csv-import-dialog.component.html | 198 +----------------- ...ttributes-csv-import-dialog.component.scss | 84 +------- ...-attributes-csv-import-dialog.component.ts | 83 ++------ .../dictionary-listing-screen.component.ts | 10 +- .../project-overview-screen.component.html | 2 - .../shared/base/base-listing.component.ts | 4 + 10 files changed, 337 insertions(+), 351 deletions(-) create mode 100644 apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/active-fields-listing/active-fields-listing.component.html create mode 100644 apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/active-fields-listing/active-fields-listing.component.scss create mode 100644 apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/active-fields-listing/active-fields-listing.component.ts diff --git a/apps/red-ui/src/app/modules/admin/admin.module.ts b/apps/red-ui/src/app/modules/admin/admin.module.ts index 2c12acb71..3f31c7f61 100644 --- a/apps/red-ui/src/app/modules/admin/admin.module.ts +++ b/apps/red-ui/src/app/modules/admin/admin.module.ts @@ -32,6 +32,7 @@ import { AddEditUserDialogComponent } from './dialogs/add-edit-user-dialog/add-e import { UsersStatsComponent } from './components/users-stats/users-stats.component'; import { ConfirmDeleteUsersDialogComponent } from './dialogs/confirm-delete-users-dialog/confirm-delete-users-dialog.component'; import { FileAttributesCsvImportDialogComponent } from './dialogs/file-attributes-csv-import-dialog/file-attributes-csv-import-dialog.component'; +import { ActiveFieldsListingComponent } from './dialogs/file-attributes-csv-import-dialog/active-fields-listing/active-fields-listing.component'; const dialogs = [ AddEditRuleSetDialogComponent, @@ -41,7 +42,8 @@ const dialogs = [ EditColorDialogComponent, SmtpAuthDialogComponent, AddEditUserDialogComponent, - ConfirmDeleteUsersDialogComponent + ConfirmDeleteUsersDialogComponent, + FileAttributesCsvImportDialogComponent ]; const screens = [ @@ -66,12 +68,14 @@ const components = [ ComboChartComponent, ComboSeriesVerticalComponent, UsersStatsComponent, + ActiveFieldsListingComponent, + ...dialogs, ...screens ]; @NgModule({ - declarations: [...components, FileAttributesCsvImportDialogComponent], + declarations: [...components], providers: [AdminDialogService], imports: [CommonModule, SharedModule, AdminRoutingModule, AceEditorModule, NgxChartsModule, ColorPickerModule] }) diff --git a/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/active-fields-listing/active-fields-listing.component.html b/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/active-fields-listing/active-fields-listing.component.html new file mode 100644 index 000000000..a33c01a53 --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/active-fields-listing/active-fields-listing.component.html @@ -0,0 +1,175 @@ +
+
+
+ + +
+ + {{ 'file-attributes-csv-import.table-header.title' | translate: { length: allEntities.length } }} + + + + + + + + + + +
+ + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + +
+
+
+ + + + + +
+
+
+ +
+
+
+ {{ field.name }} +
+
+
+ +
+
+ + + + + + + + +
+
+
+ + + + {{ 'file-attributes-csv-import.types.' + type | translate }} + + + +
+
+
+ +
+
+
+
+ + +
+
+
+
+
diff --git a/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/active-fields-listing/active-fields-listing.component.scss b/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/active-fields-listing/active-fields-listing.component.scss new file mode 100644 index 000000000..42b2d5706 --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/active-fields-listing/active-fields-listing.component.scss @@ -0,0 +1,84 @@ +@import '../../../../../../assets/styles/red-variables'; + +redaction-table-col-name::ng-deep { + > div { + padding: 0 13px 0 10px !important; + + &.name { + padding-left: 22px !important; + } + } +} + +.header-item { + padding: 0 24px 0 10px; + box-shadow: none; + border-top: 1px solid $separator; + + .all-caps-label { + margin-right: 10px; + } + + redaction-circle-button { + margin-right: 2px; + } + + .separator { + margin-left: 14px; + background-color: $separator; + width: 1px; + height: 30px; + margin-right: 16px; + } +} + +cdk-virtual-scroll-viewport { + height: calc(100% - 80px); + + ::ng-deep.cdk-virtual-scroll-content-wrapper { + grid-template-columns: 30px minmax(0, 25vw) 150px auto auto auto 11px; + + .table-item { + > div { + height: 50px; + + &:not(.scrollbar-placeholder) { + padding-left: 10px; + + &.center { + align-items: center; + } + } + + &.name { + flex-direction: row; + align-items: center; + justify-content: flex-start; + + &:not(.editing) { + padding-left: 22px; + } + + .edit-name-button { + display: none; + } + + redaction-circle-button:first-of-type { + margin-left: 7px; + margin-right: 2px; + } + } + } + + &:hover .name .edit-name-button { + display: block; + } + } + } + + &.has-scrollbar:hover { + ::ng-deep.cdk-virtual-scroll-content-wrapper { + grid-template-columns: 30px minmax(0, 25vw) 150px auto auto auto; + } + } +} diff --git a/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/active-fields-listing/active-fields-listing.component.ts b/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/active-fields-listing/active-fields-listing.component.ts new file mode 100644 index 000000000..84c01afd9 --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/active-fields-listing/active-fields-listing.component.ts @@ -0,0 +1,40 @@ +import { Component, EventEmitter, Injector, Input, OnChanges, Output, SimpleChanges } from '@angular/core'; +import { BaseListingComponent } from '../../../../shared/base/base-listing.component'; +import { Field } from '../file-attributes-csv-import-dialog.component'; + +@Component({ + selector: 'redaction-active-fields-listing', + templateUrl: './active-fields-listing.component.html', + styleUrls: ['./active-fields-listing.component.scss'] +}) +export class ActiveFieldsListingComponent extends BaseListingComponent implements OnChanges { + @Input() public allEntities: Field[]; + @Output() public allEntitiesChange = new EventEmitter(); + @Output() public setHoveredColumn = new EventEmitter(); + @Output() public toggleFieldActive = new EventEmitter(); + + protected readonly _selectionKey = 'csvColumn'; + + constructor(protected readonly _injector: Injector) { + super(_injector); + } + + ngOnChanges(changes: SimpleChanges): void { + if (changes.allEntities) { + this.displayedEntities = this.allEntities; + this._updateSelection(); + } + } + + public deactivateSelection() { + this.allEntities = [...this.allEntities.filter((field) => !this.isEntitySelected(field))]; + this.allEntitiesChange.emit(this.allEntities); + this.selectedEntitiesIds = []; + } + + public setAttributeForSelection(attribute: string, value: any) { + for (const csvColumn of this.selectedEntitiesIds) { + this.allEntities.find((f) => f.csvColumn === csvColumn)[attribute] = value; + } + } +} diff --git a/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/file-attributes-csv-import-dialog.component.html b/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/file-attributes-csv-import-dialog.component.html index 443b5c7c3..2131fdde6 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/file-attributes-csv-import-dialog.component.html +++ b/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/file-attributes-csv-import-dialog.component.html @@ -90,7 +90,7 @@
-
-
-
- - -
- - {{ 'file-attributes-csv-import.table-header.title' | translate: { length: activeFields.length } }} - - - - - - - - - - -
- - - - - - - - - - - - - - - - -
-
- -
-
- - - - - - - - - -
-
-
- - - - - -
-
-
- -
-
-
- {{ field.name }} -
-
-
- -
-
- - - - - - - - -
-
-
- - - - {{ 'file-attributes-csv-import.types.' + type | translate }} - - - -
-
-
- -
-
-
-
- - -
-
-
-
-
+
diff --git a/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/file-attributes-csv-import-dialog.component.scss b/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/file-attributes-csv-import-dialog.component.scss index fdf45c402..88d6fc0d2 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/file-attributes-csv-import-dialog.component.scss +++ b/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/file-attributes-csv-import-dialog.component.scss @@ -114,6 +114,7 @@ > .left { width: 375px; + min-width: 375px; background: $grey-2; .csv-header-pill-content { @@ -198,88 +199,5 @@ > .content-container { width: 100%; - - redaction-table-col-name::ng-deep { - > div { - padding: 0 13px 0 10px !important; - - &.name { - padding-left: 22px !important; - } - } - } - - .header-item { - padding: 0 24px 0 10px; - box-shadow: none; - border-top: 1px solid $separator; - - .all-caps-label { - margin-right: 10px; - } - - redaction-circle-button { - margin-right: 2px; - } - - .separator { - margin-left: 14px; - background-color: $separator; - width: 1px; - height: 30px; - margin-right: 16px; - } - } - - cdk-virtual-scroll-viewport { - height: calc(100% - 80px); - - ::ng-deep.cdk-virtual-scroll-content-wrapper { - grid-template-columns: 30px minmax(0, 25vw) 150px auto auto auto 11px; - - .table-item { - > div { - height: 50px; - - &:not(.scrollbar-placeholder) { - padding-left: 10px; - - &.center { - align-items: center; - } - } - - &.name { - flex-direction: row; - align-items: center; - justify-content: flex-start; - - &:not(.editing) { - padding-left: 22px; - } - - .edit-name-button { - display: none; - } - - redaction-circle-button:first-of-type { - margin-left: 7px; - margin-right: 2px; - } - } - } - - &:hover .name .edit-name-button { - display: block; - } - } - } - - &.has-scrollbar:hover { - ::ng-deep.cdk-virtual-scroll-content-wrapper { - grid-template-columns: 30px minmax(0, 25vw) 150px auto auto auto; - } - } - } } } diff --git a/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/file-attributes-csv-import-dialog.component.ts b/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/file-attributes-csv-import-dialog.component.ts index 45a7afd1c..64fa985b3 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/file-attributes-csv-import-dialog.component.ts +++ b/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/file-attributes-csv-import-dialog.component.ts @@ -1,13 +1,13 @@ -import { Component, Inject, ViewChild } from '@angular/core'; -import { AbstractControl, FormBuilder, FormGroup, ValidatorFn, Validators } from '@angular/forms'; +import { Component, Inject, Injector, ViewChild } from '@angular/core'; +import { AbstractControl, FormGroup, ValidatorFn, Validators } from '@angular/forms'; import { AppStateService } from '../../../../state/app-state.service'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import * as Papa from 'papaparse'; import { FileAttributesControllerService } from '@redaction/red-ui-http'; import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling'; -import { debounce } from '../../../../utils/debounce'; import { Observable } from 'rxjs'; import { map, startWith } from 'rxjs/operators'; +import { BaseListingComponent } from '../../../shared/base/base-listing.component'; enum FieldType { Text = 'Text', @@ -15,7 +15,7 @@ enum FieldType { Date = 'Date' } -interface Field { +export interface Field { csvColumn: string; name: string; type: FieldType; @@ -30,17 +30,16 @@ interface Field { templateUrl: './file-attributes-csv-import-dialog.component.html', styleUrls: ['./file-attributes-csv-import-dialog.component.scss'] }) -export class FileAttributesCsvImportDialogComponent { +export class FileAttributesCsvImportDialogComponent extends BaseListingComponent { + protected readonly _searchKey = 'csvColumn'; + public csvFile: File; public ruleSetId: string; public parseResult: { data: any[]; errors: any[]; meta: any; fields: Field[] }; public hoveredColumn: string; public activeFields: Field[] = []; - public selectedFields: string[] = []; public baseConfigForm: FormGroup; public isSearchOpen = false; - public searchForm: FormGroup; - public filteredFields: Field[]; public previewExpanded = true; public filteredKeyOptions: Observable; public keepPreview = false; @@ -50,18 +49,15 @@ export class FileAttributesCsvImportDialogComponent { constructor( private readonly _appStateService: AppStateService, - private readonly _formBuilder: FormBuilder, private readonly _fileAttributesControllerService: FileAttributesControllerService, public dialogRef: MatDialogRef, + protected readonly _injector: Injector, @Inject(MAT_DIALOG_DATA) public data: { csv: File; ruleSetId: string } ) { + super(_injector); this.csvFile = data.csv; this.ruleSetId = data.ruleSetId; - this.searchForm = this._formBuilder.group({ - query: [''] - }); - this.baseConfigForm = this._formBuilder.group({ filenameMappingColumnHeaderName: ['', [Validators.required, this._autocompleteStringValidator()]], delimiter: [undefined, Validators.required], @@ -69,13 +65,6 @@ export class FileAttributesCsvImportDialogComponent { }); this._readFile(); - - this.searchForm.valueChanges.subscribe((value) => this._executeSearch(value)); - } - - @debounce(200) - private _executeSearch(value: { query: string }) { - this.filteredFields = this.parseResult.fields.filter((f) => f.csvColumn.toLowerCase().includes(value.query.toLowerCase())); } private _autocompleteStringValidator(): ValidatorFn { @@ -98,12 +87,14 @@ export class FileAttributesCsvImportDialogComponent { if (!this.baseConfigForm.get('delimiter').value) { this.baseConfigForm.patchValue({ delimiter: this.parseResult.meta.delimiter }); } - this.parseResult.fields = this.parseResult.meta.fields.map((field) => this._buildAttribute(field)); - this.filteredFields = [...this.parseResult.fields]; + this.allEntities = this.parseResult.meta.fields.map((field) => this._buildAttribute(field)); + this.displayedEntities = [...this.allEntities]; this.filteredKeyOptions = this.baseConfigForm.get('filenameMappingColumnHeaderName').valueChanges.pipe( startWith(''), - map((value: string) => this.parseResult.meta.fields.filter((field) => field.toLowerCase().indexOf(value.toLowerCase()) !== -1)) + map((value: string) => + this.allEntities.filter((field) => field.csvColumn.toLowerCase().indexOf(value.toLowerCase()) !== -1).map((field) => field.csvColumn) + ) ); }); reader.readAsText(this.csvFile, this.baseConfigForm.get('encoding').value); @@ -137,9 +128,6 @@ export class FileAttributesCsvImportDialogComponent { } else { this.activeFields.splice(this.activeFields.indexOf(field), 1); this.activeFields = [...this.activeFields]; - if (this.isFieldSelected(field.csvColumn)) { - this.toggleFieldSelected(field.csvColumn); - } } } @@ -157,52 +145,11 @@ export class FileAttributesCsvImportDialogComponent { } public activateAll() { - this.activeFields = [...this.parseResult.fields]; + this.activeFields = [...this.allEntities]; } public deactivateAll() { this.activeFields = []; - this.selectedFields = []; - } - - public toggleFieldSelected(field: string) { - const idx = this.selectedFields.indexOf(field); - if (idx === -1) { - this.selectedFields.push(field); - } else { - this.selectedFields.splice(idx, 1); - } - } - - public toggleSelectAll() { - if (this.areSomeFieldsSelected) { - this.selectedFields = []; - } else { - this.selectedFields = this.activeFields.map((field) => field.csvColumn); - } - } - - public get areAllFieldsSelected() { - return this.activeFields.length !== 0 && this.selectedFields.length === this.activeFields.length; - } - - public get areSomeFieldsSelected() { - return this.selectedFields.length > 0; - } - - public isFieldSelected(field: string) { - return this.selectedFields.indexOf(field) !== -1; - } - - public deactivateSelection() { - this.activeFields = [...this.activeFields.filter((field) => !this.isFieldSelected(field.csvColumn))]; - this.selectedFields = []; - } - - public setAttributeForSelection(attribute: string, value: any) { - for (const csvColumn of this.selectedFields) { - this.activeFields.find((f) => f.csvColumn === csvColumn)[attribute] = value; - } } public async save() { diff --git a/apps/red-ui/src/app/modules/admin/screens/dictionary-listing/dictionary-listing-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/dictionary-listing/dictionary-listing-screen.component.ts index 71bdcc55b..e9b951c22 100644 --- a/apps/red-ui/src/app/modules/admin/screens/dictionary-listing/dictionary-listing-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/dictionary-listing/dictionary-listing-screen.component.ts @@ -2,7 +2,7 @@ import { Component, Injector, OnInit } from '@angular/core'; import { DoughnutChartConfig } from '../../../shared/components/simple-doughnut-chart/simple-doughnut-chart.component'; import { DictionaryControllerService, TypeValue } from '@redaction/red-ui-http'; import { AppStateService } from '../../../../state/app-state.service'; -import { tap } from 'rxjs/operators'; +import { defaultIfEmpty, tap } from 'rxjs/operators'; import { forkJoin } from 'rxjs'; import { PermissionsService } from '../../../../services/permissions.service'; import { ActivatedRoute } from '@angular/router'; @@ -51,9 +51,11 @@ export class DictionaryListingScreenComponent extends BaseListingComponent { - this._calculateData(); - }); + forkJoin(dataObs) + .pipe(defaultIfEmpty(null)) + .subscribe(() => { + this._calculateData(); + }); } private _calculateData() { diff --git a/apps/red-ui/src/app/modules/projects/screens/project-overview-screen/project-overview-screen.component.html b/apps/red-ui/src/app/modules/projects/screens/project-overview-screen/project-overview-screen.component.html index 01b8ba998..f8c08808d 100644 --- a/apps/red-ui/src/app/modules/projects/screens/project-overview-screen/project-overview-screen.component.html +++ b/apps/red-ui/src/app/modules/projects/screens/project-overview-screen/project-overview-screen.component.html @@ -108,8 +108,6 @@ [selectedFileIds]="selectedEntitiesIds" (reload)="bulkActionPerformed()" > - - {{ selectedEntitiesIds.length }} selected
diff --git a/apps/red-ui/src/app/modules/shared/base/base-listing.component.ts b/apps/red-ui/src/app/modules/shared/base/base-listing.component.ts index e42e89b05..e2d99e543 100644 --- a/apps/red-ui/src/app/modules/shared/base/base-listing.component.ts +++ b/apps/red-ui/src/app/modules/shared/base/base-listing.component.ts @@ -88,6 +88,10 @@ export class BaseListingComponent { this.displayedEntities = (this.filters.length ? this.filteredEntities : this.allEntities).filter((entity) => this._searchField(entity).toLowerCase().includes(this.searchForm.get('query').value.toLowerCase()) ); + this._updateSelection(); + } + + protected _updateSelection() { if (this._selectionKey) { this.selectedEntitiesIds = this.displayedEntities.map((entity) => entity[this.selectionKey]).filter((id) => this.selectedEntitiesIds.includes(id)); }