318 lines
10 KiB
TypeScript

/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* 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
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { AppHookService, AppService } from '@alfresco/aca-shared';
import { AppStore, SearchByTermAction, SearchOptionIds, SearchOptionModel, SnackbarErrorAction } from '@alfresco/aca-shared/store';
import { SearchQueryBuilderService } from '@alfresco/adf-content-services';
import { AppConfigService } from '@alfresco/adf-core';
import { Component, OnDestroy, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import { MatMenuModule, MatMenuTrigger } from '@angular/material/menu';
import { NavigationEnd, PRIMARY_OUTLET, Router, RouterEvent, UrlSegment, UrlSegmentGroup, UrlTree } from '@angular/router';
import { Store } from '@ngrx/store';
import { Subject } from 'rxjs';
import { filter, takeUntil } from 'rxjs/operators';
import { SearchInputControlComponent } from '../search-input-control/search-input-control.component';
import { SearchNavigationService } from '../search-navigation.service';
import { SearchLibrariesQueryBuilderService } from '../search-libraries-results/search-libraries-query-builder.service';
import { CommonModule } from '@angular/common';
import { TranslateModule } from '@ngx-translate/core';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { A11yModule } from '@angular/cdk/a11y';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { FormsModule } from '@angular/forms';
@Component({
standalone: true,
imports: [
CommonModule,
TranslateModule,
MatMenuModule,
MatButtonModule,
MatIconModule,
MatFormFieldModule,
MatInputModule,
A11yModule,
MatCheckboxModule,
FormsModule,
SearchInputControlComponent
],
selector: 'aca-search-input',
templateUrl: './search-input.component.html',
styleUrls: ['./search-input.component.scss'],
encapsulation: ViewEncapsulation.None,
host: { class: 'aca-search-input' }
})
export class SearchInputComponent implements OnInit, OnDestroy {
onDestroy$: Subject<boolean> = new Subject<boolean>();
hasOneChange = false;
hasNewChange = false;
navigationTimer: any;
has400LibraryError = false;
hasLibrariesConstraint = false;
searchOnChange: boolean;
searchedWord: string = null;
searchOptions: Array<SearchOptionModel> = [
{
id: SearchOptionIds.Files,
key: 'SEARCH.INPUT.FILES',
value: false,
shouldDisable: this.isLibrariesChecked.bind(this)
},
{
id: SearchOptionIds.Folders,
key: 'SEARCH.INPUT.FOLDERS',
value: false,
shouldDisable: this.isLibrariesChecked.bind(this)
},
{
id: SearchOptionIds.Libraries,
key: 'SEARCH.INPUT.LIBRARIES',
value: false,
shouldDisable: this.isContentChecked.bind(this)
}
];
@ViewChild('searchInputControl', { static: true })
searchInputControl: SearchInputControlComponent;
@ViewChild(MatMenuTrigger, { static: true })
trigger: MatMenuTrigger;
constructor(
private queryBuilder: SearchQueryBuilderService,
private queryLibrariesBuilder: SearchLibrariesQueryBuilderService,
private config: AppConfigService,
private router: Router,
private store: Store<AppStore>,
private appHookService: AppHookService,
private appService: AppService,
public searchInputService: SearchNavigationService
) {
this.searchOnChange = this.config.get<boolean>('search.aca:triggeredOnChange', true);
}
ngOnInit() {
this.showInputValue();
this.router.events
.pipe(takeUntil(this.onDestroy$))
.pipe(filter((e) => e instanceof RouterEvent))
.subscribe((event) => {
if (event instanceof NavigationEnd) {
this.showInputValue();
}
});
this.appHookService.library400Error.pipe(takeUntil(this.onDestroy$)).subscribe(() => {
this.has400LibraryError = true;
this.hasLibrariesConstraint = this.evaluateLibrariesConstraint();
});
}
exitSearch() {
this.searchInputService.navigateBack();
}
showInputValue() {
this.appService.setAppNavbarMode('collapsed');
this.has400LibraryError = false;
this.hasLibrariesConstraint = this.evaluateLibrariesConstraint();
this.searchedWord = this.getUrlSearchTerm();
if (this.searchInputControl) {
this.searchInputControl.searchTerm = this.searchedWord;
}
}
ngOnDestroy(): void {
this.appService.setAppNavbarMode('expanded');
this.onDestroy$.next(true);
this.onDestroy$.complete();
this.removeContentFilters();
}
onMenuOpened() {
if (this.searchInputControl) {
this.searchInputControl.searchInput?.nativeElement?.focus();
}
}
/**
* Called when the user submits the search, e.g. hits enter or clicks submit
*
* @param event Parameters relating to the search
*/
onSearchSubmit(event: any) {
const searchTerm = event.target ? (event.target as HTMLInputElement).value : event;
if (searchTerm) {
this.searchedWord = searchTerm;
this.searchByOption();
} else {
this.store.dispatch(new SnackbarErrorAction('APP.BROWSE.SEARCH.EMPTY_SEARCH'));
}
if (this.trigger) {
this.trigger.closeMenu();
}
}
onSearchChange(searchTerm: string) {
if (!this.searchOnChange) {
return;
}
this.has400LibraryError = false;
this.hasLibrariesConstraint = this.evaluateLibrariesConstraint();
this.searchedWord = searchTerm;
if (this.hasOneChange) {
this.hasNewChange = true;
} else {
this.hasOneChange = true;
}
if (this.hasNewChange) {
clearTimeout(this.navigationTimer);
this.hasNewChange = false;
}
this.navigationTimer = setTimeout(() => {
if (searchTerm) {
this.store.dispatch(new SearchByTermAction(searchTerm, this.searchOptions));
}
this.hasOneChange = false;
}, 1000);
}
searchByOption() {
this.syncInputValues();
this.has400LibraryError = false;
if (this.isLibrariesChecked()) {
this.hasLibrariesConstraint = this.evaluateLibrariesConstraint();
if (this.onLibrariesSearchResults && this.isSameSearchTerm()) {
this.queryLibrariesBuilder.update();
} else if (this.searchedWord) {
this.store.dispatch(new SearchByTermAction(this.searchedWord, this.searchOptions));
}
} else {
if (this.isFoldersChecked() && !this.isFilesChecked()) {
this.filterContent(SearchOptionIds.Folders);
} else if (this.isFilesChecked() && !this.isFoldersChecked()) {
this.filterContent(SearchOptionIds.Files);
} else {
this.removeContentFilters();
}
if (this.onSearchResults && this.isSameSearchTerm()) {
this.queryBuilder.update();
} else if (this.searchedWord) {
this.store.dispatch(new SearchByTermAction(this.searchedWord, this.searchOptions));
}
}
}
get onLibrariesSearchResults() {
return this.router.url.indexOf('/search-libraries') === 0;
}
get onSearchResults() {
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 {
return this.isOptionChecked(SearchOptionIds.Libraries);
}
isOptionChecked(optionId: string): boolean {
const libItem = this.searchOptions.find((item) => item.id === optionId);
return !!libItem && libItem.value;
}
isContentChecked(): boolean {
return this.isFilesChecked() || this.isFoldersChecked();
}
evaluateLibrariesConstraint(): boolean {
if (this.isLibrariesChecked()) {
return this.has400LibraryError || this.searchInputControl.isTermTooShort();
}
return false;
}
filterContent(option: SearchOptionIds.Folders | SearchOptionIds.Files) {
const oppositeOption = option === SearchOptionIds.Folders ? SearchOptionIds.Files : SearchOptionIds.Folders;
this.queryBuilder.addFilterQuery(`+TYPE:'cm:${option}'`);
this.queryBuilder.removeFilterQuery(`+TYPE:'cm:${oppositeOption}'`);
}
removeContentFilters() {
this.queryBuilder.removeFilterQuery(`+TYPE:'cm:${SearchOptionIds.Files}'`);
this.queryBuilder.removeFilterQuery(`+TYPE:'cm:${SearchOptionIds.Folders}'`);
}
syncInputValues() {
if (this.searchInputControl.searchTerm !== this.searchedWord) {
if (this.searchInputControl.searchTerm) {
this.searchedWord = this.searchInputControl.searchTerm;
} else {
this.searchInputControl.searchTerm = this.searchedWord;
}
}
}
getUrlSearchTerm(): string {
let searchTerm = '';
if (this.onSearchResults || this.onLibrariesSearchResults) {
const urlTree: UrlTree = this.router.parseUrl(this.router.url);
const urlSegmentGroup: UrlSegmentGroup = urlTree.root.children[PRIMARY_OUTLET];
if (urlSegmentGroup) {
const urlSegments: UrlSegment[] = urlSegmentGroup.segments;
searchTerm = urlSegments[0].parameters['q'] ? decodeURIComponent(urlSegments[0].parameters['q']) : '';
}
}
return searchTerm;
}
isSameSearchTerm(): boolean {
const urlSearchTerm = this.getUrlSearchTerm();
return this.searchedWord === urlSearchTerm;
}
}