add help mode
This commit is contained in:
parent
eef1e59fdd
commit
a935fb413b
@ -44,6 +44,7 @@
|
|||||||
"@angular-eslint/no-output-rename": "error",
|
"@angular-eslint/no-output-rename": "error",
|
||||||
"@angular-eslint/prefer-output-readonly": "error",
|
"@angular-eslint/prefer-output-readonly": "error",
|
||||||
"@typescript-eslint/unbound-method": "error",
|
"@typescript-eslint/unbound-method": "error",
|
||||||
|
"@typescript-eslint/no-floating-promises": "off",
|
||||||
"@typescript-eslint/lines-between-class-members": "off",
|
"@typescript-eslint/lines-between-class-members": "off",
|
||||||
"@typescript-eslint/naming-convention": [
|
"@typescript-eslint/naming-convention": [
|
||||||
"error",
|
"error",
|
||||||
|
|||||||
40
src/assets/styles/_dialogs.scss
Normal file
40
src/assets/styles/_dialogs.scss
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
@import 'apps/red-ui/src/assets/styles/variables';
|
||||||
|
|
||||||
|
.mat-dialog-container {
|
||||||
|
color: $accent;
|
||||||
|
padding: 0 !important;
|
||||||
|
border-radius: 8px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog {
|
||||||
|
position: relative;
|
||||||
|
min-height: 80px;
|
||||||
|
|
||||||
|
.dialog-close {
|
||||||
|
position: absolute;
|
||||||
|
top: 16px;
|
||||||
|
right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-header {
|
||||||
|
padding: 32px 60px 0 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-content {
|
||||||
|
padding: 24px 32px 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-actions {
|
||||||
|
height: 81px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-top: 1px solid $separator;
|
||||||
|
padding: 0 32px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
margin-right: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -27,3 +27,15 @@
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.heading {
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heading-l {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
|
|||||||
@ -3,3 +3,4 @@
|
|||||||
@import 'texts';
|
@import 'texts';
|
||||||
@import 'tables';
|
@import 'tables';
|
||||||
@import 'layout';
|
@import 'layout';
|
||||||
|
@import 'dialogs';
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
export * from './lib/common-ui.module';
|
export * from './lib/common-ui.module';
|
||||||
export * from './lib/buttons/icon-button/icon-button.type';
|
export * from './lib/buttons/icon-button/icon-button.type';
|
||||||
export * from './lib/buttons/icon-button/icon-button.component';
|
export * from './lib/buttons/icon-button/icon-button.component';
|
||||||
|
export * from './lib/utils/injection-tokens';
|
||||||
export * from './lib/utils/functions';
|
export * from './lib/utils/functions';
|
||||||
export * from './lib/utils/operators';
|
export * from './lib/utils/operators';
|
||||||
export * from './lib/utils/auto-unsubscribe.directive';
|
export * from './lib/utils/auto-unsubscribe.directive';
|
||||||
export * from './lib/utils/pipes/humanize.pipe';
|
export * from './lib/utils/pipes/humanize.pipe';
|
||||||
|
export * from './lib/utils/types/events.type';
|
||||||
export * from './lib/utils/types/utility-types';
|
export * from './lib/utils/types/utility-types';
|
||||||
export * from './lib/utils/types/tooltip-positions.type';
|
export * from './lib/utils/types/tooltip-positions.type';
|
||||||
export * from './lib/utils/decorators/bind.decorator';
|
export * from './lib/utils/decorators/bind.decorator';
|
||||||
@ -36,3 +38,7 @@ export * from './lib/misc/status-bar/status-bar-config.model';
|
|||||||
export * from './lib/inputs/round-checkbox/round-checkbox.component';
|
export * from './lib/inputs/round-checkbox/round-checkbox.component';
|
||||||
export * from './lib/inputs/editable-input/editable-input.component';
|
export * from './lib/inputs/editable-input/editable-input.component';
|
||||||
export * from './lib/inputs/input-with-action/input-with-action.component';
|
export * from './lib/inputs/input-with-action/input-with-action.component';
|
||||||
|
export * from './lib/help-mode/help-mode.service';
|
||||||
|
export * from './lib/help-mode/help-mode.directive';
|
||||||
|
export * from './lib/help-mode/help-mode/help-mode.component';
|
||||||
|
export * from './lib/help-mode/help-mode-dialog/help-mode-dialog.component';
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import { TranslateModule } from '@ngx-translate/core';
|
|||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
import { MatMenuModule } from '@angular/material/menu';
|
import { MatMenuModule } from '@angular/material/menu';
|
||||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||||
|
import { MatDialogModule } from '@angular/material/dialog';
|
||||||
import { IconButtonComponent } from './buttons/icon-button/icon-button.component';
|
import { IconButtonComponent } from './buttons/icon-button/icon-button.component';
|
||||||
import { ChevronButtonComponent } from './buttons/chevron-button/chevron-button.component';
|
import { ChevronButtonComponent } from './buttons/chevron-button/chevron-button.component';
|
||||||
import { CircleButtonComponent } from './buttons/circle-button/circle-button.component';
|
import { CircleButtonComponent } from './buttons/circle-button/circle-button.component';
|
||||||
@ -22,12 +23,15 @@ import { StatusBarComponent } from './misc/status-bar/status-bar.component';
|
|||||||
import { EditableInputComponent } from './inputs/editable-input/editable-input.component';
|
import { EditableInputComponent } from './inputs/editable-input/editable-input.component';
|
||||||
import { PopupFilterComponent } from './filtering/popup-filter/popup-filter.component';
|
import { PopupFilterComponent } from './filtering/popup-filter/popup-filter.component';
|
||||||
import { InputWithActionComponent } from './inputs/input-with-action/input-with-action.component';
|
import { InputWithActionComponent } from './inputs/input-with-action/input-with-action.component';
|
||||||
|
import { HelpModeDirective } from './help-mode/help-mode.directive';
|
||||||
|
import { HelpModeComponent } from './help-mode/help-mode/help-mode.component';
|
||||||
|
import { HelpModeDialogComponent } from './help-mode/help-mode-dialog/help-mode-dialog.component';
|
||||||
|
|
||||||
const buttons = [IconButtonComponent, ChevronButtonComponent, CircleButtonComponent];
|
const buttons = [IconButtonComponent, ChevronButtonComponent, CircleButtonComponent];
|
||||||
|
|
||||||
const inputs = [RoundCheckboxComponent, EditableInputComponent, InputWithActionComponent];
|
const inputs = [RoundCheckboxComponent, EditableInputComponent, InputWithActionComponent];
|
||||||
|
|
||||||
const matModules = [MatIconModule, MatButtonModule, MatTooltipModule, MatMenuModule, MatCheckboxModule];
|
const matModules = [MatIconModule, MatButtonModule, MatTooltipModule, MatMenuModule, MatCheckboxModule, MatDialogModule];
|
||||||
|
|
||||||
const modules = [...matModules, FormsModule, TranslateModule];
|
const modules = [...matModules, FormsModule, TranslateModule];
|
||||||
|
|
||||||
@ -38,10 +42,12 @@ const components = [
|
|||||||
QuickFiltersComponent,
|
QuickFiltersComponent,
|
||||||
PopupFilterComponent,
|
PopupFilterComponent,
|
||||||
TableHeaderComponent,
|
TableHeaderComponent,
|
||||||
StatusBarComponent
|
StatusBarComponent,
|
||||||
|
HelpModeComponent,
|
||||||
|
HelpModeDialogComponent
|
||||||
];
|
];
|
||||||
|
|
||||||
const utils = [SortByPipe, HumanizePipe, SyncWidthDirective];
|
const utils = [SortByPipe, HumanizePipe, SyncWidthDirective, HelpModeDirective];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [...components, ...utils],
|
declarations: [...components, ...utils],
|
||||||
|
|||||||
@ -0,0 +1,8 @@
|
|||||||
|
<section class="dialog">
|
||||||
|
<div class="content">
|
||||||
|
<p class="heading-l pre" [innerHTML]="'help-mode.welcome-to-help-mode' | translate"></p>
|
||||||
|
<img src="assets/illustrations/illustration.gif" alt="" width="335" />
|
||||||
|
<p class="pre" [innerHTML]="'help-mode.clicking-anywhere-on' | translate"></p>
|
||||||
|
</div>
|
||||||
|
<iqser-circle-button class="dialog-close" icon="iqser:close" mat-dialog-close></iqser-circle-button>
|
||||||
|
</section>
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
section {
|
||||||
|
background: #ecedf0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
width: 440px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
padding-top: 20px;
|
||||||
|
padding-bottom: 30px;
|
||||||
|
line-height: 18px;
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
templateUrl: './help-mode-dialog.component.html',
|
||||||
|
styleUrls: ['./help-mode-dialog.component.scss']
|
||||||
|
})
|
||||||
|
export class HelpModeDialogComponent {}
|
||||||
34
src/lib/help-mode/help-mode.directive.ts
Normal file
34
src/lib/help-mode/help-mode.directive.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { Directive, ElementRef, HostListener, Input, OnInit, Renderer2 } from '@angular/core';
|
||||||
|
import { HelpModeService } from './help-mode.service';
|
||||||
|
|
||||||
|
@Directive({
|
||||||
|
selector: '[iqserHelpMode]',
|
||||||
|
exportAs: 'iqserHelpMode'
|
||||||
|
})
|
||||||
|
export class HelpModeDirective implements OnInit {
|
||||||
|
@Input('iqserHelpMode') elementName!: string;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly _elementRef: ElementRef,
|
||||||
|
private readonly _renderer: Renderer2,
|
||||||
|
private readonly _helpModeService: HelpModeService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this._createHelperElement();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _createHelperElement() {
|
||||||
|
const element = this._elementRef.nativeElement as HTMLElement;
|
||||||
|
|
||||||
|
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(this.elementName, element, helperElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('click') onClick(): void {
|
||||||
|
this._helpModeService.openDocsFor(this.elementName);
|
||||||
|
}
|
||||||
|
}
|
||||||
91
src/lib/help-mode/help-mode.service.ts
Normal file
91
src/lib/help-mode/help-mode.service.ts
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
import { Inject, Injectable, Renderer2, RendererFactory2 } from '@angular/core';
|
||||||
|
import { MatDialog, MatDialogRef } from '@angular/material/dialog';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { HelpModeDialogComponent } from './help-mode-dialog/help-mode-dialog.component';
|
||||||
|
import { HELP_DOCS } from '../utils/injection-tokens';
|
||||||
|
|
||||||
|
interface Helper {
|
||||||
|
readonly element: HTMLElement;
|
||||||
|
readonly helperElement: HTMLElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class HelpModeService {
|
||||||
|
isHelpModeActive = false;
|
||||||
|
helpModeDialogIsOpened = false;
|
||||||
|
|
||||||
|
private readonly _elements: Record<string, Helper> = {};
|
||||||
|
private readonly _renderer: Renderer2;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(HELP_DOCS) private readonly _docs: Record<string, Record<string, string>>,
|
||||||
|
private readonly _dialog: MatDialog,
|
||||||
|
private readonly _rendererFactory: RendererFactory2,
|
||||||
|
private readonly _translateService: TranslateService
|
||||||
|
) {
|
||||||
|
this._renderer = this._rendererFactory.createRenderer(null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
openHelpModeDialog(): MatDialogRef<HelpModeDialogComponent> {
|
||||||
|
this.helpModeDialogIsOpened = true;
|
||||||
|
|
||||||
|
const ref = this._dialog.open(HelpModeDialogComponent, {
|
||||||
|
width: '600px'
|
||||||
|
});
|
||||||
|
|
||||||
|
ref.afterClosed()
|
||||||
|
.toPromise()
|
||||||
|
.then(() => {
|
||||||
|
this.helpModeDialogIsOpened = false;
|
||||||
|
});
|
||||||
|
return ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
openDocsFor(elementName: string): void {
|
||||||
|
if (this.isHelpModeActive) {
|
||||||
|
window.open(this._docs[elementName][this._translateService.currentLang]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
activateHelpMode(): void {
|
||||||
|
this.isHelpModeActive = true;
|
||||||
|
this.openHelpModeDialog();
|
||||||
|
this._enableHelperElements();
|
||||||
|
}
|
||||||
|
|
||||||
|
deactivateHelpMode(): void {
|
||||||
|
this.isHelpModeActive = false;
|
||||||
|
this._disableHelperElements();
|
||||||
|
}
|
||||||
|
|
||||||
|
highlightHelperElements(): void {
|
||||||
|
if (!this.isHelpModeActive) return;
|
||||||
|
|
||||||
|
Object.values(this._elements).forEach(({ helperElement }) => {
|
||||||
|
this._renderer.addClass(helperElement, 'highlight');
|
||||||
|
setTimeout(() => {
|
||||||
|
this._renderer.removeClass(helperElement, 'highlight');
|
||||||
|
}, 500);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addElement(elementName: string, element: HTMLElement, helperElement: HTMLElement): void {
|
||||||
|
this._elements[elementName] = { element, helperElement };
|
||||||
|
}
|
||||||
|
|
||||||
|
private _enableHelperElements() {
|
||||||
|
Object.values(this._elements).forEach(({ element, helperElement }) => {
|
||||||
|
this._renderer.setStyle(element, 'position', 'relative');
|
||||||
|
this._renderer.appendChild(element, helperElement);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _disableHelperElements() {
|
||||||
|
Object.values(this._elements).forEach(({ element, helperElement }) => {
|
||||||
|
this._renderer.removeStyle(element, 'position');
|
||||||
|
this._renderer.removeChild(element, helperElement);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/lib/help-mode/help-mode/help-mode.component.html
Normal file
20
src/lib/help-mode/help-mode/help-mode.component.html
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<div class="help-button" *ngIf="!helpModeService.isHelpModeActive" (click)="helpModeService.activateHelpMode()">
|
||||||
|
<mat-icon svgIcon="red:help-outline"></mat-icon>
|
||||||
|
<div class="text">{{ 'help-mode.button-text' | translate }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="help-mode-border" *ngIf="helpModeService.isHelpModeActive">
|
||||||
|
<div class="bottom">
|
||||||
|
<p class="heading">{{ 'help-mode.text' | translate }}</p>
|
||||||
|
<a class="instructions" *ngIf="!helpModeService.helpModeDialogIsOpened" (click)="helpModeService.openHelpModeDialog()">
|
||||||
|
{{ 'help-mode.instructions' | translate }}
|
||||||
|
</a>
|
||||||
|
<div class="close">
|
||||||
|
(esc)
|
||||||
|
<iqser-circle-button
|
||||||
|
class="dialog-close"
|
||||||
|
icon="iqser:close"
|
||||||
|
(click)="helpModeService.deactivateHelpMode()"
|
||||||
|
></iqser-circle-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
72
src/lib/help-mode/help-mode/help-mode.component.scss
Normal file
72
src/lib/help-mode/help-mode/help-mode.component.scss
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
@import '../../../../../../apps/red-ui/src/assets/styles/variables';
|
||||||
|
|
||||||
|
.help-button {
|
||||||
|
width: 44px;
|
||||||
|
height: 40px;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 20px;
|
||||||
|
right: 0;
|
||||||
|
z-index: 1;
|
||||||
|
background: $green-2;
|
||||||
|
border-top-left-radius: 8px;
|
||||||
|
border-bottom-left-radius: 8px;
|
||||||
|
box-shadow: -1px 1px 5px 0 rgba(40, 50, 65, 0.25);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.25s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-button:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
width: fit-content;
|
||||||
|
padding-left: 10px;
|
||||||
|
padding-right: 10px;
|
||||||
|
|
||||||
|
.text {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-icon {
|
||||||
|
padding-right: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-mode-border {
|
||||||
|
box-sizing: border-box;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
border-left: 8px solid $green-2;
|
||||||
|
border-right: 8px solid $green-2;
|
||||||
|
border-top: 8px solid $green-2;
|
||||||
|
border-bottom: 60px solid $green-2;
|
||||||
|
z-index: 10;
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.bottom {
|
||||||
|
position: fixed;
|
||||||
|
height: 60px;
|
||||||
|
width: 95%;
|
||||||
|
bottom: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
pointer-events: visiblePainted;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: black;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/lib/help-mode/help-mode/help-mode.component.ts
Normal file
29
src/lib/help-mode/help-mode/help-mode.component.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { Component, HostListener } from '@angular/core';
|
||||||
|
import { HelpModeService } from '../help-mode.service';
|
||||||
|
import { IqserEventTarget } from '../../utils/types/events.type';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'iqser-help-mode',
|
||||||
|
templateUrl: './help-mode.component.html',
|
||||||
|
styleUrls: ['./help-mode.component.scss']
|
||||||
|
})
|
||||||
|
export class HelpModeComponent {
|
||||||
|
constructor(readonly helpModeService: HelpModeService) {}
|
||||||
|
|
||||||
|
@HostListener('document:keydown.escape') onEscKeydownHandler(): void {
|
||||||
|
if (!this.helpModeService.helpModeDialogIsOpened) {
|
||||||
|
this.helpModeService.deactivateHelpMode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('document:keydown.h', ['$event']) onHKeydownHandler(event: KeyboardEvent): void {
|
||||||
|
const node = (event.target as IqserEventTarget).localName;
|
||||||
|
if (!this.helpModeService.isHelpModeActive && node !== 'input' && node !== 'textarea') {
|
||||||
|
this.helpModeService.activateHelpMode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('click') onClick(): void {
|
||||||
|
this.helpModeService.highlightHelperElements();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -17,7 +17,7 @@ export class InputWithActionComponent {
|
|||||||
@Output() readonly valueChange = new EventEmitter<string>();
|
@Output() readonly valueChange = new EventEmitter<string>();
|
||||||
|
|
||||||
get hasContent(): boolean {
|
get hasContent(): boolean {
|
||||||
return !!this.value.length;
|
return !!this.value?.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
get computedWidth(): string {
|
get computedWidth(): string {
|
||||||
|
|||||||
3
src/lib/utils/injection-tokens.ts
Normal file
3
src/lib/utils/injection-tokens.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { InjectionToken } from '@angular/core';
|
||||||
|
|
||||||
|
export const HELP_DOCS = new InjectionToken<Record<string, Record<string, string>>>('Links to user manual or help docs');
|
||||||
3
src/lib/utils/types/events.type.ts
Normal file
3
src/lib/utils/types/events.type.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export interface IqserEventTarget extends EventTarget {
|
||||||
|
localName: string;
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user