From feebed2c567898cd2d125d0c839ecea239cd02d5 Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Thu, 19 Aug 2021 17:24:03 +0300 Subject: [PATCH 01/11] add popup filters --- src/assets/styles/_variables.scss | 1 + src/index.ts | 2 + src/lib/common-ui.module.ts | 15 ++- src/lib/filtering/filter.service.ts | 3 +- .../popup-filter/popup-filter.component.html | 109 +++++++++++++++++ .../popup-filter/popup-filter.component.scss | 41 +++++++ .../popup-filter/popup-filter.component.ts | 111 ++++++++++++++++++ .../quick-filters.component.scss | 2 +- src/lib/utils/operators.ts | 10 ++ 9 files changed, 290 insertions(+), 4 deletions(-) create mode 100644 src/lib/filtering/popup-filter/popup-filter.component.html create mode 100644 src/lib/filtering/popup-filter/popup-filter.component.scss create mode 100644 src/lib/filtering/popup-filter/popup-filter.component.ts create mode 100644 src/lib/utils/operators.ts diff --git a/src/assets/styles/_variables.scss b/src/assets/styles/_variables.scss index 96c6bcb..073ea5d 100644 --- a/src/assets/styles/_variables.scss +++ b/src/assets/styles/_variables.scss @@ -7,3 +7,4 @@ $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/index.ts b/src/index.ts index 1ceca69..c9c5448 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,7 @@ export * from './lib/common-ui.module'; export * from './lib/buttons/icon-button/icon-button.type'; export * from './lib/buttons/icon-button/icon-button.component'; export * from './lib/utils/functions'; +export * from './lib/utils/operators'; export * from './lib/utils/auto-unsubscribe.directive'; export * from './lib/utils/pipes/humanize.pipe'; export * from './lib/utils/types/utility-types'; @@ -16,6 +17,7 @@ export * from './lib/filtering/filter.service'; export * from './lib/filtering/models/filter.model'; export * from './lib/filtering/models/filter-group.model'; export * from './lib/filtering/models/nested-filter.model'; +export * from './lib/filtering/popup-filter/popup-filter.component'; export * from './lib/filtering/quick-filters/quick-filters.component'; export * from './lib/sorting/sort-by.pipe'; export * from './lib/sorting/sorting.service'; diff --git a/src/lib/common-ui.module.ts b/src/lib/common-ui.module.ts index a0d40b8..f74b5c3 100644 --- a/src/lib/common-ui.module.ts +++ b/src/lib/common-ui.module.ts @@ -6,6 +6,8 @@ import { DomSanitizer } from '@angular/platform-browser'; import { MatTooltipModule } from '@angular/material/tooltip'; import { TranslateModule } from '@ngx-translate/core'; import { FormsModule } from '@angular/forms'; +import { MatMenuModule } from '@angular/material/menu'; +import { MatCheckboxModule } from '@angular/material/checkbox'; import { IconButtonComponent } from './buttons/icon-button/icon-button.component'; import { ChevronButtonComponent } from './buttons/chevron-button/chevron-button.component'; import { CircleButtonComponent } from './buttons/circle-button/circle-button.component'; @@ -18,16 +20,25 @@ import { TableHeaderComponent } from './tables/table-header/table-header.compone import { SyncWidthDirective } from './tables/sync-width.directive'; import { StatusBarComponent } from './misc/status-bar/status-bar.component'; import { EditableInputComponent } from './inputs/editable-input/editable-input.component'; +import { PopupFilterComponent } from './filtering/popup-filter/popup-filter.component'; const buttons = [IconButtonComponent, ChevronButtonComponent, CircleButtonComponent]; const inputs = [RoundCheckboxComponent, EditableInputComponent]; -const matModules = [MatIconModule, MatButtonModule, MatTooltipModule]; +const matModules = [MatIconModule, MatButtonModule, MatTooltipModule, MatMenuModule, MatCheckboxModule]; const modules = [...matModules, FormsModule, TranslateModule]; -const components = [...buttons, ...inputs, TableColumnNameComponent, QuickFiltersComponent, TableHeaderComponent, StatusBarComponent]; +const components = [ + ...buttons, + ...inputs, + TableColumnNameComponent, + QuickFiltersComponent, + PopupFilterComponent, + TableHeaderComponent, + StatusBarComponent +]; const utils = [SortByPipe, HumanizePipe, SyncWidthDirective]; diff --git a/src/lib/filtering/filter.service.ts b/src/lib/filtering/filter.service.ts index 60de9d2..e8bdd48 100644 --- a/src/lib/filtering/filter.service.ts +++ b/src/lib/filtering/filter.service.ts @@ -4,6 +4,7 @@ import { distinctUntilChanged, map, startWith, switchMap } from 'rxjs/operators' import { processFilters, toFlatFilters } from './filter-utils'; import { FilterGroup } from './models/filter-group.model'; import { NestedFilter } from './models/nested-filter.model'; +import { get } from '../utils/operators'; @Injectable() export class FilterService { @@ -61,7 +62,7 @@ export class FilterService { } getGroup$(slug: string): Observable { - return this.filterGroups$.pipe(map(all => all.find(f => f.slug === slug))); + return this.filterGroups$.pipe(get(group => group.slug === slug)); } reset(): void { diff --git a/src/lib/filtering/popup-filter/popup-filter.component.html b/src/lib/filtering/popup-filter/popup-filter.component.html new file mode 100644 index 0000000..335429b --- /dev/null +++ b/src/lib/filtering/popup-filter/popup-filter.component.html @@ -0,0 +1,109 @@ + + + + + + + +
+
+
+
+
+
+
+ +
+ +
+ +
+
+
+
+ +
+ +
+
+
+
+ + + {{ filter?.label }} + + + +
+
+ + +
+
 
