Pull request #48: RED-767: Search

Merge in RED/ui from search to master

* commit '14f828cbcae5839bcd5ee5de3b21f05bad88f9b8':
  Project listing search
  Search in project overview
This commit is contained in:
Timo Bejan 2020-11-27 14:24:46 +01:00
commit cf97303a25
12 changed files with 99 additions and 20 deletions

View File

@ -44,6 +44,7 @@ export class IconsModule {
'radio-selected',
'refresh',
'report',
'search',
'secret',
'sort-asc',
'sort-desc',

View File

@ -24,7 +24,7 @@
</div>
<span class="all-caps-label">
{{ 'dictionary-listing.table-header.title' | translate: { length: 0 } }}
{{ 'dictionary-listing.table-header.title' | translate: { length: dictionaries.length } }}
</span>
</div>

View File

@ -189,7 +189,7 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
private _configureTextPopup() {
this.instance.textPopup.add(<any>{
type: 'actionButton',
img: '/assets/icons/general/search-viewer.svg',
img: '/assets/icons/general/search.svg',
title: this._translateService.instant('pdf-viewer.text-popup.actions.search'),
onClick: () => {
const text = this.instance.docViewer.getSelectedText();

View File

@ -21,6 +21,12 @@
[filters]="needsWorkFilters"
[icon]="'red:needs-work'"
></redaction-filter>
<form [formGroup]="searchForm">
<div class="red-input-group">
<input [placeholder]="'project-listing.search' | translate" formControlName="query" name="query" type="text" class="with-icon" />
<mat-icon class="icon-right" svgIcon="red:search"></mat-icon>
</div>
</form>
</div>
<redaction-icon-button
*ngIf="permissionsService.isManager()"

View File

@ -23,6 +23,8 @@ import { TranslateChartService } from '../../utils/translate-chart.service';
import { RedactionFilterSorter } from '../../common/sorters/redaction-filter-sorter';
import { StatusSorter } from '../../common/sorters/status-sorter';
import { Router } from '@angular/router';
import { FormBuilder, FormGroup } from '@angular/forms';
import { debounce } from '../../utils/debounce';
@Component({
selector: 'redaction-project-listing-screen',
@ -33,6 +35,8 @@ export class ProjectListingScreenComponent implements OnInit, OnDestroy {
public projectsChartData: DoughnutChartConfig[] = [];
public documentsChartData: DoughnutChartConfig[] = [];
public searchForm: FormGroup;
public statusFilters: FilterModel[];
public peopleFilters: FilterModel[];
public needsWorkFilters: FilterModel[];
@ -55,8 +59,15 @@ export class ProjectListingScreenComponent implements OnInit, OnDestroy {
private readonly _translateService: TranslateService,
private readonly _router: Router,
public readonly sortingService: SortingService,
public readonly translateChartService: TranslateChartService
) {}
public readonly translateChartService: TranslateChartService,
private readonly _formBuilder: FormBuilder
) {
this.searchForm = this._formBuilder.group({
query: ['']
});
this.searchForm.valueChanges.subscribe((value) => this._executeSearch(value));
}
public ngOnInit(): void {
this.appStateService.reset();
@ -237,19 +248,30 @@ export class ProjectListingScreenComponent implements OnInit, OnDestroy {
this._filterProjects();
}
private _filterProjects() {
private get _filteredProjects(): ProjectWrapper[] {
const filters = [
{ values: this.statusFilters, checker: projectStatusChecker },
{ values: this.peopleFilters, checker: projectMemberChecker },
{ values: this.needsWorkFilters, checker: annotationFilterChecker, matchAll: true, checkerArgs: this.permissionsService }
];
return getFilteredEntities(this.appStateService.allProjects, filters);
}
private _filterProjects() {
this.detailsContainerFilters = {
statusFilters: this.statusFilters.map((f) => ({ ...f }))
};
this.displayedProjects = getFilteredEntities(this.appStateService.allProjects, filters);
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()));
}
openEditProjectDialog($event: MouseEvent, project: Project) {
this._dialogService.openEditProjectDialog($event, project, () => {
this._calculateData();

View File

@ -22,6 +22,12 @@
[filters]="needsWorkFilters"
[icon]="'red:needs-work'"
></redaction-filter>
<form [formGroup]="searchForm">
<div class="red-input-group">
<input [placeholder]="'project-overview.search' | translate" formControlName="query" name="query" type="text" class="with-icon" />
<mat-icon class="icon-right" svgIcon="red:search"></mat-icon>
</div>
</form>
</div>
<div class="actions">

View File

@ -23,6 +23,8 @@ import { Subscription, timer } from 'rxjs';
import { tap } from 'rxjs/operators';
import { RedactionFilterSorter } from '../../common/sorters/redaction-filter-sorter';
import { StatusSorter } from '../../common/sorters/status-sorter';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { debounce } from '../../utils/debounce';
@Component({
selector: 'redaction-project-overview-screen',
@ -35,6 +37,7 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
public peopleFilters: FilterModel[];
public needsWorkFilters: FilterModel[];
public collapsedDetails = false;
public searchForm: FormGroup;
displayedFiles: FileStatusWrapper[] = [];
@ -61,8 +64,15 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
private readonly _router: Router,
private readonly _changeDetectorRef: ChangeDetectorRef,
private readonly _translateService: TranslateService,
private readonly _fileDropOverlayService: FileDropOverlayService
private readonly _fileDropOverlayService: FileDropOverlayService,
private readonly _formBuilder: FormBuilder
) {
this.searchForm = this._formBuilder.group({
query: ['']
});
this.searchForm.valueChanges.subscribe((value) => this._executeSearch(value));
this._activatedRoute.params.subscribe((params) => {
this.appStateService.activateProject(params.projectId);
});
@ -89,6 +99,12 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
this.filesAutoUpdateTimer.unsubscribe();
}
@debounce(200)
private _executeSearch(value: { query: string }) {
this.displayedFiles = this._filteredFiles.filter((file) => file.filename.toLowerCase().includes(value.query.toLowerCase()));
this.selectedFileIds = this.displayedFiles.map((d) => d.fileId).filter((x) => this.selectedFileIds.includes(x));
}
get displayReanalyseBtn() {
return !!(
this.permissionsService.isManagerAndOwner() &&
@ -263,7 +279,7 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
: [];
}
private _filterFiles() {
private get _filteredFiles(): FileStatusWrapper[] {
const filters = [
{ values: this.statusFilters, checker: keyChecker('status') },
{ values: this.peopleFilters, checker: keyChecker('currentReviewer') },
@ -274,7 +290,11 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
checkerArgs: this.permissionsService
}
];
this.displayedFiles = getFilteredEntities(this.appStateService.activeProject.files, filters);
return getFilteredEntities(this.appStateService.activeProject.files, filters);
}
private _filterFiles() {
this.displayedFiles = this._filteredFiles.filter((file) => file.filename.toLowerCase().includes(this.searchForm.get('query').value.toLowerCase()));
this.selectedFileIds = this.displayedFiles.map((d) => d.fileId).filter((x) => this.selectedFileIds.includes(x));
this.detailsContainerFilters = {
needsWorkFilters: this.needsWorkFilters.map((f) => ({ ...f })),

View File

@ -69,6 +69,7 @@
"action": "Download Redaction Report"
},
"project-listing": {
"search": "Project name...",
"reanalyse": {
"action": "Reanalyse entire project"
},
@ -146,6 +147,7 @@
"collapse": "Hide Details"
},
"project-overview": {
"search": "Document name...",
"header-actions": {
"edit": "Edit",
"delete": "Delete",

View File

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 512.005 512.005" style="enable-background:new 0 0 512.005 512.005;" xml:space="preserve">
<g fill="rgb(136,142,149)">
<path d="M505.749,475.587l-145.6-145.6c28.203-34.837,45.184-79.104,45.184-127.317c0-111.744-90.923-202.667-202.667-202.667
S0,90.925,0,202.669s90.923,202.667,202.667,202.667c48.213,0,92.48-16.981,127.317-45.184l145.6,145.6
c4.16,4.16,9.621,6.251,15.083,6.251s10.923-2.091,15.083-6.251C514.091,497.411,514.091,483.928,505.749,475.587z
M202.667,362.669c-88.235,0-160-71.765-160-160s71.765-160,160-160s160,71.765,160,160S290.901,362.669,202.667,362.669z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 875 B

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="100px" height="100px" viewBox="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>search</title>
<g id="search" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M95.5,74.5 L76.5,55.5 C78.5,50.5 79.5,45.5 79.5,40 C79.5,18 61.5,0 39.5,0 C18,0 0,18 0,40 C0,62 18,80 40,80 C45.5,80 51,79 55.5,77 L74.5,96 C77.5,99 81.5,100.5 85,100.5 C89,100.5 92.5,99 95.5,96 C101.5,90 101.5,80 95.5,74.5 Z M10,40 C10,23.5 23.5,10 40,10 C56.5,10 70,23.5 70,40 C70,56.5 56.5,70 40,70 C23.5,70 10,56.5 10,40 Z M88.5,88.5 C86.5,90.5 83.5,90.5 81.5,88.5 L64.5,71.5 C67,69.5 69.5,67 71.5,64.5 L88.5,81.5 C90.5,83.5 90.5,86.5 88.5,88.5 Z" id="Shape" fill="currentColor" fill-rule="nonzero"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 841 B

View File

@ -5,6 +5,7 @@
display: flex;
flex-direction: column;
margin-top: 13px;
position: relative;
.mat-form-field-underline {
display: none;
@ -20,6 +21,14 @@
margin-top: 0;
}
.icon-right {
width: 14px;
height: 14px;
position: absolute;
top: 10px;
right: 10px;
}
input,
textarea,
mat-select {
@ -36,6 +45,10 @@
margin-top: 3px;
min-height: 34px;
&.with-icon {
padding-right: 34px;
}
&:focus {
border-color: $grey-1;
}

View File

@ -29,9 +29,22 @@ body {
gap: 2px;
display: flex;
align-items: center;
> div:first-child {
margin-right: 6px;
}
form {
margin-left: 6px;
.red-input-group {
width: 250px;
input {
margin-top: 0;
}
}
}
}
.actions {