From 90287baf62e8a5cfbec742b6947352c56c255bdb Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Sun, 19 Sep 2021 23:42:46 +0300 Subject: [PATCH 1/7] add server error interceptor and update full page error --- src/assets/icons/offline.svg | 1 + src/assets/styles/_full-pages.scss | 9 ++-- .../full-page-error.component.html | 47 ++++++++++++----- .../full-page-error.component.scss | 37 +++++++++---- .../full-page-error.component.ts | 8 +++ src/lib/error/index.ts | 2 + src/lib/error/max-retries.token.ts | 3 ++ src/lib/error/server-error-interceptor.ts | 52 +++++++++++++++++++ src/lib/icons/icons.module.ts | 1 + src/lib/listing/sync-width.directive.ts | 2 +- .../utils/decorators/debounce.decorator.ts | 2 +- 11 files changed, 134 insertions(+), 30 deletions(-) create mode 100644 src/assets/icons/offline.svg create mode 100644 src/lib/error/max-retries.token.ts create mode 100644 src/lib/error/server-error-interceptor.ts diff --git a/src/assets/icons/offline.svg b/src/assets/icons/offline.svg new file mode 100644 index 0000000..d5c6610 --- /dev/null +++ b/src/assets/icons/offline.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/styles/_full-pages.scss b/src/assets/styles/_full-pages.scss index 9c9c6a4..90a85ae 100644 --- a/src/assets/styles/_full-pages.scss +++ b/src/assets/styles/_full-pages.scss @@ -1,17 +1,14 @@ -@import 'variables'; +@use 'variables'; .full-page-section, .full-page-content { position: fixed; - top: 0; - bottom: 0; - left: 0; - right: 0; + inset: 0; } .full-page-section { opacity: 0.7; - background: $white; + background: variables.$white; z-index: 900; } diff --git a/src/lib/error/full-page-error/full-page-error.component.html b/src/lib/error/full-page-error/full-page-error.component.html index e55ee4d..1ffdcf7 100644 --- a/src/lib/error/full-page-error/full-page-error.component.html +++ b/src/lib/error/full-page-error/full-page-error.component.html @@ -1,15 +1,36 @@ -
-
- -
-
{{ error.message }}
- -
+ +
+ + +
+ + +
+
+ + +
+ +
+ + +
+ +
{{ error.message }}
+ + +
+
diff --git a/src/lib/error/full-page-error/full-page-error.component.scss b/src/lib/error/full-page-error/full-page-error.component.scss index 8791f01..6638559 100644 --- a/src/lib/error/full-page-error/full-page-error.component.scss +++ b/src/lib/error/full-page-error/full-page-error.component.scss @@ -1,13 +1,32 @@ -@import '../../../assets/styles/variables'; +@use '../../../assets/styles/variables'; + +.offline-box { + position: fixed; + bottom: 20px; + right: 20px; + height: 40px; + width: 300px; + background: variables.$white; + border: 1px solid variables.$separator; + border-radius: 10px; + padding: 14px; + + > mat-icon { + opacity: 0.3; + } + + > iqser-circle-button { + flex-grow: 1; + justify-content: flex-end; + } +} .full-page-section { opacity: 0.95; } .full-page-content { - display: flex; flex-direction: column; - align-items: center; text-align: center; > mat-icon { @@ -16,12 +35,12 @@ opacity: 0.1; } - .heading-l { - color: $grey-7; - font-weight: initial; - } - .error { - color: $red-1; + color: variables.$primary; } } + +:is(.offline-box, .full-page-content) .heading-l { + color: #9398a0; + font-weight: initial; +} diff --git a/src/lib/error/full-page-error/full-page-error.component.ts b/src/lib/error/full-page-error/full-page-error.component.ts index dcc4fce..43862b9 100644 --- a/src/lib/error/full-page-error/full-page-error.component.ts +++ b/src/lib/error/full-page-error/full-page-error.component.ts @@ -1,11 +1,19 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { IconButtonTypes } from '../../buttons'; import { ErrorService } from '../error.service'; +import { animate, state, style, transition, trigger } from '@angular/animations'; @Component({ selector: 'iqser-full-page-error', templateUrl: './full-page-error.component.html', 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 }) export class FullPageErrorComponent { diff --git a/src/lib/error/index.ts b/src/lib/error/index.ts index 9a01d8a..42fbcd1 100644 --- a/src/lib/error/index.ts +++ b/src/lib/error/index.ts @@ -1,2 +1,4 @@ export * from './error.service'; +export * from './max-retries.token'; +export * from './server-error-interceptor'; export * from './full-page-error/full-page-error.component'; diff --git a/src/lib/error/max-retries.token.ts b/src/lib/error/max-retries.token.ts new file mode 100644 index 0000000..b2ad58a --- /dev/null +++ b/src/lib/error/max-retries.token.ts @@ -0,0 +1,3 @@ +import { InjectionToken } from '@angular/core'; + +export const MAX_RETRIES_ON_SERVER_ERROR = new InjectionToken('Number of retries before giving up'); diff --git a/src/lib/error/server-error-interceptor.ts b/src/lib/error/server-error-interceptor.ts new file mode 100644 index 0000000..913805d --- /dev/null +++ b/src/lib/error/server-error-interceptor.ts @@ -0,0 +1,52 @@ +import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http'; +import { Inject, Injectable, Optional } from '@angular/core'; +import { ErrorService } from '@iqser/common-ui'; +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'; + +function updateSeconds(seconds: number) { + if (seconds === 0 || seconds === 1) { + return seconds + 1; + } else { + return seconds * seconds; + } +} + +function backoffOnServerError(maxRetries = 3): MonoTypeOperatorFunction> { + 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, next: HttpHandler): Observable> { + 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) + ); + } +} diff --git a/src/lib/icons/icons.module.ts b/src/lib/icons/icons.module.ts index 8c39496..85445a7 100644 --- a/src/lib/icons/icons.module.ts +++ b/src/lib/icons/icons.module.ts @@ -18,6 +18,7 @@ export class IqserIconsModule { 'edit', 'failure', 'help-outline', + 'offline', 'refresh', 'search', 'sort-asc', diff --git a/src/lib/listing/sync-width.directive.ts b/src/lib/listing/sync-width.directive.ts index c7cde8c..d2f4693 100644 --- a/src/lib/listing/sync-width.directive.ts +++ b/src/lib/listing/sync-width.directive.ts @@ -8,7 +8,7 @@ export class SyncWidthDirective implements OnDestroy { private readonly _interval: number; constructor(private readonly _elementRef: ElementRef) { - this._interval = setInterval(() => { + this._interval = window.setInterval(() => { this._matchWidth(); }, 1000); } diff --git a/src/lib/utils/decorators/debounce.decorator.ts b/src/lib/utils/decorators/debounce.decorator.ts index 0da4c4d..8be812b 100644 --- a/src/lib/utils/decorators/debounce.decorator.ts +++ b/src/lib/utils/decorators/debounce.decorator.ts @@ -6,7 +6,7 @@ export function Debounce(delay = 300): MethodDecorator { descriptorCopy.value = function _new(...args: unknown[]) { 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; From dde0d7e7a5c082c4c6ecf1d11b10b8ff76fbdc7b Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Tue, 21 Sep 2021 01:24:54 +0300 Subject: [PATCH 2/7] wip --- .../{_mixins.scss => _common-mixins.scss} | 12 +++--- src/assets/styles/_common-variables.scss | 25 +++++++++++ src/assets/styles/_variables.scss | 10 ----- .../{_buttons.scss => common-buttons.scss} | 30 ++++++------- .../{_dialogs.scss => common-dialogs.scss} | 6 +-- ...full-pages.scss => common-full-pages.scss} | 4 +- .../{_inputs.scss => common-inputs.scss} | 43 ++++++++++--------- .../{_layout.scss => common-layout.scss} | 0 src/assets/styles/common-styles.scss | 7 +++ .../{_tables.scss => common-tables.scss} | 0 .../styles/{_texts.scss => common-texts.scss} | 18 ++++---- src/assets/styles/common.scss | 7 --- .../circle-button.component.scss | 10 ++--- .../icon-button/icon-button.component.scss | 6 +-- .../empty-state/empty-state.component.scss | 4 +- .../full-page-error.component.scss | 8 ++-- .../popup-filter/popup-filter.component.scss | 4 +- .../quick-filters.component.scss | 10 ++--- .../help-mode/help-mode.component.scss | 12 +++--- .../round-checkbox.component.scss | 10 ++--- .../scroll-button.component.scss | 6 +-- .../table-column-name.component.scss | 4 +- .../table-header/table-header.component.scss | 8 ++-- src/lib/listing/table/table.component.scss | 17 ++++---- 24 files changed, 124 insertions(+), 137 deletions(-) rename src/assets/styles/{_mixins.scss => _common-mixins.scss} (71%) create mode 100644 src/assets/styles/_common-variables.scss delete mode 100644 src/assets/styles/_variables.scss rename src/assets/styles/{_buttons.scss => common-buttons.scss} (70%) rename src/assets/styles/{_dialogs.scss => common-dialogs.scss} (83%) rename src/assets/styles/{_full-pages.scss => common-full-pages.scss} (85%) rename src/assets/styles/{_inputs.scss => common-inputs.scss} (82%) rename src/assets/styles/{_layout.scss => common-layout.scss} (100%) create mode 100644 src/assets/styles/common-styles.scss rename src/assets/styles/{_tables.scss => common-tables.scss} (100%) rename src/assets/styles/{_texts.scss => common-texts.scss} (84%) delete mode 100644 src/assets/styles/common.scss diff --git a/src/assets/styles/_mixins.scss b/src/assets/styles/_common-mixins.scss similarity index 71% rename from src/assets/styles/_mixins.scss rename to src/assets/styles/_common-mixins.scss index 7236bd7..92eb3c8 100644 --- a/src/assets/styles/_mixins.scss +++ b/src/assets/styles/_common-mixins.scss @@ -1,5 +1,3 @@ -@import 'variables'; - @mixin line-clamp($lines) { display: -webkit-box; -webkit-line-clamp: $lines; @@ -23,7 +21,7 @@ } @mixin scroll-bar { - scrollbar-color: $grey-5 $grey-2; + scrollbar-color: var(--iqser-quick-filter-border) var(--iqser-filter-bg); scrollbar-width: thin; &::-webkit-scrollbar { @@ -32,19 +30,19 @@ /* Track */ &::-webkit-scrollbar-track { - background: $grey-2; + background: var(--iqser-filter-bg); } /* Handle */ &::-webkit-scrollbar-thumb { - background: $grey-5; + background: var(--iqser-quick-filter-border); } } @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 { - box-shadow: 0 4px 3px 2px $grey-4; + box-shadow: 0 4px 3px 2px var(--iqser-btn-bg-hover); } diff --git a/src/assets/styles/_common-variables.scss b/src/assets/styles/_common-variables.scss new file mode 100644 index 0000000..f51ce18 --- /dev/null +++ b/src/assets/styles/_common-variables.scss @@ -0,0 +1,25 @@ +@use 'sass:meta'; + +:root { + --iqser-primary: lightblue; + --iqser-primary-2: orange; + --iqser-accent: blue; + --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-filter-bg: #f4f5f7; + --iqser-helpmode-primary: green; +} + +@mixin configure($args...) { + :root { + @each $name, $value in meta.keywords($args) { + --#{$name}: #{$value}; + } + } +} diff --git a/src/assets/styles/_variables.scss b/src/assets/styles/_variables.scss deleted file mode 100644 index cf11624..0000000 --- a/src/assets/styles/_variables.scss +++ /dev/null @@ -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; diff --git a/src/assets/styles/_buttons.scss b/src/assets/styles/common-buttons.scss similarity index 70% rename from src/assets/styles/_buttons.scss rename to src/assets/styles/common-buttons.scss index 4c81a84..f728b82 100644 --- a/src/assets/styles/_buttons.scss +++ b/src/assets/styles/common-buttons.scss @@ -1,5 +1,3 @@ -@import 'variables'; - .mat-button, .mat-flat-button { border-radius: 17px !important; @@ -36,19 +34,19 @@ padding: 0 14px; transition: background-color 0.2s, color 0.2s; - background-color: $primary; + background-color: var(--iqser-primary); &.mat-button-disabled { - background-color: $primary; + background-color: var(--iqser-primary); .mat-button-wrapper { - color: $white; + color: var(--iqser-white); opacity: 0.5; } } &:not(.mat-button-disabled):hover { - background-color: $primary-2; + background-color: var(--iqser-primary-2); } } @@ -64,30 +62,31 @@ iqser-circle-button { transition: background-color 0.2s; &.overlay { - background: rgba($primary, 0.1); + // TODO + background: rgba(var(--iqser-primary), 0.1); } &:not(.overlay):hover { - background-color: $btn-bg; + background-color: var(--iqser-btn-bg); } &.primary { font-weight: 500 !important; - background-color: $primary; - color: $white; + background-color: var(--iqser-primary); + color: var(--iqser-white); &:hover { - background-color: $primary-2; + background-color: var(--iqser-primary-2); } } &.dark-bg:hover { - background-color: $btn-bg-hover; + background-color: var(--iqser-btn-bg-hover); } } .dot { - background: $primary; + background: var(--iqser-primary); height: 10px; width: 10px; border-radius: 50%; @@ -102,10 +101,11 @@ iqser-circle-button, iqser-icon-button { &[aria-expanded='true'] { button { - background: rgba($primary, 0.1); + // TODO + background: rgba(var(--iqser-primary), 0.1); &.primary { - background: $primary-2; + background: var(--iqser-primary-2); } } } diff --git a/src/assets/styles/_dialogs.scss b/src/assets/styles/common-dialogs.scss similarity index 83% rename from src/assets/styles/_dialogs.scss rename to src/assets/styles/common-dialogs.scss index 5a68806..40751e1 100644 --- a/src/assets/styles/_dialogs.scss +++ b/src/assets/styles/common-dialogs.scss @@ -1,7 +1,5 @@ -@import 'apps/red-ui/src/assets/styles/variables'; - .mat-dialog-container { - color: $accent; + color: var(--iqser-accent); padding: 0 !important; border-radius: 8px !important; } @@ -27,7 +25,7 @@ .dialog-actions { height: 81px; box-sizing: border-box; - border-top: 1px solid $separator; + border-top: 1px solid var(--iqser-separator); padding: 0 32px; align-items: center; diff --git a/src/assets/styles/_full-pages.scss b/src/assets/styles/common-full-pages.scss similarity index 85% rename from src/assets/styles/_full-pages.scss rename to src/assets/styles/common-full-pages.scss index 90a85ae..3b37d46 100644 --- a/src/assets/styles/_full-pages.scss +++ b/src/assets/styles/common-full-pages.scss @@ -1,5 +1,3 @@ -@use 'variables'; - .full-page-section, .full-page-content { position: fixed; @@ -8,7 +6,7 @@ .full-page-section { opacity: 0.7; - background: variables.$white; + background: var(--iqser-white); z-index: 900; } diff --git a/src/assets/styles/_inputs.scss b/src/assets/styles/common-inputs.scss similarity index 82% rename from src/assets/styles/_inputs.scss rename to src/assets/styles/common-inputs.scss index 02ed6c8..edc524f 100644 --- a/src/assets/styles/_inputs.scss +++ b/src/assets/styles/common-inputs.scss @@ -1,5 +1,4 @@ -@import 'variables'; -@import 'mixins'; +@use 'common-mixins' as mixins; form .iqser-input-group:not(first-of-type) { margin-top: 14px; @@ -22,10 +21,10 @@ form .iqser-input-group:not(first-of-type) { position: absolute; right: 1px; bottom: 1px; - background: $quick-filter-border; + background: var(--iqser-quick-filter-border); height: 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-bottom-right-radius: 7px; cursor: pointer; @@ -35,13 +34,13 @@ form .iqser-input-group:not(first-of-type) { justify-content: center; &:hover { - background: $btn-bg; + background: var(--iqser-btn-bg); } mat-icon { width: 14px; height: 14px; - color: $accent; + color: var(--iqser-accent); } &.disabled { @@ -60,7 +59,7 @@ form .iqser-input-group:not(first-of-type) { .mat-form-field-label { opacity: 0.7 !important; - color: $accent !important; + color: var(--iqser-accent) !important; transform: translateY(-1.34em) !important; } @@ -83,9 +82,9 @@ form .iqser-input-group:not(first-of-type) { } .mat-button-toggle-checked { - background: $primary; + background: var(--iqser-primary); transition: background-color 0.25s ease; - color: $white; + color: var(--iqser-white); } input, @@ -94,7 +93,7 @@ form .iqser-input-group:not(first-of-type) { box-sizing: border-box; padding-left: 11px; padding-right: 11px; - border: 1px solid $quick-filter-border; + border: 1px solid var(--iqser-quick-filter-border); font-family: Inter, sans-serif; font-size: 13px; background-color: #ffffff; @@ -108,26 +107,28 @@ form .iqser-input-group:not(first-of-type) { } &:focus:not(:disabled):not(.mat-select-disabled) { - border-color: $accent; + border-color: var(--iqser-accent); } &::placeholder { - color: $accent; + color: var(--iqser-accent); opacity: 0.7; } &.ng-invalid.ng-touched { - border-color: rgba($primary, 0.3); + // TODO + border-color: rgba(var(--iqser-primary, 0.3)); &:focus { - border-color: $primary; + border-color: var(--iqser-primary); } } &:disabled, &.mat-select-disabled { - background-color: $filter-bg; - color: rgba($accent, 0.3); + background-color: var(--iqser-filter-bg); + // TODO + color: rgba(var(--iqser-accent, 0.3)); } } @@ -154,7 +155,7 @@ form .iqser-input-group:not(first-of-type) { resize: vertical; padding-top: 7px; padding-bottom: 7px; - @include scroll-bar; + @include mixins.scroll-bar; &.has-scrollbar { border-top-right-radius: 0; @@ -168,7 +169,7 @@ form .iqser-input-group:not(first-of-type) { letter-spacing: 0; line-height: 14px; margin-bottom: 2px; - color: $accent; + color: var(--iqser-accent); &.mat-checkbox-layout { opacity: 1; @@ -178,7 +179,7 @@ form .iqser-input-group:not(first-of-type) { &.required label:after { content: ' *'; - color: $primary; + color: var(--iqser-primary); } &.datepicker-wrapper { @@ -196,10 +197,10 @@ form .iqser-input-group:not(first-of-type) { position: absolute; right: 0; top: 1px; - color: $accent; + color: var(--iqser-accent); &.mat-datepicker-toggle-active { - color: $primary; + color: var(--iqser-primary); } .mat-icon-button { diff --git a/src/assets/styles/_layout.scss b/src/assets/styles/common-layout.scss similarity index 100% rename from src/assets/styles/_layout.scss rename to src/assets/styles/common-layout.scss diff --git a/src/assets/styles/common-styles.scss b/src/assets/styles/common-styles.scss new file mode 100644 index 0000000..43d57f2 --- /dev/null +++ b/src/assets/styles/common-styles.scss @@ -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'; diff --git a/src/assets/styles/_tables.scss b/src/assets/styles/common-tables.scss similarity index 100% rename from src/assets/styles/_tables.scss rename to src/assets/styles/common-tables.scss diff --git a/src/assets/styles/_texts.scss b/src/assets/styles/common-texts.scss similarity index 84% rename from src/assets/styles/_texts.scss rename to src/assets/styles/common-texts.scss index 82cf526..dc6a794 100644 --- a/src/assets/styles/_texts.scss +++ b/src/assets/styles/common-texts.scss @@ -1,5 +1,4 @@ -@import 'variables'; -@import 'mixins'; +@use 'common-mixins' as mixins; .all-caps-label { text-transform: uppercase; @@ -24,17 +23,18 @@ } &.primary { - color: $primary; + color: var(--iqser-primary); opacity: 1; } } a { - color: $primary; + color: var(--iqser-primary); transition: color 0.1s; &:hover { - color: lighten($primary, 10%); + color: var(--iqser-primary); + filter: brightness(140%); } &.with-underline { @@ -48,7 +48,7 @@ a { pre { font-family: Inter, sans-serif; - color: $accent; + color: var(--iqser-accent); } .heading-xl { @@ -102,17 +102,17 @@ pre { } .large-label { - color: $accent; + color: var(--iqser-accent); font-size: 13px; line-height: 16px; } .clamp-1 { - @include line-clamp(1); + @include mixins.line-clamp(1); } .clamp-2 { - @include line-clamp(2); + @include mixins.line-clamp(2); } .text-overflow { diff --git a/src/assets/styles/common.scss b/src/assets/styles/common.scss deleted file mode 100644 index 4e56f5a..0000000 --- a/src/assets/styles/common.scss +++ /dev/null @@ -1,7 +0,0 @@ -@import 'inputs'; -@import 'buttons'; -@import 'texts'; -@import 'tables'; -@import 'full-pages'; -@import 'layout'; -@import 'dialogs'; diff --git a/src/lib/buttons/circle-button/circle-button.component.scss b/src/lib/buttons/circle-button/circle-button.component.scss index 0e44ca4..94bf11c 100644 --- a/src/lib/buttons/circle-button/circle-button.component.scss +++ b/src/lib/buttons/circle-button/circle-button.component.scss @@ -1,5 +1,3 @@ -@import '../../../assets/styles/common'; - :host { height: var(--size); width: var(--size); @@ -26,15 +24,15 @@ } &.primary.mat-button-disabled { - background-color: $btn-bg; - color: $white !important; + background-color: var(--iqser-btn-bg); + color: var(--iqser-white) !important; } &.warn:not([disabled]) { - background-color: $warn; + background-color: var(--iqser-warn); &:hover { - background-color: $warn; + background-color: var(--iqser-warn); } } } diff --git a/src/lib/buttons/icon-button/icon-button.component.scss b/src/lib/buttons/icon-button/icon-button.component.scss index 787b413..056d65d 100644 --- a/src/lib/buttons/icon-button/icon-button.component.scss +++ b/src/lib/buttons/icon-button/icon-button.component.scss @@ -1,5 +1,3 @@ -@import '../../../assets/styles/common'; - button { padding: 0 14px; width: 100%; @@ -9,10 +7,10 @@ button { } &.show-bg { - background-color: $btn-bg; + background-color: var(--iqser-btn-bg); &:not(.mat-button-disabled):hover { - background-color: $btn-bg-hover; + background-color: var(--iqser-btn-bg-hover); } } diff --git a/src/lib/empty-states/empty-state/empty-state.component.scss b/src/lib/empty-states/empty-state/empty-state.component.scss index 042e4c1..09d4f96 100644 --- a/src/lib/empty-states/empty-state/empty-state.component.scss +++ b/src/lib/empty-states/empty-state/empty-state.component.scss @@ -1,5 +1,3 @@ -@import '../../../assets/styles/common'; - .empty-state { display: flex; flex-direction: column; @@ -13,7 +11,7 @@ } .heading-l { - color: $grey-7; + color: var(--iqser-disabled); } > .heading-l, diff --git a/src/lib/error/full-page-error/full-page-error.component.scss b/src/lib/error/full-page-error/full-page-error.component.scss index 6638559..d1f3986 100644 --- a/src/lib/error/full-page-error/full-page-error.component.scss +++ b/src/lib/error/full-page-error/full-page-error.component.scss @@ -1,13 +1,11 @@ -@use '../../../assets/styles/variables'; - .offline-box { position: fixed; bottom: 20px; right: 20px; height: 40px; width: 300px; - background: variables.$white; - border: 1px solid variables.$separator; + background: var(--iqser-white); + border: 1px solid var(--iqser-separator); border-radius: 10px; padding: 14px; @@ -36,7 +34,7 @@ } .error { - color: variables.$primary; + color: var(--iqser-primary); } } diff --git a/src/lib/filtering/popup-filter/popup-filter.component.scss b/src/lib/filtering/popup-filter/popup-filter.component.scss index d569c5d..c20e277 100644 --- a/src/lib/filtering/popup-filter/popup-filter.component.scss +++ b/src/lib/filtering/popup-filter/popup-filter.component.scss @@ -1,5 +1,3 @@ -@import '../../../assets/styles/variables'; - .filter-menu-options, .filter-menu-header { display: flex; @@ -27,7 +25,7 @@ } .filter-options { - background-color: $filter-bg; + background-color: var(--iqser-filter-bg); padding-bottom: 8px; } diff --git a/src/lib/filtering/quick-filters/quick-filters.component.scss b/src/lib/filtering/quick-filters/quick-filters.component.scss index 13ef48d..0974792 100644 --- a/src/lib/filtering/quick-filters/quick-filters.component.scss +++ b/src/lib/filtering/quick-filters/quick-filters.component.scss @@ -1,5 +1,3 @@ -@import '../../../assets/styles/variables'; - :host { display: flex; flex: 1; @@ -8,9 +6,9 @@ .quick-filter { box-sizing: border-box; - border: 1px solid $quick-filter-border; + border: 1px solid var(--iqser-quick-filter-border); border-radius: 17px; - background-color: $btn-bg; + background-color: var(--iqser-btn-bg); padding: 0 14px; height: 34px; display: flex; @@ -19,11 +17,11 @@ transition: background-color 0.2s; &:hover { - background-color: $white; + background-color: var(--iqser-white); } &.active { - background-color: $white; + background-color: var(--iqser-white); font-weight: 600; border: none; } diff --git a/src/lib/help-mode/help-mode/help-mode.component.scss b/src/lib/help-mode/help-mode/help-mode.component.scss index fb99479..e848a0c 100644 --- a/src/lib/help-mode/help-mode/help-mode.component.scss +++ b/src/lib/help-mode/help-mode/help-mode.component.scss @@ -1,5 +1,3 @@ -@import '../../../../../../apps/red-ui/src/assets/styles/variables'; - .help-button { width: 44px; height: 40px; @@ -7,7 +5,7 @@ bottom: 20px; right: 0; z-index: 1; - background: $green-2; + background: var(--iqser-helpmode-primary); border-top-left-radius: 8px; border-bottom-left-radius: 8px; box-shadow: -1px 1px 5px 0 rgba(40, 50, 65, 0.25); @@ -40,10 +38,10 @@ box-sizing: border-box; height: 100%; width: 100%; - border-left: 8px solid $green-2; - border-right: 8px solid $green-2; - border-top: 8px solid $green-2; - border-bottom: 60px solid $green-2; + border-left: 8px solid var(--iqser-helpmode-primary); + border-right: 8px solid var(--iqser-helpmode-primary); + border-top: 8px solid var(--iqser-helpmode-primary); + border-bottom: 60px solid var(--iqser-helpmode-primary); z-index: 10; position: absolute; display: flex; diff --git a/src/lib/inputs/round-checkbox/round-checkbox.component.scss b/src/lib/inputs/round-checkbox/round-checkbox.component.scss index 99c3e9b..97f77f2 100644 --- a/src/lib/inputs/round-checkbox/round-checkbox.component.scss +++ b/src/lib/inputs/round-checkbox/round-checkbox.component.scss @@ -1,5 +1,3 @@ -@import '../../../assets/styles/common'; - :host { cursor: pointer; } @@ -12,22 +10,22 @@ &.inactive { border: 1px solid #d3d5da; - background-color: $white; + background-color: var(--iqser-white); } .mat-icon { - color: $primary; + color: var(--iqser-primary); width: var(--size); height: var(--size); } &.with-bg { .mat-icon { - color: $white; + color: var(--iqser-white); } &.inactive { - border: 1px solid $btn-bg; + border: 1px solid var(--iqser-btn-bg); background-color: transparent; } } diff --git a/src/lib/listing/scroll-button/scroll-button.component.scss b/src/lib/listing/scroll-button/scroll-button.component.scss index 093aaa9..ee4a294 100644 --- a/src/lib/listing/scroll-button/scroll-button.component.scss +++ b/src/lib/listing/scroll-button/scroll-button.component.scss @@ -1,7 +1,5 @@ -@import '../../../assets/styles/common'; - .scroll-button { - background-color: $white; + background-color: var(--iqser-white); position: absolute; right: 0; height: 40px; @@ -26,5 +24,5 @@ mat-icon { width: 22px; height: 22px; - color: $grey-7; + color: var(--iqser-disabled); } diff --git a/src/lib/listing/table-column-name/table-column-name.component.scss b/src/lib/listing/table-column-name/table-column-name.component.scss index 731b910..7fb6e0d 100644 --- a/src/lib/listing/table-column-name/table-column-name.component.scss +++ b/src/lib/listing/table-column-name/table-column-name.component.scss @@ -1,5 +1,3 @@ -@import '../../../assets/styles/common'; - :host { display: flex; height: 30px; @@ -38,7 +36,7 @@ .sort-arrows-container { display: none; - color: $primary; + color: var(--iqser-primary); margin-left: 8px; mat-icon { diff --git a/src/lib/listing/table-header/table-header.component.scss b/src/lib/listing/table-header/table-header.component.scss index 106f35c..1a0c691 100644 --- a/src/lib/listing/table-header/table-header.component.scss +++ b/src/lib/listing/table-header/table-header.component.scss @@ -1,8 +1,6 @@ -@import '../../../assets/styles/common'; - .table-header { display: flex; - border-bottom: 1px solid $separator; + border-bottom: 1px solid var(--iqser-separator); &.no-data.selection-enabled:not([synced='true']) { padding-left: 30px; @@ -10,12 +8,12 @@ } .header-item { - background-color: $btn-bg; + background-color: var(--iqser-btn-bg); height: 50px; display: flex; align-items: center; z-index: 1; - border-bottom: 1px solid $separator; + border-bottom: 1px solid var(--iqser-separator); box-sizing: border-box; padding: 0 24px; diff --git a/src/lib/listing/table/table.component.scss b/src/lib/listing/table/table.component.scss index ee2c2ce..dc36f05 100644 --- a/src/lib/listing/table/table.component.scss +++ b/src/lib/listing/table/table.component.scss @@ -1,4 +1,4 @@ -@import '../../../assets/styles/common'; +@use '../../../assets/styles/common-mixins' as mixins; :host cdk-virtual-scroll-viewport { height: calc(100vh - 50px - 31px - 111px); @@ -25,7 +25,7 @@ justify-content: center; position: relative; box-sizing: border-box; - border-bottom: 1px solid $separator; + border-bottom: 1px solid var(--iqser-separator); height: var(--itemSize); padding: 0 10px; @@ -61,18 +61,17 @@ } &.disabled > div { - background-color: $grey-2; - color: $grey-7; + background-color: var(--iqser-filter-bg); + color: var(--iqser-disabled); .action-buttons { color: initial; } } - .table-item-title { font-weight: 600; - @include line-clamp(1); + @include mixins.line-clamp(1); } .action-buttons { @@ -87,7 +86,7 @@ padding-left: 100px; padding-right: 21px; 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-filter-bg) 35%); mat-icon { width: 14px; @@ -114,14 +113,14 @@ } &:hover:not(.disabled) > div { - background-color: $grey-8; + background-color: var(--iqser-not-disabled-table-item); } } } &:hover { overflow-y: auto !important; - @include scroll-bar; + @include mixins.scroll-bar; &.has-scrollbar { .table-item { From a16d1db3ab938dccc2c9d82c5e3d283bb857f46c Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Tue, 21 Sep 2021 17:17:41 +0300 Subject: [PATCH 3/7] fix rgba variables --- src/assets/styles/_common-functions.scss | 3 +++ src/assets/styles/_common-variables.scss | 2 ++ src/assets/styles/common-buttons.scss | 6 ++---- src/assets/styles/common-inputs.scss | 6 ++---- 4 files changed, 9 insertions(+), 8 deletions(-) create mode 100644 src/assets/styles/_common-functions.scss diff --git a/src/assets/styles/_common-functions.scss b/src/assets/styles/_common-functions.scss new file mode 100644 index 0000000..efdd0c0 --- /dev/null +++ b/src/assets/styles/_common-functions.scss @@ -0,0 +1,3 @@ +@function hexToRgb($color) { + @return #{red($color) + ', ' + green($color) + ', ' + blue($color)}; +} diff --git a/src/assets/styles/_common-variables.scss b/src/assets/styles/_common-variables.scss index f51ce18..9ea478f 100644 --- a/src/assets/styles/_common-variables.scss +++ b/src/assets/styles/_common-variables.scss @@ -2,8 +2,10 @@ :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; diff --git a/src/assets/styles/common-buttons.scss b/src/assets/styles/common-buttons.scss index f728b82..2624669 100644 --- a/src/assets/styles/common-buttons.scss +++ b/src/assets/styles/common-buttons.scss @@ -62,8 +62,7 @@ iqser-circle-button { transition: background-color 0.2s; &.overlay { - // TODO - background: rgba(var(--iqser-primary), 0.1); + background: rgba(var(--iqser-primary-rgb), 0.1); } &:not(.overlay):hover { @@ -101,8 +100,7 @@ iqser-circle-button, iqser-icon-button { &[aria-expanded='true'] { button { - // TODO - background: rgba(var(--iqser-primary), 0.1); + background: rgba(var(--iqser-primary-rgb), 0.1); &.primary { background: var(--iqser-primary-2); diff --git a/src/assets/styles/common-inputs.scss b/src/assets/styles/common-inputs.scss index edc524f..f67dc0f 100644 --- a/src/assets/styles/common-inputs.scss +++ b/src/assets/styles/common-inputs.scss @@ -116,8 +116,7 @@ form .iqser-input-group:not(first-of-type) { } &.ng-invalid.ng-touched { - // TODO - border-color: rgba(var(--iqser-primary, 0.3)); + border-color: rgba(var(--iqser-primary-rgb), 0.3); &:focus { border-color: var(--iqser-primary); @@ -127,8 +126,7 @@ form .iqser-input-group:not(first-of-type) { &:disabled, &.mat-select-disabled { background-color: var(--iqser-filter-bg); - // TODO - color: rgba(var(--iqser-accent, 0.3)); + color: rgba(var(--iqser-accent-rgb), 0.3); } } From 4f0d8022eddb1fa3306a653883ac5b0972f75d13 Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Thu, 23 Sep 2021 18:49:52 +0300 Subject: [PATCH 4/7] fix server error interceptor and add log pipe --- src/lib/common-ui.module.ts | 5 +++-- src/lib/error/server-error-interceptor.ts | 2 +- src/lib/utils/pipes/log.pipe.ts | 11 +++++++++++ 3 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 src/lib/utils/pipes/log.pipe.ts diff --git a/src/lib/common-ui.module.ts b/src/lib/common-ui.module.ts index d255dc3..6222977 100644 --- a/src/lib/common-ui.module.ts +++ b/src/lib/common-ui.module.ts @@ -16,6 +16,7 @@ import { IqserIconsModule } from './icons'; import { IqserButtonsModule } from './buttons'; import { IqserScrollbarModule } from './scrollbar'; import { IqserEmptyStatesModule } from './empty-states'; +import { LogPipe } from './utils/pipes/log.pipe'; const matModules = [MatIconModule, MatProgressSpinnerModule]; const modules = [ @@ -33,8 +34,8 @@ const components = [StatusBarComponent, FullPageLoadingIndicatorComponent, FullP const pipes = [SortByPipe, HumanizePipe]; @NgModule({ - declarations: [...components, ...pipes], + declarations: [...components, ...pipes, LogPipe], imports: [CommonModule, ...matModules, ...modules], - exports: [...components, ...pipes, ...modules] + exports: [...components, ...pipes, ...modules, LogPipe] }) export class CommonUiModule {} diff --git a/src/lib/error/server-error-interceptor.ts b/src/lib/error/server-error-interceptor.ts index 913805d..08d386f 100644 --- a/src/lib/error/server-error-interceptor.ts +++ b/src/lib/error/server-error-interceptor.ts @@ -19,7 +19,7 @@ function backoffOnServerError(maxRetries = 3): MonoTypeOperatorFunction (seconds = updateSeconds(seconds))), mergeMap((error: HttpErrorResponse, index) => { - if ((error.status <= 500 && error.status !== 0) || index === maxRetries) { + if ((error.status < 500 && error.status !== 0) || index === maxRetries) { return throwError(error); } else { console.error('An error occurred: ', error); diff --git a/src/lib/utils/pipes/log.pipe.ts b/src/lib/utils/pipes/log.pipe.ts new file mode 100644 index 0000000..d8dc377 --- /dev/null +++ b/src/lib/utils/pipes/log.pipe.ts @@ -0,0 +1,11 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'log' +}) +export class LogPipe implements PipeTransform { + transform(value: T, message = ''): T { + console.log(message, value); + return value; + } +} From b55e3bf0ddbda8e8459cb0ae23c8abd5fff46a18 Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Fri, 24 Sep 2021 19:30:44 +0300 Subject: [PATCH 5/7] add on change decorator --- .../utils/decorators/on-change.decorator.ts | 60 +++++++++++++++++++ src/lib/utils/index.ts | 1 + 2 files changed, 61 insertions(+) create mode 100644 src/lib/utils/decorators/on-change.decorator.ts diff --git a/src/lib/utils/decorators/on-change.decorator.ts b/src/lib/utils/decorators/on-change.decorator.ts new file mode 100644 index 0000000..b083e4d --- /dev/null +++ b/src/lib/utils/decorators/on-change.decorator.ts @@ -0,0 +1,60 @@ +import { FunctionKeys } from '../types/utility-types'; + +export interface SimpleChange { + readonly previousValue: T; + readonly currentValue: T; + readonly isFirstChange: boolean; +} + +export type CallBackFunction = (value: T, change: SimpleChange) => void; + +type TypedPropertyDecorator = (target: C, key: PropertyKey) => void; + +const CACHED_VALUE_KEY = Symbol(); +const IS_FIRST_CHANGE_KEY = Symbol(); + +interface Instance { + [CACHED_VALUE_KEY]: T; + [IS_FIRST_CHANGE_KEY]: boolean; + + [key: string]: unknown; +} + +export function OnChange(callback: CallBackFunction | string): PropertyDecorator; +// eslint-disable-next-line @typescript-eslint/ban-types +export function OnChange(callback: CallBackFunction | FunctionKeys): TypedPropertyDecorator; +// eslint-disable-next-line @typescript-eslint/ban-types +export function OnChange(callback: CallBackFunction | FunctionKeys): TypedPropertyDecorator { + return function _onChange(target: C, key: PropertyKey) { + Object.defineProperty(target, key, { + set(value: T) { + const instance = this as Instance; + const callBackFn = >(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 = { + 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)[CACHED_VALUE_KEY]; + } + }); + }; +} diff --git a/src/lib/utils/index.ts b/src/lib/utils/index.ts index e601a91..f721eff 100644 --- a/src/lib/utils/index.ts +++ b/src/lib/utils/index.ts @@ -8,3 +8,4 @@ export * from './types/tooltip-positions.type'; export * from './decorators/bind.decorator'; export * from './decorators/required.decorator'; export * from './decorators/debounce.decorator'; +export * from './decorators/on-change.decorator'; From 666cb3158ca4599e4832fe5badafdab65f7bde48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Mon, 27 Sep 2021 21:37:12 +0300 Subject: [PATCH 6/7] Fixed scroll buttons --- .../scroll-button/scroll-button.component.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/lib/listing/scroll-button/scroll-button.component.ts b/src/lib/listing/scroll-button/scroll-button.component.ts index 09ad966..c6da626 100644 --- a/src/lib/listing/scroll-button/scroll-button.component.ts +++ b/src/lib/listing/scroll-button/scroll-button.component.ts @@ -1,7 +1,7 @@ import { ChangeDetectionStrategy, Component, HostListener, Input, OnInit } from '@angular/core'; import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling'; -import { concatMap, delay, distinctUntilChanged, map, startWith } from 'rxjs/operators'; -import { Observable, of } from 'rxjs'; +import { delay, distinctUntilChanged, map, startWith } from 'rxjs/operators'; +import { combineLatest, fromEvent, Observable } from 'rxjs'; import { Required } from '../../utils'; const ButtonTypes = { @@ -34,11 +34,14 @@ export class ScrollButtonComponent implements OnInit { const showScrollUp = () => scrollIsNeeded() && !reachedEnd(ButtonTypes.top); const showScrollDown = () => scrollIsNeeded() && !reachedEnd(ButtonTypes.bottom); - const scroll$ = this.scrollViewport.elementScrolled().pipe( - startWith(''), - /** Delay first value so that we can wait for items to be rendered in viewport and get correct values */ - concatMap((value, index) => (index === 0 ? of(value).pipe(delay(0)) : of(value))) - ); + /** Force an initial emit, so combineLatest works */ + const scrolled$ = this.scrollViewport.elementScrolled().pipe(startWith(null)); + const resized$ = fromEvent(window, 'resize').pipe(startWith(null)); + 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.showScrollDown$ = scroll$.pipe(map(showScrollDown), distinctUntilChanged()); } From ea82e4351f217045e064ff10ee73ebbd190f330d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adina=20=C8=9Aeudan?= Date: Mon, 27 Sep 2021 21:40:08 +0300 Subject: [PATCH 7/7] Fixed import --- src/lib/error/server-error-interceptor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/error/server-error-interceptor.ts b/src/lib/error/server-error-interceptor.ts index 08d386f..2124e5f 100644 --- a/src/lib/error/server-error-interceptor.ts +++ b/src/lib/error/server-error-interceptor.ts @@ -1,9 +1,9 @@ import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http'; import { Inject, Injectable, Optional } from '@angular/core'; -import { ErrorService } from '@iqser/common-ui'; 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) {