migrate control flow

This commit is contained in:
Dan Percic 2024-06-18 10:22:27 +03:00
parent 0b64044f57
commit f60ea513ac
49 changed files with 901 additions and 784 deletions

View File

@ -6,7 +6,7 @@ module.exports = {
globals: { globals: {
NodeJS: true, NodeJS: true,
}, },
ignorePatterns: ['!**/*'], ignorePatterns: ['!**/*', 'jest.config.ts'],
overrides: [ overrides: [
{ {
files: ['*.ts'], files: ['*.ts'],

View File

@ -1,30 +1,38 @@
<section class="dialog"> <section class="dialog">
<div [class.warn]="isDeleteAction" [innerHTML]="config.title" class="dialog-header heading-l"></div> <div [class.warn]="isDeleteAction" [innerHTML]="config.title" class="dialog-header heading-l"></div>
<div *ngIf="showToast && config.toastMessage" class="inline-dialog-toast toast-error"> @if (showToast && config.toastMessage) {
<div [translate]="config.toastMessage"></div> <div class="inline-dialog-toast toast-error">
<a (click)="showToast = false" class="toast-close-button"> <div [translate]="config.toastMessage"></div>
<mat-icon svgIcon="iqser:close"></mat-icon> <a (click)="showToast = false" class="toast-close-button">
</a> <mat-icon svgIcon="iqser:close"></mat-icon>
</div> </a>
</div>
}
<div class="dialog-content"> <div class="dialog-content">
<p [class.heading]="isDeleteAction" [innerHTML]="config.question" class="mt-0 mb-8"></p> <p [class.heading]="isDeleteAction" [innerHTML]="config.question" class="mt-0 mb-8"></p>
<p *ngIf="config.details" [innerHTML]="config.details" class="mt-0"></p> @if (config.details) {
<p [innerHTML]="config.details" class="mt-0"></p>
}
<div *ngIf="config.requireInput" class="iqser-input-group required w-300 mt-24"> @if (config.requireInput) {
<label>{{ inputLabel }}</label> <div class="iqser-input-group required w-300 mt-24">
<input [(ngModel)]="inputValue" id="confirmation-input" /> <label>{{ inputLabel }}</label>
</div> <input [(ngModel)]="inputValue" id="confirmation-input" />
</div>
}
<div *ngIf="config.checkboxes.length > 0" class="mt-24 checkboxes-wrapper"> @if (config.checkboxes.length > 0) {
<ng-container *ngFor="let checkbox of config.checkboxes"> <div class="mt-24 checkboxes-wrapper">
<mat-checkbox [(ngModel)]="checkbox.value" [class.error]="!checkbox.value && showToast" color="primary"> @for (checkbox of config.checkboxes; track checkbox) {
{{ checkbox.label | translate: config.translateParams }} <mat-checkbox [(ngModel)]="checkbox.value" [class.error]="!checkbox.value && showToast" color="primary">
</mat-checkbox> {{ checkbox.label | translate: config.translateParams }}
<ng-container *ngTemplateOutlet="checkbox.extraContent; context: { data: checkbox.extraContentData }"></ng-container> </mat-checkbox>
</ng-container> <ng-container *ngTemplateOutlet="checkbox.extraContent; context: { data: checkbox.extraContentData }"></ng-container>
</div> }
</div>
}
</div> </div>
<div class="dialog-actions"> <div class="dialog-actions">
@ -36,21 +44,26 @@
buttonId="confirm" buttonId="confirm"
></iqser-icon-button> ></iqser-icon-button>
<iqser-icon-button @if (config.alternativeConfirmationText) {
(action)="confirm(confirmOptions.CONFIRM_WITH_ACTION)" <iqser-icon-button
*ngIf="config.alternativeConfirmationText" (action)="confirm(confirmOptions.CONFIRM_WITH_ACTION)"
[disabled]="config.requireInput && confirmationDoesNotMatch()" [disabled]="config.requireInput && confirmationDoesNotMatch()"
[label]="config.alternativeConfirmationText" [label]="config.alternativeConfirmationText"
[type]="iconButtonTypes.primary" [type]="iconButtonTypes.primary"
></iqser-icon-button> ></iqser-icon-button>
}
<div (click)="confirm(confirmOptions.DISCARD_CHANGES)" *ngIf="config.discardChangesText" class="all-caps-label cancel"> @if (config.discardChangesText) {
{{ config.discardChangesText }} <div (click)="confirm(confirmOptions.DISCARD_CHANGES)" class="all-caps-label cancel">
</div> {{ config.discardChangesText }}
</div>
}
<div (click)="deny()" *ngIf="!config.discardChangesText" class="all-caps-label cancel"> @if (!config.discardChangesText) {
{{ config.denyText }} <div (click)="deny()" class="all-caps-label cancel">
</div> {{ config.denyText }}
</div>
}
</div> </div>
<iqser-circle-button class="dialog-close" icon="iqser:close" mat-dialog-close></iqser-circle-button> <iqser-circle-button class="dialog-close" icon="iqser:close" mat-dialog-close></iqser-circle-button>

View File

@ -1,4 +1,4 @@
import { NgForOf, NgIf, NgTemplateOutlet } from '@angular/common'; import { NgTemplateOutlet } from '@angular/common';
import { ChangeDetectionStrategy, Component, HostListener, inject, TemplateRef } from '@angular/core'; import { ChangeDetectionStrategy, Component, HostListener, inject, TemplateRef } from '@angular/core';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatCheckboxModule } from '@angular/material/checkbox';
@ -72,10 +72,8 @@ function getConfig(options?: IConfirmationDialogData): InternalConfirmationDialo
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true, standalone: true,
imports: [ imports: [
NgIf,
MatIconModule, MatIconModule,
FormsModule, FormsModule,
NgForOf,
MatCheckboxModule, MatCheckboxModule,
TranslateModule, TranslateModule,
NgTemplateOutlet, NgTemplateOutlet,

View File

@ -6,7 +6,9 @@
}" }"
class="empty-state" class="empty-state"
> >
<mat-icon *ngIf="icon" [svgIcon]="icon"></mat-icon> @if (icon) {
<mat-icon [svgIcon]="icon"></mat-icon>
}
<div class="ng-content-wrapper heading-l"> <div class="ng-content-wrapper heading-l">
<ng-content></ng-content> <ng-content></ng-content>
@ -14,13 +16,14 @@
<div [innerHTML]="text" class="heading-l"></div> <div [innerHTML]="text" class="heading-l"></div>
<iqser-icon-button @if (showButton) {
(action)="action.emit()" <iqser-icon-button
*ngIf="showButton" (action)="action.emit()"
[buttonId]="buttonId" [buttonId]="buttonId"
[icon]="buttonIcon" [icon]="buttonIcon"
[attr.help-mode-key]="helpModeKey" [attr.help-mode-key]="helpModeKey"
[label]="buttonLabel" [label]="buttonLabel"
[type]="iconButtonTypes.primary" [type]="iconButtonTypes.primary"
></iqser-icon-button> ></iqser-icon-button>
}
</div> </div>

View File

@ -1,7 +1,7 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { IconButtonComponent, IconButtonTypes } from '../buttons'; import { IconButtonComponent, IconButtonTypes } from '../buttons';
import { randomString } from '../utils'; import { randomString } from '../utils';
import { NgIf, NgStyle } from '@angular/common'; import { NgStyle } from '@angular/common';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
@Component({ @Component({
@ -10,7 +10,7 @@ import { MatIconModule } from '@angular/material/icon';
styleUrls: ['./empty-state.component.scss'], styleUrls: ['./empty-state.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true, standalone: true,
imports: [NgStyle, MatIconModule, NgIf, IconButtonComponent], imports: [NgStyle, MatIconModule, IconButtonComponent],
}) })
export class EmptyStateComponent implements OnInit { export class EmptyStateComponent implements OnInit {
readonly iconButtonTypes = IconButtonTypes; readonly iconButtonTypes = IconButtonTypes;

View File

@ -1,8 +1,5 @@
<div @if (errorService.connectionStatus$ | async; as status) {
*ngIf="errorService.connectionStatus$ | async as status" <div [@animateOpenClose]="status" [ngClass]="status" class="indicator flex-align-items-center">
[@animateOpenClose]="status" <span [translate]="connectionStatusTranslations[status]"></span>
[ngClass]="status" </div>
class="indicator flex-align-items-center" }
>
<span [translate]="connectionStatusTranslations[status]"></span>
</div>

View File

@ -1,13 +1,11 @@
<ng-container *ngIf="errorService.error$ | async as error"> @if (errorService.error$ | async; as error) {
<section class="full-page-section"></section> <section class="full-page-section"></section>
<section class="full-page-content flex-align-items-center"> <section class="full-page-content flex-align-items-center">
<mat-icon svgIcon="iqser:failure"></mat-icon> <mat-icon svgIcon="iqser:failure"></mat-icon>
<div [translate]="errorTitle(error)" class="heading-l mt-24"></div> <div [translate]="errorTitle(error)" class="heading-l mt-24"></div>
@if (error.message) {
<div *ngIf="error.message" class="mt-16 error">{{ error.message }}</div> <div class="mt-16 error">{{ error.message }}</div>
}
<iqser-icon-button <iqser-icon-button
(action)="action(error)" (action)="action(error)"
[icon]="actionIcon(error)" [icon]="actionIcon(error)"
@ -16,4 +14,4 @@
class="mt-20" class="mt-20"
></iqser-icon-button> ></iqser-icon-button>
</section> </section>
</ng-container> }

View File

@ -1,43 +1,47 @@
<ng-container *ngIf="primaryFilterGroup$ | async as primaryGroup"> @if (primaryFilterGroup$ | async; as primaryGroup) {
<div *ngIf="primaryGroup.filterceptionPlaceholder" class="input-wrapper"> @if (primaryGroup.filterceptionPlaceholder) {
<iqser-input-with-action <div class="input-wrapper">
[(value)]="searchService.searchValue" <iqser-input-with-action
[id]="'filterception-' + primaryGroup.slug" [(value)]="searchService.searchValue"
[placeholder]="primaryGroup.filterceptionPlaceholder" [id]="'filterception-' + primaryGroup.slug"
[width]="'full'" [placeholder]="primaryGroup.filterceptionPlaceholder"
></iqser-input-with-action> [width]="'full'"
</div> ></iqser-input-with-action>
<ng-container *ngTemplateOutlet="filterHeader"></ng-container>
<div *ngIf="primaryFilters$ | async as filters" class="filter-content">
<ng-container
*ngFor="let filter of filters"
[ngTemplateOutletContext]="{
filter: filter,
filterGroup: primaryGroup,
atLeastOneIsExpandable: atLeastOneFilterIsExpandable$ | async,
}"
[ngTemplateOutlet]="defaultFilterTemplate"
></ng-container>
</div>
<div *ngIf="secondaryFilterGroup$ | async as secondaryGroup" class="filter-options">
<div class="filter-menu-options">
<div class="all-caps-label" translate="filter-menu.filter-options"></div>
</div> </div>
}
<ng-container <ng-container *ngTemplateOutlet="filterHeader"></ng-container>
*ngFor="let filter of secondaryGroup.filters" @if (primaryFilters$ | async; as filters) {
[ngTemplateOutletContext]="{ <div class="filter-content">
filter: filter, @for (filter of filters; track filter) {
filterGroup: secondaryGroup, <ng-container
atLeastOneIsExpandable: atLeastOneSecondaryFilterIsExpandable$ | async, [ngTemplateOutletContext]="{
}" filter: filter,
[ngTemplateOutlet]="defaultFilterTemplate" filterGroup: primaryGroup,
></ng-container> atLeastOneIsExpandable: atLeastOneFilterIsExpandable$ | async,
</div> }"
</ng-container> [ngTemplateOutlet]="defaultFilterTemplate"
></ng-container>
}
</div>
}
@if (secondaryFilterGroup$ | async; as secondaryGroup) {
<div class="filter-options">
<div class="filter-menu-options">
<div class="all-caps-label" translate="filter-menu.filter-options"></div>
</div>
@for (filter of secondaryGroup.filters; track filter) {
<ng-container
[ngTemplateOutletContext]="{
filter: filter,
filterGroup: secondaryGroup,
atLeastOneIsExpandable: atLeastOneSecondaryFilterIsExpandable$ | async,
}"
[ngTemplateOutlet]="defaultFilterTemplate"
></ng-container>
}
</div>
}
}
<ng-template #defaultFilterLabelTemplate let-filter="filter"> <ng-template #defaultFilterLabelTemplate let-filter="filter">
{{ filter?.label }} {{ filter?.label }}
@ -45,30 +49,46 @@
<!--TODO: move to separate component--> <!--TODO: move to separate component-->
<ng-template #filterHeader> <ng-template #filterHeader>
<div *ngIf="primaryFilterGroup$ | async as primaryGroup" class="filter-menu-header"> @if (primaryFilterGroup$ | async; as primaryGroup) {
<div [translateParams]="{ count: primaryGroup.filters.length }" [translate]="primaryFiltersLabel" class="all-caps-label"></div> <div class="filter-menu-header">
<div class="actions"> <div [translateParams]="{ count: primaryGroup.filters.length }" [translate]="primaryFiltersLabel" class="all-caps-label"></div>
<div <div class="actions">
(click)="activatePrimaryFilters()" @if (!primaryGroup.singleSelect) {
*ngIf="!primaryGroup.singleSelect" <div
class="all-caps-label primary pointer" (click)="activatePrimaryFilters()"
iqserStopPropagation class="all-caps-label primary pointer"
translate="actions.all" iqserStopPropagation
></div> translate="actions.all"
<div (click)="deactivateFilters()" class="all-caps-label primary pointer" iqserStopPropagation translate="actions.none"></div> ></div>
}
<div
(click)="deactivateFilters()"
class="all-caps-label primary pointer"
iqserStopPropagation
translate="actions.none"
></div>
</div>
</div> </div>
</div> }
</ng-template> </ng-template>
<!--TODO: move to separate component--> <!--TODO: move to separate component-->
<ng-template #defaultFilterTemplate let-atLeastOneIsExpandable="atLeastOneIsExpandable" let-filter="filter" let-filterGroup="filterGroup"> <ng-template #defaultFilterTemplate let-atLeastOneIsExpandable="atLeastOneIsExpandable" let-filter="filter" let-filterGroup="filterGroup">
<div (click)="toggleFilterExpanded(filter)" class="mat-mdc-menu-item flex" iqserStopPropagation> <div (click)="toggleFilterExpanded(filter)" class="mat-mdc-menu-item flex" iqserStopPropagation>
<div *ngIf="filter.children?.length > 0" class="arrow-wrapper"> @if (filter.children?.length > 0) {
<mat-icon *ngIf="filter.expanded" color="accent" svgIcon="iqser:arrow-down"></mat-icon> <div class="arrow-wrapper">
<mat-icon *ngIf="!filter.expanded" color="accent" svgIcon="iqser:arrow-right"></mat-icon> @if (filter.expanded) {
</div> <mat-icon color="accent" svgIcon="iqser:arrow-down"></mat-icon>
}
@if (!filter.expanded) {
<mat-icon color="accent" svgIcon="iqser:arrow-right"></mat-icon>
}
</div>
}
<div *ngIf="atLeastOneIsExpandable && filter.children?.length === 0" class="arrow-wrapper spacer">&nbsp;</div> @if (atLeastOneIsExpandable && filter.children?.length === 0) {
<div class="arrow-wrapper spacer">&nbsp;</div>
}
<mat-checkbox <mat-checkbox
(click)="filterCheckboxClicked(filter, filterGroup)" (click)="filterCheckboxClicked(filter, filterGroup)"
@ -87,16 +107,23 @@
<ng-container [ngTemplateOutletContext]="{ filter: filter }" [ngTemplateOutlet]="actionsTemplate"></ng-container> <ng-container [ngTemplateOutletContext]="{ filter: filter }" [ngTemplateOutlet]="actionsTemplate"></ng-container>
</div> </div>
<div *ngIf="filter.children?.length && filter.expanded"> @if (filter.children?.length && filter.expanded) {
<div *ngFor="let child of filter.children" class="padding-left mat-mdc-menu-item" iqserStopPropagation> <div>
<mat-checkbox (click)="filterCheckboxClicked(child, filterGroup, filter)" [checked]="child.checked" iqserStopPropagation> @for (child of filter.children; track child) {
<ng-container <div class="padding-left mat-mdc-menu-item" iqserStopPropagation>
[ngTemplateOutletContext]="{ filter: child }" <mat-checkbox
[ngTemplateOutlet]="filterGroup.filterTemplate ?? defaultFilterLabelTemplate" (click)="filterCheckboxClicked(child, filterGroup, filter)"
></ng-container> [checked]="child.checked"
</mat-checkbox> iqserStopPropagation
>
<ng-container [ngTemplateOutletContext]="{ filter: child }" [ngTemplateOutlet]="actionsTemplate"></ng-container> <ng-container
[ngTemplateOutletContext]="{ filter: child }"
[ngTemplateOutlet]="filterGroup.filterTemplate ?? defaultFilterLabelTemplate"
></ng-container>
</mat-checkbox>
<ng-container [ngTemplateOutletContext]="{ filter: child }" [ngTemplateOutlet]="actionsTemplate"></ng-container>
</div>
}
</div> </div>
</div> }
</ng-template> </ng-template>

View File

@ -1,5 +1,5 @@
<ng-container *ngIf="primaryFilterGroup$ | async as primaryGroup"> @if (primaryFilterGroup$ | async; as primaryGroup) {
<ng-container *ngIf="primaryGroup.icon"> @if (primaryGroup.icon) {
<iqser-icon-button <iqser-icon-button
[attr.aria-expanded]="expanded$ | async" [attr.aria-expanded]="expanded$ | async"
[class.disabled]="primaryFiltersDisabled$ | async" [class.disabled]="primaryFiltersDisabled$ | async"
@ -10,9 +10,8 @@
[showDot]="hasActiveFilters$ | async" [showDot]="hasActiveFilters$ | async"
buttonId="{{ primaryGroup.slug }}" buttonId="{{ primaryGroup.slug }}"
></iqser-icon-button> ></iqser-icon-button>
</ng-container> }
@if (!primaryGroup.icon) {
<ng-container *ngIf="!primaryGroup.icon">
<iqser-chevron-button <iqser-chevron-button
[attr.aria-expanded]="expanded$ | async" [attr.aria-expanded]="expanded$ | async"
[class.disabled]="primaryFiltersDisabled$ | async" [class.disabled]="primaryFiltersDisabled$ | async"
@ -21,8 +20,7 @@
[matMenuTriggerFor]="filterMenu" [matMenuTriggerFor]="filterMenu"
[showDot]="hasActiveFilters$ | async" [showDot]="hasActiveFilters$ | async"
></iqser-chevron-button> ></iqser-chevron-button>
</ng-container> }
<mat-menu <mat-menu
#filterMenu="matMenu" #filterMenu="matMenu"
(closed)="expanded.next(false)" (closed)="expanded.next(false)"
@ -41,4 +39,4 @@
</ng-template> </ng-template>
</div> </div>
</mat-menu> </mat-menu>
</ng-container> }

View File

@ -1,12 +1,13 @@
<ng-container *ngIf="quickFilters$ | async as filters"> @if (quickFilters$ | async; as filters) {
<div @for (filter of filters; track filter) {
(click)="filterService.toggleFilter('quickFilters', filter.id)" <div
*ngFor="let filter of filters" (click)="filterService.toggleFilter('quickFilters', filter.id)"
[class.active]="filter.checked" [class.active]="filter.checked"
[class.disabled]="filter.disabled" [class.disabled]="filter.disabled"
class="quick-filter" class="quick-filter"
[attr.help-mode-key]="filter.helpModeKey" [attr.help-mode-key]="filter.helpModeKey"
> >
{{ filter.label }} {{ filter.label }}
</div> </div>
</ng-container> }
}

View File

@ -1,48 +1,51 @@
<iqser-icon-button @if (type() === 'text' && icon()) {
*ngIf="type() === 'text' && icon()" <iqser-icon-button
[attr.aria-expanded]="expanded()" [attr.aria-expanded]="expanded()"
[class.disabled]="disabled()" [class.disabled]="disabled()"
[disabled]="disabled()" [disabled]="disabled()"
[icon]="icon()" [icon]="icon()"
[label]="label()" [label]="label()"
[matMenuTriggerFor]="filterMenu" [matMenuTriggerFor]="filterMenu"
[showDot]="hasActiveFilters()" [showDot]="hasActiveFilters()"
></iqser-icon-button> ></iqser-icon-button>
}
<iqser-chevron-button @if (type() === 'text' && !icon()) {
[attr.aria-expanded]="expanded()" <iqser-chevron-button
[class.disabled]="disabled()" [attr.aria-expanded]="expanded()"
[disabled]="disabled()" [class.disabled]="disabled()"
[label]="label()" [disabled]="disabled()"
[matMenuTriggerFor]="filterMenu" [label]="label()"
[showDot]="hasActiveFilters()" [matMenuTriggerFor]="filterMenu"
*ngIf="type() === 'text' && !icon()" [showDot]="hasActiveFilters()"
></iqser-chevron-button> ></iqser-chevron-button>
}
<iqser-circle-button @if (type() === 'icon') {
[attr.aria-expanded]="expanded()" <iqser-circle-button
[class.disabled]="disabled()" [attr.aria-expanded]="expanded()"
[disabled]="disabled()" [class.disabled]="disabled()"
[matMenuTriggerFor]="filterMenu" [disabled]="disabled()"
[showDot]="hasActiveFilters()" [matMenuTriggerFor]="filterMenu"
[icon]="icon() || 'iqser:filter-list'" [showDot]="hasActiveFilters()"
*ngIf="type() === 'icon'" [icon]="icon() || 'iqser:filter-list'"
></iqser-circle-button> ></iqser-circle-button>
}
<mat-menu #filterMenu="matMenu" (closed)="expanded.set(false)" xPosition="before"> <mat-menu #filterMenu="matMenu" (closed)="expanded.set(false)" xPosition="before">
<div iqserStopPropagation> <div iqserStopPropagation>
<ng-template matMenuContent> <ng-template matMenuContent>
<div class="input-wrapper"> <div class="input-wrapper">
<iqser-input-with-action <iqser-input-with-action
[value]="searchValue()"
(valueChange)="searchValue.set($event)" (valueChange)="searchValue.set($event)"
[placeholder]="filterPlaceholder()" [placeholder]="filterPlaceholder()"
[value]="searchValue()"
[width]="'full'" [width]="'full'"
></iqser-input-with-action> ></iqser-input-with-action>
</div> </div>
<div class="filter-menu-header"> <div class="filter-menu-header">
<div translate="filter-menu.label" class="all-caps-label"></div> <div class="all-caps-label" translate="filter-menu.label"></div>
<div class="actions"> <div class="actions">
<div (click)="_selectAll()" class="all-caps-label primary pointer" iqserStopPropagation translate="actions.all"></div> <div (click)="_selectAll()" class="all-caps-label primary pointer" iqserStopPropagation translate="actions.all"></div>
<div (click)="_clear()" class="all-caps-label primary pointer" iqserStopPropagation translate="actions.none"></div> <div (click)="_clear()" class="all-caps-label primary pointer" iqserStopPropagation translate="actions.none"></div>
@ -50,11 +53,13 @@
</div> </div>
<div class="filter-content"> <div class="filter-content">
<div mat-menu-item *ngFor="let option of displayedOptions()" (click)="_filterCheckboxClicked(option)"> @for (option of displayedOptions(); track option) {
<mat-checkbox [checked]="selectedOptions().includes(option)" class="filter-menu-checkbox"> <div mat-menu-item (click)="_filterCheckboxClicked(option)">
<span class="clamp-1">{{ option.label }}</span> <mat-checkbox [checked]="selectedOptions().includes(option)" class="filter-menu-checkbox">
</mat-checkbox> <span class="clamp-1">{{ option.label }}</span>
</div> </mat-checkbox>
</div>
}
</div> </div>
</ng-template> </ng-template>
</div> </div>

View File

@ -1,6 +1,6 @@
import { Component, computed, effect, input, output, signal, untracked } from '@angular/core'; import { Component, computed, effect, input, output, signal, untracked } from '@angular/core';
import { MatMenuModule } from '@angular/material/menu'; import { MatMenuModule } from '@angular/material/menu';
import { CommonModule } from '@angular/common';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { MatCheckbox } from '@angular/material/checkbox'; import { MatCheckbox } from '@angular/material/checkbox';
import { ChevronButtonComponent, CircleButtonComponent, IconButtonComponent } from '../../buttons'; import { ChevronButtonComponent, CircleButtonComponent, IconButtonComponent } from '../../buttons';
@ -14,7 +14,6 @@ import { SimpleFilterOption } from '../models/simple-filter-option';
styleUrls: ['./simple-popup-filter.component.scss'], styleUrls: ['./simple-popup-filter.component.scss'],
standalone: true, standalone: true,
imports: [ imports: [
CommonModule,
MatMenuModule, MatMenuModule,
IconButtonComponent, IconButtonComponent,
ChevronButtonComponent, ChevronButtonComponent,

View File

@ -1,19 +1,23 @@
<div *ngIf="helpModeService.isHelpModeActive$ | async"> @if (helpModeService.isHelpModeActive$ | async) {
<div class="help-mode-border"></div> <div>
<div class="bottom small-label full-opacity"> <div class="help-mode-border"></div>
<strong>{{ 'help-mode.bottom-text' | translate }}</strong> <div class="bottom small-label full-opacity">
<a (click)="helpModeService.openHelpModeDialog()" *ngIf="(helpModeService.helpModeDialogIsOpened$ | async) === false"> <strong>{{ 'help-mode.bottom-text' | translate }}</strong>
{{ 'help-mode.instructions' | translate }} @if ((helpModeService.helpModeDialogIsOpened$ | async) === false) {
</a> <a (click)="helpModeService.openHelpModeDialog()">
<div class="close"> {{ 'help-mode.instructions' | translate }}
(esc) </a>
<iqser-circle-button }
(click)="helpModeService.deactivateHelpMode()" <div class="close">
[iconSize]="10" (esc)
[size]="20" <iqser-circle-button
class="ml-8" (click)="helpModeService.deactivateHelpMode()"
icon="iqser:close" [iconSize]="10"
></iqser-circle-button> [size]="20"
class="ml-8"
icon="iqser:close"
></iqser-circle-button>
</div>
</div> </div>
</div> </div>
</div> }

View File

@ -1,50 +1,52 @@
<div [class.row]="displayInRow" class="iqser-input-group"> <div [class.row]="displayInRow" class="iqser-input-group">
<div @for (option of options; track option) {
(click)="toggleOption(option)" <div
*ngFor="let option of options" (click)="toggleOption(option)"
[class.active]="option.value === value?.value" [class.active]="option.value === value?.value"
[class.disabled]="option.disabled" [class.disabled]="option.disabled"
[id]="groupId(option)" [id]="groupId(option)"
[matTooltipPosition]="'above'" [matTooltipPosition]="'above'"
[matTooltip]="option.tooltip || '' | translate" [matTooltip]="option.tooltip || '' | translate"
[ngClass]="{ 'mb-8': !displayInRow, 'mr-8': displayInRow }" [ngClass]="{ 'mb-8': !displayInRow, 'mr-8': displayInRow }"
class="option pointer" class="option pointer"
> >
<div *ngIf="option.icon; else withoutIcon" class="icon-option"> @if (option.icon) {
<mat-icon [svgIcon]="option.icon" class="icon"></mat-icon> <div class="icon-option">
<mat-icon [svgIcon]="option.icon" class="icon"></mat-icon>
<div class="text"> <div class="text">
<label class="details-radio-label pointer">{{ option.label | translate: option.descriptionParams }}</label> <label class="details-radio-label pointer">{{ option.label | translate: option.descriptionParams }}</label>
<span class="hint">{{ option.description | translate: option.descriptionParams | replaceNbsp }}</span> <span class="hint">{{ option.description | translate: option.descriptionParams | replaceNbsp }}</span>
@if (option.extraOption && !option.extraOption.hidden && isSelected(option)) {
<div *ngIf="option.extraOption && !option.extraOption.hidden && isSelected(option)" class="iqser-input-group"> <div class="iqser-input-group">
<mat-checkbox <mat-checkbox
(change)="emitExtraOption()" (change)="emitExtraOption()"
[(ngModel)]="option.extraOption.checked" [(ngModel)]="option.extraOption.checked"
[checked]="option.extraOption.checked" [checked]="option.extraOption.checked"
[disabled]="!!option.extraOption.disabled" [disabled]="!!option.extraOption.disabled"
color="primary" color="primary"
> >
{{ option.extraOption.label | translate }} {{ option.extraOption.label | translate }}
</mat-checkbox> </mat-checkbox>
<span @if (option.extraOption.description) {
*ngIf="option.extraOption.description" <span
[innerHTML]="option.extraOption.description | translate" [innerHTML]="option.extraOption.description | translate"
class="hint extra-option-description" class="hint extra-option-description"
></span> ></span>
}
</div>
}
</div>
@if (isSelected(option)) {
<mat-icon class="checked" svgIcon="iqser:radio-selected"></mat-icon>
}
</div> </div>
</div> } @else {
<div class="flex-align-items-center mb-8">
<mat-icon *ngIf="isSelected(option)" class="checked" svgIcon="iqser:radio-selected"></mat-icon> <iqser-round-checkbox [active]="isSelected(option)" class="mr-6"></iqser-round-checkbox>
<label class="details-radio-label pointer">{{ option.label | translate }}</label>
</div>
<span class="hint">{{ option.description | translate }}</span>
}
</div> </div>
}
<ng-template #withoutIcon>
<div class="flex-align-items-center mb-8">
<iqser-round-checkbox [active]="isSelected(option)" class="mr-6"></iqser-round-checkbox>
<label class="details-radio-label pointer">{{ option.label | translate }}</label>
</div>
<span class="hint">{{ option.description | translate }}</span>
</ng-template>
</div>
</div> </div>

View File

@ -1,4 +1,4 @@
import { NgClass, NgForOf, NgIf } from '@angular/common'; import { NgClass } from '@angular/common';
import { booleanAttribute, Component, EventEmitter, Input, Output } from '@angular/core'; import { booleanAttribute, Component, EventEmitter, Input, Output } from '@angular/core';
import { FormsModule, NG_VALIDATORS, NG_VALUE_ACCESSOR, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, NG_VALIDATORS, NG_VALUE_ACCESSOR, ReactiveFormsModule } from '@angular/forms';
import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatCheckboxModule } from '@angular/material/checkbox';
@ -28,12 +28,10 @@ import { DetailsRadioOption } from './details-radio-option';
}, },
], ],
imports: [ imports: [
NgForOf,
NgClass, NgClass,
RoundCheckboxComponent, RoundCheckboxComponent,
TranslateModule, TranslateModule,
MatIconModule, MatIconModule,
NgIf,
FormsModule, FormsModule,
MatCheckboxModule, MatCheckboxModule,
ReactiveFormsModule, ReactiveFormsModule,

View File

@ -1,7 +1,9 @@
<div [class.datepicker-wrapper]="isDate" [ngClass]="classList" class="iqser-input-group"> <div [class.datepicker-wrapper]="isDate" [ngClass]="classList" class="iqser-input-group">
<label *ngIf="label"> {{ label }} </label> @if (label) {
<label> {{ label }} </label>
}
<ng-container *ngIf="isDate"> @if (isDate) {
<input <input
(ngModelChange)="onChange($event)" (ngModelChange)="onChange($event)"
[(ngModel)]="input" [(ngModel)]="input"
@ -15,26 +17,21 @@
<mat-icon matDatepickerToggleIcon svgIcon="iqser:calendar"></mat-icon> <mat-icon matDatepickerToggleIcon svgIcon="iqser:calendar"></mat-icon>
</mat-datepicker-toggle> </mat-datepicker-toggle>
<mat-datepicker #picker (closed)="onCloseDatepicker()" (opened)="onOpenDatepicker()"></mat-datepicker> <mat-datepicker #picker (closed)="onCloseDatepicker()" (opened)="onOpenDatepicker()"></mat-datepicker>
</ng-container> }
<input @if (isText) {
(ngModelChange)="onChange($event)" <input
*ngIf="isText" (ngModelChange)="onChange($event)"
[(ngModel)]="input" [(ngModel)]="input"
[disabled]="disabled" [disabled]="disabled"
[id]="id" [id]="id"
[placeholder]="placeholder || ''" [placeholder]="placeholder || ''"
iqserStopPropagation iqserStopPropagation
type="text" type="text"
/> />
}
<input @if (isNumber) {
(ngModelChange)="onChange($event)" <input (ngModelChange)="onChange($event)" [(ngModel)]="input" [disabled]="disabled" [id]="id" iqserStopPropagation type="number" />
*ngIf="isNumber" }
[(ngModel)]="input"
[disabled]="disabled"
[id]="id"
iqserStopPropagation
type="number"
/>
</div> </div>

View File

@ -1,7 +1,7 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { FormsModule, NG_VALIDATORS, NG_VALUE_ACCESSOR } from '@angular/forms'; import { FormsModule, NG_VALIDATORS, NG_VALUE_ACCESSOR } from '@angular/forms';
import { FormFieldComponent } from '../form-field/form-field-component.directive'; import { FormFieldComponent } from '../form-field/form-field-component.directive';
import { NgClass, NgIf } from '@angular/common'; import { NgClass } from '@angular/common';
import { MatDatepickerModule } from '@angular/material/datepicker'; import { MatDatepickerModule } from '@angular/material/datepicker';
import { StopPropagationDirective } from '../../directives'; import { StopPropagationDirective } from '../../directives';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
@ -36,7 +36,7 @@ type DynamicInput = number | string | Date;
useExisting: DynamicInputComponent, useExisting: DynamicInputComponent,
}, },
], ],
imports: [NgClass, NgIf, FormsModule, MatDatepickerModule, StopPropagationDirective, MatIconModule, MatInputModule], imports: [NgClass, FormsModule, MatDatepickerModule, StopPropagationDirective, MatIconModule, MatInputModule],
}) })
export class DynamicInputComponent extends FormFieldComponent<DynamicInput> { export class DynamicInputComponent extends FormFieldComponent<DynamicInput> {
@Input() label?: string; @Input() label?: string;

View File

@ -1,34 +1,30 @@
<ng-container *ngIf="!editing"> @if (!editing) {
<div *ngIf="showPreview"> @if (showPreview) {
{{ value }} <div>
</div> {{ value }}
</div>
}
<div class="flex"> <div class="flex">
<iqser-circle-button @if (canEdit) {
*ngIf="canEdit" <iqser-circle-button
(action)="editing = true" (action)="editing = true"
[tooltip]="editTooltip" [tooltip]="editTooltip"
[type]="buttonsType" [type]="buttonsType"
[attr.help-mode-key]="helpModeKey" [attr.help-mode-key]="helpModeKey"
class="edit-button" class="edit-button"
icon="iqser:edit" icon="iqser:edit"
></iqser-circle-button> ></iqser-circle-button>
}
<ng-content select="[slot=editing]"></ng-content> <ng-content select="[slot=editing]"></ng-content>
</div> </div>
</ng-container> }
<ng-container *ngIf="editing"> @if (editing) {
<form (submit)="saveValue()"> <form (submit)="saveValue()">
<div [class]="'iqser-input-group ' + class"> <div [class]="'iqser-input-group ' + class">
<input @if (!parentId) {
*ngIf="!parentId; else expandableInput" <input (ngModelChange)="newValue = $event" [ngModel]="value" [placeholder]="placeholder" name="name" />
(ngModelChange)="newValue = $event" } @else {
[ngModel]="value"
[placeholder]="placeholder"
name="name"
/>
<ng-template #expandableInput>
<textarea <textarea
(ngModelChange)="newValue = $event" (ngModelChange)="newValue = $event"
[ngModel]="value" [ngModel]="value"
@ -38,13 +34,11 @@
[style.width]="this.textArea.width + 'px'" [style.width]="this.textArea.width + 'px'"
[style.height]="this.textArea.height + 'px'" [style.height]="this.textArea.height + 'px'"
></textarea> ></textarea>
</ng-template> }
</div> </div>
</form> </form>
<div class="flex"> <div class="flex">
<iqser-circle-button (action)="saveValue()" [tooltip]="saveTooltip" [type]="buttonsType" icon="iqser:check"></iqser-circle-button> <iqser-circle-button (action)="saveValue()" [tooltip]="saveTooltip" [type]="buttonsType" icon="iqser:check"></iqser-circle-button>
<iqser-circle-button <iqser-circle-button
(action)="editing = false" (action)="editing = false"
[tooltip]="cancelTooltip" [tooltip]="cancelTooltip"
@ -52,4 +46,4 @@
icon="iqser:close" icon="iqser:close"
></iqser-circle-button> ></iqser-circle-button>
</div> </div>
</ng-container> }

View File

@ -1,4 +1,3 @@
import { NgIf } from '@angular/common';
import { ChangeDetectionStrategy, Component, EventEmitter, HostBinding, Input, OnChanges, Output, SimpleChanges } from '@angular/core'; import { ChangeDetectionStrategy, Component, EventEmitter, HostBinding, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { CircleButtonComponent, CircleButtonType, CircleButtonTypes } from '../../buttons'; import { CircleButtonComponent, CircleButtonType, CircleButtonTypes } from '../../buttons';
@ -9,7 +8,7 @@ import { CircleButtonComponent, CircleButtonType, CircleButtonTypes } from '../.
styleUrls: ['./editable-input.component.scss'], styleUrls: ['./editable-input.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true, standalone: true,
imports: [NgIf, CircleButtonComponent, FormsModule], imports: [CircleButtonComponent, FormsModule],
}) })
export class EditableInputComponent implements OnChanges { export class EditableInputComponent implements OnChanges {
@Input() id?: string; @Input() id?: string;

View File

@ -11,22 +11,24 @@
type="text" type="text"
/> />
<span *ngIf="hint" class="hint">{{ hint }}</span> @if (hint) {
<span class="hint">{{ hint }}</span>
}
<mat-icon *ngIf="isSearch && !hasContent" class="icon-right" svgIcon="iqser:search"></mat-icon> @if (isSearch && !hasContent) {
<mat-icon class="icon-right" svgIcon="iqser:search"></mat-icon>
}
<iqser-circle-button @if (isSearch && hasContent) {
(action)="reset()" <iqser-circle-button (action)="reset()" [buttonId]="inputId + '-clear'" icon="iqser:close"></iqser-circle-button>
*ngIf="isSearch && hasContent" }
[buttonId]="inputId + '-clear'"
icon="iqser:close"
></iqser-circle-button>
<iqser-circle-button @if (!isSearch) {
(action)="executeAction()" <iqser-circle-button
*ngIf="!isSearch" (action)="executeAction()"
[buttonId]="actionButtonId" [buttonId]="actionButtonId"
[disabled]="!hasContent" [disabled]="!hasContent"
[icon]="icon!" [icon]="icon!"
></iqser-circle-button> ></iqser-circle-button>
}
</form> </form>

View File

@ -1,7 +1,7 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, Output } from '@angular/core'; import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, Output } from '@angular/core';
import { randomString } from '../../utils'; import { randomString } from '../../utils';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { NgIf } from '@angular/common';
import { CircleButtonComponent } from '../../buttons'; import { CircleButtonComponent } from '../../buttons';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
@ -11,7 +11,7 @@ import { MatIconModule } from '@angular/material/icon';
styleUrls: ['./input-with-action.component.scss'], styleUrls: ['./input-with-action.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true, standalone: true,
imports: [FormsModule, NgIf, MatIconModule, CircleButtonComponent], imports: [FormsModule, MatIconModule, CircleButtonComponent],
}) })
export class InputWithActionComponent { export class InputWithActionComponent {
@Input() inputId = `${randomString() + '-search-input'}`; @Input() inputId = `${randomString() + '-search-input'}`;

View File

@ -6,6 +6,10 @@
[class.with-bg]="type === 'with-bg'" [class.with-bg]="type === 'with-bg'"
class="wrapper" class="wrapper"
> >
<mat-icon *ngIf="active && !indeterminate" svgIcon="iqser:radio-selected"></mat-icon> @if (active && !indeterminate) {
<mat-icon *ngIf="indeterminate" svgIcon="iqser:radio-indeterminate"></mat-icon> <mat-icon svgIcon="iqser:radio-selected"></mat-icon>
}
@if (indeterminate) {
<mat-icon svgIcon="iqser:radio-indeterminate"></mat-icon>
}
</div> </div>

View File

@ -1,6 +1,5 @@
import { booleanAttribute, ChangeDetectionStrategy, Component, ElementRef, HostBinding, Input, OnInit, ViewChild } from '@angular/core'; import { booleanAttribute, ChangeDetectionStrategy, Component, ElementRef, HostBinding, Input, OnInit, ViewChild } from '@angular/core';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { NgIf } from '@angular/common';
@Component({ @Component({
selector: 'iqser-round-checkbox', selector: 'iqser-round-checkbox',
@ -8,7 +7,7 @@ import { NgIf } from '@angular/common';
styleUrls: ['./round-checkbox.component.scss'], styleUrls: ['./round-checkbox.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true, standalone: true,
imports: [MatIconModule, NgIf], imports: [MatIconModule],
}) })
export class RoundCheckboxComponent implements OnInit { export class RoundCheckboxComponent implements OnInit {
@Input() size = 20; @Input() size = 20;

View File

@ -1,94 +1,98 @@
<div class="page-header"> <div class="page-header">
<div *ngIf="pageLabel" class="breadcrumb">{{ pageLabel }}</div> @if (pageLabel) {
<div class="breadcrumb">{{ pageLabel }}</div>
}
<div *ngIf="filters$ | async as filters" class="filters"> @if (filters$ | async; as filters) {
<ng-content select="[slot=beforeFilters]"></ng-content> <div class="filters">
<ng-content select="[slot=beforeFilters]"></ng-content>
<div @if (filters.length && searchPosition !== searchPositions.beforeFilters) {
*ngIf="filters.length && searchPosition !== searchPositions.beforeFilters" <div class="text-muted" translate="filters.filter-by"></div>
class="text-muted" }
translate="filters.filter-by" @if (searchPosition === searchPositions.beforeFilters) {
></div> <ng-container [ngTemplateOutlet]="searchBar"></ng-container>
}
<ng-container *ngIf="searchPosition === searchPositions.beforeFilters" [ngTemplateOutlet]="searchBar"></ng-container> @for (filter of filters; track trackByLabel($index, filter)) {
@if (!filter.hide) {
<ng-container *ngFor="let filter of filters; trackBy: trackByLabel"> <iqser-popup-filter [primaryFiltersSlug]="filter.slug" [attr.help-mode-key]="filterHelpModeKey"></iqser-popup-filter>
<iqser-popup-filter }
*ngIf="!filter.hide" }
[primaryFiltersSlug]="filter.slug" @for (filter$ of filterService.singleFilters; track filter$) {
[attr.help-mode-key]="filterHelpModeKey" @if (filter$ | async; as filter) {
></iqser-popup-filter> <iqser-single-filter [filter]="filter"></iqser-single-filter>
</ng-container> }
}
<ng-container *ngFor="let filter$ of filterService.singleFilters"> @if (searchPosition === searchPositions.afterFilters) {
<iqser-single-filter *ngIf="filter$ | async as filter" [filter]="filter"></iqser-single-filter> <ng-container [ngTemplateOutlet]="searchBar"></ng-container>
</ng-container> }
@if (!hideResetButton && (showResetFilters$ | async) === true) {
<ng-container *ngIf="searchPosition === searchPositions.afterFilters" [ngTemplateOutlet]="searchBar"></ng-container> <div
(click)="resetFilters()"
<div [attr.help-mode-key]="'filter_' + helpModeKey + '_list'"
(click)="resetFilters()" class="reset-filters"
*ngIf="!hideResetButton && (showResetFilters$ | async) === true" translate="reset-filters"
[attr.help-mode-key]="'filter_' + helpModeKey + '_list'" ></div>
class="reset-filters" }
translate="reset-filters"
></div>
</div>
<div *ngIf="showCloseButton || actionConfigs || buttonConfigs || viewModeSelection" class="actions">
<ng-container *ngIf="searchPosition === searchPositions.withActions" [ngTemplateOutlet]="searchBar"></ng-container>
<ng-container [ngTemplateOutlet]="viewModeSelection"></ng-container>
<ng-container *ngFor="let config of buttonConfigs; trackBy: trackByLabel">
<iqser-icon-button
(action)="config.action($event)"
*ngIf="!config.hide"
[buttonId]="config.label.replace('.', '-')"
[icon]="config.icon"
[label]="config.label | translate"
[type]="config.type"
[attr.help-mode-key]="config.helpModeKey"
></iqser-icon-button>
</ng-container>
<div class="actions">
<ng-container *ngFor="let config of actionConfigs; trackBy: trackByLabel">
<iqser-circle-button
(action)="config.action($event)"
*ngIf="!config.hide"
[buttonId]="config.id"
[disabled]="config.disabled$ && (config.disabled$ | async)"
[icon]="config.icon"
[tooltip]="config.label"
[attr.help-mode-key]="config.helpModeKey"
></iqser-circle-button>
</ng-container>
<!-- Extra custom actions here -->
<ng-content select="[slot=right]"></ng-content>
<iqser-circle-button
buttonId="close-view-btn"
(action)="closeAction.emit()"
*ngIf="showCloseButton"
[class.ml-6]="actionConfigs"
[icon]="'iqser:close'"
[attr.help-mode-key]="'close_dossier'"
[tooltip]="'common.close' | translate"
></iqser-circle-button>
</div> </div>
</div> }
@if (showCloseButton || actionConfigs || buttonConfigs || viewModeSelection) {
<div class="actions">
@if (searchPosition === searchPositions.withActions) {
<ng-container [ngTemplateOutlet]="searchBar"></ng-container>
}
<ng-container [ngTemplateOutlet]="viewModeSelection"></ng-container>
@for (config of buttonConfigs; track trackByLabel($index, config)) {
@if (!config.hide) {
<iqser-icon-button
(action)="config.action($event)"
[buttonId]="config.label.replace('.', '-')"
[icon]="config.icon"
[label]="config.label | translate"
[type]="config.type"
[attr.help-mode-key]="config.helpModeKey"
></iqser-icon-button>
}
}
<div class="actions">
@for (config of actionConfigs; track trackByLabel($index, config)) {
@if (!config.hide) {
<iqser-circle-button
(action)="config.action($event)"
[buttonId]="config.id"
[disabled]="config.disabled$ && (config.disabled$ | async)"
[icon]="config.icon"
[tooltip]="config.label"
[attr.help-mode-key]="config.helpModeKey"
></iqser-circle-button>
}
}
<!-- Extra custom actions here -->
<ng-content select="[slot=right]"></ng-content>
@if (showCloseButton) {
<iqser-circle-button
buttonId="close-view-btn"
(action)="closeAction.emit()"
[class.ml-6]="actionConfigs"
[icon]="'iqser:close'"
[attr.help-mode-key]="'close_dossier'"
[tooltip]="'common.close' | translate"
></iqser-circle-button>
}
</div>
</div>
}
</div> </div>
<ng-template #searchBar> <ng-template #searchBar>
<iqser-input-with-action @if (searchPlaceholder && searchService) {
[inputId]="searchInputId" <iqser-input-with-action
(valueChange)="searchService.searchValue = $event" [inputId]="searchInputId"
*ngIf="searchPlaceholder && searchService" (valueChange)="searchService.searchValue = $event"
[class.mr-8]="searchPosition === searchPositions.beforeFilters" [class.mr-8]="searchPosition === searchPositions.beforeFilters"
[placeholder]="searchPlaceholder" [placeholder]="searchPlaceholder"
[value]="searchService.valueChanges$ | async" [value]="searchService.valueChanges$ | async"
[width]="searchWidth" [width]="searchWidth"
></iqser-input-with-action> ></iqser-input-with-action>
}
</ng-template> </ng-template>

View File

@ -1,14 +1,24 @@
<div (click)="!!sortByKey && sortingService?.toggleSort(sortByKey)" [class.pointer]="!!sortByKey" [ngClass]="class"> <div (click)="!!sortByKey && sortingService?.toggleSort(sortByKey)" [class.pointer]="!!sortByKey" [ngClass]="class">
<mat-icon *ngIf="!!leftIcon" [svgIcon]="leftIcon"></mat-icon> @if (!!leftIcon) {
<mat-icon [svgIcon]="leftIcon"></mat-icon>
}
<span [matTooltip]="label" class="all-caps-label clamp-1" matTooltipPosition="above">{{ label }}</span> <span [matTooltip]="label" class="all-caps-label clamp-1" matTooltipPosition="above">{{ label }}</span>
<mat-icon *ngIf="!!rightIcon" [matTooltip]="rightIconTooltip | translate" [svgIcon]="rightIcon" matTooltipPosition="above"></mat-icon> @if (!!rightIcon) {
<mat-icon [matTooltip]="rightIconTooltip | translate" [svgIcon]="rightIcon" matTooltipPosition="above"></mat-icon>
}
<ng-container *ngIf="sortingService?.sortingOption$ | async as sortingOption"> @if (sortingService?.sortingOption$ | async; as sortingOption) {
<div *ngIf="!!sortByKey" [class.force-display]="sortingOption.column === sortByKey" class="sort-arrows-container"> @if (!!sortByKey) {
<mat-icon *ngIf="sortingOption.order === sortingOrders.asc" svgIcon="iqser:sort-asc"></mat-icon> <div [class.force-display]="sortingOption.column === sortByKey" class="sort-arrows-container">
<mat-icon *ngIf="sortingOption.order === sortingOrders.desc" svgIcon="iqser:sort-desc"></mat-icon> @if (sortingOption.order === sortingOrders.asc) {
</div> <mat-icon svgIcon="iqser:sort-asc"></mat-icon>
</ng-container> }
@if (sortingOption.order === sortingOrders.desc) {
<mat-icon svgIcon="iqser:sort-desc"></mat-icon>
}
</div>
}
}
</div> </div>

View File

@ -10,23 +10,22 @@
<ng-container *cdkVirtualFor="let entity of listingService.sortedDisplayedEntities$ | async; trackBy: trackBy"> <ng-container *cdkVirtualFor="let entity of listingService.sortedDisplayedEntities$ | async; trackBy: trackBy">
<!-- mouseenter and mouseleave triggers change detection event if itemMouse functions are undefined --> <!-- mouseenter and mouseleave triggers change detection event if itemMouse functions are undefined -->
<!-- this little hack below ensures that change detection won't be triggered if functions are undefined --> <!-- this little hack below ensures that change detection won't be triggered if functions are undefined -->
<div @if (itemMouseEnterFn || itemMouseLeaveFn) {
(mouseenter)="itemMouseEnterFn && itemMouseEnterFn(entity)" <div
(mouseleave)="itemMouseLeaveFn && itemMouseLeaveFn(entity)" (mouseenter)="itemMouseEnterFn && itemMouseEnterFn(entity)"
*ngIf="itemMouseEnterFn || itemMouseLeaveFn; else withoutMouseEvents" (mouseleave)="itemMouseLeaveFn && itemMouseLeaveFn(entity)"
[class.help-mode-active]="helpModeService?.isHelpModeActive$ | async" [class.help-mode-active]="helpModeService?.isHelpModeActive$ | async"
[id]="rowIdPrefix + '-' + ((entity[namePropertyKey] | snakeCase) ?? entity.id)" [id]="rowIdPrefix + '-' + ((entity[namePropertyKey] | snakeCase) ?? entity.id)"
[ngClass]="getTableItemClasses(entity)" [ngClass]="getTableItemClasses(entity)"
[routerLink]="entity.routerLink" [routerLink]="entity.routerLink"
> >
<iqser-table-item <iqser-table-item
(click)="multiSelect(entity, $event)" (click)="multiSelect(entity, $event)"
[entity]="entity" [entity]="entity"
[selectionEnabled]="selectionEnabled" [selectionEnabled]="selectionEnabled"
></iqser-table-item> ></iqser-table-item>
</div> </div>
} @else {
<ng-template #withoutMouseEvents>
<div <div
[class.help-mode-active]="helpModeService?.isHelpModeActive$ | async" [class.help-mode-active]="helpModeService?.isHelpModeActive$ | async"
[id]="rowIdPrefix + '-' + ((entity[namePropertyKey] | snakeCase) ?? entity.id)" [id]="rowIdPrefix + '-' + ((entity[namePropertyKey] | snakeCase) ?? entity.id)"
@ -39,7 +38,7 @@
[selectionEnabled]="selectionEnabled" [selectionEnabled]="selectionEnabled"
></iqser-table-item> ></iqser-table-item>
</div> </div>
</ng-template> }
</ng-container> </ng-container>
</cdk-virtual-scroll-viewport> </cdk-virtual-scroll-viewport>
</div> </div>

View File

@ -1,6 +1,8 @@
<div [id]="'select-' + entity.id" (click)="toggleEntitySelected($event, entity)" *ngIf="selectionEnabled" class="selection-column"> @if (selectionEnabled) {
<iqser-round-checkbox [active]="isSelected$ | async"></iqser-round-checkbox> <div [id]="'select-' + entity.id" (click)="toggleEntitySelected($event, entity)" class="selection-column">
</div> <iqser-round-checkbox [active]="isSelected$ | async"></iqser-round-checkbox>
</div>
}
<ng-container *ngTemplateOutlet="listingComponent.tableItemTemplate; context: { entity: entity }"></ng-container> <ng-container *ngTemplateOutlet="listingComponent.tableItemTemplate; context: { entity: entity }"></ng-container>

View File

@ -1,50 +1,56 @@
<div [class.selection-enabled]="selectionEnabled" class="header-item"> <div [class.selection-enabled]="selectionEnabled" class="header-item">
<div [attr.help-mode-key]="helpModeKey" class="header-title"> <div [attr.help-mode-key]="helpModeKey" class="header-title">
<iqser-round-checkbox @if (selectionEnabled) {
(click)="listingService.selectAll()" <iqser-round-checkbox
*ngIf="selectionEnabled" (click)="listingService.selectAll()"
[active]="listingService.areAllSelected$ | async" [active]="listingService.areAllSelected$ | async"
[indeterminate]="listingService.notAllSelected$ | async" [indeterminate]="listingService.notAllSelected$ | async"
id="select-all-entities-toggle" id="select-all-entities-toggle"
></iqser-round-checkbox> ></iqser-round-checkbox>
}
<span class="all-caps-label"> <span class="all-caps-label">
{{ tableHeaderLabel | translate: { length: totalSize || (listingService.displayedLength$ | async) } }} {{ tableHeaderLabel | translate: { length: totalSize || (listingService.displayedLength$ | async) } }}
<span *ngIf="listingService.selectedLength$ | async as selectedItems"> @if (listingService.selectedLength$ | async; as selectedItems) {
({{ 'table-header.selected-count' | translate: { count: selectedItems } }}) <span> ({{ 'table-header.selected-count' | translate: { count: selectedItems } }}) </span>
</span> }
</span> </span>
</div> </div>
<ng-container [ngTemplateOutlet]="bulkActions"></ng-container> <ng-container [ngTemplateOutlet]="bulkActions"></ng-container>
<iqser-quick-filters *ngIf="quickFilters$ | async"></iqser-quick-filters> @if (quickFilters$ | async) {
<iqser-quick-filters></iqser-quick-filters>
}
<!-- Custom content--> <!-- Custom content-->
<ng-content></ng-content> <ng-content></ng-content>
</div> </div>
<div @if (listingMode === listingModes.table) {
*ngIf="listingMode === listingModes.table" <div
[class.no-data]="entitiesService.noData$ | async" [class.no-data]="entitiesService.noData$ | async"
[class.selection-enabled]="selectionEnabled" [class.selection-enabled]="selectionEnabled"
class="table-header" class="table-header"
iqserSyncWidth="table-item" iqserSyncWidth="table-item"
> >
<div *ngIf="selectionEnabled" class="select-oval-placeholder"></div> @if (selectionEnabled) {
<div class="select-oval-placeholder"></div>
<iqser-table-column-name }
*ngFor="let config of tableColumnConfigs" @for (config of tableColumnConfigs; track config) {
[class]="config.class" <iqser-table-column-name
[id]="config.id" [class]="config.class"
[label]="config.notTranslatable ? config.label : (config.label | translate)" [id]="config.id"
[leftIcon]="config.leftIcon" [label]="config.notTranslatable ? config.label : (config.label | translate)"
[rightIconTooltip]="config.rightIconTooltip" [leftIcon]="config.leftIcon"
[rightIcon]="config.rightIcon" [rightIconTooltip]="config.rightIconTooltip"
[sortByKey]="config.sortByKey" [rightIcon]="config.rightIcon"
></iqser-table-column-name> [sortByKey]="config.sortByKey"
></iqser-table-column-name>
<div *ngIf="hasEmptyColumn"></div> }
@if (hasEmptyColumn) {
<div class="scrollbar-placeholder"></div> <div></div>
</div> }
<div class="scrollbar-placeholder"></div>
</div>
}

View File

@ -1,42 +1,46 @@
<iqser-table-header <iqser-table-header
[bulkActions]="bulkActions" [bulkActions]="bulkActions"
[hasEmptyColumn]="!!emptyColumnWidth" [hasEmptyColumn]="!!emptyColumnWidth"
[helpModeKey]="headerHelpModeKey"
[listingMode]="listingModes.table" [listingMode]="listingModes.table"
[selectionEnabled]="selectionEnabled" [selectionEnabled]="selectionEnabled"
[tableColumnConfigs]="tableColumnConfigs" [tableColumnConfigs]="tableColumnConfigs"
[tableHeaderLabel]="tableHeaderLabel" [tableHeaderLabel]="tableHeaderLabel"
[helpModeKey]="headerHelpModeKey"
[totalSize]="totalSize" [totalSize]="totalSize"
> >
<ng-container *ngTemplateOutlet="headerTemplate"></ng-container> <ng-container *ngTemplateOutlet="headerTemplate"></ng-container>
</iqser-table-header> </iqser-table-header>
<iqser-empty-state @if (entitiesService.noData$ | async) {
(action)="noDataAction.emit()" <iqser-empty-state
*ngIf="entitiesService.noData$ | async" (action)="noDataAction.emit()"
[buttonIcon]="noDataButtonIcon" [buttonIcon]="noDataButtonIcon"
[buttonLabel]="noDataButtonLabel" [buttonLabel]="noDataButtonLabel"
[icon]="noDataIcon" [icon]="noDataIcon"
[showButton]="showNoDataButton" [showButton]="showNoDataButton"
[text]="noDataText" [text]="noDataText"
></iqser-empty-state> ></iqser-empty-state>
}
<iqser-empty-state *ngIf="listingComponent.noMatch$ | async" [text]="noMatchText"></iqser-empty-state> @if (listingComponent.noMatch$ | async) {
<iqser-empty-state [text]="noMatchText"></iqser-empty-state>
}
<iqser-table-content <iqser-table-content
#tableContent #tableContent
[itemMouseEnterFn]="itemMouseEnterFn" [itemMouseEnterFn]="itemMouseEnterFn"
[itemMouseLeaveFn]="itemMouseLeaveFn" [itemMouseLeaveFn]="itemMouseLeaveFn"
[itemSize]="itemSize" [itemSize]="itemSize"
[namePropertyKey]="namePropertyKey"
[rowIdPrefix]="rowIdPrefix"
[selectionEnabled]="selectionEnabled" [selectionEnabled]="selectionEnabled"
[tableItemClasses]="tableItemClasses" [tableItemClasses]="tableItemClasses"
[rowIdPrefix]="rowIdPrefix"
[namePropertyKey]="namePropertyKey"
></iqser-table-content> ></iqser-table-content>
<iqser-scroll-button @if (hasScrollButton && tableContent?.scrollViewport) {
*ngIf="hasScrollButton && tableContent?.scrollViewport" <iqser-scroll-button
[itemSize]="itemSize" [itemSize]="itemSize"
[scrollViewport]="tableContent.scrollViewport" [scrollViewport]="tableContent.scrollViewport"
[helpModeKey]="helpModeKey" [helpModeKey]="helpModeKey"
></iqser-scroll-button> ></iqser-scroll-button>
}

View File

@ -1,43 +1,46 @@
<ng-container *ngIf="componentContext$ | async as ctx"> @if (componentContext$ | async; as ctx) {
<ng-container *ngIf="ctx.entities as entities"> @if (ctx.entities; as entities) {
<div class="heading"> <div class="heading">
<span>{{ column.label | translate }} ({{ entities.length || 0 }})</span> <span>{{ column.label | translate }} ({{ entities.length || 0 }})</span>
<span @if (!activeSelection && !selectionColumn && entities.length > 1) {
(click)="enableSelection()" <span (click)="enableSelection()" class="all-caps-label primary pointer" translate="workflow.selection.select"></span>
*ngIf="!activeSelection && !selectionColumn && entities.length > 1" }
class="all-caps-label primary pointer" @if (activeSelection) {
translate="workflow.selection.select" <div class="flex">
></span> <span (click)="selectAll()" class="all-caps-label primary pointer mr-10" translate="workflow.selection.all"></span>
<div *ngIf="activeSelection" class="flex"> <span (click)="selectNone()" class="all-caps-label primary pointer" translate="workflow.selection.none"></span>
<span (click)="selectAll()" class="all-caps-label primary pointer mr-10" translate="workflow.selection.all"></span> </div>
<span (click)="selectNone()" class="all-caps-label primary pointer" translate="workflow.selection.none"></span> }
</div>
</div> </div>
<div *ngIf="activeSelection" class="multi-select mb-8"> @if (activeSelection) {
<div class="selected-wrapper"> <div class="multi-select mb-8">
<iqser-round-checkbox <div class="selected-wrapper">
(click)="toggleSelectAll()" <iqser-round-checkbox
[active]="ctx.allSelected" (click)="toggleSelectAll()"
[indeterminate]="ctx.indeterminate" [active]="ctx.allSelected"
type="with-bg" [indeterminate]="ctx.indeterminate"
></iqser-round-checkbox> type="with-bg"
></iqser-round-checkbox>
<span <span
[translateParams]="{ count: listingService.selectedLength$ | async }" [translateParams]="{ count: listingService.selectedLength$ | async }"
[translate]="'workflow.selection.count'" [translate]="'workflow.selection.count'"
class="all-caps-label" class="all-caps-label"
></span> ></span>
</div>
<div #bulkActionsContainer class="flex-1 overflow-hidden">
@if (bulkActionsContainerWidth) {
<ng-container
[ngTemplateOutletContext]="{ maxWidth: bulkActionsContainerWidth }"
[ngTemplateOutlet]="bulkActions"
></ng-container>
}
</div>
<iqser-circle-button
(action)="disableSelection()"
[type]="circleButtonTypes.primary"
icon="iqser:close"
></iqser-circle-button>
</div> </div>
}
<div #bulkActionsContainer class="flex-1 overflow-hidden"> }
<ng-container }
*ngIf="bulkActionsContainerWidth"
[ngTemplateOutletContext]="{ maxWidth: bulkActionsContainerWidth }"
[ngTemplateOutlet]="bulkActions"
></ng-container>
</div>
<iqser-circle-button (action)="disableSelection()" [type]="circleButtonTypes.primary" icon="iqser:close"></iqser-circle-button>
</div>
</ng-container>
</ng-container>

View File

@ -1,69 +1,78 @@
<ng-container *ngIf="componentContext$ | async as ctx"> @if (componentContext$ | async; as ctx) {
<iqser-table-header [tableHeaderLabel]="listingComponent.tableHeaderLabel" listingMode="workflow"></iqser-table-header> <iqser-table-header [tableHeaderLabel]="listingComponent.tableHeaderLabel" listingMode="workflow"></iqser-table-header>
@if (ctx.noData) {
<iqser-empty-state <iqser-empty-state
(action)="noDataAction.emit()" (action)="noDataAction.emit()"
*ngIf="ctx.noData" [buttonIcon]="noDataButtonIcon"
[buttonIcon]="noDataButtonIcon" [buttonLabel]="noDataButtonLabel"
[buttonLabel]="noDataButtonLabel" [icon]="noDataIcon"
[icon]="noDataIcon" [showButton]="showNoDataButton"
[showButton]="showNoDataButton" [text]="noDataText"
[text]="noDataText" ></iqser-empty-state>
></iqser-empty-state> }
@if (!ctx.noData) {
<div *ngIf="!ctx.noData" cdkDropListGroup class="columns-wrapper"> <div cdkDropListGroup class="columns-wrapper">
<div @for (column of config.columns; track column) {
*ngFor="let column of config.columns"
[class.dragging]="dragging"
[class.list-can-receive]="isReceiving(column)"
[class.list-dragging]="isDragging(column)"
[class.list-source]="isSource(column)"
[style.--color]="column.color"
class="column"
>
<iqser-column-header [(selectionColumn)]="selectionColumn" [bulkActions]="bulkActions" [column]="column"></iqser-column-header>
<div
(cdkDropListDropped)="move($event)"
*ngIf="column.entities | async as entities"
[cdkDropListData]="entities"
[cdkDropListEnterPredicate]="canMoveTo(column)"
[class.multi-select-active]="selectionColumn === column"
[id]="column.key"
cdkDropList
cdkDropListSortingDisabled
>
<div <div
(cdkDragEnded)="stopDragging()" [class.dragging]="dragging"
(cdkDragStarted)="startDragging(column, $event)" [class.list-can-receive]="isReceiving(column)"
(click)="selectionColumn === column && listingService.select(entity)" [class.list-dragging]="isDragging(column)"
*ngFor="let entity of entities; trackBy: trackBy" [class.list-source]="isSource(column)"
[cdkDragData]="entity" [style.--color]="column.color"
[class.no-border]="dragging && ctx.draggingEntities.includes(entity)" class="column"
[class.selected]="all[entity.id].isSelected$ | async"
[ngClass]="all[entity.id].classes$ | async"
cdkDrag
> >
<ng-container *ngIf="!ctx.draggingEntities.includes(entity)"> <iqser-column-header
<ng-container *ngTemplateOutlet="itemTemplate; context: { entity: entity }"></ng-container> [(selectionColumn)]="selectionColumn"
</ng-container> [bulkActions]="bulkActions"
[column]="column"
<ng-template cdkDragPlaceholder> ></iqser-column-header>
<div *ngFor="let e of ctx.draggingEntities" [style.min-height]="itemHeight + 'px'" class="placeholder"></div> @if (column.entities | async; as entities) {
</ng-template> <div
(cdkDropListDropped)="move($event)"
<ng-template cdkDragPreview [matchSize]="true"> [cdkDropListData]="entities"
<ng-container *ngFor="let e of ctx.draggingEntities"> [cdkDropListEnterPredicate]="canMoveTo(column)"
<div [class.selected]="all[e.id].isSelected$ | async" [ngClass]="all[e.id].classes$ | async"> [class.multi-select-active]="selectionColumn === column"
<ng-container *ngTemplateOutlet="itemTemplate; context: { entity: e }"></ng-container> [id]="column.key"
</div> cdkDropList
</ng-container> cdkDropListSortingDisabled
</ng-template> >
@for (entity of entities; track trackBy($index, entity)) {
<div
(cdkDragEnded)="stopDragging()"
(cdkDragStarted)="startDragging(column, $event)"
(click)="selectionColumn === column && listingService.select(entity)"
[cdkDragData]="entity"
[class.no-border]="dragging && ctx.draggingEntities.includes(entity)"
[class.selected]="all[entity.id].isSelected$ | async"
[ngClass]="all[entity.id].classes$ | async"
cdkDrag
>
@if (!ctx.draggingEntities.includes(entity)) {
<ng-container *ngTemplateOutlet="itemTemplate; context: { entity: entity }"></ng-container>
}
<ng-template cdkDragPlaceholder>
@for (e of ctx.draggingEntities; track e) {
<div [style.min-height]="itemHeight + 'px'" class="placeholder"></div>
}
</ng-template>
<ng-template cdkDragPreview [matchSize]="true">
@for (e of ctx.draggingEntities; track e) {
<div [class.selected]="all[e.id].isSelected$ | async" [ngClass]="all[e.id].classes$ | async">
<ng-container *ngTemplateOutlet="itemTemplate; context: { entity: e }"></ng-container>
</div>
}
</ng-template>
</div>
}
@if (column.key === addElementColumn) {
<div (click)="addElement.emit()" class="add-btn">
<mat-icon [svgIcon]="addElementIcon"></mat-icon>
</div>
}
</div>
}
</div> </div>
}
<div (click)="addElement.emit()" *ngIf="column.key === addElementColumn" class="add-btn">
<mat-icon [svgIcon]="addElementIcon"></mat-icon>
</div>
</div>
</div> </div>
</div> }
</ng-container> }

View File

@ -1,12 +1,12 @@
<ng-container *ngIf="loadingService.isLoading() as config"> @if (loadingService.isLoading(); as config) {
<section class="full-page-section"></section> <section class="full-page-section"></section>
<section class="full-page-content"> <section class="full-page-content">
<mat-spinner *ngIf="config.type === 'spinner'" diameter="40"></mat-spinner> @if (config.type === 'spinner') {
<mat-spinner diameter="40"></mat-spinner>
<ng-container *ngIf="config.type === 'progress-bar'"> }
@if (config.type === 'progress-bar') {
<iqser-progress-loading [config]="config"></iqser-progress-loading> <iqser-progress-loading [config]="config"></iqser-progress-loading>
</ng-container> }
<ng-content></ng-content> <ng-content></ng-content>
</section> </section>
</ng-container> }

View File

@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, Component, Input, Optional } from '@angular/core'; import { ChangeDetectionStrategy, Component, Input, Optional, OnInit } from '@angular/core';
import { ProgressBarConfigModel } from './progress-bar-config.model'; import { ProgressBarConfigModel } from './progress-bar-config.model';
import { FilterService, INestedFilter } from '../../filtering'; import { FilterService, INestedFilter } from '../../filtering';
import { Observable, of } from 'rxjs'; import { Observable, of } from 'rxjs';
@ -11,7 +11,7 @@ import { map } from 'rxjs/operators';
styleUrls: ['./progress-bar.component.scss'], styleUrls: ['./progress-bar.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class ProgressBarComponent { export class ProgressBarComponent implements OnInit {
@Input() config!: ProgressBarConfigModel; @Input() config!: ProgressBarConfigModel;
@Input() filterKey?: string; @Input() filterKey?: string;

View File

@ -1,4 +1,6 @@
<h1 *ngIf="config.title">{{ config.title }}</h1> @if (config.title) {
<h1>{{ config.title }}</h1>
}
<div class="pt-8"> <div class="pt-8">
<mat-progress-bar <mat-progress-bar
@ -8,10 +10,16 @@
></mat-progress-bar> ></mat-progress-bar>
<div class="pt-10"> <div class="pt-10">
<ng-container *ngIf="config.value">{{ config.value }}%</ng-container> @if (config.value) {
{{ config.value }}%
}
<ng-container *ngIf="config.value && config.remainingTime"><span> - </span></ng-container> @if (config.value && config.remainingTime) {
<span> - </span>
}
<ng-container *ngIf="config.remainingTime">{{ config.remainingTime }} remaining...</ng-container> @if (config.remainingTime) {
{{ config.remainingTime }} remaining...
}
</div> </div>
</div> </div>

View File

@ -1,26 +1,27 @@
<div <div
id="pagination-prev-page-btn"
(click)="selectPage(currentPage - 1)" (click)="selectPage(currentPage - 1)"
[class.disabled]="currentPage < 1" [class.disabled]="currentPage < 1"
class="page" class="page"
id="pagination-prev-page-btn"
translate="pagination.previous" translate="pagination.previous"
></div> ></div>
<span>|</span> <span>|</span>
<div @for (page of displayedPages; track page) {
(click)="selectPage(page)" <div
*ngFor="let page of displayedPages" (click)="selectPage(page)"
[class.active]="page === currentPage" [class.active]="page === currentPage"
[class.dots]="page === '...'" [class.dots]="page === '...'"
class="page" class="page"
[id]="isNumber(page) ? 'pagination-select-page-' + page + '-btn' : 'pagination-pages-between'" [id]="isNumber(page) ? 'pagination-select-page-' + page + '-btn' : 'pagination-pages-between'"
> >
{{ displayValue(page) }} {{ displayValue(page) }}
</div> </div>
}
<span>|</span> <span>|</span>
<div <div
id="pagination-next-page-btn"
(click)="selectPage(currentPage + 1)" (click)="selectPage(currentPage + 1)"
[class.disabled]="currentPage >= totalPages - 1" [class.disabled]="currentPage >= totalPages - 1"
class="page" class="page"
id="pagination-next-page-btn"
translate="pagination.next" translate="pagination.next"
></div> ></div>

View File

@ -1,5 +1,5 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { NgForOf } from '@angular/common';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { PaginationSettings } from './pagination-settings'; import { PaginationSettings } from './pagination-settings';
@ -9,7 +9,7 @@ import { PaginationSettings } from './pagination-settings';
styleUrls: ['./pagination.component.scss'], styleUrls: ['./pagination.component.scss'],
standalone: true, standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [NgForOf, TranslateModule], imports: [TranslateModule],
}) })
export class PaginationComponent { export class PaginationComponent {
displayedPages: (number | string)[] = []; displayedPages: (number | string)[] = [];

View File

@ -1,24 +1,23 @@
```typescript ```typescript
import { Component, OnInit } from "@angular/core"; import { Component, OnInit } from '@angular/core';
import { HttpClient } from "@angular/common/http"; import { HttpClient } from '@angular/common/http';
import { IqserPermissionsService } from "./permissions.service"; import { IqserPermissionsService } from './permissions.service';
import { IqserRolesService } from "./roles.service"; import { IqserRolesService } from './roles.service';
@Component({ @Component({
templateUrl: "./app.component.html" templateUrl: './app.component.html',
}) })
export class AppComponent implements OnInit { export class AppComponent implements OnInit {
constructor( constructor(
private permissionsService: IqserPermissionsService, private permissionsService: IqserPermissionsService,
private rolesService: IqserRolesService private rolesService: IqserRolesService,
) { ) {}
}
ngOnInit(): void { ngOnInit(): void {
const perm = ["can-edit-articles", "can-read-articles"]; const perm = ['can-edit-articles', 'can-read-articles'];
this.permissionsService.load(perm); this.permissionsService.load(perm);
const roles = ["ADMIN", "EDITOR"]; const roles = ['ADMIN', 'EDITOR'];
this.rolesService.load(roles); this.rolesService.load(roles);
} }
} }
@ -82,21 +81,21 @@ export class AppComponent implements OnInit {
``` ```
```typescript ```typescript
import { IqserRoute } from "./models/permissions-router-data.model"; import { IqserRoute } from './models/permissions-router-data.model';
import { IqserPermissionsGuard } from "./permissions-guard.service"; import { IqserPermissionsGuard } from './permissions-guard.service';
const appRoutes: IqserRoute[] = [ const appRoutes: IqserRoute[] = [
{ {
path: "home", path: 'home',
component: HomeComponent, component: HomeComponent,
canActivate: [IqserPermissionsGuard], canActivate: [IqserPermissionsGuard],
data: { data: {
permissions: { permissions: {
allow: ["ADMIN", "MODERATOR"], allow: ['ADMIN', 'MODERATOR'],
redirectTo: "/another-route" redirectTo: '/another-route',
} },
} },
} },
]; ];
const appRoutes1: IqserRoute[] = [ const appRoutes1: IqserRoute[] = [
@ -108,73 +107,73 @@ const appRoutes1: IqserRoute[] = [
permissions: { permissions: {
allow: (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => { allow: (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {
if (route.params['id'] === 42) { if (route.params['id'] === 42) {
return ['MANAGER', "UTILS"] return ['MANAGER', 'UTILS'];
} else { } else {
return 'ADMIN' return 'ADMIN';
} }
} },
} },
} },
} },
]; ];
const appRoutes2: IqserRoute[] = [ const appRoutes2: IqserRoute[] = [
{ {
path: "home", path: 'home',
component: HomeComponent, component: HomeComponent,
canActivate: [IqserPermissionsGuard], canActivate: [IqserPermissionsGuard],
data: { data: {
permissions: { permissions: {
allow: ["ADMIN", "MODERATOR"], allow: ['ADMIN', 'MODERATOR'],
redirectTo: { redirectTo: {
navigationCommands: ["123"], navigationCommands: ['123'],
navigationExtras: { navigationExtras: {
skipLocationChange: true skipLocationChange: true,
} },
} },
} },
}, },
} },
]; ];
const appRoutes3: IqserRoute[] = [ const appRoutes3: IqserRoute[] = [
{ {
path: "home", path: 'home',
component: HomeComponent, component: HomeComponent,
canActivate: [IqserPermissionsGuard], canActivate: [IqserPermissionsGuard],
data: { data: {
permissions: { permissions: {
allow: ["canReadAgenda", "canEditAgenda"], allow: ['canReadAgenda', 'canEditAgenda'],
redirectTo: { redirectTo: {
canReadAgenda: "agendaList", canReadAgenda: 'agendaList',
canEditAgenda: "dashboard", canEditAgenda: 'dashboard',
default: "login" default: 'login',
} },
} },
} },
} },
]; ];
const appRoutes4: IqserRoute[] = [ const appRoutes4: IqserRoute[] = [
{ {
path: "home", path: 'home',
component: HomeComponent, component: HomeComponent,
canActivate: [IqserPermissionsGuard], canActivate: [IqserPermissionsGuard],
data: { data: {
permissions: { permissions: {
allow: ["canEditAgenda"], allow: ['canEditAgenda'],
redirectTo: { redirectTo: {
canEditAgenda: { canEditAgenda: {
navigationCommands: "dashboard", navigationCommands: 'dashboard',
navigationExtras: { navigationExtras: {
skipLocationChange: true skipLocationChange: true,
} },
}, },
default: "login" default: 'login',
} },
} },
} },
} },
]; ];
const appRoutes5: IqserRoute[] = [ const appRoutes5: IqserRoute[] = [
@ -186,22 +185,29 @@ const appRoutes5: IqserRoute[] = [
permissions: { permissions: {
allow: ['canReadAgenda', 'canEditAgenda'], allow: ['canReadAgenda', 'canEditAgenda'],
redirectTo: { redirectTo: {
canReadAgenda: (rejectedPermissionName: string, activateRouteSnapshot: ActivatedRouteSnapshot, routeStateSnapshot: RouterStateSnapshot) => { canReadAgenda: (
rejectedPermissionName: string,
activateRouteSnapshot: ActivatedRouteSnapshot,
routeStateSnapshot: RouterStateSnapshot,
) => {
return 'dashboard'; return 'dashboard';
}, },
canEditAgenda: (rejectedPermissionName: string, activateRouteSnapshot: ActivatedRouteSnapshot, routeStateSnapshot: RouterStateSnapshot) => { canEditAgenda: (
rejectedPermissionName: string,
activateRouteSnapshot: ActivatedRouteSnapshot,
routeStateSnapshot: RouterStateSnapshot,
) => {
return { return {
navigationCommands: ['/dashboard'], navigationCommands: ['/dashboard'],
navigationExtras: { navigationExtras: {
skipLocationChange: true skipLocationChange: true,
} },
} };
}, },
default: 'login' default: 'login',
} },
} },
} },
}, },
]; ];
``` ```

View File

@ -291,7 +291,7 @@ describe('Permission directive angular testing different async functions in role
})); }));
it('should hide the component when one returns falsy value', fakeAsync(() => { it('should hide the component when one returns falsy value', fakeAsync(() => {
let content = getFixtureContent(); const content = getFixtureContent();
expect(content).toBeTruthy(); expect(content).toBeTruthy();
expect(content.innerHTML).toEqual('<div>123</div>'); expect(content.innerHTML).toEqual('<div>123</div>');

View File

@ -1,3 +1,3 @@
<ng-container *ngIf="type$ | async as type"> @if (type$ | async; as type) {
<ng-container *ngTemplateOutlet="templates[type]"></ng-container> <ng-container *ngTemplateOutlet="templates[type]"></ng-container>
</ng-container> }

View File

@ -1,7 +1,7 @@
import { ChangeDetectionStrategy, Component, HostBinding, inject, Input, TemplateRef } from '@angular/core'; import { ChangeDetectionStrategy, Component, HostBinding, inject, Input, TemplateRef } from '@angular/core';
import { SkeletonService } from '../../services'; import { SkeletonService } from '../../services';
import { IqserUserService } from '../../users'; import { IqserUserService } from '../../users';
import { AsyncPipe, NgForOf, NgIf, NgTemplateOutlet } from '@angular/common'; import { AsyncPipe, NgTemplateOutlet } from '@angular/common';
import { tap } from 'rxjs/operators'; import { tap } from 'rxjs/operators';
@Component({ @Component({
@ -10,7 +10,7 @@ import { tap } from 'rxjs/operators';
styleUrls: ['./skeleton.component.scss'], styleUrls: ['./skeleton.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true, standalone: true,
imports: [NgTemplateOutlet, NgIf, AsyncPipe, NgForOf], imports: [NgTemplateOutlet, AsyncPipe],
}) })
export class SkeletonComponent { export class SkeletonComponent {
@Input() templates!: Record<string, TemplateRef<unknown>>; @Input() templates!: Record<string, TemplateRef<unknown>>;

View File

@ -1,14 +1,17 @@
<div [ngClass]="{ small: small }" class="rectangle-container"> <div [ngClass]="{ small: small }" class="rectangle-container">
<div *ngFor="let config of configs" [style]="'flex: ' + (config.length || 1) + ';'" class="section-wrapper"> @for (config of configs; track config) {
<div <div [style]="'flex: ' + (config.length || 1) + ';'" class="section-wrapper">
[className]="'rectangle ' + config.color" <div
[ngStyle]="{ [className]="'rectangle ' + config.color"
'background-color': config.color.includes('#') ? config.color : '', [ngStyle]="{
}" 'background-color': config.color.includes('#') ? config.color : '',
></div> }"
></div>
<div *ngIf="config.label" [class]="config.cssClass + ' clamp-1'" [matTooltip]="config.label" matTooltipPosition="above"> @if (config.label) {
{{ config.label }} <div [class]="config.cssClass + ' clamp-1'" [matTooltip]="config.label" matTooltipPosition="above">
{{ config.label }}
</div>
}
</div> </div>
</div> }
</div> </div>

View File

@ -1,6 +1,6 @@
import { ChangeDetectionStrategy, Component, Input, ViewEncapsulation } from '@angular/core'; import { ChangeDetectionStrategy, Component, Input, ViewEncapsulation } from '@angular/core';
import { StatusBarConfig } from './status-bar-config.model'; import { StatusBarConfig } from './status-bar-config.model';
import { NgClass, NgForOf, NgIf, NgStyle } from '@angular/common'; import { NgClass, NgStyle } from '@angular/common';
import { MatTooltipModule } from '@angular/material/tooltip'; import { MatTooltipModule } from '@angular/material/tooltip';
@Component({ @Component({
@ -10,7 +10,7 @@ import { MatTooltipModule } from '@angular/material/tooltip';
encapsulation: ViewEncapsulation.None, encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true, standalone: true,
imports: [NgClass, NgStyle, NgForOf, MatTooltipModule, NgIf], imports: [NgClass, NgStyle, MatTooltipModule],
}) })
export class StatusBarComponent<T extends string> { export class StatusBarComponent<T extends string> {
@Input() configs: readonly StatusBarConfig<T>[] = []; @Input() configs: readonly StatusBarConfig<T>[] = [];

View File

@ -1,23 +1,35 @@
<div class="row"> <div class="row">
<div *ngIf="title" [attr.aria-label]="title" [class]="options.titleClass"> @if (title) {
{{ title }} <div [attr.aria-label]="title" [class]="options.titleClass">
</div> {{ title }}
</div>
}
<div *ngIf="message && options.enableHtml" [class]="options.messageClass" [innerHTML]="message" aria-live="polite" role="alert"></div> @if (message && options.enableHtml) {
<div [class]="options.messageClass" [innerHTML]="message" aria-live="polite" role="alert"></div>
}
<div *ngIf="message && !options.enableHtml" [attr.aria-label]="message" [class]="options.messageClass" aria-live="polite" role="alert"> @if (message && !options.enableHtml) {
{{ message }} <div [attr.aria-label]="message" [class]="options.messageClass" aria-live="polite" role="alert">
</div> {{ message }}
</div>
}
<div *ngIf="actions?.length" class="actions-wrapper"> @if (actions?.length) {
<a (click)="callAction(action.action)" *ngFor="let action of actions" iqserStopPropagation> <div class="actions-wrapper">
{{ action.title }} @for (action of actions; track action) {
</a> <a (click)="callAction(action.action)" iqserStopPropagation>
</div> {{ action.title }}
</a>
}
</div>
}
</div> </div>
<div class="text-right"> <div class="text-right">
<a (click)="remove()" *ngIf="options.closeButton" class="toast-close-button"> @if (options.closeButton) {
<mat-icon svgIcon="iqser:close"></mat-icon> <a (click)="remove()" class="toast-close-button">
</a> <mat-icon svgIcon="iqser:close"></mat-icon>
</a>
}
</div> </div>

View File

@ -2,14 +2,14 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
import { Toast } from 'ngx-toastr'; import { Toast } from 'ngx-toastr';
import { ToasterActions, ToasterOptions } from '../../services'; import { ToasterActions, ToasterOptions } from '../../services';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { NgForOf, NgIf } from '@angular/common';
import { StopPropagationDirective } from '../../directives'; import { StopPropagationDirective } from '../../directives';
@Component({ @Component({
templateUrl: './toast.component.html', templateUrl: './toast.component.html',
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true, standalone: true,
imports: [MatIconModule, NgIf, StopPropagationDirective, NgForOf], imports: [MatIconModule, StopPropagationDirective],
}) })
export class ToastComponent extends Toast { export class ToastComponent extends Toast {
get actions(): ToasterActions[] { get actions(): ToasterActions[] {

View File

@ -8,38 +8,40 @@
<iqser-spacer [height]="100"></iqser-spacer> <iqser-spacer [height]="100"></iqser-spacer>
<ng-container *ngIf="isLoggedOut || noRoleLogOut"> @if (isLoggedOut || noRoleLogOut) {
<div class="heading-xl" [translate]="isLoggedOut ? translations.IS_LOGGED_OUT : translations.NO_ROLE_LOG_OUT"></div> <div class="heading-xl" [translate]="isLoggedOut ? translations.IS_LOGGED_OUT : translations.NO_ROLE_LOG_OUT"></div>
<iqser-spacer [height]="75"></iqser-spacer> <iqser-spacer [height]="75"></iqser-spacer>
</ng-container> }
<div *ngIf="storedTenants.length" class="pb-30 subheading" translate="tenant-resolve.header.sign-in-previous-domain"></div> @if (storedTenants.length) {
<div class="pb-30 subheading" translate="tenant-resolve.header.sign-in-previous-domain"></div>
}
<div *ngIf="storedTenants.length"> @if (storedTenants.length) {
<div <div>
(click)="select(stored.tenantId)" @for (stored of storedTenants; track stored) {
*ngFor="let stored of storedTenants" <div (click)="select(stored.tenantId)" class="d-flex pointer mat-elevation-z2 card stored-tenant-card mt-10">
class="d-flex pointer mat-elevation-z2 card stored-tenant-card mt-10" <iqser-logo class="card-icon" icon="iqser:logo" mat-card-image></iqser-logo>
> <div class="card-content flex-column">
<iqser-logo class="card-icon" icon="iqser:logo" mat-card-image></iqser-logo> <span class="heading">{{ stored.tenantId }}</span>
</div>
<div class="card-content flex-column"> <mat-icon class="card-icon upside-down" svgIcon="iqser:expand"></mat-icon>
<span class="heading">{{ stored.tenantId }}</span> <div class="remove" iqserStopPropagation>
</div> <mat-icon (click)="removeStored(stored.tenantId)" svgIcon="iqser:close"></mat-icon>
</div>
<mat-icon class="card-icon upside-down" svgIcon="iqser:expand"></mat-icon> </div>
<div class="remove" iqserStopPropagation> }
<mat-icon (click)="removeStored(stored.tenantId)" svgIcon="iqser:close"></mat-icon>
</div>
</div> </div>
</div> }
<div *ngIf="storedTenants.length === 0" class="heading pb-30" translate="tenant-resolve.header.first-time"></div> @if (storedTenants.length === 0) {
<div class="heading pb-30" translate="tenant-resolve.header.first-time"></div>
}
<ng-container *ngIf="storedTenants.length"> @if (storedTenants.length) {
<iqser-spacer [height]="100"></iqser-spacer> <iqser-spacer [height]="100"></iqser-spacer>
<div class="pb-30 subheading" translate="tenant-resolve.header.join-another-domain"></div> <div class="pb-30 subheading" translate="tenant-resolve.header.join-another-domain"></div>
</ng-container> }
<form (submit)="updateTenantSelection()" [formGroup]="form" class="mat-elevation-z16 card input-card d-flex"> <form (submit)="updateTenantSelection()" [formGroup]="form" class="mat-elevation-z16 card input-card d-flex">
<mat-form-field class="iqser-input-group w-full ml-20"> <mat-form-field class="iqser-input-group w-full ml-20">

View File

@ -1,22 +1,25 @@
<div (click)="triggerAttachFile()" (fileDropped)="attachFile($event)" *ngIf="!file" class="upload-area" iqserDragDropFileUpload> @if (!file) {
<mat-icon svgIcon="iqser:upload"></mat-icon> <div (click)="triggerAttachFile()" (fileDropped)="attachFile($event)" class="upload-area" iqserDragDropFileUpload>
<mat-icon svgIcon="iqser:upload"></mat-icon>
<div translate="upload-file.upload-area-text"></div> <div translate="upload-file.upload-area-text"></div>
</div> </div>
<div *ngIf="file" class="file-area"> }
<mat-icon svgIcon="iqser:document"></mat-icon> @if (file) {
<div class="file-area">
<p>{{ file.name }}</p> <mat-icon svgIcon="iqser:document"></mat-icon>
<p>{{ file.name }}</p>
<mat-icon (click)="removeFile()" *ngIf="!readonly" svgIcon="iqser:trash"></mat-icon> @if (!readonly) {
</div> <mat-icon (click)="removeFile()" svgIcon="iqser:trash"></mat-icon>
}
</div>
}
<input <input
#attachFileInput #attachFileInput
id="file-upload-input"
(change)="attachFile($event)" (change)="attachFile($event)"
[accept]="accept" [accept]="accept"
[hidden]="true" [hidden]="true"
class="file-upload-input" class="file-upload-input"
id="file-upload-input"
type="file" type="file"
/> />

View File

@ -1,13 +1,16 @@
<div *ngIf="_user && _user | name: namePipeOptions as userName" class="wrapper"> @if (_user && _user | name: namePipeOptions; as userName) {
<div <div class="wrapper">
[className]="colorClass + ' oval ' + size + (hasBorder ? ' border' : '')" <div
[matTooltipPosition]="tooltipPosition" [className]="colorClass + ' oval ' + size + (hasBorder ? ' border' : '')"
[matTooltip]="showTooltip ? userName : ''" [matTooltipPosition]="tooltipPosition"
> [matTooltip]="showTooltip ? userName : ''"
{{ _user | name: { showInitials: true } }} >
{{ _user | name: { showInitials: true } }}
</div>
@if (withName) {
<div [class.disabled]="disabled" class="clamp-1 username" id="avatarUsername">
{{ userName }}
</div>
}
</div> </div>
}
<div *ngIf="withName" [class.disabled]="disabled" class="clamp-1 username" id="avatarUsername">
{{ userName }}
</div>
</div>

View File

@ -1,14 +1,18 @@
<button [class.overlay]="showDot" [matTooltipPosition]="'below'" [matTooltip]="'user-menu.button-text' | translate" mat-button> <button [class.overlay]="showDot" [matTooltipPosition]="'below'" [matTooltip]="'user-menu.button-text' | translate" mat-button>
<ng-container *ngIf="icon; else initialsAvatar"> @if (icon) {
<mat-icon [svgIcon]="icon" class="mr-8"></mat-icon> <ng-container>
{{ userService.currentUser$ | async | name }} <mat-icon [svgIcon]="icon" class="mr-8"></mat-icon>
</ng-container> {{ userService.currentUser$ | async | name }}
</ng-container>
} @else {
<iqser-initials-avatar [showTooltip]="false" [user]="userService.currentUser$ | async" [withName]="true"></iqser-initials-avatar>
}
<mat-icon *ngIf="showDropdownArrow" iconPositionEnd svgIcon="iqser:arrow-down"></mat-icon> @if (showDropdownArrow) {
<mat-icon iconPositionEnd svgIcon="iqser:arrow-down"></mat-icon>
}
</button> </button>
<div *ngIf="showDot" class="dot"></div> @if (showDot) {
<div class="dot"></div>
<ng-template #initialsAvatar> }
<iqser-initials-avatar [showTooltip]="false" [user]="userService.currentUser$ | async" [withName]="true"></iqser-initials-avatar>
</ng-template>

View File

@ -5,5 +5,5 @@
"types": ["jest", "node"], "types": ["jest", "node"],
"esModuleInterop": true "esModuleInterop": true
}, },
"include": ["./src/lib/**/*.spec.ts", "./src/lib/**/*.d.ts"] "include": ["./src/lib/**/*.spec.ts", "./src/lib/**/*.d.ts", "jest.config.ts"]
} }