Compare commits
8 Commits
master
...
release-4.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3909614988 | ||
|
|
b8ba2191f7 | ||
|
|
25f63ef7c6 | ||
|
|
efd7e2a085 | ||
|
|
f685955f8f | ||
|
|
7edde2a01f | ||
|
|
e6e0686794 | ||
|
|
43bfeb4e1e |
@ -206,7 +206,6 @@ module.exports = {
|
|||||||
],
|
],
|
||||||
rules: {
|
rules: {
|
||||||
'rxjs/no-ignored-subscription': 'error',
|
'rxjs/no-ignored-subscription': 'error',
|
||||||
'@angular-eslint/prefer-standalone': 'off',
|
|
||||||
'@angular-eslint/directive-selector': [
|
'@angular-eslint/directive-selector': [
|
||||||
'error',
|
'error',
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,19 +0,0 @@
|
|||||||
sonarqube:
|
|
||||||
stage: test
|
|
||||||
image:
|
|
||||||
name: sonarsource/sonar-scanner-cli:11.1
|
|
||||||
entrypoint:
|
|
||||||
- ''
|
|
||||||
variables:
|
|
||||||
SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar"
|
|
||||||
GIT_DEPTH: '0'
|
|
||||||
cache:
|
|
||||||
key: "${CI_JOB_NAME}"
|
|
||||||
paths:
|
|
||||||
- ".sonar/cache"
|
|
||||||
script:
|
|
||||||
- sonar-scanner
|
|
||||||
rules:
|
|
||||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
||||||
- if: "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH"
|
|
||||||
- if: "$CI_COMMIT_BRANCH =~ /^release/"
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
sonar.projectKey=common-ui
|
|
||||||
sonar.qualitygate.wait=false
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
<svg width="12" height="8" viewBox="0 0 12 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M6 7.4L0 1.4L1.4 0L6 4.6L10.6 0L12 1.4L6 7.4Z" fill="currentColor" />
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 184 B |
@ -1,3 +0,0 @@
|
|||||||
<svg width="12" height="8" viewBox="0 0 12 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M6 2.8L1.4 7.4L0 6L6 0L12 6L10.6 7.4L6 2.8Z" fill="currentColor" />
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 182 B |
@ -5,7 +5,6 @@ $ripple-size: 26px;
|
|||||||
flex: 0 0 $checkbox-size;
|
flex: 0 0 $checkbox-size;
|
||||||
width: $checkbox-size;
|
width: $checkbox-size;
|
||||||
height: $checkbox-size;
|
height: $checkbox-size;
|
||||||
margin-top: 4px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mat-mdc-checkbox,
|
.mat-mdc-checkbox,
|
||||||
@ -25,14 +24,10 @@ $ripple-size: 26px;
|
|||||||
--mdc-checkbox-selected-hover-state-layer-color: var(--iqser-primary);
|
--mdc-checkbox-selected-hover-state-layer-color: var(--iqser-primary);
|
||||||
--mdc-checkbox-selected-pressed-state-layer-color: var(--iqser-primary);
|
--mdc-checkbox-selected-pressed-state-layer-color: var(--iqser-primary);
|
||||||
|
|
||||||
.mdc-form-field {
|
.mdc-form-field > label {
|
||||||
align-items: start;
|
padding-left: 8px;
|
||||||
|
line-height: 24px;
|
||||||
& > label {
|
white-space: nowrap;
|
||||||
padding-left: 8px;
|
|
||||||
line-height: 24px;
|
|
||||||
white-space: normal;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mdc-checkbox__ripple {
|
.mdc-checkbox__ripple {
|
||||||
|
|||||||
@ -22,14 +22,6 @@
|
|||||||
font-size: var(--iqser-font-size);
|
font-size: var(--iqser-font-size);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.extra-small {
|
|
||||||
height: 16px;
|
|
||||||
width: 16px;
|
|
||||||
min-width: 16px;
|
|
||||||
font-size: 10px;
|
|
||||||
font-weight: lighter;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.gray-dark {
|
&.gray-dark {
|
||||||
background-color: var(--iqser-user-avatar-1);
|
background-color: var(--iqser-user-avatar-1);
|
||||||
color: var(--iqser-text);
|
color: var(--iqser-text);
|
||||||
|
|||||||
@ -47,10 +47,7 @@
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
padding-bottom: 8px;
|
padding-bottom: 8px;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
&.redaction,
|
|
||||||
&.force-annotation {
|
|
||||||
iqser-details-radio {
|
iqser-details-radio {
|
||||||
padding-top: 20px;
|
padding-top: 20px;
|
||||||
}
|
}
|
||||||
@ -75,7 +72,3 @@
|
|||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.large-form-dialog .dialog > form {
|
|
||||||
display: contents;
|
|
||||||
}
|
|
||||||
|
|||||||
@ -7,7 +7,6 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
background: var(--iqser-alt-background);
|
background: var(--iqser-alt-background);
|
||||||
height: 68px;
|
|
||||||
|
|
||||||
&.drag-over {
|
&.drag-over {
|
||||||
background-color: var(--iqser-file-drop-drag-over);
|
background-color: var(--iqser-file-drop-drag-over);
|
||||||
@ -16,6 +15,7 @@
|
|||||||
|
|
||||||
.upload-area {
|
.upload-area {
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
|
height: 88px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 0 32px;
|
padding: 0 32px;
|
||||||
|
|
||||||
@ -33,6 +33,7 @@
|
|||||||
|
|
||||||
.file-area {
|
.file-area {
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
height: 48px;
|
||||||
|
|
||||||
mat-icon:first-child {
|
mat-icon:first-child {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
|
|||||||
@ -156,11 +156,9 @@ section.settings {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
background: var(--iqser-background);
|
background: var(--iqser-background);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
&.with-transition {
|
transition:
|
||||||
transition:
|
width ease-in-out 0.2s,
|
||||||
width ease-in-out 0.2s,
|
min-width ease-in-out 0.2s;
|
||||||
min-width ease-in-out 0.2s;
|
|
||||||
}
|
|
||||||
@include common-mixins.scroll-bar;
|
@include common-mixins.scroll-bar;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
@ -256,10 +254,6 @@ section.settings {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cursor-default {
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fit-content {
|
.fit-content {
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
@use 'sass:string';
|
|
||||||
@use 'sass:list';
|
|
||||||
/* Margins, paddings */
|
/* Margins, paddings */
|
||||||
|
|
||||||
$start: 0;
|
$start: 0;
|
||||||
@ -9,19 +7,19 @@ $values: '';
|
|||||||
$sides: (top, bottom, left, right);
|
$sides: (top, bottom, left, right);
|
||||||
|
|
||||||
@for $i from $start + 1 through $end {
|
@for $i from $start + 1 through $end {
|
||||||
$values: list.append($values, $i, comma);
|
$values: append($values, $i, comma);
|
||||||
$values: list.set-nth($values, 1, $start);
|
$values: set-nth($values, 1, $start);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Check if !important can be avoided
|
// TODO: Check if !important can be avoided
|
||||||
|
|
||||||
@each $space in $values {
|
@each $space in $values {
|
||||||
@each $side in $sides {
|
@each $side in $sides {
|
||||||
.m#{string.slice($side, 0, 1)}-#{$space} {
|
.m#{str-slice($side, 0, 1)}-#{$space} {
|
||||||
margin-#{$side}: #{$space}px !important;
|
margin-#{$side}: #{$space}px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.p#{string.slice($side, 0, 1)}-#{$space} {
|
.p#{str-slice($side, 0, 1)}-#{$space} {
|
||||||
padding-#{$side}: #{$space}px !important;
|
padding-#{$side}: #{$space}px !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import { randomString } from '../../utils';
|
|||||||
templateUrl: './chevron-button.component.html',
|
templateUrl: './chevron-button.component.html',
|
||||||
styleUrls: ['./chevron-button.component.scss'],
|
styleUrls: ['./chevron-button.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
standalone: true,
|
||||||
imports: [MatIconModule, MatButtonModule],
|
imports: [MatIconModule, MatButtonModule],
|
||||||
})
|
})
|
||||||
export class ChevronButtonComponent {
|
export class ChevronButtonComponent {
|
||||||
|
|||||||
@ -24,6 +24,7 @@ import { CircleButtonType, CircleButtonTypes } from '../types/circle-button.type
|
|||||||
templateUrl: './circle-button.component.html',
|
templateUrl: './circle-button.component.html',
|
||||||
styleUrls: ['./circle-button.component.scss'],
|
styleUrls: ['./circle-button.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
standalone: true,
|
||||||
imports: [MatTooltipModule, MatIconModule, MatButtonModule, StopPropagationDirective],
|
imports: [MatTooltipModule, MatIconModule, MatButtonModule, StopPropagationDirective],
|
||||||
})
|
})
|
||||||
export class CircleButtonComponent {
|
export class CircleButtonComponent {
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import { IconButtonType, IconButtonTypes } from '../types/icon-button.type';
|
|||||||
selector: 'iqser-icon-button',
|
selector: 'iqser-icon-button',
|
||||||
templateUrl: './icon-button.component.html',
|
templateUrl: './icon-button.component.html',
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
standalone: true,
|
||||||
imports: [NgClass, MatButtonModule, MatIconModule, StopPropagationDirective],
|
imports: [NgClass, MatButtonModule, MatIconModule, StopPropagationDirective],
|
||||||
})
|
})
|
||||||
export class IconButtonComponent {
|
export class IconButtonComponent {
|
||||||
|
|||||||
@ -11,10 +11,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
<div class="dialog-content">
|
<div class="dialog-content">
|
||||||
@if (config.component) {
|
|
||||||
<ng-container #detailsComponent></ng-container>
|
|
||||||
}
|
|
||||||
|
|
||||||
<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>
|
||||||
@if (config.details) {
|
@if (config.details) {
|
||||||
<p [innerHTML]="config.details" class="mt-0"></p>
|
<p [innerHTML]="config.details" class="mt-0"></p>
|
||||||
@ -39,20 +35,14 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dialog-actions" [class.reverse]="config.cancelButtonPrimary">
|
<div class="dialog-actions">
|
||||||
@if (!config.cancelButtonPrimary) {
|
<iqser-icon-button
|
||||||
<iqser-icon-button
|
(action)="confirm(confirmOption)"
|
||||||
(action)="confirm(confirmOption)"
|
[disabled]="(config.requireInput && confirmationDoesNotMatch()) || config.disableConfirm"
|
||||||
[disabled]="(config.requireInput && confirmationDoesNotMatch()) || config.disableConfirm"
|
[label]="config.confirmationText"
|
||||||
[label]="config.confirmationText"
|
[type]="iconButtonTypes.primary"
|
||||||
[type]="iconButtonTypes.primary"
|
buttonId="confirm"
|
||||||
buttonId="confirm"
|
></iqser-icon-button>
|
||||||
></iqser-icon-button>
|
|
||||||
} @else {
|
|
||||||
<div (click)="confirm(confirmOption)" class="all-caps-label cancel no-uppercase" id="confirm">
|
|
||||||
{{ config.confirmationText }}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (config.alternativeConfirmationText) {
|
@if (config.alternativeConfirmationText) {
|
||||||
<iqser-icon-button
|
<iqser-icon-button
|
||||||
@ -70,13 +60,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@if (!config.discardChangesText) {
|
@if (!config.discardChangesText) {
|
||||||
@if (config.cancelButtonPrimary) {
|
<div (click)="deny()" class="all-caps-label cancel">
|
||||||
<iqser-icon-button (click)="deny()" [label]="config.denyText" [type]="iconButtonTypes.primary"></iqser-icon-button>
|
{{ config.denyText }}
|
||||||
} @else {
|
</div>
|
||||||
<div (click)="deny()" class="all-caps-label cancel">
|
|
||||||
{{ config.denyText }}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -6,12 +6,3 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.reverse {
|
|
||||||
flex-direction: row-reverse;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-uppercase {
|
|
||||||
text-transform: unset;
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,24 +1,15 @@
|
|||||||
import { NgTemplateOutlet } from '@angular/common';
|
import { NgTemplateOutlet } from '@angular/common';
|
||||||
import {
|
import { ChangeDetectionStrategy, Component, HostListener, inject, TemplateRef } from '@angular/core';
|
||||||
AfterViewInit,
|
|
||||||
ChangeDetectionStrategy,
|
|
||||||
Component,
|
|
||||||
HostListener,
|
|
||||||
inject,
|
|
||||||
TemplateRef,
|
|
||||||
Type,
|
|
||||||
viewChild,
|
|
||||||
ViewContainerRef,
|
|
||||||
} 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';
|
||||||
import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog';
|
import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog';
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
||||||
import { CircleButtonComponent, IconButtonTypes } from '../../buttons';
|
import { CircleButtonComponent } from '../../buttons/circle-button/circle-button.component';
|
||||||
import { IconButtonComponent } from '../../buttons';
|
import { IconButtonComponent } from '../../buttons/icon-button/icon-button.component';
|
||||||
import { ValuesOf } from '../../utils';
|
import { IconButtonTypes } from '../../buttons/types/icon-button.type';
|
||||||
|
import { ValuesOf } from '../../utils/types/utility-types';
|
||||||
|
|
||||||
export const TitleColors = {
|
export const TitleColors = {
|
||||||
DEFAULT: 'default',
|
DEFAULT: 'default',
|
||||||
@ -57,9 +48,6 @@ interface InternalConfirmationDialogData {
|
|||||||
readonly checkboxes: CheckBox[];
|
readonly checkboxes: CheckBox[];
|
||||||
readonly checkboxesValidation: boolean;
|
readonly checkboxesValidation: boolean;
|
||||||
readonly toastMessage?: string;
|
readonly toastMessage?: string;
|
||||||
readonly component?: Type<unknown>;
|
|
||||||
readonly componentInputs?: { [key: string]: unknown };
|
|
||||||
readonly cancelButtonPrimary?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IConfirmationDialogData = Partial<InternalConfirmationDialogData>;
|
export type IConfirmationDialogData = Partial<InternalConfirmationDialogData>;
|
||||||
@ -77,9 +65,6 @@ function getConfig(options?: IConfirmationDialogData): InternalConfirmationDialo
|
|||||||
denyText: options?.denyText ?? _('common.confirmation-dialog.deny'),
|
denyText: options?.denyText ?? _('common.confirmation-dialog.deny'),
|
||||||
checkboxes: options?.checkboxes ?? [],
|
checkboxes: options?.checkboxes ?? [],
|
||||||
checkboxesValidation: typeof options?.checkboxesValidation === 'boolean' ? options.checkboxesValidation : true,
|
checkboxesValidation: typeof options?.checkboxesValidation === 'boolean' ? options.checkboxesValidation : true,
|
||||||
component: options?.component,
|
|
||||||
componentInputs: options?.componentInputs,
|
|
||||||
cancelButtonPrimary: options?.cancelButtonPrimary ?? false,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,6 +72,7 @@ function getConfig(options?: IConfirmationDialogData): InternalConfirmationDialo
|
|||||||
templateUrl: './confirmation-dialog.component.html',
|
templateUrl: './confirmation-dialog.component.html',
|
||||||
styleUrls: ['./confirmation-dialog.component.scss'],
|
styleUrls: ['./confirmation-dialog.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
standalone: true,
|
||||||
imports: [
|
imports: [
|
||||||
MatIconModule,
|
MatIconModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
@ -98,14 +84,13 @@ function getConfig(options?: IConfirmationDialogData): InternalConfirmationDialo
|
|||||||
MatDialogModule,
|
MatDialogModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class ConfirmationDialogComponent implements AfterViewInit {
|
export class ConfirmationDialogComponent {
|
||||||
readonly config = getConfig(inject(MAT_DIALOG_DATA));
|
readonly config = getConfig(inject(MAT_DIALOG_DATA));
|
||||||
inputValue = '';
|
inputValue = '';
|
||||||
showToast = false;
|
showToast = false;
|
||||||
readonly inputLabel: string;
|
readonly inputLabel: string;
|
||||||
readonly confirmOptions = ConfirmOptions;
|
readonly confirmOptions = ConfirmOptions;
|
||||||
readonly iconButtonTypes = IconButtonTypes;
|
readonly iconButtonTypes = IconButtonTypes;
|
||||||
readonly detailsComponentRef = viewChild.required('detailsComponent', { read: ViewContainerRef });
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _dialogRef: MatDialogRef<ConfirmationDialogComponent, ConfirmOption>,
|
private readonly _dialogRef: MatDialogRef<ConfirmationDialogComponent, ConfirmOption>,
|
||||||
@ -130,19 +115,13 @@ export class ConfirmationDialogComponent implements AfterViewInit {
|
|||||||
return ConfirmOptions.CONFIRM;
|
return ConfirmOptions.CONFIRM;
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('window:keyup.enter', ['$event'])
|
@HostListener('window:keyup.enter')
|
||||||
onKeyupEnter(event: KeyboardEvent): void {
|
onKeyupEnter(): void {
|
||||||
event?.stopImmediatePropagation();
|
if (this.config.requireInput && !this.confirmationDoesNotMatch()) {
|
||||||
if (!this.config.requireInput || !this.confirmationDoesNotMatch()) {
|
this.confirm(ConfirmOptions.CONFIRM);
|
||||||
if (!this.config.cancelButtonPrimary) this.confirm(ConfirmOptions.CONFIRM);
|
|
||||||
else this.deny();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ngAfterViewInit() {
|
|
||||||
this.#initializeDetailsComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
confirmationDoesNotMatch(): boolean {
|
confirmationDoesNotMatch(): boolean {
|
||||||
return this.inputValue.toLowerCase() !== this.config.confirmationText.toLowerCase();
|
return this.inputValue.toLowerCase() !== this.config.confirmationText.toLowerCase();
|
||||||
}
|
}
|
||||||
@ -177,13 +156,9 @@ export class ConfirmationDialogComponent implements AfterViewInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#initializeDetailsComponent() {
|
@HostListener('window:keydown.Enter', ['$event'])
|
||||||
if (!this.config.component) return;
|
onEnter(event: KeyboardEvent): void {
|
||||||
const component = this.detailsComponentRef().createComponent(this.config.component);
|
event?.stopImmediatePropagation();
|
||||||
if (this.config.componentInputs) {
|
this.confirm(ConfirmOptions.CONFIRM);
|
||||||
for (const [key, value] of Object.entries(this.config.componentInputs)) {
|
|
||||||
(component.instance as any)[key] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,8 +2,9 @@ import { Directive, HostListener, inject } from '@angular/core';
|
|||||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||||
import { FormGroup } from '@angular/forms';
|
import { FormGroup } from '@angular/forms';
|
||||||
import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog';
|
import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog';
|
||||||
import { IconButtonTypes } from '../buttons';
|
import { IconButtonTypes } from '../buttons/types/icon-button.type';
|
||||||
import { hasFormChanged, IqserEventTarget } from '../utils';
|
import { hasFormChanged } from '../utils/functions';
|
||||||
|
import { IqserEventTarget } from '../utils/types/events.type';
|
||||||
|
|
||||||
const DIALOG_CONTAINER = 'mat-dialog-container';
|
const DIALOG_CONTAINER = 'mat-dialog-container';
|
||||||
const DATA_TYPE_SYMBOL = Symbol.for('DATA_TYPE');
|
const DATA_TYPE_SYMBOL = Symbol.for('DATA_TYPE');
|
||||||
@ -13,7 +14,7 @@ export type DATA_TYPE = typeof DATA_TYPE_SYMBOL;
|
|||||||
export type RETURN_TYPE = typeof RETURN_TYPE_SYMBOL;
|
export type RETURN_TYPE = typeof RETURN_TYPE_SYMBOL;
|
||||||
|
|
||||||
@Directive()
|
@Directive()
|
||||||
export abstract class IqserDialogComponent<ComponentType, DataType = null, ReturnType = void> {
|
export abstract class IqserDialogComponent<ComponentType, DataType, ReturnType> {
|
||||||
readonly [DATA_TYPE_SYMBOL]!: DataType;
|
readonly [DATA_TYPE_SYMBOL]!: DataType;
|
||||||
readonly [RETURN_TYPE_SYMBOL]!: ReturnType;
|
readonly [RETURN_TYPE_SYMBOL]!: ReturnType;
|
||||||
|
|
||||||
@ -22,8 +23,6 @@ export abstract class IqserDialogComponent<ComponentType, DataType = null, Retur
|
|||||||
readonly data = inject<DataType>(MAT_DIALOG_DATA);
|
readonly data = inject<DataType>(MAT_DIALOG_DATA);
|
||||||
readonly dialog = inject(MatDialog);
|
readonly dialog = inject(MatDialog);
|
||||||
readonly form?: FormGroup;
|
readonly form?: FormGroup;
|
||||||
readonly ignoredKeys: string[] = [];
|
|
||||||
|
|
||||||
initialFormValue: Record<string, unknown> = {};
|
initialFormValue: Record<string, unknown> = {};
|
||||||
|
|
||||||
constructor(private readonly _editMode = false) {
|
constructor(private readonly _editMode = false) {
|
||||||
@ -39,7 +38,7 @@ export abstract class IqserDialogComponent<ComponentType, DataType = null, Retur
|
|||||||
}
|
}
|
||||||
|
|
||||||
get changed(): boolean {
|
get changed(): boolean {
|
||||||
return !this.form || hasFormChanged(this.form, this.initialFormValue, this.ignoredKeys);
|
return !this.form || hasFormChanged(this.form, this.initialFormValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
get disabled(): boolean {
|
get disabled(): boolean {
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { booleanAttribute, Directive, input } from '@angular/core';
|
|||||||
|
|
||||||
@Directive({
|
@Directive({
|
||||||
selector: '[iqserDisableStopPropagation]',
|
selector: '[iqserDisableStopPropagation]',
|
||||||
|
standalone: true,
|
||||||
})
|
})
|
||||||
export class DisableStopPropagationDirective {
|
export class DisableStopPropagationDirective {
|
||||||
readonly iqserDisableStopPropagation = input(true, { transform: booleanAttribute });
|
readonly iqserDisableStopPropagation = input(true, { transform: booleanAttribute });
|
||||||
|
|||||||
@ -1,35 +1,39 @@
|
|||||||
import { Directive, ElementRef, OnDestroy, OnInit, signal } from '@angular/core';
|
import { ChangeDetectorRef, Directive, ElementRef, HostBinding, OnDestroy, OnInit } from '@angular/core';
|
||||||
|
|
||||||
@Directive({
|
@Directive({
|
||||||
selector: '[iqserHasScrollbar]',
|
selector: '[iqserHasScrollbar]',
|
||||||
host: {
|
standalone: true,
|
||||||
'[class]': '_class()',
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
export class HasScrollbarDirective implements OnInit, OnDestroy {
|
export class HasScrollbarDirective implements OnInit, OnDestroy {
|
||||||
|
@HostBinding('class') class = '';
|
||||||
private readonly _resizeObserver: ResizeObserver;
|
private readonly _resizeObserver: ResizeObserver;
|
||||||
protected readonly _class = signal('');
|
|
||||||
|
|
||||||
constructor(protected readonly _elementRef: ElementRef) {
|
get hasScrollbar() {
|
||||||
this._resizeObserver = new ResizeObserver(() => {
|
const element = this._elementRef?.nativeElement as HTMLElement;
|
||||||
|
return element.clientHeight < element.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected readonly _elementRef: ElementRef,
|
||||||
|
protected readonly _changeDetector: ChangeDetectorRef,
|
||||||
|
) {
|
||||||
|
this._resizeObserver = new ResizeObserver(entry => {
|
||||||
this.process();
|
this.process();
|
||||||
});
|
});
|
||||||
|
|
||||||
this._resizeObserver.observe(this._elementRef.nativeElement);
|
this._resizeObserver.observe(this._elementRef.nativeElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
private get _hasScrollbar() {
|
|
||||||
const element = this._elementRef?.nativeElement as HTMLElement;
|
|
||||||
return element.clientHeight < element.scrollHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
setTimeout(() => this.process(), 0);
|
setTimeout(() => this.process(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
process() {
|
process() {
|
||||||
const newClass = this._hasScrollbar ? 'has-scrollbar' : '';
|
const newClass = this.hasScrollbar ? 'has-scrollbar' : '';
|
||||||
this._class.set(newClass);
|
if (this.class !== newClass) {
|
||||||
|
this.class = newClass;
|
||||||
|
this._changeDetector.markForCheck();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { Directive, EventEmitter, HostListener, Input, Output } from '@angular/c
|
|||||||
|
|
||||||
@Directive({
|
@Directive({
|
||||||
selector: '[iqserHiddenAction]',
|
selector: '[iqserHiddenAction]',
|
||||||
|
standalone: true,
|
||||||
})
|
})
|
||||||
export class HiddenActionDirective {
|
export class HiddenActionDirective {
|
||||||
@Input() requiredClicks = 4;
|
@Input() requiredClicks = 4;
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { NGXLogger } from 'ngx-logger';
|
|||||||
|
|
||||||
@Directive({
|
@Directive({
|
||||||
selector: '[iqserPreventDefault]',
|
selector: '[iqserPreventDefault]',
|
||||||
|
standalone: true,
|
||||||
})
|
})
|
||||||
export class PreventDefaultDirective {
|
export class PreventDefaultDirective {
|
||||||
readonly #logger = inject(NGXLogger);
|
readonly #logger = inject(NGXLogger);
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { DisableStopPropagationDirective } from './disable-stop-propagation.dire
|
|||||||
|
|
||||||
@Directive({
|
@Directive({
|
||||||
selector: '[iqserStopPropagation]',
|
selector: '[iqserStopPropagation]',
|
||||||
|
standalone: true,
|
||||||
})
|
})
|
||||||
export class StopPropagationDirective {
|
export class StopPropagationDirective {
|
||||||
readonly #disableStopPropagation = inject(DisableStopPropagationDirective, { optional: true });
|
readonly #disableStopPropagation = inject(DisableStopPropagationDirective, { optional: true });
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { Directive, ElementRef, HostListener, Input, OnDestroy } from '@angular/
|
|||||||
|
|
||||||
@Directive({
|
@Directive({
|
||||||
selector: '[iqserSyncWidth]',
|
selector: '[iqserSyncWidth]',
|
||||||
|
standalone: true,
|
||||||
})
|
})
|
||||||
export class SyncWidthDirective implements OnDestroy {
|
export class SyncWidthDirective implements OnDestroy {
|
||||||
@Input() iqserSyncWidth!: string;
|
@Input() iqserSyncWidth!: string;
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import { randomString } from '../utils/functions';
|
|||||||
templateUrl: './empty-state.component.html',
|
templateUrl: './empty-state.component.html',
|
||||||
styleUrls: ['./empty-state.component.scss'],
|
styleUrls: ['./empty-state.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
standalone: true,
|
||||||
imports: [NgStyle, MatIconModule, IconButtonComponent],
|
imports: [NgStyle, MatIconModule, IconButtonComponent],
|
||||||
})
|
})
|
||||||
export class EmptyStateComponent {
|
export class EmptyStateComponent {
|
||||||
|
|||||||
@ -17,7 +17,6 @@ import { ErrorService } from '../error.service';
|
|||||||
]),
|
]),
|
||||||
],
|
],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
standalone: false,
|
|
||||||
})
|
})
|
||||||
export class ConnectionStatusComponent {
|
export class ConnectionStatusComponent {
|
||||||
protected readonly connectionStatusTranslations = connectionStatusTranslations;
|
protected readonly connectionStatusTranslations = connectionStatusTranslations;
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import { CustomError, ErrorService, ErrorType } from '../error.service';
|
|||||||
templateUrl: './full-page-error.component.html',
|
templateUrl: './full-page-error.component.html',
|
||||||
styleUrls: ['./full-page-error.component.scss'],
|
styleUrls: ['./full-page-error.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
standalone: false,
|
|
||||||
})
|
})
|
||||||
export class FullPageErrorComponent {
|
export class FullPageErrorComponent {
|
||||||
protected readonly iconButtonTypes = IconButtonTypes;
|
protected readonly iconButtonTypes = IconButtonTypes;
|
||||||
|
|||||||
@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
@if (primaryFilters$ | async; as filters) {
|
@if (primaryFilters$ | async; as filters) {
|
||||||
<div class="filter-content">
|
<div class="filter-content">
|
||||||
@for (filter of filters; track filter.id) {
|
@for (filter of filters; track filter) {
|
||||||
<ng-container
|
<ng-container
|
||||||
[ngTemplateOutletContext]="{
|
[ngTemplateOutletContext]="{
|
||||||
filter: filter,
|
filter: filter,
|
||||||
@ -33,7 +33,7 @@
|
|||||||
<div class="all-caps-label" translate="filter-menu.filter-options"></div>
|
<div class="all-caps-label" translate="filter-menu.filter-options"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@for (filter of secondaryGroup.filters; track filter.id) {
|
@for (filter of secondaryGroup.filters; track filter) {
|
||||||
<ng-container
|
<ng-container
|
||||||
[ngTemplateOutletContext]="{
|
[ngTemplateOutletContext]="{
|
||||||
filter: filter,
|
filter: filter,
|
||||||
|
|||||||
@ -37,6 +37,7 @@ const atLeastOneIsExpandable = pipe(
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
standalone: true,
|
||||||
imports: [AsyncPipe, InputWithActionComponent, NgTemplateOutlet, TranslateModule, MatIcon, MatCheckbox, StopPropagationDirective],
|
imports: [AsyncPipe, InputWithActionComponent, NgTemplateOutlet, TranslateModule, MatIcon, MatCheckbox, StopPropagationDirective],
|
||||||
})
|
})
|
||||||
export class FilterCardComponent implements OnInit {
|
export class FilterCardComponent implements OnInit {
|
||||||
|
|||||||
@ -16,7 +16,6 @@ function copySettings(oldFilters: INestedFilter[], newFilters: INestedFilter[])
|
|||||||
const newFilter = newFilters.find(f => f.id === filter.id);
|
const newFilter = newFilters.find(f => f.id === filter.id);
|
||||||
if (newFilter) {
|
if (newFilter) {
|
||||||
newFilter.checked = filter.checked;
|
newFilter.checked = filter.checked;
|
||||||
newFilter.expanded = filter.expanded;
|
|
||||||
newFilter.indeterminate = filter.indeterminate;
|
newFilter.indeterminate = filter.indeterminate;
|
||||||
if (filter.children && newFilter.children) {
|
if (filter.children && newFilter.children) {
|
||||||
copySettings(filter.children, newFilter.children);
|
copySettings(filter.children, newFilter.children);
|
||||||
|
|||||||
@ -44,6 +44,11 @@ export class FilterService {
|
|||||||
this.showResetFilters$ = this._showResetFilters$;
|
this.showResetFilters$ = this._showResetFilters$;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get noAnnotationsFilterChecked() {
|
||||||
|
const filterGroup = this.filterGroups.find(g => g.slug === 'primaryFilters');
|
||||||
|
return !!filterGroup?.filters[0]?.children.find(f => f.id === 'no-annotations-filter' && f.checked);
|
||||||
|
}
|
||||||
|
|
||||||
get filterGroups(): IFilterGroup[] {
|
get filterGroups(): IFilterGroup[] {
|
||||||
return Object.values(this.#filterGroups$.getValue());
|
return Object.values(this.#filterGroups$.getValue());
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,8 +8,8 @@ export class NestedFilter extends Filter implements INestedFilter, IListable {
|
|||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
helpModeKey?: string;
|
helpModeKey?: string;
|
||||||
readonly children: Filter[];
|
readonly children: Filter[];
|
||||||
override readonly skipTranslation?: boolean;
|
readonly skipTranslation?: boolean;
|
||||||
override readonly metadata?: Record<string, any>;
|
readonly metadata?: Record<string, any>;
|
||||||
|
|
||||||
constructor(nestedFilter: INestedFilter) {
|
constructor(nestedFilter: INestedFilter) {
|
||||||
super(nestedFilter);
|
super(nestedFilter);
|
||||||
|
|||||||
@ -29,6 +29,7 @@ import { IFilterGroup } from '../models/filter-group.model';
|
|||||||
StopPropagationDirective,
|
StopPropagationDirective,
|
||||||
MatMenuContent,
|
MatMenuContent,
|
||||||
],
|
],
|
||||||
|
standalone: true,
|
||||||
})
|
})
|
||||||
export class PopupFilterComponent implements OnInit {
|
export class PopupFilterComponent implements OnInit {
|
||||||
@Input() primaryFiltersSlug!: string;
|
@Input() primaryFiltersSlug!: string;
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
@if (quickFilters$ | async; as filters) {
|
@if (quickFilters$ | async; as filters) {
|
||||||
@for (filter of filters; track filter.id) {
|
@for (filter of filters; track filter) {
|
||||||
<div
|
<div
|
||||||
(click)="filterService.toggleFilter('quickFilters', filter.id)"
|
(click)="filterService.toggleFilter('quickFilters', filter.id)"
|
||||||
[class.active]="filter.checked"
|
[class.active]="filter.checked"
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import { FilterService } from '../filter.service';
|
|||||||
templateUrl: './quick-filters.component.html',
|
templateUrl: './quick-filters.component.html',
|
||||||
styleUrls: ['./quick-filters.component.scss'],
|
styleUrls: ['./quick-filters.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
standalone: false,
|
|
||||||
})
|
})
|
||||||
export class QuickFiltersComponent {
|
export class QuickFiltersComponent {
|
||||||
readonly quickFilters$ = this.filterService.getFilterModels$('quickFilters');
|
readonly quickFilters$ = this.filterService.getFilterModels$('quickFilters');
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import { SimpleFilterOption } from '../models/simple-filter-option';
|
|||||||
selector: 'iqser-simple-popup-filter',
|
selector: 'iqser-simple-popup-filter',
|
||||||
templateUrl: './simple-popup-filter.component.html',
|
templateUrl: './simple-popup-filter.component.html',
|
||||||
styleUrls: ['./simple-popup-filter.component.scss'],
|
styleUrls: ['./simple-popup-filter.component.scss'],
|
||||||
|
standalone: true,
|
||||||
imports: [
|
imports: [
|
||||||
MatMenuModule,
|
MatMenuModule,
|
||||||
IconButtonComponent,
|
IconButtonComponent,
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import { IFilter } from '../models/filter.model';
|
|||||||
selector: 'iqser-single-filter',
|
selector: 'iqser-single-filter',
|
||||||
templateUrl: './single-filter.component.html',
|
templateUrl: './single-filter.component.html',
|
||||||
styleUrls: ['./single-filter.component.scss'],
|
styleUrls: ['./single-filter.component.scss'],
|
||||||
standalone: false,
|
|
||||||
})
|
})
|
||||||
export class SingleFilterComponent {
|
export class SingleFilterComponent {
|
||||||
@Input() filter!: IFilter;
|
@Input() filter!: IFilter;
|
||||||
|
|||||||
@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
&.active,
|
&.active,
|
||||||
&.dialog-toggle {
|
&.dialog-toggle {
|
||||||
z-index: 1200;
|
z-index: 1100;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toggle-input {
|
.toggle-input {
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
|||||||
selector: 'iqser-help-button',
|
selector: 'iqser-help-button',
|
||||||
templateUrl: './help-button.component.html',
|
templateUrl: './help-button.component.html',
|
||||||
styleUrls: ['./help-button.component.scss'],
|
styleUrls: ['./help-button.component.scss'],
|
||||||
|
standalone: true,
|
||||||
imports: [MatIcon, MatTooltip],
|
imports: [MatIcon, MatTooltip],
|
||||||
})
|
})
|
||||||
export class HelpButtonComponent implements OnInit, OnDestroy {
|
export class HelpButtonComponent implements OnInit, OnDestroy {
|
||||||
|
|||||||
@ -12,6 +12,7 @@ const DEFAULT_CDK_OVERLAY_CONTAINER_ZINDEX = '800';
|
|||||||
templateUrl: './help-mode-dialog.component.html',
|
templateUrl: './help-mode-dialog.component.html',
|
||||||
styleUrls: ['./help-mode-dialog.component.scss'],
|
styleUrls: ['./help-mode-dialog.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
standalone: true,
|
||||||
imports: [MatCheckbox, CircleButtonComponent, TranslateModule],
|
imports: [MatCheckbox, CircleButtonComponent, TranslateModule],
|
||||||
})
|
})
|
||||||
export class HelpModeDialogComponent implements OnInit, OnDestroy {
|
export class HelpModeDialogComponent implements OnInit, OnDestroy {
|
||||||
|
|||||||
@ -242,7 +242,6 @@ export class HelpModeService {
|
|||||||
const iframe: HTMLIFrameElement = document.getElementById(PDF_TRON_IFRAME_ID) as HTMLIFrameElement;
|
const iframe: HTMLIFrameElement = document.getElementById(PDF_TRON_IFRAME_ID) as HTMLIFrameElement;
|
||||||
const iframeRect = iframe.getBoundingClientRect();
|
const iframeRect = iframe.getBoundingClientRect();
|
||||||
dimensions.y += iframeRect.top;
|
dimensions.y += iframeRect.top;
|
||||||
dimensions.x += iframeRect.left;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
helper.helperElement.style.cssText = `
|
helper.helperElement.style.cssText = `
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { HelpModeService } from '../help-mode.service';
|
|||||||
import { IqserEventTarget } from '../../utils';
|
import { IqserEventTarget } from '../../utils';
|
||||||
import { MatDialog } from '@angular/material/dialog';
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
import { CircleButtonComponent, CircleButtonTypes } from '../../buttons';
|
import { CircleButtonComponent, CircleButtonTypes } from '../../buttons';
|
||||||
import { AsyncPipe } from '@angular/common';
|
import { AsyncPipe, NgIf } from '@angular/common';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -11,7 +11,8 @@ import { TranslateModule } from '@ngx-translate/core';
|
|||||||
templateUrl: './help-mode.component.html',
|
templateUrl: './help-mode.component.html',
|
||||||
styleUrls: ['./help-mode.component.scss'],
|
styleUrls: ['./help-mode.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
imports: [AsyncPipe, TranslateModule, CircleButtonComponent],
|
standalone: true,
|
||||||
|
imports: [AsyncPipe, TranslateModule, NgIf, CircleButtonComponent],
|
||||||
})
|
})
|
||||||
export class HelpModeComponent {
|
export class HelpModeComponent {
|
||||||
readonly circleButtonTypes = CircleButtonTypes;
|
readonly circleButtonTypes = CircleButtonTypes;
|
||||||
|
|||||||
@ -9,7 +9,7 @@ export const PDF_TRON_IFRAME_ID = 'webviewer-1';
|
|||||||
export const WEB_VIEWER_ELEMENTS = [
|
export const WEB_VIEWER_ELEMENTS = [
|
||||||
{
|
{
|
||||||
querySelector: '.HeaderItems',
|
querySelector: '.HeaderItems',
|
||||||
documentKey: 'document_viewer_features',
|
documentKey: 'pdf_features',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -18,7 +18,6 @@ export const ScrollableParentViews = {
|
|||||||
ANNOTATIONS_LIST: 'ANNOTATIONS_LIST',
|
ANNOTATIONS_LIST: 'ANNOTATIONS_LIST',
|
||||||
SCM_EDIT_DIALOG: 'SCM_EDIT_DIALOG',
|
SCM_EDIT_DIALOG: 'SCM_EDIT_DIALOG',
|
||||||
WORKFLOW_VIEW: 'WORKFLOW_VIEW',
|
WORKFLOW_VIEW: 'WORKFLOW_VIEW',
|
||||||
COMPONENTS_VIEW: 'COMPONENTS_VIEW',
|
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const SCROLLABLE_PARENT_VIEWS_IDS = {
|
export const SCROLLABLE_PARENT_VIEWS_IDS = {
|
||||||
@ -26,7 +25,6 @@ export const SCROLLABLE_PARENT_VIEWS_IDS = {
|
|||||||
ANNOTATIONS_LIST: 'annotations-list',
|
ANNOTATIONS_LIST: 'annotations-list',
|
||||||
SCM_EDIT_DIALOG: 'scm-edit',
|
SCM_EDIT_DIALOG: 'scm-edit',
|
||||||
WORKFLOW_VIEW: 'workflow-view',
|
WORKFLOW_VIEW: 'workflow-view',
|
||||||
COMPONENTS_VIEW: 'components-view',
|
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export type ScrollableParentView = keyof typeof ScrollableParentViews;
|
export type ScrollableParentView = keyof typeof ScrollableParentViews;
|
||||||
|
|||||||
@ -1,29 +1,19 @@
|
|||||||
interface AdditionalField {
|
interface ExtraOption {
|
||||||
label: string;
|
label: string;
|
||||||
description?: string;
|
checked: boolean;
|
||||||
}
|
|
||||||
|
|
||||||
interface AdditionalCheck extends AdditionalField {
|
|
||||||
checked?: boolean;
|
|
||||||
hidden?: boolean;
|
hidden?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
}
|
description?: string;
|
||||||
|
|
||||||
interface AdditionalInput extends AdditionalField {
|
|
||||||
value: string;
|
|
||||||
placeholder?: string;
|
|
||||||
errorCode?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DetailsRadioOption<I> {
|
export interface DetailsRadioOption<I> {
|
||||||
id?: string;
|
id?: string;
|
||||||
label: string;
|
label: string;
|
||||||
description: string;
|
description: string;
|
||||||
descriptionParams?: Record<string, string | number>;
|
descriptionParams?: Record<string, string>;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
value: I;
|
value: I;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
tooltip?: string;
|
tooltip?: string;
|
||||||
additionalCheck?: AdditionalCheck;
|
extraOption?: ExtraOption;
|
||||||
additionalInput?: AdditionalInput;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,54 +14,29 @@
|
|||||||
<div class="icon-option">
|
<div class="icon-option">
|
||||||
<mat-icon [svgIcon]="option.icon" class="icon"></mat-icon>
|
<mat-icon [svgIcon]="option.icon" class="icon"></mat-icon>
|
||||||
<div class="text">
|
<div class="text">
|
||||||
<label class="details-radio-label pointer">{{
|
<label class="details-radio-label pointer">{{ option.label | translate: option.descriptionParams }}</label>
|
||||||
option.label | translate: option.descriptionParams | replaceNbsp
|
|
||||||
}}</label>
|
|
||||||
|
|
||||||
<span class="hint">{{ option.description | translate: option.descriptionParams | replaceNbsp }}</span>
|
<span class="hint">{{ option.description | translate: option.descriptionParams | replaceNbsp }}</span>
|
||||||
|
|
||||||
@if (isSelected(option)) {
|
@if (option.extraOption && !option.extraOption.hidden && isSelected(option)) {
|
||||||
@if (option.additionalCheck && !option.additionalCheck.hidden) {
|
<div class="iqser-input-group">
|
||||||
<div class="iqser-input-group w-450">
|
<mat-checkbox
|
||||||
<mat-checkbox
|
(change)="emitExtraOption()"
|
||||||
(change)="emitExtraOption()"
|
[(ngModel)]="option.extraOption.checked"
|
||||||
[(ngModel)]="option.additionalCheck.checked"
|
[checked]="option.extraOption.checked"
|
||||||
[checked]="option.additionalCheck.checked"
|
[disabled]="!!option.extraOption.disabled"
|
||||||
[disabled]="!!option.additionalCheck.disabled"
|
color="primary"
|
||||||
color="primary"
|
>
|
||||||
>
|
{{ option.extraOption.label | translate }}
|
||||||
{{ option.additionalCheck.label | translate | replaceNbsp }}
|
</mat-checkbox>
|
||||||
</mat-checkbox>
|
|
||||||
|
|
||||||
@if (option.additionalCheck.description) {
|
@if (option.extraOption.description) {
|
||||||
<span
|
<span
|
||||||
[innerHTML]="option.additionalCheck.description | translate"
|
[innerHTML]="option.extraOption.description | translate"
|
||||||
class="hint additional-check-description"
|
class="hint extra-option-description"
|
||||||
></span>
|
></span>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
|
||||||
|
|
||||||
@if (option.additionalInput) {
|
|
||||||
<div class="iqser-input-group w-full additional-input">
|
|
||||||
<span class="label"> {{ option.additionalInput.label | translate }} </span>
|
|
||||||
<div class="flex-column">
|
|
||||||
<input
|
|
||||||
[(ngModel)]="option.additionalInput.value"
|
|
||||||
[ngClass]="{ error: additionalInputTouched && hasError(option.additionalInput.errorCode) }"
|
|
||||||
[placeholder]="
|
|
||||||
option.additionalInput.placeholder ? (option.additionalInput.placeholder | translate) : ''
|
|
||||||
"
|
|
||||||
(blur)="additionalInputTouched = true"
|
|
||||||
(focus)="additionalInputTouched = false"
|
|
||||||
(keydown)="emitExtraOption()"
|
|
||||||
/>
|
|
||||||
@if (option.additionalInput.description) {
|
|
||||||
<span class="hint" [innerHTML]="option.additionalInput.description | translate"></span>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -73,10 +48,10 @@
|
|||||||
<div class="flex-align-items-center mb-8">
|
<div class="flex-align-items-center mb-8">
|
||||||
<iqser-round-checkbox [active]="isSelected(option)" class="mr-6"></iqser-round-checkbox>
|
<iqser-round-checkbox [active]="isSelected(option)" class="mr-6"></iqser-round-checkbox>
|
||||||
|
|
||||||
<label class="details-radio-label pointer">{{ option.label | translate | replaceNbsp }}</label>
|
<label class="details-radio-label pointer">{{ option.label | translate }}</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span class="hint">{{ option.description | translate | replaceNbsp }}</span>
|
<span class="hint">{{ option.description | translate }}</span>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@ -43,44 +43,10 @@ label {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.additional-check-description {
|
.extra-option-description {
|
||||||
margin-left: 23px;
|
margin-left: 23px;
|
||||||
opacity: 0.49;
|
opacity: 0.49;
|
||||||
}
|
}
|
||||||
|
|
||||||
.additional-input {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
gap: 10px;
|
|
||||||
|
|
||||||
span {
|
|
||||||
margin-top: 8px;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div {
|
|
||||||
.error {
|
|
||||||
border-color: var(--iqser-red-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
span {
|
|
||||||
font-size: 10px;
|
|
||||||
margin-top: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.flex-column {
|
|
||||||
flex: 1;
|
|
||||||
|
|
||||||
input {
|
|
||||||
width: 232px;
|
|
||||||
min-height: 30px;
|
|
||||||
height: 30px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.row {
|
.row {
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import { DetailsRadioOption } from './details-radio-option';
|
|||||||
selector: 'iqser-details-radio',
|
selector: 'iqser-details-radio',
|
||||||
templateUrl: './details-radio.component.html',
|
templateUrl: './details-radio.component.html',
|
||||||
styleUrls: ['./details-radio.component.scss'],
|
styleUrls: ['./details-radio.component.scss'],
|
||||||
|
standalone: true,
|
||||||
providers: [
|
providers: [
|
||||||
{
|
{
|
||||||
provide: NG_VALUE_ACCESSOR,
|
provide: NG_VALUE_ACCESSOR,
|
||||||
@ -41,8 +42,8 @@ import { DetailsRadioOption } from './details-radio-option';
|
|||||||
export class DetailsRadioComponent<I> extends FormFieldComponent<DetailsRadioOption<I>> {
|
export class DetailsRadioComponent<I> extends FormFieldComponent<DetailsRadioOption<I>> {
|
||||||
readonly options = input.required<DetailsRadioOption<I>[]>();
|
readonly options = input.required<DetailsRadioOption<I>[]>();
|
||||||
readonly displayInRow = input(false, { transform: booleanAttribute });
|
readonly displayInRow = input(false, { transform: booleanAttribute });
|
||||||
|
|
||||||
readonly extraOptionChanged = output<DetailsRadioOption<I>>();
|
readonly extraOptionChanged = output<DetailsRadioOption<I>>();
|
||||||
additionalInputTouched = false;
|
|
||||||
|
|
||||||
toggleOption(option: DetailsRadioOption<I>) {
|
toggleOption(option: DetailsRadioOption<I>) {
|
||||||
if (option.value !== this._value?.value && !option.disabled) {
|
if (option.value !== this._value?.value && !option.disabled) {
|
||||||
|
|||||||
@ -23,6 +23,7 @@ type DynamicInput = number | string | Date;
|
|||||||
templateUrl: './dynamic-input.component.html',
|
templateUrl: './dynamic-input.component.html',
|
||||||
styleUrls: ['./dynamic-input.component.scss'],
|
styleUrls: ['./dynamic-input.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
standalone: true,
|
||||||
providers: [
|
providers: [
|
||||||
{
|
{
|
||||||
provide: NG_VALUE_ACCESSOR,
|
provide: NG_VALUE_ACCESSOR,
|
||||||
@ -50,7 +51,7 @@ export class DynamicInputComponent extends FormFieldComponent<DynamicInput> {
|
|||||||
readonly isNumber = computed(() => this.type() === InputTypes.NUMBER);
|
readonly isNumber = computed(() => this.type() === InputTypes.NUMBER);
|
||||||
readonly isText = computed(() => this.type() === InputTypes.TEXT);
|
readonly isText = computed(() => this.type() === InputTypes.TEXT);
|
||||||
|
|
||||||
override writeValue(input: DynamicInput): void {
|
writeValue(input: DynamicInput): void {
|
||||||
this.input.set(input);
|
this.input.set(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import { CircleButtonType, CircleButtonTypes } from '../../buttons/types/circle-
|
|||||||
templateUrl: './editable-input.component.html',
|
templateUrl: './editable-input.component.html',
|
||||||
styleUrls: ['./editable-input.component.scss'],
|
styleUrls: ['./editable-input.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
standalone: true,
|
||||||
imports: [CircleButtonComponent, FormsModule],
|
imports: [CircleButtonComponent, FormsModule],
|
||||||
host: {
|
host: {
|
||||||
'[class.editing]': '_editing()',
|
'[class.editing]': '_editing()',
|
||||||
|
|||||||
@ -1,47 +1,19 @@
|
|||||||
import { ChangeDetectorRef, Directive, inject, Injector, OnInit } from '@angular/core';
|
import { ChangeDetectorRef, Directive, inject } from '@angular/core';
|
||||||
import {
|
import { ControlValueAccessor, ValidationErrors, Validator } from '@angular/forms';
|
||||||
ControlValueAccessor,
|
|
||||||
FormControl,
|
|
||||||
FormControlDirective,
|
|
||||||
FormControlName,
|
|
||||||
FormGroupDirective,
|
|
||||||
NgControl,
|
|
||||||
ValidationErrors,
|
|
||||||
Validator,
|
|
||||||
} from '@angular/forms';
|
|
||||||
|
|
||||||
@Directive()
|
@Directive()
|
||||||
export abstract class FormFieldComponent<I> implements ControlValueAccessor, Validator, OnInit {
|
export abstract class FormFieldComponent<I> implements ControlValueAccessor, Validator {
|
||||||
touched = false;
|
touched = false;
|
||||||
disabled = false;
|
disabled = false;
|
||||||
|
|
||||||
protected readonly _changeRef = inject(ChangeDetectorRef);
|
protected readonly _changeRef = inject(ChangeDetectorRef);
|
||||||
protected readonly _injector = inject(Injector);
|
|
||||||
|
|
||||||
protected _formControl: FormControl | undefined;
|
|
||||||
protected _value: I | undefined;
|
protected _value: I | undefined;
|
||||||
|
|
||||||
get value(): I | undefined {
|
get value(): I | undefined {
|
||||||
return this._value;
|
return this._value;
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
const ngControl = this._injector.get(NgControl);
|
|
||||||
|
|
||||||
if (ngControl instanceof FormControlName) {
|
|
||||||
this._formControl = this._injector.get(FormGroupDirective).getControl(ngControl);
|
|
||||||
} else {
|
|
||||||
this._formControl = (ngControl as FormControlDirective).form as FormControl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hasError(errorCode: string | undefined): boolean {
|
|
||||||
if (errorCode && this._formControl) {
|
|
||||||
return this._formControl.hasError(errorCode);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
|
||||||
onChange = (value?: I) => {};
|
onChange = (value?: I) => {};
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import { randomString } from '../../utils/functions';
|
|||||||
templateUrl: './input-with-action.component.html',
|
templateUrl: './input-with-action.component.html',
|
||||||
styleUrls: ['./input-with-action.component.scss'],
|
styleUrls: ['./input-with-action.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
standalone: true,
|
||||||
imports: [FormsModule, MatIconModule, CircleButtonComponent],
|
imports: [FormsModule, MatIconModule, CircleButtonComponent],
|
||||||
})
|
})
|
||||||
export class InputWithActionComponent {
|
export class InputWithActionComponent {
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { MatIconModule } from '@angular/material/icon';
|
|||||||
templateUrl: './round-checkbox.component.html',
|
templateUrl: './round-checkbox.component.html',
|
||||||
styleUrls: ['./round-checkbox.component.scss'],
|
styleUrls: ['./round-checkbox.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
standalone: true,
|
||||||
imports: [MatIconModule],
|
imports: [MatIconModule],
|
||||||
})
|
})
|
||||||
export class RoundCheckboxComponent {
|
export class RoundCheckboxComponent {
|
||||||
|
|||||||
@ -3,5 +3,4 @@ import { ActionConfig } from './action-config.model';
|
|||||||
|
|
||||||
export interface ButtonConfig extends ActionConfig {
|
export interface ButtonConfig extends ActionConfig {
|
||||||
readonly type?: IconButtonType;
|
readonly type?: IconButtonType;
|
||||||
readonly tooltip?: string;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -46,14 +46,11 @@
|
|||||||
@if (!config.hide) {
|
@if (!config.hide) {
|
||||||
<iqser-icon-button
|
<iqser-icon-button
|
||||||
(action)="config.action($event)"
|
(action)="config.action($event)"
|
||||||
[buttonId]="config.label.replace('.', '-')"
|
[buttonId]="(config.label | translate).replace('.', '-')"
|
||||||
[icon]="config.icon"
|
[icon]="config.icon"
|
||||||
[label]="config.label | translate"
|
[label]="config.label | translate"
|
||||||
[type]="config.type"
|
[type]="config.type"
|
||||||
[matTooltip]="(config.tooltip | translate) ?? ''"
|
|
||||||
[disabled]="config.disabled"
|
|
||||||
[attr.help-mode-key]="config.helpModeKey"
|
[attr.help-mode-key]="config.helpModeKey"
|
||||||
matTooltipPosition="above"
|
|
||||||
></iqser-icon-button>
|
></iqser-icon-button>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,7 +15,6 @@ import { filterEach } from '../../utils';
|
|||||||
import { List } from '../../utils';
|
import { List } from '../../utils';
|
||||||
import { IListable } from '../models';
|
import { IListable } from '../models';
|
||||||
import { ActionConfig, ButtonConfig, SearchPosition, SearchPositions } from './models';
|
import { ActionConfig, ButtonConfig, SearchPosition, SearchPositions } from './models';
|
||||||
import { MatTooltip } from '@angular/material/tooltip';
|
|
||||||
import { DisableStopPropagationDirective } from '../../directives';
|
import { DisableStopPropagationDirective } from '../../directives';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -23,6 +22,7 @@ import { DisableStopPropagationDirective } from '../../directives';
|
|||||||
templateUrl: './page-header.component.html',
|
templateUrl: './page-header.component.html',
|
||||||
styleUrls: ['./page-header.component.scss'],
|
styleUrls: ['./page-header.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
standalone: true,
|
||||||
imports: [
|
imports: [
|
||||||
AsyncPipe,
|
AsyncPipe,
|
||||||
NgTemplateOutlet,
|
NgTemplateOutlet,
|
||||||
@ -32,7 +32,6 @@ import { DisableStopPropagationDirective } from '../../directives';
|
|||||||
CircleButtonComponent,
|
CircleButtonComponent,
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
InputWithActionComponent,
|
InputWithActionComponent,
|
||||||
MatTooltip,
|
|
||||||
DisableStopPropagationDirective,
|
DisableStopPropagationDirective,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|||||||
@ -18,6 +18,7 @@ type ButtonType = keyof typeof ButtonTypes;
|
|||||||
templateUrl: './scroll-button.component.html',
|
templateUrl: './scroll-button.component.html',
|
||||||
styleUrls: ['./scroll-button.component.scss'],
|
styleUrls: ['./scroll-button.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
standalone: true,
|
||||||
imports: [AsyncPipe, MatIcon],
|
imports: [AsyncPipe, MatIcon],
|
||||||
})
|
})
|
||||||
export class ScrollButtonComponent implements OnInit {
|
export class ScrollButtonComponent implements OnInit {
|
||||||
|
|||||||
@ -52,7 +52,7 @@ export class ListingService<Class extends IListable<PrimaryKey>, PrimaryKey exte
|
|||||||
|
|
||||||
get selected(): Class[] {
|
get selected(): Class[] {
|
||||||
const selectedIds = this.selectedIds;
|
const selectedIds = this.selectedIds;
|
||||||
return selectedIds.map(id => this._entitiesService.all.find(a => a.id === id)).filter(a => !!a);
|
return this._entitiesService.all.filter(a => selectedIds.includes(a.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
get selectedIds(): PrimaryKey[] {
|
get selectedIds(): PrimaryKey[] {
|
||||||
|
|||||||
@ -22,14 +22,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:last-of-type > div {
|
|
||||||
padding: 0 13px 0 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:first-child > div {
|
&:first-child > div {
|
||||||
padding: 0 24px;
|
padding: 0 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:last-of-type > div {
|
||||||
|
padding: 0 13px 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.flex-end {
|
.flex-end {
|
||||||
min-width: 58px;
|
min-width: 58px;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import { Id, IListable } from '../models';
|
|||||||
templateUrl: './table-column-name.component.html',
|
templateUrl: './table-column-name.component.html',
|
||||||
styleUrls: ['./table-column-name.component.scss'],
|
styleUrls: ['./table-column-name.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
standalone: true,
|
||||||
imports: [MatIcon, MatTooltip, TranslateModule, AsyncPipe, NgClass],
|
imports: [MatIcon, MatTooltip, TranslateModule, AsyncPipe, NgClass],
|
||||||
})
|
})
|
||||||
export class TableColumnNameComponent<T extends IListable<PrimaryKey>, PrimaryKey extends Id = T['id']> {
|
export class TableColumnNameComponent<T extends IListable<PrimaryKey>, PrimaryKey extends Id = T['id']> {
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
<!-- 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 -->
|
||||||
@if (itemMouseEnterFn || itemMouseLeaveFn) {
|
@if (itemMouseEnterFn || itemMouseLeaveFn) {
|
||||||
<a
|
<div
|
||||||
(mouseenter)="itemMouseEnterFn && itemMouseEnterFn(entity)"
|
(mouseenter)="itemMouseEnterFn && itemMouseEnterFn(entity)"
|
||||||
(mouseleave)="itemMouseLeaveFn && itemMouseLeaveFn(entity)"
|
(mouseleave)="itemMouseLeaveFn && itemMouseLeaveFn(entity)"
|
||||||
[class.help-mode-active]="helpModeService?.isHelpModeActive$ | async"
|
[class.help-mode-active]="helpModeService?.isHelpModeActive$ | async"
|
||||||
@ -21,12 +21,12 @@
|
|||||||
>
|
>
|
||||||
<iqser-table-item
|
<iqser-table-item
|
||||||
(click)="multiSelect(entity, $event)"
|
(click)="multiSelect(entity, $event)"
|
||||||
[entity]="$any(entity)"
|
[entity]="entity"
|
||||||
[selectionEnabled]="selectionEnabled"
|
[selectionEnabled]="selectionEnabled"
|
||||||
></iqser-table-item>
|
></iqser-table-item>
|
||||||
</a>
|
</div>
|
||||||
} @else {
|
} @else {
|
||||||
<a
|
<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)"
|
||||||
[ngClass]="getTableItemClasses(entity)"
|
[ngClass]="getTableItemClasses(entity)"
|
||||||
@ -34,10 +34,10 @@
|
|||||||
>
|
>
|
||||||
<iqser-table-item
|
<iqser-table-item
|
||||||
(click)="multiSelect(entity, $event)"
|
(click)="multiSelect(entity, $event)"
|
||||||
[entity]="$any(entity)"
|
[entity]="entity"
|
||||||
[selectionEnabled]="selectionEnabled"
|
[selectionEnabled]="selectionEnabled"
|
||||||
></iqser-table-item>
|
></iqser-table-item>
|
||||||
</a>
|
</div>
|
||||||
}
|
}
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</cdk-virtual-scroll-viewport>
|
</cdk-virtual-scroll-viewport>
|
||||||
|
|||||||
@ -33,7 +33,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
&:has(iqser-circle-button[aria-expanded='true']),
|
|
||||||
&.help-mode-active {
|
&.help-mode-active {
|
||||||
.selection-column iqser-round-checkbox .wrapper {
|
.selection-column iqser-round-checkbox .wrapper {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
@ -72,8 +71,3 @@
|
|||||||
.display-contents {
|
.display-contents {
|
||||||
display: contents;
|
display: contents;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
|
||||||
display: contents;
|
|
||||||
@include mixins.clear-a;
|
|
||||||
}
|
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import { TableItemComponent } from './table-item/table-item.component';
|
|||||||
selector: 'iqser-table-content',
|
selector: 'iqser-table-content',
|
||||||
templateUrl: './table-content.component.html',
|
templateUrl: './table-content.component.html',
|
||||||
styleUrls: ['./table-content.component.scss'],
|
styleUrls: ['./table-content.component.scss'],
|
||||||
|
standalone: true,
|
||||||
imports: [
|
imports: [
|
||||||
CdkVirtualScrollViewport,
|
CdkVirtualScrollViewport,
|
||||||
AsyncPipe,
|
AsyncPipe,
|
||||||
@ -86,7 +87,7 @@ export class TableContentComponent<Class extends IListable<PrimaryKey>, PrimaryK
|
|||||||
getTableItemClasses(entity: Class): Record<string, boolean> {
|
getTableItemClasses(entity: Class): Record<string, boolean> {
|
||||||
const classes: Record<string, boolean> = {
|
const classes: Record<string, boolean> = {
|
||||||
'table-item': true,
|
'table-item': true,
|
||||||
'cursor-default': !entity.routerLink,
|
pointer: !!entity.routerLink && entity.routerLink.length > 0,
|
||||||
};
|
};
|
||||||
for (const key in this.tableItemClasses) {
|
for (const key in this.tableItemClasses) {
|
||||||
if (Object.prototype.hasOwnProperty.call(this.tableItemClasses, key)) {
|
if (Object.prototype.hasOwnProperty.call(this.tableItemClasses, key)) {
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import { ListingService } from '../../services/listing.service';
|
|||||||
templateUrl: './table-item.component.html',
|
templateUrl: './table-item.component.html',
|
||||||
styleUrls: ['./table-item.component.scss'],
|
styleUrls: ['./table-item.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
standalone: true,
|
||||||
imports: [RoundCheckboxComponent, AsyncPipe, NgTemplateOutlet],
|
imports: [RoundCheckboxComponent, AsyncPipe, NgTemplateOutlet],
|
||||||
})
|
})
|
||||||
export class TableItemComponent<T extends IListable> implements OnChanges {
|
export class TableItemComponent<T extends IListable> implements OnChanges {
|
||||||
@ -33,7 +34,6 @@ export class TableItemComponent<T extends IListable> implements OnChanges {
|
|||||||
|
|
||||||
toggleEntitySelected($event: MouseEvent, entity: T): void {
|
toggleEntitySelected($event: MouseEvent, entity: T): void {
|
||||||
$event.stopPropagation();
|
$event.stopPropagation();
|
||||||
$event.preventDefault();
|
|
||||||
this.listingService.select(entity, $event.shiftKey);
|
this.listingService.select(entity, $event.shiftKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import { TableColumnNameComponent } from '../table-column-name/table-column-name
|
|||||||
templateUrl: './table-header.component.html',
|
templateUrl: './table-header.component.html',
|
||||||
styleUrls: ['./table-header.component.scss'],
|
styleUrls: ['./table-header.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
standalone: true,
|
||||||
imports: [
|
imports: [
|
||||||
RoundCheckboxComponent,
|
RoundCheckboxComponent,
|
||||||
AsyncPipe,
|
AsyncPipe,
|
||||||
|
|||||||
@ -28,6 +28,7 @@ const SCROLLBAR_WIDTH = 11;
|
|||||||
selector: 'iqser-table [tableColumnConfigs] [itemSize]',
|
selector: 'iqser-table [tableColumnConfigs] [itemSize]',
|
||||||
templateUrl: './table.component.html',
|
templateUrl: './table.component.html',
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
standalone: true,
|
||||||
imports: [TableHeaderComponent, NgTemplateOutlet, AsyncPipe, EmptyStateComponent, ScrollButtonComponent, TableContentComponent],
|
imports: [TableHeaderComponent, NgTemplateOutlet, AsyncPipe, EmptyStateComponent, ScrollButtonComponent, TableContentComponent],
|
||||||
})
|
})
|
||||||
export class TableComponent<Class extends IListable<PrimaryKey>, PrimaryKey extends Id = Class['id']> implements OnChanges {
|
export class TableComponent<Class extends IListable<PrimaryKey>, PrimaryKey extends Id = Class['id']> implements OnChanges {
|
||||||
|
|||||||
@ -36,6 +36,7 @@ interface ColumnHeaderContext {
|
|||||||
templateUrl: './column-header.component.html',
|
templateUrl: './column-header.component.html',
|
||||||
styleUrls: ['./column-header.component.scss'],
|
styleUrls: ['./column-header.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
standalone: true,
|
||||||
imports: [AsyncPipe, TranslateModule, RoundCheckboxComponent, NgTemplateOutlet, CircleButtonComponent],
|
imports: [AsyncPipe, TranslateModule, RoundCheckboxComponent, NgTemplateOutlet, CircleButtonComponent],
|
||||||
})
|
})
|
||||||
export class ColumnHeaderComponent<T extends IListable, K extends string> extends ContextComponent<ColumnHeaderContext> implements OnInit {
|
export class ColumnHeaderComponent<T extends IListable, K extends string> extends ContextComponent<ColumnHeaderContext> implements OnInit {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
@use '../../../assets/styles/common-variables';
|
@import '../../../assets/styles/common-variables';
|
||||||
@use '../../../assets/styles/common-mixins' as mixins;
|
@import '../../../assets/styles/common-mixins';
|
||||||
|
|
||||||
:host {
|
:host {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -67,7 +67,7 @@
|
|||||||
|
|
||||||
.cdk-drop-list {
|
.cdk-drop-list {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
@include mixins.no-scroll-bar;
|
@include no-scroll-bar;
|
||||||
min-height: calc(100% - 36px);
|
min-height: calc(100% - 36px);
|
||||||
|
|
||||||
&.multi-select-active {
|
&.multi-select-active {
|
||||||
@ -135,5 +135,5 @@
|
|||||||
|
|
||||||
cdk-virtual-scroll-viewport {
|
cdk-virtual-scroll-viewport {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@include mixins.no-scroll-bar;
|
@include no-scroll-bar;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -50,6 +50,7 @@ interface WorkflowContext<T> {
|
|||||||
templateUrl: './workflow.component.html',
|
templateUrl: './workflow.component.html',
|
||||||
styleUrls: ['./workflow.component.scss'],
|
styleUrls: ['./workflow.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
standalone: true,
|
||||||
imports: [
|
imports: [
|
||||||
TableHeaderComponent,
|
TableHeaderComponent,
|
||||||
AsyncPipe,
|
AsyncPipe,
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import { LoadingService } from '../loading.service';
|
|||||||
selector: 'iqser-full-page-loading-indicator',
|
selector: 'iqser-full-page-loading-indicator',
|
||||||
templateUrl: './full-page-loading-indicator.component.html',
|
templateUrl: './full-page-loading-indicator.component.html',
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
standalone: false,
|
|
||||||
})
|
})
|
||||||
export class FullPageLoadingIndicatorComponent {
|
export class FullPageLoadingIndicatorComponent {
|
||||||
constructor(readonly loadingService: LoadingService) {}
|
constructor(readonly loadingService: LoadingService) {}
|
||||||
|
|||||||
@ -1,16 +1,15 @@
|
|||||||
import { ChangeDetectionStrategy, Component, Input, OnInit, Optional } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, Input, Optional, OnInit } from '@angular/core';
|
||||||
import { Observable, of } from 'rxjs';
|
|
||||||
import { map } from 'rxjs/operators';
|
|
||||||
import { FilterService, INestedFilter } from '../../filtering';
|
|
||||||
import { get, shareLast } from '../../utils';
|
|
||||||
import { ProgressBarConfigModel } from './progress-bar-config.model';
|
import { ProgressBarConfigModel } from './progress-bar-config.model';
|
||||||
|
import { FilterService, INestedFilter } from '../../filtering';
|
||||||
|
import { Observable, of } from 'rxjs';
|
||||||
|
import { get, shareLast } from '../../utils';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'iqser-progress-bar [config]',
|
selector: 'iqser-progress-bar [config]',
|
||||||
templateUrl: './progress-bar.component.html',
|
templateUrl: './progress-bar.component.html',
|
||||||
styleUrls: ['./progress-bar.component.scss'],
|
styleUrls: ['./progress-bar.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
standalone: false,
|
|
||||||
})
|
})
|
||||||
export class ProgressBarComponent implements OnInit {
|
export class ProgressBarComponent implements OnInit {
|
||||||
@Input() config!: ProgressBarConfigModel;
|
@Input() config!: ProgressBarConfigModel;
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import { ILoadingConfig } from '../loading.service';
|
|||||||
templateUrl: './progress-loading.component.html',
|
templateUrl: './progress-loading.component.html',
|
||||||
styleUrls: ['./progress-loading.component.scss'],
|
styleUrls: ['./progress-loading.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
standalone: false,
|
|
||||||
})
|
})
|
||||||
export class ProgressLoadingComponent {
|
export class ProgressLoadingComponent {
|
||||||
@Input() config!: ILoadingConfig;
|
@Input() config!: ILoadingConfig;
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { PaginationSettings } from './pagination-settings';
|
|||||||
selector: 'iqser-pagination',
|
selector: 'iqser-pagination',
|
||||||
templateUrl: './pagination.component.html',
|
templateUrl: './pagination.component.html',
|
||||||
styleUrls: ['./pagination.component.scss'],
|
styleUrls: ['./pagination.component.scss'],
|
||||||
|
standalone: true,
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
imports: [TranslateModule],
|
imports: [TranslateModule],
|
||||||
})
|
})
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { assertTemplate, IqserPermissionsDirective } from './permissions.directi
|
|||||||
|
|
||||||
@Directive({
|
@Directive({
|
||||||
selector: '[allow]',
|
selector: '[allow]',
|
||||||
|
standalone: true,
|
||||||
})
|
})
|
||||||
export class IqserAllowDirective extends IqserPermissionsDirective implements OnDestroy, OnInit {
|
export class IqserAllowDirective extends IqserPermissionsDirective implements OnDestroy, OnInit {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { assertTemplate, IqserPermissionsDirective } from './permissions.directi
|
|||||||
|
|
||||||
@Directive({
|
@Directive({
|
||||||
selector: '[deny]',
|
selector: '[deny]',
|
||||||
|
standalone: true,
|
||||||
})
|
})
|
||||||
export class IqserDenyDirective extends IqserPermissionsDirective implements OnDestroy, OnInit {
|
export class IqserDenyDirective extends IqserPermissionsDirective implements OnDestroy, OnInit {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -29,7 +29,7 @@ export function isArray<T>(value: unknown): value is T[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function toArray(value?: string | List): List {
|
export function toArray(value?: string | List): List {
|
||||||
return isString(value) ? [value] : (value ?? []);
|
return isString(value) ? [value] : value ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isRedirectWithParameters(object: any | IqserRedirectToNavigationParameters): object is IqserRedirectToNavigationParameters {
|
export function isRedirectWithParameters(object: any | IqserRedirectToNavigationParameters): object is IqserRedirectToNavigationParameters {
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { capitalize } from '../utils';
|
|||||||
|
|
||||||
@Pipe({
|
@Pipe({
|
||||||
name: 'capitalize',
|
name: 'capitalize',
|
||||||
|
standalone: true,
|
||||||
})
|
})
|
||||||
export class CapitalizePipe implements PipeTransform {
|
export class CapitalizePipe implements PipeTransform {
|
||||||
transform(value: string): string {
|
transform(value: string): string {
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { humanizeCamelCase } from '../utils';
|
|||||||
|
|
||||||
@Pipe({
|
@Pipe({
|
||||||
name: 'humanizeCamelCase',
|
name: 'humanizeCamelCase',
|
||||||
|
standalone: true,
|
||||||
})
|
})
|
||||||
export class HumanizeCamelCasePipe implements PipeTransform {
|
export class HumanizeCamelCasePipe implements PipeTransform {
|
||||||
transform(item: string): string {
|
transform(item: string): string {
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { humanize } from '../utils';
|
|||||||
|
|
||||||
@Pipe({
|
@Pipe({
|
||||||
name: 'humanize',
|
name: 'humanize',
|
||||||
|
standalone: true,
|
||||||
})
|
})
|
||||||
export class HumanizePipe implements PipeTransform {
|
export class HumanizePipe implements PipeTransform {
|
||||||
transform(item: string, lowercase = false): string {
|
transform(item: string, lowercase = false): string {
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { Pipe, PipeTransform } from '@angular/core';
|
|||||||
|
|
||||||
@Pipe({
|
@Pipe({
|
||||||
name: 'log',
|
name: 'log',
|
||||||
|
standalone: true,
|
||||||
})
|
})
|
||||||
export class LogPipe implements PipeTransform {
|
export class LogPipe implements PipeTransform {
|
||||||
transform<T>(value: T, message = ''): T {
|
transform<T>(value: T, message = ''): T {
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { Pipe, PipeTransform } from '@angular/core';
|
|||||||
|
|
||||||
@Pipe({
|
@Pipe({
|
||||||
name: 'replaceNbsp',
|
name: 'replaceNbsp',
|
||||||
|
standalone: true,
|
||||||
})
|
})
|
||||||
export class ReplaceNbspPipe implements PipeTransform {
|
export class ReplaceNbspPipe implements PipeTransform {
|
||||||
transform(value: string): string {
|
transform(value: string): string {
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { size } from '../utils';
|
|||||||
|
|
||||||
@Pipe({
|
@Pipe({
|
||||||
name: 'size',
|
name: 'size',
|
||||||
|
standalone: true,
|
||||||
})
|
})
|
||||||
export class SizePipe implements PipeTransform {
|
export class SizePipe implements PipeTransform {
|
||||||
transform(value: number): string {
|
transform(value: number): string {
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { Pipe, PipeTransform } from '@angular/core';
|
|||||||
|
|
||||||
@Pipe({
|
@Pipe({
|
||||||
name: 'snakeCase',
|
name: 'snakeCase',
|
||||||
|
standalone: true,
|
||||||
})
|
})
|
||||||
export class SnakeCasePipe implements PipeTransform {
|
export class SnakeCasePipe implements PipeTransform {
|
||||||
transform(value: string): string | undefined {
|
transform(value: string): string | undefined {
|
||||||
|
|||||||
@ -86,23 +86,3 @@ export function orderedAsyncGuards(guards: Array<AsyncGuard>): CanActivateFn {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function unorderedAsyncGuards(guards: Array<AsyncGuard>): CanActivateFn {
|
|
||||||
return async (route, state) => {
|
|
||||||
const injector = inject(Injector);
|
|
||||||
const loadingService = inject(LoadingService);
|
|
||||||
|
|
||||||
loadingService.start();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await Promise.all(guards.map(guard => runInInjectionContext(injector, () => guard(route, state))));
|
|
||||||
loadingService.stop();
|
|
||||||
return result.every(Boolean);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
loadingService.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { HeadersConfiguration } from '../utils/headers-configuration';
|
|||||||
import { List } from '../utils/types/iqser-types';
|
import { List } from '../utils/types/iqser-types';
|
||||||
|
|
||||||
export const ROOT_CHANGES_KEY = 'root';
|
export const ROOT_CHANGES_KEY = 'root';
|
||||||
|
export const LAST_CHECKED_OFFSET = 30000;
|
||||||
|
|
||||||
export interface QueryParam {
|
export interface QueryParam {
|
||||||
readonly key: string;
|
readonly key: string;
|
||||||
@ -18,7 +19,9 @@ export interface QueryParam {
|
|||||||
*/
|
*/
|
||||||
export abstract class GenericService<I> {
|
export abstract class GenericService<I> {
|
||||||
protected readonly _http = inject(HttpClient);
|
protected readonly _http = inject(HttpClient);
|
||||||
protected readonly _lastCheckedForChanges = new Map<string, string>([[ROOT_CHANGES_KEY, new Date(Date.now()).toISOString()]]);
|
protected readonly _lastCheckedForChanges = new Map<string, string>([
|
||||||
|
[ROOT_CHANGES_KEY, new Date(Date.now() - LAST_CHECKED_OFFSET).toISOString()],
|
||||||
|
]);
|
||||||
protected abstract readonly _defaultModelPath: string;
|
protected abstract readonly _defaultModelPath: string;
|
||||||
protected readonly _serviceName: string = 'redaction-gateway-v1';
|
protected readonly _serviceName: string = 'redaction-gateway-v1';
|
||||||
|
|
||||||
@ -123,6 +126,6 @@ export abstract class GenericService<I> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected _updateLastChanged(key = ROOT_CHANGES_KEY): void {
|
protected _updateLastChanged(key = ROOT_CHANGES_KEY): void {
|
||||||
this._lastCheckedForChanges.set(key, new Date().toISOString());
|
this._lastCheckedForChanges.set(key, new Date(Date.now() - LAST_CHECKED_OFFSET).toISOString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,18 +3,14 @@ import { Title } from '@angular/platform-browser';
|
|||||||
import { CacheApiService } from '../caching/cache-api.service';
|
import { CacheApiService } from '../caching/cache-api.service';
|
||||||
import { wipeAllCaches } from '../caching/cache-utils';
|
import { wipeAllCaches } from '../caching/cache-utils';
|
||||||
import { IqserAppConfig } from '../utils/iqser-app-config';
|
import { IqserAppConfig } from '../utils/iqser-app-config';
|
||||||
import { LANDING_PAGE_THEMES, MANUAL_BASE_URL, THEME_DIRECTORIES } from '../utils/constants';
|
|
||||||
import { IStoredTenantId, TenantsService } from '../tenants';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class IqserConfigService<T extends IqserAppConfig = IqserAppConfig> {
|
export class IqserConfigService<T extends IqserAppConfig = IqserAppConfig> {
|
||||||
protected readonly _cacheApiService = inject(CacheApiService);
|
protected readonly _cacheApiService = inject(CacheApiService);
|
||||||
protected readonly _titleService = inject(Title);
|
protected readonly _titleService = inject(Title);
|
||||||
protected readonly _tenantsService = inject(TenantsService);
|
|
||||||
|
|
||||||
constructor(@Inject('Doesnt matter') protected _values: T) {
|
constructor(@Inject('Doesnt matter') protected _values: T) {
|
||||||
this._checkFrontendVersion();
|
this._checkFrontendVersion();
|
||||||
this.#updateAppType();
|
|
||||||
this._titleService.setTitle(this._values.APP_NAME);
|
this._titleService.setTitle(this._values.APP_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,16 +23,6 @@ export class IqserConfigService<T extends IqserAppConfig = IqserAppConfig> {
|
|||||||
this._titleService.setTitle(this._values.APP_NAME);
|
this._titleService.setTitle(this._values.APP_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateIsDocumine(tenant: IStoredTenantId): void {
|
|
||||||
const isDocumine = tenant.documine;
|
|
||||||
this._values = {
|
|
||||||
...this._values,
|
|
||||||
IS_DOCUMINE: isDocumine,
|
|
||||||
THEME: !isDocumine ? THEME_DIRECTORIES.REDACT : THEME_DIRECTORIES.SCM,
|
|
||||||
MANUAL_BASE_URL: !isDocumine ? MANUAL_BASE_URL.REDACT_MANAGER : MANUAL_BASE_URL.DOCUMINE,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected _checkFrontendVersion(): void {
|
protected _checkFrontendVersion(): void {
|
||||||
this._cacheApiService.getCachedValue('FRONTEND_APP_VERSION').then(async lastVersion => {
|
this._cacheApiService.getCachedValue('FRONTEND_APP_VERSION').then(async lastVersion => {
|
||||||
const version = this._values.FRONTEND_APP_VERSION;
|
const version = this._values.FRONTEND_APP_VERSION;
|
||||||
@ -48,28 +34,6 @@ export class IqserConfigService<T extends IqserAppConfig = IqserAppConfig> {
|
|||||||
await this._cacheApiService.cacheValue('FRONTEND_APP_VERSION', version);
|
await this._cacheApiService.cacheValue('FRONTEND_APP_VERSION', version);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#updateAppType() {
|
|
||||||
const storedTenants = this._tenantsService.getStoredTenants();
|
|
||||||
if (storedTenants.length) {
|
|
||||||
const isRedaction = !!storedTenants.find(t => !t.documine);
|
|
||||||
const isDocumine = !!storedTenants.find(t => t.documine);
|
|
||||||
const landingPageTheme =
|
|
||||||
isRedaction && isDocumine
|
|
||||||
? LANDING_PAGE_THEMES.MIXED
|
|
||||||
: isDocumine
|
|
||||||
? LANDING_PAGE_THEMES.DOCUMINE
|
|
||||||
: LANDING_PAGE_THEMES.REDACT_MANAGER;
|
|
||||||
|
|
||||||
this._values = {
|
|
||||||
...this._values,
|
|
||||||
LANDING_PAGE_THEME: landingPageTheme,
|
|
||||||
APP_NAME: landingPageTheme === LANDING_PAGE_THEMES.MIXED ? 'Knecon Cloud' : isDocumine ? 'DocuMine' : 'RedactManager',
|
|
||||||
IS_DOCUMINE: isDocumine,
|
|
||||||
THEME: !isDocumine ? THEME_DIRECTORIES.REDACT : THEME_DIRECTORIES.SCM,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getConfig<T extends IqserAppConfig = IqserAppConfig>() {
|
export function getConfig<T extends IqserAppConfig = IqserAppConfig>() {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { APP_BASE_HREF } from '@angular/common';
|
import { APP_BASE_HREF } from '@angular/common';
|
||||||
import { inject, Injectable } from '@angular/core';
|
import { inject, Injectable } from '@angular/core';
|
||||||
import { firstValueFrom } from 'rxjs';
|
import { firstValueFrom } from 'rxjs';
|
||||||
import { List } from '../utils';
|
import { List } from '../utils/types/iqser-types';
|
||||||
import { GenericService } from './generic.service';
|
import { GenericService } from './generic.service';
|
||||||
|
|
||||||
export type UserAttributes = Record<string, List>;
|
export type UserAttributes = Record<string, List>;
|
||||||
@ -16,7 +16,7 @@ export const KEYS = {
|
|||||||
export abstract class IqserUserPreferenceService extends GenericService<UserAttributes> {
|
export abstract class IqserUserPreferenceService extends GenericService<UserAttributes> {
|
||||||
#userAttributes: UserAttributes = {};
|
#userAttributes: UserAttributes = {};
|
||||||
protected abstract readonly _devFeaturesEnabledKey: string;
|
protected abstract readonly _devFeaturesEnabledKey: string;
|
||||||
protected override readonly _serviceName: string = 'tenant-user-management';
|
protected readonly _serviceName: string = 'tenant-user-management';
|
||||||
|
|
||||||
get userAttributes(): UserAttributes {
|
get userAttributes(): UserAttributes {
|
||||||
return this.#userAttributes;
|
return this.#userAttributes;
|
||||||
@ -37,7 +37,7 @@ export abstract class IqserUserPreferenceService extends GenericService<UserAttr
|
|||||||
}
|
}
|
||||||
|
|
||||||
getLanguage(): string {
|
getLanguage(): string {
|
||||||
return this._getAttribute(KEYS.language, 'en');
|
return this._getAttribute(KEYS.language);
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveLanguage(language: string): Promise<void> {
|
async saveLanguage(language: string): Promise<void> {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { inject, Injectable } from '@angular/core';
|
import { inject, Injectable } from '@angular/core';
|
||||||
import { BehaviorSubject, Observable, switchMap } from 'rxjs';
|
import { BehaviorSubject, Observable, switchMap } from 'rxjs';
|
||||||
import { map, tap } from 'rxjs/operators';
|
import { tap } from 'rxjs/operators';
|
||||||
import { HeadersConfiguration } from '../utils/headers-configuration';
|
import { HeadersConfiguration } from '../utils/headers-configuration';
|
||||||
import { mapEach } from '../utils/operators';
|
import { mapEach } from '../utils/operators';
|
||||||
|
|
||||||
@ -26,14 +26,6 @@ export abstract class StatsService<E, I = E> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getOne(id: string): Observable<E> {
|
|
||||||
const request = this.#http.get<I>(`/${this._serviceName}/${encodeURI(this._defaultModelPath)}/${id}`, {
|
|
||||||
headers: HeadersConfiguration.getHeaders(),
|
|
||||||
});
|
|
||||||
|
|
||||||
return request.pipe(map(entity => new this._entityClass(entity)));
|
|
||||||
}
|
|
||||||
|
|
||||||
get(key: string): E {
|
get(key: string): E {
|
||||||
return this._getBehaviourSubject(key).value;
|
return this._getBehaviourSubject(key).value;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { ChangeDetectionStrategy, Component, input } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'iqser-logo',
|
selector: 'iqser-logo',
|
||||||
template: `<mat-icon [svgIcon]="icon()"></mat-icon>`,
|
template: ` <mat-icon [svgIcon]="icon"></mat-icon>`,
|
||||||
styles: [
|
styles: [
|
||||||
`
|
`
|
||||||
:host {
|
:host {
|
||||||
@ -17,8 +17,9 @@ import { MatIconModule } from '@angular/material/icon';
|
|||||||
`,
|
`,
|
||||||
],
|
],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
standalone: true,
|
||||||
imports: [MatIconModule],
|
imports: [MatIconModule],
|
||||||
})
|
})
|
||||||
export class LogoComponent {
|
export class LogoComponent {
|
||||||
readonly icon = input.required<string>();
|
@Input({ required: true }) icon!: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
|||||||
selector: 'iqser-side-nav [title]',
|
selector: 'iqser-side-nav [title]',
|
||||||
templateUrl: './side-nav.component.html',
|
templateUrl: './side-nav.component.html',
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
standalone: true,
|
||||||
})
|
})
|
||||||
export class SideNavComponent {
|
export class SideNavComponent {
|
||||||
@Input() title!: string;
|
@Input() title!: string;
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import { tap } from 'rxjs/operators';
|
|||||||
templateUrl: './skeleton.component.html',
|
templateUrl: './skeleton.component.html',
|
||||||
styleUrls: ['./skeleton.component.scss'],
|
styleUrls: ['./skeleton.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
standalone: true,
|
||||||
imports: [NgTemplateOutlet, AsyncPipe],
|
imports: [NgTemplateOutlet, AsyncPipe],
|
||||||
})
|
})
|
||||||
export class SkeletonComponent {
|
export class SkeletonComponent {
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
|||||||
`,
|
`,
|
||||||
],
|
],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
standalone: true,
|
||||||
})
|
})
|
||||||
export class SmallChipComponent {
|
export class SmallChipComponent {
|
||||||
@Input() color!: string;
|
@Input() color!: string;
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { Component, HostBinding, Input } from '@angular/core';
|
|||||||
selector: 'iqser-spacer [height]',
|
selector: 'iqser-spacer [height]',
|
||||||
template: ' <div></div> ',
|
template: ' <div></div> ',
|
||||||
styleUrls: ['./spacer.component.scss'],
|
styleUrls: ['./spacer.component.scss'],
|
||||||
|
standalone: true,
|
||||||
})
|
})
|
||||||
export class SpacerComponent {
|
export class SpacerComponent {
|
||||||
@Input({ required: true }) height!: number;
|
@Input({ required: true }) height!: number;
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import { MatTooltipModule } from '@angular/material/tooltip';
|
|||||||
styleUrls: ['./status-bar.component.scss'],
|
styleUrls: ['./status-bar.component.scss'],
|
||||||
encapsulation: ViewEncapsulation.None,
|
encapsulation: ViewEncapsulation.None,
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
standalone: true,
|
||||||
imports: [NgClass, NgStyle, MatTooltipModule],
|
imports: [NgClass, NgStyle, MatTooltipModule],
|
||||||
})
|
})
|
||||||
export class StatusBarComponent<T extends string> {
|
export class StatusBarComponent<T extends string> {
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import { StopPropagationDirective } from '../../directives';
|
|||||||
@Component({
|
@Component({
|
||||||
templateUrl: './toast.component.html',
|
templateUrl: './toast.component.html',
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
standalone: true,
|
||||||
imports: [MatIconModule, StopPropagationDirective],
|
imports: [MatIconModule, StopPropagationDirective],
|
||||||
})
|
})
|
||||||
export class ToastComponent extends Toast {
|
export class ToastComponent extends Toast {
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { SortingOrder } from './models/sorting-order.type';
|
|||||||
import { KeysOf } from '../utils';
|
import { KeysOf } from '../utils';
|
||||||
import { sort } from './functions';
|
import { sort } from './functions';
|
||||||
|
|
||||||
@Pipe({ name: 'sortBy' })
|
@Pipe({ name: 'sortBy', standalone: true })
|
||||||
export class SortByPipe implements PipeTransform {
|
export class SortByPipe implements PipeTransform {
|
||||||
transform<T>(values: T[], order: SortingOrder, column: KeysOf<T>): T[] {
|
transform<T>(values: T[], order: SortingOrder, column: KeysOf<T>): T[] {
|
||||||
return sort(values, order, column);
|
return sort(values, order, column);
|
||||||
|
|||||||
@ -1,9 +1,7 @@
|
|||||||
import { inject, Injectable } from '@angular/core';
|
import { inject, Injectable } from '@angular/core';
|
||||||
import { KeycloakEventType, KeycloakService } from 'keycloak-angular';
|
import { KeycloakService } from 'keycloak-angular';
|
||||||
import { NGXLogger } from 'ngx-logger';
|
import { NGXLogger } from 'ngx-logger';
|
||||||
import { filter, switchMap } from 'rxjs/operators';
|
|
||||||
import { getConfig } from '../../services/iqser-config.service';
|
import { getConfig } from '../../services/iqser-config.service';
|
||||||
import { log, shareLast } from '../../utils';
|
|
||||||
import { UI_ROOT } from '../../utils/tokens';
|
import { UI_ROOT } from '../../utils/tokens';
|
||||||
import { getKeycloakOptions } from '../keycloak-options';
|
import { getKeycloakOptions } from '../keycloak-options';
|
||||||
import { TenantsService } from './tenants.service';
|
import { TenantsService } from './tenants.service';
|
||||||
@ -15,12 +13,6 @@ export class KeycloakStatusService {
|
|||||||
readonly #tenantsService = inject(TenantsService);
|
readonly #tenantsService = inject(TenantsService);
|
||||||
readonly #uiRoot = inject(UI_ROOT);
|
readonly #uiRoot = inject(UI_ROOT);
|
||||||
readonly #logger = inject(NGXLogger);
|
readonly #logger = inject(NGXLogger);
|
||||||
readonly token$ = this.#keycloakService.keycloakEvents$.pipe(
|
|
||||||
log('[KEYCLOAK] New event:'),
|
|
||||||
filter(event => event.type === KeycloakEventType.OnAuthSuccess || event.type === KeycloakEventType.OnAuthRefreshSuccess),
|
|
||||||
switchMap(() => this.#keycloakService.getToken()),
|
|
||||||
shareLast(),
|
|
||||||
);
|
|
||||||
|
|
||||||
createLoginUrlAndExecute(username?: string | null) {
|
createLoginUrlAndExecute(username?: string | null) {
|
||||||
const keycloakInstance = this.#keycloakService?.getKeycloakInstance();
|
const keycloakInstance = this.#keycloakService?.getKeycloakInstance();
|
||||||
|
|||||||
@ -1,18 +1,14 @@
|
|||||||
import { inject, Injectable, signal } from '@angular/core';
|
import { inject, Injectable, signal } from '@angular/core';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { NGXLogger } from 'ngx-logger';
|
import { NGXLogger } from 'ngx-logger';
|
||||||
import { firstValueFrom, Observable, take } from 'rxjs';
|
|
||||||
import { GenericService } from '../../services';
|
|
||||||
import { List } from '../../utils';
|
import { List } from '../../utils';
|
||||||
|
import { GenericService } from '../../services';
|
||||||
import { Tenant, TenantDetails } from '../types';
|
import { Tenant, TenantDetails } from '../types';
|
||||||
import { APPLICATION_TYPES } from '../../utils/constants';
|
import { Observable } from 'rxjs';
|
||||||
import { toObservable } from '@angular/core/rxjs-interop';
|
|
||||||
import { filter } from 'rxjs/operators';
|
|
||||||
|
|
||||||
export interface IStoredTenantId {
|
export interface IStoredTenantId {
|
||||||
readonly tenantId: string;
|
readonly tenantId: string;
|
||||||
readonly created: string;
|
readonly created: string;
|
||||||
readonly documine: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type StoredTenantIds = List<IStoredTenantId>;
|
export type StoredTenantIds = List<IStoredTenantId>;
|
||||||
@ -31,35 +27,25 @@ export class TenantsService extends GenericService<Tenant> {
|
|||||||
key: localStorage.key.bind(localStorage),
|
key: localStorage.key.bind(localStorage),
|
||||||
};
|
};
|
||||||
readonly #activeTenantId = signal('');
|
readonly #activeTenantId = signal('');
|
||||||
readonly #tenantSet = signal(false);
|
|
||||||
readonly tenantSet$ = toObservable(this.#tenantSet);
|
|
||||||
protected readonly _defaultModelPath = 'tenants';
|
protected readonly _defaultModelPath = 'tenants';
|
||||||
protected override readonly _serviceName: string = 'tenant-user-management';
|
protected readonly _serviceName: string = 'tenant-user-management';
|
||||||
|
|
||||||
get activeTenantId() {
|
get activeTenantId() {
|
||||||
return this.#activeTenantId();
|
return this.#activeTenantId();
|
||||||
}
|
}
|
||||||
|
|
||||||
get activeTenant() {
|
|
||||||
return this.getStoredTenants().find(t => t.tenantId === this.activeTenantId);
|
|
||||||
}
|
|
||||||
|
|
||||||
async selectTenant(tenantId: string): Promise<boolean> {
|
async selectTenant(tenantId: string): Promise<boolean> {
|
||||||
this.#mutateStorage(tenantId);
|
this.#mutateStorage(tenantId);
|
||||||
this.#setActiveTenantId(tenantId);
|
this.#setActiveTenantId(tenantId);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async storeTenant() {
|
storeTenant() {
|
||||||
const storedTenants = this.getStoredTenants();
|
const storedTenants = this.getStoredTenants();
|
||||||
const activeTenantId = this.#activeTenantId();
|
const activeTenantId = this.#activeTenantId();
|
||||||
const existing = storedTenants.find(s => s.tenantId === activeTenantId);
|
const existing = storedTenants.find(s => s.tenantId === activeTenantId);
|
||||||
|
|
||||||
const tenant = await firstValueFrom(this.getActiveTenant());
|
|
||||||
|
|
||||||
if (existing) {
|
if (existing) {
|
||||||
this.#logger.info('[TENANTS] Stored tenant exists: ', storedTenants);
|
this.#logger.info('[TENANTS] Stored tenant exists: ', storedTenants);
|
||||||
this.#tenantSet.set(true);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,13 +54,8 @@ export class TenantsService extends GenericService<Tenant> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
storedTenants.push({
|
storedTenants.push({ tenantId: activeTenantId, created: new Date().toISOString() });
|
||||||
tenantId: activeTenantId,
|
|
||||||
created: new Date().toISOString(),
|
|
||||||
documine: tenant.applicationType === APPLICATION_TYPES.DOCUMINE,
|
|
||||||
});
|
|
||||||
this.#storageReference.setItem(STORED_TENANTS_KEY, JSON.stringify(storedTenants));
|
this.#storageReference.setItem(STORED_TENANTS_KEY, JSON.stringify(storedTenants));
|
||||||
this.#tenantSet.set(true);
|
|
||||||
this.#logger.info('[TENANTS] Stored tenants: ', storedTenants);
|
this.#logger.info('[TENANTS] Stored tenants: ', storedTenants);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,13 +104,6 @@ export class TenantsService extends GenericService<Tenant> {
|
|||||||
return this._getOne([this.activeTenantId]);
|
return this._getOne([this.activeTenantId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
waitForSettingTenant(): Observable<boolean> {
|
|
||||||
return this.tenantSet$.pipe(
|
|
||||||
filter(tenantSet => tenantSet),
|
|
||||||
take(1),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#setActiveTenantId(tenantId: string) {
|
#setActiveTenantId(tenantId: string) {
|
||||||
this.#logger.info('[TENANTS] Set current tenant id: ', tenantId);
|
this.#logger.info('[TENANTS] Set current tenant id: ', tenantId);
|
||||||
this.#activeTenantId.set(tenantId);
|
this.#activeTenantId.set(tenantId);
|
||||||
|
|||||||
@ -21,7 +21,7 @@
|
|||||||
<div>
|
<div>
|
||||||
@for (stored of storedTenants; track stored) {
|
@for (stored of storedTenants; track stored) {
|
||||||
<div (click)="select(stored.tenantId)" class="d-flex pointer mat-elevation-z2 card stored-tenant-card mt-10">
|
<div (click)="select(stored.tenantId)" class="d-flex pointer mat-elevation-z2 card stored-tenant-card mt-10">
|
||||||
<iqser-logo class="card-icon" [icon]="tenantIcon(stored)" mat-card-image></iqser-logo>
|
<iqser-logo class="card-icon" icon="iqser:logo" mat-card-image></iqser-logo>
|
||||||
<div class="card-content flex-column">
|
<div class="card-content flex-column">
|
||||||
<span class="heading">{{ stored.tenantId }}</span>
|
<span class="heading">{{ stored.tenantId }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -15,7 +15,6 @@ import { KeycloakStatusService } from '../services/keycloak-status.service';
|
|||||||
templateUrl: './tenant-select.component.html',
|
templateUrl: './tenant-select.component.html',
|
||||||
styleUrls: ['./tenant-select.component.scss'],
|
styleUrls: ['./tenant-select.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
standalone: false,
|
|
||||||
})
|
})
|
||||||
export class TenantSelectComponent {
|
export class TenantSelectComponent {
|
||||||
readonly #uiRoot = inject(UI_ROOT);
|
readonly #uiRoot = inject(UI_ROOT);
|
||||||
@ -71,10 +70,6 @@ export class TenantSelectComponent {
|
|||||||
this.#loadStoredTenants();
|
this.#loadStoredTenants();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected tenantIcon(tenant: IStoredTenantId) {
|
|
||||||
return `red:${tenant.documine ? 'documine' : 'redaction'}-logo`;
|
|
||||||
}
|
|
||||||
|
|
||||||
#loadStoredTenants() {
|
#loadStoredTenants() {
|
||||||
this.storedTenants = this.tenantsService.getStoredTenants().sort((a, b) => a.tenantId.localeCompare(b.tenantId));
|
this.storedTenants = this.tenantsService.getStoredTenants().sort((a, b) => a.tenantId.localeCompare(b.tenantId));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,7 @@
|
|||||||
import { ApplicationType } from '../../utils/constants';
|
|
||||||
|
|
||||||
export type TenantDetails = Record<string, unknown>;
|
export type TenantDetails = Record<string, unknown>;
|
||||||
|
|
||||||
export interface Tenant<TD extends TenantDetails = TenantDetails> {
|
export interface Tenant<TD extends TenantDetails = TenantDetails> {
|
||||||
tenantId: string;
|
tenantId: string;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
applicationType: ApplicationType;
|
|
||||||
details: TD;
|
details: TD;
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user