Merge branch 'master' into workflow

This commit is contained in:
Adina Țeudan 2021-09-27 22:17:11 +03:00
commit ba4fbdabe6
40 changed files with 351 additions and 177 deletions

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M21 11l2-2c-3.73-3.73-8.87-5.15-13.7-4.31l2.58 2.58c3.3-.02 6.61 1.22 9.12 3.73zm-2 2c-1.08-1.08-2.36-1.85-3.72-2.33l3.02 3.02.7-.69zM9 17l3 3 3-3c-1.65-1.66-4.34-1.66-6 0zM3.41 1.64L2 3.05 5.05 6.1C3.59 6.83 2.22 7.79 1 9l2 2c1.23-1.23 2.65-2.16 4.17-2.78l2.24 2.24C7.79 10.89 6.27 11.74 5 13l2 2c1.35-1.35 3.11-2.04 4.89-2.06l7.08 7.08 1.41-1.41L3.41 1.64z"/></svg>

After

Width:  |  Height:  |  Size: 517 B

View File

@ -0,0 +1,3 @@
@function hexToRgb($color) {
@return #{red($color) + ', ' + green($color) + ', ' + blue($color)};
}

View File

@ -1,5 +1,3 @@
@import 'variables';
@mixin line-clamp($lines) { @mixin line-clamp($lines) {
display: -webkit-box; display: -webkit-box;
-webkit-line-clamp: $lines; -webkit-line-clamp: $lines;
@ -25,7 +23,7 @@
} }
@mixin scroll-bar { @mixin scroll-bar {
scrollbar-color: $grey-5 $grey-2; scrollbar-color: var(--iqser-quick-filter-border) var(--iqser-grey-2);
scrollbar-width: thin; scrollbar-width: thin;
&::-webkit-scrollbar { &::-webkit-scrollbar {
@ -34,19 +32,19 @@
/* Track */ /* Track */
&::-webkit-scrollbar-track { &::-webkit-scrollbar-track {
background: $grey-2; background: var(--iqser-grey-2);
} }
/* Handle */ /* Handle */
&::-webkit-scrollbar-thumb { &::-webkit-scrollbar-thumb {
background: $grey-5; background: var(--iqser-quick-filter-border);
} }
} }
@mixin inset-shadow { @mixin inset-shadow {
box-shadow: inset 0 4px 3px -2px $grey-4; box-shadow: inset 0 4px 3px -2px var(--iqser-btn-bg-hover);
} }
@mixin drop-shadow { @mixin drop-shadow {
box-shadow: 0 4px 3px 2px $grey-4; box-shadow: 0 4px 3px 2px var(--iqser-btn-bg-hover);
} }

View File

@ -0,0 +1,31 @@
@use 'sass:meta';
:root {
--iqser-primary: lightblue;
--iqser-primary-rgb: 220, 230, 234;
--iqser-primary-2: orange;
--iqser-accent: blue;
--iqser-accent-rgb: 123, 234, 111;
--iqser-disabled: #9398a0;
--iqser-not-disabled-table-item: #f9fafb;
--iqser-btn-bg-hover: #e2e4e9;
--iqser-btn-bg: #f0f1f4;
--iqser-warn: #fdbd00;
--iqser-white: white;
--iqser-separator: rgba(226, 228, 233, 0.9);
--iqser-quick-filter-border: #d3d5da;
--iqser-grey-2: #f4f5f7;
--iqser-grey-3: #aaacb3;
--iqser-grey-4: #e2e4e9;
--iqser-grey-5: #d3d5da;
--iqser-grey-6: #f0f1f4;
--iqser-helpmode-primary: green;
}
@mixin configure($args...) {
:root {
@each $name, $value in meta.keywords($args) {
--#{$name}: #{$value};
}
}
}

View File

@ -1,10 +0,0 @@
// This rebel line is crying (in WebStorm) but it actually works
@import '../../../../../apps/red-ui/src/assets/styles/variables';
$btn-bg-hover: #e2e4e9 !default;
$btn-bg: #f0f1f4 !default;
$warn: #fdbd00 !default;
$white: white !default;
$separator: rgba(226, 228, 233, 0.9) !default;
$quick-filter-border: #d3d5da !default;
$filter-bg: #f4f5f7 !default;

View File

@ -1,5 +1,3 @@
@import 'variables';
.mat-button, .mat-button,
.mat-flat-button { .mat-flat-button {
border-radius: 17px !important; border-radius: 17px !important;
@ -36,19 +34,19 @@
padding: 0 14px; padding: 0 14px;
transition: background-color 0.2s, color 0.2s; transition: background-color 0.2s, color 0.2s;
background-color: $primary; background-color: var(--iqser-primary);
&.mat-button-disabled { &.mat-button-disabled {
background-color: $primary; background-color: var(--iqser-primary);
.mat-button-wrapper { .mat-button-wrapper {
color: $white; color: var(--iqser-white);
opacity: 0.5; opacity: 0.5;
} }
} }
&:not(.mat-button-disabled):hover { &:not(.mat-button-disabled):hover {
background-color: $primary-2; background-color: var(--iqser-primary-2);
} }
} }
@ -64,30 +62,30 @@ iqser-circle-button {
transition: background-color 0.2s; transition: background-color 0.2s;
&.overlay { &.overlay {
background: rgba($primary, 0.1); background: rgba(var(--iqser-primary-rgb), 0.1);
} }
&:not(.overlay):hover { &:not(.overlay):hover {
background-color: $btn-bg; background-color: var(--iqser-btn-bg);
} }
&.primary { &.primary {
font-weight: 500 !important; font-weight: 500 !important;
background-color: $primary; background-color: var(--iqser-primary);
color: $white; color: var(--iqser-white);
&:hover { &:hover {
background-color: $primary-2; background-color: var(--iqser-primary-2);
} }
} }
&.dark-bg:hover { &.dark-bg:hover {
background-color: $btn-bg-hover; background-color: var(--iqser-btn-bg-hover);
} }
} }
.dot { .dot {
background: $primary; background: var(--iqser-primary);
height: 10px; height: 10px;
width: 10px; width: 10px;
border-radius: 50%; border-radius: 50%;
@ -102,10 +100,10 @@ iqser-circle-button,
iqser-icon-button { iqser-icon-button {
&[aria-expanded='true'] { &[aria-expanded='true'] {
button { button {
background: rgba($primary, 0.1); background: rgba(var(--iqser-primary-rgb), 0.1);
&.primary { &.primary {
background: $primary-2; background: var(--iqser-primary-2);
} }
} }
} }

View File

@ -1,7 +1,5 @@
@import 'apps/red-ui/src/assets/styles/variables';
.mat-dialog-container { .mat-dialog-container {
color: $accent; color: var(--iqser-accent);
padding: 0 !important; padding: 0 !important;
border-radius: 8px !important; border-radius: 8px !important;
} }
@ -27,7 +25,7 @@
.dialog-actions { .dialog-actions {
height: 81px; height: 81px;
box-sizing: border-box; box-sizing: border-box;
border-top: 1px solid $separator; border-top: 1px solid var(--iqser-separator);
padding: 0 32px; padding: 0 32px;
align-items: center; align-items: center;

View File

@ -1,17 +1,12 @@
@import 'variables';
.full-page-section, .full-page-section,
.full-page-content { .full-page-content {
position: fixed; position: fixed;
top: 0; inset: 0;
bottom: 0;
left: 0;
right: 0;
} }
.full-page-section { .full-page-section {
opacity: 0.7; opacity: 0.7;
background: $white; background: var(--iqser-white);
z-index: 900; z-index: 900;
} }

View File

@ -1,5 +1,4 @@
@import 'variables'; @use 'common-mixins' as mixins;
@import 'mixins';
form .iqser-input-group:not(first-of-type) { form .iqser-input-group:not(first-of-type) {
margin-top: 14px; margin-top: 14px;
@ -22,10 +21,10 @@ form .iqser-input-group:not(first-of-type) {
position: absolute; position: absolute;
right: 1px; right: 1px;
bottom: 1px; bottom: 1px;
background: $quick-filter-border; background: var(--iqser-quick-filter-border);
height: 34px; height: 34px;
width: 34px; width: 34px;
border-left: 1px solid $quick-filter-border; border-left: 1px solid var(--iqser-quick-filter-border);
border-top-right-radius: 7px; border-top-right-radius: 7px;
border-bottom-right-radius: 7px; border-bottom-right-radius: 7px;
cursor: pointer; cursor: pointer;
@ -35,13 +34,13 @@ form .iqser-input-group:not(first-of-type) {
justify-content: center; justify-content: center;
&:hover { &:hover {
background: $btn-bg; background: var(--iqser-btn-bg);
} }
mat-icon { mat-icon {
width: 14px; width: 14px;
height: 14px; height: 14px;
color: $accent; color: var(--iqser-accent);
} }
&.disabled { &.disabled {
@ -60,7 +59,7 @@ form .iqser-input-group:not(first-of-type) {
.mat-form-field-label { .mat-form-field-label {
opacity: 0.7 !important; opacity: 0.7 !important;
color: $accent !important; color: var(--iqser-accent) !important;
transform: translateY(-1.34em) !important; transform: translateY(-1.34em) !important;
} }
@ -83,9 +82,9 @@ form .iqser-input-group:not(first-of-type) {
} }
.mat-button-toggle-checked { .mat-button-toggle-checked {
background: $primary; background: var(--iqser-primary);
transition: background-color 0.25s ease; transition: background-color 0.25s ease;
color: $white; color: var(--iqser-white);
} }
input, input,
@ -94,7 +93,7 @@ form .iqser-input-group:not(first-of-type) {
box-sizing: border-box; box-sizing: border-box;
padding-left: 11px; padding-left: 11px;
padding-right: 11px; padding-right: 11px;
border: 1px solid $quick-filter-border; border: 1px solid var(--iqser-quick-filter-border);
font-family: Inter, sans-serif; font-family: Inter, sans-serif;
font-size: 13px; font-size: 13px;
background-color: #ffffff; background-color: #ffffff;
@ -108,26 +107,26 @@ form .iqser-input-group:not(first-of-type) {
} }
&:focus:not(:disabled):not(.mat-select-disabled) { &:focus:not(:disabled):not(.mat-select-disabled) {
border-color: $accent; border-color: var(--iqser-accent);
} }
&::placeholder { &::placeholder {
color: $accent; color: var(--iqser-accent);
opacity: 0.7; opacity: 0.7;
} }
&.ng-invalid.ng-touched { &.ng-invalid.ng-touched {
border-color: rgba($primary, 0.3); border-color: rgba(var(--iqser-primary-rgb), 0.3);
&:focus { &:focus {
border-color: $primary; border-color: var(--iqser-primary);
} }
} }
&:disabled, &:disabled,
&.mat-select-disabled { &.mat-select-disabled {
background-color: $filter-bg; background-color: var(--iqser-grey-2);
color: rgba($accent, 0.3); color: rgba(var(--iqser-accent-rgb), 0.3);
} }
} }
@ -154,7 +153,7 @@ form .iqser-input-group:not(first-of-type) {
resize: vertical; resize: vertical;
padding-top: 7px; padding-top: 7px;
padding-bottom: 7px; padding-bottom: 7px;
@include scroll-bar; @include mixins.scroll-bar;
&.has-scrollbar { &.has-scrollbar {
border-top-right-radius: 0; border-top-right-radius: 0;
@ -168,7 +167,7 @@ form .iqser-input-group:not(first-of-type) {
letter-spacing: 0; letter-spacing: 0;
line-height: 14px; line-height: 14px;
margin-bottom: 2px; margin-bottom: 2px;
color: $accent; color: var(--iqser-accent);
&.mat-checkbox-layout { &.mat-checkbox-layout {
opacity: 1; opacity: 1;
@ -178,7 +177,7 @@ form .iqser-input-group:not(first-of-type) {
&.required label:after { &.required label:after {
content: ' *'; content: ' *';
color: $primary; color: var(--iqser-primary);
} }
&.datepicker-wrapper { &.datepicker-wrapper {
@ -196,10 +195,10 @@ form .iqser-input-group:not(first-of-type) {
position: absolute; position: absolute;
right: 0; right: 0;
top: 1px; top: 1px;
color: $accent; color: var(--iqser-accent);
&.mat-datepicker-toggle-active { &.mat-datepicker-toggle-active {
color: $primary; color: var(--iqser-primary);
} }
.mat-icon-button { .mat-icon-button {

View File

@ -0,0 +1,7 @@
@use 'common-inputs';
@use 'common-buttons';
@use 'common-texts';
@use 'common-tables';
@use 'common-full-pages';
@use 'common-layout';
@use 'common-dialogs';

View File

@ -1,5 +1,4 @@
@import 'variables'; @use 'common-mixins' as mixins;
@import 'mixins';
.all-caps-label { .all-caps-label {
text-transform: uppercase; text-transform: uppercase;
@ -24,17 +23,18 @@
} }
&.primary { &.primary {
color: $primary; color: var(--iqser-primary);
opacity: 1; opacity: 1;
} }
} }
a { a {
color: $primary; color: var(--iqser-primary);
transition: color 0.1s; transition: color 0.1s;
&:hover { &:hover {
color: lighten($primary, 10%); color: var(--iqser-primary);
filter: brightness(140%);
} }
&.with-underline { &.with-underline {
@ -48,7 +48,7 @@ a {
pre { pre {
font-family: Inter, sans-serif; font-family: Inter, sans-serif;
color: $accent; color: var(--iqser-accent);
} }
.heading-xl { .heading-xl {
@ -102,17 +102,17 @@ pre {
} }
.large-label { .large-label {
color: $accent; color: var(--iqser-accent);
font-size: 13px; font-size: 13px;
line-height: 16px; line-height: 16px;
} }
.clamp-1 { .clamp-1 {
@include line-clamp(1); @include mixins.line-clamp(1);
} }
.clamp-2 { .clamp-2 {
@include line-clamp(2); @include mixins.line-clamp(2);
} }
.no-wrap { .no-wrap {

View File

@ -1,7 +0,0 @@
@import 'inputs';
@import 'buttons';
@import 'texts';
@import 'tables';
@import 'full-pages';
@import 'layout';
@import 'dialogs';

View File

@ -1,5 +1,3 @@
@import '../../../assets/styles/common';
:host { :host {
height: var(--size); height: var(--size);
width: var(--size); width: var(--size);
@ -26,15 +24,15 @@
} }
&.primary.mat-button-disabled { &.primary.mat-button-disabled {
background-color: $btn-bg; background-color: var(--iqser-btn-bg);
color: $white !important; color: var(--iqser-white) !important;
} }
&.warn:not([disabled]) { &.warn:not([disabled]) {
background-color: $warn; background-color: var(--iqser-warn);
&:hover { &:hover {
background-color: $warn; background-color: var(--iqser-warn);
} }
} }
} }

View File

@ -1,5 +1,3 @@
@import '../../../assets/styles/common';
button { button {
padding: 0 14px; padding: 0 14px;
width: 100%; width: 100%;
@ -9,10 +7,10 @@ button {
} }
&.show-bg { &.show-bg {
background-color: $btn-bg; background-color: var(--iqser-btn-bg);
&:not(.mat-button-disabled):hover { &:not(.mat-button-disabled):hover {
background-color: $btn-bg-hover; background-color: var(--iqser-btn-bg-hover);
} }
} }

View File

@ -16,6 +16,7 @@ import { IqserIconsModule } from './icons';
import { IqserButtonsModule } from './buttons'; import { IqserButtonsModule } from './buttons';
import { IqserScrollbarModule } from './scrollbar'; import { IqserScrollbarModule } from './scrollbar';
import { IqserEmptyStatesModule } from './empty-states'; import { IqserEmptyStatesModule } from './empty-states';
import { LogPipe } from './utils/pipes/log.pipe';
const matModules = [MatIconModule, MatProgressSpinnerModule]; const matModules = [MatIconModule, MatProgressSpinnerModule];
const modules = [ const modules = [
@ -33,8 +34,8 @@ const components = [StatusBarComponent, FullPageLoadingIndicatorComponent, FullP
const pipes = [SortByPipe, HumanizePipe]; const pipes = [SortByPipe, HumanizePipe];
@NgModule({ @NgModule({
declarations: [...components, ...pipes], declarations: [...components, ...pipes, LogPipe],
imports: [CommonModule, ...matModules, ...modules], imports: [CommonModule, ...matModules, ...modules],
exports: [...components, ...pipes, ...modules] exports: [...components, ...pipes, ...modules, LogPipe]
}) })
export class CommonUiModule {} export class CommonUiModule {}

View File

@ -1,5 +1,3 @@
@import '../../../assets/styles/common';
.empty-state { .empty-state {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -13,7 +11,7 @@
} }
.heading-l { .heading-l {
color: $grey-7; color: var(--iqser-disabled);
} }
> .heading-l, > .heading-l,

View File

@ -1,15 +1,36 @@
<ng-container *ngIf="errorService.error$ | async as error"> <ng-container *ngIf="errorService.error$ | async as error">
<section class="full-page-section"></section> <ng-container *ngIf="error.status === 0; else serverError">
<section class="full-page-content"> <div class="offline-box flex-align-items-center" [@animateOpenClose]="'open'">
<mat-icon svgIcon="iqser:failure"></mat-icon> <mat-icon svgIcon="iqser:offline"></mat-icon>
<div class="heading-l mt-24" translate="error.title"></div>
<div class="mt-16 error">{{ error.message }}</div> <div class="heading-l ml-14" [translate]="'error.offline'"></div>
<iqser-icon-button
(action)="reload()" <iqser-circle-button
[label]="'error.reload' | translate" (action)="errorService.set(undefined)"
[type]="iconButtonTypes.primary" [tooltip]="'error.close' | translate"
class="mt-20" icon="iqser:close"
icon="iqser:refresh" tooltipPosition="below"
></iqser-icon-button> ></iqser-circle-button>
</section> </div>
</ng-container>
<ng-template #serverError>
<section class="full-page-section"></section>
<section class="full-page-content flex-align-items-center">
<mat-icon svgIcon="iqser:failure"></mat-icon>
<div class="heading-l mt-24" [translate]="'error.title'"></div>
<div class="mt-16 error">{{ error.message }}</div>
<iqser-icon-button
(action)="reload()"
[label]="'error.reload' | translate"
[type]="iconButtonTypes.primary"
class="mt-20"
icon="iqser:refresh"
></iqser-icon-button>
</section>
</ng-template>
</ng-container> </ng-container>

View File

@ -1,13 +1,30 @@
@import '../../../assets/styles/variables'; .offline-box {
position: fixed;
bottom: 20px;
right: 20px;
height: 40px;
width: 300px;
background: var(--iqser-white);
border: 1px solid var(--iqser-separator);
border-radius: 10px;
padding: 14px;
> mat-icon {
opacity: 0.3;
}
> iqser-circle-button {
flex-grow: 1;
justify-content: flex-end;
}
}
.full-page-section { .full-page-section {
opacity: 0.95; opacity: 0.95;
} }
.full-page-content { .full-page-content {
display: flex;
flex-direction: column; flex-direction: column;
align-items: center;
text-align: center; text-align: center;
> mat-icon { > mat-icon {
@ -16,12 +33,12 @@
opacity: 0.1; opacity: 0.1;
} }
.heading-l {
color: $grey-7;
font-weight: initial;
}
.error { .error {
color: $red-1; color: var(--iqser-primary);
} }
} }
:is(.offline-box, .full-page-content) .heading-l {
color: #9398a0;
font-weight: initial;
}

View File

@ -1,11 +1,19 @@
import { ChangeDetectionStrategy, Component } from '@angular/core'; import { ChangeDetectionStrategy, Component } from '@angular/core';
import { IconButtonTypes } from '../../buttons'; import { IconButtonTypes } from '../../buttons';
import { ErrorService } from '../error.service'; import { ErrorService } from '../error.service';
import { animate, state, style, transition, trigger } from '@angular/animations';
@Component({ @Component({
selector: 'iqser-full-page-error', selector: 'iqser-full-page-error',
templateUrl: './full-page-error.component.html', templateUrl: './full-page-error.component.html',
styleUrls: ['./full-page-error.component.scss'], styleUrls: ['./full-page-error.component.scss'],
animations: [
trigger('animateOpenClose', [
state('open', style({ bottom: '20px' })),
state('void', style({ bottom: '-70px' })),
transition('* => open, open => void', animate('1s ease-in-out'))
])
],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class FullPageErrorComponent { export class FullPageErrorComponent {

View File

@ -1,2 +1,4 @@
export * from './error.service'; export * from './error.service';
export * from './max-retries.token';
export * from './server-error-interceptor';
export * from './full-page-error/full-page-error.component'; export * from './full-page-error/full-page-error.component';

View File

@ -0,0 +1,3 @@
import { InjectionToken } from '@angular/core';
export const MAX_RETRIES_ON_SERVER_ERROR = new InjectionToken<number>('Number of retries before giving up');

View File

@ -0,0 +1,52 @@
import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Inject, Injectable, Optional } from '@angular/core';
import { MonoTypeOperatorFunction, Observable, throwError, timer } from 'rxjs';
import { catchError, mergeMap, retryWhen, tap } from 'rxjs/operators';
import { MAX_RETRIES_ON_SERVER_ERROR } from './max-retries.token';
import { ErrorService } from './error.service';
function updateSeconds(seconds: number) {
if (seconds === 0 || seconds === 1) {
return seconds + 1;
} else {
return seconds * seconds;
}
}
function backoffOnServerError(maxRetries = 3): MonoTypeOperatorFunction<HttpEvent<unknown>> {
let seconds = 0;
return retryWhen(attempts =>
attempts.pipe(
tap(() => (seconds = updateSeconds(seconds))),
mergeMap((error: HttpErrorResponse, index) => {
if ((error.status < 500 && error.status !== 0) || index === maxRetries) {
return throwError(error);
} else {
console.error('An error occurred: ', error);
console.error(`Retrying in ${seconds} seconds...`);
return timer(seconds * 1000);
}
})
)
);
}
@Injectable()
export class ServerErrorInterceptor implements HttpInterceptor {
constructor(
private readonly _errorService: ErrorService,
@Optional() @Inject(MAX_RETRIES_ON_SERVER_ERROR) private readonly _maxRetries: number
) {}
intercept(req: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
return next.handle(req).pipe(
catchError((error: HttpErrorResponse) => {
if (error.status >= 500 || error.status === 0) {
this._errorService.set(error);
}
return throwError(error);
}),
backoffOnServerError(this._maxRetries || 3)
);
}
}

View File

@ -1,5 +1,3 @@
@import '../../../assets/styles/variables';
.filter-menu-options, .filter-menu-options,
.filter-menu-header { .filter-menu-header {
display: flex; display: flex;
@ -27,7 +25,7 @@
} }
.filter-options { .filter-options {
background-color: $filter-bg; background-color: var(--iqser-grey-2);
padding-bottom: 8px; padding-bottom: 8px;
} }

View File

@ -1,5 +1,3 @@
@import '../../../assets/styles/variables';
:host { :host {
display: flex; display: flex;
flex: 1; flex: 1;
@ -8,9 +6,9 @@
.quick-filter { .quick-filter {
box-sizing: border-box; box-sizing: border-box;
border: 1px solid $quick-filter-border; border: 1px solid var(--iqser-quick-filter-border);
border-radius: 17px; border-radius: 17px;
background-color: $btn-bg; background-color: var(--iqser-btn-bg);
padding: 0 14px; padding: 0 14px;
height: 34px; height: 34px;
display: flex; display: flex;
@ -19,11 +17,11 @@
transition: background-color 0.2s; transition: background-color 0.2s;
&:hover { &:hover {
background-color: $white; background-color: var(--iqser-white);
} }
&.active { &.active {
background-color: $white; background-color: var(--iqser-white);
font-weight: 600; font-weight: 600;
border: none; border: none;
} }

View File

@ -1,5 +1,3 @@
@import '../../../../../../apps/red-ui/src/assets/styles/variables';
.help-button { .help-button {
width: 44px; width: 44px;
height: 40px; height: 40px;
@ -7,7 +5,7 @@
bottom: 20px; bottom: 20px;
right: 0; right: 0;
z-index: 1; z-index: 1;
background: $green-2; background: var(--iqser-helpmode-primary);
border-top-left-radius: 8px; border-top-left-radius: 8px;
border-bottom-left-radius: 8px; border-bottom-left-radius: 8px;
box-shadow: -1px 1px 5px 0 rgba(40, 50, 65, 0.25); box-shadow: -1px 1px 5px 0 rgba(40, 50, 65, 0.25);
@ -40,10 +38,10 @@
box-sizing: border-box; box-sizing: border-box;
height: 100%; height: 100%;
width: 100%; width: 100%;
border-left: 8px solid $green-2; border-left: 8px solid var(--iqser-helpmode-primary);
border-right: 8px solid $green-2; border-right: 8px solid var(--iqser-helpmode-primary);
border-top: 8px solid $green-2; border-top: 8px solid var(--iqser-helpmode-primary);
border-bottom: 60px solid $green-2; border-bottom: 60px solid var(--iqser-helpmode-primary);
z-index: 10; z-index: 10;
position: absolute; position: absolute;
display: flex; display: flex;

View File

@ -20,6 +20,7 @@ export class IqserIconsModule {
'help-outline', 'help-outline',
'lanes', 'lanes',
'list', 'list',
'offline',
'refresh', 'refresh',
'search', 'search',
'sort-asc', 'sort-asc',

View File

@ -1,5 +1,3 @@
@import '../../../assets/styles/common';
:host { :host {
cursor: pointer; cursor: pointer;
} }
@ -12,22 +10,22 @@
&.inactive { &.inactive {
border: 1px solid #d3d5da; border: 1px solid #d3d5da;
background-color: $white; background-color: var(--iqser-white);
} }
.mat-icon { .mat-icon {
color: $primary; color: var(--iqser-primary);
width: var(--size); width: var(--size);
height: var(--size); height: var(--size);
} }
&.with-bg { &.with-bg {
.mat-icon { .mat-icon {
color: $white; color: var(--iqser-white);
} }
&.inactive { &.inactive {
border: 1px solid $btn-bg; border: 1px solid var(--iqser-btn-bg);
background-color: transparent; background-color: transparent;
} }
} }

View File

@ -1,7 +1,5 @@
@import '../../../assets/styles/common';
.scroll-button { .scroll-button {
background-color: $white; background-color: var(--iqser-white);
position: absolute; position: absolute;
right: 0; right: 0;
height: 40px; height: 40px;
@ -26,5 +24,5 @@
mat-icon { mat-icon {
width: 22px; width: 22px;
height: 22px; height: 22px;
color: $grey-7; color: var(--iqser-disabled);
} }

View File

@ -1,7 +1,7 @@
import { ChangeDetectionStrategy, Component, HostListener, Input, OnInit } from '@angular/core'; import { ChangeDetectionStrategy, Component, HostListener, Input, OnInit } from '@angular/core';
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling'; import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
import { concatMap, delay, distinctUntilChanged, map, startWith } from 'rxjs/operators'; import { delay, distinctUntilChanged, map, startWith } from 'rxjs/operators';
import { Observable, of } from 'rxjs'; import { combineLatest, fromEvent, Observable } from 'rxjs';
import { Required } from '../../utils'; import { Required } from '../../utils';
const ButtonTypes = { const ButtonTypes = {
@ -34,11 +34,14 @@ export class ScrollButtonComponent implements OnInit {
const showScrollUp = () => scrollIsNeeded() && !reachedEnd(ButtonTypes.top); const showScrollUp = () => scrollIsNeeded() && !reachedEnd(ButtonTypes.top);
const showScrollDown = () => scrollIsNeeded() && !reachedEnd(ButtonTypes.bottom); const showScrollDown = () => scrollIsNeeded() && !reachedEnd(ButtonTypes.bottom);
const scroll$ = this.scrollViewport.elementScrolled().pipe( /** Force an initial emit, so combineLatest works */
startWith(''), const scrolled$ = this.scrollViewport.elementScrolled().pipe(startWith(null));
/** Delay first value so that we can wait for items to be rendered in viewport and get correct values */ const resized$ = fromEvent(window, 'resize').pipe(startWith(null));
concatMap((value, index) => (index === 0 ? of(value).pipe(delay(0)) : of(value))) const rangeChange$ = this.scrollViewport.renderedRangeStream.pipe(startWith(null));
);
/** Delay so that we can wait for items to be rendered in viewport and get correct values */
const scroll$ = combineLatest([scrolled$, resized$, rangeChange$]).pipe(delay(0));
this.showScrollUp$ = scroll$.pipe(map(showScrollUp), distinctUntilChanged()); this.showScrollUp$ = scroll$.pipe(map(showScrollUp), distinctUntilChanged());
this.showScrollDown$ = scroll$.pipe(map(showScrollDown), distinctUntilChanged()); this.showScrollDown$ = scroll$.pipe(map(showScrollDown), distinctUntilChanged());
} }

View File

@ -8,7 +8,7 @@ export class SyncWidthDirective implements OnDestroy {
private readonly _interval: number; private readonly _interval: number;
constructor(private readonly _elementRef: ElementRef) { constructor(private readonly _elementRef: ElementRef) {
this._interval = setInterval(() => { this._interval = window.setInterval(() => {
this._matchWidth(); this._matchWidth();
}, 1000); }, 1000);
} }

View File

@ -1,5 +1,3 @@
@import '../../../assets/styles/common';
:host { :host {
display: flex; display: flex;
height: 30px; height: 30px;
@ -38,7 +36,7 @@
.sort-arrows-container { .sort-arrows-container {
display: none; display: none;
color: $primary; color: var(--iqser-primary);
margin-left: 8px; margin-left: 8px;
mat-icon { mat-icon {

View File

@ -1,8 +1,6 @@
@import '../../../assets/styles/common';
.table-header { .table-header {
display: flex; display: flex;
border-bottom: 1px solid $separator; border-bottom: 1px solid var(--iqser-separator);
&.no-data.selection-enabled:not([synced='true']) { &.no-data.selection-enabled:not([synced='true']) {
padding-left: 30px; padding-left: 30px;
@ -10,12 +8,12 @@
} }
.header-item { .header-item {
background-color: $btn-bg; background-color: var(--iqser-btn-bg);
height: 50px; height: 50px;
display: flex; display: flex;
align-items: center; align-items: center;
z-index: 1; z-index: 1;
border-bottom: 1px solid $separator; border-bottom: 1px solid var(--iqser-separator);
box-sizing: border-box; box-sizing: border-box;
padding: 0 24px; padding: 0 24px;

View File

@ -1,4 +1,4 @@
@import '../../../assets/styles/common'; @use '../../../assets/styles/common-mixins' as mixins;
:host cdk-virtual-scroll-viewport { :host cdk-virtual-scroll-viewport {
height: calc(100vh - 50px - 31px - 111px); height: calc(100vh - 50px - 31px - 111px);
@ -25,7 +25,7 @@
justify-content: center; justify-content: center;
position: relative; position: relative;
box-sizing: border-box; box-sizing: border-box;
border-bottom: 1px solid $separator; border-bottom: 1px solid var(--iqser-separator);
height: var(--itemSize); height: var(--itemSize);
padding: 0 10px; padding: 0 10px;
@ -61,8 +61,8 @@
} }
&.disabled > div { &.disabled > div {
background-color: $grey-2; background-color: var(--iqser-grey-2);
color: $grey-7; color: var(--iqser-disabled);
.action-buttons { .action-buttons {
color: initial; color: initial;
@ -71,7 +71,7 @@
.table-item-title { .table-item-title {
font-weight: 600; font-weight: 600;
@include line-clamp(1); @include mixins.line-clamp(1);
} }
.action-buttons { .action-buttons {
@ -86,7 +86,7 @@
padding-left: 100px; padding-left: 100px;
padding-right: 21px; padding-right: 21px;
z-index: 1; z-index: 1;
background: linear-gradient(to right, rgba(244, 245, 247, 0) 0%, $grey-2 35%); background: linear-gradient(to right, rgba(244, 245, 247, 0) 0%, var(--iqser-grey-2) 35%);
mat-icon { mat-icon {
width: 14px; width: 14px;
@ -113,14 +113,14 @@
} }
&:hover:not(.disabled) > div { &:hover:not(.disabled) > div {
background-color: $grey-8; background-color: var(--iqser-not-disabled-table-item);
} }
} }
} }
&:hover { &:hover {
overflow-y: auto !important; overflow-y: auto !important;
@include scroll-bar; @include mixins.scroll-bar;
&.has-scrollbar { &.has-scrollbar {
.table-item { .table-item {

View File

@ -1,4 +1,5 @@
@import '../../../assets/styles/common'; @import '../../../assets/styles/common-variables';
@import '../../../assets/styles/common-mixins';
:host { :host {
display: flex; display: flex;
@ -15,7 +16,7 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
flex: 1; flex: 1;
background-color: $grey-2; background-color: var(--iqser-grey-2);
border-radius: 6px; border-radius: 6px;
padding-top: 18px; padding-top: 18px;
position: relative; position: relative;
@ -53,10 +54,10 @@
&:not(.list-can-receive):not(.list-dragging) { &:not(.list-can-receive):not(.list-dragging) {
background: repeating-linear-gradient( background: repeating-linear-gradient(
-45deg, -45deg,
$separator, var(--iqser-separator),
$separator 1px, var(--iqser-separator) 1px,
$white 1px, var(--iqser-white) 1px,
$white 8px var(--iqser-white) 8px
); );
> .heading, ::ng-deep.cdk-drag > * { > .heading, ::ng-deep.cdk-drag > * {
@ -76,7 +77,7 @@
} }
.cdk-drag { .cdk-drag {
background-color: $white; background-color: var(--iqser-white);
transition: background-color 0.2s, box-shadow 0.2s; transition: background-color 0.2s, box-shadow 0.2s;
border-radius: 8px; border-radius: 8px;
margin: 0 8px 4px 8px; margin: 0 8px 4px 8px;
@ -86,13 +87,13 @@
} }
&:hover { &:hover {
background-color: $grey-6; background-color: var(--iqser-grey-6);
box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.15); box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.15);
} }
} }
.cdk-drag-placeholder { .cdk-drag-placeholder {
border: 1px dashed $grey-5; border: 1px dashed var(--iqser-grey-5);
border-radius: 8px; border-radius: 8px;
min-height: var(--height); min-height: var(--height);
margin: 0 8px 4px 8px; margin: 0 8px 4px 8px;

View File

@ -6,7 +6,7 @@ export function Debounce(delay = 300): MethodDecorator {
descriptorCopy.value = function _new(...args: unknown[]) { descriptorCopy.value = function _new(...args: unknown[]) {
clearTimeout(timeout); clearTimeout(timeout);
timeout = setTimeout(() => (descriptor.value as (...params: unknown[]) => unknown).apply(this, args), delay); timeout = window.setTimeout(() => (descriptor.value as (...params: unknown[]) => unknown).apply(this, args), delay);
}; };
return descriptorCopy; return descriptorCopy;

View File

@ -0,0 +1,60 @@
import { FunctionKeys } from '../types/utility-types';
export interface SimpleChange<T> {
readonly previousValue: T;
readonly currentValue: T;
readonly isFirstChange: boolean;
}
export type CallBackFunction<T> = (value: T, change: SimpleChange<T>) => void;
type TypedPropertyDecorator<C> = (target: C, key: PropertyKey) => void;
const CACHED_VALUE_KEY = Symbol();
const IS_FIRST_CHANGE_KEY = Symbol();
interface Instance<T> {
[CACHED_VALUE_KEY]: T;
[IS_FIRST_CHANGE_KEY]: boolean;
[key: string]: unknown;
}
export function OnChange<T>(callback: CallBackFunction<T> | string): PropertyDecorator;
// eslint-disable-next-line @typescript-eslint/ban-types
export function OnChange<T, C extends Object = Object>(callback: CallBackFunction<T> | FunctionKeys<C>): TypedPropertyDecorator<C>;
// eslint-disable-next-line @typescript-eslint/ban-types
export function OnChange<T, C extends Object = Object>(callback: CallBackFunction<T> | FunctionKeys<C>): TypedPropertyDecorator<C> {
return function _onChange(target: C, key: PropertyKey) {
Object.defineProperty(target, key, {
set(value: T) {
const instance = this as Instance<T>;
const callBackFn = <CallBackFunction<T>>(typeof callback === 'string' ? instance[callback] : callback);
if (!callBackFn) {
throw new Error(`Cannot find method ${String(callback)} in class ${target.constructor.name}`);
}
instance[IS_FIRST_CHANGE_KEY] = instance[IS_FIRST_CHANGE_KEY] === undefined;
// No operation if new value is same as old value
if (!instance[IS_FIRST_CHANGE_KEY] && instance[CACHED_VALUE_KEY] === value) {
return;
}
const oldValue = instance[CACHED_VALUE_KEY];
instance[CACHED_VALUE_KEY] = value;
const simpleChange: SimpleChange<T> = {
previousValue: oldValue,
currentValue: instance[CACHED_VALUE_KEY],
isFirstChange: instance[IS_FIRST_CHANGE_KEY]
};
callBackFn.call(instance, instance[CACHED_VALUE_KEY], simpleChange);
},
get(): T {
return (this as Instance<T>)[CACHED_VALUE_KEY];
}
});
};
}

View File

@ -8,3 +8,4 @@ export * from './types/tooltip-positions.type';
export * from './decorators/bind.decorator'; export * from './decorators/bind.decorator';
export * from './decorators/required.decorator'; export * from './decorators/required.decorator';
export * from './decorators/debounce.decorator'; export * from './decorators/debounce.decorator';
export * from './decorators/on-change.decorator';

View File

@ -0,0 +1,11 @@
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'log'
})
export class LogPipe implements PipeTransform {
transform<T>(value: T, message = ''): T {
console.log(message, value);
return value;
}
}