mirror of
https://github.com/Alfresco/alfresco-content-app.git
synced 2025-07-31 17:38:28 +00:00
[ACA-19] Library Search Results (#783)
* [ACA-19] libraries results page * [ACA-19] libraries search query builder service + trigger action on search option select * [ACA-19] remove sorting * [ACA-19] extension - set custom columns for search libraries results * [ACA-19] add role column * [ACA-19] adapt text * [ACA-19] reformat with Prettier * [ACA-19] fix unit tests * [ACA-19] reformat with Prettier * [ACA-19] some unit tests & code cleanup * [ACA-19] fix navigation * [ACA-19] remove duplicates * [ACA-19] unit test
This commit is contained in:
committed by
Denys Vuika
parent
cb3754e29d
commit
1e3136332e
@@ -29,6 +29,7 @@ import { FilesComponent } from './components/files/files.component';
|
|||||||
import { LibrariesComponent } from './components/libraries/libraries.component';
|
import { LibrariesComponent } from './components/libraries/libraries.component';
|
||||||
import { GenericErrorComponent } from './components/common/generic-error/generic-error.component';
|
import { GenericErrorComponent } from './components/common/generic-error/generic-error.component';
|
||||||
import { SearchResultsComponent } from './components/search/search-results/search-results.component';
|
import { SearchResultsComponent } from './components/search/search-results/search-results.component';
|
||||||
|
import { SearchLibrariesResultsComponent } from './components/search/search-libraries-results/search-libraries-results.component';
|
||||||
import { LoginComponent } from './components/login/login.component';
|
import { LoginComponent } from './components/login/login.component';
|
||||||
import { AppAuthGuard } from './guards/auth.guard';
|
import { AppAuthGuard } from './guards/auth.guard';
|
||||||
import { AppSharedRuleGuard } from './guards/shared.guard';
|
import { AppSharedRuleGuard } from './guards/shared.guard';
|
||||||
@@ -213,7 +214,7 @@ export const APP_ROUTES: Routes = [
|
|||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
component: SearchResultsComponent,
|
component: SearchLibrariesResultsComponent,
|
||||||
data: {
|
data: {
|
||||||
title: 'APP.BROWSE.SEARCH.TITLE',
|
title: 'APP.BROWSE.SEARCH.TITLE',
|
||||||
reuse: true
|
reuse: true
|
||||||
|
@@ -26,8 +26,10 @@
|
|||||||
</app-search-input-control>
|
</app-search-input-control>
|
||||||
<div id="search-options">
|
<div id="search-options">
|
||||||
<mat-checkbox *ngFor="let option of searchOptions"
|
<mat-checkbox *ngFor="let option of searchOptions"
|
||||||
|
id="{{ option.id }}"
|
||||||
[(ngModel)]="option.value"
|
[(ngModel)]="option.value"
|
||||||
[disabled]="option.shouldDisable()"
|
[disabled]="option.shouldDisable()"
|
||||||
|
(change)="onOptionChange()"
|
||||||
(click)="$event.stopPropagation()">
|
(click)="$event.stopPropagation()">
|
||||||
{{ option.key | translate }}
|
{{ option.key | translate }}
|
||||||
</mat-checkbox>
|
</mat-checkbox>
|
||||||
|
@@ -37,6 +37,8 @@ import { AppTestingModule } from '../../../testing/app-testing.module';
|
|||||||
import { Actions, ofType } from '@ngrx/effects';
|
import { Actions, ofType } from '@ngrx/effects';
|
||||||
import { SEARCH_BY_TERM, SearchByTermAction } from '../../../store/actions';
|
import { SEARCH_BY_TERM, SearchByTermAction } from '../../../store/actions';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
|
import { SearchQueryBuilderService } from '@alfresco/adf-content-services';
|
||||||
|
import { SearchLibrariesQueryBuilderService } from '../search-libraries-results/search-libraries-query-builder.service';
|
||||||
|
|
||||||
describe('SearchInputComponent', () => {
|
describe('SearchInputComponent', () => {
|
||||||
let fixture: ComponentFixture<SearchInputComponent>;
|
let fixture: ComponentFixture<SearchInputComponent>;
|
||||||
@@ -47,7 +49,8 @@ describe('SearchInputComponent', () => {
|
|||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [AppTestingModule],
|
imports: [AppTestingModule],
|
||||||
declarations: [SearchInputComponent],
|
declarations: [SearchInputComponent],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA],
|
||||||
|
providers: [SearchQueryBuilderService, SearchLibrariesQueryBuilderService]
|
||||||
})
|
})
|
||||||
.compileComponents()
|
.compileComponents()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
@@ -38,6 +38,14 @@ import { Store } from '@ngrx/store';
|
|||||||
import { AppStore } from '../../../store/states/app.state';
|
import { AppStore } from '../../../store/states/app.state';
|
||||||
import { SearchByTermAction } from '../../../store/actions';
|
import { SearchByTermAction } from '../../../store/actions';
|
||||||
import { filter } from 'rxjs/operators';
|
import { filter } from 'rxjs/operators';
|
||||||
|
import { SearchLibrariesQueryBuilderService } from '../search-libraries-results/search-libraries-query-builder.service';
|
||||||
|
import { SearchQueryBuilderService } from '@alfresco/adf-content-services';
|
||||||
|
|
||||||
|
export enum SearchOptionIds {
|
||||||
|
Files = 'files',
|
||||||
|
Folders = 'folders',
|
||||||
|
Libraries = 'libraries'
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'aca-search-input',
|
selector: 'aca-search-input',
|
||||||
@@ -51,18 +59,21 @@ export class SearchInputComponent implements OnInit {
|
|||||||
navigationTimer: any;
|
navigationTimer: any;
|
||||||
|
|
||||||
searchedWord = null;
|
searchedWord = null;
|
||||||
searchOptions: any = [
|
searchOptions: Array<any> = [
|
||||||
{
|
{
|
||||||
|
id: SearchOptionIds.Files,
|
||||||
key: 'SEARCH.INPUT.FILES',
|
key: 'SEARCH.INPUT.FILES',
|
||||||
value: false,
|
value: false,
|
||||||
shouldDisable: this.isLibrariesChecked.bind(this)
|
shouldDisable: this.isLibrariesChecked.bind(this)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: SearchOptionIds.Folders,
|
||||||
key: 'SEARCH.INPUT.FOLDERS',
|
key: 'SEARCH.INPUT.FOLDERS',
|
||||||
value: false,
|
value: false,
|
||||||
shouldDisable: this.isLibrariesChecked.bind(this)
|
shouldDisable: this.isLibrariesChecked.bind(this)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: SearchOptionIds.Libraries,
|
||||||
key: 'SEARCH.INPUT.LIBRARIES',
|
key: 'SEARCH.INPUT.LIBRARIES',
|
||||||
value: false,
|
value: false,
|
||||||
shouldDisable: this.isContentChecked.bind(this)
|
shouldDisable: this.isContentChecked.bind(this)
|
||||||
@@ -72,7 +83,12 @@ export class SearchInputComponent implements OnInit {
|
|||||||
@ViewChild('searchInputControl')
|
@ViewChild('searchInputControl')
|
||||||
searchInputControl: SearchInputControlComponent;
|
searchInputControl: SearchInputControlComponent;
|
||||||
|
|
||||||
constructor(private router: Router, private store: Store<AppStore>) {}
|
constructor(
|
||||||
|
private librariesQueryBuilder: SearchLibrariesQueryBuilderService,
|
||||||
|
private queryBuilder: SearchQueryBuilderService,
|
||||||
|
private router: Router,
|
||||||
|
private store: Store<AppStore>
|
||||||
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.showInputValue();
|
this.showInputValue();
|
||||||
@@ -89,7 +105,7 @@ export class SearchInputComponent implements OnInit {
|
|||||||
showInputValue() {
|
showInputValue() {
|
||||||
this.searchedWord = '';
|
this.searchedWord = '';
|
||||||
|
|
||||||
if (this.onSearchResults) {
|
if (this.onSearchResults || this.onLibrariesSearchResults) {
|
||||||
const urlTree: UrlTree = this.router.parseUrl(this.router.url);
|
const urlTree: UrlTree = this.router.parseUrl(this.router.url);
|
||||||
const urlSegmentGroup: UrlSegmentGroup =
|
const urlSegmentGroup: UrlSegmentGroup =
|
||||||
urlTree.root.children[PRIMARY_OUTLET];
|
urlTree.root.children[PRIMARY_OUTLET];
|
||||||
@@ -141,14 +157,57 @@ export class SearchInputComponent implements OnInit {
|
|||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onOptionChange() {
|
||||||
|
if (this.searchedWord) {
|
||||||
|
if (this.isLibrariesChecked()) {
|
||||||
|
if (this.onLibrariesSearchResults) {
|
||||||
|
this.librariesQueryBuilder.update();
|
||||||
|
} else {
|
||||||
|
this.store.dispatch(
|
||||||
|
new SearchByTermAction(this.searchedWord, this.searchOptions)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (this.isContentChecked()) {
|
||||||
|
if (this.onSearchResults) {
|
||||||
|
// TODO: send here data to this.queryBuilder to be able to search for files/folders
|
||||||
|
this.queryBuilder.update();
|
||||||
|
} else {
|
||||||
|
this.store.dispatch(
|
||||||
|
new SearchByTermAction(this.searchedWord, this.searchOptions)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get onLibrariesSearchResults() {
|
||||||
|
return this.router.url.indexOf('/search-libraries') === 0;
|
||||||
|
}
|
||||||
|
|
||||||
get onSearchResults() {
|
get onSearchResults() {
|
||||||
return this.router.url.indexOf('/search') === 0;
|
return (
|
||||||
|
!this.onLibrariesSearchResults && this.router.url.indexOf('/search') === 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
isFilesChecked(): boolean {
|
||||||
|
return this.isOptionChecked(SearchOptionIds.Files);
|
||||||
|
}
|
||||||
|
|
||||||
|
isFoldersChecked(): boolean {
|
||||||
|
return this.isOptionChecked(SearchOptionIds.Folders);
|
||||||
}
|
}
|
||||||
|
|
||||||
isLibrariesChecked(): boolean {
|
isLibrariesChecked(): boolean {
|
||||||
return this.searchOptions[2].value;
|
return this.isOptionChecked(SearchOptionIds.Libraries);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isOptionChecked(optionId: string): boolean {
|
||||||
|
const libItem = this.searchOptions.find(item => item.id === optionId);
|
||||||
|
return !!libItem && libItem.value;
|
||||||
|
}
|
||||||
|
|
||||||
isContentChecked(): boolean {
|
isContentChecked(): boolean {
|
||||||
return this.searchOptions[0].value || this.searchOptions[1].value;
|
return this.isFilesChecked() || this.isFoldersChecked();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,96 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright 2016 Alfresco Software, Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
import { AppTestingModule } from '../../../testing/app-testing.module';
|
||||||
|
import { AlfrescoApiService } from '@alfresco/adf-core';
|
||||||
|
import { SearchLibrariesQueryBuilderService } from './search-libraries-query-builder.service';
|
||||||
|
|
||||||
|
describe('SearchLibrariesQueryBuilderService', () => {
|
||||||
|
let apiService: AlfrescoApiService;
|
||||||
|
let builder: SearchLibrariesQueryBuilderService;
|
||||||
|
let queriesApi;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [AppTestingModule]
|
||||||
|
});
|
||||||
|
|
||||||
|
apiService = TestBed.get(AlfrescoApiService);
|
||||||
|
apiService.reset();
|
||||||
|
queriesApi = apiService.getInstance().core.queriesApi;
|
||||||
|
builder = new SearchLibrariesQueryBuilderService(apiService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have empty user query by default', () => {
|
||||||
|
expect(builder.userQuery).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should trim user query value', () => {
|
||||||
|
builder.userQuery = ' something ';
|
||||||
|
expect(builder.userQuery).toEqual('something');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should build query and raise an event on update', async () => {
|
||||||
|
const query = {};
|
||||||
|
spyOn(builder, 'buildQuery').and.returnValue(query);
|
||||||
|
|
||||||
|
let eventArgs = null;
|
||||||
|
builder.updated.subscribe(args => (eventArgs = args));
|
||||||
|
|
||||||
|
await builder.update();
|
||||||
|
expect(eventArgs).toBe(query);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should build query and raise an event on execute', async () => {
|
||||||
|
const data = {};
|
||||||
|
spyOn(queriesApi, 'findSites').and.returnValue(Promise.resolve(data));
|
||||||
|
|
||||||
|
const query = {};
|
||||||
|
spyOn(builder, 'buildQuery').and.returnValue(query);
|
||||||
|
|
||||||
|
let eventArgs = null;
|
||||||
|
builder.executed.subscribe(args => (eventArgs = args));
|
||||||
|
|
||||||
|
await builder.execute();
|
||||||
|
expect(eventArgs).toBe(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should require a query fragment to build query', () => {
|
||||||
|
const compiled = builder.buildQuery();
|
||||||
|
expect(compiled).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should build query when there is a useQuery value', () => {
|
||||||
|
const searchedTerm = 'test';
|
||||||
|
|
||||||
|
builder.userQuery = searchedTerm;
|
||||||
|
|
||||||
|
const compiled = builder.buildQuery();
|
||||||
|
expect(compiled.term).toBe(searchedTerm);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use pagination settings', () => {
|
||||||
|
const searchedTerm = 'test';
|
||||||
|
|
||||||
|
builder.paging = { maxItems: 5, skipCount: 5 };
|
||||||
|
builder.userQuery = searchedTerm;
|
||||||
|
|
||||||
|
const compiled = builder.buildQuery();
|
||||||
|
expect(compiled.opts).toEqual({ maxItems: 5, skipCount: 5 });
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,86 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Alfresco Example Content Application
|
||||||
|
*
|
||||||
|
* Copyright (C) 2005 - 2018 Alfresco Software Limited
|
||||||
|
*
|
||||||
|
* This file is part of the Alfresco Example Content Application.
|
||||||
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
|
* the paid license agreement will prevail. Otherwise, the software is
|
||||||
|
* provided under the following open source license terms:
|
||||||
|
*
|
||||||
|
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { AlfrescoApiService } from '@alfresco/adf-core';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { SitePaging } from 'alfresco-js-api';
|
||||||
|
import { Subject } from 'rxjs';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class SearchLibrariesQueryBuilderService {
|
||||||
|
private _userQuery = '';
|
||||||
|
|
||||||
|
updated: Subject<any> = new Subject();
|
||||||
|
executed: Subject<any> = new Subject();
|
||||||
|
|
||||||
|
paging: { maxItems?: number; skipCount?: number } = null;
|
||||||
|
|
||||||
|
get userQuery(): string {
|
||||||
|
return this._userQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
set userQuery(value: string) {
|
||||||
|
this._userQuery = value ? value.trim() : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(private alfrescoApiService: AlfrescoApiService) {}
|
||||||
|
|
||||||
|
update(): void {
|
||||||
|
const query = this.buildQuery();
|
||||||
|
this.updated.next(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute() {
|
||||||
|
const query = this.buildQuery();
|
||||||
|
if (query) {
|
||||||
|
const data = await this.findLibraries(query);
|
||||||
|
this.executed.next(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildQuery(): any {
|
||||||
|
const query = this.userQuery;
|
||||||
|
if (query) {
|
||||||
|
const resultQuery = {
|
||||||
|
term: query,
|
||||||
|
opts: {
|
||||||
|
skipCount: this.paging && this.paging.skipCount,
|
||||||
|
maxItems: this.paging && this.paging.maxItems
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return resultQuery;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private findLibraries(libraryQuery: { term; opts }): Promise<SitePaging> {
|
||||||
|
return this.alfrescoApiService
|
||||||
|
.getInstance()
|
||||||
|
.core.queriesApi.findSites(libraryQuery.term, libraryQuery.opts)
|
||||||
|
.catch(() => ({ list: { pagination: { totalItems: 0 }, entries: [] } }));
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,99 @@
|
|||||||
|
<app-page-layout>
|
||||||
|
|
||||||
|
<app-page-layout-header>
|
||||||
|
<adf-breadcrumb root="APP.BROWSE.SEARCH_LIBRARIES.TITLE">
|
||||||
|
</adf-breadcrumb>
|
||||||
|
<adf-toolbar class="inline">
|
||||||
|
<ng-container *ngFor="let entry of actions; trackBy: trackByActionId">
|
||||||
|
<aca-toolbar-action [actionRef]="entry"></aca-toolbar-action>
|
||||||
|
</ng-container>
|
||||||
|
</adf-toolbar>
|
||||||
|
</app-page-layout-header>
|
||||||
|
|
||||||
|
<app-page-layout-content>
|
||||||
|
<div class="main-content">
|
||||||
|
<div class="adf-search-results">
|
||||||
|
<div class="adf-search-results__content">
|
||||||
|
<mat-progress-bar
|
||||||
|
*ngIf="isLoading"
|
||||||
|
color="primary"
|
||||||
|
mode="indeterminate">
|
||||||
|
</mat-progress-bar>
|
||||||
|
<div class="adf-search-results__content-header content" *ngIf="data?.list.entries.length">
|
||||||
|
<div class="content__side--left">
|
||||||
|
<div class="adf-search-results--info-text">{{ 'APP.BROWSE.SEARCH_LIBRARIES.FOUND_RESULTS' | translate: { number: totalResults } }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<adf-document-list
|
||||||
|
#documentList
|
||||||
|
acaDocumentList
|
||||||
|
[showHeader]="true"
|
||||||
|
[selectionMode]="'single'"
|
||||||
|
[sorting]="[ 'name', 'asc' ]"
|
||||||
|
[node]="data"
|
||||||
|
(node-dblclick)="navigateTo($event.detail?.node)"
|
||||||
|
(name-click)="navigateTo($event.detail?.node)">
|
||||||
|
|
||||||
|
<data-columns>
|
||||||
|
<ng-container *ngFor="let column of columns; trackBy: trackById">
|
||||||
|
|
||||||
|
<ng-container *ngIf="column.template && !(column.desktopOnly && isSmallScreen)">
|
||||||
|
<data-column
|
||||||
|
[key]="column.key"
|
||||||
|
[title]="column.title"
|
||||||
|
[type]="column.type"
|
||||||
|
[format]="column.format"
|
||||||
|
[class]="column.class"
|
||||||
|
[sortable]="column.sortable">
|
||||||
|
<ng-template let-context>
|
||||||
|
<app-dynamic-column
|
||||||
|
[id]="column.template"
|
||||||
|
[context]="context">
|
||||||
|
</app-dynamic-column>
|
||||||
|
</ng-template>
|
||||||
|
</data-column>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container *ngIf="!column.template && !(column.desktopOnly && isSmallScreen)">
|
||||||
|
<data-column
|
||||||
|
[key]="column.key"
|
||||||
|
[title]="column.title"
|
||||||
|
[type]="column.type"
|
||||||
|
[format]="column.format"
|
||||||
|
[class]="column.class"
|
||||||
|
[sortable]="column.sortable">
|
||||||
|
</data-column>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
</ng-container>
|
||||||
|
</data-columns>
|
||||||
|
|
||||||
|
|
||||||
|
<empty-folder-content>
|
||||||
|
<ng-template>
|
||||||
|
<ng-container *ngIf="data">
|
||||||
|
<div class="empty-search__block">
|
||||||
|
<p class="empty-search__text">
|
||||||
|
{{ 'APP.BROWSE.SEARCH.NO_RESULTS' | translate }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
</ng-template>
|
||||||
|
</empty-folder-content>
|
||||||
|
</adf-document-list>
|
||||||
|
|
||||||
|
<adf-pagination *ngIf="!documentList.isEmpty()"
|
||||||
|
acaPagination
|
||||||
|
[target]="documentList"
|
||||||
|
(change)="onPaginationChanged($event)">
|
||||||
|
</adf-pagination>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="sidebar" *ngIf="infoDrawerOpened$ | async">
|
||||||
|
<aca-info-drawer [node]="selection.last"></aca-info-drawer>
|
||||||
|
</div>
|
||||||
|
</app-page-layout-content>
|
||||||
|
|
||||||
|
</app-page-layout>
|
@@ -0,0 +1,42 @@
|
|||||||
|
@import 'mixins';
|
||||||
|
|
||||||
|
.adf-search-results {
|
||||||
|
@include flex-row;
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
@include flex-column;
|
||||||
|
border-left: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__content-header {
|
||||||
|
display: flex;
|
||||||
|
padding: 0 25px 0 25px;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--info-text {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 16px;
|
||||||
|
color: rgba(0, 0, 0, 0.54);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text--bold {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
@include flex-row;
|
||||||
|
flex: unset;
|
||||||
|
height: unset;
|
||||||
|
padding-top: 8px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
&__side--left {
|
||||||
|
@include flex-column;
|
||||||
|
height: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,38 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { AppTestingModule } from '../../../testing/app-testing.module';
|
||||||
|
import { AppConfigPipe, DataTableComponent } from '@alfresco/adf-core';
|
||||||
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { SearchLibrariesResultsComponent } from './search-libraries-results.component';
|
||||||
|
import { SearchLibrariesQueryBuilderService } from './search-libraries-query-builder.service';
|
||||||
|
import { DocumentListComponent } from '@alfresco/adf-content-services';
|
||||||
|
|
||||||
|
describe('SearchLibrariesResultsComponent', () => {
|
||||||
|
let component: SearchLibrariesResultsComponent;
|
||||||
|
let fixture: ComponentFixture<SearchLibrariesResultsComponent>;
|
||||||
|
|
||||||
|
const emptyPage = { list: { pagination: { totalItems: 0 }, entries: [] } };
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [AppTestingModule],
|
||||||
|
declarations: [
|
||||||
|
DataTableComponent,
|
||||||
|
DocumentListComponent,
|
||||||
|
SearchLibrariesResultsComponent,
|
||||||
|
AppConfigPipe
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA],
|
||||||
|
providers: [SearchLibrariesQueryBuilderService]
|
||||||
|
});
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(SearchLibrariesResultsComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show empty page by default', async () => {
|
||||||
|
spyOn(component, 'onSearchResultLoaded').and.callThrough();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(component.onSearchResultLoaded).toHaveBeenCalledWith(emptyPage);
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,148 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Alfresco Example Content Application
|
||||||
|
*
|
||||||
|
* Copyright (C) 2005 - 2018 Alfresco Software Limited
|
||||||
|
*
|
||||||
|
* This file is part of the Alfresco Example Content Application.
|
||||||
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
|
* the paid license agreement will prevail. Otherwise, the software is
|
||||||
|
* provided under the following open source license terms:
|
||||||
|
*
|
||||||
|
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { NodePaging, Pagination, SiteEntry } from 'alfresco-js-api';
|
||||||
|
import { ActivatedRoute, Params } from '@angular/router';
|
||||||
|
import { PageComponent } from '../../page.component';
|
||||||
|
import { Store } from '@ngrx/store';
|
||||||
|
import { AppStore } from '../../../store/states/app.state';
|
||||||
|
import { NavigateLibraryAction } from '../../../store/actions';
|
||||||
|
import { AppExtensionService } from '../../../extensions/extension.service';
|
||||||
|
import { ContentManagementService } from '../../../services/content-management.service';
|
||||||
|
import { SearchLibrariesQueryBuilderService } from './search-libraries-query-builder.service';
|
||||||
|
import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'aca-search-results',
|
||||||
|
templateUrl: './search-libraries-results.component.html',
|
||||||
|
styleUrls: ['./search-libraries-results.component.scss'],
|
||||||
|
providers: [SearchLibrariesQueryBuilderService]
|
||||||
|
})
|
||||||
|
export class SearchLibrariesResultsComponent extends PageComponent
|
||||||
|
implements OnInit {
|
||||||
|
isSmallScreen = false;
|
||||||
|
searchedWord: string;
|
||||||
|
queryParamName = 'q';
|
||||||
|
data: NodePaging;
|
||||||
|
totalResults = 0;
|
||||||
|
isLoading = false;
|
||||||
|
columns: any[] = [];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private breakpointObserver: BreakpointObserver,
|
||||||
|
private librariesQueryBuilder: SearchLibrariesQueryBuilderService,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
store: Store<AppStore>,
|
||||||
|
extensions: AppExtensionService,
|
||||||
|
content: ContentManagementService
|
||||||
|
) {
|
||||||
|
super(store, extensions, content);
|
||||||
|
|
||||||
|
librariesQueryBuilder.paging = {
|
||||||
|
skipCount: 0,
|
||||||
|
maxItems: 25
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
super.ngOnInit();
|
||||||
|
|
||||||
|
this.columns = this.extensions.documentListPresets.searchLibraries || [];
|
||||||
|
|
||||||
|
this.subscriptions.push(
|
||||||
|
this.librariesQueryBuilder.updated.subscribe(() => {
|
||||||
|
this.isLoading = true;
|
||||||
|
|
||||||
|
this.librariesQueryBuilder.execute();
|
||||||
|
}),
|
||||||
|
|
||||||
|
this.librariesQueryBuilder.executed.subscribe(data => {
|
||||||
|
this.librariesQueryBuilder.paging.skipCount = 0;
|
||||||
|
|
||||||
|
this.onSearchResultLoaded(data);
|
||||||
|
this.isLoading = false;
|
||||||
|
}),
|
||||||
|
|
||||||
|
this.breakpointObserver
|
||||||
|
.observe([Breakpoints.HandsetPortrait, Breakpoints.HandsetLandscape])
|
||||||
|
.subscribe(result => {
|
||||||
|
this.isSmallScreen = result.matches;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
if (this.route) {
|
||||||
|
this.route.params.forEach((params: Params) => {
|
||||||
|
this.searchedWord = params.hasOwnProperty(this.queryParamName)
|
||||||
|
? params[this.queryParamName]
|
||||||
|
: null;
|
||||||
|
const query = this.formatSearchQuery(this.searchedWord);
|
||||||
|
|
||||||
|
if (query && query.length > 1) {
|
||||||
|
this.librariesQueryBuilder.userQuery = query;
|
||||||
|
this.librariesQueryBuilder.update();
|
||||||
|
} else {
|
||||||
|
this.librariesQueryBuilder.userQuery = null;
|
||||||
|
this.librariesQueryBuilder.executed.next({
|
||||||
|
list: { pagination: { totalItems: 0 }, entries: [] }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private formatSearchQuery(userInput: string) {
|
||||||
|
if (!userInput) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return userInput.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
onSearchResultLoaded(nodePaging: NodePaging) {
|
||||||
|
this.data = nodePaging;
|
||||||
|
this.totalResults = this.getNumberOfResults();
|
||||||
|
}
|
||||||
|
|
||||||
|
getNumberOfResults() {
|
||||||
|
if (this.data && this.data.list && this.data.list.pagination) {
|
||||||
|
return this.data.list.pagination.totalItems;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
onPaginationChanged(pagination: Pagination) {
|
||||||
|
this.librariesQueryBuilder.paging = {
|
||||||
|
maxItems: pagination.maxItems,
|
||||||
|
skipCount: pagination.skipCount
|
||||||
|
};
|
||||||
|
this.librariesQueryBuilder.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
navigateTo(node: SiteEntry) {
|
||||||
|
if (node && node.entry && node.entry.guid) {
|
||||||
|
this.store.dispatch(new NavigateLibraryAction(node.entry.guid));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -29,6 +29,7 @@ import { CoreModule } from '@alfresco/adf-core';
|
|||||||
import { ContentModule } from '@alfresco/adf-content-services';
|
import { ContentModule } from '@alfresco/adf-content-services';
|
||||||
import { SearchResultsComponent } from './search-results/search-results.component';
|
import { SearchResultsComponent } from './search-results/search-results.component';
|
||||||
import { SearchResultsRowComponent } from './search-results-row/search-results-row.component';
|
import { SearchResultsRowComponent } from './search-results-row/search-results-row.component';
|
||||||
|
import { SearchLibrariesResultsComponent } from './search-libraries-results/search-libraries-results.component';
|
||||||
import { AppInfoDrawerModule } from '../info-drawer/info.drawer.module';
|
import { AppInfoDrawerModule } from '../info-drawer/info.drawer.module';
|
||||||
import { AppToolbarModule } from '../toolbar/toolbar.module';
|
import { AppToolbarModule } from '../toolbar/toolbar.module';
|
||||||
import { AppCommonModule } from '../common/common.module';
|
import { AppCommonModule } from '../common/common.module';
|
||||||
@@ -46,7 +47,15 @@ import { AppLayoutModule } from '../layout/layout.module';
|
|||||||
DirectivesModule,
|
DirectivesModule,
|
||||||
AppLayoutModule
|
AppLayoutModule
|
||||||
],
|
],
|
||||||
declarations: [SearchResultsComponent, SearchResultsRowComponent],
|
declarations: [
|
||||||
exports: [SearchResultsComponent, SearchResultsRowComponent]
|
SearchResultsComponent,
|
||||||
|
SearchLibrariesResultsComponent,
|
||||||
|
SearchResultsRowComponent
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
SearchResultsComponent,
|
||||||
|
SearchLibrariesResultsComponent,
|
||||||
|
SearchResultsRowComponent
|
||||||
|
]
|
||||||
})
|
})
|
||||||
export class AppSearchResultsModule {}
|
export class AppSearchResultsModule {}
|
||||||
|
@@ -79,13 +79,15 @@ export class AppExtensionService implements RuleContext {
|
|||||||
recent: Array<DocumentListPresetRef>;
|
recent: Array<DocumentListPresetRef>;
|
||||||
favorites: Array<DocumentListPresetRef>;
|
favorites: Array<DocumentListPresetRef>;
|
||||||
trashcan: Array<DocumentListPresetRef>;
|
trashcan: Array<DocumentListPresetRef>;
|
||||||
|
searchLibraries: Array<DocumentListPresetRef>;
|
||||||
} = {
|
} = {
|
||||||
files: [],
|
files: [],
|
||||||
libraries: [],
|
libraries: [],
|
||||||
shared: [],
|
shared: [],
|
||||||
recent: [],
|
recent: [],
|
||||||
favorites: [],
|
favorites: [],
|
||||||
trashcan: []
|
trashcan: [],
|
||||||
|
searchLibraries: []
|
||||||
};
|
};
|
||||||
|
|
||||||
selection: SelectionState;
|
selection: SelectionState;
|
||||||
@@ -163,7 +165,8 @@ export class AppExtensionService implements RuleContext {
|
|||||||
shared: this.getDocumentListPreset(config, 'shared'),
|
shared: this.getDocumentListPreset(config, 'shared'),
|
||||||
recent: this.getDocumentListPreset(config, 'recent'),
|
recent: this.getDocumentListPreset(config, 'recent'),
|
||||||
favorites: this.getDocumentListPreset(config, 'favorites'),
|
favorites: this.getDocumentListPreset(config, 'favorites'),
|
||||||
trashcan: this.getDocumentListPreset(config, 'trashcan')
|
trashcan: this.getDocumentListPreset(config, 'trashcan'),
|
||||||
|
searchLibraries: this.getDocumentListPreset(config, 'search-libraries')
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1189,6 +1189,51 @@
|
|||||||
"sortable": true,
|
"sortable": true,
|
||||||
"desktopOnly": true
|
"desktopOnly": true
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"search-libraries": [
|
||||||
|
{
|
||||||
|
"id": "app.libraries.thumbnail",
|
||||||
|
"key": "$thumbnail",
|
||||||
|
"type": "image",
|
||||||
|
"class": "image-table-cell",
|
||||||
|
"sortable": false,
|
||||||
|
"desktopOnly": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.libraries.name",
|
||||||
|
"key": "name",
|
||||||
|
"title": "APP.DOCUMENT_LIST.COLUMNS.NAME",
|
||||||
|
"type": "text",
|
||||||
|
"class": "adf-data-table-cell--ellipsis__name",
|
||||||
|
"sortable": true,
|
||||||
|
"template": "app.columns.libraryName",
|
||||||
|
"desktopOnly": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.libraries.id",
|
||||||
|
"key": "id",
|
||||||
|
"title": "APP.DOCUMENT_LIST.COLUMNS.ID",
|
||||||
|
"type": "text",
|
||||||
|
"sortable": true,
|
||||||
|
"desktopOnly": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.libraries.visibility",
|
||||||
|
"key": "visibility",
|
||||||
|
"title": "APP.DOCUMENT_LIST.COLUMNS.VISIBILITY",
|
||||||
|
"type": "text",
|
||||||
|
"sortable": true,
|
||||||
|
"template": "app.columns.libraryStatus",
|
||||||
|
"desktopOnly": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.libraries.role",
|
||||||
|
"key": "role",
|
||||||
|
"title": "APP.DOCUMENT_LIST.COLUMNS.ROLE",
|
||||||
|
"type": "text",
|
||||||
|
"sortable": true,
|
||||||
|
"desktopOnly": true
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -111,6 +111,10 @@
|
|||||||
"SIZE": "Size"
|
"SIZE": "Size"
|
||||||
},
|
},
|
||||||
"NO_RESULTS": "Your search returned 0 results"
|
"NO_RESULTS": "Your search returned 0 results"
|
||||||
|
},
|
||||||
|
"SEARCH_LIBRARIES": {
|
||||||
|
"TITLE": "Libraries found...",
|
||||||
|
"FOUND_RESULTS": "{{ number }} results"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ACTIONS": {
|
"ACTIONS": {
|
||||||
@@ -145,6 +149,8 @@
|
|||||||
},
|
},
|
||||||
"DOCUMENT_LIST": {
|
"DOCUMENT_LIST": {
|
||||||
"COLUMNS": {
|
"COLUMNS": {
|
||||||
|
"ID": "ID",
|
||||||
|
"ROLE": "Role",
|
||||||
"NAME": "Name",
|
"NAME": "Name",
|
||||||
"SIZE": "Size",
|
"SIZE": "Size",
|
||||||
"MODIFIED_ON": "Modified",
|
"MODIFIED_ON": "Modified",
|
||||||
|
Reference in New Issue
Block a user