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: {
NodeJS: true,
},
ignorePatterns: ['!**/*'],
ignorePatterns: ['!**/*', 'jest.config.ts'],
overrides: [
{
files: ['*.ts'],

View File

@ -1,30 +1,38 @@
<section class="dialog">
<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 class="inline-dialog-toast toast-error">
<div [translate]="config.toastMessage"></div>
<a (click)="showToast = false" class="toast-close-button">
<mat-icon svgIcon="iqser:close"></mat-icon>
</a>
</div>
}
<div class="dialog-content">
<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) {
<div class="iqser-input-group required w-300 mt-24">
<label>{{ inputLabel }}</label>
<input [(ngModel)]="inputValue" id="confirmation-input" />
</div>
}
<div *ngIf="config.checkboxes.length > 0" class="mt-24 checkboxes-wrapper">
<ng-container *ngFor="let checkbox of config.checkboxes">
@if (config.checkboxes.length > 0) {
<div class="mt-24 checkboxes-wrapper">
@for (checkbox of config.checkboxes; track checkbox) {
<mat-checkbox [(ngModel)]="checkbox.value" [class.error]="!checkbox.value && showToast" color="primary">
{{ checkbox.label | translate: config.translateParams }}
</mat-checkbox>
<ng-container *ngTemplateOutlet="checkbox.extraContent; context: { data: checkbox.extraContentData }"></ng-container>
</ng-container>
}
</div>
}
</div>
<div class="dialog-actions">
@ -36,21 +44,26 @@
buttonId="confirm"
></iqser-icon-button>
@if (config.alternativeConfirmationText) {
<iqser-icon-button
(action)="confirm(confirmOptions.CONFIRM_WITH_ACTION)"
*ngIf="config.alternativeConfirmationText"
[disabled]="config.requireInput && confirmationDoesNotMatch()"
[label]="config.alternativeConfirmationText"
[type]="iconButtonTypes.primary"
></iqser-icon-button>
}
<div (click)="confirm(confirmOptions.DISCARD_CHANGES)" *ngIf="config.discardChangesText" class="all-caps-label cancel">
@if (config.discardChangesText) {
<div (click)="confirm(confirmOptions.DISCARD_CHANGES)" class="all-caps-label cancel">
{{ config.discardChangesText }}
</div>
}
<div (click)="deny()" *ngIf="!config.discardChangesText" class="all-caps-label cancel">
@if (!config.discardChangesText) {
<div (click)="deny()" class="all-caps-label cancel">
{{ config.denyText }}
</div>
}
</div>
<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 { FormsModule } from '@angular/forms';
import { MatCheckboxModule } from '@angular/material/checkbox';
@ -72,10 +72,8 @@ function getConfig(options?: IConfirmationDialogData): InternalConfirmationDialo
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [
NgIf,
MatIconModule,
FormsModule,
NgForOf,
MatCheckboxModule,
TranslateModule,
NgTemplateOutlet,

View File

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

View File

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

View File

@ -1,8 +1,5 @@
<div
*ngIf="errorService.connectionStatus$ | async as status"
[@animateOpenClose]="status"
[ngClass]="status"
class="indicator flex-align-items-center"
>
@if (errorService.connectionStatus$ | async; as status) {
<div [@animateOpenClose]="status" [ngClass]="status" class="indicator flex-align-items-center">
<span [translate]="connectionStatusTranslations[status]"></span>
</div>
</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-content flex-align-items-center">
<mat-icon svgIcon="iqser:failure"></mat-icon>
<div [translate]="errorTitle(error)" class="heading-l mt-24"></div>
<div *ngIf="error.message" class="mt-16 error">{{ error.message }}</div>
@if (error.message) {
<div class="mt-16 error">{{ error.message }}</div>
}
<iqser-icon-button
(action)="action(error)"
[icon]="actionIcon(error)"
@ -16,4 +14,4 @@
class="mt-20"
></iqser-icon-button>
</section>
</ng-container>
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,10 +1,13 @@
<div *ngIf="helpModeService.isHelpModeActive$ | async">
@if (helpModeService.isHelpModeActive$ | async) {
<div>
<div class="help-mode-border"></div>
<div class="bottom small-label full-opacity">
<strong>{{ 'help-mode.bottom-text' | translate }}</strong>
<a (click)="helpModeService.openHelpModeDialog()" *ngIf="(helpModeService.helpModeDialogIsOpened$ | async) === false">
@if ((helpModeService.helpModeDialogIsOpened$ | async) === false) {
<a (click)="helpModeService.openHelpModeDialog()">
{{ 'help-mode.instructions' | translate }}
</a>
}
<div class="close">
(esc)
<iqser-circle-button
@ -16,4 +19,5 @@
></iqser-circle-button>
</div>
</div>
</div>
</div>
}

View File

@ -1,7 +1,7 @@
<div [class.row]="displayInRow" class="iqser-input-group">
@for (option of options; track option) {
<div
(click)="toggleOption(option)"
*ngFor="let option of options"
[class.active]="option.value === value?.value"
[class.disabled]="option.disabled"
[id]="groupId(option)"
@ -10,14 +10,14 @@
[ngClass]="{ 'mb-8': !displayInRow, 'mr-8': displayInRow }"
class="option pointer"
>
<div *ngIf="option.icon; else withoutIcon" class="icon-option">
@if (option.icon) {
<div class="icon-option">
<mat-icon [svgIcon]="option.icon" class="icon"></mat-icon>
<div class="text">
<label class="details-radio-label pointer">{{ option.label | translate: option.descriptionParams }}</label>
<span class="hint">{{ option.description | translate: option.descriptionParams | replaceNbsp }}</span>
<div *ngIf="option.extraOption && !option.extraOption.hidden && isSelected(option)" class="iqser-input-group">
@if (option.extraOption && !option.extraOption.hidden && isSelected(option)) {
<div class="iqser-input-group">
<mat-checkbox
(change)="emitExtraOption()"
[(ngModel)]="option.extraOption.checked"
@ -27,24 +27,26 @@
>
{{ option.extraOption.label | translate }}
</mat-checkbox>
@if (option.extraOption.description) {
<span
*ngIf="option.extraOption.description"
[innerHTML]="option.extraOption.description | translate"
class="hint extra-option-description"
></span>
}
</div>
}
</div>
<mat-icon *ngIf="isSelected(option)" class="checked" svgIcon="iqser:radio-selected"></mat-icon>
@if (isSelected(option)) {
<mat-icon class="checked" svgIcon="iqser:radio-selected"></mat-icon>
}
</div>
<ng-template #withoutIcon>
} @else {
<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>

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 { FormsModule, NG_VALIDATORS, NG_VALUE_ACCESSOR, ReactiveFormsModule } from '@angular/forms';
import { MatCheckboxModule } from '@angular/material/checkbox';
@ -28,12 +28,10 @@ import { DetailsRadioOption } from './details-radio-option';
},
],
imports: [
NgForOf,
NgClass,
RoundCheckboxComponent,
TranslateModule,
MatIconModule,
NgIf,
FormsModule,
MatCheckboxModule,
ReactiveFormsModule,

View File

@ -1,7 +1,9 @@
<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
(ngModelChange)="onChange($event)"
[(ngModel)]="input"
@ -15,11 +17,11 @@
<mat-icon matDatepickerToggleIcon svgIcon="iqser:calendar"></mat-icon>
</mat-datepicker-toggle>
<mat-datepicker #picker (closed)="onCloseDatepicker()" (opened)="onOpenDatepicker()"></mat-datepicker>
</ng-container>
}
@if (isText) {
<input
(ngModelChange)="onChange($event)"
*ngIf="isText"
[(ngModel)]="input"
[disabled]="disabled"
[id]="id"
@ -27,14 +29,9 @@
iqserStopPropagation
type="text"
/>
}
<input
(ngModelChange)="onChange($event)"
*ngIf="isNumber"
[(ngModel)]="input"
[disabled]="disabled"
[id]="id"
iqserStopPropagation
type="number"
/>
@if (isNumber) {
<input (ngModelChange)="onChange($event)" [(ngModel)]="input" [disabled]="disabled" [id]="id" iqserStopPropagation type="number" />
}
</div>

View File

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

View File

@ -1,11 +1,12 @@
<ng-container *ngIf="!editing">
<div *ngIf="showPreview">
@if (!editing) {
@if (showPreview) {
<div>
{{ value }}
</div>
}
<div class="flex">
@if (canEdit) {
<iqser-circle-button
*ngIf="canEdit"
(action)="editing = true"
[tooltip]="editTooltip"
[type]="buttonsType"
@ -13,22 +14,17 @@
class="edit-button"
icon="iqser:edit"
></iqser-circle-button>
}
<ng-content select="[slot=editing]"></ng-content>
</div>
</ng-container>
}
<ng-container *ngIf="editing">
@if (editing) {
<form (submit)="saveValue()">
<div [class]="'iqser-input-group ' + class">
<input
*ngIf="!parentId; else expandableInput"
(ngModelChange)="newValue = $event"
[ngModel]="value"
[placeholder]="placeholder"
name="name"
/>
<ng-template #expandableInput>
@if (!parentId) {
<input (ngModelChange)="newValue = $event" [ngModel]="value" [placeholder]="placeholder" name="name" />
} @else {
<textarea
(ngModelChange)="newValue = $event"
[ngModel]="value"
@ -38,13 +34,11 @@
[style.width]="this.textArea.width + 'px'"
[style.height]="this.textArea.height + 'px'"
></textarea>
</ng-template>
}
</div>
</form>
<div class="flex">
<iqser-circle-button (action)="saveValue()" [tooltip]="saveTooltip" [type]="buttonsType" icon="iqser:check"></iqser-circle-button>
<iqser-circle-button
(action)="editing = false"
[tooltip]="cancelTooltip"
@ -52,4 +46,4 @@
icon="iqser:close"
></iqser-circle-button>
</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 { FormsModule } from '@angular/forms';
import { CircleButtonComponent, CircleButtonType, CircleButtonTypes } from '../../buttons';
@ -9,7 +8,7 @@ import { CircleButtonComponent, CircleButtonType, CircleButtonTypes } from '../.
styleUrls: ['./editable-input.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [NgIf, CircleButtonComponent, FormsModule],
imports: [CircleButtonComponent, FormsModule],
})
export class EditableInputComponent implements OnChanges {
@Input() id?: string;

View File

@ -11,22 +11,24 @@
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
(action)="reset()"
*ngIf="isSearch && hasContent"
[buttonId]="inputId + '-clear'"
icon="iqser:close"
></iqser-circle-button>
@if (isSearch && hasContent) {
<iqser-circle-button (action)="reset()" [buttonId]="inputId + '-clear'" icon="iqser:close"></iqser-circle-button>
}
@if (!isSearch) {
<iqser-circle-button
(action)="executeAction()"
*ngIf="!isSearch"
[buttonId]="actionButtonId"
[disabled]="!hasContent"
[icon]="icon!"
></iqser-circle-button>
}
</form>

View File

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

View File

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

View File

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

View File

@ -1,94 +1,98 @@
<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) {
<div class="filters">
<ng-content select="[slot=beforeFilters]"></ng-content>
<div
*ngIf="filters.length && searchPosition !== searchPositions.beforeFilters"
class="text-muted"
translate="filters.filter-by"
></div>
<ng-container *ngIf="searchPosition === searchPositions.beforeFilters" [ngTemplateOutlet]="searchBar"></ng-container>
<ng-container *ngFor="let filter of filters; trackBy: trackByLabel">
<iqser-popup-filter
*ngIf="!filter.hide"
[primaryFiltersSlug]="filter.slug"
[attr.help-mode-key]="filterHelpModeKey"
></iqser-popup-filter>
</ng-container>
<ng-container *ngFor="let filter$ of filterService.singleFilters">
<iqser-single-filter *ngIf="filter$ | async as filter" [filter]="filter"></iqser-single-filter>
</ng-container>
<ng-container *ngIf="searchPosition === searchPositions.afterFilters" [ngTemplateOutlet]="searchBar"></ng-container>
@if (filters.length && searchPosition !== searchPositions.beforeFilters) {
<div class="text-muted" translate="filters.filter-by"></div>
}
@if (searchPosition === searchPositions.beforeFilters) {
<ng-container [ngTemplateOutlet]="searchBar"></ng-container>
}
@for (filter of filters; track trackByLabel($index, filter)) {
@if (!filter.hide) {
<iqser-popup-filter [primaryFiltersSlug]="filter.slug" [attr.help-mode-key]="filterHelpModeKey"></iqser-popup-filter>
}
}
@for (filter$ of filterService.singleFilters; track filter$) {
@if (filter$ | async; as filter) {
<iqser-single-filter [filter]="filter"></iqser-single-filter>
}
}
@if (searchPosition === searchPositions.afterFilters) {
<ng-container [ngTemplateOutlet]="searchBar"></ng-container>
}
@if (!hideResetButton && (showResetFilters$ | async) === true) {
<div
(click)="resetFilters()"
*ngIf="!hideResetButton && (showResetFilters$ | async) === true"
[attr.help-mode-key]="'filter_' + helpModeKey + '_list'"
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>
@if (showCloseButton || actionConfigs || buttonConfigs || viewModeSelection) {
<div class="actions">
@if (searchPosition === searchPositions.withActions) {
<ng-container [ngTemplateOutlet]="searchBar"></ng-container>
}
<ng-container [ngTemplateOutlet]="viewModeSelection"></ng-container>
<ng-container *ngFor="let config of buttonConfigs; trackBy: trackByLabel">
@for (config of buttonConfigs; track trackByLabel($index, config)) {
@if (!config.hide) {
<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">
@for (config of actionConfigs; track trackByLabel($index, config)) {
@if (!config.hide) {
<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>
@if (showCloseButton) {
<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>
<ng-template #searchBar>
@if (searchPlaceholder && searchService) {
<iqser-input-with-action
[inputId]="searchInputId"
(valueChange)="searchService.searchValue = $event"
*ngIf="searchPlaceholder && searchService"
[class.mr-8]="searchPosition === searchPositions.beforeFilters"
[placeholder]="searchPlaceholder"
[value]="searchService.valueChanges$ | async"
[width]="searchWidth"
></iqser-input-with-action>
}
</ng-template>

View File

@ -1,14 +1,24 @@
<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>
<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">
<div *ngIf="!!sortByKey" [class.force-display]="sortingOption.column === sortByKey" class="sort-arrows-container">
<mat-icon *ngIf="sortingOption.order === sortingOrders.asc" svgIcon="iqser:sort-asc"></mat-icon>
<mat-icon *ngIf="sortingOption.order === sortingOrders.desc" svgIcon="iqser:sort-desc"></mat-icon>
@if (sortingService?.sortingOption$ | async; as sortingOption) {
@if (!!sortByKey) {
<div [class.force-display]="sortingOption.column === sortByKey" class="sort-arrows-container">
@if (sortingOption.order === sortingOrders.asc) {
<mat-icon svgIcon="iqser:sort-asc"></mat-icon>
}
@if (sortingOption.order === sortingOrders.desc) {
<mat-icon svgIcon="iqser:sort-desc"></mat-icon>
}
</div>
</ng-container>
}
}
</div>

View File

@ -10,10 +10,10 @@
<ng-container *cdkVirtualFor="let entity of listingService.sortedDisplayedEntities$ | async; trackBy: trackBy">
<!-- 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 -->
@if (itemMouseEnterFn || itemMouseLeaveFn) {
<div
(mouseenter)="itemMouseEnterFn && itemMouseEnterFn(entity)"
(mouseleave)="itemMouseLeaveFn && itemMouseLeaveFn(entity)"
*ngIf="itemMouseEnterFn || itemMouseLeaveFn; else withoutMouseEvents"
[class.help-mode-active]="helpModeService?.isHelpModeActive$ | async"
[id]="rowIdPrefix + '-' + ((entity[namePropertyKey] | snakeCase) ?? entity.id)"
[ngClass]="getTableItemClasses(entity)"
@ -25,8 +25,7 @@
[selectionEnabled]="selectionEnabled"
></iqser-table-item>
</div>
<ng-template #withoutMouseEvents>
} @else {
<div
[class.help-mode-active]="helpModeService?.isHelpModeActive$ | async"
[id]="rowIdPrefix + '-' + ((entity[namePropertyKey] | snakeCase) ?? entity.id)"
@ -39,7 +38,7 @@
[selectionEnabled]="selectionEnabled"
></iqser-table-item>
</div>
</ng-template>
}
</ng-container>
</cdk-virtual-scroll-viewport>
</div>

View File

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

View File

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

View File

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

View File

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

View File

@ -1,19 +1,19 @@
<ng-container *ngIf="componentContext$ | async as ctx">
@if (componentContext$ | async; as ctx) {
<iqser-table-header [tableHeaderLabel]="listingComponent.tableHeaderLabel" listingMode="workflow"></iqser-table-header>
@if (ctx.noData) {
<iqser-empty-state
(action)="noDataAction.emit()"
*ngIf="ctx.noData"
[buttonIcon]="noDataButtonIcon"
[buttonLabel]="noDataButtonLabel"
[icon]="noDataIcon"
[showButton]="showNoDataButton"
[text]="noDataText"
></iqser-empty-state>
<div *ngIf="!ctx.noData" cdkDropListGroup class="columns-wrapper">
}
@if (!ctx.noData) {
<div cdkDropListGroup class="columns-wrapper">
@for (column of config.columns; track column) {
<div
*ngFor="let column of config.columns"
[class.dragging]="dragging"
[class.list-can-receive]="isReceiving(column)"
[class.list-dragging]="isDragging(column)"
@ -21,10 +21,14 @@
[style.--color]="column.color"
class="column"
>
<iqser-column-header [(selectionColumn)]="selectionColumn" [bulkActions]="bulkActions" [column]="column"></iqser-column-header>
<iqser-column-header
[(selectionColumn)]="selectionColumn"
[bulkActions]="bulkActions"
[column]="column"
></iqser-column-header>
@if (column.entities | async; as entities) {
<div
(cdkDropListDropped)="move($event)"
*ngIf="column.entities | async as entities"
[cdkDropListData]="entities"
[cdkDropListEnterPredicate]="canMoveTo(column)"
[class.multi-select-active]="selectionColumn === column"
@ -32,38 +36,43 @@
cdkDropList
cdkDropListSortingDisabled
>
@for (entity of entities; track trackBy($index, entity)) {
<div
(cdkDragEnded)="stopDragging()"
(cdkDragStarted)="startDragging(column, $event)"
(click)="selectionColumn === column && listingService.select(entity)"
*ngFor="let entity of entities; trackBy: trackBy"
[cdkDragData]="entity"
[class.no-border]="dragging && ctx.draggingEntities.includes(entity)"
[class.selected]="all[entity.id].isSelected$ | async"
[ngClass]="all[entity.id].classes$ | async"
cdkDrag
>
<ng-container *ngIf="!ctx.draggingEntities.includes(entity)">
@if (!ctx.draggingEntities.includes(entity)) {
<ng-container *ngTemplateOutlet="itemTemplate; context: { entity: entity }"></ng-container>
</ng-container>
}
<ng-template cdkDragPlaceholder>
<div *ngFor="let e of ctx.draggingEntities" [style.min-height]="itemHeight + 'px'" class="placeholder"></div>
@for (e of ctx.draggingEntities; track e) {
<div [style.min-height]="itemHeight + 'px'" class="placeholder"></div>
}
</ng-template>
<ng-template cdkDragPreview [matchSize]="true">
<ng-container *ngFor="let e of ctx.draggingEntities">
@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-container>
}
</ng-template>
</div>
<div (click)="addElement.emit()" *ngIf="column.key === addElementColumn" class="add-btn">
}
@if (column.key === addElementColumn) {
<div (click)="addElement.emit()" class="add-btn">
<mat-icon [svgIcon]="addElementIcon"></mat-icon>
</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-content">
<mat-spinner *ngIf="config.type === 'spinner'" diameter="40"></mat-spinner>
<ng-container *ngIf="config.type === 'progress-bar'">
@if (config.type === 'spinner') {
<mat-spinner diameter="40"></mat-spinner>
}
@if (config.type === 'progress-bar') {
<iqser-progress-loading [config]="config"></iqser-progress-loading>
</ng-container>
}
<ng-content></ng-content>
</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 { FilterService, INestedFilter } from '../../filtering';
import { Observable, of } from 'rxjs';
@ -11,7 +11,7 @@ import { map } from 'rxjs/operators';
styleUrls: ['./progress-bar.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ProgressBarComponent {
export class ProgressBarComponent implements OnInit {
@Input() config!: ProgressBarConfigModel;
@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">
<mat-progress-bar
@ -8,10 +10,16 @@
></mat-progress-bar>
<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>

View File

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

View File

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

View File

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

View File

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

View File

@ -1,14 +1,17 @@
<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 [style]="'flex: ' + (config.length || 1) + ';'" class="section-wrapper">
<div
[className]="'rectangle ' + config.color"
[ngStyle]="{
'background-color': config.color.includes('#') ? config.color : '',
}"
></div>
<div *ngIf="config.label" [class]="config.cssClass + ' clamp-1'" [matTooltip]="config.label" matTooltipPosition="above">
@if (config.label) {
<div [class]="config.cssClass + ' clamp-1'" [matTooltip]="config.label" matTooltipPosition="above">
{{ config.label }}
</div>
}
</div>
}
</div>

View File

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

View File

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

View File

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

View File

@ -8,38 +8,40 @@
<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>
<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">
<div
(click)="select(stored.tenantId)"
*ngFor="let stored of storedTenants"
class="d-flex pointer mat-elevation-z2 card stored-tenant-card mt-10"
>
@if (storedTenants.length) {
<div>
@for (stored of storedTenants; track stored) {
<div (click)="select(stored.tenantId)" 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">
<span class="heading">{{ stored.tenantId }}</span>
</div>
<mat-icon class="card-icon upside-down" svgIcon="iqser:expand"></mat-icon>
<div class="remove" iqserStopPropagation>
<mat-icon (click)="removeStored(stored.tenantId)" svgIcon="iqser:close"></mat-icon>
</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>
<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">
<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) {
<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>
<div *ngIf="file" class="file-area">
</div>
}
@if (file) {
<div class="file-area">
<mat-icon svgIcon="iqser:document"></mat-icon>
<p>{{ file.name }}</p>
<mat-icon (click)="removeFile()" *ngIf="!readonly" svgIcon="iqser:trash"></mat-icon>
</div>
@if (!readonly) {
<mat-icon (click)="removeFile()" svgIcon="iqser:trash"></mat-icon>
}
</div>
}
<input
#attachFileInput
id="file-upload-input"
(change)="attachFile($event)"
[accept]="accept"
[hidden]="true"
class="file-upload-input"
id="file-upload-input"
type="file"
/>

View File

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

View File

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

View File

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