Pull request #283: rip mac style spotlight search
Merge in RED/ui from RED-1991 to master * commit '488d664d4589f581f8fb714f040ac273443b5a96': rip mac style spotlight search
This commit is contained in:
commit
dfe1687c7f
@ -3,6 +3,7 @@
|
||||
<div class="red-top-bar">
|
||||
<div class="top-bar-row">
|
||||
<div *ngIf="!currentUser.isUser" class="menu-placeholder"></div>
|
||||
|
||||
<div *ngIf="currentUser.isUser" class="menu visible-lt-lg">
|
||||
<button [matMenuTriggerFor]="menuNav" mat-flat-button>
|
||||
<mat-icon svgIcon="red:menu"></mat-icon>
|
||||
@ -25,11 +26,13 @@
|
||||
</button>
|
||||
</mat-menu>
|
||||
</div>
|
||||
|
||||
<div *ngIf="currentUser.isUser" class="menu flex-2 visible-lg breadcrumbs-container">
|
||||
<a *ngIf="(isDossiersView$ | async) === false" class="breadcrumb back" redactionNavigateLastDossiersScreen>
|
||||
<mat-icon svgIcon="red:expand"></mat-icon>
|
||||
{{ 'top-bar.navigation-items.back' | translate }}
|
||||
</a>
|
||||
|
||||
<ng-container *ngIf="isDossiersView$ | async">
|
||||
<a
|
||||
[routerLinkActiveOptions]="{ exact: true }"
|
||||
@ -38,7 +41,9 @@
|
||||
routerLinkActive="active"
|
||||
translate="top-bar.navigation-items.dossiers"
|
||||
></a>
|
||||
|
||||
<mat-icon *ngIf="appStateService.activeDossier" svgIcon="red:arrow-right"></mat-icon>
|
||||
|
||||
<a
|
||||
*ngIf="appStateService.activeDossier"
|
||||
[routerLinkActiveOptions]="{ exact: true }"
|
||||
@ -48,7 +53,9 @@
|
||||
>
|
||||
{{ appStateService.activeDossier.dossierName }}
|
||||
</a>
|
||||
|
||||
<mat-icon *ngIf="appStateService.activeFile" svgIcon="red:arrow-right"></mat-icon>
|
||||
|
||||
<a
|
||||
*ngIf="appStateService.activeFile"
|
||||
[routerLink]="'/main/dossiers/' + appStateService.activeDossierId + '/file/' + appStateService.activeFile.fileId"
|
||||
@ -59,22 +66,22 @@
|
||||
</a>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<div class="center logo-wrapper">
|
||||
<redaction-hidden-action (action)="userPreferenceService.toggleDevFeatures()">
|
||||
<redaction-logo></redaction-logo>
|
||||
</redaction-hidden-action>
|
||||
<div class="app-name">{{ titleService.getTitle() }}</div>
|
||||
</div>
|
||||
|
||||
<div class="menu right flex-2">
|
||||
<div class="buttons">
|
||||
<iqser-circle-button
|
||||
(action)="openSpotlightSearch()"
|
||||
<redaction-spotlight-search
|
||||
*ngIf="(isSearchScreen$ | async) === false"
|
||||
[icon]="'iqser:search'"
|
||||
[tooltip]="'search.header-label' | translate"
|
||||
tooltipPosition="below"
|
||||
[placeholder]="'search.placeholder' | translate"
|
||||
[actions]="searchActions"
|
||||
iqserHelpMode="search"
|
||||
></iqser-circle-button>
|
||||
></redaction-spotlight-search>
|
||||
|
||||
<redaction-notifications iqserHelpMode="notifications"></redaction-notifications>
|
||||
</div>
|
||||
|
||||
@ -33,3 +33,7 @@
|
||||
margin-right: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
redaction-spotlight-search {
|
||||
margin-right: 16px !important;
|
||||
}
|
||||
|
||||
@ -6,10 +6,7 @@ import { NavigationStart, Router } from '@angular/router';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { FileDownloadService } from '@upload-download/services/file-download.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { SpotlightSearchComponent } from '@components/spotlight-search/spotlight-search.component';
|
||||
import { SpotlightSearchAction } from '@components/spotlight-search/spotlight-search-action';
|
||||
import { SpotlightSearchDialogData } from '@components/spotlight-search/spotlight-search-dialog-data';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { distinctUntilChanged, filter, map, startWith } from 'rxjs/operators';
|
||||
|
||||
@ -38,7 +35,7 @@ export class BaseScreenComponent {
|
||||
readonly currentUser = this.userService.currentUser;
|
||||
readonly isDossiersView$ = this._navigationStart$.pipe(map(isDossiersView));
|
||||
readonly isSearchScreen$ = this._navigationStart$.pipe(map(isSearchScreen));
|
||||
readonly userMenuItems: MenuItem[] = [
|
||||
readonly userMenuItems: readonly MenuItem[] = [
|
||||
{
|
||||
name: _('top-bar.navigation-items.my-account.children.my-profile'),
|
||||
routerLink: '/main/my-profile',
|
||||
@ -61,6 +58,19 @@ export class BaseScreenComponent {
|
||||
show: this.currentUser.isManager
|
||||
}
|
||||
];
|
||||
readonly searchActions: readonly SpotlightSearchAction[] = [
|
||||
{
|
||||
text: this._translateService.instant('search.this-dossier'),
|
||||
icon: 'red:enter',
|
||||
hide: (): boolean => !this.appStateService.activeDossier,
|
||||
action: (query): void => this._search(query, this.appStateService.activeDossier.dossierId)
|
||||
},
|
||||
{
|
||||
text: this._translateService.instant('search.entire-platform'),
|
||||
icon: 'red:enter',
|
||||
action: (query): void => this._search(query)
|
||||
}
|
||||
];
|
||||
|
||||
constructor(
|
||||
readonly appStateService: AppStateService,
|
||||
@ -69,33 +79,9 @@ export class BaseScreenComponent {
|
||||
readonly titleService: Title,
|
||||
readonly fileDownloadService: FileDownloadService,
|
||||
private readonly _router: Router,
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _dialog: MatDialog
|
||||
private readonly _translateService: TranslateService
|
||||
) {}
|
||||
|
||||
openSpotlightSearch() {
|
||||
const spotlightSearchActions: SpotlightSearchAction[] = [
|
||||
{
|
||||
text: this._translateService.instant('search.this-dossier'),
|
||||
icon: 'red:enter',
|
||||
hide: !this.appStateService.activeDossier,
|
||||
action: query => this._search(query, this.appStateService.activeDossier.dossierId)
|
||||
},
|
||||
{
|
||||
text: this._translateService.instant('search.entire-platform'),
|
||||
icon: 'red:enter',
|
||||
action: query => this._search(query)
|
||||
}
|
||||
];
|
||||
|
||||
this._dialog.open(SpotlightSearchComponent, {
|
||||
data: {
|
||||
actionsConfig: spotlightSearchActions,
|
||||
placeholder: this._translateService.instant('search.placeholder')
|
||||
} as SpotlightSearchDialogData
|
||||
});
|
||||
}
|
||||
|
||||
private _search(query: string, dossierId?: string) {
|
||||
const queryParams = { query, dossierId };
|
||||
this._router.navigate(['main/dossiers/search'], { queryParams }).then();
|
||||
|
||||
@ -2,5 +2,5 @@ export interface SpotlightSearchAction {
|
||||
readonly text: string;
|
||||
readonly action: (query: string) => void;
|
||||
readonly icon?: string;
|
||||
readonly hide?: boolean;
|
||||
readonly hide?: () => boolean;
|
||||
}
|
||||
|
||||
@ -1,6 +0,0 @@
|
||||
import { SpotlightSearchAction } from './spotlight-search-action';
|
||||
|
||||
export interface SpotlightSearchDialogData {
|
||||
readonly actionsConfig: SpotlightSearchAction[];
|
||||
readonly placeholder: string;
|
||||
}
|
||||
@ -1,28 +1,28 @@
|
||||
<form [formGroup]="formGroup" class="spotlight-wrapper">
|
||||
<div class="search d-flex">
|
||||
<input [placeholder]="data.placeholder" autocomplete="off" class="spotlight-row" formControlName="query" id="query" type="text" />
|
||||
<iqser-input-with-action
|
||||
(click)="openMenuIfValue()"
|
||||
(valueChange)="valueChanges$.next($event)"
|
||||
[placeholder]="placeholder"
|
||||
></iqser-input-with-action>
|
||||
|
||||
<mat-icon *ngIf="(showActions$ | async) === false" [svgIcon]="'iqser:search'" class="mr-34"></mat-icon>
|
||||
<mat-menu #menu="matMenu" xPosition="after">
|
||||
<ng-template matMenuContent>
|
||||
<div class="wrapper">
|
||||
<button
|
||||
(click)="item.action(valueChanges$.getValue())"
|
||||
*ngFor="let item of shownActions; let index = index"
|
||||
[class.highlight]="(currentActionIdx$ | async) === index"
|
||||
class="spotlight-row pointer"
|
||||
>
|
||||
<mat-icon [svgIcon]="item.icon"></mat-icon>
|
||||
<span>{{ item.text }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
</mat-menu>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="dialogRef.close()"
|
||||
*ngIf="showActions$ | async"
|
||||
class="mr-24"
|
||||
icon="iqser:close"
|
||||
></iqser-circle-button>
|
||||
</div>
|
||||
<!-- https://material.angular.io/components/menu/overview#toggling-the-menu-programmatically -->
|
||||
<!-- To toggle menu programmatically a matMenuTriggerFor directive is needed -->
|
||||
<div [matMenuTriggerFor]="menu"></div>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<ng-container *ngIf="showActions$ | async">
|
||||
<button
|
||||
(click)="item.action(formGroup.get('query').value); dialogRef.close()"
|
||||
*ngFor="let item of shownActions; let index = index"
|
||||
[class.highlight]="(currentActionIdx$ | async) === index"
|
||||
class="spotlight-row pointer"
|
||||
>
|
||||
<mat-icon [svgIcon]="item.icon" class="mr-16"></mat-icon>
|
||||
<span>{{ item.text }}</span>
|
||||
</button>
|
||||
</ng-container>
|
||||
</form>
|
||||
<!-- A hack to avoid subscribing in component -->
|
||||
<ng-container *ngIf="showActions$ | async"></ng-container>
|
||||
|
||||
@ -1,50 +1,38 @@
|
||||
@import '../../../assets/styles/variables';
|
||||
|
||||
.spotlight-wrapper {
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
top: 15%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 10px 30px 0 rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.spotlight-row {
|
||||
width: 300px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 60px;
|
||||
height: 40px;
|
||||
text-align: left;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
font-size: 13px;
|
||||
border: none;
|
||||
outline: none;
|
||||
color: $accent;
|
||||
padding: 0 24px;
|
||||
background-color: $white;
|
||||
}
|
||||
|
||||
.spotlight-row,
|
||||
.spotlight-wrapper {
|
||||
width: 750px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
border-radius: 4px;
|
||||
background-color: $grey-2;
|
||||
}
|
||||
|
||||
.search {
|
||||
background-color: $white;
|
||||
align-items: center;
|
||||
.wrapper {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 1px;
|
||||
background-color: rgba(226, 228, 233, 0.9);
|
||||
::ng-deep .mat-menu-content {
|
||||
padding: 8px !important;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
mat-icon {
|
||||
width: 14px;
|
||||
margin-left: 13px;
|
||||
margin-right: 8px;
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
}
|
||||
|
||||
@ -1,59 +1,63 @@
|
||||
import { ChangeDetectionStrategy, Component, HostListener, Inject } from '@angular/core';
|
||||
import { FormBuilder } from '@angular/forms';
|
||||
import { debounceTime, distinctUntilChanged, map, startWith } from 'rxjs/operators';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { SpotlightSearchDialogData } from '@components/spotlight-search/spotlight-search-dialog-data';
|
||||
import { ChangeDetectionStrategy, Component, HostListener, Input, ViewChild } from '@angular/core';
|
||||
import { debounceTime, distinctUntilChanged, map, tap } from 'rxjs/operators';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { SpotlightSearchAction } from '@components/spotlight-search/spotlight-search-action';
|
||||
import { MatMenuTrigger } from '@angular/material/menu';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-spotlight-search',
|
||||
selector: 'redaction-spotlight-search [actions]',
|
||||
templateUrl: './spotlight-search.component.html',
|
||||
styleUrls: ['./spotlight-search.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class SpotlightSearchComponent {
|
||||
private readonly _currentActionIdx$ = new BehaviorSubject(0);
|
||||
@Input() actions: readonly SpotlightSearchAction[];
|
||||
@Input() placeholder?: string;
|
||||
@ViewChild(MatMenuTrigger) private readonly _menuTrigger!: MatMenuTrigger;
|
||||
|
||||
private readonly _currentActionIdx$ = new BehaviorSubject(0);
|
||||
readonly currentActionIdx$ = this._currentActionIdx$.asObservable().pipe(distinctUntilChanged());
|
||||
readonly shownActions = this.data.actionsConfig.filter(a => !a.hide);
|
||||
readonly formGroup = this._formBuilder.group({ query: [''] });
|
||||
readonly showActions$ = this.formGroup.get('query').valueChanges.pipe(
|
||||
startWith(''),
|
||||
|
||||
readonly valueChanges$ = new BehaviorSubject<string>('');
|
||||
readonly showActions$ = this.valueChanges$.pipe(
|
||||
debounceTime(300),
|
||||
map(value => value !== '')
|
||||
map(value => value !== ''),
|
||||
tap(show => (show ? this._menuTrigger.openMenu() : this._menuTrigger.closeMenu()))
|
||||
);
|
||||
|
||||
constructor(
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
readonly dialogRef: MatDialogRef<SpotlightSearchComponent>,
|
||||
@Inject(MAT_DIALOG_DATA)
|
||||
readonly data: SpotlightSearchDialogData
|
||||
) {}
|
||||
get shownActions(): readonly SpotlightSearchAction[] {
|
||||
return this.actions.filter(a => !a.hide?.());
|
||||
}
|
||||
|
||||
@HostListener('document:keydown', ['$event'])
|
||||
handleKeyDown(event: KeyboardEvent): void {
|
||||
if (['ArrowDown', 'ArrowUp'].includes(event.code)) {
|
||||
event.preventDefault();
|
||||
return event.stopPropagation();
|
||||
openMenuIfValue(): void {
|
||||
const value = this.valueChanges$.getValue();
|
||||
if (value && value !== '') {
|
||||
this._menuTrigger.openMenu();
|
||||
}
|
||||
}
|
||||
|
||||
@HostListener('document:keyup', ['$event'])
|
||||
handleKeyUp(event: KeyboardEvent): void {
|
||||
if (['ArrowDown', 'ArrowUp'].includes(event.code)) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const index = this._currentActionIdx + event.code === 'ArrowDown' ? 1 : -1;
|
||||
return this._currentActionIdx$.next(this._fixIndexOutOfBound(index));
|
||||
}
|
||||
@HostListener('document:keydown.arrowDown', ['$event'])
|
||||
@HostListener('document:keydown.arrowUp', ['$event'])
|
||||
handleKeyDown(event: KeyboardEvent): void {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
@HostListener('document:keyup.arrowDown', ['$event'])
|
||||
handleKeyUpArrowDown(event: KeyboardEvent): void {
|
||||
this.handleKeyDown(event);
|
||||
const index = this._currentActionIdx + 1;
|
||||
this._currentActionIdx$.next(index >= this.actions.length ? 0 : index);
|
||||
}
|
||||
|
||||
@HostListener('document:keyup.arrowUp', ['$event'])
|
||||
handleKeyUpArrowUp(event: KeyboardEvent): void {
|
||||
this.handleKeyDown(event);
|
||||
const index = this._currentActionIdx - 1;
|
||||
this._currentActionIdx$.next(index < 0 ? this.actions.length - 1 : index);
|
||||
}
|
||||
|
||||
private get _currentActionIdx(): number {
|
||||
return this._currentActionIdx$.getValue();
|
||||
}
|
||||
|
||||
private _fixIndexOutOfBound(index: number): number {
|
||||
const indexOutOfBound = index < 0 || index >= this.shownActions.length;
|
||||
return indexOutOfBound ? Math.abs(this.shownActions.length - Math.abs(index)) : index;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1212,7 +1212,6 @@
|
||||
},
|
||||
"search": {
|
||||
"entire-platform": "",
|
||||
"header-label": "",
|
||||
"placeholder": "",
|
||||
"this-dossier": ""
|
||||
},
|
||||
|
||||
@ -1349,10 +1349,9 @@
|
||||
"table-header": "{length} search {length, plural, one{result} other{results}}"
|
||||
},
|
||||
"search": {
|
||||
"entire-platform": "entire platform",
|
||||
"header-label": "Search entire platform",
|
||||
"placeholder": "Search for documents or document content",
|
||||
"this-dossier": "in this dossier"
|
||||
"entire-platform": "all documents",
|
||||
"placeholder": "Search...",
|
||||
"this-dossier": "documents in this dossier"
|
||||
},
|
||||
"smtp-auth-config": {
|
||||
"actions": {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user