+ + + + +
+ +
+
+ + + + + +
+
+
+
diff --git a/src/lib/filtering/popup-filter/popup-filter.component.scss b/src/lib/filtering/popup-filter/popup-filter.component.scss new file mode 100644 index 0000000..1a48d2f --- /dev/null +++ b/src/lib/filtering/popup-filter/popup-filter.component.scss @@ -0,0 +1,41 @@ +@import '../../../assets/styles/variables'; + +.filter-menu-options, +.filter-menu-header { + display: flex; + justify-content: space-between; + padding: 8px 16px 16px 16px; + width: 350px; + + .actions { + display: flex; + + > *:not(:last-child) { + margin-right: 8px; + } + } +} + +.filter-content { + max-height: 570px; + overflow: auto; +} + +.filter-menu-options { + margin-top: 8px; + padding: 16px 16px 3px; +} + +.filter-options { + background-color: $filter-bg; + padding-bottom: 8px; +} + +::ng-deep .filter-menu-checkbox { + width: 100%; + + label { + width: 100%; + height: 100%; + } +} diff --git a/src/lib/filtering/popup-filter/popup-filter.component.ts b/src/lib/filtering/popup-filter/popup-filter.component.ts new file mode 100644 index 0000000..f504f4b --- /dev/null +++ b/src/lib/filtering/popup-filter/popup-filter.component.ts @@ -0,0 +1,111 @@ +import { ChangeDetectionStrategy, Component, Input, OnInit, TemplateRef } from '@angular/core'; +import { FilterGroup, FilterService, handleCheckedValue, NestedFilter } from '@iqser/common-ui'; +import { MAT_CHECKBOX_DEFAULT_OPTIONS } from '@angular/material/checkbox'; +import { BehaviorSubject, combineLatest, Observable, pipe } from 'rxjs'; +import { delay, distinctUntilChanged, map, shareReplay } from 'rxjs/operators'; +import { any } from '../../utils/operators'; + +const areExpandable = (nestedFilter: NestedFilter) => !!nestedFilter?.children?.length; +const atLeastOneIsExpandable = pipe( + map(group => !!group?.filters.some(areExpandable)), + distinctUntilChanged(), + shareReplay() +); + +@Component({ + selector: 'iqser-popup-filter [primaryFiltersSlug]', + templateUrl: './popup-filter.component.html', + styleUrls: ['./popup-filter.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [ + { + provide: MAT_CHECKBOX_DEFAULT_OPTIONS, + useValue: { + clickAction: 'noop', + color: 'primary' + } + } + ] +}) +export class PopupFilterComponent implements OnInit { + @Input() primaryFiltersSlug!: string; + @Input() actionsTemplate?: TemplateRef; + @Input() secondaryFiltersSlug = ''; + + atLeastOneFilterIsExpandable$?: Observable; + atLeastOneSecondaryFilterIsExpandable$?: Observable; + hasActiveFilters$?: Observable; + readonly expanded = new BehaviorSubject(false); + readonly expanded$ = this.expanded.asObservable().pipe(delay(200)); + + primaryFilterGroup$!: Observable; + secondaryFilterGroup$!: Observable; + + constructor(readonly filterService: FilterService) {} + + ngOnInit(): void { + this.primaryFilterGroup$ = this.filterService.getGroup$(this.primaryFiltersSlug); + this.secondaryFilterGroup$ = this.filterService.getGroup$(this.secondaryFiltersSlug); + + this.hasActiveFilters$ = this._hasActiveFilters$; + this.atLeastOneFilterIsExpandable$ = atLeastOneIsExpandable(this.primaryFilterGroup$); + this.atLeastOneSecondaryFilterIsExpandable$ = atLeastOneIsExpandable(this.secondaryFilterGroup$); + } + + filterCheckboxClicked($event: MouseEvent, nestedFilter: NestedFilter, parent?: NestedFilter): void { + $event.stopPropagation(); + + // eslint-disable-next-line no-param-reassign + nestedFilter.checked = !nestedFilter.checked; + + if (parent) { + handleCheckedValue(parent); + } else { + // eslint-disable-next-line no-param-reassign + if (nestedFilter.indeterminate) nestedFilter.checked = false; + // eslint-disable-next-line no-param-reassign + nestedFilter.indeterminate = false; + // eslint-disable-next-line no-return-assign,no-param-reassign + nestedFilter.children?.forEach(f => (f.checked = nestedFilter.checked)); + } + + this.filterService.refresh(); + } + + activatePrimaryFilters(): void { + this._setFilters(this.primaryFiltersSlug, true); + } + + deactivateFilters(): void { + this._setFilters(this.primaryFiltersSlug); + if (this.secondaryFiltersSlug) this._setFilters(this.secondaryFiltersSlug); + } + + toggleFilterExpanded($event: MouseEvent, nestedFilter: NestedFilter): void { + $event.stopPropagation(); + // eslint-disable-next-line no-param-reassign + nestedFilter.expanded = !nestedFilter.expanded; + this.filterService.refresh(); + } + + private get _hasActiveFilters$() { + return combineLatest([this.primaryFilterGroup$, this.secondaryFilterGroup$]).pipe( + map(([primary, secondary]) => [...(primary?.filters || []), ...(secondary?.filters || [])]), + any(f => f.checked || !!f.indeterminate), + distinctUntilChanged() + ); + } + + private _setFilters(filterGroup: string, checked = false) { + const filters = this.filterService.getGroup(filterGroup)?.filters; + filters?.forEach(f => { + // eslint-disable-next-line no-param-reassign + f.checked = checked; + // eslint-disable-next-line no-param-reassign + f.indeterminate = false; + // eslint-disable-next-line no-return-assign,no-param-reassign + f.children?.forEach(ff => (ff.checked = checked)); + }); + this.filterService.refresh(); + } +} diff --git a/src/lib/filtering/quick-filters/quick-filters.component.scss b/src/lib/filtering/quick-filters/quick-filters.component.scss index 879adf8..13ef48d 100644 --- a/src/lib/filtering/quick-filters/quick-filters.component.scss +++ b/src/lib/filtering/quick-filters/quick-filters.component.scss @@ -1,4 +1,4 @@ -@import '../../../assets/styles/common'; +@import '../../../assets/styles/variables'; :host { display: flex; diff --git a/src/lib/utils/operators.ts b/src/lib/utils/operators.ts new file mode 100644 index 0000000..3e6c9d0 --- /dev/null +++ b/src/lib/utils/operators.ts @@ -0,0 +1,10 @@ +import { map } from 'rxjs/operators'; +import { OperatorFunction } from 'rxjs'; + +export function get(predicate: (value: T, index: number) => boolean): OperatorFunction { + return map(entities => entities.find(predicate)); +} + +export function any(predicate: (value: T, index: number) => boolean): OperatorFunction { + return map(entities => entities.some(predicate)); +} From e6e9fcdc723a6545dac16ab5c45dfde9512d2a91 Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Fri, 20 Aug 2021 00:05:56 +0300 Subject: [PATCH 02/11] add toaster service --- src/index.ts | 2 + src/lib/services/error-message.service.ts | 19 +++++ src/lib/services/toaster.service.ts | 85 +++++++++++++++++++++++ 3 files changed, 106 insertions(+) create mode 100644 src/lib/services/error-message.service.ts create mode 100644 src/lib/services/toaster.service.ts diff --git a/src/index.ts b/src/index.ts index c9c5448..85cb447 100644 --- a/src/index.ts +++ b/src/index.ts @@ -23,6 +23,8 @@ export * from './lib/sorting/sort-by.pipe'; export * from './lib/sorting/sorting.service'; export * from './lib/sorting/models/sorting-option.model'; export * from './lib/sorting/models/sorting-order.type'; +export * from './lib/services/toaster.service'; +export * from './lib/services/error-message.service'; export * from './lib/search/search.service'; export * from './lib/tables/entities.service'; export * from './lib/tables/listing-component.directive'; diff --git a/src/lib/services/error-message.service.ts b/src/lib/services/error-message.service.ts new file mode 100644 index 0000000..a1a5d5a --- /dev/null +++ b/src/lib/services/error-message.service.ts @@ -0,0 +1,19 @@ +import { Injectable } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { HttpErrorResponse } from '@angular/common/http'; + +@Injectable({ + providedIn: 'root' +}) +export class ErrorMessageService { + constructor(private readonly _translateService: TranslateService) {} + + private _parseErrorResponse(err: HttpErrorResponse): string { + // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/restrict-template-expressions + return err?.error?.message?.includes('message') ? ` ${err.error.message.match('"message":"(.*?)\\"')[1]}` : ''; + } + + getMessage(error: HttpErrorResponse, defaultMessage: string): string { + return (this._translateService.instant(defaultMessage) as string) + this._parseErrorResponse(error); + } +} diff --git a/src/lib/services/toaster.service.ts b/src/lib/services/toaster.service.ts new file mode 100644 index 0000000..0d19ee6 --- /dev/null +++ b/src/lib/services/toaster.service.ts @@ -0,0 +1,85 @@ +import { Injectable } from '@angular/core'; +import { ActiveToast, ToastrService } from 'ngx-toastr'; +import { IndividualConfig } from 'ngx-toastr/toastr/toastr-config'; +import { NavigationStart, Router } from '@angular/router'; +import { TranslateService } from '@ngx-translate/core'; +import { HttpErrorResponse } from '@angular/common/http'; +import { filter } from 'rxjs/operators'; +import { ErrorMessageService } from './error-message.service'; + +const enum NotificationType { + SUCCESS = 'SUCCESS', + WARNING = 'WARNING', + INFO = 'INFO' +} + +export interface ToasterOptions extends IndividualConfig { + readonly title?: string; + /** + * These params are used as interpolateParams for translate service + */ + // eslint-disable-next-line @typescript-eslint/ban-types + readonly params?: object; + readonly actions?: { readonly title?: string; readonly action: () => void }[]; +} + +export interface ErrorToasterOptions extends ToasterOptions { + /** + * Pass an http error that will be processed by error message service and shown in toast + */ + readonly error?: HttpErrorResponse; +} + +@Injectable({ + providedIn: 'root' +}) +export class Toaster { + constructor( + private readonly _toastr: ToastrService, + private readonly _router: Router, + private readonly _translateService: TranslateService, + private readonly _errorMessageService: ErrorMessageService + ) { + _router.events.pipe(filter(event => event instanceof NavigationStart)).subscribe(() => { + _toastr.clear(); + }); + } + + error(message: string, options?: Partial): ActiveToast { + let resultedMsg; + if (options?.error) resultedMsg = this._errorMessageService.getMessage(options.error, message); + else resultedMsg = this._translateService.instant(message, options?.params) as string; + + return this._toastr.error(resultedMsg, options?.title, options); + } + + info(message: string, options?: Partial): ActiveToast { + return this._showToastNotification(message, NotificationType.INFO, options); + } + + success(message: string, options?: Partial): ActiveToast { + return this._showToastNotification(message, NotificationType.SUCCESS, options); + } + + warning(message: string, options?: Partial): ActiveToast { + return this._showToastNotification(message, NotificationType.WARNING, options); + } + + private _showToastNotification( + message: string, + notificationType = NotificationType.INFO, + options?: Partial + ): ActiveToast { + const translatedMsg = this._translateService.instant(message, options?.params) as string; + + switch (notificationType) { + case NotificationType.SUCCESS: + return this._toastr.success(translatedMsg, options?.title, options); + case NotificationType.WARNING: + return this._toastr.warning(translatedMsg, options?.title, options); + case NotificationType.INFO: + default: + return this._toastr.info(translatedMsg, options?.title, options); + } + } +} From b051f65c183365136c6e29f30bd4f7bea85aff3b Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Sat, 21 Aug 2021 00:42:43 +0300 Subject: [PATCH 03/11] refactor search service --- src/lib/search/search.service.ts | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/src/lib/search/search.service.ts b/src/lib/search/search.service.ts index 9df6116..81b55dc 100644 --- a/src/lib/search/search.service.ts +++ b/src/lib/search/search.service.ts @@ -1,31 +1,19 @@ import { Injectable } from '@angular/core'; -import { FormBuilder } from '@angular/forms'; -import { map, startWith } from 'rxjs/operators'; +import { BehaviorSubject } from 'rxjs'; import { KeysOf } from '../utils/types/utility-types'; -const controlsConfig = { - query: [''] -} as const; - -type FormControls = { [key in KeysOf]: string }; - @Injectable() export class SearchService { - readonly searchForm = this._formBuilder.group(controlsConfig); - readonly valueChanges$ = this.searchForm.valueChanges.pipe( - startWith(''), - map((values: FormControls) => values.query) - ); + private readonly _query$ = new BehaviorSubject(''); + readonly valueChanges$ = this._query$.asObservable(); private _searchKey!: KeysOf; - constructor(private readonly _formBuilder: FormBuilder) {} - get searchValue(): string { - return this.searchForm.get('query')?.value as string; + return this._query$.getValue(); } set searchValue(value: string) { - this.searchForm.patchValue({ query: value }); + this._query$.next(value); } searchIn(entities: T[]): T[] { @@ -40,7 +28,7 @@ export class SearchService { } reset(): void { - this.searchForm.reset({ query: '' }, { emitEvent: true }); + this._query$.next(''); } private _searchField(entity: T): string { From eef1e59fddbd79cb945d929b16eb68e4e65a98fe Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Sat, 21 Aug 2021 15:28:41 +0300 Subject: [PATCH 04/11] add input with action --- src/assets/icons/search.svg | 9 + src/assets/styles/_inputs.scss | 267 ++++++++++++++++++ src/assets/styles/_layout.scss | 3 + src/assets/styles/_mixins.scss | 29 ++ src/assets/styles/common.scss | 2 + src/index.ts | 2 + src/lib/common-ui.module.ts | 5 +- .../editable-input.component.html | 37 +-- .../input-with-action.component.html | 25 ++ .../input-with-action.component.scss | 14 + .../input-with-action.component.ts | 43 +++ 11 files changed, 416 insertions(+), 20 deletions(-) create mode 100644 src/assets/icons/search.svg create mode 100644 src/assets/styles/_inputs.scss create mode 100644 src/assets/styles/_layout.scss create mode 100644 src/assets/styles/_mixins.scss create mode 100644 src/lib/inputs/input-with-action/input-with-action.component.html create mode 100644 src/lib/inputs/input-with-action/input-with-action.component.scss create mode 100644 src/lib/inputs/input-with-action/input-with-action.component.ts diff --git a/src/assets/icons/search.svg b/src/assets/icons/search.svg new file mode 100644 index 0000000..8609f0d --- /dev/null +++ b/src/assets/icons/search.svg @@ -0,0 +1,9 @@ + + + + + + diff --git a/src/assets/styles/_inputs.scss b/src/assets/styles/_inputs.scss new file mode 100644 index 0000000..eefa5fe --- /dev/null +++ b/src/assets/styles/_inputs.scss @@ -0,0 +1,267 @@ +@import 'variables'; +@import 'mixins'; + +form .iqser-input-group:not(first-of-type) { + margin-top: 14px; +} + +.iqser-input-group { + display: flex; + flex-direction: column; + position: relative; + height: fit-content; + + .hint { + margin-top: 5px; + font-size: 11px; + line-height: 14px; + opacity: 0.7; + } + + .input-icon { + position: absolute; + right: 1px; + bottom: 1px; + background: $quick-filter-border; + height: 34px; + width: 34px; + border-left: 1px solid $quick-filter-border; + border-top-right-radius: 7px; + border-bottom-right-radius: 7px; + cursor: pointer; + transition: background-color 0.25s ease; + display: flex; + align-items: center; + justify-content: center; + + &:hover { + background: $btn-bg; + } + + mat-icon { + width: 14px; + height: 14px; + color: $accent; + } + + &.disabled { + cursor: default; + } + } + + .mat-form-field-underline { + display: none; + } + + .mat-form-field-wrapper, + .mat-form-field-infix { + padding-bottom: 0; + } + + .mat-form-field-label { + opacity: 0.7 !important; + color: $accent !important; + transform: translateY(-1.34em) !important; + } + + &:first-child { + margin-top: 0; + } + + .icon-right { + width: 14px; + height: 14px; + position: absolute; + top: 10px; + right: 10px; + } + + .slider-row { + display: flex; + flex-direction: row; + align-items: center; + } + + .mat-button-toggle-checked { + background: $primary; + transition: background-color 0.25s ease; + color: $white; + } + + input, + textarea, + mat-select { + box-sizing: border-box; + padding-left: 11px; + padding-right: 11px; + border: 1px solid $quick-filter-border; + font-family: Inter, sans-serif; + font-size: 13px; + background-color: #ffffff; + border-radius: 8px; + outline: none; + margin-top: 3px; + min-height: 36px; + + &.with-icon { + padding-right: 34px; + } + + &:focus:not(:disabled):not(.mat-select-disabled) { + border-color: $accent; + } + + &::placeholder { + color: $accent; + opacity: 0.7; + } + + &.ng-invalid.ng-touched { + border-color: rgba($primary, 0.3); + + &:focus { + border-color: $primary; + } + } + + &:disabled, + &.mat-select-disabled { + background-color: $filter-bg; + color: rgba($accent, 0.3); + } + } + + textarea { + line-height: 18px; + } + + .hex-color-input { + width: 150px; + max-width: 150px; + } + + mat-select { + .mat-select-trigger { + height: 32px; + } + + .mat-select-value { + vertical-align: middle; + } + } + + textarea { + resize: vertical; + padding-top: 7px; + padding-bottom: 7px; + @include scroll-bar; + + &.has-scrollbar { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + } + + label:not(.mat-slide-toggle-label) { + opacity: 0.7; + font-size: 11px; + letter-spacing: 0; + line-height: 14px; + margin-bottom: 2px; + color: $accent; + + &.mat-checkbox-layout { + opacity: 1; + margin-bottom: 0; + } + } + + &.required label:after { + content: ' *'; + color: $primary; + } + + &.datepicker-wrapper { + position: relative; + display: flex; + margin-top: 0; + width: 120px; + + .mat-datepicker-input { + margin-top: 0; + width: 120px; + } + + .mat-datepicker-toggle { + position: absolute; + right: 0; + bottom: 0; + color: $accent; + + &.mat-datepicker-toggle-active { + color: $primary; + } + + .mat-icon-button { + width: 34px; + height: 34px; + line-height: 34px; + } + + mat-icon { + width: 14px; + height: 17px; + } + } + } + + &.w-75 { + width: 75px; + max-width: 75px; + } + + &.w-110 { + width: 110px; + max-width: 110px; + } + + &.w-150 { + max-width: 150px; + width: 150px; + } + + &.w-160 { + width: 160px; + max-width: 160px; + } + + &.w-200 { + width: 200px; + max-width: 200px; + } + + &.w-250 { + width: 250px; + max-width: 250px; + } + + &.w-300 { + width: 300px; + max-width: 300px; + } + + &.w-400 { + width: 400px; + max-width: 400px; + } + + &.w-450 { + width: 450px; + max-width: 450px; + } + + &.w-full { + width: 100%; + max-width: 100%; + } +} diff --git a/src/assets/styles/_layout.scss b/src/assets/styles/_layout.scss new file mode 100644 index 0000000..ef030d8 --- /dev/null +++ b/src/assets/styles/_layout.scss @@ -0,0 +1,3 @@ +.mt-0 { + margin-top: 0 !important; +} diff --git a/src/assets/styles/_mixins.scss b/src/assets/styles/_mixins.scss new file mode 100644 index 0000000..e0c5955 --- /dev/null +++ b/src/assets/styles/_mixins.scss @@ -0,0 +1,29 @@ +@import 'variables'; + +@mixin no-scroll-bar { + scrollbar-width: none; /* Firefox */ + -ms-overflow-style: none; /* IE 10+ */ + &::-webkit-scrollbar { + width: 0; + background: transparent; /* Chrome/Safari/Webkit */ + } +} + +@mixin scroll-bar { + scrollbar-color: $quick-filter-border $filter-bg; + scrollbar-width: thin; + + &::-webkit-scrollbar { + width: 11px; + } + + /* Track */ + &::-webkit-scrollbar-track { + background: $filter-bg; + } + + /* Handle */ + &::-webkit-scrollbar-thumb { + background: $quick-filter-border; + } +} diff --git a/src/assets/styles/common.scss b/src/assets/styles/common.scss index 0a34c3b..c241880 100644 --- a/src/assets/styles/common.scss +++ b/src/assets/styles/common.scss @@ -1,3 +1,5 @@ +@import 'inputs'; @import 'buttons'; @import 'texts'; @import 'tables'; +@import 'layout'; diff --git a/src/index.ts b/src/index.ts index 85cb447..b972240 100644 --- a/src/index.ts +++ b/src/index.ts @@ -33,4 +33,6 @@ export * from './lib/tables/table-column-name/table-column-name.component'; export * from './lib/tables/table-header/table-header.component'; export * from './lib/misc/status-bar/status-bar.component'; export * from './lib/misc/status-bar/status-bar-config.model'; +export * from './lib/inputs/round-checkbox/round-checkbox.component'; export * from './lib/inputs/editable-input/editable-input.component'; +export * from './lib/inputs/input-with-action/input-with-action.component'; diff --git a/src/lib/common-ui.module.ts b/src/lib/common-ui.module.ts index f74b5c3..9ff2c29 100644 --- a/src/lib/common-ui.module.ts +++ b/src/lib/common-ui.module.ts @@ -21,10 +21,11 @@ import { SyncWidthDirective } from './tables/sync-width.directive'; import { StatusBarComponent } from './misc/status-bar/status-bar.component'; import { EditableInputComponent } from './inputs/editable-input/editable-input.component'; import { PopupFilterComponent } from './filtering/popup-filter/popup-filter.component'; +import { InputWithActionComponent } from './inputs/input-with-action/input-with-action.component'; const buttons = [IconButtonComponent, ChevronButtonComponent, CircleButtonComponent]; -const inputs = [RoundCheckboxComponent, EditableInputComponent]; +const inputs = [RoundCheckboxComponent, EditableInputComponent, InputWithActionComponent]; const matModules = [MatIconModule, MatButtonModule, MatTooltipModule, MatMenuModule, MatCheckboxModule]; @@ -49,7 +50,7 @@ const utils = [SortByPipe, HumanizePipe, SyncWidthDirective]; }) export class CommonUiModule { constructor(private readonly _iconRegistry: MatIconRegistry, private readonly _sanitizer: DomSanitizer) { - const icons = ['arrow-down', 'check', 'close', 'edit', 'sort-asc', 'sort-desc']; + const icons = ['arrow-down', 'check', 'close', 'edit', 'sort-asc', 'sort-desc', 'search']; icons.forEach(icon => { _iconRegistry.addSvgIconInNamespace('iqser', icon, _sanitizer.bypassSecurityTrustResourceUrl(`/assets/icons/${icon}.svg`)); diff --git a/src/lib/inputs/editable-input/editable-input.component.html b/src/lib/inputs/editable-input/editable-input.component.html index 9a2e06e..5d731a5 100644 --- a/src/lib/inputs/editable-input/editable-input.component.html +++ b/src/lib/inputs/editable-input/editable-input.component.html @@ -1,23 +1,24 @@ -
- {{ value }} -
- -
-
- + +
+ {{ value }}
- - + +
- - +
+
+ +
+
+ + +
diff --git a/src/lib/inputs/input-with-action/input-with-action.component.html b/src/lib/inputs/input-with-action/input-with-action.component.html new file mode 100644 index 0000000..8591c2b --- /dev/null +++ b/src/lib/inputs/input-with-action/input-with-action.component.html @@ -0,0 +1,25 @@ +
+ + + {{ hint }} + + + + + + +
diff --git a/src/lib/inputs/input-with-action/input-with-action.component.scss b/src/lib/inputs/input-with-action/input-with-action.component.scss new file mode 100644 index 0000000..f2c22fd --- /dev/null +++ b/src/lib/inputs/input-with-action/input-with-action.component.scss @@ -0,0 +1,14 @@ +:host { + display: block; +} + +mat-icon.disabled { + opacity: 0.7; + cursor: not-allowed; +} + +iqser-circle-button { + position: absolute; + top: 4px; + right: 5px; +} diff --git a/src/lib/inputs/input-with-action/input-with-action.component.ts b/src/lib/inputs/input-with-action/input-with-action.component.ts new file mode 100644 index 0000000..8863b91 --- /dev/null +++ b/src/lib/inputs/input-with-action/input-with-action.component.ts @@ -0,0 +1,43 @@ +import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; + +@Component({ + selector: 'iqser-input-with-action', + templateUrl: './input-with-action.component.html', + styleUrls: ['./input-with-action.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class InputWithActionComponent { + @Input() placeholder = ''; + @Input() hint?: string; + @Input() width: number | 'full' = 250; + @Input() icon?: string; + @Input() autocomplete: 'on' | 'off' = 'on'; + @Input() value = ''; + @Output() readonly action = new EventEmitter(); + @Output() readonly valueChange = new EventEmitter(); + + get hasContent(): boolean { + return !!this.value.length; + } + + get computedWidth(): string { + return this.width === 'full' ? '100%' : `${this.width}px`; + } + + reset(): void { + this.value = ''; + this.valueChange.emit(this.value); + } + + get isSearch(): boolean { + return this.action.observers.length === 0; + } + + executeAction($event: MouseEvent): void { + $event.stopPropagation(); + + if (this.hasContent) { + this.action.emit(this.value); + } + } +} From a935fb413b78314e528ad53426c23fc7a64b9a35 Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Sun, 22 Aug 2021 19:04:29 +0300 Subject: [PATCH 05/11] add help mode --- .eslintrc.json | 1 + src/assets/styles/_dialogs.scss | 40 ++++++++ src/assets/styles/_texts.scss | 12 +++ src/assets/styles/common.scss | 1 + src/index.ts | 6 ++ src/lib/common-ui.module.ts | 12 ++- .../help-mode-dialog.component.html | 8 ++ .../help-mode-dialog.component.scss | 16 ++++ .../help-mode-dialog.component.ts | 7 ++ src/lib/help-mode/help-mode.directive.ts | 34 +++++++ src/lib/help-mode/help-mode.service.ts | 91 +++++++++++++++++++ .../help-mode/help-mode.component.html | 20 ++++ .../help-mode/help-mode.component.scss | 72 +++++++++++++++ .../help-mode/help-mode.component.ts | 29 ++++++ .../input-with-action.component.ts | 2 +- src/lib/utils/injection-tokens.ts | 3 + src/lib/utils/types/events.type.ts | 3 + 17 files changed, 353 insertions(+), 4 deletions(-) create mode 100644 src/assets/styles/_dialogs.scss create mode 100644 src/lib/help-mode/help-mode-dialog/help-mode-dialog.component.html create mode 100644 src/lib/help-mode/help-mode-dialog/help-mode-dialog.component.scss create mode 100644 src/lib/help-mode/help-mode-dialog/help-mode-dialog.component.ts create mode 100644 src/lib/help-mode/help-mode.directive.ts create mode 100644 src/lib/help-mode/help-mode.service.ts create mode 100644 src/lib/help-mode/help-mode/help-mode.component.html create mode 100644 src/lib/help-mode/help-mode/help-mode.component.scss create mode 100644 src/lib/help-mode/help-mode/help-mode.component.ts create mode 100644 src/lib/utils/injection-tokens.ts create mode 100644 src/lib/utils/types/events.type.ts diff --git a/.eslintrc.json b/.eslintrc.json index 6b93cff..f4c2934 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -44,6 +44,7 @@ "@angular-eslint/no-output-rename": "error", "@angular-eslint/prefer-output-readonly": "error", "@typescript-eslint/unbound-method": "error", + "@typescript-eslint/no-floating-promises": "off", "@typescript-eslint/lines-between-class-members": "off", "@typescript-eslint/naming-convention": [ "error", diff --git a/src/assets/styles/_dialogs.scss b/src/assets/styles/_dialogs.scss new file mode 100644 index 0000000..5a68806 --- /dev/null +++ b/src/assets/styles/_dialogs.scss @@ -0,0 +1,40 @@ +@import 'apps/red-ui/src/assets/styles/variables'; + +.mat-dialog-container { + color: $accent; + padding: 0 !important; + border-radius: 8px !important; +} + +.dialog { + position: relative; + min-height: 80px; + + .dialog-close { + position: absolute; + top: 16px; + right: 16px; + } + + .dialog-header { + padding: 32px 60px 0 32px; + } + + .dialog-content { + padding: 24px 32px 40px; + } + + .dialog-actions { + height: 81px; + box-sizing: border-box; + border-top: 1px solid $separator; + padding: 0 32px; + align-items: center; + + display: flex; + + > * { + margin-right: 16px; + } + } +} diff --git a/src/assets/styles/_texts.scss b/src/assets/styles/_texts.scss index 4455d95..bc2aa1c 100644 --- a/src/assets/styles/_texts.scss +++ b/src/assets/styles/_texts.scss @@ -27,3 +27,15 @@ opacity: 1; } } + +.heading { + font-size: 16px; + line-height: 20px; + font-weight: 600; +} + +.heading-l { + font-size: 20px; + font-weight: 600; + line-height: 24px; +} diff --git a/src/assets/styles/common.scss b/src/assets/styles/common.scss index c241880..b4cc8b0 100644 --- a/src/assets/styles/common.scss +++ b/src/assets/styles/common.scss @@ -3,3 +3,4 @@ @import 'texts'; @import 'tables'; @import 'layout'; +@import 'dialogs'; diff --git a/src/index.ts b/src/index.ts index b972240..02f1a25 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,12 @@ export * from './lib/common-ui.module'; export * from './lib/buttons/icon-button/icon-button.type'; export * from './lib/buttons/icon-button/icon-button.component'; +export * from './lib/utils/injection-tokens'; export * from './lib/utils/functions'; export * from './lib/utils/operators'; export * from './lib/utils/auto-unsubscribe.directive'; export * from './lib/utils/pipes/humanize.pipe'; +export * from './lib/utils/types/events.type'; export * from './lib/utils/types/utility-types'; export * from './lib/utils/types/tooltip-positions.type'; export * from './lib/utils/decorators/bind.decorator'; @@ -36,3 +38,7 @@ export * from './lib/misc/status-bar/status-bar-config.model'; export * from './lib/inputs/round-checkbox/round-checkbox.component'; export * from './lib/inputs/editable-input/editable-input.component'; export * from './lib/inputs/input-with-action/input-with-action.component'; +export * from './lib/help-mode/help-mode.service'; +export * from './lib/help-mode/help-mode.directive'; +export * from './lib/help-mode/help-mode/help-mode.component'; +export * from './lib/help-mode/help-mode-dialog/help-mode-dialog.component'; diff --git a/src/lib/common-ui.module.ts b/src/lib/common-ui.module.ts index 9ff2c29..c0bbebe 100644 --- a/src/lib/common-ui.module.ts +++ b/src/lib/common-ui.module.ts @@ -8,6 +8,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { FormsModule } from '@angular/forms'; import { MatMenuModule } from '@angular/material/menu'; import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatDialogModule } from '@angular/material/dialog'; import { IconButtonComponent } from './buttons/icon-button/icon-button.component'; import { ChevronButtonComponent } from './buttons/chevron-button/chevron-button.component'; import { CircleButtonComponent } from './buttons/circle-button/circle-button.component'; @@ -22,12 +23,15 @@ import { StatusBarComponent } from './misc/status-bar/status-bar.component'; import { EditableInputComponent } from './inputs/editable-input/editable-input.component'; import { PopupFilterComponent } from './filtering/popup-filter/popup-filter.component'; import { InputWithActionComponent } from './inputs/input-with-action/input-with-action.component'; +import { HelpModeDirective } from './help-mode/help-mode.directive'; +import { HelpModeComponent } from './help-mode/help-mode/help-mode.component'; +import { HelpModeDialogComponent } from './help-mode/help-mode-dialog/help-mode-dialog.component'; const buttons = [IconButtonComponent, ChevronButtonComponent, CircleButtonComponent]; const inputs = [RoundCheckboxComponent, EditableInputComponent, InputWithActionComponent]; -const matModules = [MatIconModule, MatButtonModule, MatTooltipModule, MatMenuModule, MatCheckboxModule]; +const matModules = [MatIconModule, MatButtonModule, MatTooltipModule, MatMenuModule, MatCheckboxModule, MatDialogModule]; const modules = [...matModules, FormsModule, TranslateModule]; @@ -38,10 +42,12 @@ const components = [ QuickFiltersComponent, PopupFilterComponent, TableHeaderComponent, - StatusBarComponent + StatusBarComponent, + HelpModeComponent, + HelpModeDialogComponent ]; -const utils = [SortByPipe, HumanizePipe, SyncWidthDirective]; +const utils = [SortByPipe, HumanizePipe, SyncWidthDirective, HelpModeDirective]; @NgModule({ declarations: [...components, ...utils], diff --git a/src/lib/help-mode/help-mode-dialog/help-mode-dialog.component.html b/src/lib/help-mode/help-mode-dialog/help-mode-dialog.component.html new file mode 100644 index 0000000..42884bb --- /dev/null +++ b/src/lib/help-mode/help-mode-dialog/help-mode-dialog.component.html @@ -0,0 +1,8 @@ +
+
+

+ +

+
+ +
diff --git a/src/lib/help-mode/help-mode-dialog/help-mode-dialog.component.scss b/src/lib/help-mode/help-mode-dialog/help-mode-dialog.component.scss new file mode 100644 index 0000000..17dd08c --- /dev/null +++ b/src/lib/help-mode/help-mode-dialog/help-mode-dialog.component.scss @@ -0,0 +1,16 @@ +section { + background: #ecedf0; + display: flex; + justify-content: center; +} + +.content { + width: 440px; + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + padding-top: 20px; + padding-bottom: 30px; + line-height: 18px; +} diff --git a/src/lib/help-mode/help-mode-dialog/help-mode-dialog.component.ts b/src/lib/help-mode/help-mode-dialog/help-mode-dialog.component.ts new file mode 100644 index 0000000..38933ad --- /dev/null +++ b/src/lib/help-mode/help-mode-dialog/help-mode-dialog.component.ts @@ -0,0 +1,7 @@ +import { Component } from '@angular/core'; + +@Component({ + templateUrl: './help-mode-dialog.component.html', + styleUrls: ['./help-mode-dialog.component.scss'] +}) +export class HelpModeDialogComponent {} diff --git a/src/lib/help-mode/help-mode.directive.ts b/src/lib/help-mode/help-mode.directive.ts new file mode 100644 index 0000000..c2110b2 --- /dev/null +++ b/src/lib/help-mode/help-mode.directive.ts @@ -0,0 +1,34 @@ +import { Directive, ElementRef, HostListener, Input, OnInit, Renderer2 } from '@angular/core'; +import { HelpModeService } from './help-mode.service'; + +@Directive({ + selector: '[iqserHelpMode]', + exportAs: 'iqserHelpMode' +}) +export class HelpModeDirective implements OnInit { + @Input('iqserHelpMode') elementName!: string; + + constructor( + private readonly _elementRef: ElementRef, + private readonly _renderer: Renderer2, + private readonly _helpModeService: HelpModeService + ) {} + + ngOnInit(): void { + this._createHelperElement(); + } + + private _createHelperElement() { + const element = this._elementRef.nativeElement as HTMLElement; + + const helperElement = this._renderer.createElement('div') as HTMLElement; + this._renderer.addClass(helperElement, 'help-mode-on-mouse-over'); + this._renderer.addClass(helperElement, `help-mode-on-mouse-over-${this.elementName}`); + + this._helpModeService.addElement(this.elementName, element, helperElement); + } + + @HostListener('click') onClick(): void { + this._helpModeService.openDocsFor(this.elementName); + } +} diff --git a/src/lib/help-mode/help-mode.service.ts b/src/lib/help-mode/help-mode.service.ts new file mode 100644 index 0000000..e471839 --- /dev/null +++ b/src/lib/help-mode/help-mode.service.ts @@ -0,0 +1,91 @@ +import { Inject, Injectable, Renderer2, RendererFactory2 } from '@angular/core'; +import { MatDialog, MatDialogRef } from '@angular/material/dialog'; +import { TranslateService } from '@ngx-translate/core'; +import { HelpModeDialogComponent } from './help-mode-dialog/help-mode-dialog.component'; +import { HELP_DOCS } from '../utils/injection-tokens'; + +interface Helper { + readonly element: HTMLElement; + readonly helperElement: HTMLElement; +} + +@Injectable({ + providedIn: 'root' +}) +export class HelpModeService { + isHelpModeActive = false; + helpModeDialogIsOpened = false; + + private readonly _elements: Record = {}; + private readonly _renderer: Renderer2; + + constructor( + @Inject(HELP_DOCS) private readonly _docs: Record>, + private readonly _dialog: MatDialog, + private readonly _rendererFactory: RendererFactory2, + private readonly _translateService: TranslateService + ) { + this._renderer = this._rendererFactory.createRenderer(null, null); + } + + openHelpModeDialog(): MatDialogRef { + this.helpModeDialogIsOpened = true; + + const ref = this._dialog.open(HelpModeDialogComponent, { + width: '600px' + }); + + ref.afterClosed() + .toPromise() + .then(() => { + this.helpModeDialogIsOpened = false; + }); + return ref; + } + + openDocsFor(elementName: string): void { + if (this.isHelpModeActive) { + window.open(this._docs[elementName][this._translateService.currentLang]); + } + } + + activateHelpMode(): void { + this.isHelpModeActive = true; + this.openHelpModeDialog(); + this._enableHelperElements(); + } + + deactivateHelpMode(): void { + this.isHelpModeActive = false; + this._disableHelperElements(); + } + + highlightHelperElements(): void { + if (!this.isHelpModeActive) return; + + Object.values(this._elements).forEach(({ helperElement }) => { + this._renderer.addClass(helperElement, 'highlight'); + setTimeout(() => { + this._renderer.removeClass(helperElement, 'highlight'); + }, 500); + }); + } + + addElement(elementName: string, element: HTMLElement, helperElement: HTMLElement): void { + this._elements[elementName] = { element, helperElement }; + } + + private _enableHelperElements() { + Object.values(this._elements).forEach(({ element, helperElement }) => { + this._renderer.setStyle(element, 'position', 'relative'); + this._renderer.appendChild(element, helperElement); + }); + } + + private _disableHelperElements() { + Object.values(this._elements).forEach(({ element, helperElement }) => { + this._renderer.removeStyle(element, 'position'); + this._renderer.removeChild(element, helperElement); + }); + } +} diff --git a/src/lib/help-mode/help-mode/help-mode.component.html b/src/lib/help-mode/help-mode/help-mode.component.html new file mode 100644 index 0000000..2e86423 --- /dev/null +++ b/src/lib/help-mode/help-mode/help-mode.component.html @@ -0,0 +1,20 @@ +
+ +
{{ 'help-mode.button-text' | translate }}
+
+
+
+

{{ 'help-mode.text' | translate }}

+ + {{ 'help-mode.instructions' | translate }} + +
+ (esc) + +
+
+
diff --git a/src/lib/help-mode/help-mode/help-mode.component.scss b/src/lib/help-mode/help-mode/help-mode.component.scss new file mode 100644 index 0000000..fb99479 --- /dev/null +++ b/src/lib/help-mode/help-mode/help-mode.component.scss @@ -0,0 +1,72 @@ +@import '../../../../../../apps/red-ui/src/assets/styles/variables'; + +.help-button { + width: 44px; + height: 40px; + position: absolute; + bottom: 20px; + right: 0; + z-index: 1; + background: $green-2; + border-top-left-radius: 8px; + border-bottom-left-radius: 8px; + box-shadow: -1px 1px 5px 0 rgba(40, 50, 65, 0.25); + display: flex; + align-items: center; + justify-content: center; + transition: all 0.25s; +} + +.help-button:hover { + cursor: pointer; + width: fit-content; + padding-left: 10px; + padding-right: 10px; + + .text { + display: block; + } + + mat-icon { + padding-right: 8px; + } +} + +.text { + display: none; +} + +.help-mode-border { + 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; + z-index: 10; + position: absolute; + display: flex; + justify-content: center; + + .bottom { + position: fixed; + height: 60px; + width: 95%; + bottom: 0; + display: flex; + justify-content: space-between; + align-items: center; + pointer-events: visiblePainted; + + a { + color: black; + text-decoration: underline; + } + + .close { + display: flex; + align-items: center; + } + } +} diff --git a/src/lib/help-mode/help-mode/help-mode.component.ts b/src/lib/help-mode/help-mode/help-mode.component.ts new file mode 100644 index 0000000..6293193 --- /dev/null +++ b/src/lib/help-mode/help-mode/help-mode.component.ts @@ -0,0 +1,29 @@ +import { Component, HostListener } from '@angular/core'; +import { HelpModeService } from '../help-mode.service'; +import { IqserEventTarget } from '../../utils/types/events.type'; + +@Component({ + selector: 'iqser-help-mode', + templateUrl: './help-mode.component.html', + styleUrls: ['./help-mode.component.scss'] +}) +export class HelpModeComponent { + constructor(readonly helpModeService: HelpModeService) {} + + @HostListener('document:keydown.escape') onEscKeydownHandler(): void { + if (!this.helpModeService.helpModeDialogIsOpened) { + this.helpModeService.deactivateHelpMode(); + } + } + + @HostListener('document:keydown.h', ['$event']) onHKeydownHandler(event: KeyboardEvent): void { + const node = (event.target as IqserEventTarget).localName; + if (!this.helpModeService.isHelpModeActive && node !== 'input' && node !== 'textarea') { + this.helpModeService.activateHelpMode(); + } + } + + @HostListener('click') onClick(): void { + this.helpModeService.highlightHelperElements(); + } +} diff --git a/src/lib/inputs/input-with-action/input-with-action.component.ts b/src/lib/inputs/input-with-action/input-with-action.component.ts index 8863b91..809eccc 100644 --- a/src/lib/inputs/input-with-action/input-with-action.component.ts +++ b/src/lib/inputs/input-with-action/input-with-action.component.ts @@ -17,7 +17,7 @@ export class InputWithActionComponent { @Output() readonly valueChange = new EventEmitter(); get hasContent(): boolean { - return !!this.value.length; + return !!this.value?.length; } get computedWidth(): string { diff --git a/src/lib/utils/injection-tokens.ts b/src/lib/utils/injection-tokens.ts new file mode 100644 index 0000000..3bb8bd9 --- /dev/null +++ b/src/lib/utils/injection-tokens.ts @@ -0,0 +1,3 @@ +import { InjectionToken } from '@angular/core'; + +export const HELP_DOCS = new InjectionToken>>('Links to user manual or help docs'); diff --git a/src/lib/utils/types/events.type.ts b/src/lib/utils/types/events.type.ts new file mode 100644 index 0000000..2d5c97d --- /dev/null +++ b/src/lib/utils/types/events.type.ts @@ -0,0 +1,3 @@ +export interface IqserEventTarget extends EventTarget { + localName: string; +} From c20bac823277e659c1c9797bdd034a9f5b2fc539 Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Mon, 23 Aug 2021 12:59:25 +0300 Subject: [PATCH 06/11] use help mode with on push change detection --- src/assets/icons/help-outline.svg | 12 ++++++++++ src/lib/common-ui.module.ts | 2 +- .../help-mode-dialog.component.ts | 5 ++-- src/lib/help-mode/help-mode.service.ts | 23 ++++++++++++++----- .../help-mode/help-mode.component.html | 15 +++++------- .../help-mode/help-mode.component.ts | 5 ++-- 6 files changed, 42 insertions(+), 20 deletions(-) create mode 100644 src/assets/icons/help-outline.svg diff --git a/src/assets/icons/help-outline.svg b/src/assets/icons/help-outline.svg new file mode 100644 index 0000000..2fd05c3 --- /dev/null +++ b/src/assets/icons/help-outline.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/lib/common-ui.module.ts b/src/lib/common-ui.module.ts index c0bbebe..1b53185 100644 --- a/src/lib/common-ui.module.ts +++ b/src/lib/common-ui.module.ts @@ -56,7 +56,7 @@ const utils = [SortByPipe, HumanizePipe, SyncWidthDirective, HelpModeDirective]; }) export class CommonUiModule { constructor(private readonly _iconRegistry: MatIconRegistry, private readonly _sanitizer: DomSanitizer) { - const icons = ['arrow-down', 'check', 'close', 'edit', 'sort-asc', 'sort-desc', 'search']; + const icons = ['arrow-down', 'check', 'close', 'edit', 'sort-asc', 'sort-desc', 'search', 'help-outline']; icons.forEach(icon => { _iconRegistry.addSvgIconInNamespace('iqser', icon, _sanitizer.bypassSecurityTrustResourceUrl(`/assets/icons/${icon}.svg`)); diff --git a/src/lib/help-mode/help-mode-dialog/help-mode-dialog.component.ts b/src/lib/help-mode/help-mode-dialog/help-mode-dialog.component.ts index 38933ad..ae75872 100644 --- a/src/lib/help-mode/help-mode-dialog/help-mode-dialog.component.ts +++ b/src/lib/help-mode/help-mode-dialog/help-mode-dialog.component.ts @@ -1,7 +1,8 @@ -import { Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; @Component({ templateUrl: './help-mode-dialog.component.html', - styleUrls: ['./help-mode-dialog.component.scss'] + styleUrls: ['./help-mode-dialog.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush }) export class HelpModeDialogComponent {} diff --git a/src/lib/help-mode/help-mode.service.ts b/src/lib/help-mode/help-mode.service.ts index e471839..220d04b 100644 --- a/src/lib/help-mode/help-mode.service.ts +++ b/src/lib/help-mode/help-mode.service.ts @@ -1,6 +1,7 @@ import { Inject, Injectable, Renderer2, RendererFactory2 } from '@angular/core'; import { MatDialog, MatDialogRef } from '@angular/material/dialog'; import { TranslateService } from '@ngx-translate/core'; +import { BehaviorSubject } from 'rxjs'; import { HelpModeDialogComponent } from './help-mode-dialog/help-mode-dialog.component'; import { HELP_DOCS } from '../utils/injection-tokens'; @@ -13,8 +14,10 @@ interface Helper { providedIn: 'root' }) export class HelpModeService { - isHelpModeActive = false; - helpModeDialogIsOpened = false; + private readonly _isHelpModeActive$ = new BehaviorSubject(false); + readonly isHelpModeActive$ = this._isHelpModeActive$.asObservable(); + private readonly _helpModeDialogIsOpened$ = new BehaviorSubject(false); + readonly helpModeDialogIsOpened$ = this._helpModeDialogIsOpened$.asObservable(); private readonly _elements: Record = {}; private readonly _renderer: Renderer2; @@ -28,8 +31,16 @@ export class HelpModeService { this._renderer = this._rendererFactory.createRenderer(null, null); } + get isHelpModeActive(): boolean { + return this._isHelpModeActive$.getValue(); + } + + get helpModeDialogIsOpened(): boolean { + return this._helpModeDialogIsOpened$.getValue(); + } + openHelpModeDialog(): MatDialogRef { - this.helpModeDialogIsOpened = true; + this._helpModeDialogIsOpened$.next(true); const ref = this._dialog.open(HelpModeDialogComponent, { width: '600px' @@ -38,7 +49,7 @@ export class HelpModeService { ref.afterClosed() .toPromise() .then(() => { - this.helpModeDialogIsOpened = false; + this._helpModeDialogIsOpened$.next(false); }); return ref; } @@ -50,13 +61,13 @@ export class HelpModeService { } activateHelpMode(): void { - this.isHelpModeActive = true; + this._isHelpModeActive$.next(true); this.openHelpModeDialog(); this._enableHelperElements(); } deactivateHelpMode(): void { - this.isHelpModeActive = false; + this._isHelpModeActive$.next(false); this._disableHelperElements(); } diff --git a/src/lib/help-mode/help-mode/help-mode.component.html b/src/lib/help-mode/help-mode/help-mode.component.html index 2e86423..4a01d8b 100644 --- a/src/lib/help-mode/help-mode/help-mode.component.html +++ b/src/lib/help-mode/help-mode/help-mode.component.html @@ -1,20 +1,17 @@ -
- +
+
{{ 'help-mode.button-text' | translate }}
-
+ +

{{ 'help-mode.text' | translate }}

- + {{ 'help-mode.instructions' | translate }}
(esc) - +
diff --git a/src/lib/help-mode/help-mode/help-mode.component.ts b/src/lib/help-mode/help-mode/help-mode.component.ts index 6293193..5709d51 100644 --- a/src/lib/help-mode/help-mode/help-mode.component.ts +++ b/src/lib/help-mode/help-mode/help-mode.component.ts @@ -1,11 +1,12 @@ -import { Component, HostListener } from '@angular/core'; +import { ChangeDetectionStrategy, Component, HostListener } from '@angular/core'; import { HelpModeService } from '../help-mode.service'; import { IqserEventTarget } from '../../utils/types/events.type'; @Component({ selector: 'iqser-help-mode', templateUrl: './help-mode.component.html', - styleUrls: ['./help-mode.component.scss'] + styleUrls: ['./help-mode.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush }) export class HelpModeComponent { constructor(readonly helpModeService: HelpModeService) {} From aa6c78967fa5d2857dede568071eaad55428d540 Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Mon, 23 Aug 2021 18:12:48 +0300 Subject: [PATCH 07/11] move help mode and buttons to separate modules --- src/index.ts | 10 ++-------- src/lib/buttons/buttons.module.ts | 17 ++++++++++++++++ .../circle-button/circle-button.component.ts | 2 +- .../icon-button/icon-button.component.ts | 2 +- src/lib/buttons/index.ts | 5 +++++ .../circle-button.type.ts | 0 .../icon-button.type.ts | 0 src/lib/common-ui.module.ts | 18 ++++------------- src/lib/help-mode/help-mode.module.ts | 20 +++++++++++++++++++ src/lib/help-mode/help-mode.service.ts | 4 +--- src/lib/help-mode/index.ts | 5 +++++ .../editable-input.component.ts | 2 +- 12 files changed, 57 insertions(+), 28 deletions(-) create mode 100644 src/lib/buttons/buttons.module.ts create mode 100644 src/lib/buttons/index.ts rename src/lib/buttons/{circle-button => types}/circle-button.type.ts (100%) rename src/lib/buttons/{icon-button => types}/icon-button.type.ts (100%) create mode 100644 src/lib/help-mode/help-mode.module.ts create mode 100644 src/lib/help-mode/index.ts diff --git a/src/index.ts b/src/index.ts index 02f1a25..c1f780c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,4 @@ export * from './lib/common-ui.module'; -export * from './lib/buttons/icon-button/icon-button.type'; -export * from './lib/buttons/icon-button/icon-button.component'; export * from './lib/utils/injection-tokens'; export * from './lib/utils/functions'; export * from './lib/utils/operators'; @@ -12,8 +10,6 @@ export * from './lib/utils/types/tooltip-positions.type'; export * from './lib/utils/decorators/bind.decorator'; export * from './lib/utils/decorators/required.decorator'; export * from './lib/utils/decorators/debounce.decorator'; -export * from './lib/buttons/circle-button/circle-button.type'; -export * from './lib/buttons/circle-button/circle-button.component'; export * from './lib/filtering/filter-utils'; export * from './lib/filtering/filter.service'; export * from './lib/filtering/models/filter.model'; @@ -38,7 +34,5 @@ export * from './lib/misc/status-bar/status-bar-config.model'; export * from './lib/inputs/round-checkbox/round-checkbox.component'; export * from './lib/inputs/editable-input/editable-input.component'; export * from './lib/inputs/input-with-action/input-with-action.component'; -export * from './lib/help-mode/help-mode.service'; -export * from './lib/help-mode/help-mode.directive'; -export * from './lib/help-mode/help-mode/help-mode.component'; -export * from './lib/help-mode/help-mode-dialog/help-mode-dialog.component'; +export * from './lib/buttons'; +export * from './lib/help-mode'; diff --git a/src/lib/buttons/buttons.module.ts b/src/lib/buttons/buttons.module.ts new file mode 100644 index 0000000..f8acc95 --- /dev/null +++ b/src/lib/buttons/buttons.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { MatIconModule } from '@angular/material/icon'; +import { MatButtonModule } from '@angular/material/button'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { ChevronButtonComponent } from './chevron-button/chevron-button.component'; +import { CircleButtonComponent } from './circle-button/circle-button.component'; +import { IconButtonComponent } from './icon-button/icon-button.component'; + +const components = [ChevronButtonComponent, CircleButtonComponent, IconButtonComponent]; + +@NgModule({ + declarations: [...components], + imports: [CommonModule, MatIconModule, MatButtonModule, MatTooltipModule], + exports: [...components] +}) +export class IqserButtonsModule {} diff --git a/src/lib/buttons/circle-button/circle-button.component.ts b/src/lib/buttons/circle-button/circle-button.component.ts index 0976335..fa26439 100644 --- a/src/lib/buttons/circle-button/circle-button.component.ts +++ b/src/lib/buttons/circle-button/circle-button.component.ts @@ -1,6 +1,6 @@ import { ChangeDetectionStrategy, Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'; import { MatTooltip } from '@angular/material/tooltip'; -import { CircleButtonType, CircleButtonTypes } from './circle-button.type'; +import { CircleButtonType, CircleButtonTypes } from '../types/circle-button.type'; import { Required } from '../../utils/decorators/required.decorator'; import { IqserTooltipPosition, IqserTooltipPositions } from '../../utils/types/tooltip-positions.type'; diff --git a/src/lib/buttons/icon-button/icon-button.component.ts b/src/lib/buttons/icon-button/icon-button.component.ts index 2d1f24c..26c602e 100644 --- a/src/lib/buttons/icon-button/icon-button.component.ts +++ b/src/lib/buttons/icon-button/icon-button.component.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; -import { IconButtonType, IconButtonTypes } from './icon-button.type'; +import { IconButtonType, IconButtonTypes } from '../types/icon-button.type'; import { Required } from '../../utils/decorators/required.decorator'; @Component({ diff --git a/src/lib/buttons/index.ts b/src/lib/buttons/index.ts new file mode 100644 index 0000000..481dd0f --- /dev/null +++ b/src/lib/buttons/index.ts @@ -0,0 +1,5 @@ +export * from './buttons.module'; +export * from './types/icon-button.type'; +export * from './icon-button/icon-button.component'; +export * from './types/circle-button.type'; +export * from './circle-button/circle-button.component'; diff --git a/src/lib/buttons/circle-button/circle-button.type.ts b/src/lib/buttons/types/circle-button.type.ts similarity index 100% rename from src/lib/buttons/circle-button/circle-button.type.ts rename to src/lib/buttons/types/circle-button.type.ts diff --git a/src/lib/buttons/icon-button/icon-button.type.ts b/src/lib/buttons/types/icon-button.type.ts similarity index 100% rename from src/lib/buttons/icon-button/icon-button.type.ts rename to src/lib/buttons/types/icon-button.type.ts diff --git a/src/lib/common-ui.module.ts b/src/lib/common-ui.module.ts index 1b53185..d56cb8d 100644 --- a/src/lib/common-ui.module.ts +++ b/src/lib/common-ui.module.ts @@ -9,9 +9,6 @@ import { FormsModule } from '@angular/forms'; import { MatMenuModule } from '@angular/material/menu'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatDialogModule } from '@angular/material/dialog'; -import { IconButtonComponent } from './buttons/icon-button/icon-button.component'; -import { ChevronButtonComponent } from './buttons/chevron-button/chevron-button.component'; -import { CircleButtonComponent } from './buttons/circle-button/circle-button.component'; import { RoundCheckboxComponent } from './inputs/round-checkbox/round-checkbox.component'; import { SortByPipe } from './sorting/sort-by.pipe'; import { HumanizePipe } from './utils/pipes/humanize.pipe'; @@ -23,31 +20,24 @@ import { StatusBarComponent } from './misc/status-bar/status-bar.component'; import { EditableInputComponent } from './inputs/editable-input/editable-input.component'; import { PopupFilterComponent } from './filtering/popup-filter/popup-filter.component'; import { InputWithActionComponent } from './inputs/input-with-action/input-with-action.component'; -import { HelpModeDirective } from './help-mode/help-mode.directive'; -import { HelpModeComponent } from './help-mode/help-mode/help-mode.component'; -import { HelpModeDialogComponent } from './help-mode/help-mode-dialog/help-mode-dialog.component'; - -const buttons = [IconButtonComponent, ChevronButtonComponent, CircleButtonComponent]; +import { IqserButtonsModule } from './buttons'; const inputs = [RoundCheckboxComponent, EditableInputComponent, InputWithActionComponent]; const matModules = [MatIconModule, MatButtonModule, MatTooltipModule, MatMenuModule, MatCheckboxModule, MatDialogModule]; -const modules = [...matModules, FormsModule, TranslateModule]; +const modules = [...matModules, FormsModule, TranslateModule, IqserButtonsModule]; const components = [ - ...buttons, ...inputs, TableColumnNameComponent, QuickFiltersComponent, PopupFilterComponent, TableHeaderComponent, - StatusBarComponent, - HelpModeComponent, - HelpModeDialogComponent + StatusBarComponent ]; -const utils = [SortByPipe, HumanizePipe, SyncWidthDirective, HelpModeDirective]; +const utils = [SortByPipe, HumanizePipe, SyncWidthDirective]; @NgModule({ declarations: [...components, ...utils], diff --git a/src/lib/help-mode/help-mode.module.ts b/src/lib/help-mode/help-mode.module.ts new file mode 100644 index 0000000..2d17ac7 --- /dev/null +++ b/src/lib/help-mode/help-mode.module.ts @@ -0,0 +1,20 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { MatIconModule } from '@angular/material/icon'; +import { TranslateModule } from '@ngx-translate/core'; +import { MatDialogModule } from '@angular/material/dialog'; +import { HelpModeDialogComponent } from './help-mode-dialog/help-mode-dialog.component'; +import { HelpModeComponent } from './help-mode/help-mode.component'; +import { HelpModeDirective } from './help-mode.directive'; +import { IqserButtonsModule } from '../buttons'; +import { HelpModeService } from './help-mode.service'; + +const components = [HelpModeComponent, HelpModeDialogComponent, HelpModeDirective]; + +@NgModule({ + declarations: [...components], + imports: [CommonModule, MatIconModule, MatDialogModule, TranslateModule, IqserButtonsModule], + exports: [...components], + providers: [HelpModeService] +}) +export class IqserHelpModeModule {} diff --git a/src/lib/help-mode/help-mode.service.ts b/src/lib/help-mode/help-mode.service.ts index 220d04b..0ddbebd 100644 --- a/src/lib/help-mode/help-mode.service.ts +++ b/src/lib/help-mode/help-mode.service.ts @@ -10,9 +10,7 @@ interface Helper { readonly helperElement: HTMLElement; } -@Injectable({ - providedIn: 'root' -}) +@Injectable() export class HelpModeService { private readonly _isHelpModeActive$ = new BehaviorSubject(false); readonly isHelpModeActive$ = this._isHelpModeActive$.asObservable(); diff --git a/src/lib/help-mode/index.ts b/src/lib/help-mode/index.ts new file mode 100644 index 0000000..4188d4b --- /dev/null +++ b/src/lib/help-mode/index.ts @@ -0,0 +1,5 @@ +export * from './help-mode.module'; +export * from './help-mode.service'; +export * from './help-mode.directive'; +export * from './help-mode/help-mode.component'; +export * from './help-mode-dialog/help-mode-dialog.component'; diff --git a/src/lib/inputs/editable-input/editable-input.component.ts b/src/lib/inputs/editable-input/editable-input.component.ts index c8391bc..ece127b 100644 --- a/src/lib/inputs/editable-input/editable-input.component.ts +++ b/src/lib/inputs/editable-input/editable-input.component.ts @@ -1,6 +1,6 @@ import { ChangeDetectionStrategy, Component, EventEmitter, HostBinding, Input, Output } from '@angular/core'; import { Required } from '../../utils/decorators/required.decorator'; -import { CircleButtonType } from '../../buttons/circle-button/circle-button.type'; +import { CircleButtonType } from '../../buttons/types/circle-button.type'; @Component({ selector: 'iqser-editable-input', From 7d14fc27fba7cca319f0552ade4ffe0d8cd08866 Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Mon, 23 Aug 2021 18:16:13 +0300 Subject: [PATCH 08/11] mode help docs token to help mode module --- src/index.ts | 1 - src/lib/help-mode/help-mode.service.ts | 2 +- src/lib/help-mode/index.ts | 1 + src/lib/{utils/injection-tokens.ts => help-mode/tokens.ts} | 0 4 files changed, 2 insertions(+), 2 deletions(-) rename src/lib/{utils/injection-tokens.ts => help-mode/tokens.ts} (100%) diff --git a/src/index.ts b/src/index.ts index c1f780c..bca8310 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,4 @@ export * from './lib/common-ui.module'; -export * from './lib/utils/injection-tokens'; export * from './lib/utils/functions'; export * from './lib/utils/operators'; export * from './lib/utils/auto-unsubscribe.directive'; diff --git a/src/lib/help-mode/help-mode.service.ts b/src/lib/help-mode/help-mode.service.ts index 0ddbebd..3dcb8ee 100644 --- a/src/lib/help-mode/help-mode.service.ts +++ b/src/lib/help-mode/help-mode.service.ts @@ -3,7 +3,7 @@ import { MatDialog, MatDialogRef } from '@angular/material/dialog'; import { TranslateService } from '@ngx-translate/core'; import { BehaviorSubject } from 'rxjs'; import { HelpModeDialogComponent } from './help-mode-dialog/help-mode-dialog.component'; -import { HELP_DOCS } from '../utils/injection-tokens'; +import { HELP_DOCS } from './tokens'; interface Helper { readonly element: HTMLElement; diff --git a/src/lib/help-mode/index.ts b/src/lib/help-mode/index.ts index 4188d4b..cda1a99 100644 --- a/src/lib/help-mode/index.ts +++ b/src/lib/help-mode/index.ts @@ -1,3 +1,4 @@ +export * from './tokens'; export * from './help-mode.module'; export * from './help-mode.service'; export * from './help-mode.directive'; diff --git a/src/lib/utils/injection-tokens.ts b/src/lib/help-mode/tokens.ts similarity index 100% rename from src/lib/utils/injection-tokens.ts rename to src/lib/help-mode/tokens.ts From 844e2657774200f14f79ed5c75e06f2c6c554bb4 Mon Sep 17 00:00:00 2001 From: Timo Bejan Date: Tue, 24 Aug 2021 13:34:23 +0300 Subject: [PATCH 09/11] Listable by ID instead of reference --- src/index.ts | 1 + src/lib/tables/entities.service.ts | 12 ++++++++---- src/lib/tables/listing-component.directive.ts | 3 ++- src/lib/tables/models/listable.ts | 3 +++ .../tables/table-header/table-header.component.ts | 3 ++- 5 files changed, 16 insertions(+), 6 deletions(-) create mode 100644 src/lib/tables/models/listable.ts diff --git a/src/index.ts b/src/index.ts index bca8310..fef87ed 100644 --- a/src/index.ts +++ b/src/index.ts @@ -35,3 +35,4 @@ export * from './lib/inputs/editable-input/editable-input.component'; export * from './lib/inputs/input-with-action/input-with-action.component'; export * from './lib/buttons'; export * from './lib/help-mode'; +export * from './lib/tables/models/listable'; diff --git a/src/lib/tables/entities.service.ts b/src/lib/tables/entities.service.ts index 99754b9..cfa79ad 100644 --- a/src/lib/tables/entities.service.ts +++ b/src/lib/tables/entities.service.ts @@ -4,12 +4,13 @@ import { distinctUntilChanged, map, tap } from 'rxjs/operators'; import { FilterService } from '../filtering/filter.service'; import { SearchService } from '../search/search.service'; import { getFilteredEntities } from '../filtering/filter-utils'; +import { Listable } from './models/listable'; const toLengthValue = (entities: unknown[]) => entities?.length ?? 0; const getLength = pipe(map(toLengthValue), distinctUntilChanged()); @Injectable() -export class EntitiesService { +export class EntitiesService { private readonly _all$ = new BehaviorSubject([]); readonly all$ = this._all$.asObservable(); readonly allLength$ = this._all$.pipe(getLength); @@ -18,8 +19,9 @@ export class EntitiesService { readonly displayed$ = this._getDisplayed$; readonly displayedLength$ = this.displayed$.pipe(getLength); - private readonly _selected$ = new BehaviorSubject([]); + private readonly _selected$ = new BehaviorSubject<(string | number)[]>([]); readonly selected$ = this._selected$.asObservable(); + readonly selectedEntities$ = this._selected$.asObservable().pipe(map(() => this.selected)); readonly selectedLength$ = this._selected$.pipe(getLength); readonly noData$ = this._noData$; @@ -34,7 +36,8 @@ export class EntitiesService { } get selected(): T[] { - return Object.values(this._selected$.getValue()); + const selectedIds = Object.values(this._selected$.getValue()); + return this.all.filter(a => selectedIds.indexOf(a.id) !== -1); } private get _getDisplayed$(): Observable { @@ -87,7 +90,8 @@ export class EntitiesService { } setSelected(newEntities: T[]): void { - this._selected$.next(newEntities); + const selectedIds = newEntities.map(e => e.id); + this._selected$.next(selectedIds); } isSelected(entity: T): boolean { diff --git a/src/lib/tables/listing-component.directive.ts b/src/lib/tables/listing-component.directive.ts index fc5b894..f878981 100644 --- a/src/lib/tables/listing-component.directive.ts +++ b/src/lib/tables/listing-component.directive.ts @@ -10,11 +10,12 @@ import { SearchService } from '../search/search.service'; import { KeysOf } from '../utils/types/utility-types'; import { TableColumnConfig } from './models/table-column-config.model'; import { EntitiesService } from './entities.service'; +import { Listable } from './models/listable'; export const DefaultListingServices = [FilterService, SearchService, EntitiesService, SortingService] as const; @Directive() -export abstract class ListingComponent extends AutoUnsubscribe implements OnDestroy { +export abstract class ListingComponent extends AutoUnsubscribe implements OnDestroy { readonly filterService = this._injector.get(FilterService); readonly searchService = this._injector.get>(SearchService); readonly sortingService = this._injector.get>(SortingService); diff --git a/src/lib/tables/models/listable.ts b/src/lib/tables/models/listable.ts new file mode 100644 index 0000000..a6635c2 --- /dev/null +++ b/src/lib/tables/models/listable.ts @@ -0,0 +1,3 @@ +export interface Listable { + readonly id: string | number; +} diff --git a/src/lib/tables/table-header/table-header.component.ts b/src/lib/tables/table-header/table-header.component.ts index 6b673bc..158e17c 100644 --- a/src/lib/tables/table-header/table-header.component.ts +++ b/src/lib/tables/table-header/table-header.component.ts @@ -3,6 +3,7 @@ import { Required } from '../../utils/decorators/required.decorator'; import { FilterService } from '../../filtering/filter.service'; import { TableColumnConfig } from '../models/table-column-config.model'; import { EntitiesService } from '../entities.service'; +import { Listable } from '../models/listable'; @Component({ selector: 'iqser-table-header', @@ -10,7 +11,7 @@ import { EntitiesService } from '../entities.service'; styleUrls: ['./table-header.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush }) -export class TableHeaderComponent { +export class TableHeaderComponent { @Input() @Required() tableHeaderLabel!: string; @Input() @Required() tableColumnConfigs!: readonly TableColumnConfig[]; @Input() hasEmptyColumn = false; From 83e4fe23f97967e867ef16be45438fb8352040a6 Mon Sep 17 00:00:00 2001 From: Timo Bejan Date: Tue, 24 Aug 2021 15:04:29 +0300 Subject: [PATCH 10/11] set relativ paths - temporary fix --- src/assets/styles/_variables.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/assets/styles/_variables.scss b/src/assets/styles/_variables.scss index 073ea5d..cf11624 100644 --- a/src/assets/styles/_variables.scss +++ b/src/assets/styles/_variables.scss @@ -1,5 +1,5 @@ // This rebel line is crying (in WebStorm) but it actually works -@import '~/src/assets/styles/variables'; +@import '../../../../../apps/red-ui/src/assets/styles/variables'; $btn-bg-hover: #e2e4e9 !default; $btn-bg: #f0f1f4 !default; From 63d6daa50ae8816cf0ef333f83c41b5a3369f4e5 Mon Sep 17 00:00:00 2001 From: Timo Bejan Date: Wed, 25 Aug 2021 10:32:57 +0300 Subject: [PATCH 11/11] minor updates for build --- src/lib/filtering/popup-filter/popup-filter.component.ts | 5 ++++- src/lib/tables/table-header/table-header.component.html | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/lib/filtering/popup-filter/popup-filter.component.ts b/src/lib/filtering/popup-filter/popup-filter.component.ts index f504f4b..cf3ffc1 100644 --- a/src/lib/filtering/popup-filter/popup-filter.component.ts +++ b/src/lib/filtering/popup-filter/popup-filter.component.ts @@ -1,9 +1,12 @@ import { ChangeDetectionStrategy, Component, Input, OnInit, TemplateRef } from '@angular/core'; -import { FilterGroup, FilterService, handleCheckedValue, NestedFilter } from '@iqser/common-ui'; import { MAT_CHECKBOX_DEFAULT_OPTIONS } from '@angular/material/checkbox'; import { BehaviorSubject, combineLatest, Observable, pipe } from 'rxjs'; import { delay, distinctUntilChanged, map, shareReplay } from 'rxjs/operators'; import { any } from '../../utils/operators'; +import { handleCheckedValue } from '../filter-utils'; +import { FilterService } from '../filter.service'; +import { FilterGroup } from '../models/filter-group.model'; +import { NestedFilter } from '../models/nested-filter.model'; const areExpandable = (nestedFilter: NestedFilter) => !!nestedFilter?.children?.length; const atLeastOneIsExpandable = pipe( diff --git a/src/lib/tables/table-header/table-header.component.html b/src/lib/tables/table-header/table-header.component.html index 5c8278b..e9862b2 100644 --- a/src/lib/tables/table-header/table-header.component.html +++ b/src/lib/tables/table-header/table-header.component.html @@ -12,7 +12,7 @@ - +