Pull request #11: VM/RED-3370

Merge in SL/common-ui from VM/RED-3370 to master

* commit '39c089ec87d11c48e3ef2186aaafe59d17085b98':
  updated key for reset dossier list filters helper element
  fix to display helpers only when the elements are visible
  WIP RED-2657 -> fixing more keys
  added helper element only if elementName property exists, updated breadcrumbs container for helpmode
  check if body contains helper element before removing it
  added back remained keys for dossiers list screen
  WIP on refactoring help mode logic
This commit is contained in:
Valentin-Gabriel Mihai 2022-02-18 09:47:51 +01:00
commit 4e55bf79f3
15 changed files with 178 additions and 171 deletions

View File

@ -13,29 +13,32 @@
min-width: 16px;
}
}
.breadcrumbs {
display: flex;
align-items: center;
.breadcrumb {
text-decoration: none;
color: var(--iqser-accent);
font-weight: 600;
width: fit-content;
white-space: nowrap;
.breadcrumb {
text-decoration: none;
color: var(--iqser-accent);
font-weight: 600;
width: fit-content;
white-space: nowrap;
&.back {
display: flex !important;
justify-content: center;
align-items: center;
&.back {
display: flex !important;
justify-content: center;
align-items: center;
mat-icon {
margin-right: 8px;
}
}
mat-icon {
margin-right: 8px;
&:last-child {
@include common-mixins.line-clamp(1);
}
&.active {
color: var(--iqser-primary);
}
}
&:last-child {
@include common-mixins.line-clamp(1);
}
&.active {
color: var(--iqser-primary);
}
}

View File

@ -1,103 +1,12 @@
.help-mode-on-mouse-over {
.help-mode {
z-index: 10;
position: absolute;
top: -5px;
left: -5px;
width: 100%;
height: 100%;
padding-right: 5px;
padding-bottom: 10px;
transition: all 0.25s;
}
.help-highlight,
.help-mode-on-mouse-over:hover {
.help-mode:hover {
background: rgba(92, 229, 148, 0.5);
box-shadow: 0 0 0 2px var(--iqser-helpmode-primary) inset;
cursor: help;
}
.help-mode-on-mouse-over-new-dossier-button,
.help-mode-on-mouse-over-filter-dossier-list,
.help-mode-on-mouse-over-filter-document-list,
.help-mode-on-mouse-over-dossiers-quickfilter-my-dossiers,
.help-mode-on-mouse-over-new-dossier {
padding-right: 10px;
}
.help-mode-on-mouse-over-search-in-entire-application,
.help-mode-on-mouse-over-open-notifications,
.help-mode-on-mouse-over-bulk-select-annotations,
.help-mode-on-mouse-over-assign-reviewer {
padding-left: 4px;
}
.help-mode-on-mouse-over-edit-dossier-owner {
padding-left: 5px;
}
.help-mode-on-mouse-over-edit-dossier-member {
margin-left: 3px;
}
.help-mode-on-mouse-over-open-usermenu {
padding-top: 1px;
margin-left: 5px;
}
.help-mode-on-mouse-over-standard-view,
.help-mode-on-mouse-over-delta-view,
.help-mode-on-mouse-over-preview-view {
margin-left: 1px;
}
.help-mode-on-mouse-over-workload-filter {
height: 14px;
width: 50px;
margin-top: 10px;
margin-left: 15px;
}
.help-mode-on-mouse-over-reset-filters {
padding-left: 4px;
}
.help-mode-on-mouse-over-dossier-list,
.help-mode-on-mouse-over-document-list {
margin-top: 5px;
height: calc(100% - 70px);
}
.help-mode-on-mouse-over-dossier-features {
height: 50px;
margin-top: 17px;
width: 95%;
}
.help-mode-on-mouse-over-edit-dossier-from-list {
padding-left: 5px ;
}
.help-mode-on-mouse-over-redaction-edit-reason,
.help-mode-on-mouse-over-redaction-remove-only-here,
.help-mode-on-mouse-over-redaction-remove-from-dictionary,
.help-mode-on-mouse-over-redaction-false-positive,
.help-mode-on-mouse-over-recommendation-accept-or-reject {
width: 20px;
height: 20px;
margin-left: 9px;
margin-top: 8px;
}
.help-mode-on-mouse-over-document-features {
margin-top: 7px;
margin-left: 7px;
height: 20px;
width: 95%;
}
.help-mode-on-mouse-over-navigate-in-breadcrumbs {
height: 20px;
margin-top: 20px;
}

View File

@ -8,9 +8,10 @@ import { PopupFilterComponent } from './popup-filter/popup-filter.component';
import { QuickFiltersComponent } from './quick-filters/quick-filters.component';
import { IqserIconsModule } from '../icons';
import { IqserInputsModule } from '../inputs';
import { IqserHelpModeModule } from '../help-mode';
const matModules = [MatCheckboxModule, MatMenuModule];
const modules = [TranslateModule, IqserButtonsModule, IqserIconsModule, IqserInputsModule];
const modules = [TranslateModule, IqserButtonsModule, IqserIconsModule, IqserInputsModule, IqserHelpModeModule];
const components = [QuickFiltersComponent, PopupFilterComponent];
@NgModule({

View File

@ -8,4 +8,5 @@ export interface IFilter {
readonly checker?: (obj?: unknown) => boolean;
readonly required?: boolean;
readonly disabled?: boolean;
readonly helpModeKey?: string;
}

View File

@ -6,6 +6,7 @@ export class NestedFilter extends Filter implements INestedFilter, IListable {
expanded: boolean;
indeterminate: boolean;
disabled?: boolean;
helpModeKey?: string;
readonly children: Filter[];
constructor(nestedFilter: INestedFilter) {
@ -13,6 +14,7 @@ export class NestedFilter extends Filter implements INestedFilter, IListable {
this.expanded = !!nestedFilter.expanded;
this.indeterminate = !!nestedFilter.indeterminate;
this.disabled = !!nestedFilter.disabled;
this.helpModeKey = nestedFilter.helpModeKey;
this.children = nestedFilter.children ?? [];
}
}

View File

@ -5,6 +5,7 @@
[class.active]="filter.checked"
[class.disabled]="filter.disabled"
class="quick-filter"
[iqserHelpMode]="filter.helpModeKey"
>
{{ filter.label }}
</div>

View File

@ -1,4 +1,4 @@
import { Directive, ElementRef, HostListener, Input, OnInit, Renderer2 } from '@angular/core';
import { Directive, ElementRef, Input, OnInit, Renderer2 } from '@angular/core';
import { HelpModeService } from './help-mode.service';
import { Router } from '@angular/router';
@ -8,6 +8,7 @@ import { Router } from '@angular/router';
})
export class HelpModeDirective implements OnInit {
@Input('iqserHelpMode') elementName!: string;
@Input() isVirtualScrollElement: boolean = false;
private _path: string;
constructor(
@ -20,32 +21,24 @@ export class HelpModeDirective implements OnInit {
}
ngOnInit(): void {
this._createHelperElement();
}
private _createHelperElement() {
const elementNameWithId = `${this.elementName}-${this._getRandomId()}`;
const element = this._elementRef.nativeElement as HTMLElement;
if (!this._isDisabledElement()) {
const helperElement = this._renderer.createElement('div') as HTMLElement;
this._renderer.addClass(helperElement, 'help-mode-on-mouse-over');
this._renderer.addClass(helperElement, `help-mode-on-mouse-over-${this.elementName}`);
this._helpModeService.addElement(elementNameWithId, element, helperElement);
if (this.elementName) {
this._createHelperElement();
}
}
private _isDisabledElement() {
return this._path === 'dossiers' && this.elementName === 'filter-for-status';
private _createHelperElement() {
const element = this._elementRef.nativeElement as HTMLElement;
const helperElementName = `${this.elementName}-${this._generateId()}`;
const helperElement = this._renderer.createElement('a') as HTMLElement;
this._renderer.setAttribute(helperElement, 'href', this._helpModeService.getDocsLink(this.elementName));
this._renderer.setAttribute(helperElement, 'target', '_blank');
this._renderer.addClass(helperElement, 'help-mode');
this._helpModeService.addElement(helperElementName, element, helperElement, this.isVirtualScrollElement);
}
private _getRandomId(): string {
return Math.random().toString(36).substr(2, 9);
}
@HostListener('click') onClick(): void {
this._helpModeService.openDocsFor(this.elementName);
private _generateId(): string {
return Math.random().toString(36).substring(2, 9);
}
}

View File

@ -8,8 +8,12 @@ import { HELP_DOCS, MANUAL_BASE_URL } from './tokens';
interface Helper {
readonly element: HTMLElement;
readonly helperElement: HTMLElement;
readonly isVirtualScrollElement: boolean;
}
const VIRTUAL_SCROLL_ID = 'virtual-scroll';
const SCROLL_BUTTONS_IDS = ['scroll-up', 'scroll-down'];
@Injectable({
providedIn: 'root',
})
@ -19,7 +23,7 @@ export class HelpModeService {
private readonly _helpModeDialogIsOpened$ = new BehaviorSubject(false);
readonly helpModeDialogIsOpened$ = this._helpModeDialogIsOpened$.asObservable();
private readonly _elements: Record<string, Helper> = {};
private readonly _helperElements: Record<string, Helper> = {};
private readonly _renderer: Renderer2;
constructor(
@ -53,29 +57,31 @@ export class HelpModeService {
return ref;
}
openDocsFor(elementName: string): void {
if (this.isHelpModeActive) {
window.open(`${this._manualBaseURL}${this._docs[elementName][this._translateService.currentLang]}`);
}
getDocsLink(elementName: string): string {
return this._docs[elementName] ? `${this._manualBaseURL}${this._docs[elementName][this._translateService.currentLang]}` : '';
}
activateHelpMode(): void {
this._isHelpModeActive$.next(true);
this.openHelpModeDialog();
this._enableHelperElements();
if (!this.isHelpModeActive) {
document.body.style.setProperty('overflow', 'hidden');
this._isHelpModeActive$.next(true);
this.openHelpModeDialog();
setTimeout(() => {
this._enableHelperElements();
});
}
}
deactivateHelpMode(): void {
this._isHelpModeActive$.next(false);
this._disableHelperElements();
if (this.isHelpModeActive) {
document.body.style.removeProperty('overflow');
this._isHelpModeActive$.next(false);
this._disableHelperElements();
}
}
highlightHelperElements(): void {
if (!this.isHelpModeActive) {
return;
}
Object.values(this._elements).forEach(({ helperElement }) => {
Object.values(this._helperElements).forEach(({ element, helperElement }) => {
this._renderer.addClass(helperElement, 'help-highlight');
setTimeout(() => {
this._renderer.removeClass(helperElement, 'help-highlight');
@ -83,21 +89,95 @@ export class HelpModeService {
});
}
addElement(elementName: string, element: HTMLElement, helperElement: HTMLElement): void {
this._elements[elementName] = { element, helperElement };
addElement(helperElementName: string, element: HTMLElement, helperElement: HTMLElement, isVirtualScrollElement: boolean): void {
this._helperElements[helperElementName] = { element, helperElement, isVirtualScrollElement };
}
updateHelperElements() {
Object.values(this._helperElements).forEach(({element, helperElement, isVirtualScrollElement }) => {
this._updateHelperElement(element, helperElement, isVirtualScrollElement);
});
}
private _isElementVisible(element: HTMLElement, isVirtualScrollElement: boolean): boolean {
const elementRect = element.getBoundingClientRect();
if (elementRect.top === 0 && elementRect.left === 0 && elementRect.bottom === 0 && elementRect.bottom === 0) {
return false;
}
if (isVirtualScrollElement) {
const virtualScroll: any = document.getElementById(VIRTUAL_SCROLL_ID);
if (!virtualScroll) {
return false;
}
const virtualScrollRect = virtualScroll.getBoundingClientRect();
if (!(elementRect.top > virtualScrollRect.top
&& elementRect.left > virtualScrollRect.left
&& elementRect.bottom < virtualScrollRect.bottom
&& elementRect.right < virtualScrollRect.right)) {
return false;
}
for (const id of SCROLL_BUTTONS_IDS) {
const scroll: any = document.getElementById(id);
const elementRect = element.getBoundingClientRect();
const scrollRect = scroll.getBoundingClientRect();
if(elementRect.top + elementRect.height > scrollRect.top
&& elementRect.left + elementRect.width > scrollRect.left
&& elementRect.bottom - elementRect.height < scrollRect.bottom
&& elementRect.right - elementRect.width < scrollRect.right) {
return false
}
}
}
return true;
}
private _updateHelperElement(element: HTMLElement, helperElement: HTMLElement, isVirtualScrollElement: boolean) {
if (this._isElementVisible(element, isVirtualScrollElement)) {
const dimensions = this._getElementDimensions(element);
helperElement.style.cssText = `
top:${dimensions.y}px;
left:${dimensions.x}px;
width:${dimensions.width}px;
height:${dimensions.height}px;
`;
helperElement.classList.add('help-mode')
} else {
helperElement.classList.remove('help-mode')
}
}
private _enableHelperElements() {
Object.values(this._elements).forEach(({ element, helperElement }) => {
this._renderer.setStyle(element, 'position', 'relative');
this._renderer.appendChild(element, helperElement);
Object.values(this._helperElements).forEach(({ element, helperElement, isVirtualScrollElement }) => {
document.body.appendChild(helperElement)
this._updateHelperElement(element, helperElement, isVirtualScrollElement);
});
}
private _disableHelperElements() {
Object.values(this._elements).forEach(({ element, helperElement }) => {
this._renderer.removeStyle(element, 'position');
this._renderer.removeChild(element, helperElement);
Object.values(this._helperElements).forEach(({ helperElement }) => {
if (document.body.contains(helperElement)) {
document.body.removeChild(helperElement);
}
});
}
private _getElementDimensions(element: HTMLElement) {
const rect = element.getBoundingClientRect();
return {
y: rect.top,
x: rect.left,
height: rect.height,
width: rect.width,
};
}
}

View File

@ -25,6 +25,14 @@ export class HelpModeComponent {
}
@HostListener('click') onClick(): void {
this.helpModeService.highlightHelperElements();
if (this.helpModeService.isHelpModeActive) {
this.helpModeService.highlightHelperElements();
}
}
@HostListener('window:resize') onResize(event: any) {
if (this.helpModeService.isHelpModeActive) {
this.helpModeService.updateHelperElements();
}
}
}

View File

@ -3,5 +3,4 @@ import { ActionConfig } from './action-config.model';
export interface ButtonConfig extends ActionConfig {
readonly type?: IconButtonType;
readonly helpModeKey?: string;
}

View File

@ -1,13 +1,13 @@
<div class="page-header">
<div *ngIf="pageLabel" class="breadcrumb">{{ pageLabel }}</div>
<div *ngIf="filters$ | async as filters" [iqserHelpMode]="helpModeKey" class="filters">
<div *ngIf="filters$ | async as filters" class="filters">
<div *ngIf="filters.length && searchPosition !== searchPositions.beforeFilters" translate="filters.filter-by"></div>
<ng-container *ngIf="searchPosition === searchPositions.beforeFilters" [ngTemplateOutlet]="searchBar"></ng-container>
<ng-container *ngFor="let config of filters; trackBy: trackByLabel">
<iqser-popup-filter *ngIf="!config.hide" [primaryFiltersSlug]="config.slug"></iqser-popup-filter>
<iqser-popup-filter *ngIf="!config.hide" [primaryFiltersSlug]="config.slug" [iqserHelpMode]="filterHelpModeKey"></iqser-popup-filter>
</ng-container>
<ng-container *ngIf="searchPosition === searchPositions.afterFilters" [ngTemplateOutlet]="searchBar"></ng-container>
@ -16,8 +16,8 @@
(click)="resetFilters()"
*ngIf="showResetFilters$ | async"
class="reset-filters"
iqserHelpMode="reset-filters"
translate="reset-filters"
[iqserHelpMode]="resetFiltersHelpModeKey"
></div>
</div>
@ -30,9 +30,9 @@
*ngIf="!config.hide"
[icon]="config.icon"
[id]="config.label.replace('.', '-')"
[iqserHelpMode]="config.helpModeKey"
[label]="config.label | translate"
[type]="config.type"
[iqserHelpMode]="config.helpModeKey"
></iqser-icon-button>
</ng-container>
@ -43,9 +43,9 @@
*ngIf="!config.hide"
[disabled]="config.disabled$ && (config.disabled$ | async)"
[icon]="config.icon"
[iqserHelpMode]="config.helpModeKey"
[tooltip]="config.label"
tooltipPosition="below"
[iqserHelpMode]="config.helpModeKey"
></iqser-circle-button>
</ng-container>
@ -59,6 +59,7 @@
[tooltip]="'common.close' | translate"
icon="iqser:close"
tooltipPosition="below"
iqserHelpMode="edit_dossier_in_dossier"
></iqser-circle-button>
</div>
</div>

View File

@ -25,7 +25,7 @@ export class PageHeaderComponent<T extends IListable> {
@Input() viewModeSelection?: TemplateRef<unknown>;
@Input() searchPlaceholder?: string;
@Input() searchWidth?: number | 'full';
@Input() helpModeKey?: 'filter-dossier-list' | 'filter-document-list';
@Input() helpModeKey?: 'dossier' | 'document';
@Input() searchPosition: SearchPosition = SearchPositions.afterFilters;
@Output() readonly closeAction = new EventEmitter();
@ -53,4 +53,12 @@ export class PageHeaderComponent<T extends IListable> {
trackByLabel<K extends { label?: string }>(index: number, item: K): string | undefined {
return item.label;
}
get filterHelpModeKey() {
return !!this.helpModeKey ? `filter_${this.helpModeKey}_list` : '';
}
get resetFiltersHelpModeKey() {
return this.helpModeKey === 'dossier' ? 'reset_filters' : `delete_document_filter`;
}
}

View File

@ -1,7 +1,7 @@
<button (click)="scroll(buttonType.top)" [hidden]="(showScrollUp$ | async) === false" class="scroll-button top pointer">
<button (click)="scroll(buttonType.top)" [hidden]="(showScrollUp$ | async) === false" class="scroll-button top pointer" iqserHelpMode="scroll_up_and_down" id="scroll-up">
<mat-icon svgIcon="iqser:arrow-down-o"></mat-icon>
</button>
<button (click)="scroll(buttonType.bottom)" [hidden]="(showScrollDown$ | async) === false" class="scroll-button bottom pointer">
<button (click)="scroll(buttonType.bottom)" [hidden]="(showScrollDown$ | async) === false" class="scroll-button bottom pointer" iqserHelpMode="scroll_up_and_down" id="scroll-down">
<mat-icon svgIcon="iqser:arrow-down-o"></mat-icon>
</button>

View File

@ -5,6 +5,7 @@
[maxBufferPx]="1500"
[minBufferPx]="300"
iqserHasScrollbar
id="virtual-scroll"
>
<ng-container *cdkVirtualFor="let entity of listingComponent.sortedDisplayedEntities$; trackBy: trackBy; templateCacheSize: 60">
<!-- mouseenter and mouseleave triggers change detection event if itemMouse functions are undefined -->

View File

@ -12,7 +12,7 @@
<ng-container [ngTemplateOutlet]="bulkActions"></ng-container>
<iqser-quick-filters *ngIf="quickFilters$ | async" iqserHelpMode="dossiers-quickfilter-my-dossiers"></iqser-quick-filters>
<iqser-quick-filters *ngIf="quickFilters$ | async"></iqser-quick-filters>
<!-- Custom content-->
<ng-content></ng-content>