From fa73710f7a1be075768b3b22797c46aff8cd9da2 Mon Sep 17 00:00:00 2001 From: Hadi Khazaee Asl Date: Tue, 11 Aug 2026 22:10:12 +0330 Subject: [PATCH] last ... --- .gitmodules | 27 +- ContextGenerator.ps1 | 7 +- ContextGenerator.sh | 5 + ...ashboard-xApiSDKHolder-20260809_235725.txt | 10414 ++ .../xDashboard-xClient-20260810_005046.txt | 57058 +++++++++ ...meworkComponentsHolder-20260809_235642.txt | 96003 ++++++++++++++++ ...d-xFrameworkCoreHolder-20260809_235514.txt | 12920 +++ ...eworkIdentitySDKHolder-20260809_235714.txt | 8160 ++ ...eworkPushServiceHolder-20260809_235659.txt | 1074 + ...rameworkServicesHolder-20260809_235622.txt | 8256 ++ Documents/Scripts/nugetModuleHandler.js | 1048 + Documents/Scripts/x-color.tools.js | 506 + Documents/Scripts/x-file.tools.js | 887 + Documents/Scripts/x-shell.tools.js | 219 + Documents/Scripts/x-type-detector.tools.js | 268 + Documents/Scripts/x-value.tools.js | 1528 + Modules/xApiSDKHolder | 2 +- Modules/xCategoryService | 2 +- Modules/xCommons | 2 +- Modules/xDataService | 2 +- Modules/xEventService | 2 +- Modules/xExceptions | 2 +- Modules/xFileService | 2 +- Modules/xFrameworkComponentsHolder | 1 + Modules/xFrameworkCoreHolder | 1 + Modules/xFrameworkIdentitySDKHolder | 1 + Modules/xFrameworkPushServiceHolder | 1 + Modules/xFrameworkServicesHolder | 1 + Modules/xHttpService | 2 +- Modules/xIdentityModels | 2 +- Modules/xIdentityService | 2 +- Modules/xMessageService | 2 +- Modules/xModels | 2 +- Modules/xPushService | 2 +- Modules/xStorageService | 2 +- Modules/xStringService | 2 +- Modules/xTagService | 2 +- Modules/xTermsAndConditionsService | 2 +- Modules/xWebinarService | 2 +- package.json | 298 +- xClient | 2 +- 41 files changed, 198696 insertions(+), 25 deletions(-) create mode 100644 Documents/GeneratedContext/xDashboard-xApiSDKHolder-20260809_235725.txt create mode 100644 Documents/GeneratedContext/xDashboard-xClient-20260810_005046.txt create mode 100644 Documents/GeneratedContext/xDashboard-xFrameworkComponentsHolder-20260809_235642.txt create mode 100644 Documents/GeneratedContext/xDashboard-xFrameworkCoreHolder-20260809_235514.txt create mode 100644 Documents/GeneratedContext/xDashboard-xFrameworkIdentitySDKHolder-20260809_235714.txt create mode 100644 Documents/GeneratedContext/xDashboard-xFrameworkPushServiceHolder-20260809_235659.txt create mode 100644 Documents/GeneratedContext/xDashboard-xFrameworkServicesHolder-20260809_235622.txt create mode 100644 Documents/Scripts/nugetModuleHandler.js create mode 100644 Documents/Scripts/x-color.tools.js create mode 100644 Documents/Scripts/x-file.tools.js create mode 100644 Documents/Scripts/x-shell.tools.js create mode 100644 Documents/Scripts/x-type-detector.tools.js create mode 100644 Documents/Scripts/x-value.tools.js create mode 160000 Modules/xFrameworkComponentsHolder create mode 160000 Modules/xFrameworkCoreHolder create mode 160000 Modules/xFrameworkIdentitySDKHolder create mode 160000 Modules/xFrameworkPushServiceHolder create mode 160000 Modules/xFrameworkServicesHolder diff --git a/.gitmodules b/.gitmodules index 5ed9a87..a0ebb14 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,5 @@ -# xDashboard Modules +# +# xDashboard Modules ... [submodule "Modules/xExceptions"] path = Modules/xExceptions url = https://git.saherelm.ir/saherelm/xExceptions.git @@ -57,7 +58,26 @@ path = Modules/xAiService url = https://git.saherelm.ir/saherelm/xAiService.git -# xSaherElm Modules +# +# xFramework Modules ... +[submodule "Modules/xFrameworkCoreHolder"] + path = Modules/xFrameworkCoreHolder + url = https://git.saherelm.ir/saherelm/xFrameworkCoreHolder.git +[submodule "Modules/xFrameworkServicesHolder"] + path = Modules/xFrameworkServicesHolder + url = https://git.saherelm.ir/saherelm/xFrameworkServicesHolder.git +[submodule "Modules/xFrameworkComponentsHolder"] + path = Modules/xFrameworkComponentsHolder + url = https://git.saherelm.ir/saherelm/xFrameworkComponentsHolder.git +[submodule "Modules/xFrameworkPushServiceHolder"] + path = Modules/xFrameworkPushServiceHolder + url = https://git.saherelm.ir/saherelm/xFrameworkPushServiceHolder.git +[submodule "Modules/xFrameworkIdentitySDKHolder"] + path = Modules/xFrameworkIdentitySDKHolder + url = https://git.saherelm.ir/saherelm/xFrameworkIdentitySDKHolder.git + +# +# xSaherElm Modules ... [submodule "Modules/xIdentityHelper"] path = Modules/xIdentityHelper url = https://git.saherelm.ir/saherelm/xSaherElmIdentityHelper.git @@ -68,7 +88,8 @@ path = Modules/xApiSDKHolder url = https://git.saherelm.ir/saherelm/xSaherElmApiSDKHolder.git -# SaherElm Applications +# +# SaherElm Applications ... [submodule "xClient"] path = xClient url = https://git.saherelm.ir/saherelm/xSaherElmClient.git diff --git a/ContextGenerator.ps1 b/ContextGenerator.ps1 index b43aa78..90b19f7 100644 --- a/ContextGenerator.ps1 +++ b/ContextGenerator.ps1 @@ -32,10 +32,13 @@ Get-ChildItem -LiteralPath $searchPath -Recurse -File | Where-Object { $_.Name -eq 'Dockerfile' -or $_.Name -eq 'docker-compose.yaml' -or - $_.Extension -eq '.cs' + $_.Extension -eq '.cs' -or + $_.Extension -eq '.ts' -or + $_.Extension -eq '.html' -or + $_.Extension -eq '.scss' } | Where-Object { - $_.FullName -notmatch '[\\/](bin|obj)([\\/]|$)' + $_.FullName -notmatch '[\\/](bin|obj|dist|node_modules)([\\/]|$)' } | ForEach-Object { "`n### FILE: $($_.FullName)`n" diff --git a/ContextGenerator.sh b/ContextGenerator.sh index 72536b3..510f5b6 100644 --- a/ContextGenerator.sh +++ b/ContextGenerator.sh @@ -39,9 +39,14 @@ find "$SEARCH_PATH" -type f \( \ -name "Dockerfile" \ -o -name "docker-compose.yaml" \ -o -name "*.cs" \ + -o -name "*.ts" \ + -o -name "*.html" \ + -o -name "*.scss" \ \) \ -not -path "*/bin/*" \ -not -path "*/obj/*" \ +-not -path "*/dist/*" \ +-not -path "*/node_modules/*" \ -print0 | while IFS= read -r -d '' file; do printf '\n### FILE: %s\n\n' "$file" diff --git a/Documents/GeneratedContext/xDashboard-xApiSDKHolder-20260809_235725.txt b/Documents/GeneratedContext/xDashboard-xApiSDKHolder-20260809_235725.txt new file mode 100644 index 0000000..f1d8841 --- /dev/null +++ b/Documents/GeneratedContext/xDashboard-xApiSDKHolder-20260809_235725.txt @@ -0,0 +1,10414 @@ + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\public-api.ts + +/* + * Public API Surface of x-framework-api-sdk + */ +// +// Base ... +export * from './lib/base/x-base.guard'; +export * from './lib/base/x-base-entity.service'; +export * from './lib/base/i-x-entity-actions.interface'; + +// +// Config ... +export * from './lib/config/x-saherelm-api-sdk.config'; + +// +// Models ... +export * from './lib/models/x-person.dto'; +export * from './lib/models/x-reference.dto'; + +// +// Tokens ... +export * from './lib/tokens/x-injectable-tokens'; + +// +// Tools ... +export * from './lib/tools/x-role.tools'; +export * from './lib/tools/x-model.tools'; +export * from './lib/tools/x-route.tools'; + +// +// Services ... +export * from './lib/services/x-push-token-fetcher.service'; + +// +// Typings ... +export * from './lib/typings/x-role.typings'; +export * from './lib/strings/x-string.typings'; +export * from './lib/typings/x-endpoint.typings'; +export * from './lib/typings/x-push-provider.typings'; + +// +// Guards ... +export * from './lib/guards/user.guard'; +export * from './lib/guards/admin.guard'; +export * from './lib/guards/agent.guard'; +export * from './lib/guards/admin-or-agent.guard'; + +// +// Strings ... +export * from './lib/strings/x-string.typings'; +export * from './lib/strings/x-string.services'; +export * from './lib/strings/models/x-string.dto'; +export * from './lib/configurations/x-configurations.service'; +export * from './lib/strings/services/x-string.entity.service'; +export * from './lib/strings/services/x-string.provider.service'; +export * from './lib/strings/services/x-string.entity.push.service'; + +// +// Terms ... +export * from './lib/terms/x-terms.service'; +export * from './lib/terms/x-terms.typings.dto'; +export * from './lib/terms/services/x-terms.push.service'; +export * from './lib/terms/services/x-terms.provider.service'; + +// +// Tags ... +export * from './lib/tags/x-tag.typings'; +export * from './lib/tags/x-tag.services'; +export * from './lib/tags/models/x-tag.dto'; +export * from './lib/tags/services/x-tag.entity.service'; +export * from './lib/tags/services/x-tag.provider.service'; +export * from './lib/tags/services/x-tag.entity.push.service'; + +// +// Files ... +export * from './lib/files/x-file.typings'; +export * from './lib/files/x-file.services'; +export * from './lib/files/models/x-file.dto'; +export * from './lib/files/services/x-file.entity.service'; +export * from './lib/files/services/x-file.provider.service'; +export * from './lib/files/services/x-file.entity.push.service'; + +// +// Webinars ... +export * from './lib/webinars/x-webinar.typings'; +export * from './lib/webinars/x-webinar.services'; +export * from './lib/webinars/models/x-webinar.dto'; +export * from './lib/webinars/services/x-webinar.push.service'; +export * from './lib/webinars/services/x-webinar.entity.service'; +export * from './lib/webinars/services/x-webinar.provider.service'; +export * from './lib/webinars/services/x-webinar.entity.push.service'; +export * from './lib/webinars/services/x-webinarsubscriber.entity.service'; + +// +// Module Service ... +export * from './lib/x-saherelm-api.service'; + +// +// Module ... +export * from './lib/x-saherelm-api-sdk.module'; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\lib\x-saherelm-api-sdk.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { HttpClientModule } from '@angular/common/http'; +import { XFrameworkCoreModule } from 'x-framework-core'; +import { XFrameworkServicesModule } from 'x-framework-services'; +import { XFrameworkIdentitySdkModule } from 'x-framework-identity-sdk'; + +@NgModule({ + declarations: [], + imports: [ + CommonModule, + HttpClientModule, + XFrameworkCoreModule, + XFrameworkServicesModule, + XFrameworkIdentitySdkModule, + ], + exports: [ + CommonModule, + HttpClientModule, + XFrameworkCoreModule, + XFrameworkServicesModule, + XFrameworkIdentitySdkModule, + ], + providers: [], +}) +export class XSaherElmApiSdkModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\lib\x-saherelm-api.service.ts + +import { XLoggable } from 'x-framework-core'; +import { Inject, Injectable } from '@angular/core'; +import { XTagServices } from './tags/x-tag.services'; +import { XFileServices } from './files/x-file.services'; +import { XTermsServices } from './terms/x-terms.service'; +import { XStringServices } from './strings/x-string.services'; +import { XWebinarServices } from './webinars/x-webinar.services'; +import { X_SAHERELM_API_SDK_CONFIG } from './tokens/x-injectable-tokens'; +import { XSaherElmApiSDKConfig } from './config/x-saherelm-api-sdk.config'; +import { XConfigurationsService } from './configurations/x-configurations.service'; + +@Injectable({ + providedIn: 'root', +}) +export class XSaherElmAPIService extends XLoggable { + // + //#region Constructor ... + constructor( + public tags: XTagServices, + public files: XFileServices, + public terms: XTermsServices, + public strings: XStringServices, + public webinars: XWebinarServices, + @Inject(X_SAHERELM_API_SDK_CONFIG) + public config: XSaherElmApiSDKConfig, + public configurationService: XConfigurationsService + ) { + super(config); + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\lib\base\i-x-entity-actions.interface.ts + +import { Observable } from 'rxjs'; +import { XBaseRangeRequestDto } from 'x-framework-core'; +import { HttpEvent, HttpResponse } from '@angular/common/http'; +import { XQueryDto, XQueryResultDto } from 'x-framework-identity-sdk'; + +export interface IXEntityActions { + // + //#region Repository Implementation ... + // + //#region Retrieve ... + // + //#region Get ... + /** + * Retrieve Specified Item based on Id + * + * @param id the Object ID ... + * @param containsDetail specify retrieve full details + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + callGet( + id: TKey, + containsDetail?: boolean, + observe?: 'body', + reportProgress?: boolean + ): Observable; + callGet( + id: TKey, + containsDetail?: boolean, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + callGet( + id: TKey, + containsDetail?: boolean, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + callGet( + id: TKey, + containsDetail?: boolean, + observe?: any, + reportProgress?: boolean + ): Observable; + //#endregion + + // + //#region GetAll ... + /** + * Retrieve all Items + * + * @param containsDetail specify retrieve full details + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + callGetAll( + containsDetail?: boolean, + observe?: 'body', + reportProgress?: boolean + ): Observable>; + callGetAll( + containsDetail?: boolean, + observe?: 'response', + reportProgress?: boolean + ): Observable>>; + callGetAll( + containsDetail?: boolean, + observe?: 'events', + reportProgress?: boolean + ): Observable>>; + callGetAll( + containsDetail?: boolean, + observe?: any, + reportProgress?: boolean + ): Observable; + //#endregion + + // + //#region FindOne ... + /** + * Fine first Item which matched to the query string + * + * @param query search pattern + * @param containsDetail specify retrieve full details + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + callFindOne( + query: string, + containsDetail?: boolean, + observe?: 'body', + reportProgress?: boolean + ): Observable; + callFindOne( + query: string, + containsDetail?: boolean, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + callFindOne( + query: string, + containsDetail?: boolean, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + callFindOne( + query: string, + containsDetail?: boolean, + observe?: any, + reportProgress?: boolean + ): Observable; + //#endregion + + // + //#region FindMany ... + /** + * Find a Collection of items which matched to Specified Query string + * + * @param query search pattern + * @param containsDetail specify retrieve full details + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + callFindMany( + query: string, + containsDetail?: boolean, + observe?: 'body', + reportProgress?: boolean + ): Observable>; + callFindMany( + query: string, + containsDetail?: boolean, + observe?: 'response', + reportProgress?: boolean + ): Observable>>; + callFindMany( + query: string, + containsDetail?: boolean, + observe?: 'events', + reportProgress?: boolean + ): Observable>>; + callFindMany( + query: string, + containsDetail?: boolean, + observe?: any, + reportProgress?: boolean + ): Observable; + //#endregion + + // + //#region Query ... + /** + * Retrieve Items based on XQuery model + * + * @param query specify data projection attributes + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + callQuery( + query: XQueryDto, + observe?: 'body', + reportProgress?: boolean + ): Observable>; + callQuery( + query: XQueryDto, + observe?: 'response', + reportProgress?: boolean + ): Observable>>; + callQuery( + query: XQueryDto, + observe?: 'events', + reportProgress?: boolean + ): Observable>>; + callQuery( + query: XQueryDto, + observe?: any, + reportProgress?: boolean + ): Observable; + //#endregion + //#endregion + + // + //#region Add ... + // + //#region Add ... + /** + * Add New Item + * + * @param body the new Content which requires to add + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + callAdd( + body: TModel, + connectionId?: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + callAdd( + body: TModel, + connectionId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + callAdd( + body: TModel, + connectionId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + callAdd( + body: TModel, + connectionId?: string, + observe?: any, + reportProgress?: boolean + ): Observable; + //#endregion + + // + //#region AddOrUpdate ... + /** + * Add New Item or Update Exists Item + * + * @param body hte instance of Entity + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + callAddOrUpdate( + body: TModel, + connectionId?: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + callAddOrUpdate( + body: TModel, + connectionId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + callAddOrUpdate( + body: TModel, + connectionId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + callAddOrUpdate( + body: TModel, + connectionId?: string, + observe?: any, + reportProgress?: boolean + ): Observable; + //#endregion + + // + //#region AddMany ... + /** + * Add a Collection of items + * + * @param body an instance of XBaseRangeRequest which contains a collection of items + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + callAddMany( + body: XBaseRangeRequestDto, + connectionId?: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + callAddMany( + body: XBaseRangeRequestDto, + connectionId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + callAddMany( + body: XBaseRangeRequestDto, + connectionId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + callAddMany( + body: XBaseRangeRequestDto, + connectionId?: string, + observe?: any, + reportProgress?: boolean + ): Observable; + //#endregion + //#endregion + + // + //#region Update ... + // + //#region Update ... + /** + * Update Specified Item + * + * @param body the instance with all changes + * @param id the Id of Object which are going to replace or update with this + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + callUpdate( + id: TKey, + body: TModel, + connectionId?: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + callUpdate( + id: TKey, + body: TModel, + connectionId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + callUpdate( + id: TKey, + body: TModel, + connectionId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + callUpdate( + id: TKey, + body: TModel, + connectionId?: string, + observe?: any, + reportProgress?: boolean + ): Observable; + //#endregion + + // + //#region UpdateMany ... + /** + * Update a Collection of Items + * + * @param body an instance of XBaseRangeRequestDto + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + callUpdateMany( + body: XBaseRangeRequestDto, + connectionId?: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + callUpdateMany( + body: XBaseRangeRequestDto, + connectionId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + callUpdateMany( + body: XBaseRangeRequestDto, + connectionId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + callUpdateMany( + body: XBaseRangeRequestDto, + connectionId?: string, + observe?: any, + reportProgress?: boolean + ): Observable; + //#endregion + //#endregion + + // + //#region Exists ... + /** + * Check an items is Exists or not + * + * @param id the id of item + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + callIsExists( + id: TKey, + observe?: 'body', + reportProgress?: boolean + ): Observable; + callIsExists( + id: TKey, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + callIsExists( + id: TKey, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + callIsExists( + id: TKey, + observe?: any, + reportProgress?: boolean + ): Observable; + //#endregion + + // + //#region Remove ... + // + //#region Remove ... + /** + * Remove Specified item by its ID + * + * @param id pointer to specific instance + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + callRemove( + id: TKey, + connectionId?: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + callRemove( + id: TKey, + connectionId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + callRemove( + id: TKey, + connectionId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + callRemove( + id: TKey, + connectionId?: string, + observe?: any, + reportProgress?: boolean + ): Observable; + //#endregion + + // + //#region RemoveMany ... + /** + * Remove a Collection of Items base on RangeRequest + * + * @param body an instance of XBaseRangeRequestDto + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + callRemoveMany( + body: XBaseRangeRequestDto, + connectionId?: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + callRemoveMany( + body: XBaseRangeRequestDto, + connectionId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + callRemoveMany( + body: XBaseRangeRequestDto, + connectionId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + callRemoveMany( + body: XBaseRangeRequestDto, + connectionId?: string, + observe?: any, + reportProgress?: boolean + ): Observable; + //#endregion + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\lib\base\x-base-entity.service.ts + +import { + XQueryDto, + XQueryResultDto, + XBaseApiService, +} from 'x-framework-identity-sdk'; +import { Observable } from 'rxjs'; +import { + XBaseEndPointAction, + XBaseEndPointParam, +} from '../typings/x-endpoint.typings'; +import { IXEntityActions } from './i-x-entity-actions.interface'; +import { HttpResponse, HttpEvent, HttpParams } from '@angular/common/http'; +import { applyConnectionIdToHeader } from '../typings/x-push-provider.typings'; +import { toXString, XBaseRangeRequestDto, XValidators } from 'x-framework-core'; + +export abstract class XBaseEntityService + extends XBaseApiService + implements IXEntityActions +{ + // + //#region Repository Implementation ... + // + //#region Retrieve ... + // + //#region Get ... + public callGet( + id: TKey, + containsDetail?: boolean, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public callGet( + id: TKey, + containsDetail?: boolean, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public callGet( + id: TKey, + containsDetail?: boolean, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public callGet( + id: TKey, + containsDetail?: boolean, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotNull(id); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XBaseEndPointAction, + XBaseEndPointParam + >(XBaseEndPointAction.Get); + + // + // Preparing HttpParams ... + let params: HttpParams = null; + if (!!containsDetail) { + params = this.addContainsDetailQueryParam(params, containsDetail); + } + + // + // Prepare Result ... + return this.httpClient.get(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region GetAll ... + public callGetAll( + containsDetail?: boolean, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public callGetAll( + containsDetail?: boolean, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public callGetAll( + containsDetail?: boolean, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public callGetAll( + containsDetail?: boolean, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XBaseEndPointAction, + XBaseEndPointParam + >(XBaseEndPointAction.GetAll); + + // + // Preparing HttpParams ... + let params: HttpParams = null; + if (!!containsDetail) { + params = this.addContainsDetailQueryParam(params, containsDetail); + } + + // + // Prepare Result ... + return this.httpClient.get>(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region FindOne ... + public callFindOne( + query: string, + containsDetail?: boolean, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public callFindOne( + query: string, + containsDetail?: boolean, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public callFindOne( + query: string, + containsDetail?: boolean, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public callFindOne( + query: string, + containsDetail?: boolean, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotNull(query); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XBaseEndPointAction, + XBaseEndPointParam + >(XBaseEndPointAction.FindOne, { + key: XBaseEndPointParam.XQuery, + value: query, + }); + + // + // Preparing HttpParams ... + let params: HttpParams = null; + if (!!containsDetail) { + params = this.addContainsDetailQueryParam(params, containsDetail); + } + + // + // Prepare Result ... + return this.httpClient.get(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region FindMany ... + public callFindMany( + query: string, + containsDetail?: boolean, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public callFindMany( + query: string, + containsDetail?: boolean, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public callFindMany( + query: string, + containsDetail?: boolean, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public callFindMany( + query: string, + containsDetail?: boolean, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XBaseEndPointAction, + XBaseEndPointParam + >(XBaseEndPointAction.FindMany, { + key: XBaseEndPointParam.XQuery, + value: query, + }); + + // + // Preparing HttpParams ... + let params: HttpParams = null; + if (!!containsDetail) { + params = this.addContainsDetailQueryParam(params, containsDetail); + } + + // + // Prepare Result ... + return this.httpClient.get>(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region Query ... + public callQuery( + query: XQueryDto, + observe?: 'body', + reportProgress?: boolean + ): Observable>; + public callQuery( + query: XQueryDto, + observe?: 'response', + reportProgress?: boolean + ): Observable>>; + public callQuery( + query: XQueryDto, + observe?: 'events', + reportProgress?: boolean + ): Observable>>; + public callQuery( + query: XQueryDto, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotNull(query); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XBaseEndPointAction, + XBaseEndPointParam + >(XBaseEndPointAction.Query); + + // + // Preparing HttpParams ... + const params: HttpParams = this.generateXQueryHttpParams(query); + + // + // Prepare Result ... + return this.httpClient.get>(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + //#endregion + + // + //#region Add ... + // + //#region Add ... + public callAdd( + body: TModel, + connectionId?: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public callAdd( + body: TModel, + connectionId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public callAdd( + body: TModel, + connectionId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public callAdd( + body: TModel, + connectionId?: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotNull(body); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + headers = applyConnectionIdToHeader(connectionId, headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XBaseEndPointAction, + XBaseEndPointParam + >(XBaseEndPointAction.Add); + + // + // Prepare Result ... + return this.httpClient.post(endPointPath, body, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region AddOrUpdate ... + public callAddOrUpdate( + body: TModel, + connectionId?: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public callAddOrUpdate( + body: TModel, + connectionId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public callAddOrUpdate( + body: TModel, + connectionId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public callAddOrUpdate( + body: TModel, + connectionId?: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotNull(body); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + headers = applyConnectionIdToHeader(connectionId, headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XBaseEndPointAction, + XBaseEndPointParam + >(XBaseEndPointAction.AddOrUpdate); + + // + // Prepare Result ... + return this.httpClient.post(endPointPath, body, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region AddMany ... + public callAddMany( + body: XBaseRangeRequestDto, + connectionId?: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public callAddMany( + body: XBaseRangeRequestDto, + connectionId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public callAddMany( + body: XBaseRangeRequestDto, + connectionId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public callAddMany( + body: XBaseRangeRequestDto, + connectionId?: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotNull(body); + XValidators.validateHasChilds(body.items); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + headers = applyConnectionIdToHeader(connectionId, headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XBaseEndPointAction, + XBaseEndPointParam + >(XBaseEndPointAction.AddMany); + + // + // Prepare Result ... + return this.httpClient.post(endPointPath, body, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + //#endregion + + // + //#region Update ... + // + //#region Update ... + public callUpdate( + id: TKey, + body: TModel, + connectionId?: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public callUpdate( + id: TKey, + body: TModel, + connectionId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public callUpdate( + id: TKey, + body: TModel, + connectionId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public callUpdate( + id: TKey, + body: TModel, + connectionId?: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotNull(id); + XValidators.validateNotNull(body); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + headers = applyConnectionIdToHeader(connectionId, headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XBaseEndPointAction, + XBaseEndPointParam + >(XBaseEndPointAction.Update, { + key: XBaseEndPointParam.XId, + value: toXString(id), + }); + + // + // Prepare Result ... + return this.httpClient.put(endPointPath, body, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region UpdateMany ... + public callUpdateMany( + body: XBaseRangeRequestDto, + connectionId?: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public callUpdateMany( + body: XBaseRangeRequestDto, + connectionId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public callUpdateMany( + body: XBaseRangeRequestDto, + connectionId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public callUpdateMany( + body: XBaseRangeRequestDto, + connectionId?: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotNull(body); + XValidators.validateHasChilds(body.items); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + headers = applyConnectionIdToHeader(connectionId, headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XBaseEndPointAction, + XBaseEndPointParam + >(XBaseEndPointAction.UpdateMany); + + // + // Prepare Result ... + return this.httpClient.post(endPointPath, body, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + //#endregion + + // + //#region Exists ... + public callIsExists( + id: TKey, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public callIsExists( + id: TKey, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public callIsExists( + id: TKey, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public callIsExists( + id: TKey, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotNull(id); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XBaseEndPointAction, + XBaseEndPointParam + >(XBaseEndPointAction.IsExists, { + key: XBaseEndPointParam.XId, + value: toXString(id), + }); + + // + // Prepare Result ... + return this.httpClient.get(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region Remove ... + // + //#region Remove ... + public callRemove( + id: TKey, + connectionId?: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public callRemove( + id: TKey, + connectionId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public callRemove( + id: TKey, + connectionId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public callRemove( + id: TKey, + connectionId?: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotNull(id); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + headers = applyConnectionIdToHeader(connectionId, headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XBaseEndPointAction, + XBaseEndPointParam + >(XBaseEndPointAction.Remove, { + key: XBaseEndPointParam.XId, + value: toXString(id), + }); + + // + // Prepare Result ... + return this.httpClient.delete(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region RemoveMany ... + public callRemoveMany( + body: XBaseRangeRequestDto, + connectionId?: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public callRemoveMany( + body: XBaseRangeRequestDto, + connectionId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public callRemoveMany( + body: XBaseRangeRequestDto, + connectionId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public callRemoveMany( + body: XBaseRangeRequestDto, + connectionId?: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotNull(body); + XValidators.validateHasChilds(body.items); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + headers = applyConnectionIdToHeader(connectionId, headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XBaseEndPointAction, + XBaseEndPointParam + >(XBaseEndPointAction.RemoveMany); + + // + // Prepare Result ... + return this.httpClient.post(endPointPath, body, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\lib\base\x-base.guard.ts + +import { + Route, + CanLoad, + UrlTree, + UrlSegment, + CanActivate, + CanActivateChild, + RouterStateSnapshot, + ActivatedRouteSnapshot, +} from '@angular/router'; +import { Observable } from 'rxjs'; +import { + XAccountService, + XFrameworkIdentitySDKConfig, + X_FRAMEWORK_IDENTITY_SDK_CONFIG, +} from 'x-framework-identity-sdk'; +import { filter, map, tap } from 'rxjs/operators'; +import { Inject, Injectable } from '@angular/core'; +import { XManagerService } from 'x-framework-services'; +import { XAvailableRoles } from '../typings/x-role.typings'; +import { checkIsInRoleByInfo, toRoleInfo } from '../tools/x-role.tools'; +import { isNullOrUndefined, XExceptionIDs, XLoggable, XOneOrManyType } from 'x-framework-core'; + +@Injectable({ + providedIn: 'root', +}) +export abstract class XBaseGuard + extends XLoggable + implements CanActivate, CanActivateChild, CanLoad +{ + // + abstract roles: XOneOrManyType; + + // + constructor( + private authService: XAccountService, + private managerService: XManagerService, + @Inject(X_FRAMEWORK_IDENTITY_SDK_CONFIG) + public config: XFrameworkIdentitySDKConfig + ) { + super(config); + } + + // + //#region Actions ... + // + canActivate( + next: ActivatedRouteSnapshot, + state: RouterStateSnapshot + ): + | Observable + | Promise + | boolean + | UrlTree { + return this.handleGuard(); + } + + // + canActivateChild( + next: ActivatedRouteSnapshot, + state: RouterStateSnapshot + ): + | Observable + | Promise + | boolean + | UrlTree { + return this.handleGuard(); + } + + // + canLoad( + route: Route, + segments: UrlSegment[] + ): Observable | Promise | boolean { + return this.handleGuard(); + } + //#endregion + + // + //#region Private Action ... + private handleGuard(): Observable { + return this.authService.state$.pipe( + filter( + (state) => + !isNullOrUndefined(state) && !!state.isLoggedIn && !!state.profile + ), + map((state) => state.profile), + map((user) => toRoleInfo(user)), + map((roleInfo) => checkIsInRoleByInfo(this.roles, roleInfo)), + tap((isPassed) => { + // + if (!isPassed) { + this.handleGuardReject(); + } + }) + ); + } + + // + private handleGuardReject(): void { + // + this.managerService.notificationService.presentErrorNotification({ + exception: XExceptionIDs.NotAllowed, + dissmissable: true, + }); + + // + this.managerService.navigateByPage(this.config.Pages.Login); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\lib\config\x-saherelm-api-sdk.config.ts + +import { XFrameworkCoreConfig } from 'x-framework-core'; +import { XFrameworkServicesConfig } from 'x-framework-services'; +import { XFrameworkIdentitySDKConfig } from 'x-framework-identity-sdk'; + +export type XSharedConfig = XFrameworkCoreConfig & + XFrameworkServicesConfig & + XFrameworkIdentitySDKConfig & { [key: string]: any }; + +// tslint:disable-next-line:no-empty-interface +export interface XSaherElmApiSDKConfig extends XSharedConfig { + // + // Push Service Configuration ... + pushMessageAction: string; + pushServiceRoute: string; + pushManagerReconnectDelay: number; + + // + corsBypasserProxy: string; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\lib\configurations\x-configurations.service.ts + +import { HttpClient } from '@angular/common/http'; +import { Injectable, Inject } from '@angular/core'; +import { XApiEndPoint } from '../typings/x-endpoint.typings'; +import { X_SAHERELM_API_SDK_CONFIG } from '../tokens/x-injectable-tokens'; +import { XSaherElmApiSDKConfig } from '../config/x-saherelm-api-sdk.config'; +import { XApiConfiguration, XBaseApiService, X_API_CONFIG } from 'x-framework-identity-sdk'; + +@Injectable({ + providedIn: 'root', +}) +export class XConfigurationsService extends XBaseApiService { + // + //#region Constructor ... + constructor( + protected httpClient: HttpClient, + @Inject(X_SAHERELM_API_SDK_CONFIG) config: XSaherElmApiSDKConfig, + @Inject(X_API_CONFIG) apiConfig: XApiConfiguration + ) { + // + super( + XApiEndPoint.Configurations, + true, + true, + httpClient, + config, + apiConfig + ); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\lib\files\x-file.services.ts + +import { Injectable } from '@angular/core'; +import { XFileEntityService } from './services/x-file.entity.service'; +import { XFileProviderService } from './services/x-file.provider.service'; +import { XFileEntityPushService } from './services/x-file.entity.push.service'; + +/** + * Export All Provided File related Services ... + */ +@Injectable({ + providedIn: 'root', +}) +export class XFileServices { + // + //#region Constructor ... + public constructor( + public fileEntityService: XFileEntityService, + public fileProviderService: XFileProviderService, + public fileEntityPushService: XFileEntityPushService + ) {} + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\lib\files\x-file.typings.ts + +/** + * All Available EndPoint Routes ... + */ +export enum XFileServiceRoute { + All = 'All', + Upload = '', + Remove = '', + Get = '{id}', + Query = 'Query', + GetTags = '{id}/Tags', + QueryOwned = 'Query/Owned', + StreamById = '{id}/Stream/ById', + AttachTags = '{id}/Tags/AttachMany', + DetachTags = '{id}/Tags/DetachMany', + DownloadById = '{id}/Download/ById', + StreamByName = '{name}/Stream/ByName', + DownloadByName = '{id}/Download/ByName', +} + +/** + * All Params Which Used in Related Services ... + */ +export enum XFileParam { + Id = 'id', + Ids = 'ids', + Tags = 'tags', + Name = 'name', + Files = 'files', +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\lib\files\models\x-file.dto.ts + +import { + XBaseDto, + hasChild, + XFileType, + typeGuard, + isSameObject, + isNullOrUndefined, + isNullOrEmptyString, +} from 'x-framework-core'; +import { + XReferenceDto, + parseXReferences, + references2String, +} from '../../models/x-reference.dto'; +import { XPersonDto } from '../../models/x-person.dto'; + +/** + * a Model for Describing a File ... + */ +export interface XFileEntity extends XBaseDto { + id: string; + type: XFileType; + name: string; + thumb?: string; + path: string; + thumbPath?: string; + uploadedOn: Date; + fileName?: string; + ownerId: string; + references: string; +} + +/** + * a Model for Describing a File ... + */ +export interface XFileDto extends XBaseDto { + id: string; + type: XFileType; + name: string; + thumb?: string; + path: string; + thumbPath?: string; + uploadedOn: Date; + fileName?: string; + ownerId: string; + owner?: XPersonDto; + references: Array>; +} + +/** + * Default model ... + */ +export const defaultFileDto: XFileDto = { + id: '', + name: '', + path: '', + ownerId: '', + references: [], + type: undefined, + thumb: undefined, + owner: undefined, + fileName: undefined, + thumbPath: undefined, + uploadedOn: undefined, +}; + +/** + * Preparing Dto Field ... + * + * @param model + */ +export function prepareFileDtoFields(model?: XFileDto) { + // + if (isNullOrUndefined(model)) { + // + model = { + ...defaultFileDto, + }; + } + + // + model = { + id: !isNullOrEmptyString(model.id) ? model.id : defaultFileDto.id, + name: !isNullOrEmptyString(model.name) ? model.name : defaultFileDto.name, + path: !isNullOrEmptyString(model.path) ? model.path : defaultFileDto.path, + ownerId: !isNullOrEmptyString(model.ownerId) + ? model.ownerId + : defaultFileDto.ownerId, + references: + !isNullOrUndefined(model.references) && !hasChild(model.references) + ? [...model.references] + : defaultFileDto.references, + type: !isNullOrUndefined(model.type) ? model.type : defaultFileDto.type, + thumb: !isNullOrEmptyString(model.thumb) + ? model.thumb + : defaultFileDto.thumb, + owner: !isNullOrUndefined(model.owner) + ? { ...model.owner } + : defaultFileDto.owner, + fileName: !isNullOrEmptyString(model.fileName) + ? model.fileName + : defaultFileDto.fileName, + thumbPath: !isNullOrEmptyString(model.thumbPath) + ? model.thumbPath + : defaultFileDto.thumbPath, + uploadedOn: !isNullOrUndefined(model.uploadedOn) + ? model.uploadedOn + : defaultFileDto.uploadedOn, + }; + + // + return model; +} + +/** + * Check Model is Default or not ... + * + * @param model + */ +export function isDefaultFileDto(model?: XFileDto) { + return isSameObject(prepareFileDtoFields(model), defaultFileDto); +} + +/** + * Model Type Guard ... + */ +export const isXFileDto = typeGuard([ + 'id', + 'name', + 'ownerId', + 'type', + 'uploadedOn', + 'path', +]); + +/** + * Converts a FileDto to Entity ... + * + * @param value + * @returns + */ +export function fileDtoToEntity(value: XFileDto) { + // + let result: XFileEntity = undefined; + + // + if (!isNullOrUndefined(value) && isXFileDto(value)) { + // + const references = references2String(value.references); + result = { + id: value.id, + type: value.type, + name: value.name, + thumb: value.thumb, + path: value.path, + thumbPath: value.thumbPath, + uploadedOn: value.uploadedOn, + fileName: value.fileName, + ownerId: value.ownerId, + references, + }; + } + + // + return result; +} + +/** + * Converts a FileEntity to Dto ... + * + * @param value + * @param owner + * @returns + */ +export function fileEntityToDto( + value: XFileEntity, + owner: XPersonDto = undefined +) { + // + let result: XFileDto = undefined; + + // + if (!isNullOrUndefined(value) && isXFileDto(value)) { + // + const references = parseXReferences(value.references); + result = { + id: value.id, + type: value.type, + name: value.name, + thumb: value.thumb, + path: value.path, + thumbPath: value.thumbPath, + uploadedOn: value.uploadedOn, + fileName: value.fileName, + ownerId: value.ownerId, + owner, + references, + }; + } + + // + return result; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\lib\files\services\x-file.entity.push.service.ts + +import { Injectable } from "@angular/core"; +import { XFileEntity } from "../models/x-file.dto"; +import { XBaseEntityPushService } from "x-framework-push-service"; +import { HttpTransportType, ITransport } from "@microsoft/signalr"; + +@Injectable({ + providedIn: 'root' +}) +export class XFileEntityPushService extends XBaseEntityPushService { + // + //#region Abstracts ... + hubName(): string { + return 'fileEntityHub'; + } + + requiredAccessToken(): boolean { + return true; + } + + transportType(): HttpTransportType | ITransport { + return HttpTransportType.LongPolling; + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\lib\files\services\x-file.entity.service.ts + +import { Router } from '@angular/router'; +import { HttpClient } from '@angular/common/http'; +import { Inject, Injectable } from '@angular/core'; +import { XFileEntity } from '../models/x-file.dto'; +import { XApiEndPoint } from '../../typings/x-endpoint.typings'; +import { XBaseEntityService } from '../../base/x-base-entity.service'; +import { X_API_CONFIG, XApiConfiguration } from 'x-framework-identity-sdk'; +import { X_SAHERELM_API_SDK_CONFIG } from '../../tokens/x-injectable-tokens'; +import { XSaherElmApiSDKConfig } from '../../config/x-saherelm-api-sdk.config'; + +/** + * Provides all Entity Actions as a Service ... + */ +@Injectable({ + providedIn: 'root', +}) +export class XFileEntityService extends XBaseEntityService< + XFileEntity, + string +> { + // + //#region Constructor ... + constructor( + protected router: Router, + protected httpClient: HttpClient, + @Inject(X_SAHERELM_API_SDK_CONFIG) config: XSaherElmApiSDKConfig, + @Inject(X_API_CONFIG) apiConfig: XApiConfiguration + ) { + super( + XApiEndPoint.FilesEntity, + true, + true, + httpClient, + config, + apiConfig + ); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\lib\files\services\x-file.provider.service.ts + +import { + toArray, + hasChild, + XValidators, + XOneOrManyType, + isNullOrEmptyString, +} from 'x-framework-core'; +import { + HttpEvent, + HttpParams, + HttpClient, + HttpHeaders, + HttpResponse, +} from '@angular/common/http'; +import { + XQueryDto, + X_API_CONFIG, + XBaseApiService, + XQueryResultDto, + XApiConfiguration, +} from 'x-framework-identity-sdk'; +import { Observable } from 'rxjs'; +import { Router } from '@angular/router'; +import { XFileDto } from '../models/x-file.dto'; +import { Inject, Injectable } from '@angular/core'; +import { fillRouteParams } from '../../tools/x-route.tools'; +import { XApiEndPoint } from '../../typings/x-endpoint.typings'; +import { XFileParam, XFileServiceRoute } from '../x-file.typings'; +import { X_SAHERELM_API_SDK_CONFIG } from '../../tokens/x-injectable-tokens'; +import { XSaherElmApiSDKConfig } from '../../config/x-saherelm-api-sdk.config'; +import { applyConnectionIdToHeader } from '../../typings/x-push-provider.typings'; + +/** + * Provides all Provider Actions as a Service ... + */ +@Injectable({ + providedIn: 'root', +}) +export class XFileProviderService extends XBaseApiService { + // + //#region Constructor ... + constructor( + protected router: Router, + protected httpClient: HttpClient, + @Inject(X_SAHERELM_API_SDK_CONFIG) config: XSaherElmApiSDKConfig, + @Inject(X_API_CONFIG) apiConfig: XApiConfiguration + ) { + super(XApiEndPoint.FilesService, true, true, httpClient, config, apiConfig); + } + //#endregion + + // + //#region Tools ... + // + //#region StreamById ... + /** + * Stream Specified File ... + * + * @param id + * @param observe + * @param reportProgress + */ + public streamById( + id: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public streamById( + id: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public streamById( + id: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public streamById( + id: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(id); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + + // + // Prepare Endpoint + const endPointPath = fillRouteParams( + // + // Base Route on Endpoint ... + this.getActionRoute(XFileServiceRoute.StreamById), + // + // Params ... + [ + { + key: XFileParam.Id, + value: id, + }, + ] + ); + + // + // Prepare Result ... + return this.httpClient.get(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + responseType: 'arraybuffer', + reportProgress, + }); + } + //#endregion + + // + //#region StreamByName ... + /** + * Stream Specified File ... + * + * @param name + * @param observe + * @param reportProgress + */ + public streamByName( + name: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public streamByName( + name: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public streamByName( + name: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public streamByName( + name: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(name); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + + // + // Prepare Endpoint + const endPointPath = fillRouteParams( + // + // Base Route on Endpoint ... + this.getActionRoute(XFileServiceRoute.StreamByName), + // + // Params ... + [ + { + key: XFileParam.Name, + value: name, + }, + ] + ); + + // + // Prepare Result ... + return this.httpClient.get(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + responseType: 'arraybuffer', + reportProgress, + }); + } + //#endregion + + // + //#region DownloadById ... + /** + * Download Specified File ... + * + * @param id + * @param observe + * @param reportProgress + */ + public downloadById( + id: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public downloadById( + id: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public downloadById( + id: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public downloadById( + id: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(id); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + + // + // Prepare Endpoint + const endPointPath = fillRouteParams( + // + // Base Route on Endpoint ... + this.getActionRoute(XFileServiceRoute.DownloadById), + // + // Params ... + [ + { + key: XFileParam.Id, + value: id, + }, + ] + ); + + // + // Prepare Result ... + return this.httpClient.get(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + responseType: 'blob', + reportProgress, + }); + } + //#endregion + + // + //#region DownloadByName ... + /** + * Download Specified File ... + * + * @param name + * @param observe + * @param reportProgress + */ + public downloadByName( + name: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public downloadByName( + name: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public downloadByName( + name: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public downloadByName( + name: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(name); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + + // + // Prepare Endpoint + const endPointPath = fillRouteParams( + // + // Base Route on Endpoint ... + this.getActionRoute(XFileServiceRoute.DownloadByName), + // + // Params ... + [ + { + key: XFileParam.Name, + value: name, + }, + ] + ); + + // + // Prepare Result ... + return this.httpClient.get(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + responseType: 'blob', + reportProgress, + }); + } + //#endregion + + // + //#region Upload ... + /** + * Upload ... + * + * @param files + * @param connectionId + * @param observe + * @param reportProgress + */ + public upload( + files: XOneOrManyType, + connectionId?: string, + observe?: 'body', + reportProgress?: boolean + ): Observable>; + public upload( + files: XOneOrManyType, + connectionId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>>; + public upload( + files: XOneOrManyType, + connectionId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>>; + public upload( + files: XOneOrManyType, + connectionId?: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotNull(files); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Preparing Form Data ... + let formData = new FormData(); + const filesArr = toArray(files); + if (hasChild(filesArr)) { + // + // Convert Files Array to Form Data ... + filesArr.forEach((f) => { + formData.append(XFileParam.Files, f, f.name); + }); + } + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = applyConnectionIdToHeader(connectionId, headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute(XFileServiceRoute.Upload); + + // + // Prepare Result ... + return this.httpClient.post>(endPointPath, formData, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region Remove ... + /** + * Remove Files ... + * + * @param id + * @param ids + * @param connectionId + * @param observe + * @param reportProgress + */ + public remove( + ids?: string, + connectionId?: string, + observe?: 'body', + reportProgress?: boolean + ): Observable>; + public remove( + ids?: string, + connectionId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>>; + public remove( + ids?: string, + connectionId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>>; + public remove( + ids?: string, + connectionId?: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(ids); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Adding ids to Parmas ... + if (!isNullOrEmptyString(ids)) { + // + params = this.addCustomParam(params, { + key: XFileParam.Ids, + value: ids, + }); + } + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + headers = applyConnectionIdToHeader(connectionId, headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute(XFileServiceRoute.Remove); + + // + // Prepare Result ... + return this.httpClient.delete>(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + //#endregion + + // + //#region Model ... + // + //#region Get ... + /** + * Get Specified Model ... + */ + public get( + id: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public get( + id: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public get( + id: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public get( + id: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(id); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint + const endPointPath = fillRouteParams( + // + // Base Route on Endpoint ... + this.getActionRoute(XFileServiceRoute.Get), + // + // Params ... + [ + { + key: XFileParam.Id, + value: id, + }, + ] + ); + + // + // Prepare Result ... + return this.httpClient.get(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region All ... + /** + * Retrieve all Exists Models ... + * + * @param observe + * @param reportProgress + */ + public all( + observe?: 'body', + reportProgress?: boolean + ): Observable>; + public all( + observe?: 'response', + reportProgress?: boolean + ): Observable>>; + public all( + observe?: 'events', + reportProgress?: boolean + ): Observable>>; + public all( + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Preparing HttpParams ... + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute(XFileServiceRoute.All); + + // + // Prepare Result ... + return this.httpClient.get>(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region Query ... + /** + * Query Model ... + * + * @param query + * @param observe + * @param reportProgress + */ + public query( + query: XQueryDto, + observe?: 'body', + reportProgress?: boolean + ): Observable>; + public query( + query: XQueryDto, + observe?: 'response', + reportProgress?: boolean + ): Observable>>; + public query( + query: XQueryDto, + observe?: 'events', + reportProgress?: boolean + ): Observable>>; + public query( + query: XQueryDto, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotNull(query); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Preparing HttpParams ... + params = this.generateXQueryHttpParams(query); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute(XFileServiceRoute.Query); + + // + // Prepare Result ... + return this.httpClient.get>(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region QueryOwned ... + /** + * Query Owned Models ... + * + * @param query + * @param observe + * @param reportProgress + */ + public queryOwned( + query: XQueryDto, + observe?: 'body', + reportProgress?: boolean + ): Observable>; + public queryOwned( + query: XQueryDto, + observe?: 'response', + reportProgress?: boolean + ): Observable>>; + public queryOwned( + query: XQueryDto, + observe?: 'events', + reportProgress?: boolean + ): Observable>>; + public queryOwned( + query: XQueryDto, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotNull(query); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Preparing HttpParams ... + params = this.generateXQueryHttpParams(query); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute(XFileServiceRoute.QueryOwned); + + // + // Prepare Result ... + return this.httpClient.get>(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + //#endregion + + // + //#region Tags ... + // + //#region AttachTags ... + /** + * Attach Specified Tags of Specified Model ... + * + * @param id + * @param tags + * @param connectionId + * @param observe + * @param reportProgress + */ + public attachTags( + id: string, + tags?: string, + connectionId?: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public attachTags( + id: string, + tags?: string, + connectionId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public attachTags( + id: string, + tags?: string, + connectionId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public attachTags( + id: string, + tags?: string, + connectionId?: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(id); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Attach Tags to Params ... + if (!isNullOrEmptyString(tags)) { + // + params = this.addCustomParam(params, { + key: XFileParam.Tags, + value: tags, + }); + } + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + headers = applyConnectionIdToHeader(connectionId, headers); + + // + // Prepare Endpoint + const endPointPath = fillRouteParams( + // + // Base Route on Endpoint ... + this.getActionRoute(XFileServiceRoute.AttachTags), + // + // Params ... + [ + { + key: XFileParam.Id, + value: id, + }, + ] + ); + + // + // Prepare Result ... + return this.httpClient.post(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region DetachTags ... + /** + * Detach Specified Tags of Specified Model ... + * + * @param id + * @param tags + * @param connectionId + * @param observe + * @param reportProgress + */ + public detachTags( + id: string, + tags?: string, + connectionId?: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public detachTags( + id: string, + tags?: string, + connectionId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public detachTags( + id: string, + tags?: string, + connectionId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public detachTags( + id: string, + tags?: string, + connectionId?: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(id); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Attach Tags to Params ... + if (!isNullOrEmptyString(tags)) { + // + params = this.addCustomParam(params, { + key: XFileParam.Tags, + value: tags, + }); + } + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + headers = applyConnectionIdToHeader(connectionId, headers); + + // + // Prepare Endpoint + const endPointPath = fillRouteParams( + // + // Base Route on Endpoint ... + this.getActionRoute(XFileServiceRoute.DetachTags), + // + // Params ... + [ + { + key: XFileParam.Id, + value: id, + }, + ] + ); + + // + // Prepare Result ... + return this.httpClient.delete(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region GetTags ... + /** + * Retrieve Tags of Specified Model ... + * + * @param id + * @param observe + * @param reportProgress + */ + public getTags( + id: string, + observe?: 'body', + reportProgress?: boolean + ): Observable>; + public getTags( + id: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>>; + public getTags( + id: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>>; + public getTags( + id: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(id); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint + const endPointPath = fillRouteParams( + // + // Base Route on Endpoint ... + this.getActionRoute(XFileServiceRoute.GetTags), + // + // Params ... + [ + { + key: XFileParam.Id, + value: id, + }, + ] + ); + + // + // Prepare Result ... + return this.httpClient.get>(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\lib\guards\admin-or-agent.guard.ts + +import { Injectable } from '@angular/core'; +import { XBaseGuard } from '../base/x-base.guard'; +import { XAvailableRoles } from '../typings/x-role.typings'; + +@Injectable({ + providedIn: 'root', +}) +export class AdminOrAgentGuard extends XBaseGuard { + roles = [ + XAvailableRoles.Admin, + XAvailableRoles.Agent + ]; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\lib\guards\admin.guard.ts + +import { Injectable } from '@angular/core'; +import { XBaseGuard } from '../base/x-base.guard'; +import { XAvailableRoles } from '../typings/x-role.typings'; + +@Injectable({ + providedIn: 'root', +}) +export class AdminGuard extends XBaseGuard { + roles = XAvailableRoles.Admin; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\lib\guards\agent.guard.ts + +import { Injectable } from '@angular/core'; +import { XBaseGuard } from '../base/x-base.guard'; +import { XAvailableRoles } from '../typings/x-role.typings'; + +@Injectable({ + providedIn: 'root', +}) +export class AgentGuard extends XBaseGuard { + roles = XAvailableRoles.Agent; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\lib\guards\user.guard.ts + +import { Injectable } from '@angular/core'; +import { XBaseGuard } from '../base/x-base.guard'; +import { XAvailableRoles } from '../typings/x-role.typings'; + +@Injectable({ + providedIn: 'root', +}) +export class UserGuard extends XBaseGuard { + roles = XAvailableRoles.User; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\lib\models\x-person.dto.ts + +import { + XBaseDto, + typeGuard, + isSameObject, + isNullOrUndefined, + isNullOrEmptyString, +} from 'x-framework-core'; + +/** + * Simply Represent a User ... + */ +export interface XPersonDto extends XBaseDto { + username: string; + firstname: string; + lastname: string; + avatar: string; +} + +/** + * Default Model ... + */ +export const defaultPersonDto: XPersonDto = { + username: '', + firstname: '', + lastname: '', + avatar: '', +}; + +/** + * Prepare Model Fields ... + * + * @param model + * @returns + */ +export function preparePersonDtoFields(model?: XPersonDto) { + // + if (isNullOrUndefined(model)) { + model = { + ...defaultPersonDto, + }; + } + + // + model = { + avatar: isNullOrEmptyString(model.avatar) + ? defaultPersonDto.avatar + : model.avatar, + username: isNullOrEmptyString(model.username) + ? defaultPersonDto.username + : model.username, + lastname: isNullOrEmptyString(model.lastname) + ? defaultPersonDto.lastname + : model.lastname, + firstname: isNullOrEmptyString(model.firstname) + ? defaultPersonDto.firstname + : model.firstname, + }; + + // + return model; +} + +/** + * Check Model is Default or not ... + * + * @param model + * @returns + */ +export function isDefaultPersonDto(model?: XPersonDto) { + return isSameObject(preparePersonDtoFields(model), defaultPersonDto); +} + +/** + * Model Type Guard ... + */ +export const isXPersonDto = typeGuard([ + 'username', + 'firstname', + 'lastname', +]); + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\lib\models\x-reference.dto.ts + +import { + nameof, + toNumber, + XBaseDto, + hasChild, + toXString, + typeGuard, + isSameObject, + toListString, + parseListString, + isNullOrUndefined, + isNullOrEmptyString, +} from 'x-framework-core'; + +export interface XReferenceDto extends XBaseDto { + index: number; + providedFor: string; + referencedTo: T; +} + +/** + * Model Type Guard ... + */ +export const isXReferenceDto = typeGuard>([ + 'index', + 'providedFor', + 'referencedTo', +]); + +export const defaultReferenceDto: XReferenceDto = { + index: 0, + providedFor: '', + referencedTo: undefined, +}; + +export function prepareReferenceDtoFields(model?: XReferenceDto) { + // + if (isNullOrUndefined(model)) { + // + model = { + ...defaultReferenceDto, + }; + } + + // + model = { + index: !isNullOrUndefined(model.index) + ? model.index + : defaultReferenceDto.index, + referencedTo: !isNullOrUndefined(model.referencedTo) + ? model.referencedTo + : defaultReferenceDto.referencedTo, + providedFor: !isNullOrEmptyString(model.providedFor) + ? model.providedFor + : defaultReferenceDto.providedFor, + }; + + // + return model; +} + +export function isDefaultReferenceDto(model?: XReferenceDto) { + return isSameObject(prepareReferenceDtoFields(model), defaultReferenceDto); +} + +/** + * Converts any object to XReferenceDto ... + * + * @param value + * @returns + */ +export function parseXReferenceDto(value: any) { + // + let result: XReferenceDto = undefined; + + // + let isPassed = false; + isPassed = + !isNullOrUndefined(value) && + Object.keys(value).includes(nameof>('index')) && + Object.keys(value).includes(nameof>('referencedTo')) && + Object.keys(value).includes(nameof>('providedFor')); + if (isPassed) { + // + result = { + index: toNumber(value.index), + providedFor: toXString(value.providedFor), + referencedTo: value.referencedTo as unknown as T, + }; + } + + // + return result; +} + +/** + * Parse an String to XReferenceDto ... + * + * @param value + * @returns + */ +export function parseXReference(value: string) { + // + var result: XReferenceDto = undefined; + + // + if (!isNullOrEmptyString(value)) { + // + let parts = parseListString(value, '_'); + let isValid = + !isNullOrUndefined(parts) && hasChild(parts) && parts.length >= 3; + + // + if (isValid) { + // + result = { + index: toNumber(parts[2]), + referencedTo: parts[1] as unknown as T, + providedFor: toXString(parts[0]), + }; + } + } + + // + return result; +} + +/** + * Parse References List From String ... + * + * @param value + * @returns + */ +export function parseXReferences(value: string) { + // + let result: Array> = []; + + // + if (!isNullOrEmptyString(value)) { + // + let parts = parseListString(value); + let isValid = !isNullOrUndefined(parts) && hasChild(parts); + if (isValid) { + // + parts.forEach((part) => { + // + var reference = parseXReference(part); + isValid = !isNullOrUndefined(reference); + if (isValid) { + result.push(reference); + } + }); + } + } + + // + return result; +} + +/** + * Converts an XReferenceDto to String Reperesentation ... + * + * @param value + * @param separator + * @returns + */ +export function reference2String( + value: XReferenceDto, + separator: string = '_' +) { + // + let result: string = undefined; + + // + if (!isNullOrUndefined(value)) { + result = `${value.providedFor}${separator}${value.referencedTo}${separator}${value.index}`; + } + + // + return result; +} + +/** + * Converts references Array to String Representation ... + * + * @param value + * @param separator + * @returns + */ +export function references2String( + value: Array>, + separator: string = ',' +) { + // + let result: string = undefined; + + // + let arr: Array = []; + if (!isNullOrUndefined(value) && hasChild(value)) { + // + value.forEach((v) => { + // + let refStr = reference2String(v); + arr.push(refStr); + }); + } + + // + result = toListString(arr, separator); + + // + return result; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\lib\services\x-push-token-fetcher.service.ts + +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { Inject, Injectable } from '@angular/core'; +import { XAccountService } from 'x-framework-identity-sdk'; +import { isNullOrUndefined, XBaseService } from 'x-framework-core'; +import { IXPushServiceTokenFetcher } from 'x-framework-push-service'; +import { X_SAHERELM_API_SDK_CONFIG } from '../tokens/x-injectable-tokens'; +import { XSaherElmApiSDKConfig } from '../config/x-saherelm-api-sdk.config'; + +@Injectable({ + providedIn: 'root', +}) +export class XPushTokenFetcherService + extends XBaseService + implements IXPushServiceTokenFetcher +{ + // + //#region Props ... + token: string; + token$: Observable = this.accountsService.state$.pipe( + map((state) => (isNullOrUndefined(state) ? null : state.accessToken)) + ); + //#endregion + + // + //#region Constructor ... + constructor( + // + @Inject(X_SAHERELM_API_SDK_CONFIG) + public config: XSaherElmApiSDKConfig, + // + public accountsService: XAccountService + ) { + super(config); + + // + accountsService.state$.subscribe((state) => { + // + if (isNullOrUndefined(state)) { + this.token = null; + } else { + this.token = state.accessToken; + } + }); + } + //#endregion + + /** + * retrieve current user's access token ... + * @returns token + */ + public getToken(): string { + return this.token; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\lib\strings\x-string.services.ts + +import { Injectable } from '@angular/core'; +import { XStringEntityService } from './services/x-string.entity.service'; +import { XStringProviderService } from './services/x-string.provider.service'; +import { XStringEntityPushService } from './services/x-string.entity.push.service'; + +/** + * Export All Provided String related Services ... + */ +@Injectable({ + providedIn: 'root', +}) +export class XStringServices { + // + //#region Constructor ... + public constructor( + public stringEntityService: XStringEntityService, + public stringProviderService: XStringProviderService, + public stringEntityPushService: XStringEntityPushService + ) {} + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\lib\strings\x-string.typings.ts + +/** + * All Available EndPoint Routes ... + */ +export enum XStringsServiceRoute { + // + // Retrievers ... + Get = '', + Resources = 'Resources', + Languages = 'Languages', + AsResource = 'AsResource', + ResourceValue = 'ResourceValue', + IsResourceExists = 'IsResourceExists', + + // + // Query ... + QueryResourceIds = 'QueryResourceIds', + QueryLocaleResources = 'QueryLocaleResources', + QueryLanguageResourcs = 'QueryLanguageResourcs', + + // + // Add ... + Add = 'Add', + AddModel = 'AddModel', + AddLocale = 'AddLocale', + AddResource = 'AddResource', + AddOrUpdate = 'AddOrUpdate', + AddOrUpdateModel = 'AddOrUpdateModel', + AddOrUpdateLocale = 'AddOrUpdateLocale', + + // + // Update ... + Update = 'Update', + UpdateModel = 'UpdateModel', + UpdateLocale = 'UpdateLocale', + + // + // Remove ... + Remove = 'Remove', + RemoveLocales = 'RemoveLocales', + RemoveResource = 'RemoveResource', + RemoveLanguage = 'RemoveLanguage', +} + +/** + * All Params Which Used in Related Services ... + */ +export enum XStringParam { + Value = 'value', + Resource = 'resource', + Language = 'language', +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\lib\strings\models\x-string.dto.ts + +import { + XBaseDto, + hasChild, + typeGuard, + isSameObject, + XBaseNumberIDDto, + isNullOrUndefined, + isNullOrEmptyString, +} from 'x-framework-core'; + +/** + * Represent a Locale Resource ... + */ +export interface XStringDto extends XBaseNumberIDDto { + language: string; + resourceTitle: string; + translatedValue: string; +} + +/** + * Default Model ... + */ +export const defaultStringDto: XStringDto = { + id: undefined, + language: '', + resourceTitle: '', + translatedValue: '', +}; + +/** + * Prepare Model Fields ... + * + * @param model + * @returns + */ +export function prepareStringDtoFields(model?: XStringDto) { + // + if (isNullOrUndefined(model)) { + model = { + ...defaultStringDto, + }; + } + + // + model = { + id: isNullOrUndefined(model.id) ? defaultStringDto.id : model.id, + language: isNullOrEmptyString(model.language) + ? defaultStringDto.language + : model.language, + resourceTitle: isNullOrEmptyString(model.resourceTitle) + ? defaultStringDto.resourceTitle + : model.resourceTitle, + translatedValue: isNullOrEmptyString(model.translatedValue) + ? defaultStringDto.translatedValue + : model.translatedValue, + }; + + // + return model; +} + +/** + * Check Model is Default or not ... + * + * @param model + * @returns + */ +export function isDefaultStringDto(model?: XStringDto) { + return isSameObject(prepareStringDtoFields(model), defaultStringDto); +} + +/** + * Simply Represent a Locale ... + */ +export interface XLocaleResourceDto extends XBaseDto { + language: string; + value: string; +} + +/** + * Default Model ... + */ +export const defaultLocaleResourceDto: XLocaleResourceDto = { + value: '', + language: '', +}; + +/** + * Prepare Model Fields ... + * + * @param model + * @returns + */ +export function prepareLocaleResourceDtoFields(model?: XLocaleResourceDto) { + // + if (isNullOrUndefined(model)) { + model = { + ...defaultLocaleResourceDto, + }; + } + + // + model = { + value: isNullOrEmptyString(model.value) + ? defaultLocaleResourceDto.value + : model.value, + language: isNullOrEmptyString(model.language) + ? defaultLocaleResourceDto.language + : model.language, + }; + + // + return model; +} + +/** + * Check Model is Default or not ... + * + * @param model + * @returns + */ +export function isDefaultLocaleResourceDto(model?: XLocaleResourceDto) { + return isSameObject( + prepareLocaleResourceDtoFields(model), + defaultLocaleResourceDto + ); +} + +/** + * Represent a Locale by it's Resources ... + */ +export interface XResourceDto extends XBaseDto { + resource: string; + locales: Array; +} + +/** + * Default Model ... + */ +export const defaultResourceDto: XResourceDto = { + resource: '', + locales: [], +}; + +/** + * Prepare Model Fields ... + * + * @param model + * @returns + */ +export function prepareResourceDtoFields(model?: XResourceDto) { + // + if (isNullOrUndefined(model)) { + model = { + ...defaultResourceDto, + }; + } + + // + model = { + resource: isNullOrEmptyString(model.resource) + ? defaultResourceDto.resource + : model.resource, + locales: + isNullOrUndefined(model.locales) || !hasChild(model.locales) + ? defaultResourceDto.locales + : model.locales.map((l) => prepareLocaleResourceDtoFields(l)), + }; + + // + return model; +} + +/** + * Check Model is Default or not ... + * + * @param model + * @returns + */ +export function isDefaultResourceDto(model?: XResourceDto) { + return isSameObject(prepareResourceDtoFields(model), defaultResourceDto); +} + +/** + * Model Type Guard ... + */ +export const isXStringDto = typeGuard([ + 'id', + 'language', + 'resourceTitle', + 'translatedValue', +]); + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\lib\strings\services\x-string.entity.push.service.ts + +import { Injectable } from '@angular/core'; +import { XStringDto } from '../models/x-string.dto'; +import { XBaseEntityPushService } from 'x-framework-push-service'; +import { HttpTransportType, ITransport } from '@microsoft/signalr'; + +@Injectable({ + providedIn: 'root', +}) +export class XStringEntityPushService extends XBaseEntityPushService { + // + //#region Abstracts ... + hubName(): string { + return 'stringEntityHub'; + } + + requiredAccessToken(): boolean { + return true; + } + + transportType(): HttpTransportType | ITransport { + return HttpTransportType.LongPolling; + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\lib\strings\services\x-string.entity.service.ts + +import { Router } from '@angular/router'; +import { HttpClient } from '@angular/common/http'; +import { Inject, Injectable } from '@angular/core'; +import { XStringDto } from '../models/x-string.dto'; +import { XApiEndPoint } from '../../typings/x-endpoint.typings'; +import { XBaseEntityService } from '../../base/x-base-entity.service'; +import { X_API_CONFIG, XApiConfiguration } from 'x-framework-identity-sdk'; +import { X_SAHERELM_API_SDK_CONFIG } from '../../tokens/x-injectable-tokens'; +import { XSaherElmApiSDKConfig } from '../../config/x-saherelm-api-sdk.config'; + +/** + * Provides all Entity Actions as a Service ... + */ +@Injectable({ + providedIn: 'root', +}) +export class XStringEntityService extends XBaseEntityService< + XStringDto, + number +> { + // + //#region Constructor ... + constructor( + protected router: Router, + protected httpClient: HttpClient, + @Inject(X_SAHERELM_API_SDK_CONFIG) config: XSaherElmApiSDKConfig, + @Inject(X_API_CONFIG) apiConfig: XApiConfiguration + ) { + super( + XApiEndPoint.StringsEntity, + true, + true, + httpClient, + config, + apiConfig + ); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\lib\strings\services\x-string.provider.service.ts + +import { + HttpEvent, + HttpClient, + HttpParams, + HttpHeaders, + HttpResponse, +} from '@angular/common/http'; +import { + XQueryDto, + XStringDto, + X_API_CONFIG, + XBaseApiService, + XQueryResultDto, + XApiConfiguration, +} from 'x-framework-identity-sdk'; +import { Observable } from 'rxjs'; +import { Router } from '@angular/router'; +import { + XApiEndPoint, + XBaseEndPointParam, +} from '../../typings/x-endpoint.typings'; +import { XValidators } from 'x-framework-core'; +import { Inject, Injectable } from '@angular/core'; +import { XStringParam, XStringsServiceRoute } from '../x-string.typings'; +import { XLocaleResourceDto, XResourceDto } from '../models/x-string.dto'; +import { X_SAHERELM_API_SDK_CONFIG } from '../../tokens/x-injectable-tokens'; +import { XSaherElmApiSDKConfig } from '../../config/x-saherelm-api-sdk.config'; +import { applyConnectionIdToHeader } from '../../typings/x-push-provider.typings'; + +/** + * Provides all Provider Actions as a Service ... + */ +@Injectable({ + providedIn: 'root', +}) +export class XStringProviderService extends XBaseApiService { + // + //#region Constructor ... + constructor( + protected router: Router, + protected httpClient: HttpClient, + @Inject(X_SAHERELM_API_SDK_CONFIG) config: XSaherElmApiSDKConfig, + @Inject(X_API_CONFIG) apiConfig: XApiConfiguration + ) { + super( + XApiEndPoint.StringsService, + true, + true, + httpClient, + config, + apiConfig + ); + } + //#endregion + + // + //#region Actions ... + // + //#region Retrievers ... + // + //#region Get ... + /** + * Retrieve Specified Resource ... + * + * @param resource + * @param language + * @param observe + * @param reportProgress + */ + public get( + resource: string, + language: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public get( + resource: string, + language: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public get( + resource: string, + language: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public get( + resource: string, + language: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(resource); + XValidators.validateNotEmpty(language); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XStringsServiceRoute, + XBaseEndPointParam + >(XStringsServiceRoute.Get); + + // + // Preparing HttpParams ... + params = this.addCustomParam(params, { + key: XStringParam.Resource, + value: resource, + }); + params = this.addCustomParam(params, { + key: XStringParam.Language, + value: language, + }); + + // + // Prepare Result ... + return this.httpClient.get(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region Languages ... + /** + * Retrieve all Available Languages List ... + * + * @param observe + * @param reportProgress + */ + public languages( + observe?: 'body', + reportProgress?: boolean + ): Observable>; + public languages( + observe?: 'response', + reportProgress?: boolean + ): Observable>>; + public languages( + observe?: 'events', + reportProgress?: boolean + ): Observable>>; + public languages( + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XStringsServiceRoute, + XBaseEndPointParam + >(XStringsServiceRoute.Languages); + + // + // Preparing HttpParams ... + + // + // Prepare Result ... + return this.httpClient.get>(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region Resources ... + /** + * Retrieve Specified Resource Items as XResourceDto Presentation ... + * + * @param resource + * @param observe + * @param reportProgress + */ + public resources( + resource: string, + observe?: 'body', + reportProgress?: boolean + ): Observable>; + public resources( + resource: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>>; + public resources( + resource: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>>; + public resources( + resource: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(resource); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XStringsServiceRoute, + XBaseEndPointParam + >(XStringsServiceRoute.Resources); + + // + // Preparing HttpParams ... + params = this.addCustomParam(params, { + key: XStringParam.Resource, + value: resource, + }); + + // + // Prepare Result ... + return this.httpClient.get>(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region AsResource ... + /** + * Retrieve Specified Resource Items as XResourceDto Presentation ... + * + * @param resource + * @param observe + * @param reportProgress + */ + public asResource( + resource: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public asResource( + resource: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public asResource( + resource: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public asResource( + resource: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(resource); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XStringsServiceRoute, + XBaseEndPointParam + >(XStringsServiceRoute.AsResource); + + // + // Preparing HttpParams ... + params = this.addCustomParam(params, { + key: XStringParam.Resource, + value: resource, + }); + + // + // Prepare Result ... + return this.httpClient.get(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region ResourceValue ... + /** + * Retrieve Specified Translation of Specified Resource ... + * + * @param resource + * @param language + * @param observe + * @param reportProgress + */ + public resourceValue( + resource: string, + language: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public resourceValue( + resource: string, + language: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public resourceValue( + resource: string, + language: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public resourceValue( + resource: string, + language: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(resource); + XValidators.validateNotEmpty(language); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XStringsServiceRoute, + XBaseEndPointParam + >(XStringsServiceRoute.ResourceValue); + + // + // Preparing HttpParams ... + params = this.addCustomParam(params, { + key: XStringParam.Resource, + value: resource, + }); + params = this.addCustomParam(params, { + key: XStringParam.Language, + value: language, + }); + + // + // Prepare Result ... + return this.httpClient.get(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region IsResourceExists ... + /** + * Check Specified Resource Exists or not ... + * + * @param resource + * @param language + * @param observe + * @param reportProgress + */ + public isResourceExists( + resource: string, + language: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public isResourceExists( + resource: string, + language: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public isResourceExists( + resource: string, + language: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public isResourceExists( + resource: string, + language: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(resource); + XValidators.validateNotEmpty(language); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XStringsServiceRoute, + XBaseEndPointParam + >(XStringsServiceRoute.IsResourceExists); + + // + // Preparing HttpParams ... + params = this.addCustomParam(params, { + key: XStringParam.Resource, + value: resource, + }); + params = this.addCustomParam(params, { + key: XStringParam.Language, + value: language, + }); + + // + // Prepare Result ... + return this.httpClient.get(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + //#endregion + + // + //#region Query ... + // + //#region QueryResourceIds ... + /** + * Query Resource IDs ... + * + * @param query + * @param observe + * @param reportProgress + */ + public queryResourceIds( + query: XQueryDto, + observe?: 'body', + reportProgress?: boolean + ): Observable>; + public queryResourceIds( + query: XQueryDto, + observe?: 'response', + reportProgress?: boolean + ): Observable>>; + public queryResourceIds( + query: XQueryDto, + observe?: 'events', + reportProgress?: boolean + ): Observable>>; + public queryResourceIds( + query: XQueryDto, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotNull(query); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Query as Params ... + params = this.generateXQueryHttpParams(query); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XStringsServiceRoute, + XBaseEndPointParam + >(XStringsServiceRoute.QueryResourceIds); + + // + // Prepare Result ... + return this.httpClient.get>(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region QueryLocaleResources ... + /** + * Query Locale Resources ... + * + * @param query + * @param language + * @param observe + * @param reportProgress + */ + public queryLocaleResources( + query: XQueryDto, + language: string, + observe?: 'body', + reportProgress?: boolean + ): Observable>; + public queryLocaleResources( + query: XQueryDto, + language: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>>; + public queryLocaleResources( + query: XQueryDto, + language: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>>; + public queryLocaleResources( + query: XQueryDto, + language: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotNull(query); + XValidators.validateNotEmpty(language); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Query as Params ... + params = this.generateXQueryHttpParams(query); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XStringsServiceRoute, + XBaseEndPointParam + >(XStringsServiceRoute.QueryLocaleResources); + + // + // Prepare Result ... + return this.httpClient.get>( + endPointPath, + { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + } + ); + } + //#endregion + + // + //#region QueryLanguageResources ... + /** + * Query Specified Language Resources ... + * + * @param query + * @param language + * @param observe + * @param reportProgress + */ + public queryLanguageResources( + query: XQueryDto, + language: string, + observe?: 'body', + reportProgress?: boolean + ): Observable>; + public queryLanguageResources( + query: XQueryDto, + language: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>>; + public queryLanguageResources( + query: XQueryDto, + language: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>>; + public queryLanguageResources( + query: XQueryDto, + language: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotNull(query); + XValidators.validateNotEmpty(language); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Query as Params ... + params = this.generateXQueryHttpParams(query); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XStringsServiceRoute, + XBaseEndPointParam + >(XStringsServiceRoute.QueryLocaleResources); + + // + // Prepare Result ... + return this.httpClient.get>(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + //#endregion + + // + //#region Add ... + // + //#region Add ... + /** + * Add Specified Resource ... + * + * @param resource + * @param value + * @param language + * @param connectionId + * @param observe + * @param reportProgress + */ + public add( + resource: string, + value: string, + language: string, + connectionId?: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public add( + resource: string, + value: string, + language: string, + connectionId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public add( + resource: string, + value: string, + language: string, + connectionId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public add( + resource: string, + value: string, + language: string, + connectionId?: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(value); + XValidators.validateNotEmpty(resource); + XValidators.validateNotEmpty(language); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + headers = applyConnectionIdToHeader(connectionId, headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XStringsServiceRoute, + XBaseEndPointParam + >(XStringsServiceRoute.Add); + + // + // Preparing HttpParams ... + params = this.addCustomParam(params, { + key: XStringParam.Value, + value: value, + }); + params = this.addCustomParam(params, { + key: XStringParam.Resource, + value: resource, + }); + params = this.addCustomParam(params, { + key: XStringParam.Language, + value: language, + }); + + // + // Prepare Result ... + return this.httpClient.post(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region AddModel ... + /** + * Add Specified Resource ... + * + * @param model + * @param connectionId + * @param observe + * @param reportProgress + */ + public addModel( + model: XStringDto, + connectionId?: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public addModel( + model: XStringDto, + connectionId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public addModel( + model: XStringDto, + connectionId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public addModel( + model: XStringDto, + connectionId?: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotNull(model); + XValidators.validateNotEmpty(model.resourceTitle); + XValidators.validateNotEmpty(model.translatedValue); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + headers = applyConnectionIdToHeader(connectionId, headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XStringsServiceRoute, + XBaseEndPointParam + >(XStringsServiceRoute.AddModel); + + // + // Prepare Result ... + return this.httpClient.post(endPointPath, model, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region AddLocale ... + /** + * Add Specified Locale Resource ... + * + * @param model + * @param resource + * @param connectionId + * @param observe + * @param reportProgress + */ + public addLocale( + model: XLocaleResourceDto, + resource: string, + connectionId?: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public addLocale( + model: XLocaleResourceDto, + resource: string, + connectionId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public addLocale( + model: XLocaleResourceDto, + resource: string, + connectionId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public addLocale( + model: XLocaleResourceDto, + resource: string, + connectionId?: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotNull(model); + XValidators.validateNotEmpty(resource); + XValidators.validateNotEmpty(model.value); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + headers = applyConnectionIdToHeader(connectionId, headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XStringsServiceRoute, + XBaseEndPointParam + >(XStringsServiceRoute.AddLocale); + + // + // Preparing HttpParams ... + params = this.addCustomParam(params, { + key: XStringParam.Resource, + value: resource, + }); + + // + // Prepare Result ... + return this.httpClient.post(endPointPath, model, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region AddResource ... + /** + * Add Or Update all XResourceDto(s) Locales ... + * + * @param model + * @param connectionId + * @param observe + * @param reportProgress + */ + public addResource( + model: XResourceDto, + connectionId?: string, + observe?: 'body', + reportProgress?: boolean + ): Observable>; + public addResource( + model: XResourceDto, + connectionId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>>; + public addResource( + model: XResourceDto, + connectionId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>>; + public addResource( + model: XResourceDto, + connectionId?: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotNull(model); + XValidators.validateNotEmpty(model.resource); + XValidators.validateHasChilds(model.locales); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + headers = applyConnectionIdToHeader(connectionId, headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XStringsServiceRoute, + XBaseEndPointParam + >(XStringsServiceRoute.AddResource); + + // + // Preparing HttpParams ... + + // + // Prepare Result ... + return this.httpClient.post>(endPointPath, model, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region AddOrUpdate ... + /** + * Add Or Update Resource ... + * + * @param resource + * @param value + * @param language + * @param connectionId + * @param observe + * @param reportProgress + */ + public addOrUpdate( + resource: string, + value: string, + language: string, + connectionId?: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public addOrUpdate( + resource: string, + value: string, + language: string, + connectionId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public addOrUpdate( + resource: string, + value: string, + language: string, + connectionId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public addOrUpdate( + resource: string, + value: string, + language: string, + connectionId?: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(value); + XValidators.validateNotEmpty(resource); + XValidators.validateNotEmpty(language); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + headers = applyConnectionIdToHeader(connectionId, headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XStringsServiceRoute, + XBaseEndPointParam + >(XStringsServiceRoute.AddOrUpdate); + + // + // Preparing HttpParams ... + params = this.addCustomParam(params, { + key: XStringParam.Value, + value: value, + }); + params = this.addCustomParam(params, { + key: XStringParam.Resource, + value: resource, + }); + params = this.addCustomParam(params, { + key: XStringParam.Language, + value: language, + }); + + // + // Prepare Result ... + return this.httpClient.post(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region AddOrUpdateModel ... + /** + * Add Or Update Resource ... + * + * @param model + * @param connectionId + * @param observe + * @param reportProgress + */ + public addOrUpdateModel( + model: XStringDto, + connectionId?: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public addOrUpdateModel( + model: XStringDto, + connectionId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public addOrUpdateModel( + model: XStringDto, + connectionId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public addOrUpdateModel( + model: XStringDto, + connectionId?: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotNull(model); + XValidators.validateNotEmpty(model.resourceTitle); + XValidators.validateNotEmpty(model.translatedValue); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + headers = applyConnectionIdToHeader(connectionId, headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XStringsServiceRoute, + XBaseEndPointParam + >(XStringsServiceRoute.AddOrUpdateModel); + + // + // Prepare Result ... + return this.httpClient.post(endPointPath, model, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region AddOrUpdateLocale ... + /** + * Add Or Update Specified Locale Resource ... + * + * @param model + * @param resource + * @param connectionId + * @param observe + * @param reportProgress + */ + public addOrUpdateLocale( + model: XLocaleResourceDto, + resource: string, + connectionId?: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public addOrUpdateLocale( + model: XLocaleResourceDto, + resource: string, + connectionId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public addOrUpdateLocale( + model: XLocaleResourceDto, + resource: string, + connectionId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public addOrUpdateLocale( + model: XLocaleResourceDto, + resource: string, + connectionId?: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotNull(model); + XValidators.validateNotEmpty(resource); + XValidators.validateNotEmpty(model.value); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + headers = applyConnectionIdToHeader(connectionId, headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XStringsServiceRoute, + XBaseEndPointParam + >(XStringsServiceRoute.AddOrUpdateLocale); + + // + // Preparing HttpParams ... + params = this.addCustomParam(params, { + key: XStringParam.Resource, + value: resource, + }); + + // + // Prepare Result ... + return this.httpClient.post(endPointPath, model, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + //#endregion + + // + //#region Update ... + // + //#region Update ... + /** + * Update Specified Resource ... + * + * @param resource + * @param value + * @param language + * @param connectionId + * @param observe + * @param reportProgress + */ + public update( + resource: string, + value: string, + language: string, + connectionId?: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public update( + resource: string, + value: string, + language: string, + connectionId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public update( + resource: string, + value: string, + language: string, + connectionId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public update( + resource: string, + value: string, + language: string, + connectionId?: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(value); + XValidators.validateNotEmpty(resource); + XValidators.validateNotEmpty(language); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + headers = applyConnectionIdToHeader(connectionId, headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XStringsServiceRoute, + XBaseEndPointParam + >(XStringsServiceRoute.Update); + + // + // Preparing HttpParams ... + params = this.addCustomParam(params, { + key: XStringParam.Value, + value: value, + }); + params = this.addCustomParam(params, { + key: XStringParam.Resource, + value: resource, + }); + params = this.addCustomParam(params, { + key: XStringParam.Language, + value: language, + }); + + // + // Prepare Result ... + return this.httpClient.put(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region UpdateModel ... + /** + * Update Specified Resource ... + * + * @param model + * @param connectionId + * @param observe + * @param reportProgress + */ + public updateModel( + model: XStringDto, + connectionId?: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public updateModel( + model: XStringDto, + connectionId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public updateModel( + model: XStringDto, + connectionId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public updateModel( + model: XStringDto, + connectionId?: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotNull(model); + XValidators.validateNotEmpty(model.resourceTitle); + XValidators.validateNotEmpty(model.translatedValue); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + headers = applyConnectionIdToHeader(connectionId, headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XStringsServiceRoute, + XBaseEndPointParam + >(XStringsServiceRoute.UpdateModel); + + // + // Prepare Result ... + return this.httpClient.put(endPointPath, model, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region UpdateLocale ... + /** + * Update Specified Locale Resource ... + * + * @param model + * @param resource + * @param connectionId + * @param observe + * @param reportProgress + */ + public updateLocale( + model: XLocaleResourceDto, + resource: string, + connectionId?: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public updateLocale( + model: XLocaleResourceDto, + resource: string, + connectionId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public updateLocale( + model: XLocaleResourceDto, + resource: string, + connectionId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public updateLocale( + model: XLocaleResourceDto, + resource: string, + connectionId?: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotNull(model); + XValidators.validateNotEmpty(resource); + XValidators.validateNotEmpty(model.value); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + headers = applyConnectionIdToHeader(connectionId, headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XStringsServiceRoute, + XBaseEndPointParam + >(XStringsServiceRoute.UpdateLocale); + + // + // Preparing HttpParams ... + params = this.addCustomParam(params, { + key: XStringParam.Resource, + value: resource, + }); + + // + // Prepare Result ... + return this.httpClient.put(endPointPath, model, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + //#endregion + + // + //#region Remove ... + // + //#region Remove ... + /** + * Remove Specified Resource ... + * + * @param resource + * @param language + * @param connectionId + * @param observe + * @param reportProgress + */ + public remove( + resource: string, + language: string, + connectionId?: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public remove( + resource: string, + language: string, + connectionId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public remove( + resource: string, + language: string, + connectionId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public remove( + resource: string, + language: string, + connectionId?: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(resource); + XValidators.validateNotEmpty(language); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Instantiiate Headers from Default Headers ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + headers = applyConnectionIdToHeader(connectionId, headers); + + // + // Preparing HttpParams ... + params = this.addCustomParam(params, { + key: XStringParam.Resource, + value: resource, + }); + params = this.addCustomParam(params, { + key: XStringParam.Language, + value: language, + }); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XStringsServiceRoute, + XBaseEndPointParam + >(XStringsServiceRoute.Remove); + + // + // Prepare Result ... + return this.httpClient.delete(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region RemoveLocales ... + public removeLocales( + resource: string, + connectionId?: string, + observe?: 'body', + reportProgress?: boolean + ): Observable>; + public removeLocales( + resource: string, + connectionId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>>; + public removeLocales( + resource: string, + connectionId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>>; + public removeLocales( + resource: string, + connectionId?: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(resource); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Instantiiate Headers from Default Headers ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + headers = applyConnectionIdToHeader(connectionId, headers); + + // + // Preparing HttpParams ... + params = this.addCustomParam(params, { + key: XStringParam.Resource, + value: resource, + }); + + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XStringsServiceRoute, + XBaseEndPointParam + >(XStringsServiceRoute.RemoveLocales); + + // + // Prepare Result ... + return this.httpClient.delete>(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region RemoveResource ... + /** + * Remove all Resource of Specified XResourceDto ... + * + * @param model + * @param connectionId + * @param observe + * @param reportProgress + */ + public removeResource( + model: XResourceDto, + connectionId?: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public removeResource( + model: XResourceDto, + connectionId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public removeResource( + model: XResourceDto, + connectionId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public removeResource( + model: XResourceDto, + connectionId?: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotNull(model); + XValidators.validateNotEmpty(model.resource); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Instantiiate Headers from Default Headers ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + headers = applyConnectionIdToHeader(connectionId, headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XStringsServiceRoute, + XBaseEndPointParam + >(XStringsServiceRoute.RemoveResource); + + // + // Prepare Result ... + return this.httpClient.post(endPointPath, model, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region RemoveLanguage ... + /** + * Remove all Specified Language's Resources ... + * + * @param language + * @param connectionId + * @param observe + * @param reportProgress + */ + public removeLanguage( + language: string, + connectionId?: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public removeLanguage( + language: string, + connectionId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public removeLanguage( + language: string, + connectionId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public removeLanguage( + language: string, + connectionId?: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotNull(language); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Instantiiate Headers from Default Headers ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + headers = applyConnectionIdToHeader(connectionId, headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XStringsServiceRoute, + XBaseEndPointParam + >(XStringsServiceRoute.RemoveLanguage); + + // + // Prepare Result ... + return this.httpClient.delete(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\lib\tags\x-tag.services.ts + +import { Injectable } from '@angular/core'; +import { XTagEntityService } from './services/x-tag.entity.service'; +import { XTagProviderService } from './services/x-tag.provider.service'; +import { XTagEntityPushService } from './services/x-tag.entity.push.service'; + +/** + * Export All Provided Tag related Services ... + */ +@Injectable({ + providedIn: 'root', +}) +export class XTagServices { + // + //#region Constructor ... + public constructor( + public tagEntityService: XTagEntityService, + public tagProviderService: XTagProviderService, + public tagEntityPushService: XTagEntityPushService + ) {} + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\lib\tags\x-tag.typings.ts + +/** + * All Available EndPoint Routes ... + */ +export enum XTagServiceRoute { + Add = '', + All = 'All', + Get = '{id}', + Update = '{id}', + Remove = '{id}', + Query = 'Query', + GetTag = 'GetTag', + FindOne = 'FindOne', + FindMany = 'FindMany', +} + +/** + * All Params Which Used in Related Services ... + */ +export enum XTagParam { + Id = 'id', + Tag = 'tag', + Query = 'query' +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\lib\tags\models\x-tag.dto.ts + +import { + hasChild, + XBaseDto, + typeGuard, + isSameObject, + isNullOrUndefined, + isNullOrEmptyString, +} from 'x-framework-core'; +import { + XReferenceDto, + parseXReferences, + references2String, +} from '../../models/x-reference.dto'; + +/** + * Represent Specified Tag ... + */ +export interface XTagEntity extends XBaseDto { + id: number; + tag: string; + references: string; +} + +/** + * Represent Specified Tag ... + */ +export interface XTagDto extends XBaseDto { + id: number; + tag: string; + references: Array>; +} + +/** + * Default Model ... + */ +export const defaultTagDto: XTagDto = { + id: 0, + tag: undefined, + references: [], +}; + +/** + * Prepare Model Fields ... + * + * @param model + * @returns + */ +export function prepareTagDtoFields(model?: XTagDto) { + // + if (isNullOrUndefined(model)) { + // + model = { + ...defaultTagDto, + }; + } + + // + model = { + // + id: + !isNullOrUndefined(model.id) && model.id > 0 + ? model.id + : defaultTagDto.id, + tag: !isNullOrEmptyString(model.tag) ? model.tag : defaultTagDto.tag, + references: + !isNullOrUndefined(model.references) && hasChild(model.references) + ? [...model.references] + : defaultTagDto.references, + }; + + // + return model; +} + +/** + * Check Model is Default or not ... + * + * @param model + * @returns + */ +export function isDefaultTagDto(model?: XTagDto) { + return isSameObject(prepareTagDtoFields(model), defaultTagDto); +} + +/** + * Model Type Guard ... + */ +export const isXTagDto = typeGuard(['id', 'tag']); + +/** + * Tag Dto to Tag Entity ... + * + * @param value + * @returns + */ +export function tagDto2Entity(value: XTagDto) { + // + let result: XTagEntity = undefined; + + // + if (!isNullOrUndefined(value) && isXTagDto(value)) { + // + const references = references2String(value.references); + result = { + id: value.id, + tag: value.tag, + references, + }; + } + + // + return result; +} + +/** + * Tag Entity to Tag Dto ... + * + * @param value + * @returns + */ +export function tagEntity2Dto(value: XTagEntity) { + // + let result: XTagDto = undefined; + + // + if (!isNullOrUndefined(value) && isXTagDto(value)) { + // + const references = parseXReferences(value.references); + result = { + id: value.id, + tag: value.tag, + references, + }; + } + + // + return result; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\lib\tags\services\x-tag.entity.push.service.ts + +import { Injectable } from '@angular/core'; +import { XTagEntity } from '../models/x-tag.dto'; +import { XBaseEntityPushService } from 'x-framework-push-service'; +import { HttpTransportType, ITransport } from '@microsoft/signalr'; + +@Injectable({ + providedIn: 'root', +}) +export class XTagEntityPushService extends XBaseEntityPushService { + // + //#region Props ... + //#endregion + + // + //#region Abstracts ... + hubName(): string { + return 'tagEntityHub'; + } + + requiredAccessToken(): boolean { + return true; + } + + transportType(): HttpTransportType | ITransport { + return HttpTransportType.LongPolling; + } + + registerSubjects(): void { + super.registerSubjects(); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\lib\tags\services\x-tag.entity.service.ts + +import { Router } from '@angular/router'; +import { XTagEntity } from '../models/x-tag.dto'; +import { HttpClient } from '@angular/common/http'; +import { Inject, Injectable } from '@angular/core'; +import { XApiEndPoint } from '../../typings/x-endpoint.typings'; +import { XBaseEntityService } from '../../base/x-base-entity.service'; +import { X_API_CONFIG, XApiConfiguration } from 'x-framework-identity-sdk'; +import { X_SAHERELM_API_SDK_CONFIG } from '../../tokens/x-injectable-tokens'; +import { XSaherElmApiSDKConfig } from '../../config/x-saherelm-api-sdk.config'; + +/** + * Provides all Entity Actions as a Service ... + */ +@Injectable({ + providedIn: 'root', +}) +export class XTagEntityService extends XBaseEntityService< + XTagEntity, + string +> { + // + //#region Constructor ... + constructor( + protected router: Router, + protected httpClient: HttpClient, + @Inject(X_SAHERELM_API_SDK_CONFIG) config: XSaherElmApiSDKConfig, + @Inject(X_API_CONFIG) apiConfig: XApiConfiguration + ) { + super( + XApiEndPoint.TagsEntity, + true, + true, + httpClient, + config, + apiConfig + ); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\lib\tags\services\x-tag.provider.service.ts + +import { + toArray, + hasChild, + XValidators, + XOneOrManyType, + isNullOrEmptyString, +} from 'x-framework-core'; +import { + HttpEvent, + HttpParams, + HttpClient, + HttpHeaders, + HttpResponse, +} from '@angular/common/http'; +import { + XQueryDto, + X_API_CONFIG, + XBaseApiService, + XQueryResultDto, + XApiConfiguration, +} from 'x-framework-identity-sdk'; +import { Observable } from 'rxjs'; +import { Router } from '@angular/router'; +import { XTagDto } from '../models/x-tag.dto'; +import { Inject, Injectable } from '@angular/core'; +import { fillRouteParams } from '../../tools/x-route.tools'; +import { XTagParam, XTagServiceRoute } from '../x-tag.typings'; +import { XApiEndPoint } from '../../typings/x-endpoint.typings'; +import { X_SAHERELM_API_SDK_CONFIG } from '../../tokens/x-injectable-tokens'; +import { XSaherElmApiSDKConfig } from '../../config/x-saherelm-api-sdk.config'; +import { applyConnectionIdToHeader } from '../../typings/x-push-provider.typings'; + +/** + * Provides all Provider Actions as a Service ... + */ +@Injectable({ + providedIn: 'root', +}) +export class XTagProviderService extends XBaseApiService { + // + //#region Constructor ... + constructor( + protected router: Router, + protected httpClient: HttpClient, + @Inject(X_SAHERELM_API_SDK_CONFIG) config: XSaherElmApiSDKConfig, + @Inject(X_API_CONFIG) apiConfig: XApiConfiguration + ) { + super(XApiEndPoint.TagsService, true, true, httpClient, config, apiConfig); + } + //#endregion + + // + //#region Actions ... + // + //#region Add ... + /** + * Add Model ... + * + * @param model + * @param connectionId + * @param observe + * @param reportProgress + */ + public add( + model: XTagDto, + connectionId?: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public add( + model: XTagDto, + connectionId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public add( + model: XTagDto, + connectionId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public add( + model: XTagDto, + connectionId?: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotNull(model); + XValidators.validateNotEmpty(model.tag); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + headers = applyConnectionIdToHeader(connectionId, headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute(XTagServiceRoute.Add); + + // + // Prepare Result ... + return this.httpClient.post(endPointPath, model, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region Update ... + /** + * Update Model ... + * + * @param id + * @param model + * @param connectionId + * @param observe + * @param reportProgress + */ + public update( + id: number, + model: XTagDto, + connectionId?: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public update( + id: number, + model: XTagDto, + connectionId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public update( + id: number, + model: XTagDto, + connectionId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public update( + id: number, + model: XTagDto, + connectionId?: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotNull(model); + XValidators.validateNotEmpty(model.tag); + + // + if (model.id !== id) { + model.id = id; + } + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + headers = applyConnectionIdToHeader(connectionId, headers); + + // + // Prepare Endpoint + const endPointPath = fillRouteParams( + // + // Base Route on Endpoint ... + this.getActionRoute(XTagServiceRoute.Update), + // + // Params ... + [ + { + key: XTagParam.Id, + value: id.toString(), + }, + ] + ); + + // + // Prepare Result ... + return this.httpClient.put(endPointPath, model, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region Remove ... + /** + * Remove Model ... + * + * @param id + * @param connectionId + * @param observe + * @param reportProgress + */ + public remove( + id: number, + connectionId?: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public remove( + id: number, + connectionId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public remove( + id: number, + connectionId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public remove( + id: number, + connectionId?: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + headers = applyConnectionIdToHeader(connectionId, headers); + + // + // Prepare Endpoint ... + const endPointPath = fillRouteParams( + this.getActionRoute(XTagServiceRoute.Remove), + [ + { + key: XTagParam.Id, + value: id.toString(), + }, + ] + ); + + // + // Prepare Result ... + return this.httpClient.delete(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region Get ... + /** + * Get Specified Model ... + */ + public get( + id: number, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public get( + id: number, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public get( + id: number, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public get( + id: number, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint ... + const endPointPath = fillRouteParams( + // + // Base Route on Endpoint ... + this.getActionRoute(XTagServiceRoute.Get), + // + // Params ... + [ + { + key: XTagParam.Id, + value: id.toString(), + }, + ] + ); + + // + // Prepare Result ... + return this.httpClient.get(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region GetTag ... + /** + * Get Specified Model by Providing Label ... + */ + public getTag( + tag: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public getTag( + tag: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public getTag( + tag: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public getTag( + tag: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(tag); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Attach Tag to Params ... + params = this.addCustomParam(params, { + key: XTagParam.Tag, + value: tag + }); + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint ... + const endPointPath = this.getActionRoute(XTagServiceRoute.GetTag); + + // + // Prepare Result ... + return this.httpClient.get(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region All ... + /** + * Retrieve all Exists Models ... + * + * @param observe + * @param reportProgress + */ + public all( + observe?: 'body', + reportProgress?: boolean + ): Observable>; + public all( + observe?: 'response', + reportProgress?: boolean + ): Observable>>; + public all( + observe?: 'events', + reportProgress?: boolean + ): Observable>>; + public all( + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Preparing HttpParams ... + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute(XTagServiceRoute.All); + + // + // Prepare Result ... + return this.httpClient.get>(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region FindOne ... + /** + * Detect Specified Model by Providing Label Query ... + */ + public findOne( + query: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public findOne( + query: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public findOne( + query: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public findOne( + query: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(query); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Attach Tag to Params ... + params = this.addCustomParam(params, { + key: XTagParam.Query, + value: query + }); + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint ... + const endPointPath = this.getActionRoute(XTagServiceRoute.FindOne); + + // + // Prepare Result ... + return this.httpClient.get(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region FindMany ... + /** + * Detect Specified Models by Providing Label Query ... + */ + public findMany( + query: string, + observe?: 'body', + reportProgress?: boolean + ): Observable>; + public findMany( + query: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>>; + public findMany( + query: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>>; + public findMany( + query: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(query); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Attach Tag to Params ... + params = this.addCustomParam(params, { + key: XTagParam.Query, + value: query + }); + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint ... + const endPointPath = this.getActionRoute(XTagServiceRoute.FindMany); + + // + // Prepare Result ... + return this.httpClient.get>(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region Query ... + /** + * Query Model ... + * + * @param query + * @param observe + * @param reportProgress + */ + public query( + query: XQueryDto, + observe?: 'body', + reportProgress?: boolean + ): Observable>; + public query( + query: XQueryDto, + observe?: 'response', + reportProgress?: boolean + ): Observable>>; + public query( + query: XQueryDto, + observe?: 'events', + reportProgress?: boolean + ): Observable>>; + public query( + query: XQueryDto, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotNull(query); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Preparing HttpParams ... + params = this.generateXQueryHttpParams(query); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute(XTagServiceRoute.Query); + + // + // Prepare Result ... + return this.httpClient.get>(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\lib\terms\x-terms.service.ts + +import { Injectable } from '@angular/core'; +import { XTermsPushService } from './services/x-terms.push.service'; +import { XTermsProviderService } from './services/x-terms.provider.service'; + +/** + * Export All Provided Terms related Services ... + */ +@Injectable({ + providedIn: 'root', +}) +export class XTermsServices { + // + //#region Constructor ... + public constructor( + public termsPushService: XTermsPushService, + public termsProviderService: XTermsProviderService + ) {} + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\lib\terms\x-terms.typings.dto.ts + +/** + * All Available EndPoint Routes ... + */ +export enum XTermsServiceRoute { + Add = 'Terms/{language}', + Get = 'Terms/{language}', + Update = 'Terms/{language}', + Remove = 'Terms/{language}', + Has = 'Terms/{language}/Has', + Languages = 'Terms/Languages', +} + +/** + * All Params Which Used in Related Services ... + */ +export enum XTermsParam { + Terms = 'terms', + Language = 'language', +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\lib\terms\services\x-terms.provider.service.ts + +import { + HttpEvent, + HttpClient, + HttpParams, + HttpHeaders, + HttpResponse, +} from '@angular/common/http'; +import { + X_API_CONFIG, + XBaseApiService, + XApiConfiguration, +} from 'x-framework-identity-sdk'; +import { Observable } from 'rxjs'; +import { Router } from '@angular/router'; +import { Inject, Injectable } from '@angular/core'; +import { XLocale, XValidators } from 'x-framework-core'; +import { fillRouteParams } from '../../tools/x-route.tools'; +import { XApiEndPoint } from '../../typings/x-endpoint.typings'; +import { XTermsParam, XTermsServiceRoute } from '../x-terms.typings.dto'; +import { X_SAHERELM_API_SDK_CONFIG } from '../../tokens/x-injectable-tokens'; +import { XSaherElmApiSDKConfig } from '../../config/x-saherelm-api-sdk.config'; +import { applyConnectionIdToHeader } from '../../typings/x-push-provider.typings'; + +/** + * Provides all Provider Actions as a Service ... + */ +@Injectable({ + providedIn: 'root', +}) +export class XTermsProviderService extends XBaseApiService { + // + //#region Constructor ... + constructor( + protected router: Router, + protected httpClient: HttpClient, + @Inject(X_SAHERELM_API_SDK_CONFIG) config: XSaherElmApiSDKConfig, + @Inject(X_API_CONFIG) apiConfig: XApiConfiguration + ) { + super( + XApiEndPoint.TermsAndConditions, + true, + true, + httpClient, + config, + apiConfig + ); + } + //#endregion + + // + //#region Actions ... + // + //#region Get ... + /** + * Get Specified Languages Terms and Conditions ... + * + * @param language + * @param observe + * @param reportProgress + */ + public get( + language: XLocale | string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public get( + language: XLocale | string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public get( + language: XLocale | string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public get( + language: XLocale | string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(language); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint ... + const endPointPath = fillRouteParams( + // + // Base Route on Endpoint ... + this.getActionRoute(XTermsServiceRoute.Get), + // + // Params ... + [ + { + key: XTermsParam.Language, + value: language, + }, + ] + ); + + // + // Prepare Result ... + return this.httpClient.get(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region Languages ... + /** + * Get Languages Lists which Contains Terms and Conditions ... + * + * @param observe + * @param reportProgress + */ + public getLanguages( + observe?: 'body', + reportProgress?: boolean + ): Observable>; + public getLanguages( + observe?: 'response', + reportProgress?: boolean + ): Observable>>; + public getLanguages( + observe?: 'events', + reportProgress?: boolean + ): Observable>>; + public getLanguages( + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint ... + const endPointPath = this.getActionRoute(XTermsServiceRoute.Languages); + + // + // Prepare Result ... + return this.httpClient.get>(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region Has ... + /** + * Check Specified Languages Terms and Conditions Exists or not ... + * + * @param language + * @param observe + * @param reportProgress + */ + public has( + language: XLocale | string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public has( + language: XLocale | string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public has( + language: XLocale | string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public has( + language: XLocale | string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(language); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint ... + const endPointPath = fillRouteParams( + // + // Base Route on Endpoint ... + this.getActionRoute(XTermsServiceRoute.Has), + // + // Params ... + [ + { + key: XTermsParam.Language, + value: language, + }, + ] + ); + + // + // Prepare Result ... + return this.httpClient.get(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region Add ... + /** + * Add Terms and Conditions For Spevified Language ... + * + * @param language + * @param terms + * @param connectionId + * @param observe + * @param reportProgress + */ + public add( + language: XLocale | string, + terms: string, + connectionId?: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public add( + language: XLocale | string, + terms: string, + connectionId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public add( + language: XLocale | string, + terms: string, + connectionId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public add( + language: XLocale | string, + terms: string, + connectionId?: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(terms); + XValidators.validateNotEmpty(language); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + headers = applyConnectionIdToHeader(connectionId, headers); + + // + // Prepare Endpoint ... + const endPointPath = fillRouteParams( + // + // Base Route on Endpoint ... + this.getActionRoute(XTermsServiceRoute.Add), + // + // Params ... + [ + { + key: XTermsParam.Language, + value: language, + }, + ] + ); + + // + // Add terms Query String ... + params = this.addCustomParam(params, { + key: XTermsParam.Terms, + value: terms, + }); + + // + // Prepare Result ... + return this.httpClient.post(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region Update ... + /** + * Update Terms and Conditions For Spevified Language ... + * + * @param language + * @param terms + * @param connectionId + * @param observe + * @param reportProgress + */ + public update( + language: XLocale | string, + terms: string, + connectionId?: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public update( + language: XLocale | string, + terms: string, + connectionId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public update( + language: XLocale | string, + terms: string, + connectionId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public update( + language: XLocale | string, + terms: string, + connectionId?: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(terms); + XValidators.validateNotEmpty(language); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + headers = applyConnectionIdToHeader(connectionId, headers); + + // + // Prepare Endpoint ... + const endPointPath = fillRouteParams( + // + // Base Route on Endpoint ... + this.getActionRoute(XTermsServiceRoute.Update), + // + // Params ... + [ + { + key: XTermsParam.Language, + value: language, + }, + ] + ); + + // + // Add terms Query String ... + params = this.addCustomParam(params, { + key: XTermsParam.Terms, + value: terms, + }); + + // + // Prepare Result ... + return this.httpClient.put(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region Remove ... + /** + * Remove Terms and Conditions For Spevified Language ... + * + * @param language + * @param connectionId + * @param observe + * @param reportProgress + */ + public remove( + language: XLocale | string, + connectionId?: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public remove( + language: XLocale | string, + connectionId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public remove( + language: XLocale | string, + connectionId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public remove( + language: XLocale | string, + connectionId?: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(language); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + headers = applyConnectionIdToHeader(connectionId, headers); + + // + // Prepare Endpoint ... + const endPointPath = fillRouteParams( + // + // Base Route on Endpoint ... + this.getActionRoute(XTermsServiceRoute.Remove), + // + // Params ... + [ + { + key: XTermsParam.Language, + value: language, + }, + ] + ); + + // + // Prepare Result ... + return this.httpClient.delete(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\lib\terms\services\x-terms.push.service.ts + +import { Subject } from 'rxjs'; +import { + XBasePushService, + XBaseEntityPushAction, + IXPushServiceTokenFetcher, + XFrameworkPushServiceConfig, + X_FRAMEWORK_PUSH_SERVICE_CONFIG, + X_FRAMEWORK_PUSH_SERVICE_TOKEN_FETCHER, +} from 'x-framework-push-service'; +import { parseJson } from 'x-framework-core'; +import { Inject, Injectable, Optional } from '@angular/core'; +import { XStringDto } from '../../strings/models/x-string.dto'; +import { HttpTransportType, ITransport } from '@microsoft/signalr'; + +@Injectable({ + providedIn: 'root', +}) +export class XTermsPushService extends XBasePushService { + // + //#region Props ... + onTermsAdded$ = new Subject<{ + model: XStringDto; + actorId: string; + }>(); + onTermsDeleted$ = new Subject<{ + model: XStringDto; + actorId: string; + }>(); + onTermsUpdated$ = new Subject<{ + model: XStringDto; + actorId: string; + }>(); + //#endregion + + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_PUSH_SERVICE_CONFIG) + public config: XFrameworkPushServiceConfig, + @Optional() + @Inject(X_FRAMEWORK_PUSH_SERVICE_TOKEN_FETCHER) + public tokenFetcher?: IXPushServiceTokenFetcher + ) { + super(config, tokenFetcher); + } + //#endregion + + // + //#region Abstraction ... + hubName(): string { + return 'termsHub'; + } + + requiredAccessToken(): boolean { + return true; + } + + transportType(): HttpTransportType | ITransport { + return HttpTransportType.LongPolling; + } + + registerSubjects(): void { + super.registerSubjects(); + + // + this.onTermsAdded((model: XStringDto, actorId: string) => { + this.onTermsAdded$.next({ model, actorId }); + }); + + // + this.onTermsDeleted((model: XStringDto, actorId: string) => { + this.onTermsDeleted$.next({ model, actorId }); + }); + + // + this.onTermsUpdated((model: XStringDto, actorId: string) => { + this.onTermsUpdated$.next({ model, actorId }); + }); + } + //#endregion + + // + //#region Event Listeners ... + // + //#region Onners ... + /** + * Register Terms Added Event Listener ... + * + * @param callback + */ + public onTermsAdded(callback: (model: XStringDto, actorId: string) => any) { + // + this.on(XBaseEntityPushAction.Add, (modelJson: string, actorId: string) => { + // + const model = parseJson(modelJson); + callback(model, actorId); + }); + } + + /** + * Register Terms Deleted Event Listener ... + * + * @param callback + */ + public onTermsDeleted(callback: (model: XStringDto, actorId: string) => any) { + // + this.on( + XBaseEntityPushAction.Delete, + (modelJson: string, actorId: string) => { + // + const model = parseJson(modelJson); + callback(model, actorId); + } + ); + } + + /** + * Register Terms Updated Event Listener ... + * + * @param callback + */ + public onTermsUpdated(callback: (model: XStringDto, actorId: string) => any) { + // + this.on( + XBaseEntityPushAction.Update, + (modelJson: string, actorId: string) => { + // + const model = parseJson(modelJson); + callback(model, actorId); + } + ); + } + //#endregion + + // + //#region Offers ... + /** + * UnRegister Added Event Listener ... + * + * @param callback + */ + public offTermsAdded(callback?: (model: XStringDto, actorId: string) => any) { + this.off(XBaseEntityPushAction.Add, callback); + } + + /** + * UnRegister Deleted Event Listener ... + * + * @param callback + */ + public offTermsDeleted( + callback?: (model: XStringDto, actorId: string) => any + ) { + this.off(XBaseEntityPushAction.Delete, callback); + } + + /** + * UnRegister Updated Event Listener ... + * + * @param callback + */ + public offTermsUpdated( + callback?: (model: XStringDto, actorId: string) => any + ) { + this.off(XBaseEntityPushAction.Update, callback); + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\lib\tokens\x-injectable-tokens.ts + +import { InjectionToken } from '@angular/core'; +import { XSaherElmApiSDKConfig } from '../config/x-saherelm-api-sdk.config'; + +export const X_SAHERELM_API_SDK_CONFIG = new InjectionToken( + 'x_config' +); + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\lib\tools\x-model.tools.ts + +import { isArray, isObject } from 'x-framework-core'; + +/** + * since for better handling soft deleted in database from api side the deleted + * property added to all XEntities, for better comparing objects we have to delete it. + * this method do this for us ... + * + * @param model the input object + * @returns clear object + */ +export function prepareModel(model: T) { + // + if (!isObject(model) || isArray(model)) { + return model; + } + + // + delete (model as any)['deleted']; + + // + return model; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\lib\tools\x-role.tools.ts + +import { + toArray, + hasChild, + XExceptionIDs, + throwException, + XOneOrManyType, +} from 'x-framework-core'; +import { XUserProfileDto } from 'x-framework-identity-sdk'; +import { XUserRoleInfo, XAvailableRoles } from '../typings/x-role.typings'; + +export function toRoleInfo(user: XUserProfileDto): XUserRoleInfo { + // + if (!user) { + return null; + } + + // + const result: XUserRoleInfo = { + isAdmin: hasChild(user.roles) && user.roles.includes(XAvailableRoles.Admin), + isResponsibleManager: + hasChild(user.roles) && + user.roles.includes(XAvailableRoles.ResponsibleManager), + isJournalist: + hasChild(user.roles) && user.roles.includes(XAvailableRoles.Journalist), + isChiefClerk: + hasChild(user.roles) && user.roles.includes(XAvailableRoles.ChiefClerk), + isChiefClerkAssistant: + hasChild(user.roles) && + user.roles.includes(XAvailableRoles.ChiefClerkAssistant), + isActuary: + hasChild(user.roles) && user.roles.includes(XAvailableRoles.Actuary), + isActuaryAssistant: + hasChild(user.roles) && + user.roles.includes(XAvailableRoles.ActuaryAssistant), + isReporter: + hasChild(user.roles) && user.roles.includes(XAvailableRoles.Reporter), + isReporterAssistant: + hasChild(user.roles) && + user.roles.includes(XAvailableRoles.ReporterAssistant), + isAgent: hasChild(user.roles) && user.roles.includes(XAvailableRoles.Agent), + isUser: hasChild(user.roles) && user.roles.includes(XAvailableRoles.User), + }; + + // + return result; +} + +export function toRoleInfoByRoles(roles: string[]): XUserRoleInfo { + // + if (!hasChild(roles)) { + return null; + } + + // + const result: XUserRoleInfo = { + isAdmin: hasChild(roles) && roles.includes(XAvailableRoles.Admin), + isResponsibleManager: + hasChild(roles) && roles.includes(XAvailableRoles.ResponsibleManager), + isJournalist: hasChild(roles) && roles.includes(XAvailableRoles.Journalist), + isChiefClerk: hasChild(roles) && roles.includes(XAvailableRoles.ChiefClerk), + isChiefClerkAssistant: + hasChild(roles) && roles.includes(XAvailableRoles.ChiefClerkAssistant), + isActuary: hasChild(roles) && roles.includes(XAvailableRoles.Actuary), + isActuaryAssistant: + hasChild(roles) && roles.includes(XAvailableRoles.ActuaryAssistant), + isReporter: hasChild(roles) && roles.includes(XAvailableRoles.Reporter), + isReporterAssistant: + hasChild(roles) && roles.includes(XAvailableRoles.ReporterAssistant), + isAgent: hasChild(roles) && roles.includes(XAvailableRoles.Agent), + isUser: hasChild(roles) && roles.includes(XAvailableRoles.User), + }; + + // + return result; +} + +export function getTopRoleByRoleInfo(roleInfo: XUserRoleInfo) { + // + if (!roleInfo) { + throwException(XExceptionIDs.InvalidArgs); + } + + // + if (roleInfo.isAdmin) { + return XAvailableRoles.Admin; + } else if (roleInfo.isResponsibleManager) { + return XAvailableRoles.ResponsibleManager; + } else if (roleInfo.isJournalist) { + return XAvailableRoles.Journalist; + } else if (roleInfo.isChiefClerk) { + return XAvailableRoles.ChiefClerk; + } else if (roleInfo.isChiefClerkAssistant) { + return XAvailableRoles.ChiefClerkAssistant; + } else if (roleInfo.isActuary) { + return XAvailableRoles.Actuary; + } else if (roleInfo.isActuaryAssistant) { + return XAvailableRoles.ActuaryAssistant; + } else if (roleInfo.isReporter) { + return XAvailableRoles.Reporter; + } else if (roleInfo.isReporterAssistant) { + return XAvailableRoles.ReporterAssistant; + } else if (roleInfo.isAgent) { + return XAvailableRoles.Agent; + } else if (roleInfo.isUser) { + return XAvailableRoles.User; + } else { + return null; + } +} + +export function getTopRoleByRoles(roles: string[]) { + // + const roleInfo = toRoleInfoByRoles(roles); + if (!roleInfo) { + return null; + } + + // + return getTopRoleByRoleInfo(roleInfo); +} + +export function getTopRoleByProfile(user: XUserProfileDto) { + // + const roleInfo = toRoleInfo(user); + if (!roleInfo) { + return null; + } + + // + return getTopRoleByRoleInfo(roleInfo); +} + +export function checkIsInRoleByInfo( + roles: XOneOrManyType, + roleInfo: XUserRoleInfo +): boolean { + // + let result = false; + + // + // Normalize Roles ... + const normalRoles = toArray(roles); + + // + for (const role of normalRoles) { + switch (role) { + // + case XAvailableRoles.Admin: + result = result || roleInfo.isAdmin; + break; + + // + case XAvailableRoles.ResponsibleManager: + result = result || roleInfo.isResponsibleManager; + break; + + // + case XAvailableRoles.Journalist: + result = result || roleInfo.isJournalist; + break; + + // + case XAvailableRoles.ChiefClerk: + result = result || roleInfo.isChiefClerk; + break; + + // + case XAvailableRoles.ChiefClerkAssistant: + result = result || roleInfo.isChiefClerkAssistant; + break; + + // + case XAvailableRoles.Actuary: + result = result || roleInfo.isActuary; + break; + + // + case XAvailableRoles.ActuaryAssistant: + result = result || roleInfo.isActuaryAssistant; + break; + + // + case XAvailableRoles.Reporter: + result = result || roleInfo.isReporter; + break; + + // + case XAvailableRoles.ReporterAssistant: + result = result || roleInfo.isReporterAssistant; + break; + + // + case XAvailableRoles.Agent: + result = result || roleInfo.isAgent; + break; + + // + case XAvailableRoles.User: + result = result || roleInfo.isUser; + break; + + // + default: + result = result || false; + break; + } + } + + // + return result; +} + +export function checkIsInRoleByUser( + role: XAvailableRoles, + user: XUserProfileDto +): boolean { + // + // Validate Args ... + if (!role || !user) { + return false; + } + + // + const roleInfo = toRoleInfo(user); + if (!roleInfo) { + return false; + } + + // + switch (role) { + // + case XAvailableRoles.Admin: + return roleInfo.isAdmin; + + // + case XAvailableRoles.ResponsibleManager: + return roleInfo.isResponsibleManager; + + // + case XAvailableRoles.Journalist: + return roleInfo.isJournalist; + + // + case XAvailableRoles.ChiefClerk: + return roleInfo.isChiefClerk; + + // + case XAvailableRoles.ChiefClerkAssistant: + return roleInfo.isChiefClerkAssistant; + + // + case XAvailableRoles.Actuary: + return roleInfo.isActuary; + + // + case XAvailableRoles.ActuaryAssistant: + return roleInfo.isActuaryAssistant; + + // + case XAvailableRoles.Reporter: + return roleInfo.isReporter; + + // + case XAvailableRoles.ReporterAssistant: + return roleInfo.isReporterAssistant; + + // + case XAvailableRoles.Agent: + return roleInfo.isAgent; + + // + case XAvailableRoles.User: + return roleInfo.isUser; + + // + default: + return false; + } +} + +export function extractAvaialableRoles() { + // + const AllRoles = Object.assign({}, XAvailableRoles); + let result = Object.values(AllRoles); + return result; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\lib\tools\x-route.tools.ts + +import { + hasChild, + XKeyValue, + isNullOrUndefined, + isNullOrEmptyString, +} from 'x-framework-core'; + +/** + * Prepare a route by replacing it's params by providing values ... + */ +export function fillRouteParams( + route: string, + params?: Array> +) { + // + let result = ''; + + // + // Validate ... + if (isNullOrEmptyString(route)) { + return result; + } + + // + // Check Params Has Child ... + if (!isNullOrUndefined(params) && hasChild(params)) { + // + result = route; + for (let param of params) { + // + const key = `{${param.key}}`; + result = result.replace(key, param.value); + } + } + + // + return result; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\lib\typings\x-endpoint.typings.ts + +// +//#region Custom Header and Params ... +export enum XCHeader { + Role = 'role', + ForceRole = 'forceRole', +} +//#endregion + +// +//#region Params ... +/** + * Params Usually Used in Query Strings ... + */ +export enum XBaseParams { + XId = 'id', + XIds = 'ids', + XSoftDelete = 'softDelete', + XIgnoreSoftDeleteds = 'ignoreSoftDeleteds', +} + +/** + * prepare all Route Params which used in Account Services Endpoints ... + */ +export enum XBaseEndPointParam { + XId = '{xId}', + XIds = '{xIds}', + XHash = '{xHash}', + XDate = '{xDate}', + XQuery = '{xQuery}', + XTitle = '{xTitle}', + XIdentifier = '{xIdentifier}', + XConnectionID = '{xConnectionId}', + XConnectionIDs = '{xConnectionIds}', + XUserSelectByParam = '{xUserSelectByParam}', + XIgnoreSoftDeleteds = '{xIgnoreSoftDeleteds}', + XUserSelectByParams = '{xUserSelectByParams}', +} +export type XBaseEndPointParams = keyof typeof XBaseEndPointParam; + +/** + * All Commonly Used EndPoints Actions ... + */ +export enum XBaseEndPointAction { + // + // TODO: Complete this ... + Get = '{xId}', + GetAll = '', + FindOne = 'FindOne/{xQuery}', + FindMany = 'FindMany/{xQuery}', + Query = 'Query', + RequestPage = 'RequestPage', + Add = '', + AddOrUpdate = 'AddOrUpdate', + AddMany = 'AddMany', + Update = '{xId}', + UpdateMany = 'UpdateMany', + IsExists = '{xId}/IsExists', + Remove = '{xId}', + RemoveMany = 'RemoveMany', +} +export type XBaseEndPointActions = keyof typeof XBaseEndPointAction; +//#endregion + +// +//#region Api EndPoints ... +/** + * Represent all API End Points ... + */ +export enum XApiEndPoint { + TagsEntity = 'Entities/Tags', + TagsService = 'Services/Tags', + FilesEntity = 'Entities/Files', + FilesService = 'Services/Files', + Configurations = 'Configurations', + StringsEntity = 'Entities/Strings', + StringsService = 'Services/Strings', + WebinarsEntity = 'Entities/Webinars', + WebinarsService = 'Services/Webinars', + TermsAndConditions = 'Configurations', +} + +export declare type XApiEndPoints = keyof typeof XApiEndPoint | string; +//#endregion + +// +//#region Configuration ... +export enum XConfigurationEndPoint { +} + +export type XConfigurationEndPoints = keyof typeof XConfigurationEndPoint; +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\lib\typings\x-push-provider.typings.ts + +import { HttpHeaders } from '@angular/common/http'; +import { isNullOrEmptyString, isNullOrUndefined } from 'x-framework-core'; + +export enum XPushHeaderKeys { + ConnectionId = 'requestID', +} + +/** + * add Connection Id to Headers ... + * + * @param connectionId user connection id ... + * @param headers an instance of HttpHeaders ... + * + * @returns HttpHeaders instance + */ +export function applyConnectionIdToHeader( + connectionId?: string, + headers?: HttpHeaders +) { + // + // New Instance of Headers if Not provided ... + if (isNullOrUndefined(headers)) { + headers = new HttpHeaders(); + } + + // + if (!isNullOrEmptyString(connectionId)) { + headers = headers.set(XPushHeaderKeys.ConnectionId, connectionId); + } + + // + return headers; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\lib\typings\x-role.typings.ts + +export enum XAvailableRoles { + Admin = 'admin', + ResponsibleManager = 'responsible_manager', + Journalist = 'journalist', + ChiefClerk = 'chief_clerk', + ChiefClerkAssistant = 'chief_clerk_assistant', + Actuary = 'actuary', + ActuaryAssistant = 'actuary_assistant', + Reporter = 'reporter', + ReporterAssistant = 'reporter_assistant', + Agent = 'agent', + User = 'user', +} + +export interface XUserRoleInfo { + isAdmin: boolean; + isResponsibleManager: boolean; + isJournalist: boolean; + isChiefClerk: boolean; + isChiefClerkAssistant: boolean; + isActuary: boolean; + isActuaryAssistant: boolean; + isReporter: boolean; + isReporterAssistant: boolean; + isAgent: boolean; + isUser: boolean; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\lib\webinars\x-webinar.services.ts + +import { Injectable } from '@angular/core'; +import { XWebinarPushService } from './services/x-webinar.push.service'; +import { XWebinarEntityService } from './services/x-webinar.entity.service'; +import { XWebinarProviderService } from './services/x-webinar.provider.service'; +import { XWebinarEntityPushService } from './services/x-webinar.entity.push.service'; +import { XWebinarSubscriberEntityService } from './services/x-webinarsubscriber.entity.service'; + +/** + * Export All Provided Webinar related Services ... + */ +@Injectable({ + providedIn: 'root', +}) +export class XWebinarServices { + // + //#region Constructor ... + public constructor( + public webinarPushService: XWebinarPushService, + public webinarEntityService: XWebinarEntityService, + public webinarProviderService: XWebinarProviderService, + public webinarEntityPushService: XWebinarEntityPushService, + public webinarSubscriberEntityService: XWebinarSubscriberEntityService + ) {} + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\lib\webinars\x-webinar.typings.ts + +/** + * All Available EndPoint Routes ... + */ +export enum XWebinarServiceRoute { + CreateWebinar = '', + GetWebinar = '{id}', + GetWebinars = 'All', + UpdateWebinar = '{id}', + RemoveWebinar = '{id}', + QueryWebinars = 'Query', + GetFiles = '{id}/Files', + GetTags = '{id}/Tags', + AttachTags = '{id}/Tags', + DetachTags = '{id}/Tags', + RemoveFiles = '{id}/Files', + UploadFiles = '{id}/Files', + QueryOwnedWebinars = 'Query/Owned', + GetSubscribers = '{id}/Subscribers', + QueryJoinableWebinars = 'Query/Joinable', + StreamFile = '{id}/Files/{fileId}/Stream', + Subscribe = '{id}/Subscribe/{subscriberId}', + QuerySubscribers = '{id}/Subscribers/Query', + DownloadFile = '{id}/Files/{fileId}/Download', + Unsubscribe = '{id}/Unsubscribe/{subscriberId}', + GetSubscriber = '{id}/Subscriber/{subscriberId}', + IsSubscribed = '{id}/IsSubscribed/{subscriberId}', +} + +/** + * All Params Which Used in Related Services ... + */ +export enum XWebinarParam { + Id = 'id', + Ids = 'ids', + Tags = 'tags', + Files = 'files', + FileId = 'fileId', + SubscriberId = 'subscriberId', +} + +/** + * Webinar Hub Actions ... + */ +export enum XWebinarPushAction { + /// + /// When a Webinar is Available to Join ... + /// + WebinarOpened = 'WebinarOpened', + + /// + /// When a Webinar Closed ... + /// + WebinarClosed = 'WebinarClosed', + + /// + /// When a Viewer is Joined to Webinar ... + /// + ViewerJoined = 'ViewerJoined', + + /// + /// When a Webinar Subscriber Leaved a Webinar ... + /// + ViewerLeaved = 'ViewerLeaved', + + /// + /// Retrieve Specified Webinar Visitors ... + /// + WebinarVisitors = 'WebinarVisitors', + + /// + /// Send a Message to Webinar ... + /// + Message = 'Message', +} + +/** + * Webinar Entity Hub Actions ... + */ +export enum XWebinarEntityPushAction { + Subscribe = 'Subscribe', + Unsubscribe = 'Unsubscribe', +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\lib\webinars\models\x-webinar.dto.ts + +import { + XBaseDto, + typeGuard, + isSameObject, + XBaseNumberIDDto, + isNullOrUndefined, + isNullOrEmptyString, +} from 'x-framework-core'; +import { + XResourceDto, + prepareResourceDtoFields, +} from '../../strings/models/x-string.dto'; +import { preparePersonDtoFields, XPersonDto } from '../../models/x-person.dto'; + +/** + * Describe Webinar Types ... + */ +export enum XWebinarType { + Public = 0, + Private = 1, +} + +export function ToWebinarType(index: number) { + // + if (index == 0) { + return XWebinarType.Public; + } else if (index == 1) { + return XWebinarType.Private; + } + + // + return undefined; +} + +/** + * Represent a Webinar Model ... + */ +export interface XWebinarDto extends XBaseDto { + id: string; + ownerId: string; + createdOn: Date; + enabled: boolean; + type: XWebinarType; + owner?: XPersonDto; + title: XResourceDto; + description: XResourceDto; +} + +/** + * Default Model ... + */ +export const defaultWebinarDto: XWebinarDto = { + id: '', + ownerId: '', + enabled: false, + type: undefined, + owner: undefined, + title: undefined, + createdOn: undefined, + description: undefined, +}; + +/** + * Prepare Model Fields ... + * + * @param model + * @returns + */ +export function prepareWebinarDtoFields(model?: XWebinarDto) { + // + if (isNullOrUndefined(model)) { + model = { + ...defaultWebinarDto, + }; + } + + // + model = { + id: isNullOrEmptyString(model.id) ? defaultWebinarDto.id : model.id, + type: isNullOrUndefined(model.type) ? defaultWebinarDto.type : model.type, + enabled: isNullOrUndefined(model.enabled) + ? defaultWebinarDto.enabled + : model.enabled, + ownerId: isNullOrEmptyString(model.ownerId) + ? defaultWebinarDto.ownerId + : model.ownerId, + createdOn: isNullOrUndefined(model.createdOn) + ? defaultWebinarDto.createdOn + : model.createdOn, + owner: isNullOrUndefined(model.owner) + ? defaultWebinarDto.owner + : preparePersonDtoFields(model.owner), + title: isNullOrUndefined(model.title) + ? defaultWebinarDto.title + : prepareResourceDtoFields(model.title), + description: isNullOrUndefined(model.description) + ? defaultWebinarDto.description + : prepareResourceDtoFields(model.description), + }; + + // + return model; +} + +/** + * Check Model is Default or not ... + * + * @param model + * @returns + */ +export function isDefaultWebinarModel(model?: XWebinarDto) { + return isSameObject(prepareWebinarDtoFields(model), defaultWebinarDto); +} + +/** + * Webinar Subscriber Model ... + */ +export interface XWebinarSubscriberDto extends XBaseNumberIDDto { + webinarId: string; + subscribedOn: Date; + subscriberId: string; + subscriber: XPersonDto; +} + +/** + * Default Model ... + */ +export const defaultWebinarSubscriberDto: XWebinarSubscriberDto = { + id: undefined, + webinarId: '', + subscriberId: '', + subscriber: undefined, + subscribedOn: undefined, +}; + +/** + * Prepare Model Fields ... + * + * @param model + * @returns + */ +export function prepareWebinarSubscriberDtoFields( + model?: XWebinarSubscriberDto +) { + // + if (isNullOrUndefined(model)) { + model = { + ...defaultWebinarSubscriberDto, + }; + } + + // + model = { + id: + isNullOrUndefined(model.id) || model.id <= 0 + ? defaultWebinarSubscriberDto.id + : model.id, + webinarId: isNullOrEmptyString(model.webinarId) + ? defaultWebinarSubscriberDto.webinarId + : model.webinarId, + subscriberId: isNullOrEmptyString(model.subscriberId) + ? defaultWebinarSubscriberDto.subscriberId + : model.subscriberId, + subscribedOn: isNullOrUndefined(model.subscribedOn) + ? defaultWebinarSubscriberDto.subscribedOn + : model.subscribedOn, + subscriber: isNullOrUndefined(model.subscriber) + ? defaultWebinarSubscriberDto.subscriber + : preparePersonDtoFields(model.subscriber), + }; + + // + return model; +} + +/** + * Check Model is Default or not ... + * + * @param model + * @returns + */ +export function isDefaultWebinarSubscriberDto(model?: XWebinarSubscriberDto) { + return isSameObject( + prepareWebinarSubscriberDtoFields(model), + defaultWebinarSubscriberDto + ); +} + +/** + * Model Type Guard ... + */ +export const isXWebinarDto = typeGuard([ + 'id', + 'ownerId', + 'type', + 'createdOn', + 'description', + 'title', +]); + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\lib\webinars\services\x-webinar.entity.push.service.ts + +import { Subject } from 'rxjs'; +import { Injectable } from '@angular/core'; +import { isNullOrUndefined, parseJson } from 'x-framework-core'; +import { XWebinarEntityPushAction } from '../x-webinar.typings'; +import { XBaseEntityPushService } from 'x-framework-push-service'; +import { HttpTransportType, ITransport } from '@microsoft/signalr'; +import { XWebinarDto, XWebinarSubscriberDto } from '../models/x-webinar.dto'; + +@Injectable({ + providedIn: 'root', +}) +export class XWebinarEntityPushService extends XBaseEntityPushService { + // + //#region Props ... + onSubscribed$ = new Subject<{ + model: XWebinarSubscriberDto; + actorId: string; + }>(); + onUnsubscribed$ = new Subject<{ + model: XWebinarSubscriberDto; + actorId: string; + }>(); + //#endregion + + // + //#region Abstracts ... + hubName(): string { + return 'webinarEntityHub'; + } + + requiredAccessToken(): boolean { + return true; + } + + transportType(): HttpTransportType | ITransport { + return HttpTransportType.LongPolling; + } + + registerSubjects(): void { + super.registerSubjects(); + + // + this.onSubscribed((model: XWebinarSubscriberDto, actorId: string) => { + this.onSubscribed$.next({ model, actorId }); + }); + + // + this.onUnsubscribed((model: XWebinarSubscriberDto, actorId: string) => { + this.onUnsubscribed$.next({ model, actorId }); + }); + } + //#endregion + + // + //#region Actions ... + /** + * Fire New Subscriber Action ... + * + * @param model + * @returns + */ + public async subscribe(model: XWebinarSubscriberDto) { + // + // Validate ... + let isValid = !isNullOrUndefined(model); + if (!isValid) { + return; + } + + // + await this.invoke( + XWebinarEntityPushAction.Subscribe, + JSON.stringify(model) + ); + } + + /** + * Fire Unsubscribe Action ... + * + * @param model + * @returns + */ + public async unsubscribe(model: XWebinarSubscriberDto) { + // + // Validate ... + let isValid = !isNullOrUndefined(model); + if (!isValid) { + return; + } + + // + await this.invoke( + XWebinarEntityPushAction.Unsubscribe, + JSON.stringify(model) + ); + } + //#endregion + + // + //#region Event Listeners ... + // + //#region Onners ... + /** + * Register Subscribe Event Listener ... + * + * @param callback + */ + public onSubscribed( + callback: (model: XWebinarSubscriberDto, actorId: string) => any + ) { + // + this.on( + XWebinarEntityPushAction.Subscribe, + (modelJson: string, actorId: string) => { + // + const model = parseJson(modelJson); + callback(model, actorId); + } + ); + } + + /** + * Register Unsubscribe Event Listener ... + * + * @param callback + */ + public onUnsubscribed( + callback: (model: XWebinarSubscriberDto, actorId: string) => any + ) { + // + this.on( + XWebinarEntityPushAction.Unsubscribe, + (modelJson: string, actorId: string) => { + // + const model = parseJson(modelJson); + callback(model, actorId); + } + ); + } + //#endregion + + // + //#region Offers ... + /** + * UnRegister Subscribe Event Listener ... + * + * @param callback + */ + public offSubscribed( + callback?: (model: XWebinarSubscriberDto, actorId: string) => any + ) { + this.off(XWebinarEntityPushAction.Subscribe, callback); + } + + /** + * UnRegister Unsubscribe Event Listener ... + * + * @param callback + */ + public offUnsubscribed( + callback?: (model: XWebinarSubscriberDto, actorId: string) => any + ) { + this.off(XWebinarEntityPushAction.Unsubscribe, callback); + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\lib\webinars\services\x-webinar.entity.service.ts + +import { Router } from '@angular/router'; +import { HttpClient } from '@angular/common/http'; +import { Inject, Injectable } from '@angular/core'; +import { XWebinarDto } from '../models/x-webinar.dto'; +import { XApiEndPoint } from '../../typings/x-endpoint.typings'; +import { XBaseEntityService } from '../../base/x-base-entity.service'; +import { X_API_CONFIG, XApiConfiguration } from 'x-framework-identity-sdk'; +import { X_SAHERELM_API_SDK_CONFIG } from '../../tokens/x-injectable-tokens'; +import { XSaherElmApiSDKConfig } from '../../config/x-saherelm-api-sdk.config'; + +/** + * Provides all Entity Actions as a Service ... + */ +@Injectable({ + providedIn: 'root', +}) +export class XWebinarEntityService extends XBaseEntityService< + XWebinarDto, + string +> { + // + //#region Constructor ... + constructor( + protected router: Router, + protected httpClient: HttpClient, + @Inject(X_SAHERELM_API_SDK_CONFIG) config: XSaherElmApiSDKConfig, + @Inject(X_API_CONFIG) apiConfig: XApiConfiguration + ) { + super( + XApiEndPoint.WebinarsEntity, + true, + true, + httpClient, + config, + apiConfig + ); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\lib\webinars\services\x-webinar.provider.service.ts + +import { + toArray, + hasChild, + toFormData, + XValidators, + toListString, + XOneOrManyType, + isNullOrUndefined, + isNullOrEmptyString, +} from 'x-framework-core'; +import { + HttpEvent, + HttpClient, + HttpParams, + HttpHeaders, + HttpResponse, +} from '@angular/common/http'; +import { + XQueryDto, + X_API_CONFIG, + XBaseApiService, + XQueryResultDto, + XApiConfiguration, +} from 'x-framework-identity-sdk'; +import { Observable } from 'rxjs'; +import { Router } from '@angular/router'; +import { Inject, Injectable } from '@angular/core'; +import { XFileDto } from '../../files/models/x-file.dto'; +import { fillRouteParams } from '../../tools/x-route.tools'; +import { XApiEndPoint } from '../../typings/x-endpoint.typings'; +import { XWebinarParam, XWebinarServiceRoute } from '../x-webinar.typings'; +import { X_SAHERELM_API_SDK_CONFIG } from '../../tokens/x-injectable-tokens'; +import { XWebinarDto, XWebinarSubscriberDto } from '../models/x-webinar.dto'; +import { XSaherElmApiSDKConfig } from '../../config/x-saherelm-api-sdk.config'; +import { applyConnectionIdToHeader } from '../../typings/x-push-provider.typings'; + +/** + * Provides all Provider Actions as a Service ... + */ +@Injectable({ + providedIn: 'root', +}) +export class XWebinarProviderService extends XBaseApiService { + // + //#region Constructor ... + constructor( + protected router: Router, + protected httpClient: HttpClient, + @Inject(X_SAHERELM_API_SDK_CONFIG) config: XSaherElmApiSDKConfig, + @Inject(X_API_CONFIG) apiConfig: XApiConfiguration + ) { + super( + XApiEndPoint.WebinarsService, + true, + true, + httpClient, + config, + apiConfig + ); + } + //#endregion + + // + //#region Webinar ... + // + //#region Create ... + /** + * Create Specified Described Webinars ... + * + * @param model + * @param tags + * @param files + * @param connectionId + * @param observe + * @param reportProgress + */ + public create( + model: XWebinarDto, + tags?: XOneOrManyType, + files?: XOneOrManyType, + connectionId?: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public create( + model: XWebinarDto, + tags?: XOneOrManyType, + files?: XOneOrManyType, + connectionId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public create( + model: XWebinarDto, + tags?: XOneOrManyType, + files?: XOneOrManyType, + connectionId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public create( + model: XWebinarDto, + tags?: XOneOrManyType, + files?: XOneOrManyType, + connectionId?: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotNull(model); + XValidators.validateNotNull(model.title); + XValidators.validateNotEmpty(model.ownerId); + XValidators.validateNotNull(model.description); + + // + // Since Here we support Medias, must sent all Body as + // Form Data ... + let formData: FormData = new FormData(); + + // + // Add Model to FormData ... + formData = toFormData( + { ...model }, // Model ... + undefined, // Options ... + formData, // Exists FormData Using ... + undefined // Keys Prefix ... + ); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = applyConnectionIdToHeader(connectionId, headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute( + XWebinarServiceRoute.CreateWebinar + ); + + // + // Handle Tags ... + if (!isNullOrUndefined(tags)) { + // + const tagsArr = toArray(tags); + if (hasChild(tagsArr)) { + // + const tagsListString = toListString(tagsArr); + if (!isNullOrEmptyString(tagsListString)) { + // + params = this.addCustomParam(params, { + key: XWebinarParam.Tags, + value: tagsListString, + }); + } + } + } + + // + // Handle Files ... + if (!isNullOrUndefined(files)) { + // + const filesArr = toArray(files); + if (hasChild(filesArr)) { + // + // Convert Files Array to Form Data ... + filesArr.forEach((f) => { + formData.append(XWebinarParam.Files, f, f.name); + }); + } + } + + // + // Prepare Result ... + return this.httpClient.post(endPointPath, formData, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region Update ... + /** + * Update Webinar ... + * + * @param model + * @param connectionId + * @param observe + * @param reportProgress + */ + public update( + model: XWebinarDto, + connectionId?: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public update( + model: XWebinarDto, + connectionId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public update( + model: XWebinarDto, + connectionId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public update( + model: XWebinarDto, + connectionId?: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotNull(model); + XValidators.validateNotEmpty(model.id); + XValidators.validateNotNull(model.title); + XValidators.validateNotEmpty(model.ownerId); + XValidators.validateNotNull(model.description); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + headers = applyConnectionIdToHeader(connectionId, headers); + + // + // Prepare Endpoint + const endPointPath = fillRouteParams( + // + // Base Route on Endpoint ... + this.getActionRoute(XWebinarServiceRoute.UpdateWebinar), + // + // Params ... + [ + { + key: XWebinarParam.Id, + value: model.id, + }, + ] + ); + + // + // Prepare Result ... + return this.httpClient.put(endPointPath, model, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region Remove ... + /** + * Remove Specified Webinar ... + * + * @param id + * @param connectionId + * @param observe + * @param reportProgress + */ + public remove( + id: string, + connectionId?: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public remove( + id: string, + connectionId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public remove( + id: string, + connectionId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public remove( + id: string, + connectionId?: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(id); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + headers = applyConnectionIdToHeader(connectionId, headers); + + // + // Prepare Endpoint + const endPointPath = fillRouteParams( + // + // Base Route on Endpoint ... + this.getActionRoute(XWebinarServiceRoute.RemoveWebinar), + // + // Params ... + [ + { + key: XWebinarParam.Id, + value: id, + }, + ] + ); + + // + // Prepare Result ... + return this.httpClient.delete(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region Get ... + /** + * Get Specified Webinar ... + */ + public get( + id: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public get( + id: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public get( + id: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public get( + id: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(id); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + // headers = applyConnectionIdToHeader(connectionId, headers); + + // + // Prepare Endpoint + const endPointPath = fillRouteParams( + // + // Base Route on Endpoint ... + this.getActionRoute(XWebinarServiceRoute.GetWebinar), + // + // Params ... + [ + { + key: XWebinarParam.Id, + value: id, + }, + ] + ); + + // + // Prepare Result ... + return this.httpClient.get(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region All ... + /** + * Retrieve all Exists Webinars ... + * + * @param observe + * @param reportProgress + */ + public all( + observe?: 'body', + reportProgress?: boolean + ): Observable>; + public all( + observe?: 'response', + reportProgress?: boolean + ): Observable>>; + public all( + observe?: 'events', + reportProgress?: boolean + ): Observable>>; + public all( + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Preparing HttpParams ... + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute(XWebinarServiceRoute.GetWebinars); + + // + // Prepare Result ... + return this.httpClient.get>(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region Query ... + /** + * Query Webinars ... + * + * @param query + * @param observe + * @param reportProgress + */ + public query( + query: XQueryDto, + observe?: 'body', + reportProgress?: boolean + ): Observable>; + public query( + query: XQueryDto, + observe?: 'response', + reportProgress?: boolean + ): Observable>>; + public query( + query: XQueryDto, + observe?: 'events', + reportProgress?: boolean + ): Observable>>; + public query( + query: XQueryDto, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotNull(query); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Preparing HttpParams ... + params = this.generateXQueryHttpParams(query); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute( + XWebinarServiceRoute.QueryWebinars + ); + + // + // Prepare Result ... + return this.httpClient.get>(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region QueryOwned ... + /** + * Query Owned Webinars ... + * + * @param query + * @param observe + * @param reportProgress + */ + public queryOwned( + query: XQueryDto, + observe?: 'body', + reportProgress?: boolean + ): Observable>; + public queryOwned( + query: XQueryDto, + observe?: 'response', + reportProgress?: boolean + ): Observable>>; + public queryOwned( + query: XQueryDto, + observe?: 'events', + reportProgress?: boolean + ): Observable>>; + public queryOwned( + query: XQueryDto, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotNull(query); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Preparing HttpParams ... + params = this.generateXQueryHttpParams(query); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute( + XWebinarServiceRoute.QueryOwnedWebinars + ); + + // + // Prepare Result ... + return this.httpClient.get>(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region QueryJoinable ... + /** + * Query Joinable Webinars ... + * + * @param query + * @param observe + * @param reportProgress + */ + public queryJoinable( + query: XQueryDto, + observe?: 'body', + reportProgress?: boolean + ): Observable>; + public queryJoinable( + query: XQueryDto, + observe?: 'response', + reportProgress?: boolean + ): Observable>>; + public queryJoinable( + query: XQueryDto, + observe?: 'events', + reportProgress?: boolean + ): Observable>>; + public queryJoinable( + query: XQueryDto, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotNull(query); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Preparing HttpParams ... + params = this.generateXQueryHttpParams(query); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute( + XWebinarServiceRoute.QueryJoinableWebinars + ); + + // + // Prepare Result ... + return this.httpClient.get>(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + //#endregion + + // + //#region Subscriber ... + // + //#region Subscribe ... + /** + * Subscribe a User to Webinar ... + * + * @param webinarId + * @param subscriberId + * @param connectionId + * @param observe + * @param reportProgress + */ + public subscribe( + webinarId: string, + subscriberId: string, + connectionId?: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public subscribe( + webinarId: string, + subscriberId: string, + connectionId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public subscribe( + webinarId: string, + subscriberId: string, + connectionId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public subscribe( + webinarId: string, + subscriberId: string, + connectionId?: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(webinarId); + XValidators.validateNotEmpty(subscriberId); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + headers = applyConnectionIdToHeader(connectionId, headers); + + // + // Prepare Endpoint + const endPointPath = fillRouteParams( + // + // Base Route on Endpoint ... + this.getActionRoute(XWebinarServiceRoute.Subscribe), + // + // Params ... + [ + { + key: XWebinarParam.Id, + value: webinarId, + }, + { + key: XWebinarParam.SubscriberId, + value: subscriberId, + }, + ] + ); + + // + // Prepare Result ... + return this.httpClient.post(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region Unsubscribe ... + /** + * Unsubscribe Specified User from a Specified Webinar ... + * + * @param webinarId + * @param subscriberId + * @param connectionId + * @param observe + * @param reportProgress + */ + public unsubscribe( + webinarId: string, + subscriberId: string, + connectionId?: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public unsubscribe( + webinarId: string, + subscriberId: string, + connectionId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public unsubscribe( + webinarId: string, + subscriberId: string, + connectionId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public unsubscribe( + webinarId: string, + subscriberId: string, + connectionId?: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(webinarId); + XValidators.validateNotEmpty(subscriberId); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + headers = applyConnectionIdToHeader(connectionId, headers); + + // + // Prepare Endpoint + const endPointPath = fillRouteParams( + // + // Base Route on Endpoint ... + this.getActionRoute(XWebinarServiceRoute.Unsubscribe), + // + // Params ... + [ + { + key: XWebinarParam.Id, + value: webinarId, + }, + { + key: XWebinarParam.SubscriberId, + value: subscriberId, + }, + ] + ); + + // + // Prepare Result ... + return this.httpClient.delete(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region IsSubscribed ... + /** + * Check Specified User Subscribed on a Webinar or not ... + * + * @param webinarId + * @param subscriberId + * @param observe + * @param reportProgress + */ + public isSubscribed( + webinarId: string, + subscriberId: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public isSubscribed( + webinarId: string, + subscriberId: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public isSubscribed( + webinarId: string, + subscriberId: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public isSubscribed( + webinarId: string, + subscriberId: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(webinarId); + XValidators.validateNotEmpty(subscriberId); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint + const endPointPath = fillRouteParams( + // + // Base Route on Endpoint ... + this.getActionRoute(XWebinarServiceRoute.IsSubscribed), + // + // Params ... + [ + { + key: XWebinarParam.Id, + value: webinarId, + }, + { + key: XWebinarParam.SubscriberId, + value: subscriberId, + }, + ] + ); + + // + // Prepare Result ... + return this.httpClient.get(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region GetSubscriber ... + /** + * Retrieve Specified Subscriber ... + * + * @param webinarId + * @param subscriberId + * @param observe + * @param reportProgress + */ + public getSubscriber( + webinarId: string, + subscriberId: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public getSubscriber( + webinarId: string, + subscriberId: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public getSubscriber( + webinarId: string, + subscriberId: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public getSubscriber( + webinarId: string, + subscriberId: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(webinarId); + XValidators.validateNotEmpty(subscriberId); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint + const endPointPath = fillRouteParams( + // + // Base Route on Endpoint ... + this.getActionRoute(XWebinarServiceRoute.GetSubscriber), + // + // Params ... + [ + { + key: XWebinarParam.Id, + value: webinarId, + }, + { + key: XWebinarParam.SubscriberId, + value: subscriberId, + }, + ] + ); + + // + // Prepare Result ... + return this.httpClient.get(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region GetSubscribers ... + /** + * Retrieve Specified Webinars Subscribers ... + * + * @param webinarId + * @param observe + * @param reportProgress + */ + public getSubscribers( + webinarId: string, + observe?: 'body', + reportProgress?: boolean + ): Observable>; + public getSubscribers( + webinarId: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>>; + public getSubscribers( + webinarId: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>>; + public getSubscribers( + webinarId: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(webinarId); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint + const endPointPath = fillRouteParams( + // + // Base Route on Endpoint ... + this.getActionRoute(XWebinarServiceRoute.GetSubscribers), + // + // Params ... + [ + { + key: XWebinarParam.Id, + value: webinarId, + }, + ] + ); + + // + // Prepare Result ... + return this.httpClient.get>(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region QuerySubscribers ... + /** + * Query Specified Webinar's Subscribers ... + * + * @param query + * @param webinarId + * @param observe + * @param reportProgress + */ + public querySubscribers( + query: XQueryDto, + webinarId: string, + observe?: 'body', + reportProgress?: boolean + ): Observable>; + public querySubscribers( + query: XQueryDto, + webinarId: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>>; + public querySubscribers( + query: XQueryDto, + webinarId: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>>; + public querySubscribers( + query: XQueryDto, + webinarId: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotNull(query); + XValidators.validateNotEmpty(webinarId); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint + const endPointPath = fillRouteParams( + // + // Base Route on Endpoint ... + this.getActionRoute(XWebinarServiceRoute.QuerySubscribers), + // + // Params ... + [ + { + key: XWebinarParam.Id, + value: webinarId, + }, + ] + ); + + // + // Prepare Result ... + return this.httpClient.get>( + endPointPath, + { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + } + ); + } + //#endregion + //#endregion + + // + //#region Files ... + // + //#region UploadFiles ... + /** + * Upload and Reference Files to Specified Webinar ... + * + * @param webinarId + * @param files + * @param connectionId + * @param observe + * @param reportProgress + */ + public uploadFiles( + webinarId: string, + files: XOneOrManyType, + connectionId?: string, + observe?: 'body', + reportProgress?: boolean + ): Observable>; + public uploadFiles( + webinarId: string, + files: XOneOrManyType, + connectionId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>>; + public uploadFiles( + webinarId: string, + files: XOneOrManyType, + connectionId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>>; + public uploadFiles( + webinarId: string, + files: XOneOrManyType, + connectionId?: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotNull(files); + XValidators.validateNotEmpty(webinarId); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Preparing Form Data ... + let formData = new FormData(); + const filesArr = toArray(files); + if (hasChild(filesArr)) { + // + // Convert Files Array to Form Data ... + filesArr.forEach((f) => { + formData.append(XWebinarParam.Files, f, f.name); + }); + } + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = applyConnectionIdToHeader(connectionId, headers); + + // + // Prepare Endpoint + const endPointPath = fillRouteParams( + // + // Base Route on Endpoint ... + this.getActionRoute(XWebinarServiceRoute.UploadFiles), + // + // Params ... + [ + { + key: XWebinarParam.Id, + value: webinarId, + }, + ] + ); + + // + // Prepare Result ... + return this.httpClient.post>(endPointPath, formData, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region RemoveFiles ... + /** + * Remove Files of Specified Webinar ... + * + * @param id + * @param ids + * @param connectionId + * @param observe + * @param reportProgress + */ + public removeFiles( + id: string, + ids?: string, + connectionId?: string, + observe?: 'body', + reportProgress?: boolean + ): Observable>; + public removeFiles( + id: string, + ids?: string, + connectionId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>>; + public removeFiles( + id: string, + ids?: string, + connectionId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>>; + public removeFiles( + id: string, + ids?: string, + connectionId?: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(id); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Adding ids to Parmas ... + if (!isNullOrEmptyString(ids)) { + // + params = this.addCustomParam(params, { + key: XWebinarParam.Ids, + value: ids, + }); + } + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + headers = applyConnectionIdToHeader(connectionId, headers); + + // + // Prepare Endpoint + const endPointPath = fillRouteParams( + // + // Base Route on Endpoint ... + this.getActionRoute(XWebinarServiceRoute.RemoveFiles), + // + // Params ... + [ + { + key: XWebinarParam.Id, + value: id, + }, + ] + ); + + // + // Prepare Result ... + return this.httpClient.delete>(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region GetFiles ... + /** + * Get Files of Specified Webinar ... + * + * @param id + * @param connectionId + * @param observe + * @param reportProgress + */ + public getFiles( + id: string, + observe?: 'body', + reportProgress?: boolean + ): Observable>; + public getFiles( + id: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>>; + public getFiles( + id: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>>; + public getFiles( + id: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(id); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint + const endPointPath = fillRouteParams( + // + // Base Route on Endpoint ... + this.getActionRoute(XWebinarServiceRoute.GetFiles), + // + // Params ... + [ + { + key: XWebinarParam.Id, + value: id, + }, + ] + ); + + // + // Prepare Result ... + return this.httpClient.get>(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region StreamFiles ... + /** + * Stream Specified File of Specified Webinar ... + * + * @param id + * @param fileId + * @param observe + * @param reportProgress + */ + public streamFile( + id: string, + fileId: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public streamFile( + id: string, + fileId: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public streamFile( + id: string, + fileId: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public streamFile( + id: string, + fileId: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(id); + XValidators.validateNotEmpty(fileId); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + + // + // Prepare Endpoint + const endPointPath = fillRouteParams( + // + // Base Route on Endpoint ... + this.getActionRoute(XWebinarServiceRoute.StreamFile), + // + // Params ... + [ + { + key: XWebinarParam.Id, + value: id, + }, + { + key: XWebinarParam.FileId, + value: fileId, + }, + ] + ); + + // + // Prepare Result ... + return this.httpClient.get(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + responseType: 'arraybuffer', + reportProgress, + }); + } + //#endregion + + // + //#region DownloadFile ... + /** + * Download Specified File of Specified Webinar ... + * + * @param id + * @param fileId + * @param observe + * @param reportProgress + */ + public downloadFile( + id: string, + fileId: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public downloadFile( + id: string, + fileId: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public downloadFile( + id: string, + fileId: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public downloadFile( + id: string, + fileId: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(id); + XValidators.validateNotEmpty(fileId); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + + // + // Prepare Endpoint + const endPointPath = fillRouteParams( + // + // Base Route on Endpoint ... + this.getActionRoute(XWebinarServiceRoute.DownloadFile), + // + // Params ... + [ + { + key: XWebinarParam.Id, + value: id, + }, + { + key: XWebinarParam.FileId, + value: fileId, + }, + ] + ); + + // + // Prepare Result ... + return this.httpClient.get(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + responseType: 'blob', + reportProgress, + }); + } + //#endregion + //#endregion + + // + //#region Tags ... + // + //#region AttachTags ... + /** + * Attach Specified Tags of Specified Webinar ... + * + * @param id + * @param tags + * @param connectionId + * @param observe + * @param reportProgress + */ + public attachTags( + id: string, + tags?: string, + connectionId?: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public attachTags( + id: string, + tags?: string, + connectionId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public attachTags( + id: string, + tags?: string, + connectionId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public attachTags( + id: string, + tags?: string, + connectionId?: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(id); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Attach Tags to Params ... + if (!isNullOrEmptyString(tags)) { + // + params = this.addCustomParam(params, { + key: XWebinarParam.Tags, + value: tags, + }); + } + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + headers = applyConnectionIdToHeader(connectionId, headers); + + // + // Prepare Endpoint + const endPointPath = fillRouteParams( + // + // Base Route on Endpoint ... + this.getActionRoute(XWebinarServiceRoute.AttachTags), + // + // Params ... + [ + { + key: XWebinarParam.Id, + value: id, + }, + ] + ); + + // + // Prepare Result ... + return this.httpClient.post(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region DetachTags ... + /** + * Detach Specified Tags of Specified Webinar ... + * + * @param id + * @param tags + * @param connectionId + * @param observe + * @param reportProgress + */ + public detachTags( + id: string, + tags?: string, + connectionId?: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public detachTags( + id: string, + tags?: string, + connectionId?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public detachTags( + id: string, + tags?: string, + connectionId?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public detachTags( + id: string, + tags?: string, + connectionId?: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(id); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Attach Tags to Params ... + if (!isNullOrEmptyString(tags)) { + // + params = this.addCustomParam(params, { + key: XWebinarParam.Tags, + value: tags, + }); + } + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + headers = applyConnectionIdToHeader(connectionId, headers); + + // + // Prepare Endpoint + const endPointPath = fillRouteParams( + // + // Base Route on Endpoint ... + this.getActionRoute(XWebinarServiceRoute.DetachTags), + // + // Params ... + [ + { + key: XWebinarParam.Id, + value: id, + }, + ] + ); + + // + // Prepare Result ... + return this.httpClient.delete(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + + // + //#region GetTags ... + /** + * Retrieve Tags of Specified Webinar ... + * + * @param id + * @param observe + * @param reportProgress + */ + public getTags( + id: string, + observe?: 'body', + reportProgress?: boolean + ): Observable>; + public getTags( + id: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>>; + public getTags( + id: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>>; + public getTags( + id: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(id); + + // + // Base Requirements ... + let params: HttpParams = null; + let headers: HttpHeaders = this.defaultHeaders; + + // + // Authentication ... + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint + const endPointPath = fillRouteParams( + // + // Base Route on Endpoint ... + this.getActionRoute(XWebinarServiceRoute.GetTags), + // + // Params ... + [ + { + key: XWebinarParam.Id, + value: id, + }, + ] + ); + + // + // Prepare Result ... + return this.httpClient.get>(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + params, + reportProgress, + }); + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\lib\webinars\services\x-webinar.push.service.ts + +import { Subject } from 'rxjs'; +import { + XRTCPeerService, + XHubConnectionDto, + XBaseWebRTCService, + IXPushServiceTokenFetcher, + XFrameworkPushServiceConfig, + X_FRAMEWORK_PUSH_SERVICE_CONFIG, + X_FRAMEWORK_PUSH_SERVICE_TOKEN_FETCHER, +} from 'x-framework-push-service'; +import { isNullOrEmptyString } from 'x-framework-core'; +import { XWebinarPushAction } from '../x-webinar.typings'; +import { Inject, Injectable, Optional } from '@angular/core'; +import { HttpTransportType, ITransport } from '@microsoft/signalr'; + +@Injectable({ + providedIn: 'root', +}) +export class XWebinarPushService extends XBaseWebRTCService { + // + //#region Props ... + onViewerJoined$ = new Subject<{ + viewerId: string; + webinarId: string; + userId: string; + username?: string; + }>(); + onViewerLeaved$ = new Subject<{ + viewerId: string; + webinarId: string; + userId: string; + username?: string; + }>(); + onWebinarOpened$ = new Subject(); + onWebinarClosed$ = new Subject(); + //#endregion + + // + //#region Constructor ... + constructor( + public rtcPeerService: XRTCPeerService, + @Inject(X_FRAMEWORK_PUSH_SERVICE_CONFIG) + public config: XFrameworkPushServiceConfig, + @Optional() + @Inject(X_FRAMEWORK_PUSH_SERVICE_TOKEN_FETCHER) + public tokenFetcher?: IXPushServiceTokenFetcher + ) { + super(config, tokenFetcher); + } + //#endregion + + // + //#region Abstracts ... + hubName(): string { + return 'webinarHub'; + } + + requiredAccessToken(): boolean { + return true; + } + + transportType(): HttpTransportType | ITransport { + return HttpTransportType.ServerSentEvents; + } + + skipNegotiation: false + + registerSubjects(): void { + super.registerSubjects(); + + // + this.onWebinarClosed((webinarId: string) => { + this.onWebinarClosed$.next(webinarId); + }); + + // + this.onWebinarClosed((webinarId: string) => { + this.onWebinarClosed$.next(webinarId); + }); + + // + this.onViewerJoined( + ( + viewerId: string, + webinarId: string, + userId: string, + username?: string + ) => { + // + this.onViewerJoined$.next({ + userId, + viewerId, + username, + webinarId, + }); + } + ); + + // + this.onViewerLeaved( + ( + viewerId: string, + webinarId: string, + userId: string, + username?: string + ) => { + // + this.onViewerLeaved$.next({ + userId, + viewerId, + username, + webinarId, + }); + } + ); + } + //#endregion + + // + //#region Actions ... + public async openWebinar(webinarId: string) { + // + // Validate ... + if (isNullOrEmptyString(webinarId)) { + return; + } + + // + await this.invoke(XWebinarPushAction.WebinarOpened, webinarId); + } + + public async closeWebinar(webinarId: string) { + // + // Validate ... + if (isNullOrEmptyString(webinarId)) { + return; + } + + // + await this.invoke(XWebinarPushAction.WebinarClosed, webinarId); + } + + public async joinWebinar(webinarId: string) { + // + // Validate ... + if (isNullOrEmptyString(webinarId)) { + return; + } + + // + await this.invoke(XWebinarPushAction.ViewerJoined, webinarId); + } + + public async leaveWebinar(webinarId: string) { + // + // Validate ... + if (isNullOrEmptyString(webinarId)) { + return; + } + + // + await this.invoke(XWebinarPushAction.ViewerLeaved, webinarId); + } + + public async webinarVisitors(webinarId: string) { + // + let result: Array = []; + + // + // Validate ... + if (isNullOrEmptyString(webinarId)) { + return result; + } + + // + result = await this.invoke>(XWebinarPushAction.WebinarVisitors, webinarId); + return result; + } + + public async sendMessage(webinarId: string, message: string) { + // + // Validate ... + if (isNullOrEmptyString(webinarId)) { + return; + } + + // + await this.invoke(XWebinarPushAction.Message, webinarId, message); + } + //#endregion + + // + //#region Event Handlers ... + // + //#region Onners ... + public onWebinarOpened(callback: (webinarId: string) => any) { + this.on(XWebinarPushAction.WebinarOpened, callback); + } + + public onWebinarClosed(callback: (webinarId?: string) => any) { + this.on(XWebinarPushAction.WebinarClosed, callback); + } + + public onViewerJoined( + callback: ( + connectionId: string, + webinarId: string, + userId: string, + username?: string + ) => any + ) { + this.on(XWebinarPushAction.ViewerJoined, callback); + } + + public onViewerLeaved( + callback: ( + connectionId: string, + webinarId: string, + userId: string, + username?: string + ) => any + ) { + this.on(XWebinarPushAction.ViewerLeaved, callback); + } + //#endregion + + // + //#region Offers ... + public offWebinarOpened(callback?: (webinarId: string) => any) { + this.off(XWebinarPushAction.WebinarOpened, callback); + } + + public offWebinarClosed(callback?: (webinarId?: string) => any) { + this.off(XWebinarPushAction.WebinarClosed, callback); + } + + public offViewerJoined( + callback?: ( + connectionId: string, + webinarId: string, + userId: string, + username?: string + ) => any + ) { + this.off(XWebinarPushAction.ViewerJoined, callback); + } + + public offViewerLeaved( + callback?: ( + connectionId: string, + webinarId: string, + userId: string, + username?: string + ) => any + ) { + this.off(XWebinarPushAction.ViewerLeaved, callback); + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xApiSDKHolder\projects\x-saherelm-api-sdk\src\lib\webinars\services\x-webinarsubscriber.entity.service.ts + +import { Router } from '@angular/router'; +import { HttpClient } from '@angular/common/http'; +import { Inject, Injectable } from '@angular/core'; +import { XWebinarSubscriberDto } from '../models/x-webinar.dto'; +import { XApiEndPoint } from '../../typings/x-endpoint.typings'; +import { XBaseEntityService } from '../../base/x-base-entity.service'; +import { X_API_CONFIG, XApiConfiguration } from 'x-framework-identity-sdk'; +import { X_SAHERELM_API_SDK_CONFIG } from '../../tokens/x-injectable-tokens'; +import { XSaherElmApiSDKConfig } from '../../config/x-saherelm-api-sdk.config'; + +/** + * Provides all Entity Actions as a Service ... + */ +@Injectable({ + providedIn: 'root', +}) +export class XWebinarSubscriberEntityService extends XBaseEntityService< + XWebinarSubscriberDto, + number +> { + // + //#region Constructor ... + constructor( + protected router: Router, + protected httpClient: HttpClient, + @Inject(X_SAHERELM_API_SDK_CONFIG) config: XSaherElmApiSDKConfig, + @Inject(X_API_CONFIG) apiConfig: XApiConfiguration + ) { + super( + XApiEndPoint.WebinarsEntity, + true, + true, + httpClient, + config, + apiConfig + ); + } + //#endregion +} diff --git a/Documents/GeneratedContext/xDashboard-xClient-20260810_005046.txt b/Documents/GeneratedContext/xDashboard-xClient-20260810_005046.txt new file mode 100644 index 0000000..f5dc40e --- /dev/null +++ b/Documents/GeneratedContext/xDashboard-xClient-20260810_005046.txt @@ -0,0 +1,57058 @@ + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\plugins\cordova-plugin-device\types\index.d.ts + +// Type definitions for Apache Cordova Device plugin +// Project: https://github.com/apache/cordova-plugin-device +// Definitions by: Microsoft Open Technologies Inc +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped +// +// Copyright (c) Microsoft Open Technologies Inc +// Licensed under the MIT license + +/** + * This plugin defines a global device object, which describes the device's hardware and software. + * Although the object is in the global scope, it is not available until after the deviceready event. + */ +interface Device { + /** Get the version of Cordova running on the device. */ + cordova: string; + /** Indicates that Cordova initialize successfully. */ + available: boolean; + /** + * The device.model returns the name of the device's model or product. The value is set + * by the device manufacturer and may be different across versions of the same product. + */ + model: string; + /** Get the device's operating system name. */ + platform: string; + /** Get the device's Universally Unique Identifier (UUID). */ + uuid: string; + /** Get the operating system version. */ + version: string; + /** Get the device's manufacturer. */ + manufacturer: string; + /** Whether the device is running on a simulator. */ + isVirtual: boolean; + /** Get the device hardware serial number. */ + serial: string;} + +declare var device: Device; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\plugins\cordova-plugin-splashscreen\src\wp\ResolutionHelper.cs + +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +using Microsoft.Phone.Info; +using System; +using System.Windows; + +namespace WPCordovaClassLib.Cordova.Commands +{ + public enum Resolutions { WVGA, WXGA, HD }; + + public static class ResolutionHelper + { + public static Resolutions CurrentResolution + { + get + { + switch (Application.Current.Host.Content.ScaleFactor) + { + case 100: return Resolutions.WVGA; + case 160: return Resolutions.WXGA; + case 150: return Resolutions.HD; + } + throw new InvalidOperationException("Unknown resolution"); + } + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\plugins\cordova-plugin-splashscreen\src\wp\SplashScreen.cs + +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +using System; +using System.Net; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Documents; +using System.Windows.Ink; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Animation; +using System.Windows.Shapes; +using Microsoft.Phone.Info; +using System.Windows.Controls.Primitives; +using System.Diagnostics; +using System.Windows.Media.Imaging; +using System.Windows.Resources; +using System.IO; +using System.Xml.Linq; +using System.Linq; +using System.Windows.Threading; + +namespace WPCordovaClassLib.Cordova.Commands +{ + /// + /// Listens for changes to the state of the battery on the device. + /// Currently only the "isPlugged" parameter available via native APIs. + /// + public class SplashScreen : BaseCommand + { + private Popup popup; + + // Time until we dismiss the splashscreen + private int prefDelay = 3000; + + // Whether we hide it by default + private bool prefAutoHide = true; + + // Path to image to use + private string prefImagePath = "SplashScreenImage.jpg"; + + // static because autodismiss is only ever applied once, at app launch + // subsequent page loads should not cause the SplashScreen to be shown. + private static bool WasShown = false; + + public SplashScreen() + { + LoadConfigPrefs(); + + Image SplashScreen = new Image() + { + Height = Application.Current.Host.Content.ActualHeight, + Width = Application.Current.Host.Content.ActualWidth, + Stretch = Stretch.Fill + }; + + var imageResource = GetSplashScreenImageResource(); + if (imageResource != null) + { + BitmapImage splash_image = new BitmapImage(); + splash_image.SetSource(imageResource.Stream); + SplashScreen.Source = splash_image; + } + + // Instansiate the popup and set the Child property of Popup to SplashScreen + popup = new Popup() { IsOpen = false, + Child = SplashScreen, + HorizontalAlignment = HorizontalAlignment.Stretch, + VerticalAlignment = VerticalAlignment.Center + + }; + } + + public override void OnInit() + { + // we only want to autoload on the first page load. + // but OnInit is called for every page load. + if (!SplashScreen.WasShown) + { + SplashScreen.WasShown = true; + show(); + } + } + + private void LoadConfigPrefs() + { + StreamResourceInfo streamInfo = Application.GetResourceStream(new Uri("config.xml", UriKind.Relative)); + if (streamInfo != null) + { + using (StreamReader sr = new StreamReader(streamInfo.Stream)) + { + //This will Read Keys Collection for the xml file + XDocument configFile = XDocument.Parse(sr.ReadToEnd()); + + string configAutoHide = configFile.Descendants() + .Where(x => x.Name.LocalName == "preference") + .Where(x => (string)x.Attribute("name") == "AutoHideSplashScreen") + .Select(x => (string)x.Attribute("value")) + .FirstOrDefault(); + + bool bVal; + prefAutoHide = bool.TryParse(configAutoHide, out bVal) ? bVal : prefAutoHide; + + string configDelay = configFile.Descendants() + .Where(x => x.Name.LocalName == "preference") + .Where(x => (string)x.Attribute("name") == "SplashScreenDelay") + .Select(x => (string)x.Attribute("value")) + .FirstOrDefault(); + int nVal; + prefDelay = int.TryParse(configDelay, out nVal) ? nVal : prefDelay; + + string configImage = configFile.Descendants() + .Where(x => x.Name.LocalName == "preference") + .Where(x => (string)x.Attribute("name") == "SplashScreen") + .Select(x => (string)x.Attribute("value")) + .FirstOrDefault(); + + if (!String.IsNullOrEmpty(configImage)) + { + prefImagePath = configImage; + } + } + } + } + + private StreamResourceInfo GetSplashScreenImageResource() + { + // Get the base filename for the splash screen images + string imageName = System.IO.Path.GetFileNameWithoutExtension(prefImagePath); + Uri imageUri = null; + StreamResourceInfo imageResource = null; + + // First, try to get a resolution-specific splashscreen + try + { + // Determine the device's resolution + switch (ResolutionHelper.CurrentResolution) + { + case Resolutions.HD: + imageUri = new Uri(imageName + ".screen-720p.jpg", UriKind.Relative); + break; + + case Resolutions.WVGA: + imageUri = new Uri(imageName + ".screen-WVGA.jpg", UriKind.Relative); + break; + + case Resolutions.WXGA: + default: + imageUri = new Uri(imageName + ".screen-WXGA.jpg", UriKind.Relative); + break; + } + + imageResource = Application.GetResourceStream(imageUri); + } + catch (Exception) + { + // It's OK if we didn't get a resolution-specific image + } + + // Fallback to the default image name without decoration + if (imageResource == null) + { + imageUri = new Uri(prefImagePath, UriKind.Relative); + imageResource = Application.GetResourceStream(imageUri); + } + + if (imageUri != null) Debug.WriteLine("INFO :: SplashScreen: using image {0}", imageUri.OriginalString); + + return imageResource; + } + + public void show(string options = null) + { + Deployment.Current.Dispatcher.BeginInvoke(() => + { + if (!popup.IsOpen) + { + popup.Child.Opacity = 0; + + Storyboard story = new Storyboard(); + DoubleAnimation animation = new DoubleAnimation() + { + From = 0.0, + To = 1.0, + Duration = new Duration(TimeSpan.FromSeconds(0.2)) + }; + + Storyboard.SetTarget(animation, popup.Child); + Storyboard.SetTargetProperty(animation, new PropertyPath("Opacity")); + story.Children.Add(animation); + + story.Begin(); + + popup.IsOpen = true; + + if (prefAutoHide) + { + StartAutoHideTimer(); + } + } + }); + } + + public void hide(string options = null) + { + Deployment.Current.Dispatcher.BeginInvoke(() => + { + if (popup.IsOpen) + { + popup.Child.Opacity = 1.0; + + Storyboard story = new Storyboard(); + DoubleAnimation animation = new DoubleAnimation() + { + From = 1.0, + To = 0.0, + Duration = new Duration(TimeSpan.FromSeconds(0.4)) + }; + + Storyboard.SetTarget(animation, popup.Child); + Storyboard.SetTargetProperty(animation, new PropertyPath("Opacity")); + story.Children.Add(animation); + story.Completed += (object sender, EventArgs e) => + { + popup.IsOpen = false; + }; + story.Begin(); + } + }); + } + + private void StartAutoHideTimer() + { + var timer = new DispatcherTimer() { Interval = TimeSpan.FromMilliseconds(prefDelay) }; + timer.Tick += (object sender, EventArgs e) => + { + hide(); + timer.Stop(); + }; + timer.Start(); + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\plugins\cordova-plugin-splashscreen\types\index.d.ts + +// Type definitions for Apache Cordova Splashscreen plugin +// Project: https://github.com/apache/cordova-plugin-splashscreen +// Definitions by: Microsoft Open Technologies Inc +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped +// +// Copyright (c) Microsoft Open Technologies Inc +// Licensed under the MIT license. + +interface Navigator { + /** This plugin displays and hides a splash screen during application launch. */ + splashscreen: { + /** Dismiss the splash screen. */ + hide(): void; + /** Displays the splash screen. */ + show(): void; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\plugins\cordova-plugin-statusbar\src\wp\StatusBar.cs + +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + + +using Microsoft.Phone.Shell; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Threading; +using System.Windows; +using System.Windows.Media; +using System.Windows.Threading; + + +/* + * http://www.idev101.com/code/User_Interface/StatusBar.html + * https://developer.apple.com/library/ios/documentation/userexperience/conceptual/transitionguide/Bars.html + * https://developer.apple.com/library/ios/documentation/uikit/reference/UIApplication_Class/Reference/Reference.html#//apple_ref/c/econst/UIStatusBarStyleDefault + * */ + + +namespace WPCordovaClassLib.Cordova.Commands +{ + public class StatusBar : BaseCommand + { + + // returns an argb value, if the hex is only rgb, it will be full opacity + protected Color ColorFromHex(string hexString) + { + string cleanHex = hexString.Replace("#", "").Replace("0x", ""); + // turn #FFF into #FFFFFF + if (cleanHex.Length == 3) + { + cleanHex = "" + cleanHex[0] + cleanHex[0] + cleanHex[1] + cleanHex[1] + cleanHex[2] + cleanHex[2]; + } + // add an alpha 100% if it is missing + if (cleanHex.Length == 6) + { + cleanHex = "FF" + cleanHex; + } + int argb = Int32.Parse(cleanHex, NumberStyles.HexNumber); + Color clr = Color.FromArgb((byte)((argb & 0xff000000) >> 0x18), + (byte)((argb & 0xff0000) >> 0x10), + (byte)((argb & 0xff00) >> 8), + (byte)(argb & 0xff)); + return clr; + } + + public void _ready(string options) + { + Deployment.Current.Dispatcher.BeginInvoke(() => + { + bool isVis = SystemTray.IsVisible; + // TODO: pass this to JS + //Debug.WriteLine("Result::" + res); + DispatchCommandResult(new PluginResult(PluginResult.Status.OK, isVis)); + }); + } + + public void overlaysWebView(string options) + { //exec(null, null, "StatusBar", "overlaysWebView", [doOverlay]); + // string arg = JSON.JsonHelper.Deserialize(options)[0]; + } + + public void styleDefault(string options) + { //exec(null, null, "StatusBar", "styleDefault", []); + Deployment.Current.Dispatcher.BeginInvoke(() => + { + SystemTray.ForegroundColor = Colors.Black; + }); + } + + public void styleLightContent(string options) + { //exec(null, null, "StatusBar", "styleLightContent", []); + + Deployment.Current.Dispatcher.BeginInvoke(() => + { + SystemTray.ForegroundColor = Colors.White; + }); + } + + public void styleBlackTranslucent(string options) + { //exec(null, null, "StatusBar", "styleBlackTranslucent", []); + styleLightContent(options); + } + + public void styleBlackOpaque(string options) + { //exec(null, null, "StatusBar", "styleBlackOpaque", []); + styleLightContent(options); + } + + public void backgroundColorByName(string options) + { //exec(null, null, "StatusBar", "backgroundColorByName", [colorname]); + // this should NOT be called, js should now be using/converting color names to hex + } + + public void backgroundColorByHexString(string options) + { //exec(null, null, "StatusBar", "backgroundColorByHexString", [hexString]); + string argb = JSON.JsonHelper.Deserialize(options)[0]; + + Color clr = ColorFromHex(argb); + + Deployment.Current.Dispatcher.BeginInvoke(() => + { + SystemTray.Opacity = clr.A / 255.0d; + SystemTray.BackgroundColor = clr; + + }); + } + + public void hide(string options) + { //exec(null, null, "StatusBar", "hide", []); + Deployment.Current.Dispatcher.BeginInvoke(() => + { + SystemTray.IsVisible = false; + }); + + } + + public void show(string options) + { //exec(null, null, "StatusBar", "show", []); + Deployment.Current.Dispatcher.BeginInvoke(() => + { + SystemTray.IsVisible = true; + }); + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\plugins\cordova-plugin-statusbar\types\index.d.ts + +// Type definitions for Apache Cordova StatusBar plugin +// Project: https://github.com/apache/cordova-plugin-statusbar +// Definitions by: Xinkai Chen +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped + +/** +* Global object StatusBar. +*/ +interface Window { + StatusBar: StatusBar; +} + + +/** +* The StatusBar object provides some functions to customize the iOS and Android StatusBar. +*/ +interface StatusBar { + /** + * On iOS 7, make the statusbar overlay or not overlay the WebView. + * @param isOverlay On iOS 7, set to false to make the statusbar appear like iOS 6. + * Set the style and background color to suit using the other functions. + */ + overlaysWebView: (isOverlay: boolean) => void; + + /** + * Use the default statusbar (dark text, for light backgrounds). + */ + styleDefault: () => void; + + /** + * Use the lightContent statusbar (light text, for dark backgrounds). + */ + styleLightContent: () => void; + + /** + * Use the blackTranslucent statusbar (light text, for dark backgrounds). + */ + styleBlackTranslucent: () => void; + + /** + * Use the blackOpaque statusbar (light text, for dark backgrounds). + */ + styleBlackOpaque: () => void; + + /** + * On iOS 7, when you set StatusBar.statusBarOverlaysWebView to false, + * you can set the background color of the statusbar by color name. + * @param color Supported color names are: + * black, darkGray, lightGray, white, gray, red, green, blue, cyan, yellow, magenta, orange, purple, brown + */ + backgroundColorByName: (color: string) => void; + + /** + * Sets the background color of the statusbar by a hex string. + * @param color CSS shorthand properties are also supported. + * On iOS 7, when you set StatusBar.statusBarOverlaysWebView to false, you can set the background color of the statusbar by a hex string (#RRGGBB). + * On WP7 and WP8 you can also specify values as #AARRGGBB, where AA is an alpha value + */ + backgroundColorByHexString: (color: string) => void; + + /** + * Hide the statusbar. + */ + hide: () => void; + + /** + * Show the statusbar. + */ + show: () => void; + + /** + * Read this property to see if the statusbar is visible or not. + */ + isVisible: boolean; +} + +declare var StatusBar: StatusBar; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\global.d.ts + +interface MediaRecorderOptions { + mimeType?: string; + audioBitsPerSecond?: number; + videoBitsPerSecond?: number; + bitsPerSecond?: number; +} + +interface MediaRecorder { + readonly stream: MediaStream; + readonly mimeType: string; + readonly state: 'inactive' | 'recording' | 'paused'; + readonly videoBitsPerSecond: number; + readonly audioBitsPerSecond: number; + + ondataavailable: ((event: BlobEvent) => void) | null; + onerror: ((event: MediaRecorderErrorEvent) => void) | null; + onpause: (() => void) | null; + onresume: (() => void) | null; + onstart: (() => void) | null; + onstop: (() => void) | null; + + start(timeslice?: number): void; + stop(): void; + pause(): void; + resume(): void; + requestData(): void; + + readonly isTypeSupported: (type: string) => boolean; +} + +declare var MediaRecorder: { + prototype: MediaRecorder; + new(stream: MediaStream, options?: MediaRecorderOptions): MediaRecorder; + isTypeSupported?(type: string): boolean; +}; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\index.html + + + + + + xSaherElm + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\main.ts + +import { enableProdMode } from '@angular/core'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app/app.module'; +import { environment } from './environments/environment'; + +if (environment.production) { + enableProdMode(); +} + +platformBrowserDynamic().bootstrapModule(AppModule) + .catch(err => console.error(err)); + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\polyfills.ts + +/** + * This file includes polyfills needed by Angular and is loaded before the app. + * You can add your own extra polyfills to this file. + * + * This file is divided into 2 sections: + * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. + * 2. Application imports. Files imported after ZoneJS that should be loaded before your main + * file. + * + * The current setup is for so-called "evergreen" browsers; the last versions of browsers that + * automatically update themselves. This includes recent versions of Safari, Chrome (including + * Opera), Edge on the desktop, and iOS and Chrome on mobile. + * + * Learn more in https://angular.io/guide/browser-support + */ + +/*************************************************************************************************** + * BROWSER POLYFILLS + */ + +/** + * By default, zone.js will patch all possible macroTask and DomEvents + * user can disable parts of macroTask/DomEvents patch by setting following flags + * because those flags need to be set before `zone.js` being loaded, and webpack + * will put import in the top of bundle, so user need to create a separate file + * in this directory (for example: zone-flags.ts), and put the following flags + * into that file, and then add the following code before importing zone.js. + * import './zone-flags'; + * + * The flags allowed in zone-flags.ts are listed here. + * + * The following flags will work for all browsers. + * + * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame + * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick + * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames + * + * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js + * with the following flag, it will bypass `zone.js` patch for IE/Edge + * + * (window as any).__Zone_enable_cross_context_check = true; + * + */ + +/*************************************************************************************************** + * Zone JS is required by default for Angular itself. + */ +import 'zone.js'; // Included with Angular CLI. + + +/*************************************************************************************************** + * APPLICATION IMPORTS + */ + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\typings.d.ts + +interface Window { + MediaRecorder: any; + require: NodeRequire; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\app-routing.module.ts + +import { NgModule } from '@angular/core'; +import { HomePage } from './pages/home/home.page'; +import { ErrorPage } from './pages/error/error.page'; +import { AuthGuard } from 'x-framework-identity-sdk'; +import { XCanDeactivateGuard } from 'x-framework-core'; +import { Pages, BaseRoutes } from './config/page.config'; +import { StartupPage } from './pages/startup/startup.page'; +import { LandingPage } from './pages/landing/landing.page'; +import { PreloadAllModules, RouterModule, Routes } from '@angular/router'; +import { NotAuthorizedPage } from './pages/not-authorized/not-authorized.page'; + +const routes: Routes = [ + // + //#region Default ... + { + pathMatch: 'full', + path: BaseRoutes.Default, + redirectTo: Pages.Startup.baseRoute, + }, + //#endregion + + // + //#region Startup ... + { + component: StartupPage, + path: Pages.Startup.baseRoute, + canDeactivate: [XCanDeactivateGuard], + }, + //#endregion + + // + //#region Landing ... + { + component: LandingPage, + path: Pages.Landing.baseRoute, + canDeactivate: [XCanDeactivateGuard], + }, + //#endregion + + // + //#region Home ... + { + component: HomePage, + canActivate: [AuthGuard], + path: Pages.Home.baseRoute, + canDeactivate: [XCanDeactivateGuard], + }, + //#endregion + + // + //#region Webinars ... + { + path: Pages.Webinars.baseRoute, + loadChildren: () => + import('./pages/webinars/webinars-page.module').then( + (m) => m.WebinarsPageModule + ), + }, + //#endregion + + // + //#region Tags ... + { + path: Pages.Tags.baseRoute, + loadChildren: () => + import('./pages/tags/tags-page.module').then( + (m) => m.TagsPageModule + ), + }, + //#endregion + + // + //#region Files ... + { + path: Pages.Files.baseRoute, + loadChildren: () => + import('./pages/files/files-page.module').then( + (m) => m.FilesPageModule + ), + }, + //#endregion + + // + //#region Account ... + { + path: Pages.Account.baseRoute, + loadChildren: () => + import('./pages/account/account.module').then((m) => m.AccountPageModule), + }, + //#endregion + + // + //#region Not Authorized ... + { + component: NotAuthorizedPage, + path: Pages.NotAuthorized.baseRoute, + canDeactivate: [XCanDeactivateGuard], + }, + //#endregion + + // + //#region Error ... + { + component: ErrorPage, + path: Pages.Error.baseRoute, + canDeactivate: [XCanDeactivateGuard], + }, + //#endregion + + // + //#region UnKnown Routes ... + { + pathMatch: 'full', + path: BaseRoutes.Unknown, + redirectTo: Pages.Startup.baseRoute, + }, + //#endregion +]; + +@NgModule({ + imports: [ + RouterModule.forRoot(routes, { + preloadingStrategy: PreloadAllModules, + scrollPositionRestoration: 'enabled', + }), + ], + exports: [RouterModule], +}) +export class AppRoutingModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\app.component.html + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\app.component.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\app.component.ts + +import { Component } from '@angular/core'; +import { Platform } from '@ionic/angular'; + +@Component({ + selector: 'app-root', + templateUrl: 'app.component.html', + styleUrls: ['app.component.scss'] +}) +export class AppComponent { + constructor(private platform: Platform) { + this.initializeApp(); + } + + initializeApp() { + this.platform.ready().then(() => {}); + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\app.module.ts + +import { + XCanDeactivateGuard, + XFrameworkCoreModule, + X_FRAMEWORK_CORE_CONFIG, +} from 'x-framework-core'; +import { + UserGuard, + AgentGuard, + AdminGuard, + AdminOrAgentGuard, + XSaherElmApiSdkModule, + XPushTokenFetcherService, + X_SAHERELM_API_SDK_CONFIG, +} from 'x-saherelm-api-sdk'; +import { + XManagerService, + XFrameworkServicesModule, + X_FRAMEWORK_SERVICES_CONFIG, +} from 'x-framework-services'; +import { + XFrameworkComponentsModule, + X_FRAMEWORK_COMPONENTS_CONFIG, +} from 'x-framework-components'; +import { + AuthGuard, + X_API_CONFIG, + NotAuthGuard, + ApiHttpInterceptorService, + XFrameworkIdentitySdkModule, + X_FRAMEWORK_IDENTITY_SDK_CONFIG, +} from 'x-framework-identity-sdk'; +import { + XFrameworkPushServiceModule, + X_FRAMEWORK_PUSH_SERVICE_CONFIG, + X_FRAMEWORK_PUSH_SERVICE_TOKEN_FETCHER, +} from 'x-framework-push-service'; +import { NgModule } from '@angular/core'; +import { AppComponent } from './app.component'; +import { HomePage } from './pages/home/home.page'; +import { ViewsModule } from './views/views.module'; +import { XAPI_CONFIG } from './config/x-api.config'; +import { ErrorPage } from './pages/error/error.page'; +import { RouteReuseStrategy } from '@angular/router'; +import { X_CONFIG, XCONFIG } from './config/x.config'; +import { SharedModule } from './shared/shared.module'; +import { AppRoutingModule } from './app-routing.module'; +import { HTTP_INTERCEPTORS } from '@angular/common/http'; +import { BrowserModule } from '@angular/platform-browser'; +import { environment } from '../environments/environment'; +import { StartupPage } from './pages/startup/startup.page'; +import { LandingPage } from './pages/landing/landing.page'; +import { ServiceWorkerModule } from '@angular/service-worker'; +import { IonicModule, IonicRouteStrategy } from '@ionic/angular'; +import { NotAuthorizedPage } from './pages/not-authorized/not-authorized.page'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; + +@NgModule({ + declarations: [ + HomePage, + ErrorPage, + StartupPage, + LandingPage, + AppComponent, + NotAuthorizedPage, + ], + imports: [ + ViewsModule, + SharedModule, + BrowserModule, + AppRoutingModule, + XFrameworkCoreModule, + XSaherElmApiSdkModule, + IonicModule.forRoot(), + BrowserAnimationsModule, + XFrameworkServicesModule, + XFrameworkComponentsModule, + XFrameworkPushServiceModule, + XFrameworkIdentitySdkModule, + ServiceWorkerModule.register('ngsw-worker.js', { + enabled: environment.production, + }), + ], + providers: [ + // + // Ionic Routing ... + { + provide: RouteReuseStrategy, + useClass: IonicRouteStrategy, + }, + // + // Manager Service ... + XManagerService, + // + // Provide Guards ... + AuthGuard, + UserGuard, + AdminGuard, + AgentGuard, + NotAuthGuard, + AdminOrAgentGuard, + XCanDeactivateGuard, + // + //#region Configs ... + { + provide: X_FRAMEWORK_CORE_CONFIG, + useValue: XCONFIG, + }, + { + provide: X_FRAMEWORK_SERVICES_CONFIG, + useValue: XCONFIG, + }, + { + provide: X_FRAMEWORK_COMPONENTS_CONFIG, + useValue: XCONFIG, + }, + { + provide: X_FRAMEWORK_IDENTITY_SDK_CONFIG, + useValue: XCONFIG, + }, + { + provide: X_FRAMEWORK_PUSH_SERVICE_CONFIG, + useValue: XCONFIG, + }, + { + provide: X_SAHERELM_API_SDK_CONFIG, + useValue: XCONFIG, + }, + { + provide: X_API_CONFIG, + useValue: XAPI_CONFIG, + }, + { + provide: X_CONFIG, + useValue: XCONFIG, + }, + //#endregion + + // + //#region Push ... + { + provide: X_FRAMEWORK_PUSH_SERVICE_TOKEN_FETCHER, + useClass: XPushTokenFetcherService, + }, + //#endregion + + // + //#region Register Http Interceptor ... + { + provide: HTTP_INTERCEPTORS, + useClass: ApiHttpInterceptorService, + multi: true, + }, + //#endregion + ], + bootstrap: [AppComponent], +}) +export class AppModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\config\app.config.ts + +import { + DefaultLocale, + AppResourceIDs, + AvailableTranslationResources, +} from './localization.config'; +import { + RegistrationStep, + XFrameworkIdentitySDKConfig, +} from 'x-framework-identity-sdk'; +import { Pages } from './page.config'; +import { LogLevel } from '@microsoft/signalr'; +import { NotificationAudioSources } from './audio.config'; +import { XSaherElmApiSDKConfig } from 'x-saherelm-api-sdk'; +import { XFrameworkCoreConfig, XPage } from 'x-framework-core'; +import { XFrameworkServicesConfig } from 'x-framework-services'; +import { XFrameworkComponentsConfig } from 'x-framework-components'; +import { XFrameworkPushServiceConfig } from 'x-framework-push-service'; + +// tslint:disable-next-line:no-empty-interface +export interface AppConfig { + // + defaultLandingPage?: XPage; + defaultBlogPage?: XPage; + forceLogin: boolean; + + // + refreshTokensDelay?: number; + + // + // Content Configurations ... + seeMoreResourceId: string; +} + +export type XFrameworkCoreSharedConfig = Partial; +export type XFrameworkServicesSharedConfig = Partial; +export type XFrameworkComponentsSharedConfig = + Partial; +export type XFrameworkIdentitySDKSharedConfig = + Partial; +export type XFrameworkPushServiceSharedConfig = + Partial; +export type XSaherElmApiSDKSharedConfig = Partial; +export type XAppSharedConfig = Partial; + +export type XConfig = XFrameworkCoreConfig & + XFrameworkServicesConfig & + XFrameworkComponentsConfig & + XFrameworkIdentitySDKConfig & + XFrameworkPushServiceConfig & + XSaherElmApiSDKConfig & + AppConfig; +export type XSharedConfig = Partial; + +export const xFrameworkCoreSharedConfig: XFrameworkCoreSharedConfig = { + appInstanceName: 'xMabsutClient', + appNameResource: 'app_name', + appCompanyResource: 'company', + appVersion: 'v1.1', + defaultNotificationDelay: 2000, + notificationAudioSources: NotificationAudioSources, + availableLanguages: AvailableTranslationResources, + defaultLanguage: DefaultLocale, + minAllowedImageSize: 512, + maxAllowedImageSize: 5242880, + minAllowedImageCroppableSize: 512, + allowedImageExtensions: ['.png', '.jpg', '.jpeg'], + maxProfileImageSize: 5242880, + maxProfileImagesUploadFiles: 10, + defaultCountryCode: 'IR', + defaultPageSize: 5, + pageSizes: [5, 10, 20, 40, 50, 100, 200], + defaultActionDelay: 500, + poweredValue: 'SaherElmITCenter', + secretKey: 'SaherElm@09121694056@Hadi_Khazaee_Asl@yahoo.com', +}; + +export const xFrameworkServicesSharedConfig: XFrameworkServicesSharedConfig = { + geoLocationConfig: { + enableHighAccuracy: true, + timeout: 5000, + }, +}; + +export const xFrameworkComponentsSharedConfig: XFrameworkComponentsSharedConfig = + { + minAllowedFileSize: 512, + maxAllowedFileSize: 20971520, + maxAllowedSize: 966367641, + maxUploadFiles: 10, + defaultSearchDebounceTime: 500, + }; + +export const xFrameworkIdentitySDKSharedConfig: XFrameworkIdentitySDKSharedConfig = + { + // + // Pages, + Pages, + + // + minAvailableDateConst: 100 * 365 * 24 * 60 * 60 * 1000, + maxAvailableDateConst: 18 * 365 * 24 * 60 * 60 * 1000, + + // + // Registration Type ... + registrationType: [ + RegistrationStep.Terms, + RegistrationStep.Info, + RegistrationStep.Avatar, + RegistrationStep.EmailConfirmation, + RegistrationStep.Finish, + ], + + // + debounceTime: 500, + + // + nameMinLength: 3, + nameMaxLength: 20, + + // + registrationOnlyWithInvitation: false, + + // + userNameMinLength: 4, + userNameMaxLength: 20, + + // + passwordPolicies: { + requireDigit: true, + requiredLength: 6, + requiredUniqueChars: 1, + requireLowercase: true, + requireNonAlphanumeric: true, + requireUppercase: true, + }, + + // + passwordMinLength: 6, + passwordMaxLength: 20, + + // + verificationCodeLength: 6, + delayBetweenTwoVerificationCode: 180, + }; + +export const xFrameworkPushServiceSharedConfig: XFrameworkPushServiceSharedConfig = + { + // + // Push Notifications ... + hubsBaseRoute: 'hubs', + pushConnectionMaxRetry: 10, + addSupportMessageProtocol: false, + connectionLogLevel: LogLevel.None, + pushConnectionReconnectDelay: 30000, + + // + // Stun Servers ... + iceServers: [ + { + urls: [ + 'stun:stun.l.google.com:19302', + 'stun:stun1.l.google.com:19302', + 'stun:stun2.l.google.com:19302', + ], + }, + { + urls: ['turn:turn.saherelmhub.ir:3478'], + username: 'saherelm', + credential: 's@H@1694056', + }, + ], + }; + +export const xMabsutApiSDKSharedConfig: XSaherElmApiSDKSharedConfig = { + // + // Configure API ... + apiIdentifier: 'api', + apiVersion: 'v1.0', + + // + // Push Notifications ... + pushManagerReconnectDelay: 20000, + pushServiceRoute: 'push', + pushMessageAction: 'PushMessageAsync', +}; + +export const xAppSharedConfig: XAppSharedConfig = { + // + defaultLandingPage: Pages.Landing, + forceLogin: false, + + // + // Refresh Tokens in MilliSeconds ... + refreshTokensDelay: 1 * 15 * 1000, + + // + // Contents Configurations ... + seeMoreResourceId: AppResourceIDs.see_more, +}; + +export const xSharedConfig: XSharedConfig = { + ...xFrameworkCoreSharedConfig, + ...xFrameworkServicesSharedConfig, + ...xFrameworkComponentsSharedConfig, + ...xFrameworkIdentitySDKSharedConfig, + ...xFrameworkPushServiceSharedConfig, + ...xMabsutApiSDKSharedConfig, + ...xAppSharedConfig, +}; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\config\audio.config.ts + +import { XNotificationSound, XActionType } from 'x-framework-core'; + +export const NotificationAudioSources: XNotificationSound[] = [ + { + type: XActionType.User, + resourcePath: './assets/audio/user_notification.wav', + }, + { + type: XActionType.System, + resourcePath: './assets/audio/system_notification.wav', + }, + { + type: XActionType.Capture, + resourcePath: './assets/audio/capture_notification.wav', + }, +]; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\config\localization.config.ts + +import { XLocale, XLanguageDescriptor } from 'x-framework-core'; +import { TagLocalizationResources } from '../views/tags/v-tag.configs'; +import { FileLocalizationsResources } from '../views/files/v-file.configs'; +import { VideoLocalizationResources } from '../views/v-video/v-video.configs'; +import { AccountLocalizationResources } from '../views/account/account.configs'; +import { WebinarLocalizationResources } from '../views/webinar/v-webinar.configs'; +import { ReferenceLocalizationResources } from '../views/v-reference/v-reference.configs'; + +export enum AppResourceIDs { + default_loading = 'default_loading', + english = 'english', + persian = 'persian', + actor = 'actor', + index = 'index', + title = 'title', + description = 'description', + author_id = 'author_id', + attached_on = 'attached_on', + view = 'view', + viewed_on = 'viewed_on', + check_mail = 'check_mail', + name = 'name', + type = 'type', + thumbnail = 'thumbnail', + path = 'path', + uploaded_on = 'uploaded_on', + string_resource = 'string_resource', + resource_title = 'resource_title', + add_new_terms_and_condition = 'add_new_terms_and_condition', + add_new_terms_and_condition_description = 'add_new_terms_and_condition_description', + themes = 'themes', + // + state = 'state', + enabled = 'enabled', + disabled = 'disabled', + user_id = 'user_id', + content_id = 'content_id', + bann_state = 'bann_state', + email_state = 'email_state', + phone_number_state = 'phone_number_state', + avatar_list = 'avatar_list', + logging_out = 'logging_out', + phone_number = 'phone_number', + authenticating = 'authenticating', + user_identifier = 'user_identifier', + user_identifier_description = 'user_identifier_description', + password = 'password', + new_password = 'new_password', + password_confirm = 'password_confirm', + enter_a_new_password = 'enter_a_new_password', + login = 'login', + logout = 'logout', + invite_send = 'invite_send', + change_data = 'change_data', + request_reset_password = 'request_reset_password', + request_reset_password_description = 'request_reset_password_description', + request_confirm_email = 'request_confirm_email', + request_confirm_email_description = 'request_confirm_email_description', + upload_avatar = 'upload_avatar', + confirmed = 'confirmed', + unconfirmed = 'unconfirmed', + terms_and_conditions_title = 'terms_and_conditions_title', + account_info = 'account_info', + verification_code = 'verification_code', + send_verification_code = 'send_verification_code', + registration_finished = 'registration_finished', + registration_finished_description = 'registration_finished_description', + first_name = 'first_name', + last_name = 'last_name', + user_name = 'user_name', + role = 'role', + roles = 'roles', + mobile = 'mobile', + email = 'email', + date_of_birth = 'date_of_birth', + registration_date = 'registration_date', + last_login = 'last_login', + gender = 'gender', + male = 'male', + female = 'female', + landing_page = 'landing_page', + admin_role = 'admin_role', + admin_role_description = 'admin_role_description', + responsible_manager_role = 'responsible_manager_role', + responsible_manager_role_description = 'responsible_manager_role_description', + journalist_role = 'journalist_role', + journalist_role_description = 'journalist_role_description', + chief_clerk_role = 'chief_clerk_role', + chief_clerk_role_description = 'chief_clerk_role_description', + chief_clerk_assistant_role = 'chief_clerk_assistant_role', + chief_clerk_assistant_role_description = 'chief_clerk_assistant_role_description', + actuary_role = 'actuary_role', + actuary_role_description = 'actuary_role_description', + actuary_assistant_role = 'actuary_assistant_role', + actuary_assistant_role_description = 'actuary_assistant_role_description', + reporter_role = 'reporter_role', + reporter_role_description = 'reporter_role_description', + reporter_assistant_role = 'reporter_assistant_role', + reporter_assistant_role_description = 'reporter_assistant_role_description', + agent_role = 'agent_role', + agent_role_description = 'agent_role_description', + user_role = 'user_role', + user_role_description = 'user_role_description', + friendship_state = 'friendship_state', + unknown = 'unknown', + follower = 'follower', + followers = 'followers', + following = 'following', + followings = 'followings', + friend = 'friend', + block = 'block', + unblock = 'unblock', + blocked = 'blocked', + follow = 'follow', + unfollow = 'unfollow', + unfollow_as = 'unfollow_as', + unfollow_follower = 'unfollow_follower', + unfollow_following = 'unfollow_following', + accept_request = 'accept_request', + cancel_request = 'cancel_request', + pending = 'pending', + pending_follower = 'pending_follower', + pending_following = 'pending_following', + rejected = 'rejected', + accepted = 'accepted', + min_length_error = 'min_length_error', + max_length_error = 'max_length_error', + invalid_url = 'invalid_url', + invalid_phone_number = 'invalid_phone_number', + invalid_email_address = 'invalid_email_address', + email_cant_register = 'email_cant_register', + mobile_cant_register = 'mobile_cant_register', + user_name_cant_register = 'user_name_cant_register', + email_in_use = 'email_in_use', + phone_number_in_use = 'phone_number_in_use', + passwords_not_matched = 'passwords_not_matched', + // + //#region Blockchain Views ... + theme = 'theme', + symbol = 'symbol', + chart = 'chart', + date_rage = 'date_range', + chart_type = 'chart_type', + scale_mode = 'scale_mode', + select_sheet = 'select_sheet', + display_mode = 'display_mode', + analysis_tool = 'analysis_tool', + scale_position = 'scale_position', + search_symbols = 'search_symbols', + symbol_profile = 'symbol_profile', + symbol_overview = 'symbol_overview', + values_tracking = 'values_tracking', + technical_analysis = 'technical_analysis', + cryptocurrency_market = 'cryptocurrency_market', + //#endregion + // + startup_page_title = 'startup_page_title', + startup_page_description = 'startup_page_description', + landing_page_title = 'landing_page_title', + landing_page_description = 'landing_page_description', + home_page_title = 'home_page_title', + home_page_description = 'home_page_description', + error_page_title = 'error_page_title', + error_page_description = 'error_page_description', + not_authorized_page_title = 'not_authorized_page_title', + not_authorized_page_description = 'not_authorized_page_description', + theme_manager_page_title = 'theme_manager_page_title', + theme_manager_page_description = 'theme_manager_page_description', + // + settings_page_title = 'settings_page_title', + settings_page_description = 'settings_page_description', + string_resource_page_title = 'string_resource_page_title', + string_resource_page_description = 'string_resource_page_description', + terms_and_conditions_page_title = 'terms_and_conditions_page_title', + terms_and_conditions_page_description = 'terms_and_conditions_page_description', + posts_page_title = 'posts_page_title', + posts_page_description = 'posts_page_description', + media_explorer_page_title = 'media_explorer_page_title', + media_explorert_page_description = 'media_explorert_page_description', + media_gallery_explorer_page_title = 'media_gallery_explorer_page_title', + media_gallery_explorert_page_description = 'media_gallery_explorert_page_description', + media_management_page_title = 'media_management_page_title', + media_management_page_description = 'media_management_page_description', + content_explorer_page_title = 'content_explorer_page_title', + content_explorert_page_description = 'content_explorert_page_description', + content_management_page_title = 'content_management_page_title', + content_management_page_description = 'content_management_page_description', + documents_page_title = 'documents_page_title', + documents_page_description = 'documents_page_description', + document_management_page_title = 'document_management_page_title', + document_management_page_description = 'document_management_page_description', + // + // Custom ... + at = 'at', + news = 'news', + share = 'share', + media = 'media', + medias = 'medias', + summary = 'summary', + content = 'content', + see_more = 'see_more', + contents = 'contents', + posted_by = 'posted_by', + edited_by = 'edited_by', + documents = 'documents', + posted_on = 'posted_on', + editted_on = 'editted_on', + audioinput = 'audioinput', + audiooutput = 'audiooutput', + videoinput = 'videoinput', + select_media_device = 'select_media_device', + author = 'author', + private = 'private', + public = 'public', + locales = 'locales', + created_on = 'created_on', + approver = 'approver', + approver_id = 'approver_id', + approved_on = 'approved_on', + media_gallery = 'media_gallery', + media_galleries = 'media_galleries', + upload_documents = 'upload_documents', + management_panel = 'management_panel', + connect = 'connect', + // + // Video ... + mute = 'mute', + play = 'play', + pause = 'pause', + unmute = 'unmute', + volume = 'volume', + rewind = 'rewind', + forward = 'forward', + fullscreen = 'fullscreen', + unfullscreen = 'unfullscreen', + change_volume = 'change_volume', + mic_on = 'mic_on', + mic_off = 'mic_off', + video_on = 'video_on', + call_end = 'call_end', + video_off = 'video_off', + call_request = 'call_request', + stop_record = 'stop_record', + start_record = 'start_record', + stop_share_screen = 'stop_share_screen', + start_share_screen = 'start_share_screen', + switch_to_back_camera = 'switch_to_back_camera', + switch_to_front_camera = 'switch_to_front_camera', + // + // Reference ... + reference = 'reference', + references = 'references', + provided_for = 'provided_for', + referenced_to = 'referenced_to', + // + // Account ... + account = 'account', + accounts = 'accounts', + profile = 'profile', + profiles = 'profiles', + avatar = 'avatar', + avatars = 'avatars', + register = 'register', + accept_terms = 'accept_terms', + email_confirmation = 'email_confirmation', + input_user_info = 'input_user_info', + input_user_avatar = 'input_user_avatar', + finish_registration = 'finish_registration', + account_page_title = 'account_page_title', + account_page_description = 'account_page_description', + profile_page_title = 'profile_page_title', + profile_page_description = 'profile_page_description', + login_page_title = 'login_page_title', + login_page_description = 'login_page_description', + logout_page_title = 'logout_page_title', + logout_page_description = 'logout_page_description', + register_page_title = 'register_page_title', + register_page_description = 'register_page_description', + email_confirm_page_title = 'email_confirm_page_title', + email_confirm_page_description = 'email_confirm_page_description', + phone_confirm_page_title = 'phone_confirm_page_title', + phone_confirm_page_description = 'phone_confirm_page_description', + reset_password_page_title = 'reset_password_page_title', + reset_password_page_description = 'reset_password_page_description', + change_password_page_title = 'change_password_page_title', + change_password_page_description = 'change_password_page_description', + search_profiles_page_title = 'search_profiles_page_title', + search_profiles_page_description = 'search_profiles_page_description', + // + // Tags ... + tag = 'tag', + tags = 'tags', + tags_page_title = 'tags_page_title', + tags_page_description = 'tags_page_description', + tags_management_page_title = 'tags_management_page_title', + tags_management_page_description = 'tags_management_page_description', + tags_explorer_page_title = 'tags_explorer_page_title', + tags_explorer_page_description = 'tags_explorer_page_description', + // + // Files ... + file = 'file', + files = 'files', + image = 'image', + audio = 'audio', + video = 'video', + document = 'document', + file_name = 'file_name', + thumbnail_path = 'thumbnail_path', + files_page_title = 'files_page_title', + files_page_description = 'files_page_description', + files_management_page_title = 'files_management_page_title', + files_management_page_description = 'files_management_page_description', + files_explorer_page_title = 'files_explorer_page_title', + files_explorer_page_description = 'files_explorer_page_description', + // + // Webinars ... + webinar = 'webinar', + webinars = 'webinars', + close_webinar = 'close_webinar', + webinar_owner = 'webinar_owner', + webinar_joiner = 'webinar_joiner', + new_webinar_created = 'new_webinar_created', + webinars_page_title = 'webinars_page_title', + visitor_joined_message = 'visitor_joined_message', + visitor_leaved_message = 'visitor_leaved_message', + webinar_closed_message = 'webinar_closed_message', + webinars_page_description = 'webinars_page_description', + webinars_explorer_page_title = 'webinars_explorer_page_title', + webinars_management_page_title = 'webinars_management_page_title', + webinars_explorer_page_description = 'webinars_explorer_page_description', + webinars_management_page_description = 'webinars_management_page_description', +} + +export const DefaultLocale: XLocale = 'fa-IR'; + +export const AvailableTranslationResources: XLanguageDescriptor[] = [ + { + name: 'persian', + locale: 'fa-IR', + language: 'fa', + direction: 'rtl', + resources: [ + { + id: 'app_name', + value: 'ساحر علم', + }, + { + id: 'company', + value: 'فن آوران ساحر علم', + }, + { + id: 'id', + value: 'شناسه', + }, + { + id: 'file_name', + value: 'نام فایل', + }, + { + id: 'error', + value: 'خطا', + }, + { + id: 'required', + value: 'الزامی است.', + }, + { + id: 'loading', + value: 'بارگزاری', + }, + { + id: 'username', + value: 'نام کاربری', + }, + { + id: 'password', + value: 'کلمه عبور', + }, + { + id: 'first_name', + value: 'نام', + }, + { + id: 'last_name', + value: 'نام خانوادگی', + }, + { + id: 'verified', + value: 'تایید شده', + }, + { + id: 'unverified', + value: 'تایید نشده', + }, + { + id: 'thumbnail', + value: 'پیش نمایش', + }, + { + id: 'language', + value: 'زبان', + }, + { + id: 'take_screenshot', + value: 'تصویر برداری ازمحیط', + }, + { + id: 'enable_capture_mode', + value: 'فعالسازی حالت تصویر برداری', + }, + { + id: 'disable_capture_mode', + value: 'غیرفعالسازی حالت تصویر برداری', + }, + { + id: 'be', + value: 'باشد', + }, + { + id: 'not_to_be', + value: 'نباشد', + }, + { + id: 'enabled', + value: 'فعال', + }, + { + id: 'disabled', + value: 'غیر فعال', + }, + { + id: 'banned', + value: 'مسدود', + }, + { + id: 'un_banned', + value: 'غیر مسدود', + }, + { + id: 'audio', + value: 'صوت', + }, + { + id: 'video', + value: 'فیلم', + }, + { + id: 'cover_image', + value: 'تصویر', + }, + { + id: 'image', + value: 'تصویر', + }, + { + id: 'unknown', + value: 'ناشناس', + }, + { + id: 'location', + value: 'موقعیت جغرافیای', + }, + { + id: 'are_you_sure', + value: 'اطمینان دارید ؟', + }, + { + id: 'success_msg', + value: 'عملیات با موفقیت انجام شد', + }, + { + id: 'locked_msg', + value: + 'برای ترک این صفحه باید فعالیت جاری را لغو کرده و یا به اتمام برسانید', + }, + { + id: 'empty_content', + value: 'خالی است', + }, + { + id: 'no_item', + value: 'محتوایی ثبت نشده است', + }, + { + id: 'drag_drop_here', + value: 'محل رها کردن فایل', + }, + { + id: 'map_dialog_title', + value: 'نمایش موقعیت مکانی', + }, + { + id: 'map_dialog_select_title', + value: 'انتخاب موقعیت جغرافیایی', + }, + { + id: 'color_picker_dialog_title', + value: 'انتخاب رنگ', + }, + { + id: 'dismiss', + value: 'رد کردن', + }, + { + id: 'accept', + value: 'می پذیرم', + }, + { + id: 'ok', + value: 'بسیار خب', + }, + { + id: 'save', + value: 'ذخیره', + }, + { + id: 'cancel', + value: 'لغو', + }, + { + id: 'retry', + value: 'سعی مجدد', + }, + { + id: 'edit', + value: 'ویرایش', + }, + { + id: 'reset', + value: 'باز نشاندن', + }, + { + id: 'change', + value: 'تغییر', + }, + { + id: 'start', + value: 'آغاز', + }, + { + id: 'yes', + value: 'بله', + }, + { + id: 'no', + value: 'خیر', + }, + { + id: 'verify', + value: 'تایید کردن', + }, + { + id: 'confirm', + value: 'تاییدیه', + }, + { + id: 'crop', + value: 'برش', + }, + { + id: 'clear', + value: 'پاک کردن', + }, + { + id: 'set', + value: 'تنظیم', + }, + { + id: 'select', + value: 'انتخاب', + }, + { + id: 'send', + value: 'ارسال', + }, + { + id: 'show', + value: 'نمایش دادن', + }, + { + id: 'hide', + value: 'پنهان کردن', + }, + { + id: 'search', + value: 'جستجو', + }, + { + id: 'delete', + value: 'حذف', + }, + { + id: 'clone', + value: 'نمونه گیری', + }, + { + id: 'select_all', + value: 'انتخاب همه', + }, + { + id: 'select_file', + value: 'انتخاب فایل(ها)', + }, + { + id: 'select_all_group', + value: 'انتخاب همه موارد فیلتر شده', + }, + { + id: 'deselect_all', + value: 'لغو همه انتخاب ها', + }, + { + id: 'deselect_all_group', + value: 'لغو انتخاب همه موارد فیلتر شده', + }, + { + id: 'inverse_selection', + value: 'انتخاب معکوس', + }, + { + id: 'selected_location', + value: 'انتخاب محل', + }, + { + id: 'preset_colors', + value: 'رنگ های پیش فرض', + }, + { + id: 'copy_to_clipboard', + value: 'ثبت در حافظه موقت', + }, + { + id: 'color', + value: 'رنگ', + }, + { + id: 'colors', + value: 'رنگها', + }, + { + id: 'upload', + value: 'بارگزاری', + }, + { + id: 'import', + value: 'وارد کردن', + }, + { + id: 'continue', + value: 'ادامه', + }, + { + id: 'finish', + value: 'پایان', + }, + { + id: 'enable', + value: 'فعال', + }, + { + id: 'disable', + value: 'غیر فعال', + }, + { + id: 'all', + value: 'همه', + }, + { + id: 'new', + value: 'جدید', + }, + { + id: 'add', + value: 'افزودن', + }, + { + id: 'update', + value: 'بروز رسانی', + }, + { + id: 'details', + value: 'جزئیات', + }, + { + id: 'translation', + value: 'ترجمه', + }, + { + id: 'download', + value: 'دریافت', + }, + { + id: 'list', + value: 'فهرست', + }, + { + id: 'options', + value: 'انتخاب ها', + }, + { + id: 'descriptions', + value: 'توضیحات', + }, + { + id: 'type', + value: 'نوع', + }, + { + id: 'media', + value: 'رسانه', + }, + { + id: 'medias', + value: 'رسانه ها', + }, + { + id: 'summary', + value: 'خلاصه', + }, + { + id: 'go', + value: 'رفتن', + }, + { + id: 'input', + value: 'ورود', + }, + { + id: 'preview', + value: 'پیش نمایش', + }, + { + id: 'close', + value: 'بستن', + }, + { + id: 'undo', + value: 'لغو کردن', + }, + { + id: 'redo', + value: 'انجام دوباره', + }, + { + id: 'refresh', + value: 'بازیابی', + }, + { + id: 'submit', + value: 'ثبت', + }, + { + id: 'first', + value: 'نخستین', + }, + { + id: 'last', + value: 'آخرین', + }, + { + id: 'previous', + value: 'قبلی', + }, + { + id: 'next', + value: 'بعدی', + }, + { + id: 'back', + value: 'بازگشت', + }, + { + id: 'x_markdown_preview', + value: 'پیش نمایش', + }, + { + id: 'x_markdown_image', + value: 'تصویر', + }, + { + id: 'x_markdown_link', + value: 'پیوند', + }, + { + id: 'x_markdown_ol', + value: 'فهرست ترتیبی', + }, + { + id: 'x_markdown_ul', + value: 'فهرست بدون ترتیب', + }, + { + id: 'x_markdown_quote', + value: 'نقل قول', + }, + { + id: 'x_markdown_code', + value: 'کد منبع', + }, + { + id: 'x_markdown_heading', + value: 'عنوان', + }, + { + id: 'x_markdown_italic', + value: 'ایتالیک', + }, + { + id: 'x_markdown_bold', + value: 'پر رنگ', + }, + { + id: 'x_markdown_guide', + value: 'راهنمای نگارش', + }, + { + id: 'theme', + value: 'پوسته', + }, + { + id: 'theme_mode', + value: 'حالت نمایشی', + }, + { + id: 'theme_dark', + value: 'تیره', + }, + { + id: 'theme_light', + value: 'روشن', + }, + { + id: 'theme_name', + value: 'نام پوسته', + }, + { + id: 'theme_name_description', + value: 'نام پوسته را وارد کنید', + }, + { + id: 'theme_clone_title', + value: 'نمونه گیری از پوسته', + }, + { + id: 'input_color_title', + value: 'تعیین رنگ', + }, + { + id: 'input_color_description', + value: 'مقدار رنگ را وارد کنید', + }, + { + id: 'ide_mode', + value: 'نوع کد', + }, + { + id: 'ide_format', + value: 'حالت دهی کد', + }, + { + id: 'ex_100', + value: 'داده نا معتبر', + }, + { + id: 'ex_101', + value: 'تاریخ نامعتبر', + }, + { + id: 'ex_102', + value: 'درخواست اشتباه', + }, + { + id: 'ex_103', + value: 'ورودی نامعتبر', + }, + { + id: 'ex_104', + value: 'پیام نامعتبر', + }, + { + id: 'ex_105', + value: 'نشانه نامعتبر', + }, + { + id: 'ex_106', + value: 'کد اععتبارسنجی نامعتبر', + }, + { + id: 'ex_107', + value: 'پست الکترونیک نامعتبر', + }, + { + id: 'ex_108', + value: 'شماره تلفن همراه نامعتبر', + }, + { + id: 'ex_109', + value: 'کلمه عبور نامعتبر', + }, + { + id: 'ex_110', + value: 'آدرس اینترنتی نامعتبر', + }, + { + id: 'ex_111', + value: 'دستگاه نامعتبر', + }, + { + id: 'ex_112', + value: 'تصویر پروفایل نامعتبر', + }, + { + id: 'ex_113', + value: 'فایل نامعتبر', + }, + { + id: 'ex_114', + value: 'نام کاربری نامعتبر', + }, + { + id: 'ex_200', + value: 'یافت نشد', + }, + { + id: 'ex_201', + value: 'عدم تایید جواز', + }, + { + id: 'ex_202', + value: 'سطح دسترسی نامعتبر', + }, + { + id: 'ex_203', + value: 'غیر قابل نمایش', + }, + { + id: 'ex_300', + value: 'حساب کاربری قفل شده است', + }, + { + id: 'ex_301', + value: 'خطا در ورود', + }, + { + id: 'ex_302', + value: 'کاربر قبلا ثبت نام کرده است', + }, + { + id: 'ex_303', + value: 'کاربر غیرفعال است', + }, + { + id: 'ex_304', + value: 'دستگاه شما قفل شده است تا : (0)', + }, + { + id: 'ex_305', + value: 'امکان درخواست کد اعتبارسنجی وجود ندارد تا : (0)', + }, + { + id: 'ex_306', + value: 'کلمه عبورها یکسان هستند', + }, + { + id: 'ex_307', + value: 'شماره های تلفن یکسان هستند', + }, + { + id: 'ex_308', + value: 'آدرس های پست الکترونیک یکسان هستند', + }, + { + id: 'ex_309', + value: 'شماره تلفن قبلا ثبت شده است', + }, + { + id: 'ex_310', + value: 'پست الکترونیک قبلا ثبت شده است', + }, + { + id: 'ex_311', + value: 'تلفن همراه تایید نشده است', + }, + { + id: 'ex_312', + value: 'پست الکترونیک تایید نشده است', + }, + { + id: 'ex_313', + value: 'مطلب ممنوعه', + }, + { + id: 'ex_314', + value: 'کاربر ممنوعه', + }, + { + id: 'ex_400', + value: 'خطای نا شناخته', + }, + { + id: 'ex_401', + value: 'مورد تکراری', + }, + { + id: 'ex_402', + value: 'شکست عملیات', + }, + { + id: 'ex_403', + value: 'تاریخ مصرف منقضی شده است', + }, + { + id: 'ex_404', + value: 'غیر قابل اسفاده', + }, + { + id: 'ex_405', + value: 'تعداد برچسب ها بیش از جد مجاز است', + }, + { + id: 'ex_406', + value: 'اتمام مهلت زمانی', + }, + { + id: 'ex_500', + value: 'پرونده خالی است', + }, + { + id: 'ex_501', + value: 'اندازه بیش از حد مجاز', + }, + { + id: 'ex_502', + value: 'گونه نامعتبر فایل', + }, + { + id: 'ex_503', + value: 'تعداد رسانه ها بیش از حد مجاز است', + }, + { + id: 'ex_600', + value: 'پیکربندی اشتباه', + }, + { + id: 'ex_601', + value: 'خطا در پیکربندی سامانه ذخیره سازی', + }, + { + id: 'ex_602', + value: 'خطا در پیکربندی سامانه ذخیره سازی', + }, + { + id: 'ex_900', + value: 'تعداد فایل ها بیش از حد مجاز است', + }, + { + id: 'ex_901', + value: 'مجموع اندازه فایل ها کمتر از حد مجاز است', + }, + { + id: 'ex_902', + value: 'مجموع اندازه فایل ها بیش از حد مجاز است', + }, + { + id: 'ex_903', + value: 'شرایط و ضوابط باید پذیرفته شوند', + }, + { + id: 'ex_904', + value: 'عدم پشتیبانی از سیستم موقعیت یاب', + }, + { + id: 'ex_905', + value: 'مجوز دسترسی به موقعیت خود را صادر نکرده اید', + }, + { + id: 'ex_906', + value: 'دسترسی به موقعیت جغرافیایی امکان پذیر نیست', + }, + { + id: 'ex_907', + value: 'عدم پشتیبانی از سیستم ذخیره سازی', + }, + { + id: 'ex_908', + value: 'برای دسترسی به محتوا باید از سامانه خارج شوید', + }, + { + id: 'ex_909', + value: 'زبان انتخاب شده نامعتبر است', + }, + { + id: 'ex_910', + value: 'داده نامعتبر برای انتخاب کاربر', + }, + { + id: 'default_loading', + value: 'در حال بارگزاری', + }, + { + id: 'persian', + value: 'فارسی', + }, + { + id: 'english', + value: 'انگلیسی', + }, + { + id: 'actor', + value: 'بازیگر', + }, + { + id: 'index', + value: 'اندیس', + }, + { + id: 'title', + value: 'عنوان', + }, + { + id: 'description', + value: 'توضیحات', + }, + { + id: 'author_id', + value: 'شناسه ناشر', + }, + { + id: 'attached_on', + value: 'تاریخ الصاق', + }, + { + id: 'view', + value: 'نمایش', + }, + { + id: 'viewed_on', + value: 'تاریخ دیدار', + }, + { + id: 'check_mail', + value: 'صندوق پست الکترونیک خود را بررسی کنید', + }, + { + id: 'name', + value: 'نام', + }, + { + id: 'type', + value: 'نوع', + }, + { + id: 'path', + value: 'مسیر', + }, + { + id: 'uploaded_on', + value: 'زمان بارگزاری', + }, + { + id: 'string_resource', + value: 'منبع متنی', + }, + { + id: 'resource_title', + value: 'شناسه منبع', + }, + { + id: 'add_new_terms_and_condition', + value: 'افزودن شرایط ثبت نام', + }, + { + id: 'add_new_terms_and_condition_description', + value: 'افزودن ترجمه جدید از شرایط و ضوابط ثبت نام', + }, + { + id: 'themes', + value: 'پوسته ها', + }, + // + { + id: 'state', + value: 'وضعیت', + }, + { + id: 'enabled', + value: 'فعال', + }, + { + id: 'disabled', + value: 'غیر فعال', + }, + { + id: 'user_id', + value: 'شناسه کاربر', + }, + { + id: 'content_id', + value: 'شناسه محتوا', + }, + { + id: 'bann_state', + value: 'تحریم', + }, + { + id: 'email_state', + value: 'وضعیت پست الکترونیک', + }, + { + id: 'phone_number_state', + value: 'وضعیت شماره تماس', + }, + { + id: 'avatar_list', + value: 'فهرست تصاویر پروفایل', + }, + { + id: 'logging_out', + value: 'درحال خروج از سامانه', + }, + { + id: 'phone_number', + value: 'شماره تماس', + }, + { + id: 'authenticating', + value: 'در حال احراز هویت', + }, + { + id: 'user_identifier', + value: 'شناسه کاربر', + }, + { + id: 'user_identifier_description', + value: + 'شناسه کاربر می تواند نام کاربری، آدرس پست الکترونیک و یا شماره تلفن همراه کاربر باشد', + }, + { + id: 'password', + value: 'کلمه عبور', + }, + { + id: 'new_password', + value: 'کلمه عبور جدید', + }, + { + id: 'password_confirm', + value: 'تکرار کلمه عبور', + }, + { + id: 'enter_a_new_password', + value: 'کلمه عبور جدید وارد کنید', + }, + { + id: 'login', + value: 'ورود', + }, + { + id: 'logout', + value: 'خروج', + }, + { + id: 'invite_send', + value: 'ارسال دعوتنامه', + }, + { + id: 'change_data', + value: 'اصلاح داده های ورودی', + }, + { + id: 'request_reset_password', + value: 'درخواست باز نشاندن کلمه عبور', + }, + { + id: 'request_reset_password_description', + value: 'درخواست باز نشاندن کلمه عبور حساب کاربری', + }, + { + id: 'request_confirm_email', + value: 'درخواست مجدد فعالسازی', + }, + { + id: 'request_confirm_email_description', + value: 'درخواست مجدد پیوند فعالسازی حساب کاربری', + }, + { + id: 'upload_avatar', + value: 'بارگزاری تصویر پروفایل', + }, + { + id: 'confirmed', + value: 'تایید شده', + }, + { + id: 'unconfirmed', + value: 'تایید نشده', + }, + { + id: 'terms_and_conditions_title', + value: 'شرایط و ضوابط ثبت نام', + }, + { + id: 'account_info', + value: 'اطلاعات حساب کاربری', + }, + { + id: 'verification_code', + value: 'کد اعتبار سنجی', + }, + { + id: 'send_verification_code', + value: 'ارسال کد اعتبار سنجی', + }, + { + id: 'registration_finished', + value: 'پایان ثبت نام', + }, + { + id: 'registration_finished_description', + value: + 'در این مرحله پیوند فعال سازی حساب کاربری به پست الکترونیک شما ارسال خواهد شد.', + }, + { + id: 'first_name', + value: 'نام', + }, + { + id: 'last_name', + value: 'نام خانوادگی', + }, + { + id: 'user_name', + value: 'نام کاربری', + }, + { + id: 'role', + value: 'سطح دسترسی', + }, + { + id: 'roles', + value: 'سطوح دسترسی', + }, + { + id: 'mobile', + value: 'تلفن همراه', + }, + { + id: 'email', + value: 'پست الکترونیک', + }, + { + id: 'date_of_birth', + value: 'تاریخ تولد', + }, + { + id: 'registration_date', + value: 'تاریخ عضویت', + }, + { + id: 'last_login', + value: 'آخرین ورود', + }, + { + id: 'gender', + value: 'جنسیت', + }, + { + id: 'male', + value: 'مذکر', + }, + { + id: 'female', + value: 'مونث', + }, + { + id: 'landing_page', + value: 'صفحه نخست', + }, + { + id: 'admin_role', + value: 'سرپرست', + }, + { + id: 'admin_role_description', + value: 'ناظر و سرپرست سامانه', + }, + { + id: 'responsible_manager_role', + value: 'مدیر مسئول', + }, + { + id: 'responsible_manager_role_description', + value: 'مدیریت کلیه بخش های سامانه', + }, + { + id: 'journalist_role', + value: 'روزنامه نگار', + }, + { + id: 'journalist_role_description', + value: 'مدیریت سرویس های خبری', + }, + { + id: 'chief_clerk_role', + value: 'سر دبیر', + }, + { + id: 'chief_clerk_role_description', + value: 'مدیریت اخبار', + }, + { + id: 'chief_clerk_assistant_role', + value: 'دستیار سر دبیر', + }, + { + id: 'chief_clerk_assistant_role_description', + value: 'دستیار سردبیر', + }, + { + id: 'actuary_role', + value: 'دبیر', + }, + { + id: 'actuary_role_description', + value: 'هماهنگی انتشار اخبار', + }, + { + id: 'actuary_assistant_role', + value: 'دستیار دبیر', + }, + { + id: 'actuary_assistant_role_description', + value: 'دستیار دبیر', + }, + { + id: 'reporter_role', + value: 'خبرنگار', + }, + { + id: 'reporter_role_description', + value: 'فراهم کننده گزارش های خبری', + }, + { + id: 'reporter_assistant_role', + value: 'دستیار خبرنگار', + }, + { + id: 'reporter_assistant_role_description', + value: 'دستیار خبرنگار', + }, + { + id: 'agent_role', + value: 'دستیار', + }, + { + id: 'agent_role_description', + value: 'نماینده یا دستیار که به تسهیل ارائه خدمات سامانه کمک می کنند', + }, + { + id: 'user_role', + value: 'کاربر', + }, + { + id: 'user_role_description', + value: 'استفاده کننده از خدمات ارائه شده', + }, + { + id: 'friendship_state', + value: 'وضعیت ارتباط', + }, + { + id: 'unknown', + value: 'ناشناس', + }, + { + id: 'follower', + value: 'دنبال کننده', + }, + { + id: 'followers', + value: 'دنبال کننده ها', + }, + { + id: 'following', + value: 'دنبال شده', + }, + { + id: 'followings', + value: 'دنبال شده ها', + }, + { + id: 'friend', + value: 'دوست', + }, + { + id: 'block', + value: 'مسدود کردن', + }, + { + id: 'unblock', + value: 'رفع مسدودی', + }, + { + id: 'blocked', + value: 'مسدود شده', + }, + { + id: 'follow', + value: 'دنبال کردن', + }, + { + id: 'unfollow', + value: 'قطع ارتباط', + }, + { + id: 'unfollow_as', + value: 'قطع ارتباط از نوع', + }, + { + id: 'unfollow_follower', + value: 'قطع ارتباط دنبال کننده', + }, + { + id: 'unfollow_following', + value: 'قطع ارتباط دنبال شونده', + }, + { + id: 'cancel_request', + value: 'لغو درخواست', + }, + { + id: 'accept_request', + value: 'پذیرش درخواست', + }, + { + id: 'pending', + value: 'در انتظار', + }, + { + id: 'pending_follower', + value: 'در انتظار تایید شما', + }, + { + id: 'pending_following', + value: 'منتظر تایید ایشان', + }, + { + id: 'rejected', + value: 'رد شده', + }, + { + id: 'accepted', + value: 'قبول شده', + }, + { + id: 'min_length_error', + value: 'کمتر از حد مجاز', + }, + { + id: 'max_length_error', + value: 'بیشتر از حد مجاز', + }, + { + id: 'invalid_url', + value: 'آدرس اینترنتی معتبر نیست', + }, + { + id: 'invalid_phone_number', + value: 'شماره تلفن وارد شده معتبر نیست', + }, + { + id: 'invalid_email_address', + value: 'آدرس پست الکترونیک نامعتبر است', + }, + { + id: 'email_cant_register', + value: 'پست الکترونیک قابل استفاده نیست', + }, + { + id: 'mobile_cant_register', + value: 'شماره تلفن قابل استفاده نیست', + }, + { + id: 'user_name_cant_register', + value: 'نام کاربری قابل استفاده نیست', + }, + { + id: 'email_in_use', + value: 'پست الکترونیک در حال استفاده است', + }, + { + id: 'phone_number_in_use', + value: 'شماره تلفن همراه در حال استفاده است', + }, + { + id: 'passwords_not_matched', + value: 'کلمه عبور و تکران آن یکسان نیست', + }, + // + //#region Blockchain Views ... + { + id: 'symbol', + value: 'نماد', + }, + { + id: 'theme', + value: 'پوسته', + }, + { + id: 'chart', + value: 'نمودار', + }, + { + id: 'date_range', + value: 'بازه زمانی', + }, + { + id: 'chart_type', + value: 'نوع نمودار', + }, + { + id: 'cryptocurrency_market', + value: 'وضعیت بازار', + }, + { + id: 'analysis_tool', + value: 'ابزار تحلیل', + }, + { + id: 'symbol_overview', + value: 'خلاصه وضعیت', + }, + { + id: 'values_tracking', + value: 'ردیابی ارزش ها', + }, + { + id: 'technical_analysis', + value: 'تحلیل تکنیکال', + }, + { + id: 'select_sheet', + value: 'انتخاب برگه', + }, + { + id: 'display_mode', + value: 'نحوه نمایش', + }, + { + id: 'search_symbols', + value: 'جستجوی نمادها', + }, + { + id: 'symbol_profile', + value: 'معرفی نمایه', + }, + { + id: 'scale_mode', + value: 'نحوه افزایش رشد', + }, + { + id: 'scale_position', + value: 'محل نمایش رشد', + }, + //#endregion + // + { + id: 'startup_page_title', + value: 'آغازگر', + }, + { + id: 'startup_page_description', + value: 'آغاز فرآیند بارگزاری سامانه', + }, + { + id: 'landing_page_title', + value: 'ساحرعلم', + }, + { + id: 'landing_page_description', + value: 'ارائه راه کارهای فن آورانه مبتنی بر کسب و کارها', + }, + { + id: 'home_page_title', + value: 'پنل مدیریت', + }, + { + id: 'home_page_description', + value: 'ابزارهای مدیریت', + }, + { + id: 'error_page_title', + value: 'خطا', + }, + { + id: 'error_page_description', + value: 'گزارش خطای واقع شده', + }, + { + id: 'not_authorized_page_title', + value: 'عدم دسترسی', + }, + { + id: 'not_authorized_page_description', + value: 'جواز دسترسی احراز نشد', + }, + { + id: 'theme_manager_page_title', + value: 'مدیریت پوسته', + }, + { + id: 'theme_manager_page_description', + value: 'مدیریت و دستکاری پوسته های نمایشی برنامه', + }, + // + { + id: 'confirm_page_title', + value: 'تایید ثبت نام', + }, + { + id: 'confirm_page_description', + value: 'تایید ثبت نام و فعالسازی حساب کاربری', + }, + { + id: 'invite_page_title', + value: 'دعوتنامه', + }, + { + id: 'invite_page_description', + value: 'دعوت از کاربران جهت عضویت در سیستم', + }, + { + id: 'avatars_page_title', + value: 'تصاویر پروفایل', + }, + { + id: 'avatars_page_description', + value: 'مدیریت تصاویر پروفایل کاربر', + }, + { + id: 'users_management_page_title', + value: 'کاربران', + }, + { + id: 'users_management_page_description', + value: 'ابزارهای مدیریت کاربران', + }, + // + { + id: 'settings_page_title', + value: 'تنظیمات', + }, + { + id: 'settings_page_description', + value: 'امکانات لازم جهت پیکربندی سامانه', + }, + { + id: 'string_resource_page_title', + value: 'منابع متنی', + }, + { + id: 'string_resource_page_description', + value: 'مدیریت منابع متنی مورد استفاده در سامانه', + }, + { + id: 'terms_and_conditions_page_title', + value: 'شرایط و ضوابط ثبت نام', + }, + { + id: 'terms_and_conditions_page_description', + value: 'مدیریت شرایط و ضوابط ثبت نام', + }, + { + id: 'posts_page_title', + value: 'مطالب', + }, + { + id: 'posts_page_description', + value: 'ابزارهای مدیریت مطالب', + }, + { + id: 'content_explorer_page_title', + value: 'مرورگر محتوا', + }, + { + id: 'content_explorert_page_description', + value: 'مرور محتواهای منتشر شده', + }, + { + id: 'content_management_page_title', + value: 'مدیریت محتواها', + }, + { + id: 'content_management_page_description', + value: 'مدیریت محتواهای ارسال شده', + }, + { + id: 'media_explorer_page_title', + value: 'مرورگر رسانه', + }, + { + id: 'media_explorert_page_description', + value: 'مرور رسانه های منتشر شده', + }, + { + id: 'media_gallery_explorer_page_title', + value: 'نگارخانه', + }, + { + id: 'media_gallery_explorert_page_description', + value: 'نمایش نگاره های ارسالی', + }, + { + id: 'media_management_page_title', + value: 'مدیریت رسانه ها', + }, + { + id: 'media_management_page_description', + value: 'مدیریت رسانه های ارسال شده', + }, + { + id: 'documents_page_title', + value: 'اسناد', + }, + { + id: 'documents_page_description', + value: 'ابزارهای مدیریت اسناد', + }, + { + id: 'document_management_page_title', + value: 'مدیریت اسناد', + }, + { + id: 'document_management_page_description', + value: 'مدیریت اسناد بارگزاری شده', + }, + // + // Custom ... + { + id: 'at', + value: 'در', + }, + { + id: 'posted_by', + value: 'ارسال شده توسط', + }, + { + id: 'edited_by', + value: 'ویرایش شده توسط', + }, + { + id: 'management_panel', + value: 'پنل مدیریت', + }, + { + id: 'connect', + value: 'اتصال', + }, + { + id: 'references', + value: 'مراجع', + }, + { + id: 'share', + value: 'اشتراک گزاری', + }, + { + id: 'media', + value: 'رسانه', + }, + { + id: 'medias', + value: 'رسانه ها', + }, + { + id: 'news', + value: 'اخبار', + }, + { + id: 'content', + value: 'محتوا', + }, + { + id: 'see_more', + value: 'ادامه مطلب »', + }, + { + id: 'editted_on', + value: 'ویرایش شده در', + }, + { + id: 'audioinput', + value: 'ورودی صوت', + }, + { + id: 'audiooutput', + value: 'خروجی صوت', + }, + { + id: 'videoinput', + value: 'ورودی تصویر', + }, + { + id: 'select_media_device', + value: 'انتخاب ابزار مقصد', + }, + { + id: 'author', + value: 'ناشر', + }, + { + id: 'private', + value: 'خصوصی', + }, + { + id: 'public', + value: 'عمومی', + }, + { + id: 'locales', + value: 'منابع متنی', + }, + { + id: 'created_on', + value: 'تاریخ ایجاد', + }, + { + id: 'approver', + value: 'تایید کننده', + }, + { + id: 'approver_id', + value: 'تایید شده توسط', + }, + { + id: 'approved_on', + value: 'تایید شده در', + }, + { + id: 'media_gallery', + value: 'نگاره', + }, + { + id: 'media_galleries', + value: 'نگاره ها', + }, + { + id: 'contents', + value: 'محتواها', + }, + { + id: 'documents', + value: 'اسناد', + }, + { + id: 'posted_on', + value: 'ارسال شده در', + }, + { + id: 'upload_documents', + value: 'بارگزاری اسناد', + }, + // + // Video ... + { ...VideoLocalizationResources.mute.fa }, + { ...VideoLocalizationResources.play.fa }, + { ...VideoLocalizationResources.pause.fa }, + { ...VideoLocalizationResources.unmute.fa }, + { ...VideoLocalizationResources.volume.fa }, + { ...VideoLocalizationResources.rewind.fa }, + { ...VideoLocalizationResources.forward.fa }, + { ...VideoLocalizationResources.fullscreen.fa }, + { ...VideoLocalizationResources.unfullscreen.fa }, + { ...VideoLocalizationResources.change_volume.fa }, + { ...VideoLocalizationResources.mic_on.fa }, + { ...VideoLocalizationResources.mic_off.fa }, + { ...VideoLocalizationResources.video_on.fa }, + { ...VideoLocalizationResources.video_off.fa }, + { ...VideoLocalizationResources.call_end.fa }, + { ...VideoLocalizationResources.call_request.fa }, + { ...VideoLocalizationResources.stop_record.fa }, + { ...VideoLocalizationResources.start_record.fa }, + { ...VideoLocalizationResources.stop_share_screen.fa }, + { ...VideoLocalizationResources.start_share_screen.fa }, + { ...VideoLocalizationResources.switch_to_back_camera.fa }, + { ...VideoLocalizationResources.switch_to_front_camera.fa }, + // + // Reference ... + { ...ReferenceLocalizationResources.reference.fa }, + { ...ReferenceLocalizationResources.references.fa }, + { ...ReferenceLocalizationResources.provided_for.fa }, + { ...ReferenceLocalizationResources.referenced_to.fa }, + // + // Account ... + { ...AccountLocalizationResources.account.fa }, + { ...AccountLocalizationResources.accounts.fa }, + { ...AccountLocalizationResources.profile.fa }, + { ...AccountLocalizationResources.profiles.fa }, + { ...AccountLocalizationResources.avatar.fa }, + { ...AccountLocalizationResources.avatars.fa }, + { ...AccountLocalizationResources.register.fa }, + { ...AccountLocalizationResources.accept_terms.fa }, + { ...AccountLocalizationResources.email_confirmation.fa }, + { ...AccountLocalizationResources.input_user_info.fa }, + { ...AccountLocalizationResources.input_user_avatar.fa }, + { ...AccountLocalizationResources.finish_registration.fa }, + { ...AccountLocalizationResources.account_page_title.fa }, + { ...AccountLocalizationResources.account_page_description.fa }, + { ...AccountLocalizationResources.profile_page_title.fa }, + { ...AccountLocalizationResources.profile_page_description.fa }, + { ...AccountLocalizationResources.login_page_title.fa }, + { ...AccountLocalizationResources.login_page_description.fa }, + { ...AccountLocalizationResources.logout_page_title.fa }, + { ...AccountLocalizationResources.logout_page_description.fa }, + { ...AccountLocalizationResources.register_page_title.fa }, + { ...AccountLocalizationResources.register_page_description.fa }, + { ...AccountLocalizationResources.email_confirm_page_title.fa }, + { ...AccountLocalizationResources.email_confirm_page_description.fa }, + { ...AccountLocalizationResources.phone_confirm_page_title.fa }, + { ...AccountLocalizationResources.phone_confirm_page_description.fa }, + { ...AccountLocalizationResources.reset_password_page_title.fa }, + { ...AccountLocalizationResources.reset_password_page_description.fa }, + { ...AccountLocalizationResources.change_password_page_title.fa }, + { ...AccountLocalizationResources.change_password_page_description.fa }, + { ...AccountLocalizationResources.search_profiles_page_title.fa }, + { ...AccountLocalizationResources.search_profiles_page_description.fa }, + // + // Tag ... + { ...TagLocalizationResources.tag.fa }, + { ...TagLocalizationResources.tags.fa }, + { ...TagLocalizationResources.tags_page_title.fa }, + { ...TagLocalizationResources.tags_page_description.fa }, + { ...TagLocalizationResources.tags_management_page_title.fa }, + { ...TagLocalizationResources.tags_management_page_description.fa }, + { ...TagLocalizationResources.tags_explorer_page_title.fa }, + { ...TagLocalizationResources.tags_explorer_page_description.fa }, + // + // File ... + { ...FileLocalizationsResources.file.fa }, + { ...FileLocalizationsResources.files.fa }, + { ...FileLocalizationsResources.image.fa }, + { ...FileLocalizationsResources.audio.fa }, + { ...FileLocalizationsResources.video.fa }, + { ...FileLocalizationsResources.document.fa }, + { ...FileLocalizationsResources.file_name.fa }, + { ...FileLocalizationsResources.thumbnail_path.fa }, + { ...FileLocalizationsResources.files_page_title.fa }, + { ...FileLocalizationsResources.files_page_description.fa }, + { ...FileLocalizationsResources.files_management_page_title.fa }, + { ...FileLocalizationsResources.files_management_page_description.fa }, + { ...FileLocalizationsResources.files_explorer_page_title.fa }, + { ...FileLocalizationsResources.files_explorer_page_description.fa }, + // + // Webinar ... + { ...WebinarLocalizationResources.webinar.fa }, + { ...WebinarLocalizationResources.webinars.fa }, + { ...WebinarLocalizationResources.close_webinar.fa }, + { ...WebinarLocalizationResources.webinar_owner.fa }, + { ...WebinarLocalizationResources.webinar_joiner.fa }, + { ...WebinarLocalizationResources.new_webinar_created.fa }, + { ...WebinarLocalizationResources.webinars_page_title.fa }, + { ...WebinarLocalizationResources.visitor_joined_message.fa }, + { ...WebinarLocalizationResources.visitor_leaved_message.fa }, + { ...WebinarLocalizationResources.webinar_closed_message.fa }, + { ...WebinarLocalizationResources.webinars_page_description.fa }, + { ...WebinarLocalizationResources.webinars_management_page_title.fa }, + { + ...WebinarLocalizationResources.webinars_explorer_page_title.fa, + }, + { + ...WebinarLocalizationResources.webinars_explorer_page_description.fa, + }, + { + ...WebinarLocalizationResources.webinars_management_page_description.fa, + }, + ], + }, + { + name: 'english', + locale: 'en-US', + language: 'en', + direction: 'ltr', + resources: [ + { + id: 'app_name', + value: 'SaherElm', + }, + { + id: 'company', + value: 'SaherElm IT Center', + }, + { + id: 'id', + value: 'ID', + }, + { + id: 'file_name', + value: 'File Name', + }, + { + id: 'error', + value: 'Error', + }, + { + id: 'required', + value: 'required', + }, + { + id: 'loading', + value: 'loading', + }, + { + id: 'username', + value: 'Username', + }, + { + id: 'password', + value: 'Password', + }, + { + id: 'first_name', + value: 'Firstname', + }, + { + id: 'last_name', + value: 'Lastname', + }, + { + id: 'verified', + value: 'Verified', + }, + { + id: 'unverified', + value: 'Unverified', + }, + { + id: 'thumbnail', + value: 'Thumbnail', + }, + { + id: 'language', + value: 'Language', + }, + { + id: 'take_screenshot', + value: 'Take Screenshot', + }, + { + id: 'enable_capture_mode', + value: 'Enable Capture Mode', + }, + { + id: 'disable_capture_mode', + value: 'Disable Capture Mode', + }, + { + id: 'be', + value: 'Be', + }, + { + id: 'not_to_be', + value: 'Not to be', + }, + { + id: 'enabled', + value: 'Enabled', + }, + { + id: 'disabled', + value: 'Disabled', + }, + { + id: 'banned', + value: 'Banned', + }, + { + id: 'un_banned', + value: 'UnBanned', + }, + { + id: 'audio', + value: 'Audio', + }, + { + id: 'video', + value: 'Video', + }, + { + id: 'cover_image', + value: 'Cover Image', + }, + { + id: 'image', + value: 'Image', + }, + { + id: 'avatar', + value: 'Avatar', + }, + { + id: 'unknown', + value: 'Unknown', + }, + { + id: 'location', + value: 'Location', + }, + { + id: 'are_you_sure', + value: 'Are You Sure ?', + }, + { + id: 'success_msg', + value: 'Operation done Successfully ...', + }, + { + id: 'locked_msg', + value: 'You have to finish/cancel current proccess for leaving', + }, + { + id: 'empty_content', + value: 'Empty', + }, + { + id: 'no_item', + value: 'there is no Content', + }, + { + id: 'drag_drop_here', + value: 'Drop File Here', + }, + { + id: 'map_dialog_title', + value: 'Show Location', + }, + { + id: 'map_dialog_select_title', + value: 'Select Location', + }, + { + id: 'color_picker_dialog_title', + value: 'Select Color', + }, + { + id: 'dismiss', + value: 'dismiss', + }, + { + id: 'accept', + value: 'Accept', + }, + { + id: 'ok', + value: 'Ok', + }, + { + id: 'save', + value: 'Save', + }, + { + id: 'cancel', + value: 'Cancel', + }, + { + id: 'retry', + value: 'Retry', + }, + { + id: 'edit', + value: 'Edit', + }, + { + id: 'reset', + value: 'Reset', + }, + { + id: 'change', + value: 'Change', + }, + { + id: 'start', + value: 'Start', + }, + { + id: 'yes', + value: 'Yes', + }, + { + id: 'no', + value: 'No', + }, + { + id: 'verify', + value: 'Verify', + }, + { + id: 'confirm', + value: 'Confirm', + }, + { + id: 'crop', + value: 'Crop', + }, + { + id: 'clear', + value: 'Clear', + }, + { + id: 'set', + value: 'Set', + }, + { + id: 'select', + value: 'Select', + }, + { + id: 'send', + value: 'Send', + }, + { + id: 'show', + value: 'Show', + }, + { + id: 'hide', + value: 'Hide', + }, + { + id: 'search', + value: 'Search', + }, + { + id: 'delete', + value: 'Delete', + }, + { + id: 'clone', + value: 'Clone', + }, + { + id: 'select_all', + value: 'Select All', + }, + { + id: 'select_file', + value: 'Select File(s)', + }, + { + id: 'select_all_group', + value: 'Select All Filtered Items', + }, + { + id: 'deselect_all', + value: 'Deselect All', + }, + { + id: 'deselect_all_group', + value: 'Deselect All Filtered Items', + }, + { + id: 'inverse_selection', + value: 'Inverse Selection', + }, + { + id: 'selected_location', + value: 'Selected Location', + }, + { + id: 'preset_colors', + value: 'Preset Colors', + }, + { + id: 'copy_to_clipboard', + value: 'Copy to Clipboard', + }, + { + id: 'color', + value: 'Color', + }, + { + id: 'colors', + value: 'Colors', + }, + { + id: 'upload', + value: 'Upload', + }, + { + id: 'import', + value: 'Import', + }, + { + id: 'continue', + value: 'Continue', + }, + { + id: 'finish', + value: 'Finish', + }, + { + id: 'enable', + value: 'Enable', + }, + { + id: 'disable', + value: 'Disable', + }, + { + id: 'all', + value: 'All', + }, + { + id: 'new', + value: 'New', + }, + { + id: 'add', + value: 'Add', + }, + { + id: 'update', + value: 'Update', + }, + { + id: 'details', + value: 'Details', + }, + { + id: 'translation', + value: 'Translation', + }, + { + id: 'download', + value: 'Download', + }, + { + id: 'list', + value: 'List', + }, + { + id: 'options', + value: 'Options', + }, + { + id: 'descriptions', + value: 'Description', + }, + { + id: 'type', + value: 'Type', + }, + { + id: 'media', + value: 'Media', + }, + { + id: 'medias', + value: 'Medias', + }, + { + id: 'summary', + value: 'ُSummary', + }, + { + id: 'go', + value: 'Go', + }, + { + id: 'input', + value: 'Input', + }, + { + id: 'preview', + value: 'Preview', + }, + { + id: 'close', + value: 'Close', + }, + { + id: 'undo', + value: 'Undo', + }, + { + id: 'redo', + value: 'Redo', + }, + { + id: 'refresh', + value: 'Refresh', + }, + { + id: 'submit', + value: 'Submit', + }, + { + id: 'first', + value: 'First', + }, + { + id: 'last', + value: 'Last', + }, + { + id: 'previous', + value: 'Previous', + }, + { + id: 'next', + value: 'Next', + }, + { + id: 'back', + value: 'Back', + }, + { + id: 'x_markdown_preview', + value: 'Preview', + }, + { + id: 'x_markdown_image', + value: 'Image', + }, + { + id: 'x_markdown_link', + value: 'Link', + }, + { + id: 'x_markdown_ol', + value: 'Ordered List', + }, + { + id: 'x_markdown_ul', + value: 'Unordered List', + }, + { + id: 'x_markdown_quote', + value: 'Quote', + }, + { + id: 'x_markdown_code', + value: 'Code', + }, + { + id: 'x_markdown_heading', + value: 'Heading', + }, + { + id: 'x_markdown_italic', + value: 'Italic', + }, + { + id: 'x_markdown_bold', + value: 'Bold', + }, + { + id: 'x_markdown_guide', + value: 'Markdown Guide', + }, + { + id: 'theme', + value: 'Theme', + }, + { + id: 'theme_mode', + value: 'Theme Mode', + }, + { + id: 'theme_dark', + value: 'Dark', + }, + { + id: 'theme_light', + value: 'Light', + }, + { + id: 'theme_name', + value: 'Theme Name', + }, + { + id: 'theme_name_description', + value: 'Enter Theme name', + }, + { + id: 'theme_clone_title', + value: 'Theme Cloning', + }, + { + id: 'input_color_title', + value: 'Color Value', + }, + { + id: 'input_color_description', + value: 'input color value', + }, + { + id: 'ide_mode', + value: 'Code Style', + }, + { + id: 'ide_format', + value: 'Code Frormatting', + }, + { + id: 'ex_100', + value: 'Invalid Data ...', + }, + { + id: 'ex_101', + value: 'Invalid Date ...', + }, + { + id: 'ex_102', + value: 'Bad Request ...', + }, + { + id: 'ex_103', + value: 'Invalid Args ...', + }, + { + id: 'ex_104', + value: 'Invalid Message ...', + }, + { + id: 'ex_105', + value: 'Invalid Token ...', + }, + { + id: 'ex_106', + value: 'Invalid Verification Code ...', + }, + { + id: 'ex_107', + value: 'Invalid Email Address ...', + }, + { + id: 'ex_108', + value: 'Invalid Mobile Number ...', + }, + { + id: 'ex_109', + value: 'Invalid Password ...', + }, + { + id: 'ex_110', + value: 'Invalid URL ...', + }, + { + id: 'ex_111', + value: 'Invalid Device ...', + }, + { + id: 'ex_112', + value: 'Invalid Profile Image ...', + }, + { + id: 'ex_113', + value: 'Invalid File ...', + }, + { + id: 'ex_114', + value: 'Invalid Username ...', + }, + { + id: 'ex_200', + value: 'Not Found ...', + }, + { + id: 'ex_201', + value: 'Not Allowed ...', + }, + { + id: 'ex_202', + value: 'Not Authorized ...', + }, + { + id: 'ex_203', + value: 'Not Visible ...', + }, + { + id: 'ex_300', + value: 'Account Locked out ...', + }, + { + id: 'ex_301', + value: 'Login Failed ...', + }, + { + id: 'ex_302', + value: 'User Registered Before ...', + }, + { + id: 'ex_303', + value: 'User Disabled ...', + }, + { + id: 'ex_304', + value: 'Device Banned Until:(0)', + }, + { + id: 'ex_305', + value: `You can not Request Verification code Until:(0)`, + }, + { + id: 'ex_306', + value: 'Passwords Same ...', + }, + { + id: 'ex_307', + value: 'Mobiles Same ...', + }, + { + id: 'ex_308', + value: 'Emails Same ...', + }, + { + id: 'ex_309', + value: 'Mobile In Used ...', + }, + { + id: 'ex_310', + value: 'Email In Used ...', + }, + { + id: 'ex_311', + value: 'Mobile Not Confirmed ...', + }, + { + id: 'ex_312', + value: 'Email Not Confirmed ...', + }, + { + id: 'ex_313', + value: 'Post Banned ...', + }, + { + id: 'ex_314', + value: 'User Banned ...', + }, + { + id: 'ex_400', + value: 'Unknown Error ...', + }, + { + id: 'ex_401', + value: 'Duplicate ...', + }, + { + id: 'ex_402', + value: 'Action Failed ...', + }, + { + id: 'ex_403', + value: 'Expired Date ...', + }, + { + id: 'ex_404', + value: 'Unavailable ...', + }, + { + id: 'ex_405', + value: 'Max Allowed Tag Exceeded ...', + }, + { + id: 'ex_406', + value: 'Timeout ...', + }, + { + id: 'ex_500', + value: 'Empty File ...', + }, + { + id: 'ex_501', + value: 'max File size exceeded ...', + }, + { + id: 'ex_502', + value: 'Unsupported File Type ...', + }, + { + id: 'ex_503', + value: 'Max Allowed Media Exceeded ...', + }, + { + id: 'ex_600', + value: 'Invalid Configuration ...', + }, + { + id: 'ex_601', + value: 'Storage Service Initiallization Failed ...', + }, + { + id: 'ex_602', + value: 'Message Service Initialization Failed ...', + }, + { + id: 'ex_900', + value: 'Max Allowed Files Exceeded ...', + }, + { + id: 'ex_901', + value: 'Min Files Size Exceeded ...', + }, + { + id: 'ex_902', + value: 'Max Files Size Exceeded ...', + }, + { + id: 'ex_903', + value: 'Must Accept Terms And Conditions ...', + }, + { + id: 'ex_904', + value: 'Browser does not support Location Services ...', + }, + { + id: 'ex_905', + value: 'You have rejected access to your Location ...', + }, + { + id: 'ex_906', + value: 'Unable to determine your Location ...', + }, + { + id: 'ex_907', + value: 'Browser does not support Storage Services ...', + }, + { + id: 'ex_908', + value: 'Must Logout to access content ...', + }, + { + id: 'ex_909', + value: 'Invalid Locale ...', + }, + { + id: 'ex_910', + value: 'Invalid User Selector ...', + }, + { + id: 'default_loading', + value: 'Loading', + }, + { + id: 'persian', + value: 'Persian', + }, + { + id: 'english', + value: 'English', + }, + { + id: 'actor', + value: 'Actor', + }, + { + id: 'index', + value: 'Index', + }, + { + id: 'title', + value: 'Title', + }, + { + id: 'description', + value: 'Description', + }, + { + id: 'author_id', + value: 'Author Id', + }, + { + id: 'attached_on', + value: 'Attached On', + }, + { + id: 'view', + value: 'View', + }, + { + id: 'viewed_on', + value: 'Viewed On', + }, + { + id: 'check_mail', + value: 'Check your mailbox ...', + }, + { + id: 'name', + value: 'Name', + }, + { + id: 'type', + value: 'Type', + }, + { + id: 'path', + value: 'Path', + }, + { + id: 'uploaded_on', + value: 'Uploaded On', + }, + { + id: 'string_resource', + value: 'String Resource', + }, + { + id: 'resource_title', + value: 'ًResource ID', + }, + { + id: 'add_new_terms_and_condition', + value: 'Add New Terms and Conditions', + }, + { + id: 'add_new_terms_and_condition_description', + value: 'Add New Terms and Conditions Translation', + }, + { + id: 'theme', + value: 'Themes', + }, + // + { + id: 'state', + value: 'State', + }, + { + id: 'enabled', + value: 'Enable', + }, + { + id: 'disabled', + value: 'Disable', + }, + { + id: 'user_id', + value: 'User Id', + }, + { + id: 'content_id', + value: 'Content Id', + }, + { + id: 'bann_state', + value: 'Bann State', + }, + { + id: 'email_state', + value: 'Email State', + }, + { + id: 'phone_number_state', + value: 'Phone NUmber State', + }, + { + id: 'avatar', + value: 'Avatar', + }, + { + id: 'avatar_list', + value: 'Avatars List', + }, + { + id: 'logging_out', + value: 'Logging Out ...', + }, + { + id: 'phone_number', + value: 'Phone Number', + }, + { + id: 'authenticating', + value: 'Authenticating', + }, + { + id: 'user_identifier', + value: 'User ID', + }, + { + id: 'user_identifier_description', + value: 'User ID can be UserName, Email Address or Mobile Number', + }, + { + id: 'password', + value: 'Password', + }, + { + id: 'new_password', + value: 'New Password', + }, + { + id: 'password_confirm', + value: 'Password Confirm', + }, + { + id: 'enter_a_new_password', + value: 'Enter a new Password', + }, + { + id: 'login', + value: 'Login', + }, + { + id: 'logout', + value: 'Logout', + }, + { + id: 'invite_send', + value: 'Send Invitation', + }, + { + id: 'change_data', + value: 'Change Input Data', + }, + { + id: 'request_reset_password', + value: 'Request Reset Password', + }, + { + id: 'request_reset_password_description', + value: 'Request Reset Forgotten Password', + }, + { + id: 'request_confirm_email', + value: 'Request again', + }, + { + id: 'request_confirm_email_description', + value: 'Request again Email Verification Link', + }, + { + id: 'upload_avatar', + value: 'Upload Avatar', + }, + { + id: 'confirmed', + value: 'Confirmed', + }, + { + id: 'unconfirmed', + value: 'UnConfirmed', + }, + { + id: 'terms_and_conditions_title', + value: 'Registration Terms and Conditions', + }, + { + id: 'account_info', + value: 'Account Info', + }, + { + id: 'verification_code', + value: 'Verification Code', + }, + { + id: 'send_verification_code', + value: 'Send Verification Code', + }, + { + id: 'registration_finished', + value: 'Registeration Finished', + }, + { + id: 'registration_finished_description', + value: + 'After this yuo get an actiovation link on your email, check and follow it ...', + }, + { + id: 'first_name', + value: 'First Name', + }, + { + id: 'last_name', + value: 'Last Name', + }, + { + id: 'user_name', + value: 'Username', + }, + { + id: 'role', + value: 'Role', + }, + { + id: 'roles', + value: 'Roles', + }, + { + id: 'mobile', + value: 'Mobile', + }, + { + id: 'email', + value: 'Email', + }, + { + id: 'date_of_birth', + value: 'Date Of Birth', + }, + { + id: 'registration_date', + value: 'Registration Date', + }, + { + id: 'last_login', + value: 'Last Login', + }, + { + id: 'gender', + value: 'Gender', + }, + { + id: 'male', + value: 'Male', + }, + { + id: 'female', + value: 'Female', + }, + { + id: 'landing_page', + value: 'ّLanding Page', + }, + { + id: 'admin_role', + value: 'Admin', + }, + { + id: 'admin_role_description', + value: 'Maintainer and Controller', + }, + { + id: 'responsible_manager_role', + value: 'Responsible Manager', + }, + { + id: 'responsible_manager_role_description', + value: 'Responsible Manager Descriptions', + }, + { + id: 'journalist_role', + value: 'Journalist', + }, + { + id: 'journalist_role_description', + value: 'Journalist Descriptions', + }, + { + id: 'chief_clerk_role', + value: 'Chief Clerk', + }, + { + id: 'chief_clerk_role_description', + value: 'Chief Clerk Descriptions', + }, + { + id: 'chief_clerk_assistant_role', + value: 'Chief Clerk Assistant', + }, + { + id: 'chief_clerk_assistant_role_description', + value: 'Chief Clerk Assistant Descriptions', + }, + { + id: 'actuary_role', + value: 'Actuary', + }, + { + id: 'actuary_role_description', + value: 'Actuary Descriptions', + }, + { + id: 'actuary_assistant_role', + value: 'Actuary Assistant', + }, + { + id: 'actuary_assistant_role_description', + value: 'Actuary Assistant Descriptions', + }, + { + id: 'reporter_role', + value: 'Reporter', + }, + { + id: 'reporter_role_description', + value: 'Reporter Descriptions', + }, + { + id: 'reporter_assistant_role', + value: 'Reporter Assistant', + }, + { + id: 'reporter_assistant_role_description', + value: 'Reporter Assistant Descriptions', + }, + { + id: 'agent_role', + value: 'Agent', + }, + { + id: 'agent_role_description', + value: 'Service Provider Agent', + }, + { + id: 'user_role', + value: 'User', + }, + { + id: 'user_role_description', + value: 'consumer of provided Services', + }, + { + id: 'friendship_state', + value: 'Friendship State', + }, + { + id: 'unknown', + value: 'Unknown', + }, + { + id: 'follower', + value: 'Follower', + }, + { + id: 'followers', + value: 'Followers', + }, + { + id: 'following', + value: 'Following', + }, + { + id: 'followings', + value: 'Followings', + }, + { + id: 'friend', + value: 'Friend', + }, + { + id: 'block', + value: 'Block', + }, + { + id: 'unblock', + value: 'Unblock', + }, + { + id: 'blocked', + value: 'Blocked', + }, + { + id: 'follow', + value: 'Follow', + }, + { + id: 'unfollow', + value: 'Unfollow', + }, + { + id: 'unfollow_as', + value: 'Unfollow Friendshp', + }, + { + id: 'unfollow_follower', + value: 'Unfollow Follower', + }, + { + id: 'unfollow_following', + value: 'Unfollow Following', + }, + { + id: 'cancel_request', + value: 'Cancel Request', + }, + { + id: 'accept_request', + value: 'Accept Request', + }, + { + id: 'pending', + value: 'Pending', + }, + { + id: 'pending_follower', + value: 'waiting for your response', + }, + { + id: 'pending_following', + value: 'wait for response', + }, + { + id: 'rejected', + value: 'Rejected', + }, + { + id: 'accepted', + value: 'Accepted', + }, + { + id: 'min_length_error', + value: 'less than minimum required', + }, + { + id: 'max_length_error', + value: 'more than maximum required', + }, + { + id: 'invalid_url', + value: 'Invalid URL', + }, + { + id: 'invalid_phone_number', + value: 'Invalid Phone Number', + }, + { + id: 'invalid_email_address', + value: 'Invalid Email Address', + }, + { + id: 'email_cant_register', + value: 'Email not available ...', + }, + { + id: 'mobile_cant_register', + value: 'Mobile not available ...', + }, + { + id: 'user_name_cant_register', + value: 'Username not available ...', + }, + { + id: 'email_in_use', + value: 'Email Address in Use', + }, + { + id: 'phone_number_in_use', + value: 'Phone Number in Use', + }, + { + id: 'passwords_not_matched', + value: 'Passwords not matched', + }, + // + //#region Blockchain Views ... + { + id: 'symbol', + value: 'Symbol', + }, + { + id: 'theme', + value: 'ُTheme', + }, + { + id: 'chart', + value: 'Chart', + }, + { + id: 'date_range', + value: 'Date Range', + }, + { + id: 'chart_type', + value: 'Chart Type', + }, + { + id: 'cryptocurrency_market', + value: 'Market State', + }, + { + id: 'analysis_tool', + value: 'Analysis Tool', + }, + { + id: 'symbol_overview', + value: 'Symbol Overview', + }, + { + id: 'values_tracking', + value: 'Values Tracking', + }, + { + id: 'technical_analysis', + value: 'Technical Analysis', + }, + { + id: 'select_sheet', + value: 'Select Sheet', + }, + { + id: 'display_mode', + value: 'Display Mode', + }, + { + id: 'search_symbols', + value: 'Search Symbols', + }, + { + id: 'symbol_profile', + value: 'Symbol Profile', + }, + { + id: 'scale_mode', + value: 'Scale Mode', + }, + { + id: 'scale_position', + value: 'Scale Position', + }, + //#endregion + // + { + id: 'startup_page_title', + value: 'Startup', + }, + { + id: 'startup_page_description', + value: 'Start Loading Application', + }, + { + id: 'landing_page_title', + value: 'First Page', + }, + { + id: 'landing_page_description', + value: 'First Page was shown', + }, + { + id: 'home_page_title', + value: 'Management Panel', + }, + { + id: 'home_page_description', + value: 'Management Tools', + }, + { + id: 'error_page_title', + value: 'Error', + }, + { + id: 'error_page_description', + value: 'Repport Details of happens Error', + }, + { + id: 'not_authorized_page_title', + value: 'Unauthorized Access', + }, + { + id: 'not_authorized_page_description', + value: 'required Permissions not found', + }, + { + id: 'theme_manager_page_title', + value: 'Theme Manager', + }, + { + id: 'theme_manager_page_description', + value: 'add/remove and edit Application Themes', + }, + // + { + id: 'confirm_page_title', + value: 'Confirm Registration', + }, + { + id: 'confirm_page_description', + value: 'Confirm Registration and Enable User Account', + }, + { + id: 'invite_page_title', + value: 'Invite Users', + }, + { + id: 'invite_page_description', + value: 'Send Invitation Email and Invite Users to Join', + }, + { + id: 'avatars_page_title', + value: 'Profile Avatars', + }, + { + id: 'avatars_page_description', + value: 'Manage User Profile Avatars', + }, + { + id: 'users_management_page_title', + value: 'Users', + }, + { + id: 'users_management_page_description', + value: 'Users Management Tools', + }, + // + { + id: 'settings_page_title', + value: 'Settings', + }, + { + id: 'settings_page_description', + value: 'Tools for Configuring System', + }, + { + id: 'string_resource_page_title', + value: 'String Resources', + }, + { + id: 'string_resource_page_description', + value: 'String Resources Management', + }, + { + id: 'terms_and_conditions_page_title', + value: 'Terms and Conditions', + }, + { + id: 'terms_and_conditions_page_description', + value: 'Terms and Conditions Management', + }, + { + id: 'posts_page_title', + value: 'Posts', + }, + { + id: 'posts_page_description', + value: 'Tools for Posts', + }, + { + id: 'content_explorer_page_title', + value: 'Contents Explorer', + }, + { + id: 'content_explorert_page_description', + value: 'Explore Published Contents', + }, + { + id: 'content_management_page_title', + value: 'Content Management', + }, + { + id: 'content_management_page_description', + value: 'Content Management Tools', + }, + { + id: 'media_explorer_page_title', + value: 'Media Explorer', + }, + { + id: 'media_explorert_page_description', + value: 'Explore Published Medias', + }, + { + id: 'media_gallery_explorer_page_title', + value: 'Media Galleries', + }, + { + id: 'media_gallery_explorert_page_description', + value: 'Review Posted Galleries', + }, + { + id: 'media_management_page_title', + value: 'Media Management', + }, + { + id: 'media_management_page_description', + value: 'Media Management Tools', + }, + { + id: 'documents_page_title', + value: 'Docments', + }, + { + id: 'documents_page_description', + value: 'Tools for Documents', + }, + { + id: 'document_management_page_title', + value: 'Document Management', + }, + { + id: 'document_management_page_description', + value: 'Document Management Tools', + }, + // + // Custom ... + { + id: 'at', + value: 'at', + }, + { + id: 'posted_by', + value: 'posted by', + }, + { + id: 'edited_by', + value: 'edited by', + }, + { + id: 'management_panel', + value: 'Management Panel', + }, + { + id: 'connect', + value: 'Connect', + }, + { + id: 'references', + value: 'References', + }, + { + id: 'news', + value: 'News', + }, + { + id: 'share', + value: 'Share', + }, + { + id: 'media', + value: 'Media', + }, + { + id: 'medias', + value: 'Medias', + }, + { + id: 'content', + value: 'Content', + }, + { + id: 'see_more', + value: '« see more', + }, + { + id: 'editted_on', + value: 'Editted on', + }, + { + id: 'audioinput', + value: 'Audio Input', + }, + { + id: 'audiooutput', + value: 'Audio Output', + }, + { + id: 'videoinput', + value: 'Video Input', + }, + { + id: 'select_media_device', + value: 'Select Media Device', + }, + { + id: 'author', + value: 'Author', + }, + { + id: 'private', + value: 'Private', + }, + { + id: 'public', + value: 'Public', + }, + { + id: 'locales', + value: 'Locales', + }, + { + id: 'created_on', + value: 'Created On', + }, + { + id: 'approver', + value: 'Approver', + }, + { + id: 'approver_id', + value: 'Approved by', + }, + { + id: 'approved_on', + value: 'Approved on', + }, + { + id: 'media_gallery', + value: 'Media Gallery', + }, + { + id: 'media_galleries', + value: 'Media Galleries', + }, + { + id: 'contents', + value: 'Contents', + }, + { + id: 'posted_on', + value: 'Posted On', + }, + { + id: 'documents', + value: 'Documents', + }, + { + id: 'upload_documents', + value: 'Upload Documents', + }, + // + // Video ... + { ...VideoLocalizationResources.mute.en }, + { ...VideoLocalizationResources.play.en }, + { ...VideoLocalizationResources.pause.en }, + { ...VideoLocalizationResources.unmute.en }, + { ...VideoLocalizationResources.volume.en }, + { ...VideoLocalizationResources.rewind.en }, + { ...VideoLocalizationResources.forward.en }, + { ...VideoLocalizationResources.fullscreen.en }, + { ...VideoLocalizationResources.unfullscreen.en }, + { ...VideoLocalizationResources.change_volume.en }, + { ...VideoLocalizationResources.mic_on.en }, + { ...VideoLocalizationResources.mic_off.en }, + { ...VideoLocalizationResources.video_on.en }, + { ...VideoLocalizationResources.video_off.en }, + { ...VideoLocalizationResources.call_end.en }, + { ...VideoLocalizationResources.call_request.en }, + { ...VideoLocalizationResources.start_record.en }, + { ...VideoLocalizationResources.start_record.en }, + { ...VideoLocalizationResources.stop_share_screen.en }, + { ...VideoLocalizationResources.start_share_screen.en }, + { ...VideoLocalizationResources.switch_to_back_camera.en }, + { ...VideoLocalizationResources.switch_to_front_camera.en }, + // + // Reference ... + { ...ReferenceLocalizationResources.reference.en }, + { ...ReferenceLocalizationResources.references.en }, + { ...ReferenceLocalizationResources.provided_for.en }, + { ...ReferenceLocalizationResources.referenced_to.en }, + // + // Account ... + { ...AccountLocalizationResources.account.en }, + { ...AccountLocalizationResources.accounts.en }, + { ...AccountLocalizationResources.profile.en }, + { ...AccountLocalizationResources.profiles.en }, + { ...AccountLocalizationResources.avatar.en }, + { ...AccountLocalizationResources.avatars.en }, + { ...AccountLocalizationResources.register.en }, + { ...AccountLocalizationResources.accept_terms.en }, + { ...AccountLocalizationResources.email_confirmation.en }, + { ...AccountLocalizationResources.input_user_info.en }, + { ...AccountLocalizationResources.input_user_avatar.en }, + { ...AccountLocalizationResources.finish_registration.en }, + { ...AccountLocalizationResources.account_page_title.en }, + { ...AccountLocalizationResources.account_page_description.en }, + { ...AccountLocalizationResources.profile_page_title.en }, + { ...AccountLocalizationResources.profile_page_description.en }, + { ...AccountLocalizationResources.login_page_title.en }, + { ...AccountLocalizationResources.login_page_description.en }, + { ...AccountLocalizationResources.logout_page_title.en }, + { ...AccountLocalizationResources.logout_page_description.en }, + { ...AccountLocalizationResources.register_page_title.en }, + { ...AccountLocalizationResources.register_page_description.en }, + { ...AccountLocalizationResources.email_confirm_page_title.en }, + { ...AccountLocalizationResources.email_confirm_page_description.en }, + { ...AccountLocalizationResources.phone_confirm_page_title.en }, + { ...AccountLocalizationResources.phone_confirm_page_description.en }, + { ...AccountLocalizationResources.reset_password_page_title.en }, + { ...AccountLocalizationResources.reset_password_page_description.en }, + { ...AccountLocalizationResources.change_password_page_title.en }, + { ...AccountLocalizationResources.change_password_page_description.en }, + { ...AccountLocalizationResources.search_profiles_page_title.en }, + { ...AccountLocalizationResources.search_profiles_page_description.en }, + // + // Tag ... + { ...TagLocalizationResources.tag.en }, + { ...TagLocalizationResources.tags.en }, + { ...TagLocalizationResources.tags_page_title.en }, + { ...TagLocalizationResources.tags_page_description.en }, + { ...TagLocalizationResources.tags_management_page_title.en }, + { ...TagLocalizationResources.tags_management_page_description.en }, + { ...TagLocalizationResources.tags_explorer_page_title.en }, + { ...TagLocalizationResources.tags_explorer_page_description.en }, + // + // File ... + { ...FileLocalizationsResources.file.en }, + { ...FileLocalizationsResources.files.en }, + { ...FileLocalizationsResources.image.en }, + { ...FileLocalizationsResources.audio.en }, + { ...FileLocalizationsResources.video.en }, + { ...FileLocalizationsResources.document.en }, + { ...FileLocalizationsResources.file_name.en }, + { ...FileLocalizationsResources.thumbnail_path.en }, + { ...FileLocalizationsResources.files_page_title.en }, + { ...FileLocalizationsResources.files_page_description.en }, + { ...FileLocalizationsResources.files_management_page_title.en }, + { ...FileLocalizationsResources.files_management_page_description.en }, + { ...FileLocalizationsResources.files_explorer_page_title.en }, + { ...FileLocalizationsResources.files_explorer_page_description.en }, + // + // Webinar ... + { ...WebinarLocalizationResources.webinar.en }, + { ...WebinarLocalizationResources.webinars.en }, + { ...WebinarLocalizationResources.close_webinar.en }, + { ...WebinarLocalizationResources.webinar_owner.en }, + { ...WebinarLocalizationResources.webinar_joiner.en }, + { ...WebinarLocalizationResources.new_webinar_created.en }, + { ...WebinarLocalizationResources.webinars_page_title.en }, + { ...WebinarLocalizationResources.visitor_joined_message.en }, + { ...WebinarLocalizationResources.visitor_leaved_message.en }, + { ...WebinarLocalizationResources.webinar_closed_message.en }, + { ...WebinarLocalizationResources.webinars_page_description.en }, + { ...WebinarLocalizationResources.webinars_management_page_title.en }, + { + ...WebinarLocalizationResources.webinars_explorer_page_title.en, + }, + { + ...WebinarLocalizationResources.webinars_explorer_page_description.en, + }, + { + ...WebinarLocalizationResources.webinars_management_page_description.en, + }, + ], + }, +]; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\config\page.config.ts + +import { + XUserRoleInfo, + XAvailableRoles, + getTopRoleByRoleInfo, +} from 'x-saherelm-api-sdk'; +import { + XIconNames, + XNavigatorListItem, + toNavigatiorListItem, +} from 'x-framework-components'; +import { AppResourceIDs } from './localization.config'; +import { TagRoutes, Tags } from '../views/tags/v-tag.configs'; +import { toArray, XPage, XResourceIDs } from 'x-framework-core'; +import { FileRoutes, Files } from '../views/files/v-file.configs'; +import { XSimplePage } from '../views/typings/simple.page.typings'; +import { Account, AccountRoutes } from '../views/account/account.configs'; +import { WebinarRoutes, Webinars } from '../views/webinar/v-webinar.configs'; + +// +//#region Route Config ... +export enum BaseRoutes { + Default = '', + Unknown = '**', +} + +export enum StartupRoutes { + Default = 'startup', +} + +export enum LandingRoutes { + Default = '#', +} + +export enum NotAuthorizedRoutes { + Default = 'not-authorized', +} + +export enum ErrorRoutes { + Default = 'error', +} + +export enum HomeRoutes { + Default = 'home', +} + +export enum AppRoutes { + Default = BaseRoutes.Default, + Startup = StartupRoutes.Default, + Landing = LandingRoutes.Default, + Home = HomeRoutes.Default, + Webinars = WebinarRoutes.Default, + Tags = TagRoutes.Default, + Files = FileRoutes.Default, + Account = AccountRoutes.Default, + NotAuthorized = NotAuthorizedRoutes.Default, + Error = ErrorRoutes.Default, + Unknown = BaseRoutes.Unknown, +} +//#endregion + +// +//#region Page Configs ... +export enum PageName { + // + //#region Startup ... + Startup = AppRoutes.Startup, + //#endregion + + // + //#region Landing ... + Landing = AppRoutes.Landing, + //#endregion + + // + //#region Home ... + Home = AppRoutes.Home, + //#endregion + + // + //#region Webinar ... + Webinars = AppRoutes.Webinars, + WebinarsExplorer = WebinarRoutes.WebinarsExplorer, + WebinarsManagement = WebinarRoutes.WebinarsManagement, + //#endregion + + // + //#region Tag ... + Tags = AppRoutes.Tags, + TagsExplorer = TagRoutes.TagsExplorer, + TagsManagement = TagRoutes.TagsManagement, + //#endregion + + // + //#region File ... + Files = AppRoutes.Files, + FilesExplorer = FileRoutes.FilesExplorer, + FilesManagement = FileRoutes.FilesManagement, + //#endregion + + // + //#region Account ... + Account = AppRoutes.Account, + Login = AccountRoutes.Login, + Logout = AccountRoutes.Logout, + Profile = AccountRoutes.Profile, + Register = AccountRoutes.Register, + EmailConfirm = AccountRoutes.EmailConfirm, + PhoneConfirm = AccountRoutes.PhoneConfirm, + ResetPassword = AccountRoutes.ResetPassword, + ChangePassword = AccountRoutes.ChangePassword, + SearchProfiles = AccountRoutes.SearchProfiles, + //#endregion + + // + //#region NotAuthorized ... + NotAuthorized = AppRoutes.NotAuthorized, + //#endregion + + // + //#region Error ... + Error = AppRoutes.Error, + //#endregion +} + +export type PageNames = keyof typeof PageName; + +export type PageNameIdentifier = PageName | PageNames | string; + +export type PageIndexType = { + [key in PageNames]?: XPage | XSimplePage; +}; + +export const PageIndex: PageIndexType = { + // + //#region Startup ... + Startup: { + id: `${PageName.Startup}`, + name: `${PageName.Startup}`, + title: AppResourceIDs.startup_page_title, + description: AppResourceIDs.startup_page_description, + baseRoute: `${AppRoutes.Startup}`, + route: ['/', `${AppRoutes.Startup}`], + icon: XIconNames.friendship_unknown, + }, + //#endregion + + // + //#region Landing ... + Landing: { + id: `${PageName.Landing}`, + name: `${PageName.Landing}`, + title: XResourceIDs.app_name, + description: XResourceIDs.company, + baseRoute: `${AppRoutes.Landing}`, + route: ['/', `${AppRoutes.Landing}`], + icon: XIconNames.posts, + }, + //#endregion + + // + //#region Home ... + Home: { + icon: XIconNames.home, + id: `${PageName.Home}`, + name: `${PageName.Home}`, + baseRoute: `${AppRoutes.Home}`, + route: ['/', `${AppRoutes.Home}`], + title: AppResourceIDs.home_page_title, + description: AppResourceIDs.home_page_description, + }, + //#endregion + + // + // Webinars ... + Webinars: { + ...Webinars, + }, + + // + // Tags ... + Tags: { + ...Tags, + }, + + // + // Files ... + Files: { + ...Files, + }, + + // + // Account ... + Account: { + ...Account, + }, + + // + //#region Error Page ... + Error: { + id: `${PageName.Error}`, + name: `${PageName.Error}`, + title: AppResourceIDs.error_page_title, + description: AppResourceIDs.error_page_description, + baseRoute: `${AppRoutes.Error}`, + route: ['/', `${AppRoutes.Error}`], + icon: XIconNames.error, + }, + //#endregion + + // + //#region Not Authorized Page ... + NotAuthorized: { + id: `${PageName.NotAuthorized}`, + name: `${PageName.NotAuthorized}`, + title: AppResourceIDs.not_authorized_page_title, + description: AppResourceIDs.not_authorized_page_description, + baseRoute: `${AppRoutes.NotAuthorized}`, + route: ['/', `${AppRoutes.NotAuthorized}`], + icon: XIconNames.friendship_block, + }, + //#endregion +}; + +// +// a Const of Available Pages ... +const accountChilds = toArray(PageIndex.Account.childs); +export const Pages: any = { + // + //#region Startup ... + Startup: PageIndex.Startup, + //#endregion + + // + //#region Landing ... + Landing: PageIndex.Landing, + //#endregion + + // + //#region Home ... + Home: PageIndex.Home, + //#endregion + + // + // Webinars ... + Webinars: PageIndex.Webinars, + WebinarsExplorer: Webinars.childs[1], + WebinarsManagement: Webinars.childs[0], + + // + // Tags ... + Tags: PageIndex.Tags, + TagsExplorer: Tags.childs[1], + TagsManagement: Tags.childs[0], + + // + // Files ... + Files: PageIndex.Files, + FilesExplorer: Files.childs[1], + FilesManagement: Files.childs[0], + + // + //#region Account ... + Account: PageIndex.Account, + Login: Account.childs[0], + Logout: Account.childs[1], + Profile: Account.childs[2], + Register: Account.childs[3], + EmailConfirm: Account.childs[4], + PhoneConfirm: Account.childs[5], + ResetPassword: Account.childs[6], + ChangePassword: Account.childs[7], + SearchProfiles: Account.childs[8], + //#endregion + + // + //#region Error ... + Error: PageIndex.Error, + //#endregion + + // + //#region Notuthorized ... + NotAuthorized: PageIndex.NotAuthorized, + //#endregion +}; +//#endregion + +// +//#region Navigator Pages ... +// +//#region Navigation Items ... +// +// Admin Naigator Pages ... +const AdminNavPageItems: XNavigatorListItem[] = [ + // + // Home ... + { + ...toNavigatiorListItem(PageIndex.Home), + }, + + // + // Webinars ... + { + ...toNavigatiorListItem(PageIndex.Webinars), + }, + + // + // Tags ... + { + ...toNavigatiorListItem(PageIndex.Tags), + }, + + // + // Files ... + { + ...toNavigatiorListItem(PageIndex.Files), + }, + + // + // Account ... + { + ...toNavigatiorListItem(PageIndex.Account), + childs: [ + // + // Profile ... + { ...toNavigatiorListItem(Pages.Profile) }, + // + // Change Password ... + { ...toNavigatiorListItem(Pages.ChangePassword) }, + // + // Search Profiles ... + { ...toNavigatiorListItem(Pages.SearchProfiles) }, + ], + href: '', + }, + + // + // Logout ... + { + ...toNavigatiorListItem(Pages.Logout), + href: '', + }, +]; + +// +// Agent Navigator Pages ... +const AgentNavPageItems: XNavigatorListItem[] = [ + // + // Home ... + { + ...toNavigatiorListItem(PageIndex.Home), + }, + + // + // Webinars ... + { + ...toNavigatiorListItem(PageIndex.Webinars), + }, + + // + // Account ... + { + ...toNavigatiorListItem(PageIndex.Account), + childs: [ + // + // Profile ... + { ...toNavigatiorListItem(Pages.Profile) }, + // + // Change Password ... + { ...toNavigatiorListItem(Pages.ChangePassword) }, + // + // Search Profiles ... + { ...toNavigatiorListItem(Pages.SearchProfiles) }, + ], + href: '', + }, + + // + // Logout ... + { + ...toNavigatiorListItem(Pages.Logout), + href: '', + }, +]; + +// +// Users Navigator Pages ... +const UserNavPageItems: XNavigatorListItem[] = [ + // + // Home ... + { + ...toNavigatiorListItem(PageIndex.Home), + }, + + // + // Webinars ... + { + ...toNavigatiorListItem(PageIndex.Webinars), + childs: [ + // + // Webinars Explorer ... + { ...toNavigatiorListItem(Pages.WebinarsExplorer) }, + ], + }, + + // + // Account ... + { + ...toNavigatiorListItem(PageIndex.Account), + childs: [ + // + // Profile ... + { ...toNavigatiorListItem(Pages.Profile) }, + // + // Change Password ... + { ...toNavigatiorListItem(Pages.ChangePassword) }, + // + // Search Profiles ... + { ...toNavigatiorListItem(Pages.SearchProfiles) }, + ], + }, + + // + // Logout ... + { + ...toNavigatiorListItem(Pages.Logout), + href: '', + }, +]; + +// +// Guest ... +const GuestNavPageItems: XNavigatorListItem[] = [ + // + // Register ... + { + ...toNavigatiorListItem(Pages.Register), + // href: '', + }, + // + // Login ... + { + ...toNavigatiorListItem(Pages.Login), + // href: '', + }, +]; +//#endregion + +// +//#region Navigator Pages Provider ... +export async function getNavPages( + roleInfo: XUserRoleInfo, +): Promise { + // + if (!roleInfo) { + // + // Guest Users ... + return GuestNavPageItems; + } + + // + // TODO: Fix these ... + const topRole = getTopRoleByRoleInfo(roleInfo); + switch (topRole) { + // + case XAvailableRoles.Admin: + return AdminNavPageItems; + + // + case XAvailableRoles.Agent: + return AgentNavPageItems; + + // + // TODO: Fix this ... + case XAvailableRoles.User: + return UserNavPageItems; + + // + default: + return GuestNavPageItems; + } +} +//#endregion +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\config\x-api.config.ts + +import { XCONFIG } from './x.config'; +import { XApiConfiguration } from 'x-framework-identity-sdk'; + +/** + * Api Configuration + */ +export const XAPI_CONFIG = new XApiConfiguration({ + // + baseApiPath: XCONFIG.baseUrl, + apiVersion: XCONFIG.apiVersion, + poweredByValue: XCONFIG.poweredValue, + revisionSecretKey: XCONFIG.secretKey, +}); + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\config\x.config.ts + +import { XConfig } from './app.config'; +import { InjectionToken } from '@angular/core'; +import { environment } from '../../environments/environment'; + +export const XCONFIG: XConfig = JSON.parse( + JSON.stringify(environment.configurations) +); + +export const X_CONFIG = new InjectionToken('x_config'); + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\account\account.module.ts + +import { NgModule } from '@angular/core'; +import { LoginPage } from './login/login.page'; +import { RouterModule } from '@angular/router'; +import { AdminGuard } from 'x-saherelm-api-sdk'; +import { LogoutPage } from './logout/logout.page'; +import { ProfilePage } from './profile/profile.page'; +import { XCanDeactivateGuard } from 'x-framework-core'; +import { ViewsModule } from 'src/app/views/public-api'; +import { RegisterPage } from './register/register.page'; +import { BaseRoutes } from 'src/app/config/page.config'; +import { AuthGuard, NotAuthGuard } from 'x-framework-identity-sdk'; +import { AccountRoutes } from 'src/app/views/account/account.configs'; +import { EmailConfirmPage } from './email-confirm/email-confirm.page'; +import { PhoneConfirmPage } from './phone-confirm/phone-confirm.page'; +import { ResetPasswordPage } from './reset-password/reset-password.page'; +import { ChangePasswordPage } from './change-password/change-password.page'; +import { SearchProfilesPage } from './search-profiles/search-profiles.page'; + +@NgModule({ + declarations: [ + LoginPage, + LogoutPage, + ProfilePage, + RegisterPage, + PhoneConfirmPage, + EmailConfirmPage, + ResetPasswordPage, + ChangePasswordPage, + SearchProfilesPage, + ], + imports: [ + ViewsModule, + RouterModule.forChild([ + { + path: BaseRoutes.Default, + redirectTo: AccountRoutes.Profile, + pathMatch: 'full', + }, + // + // Login ... + { + path: AccountRoutes.Login, + component: LoginPage, + canActivate: [NotAuthGuard], + canDeactivate: [XCanDeactivateGuard], + }, + // + // Logout ... + { + path: AccountRoutes.Logout, + component: LogoutPage, + canActivate: [AuthGuard], + canDeactivate: [XCanDeactivateGuard], + }, + // + // Profile ... + { + path: AccountRoutes.Profile, + component: ProfilePage, + canActivate: [AuthGuard], + canDeactivate: [XCanDeactivateGuard], + }, + // + // Register ... + { + path: AccountRoutes.Register, + component: RegisterPage, + canActivate: [NotAuthGuard], + canDeactivate: [XCanDeactivateGuard], + }, + // + // Email Confirm ... + { + path: AccountRoutes.EmailConfirm, + component: EmailConfirmPage, + canActivate: [NotAuthGuard], + canDeactivate: [XCanDeactivateGuard], + }, + // + // Phone Confirm ... + { + path: AccountRoutes.PhoneConfirm, + component: PhoneConfirmPage, + canActivate: [NotAuthGuard], + canDeactivate: [XCanDeactivateGuard], + }, + // + // Reset Password ... + { + path: AccountRoutes.ResetPassword, + component: ResetPasswordPage, + canActivate: [NotAuthGuard], + canDeactivate: [XCanDeactivateGuard], + }, + // + // Change Password ... + { + path: AccountRoutes.ChangePassword, + component: ChangePasswordPage, + canActivate: [AuthGuard], + canDeactivate: [XCanDeactivateGuard], + }, + // + // Search Profiles ... + { + path: AccountRoutes.SearchProfiles, + component: SearchProfilesPage, + canActivate: [AuthGuard], + canDeactivate: [XCanDeactivateGuard], + }, + { + path: BaseRoutes.Unknown, + pathMatch: 'full', + redirectTo: AccountRoutes.Default, + }, + ]), + ], + exports: [ + // + ViewsModule, + RouterModule, + // + LoginPage, + LogoutPage, + ProfilePage, + RegisterPage, + PhoneConfirmPage, + EmailConfirmPage, + ResetPasswordPage, + ChangePasswordPage, + SearchProfilesPage, + ], +}) +export class AccountPageModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\account\account.typings.ts + +export const LoginTasks = { + Login: 'login', + RequestResetPassword: 'request_reset_password', + ResendEmailVerificationLink: 'resend_email_verification_link', +}; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\account\change-password\change-password.page.html + + + + + + + + + + Change Password ... + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\account\change-password\change-password.page.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\account\change-password\change-password.page.ts + +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { VPageComponent } from 'src/app/views/v-page/v-page.component'; + +@Component({ + selector: 'app-change-password', + templateUrl: './change-password.page.html', + styleUrls: ['./change-password.page.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ChangePasswordPage extends VPageComponent { + // + //#region Props ... + hasSide = true; + toolbarShowSubTitle = true; + showToolbarEndSlot = true; + showToolbarContent = false; + titleRes = this.AppResourceIDs.change_password_page_title; + toolbarSubTitle = this.resourceProvider(this.titleRes); + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\account\email-confirm\email-confirm.page.html + + + + + + + + + + Email Confirm ... + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\account\email-confirm\email-confirm.page.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\account\email-confirm\email-confirm.page.ts + +import { XSideType } from 'x-framework-components'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { VPageComponent } from 'src/app/views/v-page/v-page.component'; + +@Component({ + selector: 'app-email-confirm', + templateUrl: './email-confirm.page.html', + styleUrls: ['./email-confirm.page.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class EmailConfirmPage extends VPageComponent { + // + //#region Props ... + hasSide = true; + toolbarShowSubTitle = true; + showToolbarEndSlot = true; + showToolbarContent = false; + titleRes = this.AppResourceIDs.email_confirm_page_title; + toolbarSubTitle = this.resourceProvider(this.titleRes); + + /** + * use this for Menu always Visible ... + */ + toggleMenuWhen = ''; + sideType = XSideType.Overlay; + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\account\login\login.page.html + + + + + + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\account\login\login.page.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\account\login\login.page.ts + +import { + XTask, + XParam, + getTaskName, + isNullOrUndefined, +} from 'x-framework-core'; +import { filter } from 'rxjs/operators'; +import { LoginTasks } from '../account.typings'; +import { Pages } from 'src/app/config/page.config'; +import { XSideType } from 'x-framework-components'; +import { XLoginRequestDto } from 'x-framework-identity-sdk'; +import { VModelState } from 'src/app/views/v-dto/v-dto.typings'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; +import { VPageComponent } from 'src/app/views/v-page/v-page.component'; + +@Component({ + selector: 'app-login', + templateUrl: './login.page.html', + styleUrls: ['./login.page.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +// tslint:disable-next-line:component-class-suffix +export class LoginPage extends VPageComponent { + // + //#region Props ... + // + hasSide = true; + toolbarShowSubTitle = true; + showToolbarEndSlot = true; + showToolbarContent = false; + titleRes = this.AppResourceIDs.login_page_title; + toolbarSubTitle = this.resourceProvider(this.titleRes); + + /** + * use this for Menu always Visible ... + */ + toggleMenuWhen = ''; + sideType = XSideType.Overlay; + + // + showEmailConfirm = false; + showResetPassword = false; + + // + model: XLoginRequestDto = { + userSelectBy: '', + password: '', + }; + //#endregion + + // + //#region Constructor ... + //#endregion + + // + //#region LifeCycles ... + //#endregion + + // + //#region Register Handlers ... + //#endregion + + // + //#region Task Handlers ... + async onTaskStart(name: string, task: XTask) { + super.onTaskStart(name, task); + + // + // Prevent Moving Forward ... + if (!this.isPageTask(name)) { + return; + } + + // + // Set Loading State ... + await this.setLoadingState(true); + + // + // Handle Flags for Confirm Email and Reset Password ... + // TODO: Complete this ... + + // + if (name === LoginTasks.ResendEmailVerificationLink) { + this.showEmailConfirm = false; + } + + // + if (name === LoginTasks.RequestResetPassword) { + this.showResetPassword = false; + } + + // + // Apply Changes ... + this.detectChanges(); + } + + async onTaskFinished(name: string, task: XTask) { + super.onTaskFinished(name, task); + + // + // Prevent Moving Forward ... + if (!this.isPageTask(name)) { + return; + } + + // + // Set Loading State ... + await this.setLoadingState(false); + + // + // Handle User Authenticated ... + if (name === LoginTasks.Login) { + this.handleUserAuthenticated(); + return; + } + + // + // If Email Verification is Passed ... + if ( + name === LoginTasks.ResendEmailVerificationLink || + name === LoginTasks.RequestResetPassword + ) { + // + await this.managerService.notificationService.presentSuccessNotification({ + message: this.resourceProvider(this.AppResourceIDs.check_mail), + dissmissable: true, + }); + } + } + + async onTaskFailed(name: string, task: XTask) { + super.onTaskFailed(name, task); + + // + // Prevent Moving Forward ... + if (!this.isPageTask(name)) { + return; + } + + // + // Set Loading State ... + await this.setLoadingState(false); + + // + // Retrieve Exception ... + const exception = task.error; + await this.handleErrorState(exception, true); + + // + // Handle Email Verification Error ... + if (task.name === LoginTasks.ResendEmailVerificationLink) { + // + this.uiDisabled = true; + this.showEmailConfirm = true; + + // + this.detectChanges(); + return; + } + + // + // Handle Request Reset Password Error ... + if (task.name === LoginTasks.RequestResetPassword) { + // + this.uiDisabled = true; + this.showResetPassword = true; + + // + this.detectChanges(); + return; + } + + // + // Handle Other Errors ... + switch (exception.id) { + // + // Email Not Confirmed ... + case 135: + // + this.uiDisabled = true; + this.showEmailConfirm = true; + break; + + // + // User Disabled ... + case 117: + // + this.uiDisabled = true; + this.showEmailConfirm = true; + break; + // + // Login Failed (Username is Ok ... Password Mistmatch ...) ... + case 106: + // + this.uiDisabled = true; + this.showResetPassword = true; + break; + } + + // + // Apply Changes ... + this.detectChanges(); + } + //#endregion + + // + //#region UI Providers ... + //#endregion + + // + //#region UI Handlers ... + /** + * Handle Login ... + */ + async handleLogin() { + // + // Prevent Moving Forward when Context is Busy ... + if (this.loading || this.uiDisabled || isNullOrUndefined(this.model)) { + return; + } + + // + if (this.lock) { + this.lock = false; + } + + // + // Prepare User Select By ... + const model = { + ...this.model, + userSelectBy: this.model.userSelectBy.toLowerCase(), + }; + + // + // Prepare Task Requirements ... + const mTaskName = LoginTasks.Login; + const mTask = this.sharedService.authService.login(model); + + // + // Add or Update Task to Queue ... + await this.addOrUpdateTask({ + name: mTaskName, + priority: 0, + affectLoading: true, + task: mTask, + }); + + // + // Dispatch Task ... + await this.dispatchTask(mTaskName); + } + + /** + * Update Model ... + * + * @param state + */ + async handleModelStateChange(state: VModelState) { + // + // Update Model Fields ... + this.model.device = state.model.device; + this.model.language = state.model.language; + this.model.password = state.model.password; + this.model.userSelectBy = state.model.userSelectBy; + } + + /** + * Handling Lock State ... + * + * @param event + * @returns + */ + handleLockStateChanged(event: { id: string; state: boolean }) { + // + if (isNullOrUndefined(event)) { + return; + } + + // + this.lock = event.state; + } + //#endregion + + // + //#region Actions ... + //#endregion + + // + //#region Private Actions ... + // + //#region Task Providers ... + private getTaskNames() { + return [ + // + LoginTasks.Login, + LoginTasks.RequestResetPassword, + LoginTasks.ResendEmailVerificationLink, + ]; + } + + private async isPageTask(key: string | any) { + // + const pageTasks = this.getTaskNames(); + const mTaskKey = getTaskName(key); + const result = pageTasks.includes(mTaskKey); + + // + return result; + } + //#endregion + + // + //#region Task Handlers ... + private handleUserAuthenticated() { + // + const hasAfterLoginRedirectPage = !isNullOrUndefined( + this.config.afterLoginRedirectPage + ); + + // + const url = this.managerService.isContainQueryParam(XParam.ReturnUrl) + ? this.managerService.getQueryParamValOfType(XParam.ReturnUrl) + : hasAfterLoginRedirectPage + ? this.managerService.getPageRoute(this.config.afterLoginRedirectPage) + : this.managerService.getPageRoute(Pages.Home); + + // + const mSubscription = this.sharedService.state$ + .asObservable() + .pipe(filter((res) => !isNullOrUndefined(res) && res.isLoggedIn === true)) + // tslint:disable-next-line: deprecation + .subscribe(async () => { + // + this.runTimedOut(async () => { + // + if (this.lock) { + this.lock = false; + } + + // + await this.managerService.navigateByUrlReplace(url); + }); + + // + mSubscription.unsubscribe(); + }); + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\account\logout\logout.page.html + + + + + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\account\logout\logout.page.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\account\logout\logout.page.ts + +import { Pages } from 'src/app/config/page.config'; +import { XSideType } from 'x-framework-components'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; +import { VPageComponent } from 'src/app/views/v-page/v-page.component'; + +const LogoutKeys = { + LoggingOutID: 'logging_out', +}; + +@Component({ + selector: 'app-logout', + templateUrl: './logout.page.html', + styleUrls: ['./logout.page.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +// tslint:disable-next-line:component-class-suffix +export class LogoutPage extends VPageComponent { + // + //#region Props ... + hasSide = true; + toolbarShowSubTitle = true; + showToolbarEndSlot = false; + showToolbarContent = false; + titleRes = this.AppResourceIDs.logout_page_title; + toolbarSubTitle = this.resourceProvider(this.titleRes); + + /** + * use this for Menu always Visible ... + */ + toggleMenuWhen = ''; + sideType = XSideType.Overlay; + //#endregion + + // + //#region Constructor ... + //#endregion + + // + //#region LifeCycles ... + async afterViewInit() { + super.afterViewInit(); + + // + await this.handleLogoutAction(); + } + //#endregion + + // + //#region Private Actions ... + private async handleLogoutAction() { + // + await this.managerService.dialogService.presentLoading({ + id: LogoutKeys.LoggingOutID, + message: this.resourceProvider(this.AppResourceIDs.logging_out), + }); + + // + this.loading = true; + + // + await this.sharedService.authService.logout(); + this.managerService.settingsService.restart(); + + // + this.loading = false; + this.managerService.dialogService.loadingController.dismiss( + LogoutKeys.LoggingOutID + ); + + // + await this.managerService.navigateByPageReplace(Pages.Startup); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\account\phone-confirm\phone-confirm.page.html + + + + + + + + + + Phone Confirm ... + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\account\phone-confirm\phone-confirm.page.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\account\phone-confirm\phone-confirm.page.ts + +import { XSideType } from 'x-framework-components'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { VPageComponent } from 'src/app/views/v-page/v-page.component'; + +@Component({ + selector: 'app-phone-confirm', + templateUrl: './phone-confirm.page.html', + styleUrls: ['./phone-confirm.page.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class PhoneConfirmPage extends VPageComponent { + // + //#region Props ... + hasSide = true; + toolbarShowSubTitle = true; + showToolbarEndSlot = true; + showToolbarContent = false; + titleRes = this.AppResourceIDs.phone_confirm_page_title; + toolbarSubTitle = this.resourceProvider(this.titleRes); + + /** + * use this for Menu always Visible ... + */ + toggleMenuWhen = ''; + sideType = XSideType.Overlay; + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\account\profile\profile.page.html + + + + + + + + + + Profile ... + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\account\profile\profile.page.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\account\profile\profile.page.ts + +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { VPageComponent } from 'src/app/views/v-page/v-page.component'; + +@Component({ + selector: 'app-profile', + templateUrl: './profile.page.html', + styleUrls: ['./profile.page.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ProfilePage extends VPageComponent { + // + //#region Props ... + hasSide = true; + toolbarShowSubTitle = true; + showToolbarEndSlot = true; + showToolbarContent = false; + titleRes = this.AppResourceIDs.profile_page_title; + toolbarSubTitle = this.resourceProvider(this.titleRes); + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\account\register\register.page.html + + + + + + + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\account\register\register.page.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\account\register\register.page.ts + +import { XSideType } from 'x-framework-components'; +import { VPageComponent } from 'src/app/views/public-api'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; + +@Component({ + selector: 'app-register', + templateUrl: './register.page.html', + styleUrls: ['./register.page.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +// tslint:disable-next-line: component-class-suffix +export class RegisterPage extends VPageComponent { + // + //#region Props ... + hasSide = true; + toolbarShowSubTitle = true; + titleRes = this.AppResourceIDs.register_page_title; + toolbarSubTitle = this.resourceProvider(this.titleRes); + + /** + * use this for Menu always Visible ... + */ + toggleMenuWhen = ''; + sideType = XSideType.Overlay; + //#endregion + + // + //#region Constructor ... + //#endregion + + // + //#region LifeCycles ... + //#endregion + + // + //#region Constructor ... + //#endregion + + // + //#region Register Handlers ... + //#endregion + + // + //#region Task Handlers ... + //#endregion + + // + //#region UI Providers ... + //#endregion + + // + //#region UI Handlers ... + //#endregion + + // + //#region Actions ... + //#endregion + + // + //#region Private ... + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\account\reset-password\reset-password.page.html + + + + + + + + + + Reset Password ... + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\account\reset-password\reset-password.page.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\account\reset-password\reset-password.page.ts + +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { VPageComponent } from 'src/app/views/v-page/v-page.component'; +import { XSideType } from 'x-framework-components'; + +@Component({ + selector: 'app-reset-password', + templateUrl: './reset-password.page.html', + styleUrls: ['./reset-password.page.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ResetPasswordPage extends VPageComponent { + // + //#region Props ... + hasSide = true; + toolbarShowSubTitle = true; + showToolbarEndSlot = true; + showToolbarContent = false; + titleRes = this.AppResourceIDs.reset_password_page_title; + toolbarSubTitle = this.resourceProvider(this.titleRes); + + /** + * use this for Menu always Visible ... + */ + toggleMenuWhen = ''; + sideType = XSideType.Overlay; + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\account\search-profiles\search-profiles.page.html + + + + + + + + + + Search Profiles ... + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\account\search-profiles\search-profiles.page.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\account\search-profiles\search-profiles.page.ts + +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { VPageComponent } from 'src/app/views/v-page/v-page.component'; + +@Component({ + selector: 'app-search-profiles', + templateUrl: './search-profiles.page.html', + styleUrls: ['./search-profiles.page.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class SearchProfilesPage extends VPageComponent { + // + //#region Props ... + hasSide = true; + toolbarShowSubTitle = true; + showToolbarEndSlot = true; + showToolbarContent = false; + titleRes = this.AppResourceIDs.search_profiles_page_title; + toolbarSubTitle = this.resourceProvider(this.titleRes); + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\error\error.page.html + + + + + + + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\error\error.page.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\error\error.page.ts + +import { Pages } from 'src/app/config/page.config'; +import { XButtonType } from 'x-framework-components'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; +import { VPageComponent } from 'src/app/views/v-page/v-page.component'; +import { toArray, XOneOrManyType, XParam } from 'x-framework-core'; + +@Component({ + selector: 'app-error', + templateUrl: './error.page.html', + styleUrls: ['./error.page.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +// tslint:disable-next-line: component-class-suffix +export class ErrorPage extends VPageComponent { + // + //#region Props ... + hasSide = false; + toolbarHasBack = true; + toolbarShowBack = true; + showToolbarEndSlot = false; + showToolbarContent = false; + toolbarShowSubTitle = true; + titleRes = this.AppResourceIDs.error_page_title; + toolbarSubTitle = this.resourceProvider(this.titleRes); + + // + errors: XOneOrManyType; + returnUrl = this.managerService.getPageRoute(Pages.Startup); + + // + toolbarBackHandler = () => { + this.managerService.navigateByUrlReplace(this.returnUrl); + }; + + // + //#region Private/ReadOnly ... + readonly ButtonTypes = Object.assign({}, XButtonType); + //#endregion + //#endregion + + // + //#region Constructor ... + //#endregion + + // + //#region LifeCycle ... + parseParams(): void { + super.parseParams(); + + // + this.errors = [ + ...toArray( + this.managerService.getStateValOfType>( + XParam.Errors + ) + ), + ]; + + // + const isContainsReturnUrl = this.managerService.isContainState( + XParam.ReturnUrl + ); + if (isContainsReturnUrl) { + this.returnUrl = this.managerService.getStateValOfType( + XParam.ReturnUrl + ); + } + + // + if (this.hasChild(this.errors)) { + this.detectChanges(); + } + } + //#endregion + + // + //#region Action Handlers ... + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\files\files-page.module.ts + +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; +import { AuthGuard } from 'x-framework-identity-sdk'; +import { XCanDeactivateGuard } from 'x-framework-core'; +import { BaseRoutes } from 'src/app/config/page.config'; +import { ViewsModule } from 'src/app/views/views.module'; +import { FileRoutes } from 'src/app/views/files/v-file.configs'; +import { FilesExplorerPage } from './files-explorer/files-explorer.page'; +import { FilesManagementPage } from './files-management/files-management.page'; + +@NgModule({ + declarations: [FilesExplorerPage, FilesManagementPage], + imports: [ + ViewsModule, + RouterModule.forChild([ + { + pathMatch: 'full', + path: BaseRoutes.Default, + redirectTo: FileRoutes.FilesExplorer, + }, + // + // Explorer ... + { + canActivate: [AuthGuard], + component: FilesExplorerPage, + canDeactivate: [XCanDeactivateGuard], + path: FileRoutes.FilesExplorer, + }, + // + // Management ... + { + canActivate: [AuthGuard], + component: FilesManagementPage, + canDeactivate: [XCanDeactivateGuard], + path: FileRoutes.FilesManagement, + }, + { + pathMatch: 'full', + path: BaseRoutes.Unknown, + redirectTo: FileRoutes.Default, + }, + ]), + ], + exports: [ViewsModule, RouterModule, FilesExplorerPage, FilesManagementPage], +}) +export class FilesPageModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\files\files-base\files-base.provider.page.html + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + {{ getOwnerFullName(value) }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\files\files-base\files-base.provider.page.scss + +.v-item, +.v-list-item, +.v-input-item, +.v-detail-item { + width: 100%; +} + +.v-media-thumbnail-present { + margin: { + left: auto; + right: auto; + } +} + +:host ::ng-deep .v-media-thumbnail-present { + margin: { + left: auto !important; + right: auto !important; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\files\files-base\files-base.provider.page.ts + +import { + Component, + ViewChild, + TemplateRef, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + XRef, + nameof, + XFileType, + isFunction, + getFieldNames, + isNullOrUndefined, + isNullOrEmptyString, + XResourceIdentifier, + getComponentSelector, +} from 'x-framework-core'; +import { + XFileDto, + XPersonDto, + XReferenceDto, + defaultFileDto, + fileEntityToDto, +} from 'x-saherelm-api-sdk'; +import { + XListItem, + XSlotName, + XButtonType, + XListItemOptionSlot, + XActionBarActionFiredModel, + defaultActionBarUploadItem, +} from 'x-framework-components'; +import { Observable, Subject } from 'rxjs'; +import { + VBaseDtoAction, + VModelFieldTypes, + VBaseDtoActionModel, + VBaseDtoModelAction, + VBaseDtoPresentType, + VModelFieldDescriptor, + VBaseDtoModelFiredActionModel, +} from 'src/app/views/v-dto/v-dto.typings'; +import { + defaultEditFabButton, + defaultOptionsFabItem, + defaultRemoveFabButton, +} from 'src/app/views/constants/fab.defaults'; +import { + toSimpleFabItems, + XSimpleFabButton, +} from 'src/app/views/typings/simple.fab.typings'; +import { + defaultEditSlideOption, + defaultRemoveSlideOption, + defaultDetailsSlideOption, +} from 'src/app/views/constants/slide.options.defaults'; +import { + processFilledItems, + VQueryPresenterTabs, + VQueryPresenterAction, + VQueryPresenterActionModel, + VQueryPresenterActionIdentifier, + VQueryFilledItemsProcessingResult, +} from 'src/app/views/v-query-presenter/v-query-presenter.typings'; +import { XQueryDto, XQueryResultDto } from 'x-framework-identity-sdk'; +import { sendAction } from 'src/app/views/tools/action-provider.tools'; +import { getFileTypeResourceID } from 'src/app/views/files/v-file.tools'; +import { getPersonFullName } from 'src/app/views/v-person/v-person.tools'; +import { PersonPresentType } from 'src/app/views/v-person/v-person.typings'; +import { VFileComponent } from 'src/app/views/files/v-file/v-file.component'; +import { VFilePresentType } from 'src/app/views/files/v-file/v-file.typings'; +import { VDtoCommonProperties } from 'src/app/views/typings/dto.common.typings'; +import { VMediaPresentType } from 'src/app/views/files/v-media/v-media.typings'; +import { VPagePresenter } from 'src/app/views/v-page-presenter/v-page-presenter.component'; +import { getQuerySelectorOfSpecifiedAttribute } from 'src/app/views/typings/common.typings'; +import { XSimpleListItemSlideOption } from 'src/app/views/typings/simple.list-item.typings'; +import { VQueryPresenterBasePage } from 'src/app/views/v-query-presenter/v-query-presenter.base.page'; + +@Component({ + template: '', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export abstract class FilesBaseProviderPage extends VQueryPresenterBasePage { + // + //#region Props ... + toolbarShowTitle = true; + toolbarShowSubTitle = true; + titleRes = this.ResourceIDs.app_name; + toolbarSubTitle = this.resourceProvider( + this.AppResourceIDs.files_management_page_title, + ); + toolbarTitle = this.resourceProvider(this.titleRes); + + // + @ViewChild('pagePresenter') + pagePresenter!: VPagePresenter; + + // + @ViewChild('fileNameRef') + fileNameRef!: TemplateRef; + + // + readonly ButtonTypes = Object.assign(XButtonType, {}); + readonly ModelFieldNames = getFieldNames(defaultFileDto); + readonly ModelFieldTypes = Object.assign(VModelFieldTypes); + readonly FilePresentTypes = Object.assign(VFilePresentType, {}); + readonly MediaPresentTypes = Object.assign(VMediaPresentType, {}); + readonly PersonPresentTypes = Object.assign(PersonPresentType, {}); + readonly BasePresentTypes = Object.assign(VBaseDtoPresentType, {}); + + // + // Prepare Commonly Used Properties for Dto Actions ... + // we can Override this for other Purposes ... + commonViewsProperties: VDtoCommonProperties = { + isInPage: false, + wrapWithCard: false, + labelColor: undefined, + valueColor: undefined, + fieldFormButtonPosition: undefined, + fieldFormIcon: undefined, + fieldFormIconColor: undefined, + fieldFormApplyIcon: undefined, + fieldFormApplyIconColor: undefined, + fieldFormCancelIcon: undefined, + fieldFormCancelIconColor: undefined, + removeCardMargin: undefined, + cardForegroundColor: undefined, + cardCssClass: undefined, + cardColor: undefined, + cardActionProvider: undefined, + formAppearance: undefined, + formActionProvider: undefined, + formRegularColor: undefined, + formAccentColor: undefined, + formHintColor: undefined, + formActiveColor: undefined, + formValidColor: undefined, + formInvalidColor: undefined, + formPendingColor: undefined, + formFillColor: undefined, + formDisabledColor: undefined, + formDisabledFillColor: undefined, + showFormCancelButton: undefined, + formCancelButtonResource: undefined, + formCancelButtonColor: undefined, + formResetButtonResource: undefined, + formResetButtonColor: undefined, + formActionButtonAddResource: undefined, + formActionButtonUpdateResource: undefined, + formActionButtonColor: undefined, + }; + + // + //#region Query Presenter Props ... + modelIdentifier: string = nameof('id'); + listTabCardTitle: XResourceIdentifier = this.AppResourceIDs.files; + + /** + * Presenter Component Action Provider ... + */ + queryPresenterActionProvider = new Subject(); + //#endregion + //#endregion + + /** + * Specified How to get Specific Item using Model Identifier ... + */ + abstract getModelTask(identifier: string): Observable; + + /** + * Specified How to Query Items ... + */ + abstract queryModelTask(query: XQueryDto): Observable>; + + /** + * Param Recieved Action ... + */ + paramAction: string = undefined; + + // + //#region LifeCycles ... + async afterViewInit(): Promise { + await super.afterViewInit(); + + // + this.applyTakeUntilWrapper( + this.managerService.settingsService.isMobile$, + ).subscribe((res) => { + // + if (!isNullOrUndefined(this.activeTab)) { + // + switch (this.activeTab) { + // + case VQueryPresenterTabs.List: + sendAction({ + provider: this.listItemActionProvider, + action: VBaseDtoAction.Refresh, + }); + break; + } + } + }); + + // + this.handleAttachTemplates(); + await this.handleConnectPushService(); + } + //#endregion + + // + //#region Providers ... + //#endregion + + // + //#region List ... + /** + * Provides List Item Template ... + */ + @ViewChild('itemTemplate') + itemTemplate!: TemplateRef; + + /** + * Provides List Item Template ... + */ + @ViewChild('listItemTemplate') + listItemTemplate!: TemplateRef; + + // + //#region Owner Custom Template for List Item View ... + private LIST_OWNER_REF: TemplateRef; + + @ViewChild('listOwnerRef') + set listOwnerRef(v: TemplateRef) { + // + this.LIST_OWNER_REF = v; + this.applyListOwnerRef(); + } + get listOwnerRef() { + return this.LIST_OWNER_REF; + } + //#endregion + + /** + * Query Object for Fetching Data ... + */ + query: XQueryDto = { + page: 1, + filter: '', + pageSize: this.config.defaultPageSize, + sortBy: nameof('uploadedOn'), + }; + + /** + * a Flag for Detect Has Query result or not ... + */ + hasQueryResultError: boolean = false; + + /** + * Fetching Data Result Model ... + */ + dtoQueryResult: XQueryResultDto = undefined; + + /** + * Specified Action Provider ... + */ + listItemActionProvider = new Subject(); + + /** + * Specified View Properties ... + */ + listItemViewProperties: VDtoCommonProperties = { + ...this.commonViewsProperties, + }; + + /** + * Specified Field Descriptors for List Items ... + */ + listItemViewFieldDescriptors: VModelFieldDescriptor[] = [ + // + // Type ... + { + hide: false, + hideLabel: false, + allowFieldForm: false, + value: defaultFileDto.type, + label: this.ResourceIDs.type, + key: this.ModelFieldNames.type, + type: this.ModelFieldTypes.Number, + valueProvider: (value) => { + return this.applyLocale( + this.resourceProvider(getFileTypeResourceID(value)), + ); + }, + } as VModelFieldDescriptor, + // + // FileName ... + { + hide: false, + hideLabel: false, + allowFieldForm: false, + value: defaultFileDto.fileName, + type: this.ModelFieldTypes.String, + key: this.ModelFieldNames.fileName, + label: this.AppResourceIDs.file_name, + valueProvider: (value) => { + return this.applyLocale(value); + }, + } as VModelFieldDescriptor, + // + // Name ... + { + hide: false, + hideLabel: false, + allowFieldForm: false, + value: defaultFileDto.name, + key: this.ModelFieldNames.name, + label: this.AppResourceIDs.name, + type: this.ModelFieldTypes.String, + } as VModelFieldDescriptor, + // + // UploadedOn ... + { + hide: false, + hideLabel: false, + allowFieldForm: false, + type: this.ModelFieldTypes.Date, + value: defaultFileDto.uploadedOn, + key: this.ModelFieldNames.uploadedOn, + label: this.AppResourceIDs.uploaded_on, + valueProvider: (value: Date) => { + // + if (isNullOrUndefined(value)) { + return ''; + } + + // + return this.applyDefaultDate(value); + }, + } as VModelFieldDescriptor, + // + // Path ... + { + hide: false, + hideLabel: true, + allowFieldForm: false, + hasCustomTemplate: true, + value: defaultFileDto.path, + key: this.ModelFieldNames.path, + label: this.AppResourceIDs.path, + type: this.ModelFieldTypes.String, + } as VModelFieldDescriptor, + // + // Owner ... + { + hide: false, + hideLabel: false, + hasCustomTemplate: true, + showContentBelowLabel: true, + value: defaultFileDto.owner, + key: this.ModelFieldNames.owner, + label: this.AppResourceIDs.author, + type: this.ModelFieldTypes.Object, + } as VModelFieldDescriptor, + // // + // // References ... + // { + // hide: false, + // hideLabel: false, + // allowFieldForm: false, + // value: defaultFileDto.references, + // type: this.ModelFieldTypes.Array, + // key: this.ModelFieldNames.references, + // label: this.AppResourceIDs.references, + // hasCustomTemplate: true, + // showContentBelowLabel: true, + // pushArrayChildsOnceToTemplate: true, + // } as VModelFieldDescriptor>>, + ]; + + /** + * Get File Owner Full Name ... + * + * @param model + * @returns + */ + getOwnerFullName(model: XPersonDto) { + return getPersonFullName(model); + } + + /** + * Hold each Items Provided Actions based on Identifier ... + */ + itemsActions = new Map>>(); + + /** + * Prepare Item Actions based on Slot ... + */ + getSlottedItemActions(slot: XSlotName, model: XFileDto) { + // + let result: Array = []; + + // + if ( + isNullOrUndefined(model) || + isNullOrEmptyString(slot) || + isNullOrEmptyString(model.id) || + isNullOrUndefined(this.itemsActions) || + !this.itemsActions.has(model.id) + ) { + return result; + } + + // + let _slot: XListItemOptionSlot = + slot === XSlotName.START || slot === XSlotName.TOP ? 'start' : 'end'; + + // + result = this.itemsActions + .get(model.id) + .value.filter((a) => a.slot === _slot); + + // + return result; + } + + /** + * Handle Non As List Item's Action ... + * + * @param id + * @param data + */ + async handleItemAction(id: string, data: XFileDto) { + // + // Validate ... + if ( + this.loading || + this.uiDisabled || + isNullOrEmptyString(id) || + isNullOrUndefined(data) + ) { + return; + } + + // + await this.doItemActions({ id, data }); + } + + /** + * Apply List Owner Template to Descriptors ... + * + * @returns + */ + applyListOwnerRef() { + // + if (isNullOrUndefined(this.LIST_OWNER_REF)) { + return; + } + + // + this.listItemViewFieldDescriptors = this.listItemViewFieldDescriptors.map( + (fd) => { + // + return { + ...fd, + template: + fd.key === this.ModelFieldNames.owner + ? this.LIST_OWNER_REF + : fd.template, + }; + }, + ); + } + + /** + * Handle Retrieve Data based on XQuery ... + * + * @param query + * @returns + */ + async handleGetQueryResult( + query: XQueryDto, + ): Promise> { + // + let result: XQueryResultDto = undefined; + + // + // Start Loading ... + this.hasQueryResultError = false; + await this.setLoadingState(true); + + // + // Try to Prepare Result ... + try { + // + // Retrieve File Dto Result ... + this.dtoQueryResult = await this.getValueAsync( + this.queryModelTask(query), + ); + + // + // Prepare Result ... + result = { + ...result, + page: this.dtoQueryResult.page, + pageSize: this.dtoQueryResult.pageSize, + totalItems: this.dtoQueryResult.totalItems, + totalPages: this.dtoQueryResult.totalPages, + totalFilteredItems: this.dtoQueryResult.totalFilteredItems, + totalFilteredPages: this.dtoQueryResult.totalFilteredPages, + }; + + // + // Converts XFileDto to XFileDto ... + result.items = this.dtoQueryResult.items.map((i) => i); + + // + // Remove Scrolling Info ... + if ( + !isNullOrUndefined(this.query) && + this.query.filter !== query.filter + ) { + // + this.removeScrollingInfo(); + this.infinityScrollDisabled = true; + } + + // + this.query = query; + } catch { + // + this.hasQueryResultError = true; + await this.handleCustomError('QueryFilesError'); + return result; + } + + // + // Stop Loading ... + await this.setLoadingState(false); + + // + return result; + } + + /** + * Get Specified Model Based on Identifier ... + * + * @param identifier + * @param applyState + * @returns + */ + async handleGetSpecifiedModel(identifier: string, applyState = false) { + // + if (isNullOrEmptyString(identifier)) { + return; + } + + // + let response: any = undefined; + try { + // + if (applyState) { + await this.setLoadingState(true); + } + + // + response = await this.getValueAsync(this.getModelTask(identifier)); + + // + } catch { + // + await this.handleCustomError('GetFileError'); + return; + } + + // + if (applyState) { + await this.setLoadingState(false); + } + + // + return response; + } + + /** + * Provide List Actions for Specific Model ... + * + * @param model + * @returns + */ + async prepareListActions(model: XFileDto) { + // + const userId = await this.getValueAsync(this.sharedService.userId$); + const isAdmin = await this.getValueAsync(this.sharedService.isAdmin$); + + // + return this.provideItemOptions(userId, isAdmin, model); + } + + /** + * Validate List Items ... + * + * @param items + * @returns + */ + async validateAndPrepareListItems( + items: Array>, + ): Promise>> { + // + let result: Array> = []; + + // + // Preparing Slide Options ... + result = await Promise.all([ + ...items.map(async (i) => { + return { + ...i, + slideOptions: [...(await this.prepareListActions(i.data))], + }; + }), + ]); + + // + return result; + } + + /** + * Prepare Result for Non As List Presenting Items ... + * + * @param current + * @param recieved + */ + async requestFillingItems( + exists: Array, + recieved: Array, + ): Promise> { + // + let result: VQueryFilledItemsProcessingResult = + processFilledItems(exists, recieved, 'id'); + + // + // Filling Actions ... + if (!isNullOrUndefined(result)) { + // + // News Items Actions ... + if (!isNullOrUndefined(result.news) && this.hasChild(result.news)) { + // + result.news.forEach(async (i) => { + // + const actions = await this.prepareListActions(i); + this.itemsActions.set(i.id, { value: actions }); + }); + } + + // + // Changed Items Actions ... + if (!isNullOrUndefined(result.changed) && this.hasChild(result.changed)) { + // + result.changed.forEach(async (i) => { + // + if (this.itemsActions.has(i.identifier)) { + // + const actions = await this.prepareListActions(i.data); + this.itemsActions.set(i.identifier, { value: actions }); + } + }); + } + } + + // + return result; + } + + /** + * When Non as List Items Filled, this method call for Change Detection ... + * + * @param ids + */ + async handleRefreshViews(ids: Array): Promise { + await super.handleRefreshViews(ids); + + // + if (isNullOrUndefined(ids) || !this.hasChild(ids)) { + return; + } + + // + const selector = getComponentSelector(VFileComponent); + ids.forEach((id) => { + // + const iSelector = `${selector}[${nameof( + 'id', + )}="${id}"][${nameof('presentType')}="${ + VBaseDtoPresentType.AsView + }"]`; + this.waitForElement(iSelector).then((el) => { + this.listItemActionProvider.next({ action: VBaseDtoAction.Refresh }); + }); + }); + } + //#endregion + + // + //#region Add ... + /** + * Provides Template for Adding Items ... + */ + @ViewChild('addItemTemplate') + addItemTemplate!: TemplateRef; + + /** + * a Model for Providing to Add ... + */ + addModel: XFileDto = { + ...defaultFileDto, + }; + + /** + * Specified Action Provider ... + */ + addItemActionProvider = new Subject(); + + /** + * Specify View Properties for Add ... + */ + addModelViewProperties: VDtoCommonProperties = { + ...this.commonViewsProperties, + isInPage: true, + wrapWithCard: true, + }; + + /** + * Describe Add Model Fields ... + */ + addModelViewFieldDescriptors: VModelFieldDescriptor[] = [ + // + // Type ... + { + hide: false, + hideLabel: false, + allowFieldForm: false, + value: defaultFileDto.type, + label: this.ResourceIDs.type, + key: this.ModelFieldNames.type, + type: this.ModelFieldTypes.Number, + valueProvider: (value) => { + return this.applyLocale( + this.resourceProvider(getFileTypeResourceID(value)), + ); + }, + } as VModelFieldDescriptor, + // + // FileName ... + { + hide: false, + hideLabel: false, + allowFieldForm: false, + value: defaultFileDto.fileName, + type: this.ModelFieldTypes.String, + key: this.ModelFieldNames.fileName, + label: this.AppResourceIDs.file_name, + valueProvider: (value) => { + return this.applyLocale(value); + }, + } as VModelFieldDescriptor, + // + // Name ... + { + hide: false, + hideLabel: false, + allowFieldForm: false, + value: defaultFileDto.name, + key: this.ModelFieldNames.name, + label: this.AppResourceIDs.name, + type: this.ModelFieldTypes.String, + } as VModelFieldDescriptor, + // + // UploadedOn ... + { + hide: false, + hideLabel: false, + allowFieldForm: false, + type: this.ModelFieldTypes.Date, + value: defaultFileDto.uploadedOn, + key: this.ModelFieldNames.uploadedOn, + label: this.AppResourceIDs.uploaded_on, + valueProvider: (value: Date) => { + // + if (isNullOrUndefined(value)) { + return ''; + } + + // + return this.applyDefaultDate(value); + }, + } as VModelFieldDescriptor, + // + // Path ... + { + hide: false, + hideLabel: true, + allowFieldForm: false, + hasCustomTemplate: true, + value: defaultFileDto.path, + key: this.ModelFieldNames.path, + label: this.AppResourceIDs.path, + type: this.ModelFieldTypes.String, + } as VModelFieldDescriptor, + // + // Owner ... + { + hide: false, + hideLabel: false, + hasCustomTemplate: true, + showContentBelowLabel: true, + value: defaultFileDto.owner, + key: this.ModelFieldNames.owner, + label: this.AppResourceIDs.author, + type: this.ModelFieldTypes.Object, + } as VModelFieldDescriptor, + // // + // // References ... + // { + // hide: false, + // hideLabel: false, + // allowFieldForm: false, + // value: defaultFileDto.references, + // type: this.ModelFieldTypes.Array, + // key: this.ModelFieldNames.references, + // label: this.AppResourceIDs.references, + // hasCustomTemplate: true, + // showContentBelowLabel: true, + // pushArrayChildsOnceToTemplate: true, + // } as VModelFieldDescriptor>>, + ]; + + /** + * Handle Locking Page when a Form Lock Content ... + * + * @param event + */ + handleAddViewLockNotifier(event: { id: string; state: boolean }) { + this.lock = event.state; + } + + /** + * Handle Actiond Fired form FileUpload ... + * + * @param event + * @returns + */ + async handleFileUploadActionFired(event: XActionBarActionFiredModel) { + // + // Validate ... + if (isNullOrUndefined(event) || isNullOrEmptyString(event.action)) { + return; + } + + // + // Upload ... + if (event.action === defaultActionBarUploadItem.id) { + // + await this.handleAddModel({ ...defaultFileDto }); + } + } + + /** + * Holding File(s) for Upload ... + */ + filesForUpload: Array = []; + handleFileUploadFilesChanged(event: Array) { + // + // Update Files For Upload List ... + if (isNullOrUndefined(event)) { + this.filesForUpload = []; + } else { + this.filesForUpload = [...event]; + } + } + + /** + * Handle Cancel Add Form View ... + * + * @param event + */ + async handleAddViewActionFired(event: VBaseDtoModelFiredActionModel) { + // + // Cancel Form ... + if (event.action === VBaseDtoModelAction.Cancel) { + // + // Since Cancel only Fired when Lock Breaked, here we do not need to handle it ... + this.sendQueryPresenterAction( + VQueryPresenterAction.ChangeTab, + this.prevTab || VQueryPresenterTabs.List, + ); + } + // + // Reset Form ... + else if (event.action === VBaseDtoModelAction.Reset) { + } + // + // Add Or Update Form ... + else if (event.action === VBaseDtoModelAction.AddOrUpdate) { + // + const pModel: Required = { + ...event.payload.model, + } as Required; + await this.handleAddModel(pModel); + } + } + + /** + * Do Add Model ... + * + * @param model + * @returns + */ + async handleAddModel(model: XFileDto) { + // + if (isNullOrUndefined(model) || !this.hasChild(this.filesForUpload)) { + return; + } + + // + await this.setLoadingState(true); + + // + let response: Array = undefined; + try { + // + // Retrieve Connection ID ... + const connectionId = + this.sharedService.saherElmAPIService.files.fileEntityPushService + .connectionId; + + // + // Issue Task ... + response = await this.getValueAsync( + this.sharedService.saherElmAPIService.files.fileProviderService.upload( + [...this.filesForUpload], // Model ... + connectionId, // Connection Id ... + ), + ); + } catch (ex) { + // + await this.handleCustomError('AddModelError'); + return; + } + + // + this.lock = false; + this.filesForUpload = []; + this.addModel = undefined; + this.showSuccess(); + await this.setLoadingState(false); + this.sendQueryPresenterAction(VQueryPresenterAction.ResetQuery, true); + } + //#endregion + + // + //#region Update ... + /** + * Provides Template for Update Items ... + */ + @ViewChild('updateItemTemplate') + updateItemTemplate!: TemplateRef; + + /** + * a Model for Providing to Update ... + */ + updateModel: XFileDto = undefined; + + /** + * Specified Action Provider ... + */ + updateItemActionProvider = new Subject(); + + /** + * Specify View Properties for Update ... + */ + updateModelViewProperties: VDtoCommonProperties = { + ...this.commonViewsProperties, + isInPage: true, + wrapWithCard: true, + }; + + /** + * Describe Update Model Fields ... + */ + updateModelViewFieldDescriptors: VModelFieldDescriptor[] = [ + // + // Type ... + { + hide: false, + hideLabel: false, + allowFieldForm: false, + value: defaultFileDto.type, + label: this.ResourceIDs.type, + key: this.ModelFieldNames.type, + type: this.ModelFieldTypes.Number, + valueProvider: (value) => { + return this.applyLocale( + this.resourceProvider(getFileTypeResourceID(value)), + ); + }, + } as VModelFieldDescriptor, + // + // FileName ... + { + hide: false, + hideLabel: false, + allowFieldForm: false, + value: defaultFileDto.fileName, + type: this.ModelFieldTypes.String, + key: this.ModelFieldNames.fileName, + label: this.AppResourceIDs.file_name, + valueProvider: (value) => { + return this.applyLocale(value); + }, + } as VModelFieldDescriptor, + // + // Name ... + { + hide: false, + hideLabel: false, + allowFieldForm: false, + value: defaultFileDto.name, + key: this.ModelFieldNames.name, + label: this.AppResourceIDs.name, + type: this.ModelFieldTypes.String, + } as VModelFieldDescriptor, + // + // UploadedOn ... + { + hide: false, + hideLabel: false, + allowFieldForm: false, + type: this.ModelFieldTypes.Date, + value: defaultFileDto.uploadedOn, + key: this.ModelFieldNames.uploadedOn, + label: this.AppResourceIDs.uploaded_on, + valueProvider: (value: Date) => { + // + if (isNullOrUndefined(value)) { + return ''; + } + + // + return this.applyDefaultDate(value); + }, + } as VModelFieldDescriptor, + // + // Path ... + { + hide: false, + hideLabel: true, + allowFieldForm: false, + hasCustomTemplate: true, + value: defaultFileDto.path, + key: this.ModelFieldNames.path, + label: this.AppResourceIDs.path, + type: this.ModelFieldTypes.String, + } as VModelFieldDescriptor, + // + // Owner ... + { + hide: false, + hideLabel: false, + hasCustomTemplate: true, + showContentBelowLabel: true, + value: defaultFileDto.owner, + key: this.ModelFieldNames.owner, + label: this.AppResourceIDs.author, + type: this.ModelFieldTypes.Object, + } as VModelFieldDescriptor, + // // + // // References ... + // { + // hide: false, + // hideLabel: false, + // allowFieldForm: false, + // value: defaultFileDto.references, + // type: this.ModelFieldTypes.Array, + // key: this.ModelFieldNames.references, + // label: this.AppResourceIDs.references, + // hasCustomTemplate: true, + // showContentBelowLabel: true, + // pushArrayChildsOnceToTemplate: true, + // } as VModelFieldDescriptor>>, + ]; + + /** + * Handle Locking Page when a Form Lock Content ... + * + * @param event + */ + handleUpdateViewLockNotifier(event: { id: string; state: boolean }) { + this.lock = event.state; + } + + /** + * Tags Actions Provider for Details Section ... + * + * @param t + * @returns + */ + detailsTagsActionsProvider: (t: string) => Array = + (t) => { + return []; + }; + + /** + * Files Actions Provider for Details Section ... + * + * @param f + * @returns + */ + detailsFilesActionsProvider: ( + f: XFileDto | File, + ) => Array = (f) => { + return []; + }; + + /** + * Handle Cancel Update Form View ... + * + * @param event + */ + async handleUpdateViewActionFired(event: VBaseDtoModelFiredActionModel) { + // + // Cancel Form ... + if (event.action === VBaseDtoModelAction.Cancel) { + // + // Since Cancel only Fired when Lock Breaked, here we do not need to handle it ... + this.sendQueryPresenterAction( + VQueryPresenterAction.ChangeTab, + this.prevTab || VQueryPresenterTabs.List, + ); + } + // + // Reset Form ... + else if (event.action === VBaseDtoModelAction.Reset) { + } + // + // Add Or Update Form ... + else if (event.action === VBaseDtoModelAction.AddOrUpdate) { + // + const pModel: Required = { + ...event.payload.model, + } as Required; + await this.handleUpdateModel(pModel); + } + } + + /** + * Do Update Model ... + * + * @param model + * @returns + */ + async handleUpdateModel(model: XFileDto) { + // + const prevTab = this.prevTab; + + // + if (isNullOrUndefined(model)) { + return; + } + + // + await this.setLoadingState(true); + + // + let response: XFileDto = undefined; + try { + // + // Prepare Dto Model to Add ... + const dto = + !isNullOrUndefined(this.dtoQueryResult) && + !isNullOrUndefined(this.dtoQueryResult.items) && + this.hasChild(this.dtoQueryResult.items) + ? this.dtoQueryResult.items.find((i) => i.id === model.id) + : undefined; + if (isNullOrUndefined(dto)) { + throw 'UpdateModelError'; + } + + // + // Preparing Update Model ... + const dtoModel = { ...model }; + + // + // Retrieve Connection ID ... + const connectionId = + this.sharedService.saherElmAPIService.files.fileEntityPushService + .connectionId; + + // + // Filling Owner ID ... + dtoModel.ownerId = await this.getValueAsync(this.sharedService.userId$); + + // + // Issue Task ... + // response = await this.getValueAsync( + // this.sharedService.saherElmAPIService.files.fileProviderService.update( + // dtoModel.id, + // dtoModel, + // connectionId + // ) + // ); + } catch (ex) { + // + await this.handleCustomError('UpdateModelError'); + return; + } + + // + if (isNullOrUndefined(response)) { + // + await this.handleCustomError('UpdateModelError'); + return; + } + + // + this.lock = false; + this.updateModel = undefined; + await this.setLoadingState(false); + this.showSuccess(); + + // + this.prevTab = prevTab; + model = { ...response }; + const actions = await this.prepareListActions(model); + + // + if (this.prevTab === VQueryPresenterTabs.Details) { + // + this.detailsModel = { + ...model, + }; + } + + // + this.sendQueryPresenterAction(VQueryPresenterAction.UpdateModel, { + model, + actions, + key: nameof('id'), + }); + + // + // Check Handle Back ... + if ( + this.toolbarHasBack && + this.toolbarShowBack && + isFunction(this.toolbarBackHandler) + ) { + await this.toolbarBackHandler(); + } + } + //#endregion + + // + //#region Details ... + /** + * Provides Template for Details Items ... + */ + @ViewChild('detailsItemTemplate') + detailsItemTemplate!: TemplateRef; + + /** + * a Model for Providing to Details ... + */ + detailsModel: XFileDto = undefined; + + /** + * Specified Action Provider ... + */ + detailsItemActionProvider = new Subject(); + + /** + * Specify View Properties for Details ... + */ + detailsModelViewProperties: VDtoCommonProperties = { + ...this.commonViewsProperties, + isInPage: true, + wrapWithCard: true, + }; + + /** + * Describe Details Model Fields ... + */ + detailsModelViewFieldDescriptors: VModelFieldDescriptor[] = [ + // + // Type ... + { + hide: false, + hideLabel: false, + allowFieldForm: false, + value: defaultFileDto.type, + label: this.ResourceIDs.type, + key: this.ModelFieldNames.type, + type: this.ModelFieldTypes.Number, + valueProvider: (value) => { + return this.applyLocale( + this.resourceProvider(getFileTypeResourceID(value)), + ); + }, + } as VModelFieldDescriptor, + // + // FileName ... + { + hide: false, + hideLabel: false, + allowFieldForm: false, + value: defaultFileDto.fileName, + type: this.ModelFieldTypes.String, + key: this.ModelFieldNames.fileName, + label: this.AppResourceIDs.file_name, + valueProvider: (value) => { + return this.applyLocale(value); + }, + } as VModelFieldDescriptor, + // + // Name ... + { + hide: false, + hideLabel: false, + allowFieldForm: false, + value: defaultFileDto.name, + key: this.ModelFieldNames.name, + label: this.AppResourceIDs.name, + type: this.ModelFieldTypes.String, + } as VModelFieldDescriptor, + // + // UploadedOn ... + { + hide: false, + hideLabel: false, + allowFieldForm: false, + type: this.ModelFieldTypes.Date, + value: defaultFileDto.uploadedOn, + key: this.ModelFieldNames.uploadedOn, + label: this.AppResourceIDs.uploaded_on, + valueProvider: (value: Date) => { + // + if (isNullOrUndefined(value)) { + return ''; + } + + // + return this.applyDefaultDate(value); + }, + } as VModelFieldDescriptor, + // + // Path ... + { + hide: false, + hideLabel: true, + allowFieldForm: false, + hasCustomTemplate: true, + value: defaultFileDto.path, + key: this.ModelFieldNames.path, + label: this.AppResourceIDs.path, + type: this.ModelFieldTypes.String, + } as VModelFieldDescriptor, + // + // Owner ... + { + hide: false, + hideLabel: false, + hasCustomTemplate: true, + showContentBelowLabel: true, + value: defaultFileDto.owner, + key: this.ModelFieldNames.owner, + label: this.AppResourceIDs.author, + type: this.ModelFieldTypes.Object, + } as VModelFieldDescriptor, + // // + // // References ... + // { + // hide: false, + // hideLabel: false, + // allowFieldForm: false, + // value: defaultFileDto.references, + // type: this.ModelFieldTypes.Array, + // key: this.ModelFieldNames.references, + // label: this.AppResourceIDs.references, + // hasCustomTemplate: true, + // showContentBelowLabel: true, + // pushArrayChildsOnceToTemplate: true, + // } as VModelFieldDescriptor>>, + ]; + + /** + * Provides Card Title for Specified Model ... + * + * @param model + * @returns + */ + getModelCardTitle(model: XFileDto) { + // + // Since card title's are Rescource ID's ... + return this.applyLocale(`${model.fileName} `); + } + //#endregion + + // + //#region Handlers ... + /** + * Handle Errors ... + * + * @param error + */ + async handleCustomError( + error?: VQueryPresenterActionIdentifier, + ): Promise { + // + await this.setLoadingState(false); + + // + // TODO: Implement this ... + console.log('handle Custom Error: ', error); + } + + /** + * Override this for adding additional futures ... + * + * @param tab + */ + async handleStateAfterTabOpen(tab: number) { + await super.handleStateAfterTabOpen(tab); + + // + let selector: string = undefined; + let fabs: XSimpleFabButton[] = []; + let model: XFileDto = undefined; + + // + // Remove Edit From Fab Actions ... + let actions = await toSimpleFabItems(this.actions); + if (!isNullOrUndefined(actions) && this.hasChild(actions.childs)) { + actions = { + ...actions, + childs: [ + ...actions.childs.filter((ch) => ch.id !== defaultEditFabButton.id), + ], + }; + } + this.closeActions(); + this.actions = undefined; + + // + const userId = await this.getValueAsync(this.sharedService.userId$); + const isAdmin = await this.getValueAsync(this.sharedService.isAdmin$); + const isAgent = await this.getValueAsync(this.sharedService.isAgent$); + const isAdminAgent = isAdmin || isAgent; + + // + switch (tab) { + // + case VQueryPresenterTabs.List: + // + // if Show Items as List ... + if (this.showItemsAsList) { + // + sendAction({ + provider: this.listItemActionProvider, + action: VBaseDtoAction.SetProperties, + payload: { + props: { ...this.listItemViewProperties }, + }, + }); + } else { + // + // Call each Items Action Provider Separately ... + + // + // Prepare Non As List Items Properties ... + const properties: VDtoCommonProperties = { + ...this.listItemViewProperties, + isInPage: true, + wrapWithCard: true, + cardShowActions: true, // Since Each Items has Separate Actions ... + }; + + // + sendAction({ + provider: this.listItemActionProvider, + action: VBaseDtoAction.SetProperties, + payload: { + props: { ...properties }, + }, + }); + } + + // + break; + + // + case VQueryPresenterTabs.Add: + // + this.addModel = { + ...defaultFileDto, + }; + + // + sendAction({ + provider: this.addItemActionProvider, + action: VBaseDtoAction.SetProperties, + payload: { + props: { ...this.addModelViewProperties }, + }, + }); + break; + + // + case VQueryPresenterTabs.Update: + // + model = { ...this.updateModel }; + + // + sendAction({ + provider: this.updateItemActionProvider, + action: VBaseDtoAction.SetProperties, + payload: { + props: { ...this.updateModelViewProperties }, + }, + }); + break; + + // + case VQueryPresenterTabs.Details: + // + model = { ...this.detailsModel }; + + // + sendAction({ + provider: this.detailsItemActionProvider, + action: VBaseDtoAction.SetProperties, + payload: { + props: { ...this.detailsModelViewProperties }, + }, + }); + + // + // Adding Custom Actions ... + + // + let isOwner = this.detailsModel.ownerId === userId; + + // + // Actions Preparing ... + actions = { + ...(!isNullOrUndefined(actions) ? actions : defaultOptionsFabItem), + childs: [ + ...(!isNullOrUndefined(actions) && this.hasChild(actions.childs) + ? actions.childs + : []), + ...fabs, + ], + }; + + // + // Issue Action if Waiting for Details ... + if (!isNullOrEmptyString(this.paramAction)) { + // + const action = this.paramAction; + this.paramAction = undefined; + + // + await this.doItemActions({ + id: action, + data: { ...this.detailsModel }, + }); + } + break; + } + + // + // Here can Handle Actions Permissions and States ... + if (!isNullOrUndefined(actions)) { + // + // Only if model Exists ... + if (!isNullOrUndefined(model)) { + // + // Retrieve Requirements ... + + // + const isOwner = model.ownerId === userId; + + // + const canRemove = isOwner || isAdmin; + + // + // Remove ... + if ( + actions.id === defaultRemoveFabButton.id || + (!isNullOrUndefined(actions.childs) && + this.hasChild(actions.childs) && + actions.childs.findIndex( + (a) => a.id === defaultRemoveFabButton.id, + )) >= 0 + ) { + // + if (actions.id === defaultRemoveFabButton.id) { + actions.disabled = + !canRemove || actions.disabled || this.loading || this.uiDisabled; + } else { + // + actions.childs = [ + ...actions.childs.map((ch) => { + return { + ...ch, + disabled: + ch.id === defaultRemoveFabButton.id + ? !canRemove || + ch.disabled || + this.loading || + this.uiDisabled + : ch.disabled, + }; + }), + ]; + } + } + } + + // + // Update Actions ... + this.actions = { + ...actions, + }; + } + } + + /** + * Override this for adding additional futures ... + * + * @param from + * @param to + */ + async handleStateBeforeTabOpen(from: number, to: number) { + await super.handleStateBeforeTabOpen(from, to); + + // + this.closeActions(); + + // + switch (to) { + // + case VQueryPresenterTabs.List: + // + this.addModel = undefined; + this.updateModel = undefined; + this.detailsModel = undefined; + break; + + // + case VQueryPresenterTabs.Add: + // + this.updateModel = undefined; + this.detailsModel = undefined; + break; + + // + case VQueryPresenterTabs.Update: + // + this.addModel = undefined; + this.detailsModel = undefined; + break; + + // + case VQueryPresenterTabs.Details: + // + if ( + isNullOrUndefined(this.detailsModel) && + !isNullOrUndefined(this.updateModel) && + from === VQueryPresenterTabs.Update + ) { + // + this.detailsModel = { + ...this.updateModel, + }; + } + + // + this.addModel = undefined; + this.updateModel = undefined; + break; + } + } + + /** + * Here We Do All Items Based Actions ... + * + * @param info + * @returns + */ + async doItemActions(info: { id: string; data: XFileDto }): Promise { + // + if (isNullOrUndefined(info) || isNullOrEmptyString(info.id)) { + return; + } + + // + let isKnown = false; + + // + // Details ... + isKnown = info.id === defaultDetailsSlideOption.id; + if (isKnown) { + // + this.detailsModel = { + ...info.data, + }; + + // + this.sendQueryPresenterAction( + VQueryPresenterAction.ChangeTab, + VQueryPresenterTabs.Details, + ); + } + + // + // Edit ... + isKnown = + info.id === defaultEditSlideOption.id || + info.id === defaultEditFabButton.id; + if (isKnown) { + // + if (!isNullOrUndefined(info.data)) { + // + this.updateModel = { + ...info.data, + }; + } else if ( + !isNullOrUndefined(this.detailsModel) && + this.activeTab === VQueryPresenterTabs.Details + ) { + // + this.updateModel = { + ...this.detailsModel, + }; + } + + // + if (!isNullOrUndefined(this.updateModel)) { + // + this.sendQueryPresenterAction( + VQueryPresenterAction.ChangeTab, + VQueryPresenterTabs.Update, + ); + } + } + + // + // Remove ... + isKnown = + info.id === defaultRemoveSlideOption.id || + info.id === defaultRemoveFabButton.id; + if (isKnown) { + // + if ( + isNullOrUndefined(info.data) && + !isNullOrUndefined(this.detailsModel) && + this.activeTab === VQueryPresenterTabs.Details + ) { + // + info.data = { + ...this.detailsModel, + }; + } + + // + if (!isNullOrUndefined(info.data)) { + await this.handleRemoveFile(info.data); + } + } + } + + /** + * Handle Specified Param ... + * + * @param identifier + * @param acttion + */ + async handleParamAction(identifier: string, action: string): Promise { + await super.handleParamAction(identifier, action); + + // + if (isNullOrEmptyString(identifier) || isNullOrEmptyString(action)) { + // + this.hasParams = false; + return; + } + + // + let model = await this.handleGetSpecifiedModel(identifier, true); + if (!isNullOrUndefined(model)) { + // + this.paramAction = action; + this.detailsModel = { ...model }; + this.activeTab = VQueryPresenterTabs.Details; + this.sendQueryPresenterAction( + VQueryPresenterAction.ChangeTab, + this.activeTab, + ); + } else { + this.hasParams = false; + } + } + + /** + * Handle Removing File ... + * + * @param model + */ + async handleRemoveFile(model: XFileDto) { + // + await this.doConfirmedAction(async () => { + // + await this.setLoadingState(true); + + // + try { + // + const connectionId = + this.sharedService.saherElmAPIService.files.fileEntityPushService + .connectionId; + + // + await this.getValueAsync( + this.sharedService.saherElmAPIService.files.fileProviderService.remove( + model.id, + connectionId, + ), + ); + } catch { + // + await this.handleCustomError('RemoveModelError'); + return; + } + + // + this.sendQueryPresenterAction(VQueryPresenterAction.RemoveModel, { + model, + actions: [], + key: nameof('id'), + }); + + // + this.showSuccess(); + await this.setLoadingState(false); + + // + // Handle Change Tabs if in Details ... + if ( + this.activeTab === VQueryPresenterTabs.List || + this.activeTab === VQueryPresenterTabs.Details + ) { + // + this.prevTab = undefined; + this.sendQueryPresenterAction( + VQueryPresenterAction.ChangeTab, + VQueryPresenterTabs.List, + ); + } + }); + } + + /** + * Handle Running toolbar Back Handler on Specified Case + * when User Pressed Escape Button ... + * + * @param event + * @returns + */ + async handleGlobalEscapePressed(event: any) { + // + if ( + this.lock || + !this.toolbarHasBack || + !this.toolbarShowBack || + !isFunction(this.toolbarBackHandler) || + [VQueryPresenterTabs.Add, VQueryPresenterTabs.Update].includes( + this.activeTab, + ) + ) { + return; + } + + // + await this.toolbarBackHandler(); + } + //#endregion + + // + //#region Private ... + private provideItemOptions(userId: string, isAdmin: boolean, item: XFileDto) { + // + let result: Array = []; + + // + // Validate ... + if (!isNullOrUndefined(item) && !isNullOrEmptyString(userId)) { + // + const isOwner = item.ownerId === userId; + + // + const canRemove = isOwner || isAdmin; + + // + // Details ... + result.push({ + ...defaultDetailsSlideOption, + slot: 'start', + disabled: this.loading || this.uiDisabled, + } as XSimpleListItemSlideOption); + + // + // Remove ... + result.push({ + ...defaultRemoveSlideOption, + slot: 'end', + disabled: this.loading || this.uiDisabled || !canRemove, + } as XSimpleListItemSlideOption); + } + + // + return result; + } + + private handleAttachTemplates() { + // + // List Item ... + let hasTemplate = !isNullOrUndefined( + this.listItemViewFieldDescriptors.find( + (fd) => + fd.key === this.ModelFieldNames.path && + !!fd.hasCustomTemplate && + !isNullOrUndefined(fd.template), + ), + ); + if (!hasTemplate && !isNullOrUndefined(this.fileNameRef)) { + // + this.listItemViewFieldDescriptors = this.listItemViewFieldDescriptors.map( + (fd) => { + // + return { + ...fd, + template: + fd.key === this.ModelFieldNames.path && + !!fd.hasCustomTemplate && + isNullOrUndefined(fd.template) + ? this.fileNameRef + : fd.template, + }; + }, + ); + } + + // + // Add Item ... + hasTemplate = !isNullOrUndefined( + this.addModelViewFieldDescriptors.find( + (fd) => + fd.key === this.ModelFieldNames.path && + !!fd.hasCustomTemplate && + !isNullOrUndefined(fd.template), + ), + ); + if (!hasTemplate && !isNullOrUndefined(this.fileNameRef)) { + // + this.addModelViewFieldDescriptors = this.addModelViewFieldDescriptors.map( + (fd) => { + // + return { + ...fd, + template: + fd.key === this.ModelFieldNames.path && + !!fd.hasCustomTemplate && + isNullOrUndefined(fd.template) + ? this.fileNameRef + : fd.template, + }; + }, + ); + } + + // + // Update Item ... + hasTemplate = !isNullOrUndefined( + this.updateModelViewFieldDescriptors.find( + (fd) => + fd.key === this.ModelFieldNames.path && + !!fd.hasCustomTemplate && + !isNullOrUndefined(fd.template), + ), + ); + if (!hasTemplate && !isNullOrUndefined(this.fileNameRef)) { + // + this.updateModelViewFieldDescriptors = + this.updateModelViewFieldDescriptors.map((fd) => { + // + return { + ...fd, + template: + fd.key === this.ModelFieldNames.path && + !!fd.hasCustomTemplate && + isNullOrUndefined(fd.template) + ? this.fileNameRef + : fd.template, + }; + }); + } + + // + // Details ... + hasTemplate = !isNullOrUndefined( + this.detailsModelViewFieldDescriptors.find( + (fd) => + fd.key === this.ModelFieldNames.path && + !!fd.hasCustomTemplate && + !isNullOrUndefined(fd.template), + ), + ); + if (!hasTemplate && !isNullOrUndefined(this.fileNameRef)) { + // + this.detailsModelViewFieldDescriptors = + this.detailsModelViewFieldDescriptors.map((fd) => { + // + return { + ...fd, + template: + fd.key === this.ModelFieldNames.path && + !!fd.hasCustomTemplate && + isNullOrUndefined(fd.template) + ? this.fileNameRef + : fd.template, + }; + }); + } + } + + isPushServiceConnected = false; + private async handleConnectPushService() { + // + // Preparing Connections ... + try { + // + // Check Connected Before ... + if ( + this.sharedService.saherElmAPIService.files.fileEntityPushService.isConnected() + ) { + this.isPushServiceConnected = true; + } else { + // + this.isPushServiceConnected = true; + await this.sharedService.saherElmAPIService.files.fileEntityPushService.connect(); + } + } catch { + // + await this.handleCustomError('PushServiceConnection'); + return; + } + + // + // Check Connected ... + if (!this.isPushServiceConnected) { + return; + } + + // + // On Added ... + this.applyTakeUntilWrapper( + this.sharedService.saherElmAPIService.files.fileEntityPushService + .onAdded$, + ).subscribe(async (event) => { + // + if (isNullOrUndefined(event)) { + return; + } + }); + + // + // On Removed ... + this.applyTakeUntilWrapper( + this.sharedService.saherElmAPIService.files.fileEntityPushService + .onDeleted$, + ).subscribe(async (event) => { + // + if (isNullOrUndefined(event) && isNullOrUndefined(this.dtoQueryResult)) { + return; + } + + // + // Check if Model Exists in Current Context or not ... + let model = this.dtoQueryResult.items.find( + (i) => i.id === event.model.id, + ); + const modelP = fileEntityToDto(event.model, model.owner); + if (!isNullOrUndefined(model) && !isNullOrUndefined(modelP)) { + // + const actions = await this.prepareListActions(modelP); + + // + this.sendQueryPresenterAction(VQueryPresenterAction.RemoveModel, { + actions, + model: modelP, + key: nameof('id'), + }); + } + + // + // Check Details or Edit Tab ... + if ( + (!isNullOrUndefined(model) && + !isNullOrUndefined(this.detailsModel) && + this.detailsModel.id === model.id && + this.activeTab === VQueryPresenterTabs.Details) || + (!isNullOrUndefined(model) && + !isNullOrUndefined(this.updateModel) && + this.updateModel.id === model.id && + this.activeTab === VQueryPresenterTabs.Update) + ) { + // + // Force Unlock ... + this.lock = false; + + // + this.sendQueryPresenterAction( + VQueryPresenterAction.ChangeTab, + VQueryPresenterTabs.List, + ); + } + }); + + // + // On Updated ... + this.applyTakeUntilWrapper( + this.sharedService.saherElmAPIService.files.fileEntityPushService + .onUpdated$, + ).subscribe(async (event) => { + // + if (isNullOrUndefined(event) || isNullOrUndefined(this.dtoQueryResult)) { + return; + } + + // + // Check if Model Exists in Current Context or not ... + let model = this.dtoQueryResult.items.find( + (i) => i.id === event.model.id, + ); + if (isNullOrUndefined(model)) { + return; + } + + // + let fromEntity: XFileDto = undefined; + try { + fromEntity = await this.getValueAsync( + this.sharedService.saherElmAPIService.files.fileProviderService.get( + event.model.id, + ), + ); + } catch { + return; + } + + // + if (isNullOrUndefined(fromEntity)) { + return; + } + + // + const modelP = fileEntityToDto(event.model, fromEntity.owner); + + // + if (isNullOrUndefined(modelP)) { + return; + } + + // + // Preparing Model List Actions ... + const actions = await this.prepareListActions(modelP); + + // + // Updating Model on Query Presenter ... + this.sendQueryPresenterAction(VQueryPresenterAction.UpdateModel, { + actions, + model: { ...modelP }, + key: nameof('id'), + }); + + // + // Update Tab ... + if ( + !isNullOrUndefined(this.updateModel) && + modelP.id === this.updateModel.id && + this.activeTab === VQueryPresenterTabs.Update + ) { + // + this.updateModel = { + ...modelP, + }; + + // + const selector = getQuerySelectorOfSpecifiedAttribute( + VFileComponent, + this.ModelFieldNames.id, + this.updateModel.id, + ); + await this.waitForElement(selector).then(async (el) => { + // + this.updateItemActionProvider.next({ + action: VBaseDtoAction.RenewModel, + payload: { ...this.updateModel }, + }); + + // + const prevTab = this.prevTab; + await this.handleStateAfterTabOpen(this.activeTab); + this.prevTab = prevTab; + }); + } + + // + // Details Tab ... + if ( + !isNullOrUndefined(this.detailsModel) && + modelP.id === this.detailsModel.id && + this.activeTab === VQueryPresenterTabs.Details + ) { + // + this.detailsModel = { + ...modelP, + }; + + // + const selector = getQuerySelectorOfSpecifiedAttribute( + VFileComponent, + this.ModelFieldNames.id, + this.detailsModel.id, + ); + await this.waitForElement(selector).then(async (el) => { + // + this.detailsItemActionProvider.next({ + action: VBaseDtoAction.RenewModel, + payload: { ...this.detailsModel }, + }); + + // + const prevTab = this.prevTab; + await this.handleStateAfterTabOpen(this.activeTab); + this.prevTab = prevTab; + }); + } + }); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\files\files-explorer\files-explorer.page.html + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\files\files-explorer\files-explorer.page.scss + +@import '../files-base//files-base.provider.page.scss'; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\files\files-explorer\files-explorer.page.ts + +import { Observable } from 'rxjs'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { XQueryDto, XQueryResultDto } from 'x-framework-identity-sdk'; +import { FilesBaseProviderPage } from '../files-base/files-base.provider.page'; + +@Component({ + selector: 'files-explorer', + styleUrls: ['./files-explorer.page.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + templateUrl: '../files-base/files-base.provider.page.html', +}) +export class FilesExplorerPage extends FilesBaseProviderPage { + // + //#region Props ... + //#endregion + + // + //#region LifeCycles ... + onInit(): void { + super.onInit(); + + // + this.configureAsExplorerPage(); + } + //#endregion + + // + //#region Abstractions ... + getModelTask(identifier: string): Observable { + // + return this.sharedService.saherElmAPIService.files.fileProviderService.get( + identifier, + ); + } + + queryModelTask(query: XQueryDto): Observable> { + return this.sharedService.saherElmAPIService.files.fileProviderService.query( + query, + ); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\files\files-management\files-management.page.html + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\files\files-management\files-management.page.scss + +@import '../files-base//files-base.provider.page.scss'; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\files\files-management\files-management.page.ts + +import { Observable } from 'rxjs'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; +import { XQueryDto, XQueryResultDto } from 'x-framework-identity-sdk'; +import { FilesBaseProviderPage } from '../files-base/files-base.provider.page'; + +@Component({ + selector: 'files-management', + styleUrls: ['./files-management.page.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + templateUrl: '../files-base/files-base.provider.page.html', +}) +export class FilesManagementPage extends FilesBaseProviderPage { + // + //#region Props ... + //#endregion + + // + //#region LifeCycles ... + onInit(): void { + super.onInit(); + + // + this.configureAsManagementPage(); + } + //#endregion + + // + //#region Abstractions ... + getModelTask(identifier: string): Observable { + // + return this.sharedService.saherElmAPIService.files.fileProviderService.get( + identifier, + ); + } + + queryModelTask(query: XQueryDto): Observable> { + return this.sharedService.saherElmAPIService.files.fileProviderService.query( + query, + ); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\home\home.page.html + + + + + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\home\home.page.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\home\home.page.ts + +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { VPageComponent } from 'src/app/views/v-page/v-page.component'; + +@Component({ + selector: 'app-components-home', + templateUrl: './home.page.html', + styleUrls: ['./home.page.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +// tslint:disable-next-line:component-class-suffix +export class HomePage extends VPageComponent { + // + //#region Props ... + toolbarShowTitle = true; + toolbarShowSubTitle = true; + titleRes = this.ResourceIDs.app_name; + toolbarSubTitle = this.resourceProvider(this.AppResourceIDs.home_page_title); + toolbarTitle = this.resourceProvider(this.titleRes); + //#endregion + + // + //#region Constructor ... + //#endregion + + // + //#region LifeCycles ... + //#endregion + + // + //#region Register Handlers ... + //#endregion + + // + //#region Task Handlers ... + //#endregion + + // + //#region UI Providers ... + //#endregion + + // + //#region UI Handlers ... + //#endregion + + // + //#region Actions ... + //#endregion + + // + //#region Abstracts ... + //#endregion + + // + //#region Private ... + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\landing\landing.page.html + + + + + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\landing\landing.page.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\landing\landing.page.ts + +import { XStandardType } from 'x-framework-core'; +import { XSideType } from 'x-framework-components'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; +import { VPageComponent } from 'src/app/views/v-page/v-page.component'; + +@Component({ + selector: 'app-landing', + templateUrl: './landing.page.html', + styleUrls: ['./landing.page.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +// tslint:disable-next-line: component-class-suffix +export class LandingPage extends VPageComponent { + // + //#region Props ... + hasSide = true; + showFooter = true; + toolbarShowSubTitle = true; + titleRes = this.AppResourceIDs.landing_page_title; + toolbarSubTitle = this.resourceProvider( + this.AppResourceIDs.landing_page_description + ); + toolbarTitle = this.resourceProvider(this.titleRes); + + /** + * use this for Menu always Visible ... + */ + toggleMenuWhen = ''; + sideType: XStandardType = XSideType.Overlay; + //#endregion + + // + //#region Constructor ... + //#endregion + + // + //#region LifeCycles ... + //#endregion + + // + //#region Register Handlers ... + //#endregion + + // + //#region Handlers ... + //#endregion + + // + //#region Task Handlers ... + //#endregion + + // + //#region UI Providers ... + //#endregion + + // + //#region UI Handlers ... + //#endregion + + // + //#region Actions ... + //#endregion + + // + //#region Private ... + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\landing\landing.typing.ts + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\not-authorized\not-authorized.page.html + + + + + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\not-authorized\not-authorized.page.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\not-authorized\not-authorized.page.ts + +import { Pages } from 'src/app/config/page.config'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; +import { VPageComponent } from 'src/app/views/v-page/v-page.component'; + +@Component({ + selector: 'app-not-authorized', + templateUrl: './not-authorized.page.html', + styleUrls: ['./not-authorized.page.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +// tslint:disable-next-line: component-class-suffix +export class NotAuthorizedPage extends VPageComponent { + // + //#region Page Props ... + hasSide = false; + toolbarHasBack = true; + toolbarShowBack = true; + showToolbarEndSlot = false; + showToolbarContent = false; + toolbarShowSubTitle = true; + toolbarSubTitle = this.resourceProvider(this.titleRes); + titleRes = this.AppResourceIDs.not_authorized_page_title; + + // + toolbarBackHandler = () => { + // + // TODO: Fix this ... + // this.managerService.navigateByPageReplace(Pages.); + } + //#endregion + + // + //#region Constructor ... + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\startup\startup.page.html + + + + + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\startup\startup.page.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\startup\startup.page.ts + +import { + XPage, + XExceptionIDs, + isNullOrUndefined, +} from 'x-framework-core'; +import { Pages } from 'src/app/config/page.config'; +import { VPageComponent } from 'src/app/views/v-page/v-page.component'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; + +const StartupKeys = { + AuthLoadingID: 'auth_loading', +}; + +@Component({ + selector: 'app-startup', + templateUrl: './startup.page.html', + styleUrls: ['./startup.page.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +// tslint:disable-next-line: component-class-suffix +export class StartupPage extends VPageComponent { + // + //#region Configuration Props ... + // + hasSide = false; + showToolbarEndSlot = false; + showToolbarContent = false; + + // + hasBlogPage = !isNullOrUndefined(this.config.defaultBlogPage); + hasLandingPage = !isNullOrUndefined(this.config.defaultLandingPage); + + // + private loadingModal: HTMLIonLoadingElement; + //#endregion + + // + //#region Constructor ... + //#endregion + + // + //#region LifeCycle ... + async onInit() { + super.onInit(); + + // + await this.handleStartUp(); + } + //#endregion + + // + //#region Private ... + private async handleStartUp() { + // + await this.presentLoading( + StartupKeys.AuthLoadingID, + this.resourceProvider(this.AppResourceIDs.default_loading) + ); + + // + // Retrieve Account State ... + const xAccountState = await this.sharedService.authService.currentState(); + const isAuthenticated = xAccountState ? xAccountState.isLoggedIn : false; + if (!isAuthenticated) { + // + if (!this.hasBlogPage && !this.hasLandingPage) { + // + await this.dismissLoading(StartupKeys.AuthLoadingID); + this.showError(XExceptionIDs.NotAuthorized); + } + + // + await this.finishAction(Pages.Login); + return; + } + + // + // Check Token Expiration ... + const isExpiredToken = await this.sharedService.authService.isExpired(); + if (isExpiredToken) { + // + try { + // + const mTask = this.sharedService.authService.refreshTokens(); + + // + const newTokens = await this.getValueAsync(mTask); + + // + await this.sharedService.authService.updateUserTokens( + xAccountState, + newTokens + ); + + // + await this.finishAction(Pages.Home); + } catch { + // + if (!this.hasBlogPage && !this.hasLandingPage) { + await this.handleErrorState(XExceptionIDs.LoginFailed, true); + } + + // + await this.sharedService.authService.logout(); + await this.finishAction(Pages.Login); + } + return; + } + + // + if (!this.hasBlogPage && !this.hasLandingPage) { + // + await this.finishAction(Pages.Login); + return; + } + + // + await this.finishAction(Pages.Home); + return; + } + + private async finishAction(dest?: XPage) { + // + await this.dismissLoading(StartupKeys.AuthLoadingID); + + // + if (this.hasBlogPage && !this.hasLandingPage) { + // + await this.managerService.navigateByPageReplace( + this.config.defaultBlogPage + ); + return; + } + + // + if (this.hasLandingPage) { + // + await this.managerService.navigateByPageReplace( + this.config.defaultLandingPage + ); + return; + } + + // + if (!isNullOrUndefined(dest)) { + await this.managerService.navigateByPageReplace(dest); + } + } + + private async presentLoading(id: string, message: string) { + // + if (!isNullOrUndefined(this.loadingModal) && this.loadingModal) { + await this.dismissLoading(StartupKeys.AuthLoadingID); + } + + // + this.loadingModal = await this.managerService.dialogService.presentLoading({ + id, + message, + }); + } + + private async dismissLoading(id: string) { + // + if (isNullOrUndefined(this.loadingModal)) { + return false; + } + + // + const result = + await this.managerService.dialogService.loadingController.dismiss(id); + if (!!result) { + this.loadingModal = undefined; + } + + // + return result; + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\tags\tags-page.module.ts + +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; +import { AuthGuard } from 'x-framework-identity-sdk'; +import { XCanDeactivateGuard } from 'x-framework-core'; +import { BaseRoutes } from 'src/app/config/page.config'; +import { ViewsModule } from 'src/app/views/views.module'; +import { TagRoutes } from 'src/app/views/tags/v-tag.configs'; +import { TagsExplorerPage } from './tags-explorer/tags-explorer.page'; +import { TagsManagementPage } from './tags-management/tags-management.page'; + +@NgModule({ + declarations: [TagsExplorerPage, TagsManagementPage], + imports: [ + ViewsModule, + RouterModule.forChild([ + { + pathMatch: 'full', + path: BaseRoutes.Default, + redirectTo: TagRoutes.TagsExplorer, + }, + // + // Tags Explorer ... + { + canActivate: [AuthGuard], + component: TagsExplorerPage, + canDeactivate: [XCanDeactivateGuard], + path: TagRoutes.TagsExplorer, + }, + // + // Tags Management ... + { + canActivate: [AuthGuard], + component: TagsManagementPage, + canDeactivate: [XCanDeactivateGuard], + path: TagRoutes.TagsManagement, + }, + { + pathMatch: 'full', + path: BaseRoutes.Unknown, + redirectTo: TagRoutes.Default, + }, + ]), + ], + exports: [ViewsModule, RouterModule, TagsExplorerPage, TagsManagementPage], +}) +export class TagsPageModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\tags\tags-base\tags-base.provider.page.html + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\tags\tags-base\tags-base.provider.page.scss + +.v-item, +.v-list-item, +.v-input-item, +.v-detail-item + { + width: 100%; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\tags\tags-base\tags-base.provider.page.ts + +import { + Component, + ViewChild, + TemplateRef, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + XRef, + nameof, + isFunction, + getFieldNames, + isNullOrUndefined, + isNullOrEmptyString, + XResourceIdentifier, + getComponentSelector, +} from 'x-framework-core'; +import { + XListItem, + XSlotName, + XButtonType, + XListItemOptionSlot, +} from 'x-framework-components'; +import { + TVDtoSections, + VBaseDtoAction, + VModelFieldTypes, + VBaseDtoPresentType, + VBaseDtoActionModel, + VBaseDtoModelAction, + VModelFieldDescriptor, + VBaseDtoModelFiredActionModel, +} from 'src/app/views/v-dto/v-dto.typings'; +import { Observable, Subject } from 'rxjs'; +import { Validators } from '@angular/forms'; +import { + defaultEditFabButton, + defaultOptionsFabItem, + defaultRemoveFabButton, +} from 'src/app/views/constants/fab.defaults'; +import { + toSimpleFabItems, + XSimpleFabButton, +} from 'src/app/views/typings/simple.fab.typings'; +import { + defaultEditSlideOption, + defaultRemoveSlideOption, + defaultDetailsSlideOption, +} from 'src/app/views/constants/slide.options.defaults'; +import { + processFilledItems, + VQueryPresenterTabs, + VQueryPresenterAction, + VQueryPresenterActionModel, + VQueryPresenterActionIdentifier, + VQueryFilledItemsProcessingResult, +} from 'src/app/views/v-query-presenter/v-query-presenter.typings'; +import { XQueryDto, XQueryResultDto } from 'x-framework-identity-sdk'; +import { sendAction } from 'src/app/views/tools/action-provider.tools'; +import { VTagComponent } from 'src/app/views/tags/v-tag/v-tag.component'; +import { VTagPresentType } from 'src/app/views/tags/v-tag/v-tag.typings'; +import { defaultTagDto, XReferenceDto, XTagDto } from 'x-saherelm-api-sdk'; +import { PersonPresentType } from 'src/app/views/v-person/v-person.typings'; +import { VDtoCommonProperties } from 'src/app/views/typings/dto.common.typings'; +import { VPagePresenter } from 'src/app/views/v-page-presenter/v-page-presenter.component'; +import { XSimpleListItemSlideOption } from 'src/app/views/typings/simple.list-item.typings'; +import { getQuerySelectorOfSpecifiedAttribute } from 'src/app/views/typings/common.typings'; +import { VQueryPresenterBasePage } from 'src/app/views/v-query-presenter/v-query-presenter.base.page'; + +@Component({ + template: '', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export abstract class TagsBaseProviderPage extends VQueryPresenterBasePage { + // + //#region Props ... + toolbarShowTitle = true; + toolbarShowSubTitle = true; + titleRes = this.ResourceIDs.app_name; + toolbarSubTitle = this.resourceProvider( + this.AppResourceIDs.tags_management_page_title + ); + toolbarTitle = this.resourceProvider(this.titleRes); + + // + @ViewChild('pagePresenter') + pagePresenter!: VPagePresenter; + + // + readonly ButtonTypes = Object.assign(XButtonType, {}); + readonly ModelFieldNames = getFieldNames(defaultTagDto); + readonly ModelFieldTypes = Object.assign(VModelFieldTypes); + readonly TagPresentTypes = Object.assign(VTagPresentType, {}); + readonly PersonPresentTypes = Object.assign(PersonPresentType, {}); + readonly BasePresentTypes = Object.assign(VBaseDtoPresentType, {}); + + // + // Prepare Commonly Used Properties for Dto Actions ... + // we can Override this for other Purposes ... + commonViewsProperties: VDtoCommonProperties = { + isInPage: false, + wrapWithCard: false, + labelColor: undefined, + valueColor: undefined, + fieldFormButtonPosition: undefined, + fieldFormIcon: undefined, + fieldFormIconColor: undefined, + fieldFormApplyIcon: undefined, + fieldFormApplyIconColor: undefined, + fieldFormCancelIcon: undefined, + fieldFormCancelIconColor: undefined, + removeCardMargin: undefined, + cardForegroundColor: undefined, + cardCssClass: undefined, + cardColor: undefined, + cardActionProvider: undefined, + formAppearance: undefined, + formActionProvider: undefined, + formRegularColor: undefined, + formAccentColor: undefined, + formHintColor: undefined, + formActiveColor: undefined, + formValidColor: undefined, + formInvalidColor: undefined, + formPendingColor: undefined, + formFillColor: undefined, + formDisabledColor: undefined, + formDisabledFillColor: undefined, + showFormCancelButton: undefined, + formCancelButtonResource: undefined, + formCancelButtonColor: undefined, + formResetButtonResource: undefined, + formResetButtonColor: undefined, + formActionButtonAddResource: undefined, + formActionButtonUpdateResource: undefined, + formActionButtonColor: undefined, + }; + + // + //#region Query Presenter Props ... + modelIdentifier: string = nameof('id'); + listTabCardTitle: XResourceIdentifier = this.AppResourceIDs.tags; + + /** + * Presenter Component Action Provider ... + */ + queryPresenterActionProvider = new Subject(); + //#endregion + //#endregion + + // + //#region Abstracts ... + /** + * Specified How to get Specific Item using Model Identifier ... + */ + abstract getModelTask(identifier: string): Observable; + + /** + * Specified How to Query Items ... + */ + abstract queryModelTask(query: XQueryDto): Observable>; + + /** + * Param Recieved Action ... + */ + paramAction: string = undefined; + //#endregion + + // + //#region LifeCycles ... + async afterViewInit(): Promise { + await super.afterViewInit(); + + // + this.applyTakeUntilWrapper( + this.managerService.settingsService.isMobile$ + ).subscribe((res) => { + // + if (!isNullOrUndefined(this.activeTab)) { + // + switch(this.activeTab) { + // + case VQueryPresenterTabs.List: + sendAction({ + provider: this.listItemActionProvider, + action: VBaseDtoAction.Refresh + }); + break; + } + } + }); + + // + await this.handleConnectPushService(); + } + //#endregion + + // + //#region Providers ... + //#endregion + + // + //#region List ... + /** + * Provides List Item Template ... + */ + @ViewChild('itemTemplate') + itemTemplate!: TemplateRef; + + /** + * Provides List Item Template ... + */ + @ViewChild('listItemTemplate') + listItemTemplate!: TemplateRef; + + /** + * Query Object for Fetching Data ... + */ + query: XQueryDto = { + page: 1, + filter: '', + pageSize: this.config.defaultPageSize, + sortBy: nameof('id'), + }; + + /** + * a Flag for Detect Has Query result or not ... + */ + hasQueryResultError: boolean = false; + + /** + * Fetching Data Result Model ... + */ + dtoQueryResult: XQueryResultDto = undefined; + + /** + * Specified Action Provider ... + */ + listItemActionProvider = new Subject(); + + /** + * Specified View Properties ... + */ + listItemViewProperties: VDtoCommonProperties = { + ...this.commonViewsProperties, + }; + + /** + * Specified Field Descriptors for List Items ... + */ + listItemViewFieldDescriptors: VModelFieldDescriptor[] = [ + // + // Tag ... + { + hide: false, + hideLabel: true, + allowFieldForm: false, + key: this.ModelFieldNames.tag, + value: defaultTagDto.tag, + label: this.AppResourceIDs.tag, + type: this.ModelFieldTypes.String, + valueProvider: (value) => { + return this.applyLocale(value); + }, + } as VModelFieldDescriptor, + ]; + + /** + * Hold each Items Provided Actions based on Identifier ... + */ + itemsActions = new Map>>(); + + /** + * Prepare Item Actions based on Slot ... + */ + getSlottedItemActions(slot: XSlotName, model: XTagDto) { + // + let result: Array = []; + + // + if ( + isNullOrUndefined(model) || + isNullOrEmptyString(slot) || + isNullOrUndefined(model.id) || + isNullOrUndefined(this.itemsActions) || + !this.itemsActions.has(model.id.toString()) + ) { + return result; + } + + // + let _slot: XListItemOptionSlot = + slot === XSlotName.START || slot === XSlotName.TOP ? 'start' : 'end'; + + // + result = this.itemsActions + .get(model.id.toString()) + .value.filter((a) => a.slot === _slot); + + // + return result; + } + + /** + * Handle Non As List Item's Action ... + * + * @param id + * @param data + */ + async handleItemAction(id: string, data: XTagDto) { + // + // Validate ... + if ( + this.loading || + this.uiDisabled || + isNullOrEmptyString(id) || + isNullOrUndefined(data) + ) { + return; + } + + // + await this.doItemActions({ id, data }); + } + + /** + * Handle Retrieve Data based on XQuery ... + * + * @param query + * @returns + */ + async handleGetQueryResult( + query: XQueryDto + ): Promise> { + // + let result: XQueryResultDto = undefined; + + // + // Start Loading ... + this.hasQueryResultError = false; + await this.setLoadingState(true); + + // + // Try to Prepare Result ... + try { + // + // Retrieve Webinar Dto Result ... + this.dtoQueryResult = await this.getValueAsync( + this.queryModelTask(query) + ); + + // + // Prepare Result ... + result = { + ...result, + page: this.dtoQueryResult.page, + pageSize: this.dtoQueryResult.pageSize, + totalItems: this.dtoQueryResult.totalItems, + totalPages: this.dtoQueryResult.totalPages, + totalFilteredItems: this.dtoQueryResult.totalFilteredItems, + totalFilteredPages: this.dtoQueryResult.totalFilteredPages, + }; + + // + // Converts XWebinarDto to XTagDto ... + result.items = this.dtoQueryResult.items.map((i) => i); + + // + // Remove Scrolling Info ... + if ( + !isNullOrUndefined(this.query) && + this.query.filter !== query.filter + ) { + // + this.removeScrollingInfo(); + this.infinityScrollDisabled = true; + } + + // + this.query = query; + } catch { + // + this.hasQueryResultError = true; + await this.handleCustomError('QueryWebinarsError'); + return result; + } + + // + // Stop Loading ... + await this.setLoadingState(false); + + // + return result; + } + + /** + * Get Specified Model Based on Identifier ... + * + * @param identifier + * @param applyState + * @returns + */ + async handleGetSpecifiedModel(identifier: string, applyState = false) { + // + if (isNullOrEmptyString(identifier)) { + return; + } + + // + let response: any = undefined; + try { + // + if (applyState) { + await this.setLoadingState(true); + } + + // + response = await this.getValueAsync(this.getModelTask(identifier)); + + // + } catch { + // + await this.handleCustomError('GetWebinarError'); + return; + } + + // + if (applyState) { + await this.setLoadingState(false); + } + + // + return response; + } + + /** + * Provide List Actions for Specific Model ... + * + * @param model + * @returns + */ + async prepareListActions(model: XTagDto) { + // + const userId = await this.getValueAsync(this.sharedService.userId$); + const isAdmin = await this.getValueAsync(this.sharedService.isAdmin$); + + // + return this.provideItemOptions(userId, isAdmin, model); + } + + /** + * Validate List Items ... + * + * @param items + * @returns + */ + async validateAndPrepareListItems( + items: Array> + ): Promise>> { + // + let result: Array> = []; + + // + // Preparing Slide Options ... + result = await Promise.all([ + ...items.map(async (i) => { + return { + ...i, + slideOptions: [...(await this.prepareListActions(i.data))], + }; + }), + ]); + + // + return result; + } + + /** + * Prepare Result for Non As List Presenting Items ... + * + * @param current + * @param recieved + */ + async requestFillingItems( + exists: Array, + recieved: Array + ): Promise> { + // + let result: VQueryFilledItemsProcessingResult = processFilledItems( + exists, + recieved, + 'id' + ); + + // + // Filling Actions ... + if (!isNullOrUndefined(result)) { + // + // News Items Actions ... + if (!isNullOrUndefined(result.news) && this.hasChild(result.news)) { + // + for (let i of result.news) { + // + const actions = await this.prepareListActions(i); + this.itemsActions.set(i.id.toString(), { value: actions }); + } + } + + // + // Changed Items Actions ... + if (!isNullOrUndefined(result.changed) && this.hasChild(result.changed)) { + // + for (let i of result.changed) { + // + if (this.itemsActions.has(i.identifier.toString())) { + // + const actions = await this.prepareListActions(i.data); + this.itemsActions.set(i.identifier.toString(), { value: actions }); + } + } + } + } + + // + return result; + } + + /** + * When Non as List Items Filled, this method call for Change Detection ... + * + * @param ids + */ + async handleRefreshViews(ids: Array): Promise { + await super.handleRefreshViews(ids); + + // + if (isNullOrUndefined(ids) || !this.hasChild(ids)) { + return; + } + + // + const selector = getComponentSelector(VTagComponent); + ids.forEach((id) => { + // + const iSelector = `${selector}[${nameof( + 'id' + )}="${id}"][${nameof('presentType')}="${ + VBaseDtoPresentType.AsView + }"]`; + this.waitForElement(iSelector).then((el) => { + this.listItemActionProvider.next({ action: VBaseDtoAction.Refresh }); + }); + }); + } + //#endregion + + // + //#region Add ... + /** + * Provides Template for Adding Items ... + */ + @ViewChild('addItemTemplate') + addItemTemplate!: TemplateRef; + + /** + * a Model for Providing to Add ... + */ + addModel: XTagDto = { + ...defaultTagDto, + }; + + /** + * Specified Action Provider ... + */ + addItemActionProvider = new Subject(); + + /** + * Specify View Properties for Add ... + */ + addModelViewProperties: VDtoCommonProperties = { + ...this.commonViewsProperties, + isInPage: true, + wrapWithCard: true, + }; + + /** + * Describe Add Model Fields ... + */ + addModelViewFieldDescriptors: VModelFieldDescriptor[] = [ + // + // Tag ... + { + hide: false, + hideLabel: true, + allowFieldForm: true, + key: this.ModelFieldNames.tag, + value: defaultTagDto.tag, + label: this.AppResourceIDs.tag, + type: this.ModelFieldTypes.String, + valueProvider: (value) => { + return this.applyLocale(value); + }, + formControlConfig: { + index: 0, + propName: this.ModelFieldNames.tag, + type: { + type: this.FormControlTypes.Text, + config: {}, + }, + validators: { + validators: [Validators.required], + }, + eventHandlers: {}, + appearance: { + hidden: false, + disabled: false, + placeholder: this.AppResourceIDs.tag, + appearance: this.FormControlAppearances.Outline, + icons: { + prefix: { + applyStateColor: true, + name: this.IconNames.label, + color: this.ColorNames.Tertiary, + }, + }, + }, + }, + } as VModelFieldDescriptor, + ]; + + /** + * Handle Locking Page when a Form Lock Content ... + * + * @param event + */ + handleAddViewLockNotifier(event: { id: string; state: boolean }) { + this.lock = event.state; + } + + /** + * Handle Cancel Add Form View ... + * + * @param event + */ + async handleAddViewActionFired(event: VBaseDtoModelFiredActionModel) { + // + // Cancel Form ... + if (event.action === VBaseDtoModelAction.Cancel) { + // + // Since Cancel only Fired when Lock Breaked, here we do not need to handle it ... + this.sendQueryPresenterAction( + VQueryPresenterAction.ChangeTab, + this.prevTab || VQueryPresenterTabs.List + ); + } + // + // Reset Form ... + else if (event.action === VBaseDtoModelAction.Reset) { + } + // + // Add Or Update Form ... + else if (event.action === VBaseDtoModelAction.AddOrUpdate) { + // + const pModel: Required = { + ...event.payload.model, + } as Required; + await this.handleAddModel(pModel); + } + } + + /** + * Do Add Model ... + * + * @param model + * @returns + */ + async handleAddModel(model: XTagDto) { + // + if (isNullOrUndefined(model)) { + return; + } + + // + await this.setLoadingState(true); + + // + let response: XTagDto = undefined; + try { + // + // Prepare Dto Model to Add ... + const dtoModel = { ...model }; + + // + // Remove ID ... + delete dtoModel.id; + + // + // Retrieve Connection ID ... + const connectionId = + this.sharedService.saherElmAPIService.tags.tagEntityPushService + .connectionId; + + // + // Issue Task ... + response = await this.getValueAsync( + this.sharedService.saherElmAPIService.tags.tagProviderService.add( + dtoModel, // Model ... + connectionId // Connection Id ... + ) + ); + } catch (ex) { + // + await this.handleCustomError('AddModelError'); + return; + } + + // + this.lock = false; + this.addModel = undefined; + this.showSuccess(); + await this.setLoadingState(false); + this.sendQueryPresenterAction(VQueryPresenterAction.ResetQuery, true); + } + //#endregion + + // + //#region Update ... + /** + * Provides Template for Update Items ... + */ + @ViewChild('updateItemTemplate') + updateItemTemplate!: TemplateRef; + + /** + * a Model for Providing to Update ... + */ + updateModel: XTagDto = undefined; + + /** + * Specified Action Provider ... + */ + updateItemActionProvider = new Subject(); + + /** + * Specify View Properties for Update ... + */ + updateModelViewProperties: VDtoCommonProperties = { + ...this.commonViewsProperties, + isInPage: true, + wrapWithCard: true, + }; + + /** + * Describe Update Model Fields ... + */ + updateModelViewFieldDescriptors: VModelFieldDescriptor[] = [ + // + // Tag ... + { + hide: false, + hideLabel: true, + allowFieldForm: true, + key: this.ModelFieldNames.tag, + value: defaultTagDto.tag, + label: this.AppResourceIDs.tag, + type: this.ModelFieldTypes.String, + valueProvider: (value) => { + return this.applyLocale(value); + }, + formControlConfig: { + index: 0, + propName: this.ModelFieldNames.tag, + type: { + type: this.FormControlTypes.Text, + config: {}, + }, + validators: { + validators: [Validators.required], + }, + eventHandlers: {}, + appearance: { + hidden: false, + disabled: false, + placeholder: this.AppResourceIDs.tag, + appearance: this.FormControlAppearances.Outline, + icons: { + prefix: { + applyStateColor: true, + name: this.IconNames.label, + color: this.ColorNames.Tertiary, + }, + }, + }, + }, + } as VModelFieldDescriptor, + ]; + + /** + * Handle Locking Page when a Form Lock Content ... + * + * @param event + */ + handleUpdateViewLockNotifier(event: { id: string; state: boolean }) { + this.lock = event.state; + } + + /** + * Handle Cancel Update Form View ... + * + * @param event + */ + async handleUpdateViewActionFired(event: VBaseDtoModelFiredActionModel) { + // + // Cancel Form ... + if (event.action === VBaseDtoModelAction.Cancel) { + // + // Since Cancel only Fired when Lock Breaked, here we do not need to handle it ... + this.sendQueryPresenterAction( + VQueryPresenterAction.ChangeTab, + this.prevTab || VQueryPresenterTabs.List + ); + } + // + // Reset Form ... + else if (event.action === VBaseDtoModelAction.Reset) { + } + // + // Add Or Update Form ... + else if (event.action === VBaseDtoModelAction.AddOrUpdate) { + // + const pModel: Required = { + ...event.payload.model, + } as Required; + await this.handleUpdateModel(pModel); + } + } + + /** + * Do Update Model ... + * + * @param model + * @returns + */ + async handleUpdateModel(model: XTagDto) { + // + const prevTab = this.prevTab; + + // + if (isNullOrUndefined(model)) { + return; + } + + // + await this.setLoadingState(true); + + // + let response: XTagDto = undefined; + try { + // + // Prepare Dto Model to Add ... + const dto = + !isNullOrUndefined(this.dtoQueryResult) && + !isNullOrUndefined(this.dtoQueryResult.items) && + this.hasChild(this.dtoQueryResult.items) + ? this.dtoQueryResult.items.find((i) => i.id === model.id) + : undefined; + if (isNullOrUndefined(dto)) { + throw 'UpdateModelError'; + } + + // + // Preparing Update Model ... + const dtoModel = { ...model }; + + // + // Retrieve Connection ID ... + const connectionId = + this.sharedService.saherElmAPIService.tags.tagEntityPushService + .connectionId; + + // + // Issue Task ... + response = await this.getValueAsync( + this.sharedService.saherElmAPIService.tags.tagProviderService.update( + dtoModel.id, + dtoModel, + connectionId + ) + ); + } catch (ex) { + // + await this.handleCustomError('UpdateModelError'); + return; + } + + // + if (isNullOrUndefined(response)) { + // + await this.handleCustomError('UpdateModelError'); + return; + } + + // + this.lock = false; + this.updateModel = undefined; + await this.setLoadingState(false); + this.showSuccess(); + + // + this.prevTab = prevTab; + model = { ...response }; + const actions = await this.prepareListActions(model); + + // + if (this.prevTab === VQueryPresenterTabs.Details) { + // + this.detailsModel = { + ...model, + }; + } + + // + this.sendQueryPresenterAction(VQueryPresenterAction.UpdateModel, { + model, + actions, + key: nameof('id'), + }); + + // + // Check Handle Back ... + if ( + this.toolbarHasBack && + this.toolbarShowBack && + isFunction(this.toolbarBackHandler) + ) { + await this.toolbarBackHandler(); + } + } + //#endregion + + // + //#region Details ... + /** + * Provides Template for Details Items ... + */ + @ViewChild('detailsItemTemplate') + detailsItemTemplate!: TemplateRef; + + /** + * a Model for Providing to Details ... + */ + detailsModel: XTagDto = undefined; + + /** + * Specified Action Provider ... + */ + detailsItemActionProvider = new Subject(); + + /** + * Specify View Properties for Details ... + */ + detailsModelViewProperties: VDtoCommonProperties = { + ...this.commonViewsProperties, + isInPage: true, + wrapWithCard: true, + }; + + /** + * Describe Details Model Fields ... + */ + detailsModelViewFieldDescriptors: VModelFieldDescriptor[] = [ + // + // Tag ... + { + hide: false, + hideLabel: true, + allowFieldForm: true, + key: this.ModelFieldNames.tag, + value: defaultTagDto.tag, + label: this.AppResourceIDs.tag, + type: this.ModelFieldTypes.String, + valueProvider: (value) => { + return this.applyLocale(value); + }, + } as VModelFieldDescriptor, + // + // References ... + { + hide: false, + hideLabel: false, + allowFieldForm: true, + key: this.ModelFieldNames.references, + value: defaultTagDto.references, + label: this.AppResourceIDs.references, + type: this.ModelFieldTypes.Array, + showContentBelowLabel: true, + pushArrayChildsOnceToTemplate: true, + hasCustomTemplate: true, + } as VModelFieldDescriptor>>, + ]; + + /** + * Provides Card Title for Specified Model ... + * + * @param model + * @returns + */ + getModelCardTitle(model: XTagDto) { + // + // Since card title's are Rescource ID's ... + return this.applyLocale(`${model.tag} `); + } + //#endregion + + // + //#region Handlers ... + /** + * Handle Errors ... + * + * @param error + */ + async handleCustomError( + error?: VQueryPresenterActionIdentifier + ): Promise { + // + await this.setLoadingState(false); + + // + // TODO: Implement this ... + console.log('handle Custom Error: ', error); + } + + /** + * Override this for adding additional futures ... + * + * @param tab + */ + async handleStateAfterTabOpen(tab: number) { + await super.handleStateAfterTabOpen(tab); + + // + let selector: string = undefined; + let fabs: XSimpleFabButton[] = []; + let model: XTagDto = undefined; + + // + let actions = await toSimpleFabItems(this.actions); + this.closeActions(); + this.actions = undefined; + + // + const userId = await this.getValueAsync(this.sharedService.userId$); + const isAdmin = await this.getValueAsync(this.sharedService.isAdmin$); + const isAgent = await this.getValueAsync(this.sharedService.isAgent$); + const isAdminAgent = isAdmin || isAgent; + + // + switch (tab) { + // + case VQueryPresenterTabs.List: + // + if (this.showItemsAsList) { + // + sendAction({ + provider: this.listItemActionProvider, + action: VBaseDtoAction.SetProperties, + payload: { + props: { ...this.listItemViewProperties }, + }, + }); + } else { + // + // Prepare Non As List Items Properties ... + const properties: VDtoCommonProperties = { + ...this.listItemViewProperties, + isInPage: true, + wrapWithCard: true, + cardShowActions: true, // Since Each Items has Separate Actions ... + }; + + // + sendAction({ + provider: this.listItemActionProvider, + action: VBaseDtoAction.SetProperties, + payload: { + props: { ...properties }, + }, + }); + } + + // + break; + + // + case VQueryPresenterTabs.Add: + // + this.addModel = { + ...defaultTagDto, + }; + + // + sendAction({ + provider: this.addItemActionProvider, + action: VBaseDtoAction.SetProperties, + payload: { + props: { ...this.addModelViewProperties }, + }, + }); + break; + + // + case VQueryPresenterTabs.Update: + // + model = { ...this.updateModel }; + + // + sendAction({ + provider: this.updateItemActionProvider, + action: VBaseDtoAction.SetProperties, + payload: { + props: { ...this.updateModelViewProperties }, + }, + }); + break; + + // + case VQueryPresenterTabs.Details: + // + model = { ...this.detailsModel }; + + // + sendAction({ + provider: this.detailsItemActionProvider, + action: VBaseDtoAction.SetProperties, + payload: { + props: { ...this.detailsModelViewProperties }, + }, + }); + + // + // Adding Custom Actions ... + + // + // Actions Preparing ... + actions = { + ...(!isNullOrUndefined(actions) ? actions : defaultOptionsFabItem), + childs: [ + ...(!isNullOrUndefined(actions) && this.hasChild(actions.childs) + ? actions.childs + : []), + ...fabs, + ], + }; + + // + // Issue Action if Waiting for Details ... + if (!isNullOrEmptyString(this.paramAction)) { + // + const action = this.paramAction; + this.paramAction = undefined; + + // + await this.doItemActions({ + id: action, + data: { ...this.detailsModel }, + }); + } + break; + } + + // + // Here can Handle Actions Permissions and States ... + if (!isNullOrUndefined(actions)) { + // + // Only if model Exists ... + if (!isNullOrUndefined(model)) { + // + // Retrieve Requirements ... + + // + const canEdit = isAdmin; + const canRemove = isAdmin; + + // + // Edit ... + if ( + actions.id === defaultEditFabButton.id || + (!isNullOrUndefined(actions.childs) && + this.hasChild(actions.childs) && + actions.childs.findIndex( + (a) => a.id === defaultEditFabButton.id + )) >= 0 + ) { + // + if (actions.id === defaultEditFabButton.id) { + actions.disabled = + !canEdit || actions.disabled || this.loading || this.uiDisabled; + } else { + // + actions.childs = [ + ...actions.childs.map((ch) => { + return { + ...ch, + disabled: + ch.id === defaultEditFabButton.id + ? !canEdit || + ch.disabled || + this.loading || + this.uiDisabled + : ch.disabled, + }; + }), + ]; + } + } + + // + // Remove ... + if ( + actions.id === defaultRemoveFabButton.id || + (!isNullOrUndefined(actions.childs) && + this.hasChild(actions.childs) && + actions.childs.findIndex( + (a) => a.id === defaultRemoveFabButton.id + )) >= 0 + ) { + // + if (actions.id === defaultRemoveFabButton.id) { + actions.disabled = + !canRemove || actions.disabled || this.loading || this.uiDisabled; + } else { + // + actions.childs = [ + ...actions.childs.map((ch) => { + return { + ...ch, + disabled: + ch.id === defaultRemoveFabButton.id + ? !canRemove || + ch.disabled || + this.loading || + this.uiDisabled + : ch.disabled, + }; + }), + ]; + } + } + } + + // + // Update Actions ... + this.actions = { + ...actions, + }; + } + } + + /** + * Override this for adding additional futures ... + * + * @param from + * @param to + */ + async handleStateBeforeTabOpen(from: number, to: number) { + await super.handleStateBeforeTabOpen(from, to); + + // + this.closeActions(); + + // + switch (to) { + // + case VQueryPresenterTabs.List: + // + this.addModel = undefined; + this.updateModel = undefined; + this.detailsModel = undefined; + break; + + // + case VQueryPresenterTabs.Add: + // + this.updateModel = undefined; + this.detailsModel = undefined; + break; + + // + case VQueryPresenterTabs.Update: + // + this.addModel = undefined; + this.detailsModel = undefined; + break; + + // + case VQueryPresenterTabs.Details: + // + if ( + isNullOrUndefined(this.detailsModel) && + !isNullOrUndefined(this.updateModel) && + from === VQueryPresenterTabs.Update + ) { + // + this.detailsModel = { + ...this.updateModel, + }; + } + + // + this.addModel = undefined; + this.updateModel = undefined; + break; + } + } + + /** + * Here We Do All Items Based Actions ... + * + * @param info + * @returns + */ + async doItemActions(info: { id: string; data: XTagDto }): Promise { + // + if (isNullOrUndefined(info) || isNullOrEmptyString(info.id)) { + return; + } + + // + let isKnown = false; + + // + // Details ... + isKnown = info.id === defaultDetailsSlideOption.id; + if (isKnown) { + // + this.detailsModel = { + ...info.data, + }; + + // + this.sendQueryPresenterAction( + VQueryPresenterAction.ChangeTab, + VQueryPresenterTabs.Details + ); + } + + // + // Edit ... + isKnown = + info.id === defaultEditSlideOption.id || + info.id === defaultEditFabButton.id; + if (isKnown) { + // + if (!isNullOrUndefined(info.data)) { + // + this.updateModel = { + ...info.data, + }; + } else if ( + !isNullOrUndefined(this.detailsModel) && + this.activeTab === VQueryPresenterTabs.Details + ) { + // + this.updateModel = { + ...this.detailsModel, + }; + } + + // + if (!isNullOrUndefined(this.updateModel)) { + // + this.sendQueryPresenterAction( + VQueryPresenterAction.ChangeTab, + VQueryPresenterTabs.Update + ); + } + } + + // + // Remove ... + isKnown = + info.id === defaultRemoveSlideOption.id || + info.id === defaultRemoveFabButton.id; + if (isKnown) { + // + if ( + isNullOrUndefined(info.data) && + !isNullOrUndefined(this.detailsModel) && + this.activeTab === VQueryPresenterTabs.Details + ) { + // + info.data = { + ...this.detailsModel, + }; + } + + // + if (!isNullOrUndefined(info.data)) { + await this.handleRemoveModel(info.data); + } + } + } + + /** + * Handle Specified Param ... + * + * @param identifier + * @param acttion + */ + async handleParamAction(identifier: string, action: string): Promise { + await super.handleParamAction(identifier, action); + + // + if (isNullOrEmptyString(identifier) || isNullOrEmptyString(action)) { + // + this.hasParams = false; + return; + } + + // + let model = await this.handleGetSpecifiedModel(identifier, true); + if (!isNullOrUndefined(model)) { + // + this.paramAction = action; + this.detailsModel = { ...model }; + this.activeTab = VQueryPresenterTabs.Details; + this.sendQueryPresenterAction( + VQueryPresenterAction.ChangeTab, + this.activeTab + ); + } else { + this.hasParams = false; + } + } + + /** + * Handle Removing Model ... + * + * @param model + */ + async handleRemoveModel(model: XTagDto) { + // + await this.doConfirmedAction(async () => { + // + await this.setLoadingState(true); + + // + try { + // + const connectionId = + this.sharedService.saherElmAPIService.tags.tagEntityPushService + .connectionId; + + // + await this.getValueAsync( + this.sharedService.saherElmAPIService.tags.tagProviderService.remove( + model.id, + connectionId + ) + ); + } catch (err) { + // + await this.handleCustomError('RemoveModelError'); + return; + } + + // + this.sendQueryPresenterAction(VQueryPresenterAction.RemoveModel, { + model, + actions: [], + key: nameof('id'), + }); + + // + this.showSuccess(); + await this.setLoadingState(false); + + // + // Handle Change Tabs if in Details ... + if ( + this.activeTab === VQueryPresenterTabs.List || + this.activeTab === VQueryPresenterTabs.Details + ) { + // + this.prevTab = undefined; + this.sendQueryPresenterAction( + VQueryPresenterAction.ChangeTab, + VQueryPresenterTabs.List + ); + } + }); + } + + /** + * Handle Running toolbar Back Handler on Specified Case + * when User Pressed Escape Button ... + * + * @param event + * @returns + */ + async handleGlobalEscapePressed(event: any) { + // + if ( + this.lock || + !this.toolbarHasBack || + !this.toolbarShowBack || + !isFunction(this.toolbarBackHandler) || + [VQueryPresenterTabs.Add, VQueryPresenterTabs.Update].includes( + this.activeTab + ) + ) { + return; + } + + // + await this.toolbarBackHandler(); + } + //#endregion + + // + //#region Private ... + private provideItemOptions(userId: string, isAdmin: boolean, item: XTagDto) { + // + let result: Array = []; + + // + // Validate ... + if (!isNullOrUndefined(item) && !isNullOrEmptyString(userId)) { + // + const canEdit = isAdmin; + const canRemove = isAdmin; + + // + // Details ... + result.push({ + ...defaultDetailsSlideOption, + slot: 'start', + disabled: this.loading || this.uiDisabled, + } as XSimpleListItemSlideOption); + + // + // Edit ... + result.push({ + ...defaultEditSlideOption, + slot: 'start', + disabled: this.loading || this.uiDisabled || !canEdit, + } as XSimpleListItemSlideOption); + + // + // Remove ... + result.push({ + ...defaultRemoveSlideOption, + slot: 'end', + disabled: this.loading || this.uiDisabled || !canRemove, + } as XSimpleListItemSlideOption); + } + + // + return result; + } + + isPushServiceConnected = false; + private async handleConnectPushService() { + // + // Preparing Connections ... + try { + // + // Check Connected Before ... + if ( + this.sharedService.saherElmAPIService.tags.tagEntityPushService.isConnected() + ) { + this.isPushServiceConnected = true; + } else { + // + this.isPushServiceConnected = true; + await this.sharedService.saherElmAPIService.tags.tagEntityPushService.connect(); + } + } catch { + // + await this.handleCustomError('PushServiceConnection'); + return; + } + + // + // Check Connected ... + if (!this.isPushServiceConnected) { + return; + } + + // + // On Added ... + this.applyTakeUntilWrapper( + this.sharedService.saherElmAPIService.tags.tagEntityPushService.onAdded$ + ).subscribe(async (event) => { + // + if (isNullOrUndefined(event)) { + return; + } + }); + + // + // On Removed ... + this.applyTakeUntilWrapper( + this.sharedService.saherElmAPIService.tags.tagEntityPushService.onDeleted$ + ).subscribe(async (event) => { + // + if (isNullOrUndefined(event) && isNullOrUndefined(this.dtoQueryResult)) { + return; + } + + // + // Check if Model Exists in Current Context or not ... + let model = this.dtoQueryResult.items.find( + (i) => i.id === event.model.id + ); + const modelP = { ...model }; + if (!isNullOrUndefined(model) && !isNullOrUndefined(modelP)) { + // + const actions = await this.prepareListActions(modelP); + + // + this.sendQueryPresenterAction(VQueryPresenterAction.RemoveModel, { + actions, + model: modelP, + key: nameof('id'), + }); + } + + // + // Check Details or Edit Tab ... + if ( + (!isNullOrUndefined(model) && + !isNullOrUndefined(this.detailsModel) && + this.detailsModel.id === model.id && + this.activeTab === VQueryPresenterTabs.Details) || + (!isNullOrUndefined(model) && + !isNullOrUndefined(this.updateModel) && + this.updateModel.id === model.id && + this.activeTab === VQueryPresenterTabs.Update) + ) { + // + // Force Unlock ... + this.lock = false; + + // + this.sendQueryPresenterAction( + VQueryPresenterAction.ChangeTab, + VQueryPresenterTabs.List + ); + } + }); + + // + // On Updated ... + this.applyTakeUntilWrapper( + this.sharedService.saherElmAPIService.tags.tagEntityPushService.onUpdated$ + ).subscribe(async (event) => { + // + if (isNullOrUndefined(event) || isNullOrUndefined(this.dtoQueryResult)) { + return; + } + + // + // Check if Model Exists in Current Context or not ... + let model = this.dtoQueryResult.items.find( + (i) => i.id === event.model.id + ); + if (isNullOrUndefined(model)) { + return; + } + + // + let modelP: XTagDto = undefined; + try { + // + modelP = await this.getValueAsync( + this.sharedService.saherElmAPIService.tags.tagProviderService.get( + event.model.id + ) + ); + } catch { + return; + } + + // + if (isNullOrUndefined(modelP)) { + return; + } + + // + // Preparing Model List Actions ... + const actions = await this.prepareListActions(modelP); + + // + // Updating Model on Query Presenter ... + this.sendQueryPresenterAction(VQueryPresenterAction.UpdateModel, { + actions, + model: { ...modelP }, + key: nameof('id'), + }); + + // + // Update Tab ... + if ( + !isNullOrUndefined(this.updateModel) && + modelP.id === this.updateModel.id && + this.activeTab === VQueryPresenterTabs.Update + ) { + // + this.updateModel = { + ...modelP, + }; + + // + const selector = getQuerySelectorOfSpecifiedAttribute( + VTagComponent, + this.ModelFieldNames.id, + this.updateModel.id.toString() + ); + await this.waitForElement(selector).then(async (el) => { + // + this.updateItemActionProvider.next({ + action: VBaseDtoAction.RenewModel, + payload: { ...this.updateModel }, + }); + + // + const prevTab = this.prevTab; + await this.handleStateAfterTabOpen(this.activeTab); + this.prevTab = prevTab; + }); + } + + // + // Details Tab ... + if ( + !isNullOrUndefined(this.detailsModel) && + modelP.id === this.detailsModel.id && + this.activeTab === VQueryPresenterTabs.Details + ) { + // + this.detailsModel = { + ...modelP, + }; + + // + const selector = getQuerySelectorOfSpecifiedAttribute( + VTagComponent, + this.ModelFieldNames.id, + this.detailsModel.id.toString() + ); + await this.waitForElement(selector).then(async (el) => { + // + this.detailsItemActionProvider.next({ + action: VBaseDtoAction.RenewModel, + payload: { ...this.detailsModel }, + }); + + // + const prevTab = this.prevTab; + await this.handleStateAfterTabOpen(this.activeTab); + this.prevTab = prevTab; + }); + } + }); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\tags\tags-explorer\tags-explorer.page.html + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\tags\tags-explorer\tags-explorer.page.scss + +@import '../tags-base/tags-base.provider.page.scss'; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\tags\tags-explorer\tags-explorer.page.ts + +import { Observable } from 'rxjs'; +import { toNumber } from 'x-framework-core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { XQueryDto, XQueryResultDto } from 'x-framework-identity-sdk'; +import { TagsBaseProviderPage } from '../tags-base/tags-base.provider.page'; + +@Component({ + selector: 'tags-explorer', + styleUrls: ['./tags-explorer.page.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + templateUrl: '../tags-base/tags-base.provider.page.html', +}) +export class TagsExplorerPage extends TagsBaseProviderPage { + // + //#region Props ... + //#endregion + + // + //#region LifeCycles ... + onInit(): void { + super.onInit(); + + // + this.configureAsExplorerPage(); + } + //#endregion + + // + //#region Abstractions ... + getModelTask(identifier: string): Observable { + // + return this.sharedService.saherElmAPIService.tags.tagProviderService.get( + toNumber(identifier), + ); + } + + queryModelTask(query: XQueryDto): Observable> { + return this.sharedService.saherElmAPIService.tags.tagProviderService.query( + query, + ); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\tags\tags-management\tags-management.page.html + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\tags\tags-management\tags-management.page.scss + +@import '../tags-base/tags-base.provider.page.scss'; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\tags\tags-management\tags-management.page.ts + +import { Observable } from 'rxjs'; +import { toNumber } from 'x-framework-core'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; +import { XQueryDto, XQueryResultDto } from 'x-framework-identity-sdk'; +import { TagsBaseProviderPage } from '../tags-base/tags-base.provider.page'; + +@Component({ + selector: 'tags-management', + styleUrls: ['./tags-management.page.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + templateUrl: '../tags-base/tags-base.provider.page.html', +}) +export class TagsManagementPage extends TagsBaseProviderPage { + // + //#region Props ... + //#endregion + + // + //#region LifeCycles ... + onInit(): void { + super.onInit(); + + // + this.configureAsManagementPage(); + } + //#endregion + + // + //#region Abstractions ... + getModelTask(identifier: string): Observable { + // + return this.sharedService.saherElmAPIService.tags.tagProviderService.get( + toNumber(identifier), + ); + } + + queryModelTask(query: XQueryDto): Observable> { + return this.sharedService.saherElmAPIService.tags.tagProviderService.query( + query, + ); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\tools\common.tools.ts + +import { XLocale } from 'x-framework-core'; +import { AppResourceIDs } from 'src/app/config/localization.config'; + +export function getLocaleResourceID(locale: XLocale) { + // + if (locale === 'en-US') { + return AppResourceIDs.english; + } else if (locale === 'fa-IR') { + return AppResourceIDs.persian; + } + + // + return locale; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\webinars\webinars-page.module.ts + +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; +import { AuthGuard } from 'x-framework-identity-sdk'; +import { XCanDeactivateGuard } from 'x-framework-core'; +import { BaseRoutes } from 'src/app/config/page.config'; +import { ViewsModule } from 'src/app/views/views.module'; +import { WebinarRoutes } from 'src/app/views/webinar/v-webinar.configs'; +import { WebinarsExplorerPage } from './webinars-explorer/webinars-explorer.page'; +import { WebinarsManagementPage } from './webinars-management/webinars-management.page'; + +@NgModule({ + declarations: [WebinarsExplorerPage, WebinarsManagementPage], + imports: [ + ViewsModule, + RouterModule.forChild([ + { + pathMatch: 'full', + path: BaseRoutes.Default, + redirectTo: WebinarRoutes.WebinarsExplorer, + }, + // + // Webinars Explorer ... + { + canActivate: [AuthGuard], + component: WebinarsExplorerPage, + canDeactivate: [XCanDeactivateGuard], + path: WebinarRoutes.WebinarsExplorer, + }, + // + // Webinars Management ... + { + canActivate: [AuthGuard], + component: WebinarsManagementPage, + canDeactivate: [XCanDeactivateGuard], + path: WebinarRoutes.WebinarsManagement, + }, + { + pathMatch: 'full', + path: BaseRoutes.Unknown, + redirectTo: WebinarRoutes.Default, + }, + ]), + ], + exports: [ + ViewsModule, + RouterModule, + WebinarsExplorerPage, + WebinarsManagementPage, + ], +}) +export class WebinarsPageModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\webinars\webinars-base\webinars-base.provider.page.html + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + {{ getOwnerFullName(value) }} + + + + + + + + + + + + + + + + + + + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\webinars\webinars-base\webinars-base.provider.page.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\webinars\webinars-base\webinars-base.provider.page.ts + +import { + Component, + ViewChild, + TemplateRef, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + XRef, + nameof, + isFunction, + getFieldNames, + isNullOrUndefined, + isNullOrEmptyString, + XResourceIdentifier, + getComponentSelector, +} from 'x-framework-core'; +import { + XFileDto, + XPersonDto, + XWebinarDto, + XWebinarType, +} from 'x-saherelm-api-sdk'; +import { + XListItem, + XSlotName, + XButtonType, + XFabButtonSide, + XListItemOptionSlot, +} from 'x-framework-components'; +import { Observable, Subject } from 'rxjs'; +import { + VBaseDtoAction, + VModelFieldTypes, + VBaseDtoActionModel, + VBaseDtoModelAction, + VBaseDtoPresentType, + VModelFieldDescriptor, + VBaseDtoModelFiredActionModel, +} from 'src/app/views/v-dto/v-dto.typings'; +import { Validators } from '@angular/forms'; +import { + defaultEditFabButton, + defaultCloseFabButton, + defaultOptionsFabItem, + defaultRemoveFabButton, + defaultConnectFabButton, +} from 'src/app/views/constants/fab.defaults'; +import { + ToWebinarDto, + ToWebinarViewDto, + getWebinarResourceID, + getWebinarTypeValues, +} from 'src/app/views/webinar/v-webinar.tools'; +import { + VWebinarViewDto, + defaultVWebinarViewDto, +} from 'src/app/views/webinar/v-webinar.typings'; +import { + toSimpleFabItems, + XSimpleFabButton, +} from 'src/app/views/typings/simple.fab.typings'; +import { + XSimpleFormSelectControlConfig, + XSimpleFormSelectControlOption, +} from 'src/app/views/typings/simple.form.typings'; +import { + defaultEditSlideOption, + defaultCloseSlideOption, + defaultRemoveSlideOption, + defaultConnectSlideOption, + defaultDetailsSlideOption, +} from 'src/app/views/constants/slide.options.defaults'; +import { + processFilledItems, + VQueryPresenterTabs, + VQueryPresenterAction, + VQueryPresenterActionModel, + VQueryPresenterActionIdentifier, + VQueryFilledItemsProcessingResult, +} from 'src/app/views/v-query-presenter/v-query-presenter.typings'; +import { XQueryDto, XQueryResultDto } from 'x-framework-identity-sdk'; +import { + VTagsPresenterModelState, + VTagsPresenterPresentType, +} from 'src/app/views/tags/v-tags-presenter/v-tags-presenter.typings'; +import { sendAction } from 'src/app/views/tools/action-provider.tools'; +import { + VFilesPresenterModelState, + VFilesPresenterPresentType, +} from 'src/app/views/files/v-files-presenter/v-files-presenter.typings'; +import { getPersonFullName } from 'src/app/views/v-person/v-person.tools'; +import { PersonPresentType } from 'src/app/views/v-person/v-person.typings'; +import { VDtoCommonProperties } from 'src/app/views/typings/dto.common.typings'; +import { VPagePresenter } from 'src/app/views/v-page-presenter/v-page-presenter.component'; +import { getQuerySelectorOfSpecifiedAttribute } from 'src/app/views/typings/common.typings'; +import { XSimpleListItemSlideOption } from 'src/app/views/typings/simple.list-item.typings'; +import { VQueryPresenterBasePage } from 'src/app/views/v-query-presenter/v-query-presenter.base.page'; +import { VWebinarViewComponent } from 'src/app/views/webinar/v-webinar-view/v-webinar-view.component'; +import { VWebinarModalContext } from 'src/app/views/webinar/v-webinar-connect/v-webinar-connect.typings'; +import { VWebinarConnectComponent } from 'src/app/views/webinar/v-webinar-connect/v-webinar-connect.component'; + +@Component({ + template: '', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export abstract class WebinarsBaseProviderPage extends VQueryPresenterBasePage { + // + //#region Props ... + toolbarShowTitle = true; + toolbarShowSubTitle = true; + titleRes = this.ResourceIDs.app_name; + toolbarSubTitle = this.resourceProvider( + this.AppResourceIDs.webinars_management_page_title, + ); + toolbarTitle = this.resourceProvider(this.titleRes); + + // + @ViewChild('pagePresenter') + pagePresenter!: VPagePresenter; + + // + readonly TagsPresenterPresertTypes = Object.assign( + VTagsPresenterPresentType, + {}, + ); + readonly FilesPresenterPresertTypes = Object.assign( + {}, + VFilesPresenterPresentType, + ); + readonly ButtonTypes = Object.assign(XButtonType, {}); + readonly ModelFieldTypes = Object.assign(VModelFieldTypes); + readonly ModelFieldNames = getFieldNames(defaultVWebinarViewDto); + readonly PersonPresentTypes = Object.assign(PersonPresentType, {}); + readonly BasePresentTypes = Object.assign(VBaseDtoPresentType, {}); + + // + // Prepare Commonly Used Properties for Dto Actions ... + // we can Override this for other Purposes ... + commonViewsProperties: VDtoCommonProperties = { + isInPage: false, + wrapWithCard: false, + labelColor: undefined, + valueColor: undefined, + fieldFormButtonPosition: undefined, + fieldFormIcon: undefined, + fieldFormIconColor: undefined, + fieldFormApplyIcon: undefined, + fieldFormApplyIconColor: undefined, + fieldFormCancelIcon: undefined, + fieldFormCancelIconColor: undefined, + removeCardMargin: undefined, + cardForegroundColor: undefined, + cardCssClass: undefined, + cardColor: undefined, + cardActionProvider: undefined, + formAppearance: undefined, + formActionProvider: undefined, + formRegularColor: undefined, + formAccentColor: undefined, + formHintColor: undefined, + formActiveColor: undefined, + formValidColor: undefined, + formInvalidColor: undefined, + formPendingColor: undefined, + formFillColor: undefined, + formDisabledColor: undefined, + formDisabledFillColor: undefined, + showFormCancelButton: undefined, + formCancelButtonResource: undefined, + formCancelButtonColor: undefined, + formResetButtonResource: undefined, + formResetButtonColor: undefined, + formActionButtonAddResource: undefined, + formActionButtonUpdateResource: undefined, + formActionButtonColor: undefined, + }; + + // + //#region Query Presenter Props ... + modelIdentifier: string = nameof('id'); + listTabCardTitle: XResourceIdentifier = this.AppResourceIDs.webinars; + + /** + * Presenter Component Action Provider ... + */ + queryPresenterActionProvider = new Subject(); + //#endregion + //#endregion + + /** + * Specified How to get Specific Item using Model Identifier ... + */ + abstract getModelTask(identifier: string): Observable; + + /** + * Specified How to Query Items ... + */ + abstract queryModelTask(query: XQueryDto): Observable>; + + /** + * Param Recieved Action ... + */ + paramAction: string = undefined; + + // + tagsState: VTagsPresenterModelState; + filesState: VFilesPresenterModelState; + + // + //#region LifeCycles ... + async afterViewInit(): Promise { + await super.afterViewInit(); + + // + this.applyTakeUntilWrapper( + this.managerService.settingsService.isMobile$, + ).subscribe((res) => { + // + if (!isNullOrUndefined(this.activeTab)) { + // + switch (this.activeTab) { + // + case VQueryPresenterTabs.List: + sendAction({ + provider: this.listItemActionProvider, + action: VBaseDtoAction.Refresh, + }); + break; + } + } + }); + + // + await this.handleConnectPushService(); + } + //#endregion + + // + //#region Providers ... + //#endregion + + // + //#region List ... + /** + * Provides List Item Template ... + */ + @ViewChild('itemTemplate') + itemTemplate!: TemplateRef; + + /** + * Provides List Item Template ... + */ + @ViewChild('listItemTemplate') + listItemTemplate!: TemplateRef; + + // + //#region Owner Custom Template for List Item View ... + private LIST_OWNER_REF: TemplateRef; + + @ViewChild('listOwnerRef') + set listOwnerRef(v: TemplateRef) { + // + this.LIST_OWNER_REF = v; + this.applyListOwnerRef(); + } + get listOwnerRef() { + return this.LIST_OWNER_REF; + } + //#endregion + + /** + * Query Object for Fetching Data ... + */ + query: XQueryDto = { + page: 1, + filter: '', + pageSize: this.config.defaultPageSize, + sortBy: nameof('createdOn'), + }; + + /** + * a Flag for Detect Has Query result or not ... + */ + hasQueryResultError: boolean = false; + + /** + * Fetching Data Result Model ... + */ + dtoQueryResult: XQueryResultDto = undefined; + + /** + * Specified Action Provider ... + */ + listItemActionProvider = new Subject(); + + /** + * Specified View Properties ... + */ + listItemViewProperties: VDtoCommonProperties = { + ...this.commonViewsProperties, + }; + + /** + * Specified Field Descriptors for List Items ... + */ + listItemViewFieldDescriptors: VModelFieldDescriptor[] = [ + // + // Type ... + { + hide: false, + hideLabel: false, + allowFieldForm: false, + key: this.ModelFieldNames.type, + value: defaultVWebinarViewDto.type, + label: this.AppResourceIDs.type, + type: this.ModelFieldTypes.Number, + valueProvider: (value) => { + return this.resourceProvider(getWebinarResourceID(value)); + }, + } as VModelFieldDescriptor, + // + // Title ... + { + hide: false, + hideLabel: false, + allowFieldForm: false, + key: this.ModelFieldNames.title, + value: defaultVWebinarViewDto.title, + label: this.AppResourceIDs.title, + type: this.ModelFieldTypes.String, + } as VModelFieldDescriptor, + // + // Description ... + { + hide: false, + hideLabel: false, + allowFieldForm: false, + key: this.ModelFieldNames.description, + value: defaultVWebinarViewDto.description, + label: this.AppResourceIDs.description, + type: this.ModelFieldTypes.String, + } as VModelFieldDescriptor, + // + // Owner ... + { + hide: false, + hideLabel: false, + showContentBelowLabel: false, + key: this.ModelFieldNames.owner, + value: defaultVWebinarViewDto.owner, + label: this.AppResourceIDs.author, + type: this.ModelFieldTypes.Object, + template: this.listOwnerRef, + hasCustomTemplate: true, + } as VModelFieldDescriptor, + ]; + + /** + * Get Webinar Owner Full Name ... + * + * @param model + * @returns + */ + getOwnerFullName(model: XPersonDto) { + return getPersonFullName(model); + } + + /** + * Hold each Items Provided Actions based on Identifier ... + */ + itemsActions = new Map>>(); + + /** + * Prepare Item Actions based on Slot ... + */ + getSlottedItemActions(slot: XSlotName, model: VWebinarViewDto) { + // + let result: Array = []; + + // + if ( + isNullOrUndefined(model) || + isNullOrEmptyString(slot) || + isNullOrEmptyString(model.id) || + isNullOrUndefined(this.itemsActions) || + !this.itemsActions.has(model.id) + ) { + return result; + } + + // + let _slot: XListItemOptionSlot = + slot === XSlotName.START || slot === XSlotName.TOP ? 'start' : 'end'; + + // + result = this.itemsActions + .get(model.id) + .value.filter((a) => a.slot === _slot); + + // + return result; + } + + /** + * Handle Non As List Item's Action ... + * + * @param id + * @param data + */ + async handleItemAction(id: string, data: VWebinarViewDto) { + // + // Validate ... + if ( + this.loading || + this.uiDisabled || + isNullOrEmptyString(id) || + isNullOrUndefined(data) + ) { + return; + } + + // + await this.doItemActions({ id, data }); + } + + /** + * Apply List Owner Template to Descriptors ... + * + * @returns + */ + applyListOwnerRef() { + // + if (isNullOrUndefined(this.LIST_OWNER_REF)) { + return; + } + + // + this.listItemViewFieldDescriptors = this.listItemViewFieldDescriptors.map( + (fd) => { + // + return { + ...fd, + template: + fd.key === this.ModelFieldNames.owner + ? this.LIST_OWNER_REF + : fd.template, + }; + }, + ); + } + + /** + * Handle Retrieve Data based on XQuery ... + * + * @param query + * @returns + */ + async handleGetQueryResult( + query: XQueryDto, + ): Promise> { + // + let result: XQueryResultDto = undefined; + + // + // Start Loading ... + this.hasQueryResultError = false; + await this.setLoadingState(true); + + // + // Try to Prepare Result ... + try { + // + // Retrieve Webinar Dto Result ... + this.dtoQueryResult = await this.getValueAsync( + this.queryModelTask(query), + ); + + // + // Prepare Result ... + result = { + ...result, + page: this.dtoQueryResult.page, + pageSize: this.dtoQueryResult.pageSize, + totalItems: this.dtoQueryResult.totalItems, + totalPages: this.dtoQueryResult.totalPages, + totalFilteredItems: this.dtoQueryResult.totalFilteredItems, + totalFilteredPages: this.dtoQueryResult.totalFilteredPages, + }; + + // + // Converts XWebinarDto to VWebinarViewDto ... + result.items = this.dtoQueryResult.items.map((i) => + ToWebinarViewDto(i, this.managerService.currentLocale), + ); + + // + // Remove Scrolling Info ... + if ( + !isNullOrUndefined(this.query) && + this.query.filter !== query.filter + ) { + // + this.removeScrollingInfo(); + this.infinityScrollDisabled = true; + } + + // + this.query = query; + } catch { + // + this.hasQueryResultError = true; + await this.handleCustomError('QueryWebinarsError'); + return result; + } + + // + // Stop Loading ... + await this.setLoadingState(false); + + // + return result; + } + + /** + * Get Specified Model Based on Identifier ... + * + * @param identifier + * @param applyState + * @returns + */ + async handleGetSpecifiedModel(identifier: string, applyState = false) { + // + if (isNullOrEmptyString(identifier)) { + return; + } + + // + let response: any = undefined; + try { + // + if (applyState) { + await this.setLoadingState(true); + } + + // + response = await this.getValueAsync(this.getModelTask(identifier)); + + // + } catch { + // + await this.handleCustomError('GetWebinarError'); + return; + } + + // + if (applyState) { + await this.setLoadingState(false); + } + + // + return response; + } + + /** + * Provide List Actions for Specific Model ... + * + * @param model + * @returns + */ + async prepareListActions(model: VWebinarViewDto) { + // + const userId = await this.getValueAsync(this.sharedService.userId$); + const isAdmin = await this.getValueAsync(this.sharedService.isAdmin$); + + // + return this.provideItemOptions(userId, isAdmin, model); + } + + /** + * Validate List Items ... + * + * @param items + * @returns + */ + async validateAndPrepareListItems( + items: Array>, + ): Promise>> { + // + let result: Array> = []; + + // + // Preparing Slide Options ... + result = await Promise.all([ + ...items.map(async (i) => { + return { + ...i, + slideOptions: [...(await this.prepareListActions(i.data))], + }; + }), + ]); + + // + return result; + } + + /** + * Prepare Result for Non As List Presenting Items ... + * + * @param current + * @param recieved + */ + async requestFillingItems( + exists: Array, + recieved: Array, + ): Promise> { + // + let result: VQueryFilledItemsProcessingResult = + processFilledItems(exists, recieved, 'id'); + + // + // Filling Actions ... + if (!isNullOrUndefined(result)) { + // + // News Items Actions ... + if (!isNullOrUndefined(result.news) && this.hasChild(result.news)) { + // + result.news.forEach(async (i) => { + // + const actions = await this.prepareListActions(i); + this.itemsActions.set(i.id, { value: actions }); + }); + } + + // + // Changed Items Actions ... + if (!isNullOrUndefined(result.changed) && this.hasChild(result.changed)) { + // + result.changed.forEach(async (i) => { + // + if (this.itemsActions.has(i.identifier)) { + // + const actions = await this.prepareListActions(i.data); + this.itemsActions.set(i.identifier, { value: actions }); + } + }); + } + } + + // + return result; + } + + /** + * When Non as List Items Filled, this method call for Change Detection ... + * + * @param ids + */ + async handleRefreshViews(ids: Array): Promise { + await super.handleRefreshViews(ids); + + // + if (isNullOrUndefined(ids) || !this.hasChild(ids)) { + return; + } + + // + const selector = getComponentSelector(VWebinarViewComponent); + ids.forEach((id) => { + // + const iSelector = `${selector}[${nameof( + 'id', + )}="${id}"][${nameof('presentType')}="${ + VBaseDtoPresentType.AsView + }"]`; + this.waitForElement(iSelector).then((el) => { + this.listItemActionProvider.next({ action: VBaseDtoAction.Refresh }); + }); + }); + } + //#endregion + + // + //#region Add ... + /** + * Provides Template for Adding Items ... + */ + @ViewChild('addItemTemplate') + addItemTemplate!: TemplateRef; + + /** + * a Model for Providing to Add ... + */ + addModel: VWebinarViewDto = { + ...defaultVWebinarViewDto, + language: this.managerService.currentLocale, + }; + + /** + * Specified Action Provider ... + */ + addItemActionProvider = new Subject(); + + /** + * Specify View Properties for Add ... + */ + addModelViewProperties: VDtoCommonProperties = { + ...this.commonViewsProperties, + isInPage: true, + wrapWithCard: true, + }; + + /** + * Describe Add Model Fields ... + */ + addModelViewFieldDescriptors: VModelFieldDescriptor[] = [ + // + // ID ... + { + hide: true, + hideLabel: true, + allowFieldForm: false, + key: this.ModelFieldNames.id, + label: this.ResourceIDs.id, + value: defaultVWebinarViewDto.id, + type: this.ModelFieldTypes.String, + } as VModelFieldDescriptor, + // + // Language ... + { + hide: true, + hideLabel: true, + allowFieldForm: false, + key: this.ModelFieldNames.language, + label: this.ResourceIDs.language, + value: defaultVWebinarViewDto.language, + type: this.ModelFieldTypes.String, + } as VModelFieldDescriptor, + // + // Owner ID ... + { + hide: true, + hideLabel: true, + allowFieldForm: false, + key: this.ModelFieldNames.ownerId, + label: this.AppResourceIDs.author_id, + value: defaultVWebinarViewDto.ownerId, + type: this.ModelFieldTypes.String, + } as VModelFieldDescriptor, + // + // Type ... + { + hide: false, + hideLabel: false, + allowFieldForm: true, + key: this.ModelFieldNames.type, + value: defaultVWebinarViewDto.type, + label: this.AppResourceIDs.type, + type: this.ModelFieldTypes.Number, + valueProvider: (value) => { + return this.resourceProvider(getWebinarResourceID(value)); + }, + formControlConfig: { + index: 0, + propName: this.ModelFieldNames.type, + type: { + type: this.FormControlTypes.Select, + config: { + multiple: false, + options: getWebinarTypeValues().map((k) => { + return { + value: k, + viewValue: this.resourceProvider(getWebinarResourceID(k)), + } as XSimpleFormSelectControlOption; + }), + } as XSimpleFormSelectControlConfig, + }, + validators: { + validators: [Validators.required], + }, + eventHandlers: {}, + appearance: { + hidden: false, + disabled: false, + label: this.AppResourceIDs.type, + placeholder: this.AppResourceIDs.type, + appearance: this.FormControlAppearances.Outline, + icons: { + prefix: { + applyStateColor: true, + name: this.IconNames.label, + color: this.ColorNames.Tertiary, + }, + }, + }, + }, + } as VModelFieldDescriptor, + // + // Title ... + { + hide: false, + hideLabel: false, + allowFieldForm: true, + key: this.ModelFieldNames.title, + value: defaultVWebinarViewDto.title, + label: this.AppResourceIDs.title, + type: this.ModelFieldTypes.String, + formControlConfig: { + index: 1, + propName: this.ModelFieldNames.title, + type: { + type: this.FormControlTypes.Text, + config: {}, + }, + validators: { + validators: [Validators.required], + }, + eventHandlers: {}, + focusHandler: new Subject(), + appearance: { + hidden: false, + disabled: false, + placeholder: this.AppResourceIDs.title, + appearance: this.FormControlAppearances.Outline, + icons: { + prefix: { + applyStateColor: true, + name: this.IconNames.label, + color: this.ColorNames.Tertiary, + }, + }, + }, + }, + } as VModelFieldDescriptor, + // + // Description ... + { + hide: false, + hideLabel: false, + allowFieldForm: true, + key: this.ModelFieldNames.description, + value: defaultVWebinarViewDto.description, + label: this.AppResourceIDs.description, + type: this.ModelFieldTypes.String, + formControlConfig: { + index: 2, + propName: this.ModelFieldNames.description, + type: { + type: this.FormControlTypes.Text, + config: {}, + }, + validators: { + validators: [Validators.required], + }, + eventHandlers: {}, + appearance: { + hidden: false, + disabled: false, + placeholder: this.AppResourceIDs.description, + appearance: this.FormControlAppearances.Outline, + icons: { + prefix: { + applyStateColor: true, + name: this.IconNames.label, + color: this.ColorNames.Tertiary, + }, + }, + }, + }, + } as VModelFieldDescriptor, + ]; + + /** + * Handle Locking Page when a Form Lock Content ... + * + * @param event + */ + handleAddViewLockNotifier(event: { id: string; state: boolean }) { + this.lock = event.state; + } + + /** + * Handle Cancel Add Form View ... + * + * @param event + */ + async handleAddViewActionFired(event: VBaseDtoModelFiredActionModel) { + // + // Cancel Form ... + if (event.action === VBaseDtoModelAction.Cancel) { + // + // Since Cancel only Fired when Lock Breaked, here we do not need to handle it ... + this.sendQueryPresenterAction( + VQueryPresenterAction.ChangeTab, + this.prevTab || VQueryPresenterTabs.List, + ); + } + // + // Reset Form ... + else if (event.action === VBaseDtoModelAction.Reset) { + } + // + // Add Or Update Form ... + else if (event.action === VBaseDtoModelAction.AddOrUpdate) { + // + const pModel: Required = { + ...event.payload.model, + } as Required; + await this.handleAddModel(pModel); + } + } + + /** + * Do Add Model ... + * + * @param model + * @returns + */ + async handleAddModel( + model: VWebinarViewDto, + tags?: Array, + files?: Array, + ) { + // + if (isNullOrUndefined(model)) { + return; + } + + // + await this.setLoadingState(true); + + // + let response: XWebinarDto = undefined; + try { + // + // Prepare Dto Model to Add ... + const dtoModel = ToWebinarDto(model); + + // + // Remove ID ... + delete dtoModel.id; + + // + // Preparing Tags ... + let pTags: Array = []; + if (!isNullOrUndefined(tags) && this.hasChild(tags)) { + pTags = [...tags]; + } + + // + // Preparing Files ... + let pFiles: Array = []; + if (!isNullOrUndefined(files) && this.hasChild(files)) { + pFiles = [...files]; + } + + // + // Retrieve Connection ID ... + const connectionId = + this.sharedService.saherElmAPIService.webinars.webinarEntityPushService + .connectionId; + + // + // Filling Owner ID ... + dtoModel.ownerId = await this.getValueAsync(this.sharedService.userId$); + + // + // Issue Task ... + response = await this.getValueAsync( + this.sharedService.saherElmAPIService.webinars.webinarProviderService.create( + dtoModel, // Model ... + pTags, // Tags ... + pFiles, // Files ... + connectionId, // Connection Id ... + ), + ); + } catch (ex) { + // + await this.handleCustomError('AddModelError'); + return; + } + + // + this.lock = false; + this.addModel = undefined; + this.showSuccess(); + await this.setLoadingState(false); + this.sendQueryPresenterAction(VQueryPresenterAction.ResetQuery, true); + } + //#endregion + + // + //#region Update ... + /** + * Provides Template for Update Items ... + */ + @ViewChild('updateItemTemplate') + updateItemTemplate!: TemplateRef; + + /** + * a Model for Providing to Update ... + */ + updateModel: VWebinarViewDto = undefined; + + /** + * Specified Action Provider ... + */ + updateItemActionProvider = new Subject(); + + /** + * Specify View Properties for Update ... + */ + updateModelViewProperties: VDtoCommonProperties = { + ...this.commonViewsProperties, + isInPage: true, + wrapWithCard: true, + }; + + /** + * Describe Update Model Fields ... + */ + updateModelViewFieldDescriptors: VModelFieldDescriptor[] = [ + // + // ID ... + { + hide: true, + hideLabel: true, + allowFieldForm: false, + label: this.ResourceIDs.id, + key: this.ModelFieldNames.id, + value: defaultVWebinarViewDto.id, + type: this.ModelFieldTypes.String, + } as VModelFieldDescriptor, + // + // Language ... + { + hide: true, + hideLabel: true, + allowFieldForm: false, + label: this.ResourceIDs.language, + type: this.ModelFieldTypes.String, + key: this.ModelFieldNames.language, + value: defaultVWebinarViewDto.language, + } as VModelFieldDescriptor, + // + // Type ... + { + hide: false, + hideLabel: false, + allowFieldForm: true, + key: this.ModelFieldNames.type, + label: this.AppResourceIDs.type, + type: this.ModelFieldTypes.Number, + value: defaultVWebinarViewDto.type, + valueProvider: (value) => { + return this.resourceProvider(getWebinarResourceID(value)); + }, + formControlConfig: { + index: 0, + propName: this.ModelFieldNames.type, + type: { + type: this.FormControlTypes.Select, + config: { + multiple: false, + options: getWebinarTypeValues().map((k) => { + return { + value: k, + viewValue: this.resourceProvider(getWebinarResourceID(k)), + } as XSimpleFormSelectControlOption; + }), + } as XSimpleFormSelectControlConfig, + }, + validators: { + validators: [Validators.required], + }, + eventHandlers: {}, + appearance: { + hidden: false, + disabled: false, + label: this.AppResourceIDs.type, + placeholder: this.AppResourceIDs.type, + appearance: this.FormControlAppearances.Outline, + icons: { + prefix: { + applyStateColor: true, + name: this.IconNames.label, + color: this.ColorNames.Tertiary, + }, + }, + }, + }, + } as VModelFieldDescriptor, + // + // Title ... + { + hide: false, + hideLabel: false, + allowFieldForm: true, + key: this.ModelFieldNames.title, + label: this.AppResourceIDs.title, + type: this.ModelFieldTypes.String, + value: defaultVWebinarViewDto.title, + formControlConfig: { + index: 1, + propName: this.ModelFieldNames.title, + type: { + type: this.FormControlTypes.Text, + config: {}, + }, + validators: { + validators: [Validators.required], + }, + eventHandlers: {}, + focusHandler: new Subject(), + appearance: { + hidden: false, + disabled: false, + placeholder: this.AppResourceIDs.title, + appearance: this.FormControlAppearances.Outline, + icons: { + prefix: { + applyStateColor: true, + name: this.IconNames.label, + color: this.ColorNames.Tertiary, + }, + }, + }, + }, + } as VModelFieldDescriptor, + // + // Description ... + { + hide: false, + hideLabel: false, + allowFieldForm: true, + type: this.ModelFieldTypes.String, + key: this.ModelFieldNames.description, + label: this.AppResourceIDs.description, + value: defaultVWebinarViewDto.description, + formControlConfig: { + index: 2, + propName: this.ModelFieldNames.description, + type: { + type: this.FormControlTypes.Text, + config: {}, + }, + validators: { + validators: [Validators.required], + }, + eventHandlers: {}, + appearance: { + hidden: false, + disabled: false, + placeholder: this.AppResourceIDs.description, + appearance: this.FormControlAppearances.Outline, + icons: { + prefix: { + applyStateColor: true, + name: this.IconNames.label, + color: this.ColorNames.Tertiary, + }, + }, + }, + }, + } as VModelFieldDescriptor, + ]; + + /** + * Handle Locking Page when a Form Lock Content ... + * + * @param event + */ + handleUpdateViewLockNotifier(event: { id: string; state: boolean }) { + this.lock = event.state; + } + + /** + * Tags Actions Provider for Details Section ... + * + * @param t + * @returns + */ + detailsTagsActionsProvider: (t: string) => Array = + (t) => { + return []; + }; + + /** + * Files Actions Provider for Details Section ... + * + * @param f + * @returns + */ + detailsFilesActionsProvider: ( + f: XFileDto | File, + ) => Array = (f) => { + return []; + }; + + /** + * Handle Cancel Update Form View ... + * + * @param event + */ + async handleUpdateViewActionFired(event: VBaseDtoModelFiredActionModel) { + // + // Cancel Form ... + if (event.action === VBaseDtoModelAction.Cancel) { + // + // Since Cancel only Fired when Lock Breaked, here we do not need to handle it ... + this.sendQueryPresenterAction( + VQueryPresenterAction.ChangeTab, + this.prevTab || VQueryPresenterTabs.List, + ); + } + // + // Reset Form ... + else if (event.action === VBaseDtoModelAction.Reset) { + } + // + // Add Or Update Form ... + else if (event.action === VBaseDtoModelAction.AddOrUpdate) { + // + const pModel: Required = { + ...event.payload.model, + } as Required; + await this.handleUpdateModel(pModel); + } + } + + /** + * Do Update Model ... + * + * @param model + * @returns + */ + async handleUpdateModel(model: VWebinarViewDto) { + // + const prevTab = this.prevTab; + + // + if (isNullOrUndefined(model)) { + return; + } + + // + await this.setLoadingState(true); + + // + let response: XWebinarDto = undefined; + try { + // + // Prepare Dto Model to Add ... + const dto = + !isNullOrUndefined(this.dtoQueryResult) && + !isNullOrUndefined(this.dtoQueryResult.items) && + this.hasChild(this.dtoQueryResult.items) + ? this.dtoQueryResult.items.find((i) => i.id === model.id) + : undefined; + if (isNullOrUndefined(dto)) { + throw 'UpdateModelError'; + } + + // + // Preparing Update Model ... + const dtoModel = ToWebinarDto(model, dto); + + // + // Retrieve Connection ID ... + const connectionId = + this.sharedService.saherElmAPIService.webinars.webinarEntityPushService + .connectionId; + + // + // Filling Owner ID ... + dtoModel.ownerId = await this.getValueAsync(this.sharedService.userId$); + + // + // Issue Task ... + response = await this.getValueAsync( + this.sharedService.saherElmAPIService.webinars.webinarProviderService.update( + dtoModel, + connectionId, + ), + ); + } catch (ex) { + // + await this.handleCustomError('UpdateModelError'); + return; + } + + // + if (isNullOrUndefined(response)) { + // + await this.handleCustomError('UpdateModelError'); + return; + } + + // + this.lock = false; + this.updateModel = undefined; + await this.setLoadingState(false); + this.showSuccess(); + + // + this.prevTab = prevTab; + model = ToWebinarViewDto(response, this.managerService.currentLocale); + const actions = await this.prepareListActions(model); + + // + if (this.prevTab === VQueryPresenterTabs.Details) { + // + this.detailsModel = { + ...model, + }; + } + + // + this.sendQueryPresenterAction(VQueryPresenterAction.UpdateModel, { + model, + actions, + key: nameof('id'), + }); + + // + // Check Handle Back ... + if ( + this.toolbarHasBack && + this.toolbarShowBack && + isFunction(this.toolbarBackHandler) + ) { + await this.toolbarBackHandler(); + } + } + //#endregion + + // + //#region Details ... + /** + * Provides Template for Details Items ... + */ + @ViewChild('detailsItemTemplate') + detailsItemTemplate!: TemplateRef; + + /** + * a Model for Providing to Details ... + */ + detailsModel: VWebinarViewDto = undefined; + + /** + * Specified Action Provider ... + */ + detailsItemActionProvider = new Subject(); + + /** + * Specify View Properties for Details ... + */ + detailsModelViewProperties: VDtoCommonProperties = { + ...this.commonViewsProperties, + isInPage: true, + wrapWithCard: true, + }; + + /** + * Describe Details Model Fields ... + */ + detailsModelViewFieldDescriptors: VModelFieldDescriptor[] = []; + + /** + * Provides Card Title for Specified Model ... + * + * @param model + * @returns + */ + getModelCardTitle(model: VWebinarViewDto) { + // + // Since card title's are Rescource ID's ... + return this.applyLocale(`${model.title} `); + } + //#endregion + + // + //#region Handlers ... + /** + * Handle Errors ... + * + * @param error + */ + async handleCustomError( + error?: VQueryPresenterActionIdentifier, + ): Promise { + // + await this.setLoadingState(false); + + // + // TODO: Implement this ... + console.log('handle Custom Error: ', error); + } + + /** + * Override this for adding additional futures ... + * + * @param tab + */ + async handleStateAfterTabOpen(tab: number) { + await super.handleStateAfterTabOpen(tab); + + // + let selector: string = undefined; + let fabs: XSimpleFabButton[] = []; + let model: VWebinarViewDto = undefined; + + // + let actions = await toSimpleFabItems(this.actions); + this.closeActions(); + this.actions = undefined; + + // + const userId = await this.getValueAsync(this.sharedService.userId$); + const isAdmin = await this.getValueAsync(this.sharedService.isAdmin$); + const isAgent = await this.getValueAsync(this.sharedService.isAgent$); + const isAdminAgent = isAdmin || isAgent; + + // + switch (tab) { + // + case VQueryPresenterTabs.List: + // + // if Show Items as List ... + if (this.showItemsAsList) { + // + sendAction({ + provider: this.listItemActionProvider, + action: VBaseDtoAction.SetProperties, + payload: { + props: { ...this.listItemViewProperties }, + }, + }); + } else { + // + // Call each Items Action Provider Separately ... + + // + // Prepare Non As List Items Properties ... + const properties: VDtoCommonProperties = { + ...this.listItemViewProperties, + isInPage: true, + wrapWithCard: true, + cardShowActions: true, // Since Each Items has Separate Actions ... + }; + + // + sendAction({ + provider: this.listItemActionProvider, + action: VBaseDtoAction.SetProperties, + payload: { + props: { ...properties }, + }, + }); + } + + // + break; + + // + case VQueryPresenterTabs.Add: + // + this.addModel = { + ...defaultVWebinarViewDto, + language: this.managerService.currentLocale, + }; + + // + sendAction({ + provider: this.addItemActionProvider, + action: VBaseDtoAction.SetProperties, + payload: { + props: { ...this.addModelViewProperties }, + }, + }); + break; + + // + case VQueryPresenterTabs.Update: + // + model = { ...this.updateModel }; + + // + sendAction({ + provider: this.updateItemActionProvider, + action: VBaseDtoAction.SetProperties, + payload: { + props: { ...this.updateModelViewProperties }, + }, + }); + break; + + // + case VQueryPresenterTabs.Details: + // + model = { ...this.detailsModel }; + + // + sendAction({ + provider: this.detailsItemActionProvider, + action: VBaseDtoAction.SetProperties, + payload: { + props: { ...this.detailsModelViewProperties }, + }, + }); + + // + // Adding Custom Actions ... + + // + let isOwner = this.detailsModel.ownerId === userId; + let canConnect = isOwner || this.detailsModel.enabled; + let canClose = (isOwner || isAdmin) && this.detailsModel.enabled; + + // + // Connect ... + if (canConnect) { + // + fabs = [ + ...fabs, + { + ...defaultConnectFabButton, + index: 1, + side: XFabButtonSide.TOP, + handler: async () => { + await this.doItemActions({ + id: defaultConnectFabButton.id, + data: { ...this.detailsModel }, + }); + }, + }, + ]; + } + + // + // Close ... + if (canClose) { + // + fabs = [ + ...fabs, + { + ...defaultCloseFabButton, + index: 1, + side: XFabButtonSide.START, + handler: async () => { + await this.doItemActions({ + id: defaultCloseFabButton.id, + data: { ...this.detailsModel }, + }); + }, + }, + ]; + } + + // + // Actions Preparing ... + actions = { + ...(!isNullOrUndefined(actions) ? actions : defaultOptionsFabItem), + childs: [ + ...(!isNullOrUndefined(actions) && this.hasChild(actions.childs) + ? actions.childs + : []), + ...fabs, + ], + }; + + // + // Issue Action if Waiting for Details ... + if (!isNullOrEmptyString(this.paramAction)) { + // + const action = this.paramAction; + this.paramAction = undefined; + + // + await this.doItemActions({ + id: action, + data: { ...this.detailsModel }, + }); + } + break; + } + + // + // Here can Handle Actions Permissions and States ... + if (!isNullOrUndefined(actions)) { + // + // Only if model Exists ... + if (!isNullOrUndefined(model)) { + // + // Retrieve Requirements ... + + // + const isOwner = model.ownerId === userId; + + // + const canEdit = isOwner || isAdmin; + const canRemove = isOwner || isAdmin; + const canClose = (isOwner || isAdmin) && model.enabled; + const canConnect = isOwner || model.enabled; + + // + // Edit ... + if ( + actions.id === defaultEditFabButton.id || + (!isNullOrUndefined(actions.childs) && + this.hasChild(actions.childs) && + actions.childs.findIndex( + (a) => a.id === defaultEditFabButton.id, + )) >= 0 + ) { + // + if (actions.id === defaultEditFabButton.id) { + actions.disabled = + !canEdit || actions.disabled || this.loading || this.uiDisabled; + } else { + // + actions.childs = [ + ...actions.childs.map((ch) => { + return { + ...ch, + disabled: + ch.id === defaultEditFabButton.id + ? !canEdit || + ch.disabled || + this.loading || + this.uiDisabled + : ch.disabled, + }; + }), + ]; + } + } + + // + // Remove ... + if ( + actions.id === defaultRemoveFabButton.id || + (!isNullOrUndefined(actions.childs) && + this.hasChild(actions.childs) && + actions.childs.findIndex( + (a) => a.id === defaultRemoveFabButton.id, + )) >= 0 + ) { + // + if (actions.id === defaultRemoveFabButton.id) { + actions.disabled = + !canRemove || actions.disabled || this.loading || this.uiDisabled; + } else { + // + actions.childs = [ + ...actions.childs.map((ch) => { + return { + ...ch, + disabled: + ch.id === defaultRemoveFabButton.id + ? !canRemove || + ch.disabled || + this.loading || + this.uiDisabled + : ch.disabled, + }; + }), + ]; + } + } + + // + // Close ... + if ( + actions.id === defaultCloseFabButton.id || + (!isNullOrUndefined(actions.childs) && + this.hasChild(actions.childs) && + actions.childs.findIndex( + (a) => a.id === defaultCloseFabButton.id, + )) >= 0 + ) { + // + if (actions.id === defaultCloseFabButton.id) { + actions.disabled = + !canClose || actions.disabled || this.loading || this.uiDisabled; + } else { + // + actions.childs = [ + ...actions.childs.map((ch) => { + return { + ...ch, + disabled: + ch.id === defaultCloseFabButton.id + ? !canClose || + ch.disabled || + this.loading || + this.uiDisabled + : ch.disabled, + }; + }), + ]; + } + } + + // + // Connect ... + if ( + actions.id === defaultConnectFabButton.id || + (!isNullOrUndefined(actions.childs) && + this.hasChild(actions.childs) && + actions.childs.findIndex( + (a) => a.id === defaultConnectFabButton.id, + )) >= 0 + ) { + // + if (actions.id === defaultConnectFabButton.id) { + actions.disabled = + !canConnect || + actions.disabled || + this.loading || + this.uiDisabled; + } else { + // + actions.childs = [ + ...actions.childs.map((ch) => { + return { + ...ch, + disabled: + ch.id === defaultConnectFabButton.id + ? !canConnect || + ch.disabled || + this.loading || + this.uiDisabled + : ch.disabled, + }; + }), + ]; + } + } + } + + // + // Update Actions ... + this.actions = { + ...actions, + }; + } + } + + /** + * Override this for adding additional futures ... + * + * @param from + * @param to + */ + async handleStateBeforeTabOpen(from: number, to: number) { + await super.handleStateBeforeTabOpen(from, to); + + // + this.closeActions(); + + // + switch (to) { + // + case VQueryPresenterTabs.List: + // + this.addModel = undefined; + this.updateModel = undefined; + this.detailsModel = undefined; + break; + + // + case VQueryPresenterTabs.Add: + // + this.updateModel = undefined; + this.detailsModel = undefined; + break; + + // + case VQueryPresenterTabs.Update: + // + this.addModel = undefined; + this.detailsModel = undefined; + break; + + // + case VQueryPresenterTabs.Details: + // + if ( + isNullOrUndefined(this.detailsModel) && + !isNullOrUndefined(this.updateModel) && + from === VQueryPresenterTabs.Update + ) { + // + this.detailsModel = { + ...this.updateModel, + }; + } + + // + this.addModel = undefined; + this.updateModel = undefined; + break; + } + } + + /** + * Here We Do All Items Based Actions ... + * + * @param info + * @returns + */ + async doItemActions(info: { + id: string; + data: VWebinarViewDto; + }): Promise { + // + if (isNullOrUndefined(info) || isNullOrEmptyString(info.id)) { + return; + } + + // + let isKnown = false; + + // + // Close ... + isKnown = + info.id === defaultCloseSlideOption.id || + info.id === defaultCloseFabButton.id; + if (isKnown) { + // + if ( + isNullOrUndefined(info.data) && + !isNullOrUndefined(this.detailsModel) && + this.activeTab === VQueryPresenterTabs.Details + ) { + // + info.data = { + ...this.detailsModel, + }; + } + + // + if (!isNullOrUndefined(info.data)) { + await this.handleCloseWebinar(info.data); + } + } + + // + // Conect ... + isKnown = + info.id === defaultConnectSlideOption.id || + info.id === defaultConnectFabButton.id; + if (isKnown) { + // + if ( + isNullOrUndefined(info.data) && + !isNullOrUndefined(this.detailsModel) && + this.activeTab === VQueryPresenterTabs.Details + ) { + // + info.data = { + ...this.detailsModel, + }; + } + + // + if (!isNullOrUndefined(info.data)) { + await this.handleConnectWebinar(info.data); + } + } + + // + // Details ... + isKnown = info.id === defaultDetailsSlideOption.id; + if (isKnown) { + // + this.detailsModel = { + ...info.data, + }; + + // + this.sendQueryPresenterAction( + VQueryPresenterAction.ChangeTab, + VQueryPresenterTabs.Details, + ); + } + + // + // Edit ... + isKnown = + info.id === defaultEditSlideOption.id || + info.id === defaultEditFabButton.id; + if (isKnown) { + // + if (!isNullOrUndefined(info.data)) { + // + this.updateModel = { + ...info.data, + }; + } else if ( + !isNullOrUndefined(this.detailsModel) && + this.activeTab === VQueryPresenterTabs.Details + ) { + // + this.updateModel = { + ...this.detailsModel, + }; + } + + // + if (!isNullOrUndefined(this.updateModel)) { + // + this.sendQueryPresenterAction( + VQueryPresenterAction.ChangeTab, + VQueryPresenterTabs.Update, + ); + } + } + + // + // Remove ... + isKnown = + info.id === defaultRemoveSlideOption.id || + info.id === defaultRemoveFabButton.id; + if (isKnown) { + // + if ( + isNullOrUndefined(info.data) && + !isNullOrUndefined(this.detailsModel) && + this.activeTab === VQueryPresenterTabs.Details + ) { + // + info.data = { + ...this.detailsModel, + }; + } + + // + if (!isNullOrUndefined(info.data)) { + await this.handleRemoveWebinar(info.data); + } + } + } + + /** + * Handle Specified Param ... + * + * @param identifier + * @param acttion + */ + async handleParamAction(identifier: string, action: string): Promise { + await super.handleParamAction(identifier, action); + + // + if (isNullOrEmptyString(identifier) || isNullOrEmptyString(action)) { + // + this.hasParams = false; + return; + } + + // + let model = await this.handleGetSpecifiedModel(identifier, true); + if (!isNullOrUndefined(model)) { + // + this.paramAction = action; + this.detailsModel = { ...model }; + this.activeTab = VQueryPresenterTabs.Details; + this.sendQueryPresenterAction( + VQueryPresenterAction.ChangeTab, + this.activeTab, + ); + } else { + this.hasParams = false; + } + } + + /** + * Close Specified Webinar if it's Open ... + * + * @param model + * @returns + */ + async handleCloseWebinar(model: VWebinarViewDto) { + // + if (isNullOrUndefined(model) || !model.enabled) { + return; + } + + // + model = { + ...model, + enabled: false, + }; + const dto = this.dtoQueryResult.items.find((dto) => dto.id === model.id); + const dtoModel = ToWebinarDto(model, dto); + if (isNullOrUndefined(dto) || isNullOrUndefined(dtoModel)) { + return; + } + + // + // Do Based on Confirm ... + await this.doConfirmedAction(async () => { + // + await this.setLoadingState(true); + + // + let response: XWebinarDto = undefined; + try { + // + const connectionId = + this.sharedService.saherElmAPIService.webinars + .webinarEntityPushService.connectionId; + response = await this.getValueAsync( + this.sharedService.saherElmAPIService.webinars.webinarProviderService.update( + dtoModel, + connectionId, + ), + ); + } catch { + // + await this.handleCustomError('CloseWebinarError'); + return; + } + + // + await this.setLoadingState(false); + + // + // Check if Response Provided ... + // Update Contents ... + if (!isNullOrUndefined(response)) { + // + const updateVDto = ToWebinarViewDto( + response, + this.managerService.currentLocale, + ); + const actions = await this.prepareListActions(updateVDto); + this.sendQueryPresenterAction(VQueryPresenterAction.UpdateModel, { + actions, + model: updateVDto, + key: nameof('id'), + }); + await this.handleStateAfterTabOpen(this.activeTab); + } + }); + } + + /** + * Handle Connecting to Webinar ... + * + * @param model + */ + async handleConnectWebinar(model: VWebinarViewDto) { + // + // Validate ... + if (isNullOrUndefined(model) || isNullOrEmptyString(model.id)) { + return; + } + + // + // Present Webinar Connect Model ... + let modal = await this.managerService.dialogService.presentModal({ + autoHeight: true, + fullScreen: true, + backdropDismiss: false, + component: VWebinarConnectComponent, + componentProps: { + // + context: VWebinarModalContext.Modal, + model: { ...model }, + toolbarTitle: this.AppResourceIDs.webinar, + }, + }); + if (!isNullOrUndefined(model)) { + this.lock = true; + } + + /** + * wait for Model Result ... + */ + const data = await modal.onDidDismiss(); + modal.remove(); + modal.dismiss(); + this.lock = false; + } + + /** + * Handle Removing Webinar ... + * + * @param model + */ + async handleRemoveWebinar(model: VWebinarViewDto) { + // + await this.doConfirmedAction(async () => { + // + await this.setLoadingState(true); + + // + try { + // + const connectionId = + this.sharedService.saherElmAPIService.webinars + .webinarEntityPushService.connectionId; + + // + await this.getValueAsync( + this.sharedService.saherElmAPIService.webinars.webinarProviderService.remove( + model.id, + connectionId, + ), + ); + } catch { + // + await this.handleCustomError('RemoveModelError'); + return; + } + + // + this.sendQueryPresenterAction(VQueryPresenterAction.RemoveModel, { + model, + actions: [], + key: nameof('id'), + }); + + // + this.showSuccess(); + await this.setLoadingState(false); + + // + // Handle Change Tabs if in Details ... + if ( + this.activeTab === VQueryPresenterTabs.List || + this.activeTab === VQueryPresenterTabs.Details + ) { + // + this.prevTab = undefined; + this.sendQueryPresenterAction( + VQueryPresenterAction.ChangeTab, + VQueryPresenterTabs.List, + ); + } + }); + } + + /** + * Handle Running toolbar Back Handler on Specified Case + * when User Pressed Escape Button ... + * + * @param event + * @returns + */ + async handleGlobalEscapePressed(event: any) { + // + if ( + this.lock || + !this.toolbarHasBack || + !this.toolbarShowBack || + !isFunction(this.toolbarBackHandler) || + [VQueryPresenterTabs.Add, VQueryPresenterTabs.Update].includes( + this.activeTab, + ) + ) { + return; + } + + // + await this.toolbarBackHandler(); + } + + /** + * Handle Tags State ... + * + * @param event + * @returns + */ + handleTagsStateChanged(event: VTagsPresenterModelState) { + // + if (isNullOrUndefined(event)) { + return; + } + + // + this.tagsState = { + ...event, + }; + } + + /** + * Handle Files State ... + * + * @param event + * @returns + */ + handleFilesStateChanged(event: VFilesPresenterModelState) { + // + if (isNullOrUndefined(event)) { + return; + } + + // + this.filesState = { + ...event, + }; + } + //#endregion + + // + //#region Private ... + private provideItemOptions( + userId: string, + isAdmin: boolean, + item: VWebinarViewDto, + ) { + // + let result: Array = []; + + // + // Validate ... + if (!isNullOrUndefined(item) && !isNullOrEmptyString(userId)) { + // + const isOwner = item.ownerId === userId; + + // + const canEdit = isOwner || isAdmin; + const canRemove = isOwner || isAdmin; + const canClose = (isOwner || isAdmin) && item.enabled; + + // + // Close ... + if (canClose) { + // + result.push({ + ...defaultCloseSlideOption, + slot: 'start', + disabled: this.loading || this.uiDisabled, + } as XSimpleListItemSlideOption); + } + + // + // Join ... + result.push({ + ...defaultConnectSlideOption, + slot: 'start', + icon: this.IconNames.live_tv, + disabled: + this.loading || this.uiDisabled || (!isOwner && !item.enabled), + } as XSimpleListItemSlideOption); + + // + // Details ... + result.push({ + ...defaultDetailsSlideOption, + slot: 'start', + disabled: this.loading || this.uiDisabled, + } as XSimpleListItemSlideOption); + + // + // Edit ... + result.push({ + ...defaultEditSlideOption, + slot: 'start', + disabled: this.loading || this.uiDisabled || !canEdit, + } as XSimpleListItemSlideOption); + + // + // Remove ... + result.push({ + ...defaultRemoveSlideOption, + slot: 'end', + disabled: this.loading || this.uiDisabled || !canRemove, + } as XSimpleListItemSlideOption); + } + + // + return result; + } + + isPushServiceConnected = false; + private async handleConnectPushService() { + // + // Preparing Connections ... + try { + // + // Check Connected Before ... + if ( + this.sharedService.saherElmAPIService.webinars.webinarEntityPushService.isConnected() + ) { + this.isPushServiceConnected = true; + } else { + // + this.isPushServiceConnected = true; + await this.sharedService.saherElmAPIService.webinars.webinarEntityPushService.connect(); + } + } catch { + // + await this.handleCustomError('PushServiceConnection'); + return; + } + + // + // Check Connected ... + if (!this.isPushServiceConnected) { + return; + } + + // + // On Added ... + this.applyTakeUntilWrapper( + this.sharedService.saherElmAPIService.webinars.webinarEntityPushService + .onAdded$, + ).subscribe(async (event) => { + // + if (isNullOrUndefined(event)) { + return; + } + + // + // Only Notify when a new Open Webinar Created ... + // Since here is Management Page ... + // TODO: Use this if necessary ... + // if (this.activeTab === VQueryPresenterTabs.List) { + // // + // await this.managerService.notificationService.presentInfoNotification({ + // dissmissable: true, + // message: this.resourceProvider( + // this.AppResourceIDs.new_webinar_created + // ), + // opt: { + // duration: 5000, + // buttons: [ + // { + // icon: this.IconNames.refresh, + // side: 'end', + // handler: async () => { + // this.sendQueryPresenterAction( + // VQueryPresenterAction.ResetQuery + // ); + // }, + // } as ToastButton, + // ], + // }, + // }); + // } + }); + + // + // On Removed ... + this.applyTakeUntilWrapper( + this.sharedService.saherElmAPIService.webinars.webinarEntityPushService + .onDeleted$, + ).subscribe(async (event) => { + // + if (isNullOrUndefined(event) && isNullOrUndefined(this.dtoQueryResult)) { + return; + } + + // + // Check if Model Exists in Current Context or not ... + let model = this.dtoQueryResult.items.find( + (i) => i.id === event.model.id, + ); + const modelP = ToWebinarViewDto(model, this.managerService.currentLocale); + if (!isNullOrUndefined(model) && !isNullOrUndefined(modelP)) { + // + const actions = await this.prepareListActions(modelP); + + // + this.sendQueryPresenterAction(VQueryPresenterAction.RemoveModel, { + actions, + model: modelP, + key: nameof('id'), + }); + } + + // + // Check Details or Edit Tab ... + if ( + (!isNullOrUndefined(model) && + !isNullOrUndefined(this.detailsModel) && + this.detailsModel.id === model.id && + this.activeTab === VQueryPresenterTabs.Details) || + (!isNullOrUndefined(model) && + !isNullOrUndefined(this.updateModel) && + this.updateModel.id === model.id && + this.activeTab === VQueryPresenterTabs.Update) + ) { + // + // Force Unlock ... + this.lock = false; + + // + this.sendQueryPresenterAction( + VQueryPresenterAction.ChangeTab, + VQueryPresenterTabs.List, + ); + } + }); + + // + // On Updated ... + this.applyTakeUntilWrapper( + this.sharedService.saherElmAPIService.webinars.webinarEntityPushService + .onUpdated$, + ).subscribe(async (event) => { + // + if (isNullOrUndefined(event) || isNullOrUndefined(this.dtoQueryResult)) { + return; + } + + // + // Check if Model Exists in Current Context or not ... + let model = this.dtoQueryResult.items.find( + (i) => i.id === event.model.id, + ); + if (isNullOrUndefined(model)) { + return; + } + + // + let fromEntity: XWebinarDto = undefined; + try { + fromEntity = await this.getValueAsync( + this.sharedService.saherElmAPIService.webinars.webinarProviderService.get( + event.model.id, + ), + ); + } catch { + return; + } + + // + if (isNullOrUndefined(fromEntity)) { + return; + } + + // + const modelP = ToWebinarViewDto( + fromEntity, + this.managerService.currentLocale, + ); + + // + if (isNullOrUndefined(modelP)) { + return; + } + + // + // Preparing Model List Actions ... + const actions = await this.prepareListActions(modelP); + + // + // Updating Model on Query Presenter ... + this.sendQueryPresenterAction(VQueryPresenterAction.UpdateModel, { + actions, + model: { ...modelP }, + key: nameof('id'), + }); + + // + // Update Tab ... + if ( + !isNullOrUndefined(this.updateModel) && + modelP.id === this.updateModel.id && + this.activeTab === VQueryPresenterTabs.Update + ) { + // + this.updateModel = { + ...modelP, + }; + + // + const selector = getQuerySelectorOfSpecifiedAttribute( + VWebinarViewComponent, + this.ModelFieldNames.id, + this.updateModel.id, + ); + await this.waitForElement(selector).then(async (el) => { + // + this.updateItemActionProvider.next({ + action: VBaseDtoAction.RenewModel, + payload: { ...this.updateModel }, + }); + + // + const prevTab = this.prevTab; + await this.handleStateAfterTabOpen(this.activeTab); + this.prevTab = prevTab; + }); + } + + // + // Details Tab ... + if ( + !isNullOrUndefined(this.detailsModel) && + modelP.id === this.detailsModel.id && + this.activeTab === VQueryPresenterTabs.Details + ) { + // + this.detailsModel = { + ...modelP, + }; + + // + const selector = getQuerySelectorOfSpecifiedAttribute( + VWebinarViewComponent, + this.ModelFieldNames.id, + this.detailsModel.id, + ); + await this.waitForElement(selector).then(async (el) => { + // + this.detailsItemActionProvider.next({ + action: VBaseDtoAction.RenewModel, + payload: { ...this.detailsModel }, + }); + + // + const prevTab = this.prevTab; + await this.handleStateAfterTabOpen(this.activeTab); + this.prevTab = prevTab; + }); + } + }); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\webinars\webinars-explorer\webinars-explorer.page.html + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\webinars\webinars-explorer\webinars-explorer.page.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\webinars\webinars-explorer\webinars-explorer.page.ts + +import { Observable } from 'rxjs'; +import { concatMap } from 'rxjs/operators'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { XQueryDto, XQueryResultDto } from 'x-framework-identity-sdk'; +import { ToWebinarViewDto } from 'src/app/views/webinar/v-webinar.tools'; +import { WebinarsBaseProviderPage } from '../webinars-base/webinars-base.provider.page'; + +@Component({ + selector: 'webinars-explorer', + styleUrls: ['./webinars-explorer.page.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + templateUrl: '../webinars-base/webinars-base.provider.page.html', +}) +export class WebinarsExplorerPage extends WebinarsBaseProviderPage { + // + //#region Props ... + //#endregion + + // + //#region LifeCycles ... + onInit(): void { + super.onInit(); + + // + this.configureAsExplorerPage(); + } + //#endregion + + // + //#region Abstractions ... + getModelTask(identifier: string): Observable { + // + return this.sharedService.saherElmAPIService.webinars.webinarProviderService + .get(identifier) + .pipe( + concatMap((m) => + this.getValue(ToWebinarViewDto(m, this.managerService.currentLocale)), + ), + ); + } + + queryModelTask(query: XQueryDto): Observable> { + return this.sharedService.saherElmAPIService.webinars.webinarProviderService.query( + query, + ); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\webinars\webinars-management\webinars-management.page.html + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\webinars\webinars-management\webinars-management.page.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\webinars\webinars-management\webinars-management.page.ts + +import { Observable } from 'rxjs'; +import { concatMap } from 'rxjs/operators'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; +import { XQueryDto, XQueryResultDto } from 'x-framework-identity-sdk'; +import { ToWebinarViewDto } from 'src/app/views/webinar/v-webinar.tools'; +import { WebinarsBaseProviderPage } from '../webinars-base/webinars-base.provider.page'; + +@Component({ + selector: 'webinars-management', + styleUrls: ['./webinars-management.page.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + templateUrl: '../webinars-base/webinars-base.provider.page.html', +}) +export class WebinarsManagementPage extends WebinarsBaseProviderPage { + // + //#region Props ... + //#endregion + + // + //#region LifeCycles ... + onInit(): void { + super.onInit(); + + // + this.configureAsManagementPage(); + } + //#endregion + + // + //#region Abstractions ... + getModelTask(identifier: string): Observable { + // + return this.sharedService.saherElmAPIService.webinars.webinarProviderService + .get(identifier) + .pipe( + concatMap((m) => + this.getValue(ToWebinarViewDto(m, this.managerService.currentLocale)), + ), + ); + } + + queryModelTask(query: XQueryDto): Observable> { + return this.sharedService.saherElmAPIService.webinars.webinarProviderService.query( + query, + ); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\pages\webinars\webinars-management\webinars-management.typings.ts + +import { isValueInEnum } from 'src/app/views/public-api'; + +export enum WebinarsManagementTask { + Create = 'Create_Webinar', + Remove = 'Remove_Webinar', + Update = 'Update_Webinar', + QueryList = 'QueryList_Webinars', +} + +export function isWebinarsManagementTask(value: string, forceExactSames: boolean = true) { + return isValueInEnum(value, Object.assign(WebinarsManagementTask, {}), forceExactSames); +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\shared\public-api.ts + +// +//#region Services ... +export * from './services/shared.service'; +//#endregion + +// +//#region Typings ... +export * from './typings/shared.typings'; +export * from './typings/x-electron.typings'; +export * from './services/x-scrolling-provider.service' +//#endregion + +// +//#region Tools ... +//#endregion + +// +// Module ... +export * from './shared.module'; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\shared\shared.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +@NgModule({ + declarations: [], + imports: [CommonModule], + exports: [CommonModule], +}) +export class SharedModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\shared\services\shared.service.ts + +import { + XParam, + XLoggable, + toPromise, + isNullOrUndefined, + isNullOrEmptyString, +} from 'x-framework-core'; +import { + XUserRoleInfo, + toRoleInfoByRoles, + XSaherElmAPIService, +} from 'x-saherelm-api-sdk'; +import { timer } from 'rxjs'; +import { + ApiValidators, + XAccountService, + XLoginResponseDto, + prepareUserProfileDtoFields, +} from 'x-framework-identity-sdk'; +import { X_CONFIG } from 'src/app/config/x.config'; +import { Pages } from 'src/app/config/page.config'; +import { XConfig } from 'src/app/config/app.config'; +import { XManagerService } from 'x-framework-services'; +import { XElectronService } from './x-electron.service'; +import { concatMap, filter, map } from 'rxjs/operators'; +import { EventEmitter, Inject, Injectable } from '@angular/core'; +import { XContentShareService } from './x-content-share-service'; +import { XMediaDevicesService } from './x-media-devices.service'; +import { XScrollingProviderService } from './x-scrolling-provider.service'; + +@Injectable({ + providedIn: 'root', +}) +export class SharedService extends XLoggable { + // + //#region Props ... + // + //#region User/Role/Token Helpers ... + /** + * XAccountState retriever ... + */ + state$ = this.authService.state$; + + /** + * check if a user logged in or not ... + */ + isAuthenticated$ = this.state$.pipe( + map((res) => { + // + if (!res) { + return false; + } + + // + return res.isLoggedIn; + }), + ); + + /** + * user profile retriever ... + */ + user$ = this.state$.pipe( + filter((res) => !!res), + map((state) => prepareUserProfileDtoFields(state.profile)), + ); + + /** + * user id retriever ... + */ + userId$ = this.user$.pipe(map((user) => user.userId)); + + /** + * user name retriever ... + */ + userName$ = this.user$.pipe(map((user) => user.userName)); + + /** + * token retriever ... + */ + token$ = this.state$.pipe( + filter((res) => !!res), + map((state) => state.accessToken), + ); + + /** + * retrieve role ... + */ + roles$ = this.user$.pipe(map((user) => user.roles)); + + /** + * retrieve userRoleInfo ... + */ + userRoleInfo$ = this.roles$.pipe( + map((roles) => { + // + const result: XUserRoleInfo = toRoleInfoByRoles(roles); + + // + return result; + }), + ); + + // + isAdmin$ = this.userRoleInfo$.pipe(map((info) => info && !!info.isAdmin)); + isAgent$ = this.userRoleInfo$.pipe(map((info) => info && !!info.isAgent)); + isUser$ = this.userRoleInfo$.pipe(map((info) => info && !!info.isUser)); + isAdminOrAgent$ = this.isAdmin$.pipe( + concatMap((isAdmin) => + this.isAgent$.pipe( + map((isAgent) => { + return isAdmin || isAgent; + }), + ), + ), + ); + //#endregion + + // + //#region App Event Emmitters ... + public BeforeAppUnload = new EventEmitter(); + public AppUnload = new EventEmitter(); + //#endregion + //#endregion + + // + //#region Constructor ... + constructor( + @Inject(X_CONFIG) + public config: XConfig, + public authService: XAccountService, + public apiValidators: ApiValidators, + public managerService: XManagerService, + public accountsService: XAccountService, + public electronService: XElectronService, + public saherElmAPIService: XSaherElmAPIService, + public mediaDevicesService: XMediaDevicesService, + public contentShareService: XContentShareService, + public scrollingInfoProvider: XScrollingProviderService, + ) { + super(config); + + // + // Check Refresh Token is Avaialble ... + if ( + !isNullOrUndefined(config.refreshTokensDelay) && + config.refreshTokensDelay > 0 + ) { + // + // Setting Timer ... + timer(0, config.refreshTokensDelay).subscribe(async (counter) => { + // + // Check is User Logged in or not ... + const isLoggedIn = await this.authService.isLoggedIn(); + if (isLoggedIn) { + // + // Retrieve User State ... + const state = await toPromise(this.authService.state$); + if (!isNullOrUndefined(state)) { + // + // Check User Token Expiration ... + const isExpired = await this.authService.isExpired(); + if (isExpired) { + // + // Try to Refresh Tokens if Expired ... + const tokens = await toPromise( + this.authService.refreshTokensOnly(), + ); + if (!isNullOrUndefined(tokens)) { + // + // Update User Tokens ... + let oldInfo: XLoginResponseDto = { + accessToken: state.accessToken, + refreshToken: state.refreshToken, + expiresAt: state.expiresAt, + profile: { ...state.profile }, + }; + + // + await this.authService.updateUserTokens(oldInfo, tokens); + } + } + } + } + }); + } + } + //#endregion + + // + //#region Actions ... + isMe(userId: string) { + return this.user$.pipe( + map( + (profile) => + !isNullOrUndefined(profile) && + !isNullOrEmptyString(userId) && + (userId === profile.email || + userId === profile.userId || + userId === profile.userName || + userId === profile.phoneNumber), + ), + ); + } + + /** + * Check Specified User identifier is point to current user or not ... + * + * @param userId + * @returns + */ + isNotMe(userId: string) { + // + return this.user$.pipe( + map( + (profile) => + !isNullOrUndefined(profile) && + !isNullOrEmptyString(userId) && + userId !== profile.email && + userId !== profile.userId && + userId !== profile.userName && + userId !== profile.phoneNumber, + ), + ); + } + + /** + * Generate and Retrieve Specified User's Profile View Page URL ... + * + * @param userID + * @param returnURL + * @returns + */ + getUserProfileURL(userID: string, returnURL?: string) { + // + let result = ''; + + // + // Normalize Args ... + if (isNullOrEmptyString(userID)) { + return result; + } + + // + const profileURL = this.managerService.getPageRoute(Pages.ProfileView); + const profileFullURL = this.managerService.getFullUrl(profileURL); + + // + result = `${profileFullURL}?${XParam.Id}=${userID}`; + if (!isNullOrEmptyString(returnURL)) { + result = `${result}&${XParam.ReturnUrl}=${returnURL}`; + } + + // + return result; + } + //#endregion + + // + //#region Private ... + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\shared\services\x-content-share-service.ts + +import { Injectable } from '@angular/core'; +import { isNullOrUndefined, XBaseService } from 'x-framework-core'; + +/** + * a Model for Sharng ... + */ +export interface XShrableDto { + title: string; + content: string; + link?: string; +} + +@Injectable({ + providedIn: 'root', +}) +export class XContentShareService extends XBaseService { + /** + * Check Share Content is Supported or not ... + */ + isShareSupport() { + return !isNullOrUndefined(navigator.share); + } + + /** + * Share Specific Data ... + * + * @param model XSharableDto instance + * @returns boolean + */ + async share(model: XShrableDto) { + // + // Validate State and Args ... + if (isNullOrUndefined(model) || !this.isShareSupport()) { + return false; + } + + // + await navigator.share({ + title: model.title, + text: model.content, + url: model.link, + }); + + // + return true; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\shared\services\x-electron.service.ts + +import { + getMills, + parseJson, + XBaseService, + throwException, + isNullOrUndefined, + isNullOrEmptyString, +} from 'x-framework-core'; +import { Subject } from 'rxjs'; +import { + XElectronChannel, + XElectronChannelIdentifier, + XElectronMessageDto, +} from '../typings/x-electron.typings'; +import { IpcRenderer } from 'electron'; +import { Inject, Injectable } from '@angular/core'; +import { X_CONFIG } from 'src/app/config/x.config'; +import { XConfig } from 'src/app/config/app.config'; +import { filter, first, map } from 'rxjs/operators'; + +@Injectable({ + providedIn: 'root', +}) +export class XElectronService extends XBaseService { + // + //#region Props ... + // + private IPC_RENDERER: IpcRenderer; + private ELECTRON_WINDOW_ID: string; + private MESSAGE_SUBJECT = new Subject(); + + // + readonly message$ = this.MESSAGE_SUBJECT.asObservable(); + + // + //#region Getters ... + get windowId() { + return this.ELECTRON_WINDOW_ID; + } + //#endregion + //#endregion + + // + //#region Constructor ... + constructor( + @Inject(X_CONFIG) + config: XConfig + ) { + super(config); + + // + this.initialService(); + this.logDebug('initialElectron'); + } + //#endregion + + // + //#region Actions ... + /** + * init service ... + * + * @param force re initial if initialized before ... + */ + init(force?: boolean) { + this.initialService(force); + } + + /** + * determines an application is Electron app or not ... + * + * @returns a boolean value + */ + isElectron() { + // + // Find Electron in User Agent ... + const result = + !isNullOrUndefined(window) && + !isNullOrUndefined(window.navigator) && + !isNullOrEmptyString(window.navigator.userAgent) + ? window.navigator.userAgent.toLowerCase().includes('electron') + : false; + + // + // Return result ... + return result; + } + + /** + * sending message to window of Electron ... + * + * @param message an instance of XElectronMessageDto for sending to window ... + */ + send(message: XElectronMessageDto) { + // + if ( + isNullOrUndefined(message) || + isNullOrUndefined(this.IPC_RENDERER) || + (isNullOrEmptyString(this.ELECTRON_WINDOW_ID) && + isNullOrEmptyString(message.sender)) + ) { + return; + } + + // + if (isNullOrEmptyString(message.sender)) { + message.sender = this.ELECTRON_WINDOW_ID; + } + + // + if (isNullOrEmptyString(message.channel)) { + message.channel = XElectronChannel.Default; + } + + // + if (isNullOrUndefined(message.timestamp)) { + message.timestamp = getMills(); + } + + // + this.IPC_RENDERER.send(message.channel, message); + } + //#endregion + + // + //#region Protected ... + actionHandler( + channel: XElectronChannelIdentifier, + actionName: string, + payload?: any + ) { + // + if (!this.isElectron()) { + throwException('NotAllowed'); + } + + // + this.send({ + channel, + message: actionName, + payload, + }); + + // + return this.message$.pipe( + filter((msg) => msg.channel === channel && msg.message === actionName), + map((msg) => msg.payload as TReturn), + first() + ); + } + + // + actionHandlerForFileChannel(actionName: string, payload?: any) { + return this.actionHandler( + XElectronChannel.FileService, + actionName, + payload + ); + } + + // + actionHandlerForProjectChannel(actionName: string, payload?: any) { + return this.actionHandler( + XElectronChannel.ProjectsService, + actionName, + payload + ); + } + //#endregion + + // + //#region Private ... + private initialService(force?: boolean) { + // + if (!!force) { + this.IPC_RENDERER = null; + } + + // + if (!isNullOrUndefined(this.IPC_RENDERER)) { + return; + } + + // + if (!isNullOrUndefined(window.require)) { + // + try { + // + this.IPC_RENDERER = window.require('electron').ipcRenderer; + this.registerMessageListener(); + + // + this.logDebug('Electron loaded successfully ...'); + } catch (e) { + // + this.logWarn('Electron not loaded ...'); + throw e; + } + } else { + this.logWarn('Electron not loaded ...'); + } + } + + private registerMessageListener() { + // + if (isNullOrUndefined(this.IPC_RENDERER)) { + return; + } + + // + // Register Handshake Reciever ... + this.IPC_RENDERER.on(XElectronChannel.Handshak, (event, message) => { + // + if (isNullOrUndefined(message)) { + return; + } + + // + const mMessage: XElectronMessageDto = + parseJson(message); + + // + this.ELECTRON_WINDOW_ID = mMessage.reciever; + + // + const handshakeReplyMessag: XElectronMessageDto = { + sender: mMessage.reciever, + reciever: mMessage.sender, + channel: XElectronChannel.Handshak, + }; + + // + this.send(handshakeReplyMessag); + this.logDebug('Handshake Events: ', message, mMessage); + }); + + // + let channels = { ...Object.assign({}, XElectronChannel) }; + delete channels.Handshak; + + // + // Register Non Handshake Events ... + Object.keys(channels).forEach((channel) => { + this.IPC_RENDERER.on(channels[channel], (event, message) => { + // + if (isNullOrUndefined(message)) { + return; + } + + // + const mMessage: XElectronMessageDto = + parseJson(message); + + // + this.MESSAGE_SUBJECT.next(mMessage); + + // + this.logDebug('Message Events: ', mMessage); + }); + }); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\shared\services\x-media-devices.service.ts + +import { Injectable } from '@angular/core'; +import { isValueInEnum } from 'src/app/views/tools/v-base.tools'; +import { isNullOrUndefined, XLoggable } from 'x-framework-core'; + +// +//#region Definitions ... +export enum XCameraFacing { + User = 'user', + Screen = 'screen', + Environment = 'environment', +} +export type XCameraFacingIdentifier = XCameraFacing | string; +export function isXCameraFacing(value: string) { + return isValueInEnum(value, Object.assign(XCameraFacing, {})); +} +//#endregion + +@Injectable({ + providedIn: 'root', +}) +export class XMediaDevicesService extends XLoggable { + // + //#region Media Devices ... + /** + * Enumerate Media Devices ... + * + * @returns + */ + async getMediaDevices() { + return await navigator.mediaDevices.enumerateDevices(); + } + + /** + * retrieve Requested Media Stream ... + * + * @param mode + * @returns + */ + async getMediaDevice(mode: XCameraFacing = XCameraFacing.User) { + // + let result: MediaStream = undefined; + + // + if (!isXCameraFacing(mode)) { + return result; + } + + // + try { + // + if (mode === XCameraFacing.User) { + result = await this.getFrontCameraDevice(); + } else if (mode === XCameraFacing.Environment) { + result = await this.getBackCameraDevice(); + } else if (mode === XCameraFacing.Screen) { + result = await this.getDisplayMediaDevice(); + } + } catch {} + + // + return result; + } + + /** + * Retrieve Video Input Devices ... + * + * @returns + */ + async getVideoInputMediaDevices() { + // + return (await this.getMediaDevices()).filter( + (device) => device.kind === 'videoinput' + ); + } + + /** + * Retrieve Audio Input Devices ... + * + * @returns + */ + async getAudioInputDevices() { + // + return (await this.getMediaDevices()).filter( + (device) => device.kind === 'audioinput' + ); + } + + /** + * Retrieve Audio Output Devices ... + * + * @returns + */ + async getAudioOutputDevices() { + // + return (await this.getMediaDevices()).filter( + (device) => device.kind === 'audiooutput' + ); + } + + /** + * Retrieve Front Camera Device ... + * + * @returns + */ + async getFrontCameraDevice() { + // + return await navigator.mediaDevices.getUserMedia({ + video: { facingMode: XCameraFacing.User }, + audio: true, // front camera + }); + } + + /** + * Retrieve Back Camera Device ... + * + * @returns + */ + async getBackCameraDevice() { + // + return await navigator.mediaDevices.getUserMedia({ + video: { facingMode: { exact: XCameraFacing.Environment } }, + audio: true, // back camera + }); + } + + /** + * Retrieve Display Media Device for Screen Sharing ... + * + * @param options + * @returns + */ + async getDisplayMediaDevice(options?: any) { + // + if (isNullOrUndefined(options)) { + options = {}; + } + + // + // Always Pass Video ... + options = { + video: { cursor: 'always' }, + }; + + // + return await (navigator.mediaDevices as any).getDisplayMedia(options); + } + + /** + * Check Has Camera Facing or not ... + * + * @param facingMode + * @returns + */ + async hasCameraFacing(facingMode: XCameraFacingIdentifier) { + // + let result = + isXCameraFacing(facingMode) && facingMode !== XCameraFacing.Screen; + if (!result) { + return result; + } + + // + try { + // + const stream = await navigator.mediaDevices.getUserMedia({ + video: { facingMode: { exact: facingMode } }, + }); + + // + stream.getTracks().forEach((track) => track.stop()); // stop after test + + // + result = true; + } catch (err) { + result = false; + } + + // + return result; + } + + /** + * Check has Front Camera ... + * + * @returns + */ + async hasFrontCamera() { + return await this.hasCameraFacing(XCameraFacing.User); + } + + /** + * Check has Back Camera ... + * + * @returns + */ + async hasBackCamera() { + // + const devices = await navigator.mediaDevices.enumerateDevices(); + var result = devices.some( + (d) => + d.kind === 'videoinput' && + (d.label.toLowerCase().includes('back') || + d.label.toLowerCase().includes('rear')) + ); + + // + return result; + } + + /** + * Check Can Switch Camera ... + * + * @returns + */ + async canSwitchCamera() { + // + const hasBack = await this.hasBackCamera(); + + // + return hasBack; + } + + /** + * Retrieve Default Media Devices ... + * + * @returns + */ + async getDefaultCamera() { + // + const hasFront = await this.hasFrontCamera(); + if (hasFront) { + return await this.getFrontCameraDevice(); + } + + // + return navigator.mediaDevices.getUserMedia(); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\shared\services\x-scrolling-provider.service.ts + +import { Injectable } from '@angular/core'; +import { + isNullOrEmptyString, + isNullOrUndefined, + XLoggable, +} from 'x-framework-core'; + +export interface XScrollingHoldInfo { + identifier: string; + yOffset: number; + xOffset: number; +} + +@Injectable({ + providedIn: 'root', +}) +export class XScrollingProviderService extends XLoggable { + // + private holdeds = new Map(); + + /** + * Check Has Specified Scrolling Info or not ... + * + * @param identifier + * @returns + */ + has(identifier: string) { + return this.holdeds.has(identifier); + } + + /** + * Get Specified Identifier Scrolling Info ... + * + * @param identifier + * @returns + */ + get(identifier: string) { + return this.holdeds.get(identifier); + } + + /** + * Remove Specified Identifier Scrolling Info ... + * + * @param identifier + * @returns + */ + remove(identifier: string) { + // + let result = false; + + // + result = this.holdeds.has(identifier); + if (result) { + result = this.holdeds.delete(identifier); + } + + // + return result; + } + + /** + * Set Specified Model info to Holds ... + * + * @param info + * @returns + */ + set(info: XScrollingHoldInfo) { + // + let result = false; + + // + // Validate ... + if (isNullOrUndefined(info) || isNullOrEmptyString(info.identifier)) { + return result; + } + + // + this.holdeds.set(info.identifier, info); + result = this.has(info.identifier); + + // + return result; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\shared\typings\shared.typings.ts + +export const XSharedServiceKeys = { + PushConnectionID: 'PCID', +}; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\shared\typings\x-electron.typings.ts + +import * as Electron from 'electron'; +import { XBaseDto } from 'x-framework-core'; + +export enum XElectronChannel { + Handshak = 'xHandshake', + Default = 'xMessage', + FileService = 'xFileService', + ProjectsService = 'xProjectsService', +} + +export type XElectronChannelIdentifier = XElectronChannel | string; + +export interface XElectronMessageDto extends XBaseDto { + payload?: any; + sender?: string; + message?: string; + reciever?: string; + timestamp?: number; + channel?: XElectronChannelIdentifier; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\public-api.ts + +// +// Tools ... +export * from './tools/date.tools'; +export * from './tools/v-base.tools'; +export * from './tools/action-provider.tools'; + +// +// Typings ... +export * from './typings/x.state.enum'; +export * from './typings/common.typings'; +export * from './typings/dto.common.typings'; +export * from './typings/simple.fab.typings'; +export * from './typings/simple.page.typings'; +export * from './typings/v-base-form.typings'; +export * from './typings/simple.form.typings'; +export * from './typings/simple.list-item.typings'; +export * from './typings/simple.actionbar.typings'; + +// +// Classes ... +export * from './classes/v-base.component'; + +// +// Directives ... +export * from './directives/x-html-include.directive'; +export * from './directives/x-insert-view-ref.directive'; +export * from './directives/x-render-component.directive'; +export * from './directives/x-focused-provider.directive'; +export * from './directives/x-on-escape-pressed.global.directive'; + +// +// Constants ... +export * from './constants/fab.defaults'; +export * from './constants/button.defaults'; +export * from './constants/actionbar.defaults'; +export * from './constants/slide.options.defaults'; + +// +//#region Tests ... +// +// Icons Presenter ... +export * from './tests/v-icons-presenter/v-icons-presenter.component'; +//#endregion + +// +//#region Views ... + +// +// VPage ... +export * from './v-page/v-page.component'; +export * from './v-page-presenter/v-page-presenter.typings'; +export * from './v-page-presenter/v-page-presenter.component'; + +// +// VError ... +export * from './v-error/v-error.component'; + +// +// VPaginator ... +export * from './v-paginator/v-paginator.typings'; +export * from './v-paginator/v-paginator.component'; + +// +// VDto ... +export * from './v-dto/v-dto.typings'; +export * from './v-dto/v-dto.component'; + +// +// VLazyView ... +export * from './v-lazy-view/v-lazy-view.typings'; +export * from './v-lazy-view/v-lazy-view.component'; + +// +// VQueryPresenter ... +export * from './v-query-presenter/v-query-presenter.typings'; +export * from './v-query-presenter/v-query-presenter.component'; + +// +// VPerson ... +export * from './v-person/v-person.tools'; +export * from './v-person/v-person.typings'; +export * from './v-person/v-person.component'; + +// +// VReference ... +export * from './v-reference/v-reference.typings'; +export * from './v-reference/v-reference.configs'; +export * from './v-reference/v-reference.component'; + +// +// VVideo ... +export * from './v-video/v-video.configs'; +export * from './v-video/v-video.typings'; +export * from './v-video/v-video.component'; + +// +//#region Account ... +// +// VUser ... +export * from './account/v-user/v-user.tools'; +export * from './account/v-user/v-user.typings'; +export * from './account/v-user/v-user.component'; + +// +// VFriendship ... +export * from './account/v-friendship/v-friendship.tools'; +export * from './account/v-friendship/v-friendship.typings'; +export * from './account/v-friendship/v-friendship.component'; + +// +// VAvatar ... +export * from './account/v-avatar/v-avatar.typings'; +export * from './account/v-avatar/v-avatar.component'; + +// +// VProfile ... +export * from './account/v-profile/v-profile.typings'; +export * from './account/v-profile/v-profile.component'; + +// +// VLogin ... +export * from './account/v-login/v-login.component'; + +// +// VRegister ... +export * from './account/v-register/v-register.component'; + +// +// Configs ... +export * from './account/account.configs'; +//#endregion + +// +//#region String ... +export * from './string/v-string.tools'; +export * from './string/v-string.typings'; +export * from './string/v-string/v-string.component'; +export * from './string/v-locale/v-locale.component'; +export * from './string/v-resource/v-resource.component'; +//#endregion + +// +//#region Tag ... +export * from './tags/v-tag.configs'; +export * from './tags/v-tag/v-tag.typings'; +export * from './tags/v-tag/v-tag.component'; +export * from './tags/v-tag-input/v-tag-input.typings'; +export * from './tags/v-tag-input/v-tag-input.component'; +export * from './tags/v-tags-presenter/v-tags-presenter.typings'; +export * from './tags/v-tags-presenter/v-tags-presenter.component'; +//#endregion + +// +//#region File ... +export * from './files/v-file.tools'; +export * from './files/v-file.configs'; +export * from './files/v-file/v-file.typings'; +export * from './files/v-media/v-media.config'; +export * from './files/v-file/v-file.component'; +export * from './files/v-media/v-media.typings'; +export * from './files/v-media/v-media.component'; +export * from './files/v-file-input/v-file-input.typings'; +export * from './files/v-file-input/v-file-input.component'; +export * from './files/v-files-presenter/v-files-presenter.typings'; +export * from './files/v-files-presenter/v-files-presenter.component'; +//#endregion + +// +//#region Webinar ... +export * from './webinar/v-webinar.tools'; +export * from './webinar/v-webinar.configs'; +export * from './webinar/v-webinar.typings'; +export * from './webinar/v-webinar-view/v-webinar-view.component'; +export * from './webinar/v-webinar-connect/v-webinar-connect.typings'; +export * from './webinar/v-webinar-connect/v-webinar-connect.component'; +//#endregion +//#endregion + +// +// Module ... +export * from './views.module'; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\views.module.ts + +import { + XFrameworkCoreModule, + XDirectivesModule as XFrameworkCoreDirectivesModule, +} from 'x-framework-core'; +import { + XManagerService, + XHttpInterceptorService, + XFrameworkServicesModule, + XDirectivesModule as XFrameworkServicesDirectivesModule, +} from 'x-framework-services'; +import { + XIDEModule, + XFabModule, + XMapModule, + XTabsModule, + XPageModule, + XCardModule, + XIconModule, + XListModule, + XGridModule, + XFormModule, + XTableModule, + XThemeModule, + XEmptyModule, + XColorModule, + XButtonModule, + XSliderModule, + XSpinnerModule, + XSlotterModule, + XCounterModule, + XStepperModule, + XDialogsModule, + XMarkdownModule, + XSearchBarModule, + XThumbnailModule, + XActionBarModule, + XDataPagerModule, + XFileUploadModule, + XProgressBarModule, + XColorPickerModule, + XTabNavigatorModule, + XFileDropAreaModule, + XAvatarUploadModule, + XImageCropperModule, + XDataScrollerModule, + XBaseComponentModule, + XExpandableListModule, + XModelPresenterModule, + XExpandablePanelModule, + XDescriptionListModule, + XFrameworkComponentsModule, +} from 'x-framework-components'; +import { NgModule } from '@angular/core'; +import { IonicModule } from '@ionic/angular'; +import { CommonModule } from '@angular/common'; +import { VDto } from './v-dto/v-dto.component'; +import { BidiModule } from '@angular/cdk/bidi'; +import { HTTP_INTERCEPTORS } from '@angular/common/http'; +import { MatChipsModule } from '@angular/material/chips'; +import { VPageComponent } from './v-page/v-page.component'; +import { VTagComponent } from './tags/v-tag/v-tag.component'; +import { VErrorComponent } from './v-error/v-error.component'; +import { VVideoComponent } from './v-video/v-video.component'; +import { VLazyView } from './v-lazy-view/v-lazy-view.component'; +import { VFileComponent } from './files/v-file/v-file.component'; +import { VPersonComponent } from './v-person/v-person.component'; +import { VUserComponent } from './account/v-user/v-user.component'; +import { VMediaComponent } from './files/v-media/v-media.component'; +import { VLoginComponent } from './account/v-login/v-login.component'; +import { VStringComponent } from './string/v-string/v-string.component'; +import { VLocaleComponent } from './string/v-locale/v-locale.component'; +import { VAvatarComponent } from './account/v-avatar/v-avatar.component'; +import { VPaginatorComponent } from './v-paginator/v-paginator.component'; +import { VReferenceComponent } from './v-reference/v-reference.component'; +import { VProfileComponent } from './account/v-profile/v-profile.component'; +import { VTagInputComponent } from './tags/v-tag-input/v-tag-input.component'; +import { XHTMLIncludeDirective } from './directives/x-html-include.directive'; +import { VResourceComponent } from './string/v-resource/v-resource.component'; +import { VPagePresenter } from './v-page-presenter/v-page-presenter.component'; +import { VFileInputComponent } from './files/v-file-input/v-file-input.component'; +import { XInsertViewRefDirective } from './directives/x-insert-view-ref.directive'; +import { VFriendshipComponent } from './account/v-friendship/v-friendship.component'; +import { XFocusedProviderDirective } from './directives/x-focused-provider.directive'; +import { XRenderComponentDirective } from './directives/x-render-component.directive'; +import { VWebinarViewComponent } from './webinar/v-webinar-view/v-webinar-view.component'; +import { VQueryPresenterComponent } from './v-query-presenter/v-query-presenter.component'; +import { VTagsPresenterComponent } from './tags/v-tags-presenter/v-tags-presenter.component'; +import { VIconsPresenterComponent } from './tests/v-icons-presenter/v-icons-presenter.component'; +import { VFilesPresenterComponent } from './files/v-files-presenter/v-files-presenter.component'; +import { VWebinarConnectComponent } from './webinar/v-webinar-connect/v-webinar-connect.component'; +import { XOnEscapePressedGlobalDirective } from './directives/x-on-escape-pressed.global.directive'; +import { VRegisterComponent } from './account/v-register/v-register.component'; + +@NgModule({ + entryComponents: [ + VAvatarComponent, + VProfileComponent, + VTagInputComponent, + VWebinarConnectComponent, + ], + declarations: [ + // + // Directives ... + XHTMLIncludeDirective, + XInsertViewRefDirective, + XFocusedProviderDirective, + XRenderComponentDirective, + XOnEscapePressedGlobalDirective, + + // + //#region Tests ... + VIconsPresenterComponent, + //#endregion + + // + // Account ... + VUserComponent, + VLoginComponent, + VAvatarComponent, + VProfileComponent, + VRegisterComponent, + VFriendshipComponent, + + // + // Regular ... + VPageComponent, + VErrorComponent, + + // + // Lazy View ... + VDto, + VLazyView, + VPaginatorComponent, + + // + // Data Fetcher Page ... + VPagePresenter, + + // + VVideoComponent, + VPersonComponent, + VReferenceComponent, + VQueryPresenterComponent, + + // + // String ... + VStringComponent, + VLocaleComponent, + VResourceComponent, + + // + // Tag ... + VTagComponent, + VTagInputComponent, + VTagsPresenterComponent, + + // + // File ... + VFileComponent, + VMediaComponent, + VFileInputComponent, + VFilesPresenterComponent, + + // + // Webinar ... + VWebinarViewComponent, + VWebinarConnectComponent, + ], + providers: [ + XManagerService, + // + // Register Http Interceptor ... + { + provide: HTTP_INTERCEPTORS, + useClass: XHttpInterceptorService, + multi: true, + }, + ], + imports: [ + // + BidiModule, + IonicModule, + CommonModule, + + // + XFrameworkCoreModule, + XFrameworkCoreDirectivesModule, + + // + XFrameworkServicesModule, + XFrameworkServicesDirectivesModule, + + // + XEmptyModule, + XDialogsModule, + XBaseComponentModule, + XFrameworkComponentsModule, + + // + XIDEModule, + XFabModule, + XMapModule, + XPageModule, + XCardModule, + XIconModule, + XFormModule, + XTabsModule, + XListModule, + XGridModule, + XColorModule, + XTableModule, + XThemeModule, + XButtonModule, + XSliderModule, + XSpinnerModule, + XStepperModule, + XSlotterModule, + XCounterModule, + XMarkdownModule, + XDataPagerModule, + XSearchBarModule, + XThumbnailModule, + XActionBarModule, + XFileUploadModule, + XProgressBarModule, + XColorPickerModule, + XImageCropperModule, + XFileDropAreaModule, + XDataScrollerModule, + XTabNavigatorModule, + XAvatarUploadModule, + XExpandableListModule, + XModelPresenterModule, + XExpandablePanelModule, + XDescriptionListModule, + + // + MatChipsModule, + ], + exports: [ + // + BidiModule, + IonicModule, + CommonModule, + + // + XFrameworkCoreModule, + XFrameworkCoreDirectivesModule, + + // + XFrameworkServicesModule, + XFrameworkServicesDirectivesModule, + + // + XEmptyModule, + XDialogsModule, + XBaseComponentModule, + + // + XIDEModule, + XFabModule, + XMapModule, + XPageModule, + XCardModule, + XIconModule, + XFormModule, + XTabsModule, + XListModule, + XGridModule, + XColorModule, + XTableModule, + XThemeModule, + XButtonModule, + XSliderModule, + XSpinnerModule, + XStepperModule, + XSlotterModule, + XCounterModule, + XMarkdownModule, + XDataPagerModule, + XSearchBarModule, + XThumbnailModule, + XActionBarModule, + XFileUploadModule, + XProgressBarModule, + XColorPickerModule, + XImageCropperModule, + XFileDropAreaModule, + XDataScrollerModule, + XTabNavigatorModule, + XAvatarUploadModule, + XExpandableListModule, + XModelPresenterModule, + XExpandablePanelModule, + XDescriptionListModule, + + // + MatChipsModule, + + // + // Directives ... + XHTMLIncludeDirective, + XInsertViewRefDirective, + XFocusedProviderDirective, + XRenderComponentDirective, + XOnEscapePressedGlobalDirective, + + // + //#region Tests ... + VIconsPresenterComponent, + //#endregion + + // + // Account ... + VUserComponent, + VLoginComponent, + VAvatarComponent, + VProfileComponent, + VRegisterComponent, + VFriendshipComponent, + + // + // Regular ... + VPageComponent, + VErrorComponent, + + // + // Lazy View ... + VDto, + VLazyView, + VPaginatorComponent, + + // + VPagePresenter, + + // + VVideoComponent, + VPersonComponent, + VReferenceComponent, + VQueryPresenterComponent, + + // + // String ... + VStringComponent, + VLocaleComponent, + VResourceComponent, + + // + // Tag ... + VTagComponent, + VTagInputComponent, + VTagsPresenterComponent, + + // + // File ... + VFileComponent, + VMediaComponent, + VFileInputComponent, + VFilesPresenterComponent, + + // + // Webinar ... + VWebinarViewComponent, + VWebinarConnectComponent, + ], +}) +export class ViewsModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\account\account.configs.ts + +import { XIconNames } from 'x-framework-components'; +import { XSimplePage } from '../typings/simple.page.typings'; + +// +//#region Localization Configuration ... +export enum AccountResourceIDs { + account = 'account', + accounts = 'accounts', + profile = 'profile', + profiles = 'profiles', + avatar = 'avatar', + avatars = 'avatars', + register = 'register', + accept_terms = 'accept_terms', + email_confirmation = 'email_confirmation', + input_user_info = 'input_user_info', + input_user_avatar = 'input_user_avatar', + finish_registration = 'finish_registration', + account_page_title = 'account_page_title', + account_page_description = 'account_page_description', + profile_page_title = 'profile_page_title', + profile_page_description = 'profile_page_description', + login_page_title = 'login_page_title', + login_page_description = 'login_page_description', + logout_page_title = 'logout_page_title', + logout_page_description = 'logout_page_description', + register_page_title = 'register_page_title', + register_page_description = 'register_page_description', + email_confirm_page_title = 'email_confirm_page_title', + email_confirm_page_description = 'email_confirm_page_description', + phone_confirm_page_title = 'phone_confirm_page_title', + phone_confirm_page_description = 'phone_confirm_page_description', + reset_password_page_title = 'reset_password_page_title', + reset_password_page_description = 'reset_password_page_description', + change_password_page_title = 'change_password_page_title', + change_password_page_description = 'change_password_page_description', + search_profiles_page_title = 'search_profiles_page_title', + search_profiles_page_description = 'search_profiles_page_description', +} + +// +//#region Signle Resources ... +// +//#region account ... +export const account_fa = { + id: 'account', + value: 'حساب', +}; + +export const account_en = { + id: 'account', + value: 'Account', +}; +//#endregion + +// +//#region accounts ... +export const accounts_fa = { + id: 'accounts', + value: 'حساب ها', +}; + +export const accounts_en = { + id: 'accounts', + value: 'Accounts', +}; +//#endregion + +// +//#region profile ... +export const profile_fa = { + id: 'profile', + value: 'نمایه', +}; + +export const profile_en = { + id: 'profile', + value: 'Profile', +}; +//#endregion + +// +//#region profiles ... +export const profiles_fa = { + id: 'profiles', + value: 'نمایه ها', +}; + +export const profiles_en = { + id: 'profiles', + value: 'Profiles', +}; +//#endregion + +// +//#region avatar ... +export const avatar_fa = { + id: 'avatar', + value: 'تصویر نمایه', +}; + +export const avatar_en = { + id: 'avatar', + value: 'Profile Image', +}; +//#endregion + +// +//#region avatars ... +export const avatars_fa = { + id: 'avatars', + value: 'تصاویر نمایه ها', +}; + +export const avatars_en = { + id: 'avatars', + value: 'Profile Images', +}; +//#endregion + +// +//#region register ... +export const register_fa = { + id: 'register', + value: 'ثبت نام', +}; + +export const register_en = { + id: 'register', + value: 'Register', +}; +//#endregion + +// +//#region accept_terms ... +export const accept_terms_fa = { + id: 'accept_terms', + value: 'شرایط و ضوابط ثبت نام', +}; + +export const accept_terms_en = { + id: 'accept_terms', + value: 'Terms and Conditions', +}; +//#endregion + +// +//#region email_confirmation ... +export const email_confirmation_fa = { + id: 'email_confirmation', + value: 'تایید آدرس پست الکترونیک', +}; + +export const email_confirmation_en = { + id: 'email_confirmation', + value: 'Email Confirmation', +}; +//#endregion + +// +//#region input_user_info ... +export const input_user_info_fa = { + id: 'input_user_info', + value: 'دریافت اطلاعات کاربر', +}; + +export const input_user_info_en = { + id: 'input_user_info', + value: 'Get User Info', +}; +//#endregion + +// +//#region input_user_avatar ... +export const input_user_avatar_fa = { + id: 'input_user_avatar', + value: 'دریافت تصویر نمایه', +}; + +export const input_user_avatar_en = { + id: 'input_user_avatar', + value: 'Get User Avatar', +}; +//#endregion + +// +//#region finish_registration ... +export const finish_registration_fa = { + id: 'finish_registration', + value: 'اتمام ثبت نام', +}; + +export const finish_registration_en = { + id: 'finish_registration', + value: 'Finish Registration', +}; +//#endregion + +// +//#region account_page_title ... +export const account_page_title_fa = { + id: 'account_page_title', + value: 'حساب کاربری', +}; + +export const account_page_title_en = { + id: 'account_page_title', + value: 'User Account', +}; +//#endregion + +// +//#region account_page_description ... +export const account_page_description_fa = { + id: 'account_page_description', + value: 'مدیریت حساب کاربری', +}; + +export const account_page_description_en = { + id: 'account_page_description', + value: 'Manage Account', +}; +//#endregion + +// +//#region profile_page_title ... +export const profile_page_title_fa = { + id: 'profile_page_title', + value: 'نمایه کاربر', +}; + +export const profile_page_title_en = { + id: 'profile_page_title', + value: 'User Profile', +}; +//#endregion + +// +//#region profile_page_description ... +export const profile_page_description_fa = { + id: 'profile_page_description', + value: 'اطلاعات مربوط به حساب کاربری', +}; + +export const profile_page_description_en = { + id: 'profile_page_description', + value: 'User Account Info', +}; +//#endregion + +// +//#region login_page_title ... +export const login_page_title_fa = { + id: 'login_page_title', + value: 'ورود به سامانه', +}; + +export const login_page_title_en = { + id: 'login_page_title', + value: 'Login to System', +}; +//#endregion + +// +//#region login_page_description ... +export const login_page_description_fa = { + id: 'login_page_description', + value: 'ورود به سامانه و بهره مندی از خدمات', +}; + +export const login_page_description_en = { + id: 'login_page_description', + value: 'Login to System for use Services', +}; +//#endregion + +// +//#region logout_page_title ... +export const logout_page_title_fa = { + id: 'logout_page_title', + value: 'خروج از سامانه', +}; + +export const logout_page_title_en = { + id: 'logout_page_title', + value: 'Logout from System', +}; +//#endregion + +// +//#region logout_page_description ... +export const logout_page_description_fa = { + id: 'logout_page_description', + value: 'خروج از سامانه و بازگشت به صفحه نخست', +}; + +export const logout_page_description_en = { + id: 'logout_page_description', + value: 'Logout from System and go back to First', +}; +//#endregion + +// +//#region register_page_title ... +export const register_page_title_fa = { + id: 'register_page_title', + value: 'ثبت نام در سامانه', +}; + +export const register_page_title_en = { + id: 'register_page_title', + value: 'Register in System', +}; +//#endregion + +// +//#region register_page_description ... +export const register_page_description_fa = { + id: 'register_page_description', + value: 'ثبت نام و عضویت در سامانه', +}; + +export const register_page_description_en = { + id: 'register_page_description', + value: 'Signup and Register in System', +}; +//#endregion + +// +//#region email_confirm_page_title ... +export const email_confirm_page_title_fa = { + id: 'email_confirm_page_title', + value: 'تایید آدرس پست الکترونیک', +}; + +export const email_confirm_page_title_en = { + id: 'email_confirm_page_title', + value: 'Confirm Email Address', +}; +//#endregion + +// +//#region email_confirm_page_description ... +export const email_confirm_page_description_fa = { + id: 'email_confirm_page_description', + value: 'اعتبار سنجی آدرس پست الکترونیک', +}; + +export const email_confirm_page_description_en = { + id: 'email_confirm_page_description', + value: 'Verification of Email Address', +}; +//#endregion + +// +//#region phone_confirm_page_title ... +export const phone_confirm_page_title_fa = { + id: 'phone_confirm_page_title', + value: 'تایید شماره تلفن همراه', +}; + +export const phone_confirm_page_title_en = { + id: 'phone_confirm_page_title', + value: 'Confirm Mobile Number', +}; +//#endregion + +// +//#region phone_confirm_page_description ... +export const phone_confirm_page_description_fa = { + id: 'phone_confirm_page_description', + value: 'اعتبار سنجی شماره تلفن همراه', +}; + +export const phone_confirm_page_description_en = { + id: 'phone_confirm_page_description', + value: 'Verification of Mobile Phone Number', +}; +//#endregion + +// +//#region reset_password_page_title ... +export const reset_password_page_title_fa = { + id: 'reset_password_page_title', + value: 'بازنشانی کلمه عبور', +}; + +export const reset_password_page_title_en = { + id: 'reset_password_page_title', + value: 'Password Reset', +}; +//#endregion + +// +//#region reset_password_page_description ... +export const reset_password_page_description_fa = { + id: 'reset_password_page_description', + value: 'بازنشانی کلمه عبور فراموش شده', +}; + +export const reset_password_page_description_en = { + id: 'reset_password_page_description', + value: 'Resetting Forgotten Passwor', +}; +//#endregion + +// +//#region change_password_page_title ... +export const change_password_page_title_fa = { + id: 'change_password_page_title', + value: 'تغییر کلمه عبور', +}; + +export const change_password_page_title_en = { + id: 'change_password_page_title', + value: 'Password Change', +}; +//#endregion + +// +//#region change_password_page_description ... +export const change_password_page_description_fa = { + id: 'change_password_page_description', + value: 'تغییر کلمه عبور کاربر', +}; + +export const change_password_page_description_en = { + id: 'change_password_page_description', + value: "Change User's Password", +}; +//#endregion + +// +//#region search_profiles_page_title ... +export const search_profiles_page_title_fa = { + id: 'search_profiles_page_title', + value: 'جستجوی کاربران', +}; + +export const search_profiles_page_title_en = { + id: 'search_profiles_page_title', + value: 'Users Search', +}; +//#endregion + +// +//#region search_profiles_page_description ... +export const search_profiles_page_description_fa = { + id: 'search_profiles_page_description', + value: 'جستجوی کاربران جهت برقراری ارتباط', +}; + +export const search_profiles_page_description_en = { + id: 'search_profiles_page_description', + value: 'Search Users to Lookup Connection', +}; +//#endregion +//#endregion + +// +export const AccountLocalizationResourceIDs = Object.assign( + AccountResourceIDs, + {} +); + +// +export const AccountLocalizationResources = { + // + account: { + fa: { ...account_fa }, + en: { ...account_en }, + }, + accounts: { + fa: { ...accounts_fa }, + en: { ...accounts_en }, + }, + profile: { + fa: { ...profile_fa }, + en: { ...profile_en }, + }, + profiles: { + fa: { ...profiles_fa }, + en: { ...profiles_en }, + }, + avatar: { + fa: { ...avatar_fa }, + en: { ...avatar_en }, + }, + avatars: { + fa: { ...avatars_fa }, + en: { ...avatars_en }, + }, + register: { + fa: { ...register_fa }, + en: { ...register_en }, + }, + accept_terms: { + fa: { ...accept_terms_fa }, + en: { ...accept_terms_en }, + }, + email_confirmation: { + fa: { ...email_confirmation_fa }, + en: { ...email_confirmation_en }, + }, + input_user_info: { + fa: { ...input_user_info_fa }, + en: { ...input_user_info_en }, + }, + input_user_avatar: { + fa: { ...input_user_avatar_fa }, + en: { ...input_user_avatar_en }, + }, + finish_registration: { + fa: { ...finish_registration_fa }, + en: { ...finish_registration_en }, + }, + account_page_title: { + fa: { ...account_page_title_fa }, + en: { ...account_page_title_en }, + }, + account_page_description: { + fa: { ...account_page_description_fa }, + en: { ...account_page_description_en }, + }, + profile_page_title: { + fa: { ...profile_page_title_fa }, + en: { ...profile_page_title_en }, + }, + profile_page_description: { + fa: { ...profile_page_description_fa }, + en: { ...profile_page_description_en }, + }, + login_page_title: { + fa: { ...login_page_title_fa }, + en: { ...login_page_title_en }, + }, + login_page_description: { + fa: { ...login_page_description_fa }, + en: { ...login_page_description_en }, + }, + logout_page_title: { + fa: { ...logout_page_title_fa }, + en: { ...logout_page_title_en }, + }, + logout_page_description: { + fa: { ...logout_page_description_fa }, + en: { ...logout_page_description_en }, + }, + register_page_title: { + fa: { ...register_page_title_fa }, + en: { ...register_page_title_en }, + }, + register_page_description: { + fa: { ...register_page_description_fa }, + en: { ...register_page_description_en }, + }, + email_confirm_page_title: { + fa: { ...email_confirm_page_title_fa }, + en: { ...email_confirm_page_title_en }, + }, + email_confirm_page_description: { + fa: { ...email_confirm_page_description_fa }, + en: { ...email_confirm_page_description_en }, + }, + phone_confirm_page_title: { + fa: { ...phone_confirm_page_title_fa }, + en: { ...phone_confirm_page_title_en }, + }, + phone_confirm_page_description: { + fa: { ...phone_confirm_page_description_fa }, + en: { ...phone_confirm_page_description_en }, + }, + reset_password_page_title: { + fa: { ...reset_password_page_title_fa }, + en: { ...reset_password_page_title_en }, + }, + reset_password_page_description: { + fa: { ...reset_password_page_description_fa }, + en: { ...reset_password_page_description_en }, + }, + change_password_page_title: { + fa: { ...change_password_page_title_fa }, + en: { ...change_password_page_title_en }, + }, + change_password_page_description: { + fa: { ...change_password_page_description_fa }, + en: { ...change_password_page_description_en }, + }, + search_profiles_page_title: { + fa: { ...search_profiles_page_title_fa }, + en: { ...search_profiles_page_title_en }, + }, + search_profiles_page_description: { + fa: { ...search_profiles_page_description_fa }, + en: { ...search_profiles_page_description_en }, + }, +}; +//#endregion + +// +//#region Page Configuration ... +// +export enum AccountRoutes { + Login = 'login', + Logout = 'logout', + Default = 'account', + Profile = 'profile', + Register = 'register', + EmailConfirm = 'email_confirm', + PhoneConfirm = 'phone_confirm', + ResetPassword = 'reset_password', + ChangePassword = 'change_password', + SearchProfiles = 'search_profiles', +} + +// +export const Account: XSimplePage = { + id: `${AccountRoutes.Default}`, + name: `${AccountRoutes.Default}`, + title: AccountResourceIDs.account_page_title, + description: AccountResourceIDs.account_page_description, + baseRoute: `${AccountRoutes.Default}`, + route: ['/', `${AccountRoutes.Default}`], + icon: XIconNames.collapse, + childs: [ + // + //#region Login ... + { + id: `${AccountRoutes.Login}`, + name: `${AccountRoutes.Login}`, + title: AccountResourceIDs.login_page_title, + description: AccountResourceIDs.login_page_description, + baseRoute: `${AccountRoutes.Login}`, + route: ['/', `${AccountRoutes.Default}`, `${AccountRoutes.Login}`], + icon: XIconNames.login, + }, + //#endregion + + // + //#region Logout ... + { + id: `${AccountRoutes.Logout}`, + name: `${AccountRoutes.Logout}`, + title: AccountResourceIDs.logout_page_title, + description: AccountResourceIDs.logout_page_description, + baseRoute: `${AccountRoutes.Logout}`, + route: ['/', `${AccountRoutes.Default}`, `${AccountRoutes.Logout}`], + icon: XIconNames.logout, + }, + //#endregion + + // + //#region Profile ... + { + id: `${AccountRoutes.Profile}`, + name: `${AccountRoutes.Profile}`, + title: AccountResourceIDs.profile_page_title, + description: AccountResourceIDs.profile_page_description, + baseRoute: `${AccountRoutes.Profile}`, + route: ['/', `${AccountRoutes.Default}`, `${AccountRoutes.Profile}`], + icon: XIconNames.register, + }, + //#endregion + + // + //#region Register ... + { + id: `${AccountRoutes.Register}`, + name: `${AccountRoutes.Register}`, + title: AccountResourceIDs.register_page_title, + description: AccountResourceIDs.register_page_description, + baseRoute: `${AccountRoutes.Register}`, + route: ['/', `${AccountRoutes.Default}`, `${AccountRoutes.Register}`], + icon: XIconNames.register, + }, + //#endregion + + // + //#region EmailConfirm ... + { + id: `${AccountRoutes.EmailConfirm}`, + name: `${AccountRoutes.EmailConfirm}`, + title: AccountResourceIDs.email_confirm_page_title, + description: AccountResourceIDs.email_confirm_page_description, + baseRoute: `${AccountRoutes.EmailConfirm}`, + route: ['/', `${AccountRoutes.Default}`, `${AccountRoutes.EmailConfirm}`], + icon: XIconNames.register, + }, + //#endregion + + // + //#region PhoneConfirm ... + { + id: `${AccountRoutes.PhoneConfirm}`, + name: `${AccountRoutes.PhoneConfirm}`, + title: AccountResourceIDs.phone_confirm_page_title, + description: AccountResourceIDs.phone_confirm_page_description, + baseRoute: `${AccountRoutes.PhoneConfirm}`, + route: ['/', `${AccountRoutes.Default}`, `${AccountRoutes.PhoneConfirm}`], + icon: XIconNames.register, + }, + //#endregion + + // + //#region ResetPassword ... + { + id: `${AccountRoutes.ResetPassword}`, + name: `${AccountRoutes.ResetPassword}`, + title: AccountResourceIDs.reset_password_page_title, + description: AccountResourceIDs.reset_password_page_description, + baseRoute: `${AccountRoutes.ResetPassword}`, + route: [ + '/', + `${AccountRoutes.Default}`, + `${AccountRoutes.ResetPassword}`, + ], + icon: XIconNames.register, + }, + //#endregion + + // + //#region ChangePassword ... + { + id: `${AccountRoutes.ChangePassword}`, + name: `${AccountRoutes.ChangePassword}`, + title: AccountResourceIDs.change_password_page_title, + description: AccountResourceIDs.change_password_page_description, + baseRoute: `${AccountRoutes.ChangePassword}`, + route: [ + '/', + `${AccountRoutes.Default}`, + `${AccountRoutes.ChangePassword}`, + ], + icon: XIconNames.register, + }, + //#endregion + + // + //#region SearchProfiles ... + { + id: `${AccountRoutes.SearchProfiles}`, + name: `${AccountRoutes.SearchProfiles}`, + title: AccountResourceIDs.search_profiles_page_title, + description: AccountResourceIDs.search_profiles_page_description, + baseRoute: `${AccountRoutes.SearchProfiles}`, + route: [ + '/', + `${AccountRoutes.Default}`, + `${AccountRoutes.SearchProfiles}`, + ], + icon: XIconNames.register, + }, + //#endregion + ], +}; +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\account\v-avatar\v-avatar.component.html + +
+ +
+ + + +
+ + + +
+ +
+
+ + + + +

{{ getValue(fullName) | async }}

+
+
+ + + +
+ +
+
+
+
+
+ + + +
+ + +
+ + +
+
+ + + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\account\v-avatar\v-avatar.component.scss + +@import "../../../../theme/theme.scss"; + +:host { + --dialog-toolbar-height: 0px; + --background: var(-x-color-light); +} + +x-icon { + --size: 24px; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + margin: 0 !important; +} + +.v-base-dialog { + width: 100%; + height: 100%; + // height: calc(100% - var(--dialog-toolbar-height)); + // margin-bottom: var(--dialog-toolbar-height); +} + +.v-base-dialog-content { + height: 100%; + + // + padding-bottom: var(--dialog-toolbar-height); +} + +.v-base-dialog-toolbar-area { + margin: 8px; +} + +.v-base-dialog-toolbar-item { + width: auto; + height: 48px; +} + +.v-base-dialog-toolbar-button { + width: 32px; + margin: { + left: 4px; + right: 4px; + } +} + +// +// Non Global ... +.v-avatar-image { + // + margin: auto; + display: block; + max-width: 100%; + max-height: 100%; + + // + clip-path: circle(); +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\account\v-avatar\v-avatar.component.ts + +import { + Input, + Component, + ViewChild, + ElementRef, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + XColorIdentifier, + isNullOrUndefined, + isNullOrEmptyString, + XColorWithBrightness, +} from 'x-framework-core'; +import { + VAvatarCssVar, + VAvatarModalContext, + VAvatarReturnAction, + VAvatarModalContextIdentifier, + VAvatarReturnActionIdentifier, +} from './v-avatar.typings'; +import { VBaseComponent } from '../../classes/v-base.component'; +import { XIconNames, XSlotLayout, XSlotName } from 'x-framework-components'; + +/** + * a Component to Show User Avatar as Model ... + */ +@Component({ + selector: 'v-avatar', + templateUrl: './v-avatar.component.html', + styleUrls: ['./v-avatar.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class VAvatarComponent extends VBaseComponent { + // + //#region Properties ... + // + readonly SlotNames = Object.assign({}, XSlotName); + readonly SlotLayouts = Object.assign({}, XSlotLayout); + + /** + * retrieve Toolbar container ViewChild + * to Calculate Toolbar Height ... + */ + @ViewChild('vBaseDialogToolbar') + vBaseDialogToolbarContainer!: ElementRef; + + /** + * Specified Modal Type ... + */ + @Input() + context: VAvatarModalContextIdentifier; + + /** + * user Id ... + */ + @Input() + userId: string; + + /** + * Specified Avatar Image Url ... + */ + @Input() + avatarUrl: string; + + /** + * Specified User's Full Name ... + */ + @Input() + fullName: string; + + /** + * Background Color of Component ... + */ + @Input() + backgroundColor: XColorIdentifier = XColorWithBrightness.Light; + + /** + * Toolbar Color ... + */ + @Input() + toolbarColor: XColorIdentifier = XColorWithBrightness.PrimaryTint; + + /** + * Progressbar Color ... + */ + @Input() + progressbarColor: XColorIdentifier = XColorWithBrightness.Warning; + showProgrssbar = false; + + /** + * Action Icon Names Specifiecations ... + */ + @Input() + cancelIcon = XIconNames.close; + + @Input() + selectIcon = XIconNames.profile; + + @Input() + backIcon = XIconNames.arrow_back; + //#endregion + + // + //#region LifeCycles ... + afterViewInit(): void { + super.afterViewInit(); + + // + this.applyContentSize(); + } + + /** + * Register Required Handlers ... + */ + registerInstanceHandlers(): void { + super.registerInstanceHandlers(); + } + //#endregion + + // + //#region UI Handlers ... + /** + * Use for Handle Modela Closing Action based on Context ... + * @returns + */ + async handleClosePopup(action?: VAvatarReturnActionIdentifier) { + // + if (isNullOrUndefined(this.context) || isNullOrEmptyString(this.context)) { + return; + } + + // + switch (this.context) { + // + case VAvatarModalContext.Modal: + await this.managerService.dialogService.modalController.dismiss(action); + break; + + // + case VAvatarModalContext.PopOver: + await this.managerService.dialogService.popoverController.dismiss( + action + ); + break; + } + } + + /** + * When User Clicked on Select Icon ... + */ + async handleSelectAction() { + await this.handleClosePopup(VAvatarReturnAction.OpenProfile); + } + //#endregion + + // + //#region Private ... + /** + * Calculate Toolbar Height and add a CSSVar to + * use it for Content Sizing ... + * + * @returns + */ + private async applyContentSize() { + // + const height = + this.vBaseDialogToolbarContainer.nativeElement.getBoundingClientRect() + .height; + if (height === 0) { + setTimeout(() => this.applyContentSize(), 100); + return; + } + + // + this.setCssVar(VAvatarCssVar.DialogToolbarHeight, `${height}px`); + this.detectChanges(); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\account\v-avatar\v-avatar.typings.ts + +export enum VAvatarModalContext { + Modal = 'modal', + PopOver = 'pop_over', +} + +export type VAvatarModalContextIdentifier = VAvatarModalContext | string; + +export enum VAvatarCssVar { + DialogToolbarHeight = 'dialog-toolbar-height', +} + +export enum VAvatarReturnAction { + OpenProfile = 'open_profile', +} + +export type VAvatarReturnActionIdentifier = VAvatarReturnAction | string; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\account\v-friendship\v-friendship.component.html + +
+ +
+ + + + + + +
+ {{ resourceProvider(AppResourceIDs.followers) }} +
+

+ {{ getFollowersCount() | async }} +

+
+ +
+ {{ resourceProvider(AppResourceIDs.followings) }} +
+

+ {{ getFollowingsCount() | async }} +

+
+
+
+ + + +
+ +
+ {{ resourceProvider(AppResourceIDs.friendship_state) }} +
+
+ {{ getValue(friendshipState) | async }} +
+
+
+
+
+ + +
+ + + + + +
+ + + + +
+
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\account\v-friendship\v-friendship.component.scss + +@import "../../../../theme/theme.scss"; + +// +.v-friendship { + width: 100%; + display: inline-block; +} + +// +.v-friendship-actions, +.v-friendship-actions-container, +.v-friendship-actions-menu-button { + display: inline-block; +} + +// +.v-friendship-state, +.v-friendship-actions-container { + width: 100%; + margin: { + top: 6px; + bottom: 6px; + } +} + +// +.v-friendship-state { + text-align: center; +} + +// +.v-friendship-action-button { + margin: { + left: 3px; + right: 3px; + } +} + +// +.x-title, +.x-description { + display: block; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\account\v-friendship\v-friendship.component.ts + +import { + Input, + Output, + Component, + EventEmitter, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + nameof, + XOneOrManyType, + XColorIdentifier, + isNullOrUndefined, + isNullOrEmptyString, + XColorWithBrightness, +} from 'x-framework-core'; +import { + FriendshipAction, + VFriendshipEventModel, + FriendshipActionDescriptor, + FriendshipActionsPresentType, + defaultFriendshipBlockDescriptor, + defaultFriendshipRejectDescriptor, + defaultFriendshipCancelDescriptor, + defaultFriendshipAcceptDescriptor, + defaultFriendshipUnblockDescriptor, + defaultFriendshipUnFollowDescriptor, + FriendshipActionsPresentTypeIdentifier, + defaultFriendshipFollowActionDescriptor, +} from './v-friendship.typings'; +import { + XSlotName, + XButtonType, + XSlotLayout, + XPopoverOption, + XPopoverItemOptionSlot, +} from 'x-framework-components'; +import { + XFriendshipType, + XFriendshipState, + XFriendshipInfoDto, +} from 'x-framework-identity-sdk'; +import { concatMap, filter, map } from 'rxjs/operators'; +import { FriendshipPresentType } from './v-friendship.typings'; +import { VBaseComponent } from '../../classes/v-base.component'; + +/** + * Represent Friendship Info ... + */ +@Component({ + // tslint:disable-next-line:component-selector + selector: 'v-friendship', + templateUrl: './v-friendship.component.html', + styleUrls: ['./v-friendship.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class VFriendshipComponent extends VBaseComponent { + // + //#region Props ... + // + readonly SlotNames = Object.assign({}, XSlotName); + readonly SlotLayouts = Object.assign({}, XSlotLayout); + readonly ButtonTypes = Object.assign({}, XButtonType); + readonly FriendshipActionsPresentTypes = Object.assign( + {}, + FriendshipActionsPresentType + ); + readonly PresentTypes = Object.assign({}, FriendshipPresentType); + + // + friendshipState: string; + friendshipActions: Array = []; + + // + @Input() + cssClass: XOneOrManyType = []; + + // + @Input() + color: XColorIdentifier = XColorWithBrightness.Light; + + @Input() + foregroundColor: boolean = false; + + @Input() + labelColor: XColorIdentifier = XColorWithBrightness.Primary; + + @Input() + valueColor: XColorIdentifier = XColorWithBrightness.Dark; + + // + @Input() + showFriendshipState: boolean = true; + + // + @Input() + presentType: FriendshipPresentType = FriendshipPresentType.AsProfile; + + // + @Input() + actionType: FriendshipActionsPresentTypeIdentifier = + FriendshipActionsPresentType.AsButton; + + // + @Input() + model: XFriendshipInfoDto; + + // + //#region Events ... + @Output() + actionFired = new EventEmitter(); + //#endregion + //#endregion + + // + //#region Constructor ... + onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region Model Changed ... + const isModelChanged = changeKeys.includes( + nameof('model') + ); + const isPresentTypeChanged = changeKeys.includes( + nameof('presentType') + ); + if (isModelChanged || isPresentTypeChanged) { + this.handlePrepareView(); + } + //#endregion + } + //#endregion + + // + //#region LifeCycles ... + //#endregion + + // + //#region Handlers ... + //#endregion + + // + //#region Task Handlers ... + //#endregion + + // + //#region UI Providers ... + // + canShowInfo() { + return this.getValue(this.presentType).pipe( + map( + (pt) => + pt === this.PresentTypes.AsProfile || pt === this.PresentTypes.AsInfo + ) + ); + } + + // + canShowState() { + return this.getValue(this.presentType).pipe( + map( + (pt) => + pt === this.PresentTypes.AsProfile || pt === this.PresentTypes.AsState + ), + map( + (canShow) => + canShow && + this.showFriendshipState && + !isNullOrEmptyString(this.friendshipState) + ) + ); + } + + // + canShowActions() { + return this.getValue(this.presentType).pipe( + map( + (pt) => + pt === this.PresentTypes.AsProfile || + pt === this.PresentTypes.AsFullActions || + pt === this.PresentTypes.AsFollowerActions || + pt === this.PresentTypes.AsFollowingActions + ) + ); + } + + // + canShowFriendshipActions() { + return this.getValue(this.canShowActions()).pipe( + map( + (canShow) => + canShow && + this.actionType !== this.FriendshipActionsPresentTypes.AsMenu + ) + ); + } + + // + canShowFollowerActions() { + return this.getValue(this.presentType).pipe( + map( + (pt) => + pt === this.PresentTypes.AsProfile || + pt === this.PresentTypes.AsFullActions || + pt === this.PresentTypes.AsFollowerActions + ) + ); + } + + // + canShowFollowingActions() { + return this.getValue(this.presentType).pipe( + map( + (pt) => + pt === this.PresentTypes.AsProfile || + pt === this.PresentTypes.AsFullActions || + pt === this.PresentTypes.AsFollowingActions + ) + ); + } + + /** + * Friendship Info not for Current User ... + */ + isNotMe() { + return this.getValue(this.model).pipe( + filter((info) => !!info), + concatMap((info) => this.sharedService.authService.isNotMe(info.userId)) + ); + } + + /** + * get Followers Count in current locale ... + */ + getFollowersCount() { + return this.getValue(this.model).pipe( + map((info) => { + return this.applyLocale(info ? info.followers.toString() : '0'); + }) + ); + } + + /** + * get Followings count in current locale ... + */ + getFollowingsCount() { + return this.getValue(this.model).pipe( + map((info) => { + return this.applyLocale(info ? info.followings.toString() : '0'); + }) + ); + } + //#endregion + + // + //#region UI Handlers ... + /** + * when user Click on friendship actions menu item ... + * + * @param event + */ + async handleFriendshipActionsMenuClicked(event: any) { + // + // Common Options ... + const onlyIcon = false; + const slot: XPopoverItemOptionSlot = 'start'; + + // + // Menu Popover Actions ... + const options: XPopoverOption[] = this.friendshipActions.map((fd) => { + return { + slot, + icon: fd.icon, + color: fd.color, + title: fd.title, + id: fd.identifier, + onlyIcon: onlyIcon, + handler: fd.handler, + disabled: this.uiDisabled || this.loading, + } as XPopoverOption; + }); + + // + if (!this.hasChild(options)) { + return; + } + + // + // Present Context Menu ... + await this.popoverService.presentContextMenu(options, event); + } + //#endregion + + // + //#region Overrides ... + /** + * Handle Loading Event ... + * + * @param loading + */ + onLoading(loading: boolean): void { + super.onLoading(loading); + + // + if (loading) { + this.handleUiState(true); + } else { + this.handleUiState(false); + } + } + + /** + * Handle Disabled Event ... + * + * @param isDisabled + */ + onUiDisabled(isDisabled: boolean): void { + super.onUiDisabled(isDisabled); + + // + if (isDisabled) { + this.handleUiState(true); + } else { + this.handleUiState(false); + } + } + //#endregion + + // + //#region Actions ... + //#endregion + + // + //#region Private ... + /** + * do all requirement preparation when a model + * attached to view ... + */ + private handlePrepareView() { + // + // Check Model Exists ... + if (isNullOrUndefined(this.model)) { + return; + } + + // + // Generate Friendship State ... + if (this.model.isFollower && this.model.isFollowing) { + this.friendshipState = this.resourceProvider(this.AppResourceIDs.friend); + } else if (this.model.isFollower) { + this.friendshipState = this.resourceProvider( + this.AppResourceIDs.follower + ); + } else if (this.model.isFollowing) { + this.friendshipState = this.resourceProvider( + this.AppResourceIDs.following + ); + } + + // + // Checking Actions ... + const hasActions = [ + this.PresentTypes.AsProfile, + this.PresentTypes.AsFullActions, + this.PresentTypes.AsFollowerActions, + this.PresentTypes.AsFollowingActions, + ].includes(this.presentType); + const hasFollowerActions = + hasActions && + [ + this.PresentTypes.AsProfile, + this.PresentTypes.AsFullActions, + this.PresentTypes.AsFollowerActions, + ].includes(this.presentType); + const hasFollowingActions = + hasActions && + [ + this.PresentTypes.AsProfile, + this.PresentTypes.AsFullActions, + this.PresentTypes.AsFollowingActions, + ].includes(this.presentType); + + // + // Check Allow Actions Preparation or not ... + if (hasActions) { + // + const followerActions: Array = []; + const followingActions: Array = []; + + // + // Check and Prepare Following Actions ... + if (hasFollowingActions) { + // + // Follow Action ... + const canFollow = [ + XFriendshipState.None, + XFriendshipState.Rejected, + ].includes(this.model.followingState); + if (canFollow) { + followingActions.push(FriendshipAction.Follow); + } + + // + // Cancel Action ... + const canCancel = [ + XFriendshipState.Pending, + XFriendshipState.Rejected, + ].includes(this.model.followingState); + if (canCancel) { + followingActions.push(FriendshipAction.Cancel); + } + + // + // UnFollow ... + const canUnfollow = + this.model.followingState === XFriendshipState.Accepted; + if (canUnfollow) { + followingActions.push(FriendshipAction.UnFollow); + } + } + + // + // Check and Prepare Follower Actions ... + if (hasFollowerActions) { + // + // Handle Accept Action ... + const canAccept = this.model.followerState === XFriendshipState.Pending; + if (canAccept) { + followerActions.push(FriendshipAction.Accept); + } + + // + // Handle Reject Action ... + const canReject = this.model.followerState === XFriendshipState.Pending; + if (canReject) { + followerActions.push(FriendshipAction.Reject); + } + + // + // Handle Block Action ... + const canBlock = this.model.followerState === XFriendshipState.Accepted; + if (canBlock) { + followerActions.push(FriendshipAction.Block); + } + + // + // Handle UnBlock Action .../ + const canUnblock = + this.model.followerState === XFriendshipState.Blocked; + if (canUnblock) { + followerActions.push(FriendshipAction.UnBlock); + } + + // + // Handle UnFollow Action ... + const canUnfollow = + this.model.followerState === XFriendshipState.Accepted; + if (canUnfollow) { + followerActions.push(FriendshipAction.UnFollow); + } + } + + // + // Check Actions ... + if (this.hasChild(followerActions) || this.hasChild(followingActions)) { + this.prepareActions(followerActions, followingActions); + } + } + } + + /** + * Preparing Require Action Descriptors ... + * + * @param follower + * @param following + * @returns + */ + private prepareActions( + follower: Array, + following: Array + ) { + // + this.friendshipActions = []; + if (isNullOrUndefined(this.model)) { + this.detectChanges(); + return; + } + + // + let actions: Array = []; + + // + // Prepare Follower Actions ... + if (this.hasChild(follower)) { + // + actions = [ + ...actions, + ...follower.map((identifier) => + this.getActionDescriptor(XFriendshipType.Follower, identifier) + ), + ]; + } + + // + // Prepare Following Actions ... + if (this.hasChild(following)) { + // + actions = [ + ...actions, + ...following.map((identifier) => + this.getActionDescriptor(XFriendshipType.Following, identifier) + ), + ]; + } + + // + if (this.hasChild(actions)) { + this.friendshipActions = [...actions]; + } + + // + this.detectChanges(); + } + + /** + * Prepare and Retrieve Specified Action Desxcriptor ... + * + * @param type + * @param action + * @returns + */ + private getActionDescriptor(type: XFriendshipType, action: FriendshipAction) { + let result: FriendshipActionDescriptor; + switch (action) { + // + case FriendshipAction.Accept: + result = { + ...defaultFriendshipAcceptDescriptor, + }; + break; + + // + case FriendshipAction.Reject: + result = { + ...defaultFriendshipRejectDescriptor, + }; + break; + + // + case FriendshipAction.Block: + result = { + ...defaultFriendshipBlockDescriptor, + }; + break; + + // + case FriendshipAction.UnBlock: + result = { + ...defaultFriendshipUnblockDescriptor, + }; + break; + + // + case FriendshipAction.Cancel: + result = { + ...defaultFriendshipCancelDescriptor, + }; + break; + + // + case FriendshipAction.Follow: + result = { + ...defaultFriendshipFollowActionDescriptor, + }; + break; + + // + case FriendshipAction.UnFollow: + // + // Here we have to Change Title of + // Default action based on Given Type ... + result = { + ...defaultFriendshipUnFollowDescriptor, + // + title: + type === XFriendshipType.Follower + ? this.AppResourceIDs.unfollow_follower + : type === XFriendshipType.Following + ? this.AppResourceIDs.unfollow_following + : defaultFriendshipFollowActionDescriptor.title, + }; + break; + } + + // + // Attach Requirements to Default Descriptors ... + + // + // Set Friendship Type ... + result.type = type; + + // + // Prepare Action Model ... + const actionModel: VFriendshipEventModel = { + type, + action: action, + destUser: this.model.userId, + }; + + // + // Set Handler to Fire Event ... + result.handler = () => { + this.actionFired.emit(actionModel); + }; + + // + return result; + } + + /** + * handle ui state changed event ... + * + * @param disabled + */ + private async handleUiState(disabled: boolean) {} + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\account\v-friendship\v-friendship.tools.ts + +import { + isNullOrUndefined, + XResourceIdentifier, + isNullOrEmptyString, + hasTaskId, +} from 'x-framework-core'; +import { + FriendshipActionDescriptor, + defaultFriendshipBlockDescriptor, + defaultFriendshipCancelDescriptor, + defaultFriendshipAcceptDescriptor, + defaultFriendshipRejectDescriptor, + defaultFriendshipUnblockDescriptor, + defaultFriendshipUnFollowDescriptor, + defaultFriendshipFollowActionDescriptor, +} from './v-friendship.typings'; +import { + XFriendDto, + XUserProfileDto, + XFriendshipType, + XFriendshipState, + XFriendshipInfoDto, +} from 'x-framework-identity-sdk'; +import { AppResourceIDs } from 'src/app/config/localization.config'; + +const defaultUserProfileFullName = '...'; +const defaultUserAvatar = '/assets/image/default-user-image.png'; +const defaultPlaceHolderImage = '/assets/image/img-placeholder.png'; + +/** + * retrieve a XFriendshipInfoDto instance Object's Friendship state Resource ID ... + * + * @param model + * @returns + */ +export function getFriendshipStateResourceID(model: XFriendshipInfoDto) { + // + let result: XResourceIdentifier = undefined; + + // + if (isNullOrUndefined(model)) { + return result; + } + + // + if (!!model.isFollower && !!model.isFollowing) { + result = AppResourceIDs.friend; + } else if (!!model.isFollower) { + result = AppResourceIDs.follower; + } else if (!!model.isFollowing) { + result = AppResourceIDs.following; + } + + // + return result; +} + +/** + * Prepare User Profile Full Name ... + * + * @param model + * @returns + */ +export function getFullName(model: XUserProfileDto) { + // + if ( + isNullOrUndefined(model) || + ((isNullOrUndefined(model.firstName) || + isNullOrEmptyString(model.firstName)) && + (isNullOrUndefined(model.lastName) || + isNullOrEmptyString(model.lastName))) + ) { + return defaultUserProfileFullName; + } + + // + return `${model.firstName} ${model.lastName}`; +} + +/** + * Retrieve Specified FriendshipType's Resource ID ... + * + * @param type + * @returns + */ +export function getFriendshipTypeResourceID(type: XFriendshipType) { + // + let result: XResourceIdentifier; + + // + // Validate Arg ... + if (isNullOrUndefined(type)) { + return result; + } + + // + switch (type) { + // + case XFriendshipType.Follower: + result = AppResourceIDs.follower; + break; + + // + case XFriendshipType.Following: + result = AppResourceIDs.following; + break; + } + + // + return result; +} + +/** + * Retrieve Specified FrindshipState's Resource ID ... + * (also based on FriendshipType if provided) + * + * @param state + * @param type + * @returns + */ +export function getFriendshipStateByTypeResourceID( + state: XFriendshipState, + type?: XFriendshipType +) { + // + let result: XResourceIdentifier; + + // + // Validate Arg ... + if (isNullOrUndefined(state)) { + return result; + } + + // + switch (state) { + // + case XFriendshipState.None: + result = AppResourceIDs.unknown; + break; + + // + case XFriendshipState.Accepted: + // + if (isNullOrUndefined(type)) { + result = AppResourceIDs.accepted; + } else if (type === XFriendshipType.Follower) { + result = AppResourceIDs.follower; + } else if (type === XFriendshipType.Following) { + result = AppResourceIDs.following; + } else { + result = AppResourceIDs.accepted; + } + break; + + // + case XFriendshipState.Rejected: + result = AppResourceIDs.rejected; + break; + + // + case XFriendshipState.Pending: + // + if (isNullOrUndefined(type)) { + result = AppResourceIDs.pending; + } else if (type === XFriendshipType.Follower) { + result = AppResourceIDs.pending_follower; + } else if (type === XFriendshipType.Following) { + result = AppResourceIDs.pending_following; + } else { + result = AppResourceIDs.pending; + } + break; + + // + case XFriendshipState.Blocked: + result = AppResourceIDs.blocked; + break; + } + + // + return result; +} + +/** + * retrieve a XUserProfileDto instance Object's Friendship state Resource ID ... + * + * @param model + * @returns + */ +export function getProfileFriendshipStateResourceID(model: XUserProfileDto) { + // + let result: XResourceIdentifier = undefined; + + // + if (!isNullOrUndefined(model) && !isNullOrUndefined(model.friendshipInfo)) { + result = getFriendshipStateResourceID(model.friendshipInfo); + } + + // + return result; +} + +/** + * Retrieve Required Actions Descriptors based on FriendDto ... + * these are Default Actions and Must be reviewed ... + * + * @param item + * @returns + */ +export function parseFriendAction(item: XFriendDto) { + // + const result: Array = []; + + // + // Collect Default Actions Based On Friendship Type ... + if (item.type === XFriendshipType.Follower) { + // + // Accept ... + if (canAcceptFriendshipState(item.state)) { + result.push({ ...defaultFriendshipAcceptDescriptor }); + } + + // + // Reject ... + if (canRejectFriendshipState(item.state)) { + result.push({ ...defaultFriendshipRejectDescriptor }); + } + + // + // Block ... + if (canBlockFriendshipState(item.state)) { + result.push({ ...defaultFriendshipBlockDescriptor }); + } + + // + // Unblock ... + if (canUnblockFriendshipState(item.state)) { + result.push({ ...defaultFriendshipUnblockDescriptor }); + } + + // + // UnFollow ... + if (canUnFollowFriendshipState(item.state)) { + result.push({ ...defaultFriendshipUnFollowDescriptor }); + } + } else if (item.type === XFriendshipType.Following) { + // + // Follow ... + if (canFollowFriendshipState(item.state)) { + result.push({ ...defaultFriendshipFollowActionDescriptor }); + } + + // + // Cancel ... + if (canCancelFriendshipState(item.state)) { + result.push({ ...defaultFriendshipCancelDescriptor }); + } + + // + // UnFollow ... + if (canUnFollowFriendshipState(item.state)) { + result.push({ ...defaultFriendshipUnFollowDescriptor }); + } + } + + // + return result; +} + +/** + * get Model Full Name ... + * + * @param model + * @returns + */ +export function getFullNameOfFriend(model: XFriendDto) { + // + let result = ''; + + // + if (isNullOrUndefined(model)) { + return result; + } + + // + result = `${model.firstname} ${model.lastname}`; + + // + return result; +} + +/** + * Check Can Follow or not ... + * + * @param state + * @returns + */ +export function canFollowFriendshipState(state: XFriendshipState) { + return [XFriendshipState.None, XFriendshipState.Rejected].includes(state); +} + +/** + * Check Can Cancel or not ... + * + * @param state + * @returns + */ +export function canCancelFriendshipState(state: XFriendshipState) { + return [XFriendshipState.Pending, XFriendshipState.Rejected].includes(state); +} + +/** + * Check Can UnFollow or not ... + * + * @param state + * @returns + */ +export function canUnFollowFriendshipState(state: XFriendshipState) { + return [XFriendshipState.Accepted].includes(state); +} + +/** + * Check Can Block or not ... + * + * @param state + * @returns + */ +export function canBlockFriendshipState(state: XFriendshipState) { + return [XFriendshipState.Accepted].includes(state); +} + +/** + * Check Can UnBlock or not ... + * + * @param state + * @returns + */ +export function canUnblockFriendshipState(state: XFriendshipState) { + return [XFriendshipState.Blocked].includes(state); +} + +/** + * Check Can Accept or not ... + * + * @param state + * @returns + */ +export function canAcceptFriendshipState(state: XFriendshipState) { + return [XFriendshipState.Pending].includes(state); +} + +/** + * Check Can Reject or not ... + * + * @param state + * @returns + */ +export function canRejectFriendshipState(state: XFriendshipState) { + return [XFriendshipState.Pending].includes(state); +} + +// +//#region Friendship Tasks ... +/** + * All Available Friendship Tasks ... + */ +export enum XFriendshipTasks { + Block = 'block', + Follow = 'follow', + Accept = 'accept', + Reject = 'reject', + Cancel = 'cancel', + UnBlock = 'un_block', + LoadProfile = 'load_profile', + UnFollowFollower = 'un_follow_follower', + UnFollowFollowing = 'un_follow_following', +} + +export type XFriendshipTasksIdentifier = XFriendshipTasks | string; + +/** + * export an array of all available Friendship Tasks ... + * + * @returns + */ +export function getFriendshipTasks() { + return [ + XFriendshipTasks.Block, + XFriendshipTasks.Follow, + XFriendshipTasks.Accept, + XFriendshipTasks.Reject, + XFriendshipTasks.Cancel, + XFriendshipTasks.UnBlock, + XFriendshipTasks.LoadProfile, + XFriendshipTasks.UnFollowFollower, + XFriendshipTasks.UnFollowFollowing, + ]; +} + +/** + * Check a Task Name is Friendship Task or not ... + * + * @param name + * @returns + */ +export function isFriendshipTask(name: XFriendshipTasksIdentifier) { + // + const allTasks = getFriendshipTasks(); + const result = allTasks.find( + (t) => name === t || (hasTaskId(name) && name.includes(t)) + ); + + // + return result; +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\account\v-friendship\v-friendship.typings.ts + +import { + XColorIdentifier, + XResourceIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { XIconNames } from 'x-framework-components'; +import { XFriendshipType } from 'x-framework-identity-sdk'; +import { AppResourceIDs } from 'src/app/config/localization.config'; + +/** + * Represent a Frendship Action Descriptor ... + */ +export interface FriendshipActionDescriptor { + icon: string; + payload?: string; + handler?: () => void; + type: XFriendshipType; + color: XColorIdentifier; + title: XResourceIdentifier; + identifier: FriendshipAction; +} + +export type FriendshipTypeIdentifier = XFriendshipType | string; + +/** + * Represent Friendship Actions ... + */ +export enum FriendshipAction { + Follow = 'follow', + Cancel = 'cancel', + Accept = 'accept', + Reject = 'reject', + Block = 'block', + UnBlock = 'unblock', + UnFollow = 'unfollow', +} + +export type FriendshipActionIdentifier = FriendshipAction | string; + +/** + * How to Represent Actions in Friendship View ... + */ +export enum FriendshipActionsPresentType { + AsFAB = 'AsFAB', + AsMenu = 'AsMenu', + AsButton = 'AsButton', + AsMenuAndFab = 'AsMenuAndFab', + AsMenuAndButton = 'AsMenuAndButton', +} + +export type FriendshipActionsPresentTypeIdentifier = + | FriendshipActionsPresentType + | string; + +/** + * Action Event Model ... + */ +export interface VFriendshipEventModel { + destUser: string; + type: FriendshipTypeIdentifier; + action: FriendshipActionIdentifier; +} + +/** + * All Available Present Types for Friendship View ... + */ +export enum FriendshipPresentType { + /** + * only shown follwer/following(s) count ... + */ + AsInfo = 'as_info', + /** + * represent current friendship state ... + */ + AsState = 'as_state', + /** + * contains all available actions ... + */ + AsProfile = 'as_profile', + /** + * contains only actions ... + */ + AsFullActions = 'as_full_actions', + /** + * contains only follower actions ... + */ + AsFollowerActions = 'as_follower_actions', + /** + * contains only following actions ... + */ + AsFollowingActions = 'as_following_actions', +} + +export type FriendshipPresentTypeIdentifier = FriendshipPresentType | string; + +// +// Here we implemenmt all required Default Action Descriptors ... + +/** + * Default Follow Action ... + */ +export const defaultFriendshipFollowActionDescriptor: FriendshipActionDescriptor = + { + title: AppResourceIDs.follow, + type: XFriendshipType.Following, + icon: XIconNames.friendship_reuest, + identifier: FriendshipAction.Follow, + color: XColorWithBrightness.PrimaryShade, + }; + +/** + * Default Cancel Action ... + */ +export const defaultFriendshipCancelDescriptor: FriendshipActionDescriptor = { + title: AppResourceIDs.cancel_request, + type: XFriendshipType.Follower, + icon: XIconNames.friendship_cancel, + identifier: FriendshipAction.Cancel, + color: XColorWithBrightness.WarningShade, +}; + +/** + * Default UnFollow Action ... + */ +export const defaultFriendshipUnFollowDescriptor: FriendshipActionDescriptor = { + title: AppResourceIDs.unfollow, + type: XFriendshipType.Follower, + icon: XIconNames.friendship_unfollow, + identifier: FriendshipAction.UnFollow, + color: XColorWithBrightness.DangerShade, +}; + +/** + * Default Accept Action ... + */ +export const defaultFriendshipAcceptDescriptor: FriendshipActionDescriptor = { + title: AppResourceIDs.accept_request, + type: XFriendshipType.Follower, + icon: XIconNames.friendship_accept, + identifier: FriendshipAction.Accept, + color: XColorWithBrightness.SuccessShade, +}; + +/** + * Default Reject Action ... + */ +export const defaultFriendshipRejectDescriptor: FriendshipActionDescriptor = { + title: AppResourceIDs.cancel_request, + type: XFriendshipType.Follower, + icon: XIconNames.friendship_reject, + identifier: FriendshipAction.Reject, + color: XColorWithBrightness.DangerShade, +}; + +/** + * Default Block Action ... + */ +export const defaultFriendshipBlockDescriptor: FriendshipActionDescriptor = { + title: AppResourceIDs.block, + type: XFriendshipType.Follower, + icon: XIconNames.friendship_block, + identifier: FriendshipAction.Block, + color: XColorWithBrightness.DangerShade, +}; + +/** + * Default Unblock Action ... + */ +export const defaultFriendshipUnblockDescriptor: FriendshipActionDescriptor = { + title: AppResourceIDs.unblock, + type: XFriendshipType.Follower, + icon: XIconNames.friendship_unblock, + identifier: FriendshipAction.UnBlock, + color: XColorWithBrightness.PrimaryShade, +}; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\account\v-login\v-login.component.html + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\account\v-login\v-login.component.scss + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\account\v-login\v-login.component.ts + +import { + Output, + Component, + EventEmitter, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + getFieldNames, + XStandardType, + XExceptionIDs, + isNullOrUndefined, + isNullOrEmptyString, +} from 'x-framework-core'; +import { + XFormControlType, + XFormControlConfig, + XFormControlAppearance, +} from 'x-framework-components'; +import { Validators } from '@angular/forms'; +import { XLoginRequestDto } from 'x-framework-identity-sdk'; +import { + VBaseDtoComponent, + VBaseDtoModelAction, + VBaseDtoModelFiredActionModel, + VBaseDtoPresentType, + VModelFieldDescriptor, + VModelValidationResult, +} from '../../v-dto/v-dto.typings'; +import { Subject } from 'rxjs'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'v-login', + templateUrl: './v-login.component.html', + styleUrls: ['./v-login.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class VLoginComponent extends VBaseDtoComponent { + // + //#region Props ... + // + @Output() + login: EventEmitter = new EventEmitter(); + + // + readonly FormControlTypes = Object.assign({}, XFormControlType); + readonly FormControlAppearances = Object.assign({}, XFormControlAppearance); + readonly title = this.resourceProvider(this.AppResourceIDs.login_page_title); + + // + isInPage = true; + wrapWithCard = true; + showFieldsForm = false; + cardShowActions = true; + passwordVisible = false; + showValidationErrors = false; + showFormCancelButton = false; + presentType = VBaseDtoPresentType.AsForm; + cardTitle = this.AppResourceIDs.login_page_title; + formActionButtonAddResource = this.AppResourceIDs.login; + formActionButtonUpdateResource = this.AppResourceIDs.login; + + // + defaultModel: XLoginRequestDto = { + userSelectBy: '', + password: '', + device: undefined, + language: undefined, + }; + readonly FieldNames = getFieldNames(this.defaultModel); + + /** + * Describe Model ... + */ + defaultFieldDescriptors: VModelFieldDescriptor[] = [ + // + // UserSelect By ... + { + hide: false, + hideLabel: false, + allowFieldForm: true, + key: this.FieldNames.userSelectBy, + type: this.ModelFieldTypes.String, + value: this.defaultModel.userSelectBy, + label: this.AppResourceIDs.user_identifier, + formControlConfig: { + index: 0, + propName: this.FieldNames.userSelectBy, + type: { + type: this.FormControlTypes.Text, + config: {}, + }, + validators: { + validators: [Validators.required], + }, + focusHandler: new Subject(), + eventHandlers: {}, + appearance: { + hidden: false, + disabled: false, + // label: this.AppResourceIDs.user_identifier, + appearance: this.FormControlAppearances.Outline, + placeholder: this.AppResourceIDs.user_identifier, + tooltip: this.AppResourceIDs.user_identifier_description, + icons: { + prefix: { + applyStateColor: true, + name: this.IconNames.user, + }, + }, + }, + } as XFormControlConfig, + } as VModelFieldDescriptor, + // + // Password ... + { + hide: false, + hideLabel: false, + allowFieldForm: true, + key: this.FieldNames.password, + value: this.defaultModel.password, + type: this.ModelFieldTypes.String, + label: this.AppResourceIDs.password, + formControlConfig: { + index: 1, + propName: this.FieldNames.password, + type: { + type: this.FormControlTypes.Password, + config: {}, + }, + validators: { + validators: [Validators.required], + }, + eventHandlers: {}, + appearance: { + hidden: false, + disabled: false, + tooltip: this.AppResourceIDs.password, + // label: this.AppResourceIDs.password, + placeholder: this.AppResourceIDs.password, + appearance: this.FormControlAppearances.Outline, + icons: { + prefix: { + applyStateColor: true, + name: this.IconNames.verified, + }, + suffix: { + handler: () => { + this.togglePasswordVisible(); + }, + applyStateColor: false, + color: this.ColorNames.PrimaryShade, + name: this.passwordVisible + ? this.IconNames.visible_off + : this.IconNames.visible, + }, + }, + }, + } as XFormControlConfig, + } as VModelFieldDescriptor, + ]; + //#endregion + + // + //#region LifeCycles ... + registerViewHandlers(): void { + super.registerViewHandlers(); + + // + // Handle Fire Login Event ... + this.applyTakeUntilWrapper( + this.dtoModelActionFired.asObservable() + ).subscribe((action: VBaseDtoModelFiredActionModel) => { + // + // Handle Login Action ... + if (action.action === VBaseDtoModelAction.AddOrUpdate) { + // + if (this.lock) { + this.lock = false; + } + + // + this.login.emit(); + } + }); + } + //#endregion + + // + //#region Abstract ... + /** + * Validate Model ... + * + * @param model + * @returns + */ + validate( + model?: Partial + ): VModelValidationResult { + // + let result: VModelValidationResult; + + // + let isValid = false; + const validationErrors: string[] = []; + + // + model = { + ...this.model, + ...model, + }; + const normalized = this.normalize(model); + + // + // Handle Validations ... + + // + // Model Not Null or Undefined ... + isValid = + !isNullOrUndefined(normalized) && !this.isSameAsDefault(normalized); + if (!isValid) { + // + validationErrors.push( + this.getExceptionMessage(XExceptionIDs.InvalidArgs) + ); + } else { + // + // Check Other Validations ... + + // + // User Select By ... + isValid = !isNullOrEmptyString(normalized.userSelectBy); + if (!isValid) { + // + validationErrors.push( + this.getExceptionMessage(XExceptionIDs.InvalidUserSelector) + ); + } + + // + // Password ... + isValid = !isNullOrEmptyString(normalized.password); + if (!isValid) { + // + validationErrors.push( + this.getExceptionMessage(XExceptionIDs.InvalidPassword) + ); + } + } + + // + isValid = !this.hasChild(validationErrors); + + // + // Prepare Result ... + result = { + isValid, + normalized, + validationErrors, + isValidForAdd: true, + isValidForUpdate: true, + }; + + // + return result; + } + + /** + * Prepare Model for Actions ... + * + * @param model XLoginRequestDto Model ... + * @returns + */ + prepare(model?: XLoginRequestDto): XLoginRequestDto { + // + if (isNullOrUndefined(model)) { + model = { + ...this.defaultModel, + }; + } + + // + // Preparing Model Filesd ... + model = { + userSelectBy: !isNullOrEmptyString(model.userSelectBy) + ? model.userSelectBy + : this.defaultModel.userSelectBy, + password: !isNullOrEmptyString(model.password) + ? model.password + : this.defaultModel.password, + device: !isNullOrUndefined(model.device) + ? model.device + : this.managerService.settingsService.device, + language: !isNullOrEmptyString(model.language) + ? model.language + : this.managerService.localizationService.locale, + }; + + // + return model; + } + + /** + * Handle Register Focus Handler for User Select By ... + * + * @param to + * @param from + */ + onPresentTypeChanged(to: string, from: string): void { + // + if (to === this.PresentTypes.AsForm) { + // + // Make Sure runs when Model Form Config Prepared ... + if (isNullOrUndefined(this.modelFormConfig)) { + // + setTimeout(() => this.onPresentTypeChanged(to, from), 100); + return; + } + + // + this.registerFocusHandler( + this.modelFormConfig.name, + this.FieldNames.userSelectBy + ); + } + } + //#endregion + + // + //#region Constructor ... + //#endregion + + // + //#region Overrides ... + //#endregion + + // + //#region Private ... + private async togglePasswordVisible() { + // + // Toggle Password Visibility ... + this.passwordVisible = !this.passwordVisible; + + // + // Apply Changes on Password based on Flag ... + this.defaultFieldDescriptors[1] = { + ...this.fieldDescriptors[1], + formControlConfig: { + ...this.fieldDescriptors[1].formControlConfig, + type: { + ...this.fieldDescriptors[1].formControlConfig.type, + type: this.passwordVisible + ? this.FormControlTypes.Text + : this.FormControlTypes.Password, + }, + appearance: { + ...this.fieldDescriptors[1].formControlConfig.appearance, + icons: { + ...this.fieldDescriptors[1].formControlConfig.appearance.icons, + suffix: { + ...this.fieldDescriptors[1].formControlConfig.appearance.icons + .suffix, + applyStateColor: false, + color: this.ColorNames.PrimaryShade, + name: this.passwordVisible + ? this.IconNames.visible_off + : this.IconNames.visible, + }, + }, + }, + }, + }; + + // + // Reset From Model Config to Apply Changes ... + this.modelFormConfig = undefined; + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\account\v-profile\v-profile.component.html + +
+ +
+ + + +
+ + + +
+ +
+
+ + + + +

{{ getResourceValue(AppResourceIDs.profile) | async }}

+
+
+ + + +
+ +
+
+
+
+
+ + + +
+ + +
+ + +
+
+ + + + + + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\account\v-profile\v-profile.component.scss + +@import "../../../../theme/theme.scss"; + +:host { + --dialog-toolbar-height: 0px; + --background: var(-x-color-light); +} + +x-icon { + --size: 24px; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + margin: 0 !important; +} + +.v-base-dialog { + width: 100%; + height: 100%; + // height: calc(100% - var(--dialog-toolbar-height)); + // margin-bottom: var(--dialog-toolbar-height); +} + +.v-base-dialog-content { + height: 100%; + + // + padding-bottom: var(--dialog-toolbar-height); +} + +.v-base-dialog-toolbar-area { + margin: 8px; +} + +.v-base-dialog-toolbar-item { + width: auto; + height: 48px; +} + +.v-base-dialog-toolbar-button { + width: 32px; + margin: { + left: 4px; + right: 4px; + } +} + +// +// Non Global ... +.v-avatar-image { + // + margin: auto; + display: block; + max-width: 100%; + max-height: 100%; + + // + clip-path: circle(); +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\account\v-profile\v-profile.component.ts + +import { + Input, + Component, + ViewChild, + ElementRef, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + XTask, + nameof, + XColorIdentifier, + isNullOrUndefined, + isNullOrEmptyString, + XColorWithBrightness, +} from 'x-framework-core'; +import { + VProfileCssVar, + VProfileModalContext, + VProfileComponentTasks, + VProfileModalContextIdentifier, +} from './v-profile.typings'; +import { XUserProfileDto } from 'x-framework-identity-sdk'; +import { VUserPresentType } from '../v-user/v-user.typings'; +import { VBaseComponent } from '../../classes/v-base.component'; +import { XIconNames, XSlotLayout, XSlotName } from 'x-framework-components'; + +/** + * a Component For Use to Show User Profile as Model ... + */ +@Component({ + selector: 'v-profile', + templateUrl: './v-profile.component.html', + styleUrls: ['./v-profile.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class VProfileComponent extends VBaseComponent { + // + //#region Properties ... + // + readonly SlotNames = Object.assign({}, XSlotName); + readonly SlotLayouts = Object.assign({}, XSlotLayout); + readonly UserPresentTypes = Object.assign({}, VUserPresentType); + + /** + * retrieve Toolbar container ViewChild + * to Calculate Toolbar Height ... + */ + @ViewChild('vBaseDialogToolbar') + vBaseDialogToolbarContainer!: ElementRef; + + /** + * Specified Modal Type ... + */ + @Input() + context: VProfileModalContextIdentifier; + + @Input() + userId: string; + + /** + * Specified User's Full Name ... + */ + @Input() + fullName: string; + + /** + * User Profile Info ... + */ + profile: XUserProfileDto; + + /** + * Background Color of Component ... + */ + @Input() + backgroundColor: XColorIdentifier = XColorWithBrightness.Light; + + /** + * Toolbar Color ... + */ + @Input() + toolbarColor: XColorIdentifier = XColorWithBrightness.PrimaryTint; + + /** + * Progressbar Color ... + */ + @Input() + progressbarColor: XColorIdentifier = XColorWithBrightness.Warning; + showProgrssbar = false; + + /** + * Action Icon Names Specifiecations ... + */ + @Input() + cancelIcon = XIconNames.close; + + @Input() + selectIcon = XIconNames.profile; + + @Input() + backIcon = XIconNames.arrow_back; + //#endregion + + // + //#region LifeCycles ... + async onInit() { + super.onInit(); + + // + if (!isNullOrEmptyString(this.userId)) { + await this.issueGetProfileTask(); + } + } + + async onChange(changeKeys: string[]): Promise { + super.onChange(changeKeys); + + // + const isUserIdChanged = changeKeys.includes( + nameof('userId') + ); + if (isUserIdChanged && !isNullOrEmptyString(this.userId)) { + await this.issueGetProfileTask(); + } + } + + afterViewInit(): void { + super.afterViewInit(); + + // + this.applyContentSize(); + } + + /** + * Register Required Handlers ... + */ + registerInstanceHandlers(): void { + super.registerInstanceHandlers(); + } + //#endregion + + // + //#region Task Handlers ... + async onTaskStart(name: string, task: XTask): Promise { + super.onTaskStart(name, task); + + // + if (name === VProfileComponentTasks.GetProfile) { + // + this.showProgrssbar = true; + await this.setLoadingState(true); + } + } + + async onTaskFinished(name: string, task: XTask): Promise { + super.onTaskFinished(name, task); + + // + if (name === VProfileComponentTasks.GetProfile) { + // + this.showProgrssbar = false; + await this.setLoadingState(false); + } + + // + if (name === VProfileComponentTasks.GetProfile) { + this.handleGetProfileTaskFinished(task.result); + } + } + + async onTaskFailed(name: string, task: XTask): Promise { + super.onTaskFailed(name, task); + + // + if (name === VProfileComponentTasks.GetProfile) { + // + this.showProgrssbar = false; + await this.setLoadingState(false); + } + } + //#endregion + + // + //#region UI Handlers ... + /** + * Use for Handle Modela Closing Action based on Context ... + * @returns + */ + async handleClosePopup() { + // + if (isNullOrUndefined(this.context) || isNullOrEmptyString(this.context)) { + return; + } + + // + switch (this.context) { + // + case VProfileModalContext.Modal: + await this.managerService.dialogService.modalController.dismiss(); + break; + + // + case VProfileModalContext.PopOver: + await this.managerService.dialogService.popoverController.dismiss(); + break; + } + } + //#endregion + + // + //#region Overrides ... + onLoading(loading: boolean): void { + super.onLoading(loading); + + // + this.showProgrssbar = loading; + this.detectChanges(); + } + //#endregion + + // + //#region Private ... + /** + * issue task to get user profile ... + */ + private async issueGetProfileTask() { + // + if (isNullOrEmptyString(this.userId)) { + return; + } + + // + const mTaskName = VProfileComponentTasks.GetProfile; + const mTask = this.sharedService.authService.getProfile(this.userId); + + // + await this.addOrUpdateTask({ + priority: 0, + affectLoading: true, + name: mTaskName, + task: mTask, + }); + + // + await this.dispatchTask(mTaskName); + } + + /** + * Handle User Profiile Recieived ... + * + * @param profile + */ + private handleGetProfileTaskFinished(profile: XUserProfileDto) { + // + this.profile = { + ...profile, + }; + + // + this.detectChanges(); + } + + /** + * Calculate Toolbar Height and add a CSSVar to + * use it for Content Sizing ... + * + * @returns + */ + private async applyContentSize() { + // + const height = + this.vBaseDialogToolbarContainer.nativeElement.getBoundingClientRect() + .height; + if (height === 0) { + setTimeout(() => this.applyContentSize(), 100); + return; + } + + // + this.setCssVar(VProfileCssVar.DialogToolbarHeight, `${height}px`); + this.detectChanges(); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\account\v-profile\v-profile.typings.ts + +export enum VProfileModalContext { + Modal = 'modal', + PopOver = 'pop_over', +} + +export type VProfileModalContextIdentifier = VProfileModalContext | string; + +export enum VProfileCssVar { + DialogToolbarHeight = 'dialog-toolbar-height', +} + +export enum VProfileComponentTasks { + GetProfile = 'egt_profile', +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\account\v-register\v-register.component.html + + + + + + + + +
+ +
+ + +
+ + + +
+
+ + + +
+ + + + + +
+
+ + + + + + + + + + Terms and Conditions ... + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\account\v-register\v-register.component.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\account\v-register\v-register.component.ts + +import { + XTask, + isFunction, + XTaskPriority, + nameofInstance, + XOneOrManyType, + isNullOrUndefined, + isNullOrEmptyString, +} from 'x-framework-core'; +import { + RegistrationKeys, + RegistrationTaks, + VRegistrationSteps, + isContainsRegistrationTask, +} from './v-register.typings'; +import { + XActionRequestDto, + XActionResponseDto, +} from 'x-framework-identity-sdk'; +import { Observable } from 'rxjs'; +import { Component, Input } from '@angular/core'; +import { getRegistrationStepResourceID } from './v-register.tools'; +import { VBaseInCardComponent } from '../../classes/v-base.component'; + +@Component({ + selector: 'v-register', + templateUrl: './v-register.component.html', + styleUrls: ['./v-register.component.scss'], +}) +export class VRegisterComponent extends VBaseInCardComponent { + // + //#region Props ... + readonly RegistrationSteps = Object.assign({}, VRegistrationSteps); + readonly RegistrationTaskNames = Object.assign({}, RegistrationTaks); + + /** + * Custom CSS Classes ... + */ + cssClass: XOneOrManyType = []; + + /** + * Remove Card Marigin ... + */ + removeCardMargin: boolean = false; + + // + @Input() + getTermsAndConditionsTask: (lang: string) => Observable = undefined; + + // + step: VRegistrationSteps; + actionRequest: XActionRequestDto; + actionResponse: XActionResponseDto; + //#endregion + + // + //#region LifeCycles ... + afterViewInit(): void { + super.afterViewInit(); + + // + this.loadStepInfo(); + } + + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + let isChanged = false; + let applyChanged = false; + + // + // getTermsAndConditionsTask ... + isChanged = changeKeys.includes( + nameofInstance(this, 'getTermsAndConditionsTask'), + ); + if (isChanged) { + // + let isValid = + !isNullOrUndefined(this.getTermsAndConditionsTask) && + isFunction(this.getTermsAndConditionsTask); + if (!isValid) { + // + this.getTermsAndConditionsTask = (lang: string) => { + return this.sharedService.saherElmAPIService.terms.termsProviderService.get( + lang, + ); + }; + } + } + + // + if (applyChanged) { + this.detectChanges(); + } + } + //#endregion + + // + //#region Task Handlers ... + /** + * Handle Task Start ... + * + * @param name + * @param task + */ + async onTaskStart(name: string, task: XTask) { + super.onTaskStart(name, task); + + // + // Validate Task Name ... + let isValid = isContainsRegistrationTask(name); + if (!isValid) { + return; + } + + // + await this.setLoadingState(true); + } + + /** + * Handle Task Failed ... + * + * @param name + * @param task + */ + async onTaskFailed(name: string, task: XTask) { + super.onTaskFailed(name, task); + + // + // Validate Task Name ... + let isValid = isContainsRegistrationTask(name); + if (!isValid) { + return; + } + + // + await this.setLoadingState(false); + this.showError(task.error); + } + + /** + * Handle Task Finished ... + * + * @param name + * @param task + */ + async onTaskFinished(name: string, task: XTask) { + super.onTaskFinished(name, task); + + // + // Validate Task Name ... + let isValid = isContainsRegistrationTask(name); + if (!isValid) { + return; + } + + // + await this.setLoadingState(false); + + // + if (name === this.RegistrationTaskNames.GetTermsAndConditions) { + // + // this.showSuccess(); + this.handleTermsAndConditionsTaskFinished(task.result); + } + } + //#endregion + + // + //#region Providers ... + loadStepInfo() { + // + // Read Registration Step ... + if (isNullOrUndefined(this.step)) { + // + this.step = + this.managerService.secureStorageService.readOfType( + RegistrationKeys.Step, + false, + ); + + // + // Set Terms as First Step ... + if ( + isNullOrUndefined(this.step) || + this.step == VRegistrationSteps.UnKnown + ) { + this.step = VRegistrationSteps.Terms; + } + } + + // + // Read Action Request ... + if (isNullOrUndefined(this.actionRequest)) { + // + this.actionRequest = + this.managerService.secureStorageService.readOfType( + RegistrationKeys.ActionRequest, + false, + ); + } + + // + // Read Action Response ... + if (isNullOrUndefined(this.actionResponse)) { + // + this.actionResponse = + this.managerService.secureStorageService.readOfType( + RegistrationKeys.ActionResponse, + false, + ); + } + + // + // Reload Card Sub Title ... + if (!isNullOrUndefined(this.step)) { + // + let stepResourceID = getRegistrationStepResourceID(this.step); + if (!isNullOrUndefined(stepResourceID)) { + this.cardSubTitle = this.resourceProvider(stepResourceID); + } + } + + // + // Prepare Current Step ... + this.prepareStep(); + + // + // Seva Step Info ... + this.saveStepInfo(); + } + + getCardAvatar() { + // + let result: string = undefined; + + // + if ( + !isNullOrUndefined(this.actionRequest) && + !isNullOrUndefined(this.actionRequest.avatar) + ) { + // + return this.actionRequest.avatar.name; + } + + // + return result; + } + + /** + * Preparing Registration Step Info ... + * + * @returns + */ + prepareStep() { + // + if ( + isNullOrUndefined(this.step) || + this.step !== this.RegistrationSteps.Terms || + !isFunction(this.getTermsAndConditionsTask) || + isNullOrUndefined(this.getTermsAndConditionsTask) + ) { + return; + } + + // + let isPassed = false; + + // + // Required Preparation Based on Step ... + if (this.step === this.RegistrationSteps.Terms) { + // + // Check Terms and Condiions Must read or not ... + let hasTerms = + !isNullOrUndefined(this.actionRequest) && + isNullOrEmptyString(this.actionRequest.termsAndConditions); + isPassed = hasTerms && !!this.actionRequest.acceptTerms; + + // + if (!isPassed) { + // + // Read Terms and Conditions and Attach it to ActionRequest if not Exists ... + if (!hasTerms) { + this.handleReadTermsAndConditions(); + } + } else { + } + + // + return; + } + } + //#endregion + + // + //#region Handlers ... + handleReadTermsAndConditions() { + // + // Validate ... + let isValid = + isNullOrUndefined(this.actionRequest) || + isNullOrEmptyString(this.actionRequest.termsAndConditions); + if (!isValid) { + return; + } + + // + this.issueGetTermsAndConditionsTask(); + } + //#endregion + + // + //#region Private ... + private saveStepInfo() { + // + // Step ... + this.managerService.secureStorageService.write( + RegistrationKeys.Step, + this.step, + true, + ); + + // + // Action Request ... + this.managerService.secureStorageService.write( + RegistrationKeys.ActionRequest, + this.actionRequest, + true, + ); + + // + // Action Response ... + this.managerService.secureStorageService.write( + RegistrationKeys.ActionResponse, + this.actionResponse, + true, + ); + } + + private async issueGetTermsAndConditionsTask() { + // + const name = this.RegistrationTaskNames.GetTermsAndConditions; + + // + let lang = ''; + if ( + !isNullOrUndefined(this.actionRequest) && + !isNullOrEmptyString(this.actionRequest.lang) + ) { + lang = this.actionRequest.lang; + } else { + lang = this.managerService.currentLocale; + } + if (isNullOrEmptyString(lang)) { + return; + } + + // + const task = this.getTermsAndConditionsTask(lang); + if (isNullOrUndefined(task)) { + return; + } + + // + // Add Or Update Task ... + await this.addOrUpdateTask({ + name, + task, + affectLoading: true, + priority: XTaskPriority.High, + }); + + // + // Dispatch Task ... + await this.dispatchTask(name); + } + + private handleTermsAndConditionsTaskFinished(result: string) { + console.log('recieved terms: ', result); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\account\v-register\v-register.tools.ts + +import { VRegistrationSteps } from './v-register.typings'; +import { AccountLocalizationResourceIDs } from '../account.configs'; +import { isNullOrUndefined, XResourceIdentifier } from 'x-framework-core'; + +export function getRegistrationStepResourceID(step: VRegistrationSteps) { + // + let result: XResourceIdentifier = undefined; + + // + if (!isNullOrUndefined(step)) { + // + switch (step) { + // + case VRegistrationSteps.Terms: + result = AccountLocalizationResourceIDs.accept_terms; + break; + + // + case VRegistrationSteps.EmailConfirmation: + result = AccountLocalizationResourceIDs.email_confirmation; + break; + + // + case VRegistrationSteps.UserInfo: + result = AccountLocalizationResourceIDs.input_user_info; + break; + + // + case VRegistrationSteps.AttachProfileImage: + result = AccountLocalizationResourceIDs.input_user_avatar; + break; + + // + case VRegistrationSteps.FinishRegistration: + result = AccountLocalizationResourceIDs.finish_registration; + break; + } + } + + // + return result; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\account\v-register\v-register.typings.ts + +import { isValueInEnum } from '../../tools/v-base.tools'; +import { hasChild, isNullOrUndefined } from 'x-framework-core'; + +/** + * All Steps Which has Passed for Registration ... + */ +export enum VRegistrationSteps { + /** + * Unknown ... + */ + UnKnown = 0, + /** + * User Must Accept Terms and Conditions ... + */ + Terms = 1, + /** + * User Confirm Email Address ... + */ + EmailConfirmation = 2, + /** + * User Input Info ... + */ + UserInfo = 3, + /** + * User add Profile Image ... + */ + AttachProfileImage = 4, + /** + * Finish User Registration ... + */ + FinishRegistration = 5, +} + +/** + * Registration Keys ... + */ +export enum RegistrationKeys { + Step = 'RStep', + ActionRequest = 'RAReq', + ActionResponse = 'RARes', +} + +// +//#region Tasks ... +export enum RegistrationTaks { + GetTermsAndConditions = 'Get_Terms_And_Conditions', +} + +export type RegistrationTaksIdentifier = RegistrationTaks | string; + +export function isRegistrationTaks(value: string) { + return isValueInEnum(value, Object.assign({}, RegistrationTaks)); +} + +export function isContainsRegistrationTask(value: string) { + // + let taskNames = [RegistrationTaks.GetTermsAndConditions]; + + // + let result = + hasChild(taskNames) && + !isNullOrUndefined(taskNames.find((tn) => value.includes(tn))); + + // + return result; +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\account\v-terms\v-terms.component.html + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\account\v-terms\v-terms.component.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\account\v-terms\v-terms.component.ts + +import { Input } from '@angular/core'; +import { + isNullOrEmptyString, + nameofInstance, + XLocale, + XOneOrManyType, +} from 'x-framework-core'; +import { VBaseInCardComponent } from '../../classes/v-base.component'; +import { + isVTermsPresentType, + VTermsPresentType, + VTermsPresentTypeIdentifier, +} from './v-terms.typings'; + +export class VTermsComponent extends VBaseInCardComponent { + // + //#region Props ... + readonly PresentTypes = Object.assign({}, VTermsPresentType); + + /** + * Css Class List ... + */ + @Input() + cssClass: XOneOrManyType; + + /** + * Present Type ... + */ + @Input() + presentType: VTermsPresentTypeIdentifier = this.PresentTypes.AsText; + + /** + * Specified Terms and Conditions Language ... + */ + @Input() + language: XLocale | string; + + @Input() + terms: string; + + // + lang = ''; + termsAndConditions = ''; + //#endregion + + // + //#region Life Cycles ... + async afterViewInit() { + super.afterContentInit(); + + // + await this.handlePreparePresentType(); + } + + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + let isChanged = false; + let applyChanges = false; + + // + // Present Type ... + isChanged = changeKeys.includes(nameofInstance(this, 'presentType')); + if (isChanged) { + // + if (!isVTermsPresentType(this.presentType)) { + // + applyChanges = true; + this.presentType = this.PresentTypes.AsText; + } + } + + // + // Language ... + isChanged = changeKeys.includes(nameofInstance(this, 'language')); + if (isChanged) { + // + if (isNullOrEmptyString(this.language)) { + // + applyChanges = true; + this.language = this.managerService.currentLocale; + } + } + + // + // Prepare Present Type ... + isChanged = + changeKeys.includes(nameofInstance(this, 'terms')) || + changeKeys.includes(nameofInstance(this, 'language')) || + changeKeys.includes(nameofInstance(this, 'presentType')); + if (isChanged) { + // + await this.handlePreparePresentType(); + } + + // + if (applyChanges) { + this.detectChanges(); + } + } + //#endregion + + // + //#region Providers ... + //#endregion + + // + //#region Privates ... + private async handlePreparePresentType() {} + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\account\v-terms\v-terms.typings.ts + +import { isValueInEnum } from '../../tools/v-base.tools'; + +// +//#region PresentType ... +export enum VTermsPresentType { + AsNone = 'as_none', + AsText = 'as_text', + AsForm = 'as_form', + AsMarkdown = 'as_markdown', + AsTermsManagement = 'as_terms_management', +} + +export type VTermsPresentTypeIdentifier = VTermsPresentType | string; + +export function isVTermsPresentType(value: string) { + return isValueInEnum(value, Object.assign({}, VTermsPresentType)); +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\account\v-user\v-user.component.html + + + + + + + + +
+ +
+ + +
+ +
+ +
+
+ + + +
+
+ + + +
+ + + + + + +
+ +
+ + +
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + +
+
+ + + +
+ + + + +
+
+ + + + + + + + + + + + {{ getFieldProvidedLabel(key) }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + + + + + + + + + + + + + + + +
+ + + + + + + + + + {{ getFieldProvidedValue(key) }} + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + +
+ + + + + + + +
+
+
+
+
+
+ + + + + + + + + + + + +
+ {{ getUserFullName() | async }} +
+
+ + + +
+ {{ userRole }} +
+
+ + + +
+ {{ fState }} +
+
+ + + + + + + + + + + + + + {{ getFieldProvidedLabel(key) }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ {{ err }} +
+
+
+
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\account\v-user\v-user.component.scss + +:host { + --avatar-size: 40px; +} + +.x-header { + flex: 1 1 auto; +} + +.v-dto-field { + width: 100%; + display: block; + margin: { + top: 8px; + bottom: 8px; + } +} + +.v-user { + width: 100%; + display: block; +} + +.v-user-wrapper { + padding: 6px; +} + +.v-avatar-image { + // + width: var(--avatar-size); + height: var(--avatar-size); + + // + clip-path: circle(); +} + +.v-user-full-name, +.v-user-friendship-state { + // + margin: { + left: 6px; + right: 6px; + } +} + +.v-user-role, +.v-user-friendship-state { + font-size: xx-small; +} + +.v-user-info-container { + // + width: 100%; + display: block; + padding: { + left: 6px; + right: 6px; + } + // height: var(--avatar-size); +} + +::ng-deep x-card.v-user-card { + .x-avatar { + width: var(--avatar-size) !important; + height: var(--avatar-size) !important; + } +} + +:host ::ng-deep .v-user-friendship-container { + .x-button { + .x-icon { + color: inherit !important; + } + } +} + +.v-dto-field-value-phoneNumber { + direction: ltr; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\account\v-user\v-user.component.ts + +import { + Input, + Inject, + NgZone, + Output, + Component, + Renderer2, + ElementRef, + EventEmitter, + ChangeDetectorRef, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + getTopRole, + getGenderResourceId, + getRoleTitleResourceId, +} from './v-user.tools'; +import { + XTask, + nameof, + getMills, + getTaskId, + getTaskName, + isValidEmail, + XStandardType, + XExceptionIDs, + getFieldNames, + XTaskPriority, + isNullOrUndefined, + isValidPhoneNumber, + isNullOrEmptyString, + XResourceIdentifier, +} from 'x-framework-core'; +import { + defaultAvatar, + UserProfileTasks, + VUserPresentType, + VUserDtoContainer, + VUserPresentTypeIdentifier, +} from './v-user.typings'; +import { + toRoleInfo, + XAvailableRoles, + extractAvaialableRoles, +} from 'x-saherelm-api-sdk'; +import { + XSlotName, + XFormValidators, + XPopoverService, + XFormControlType, + XFormControlConfig, + XSlotNameIdentifier, + XColorActionIdentifier, + XFormControlAppearance, + XFormSelectControlConfig, + XFormSelectControlOption, + XFormDatePickerControlConfig, + XFormDatePickerControlPickerPosition, +} from 'x-framework-components'; +import { forkJoin } from 'rxjs'; +import { + XGender, + XUserProfileDto, + XFriendshipType, + defaultUserProfileDto, + XProfileUpdateRequestDto, + prepareUserProfileDtoFields, +} from 'x-framework-identity-sdk'; +import { + VBaseDtoComponent, + VBaseDtoModelAction, + VModelFieldDescriptor, + VModelValidationResult, + VBaseDtoFieldFormPresentationIconPosition, +} from '../../v-dto/v-dto.typings'; +import { Validators } from '@angular/forms'; +import { + getFullName, + isFriendshipTask, + XFriendshipTasks, + getProfileFriendshipStateResourceID, +} from '../v-friendship/v-friendship.tools'; +import { + FriendshipAction, + FriendshipPresentType, + VFriendshipEventModel, + FriendshipActionsPresentType, + FriendshipActionsPresentTypeIdentifier, +} from '../v-friendship/v-friendship.typings'; +import { X_CONFIG } from 'src/app/config/x.config'; +import { XConfig } from 'src/app/config/app.config'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { map, first, concatMap } from 'rxjs/operators'; +import { applyFormatDate, XDateFormat } from '../../tools/date.tools'; +import { SharedService } from 'src/app/shared/services/shared.service'; + +/** + * a View Component based on VBaseDtoContainer for + * represent User profile ... + */ +@Component({ + selector: 'v-user', + templateUrl: './v-user.component.html', + styleUrls: ['./v-user.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class VUserComponent extends VBaseDtoComponent { + // + //#region Properties ... + // + readonly FriendshipActionsPresentTypes = Object.assign( + {}, + FriendshipActionsPresentType, + ); + readonly VPresentTypes = Object.assign({}, VUserPresentType); + readonly FormControlTypes = Object.assign({}, XFormControlType); + readonly BaseDtoModelActions = Object.assign({}, VBaseDtoModelAction); + readonly FriendshipPresentTypes = Object.assign({}, FriendshipPresentType); + readonly FormControlAppearances = Object.assign({}, XFormControlAppearance); + + /** + * extract XUserProfile Dto's Field Names ... + */ + readonly FieldNames = getFieldNames(defaultUserProfileDto); + + /** + * introduce Default Model Implementation ... + */ + readonly defaultModel: XUserProfileDto = { + ...defaultUserProfileDto, + }; + + /** + * Card Avatar URL ... + */ + cardAvatarUrl: string = ''; + + /** + * Remove Card Margin ... + */ + removeCardMargin: boolean = true; + + /** + * Configure Field Forms and Optional Values ... + */ + showFieldsForm = false; + hideOptionalFieldsOnView = true; + fieldFormButtonPosition = + VBaseDtoFieldFormPresentationIconPosition.BeforeValue; + + /** + * Introduce Field Descriptors ... + */ + defaultFieldDescriptors: VModelFieldDescriptor[] = [ + // + // User Name ... + { + hide: false, + hideLabel: false, + key: this.FieldNames.userName, + value: this.defaultModel.userName, + type: this.ModelFieldTypes.String, + label: this.AppResourceIDs.user_name, + } as VModelFieldDescriptor, + // + // First Name ... + { + hide: false, + hideLabel: false, + key: this.FieldNames.firstName, + value: this.defaultModel.firstName, + type: this.ModelFieldTypes.String, + label: this.AppResourceIDs.first_name, + allowFieldForm: true, + formControlConfig: { + index: 0, + propName: this.FieldNames.firstName, + type: { + type: this.FormControlTypes.Text, + config: {}, + }, + validators: { + validators: [Validators.required], + }, + eventHandlers: {}, + appearance: { + hidden: false, + disabled: false, + placeholder: this.AppResourceIDs.first_name, + appearance: this.FormControlAppearances.Outline, + icons: { + prefix: { + applyStateColor: true, + name: this.IconNames.profile, + color: this.ColorNames.Tertiary, + }, + }, + }, + } as XFormControlConfig, + } as VModelFieldDescriptor, + // + // Last Name ... + { + hide: false, + hideLabel: false, + key: this.FieldNames.lastName, + value: this.defaultModel.lastName, + type: this.ModelFieldTypes.String, + label: this.AppResourceIDs.last_name, + allowFieldForm: true, + formControlConfig: { + index: 1, + propName: this.FieldNames.lastName, + type: { + type: this.FormControlTypes.Text, + config: {}, + }, + validators: { + validators: [Validators.required], + }, + eventHandlers: {}, + appearance: { + hidden: false, + disabled: false, + placeholder: this.AppResourceIDs.last_name, + appearance: this.FormControlAppearances.Outline, + icons: { + prefix: { + applyStateColor: true, + name: this.IconNames.profile, + color: this.ColorNames.Tertiary, + }, + }, + }, + } as XFormControlConfig, + } as VModelFieldDescriptor, + // + // Date of Birth ... + { + hide: false, + hideLabel: false, + type: this.ModelFieldTypes.Date, + key: this.FieldNames.dateOfBirth, + value: this.defaultModel.dateOfBirth, + label: this.AppResourceIDs.date_of_birth, + valueProvider: (value: Date) => { + // + let result: string = ''; + + // + if (!isNullOrUndefined(value)) { + result = applyFormatDate( + value, + XDateFormat.DateOfBirth, + this.managerService, + ); + } + + // + return result; + }, + allowFieldForm: true, + formControlConfig: { + index: 2, + propName: this.FieldNames.dateOfBirth, + type: { + type: this.FormControlTypes.Date, + config: { + applyStateColor: true, + iconColor: this.ColorNames.Tertiary, + toggleIcon: this.IconNames.calendar, + datePickerPosition: XFormDatePickerControlPickerPosition.Prefix, + } as XFormDatePickerControlConfig, + }, + validators: { + validators: [Validators.required], + }, + eventHandlers: {}, + appearance: { + hidden: false, + disabled: false, + placeholder: this.AppResourceIDs.date_of_birth, + appearance: this.FormControlAppearances.Outline, + icons: { + prefix: { + applyStateColor: true, + name: this.IconNames.label, + color: this.ColorNames.Tertiary, + }, + }, + }, + } as XFormControlConfig, + } as VModelFieldDescriptor, + // + // Gender ... + { + hide: false, + hideLabel: false, + key: this.FieldNames.gender, + value: this.defaultModel.gender, + type: this.ModelFieldTypes.Number, + label: this.AppResourceIDs.gender, + valueProvider: (value: XGender) => { + // + if (isNullOrUndefined(value)) { + return ''; + } + + // + return this.resourceProvider(getGenderResourceId(value)); + }, + allowFieldForm: true, + formControlConfig: { + index: 3, + propName: this.FieldNames.gender, + type: { + type: this.FormControlTypes.Select, + config: { + multiple: false, + options: [ + // + // Male ... + { + value: XGender.Male, + viewValue: this.resourceProvider(this.AppResourceIDs.male), + }, + // + // Female ... + { + value: XGender.Female, + viewValue: this.resourceProvider(this.AppResourceIDs.female), + }, + ], + } as XFormSelectControlConfig, + }, + validators: { + validators: [Validators.required], + }, + eventHandlers: {}, + appearance: { + hidden: false, + disabled: false, + placeholder: this.AppResourceIDs.gender, + appearance: this.FormControlAppearances.Outline, + icons: { + prefix: { + applyStateColor: true, + name: this.IconNames.label, + color: this.ColorNames.Tertiary, + }, + }, + }, + } as XFormControlConfig, + } as VModelFieldDescriptor, + // + // Role/Role(s) ... + { + hide: false, + hideLabel: false, + key: this.FieldNames.roles, + value: this.defaultModel.roles, + label: this.AppResourceIDs.role, + type: this.ModelFieldTypes.Array, + valueProvider: (value) => { + // + if (isNullOrUndefined(value) || !this.hasChild(value)) { + return ''; + } + + const topRole = getTopRole(value); + let result = this.resourceProvider(getRoleTitleResourceId(topRole)); + + // + return result; + }, + allowFieldForm: true, + formControlConfig: { + index: 4, + propName: this.FieldNames.roles, + type: { + type: this.FormControlTypes.Select, + config: { + multiple: true, + options: [ + ...extractAvaialableRoles().map((ar) => { + return { + value: ar.toString(), + viewValue: this.resourceProvider(getRoleTitleResourceId(ar)), + } as XFormSelectControlOption; + }), + ], + }, + }, + validators: { + validators: [Validators.required], + }, + eventHandlers: {}, + appearance: { + hidden: false, + disabled: false, + placeholder: this.AppResourceIDs.role, + appearance: this.FormControlAppearances.Outline, + icons: { + prefix: { + applyStateColor: true, + name: this.IconNames.label, + color: this.ColorNames.Tertiary, + }, + }, + }, + }, + } as VModelFieldDescriptor>, + // + // Phone Number ... + { + hide: false, + hideLabel: false, + key: this.FieldNames.phoneNumber, + value: this.defaultModel.phoneNumber, + label: this.AppResourceIDs.phone_number, + type: this.ModelFieldTypes.String, + valueProvider: (value) => { + return this.applyLocale(value); + }, + allowFieldForm: true, + formControlConfig: { + index: 5, + propName: this.FieldNames.phoneNumber, + type: { + type: this.FormControlTypes.Tel, + config: {}, + }, + validators: { + validators: [ + Validators.required, + XFormValidators.validatePhoneNumber('IR'), + ], + }, + eventHandlers: {}, + appearance: { + hidden: false, + disabled: false, + placeholder: this.AppResourceIDs.phone_number, + appearance: this.FormControlAppearances.Outline, + icons: { + prefix: { + applyStateColor: true, + name: this.IconNames.mobile, + color: this.ColorNames.Tertiary, + }, + }, + }, + } as XFormControlConfig, + } as VModelFieldDescriptor, + // + // PhoneNumber Confirmed ... + { + hide: false, + hideLabel: false, + key: this.FieldNames.phoneNumberConfirmed, + value: this.defaultModel.phoneNumberConfirmed, + label: this.AppResourceIDs.phone_number_state, + type: this.ModelFieldTypes.Boolean, + valueProvider: (value) => { + // + let resourceID: XResourceIdentifier = undefined; + + // + if (!!value) { + resourceID = this.AppResourceIDs.confirmed; + } else { + resourceID = this.AppResourceIDs.unconfirmed; + } + + // + let result: string = ''; + if (!isNullOrUndefined(resourceID)) { + result = this.resourceProvider(resourceID); + } else { + result = value.toString(); + } + + // + return result; + }, + allowFieldForm: true, + formControlConfig: { + index: 6, + propName: this.FieldNames.phoneNumberConfirmed, + type: { + type: this.FormControlTypes.CheckBox, + config: undefined, + }, + validators: {}, + eventHandlers: {}, + appearance: { + hidden: false, + disabled: false, + placeholder: undefined, + appearance: this.FormControlAppearances.Outline, + icons: {}, + }, + }, + } as VModelFieldDescriptor, + // + // Email ... + { + hide: false, + hideLabel: false, + key: this.FieldNames.email, + value: this.defaultModel.email, + label: this.AppResourceIDs.email, + type: this.ModelFieldTypes.String, + allowFieldForm: true, + formControlConfig: { + index: 7, + propName: this.FieldNames.email, + type: { + type: this.FormControlTypes.Email, + config: {}, + }, + validators: { + validators: [Validators.required], + }, + eventHandlers: {}, + appearance: { + hidden: false, + disabled: false, + placeholder: this.AppResourceIDs.email, + appearance: this.FormControlAppearances.Outline, + icons: { + prefix: { + applyStateColor: true, + name: this.IconNames.mail, + color: this.ColorNames.Tertiary, + }, + }, + }, + } as XFormControlConfig, + } as VModelFieldDescriptor, + // + // Email Confirmed ... + { + hide: false, + hideLabel: false, + key: this.FieldNames.emailConfirmed, + value: this.defaultModel.emailConfirmed, + label: this.AppResourceIDs.email_state, + type: this.ModelFieldTypes.Boolean, + valueProvider: (value) => { + // + let resourceID: XResourceIdentifier = undefined; + + // + if (!!value) { + resourceID = this.AppResourceIDs.confirmed; + } else { + resourceID = this.AppResourceIDs.unconfirmed; + } + + // + let result: string = ''; + if (!isNullOrUndefined(resourceID)) { + result = this.resourceProvider(resourceID); + } else { + result = value.toString(); + } + + // + return result; + }, + allowFieldForm: true, + formControlConfig: { + index: 8, + propName: this.FieldNames.emailConfirmed, + type: { + type: this.FormControlTypes.CheckBox, + config: undefined, + }, + validators: {}, + eventHandlers: {}, + appearance: { + hidden: false, + disabled: false, + placeholder: undefined, + appearance: this.FormControlAppearances.Outline, + icons: {}, + }, + }, + } as VModelFieldDescriptor, + // + // Creation Date ... + { + hide: false, + hideLabel: false, + type: this.ModelFieldTypes.Date, + key: this.FieldNames.creationDate, + value: this.defaultModel.creationDate, + label: this.AppResourceIDs.registration_date, + valueProvider: (value: Date) => { + // + let result: string = ''; + + // + if (!isNullOrUndefined(value)) { + result = applyFormatDate( + value, + XDateFormat.EventDate, + this.managerService, + ); + } + + // + return result; + }, + } as VModelFieldDescriptor, + // + // Last Login Data ... + { + hide: false, + hideLabel: false, + key: this.FieldNames.lastLogin, + type: this.ModelFieldTypes.Date, + value: this.defaultModel.lastLogin, + label: this.AppResourceIDs.last_login, + valueProvider: (value: Date) => { + // + let result: string = ''; + + // + if (!isNullOrUndefined(value)) { + result = applyFormatDate( + value, + XDateFormat.EventDate, + this.managerService, + ); + } + + // + return result; + }, + } as VModelFieldDescriptor, + // + // Is Enabled ... + { + hide: false, + hideLabel: false, + key: this.FieldNames.isEnable, + label: this.AppResourceIDs.state, + value: this.defaultModel.isEnable, + type: this.ModelFieldTypes.Boolean, + valueProvider: (value) => { + // + let resourceID: XResourceIdentifier; + if (!value) { + resourceID = this.AppResourceIDs.disabled; + } else if (!!value) { + resourceID = this.AppResourceIDs.enabled; + } + + // + let result = ''; + if (!isNullOrUndefined(resourceID)) { + result = this.resourceProvider(resourceID); + } + + // + return result; + }, + allowFieldForm: true, + formControlConfig: { + index: 9, + propName: this.FieldNames.isEnable, + type: { + type: this.FormControlTypes.CheckBox, + config: undefined, + }, + validators: {}, + eventHandlers: {}, + appearance: { + hidden: false, + disabled: false, + placeholder: undefined, + appearance: this.FormControlAppearances.Outline, + icons: {}, + }, + }, + } as VModelFieldDescriptor, + // + // Is Banned ... + { + hide: false, + hideLabel: false, + key: this.FieldNames.isBanned, + value: this.defaultModel.isBanned, + type: this.ModelFieldTypes.Boolean, + label: this.AppResourceIDs.bann_state, + valueProvider: (value) => { + // + let resourceID: XResourceIdentifier; + if (!value) { + resourceID = this.AppResourceIDs.disabled; + } else if (!!value) { + resourceID = this.AppResourceIDs.enabled; + } + + // + let result = ''; + if (!isNullOrUndefined(resourceID)) { + result = this.resourceProvider(resourceID); + } + + // + return result; + }, + allowFieldForm: true, + formControlConfig: { + index: 10, + propName: this.FieldNames.isBanned, + type: { + type: this.FormControlTypes.CheckBox, + config: undefined, + }, + validators: {}, + eventHandlers: {}, + appearance: { + hidden: false, + disabled: false, + placeholder: undefined, + appearance: this.FormControlAppearances.Outline, + icons: {}, + }, + }, + } as VModelFieldDescriptor, + ]; + + // + //#region Input/Output(s) ... + /** + * Specifies Present Type ... + */ + @Input() + presentType: VUserPresentTypeIdentifier = this.propertyProvider.presentType; + + /** + * Specify Color of User Info Container ... + */ + @Input() + userInfoColor: XColorActionIdentifier = this.propertyProvider.userInfoColor; + + /** + * Specified Can User Selected or not ... + */ + @Input() + canSelectUser: boolean = this.propertyProvider.canSelectUser; + + /** + * Handle User Selected Event ... + */ + @Output() + userSelected = new EventEmitter(); + + @Input() + putUserInfoOnCardHeader: boolean = + this.propertyProvider.putUserInfoOnCardHeader; + + /** + * Specify the size of Avatar ... + */ + @Input() + avatarSize: number = this.propertyProvider.avatarSize; + + /** + * Enable Auto Styling Slots using Css Classes ... + */ + @Input() + autoStylingSlots: boolean = this.propertyProvider.autoStylingSlots; + + /** + * Specify Avatar Slot ... + */ + @Input() + avatarSlot: XSlotNameIdentifier = this.propertyProvider.avatarSlot; + + /** + * Specify Full Name Slot ... + */ + @Input() + fullNameSlot: XSlotNameIdentifier = this.propertyProvider.fullNameSlot; + + /** + * an Optional Parameters which specified edit fautures + * can processed or not ... + */ + @Input() + supportEdit?: boolean = false; + + /** + * an Optional Parameters which specified FriendshipActions fautures + * can processed or not ... + */ + @Input() + supportFriendshipActions?: boolean = true; + + /** + * Specify User Friendship Actions Menu Slot ... + */ + @Input() + friendshipActionsSlot: XSlotNameIdentifier = + this.propertyProvider.friendshipActionsSlot; + + /** + * How to Represent Friendship Actions ... + */ + @Input() + friendshipActionsType: FriendshipActionsPresentTypeIdentifier = + this.propertyProvider.friendshipActionsType; + + /** + * Show Friendship Actions on Profile View ... + */ + @Input() + showActionsOnProfileView: XStandardType = + this.propertyProvider.showActionsOnProfileView; + //#endregion + //#endregion + + // + //#region Constructor ... + public constructor( + public zone: NgZone, + @Inject(X_CONFIG) + public config: XConfig, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public sharedService: SharedService, + public managerService: XManagerService, + public changeDetector: ChangeDetectorRef, + protected popoverService: XPopoverService, + protected propertyProvider: VUserDtoContainer, + ) { + super( + zone, + config, + element, + renderer, + ruler, + sharedService, + managerService, + changeDetector, + popoverService, + propertyProvider, + ); + } + //#endregion + + // + //#region LifeCycles ... + /** + * Handle Changes ... + * + * @param changeKeys + */ + async onChange(changeKeys: string[]): Promise { + super.onChange(changeKeys); + + // + const canSelectUserChanged = changeKeys.includes( + nameof('canSelectUser'), + ); + if (canSelectUserChanged) { + // + if (isNullOrUndefined(this.canSelectUser)) { + this.canSelectUser = this.propertyProvider.canSelectUser; + } + } + + // + const isAvatarChanged = changeKeys.includes( + nameof('avatarSize'), + ); + if (isAvatarChanged) { + this.handleAvatarSizeChanged(); + } + + // + const isUserInfoColorChanged = changeKeys.includes( + nameof('userInfoColor'), + ); + if (isUserInfoColorChanged) { + // + if (isNullOrUndefined(this.userInfoColor)) { + this.userInfoColor = this.propertyProvider.userInfoColor; + } + } + + // + const isPutUserInfoOnCardHeaderChanged = changeKeys.includes( + nameof('putUserInfoOnCardHeader'), + ); + if (isPutUserInfoOnCardHeaderChanged) { + if (isNullOrUndefined(this.putUserInfoOnCardHeader)) { + this.putUserInfoOnCardHeader = + this.propertyProvider.putUserInfoOnCardHeader; + } + + // + if (this.putUserInfoOnCardHeader) { + await this.handlePutUserInfoOnCard(); + } + } + + // + const isAutoStylingSlotsChanged = changeKeys.includes( + nameof('autoStylingSlots'), + ); + if (isAutoStylingSlotsChanged) { + // + if (isNullOrUndefined(this.autoStylingSlots)) { + this.autoStylingSlots = this.propertyProvider.autoStylingSlots; + } + } + + // + const isFriendshipActionsTypeChanged = changeKeys.includes( + nameof('friendshipActionsType'), + ); + if (isFriendshipActionsTypeChanged) { + if (isNullOrUndefined(isFriendshipActionsTypeChanged)) { + this.friendshipActionsType = + this.propertyProvider.friendshipActionsType; + } + } + + // + const isShowActionsOnProfileViewChanged = changeKeys.includes( + nameof('showActionsOnProfileView'), + ); + if (isShowActionsOnProfileViewChanged) { + if (isNullOrUndefined(this.showActionsOnProfileView)) { + this.showActionsOnProfileView = + this.propertyProvider.showActionsOnProfileView; + } + } + + // + const isAvatarSlotChanged = changeKeys.includes( + nameof('avatarSlot'), + ); + const isFullNameSlotChanged = changeKeys.includes( + nameof('fullNameSlot'), + ); + const isFriendshipActionsSlotChanged = changeKeys.includes( + nameof('friendshipActionsSlot'), + ); + if ( + isAvatarSlotChanged || + isFullNameSlotChanged || + isFriendshipActionsSlotChanged + ) { + // + this.handleUserInfoSlotChanges(); + } + } + + /** + * Register Required Instance Subscriptions ... + */ + registerInstanceHandlers(): void { + super.registerInstanceHandlers(); + + // + // Register User ... + this.applyTakeUntilWrapper(this.sharedService.user$).subscribe( + async (up) => { + await this.validateFieldsOnUser(up); + }, + ); + } + //#endregion + + // + //#region Overrides ... + /** + * Validate Model for Actions ... + * + * @param model + */ + validate( + model?: Partial, + ): VModelValidationResult { + // + let result: VModelValidationResult; + + // + let isValid = false; + const validationErrors: string[] = []; + + // + model = { + ...this.model, + ...model, + }; + const normalized = this.normalize(model); + + // + // Handle Validation ... + + // + // Model not null or Undefined ... + isValid = + !isNullOrUndefined(normalized) && !this.isSameAsDefault(normalized); + if (!isValid) { + validationErrors.push( + this.getExceptionMessage(XExceptionIDs.InvalidArgs), + ); + } else { + // + // Check Other Validations ... + + // + // User ID ... + isValid = !isNullOrEmptyString(normalized.userId); + if (!isValid) { + validationErrors.push( + `${this.resourceProvider( + this.AppResourceIDs.user_id, + )}: ${this.getExceptionMessage(XExceptionIDs.InvalidUserName)}`, + ); + } + + // + // User Name ... + isValid = !isNullOrEmptyString(normalized.userName); + if (!isValid) { + validationErrors.push( + `${this.resourceProvider( + this.AppResourceIDs.user_name, + )}: ${this.getExceptionMessage(XExceptionIDs.InvalidUserName)}`, + ); + } + + // + // Email ... + isValid = + !isNullOrEmptyString(normalized.email) && + isValidEmail(normalized.email); + if (!isValid) { + validationErrors.push( + `${this.resourceProvider( + this.AppResourceIDs.email, + )}: ${this.getExceptionMessage(XExceptionIDs.InvalidEmailAddress)}`, + ); + } + + // + // PhoneNumber ... + isValid = + !isNullOrEmptyString(normalized.phoneNumber) && + isValidPhoneNumber(normalized.phoneNumber, 'IR'); + if (!isValid) { + validationErrors.push( + `${this.resourceProvider( + this.AppResourceIDs.phone_number, + )}: ${this.getExceptionMessage(XExceptionIDs.InvalidMobileNumber)}`, + ); + } + + // + // Date Of Birth ... + // User Must above 14 ... + isValid = + !isNullOrUndefined(normalized.dateOfBirth) && + getMills(normalized.dateOfBirth) <= + getMills() - 14 * 12 * 24 * 60 * 60 * 1000; + if (!isValid) { + validationErrors.push( + `${this.resourceProvider( + this.AppResourceIDs.date_of_birth, + )}: ${this.getExceptionMessage(XExceptionIDs.InvalidData)}`, + ); + } + + // + // First Name ... + isValid = !isNullOrEmptyString(normalized.firstName); + if (!isValid) { + validationErrors.push( + `${this.resourceProvider( + this.AppResourceIDs.first_name, + )}: ${this.getExceptionMessage(XExceptionIDs.InvalidArgs)}`, + ); + } + + // + // Last Name ... + isValid = !isNullOrEmptyString(normalized.lastName); + if (!isValid) { + validationErrors.push( + `${this.resourceProvider( + this.AppResourceIDs.last_name, + )}: ${this.getExceptionMessage(XExceptionIDs.InvalidArgs)}`, + ); + } + + // + // Gender ... + isValid = + !isNullOrUndefined(normalized.gender) && + [XGender.Male, XGender.Female].includes(normalized.gender); + if (!isValid) { + validationErrors.push( + `${this.resourceProvider( + this.AppResourceIDs.gender, + )}: ${this.getExceptionMessage(XExceptionIDs.InvalidArgs)}`, + ); + } + + // + // Roles ... + isValid = + !isNullOrUndefined(normalized.roles) && + this.hasChild(normalized.roles) && + normalized.roles.every((r: any) => + extractAvaialableRoles().includes(r), + ); + if (!isValid) { + validationErrors.push( + `${this.resourceProvider( + this.AppResourceIDs.role, + )}: ${this.getExceptionMessage(XExceptionIDs.InvalidArgs)}`, + ); + } + + // + } + + // + isValid = !this.hasChild(validationErrors); + const isValidForAdd = isNullOrEmptyString(normalized.userId); + const isValidForUpdate = !isNullOrEmptyString(normalized.userId); + + // + result = { + isValid, + normalized, + isValidForAdd, + isValidForUpdate, + validationErrors, + }; + + // + return result; + } + + /** + * Prepare Model Fields ... + * + * @param model + */ + prepare(model?: XUserProfileDto): XUserProfileDto { + // + if (isNullOrUndefined(model)) { + model = { + ...this.model, + }; + } + + // + return prepareUserProfileDtoFields(model); + } + + /** + * Do all we want on Model Assigned ... + */ + async onModelAssigned(): Promise { + super.onModelAssigned(); + + // + if (this.putUserInfoOnCardHeader) { + await this.handlePutUserInfoOnCard(); + } + + // + // Handling User Field Validation ... + const up = await this.getValueAsync(this.sharedService.user$); + await this.validateFieldsOnUser(up); + } + //#endregion + + // + //#region UI Providers ... + /** + * retrieve Profile Avatar ... + * + * @returns + */ + getAvatarUrl() { + return this.getValue(this.model).pipe( + map((profile) => + !isNullOrUndefined(profile) && + !isNullOrUndefined(profile.avatar) && + !isNullOrEmptyString(profile.avatar) + ? profile.avatar + : defaultAvatar, + ), + ); + } + + /** + * retrieve Profile Full Name ... + * + * @returns + */ + getUserFullName() { + return this.getValue(this.model).pipe( + map((profile) => getFullName(profile)), + ); + } + + /** + * Retrieve User To Role Value Translation ... + * + * @returns + */ + getUserRole() { + // + return this.getValue(this.model).pipe( + map((model) => { + // + let result = ''; + + // + if ( + !isNullOrUndefined(model) && + !isNullOrUndefined(model.roles) && + this.hasChild(model.roles) + ) { + // + const topRole = getTopRole(model.roles); + result = this.resourceProvider(getRoleTitleResourceId(topRole)); + } + + // + return result; + }), + ); + } + + /** + * get User's Friendship state ... + */ + getFriendshipState() { + return this.getValue(this.model).pipe( + map((model) => { + // + let result = ''; + + // + if ( + !isNullOrUndefined(model) && + !isNullOrUndefined(model.friendshipInfo) + ) { + // + let resourceID: XResourceIdentifier = undefined; + + // + resourceID = getProfileFriendshipStateResourceID(model); + + // + if (!isNullOrUndefined(resourceID)) { + result = this.resourceProvider(resourceID); + } + } + + // + return result; + }), + ); + } + + /** + * Only Show User info Section when there isn't provided on Card ... + * + * @returns + */ + canShowUserInfo() { + return forkJoin({ + wrapWithCard: this.getValue(this.wrapWithCard), + putUserInfoOnCardHeader: this.getValue(this.putUserInfoOnCardHeader), + }).pipe( + map((info) => !(!!info.wrapWithCard && !!info.putUserInfoOnCardHeader)), + ); + } + + /** + * Prepare Slot Containers Css Classes ... + * + * @param slot + * @returns + */ + getSlotContainerCSSClasses(slot: XSlotName) { + return forkJoin({ + avatarSlot: this.getValue(this.avatarSlot), + fullNameSlot: this.getValue(this.fullNameSlot), + friendshipActionsSlot: this.getValue(this.friendshipActionsSlot), + }).pipe( + map((slots) => { + // + let result: Array = []; + if (!this.autoStylingSlots) { + return result; + } + + // + result = ['center-vertical-flex']; + + // + if (slots.fullNameSlot !== slot) { + result.push('dynamic-slot'); + } + + // + return result; + }), + first(), + ); + } + + /** + * Retrieve Slot Inline Classes ... + * + * @param slot + * @returns + */ + getSloInlinetCssClasses(slot: XSlotName) { + return forkJoin({ + avatarSlot: this.getValue(this.avatarSlot), + fullNameSlot: this.getValue(this.fullNameSlot), + friendshipActionsSlot: this.getValue(this.friendshipActionsSlot), + }).pipe( + map((slots) => { + // + let result: Array = []; + if (!this.autoStylingSlots) { + return result; + } + + // + if (slots.avatarSlot === this.SlotNames.START) { + // + if (slot === slots.avatarSlot || slot === slots.fullNameSlot) { + result.push('ion-text-start'); + } else { + result.push('ion-text-end'); + } + } else if (slots.avatarSlot === this.SlotNames.END) { + // + if (slot === slots.avatarSlot || slot === slots.fullNameSlot) { + result.push('ion-text-end'); + } else { + result.push('ion-text-start'); + } + } else if (slots.avatarSlot === this.SlotNames.CENTER) { + // + if (slot === slots.avatarSlot || slot === slots.fullNameSlot) { + result.push('ion-text-center'); + } else { + // + let s = 'ion-text-start'; + if (slots.friendshipActionsSlot === this.SlotNames.END) { + s = 'ion-text-end'; + } else if (slots.friendshipActionsSlot === this.SlotNames.CENTER) { + s = 'ion-text-center'; + } + + // + result.push(s); + } + } + + // + return result; + }), + first(), + ); + } + + /** + * Provides Friendship Actions is Supported or not ... + * + * @returns + */ + isFriendshipActionsSupported() { + return this.getValue(this.supportFriendshipActions).pipe( + map((isSupport) => isNullOrUndefined(isSupport) || !!isSupport), + ); + } + //#endregion + + // + //#region UI Handlers ... + /** + * Handle User Selected ... + */ + handleUserSelected() { + // + if ( + isNullOrUndefined(this.model) || + this.loading || + this.uiDisabled || + !this.canSelectUser + ) { + return; + } + + // + this.userSelected.emit(this.model); + } + + /** + * issue Friendship Tasks ... + * + * @param event + */ + async handleFriendshipActionFired(event: VFriendshipEventModel) { + // + // Validate Event Model ... + if ( + isNullOrUndefined(event) || + isNullOrEmptyString(event.action) || + isNullOrEmptyString(event.destUser) + ) { + return; + } + + // + let mTaskName = ''; + let mTask = undefined; + + // + // Specified Task Name and Task based on Required Action ... + switch (event.action) { + // + case FriendshipAction.Block: + // + mTaskName = XFriendshipTasks.Block; + mTask = this.sharedService.accountsService.block(event.destUser); + break; + + // + case FriendshipAction.Accept: + // + mTaskName = XFriendshipTasks.Accept; + mTask = this.sharedService.accountsService.accept(event.destUser); + break; + + // + case FriendshipAction.Cancel: + // + mTaskName = XFriendshipTasks.Cancel; + mTask = this.sharedService.accountsService.cancel(event.destUser); + break; + + // + case FriendshipAction.Follow: + // + mTaskName = XFriendshipTasks.Follow; + mTask = this.sharedService.accountsService.follow(event.destUser); + break; + + // + case FriendshipAction.Reject: + // + mTaskName = XFriendshipTasks.Reject; + mTask = this.sharedService.accountsService.reject(event.destUser); + break; + + // + case FriendshipAction.UnBlock: + // + mTaskName = XFriendshipTasks.UnBlock; + mTask = this.sharedService.accountsService.unBlock(event.destUser); + break; + + // + case FriendshipAction.UnFollow: + // + if (event.type === XFriendshipType.Follower) { + // + // UnFollow Follower ... + mTaskName = XFriendshipTasks.UnFollowFollower; + mTask = this.sharedService.accountsService.unFollowFollower( + event.destUser, + ); + } else if (event.type === XFriendshipType.Following) { + // + // UnFollow Following ... + mTaskName = XFriendshipTasks.UnFollowFollowing; + mTask = this.sharedService.accountsService.unFollowFollowing( + event.destUser, + ); + } + break; + } + + // + // Check Task and Task Name prepared or not ... + if (isNullOrEmptyString(mTaskName) || isNullOrUndefined(mTask)) { + return; + } + + // + // Add Or Update Task ... + await this.addOrUpdateTask({ + task: mTask, + name: mTaskName, + priority: XTaskPriority.High, + }); + + // + // Dispatch Task ... + await this.dispatchTask(mTaskName); + } + //#endregion + + // + //#region Private ... + /** + * Apply Avatar Size CSS Var ... + */ + private handleAvatarSizeChanged() { + // + // Normalize Avatar Size ... + if (isNullOrUndefined(this.avatarSize) || this.avatarSize <= 16) { + this.avatarSize = this.propertyProvider.avatarSize; + } + + // + this.setCssVar('avatar-size', `${this.avatarSize}px`); + this.detectChanges(); + } + + /** + * Handle User Info Slot Changes ... + */ + private handleUserInfoSlotChanges() { + // + if (isNullOrUndefined(this.avatarSlot)) { + this.avatarSlot = this.propertyProvider.avatarSlot; + } + + // + if (isNullOrUndefined(this.fullNameSlot)) { + this.fullNameSlot = this.propertyProvider.fullNameSlot; + } + + // + if (isNullOrUndefined(this.friendshipActionsSlot)) { + this.friendshipActionsSlot = this.propertyProvider.friendshipActionsSlot; + } + + // + this.detectChanges(); + } + + /** + * set requirements for show user info on Card Toolbar ... + */ + private async handlePutUserInfoOnCard() { + // + const cardTitle = this.cardTitle; + const cardSubTitle = this.cardSubTitle; + + // + const role = await this.getValueAsync(this.getUserRole()); + const fullName = await this.getValueAsync(this.getUserFullName()); + const friendshipState = await this.getValueAsync(this.getFriendshipState()); + + // + this.cardAvatarUrl = await this.getValueAsync(this.getAvatarUrl()); + this.cardTitle = fullName || cardTitle; + this.cardSubTitle = friendshipState || role || cardSubTitle; + } + + /** + * Validate Fileds ... + * + * @param up + * @returns + */ + private async validateFieldsOnUser(up: XUserProfileDto) { + // + let isMe = false; + let isAdmin = false; + let canEdit = false; + + // + // Check Support Edit Value ... + const supportEdit = + isNullOrUndefined(this.supportEdit) || !!this.supportEdit; + + // + // Calculate requirement to Detect Requeted User is Same as Current User ... + // or Current User is Admin ... + if (!isNullOrUndefined(up) && !isNullOrUndefined(this.model)) { + // + isMe = + up.email === this.model.email || + up.userId === this.model.userId || + up.userName === this.model.userName || + up.phoneNumber === this.model.phoneNumber; + + // + const roleInfo = toRoleInfo(up); + isAdmin = roleInfo.isAdmin; + + // + canEdit = isMe || isAdmin; + } + + // + // Handle Configurations + // base on canEdit ... + if (canEdit && supportEdit) { + // + this.showFieldsForm = true; + this.hideOptionalFieldsOnView = false; + + // + // Here We Have to Decide Which Fields Hide ased on + // Viewer Type of User ... + if (isMe && !isAdmin) { + // + // Disable Roles, Email Confirmed, Phone Confirmed, isEnabled, isBanned ... + this.disableFieldEdits( + [ + 'roles', + 'email', + 'isEnable', + 'isBanned', + 'phoneNumber', + 'emailConfirmed', + 'phoneNumberConfirmed', + ], + true, + ); + } else if (isAdmin) { + // + // Hide Nothing ... + } + } else { + // + this.showFieldsForm = false; + this.hideOptionalFieldsOnView = true; + + // + // Disable FirstName and LastName on Can not Edit State ... + this.hideFields(['firstName', 'lastName'], true); + + // + // Check onl Required Data to Show for User ... + if (!isMe && !isAdmin) { + // + // Hide these Fields for UnKnown Users ... + this.hideFields( + [ + 'roles', + 'email', + 'isBanned', + 'isEnable', + 'lastLogin', + 'dateOfBirth', + 'phoneNumber', + 'emailConfirmed', + 'phoneNumberConfirmed', + ], + true, + ); + } + } + + // + // Apply Change Detect ... + this.runTimedOut(() => this.detectChanges(), 1000); + } + + /** + * Check Task Finish and Issue Load Profile of Requested User ... + * + * @param name + * @param task + * @returns + */ + private async handleFriendshipTaskFinished(name: string, task: XTask) { + // + if ( + isNullOrUndefined(task) || + isNullOrUndefined(this.model) || + isNullOrEmptyString(this.model.userId) || + name === XFriendshipTasks.LoadProfile + ) { + return; + } + + // + // Here we have to Aanaly Task and Rtrieve User Profile Task and Issue it ... + // + const mTaskName = XFriendshipTasks.LoadProfile; + const mTask = this.sharedService.accountsService.getProfile( + this.model.userId, + ); + + // + await this.addOrUpdateTask({ + task: mTask, + name: mTaskName, + priority: XTaskPriority.High, + }); + + // + await this.dispatchTask(mTaskName); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\account\v-user\v-user.tools.ts + +import { + toNormalString, + isNullOrUndefined, + isNullOrEmptyString, + XResourceIdentifier, +} from 'x-framework-core'; +import { defaultAvatar, defaultFullName } from './v-user.typings'; +import { XGender, XUserProfileDto } from 'x-framework-identity-sdk'; +import { AppResourceIDs } from 'src/app/config/localization.config'; +import { getTopRoleByRoles, XAvailableRoles } from 'x-saherelm-api-sdk'; + +/** + * Generate User Full Name ... + * + * @param user + * @returns + */ +export function getUserFullName(user: XUserProfileDto) { + // + if (isNullOrUndefined(user)) { + return defaultFullName; + } + + // + return `${user.firstName} ${user.lastName}`; +} + +/** + * Get User Avatar URL ... + * + * @param user + * @returns + */ +export function getUserAvatarURL(user: XUserProfileDto) { + // + if (isNullOrUndefined(user)) { + return defaultAvatar; + } + + // + return user.avatar || defaultAvatar; +} + +/** + * Retrieve Specified Role Title Resource Id ... + * + * @param role + * @returns + */ +export function getRoleTitleResourceId(role: XAvailableRoles | string) { + // + let result: XResourceIdentifier = null; + if (isNullOrEmptyString(role)) { + return result; + } + + // + const mRole = toNormalString(role); + switch (mRole) { + // + case 'admin': + result = AppResourceIDs.admin_role; + break; + + // + case 'responsible_manager': + result = AppResourceIDs.responsible_manager_role; + break; + + // + case 'journalist': + result = AppResourceIDs.journalist_role; + break; + + // + case 'chief_clerk': + result = AppResourceIDs.chief_clerk_role; + break; + + // + case 'chief_clerk_assistant': + result = AppResourceIDs.chief_clerk_assistant_role; + break; + + // + case 'actuary': + result = AppResourceIDs.actuary_role; + break; + + // + case 'actuary_assistant': + result = AppResourceIDs.actuary_assistant_role; + break; + + // + case 'reporter': + result = AppResourceIDs.reporter_role; + break; + + // + case 'reporter_assistant': + result = AppResourceIDs.reporter_assistant_role; + break; + + // + case 'agent': + result = AppResourceIDs.agent_role; + break; + + // + case 'user': + result = AppResourceIDs.user_role; + break; + } + + // + return result; +} + +/** + * Retrieve Specified Role Description Resource Id ... + * + * @param role + * @returns + */ +export function getRoleDescriptionResourceId(role: XAvailableRoles | string) { + // + let result: XResourceIdentifier = null; + if (isNullOrEmptyString(role)) { + return result; + } + + // + const mRole = toNormalString(role); + switch (mRole) { + // + case 'admin': + result = AppResourceIDs.admin_role_description; + break; + + // + case 'responsible_manager': + result = AppResourceIDs.responsible_manager_role_description; + break; + + // + case 'journalist': + result = AppResourceIDs.journalist_role_description; + break; + + // + case 'chief_clerk': + result = AppResourceIDs.chief_clerk_role_description; + break; + + // + case 'chief_clerk_assistant': + result = AppResourceIDs.chief_clerk_assistant_role_description; + break; + + // + case 'actuary': + result = AppResourceIDs.actuary_role_description; + break; + + // + case 'actuary_assistant': + result = AppResourceIDs.actuary_assistant_role_description; + break; + + // + case 'reporter': + result = AppResourceIDs.reporter_role_description; + break; + + // + case 'reporter_assistant': + result = AppResourceIDs.reporter_assistant_role_description; + break; + + // + case 'agent': + result = AppResourceIDs.agent_role_description; + break; + + // + case 'user': + result = AppResourceIDs.user_role_description; + break; + } + + // + return result; +} + +/** + * Extract User Top Role ... + * + * @param user + * @returns + */ +export function getUserTopRole(user: XUserProfileDto) { + // + if (isNullOrUndefined(user)) { + return undefined; + } + + // + return getTopRole(user.roles); +} + +/** + * Extract Top Role based on Roles Array ... + * + * @param roles + * @returns + */ +export function getTopRole(roles: Array) { + return getTopRoleByRoles(roles); +} + +/** + * Extract Gender Resource Id ... + * + * @param gender + * @returns + */ +export function getGenderResourceId(gender: XGender) { + // + if (gender === XGender.Male) { + return AppResourceIDs.male; + } else if (gender === XGender.Female) { + return AppResourceIDs.female; + } + + // + return undefined; +} + +/** + * Extract User Gender Resource Id ... + * + * @param user + * @returns + */ +export function getUserGenderResourceId(user: XUserProfileDto) { + // + if (!user) { + return undefined; + } + + // + return getGenderResourceId(user.gender); +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\account\v-user\v-user.typings.ts + +import { + XStandardType, + XColorIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { + VFriendshipEventModel, + FriendshipActionsPresentType, + FriendshipActionsPresentTypeIdentifier, +} from '../v-friendship/v-friendship.typings'; +import { EventEmitter, Injectable } from '@angular/core'; +import { XUserProfileDto } from 'x-framework-identity-sdk'; +import { XSlotName, XSlotNameIdentifier } from 'x-framework-components'; +import { VBaseDtoContainer } from '../../v-dto/v-dto.typings'; + +// +//#region Resource and Typings ... +export const defaultFullName = '...'; +export const defaultAvatar = '/assets/image/default-user-image.png'; + +/** + * Present Types ... + */ +export enum VUserPresentType { + /** + * Represent Nothing ... + */ + AsNone = 'as_none', + /** + * Represent a User as Form for Add or Update Configured Fields ... + */ + AsForm = 'as_form', + /** + * Represent Only Avatar ... + */ + AsAvatar = 'as_avatar', + /** + * Represent User Avatar and Full Name ... + */ + AsInfo = 'as_info', + /** + * Represent User Info and it's Related Axctions as a Follower ... + */ + AsFollower = 'as_follower', + AsFollowerActions = 'as_follower_actions', + /** + * Represent User Info and it's Related Axctions as a Following ... + */ + AsFollowing = 'as_following', + AsFollowingActions = 'as_following_actions', + /** + * Represent a User as it's Profile Summary which contains all + * available features ... + */ + AsProfile = 'as_profile', +} + +export type VUserPresentTypeIdentifier = VUserPresentType | string; + +export enum UserProfileTasks { + AddUserProfile = 'add_user_profile', + UpdateUserProfile = 'update_user_profile', +} +//#endregion + +// +//#region Container ... +/** + * Contains all available Input/Output(s) default Values ... + */ +@Injectable({ + providedIn: 'root', +}) +export class VUserDtoContainer extends VBaseDtoContainer { + // + readonly userInfoColor: XColorIdentifier = XColorWithBrightness.PrimaryTint; + readonly putUserInfoOnCardHeader: boolean = false; + + // + readonly avatarSize: number = 40; + readonly wrapWithCard: boolean = false; + readonly autoStylingSlots: boolean = true; + readonly avatarSlot: XSlotNameIdentifier = XSlotName.START; + readonly fullNameSlot: XSlotNameIdentifier = XSlotName.CENTER; + readonly friendshipActionsSlot: XSlotNameIdentifier = XSlotName.END; + readonly friendshipActionFired: EventEmitter = + new EventEmitter(); + + // + readonly showActionsOnProfileView: XStandardType = false; + readonly friendshipActionsType: FriendshipActionsPresentTypeIdentifier = + FriendshipActionsPresentType.AsFAB; + + // + readonly canSelectUser = false; +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\classes\v-base.component.ts + +import { + Inject, + Output, + NgZone, + Renderer2, + Component, + ElementRef, + EventEmitter, + ChangeDetectorRef, + ChangeDetectionStrategy, + TemplateRef, +} from '@angular/core'; +import { + formatDate, + isNullOrEmptyString, + isNullOrUndefined, + XColorWithBrightness, +} from 'x-framework-core'; +import { + XPopoverService, + XComponentCardContainer, + XComponentCardContainerComponent, +} from 'x-framework-components'; +import { X_CONFIG } from '../../config/x.config'; +import { XConfig } from '../../config/app.config'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { getEventDateFormatString } from '../tools/v-base.tools'; +import { AppResourceIDs } from '../../config/localization.config'; +import { XBaseComponent, XIconNames } from 'x-framework-components'; +import { SharedService } from 'src/app/shared/services/shared.service'; + +@Component({ + template: '', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export abstract class VBaseComponent extends XBaseComponent { + // + //#region Props ... + // + readonly IconNames = Object.assign({}, XIconNames); + readonly AppResourceIDs = Object.assign({}, AppResourceIDs); + readonly ColorNames = Object.assign({}, XColorWithBrightness); + //#endregion + + // + //#region Constructor ... + public constructor( + public zone: NgZone, + @Inject(X_CONFIG) + public config: XConfig, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public sharedService: SharedService, + public managerService: XManagerService, + public changeDetector: ChangeDetectorRef, + protected popoverService: XPopoverService + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region UI Handlers ... + applyDefaultDate(value: Date) { + // + const result = formatDate( + value, + this.managerService.currentLocale, + getEventDateFormatString(this.managerService.currentLocale), + true + ); + + // + return result; + } + //#endregion + + // + //#region Actions ... + /** + * do LockBreak action ... + * + * @param action a callback function ... + */ + async doLockBreakAction(action: (payload?: any) => void) { + // + // Check is Locked or not ... + if (this.lock) { + // + // if Locked, Present a Dialog for Lock Break ... + await this.managerService.dialogService.presentAreYouSureYesNoDialog( + async () => { + // + // if User Confirmed Lock Break, do Action ... + action(); + } + ); + } else { + // + // do Action if there isn't any Lock ... + action(); + } + } + + /** + * Do Confirmed Action ... + * + * @param action + */ + async doConfirmedAction(action: (payload?: any) => void) { + // + await this.managerService.dialogService.presentAreYouSureYesNoDialog( + async () => { + // + // if User Confirmed Lock Break, do Action ... + action(); + } + ); + } + //#endregion + + // + //#region Protected ... + //#endregion + + // + //#region Private ... + //#endregion +} + +@Component({ + template: '', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export abstract class VBaseInCardComponent extends XComponentCardContainerComponent { + // + //#region Props ... + // + readonly IconNames = Object.assign({}, XIconNames); + readonly AppResourceIDs = Object.assign({}, AppResourceIDs); + readonly ColorNames = Object.assign({}, XColorWithBrightness); + + // + @Output() + avatarClicked = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + public constructor( + public zone: NgZone, + @Inject(X_CONFIG) + public config: XConfig, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public sharedService: SharedService, + public managerService: XManagerService, + public changeDetector: ChangeDetectorRef, + protected popoverService: XPopoverService, + protected propertyProvider: XComponentCardContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region UI Handlers ... + applyDefaultDate(value: Date) { + // + let result = ''; + + // + if (!isNullOrUndefined(value)) { + result = formatDate( + value, + this.managerService.currentLocale, + getEventDateFormatString(this.managerService.currentLocale), + true + ); + } + + // + return result; + } + //#endregion + + // + //#region Actions ... + /** + * do LockBreak action ... + * + * @param action a callback function ... + */ + async doLockBreakAction(action: (payload?: any) => void) { + // + // Check is Locked or not ... + if (this.lock) { + // + // if Locked, Present a Dialog for Lock Break ... + await this.managerService.dialogService.presentAreYouSureYesNoDialog( + async () => { + // + // if User Confirmed Lock Break, do Action ... + action(); + } + ); + } else { + // + // do Action if there isn't any Lock ... + action(); + } + } + //#endregion + + // + //#region Protected ... + //#endregion + + // + //#region Private ... + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\constants\actionbar.defaults.ts + +import { XColorWithBrightness } from 'x-framework-core'; +import { AppResourceIDs } from 'src/app/config/localization.config'; +import { XActionBarItemSlot, XIconNames } from 'x-framework-components'; +import { XSimpleActionBarItem } from '../typings/simple.actionbar.typings'; + +export const defaultCallRequestActionBar: XSimpleActionBarItem = { + index: 0, + onlyIcon: true, + id: 'CallRequest', + icon: XIconNames.phone, + slot: XActionBarItemSlot.START, + title: AppResourceIDs.call_request, + iconColor: XColorWithBrightness.Success, +}; + +export const defaultCallEndActionBar: XSimpleActionBarItem = { + index: 0, + id: 'CallEnd', + onlyIcon: true, + icon: XIconNames.phone, + slot: XActionBarItemSlot.START, + title: AppResourceIDs.call_end, + iconColor: XColorWithBrightness.Danger, +}; + +export const defaultMuteActionBar: XSimpleActionBarItem = { + index: 0, + onlyIcon: true, + id: 'Mute', + icon: XIconNames.volume_mute, + slot: XActionBarItemSlot.START, + title: AppResourceIDs.mute, + iconColor: XColorWithBrightness.LightShade, +}; + +export const defaultUnMuteActionBar: XSimpleActionBarItem = { + index: 0, + onlyIcon: true, + id: 'Unmute', + icon: XIconNames.volume_high, + title: AppResourceIDs.unmute, + slot: XActionBarItemSlot.START, + iconColor: XColorWithBrightness.LightShade, +}; + +export const defaultFullscreenActionBar: XSimpleActionBarItem = { + index: 0, + onlyIcon: true, + id: 'Fullscreen', + icon: XIconNames.fullscreen, + slot: XActionBarItemSlot.START, + title: AppResourceIDs.fullscreen, + iconColor: XColorWithBrightness.Light, +}; + +export const defaultUnfullscreenActionBar: XSimpleActionBarItem = { + index: 0, + onlyIcon: true, + id: 'UnFullscreen', + slot: XActionBarItemSlot.START, + icon: XIconNames.close_fullscreen, + title: AppResourceIDs.unfullscreen, + iconColor: XColorWithBrightness.Light, +}; + +export const defaultMicOnActionBar: XSimpleActionBarItem = { + index: 0, + onlyIcon: true, + id: 'MicOn', + icon: XIconNames.mic, + title: AppResourceIDs.mic_on, + slot: XActionBarItemSlot.START, + iconColor: XColorWithBrightness.Light, +}; + +export const defaultMicOffActionBar: XSimpleActionBarItem = { + index: 0, + onlyIcon: true, + id: 'MicOff', + icon: XIconNames.mic_off, + title: AppResourceIDs.mic_off, + slot: XActionBarItemSlot.START, + iconColor: XColorWithBrightness.Light, +}; + +export const defaultVideoOnActionBar: XSimpleActionBarItem = { + index: 0, + onlyIcon: true, + id: 'VideoOn', + icon: XIconNames.videocam, + slot: XActionBarItemSlot.START, + title: AppResourceIDs.video_on, + iconColor: XColorWithBrightness.Light, +}; + +export const defaultVideoOffActionBar: XSimpleActionBarItem = { + index: 0, + onlyIcon: true, + id: 'VideoOff', + slot: XActionBarItemSlot.START, + title: AppResourceIDs.video_off, + iconColor: XColorWithBrightness.Light, + icon: XIconNames.video_camera_front_off, +}; + +export const defaultSwitchToFrontCameraActionBar: XSimpleActionBarItem = { + index: 0, + onlyIcon: true, + id: 'SwitchToFrontCamera', + icon: XIconNames.cameraswitch, + slot: XActionBarItemSlot.START, + iconColor: XColorWithBrightness.Light, + title: AppResourceIDs.switch_to_front_camera, +}; + +export const defaultSwitchToBackCameraActionBar: XSimpleActionBarItem = { + index: 0, + onlyIcon: true, + id: 'SwitchToBackCamera', + icon: XIconNames.cameraswitch, + slot: XActionBarItemSlot.START, + iconColor: XColorWithBrightness.Light, + title: AppResourceIDs.switch_to_back_camera, +}; + +export const defaultStartScreenShareActionBar: XSimpleActionBarItem = { + index: 0, + onlyIcon: true, + id: 'StartScreenShare', + icon: XIconNames.screen_share, + slot: XActionBarItemSlot.START, + iconColor: XColorWithBrightness.Light, + title: AppResourceIDs.start_share_screen, +}; + +export const defaultStopScreenShareActionBar: XSimpleActionBarItem = { + index: 0, + onlyIcon: true, + id: 'StopScreenShare', + slot: XActionBarItemSlot.START, + icon: XIconNames.stop_screen_share, + iconColor: XColorWithBrightness.Light, + title: AppResourceIDs.stop_share_screen, +}; + +export const defaultStartRecordActionBar: XSimpleActionBarItem = { + index: 0, + onlyIcon: true, + id: 'StartRecord', + icon: XIconNames.screen_record, + slot: XActionBarItemSlot.START, + title: AppResourceIDs.start_record, + iconColor: XColorWithBrightness.Danger, +}; + +export const defaultStopRecordActionBar: XSimpleActionBarItem = { + index: 0, + onlyIcon: true, + id: 'StopRecord', + icon: XIconNames.stop, + slot: XActionBarItemSlot.START, + title: AppResourceIDs.stop_record, + iconColor: XColorWithBrightness.Danger, +}; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\constants\button.defaults.ts + +import { + XIconNames, + XButtonType, + IXButtonContainer, +} from 'x-framework-components'; +import { AppResourceIDs } from 'src/app/config/localization.config'; +import { XColorWithBrightness, XResourceIDs } from 'x-framework-core'; + +/** + * Extends a Partial Container interface for Configuring Buttons ... + */ +export interface VActionButtonConfig extends Partial { + action?: string; +} + +// +//#region Default Action Buttons Configurations ... +export const defaultBaseActionButton: VActionButtonConfig = { + foregroundColor: true, + type: XButtonType.Icon, +}; + +export const defaultShareActionButton: VActionButtonConfig = { + ...defaultBaseActionButton, + icon: XIconNames.share, + title: AppResourceIDs.share, + iconColor: XColorWithBrightness.Tertiary, +}; + +export const defaultCopyToClipboardActionButton: VActionButtonConfig = { + ...defaultBaseActionButton, + icon: XIconNames.clipboard, + title: XResourceIDs.copy_to_clipboard, + iconColor: XColorWithBrightness.Tertiary, +}; + +// export const defaultCopyToClipboardActionButton: VActionButtonConfig = { +// ...defaultBaseActionButton, +// icon: XIconNames.clipboard, +// title: XResourceIDs.copy_to_clipboard, +// iconColor: XColorWithBrightness.Tertiary +// } + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\constants\fab.defaults.ts + +import { + XIconNames, + XFabButtonSide, + XFabButtonSize, + XFabVerticalSlot, + XFabHorizontalSlot, +} from 'x-framework-components'; +import { + XSimpleFabItem, + XSimpleFabButton, +} from '../typings/simple.fab.typings'; +import { XColorWithBrightness, XResourceIDs } from 'x-framework-core'; +import { AppResourceIDs } from 'src/app/config/localization.config'; + +// +// Edit ... +export const defaultEditFabButton: XSimpleFabButton = { + index: 0, + id: 'Edit', + disabled: false, + icon: XIconNames.edit, + side: XFabButtonSide.TOP, + size: XFabButtonSize.MINI, + tooltip: XResourceIDs.edit, + color: XColorWithBrightness.Tertiary, +}; + +// +// Remove ... +export const defaultRemoveFabButton: XSimpleFabButton = { + index: 0, + id: 'Remove', + disabled: false, + icon: XIconNames.remove, + side: XFabButtonSide.TOP, + size: XFabButtonSize.MINI, + tooltip: XResourceIDs.delete, + color: XColorWithBrightness.Danger, +}; + +// +// Add ... +export const defaultAddFabButton: XSimpleFabButton = { + index: 0, + id: 'Add', + disabled: false, + icon: XIconNames.add, + side: XFabButtonSide.TOP, + tooltip: XResourceIDs.add, + size: XFabButtonSize.REGULAR, + color: XColorWithBrightness.Primary, +}; + +// +// Upload ... +export const defaultUploadFabButton: XSimpleFabButton = { + index: 0, + id: 'Upload', + disabled: false, + icon: XIconNames.upload, + side: XFabButtonSide.TOP, + size: XFabButtonSize.REGULAR, + tooltip: XResourceIDs.upload, + color: XColorWithBrightness.Primary, +}; + +// +// Download ... +export const defaultDownloadFabButton: XSimpleFabButton = { + index: 0, + id: 'Download', + disabled: false, + side: XFabButtonSide.TOP, + icon: XIconNames.download, + size: XFabButtonSize.REGULAR, + tooltip: XResourceIDs.download, + color: XColorWithBrightness.Success, +}; + +// +// Select ... +export const defaultSelectFilesFabButton: XSimpleFabButton = { + index: 0, + disabled: false, + id: 'SelectFiles', + icon: XIconNames.touch, + side: XFabButtonSide.TOP, + size: XFabButtonSize.REGULAR, + tooltip: XResourceIDs.select, + color: XColorWithBrightness.Primary, +}; + +// +// More ... +export const defaultMoreFabButton: XSimpleFabButton = { + id: 'More', + index: 0, + disabled: false, + side: XFabButtonSide.TOP, + size: XFabButtonSize.REGULAR, + tooltip: XResourceIDs.options, + icon: XIconNames.menu_vertical, + color: XColorWithBrightness.Primary, +}; + +// +// Back ... +export const defaultBackFabButton: XSimpleFabButton = { + id: 'Back', + index: 0, + disabled: false, + side: XFabButtonSide.TOP, + size: XFabButtonSize.REGULAR, + tooltip: XResourceIDs.back, + icon: XIconNames.arrow_back, + color: XColorWithBrightness.Primary, +}; + +// +// Refresh ... +export const defaultRefreshFabButton: XSimpleFabButton = { + id: 'Refresh', + index: 0, + disabled: false, + side: XFabButtonSide.TOP, + size: XFabButtonSize.REGULAR, + tooltip: XResourceIDs.refresh, + icon: XIconNames.refresh, + color: XColorWithBrightness.Primary, +}; + +// +// Close ... +export const defaultCloseFabButton: XSimpleFabButton = { + id: 'Close', + index: 0, + disabled: false, + side: XFabButtonSide.TOP, + size: XFabButtonSize.REGULAR, + tooltip: XResourceIDs.close, + icon: XIconNames.close, + color: XColorWithBrightness.Danger, +}; + +// +// Connect ... +export const defaultConnectFabButton: XSimpleFabButton = { + id: 'Connect', + index: 0, + disabled: false, + side: XFabButtonSide.TOP, + icon: XIconNames.live_tv, + size: XFabButtonSize.REGULAR, + tooltip: AppResourceIDs.connect, + color: XColorWithBrightness.Success, +}; + +// +// Default Options Fab Item ... +export const defaultOptionsFabItem: XSimpleFabItem = { + fixed: true, + id: 'Options', + disabled: false, + size: XFabButtonSize.REGULAR, + tooltip: XResourceIDs.options, + icon: XIconNames.menu_vertical, + color: XColorWithBrightness.Primary, + // + // Positioning ... + vertical: XFabVerticalSlot.BOTTOM, + horizontal: XFabHorizontalSlot.END, +}; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\constants\slide.options.defaults.ts + +import { XIconNames } from 'x-framework-components'; +import { AppResourceIDs } from 'src/app/config/localization.config'; +import { XColorWithBrightness, XResourceIDs } from 'x-framework-core'; +import { XSimpleListItemSlideOption } from '../typings/simple.list-item.typings'; + +// Details ... +export const defaultDetailsSlideOption = { + id: 'Details', + slot: 'start', + onlyIcon: true, + disabled: false, + icon: XIconNames.visible, + title: XResourceIDs.details, + color: XColorWithBrightness.Secondary, +} as XSimpleListItemSlideOption; + +// +// Edit ... +export const defaultEditSlideOption = { + id: 'Edit', + slot: 'start', + onlyIcon: true, + disabled: false, + icon: XIconNames.edit, + title: XResourceIDs.edit, + color: XColorWithBrightness.Tertiary, +} as XSimpleListItemSlideOption; + +// +// Download ... +export const defaultDownloadSlideOption = { + slot: 'start', + id: 'Download', + onlyIcon: true, + disabled: false, + icon: XIconNames.download, + title: XResourceIDs.download, + color: XColorWithBrightness.Success, +} as XSimpleListItemSlideOption; + +// +// Remove ... +export const defaultRemoveSlideOption = { + slot: 'end', + id: 'Remove', + onlyIcon: true, + disabled: false, + icon: XIconNames.remove, + title: XResourceIDs.delete, + color: XColorWithBrightness.Danger, +} as XSimpleListItemSlideOption; + +// +// Close ... +export const defaultCloseSlideOption = { + slot: 'end', + id: 'Close', + onlyIcon: true, + disabled: false, + icon: XIconNames.close, + title: XResourceIDs.close, + color: XColorWithBrightness.Danger, +} as XSimpleListItemSlideOption; + +// +// Connect ... +export const defaultConnectSlideOption = { + slot: 'start', + id: 'Connect', + onlyIcon: true, + disabled: false, + icon: XIconNames.web, + title: AppResourceIDs.connect, + color: XColorWithBrightness.Success, +} as XSimpleListItemSlideOption; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\directives\x-focused-provider.directive.ts + +import { isNullOrEmptyString, isNullOrUndefined } from 'x-framework-core'; +import { Input, Directive, Renderer2, AfterViewInit } from '@angular/core'; + +@Directive({ + selector: '[XFocusedProvider]', +}) +export class XFocusedProviderDirective implements AfterViewInit { + // + @Input('XFocusById') focusId?: string; + @Input('XFocusByClass') focusClass?: string; + + // + focused: boolean = false; + intervalId: NodeJS.Timeout = undefined; + + // + constructor(private renderer: Renderer2) {} + + // + ngAfterViewInit(): void { + this.intervalId = setInterval(() => this.handleWorks(), 100); + } + + // + private handleWorks() { + // + let target: HTMLElement = undefined; + if ( + !isNullOrUndefined(this.intervalId) && + isNullOrEmptyString(this.focusId) && + isNullOrEmptyString(this.focusClass) + ) { + clearInterval(this.intervalId); + } + + // + // Handle Focus By Id ... + if (!isNullOrEmptyString(this.focusId)) { + target = document.getElementById(this.focusId); + } + + // + // Handle Focus By Class Name ... + if (!isNullOrEmptyString(this.focusClass) && isNullOrUndefined(target)) { + target = document.querySelector(`.${this.focusClass}`) as HTMLElement; + } + + // + // Check Conditions ... + if (!isNullOrUndefined(target) && !this.focused) { + // + target.focus(); + this.focused = true; + clearInterval(this.intervalId); + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\directives\x-html-include.directive.ts + +import { + Input, + OnChanges, + Directive, + ElementRef, + SimpleChanges, +} from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { isNullOrEmptyString, keys, nameof, toPromise } from 'x-framework-core'; + +@Directive({ + selector: '[xHTMLInclude]', +}) +export class XHTMLIncludeDirective implements OnChanges { + // + @Input('xHTMLInclude') + xHTMLInclude: string; + + // + constructor(private element: ElementRef, private http: HttpClient) {} + + // + async ngOnChanges(changes: SimpleChanges): Promise { + // + const changeKeys = keys(changes); + const isXHTMLIncludeChanged = changeKeys.includes( + nameof('xHTMLInclude') + ); + if (isXHTMLIncludeChanged && !isNullOrEmptyString(this.xHTMLInclude)) { + await this.handleLoadingHTML(); + } + } + + // + async handleLoadingHTML() { + // + console.log('reading: ', this.xHTMLInclude); + const response = await toPromise( + this.http.get(this.xHTMLInclude, { responseType: 'text' }) + ); + + // + this.element.nativeElement.innerHTML = response; + console.log(response); + + // // + // fetch(this.xHTMLInclude) + // .then((response) => response.text()) + // .then((data) => { + // // + // console.log(data); + // this.element.nativeElement.innerHTML = data; + // }); + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\directives\x-insert-view-ref.directive.ts + +import { Directive, Input, ViewContainerRef, ViewRef } from '@angular/core'; + +/** + * Get a ComponentRef instance and Show it + * inside template ... + */ +@Directive({ selector: '[xInsertViewRef]' }) +export class XInsertViewRefDirective { + // + @Input() + set xInsertViewRef(val: ViewRef | undefined) { + this.container.clear(); + if (val) { + this.container.insert(val); + } + } + + // + constructor(private container: ViewContainerRef) {} +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\directives\x-on-escape-pressed.global.directive.ts + +import { Directive, HostListener, Output, EventEmitter } from '@angular/core'; + +@Directive({ + selector: '[xOnEscapeGlobal]', +}) +export class XOnEscapePressedGlobalDirective { + // + @Output('xOnEscapeGlobal') + listener = new EventEmitter(); + + // + // Listen globally on the document for keydown events + @HostListener('document:keydown.escape', ['$event']) + handleEscape(event: KeyboardEvent): void { + // + event.preventDefault(); + this.listener.emit(); + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\directives\x-render-component.directive.ts + +import { + Type, + Input, + Output, + Directive, + OnChanges, + EventEmitter, + SimpleChanges, + ViewContainerRef, + ChangeDetectorRef, + ComponentFactoryResolver, +} from '@angular/core'; +import { hasChild, isNullOrUndefined } from 'x-framework-core'; + +/** + * a Type for Component Props ... + */ +export type TXRenderComponentProps = { + [key in keyof T]?: any; +}; + +/** + * Describe Specific Component ... + */ +export interface XRenderComponentDescriptor { + /** + * Component Type ... + */ + type: Type; + + /** + * Component Props ... + */ + inputs: TXRenderComponentProps; +} + +/** + * a Directive to Create a Component ... + */ +@Directive({ selector: '[xRenderComponent]' }) +export class XRenderComponentDirective implements OnChanges { + // + //#region Props ... + // + @Input('xRenderComponent') + type: Type; + + // + @Input('xRenderInputs') + inputs: TXRenderComponentProps; + + // + @Output('xOnRendered') + onRendered = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + constructor( + public changeDtector: ChangeDetectorRef, + public viewContainerRef: ViewContainerRef, + public componentFactoryResolver: ComponentFactoryResolver + ) {} + //#endregion + + // + //#region LifeCycles ... + ngOnChanges(changes: SimpleChanges): void { + // + let isChanged = false; + let applyChange = false; + + // + if (!isNullOrUndefined(this.type) && !isNullOrUndefined(this.inputs)) { + this.handleCreateComponent(); + } + } + //#endregion + + // + //#region Private ... + private handleCreateComponent() { + // + if (this.viewContainerRef.length > 0) { + return; + } + + // + // Clean Container ... + this.viewContainerRef.clear(); + + // + // Create Component Factory ... + const componentFacotry = + this.componentFactoryResolver.resolveComponentFactory(this.type); + + // + // Create Component ... + const componentRef = + this.viewContainerRef.createComponent(componentFacotry); + + // + // Set Properties ... + if (!isNullOrUndefined(this.inputs) && !isNullOrUndefined(componentRef)) { + // + const propKeys = Object.keys(this.inputs); + if (hasChild(propKeys)) { + // + propKeys.forEach((key) => { + (componentRef.instance as any)[key] = this.inputs[key]; + }); + } + } + + // + if (!isNullOrUndefined(componentRef)) { + this.onRendered.emit(componentRef.instance); + } + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\files\v-file.configs.ts + +import { XIconNames } from "x-framework-components"; +import { XSimplePage } from "../typings/simple.page.typings"; + +// +//#region Localization Configuration ... +export enum FileResourceIDs { + file = 'file', + files = 'files', + image = 'image', + audio = 'audio', + video = 'video', + document = 'document', + file_name = 'file_name', + thumbnail_path = 'thumbnail_path', + files_page_title = 'files_page_title', + files_page_description = 'files_page_description', + files_management_page_title = 'files_management_page_title', + files_management_page_description = 'files_management_page_description', + files_explorer_page_title = 'files_explorer_page_title', + files_explorer_page_description = 'files_explorer_page_description', +} + +// +//#region Signle Resuorces ... +// +//#region file ... +export const file_fa = { + id: 'file', + value: 'فایل', +}; + +export const file_en = { + id: 'file', + value: 'File', +}; +//#endregion + +// +//#region files ... +export const files_fa = { + id: 'files', + value: 'فایل ها', +}; + +export const files_en = { + id: 'files', + value: 'Files', +}; +//#endregion + +// +//#region image ... +export const image_fa = { + id: 'image', + value: 'تصویر', +}; + +export const image_en = { + id: 'image', + value: 'Image', +}; +//#endregion + +// +//#region audio ... +export const audio_fa = { + id: 'audio', + value: 'صوت', +}; + +export const audio_en = { + id: 'audio', + value: 'Audio', +}; +//#endregion + +// +//#region video ... +export const video_fa = { + id: 'video', + value: 'ویدیو', +}; + +export const video_en = { + id: 'video', + value: 'Video', +}; +//#endregion + +// +//#region document ... +export const document_fa = { + id: 'document', + value: 'سند', +}; + +export const document_en = { + id: 'document', + value: 'Document', +}; +//#endregion + +// +//#region file_name ... +export const file_name_fa = { + id: 'file_name', + value: '', +}; + +export const file_name_en = { + id: 'file_name', + value: '', +}; +//#endregion + +// +//#region thumbnail_path ... +export const thumbnail_path_fa = { + id: 'thumbnail_path', + value: 'آدرس تصویر بندانگشتی', +}; + +export const thumbnail_path_en = { + id: 'thumbnail_path', + value: 'Thumbnail Path', +}; +//#endregion + +// +//#region files_page_title ... +export const files_page_title_fa = { + id: 'files_page_title', + value: 'فایل', +}; + +export const files_page_title_en = { + id: 'files_page_title', + value: 'File', +}; +//#endregion + +// +//#region files_page_description ... +export const files_page_description_fa = { + id: 'files_page_description', + value: 'ابزارهای مدیریت فایل ها', +}; + +export const files_page_description_en = { + id: 'files_page_description', + value: 'Tools for Files', +}; +//#endregion + +// +//#region files_management_page_title ... +export const files_management_page_title_fa = { + id: 'files_management_page_title', + value: 'مدیریت فایل ها', +}; + +export const files_management_page_title_en = { + id: 'files_management_page_title', + value: 'Files Management', +}; +//#endregion + +// +//#region files_management_page_description ... +export const files_management_page_description_fa = { + id: 'files_management_page_description', + value: 'مدیریت فایل ها', +}; + +export const files_management_page_description_en = { + id: 'files_management_page_description', + value: 'Files Management', +}; +//#endregion + +// +//#region files_explorer_page_title ... +export const files_explorer_page_title_fa = { + id: 'files_explorer_page_title', + value: 'فایل ها', +}; + +export const files_explorer_page_title_en = { + id: 'files_explorer_page_title', + value: 'Files', +}; +//#endregion + +// +//#region files_explorer_page_description ... +export const files_explorer_page_description_fa = { + id: 'files_explorer_page_description', + value: 'مرورگر فایل', +}; + +export const files_explorer_page_description_en = { + id: 'files_explorer_page_description', + value: 'File Explorer', +}; +//#endregion +//#endregion + +// +export const FileLocalizationsResourceIDs = Object.assign(FileResourceIDs, {}); + +// +export const FileLocalizationsResources = { + // + file: { + fa: { ...file_fa }, + en: { ...file_en }, + }, + files: { + fa: { ...files_fa }, + en: { ...files_en }, + }, + image: { + fa: { ...image_fa }, + en: { ...image_en }, + }, + audio: { + fa: { ...audio_fa }, + en: { ...audio_en }, + }, + video: { + fa: { ...video_fa }, + en: { ...video_en }, + }, + document: { + fa: { ...document_fa }, + en: { ...document_en }, + }, + file_name: { + fa: { ...file_name_fa }, + en: { ...file_name_en }, + }, + thumbnail_path: { + fa: { ...thumbnail_path_fa }, + en: { ...thumbnail_path_en }, + }, + files_page_title: { + fa: { ...files_page_title_fa }, + en: { ...files_page_title_en }, + }, + files_page_description: { + fa: { ...files_page_description_fa }, + en: { ...files_page_description_en }, + }, + files_management_page_title: { + fa: { ...files_management_page_title_fa }, + en: { ...files_management_page_title_en }, + }, + files_management_page_description: { + fa: { ...files_management_page_description_fa }, + en: { ...files_management_page_description_en }, + }, + files_explorer_page_title: { + fa: { ...files_explorer_page_title_fa }, + en: { ...files_explorer_page_title_en }, + }, + files_explorer_page_description: { + fa: { ...files_explorer_page_description_fa }, + en: { ...files_explorer_page_description_en }, + }, +}; +//#endregion + +// +//#region Page Configuration ... +// +export enum FileRoutes { + Default = 'files', + FilesExplorer = 'files-explorer', + FilesManagement = 'files-management', +} + +// +export const Files: XSimplePage = { + id: `${FileRoutes.Default}`, + name: `${FileRoutes.Default}`, + title: FileResourceIDs.files_page_title, + description: FileResourceIDs.files_page_description, + baseRoute: `${FileRoutes.Default}`, + route: ['/', `${FileRoutes.Default}`], + icon: XIconNames.collapse, + childs: [ + // + //#region Files Management ... + { + id: `${FileRoutes.FilesManagement}`, + name: `${FileRoutes.FilesManagement}`, + title: FileResourceIDs.files_management_page_title, + description: FileResourceIDs.files_management_page_description, + baseRoute: `${FileRoutes.FilesManagement}`, + route: ['/', `${FileRoutes.Default}`, `${FileRoutes.FilesManagement}`], + icon: XIconNames.live_tv, + }, + //#endregion + + // + //#region Files Explorer ... + { + id: `${FileRoutes.FilesExplorer}`, + name: `${FileRoutes.FilesExplorer}`, + title: FileResourceIDs.files_explorer_page_title, + description: FileResourceIDs.files_explorer_page_description, + baseRoute: `${FileRoutes.FilesExplorer}`, + route: ['/', `${FileRoutes.Default}`, `${FileRoutes.FilesExplorer}`], + icon: XIconNames.live_tv, + }, + //#endregion + ], +}; +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\files\v-file.tools.ts + +import { + hasChild, + XFileType, + isExtensionOk, + isNullOrUndefined, + XResourceIdentifier, +} from 'x-framework-core'; +import { XFileDto } from 'x-saherelm-api-sdk'; +import { XFileStatus } from 'x-framework-components'; +import { FileLocalizationsResourceIDs } from './v-file.configs'; +import { AppResourceIDs } from 'src/app/config/localization.config'; + +/** + * Retrieve Propper Resource ID based on File Type ... + * + * @param type + * @returns + */ +export function getFileTypeResourceID(type?: XFileType) { + // + let result: XResourceIdentifier = AppResourceIDs.unknown; + + // + if (!isNullOrUndefined(type)) { + // + switch (type) { + // + // Image Type ... + case XFileType.Image: + case XFileType.CoverImage: + case XFileType.ProfileImage: + result = FileLocalizationsResourceIDs.image; + break; + // + // Audio Type ... + case XFileType.Audio: + result = FileLocalizationsResourceIDs.audio; + break; + // + // Video Type ... + case XFileType.Video: + result = FileLocalizationsResourceIDs.video; + break; + // + // Document Type ... + case XFileType.Document: + result = FileLocalizationsResourceIDs.document; + break; + } + } + + // + return result; +} + +/** + * Get Specified File Status ... + * + * @param file + * @param allowedExtensions + * @param minAllowedFileSize + * @param maxAllowedFileSize + * @returns + */ +export function getFileStatus( + file: File, + allowedExtensions: Array = [], + minAllowedFileSize: number = 0, + maxAllowedFileSize: number = 0, +) { + // + let result = XFileStatus.EMPTY; + + // + let isPassed = false; + + // + // Validate File Exists ... + if (isNullOrUndefined(file)) { + return result; + } + + // + // Check File Size ... + if (file.size <= 0) { + // + result = XFileStatus.EMPTY; + return result; + } + + // + // Check File Extension ... + // only if allowed Extensions Provided ... + if (!isNullOrUndefined(allowedExtensions) && hasChild(allowedExtensions)) { + // + isPassed = isExtensionOk(file, allowedExtensions); + if (!isPassed) { + // + result = XFileStatus.NOT_ALLOWED_TYPE; + return result; + } + } + + // + // Check File Min Allowed Size ... + if (minAllowedFileSize > 0) { + // + isPassed = file.size >= minAllowedFileSize; + if (!isPassed) { + // + result = XFileStatus.MIN_SIZE_NOT_REACHED; + return result; + } + } + + // + // Check File Max Allowed Size ... + if (maxAllowedFileSize > 0) { + // + isPassed = file.size <= maxAllowedFileSize; + if (!isPassed) { + // + result = XFileStatus.MAX_ALLOWED_SIZE_REACHED; + return result; + } + } + + // + // Everything was Passed ... + result = XFileStatus.OK; + + // + return result; +} + +/** + * Validate Adding New File to a Collection Status ... + * + * @param file + * @param files + * @param maxAllowedFiles + * @returns + */ +export function getCanAddFileStatus( + file: File, + files: Array, + maxAllowedFiles: number = 0, +) { + // + let result = XFileStatus.EMPTY; + + // + result = getFileStatus(file); + if (result !== XFileStatus.OK) { + return result; + } + + // + if (maxAllowedFiles > 0) { + // + let isPassed = files.length + 1 <= maxAllowedFiles; + if (!isPassed) { + // + result = XFileStatus.MAX_ALLOWED_FILES_REACHED; + return result; + } + } + + // + result = XFileStatus.OK; + + // + return result; +} + +/** + * Validate Adding New File to a Collection Status ... + * + * @param file + * @param filesCount + * @param maxAllowedFiles + * @returns + */ +export function getCanAddFileStatusByFilesCount( + file: File, + filesCount: number = 0, + maxAllowedFiles: number = 0, +) { + // + let result = XFileStatus.EMPTY; + + // + result = getFileStatus(file); + if (result !== XFileStatus.OK) { + return result; + } + + // + if (maxAllowedFiles > 0) { + // + let isPassed = filesCount + 1 <= maxAllowedFiles; + if (!isPassed) { + // + result = XFileStatus.MAX_ALLOWED_FILES_REACHED; + return result; + } + } + + // + result = XFileStatus.OK; + + // + return result; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\files\v-file\v-file.component.html + + + + +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\files\v-file\v-file.component.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\files\v-file\v-file.component.ts + +import { + Input, + Output, + Component, + ViewChild, + TemplateRef, + EventEmitter, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + getFileStatus, + getFileTypeResourceID, + getCanAddFileStatusByFilesCount, +} from '../v-file.tools'; +import { + toArray, + XFileType, + XFileBlob, + XExceptionIDs, + getFieldNames, + nameofInstance, + throwException, + XOneOrManyType, + XColorIdentifier, + isNullOrUndefined, + getErrorException, + isNullOrEmptyString, + XResourceIdentifier, + XExceptionIdentifier, +} from 'x-framework-core'; +import { + XFileDto, + XPersonDto, + XReferenceDto, + defaultFileDto, + prepareFileDtoFields, +} from 'x-saherelm-api-sdk'; +import { + VFilePresentType, + isVFilePresentType, + VFilePresentTypeIdentifier, +} from './v-file.typings'; +import { Subject } from 'rxjs'; +import { + XFileStatus, + XListAction, + XIconIdentifier, + XListActionModel, + XActionBarActionFiredModel, + XFormDatePickerControlConfig, + XFormDatePickerControlPickerPosition, +} from 'x-framework-components'; +import { + VBaseDtoComponent, + VModelFieldDescriptor, + VModelValidationResult, +} from '../../v-dto/v-dto.typings'; +import { + PersonPresentType, + isPersonPresentType, + PersonPresentTypeIdentifier, +} from '../../v-person/v-person.typings'; +import { Validators } from '@angular/forms'; +import { + defaultEditSlideOption, + defaultDownloadSlideOption, +} from '../../constants/slide.options.defaults'; +import { + XSimpleListItem, + XSimpleListItemSlideOption, +} from '../../typings/simple.list-item.typings'; +import { + VReferencePresentType, + isVReferencePresentType, + VReferencePresentTypeIdentifier, +} from '../../v-reference/v-reference.typings'; +import { + VTagsPresenterActionModel, + VTagsPresenterModelState, + VTagsPresenterPresentType, + isVTagsPresenterPeresentType, + VTagsPresenterPresentTypeIdentfier, +} from '../../tags/v-tags-presenter/v-tags-presenter.typings'; + +@Component({ + selector: 'v-file', + templateUrl: './v-file.component.html', + styleUrls: ['./v-file.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class VFileComponent extends VBaseDtoComponent { + // + //#region Props ... + readonly defaultModel: XFileDto = { + ...defaultFileDto, + }; + readonly FieldNames = getFieldNames(this.defaultModel); + + /** + * Override Present Type to Handle Custom Types and also Prevent Form Type ... + */ + @Input() + presentType: VFilePresentTypeIdentifier = VFilePresentType.AsView; + + /** + * Default Model Field Descriptors ... + */ + defaultFieldDescriptors: VModelFieldDescriptor[] = [ + // + // ID ... + { + hide: true, + hideLabel: true, + allowFieldForm: false, + key: this.FieldNames.id, + label: this.ResourceIDs.id, + value: this.defaultModel.id, + type: this.ModelFieldTypes.String, + } as VModelFieldDescriptor, + // + // Type ... + { + hide: false, + hideLabel: false, + allowFieldForm: false, + key: this.FieldNames.type, + label: this.ResourceIDs.type, + value: this.defaultModel.type, + type: this.ModelFieldTypes.Number, + valueProvider: (value) => { + return this.applyLocale( + this.resourceProvider(getFileTypeResourceID(value)), + ); + }, + } as VModelFieldDescriptor, + // + // Name ... + { + hide: false, + hideLabel: false, + allowFieldForm: false, + key: this.FieldNames.name, + value: this.defaultModel.name, + label: this.AppResourceIDs.name, + type: this.ModelFieldTypes.String, + } as VModelFieldDescriptor, + // + // Thumb ... + { + hide: false, + hideLabel: false, + allowFieldForm: false, + key: this.FieldNames.thumb, + value: this.defaultModel.thumb, + type: this.ModelFieldTypes.String, + label: this.AppResourceIDs.thumbnail, + } as VModelFieldDescriptor, + // + // Path ... + { + hide: false, + hideLabel: false, + allowFieldForm: false, + key: this.FieldNames.path, + value: this.defaultModel.path, + label: this.AppResourceIDs.path, + type: this.ModelFieldTypes.String, + } as VModelFieldDescriptor, + // + // ThumbPath ... + { + hide: false, + hideLabel: false, + allowFieldForm: false, + key: this.FieldNames.thumbPath, + type: this.ModelFieldTypes.String, + value: this.defaultModel.thumbPath, + label: this.AppResourceIDs.thumbnail_path, + } as VModelFieldDescriptor, + // + // UploadedOn ... + { + hide: false, + hideLabel: false, + allowFieldForm: false, + key: this.FieldNames.uploadedOn, + type: this.ModelFieldTypes.Date, + value: this.defaultModel.uploadedOn, + label: this.AppResourceIDs.uploaded_on, + valueProvider: (value: Date) => { + // + if (isNullOrUndefined(value)) { + return ''; + } + + // + return this.applyDefaultDate(value); + }, + formControlConfig: { + index: 2, + propName: this.FieldNames.uploadedOn, + type: { + type: this.FormControlTypes.Date, + config: { + iconColor: this.ColorNames.Tertiary, + toggleIcon: this.IconNames.calendar, + applyStateColor: true, + datePickerPosition: XFormDatePickerControlPickerPosition.Prefix, + } as XFormDatePickerControlConfig, + }, + validators: { + validators: [Validators.required], + }, + eventHandlers: {}, + appearance: { + hidden: false, + disabled: false, + placeholder: this.AppResourceIDs.uploaded_on, + appearance: this.FormControlAppearances.Outline, + icons: { + prefix: { + applyStateColor: true, + name: this.IconNames.calendar, + color: this.ColorNames.Tertiary, + }, + }, + }, + }, + } as VModelFieldDescriptor, + // + // FileName ... + { + hide: false, + hideLabel: false, + allowFieldForm: false, + key: this.FieldNames.fileName, + value: this.defaultModel.fileName, + type: this.ModelFieldTypes.String, + label: this.AppResourceIDs.file_name, + valueProvider: (value) => { + return this.applyLocale(value); + }, + } as VModelFieldDescriptor, + // + // Owner ID ... + { + hide: true, + hideLabel: true, + allowFieldForm: false, + key: this.FieldNames.ownerId, + value: this.defaultModel.ownerId, + type: this.ModelFieldTypes.String, + label: this.AppResourceIDs.author_id, + } as VModelFieldDescriptor, + // + // Owner ... + { + hide: false, + hideLabel: false, + showContentBelowLabel: true, + key: this.FieldNames.owner, + value: this.defaultModel.owner, + label: this.AppResourceIDs.author, + type: this.ModelFieldTypes.Object, + hasCustomTemplate: true, + } as VModelFieldDescriptor, + // + // References ... + { + hide: false, + hideLabel: false, + allowFieldForm: false, + key: this.FieldNames.references, + value: this.defaultModel.references, + label: this.AppResourceIDs.references, + type: this.ModelFieldTypes.Array, + showContentBelowLabel: true, + pushArrayChildsOnceToTemplate: true, + hasCustomTemplate: true, + } as VModelFieldDescriptor>>, + ]; + + @ViewChild('ownerRef') + ownerRef!: TemplateRef; + + /** + * Owner Present Type ... + */ + @Input() + ownerPersentType: PersonPresentTypeIdentifier = PersonPresentType.AsCard; + + @ViewChild('referencesRef') + referencesRef!: TemplateRef; + + /** + * References List Action Provider ... + */ + referencesListActionProvider = new Subject(); + + /** + * Specifie Reference Present Type ... + */ + @Input() + referencePresentType: VReferencePresentTypeIdentifier = + VReferencePresentType.AsView; + //#endregion + + // + //#region LifeCycles ... + afterViewInit(): void { + super.afterViewInit(); + + // + this.handleAttachTemplates(); + // this.handleCanAttachTagState(); + } + + onChange(changeKeys: string[]): void { + super.onChange(changeKeys); + + // + let isChanged = false; + let applyChanges = false; + + // + // PresentType ... + isChanged = changeKeys.includes(nameofInstance(this, 'presentType')); + if (isChanged) { + // + if ( + isNullOrEmptyString(this.presentType) || + !isVFilePresentType(this.presentType) + ) { + // + applyChanges = true; + this.presentType = VFilePresentType.AsView; + } + } + + // + // Owner Present Type ... + isChanged = changeKeys.includes(nameofInstance(this, 'ownerPersentType')); + if (isChanged) { + // + if (!isPersonPresentType(this.ownerPersentType)) { + // + applyChanges = true; + this.ownerPersentType = PersonPresentType.AsCard; + } + } + + // + // Reference Present Type ... + isChanged = changeKeys.includes( + nameofInstance(this, 'referencePresentType'), + ); + if (isChanged) { + // + if (!isVReferencePresentType(this.referencePresentType)) { + // + applyChanges = true; + this.referencePresentType = VReferencePresentType.AsView; + } + } + + // + if (applyChanges) { + this.detectChanges(); + } + + // + this.handleAttachTemplates(); + } + //#endregion + + // + //#region AsMedia ... + @ViewChild('mediaPresentTypeRef') + mediaPresentTypeRef!: TemplateRef; + + /** + * Provided Actions which Used only in Card Present Type ... + */ + @Input() + providedMediaActions: Array = [ + // + // Download ... + { + ...defaultDownloadSlideOption, + onlyIcon: false, + handler: (info) => { + console.log('handle remove: ', info); + }, + }, + // + // Edit ... + { + ...defaultEditSlideOption, + onlyIcon: false, + }, + ]; + //#endregion + + // + //#region AsFileUpload ... + /** + * File Upload Present Type Templat ... + */ + @ViewChild('fileUploadPresentTypeRef') + fileUploadPresentTypeRef!: TemplateRef; + + /** + * File Upload Color Handler ... + */ + @Input() + fileUploadColor: XColorIdentifier = this.ColorNames.Light; + + /** + * File Upload Foreground Color ... + */ + @Input() + fileUploadForegroundColor: boolean = false; + + /** + * number of files + */ + @Input() + fileUploadFilesCount: number; + + // + //#region File Upload Drop Area ... + @Input() + showFileUploadDropArea: boolean = true; + + @Input() + fileUploadDropAreaColor: XColorIdentifier = this.ColorNames.Light; + + @Input() + fileUploadDropAreaIcon: XIconIdentifier = this.IconNames.arrow_dropdown; + + @Input() + fileUploadDropAreaMessage: XResourceIdentifier = + this.ResourceIDs.drag_drop_here; + + @Input() + fileUploadActionBarHideActions: Array = []; + //#endregion + + // + //#region File Upload Property / Validations ... + @Input() + fileUploadEmptyFileException: XExceptionIdentifier; + + @Input() + fileUploadNotAllowedFileException: XExceptionIdentifier; + + @Input() + fileUploadMaxAllowedSizeException: XExceptionIdentifier; + + @Input() + fileUploadMaxAllowedFilesException: XExceptionIdentifier; + + @Input() + fileUploadMinAllowedFileSizeException: XExceptionIdentifier; + + @Input() + fileUploadMaxAllowedFileSizeException: XExceptionIdentifier; + + @Input() + fileUploadAllowedExtensions: Array = []; + + @Input() + fileUploadMaxAllowedFiles: number = this.config.maxUploadFiles; + + @Input() + fileUploadMaxAllowedSize: number = this.config.maxAllowedSize; + + @Input() + fileUploadMinAllowedFileSize: number = this.config.minAllowedFileSize; + + @Input() + fileUploadMaxAllowedFileSize: number = this.config.maxAllowedFileSize; + //#endregion + + // + fileUploadFiles: Array = []; + + // + @Output() + fileUploadFilesChanged = new EventEmitter>(); + + // + @Output() + fileUploadActionFired = new EventEmitter(); + + /** + * Handle File Upload Files Changed ... + * + * @param event + */ + async handleFileUploadFileChanges( + event: XOneOrManyType, + ) { + // + // Validate ... + if (isNullOrUndefined(event)) { + return; + } + + // + // Here we have to do File Checking ... + const recieveds = toArray(event); + if (this.hasChild(recieveds)) { + // + // Here we Have to Validate File and Files ... + const filteredFiles = [...recieveds.filter((r) => r instanceof File)]; + if (this.hasChild(filteredFiles)) { + // + for (let file of filteredFiles) { + // + const state = getFileStatus( + file, + this.fileUploadAllowedExtensions, + this.fileUploadMinAllowedFileSize, + this.fileUploadMaxAllowedFileSize, + ); + + // + // Check File State Valids ... + let isValid = state === XFileStatus.OK; + if (!isValid) { + // + let execptionId = this.getFileStatusExceptionId(state); + const exception = getErrorException(execptionId); + await this.managerService.notificationService.presentErrorNotification( + { + exception, + dissmissable: true, + }, + ); + throwException(exception); + } + + // + isValid = + getCanAddFileStatusByFilesCount( + file, + this.fileUploadFilesCount, + this.fileUploadMaxAllowedFiles, + ) === XFileStatus.OK; + if (!isValid) { + // + let execptionId = this.getFileStatusExceptionId(state); + const exception = getErrorException(execptionId); + await this.managerService.notificationService.presentErrorNotification( + { + exception, + dissmissable: true, + }, + ); + throwException(exception); + } + + // + this.fileUploadFiles.push(file); + } + } else { + this.fileUploadFiles = []; + } + } else { + this.fileUploadFiles = []; + } + + // + // Notify Locking ... + this.lockNotifier.next({ + id: this.uuid, + state: this.hasChild(this.fileUploadFiles), + }); + + // + // Notify Changes ... + this.fileUploadFilesChanged.emit(this.fileUploadFiles); + } + + /** + * Handle File Upload Action Fired ... + * @param event + */ + handleFileUploadActionBarActionFired(event: XActionBarActionFiredModel) { + // + // Validate ... + if (isNullOrUndefined(event) || isNullOrEmptyString(event.action)) { + return; + } + + // + this.fileUploadActionFired.emit(event); + } + //#endregion + + // + //#region Providers ... + referencesListItems: Array> = []; + getReferencesListItems(references: Array) { + // + if (this.hasChild(this.referencesListItems)) { + return this.referencesListItems; + } + + // + this.referencesListItems = [ + ...references.map((r) => { + return { + data: { ...r }, + } as XSimpleListItem; + }), + ]; + + // + this.referencesListActionProvider.next({ action: XListAction.Refresh }); + setTimeout( + () => + this.referencesListActionProvider.next({ action: XListAction.Refresh }), + 200, + ); + setTimeout( + () => + this.referencesListActionProvider.next({ action: XListAction.Refresh }), + 500, + ); + + // + return this.referencesListItems; + } + + /** + * Select Custom Present Types Template ... + * + * @param presentType + * @returns + */ + getCustomPresentTypeTemplate(presentType: string): TemplateRef { + // + if ( + (presentType === VFilePresentType.AsItem || + presentType === VFilePresentType.AsCard || + presentType === VFilePresentType.AsMedia || + presentType === VFilePresentType.AsThumbnail) && + !isNullOrUndefined(this.mediaPresentTypeRef) + ) { + return this.mediaPresentTypeRef; + } + + // + if ( + presentType === VFilePresentType.AsFileUpload && + !isNullOrUndefined(this.fileUploadPresentTypeRef) + ) { + return this.fileUploadPresentTypeRef; + } + + // + return null; + } + //#endregion + + // + //#region Abstraction ... + /** + * Prepare Model ... + * + * @param model + */ + prepare(model?: XFileDto): XFileDto { + return prepareFileDtoFields(model); + } + + /** + * Validate Model ... + * + * @param model + */ + validate(model?: Partial): VModelValidationResult { + // + let result: VModelValidationResult = undefined; + + // + let isValid = false; + const validationErrors: string[] = []; + + // + model = { + ...this.model, + ...model, + }; + const normalized = this.normalize(model); + + // + // Handle Validation ... + isValid = + !isNullOrUndefined(normalized) && !this.isSameAsDefault(normalized); + if (!isValid) { + // + validationErrors.push( + this.getExceptionMessage(XExceptionIDs.InvalidArgs), + ); + } else { + // + // Check Other Validations ... + + // + // ID ... + + // + // Type ... + isValid = !isNullOrUndefined(normalized.type); + if (!isValid) { + // + validationErrors.push( + `${this.resourceProvider( + this.ResourceIDs.type, + )}: ${this.getExceptionMessage(XExceptionIDs.InvalidArgs)}`, + ); + } + + // + // Name ... + isValid = !isNullOrEmptyString(normalized.name); + if (!isValid) { + // + validationErrors.push( + `${this.resourceProvider( + this.AppResourceIDs.name, + )}: ${this.getExceptionMessage(XExceptionIDs.InvalidArgs)}`, + ); + } + + // + // Path ... + isValid = !isNullOrEmptyString(normalized.path); + if (!isValid) { + // + validationErrors.push( + `${this.resourceProvider( + this.AppResourceIDs.path, + )}: ${this.getExceptionMessage(XExceptionIDs.InvalidArgs)}`, + ); + } + + // + // UploadedOn ... + isValid = !isNullOrUndefined(normalized.uploadedOn); + if (!isValid) { + // + validationErrors.push( + `${this.resourceProvider( + this.AppResourceIDs.uploaded_on, + )}: ${this.getExceptionMessage(XExceptionIDs.InvalidArgs)}`, + ); + } + } + + // + // Handle Result ... + + // + isValid = !this.hasChild(validationErrors); + + // + const isValidForAdd = isNullOrEmptyString(normalized.id); + const isValidForUpdate = !isNullOrEmptyString(normalized.id); + + // + result = { + isValid, + normalized, + isValidForAdd, + isValidForUpdate, + validationErrors, + }; + + // + return result; + } + //#endregion + + // + //#region Private ... + /** + * Extract Customized ExceptionIdentifier based on file Status ... + * @param state + * @returns + */ + private getFileStatusExceptionId(state: XFileStatus) { + // + let result: XExceptionIdentifier = undefined; + + // + switch (state) { + // + case XFileStatus.EMPTY: + result = this.fileUploadEmptyFileException; + break; + + // + case XFileStatus.NOT_ALLOWED_TYPE: + result = this.fileUploadNotAllowedFileException; + break; + + // + case XFileStatus.MIN_SIZE_NOT_REACHED: + result = this.fileUploadMinAllowedFileSizeException; + break; + + // + case XFileStatus.MAX_ALLOWED_SIZE_REACHED: + result = this.fileUploadMaxAllowedFileSizeException; + break; + + // + case XFileStatus.MAX_ALLOWED_FILES_REACHED: + result = this.fileUploadMaxAllowedFilesException; + break; + + // + case XFileStatus.SIZE_EXCEEDED: + result = this.fileUploadMaxAllowedSizeException; + break; + + // + } + + // + return result; + } + + /** + * Handle Attach Templates to Field Descriptors ... + * + * @returns + */ + private handleAttachTemplates() { + // + let applyChanges = false; + + // + // Check Owner Field Has Template or not ... + const hasOwnerTemplate = !isNullOrUndefined( + this.fieldDescriptors.find( + (fd) => + fd.key === this.FieldNames.owner && + !!fd.hasCustomTemplate && + !isNullOrUndefined(fd.template), + ), + ); + + // + // Check References Field Has Template or not ... + const hasReferencesTemplate = !isNullOrUndefined( + this.fieldDescriptors.find( + (fd) => + fd.key === this.FieldNames.references && + !!fd.hasCustomTemplate && + !isNullOrUndefined(fd.template), + ), + ); + + // + // if all required fields has template .... + if (hasOwnerTemplate && hasReferencesTemplate) { + return; + } + + // + // Set Owner Template ... + if (!hasOwnerTemplate && !isNullOrUndefined(this.ownerRef)) { + // + applyChanges = true; + + // + this.defaultFieldDescriptors = this.fieldDescriptors.map((fd) => { + // + return { + ...fd, + template: + fd.key === this.FieldNames.owner && + !!fd.hasCustomTemplate && + isNullOrUndefined(fd.template) + ? this.ownerRef + : fd.template, + }; + }); + + // + if (!isNullOrUndefined(this.providedFieldDescriptors)) { + // + this.providedFieldDescriptors = this.providedFieldDescriptors.map( + (fd) => { + // + return { + ...fd, + template: + fd.key === this.FieldNames.owner && + !!fd.hasCustomTemplate && + isNullOrUndefined(fd.template) + ? this.ownerRef + : fd.template, + }; + }, + ); + } + } + + // + // Set References Template ... + if (!hasReferencesTemplate && !isNullOrUndefined(this.referencesRef)) { + // + applyChanges = true; + + // + this.defaultFieldDescriptors = this.fieldDescriptors.map((fd) => { + // + return { + ...fd, + template: + fd.key === this.FieldNames.references && + !!fd.hasCustomTemplate && + isNullOrUndefined(fd.template) + ? this.referencesRef + : fd.template, + }; + }); + + // + if (!isNullOrUndefined(this.providedFieldDescriptors)) { + // + this.providedFieldDescriptors = this.providedFieldDescriptors.map( + (fd) => { + // + return { + ...fd, + template: + fd.key === this.FieldNames.references && + !!fd.hasCustomTemplate && + isNullOrUndefined(fd.template) + ? this.referencesRef + : fd.template, + }; + }, + ); + } + } + + // + if (applyChanges) { + // + this.resetFieldDescriptors(); + this.detectChanges(); + } + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\files\v-file\v-file.typings.ts + +import { isValueInEnum } from '../../tools/v-base.tools'; + +// +//#region Present Type ... +export enum VFilePresentType { + // + AsNone = 'as_none', + AsView = 'as_view', + AsItem = 'as_item', + AsCard = 'as_card', + AsMedia = 'as_media', + AsThumbnail = 'as_thumbnail', + AsFileUpload = 'as_fileupload', +} + +export type VFilePresentTypeIdentifier = VFilePresentType | string; + +export function isVFilePresentType(value: string) { + return isValueInEnum(value, Object.assign({}, VFilePresentType)); +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\files\v-file-input\v-file-input.component.html + +
+ +
+ + + +
+ + + +
+ +
+
+ + + + +

{{ getToolbarTitle() }}

+
+
+ + + +
+ +
+
+
+
+
+ + + +
+ + +
+ + +
+
+ + + + + + + +
+ +
+
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\files\v-file-input\v-file-input.component.scss + +@import "../../../../theme/theme.scss"; + +:host { + --dialog-toolbar-height: 0px; + --background: var(--x-color-light-shade); +} + +::ng-deep.fullscreen { + --width: 100% !important; + --height: 100% !important; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + margin: 0 !important; +} + +.v-base-dialog { + width: 100%; + height: 100%; + // height: calc(100% - var(--dialog-toolbar-height)); + // margin-bottom: var(--dialog-toolbar-height); +} + +.v-base-dialog-content { + height: 100%; + + // + padding-bottom: var(--dialog-toolbar-height); +} + +.v-base-dialog-toolbar-area { + margin: 8px; +} + +.v-base-dialog-toolbar-item { + width: auto; + height: 48px; +} + +.v-base-dialog-toolbar-button { + width: 32px; + margin: { + left: 4px; + right: 4px; + } +} + +.v-webinar-screen { + padding: 16px; +} + +// +// Non Global ... +.v-avatar-image { + // + margin: auto; + display: block; + max-width: 100%; + max-height: 100%; + + // + clip-path: circle(); +} + +.v-container { + height: 100%; + padding: 16px; + overflow: scroll; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\files\v-file-input\v-file-input.component.ts + +import { + Input, + Component, + ViewChild, + ElementRef, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + isSameArray, + XColorIdentifier, + isNullOrUndefined, + isNullOrEmptyString, + XResourceIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { Subject } from 'rxjs'; +import { + XSlotName, + XIconNames, + XSlotLayout, + XSlotterActionModel, + DefaultActionBarItemIds, +} from 'x-framework-components'; +import { + VFileInputFilePrperties, + VFileInputModalContext, + VFileInputModalContextIdentifier, + VFileInputReturnActionIdentifier, +} from './v-file-input.typings'; +import { + VBaseDtoAction, + VBaseDtoActionModel, + VBaseDtoPresentType, +} from '../../v-dto/v-dto.typings'; +import { VFilePresentType } from '../v-file/v-file.typings'; +import { VFileComponent } from '../v-file/v-file.component'; +import { defaultFileDto, XFileDto } from 'x-saherelm-api-sdk'; +import { VBaseComponent } from '../../classes/v-base.component'; + +@Component({ + selector: 'v-file-input', + templateUrl: './v-file-input.component.html', + styleUrls: ['./v-file-input.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class VFileInputComponent extends VBaseComponent { + // + //#region Properties ... + // + readonly SlotNames = Object.assign({}, XSlotName); + readonly SlotLayouts = Object.assign({}, XSlotLayout); + + /** + * retrieve Toolbar container ViewChild + * to Calculate Toolbar Height ... + */ + @ViewChild('vBaseDialogToolbar') + vBaseDialogToolbarContainer!: ElementRef; + + @ViewChild('vFile') + vFile!: VFileComponent; + + /** + * Specified Modal Type ... + */ + @Input() + context: VFileInputModalContextIdentifier; + + @Input() + originalFiles: Array = []; + editedFiles: Array = []; + + @Input() + fileProperties: VFileInputFilePrperties; + + // + fileModel: XFileDto = { + ...defaultFileDto, + }; + filePresentType = VFilePresentType.AsFileUpload; + fileUploadActionBarHideActions: Array = [ + DefaultActionBarItemIds.Upload, + ]; + fileActionProvider = new Subject(); + + @Input() + presentType: VBaseDtoPresentType; + + /** + * Background Color of Component ... + */ + @Input() + backgroundColor: XColorIdentifier = XColorWithBrightness.Light; + + @Input() + toolbarTitle: XResourceIdentifier = ''; + + /** + * Toolbar Color ... + */ + @Input() + toolbarColor: XColorIdentifier = XColorWithBrightness.PrimaryTint; + + /** + * Progressbar Color ... + */ + @Input() + progressbarColor: XColorIdentifier = XColorWithBrightness.Warning; + showProgrssbar = false; + + /** + * Action Icon Names Specifiecations ... + */ + @Input() + cancelIcon = XIconNames.close; + + @Input() + backIcon = XIconNames.arrow_back; + + @Input() + selectIcon = XIconNames.checked; + + // + slotterActionProvider = new Subject(); + //#endregion + + // + //#region LifeCycles ... + afterViewInit(): void { + super.afterViewInit(); + + // + this.handleFVFilePropertiesSet(); + this.fileActionProvider.next({ action: VBaseDtoAction.Refresh }); + } + //#endregion + + // + //#region Provider ... + getToolbarTitle() { + // + let result = ''; + + // + if (!isNullOrUndefined(this.toolbarTitle)) { + result = this.resourceProvider(this.toolbarTitle); + } + + // + return result; + } + //#endregion + + // + //#region Handlers ... + /** + * Handle File Upload Files Changed ... + * + * @param event + */ + handleFileUploadFileChanges(event: Array) { + this.editedFiles = [...event]; + } + + /** + * Handle Select Pressed ... + * + * @returns + */ + async handleSelectPressed() { + // + if ( + isNullOrUndefined(this.editedFiles) || + isSameArray(this.originalFiles, this.editedFiles) + ) { + return; + } + + // + await this.handleCloseDialog([...this.editedFiles]); + } + + /** + * Use for Handle Modela Closing Action based on Context ... + * @returns + */ + async handleBackPressed(action?: VFileInputReturnActionIdentifier) { + // + if ( + this.loading || + this.uiDisabled || + !isNullOrUndefined(this.error) || + isNullOrUndefined(this.context) || + isNullOrEmptyString(this.context) + ) { + return; + } + + // + await this.handleCloseDialog(action); + } + + /** + * Use for Handle Modal Closing Action based on Context ... + * + * @returns + */ + private async handleCloseDialog( + action?: VFileInputReturnActionIdentifier | any, + ) { + // + if (isNullOrUndefined(this.context) || isNullOrEmptyString(this.context)) { + return; + } + + // + switch (this.context) { + // + case VFileInputModalContext.Modal: + await this.managerService.dialogService.modalController.dismiss(action); + break; + + // + case VFileInputModalContext.PopOver: + await this.managerService.dialogService.popoverController.dismiss( + action, + ); + break; + } + } + + /** + * Handle Set File Properties ... + * + * @returns + */ + private handleFVFilePropertiesSet() { + // + // Validate ... + if ( + isNullOrUndefined(this.vFile) || + isNullOrUndefined(this.fileProperties) + ) { + return; + } + + // + let isChanged = false; + Object.keys(this.fileProperties).forEach((key) => { + // + const value = (this.fileProperties as any)[key]; + if (!isNullOrUndefined(value)) { + // + isChanged = true; + (this.vFile as any)[key] = value; + } + }); + + // + if (isChanged) { + // + this.vFile.detectChanges(); + this.fileActionProvider.next({ action: VBaseDtoAction.Refresh }); + } + + // + this.detectChanges(); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\files\v-file-input\v-file-input.typings.ts + +import { VFileComponent } from '../v-file/v-file.component'; + +export enum VFileInputModalContext { + Modal = 'modal', + PopOver = 'pop_over', +} + +export type VFileInputModalContextIdentifier = VFileInputModalContext | string; + +export enum VFileInputCssVar { + DialogToolbarHeight = 'dialog-toolbar-height', +} + +export enum VFileInputReturnAction {} + +export type VFileInputReturnActionIdentifier = VFileInputReturnAction | string; + +export type VFileInputFilePrperties = Pick< + Partial, + | 'fileUploadAllowedExtensions' + | 'fileUploadColor' + | 'fileUploadDropAreaColor' + | 'fileUploadDropAreaIcon' + | 'fileUploadDropAreaMessage' + | 'fileUploadForegroundColor' + | 'fileUploadMaxAllowedFileSize' + | 'fileUploadMaxAllowedFiles' + | 'fileUploadMaxAllowedSize' + | 'fileUploadMinAllowedFileSize' + | 'fileUploadEmptyFileException' + | 'fileUploadNotAllowedFileException' + | 'fileUploadMaxAllowedSizeException' + | 'fileUploadMaxAllowedFilesException' + | 'fileUploadMaxAllowedFileSizeException' + | 'fileUploadMinAllowedFileSizeException' +>; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\files\v-files-presenter\v-files-presenter.component.html + + + + + + + + +
+ +
+ + +
+ + + +
+
+ + + +
+ + + + + + + + + + + + +
+
+ + + + + + + + + + +
+ +
+
+ + +As Medias + + +As Thumbnails + + + + + + + + + {{ getResourceValue(title) | async }} + + + + + + + + + + +
+ +
+
+ + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\files\v-files-presenter\v-files-presenter.component.scss + +.v-files-label { + // + &::after { + content: ":"; + } + + // + padding: { + top: 3px; + bottom: 3px; + } +} + +.v-component-wrapper { + padding: { + bottom: 6px; + } +} + +.v-present-type-wrapper { + padding: { + top: 6px; + bottom: 6px; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\files\v-files-presenter\v-files-presenter.component.ts + +import { + Input, + Output, + Component, + EventEmitter, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + isNumber, + toNumber, + isFunction, + nameofInstance, + XColorIdentifier, + isNullOrUndefined, + XResourceIdentifier, + isNullOrEmptyString, +} from 'x-framework-core'; +import { + XListItem, + XListAction, + XListActionModel, + XActionBarItemSlot, + XListItemOptionSelectedEvent, +} from 'x-framework-components'; +import { + VFilesPresenterAction, + isVFilesPresenterAction, + VFilesPresenterModelState, + VFilesPresenterActionModel, + VFilesPresenterPresentType, + isVFilesPresenterPeresentType, + VFilesPresenterPresentTypeIdentfier, +} from './v-files-presenter.typings'; +import { Observable, Subject } from 'rxjs'; +import { + VFileInputFilePrperties, + VFileInputModalContext, +} from '../v-file-input/v-file-input.typings'; +import { isXFileDto, XFileDto } from 'x-saherelm-api-sdk'; +import { VFilePresentType } from '../v-file/v-file.typings'; +import { VBaseInCardComponent } from '../../classes/v-base.component'; +import { VFileInputComponent } from '../v-file-input/v-file-input.component'; +import { XSimpleActionBarItem } from '../../typings/simple.actionbar.typings'; +import { defaultRemoveSlideOption } from '../../constants/slide.options.defaults'; +import { XSimpleListItemSlideOption } from '../../typings/simple.list-item.typings'; + +@Component({ + selector: 'v-files-presenter', + templateUrl: './v-files-presenter.component.html', + styleUrls: ['./v-files-presenter.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class VFilesPresenterComponent extends VBaseInCardComponent { + // + //#region Props ... + readonly FilesPresenterPresentTypes = Object.assign( + {}, + VFilesPresenterPresentType, + ); + readonly FilePresentTypes = Object.assign({}, VFilePresentType); + + @Input() + showLabel: boolean = true; + + @Input() + cssClass: Array = []; + + @Input() + title: XResourceIdentifier = this.AppResourceIDs.files; + + @Input() + titleColor: XColorIdentifier = this.ColorNames.Tertiary; + + @Input() + presentType: VFilesPresenterPresentTypeIdentfier = + VFilesPresenterPresentType.AsList; + + @Input() + fileBackgroundColor: XColorIdentifier = this.ColorNames.LightShade; + + @Input() + filesReader: Observable> = undefined; + + @Input() + fileProperties: VFileInputFilePrperties; + + @Input() + fileActionsProvider: ( + file: XFileDto | File, + ) => Array = (f) => { + return [ + // + // Remove ... + { + ...defaultRemoveSlideOption, + icon: this.IconNames.clear, + }, + ]; + }; + + @Input() + actionProvider = new Subject(); + + @Input() + canAttach: boolean = false; + actionBarItems: Array = []; + + /** + * Model State Changed Notifier ... + */ + @Output() + modelStateChanged = new EventEmitter(); + + // + files: Array = []; + originalFiles: Array = []; + listActionProvider = new Subject(); + filesListItems: Array> = []; + //#endregion + + // + //#region LifeCycles ... + async afterViewInit(): Promise { + super.afterViewInit(); + + // + await this.handleReadingFiles(); + this.handleRegisterActionProvider(); + } + + async onChange(changeKeys: string[]): Promise { + super.onChange(changeKeys); + + // + let isChanged = false; + let applyChanges = false; + + // + // Files Reader ... + isChanged = changeKeys.includes(nameofInstance(this, 'filesReader')); + if (isChanged) { + // + if (!isNullOrUndefined(this.filesReader)) { + await this.handleReadingFiles(); + } + } + + // + // Can Attach ... + isChanged = changeKeys.includes(nameofInstance(this, 'canAttach')); + if (isChanged) { + // + if (this.canAttach) { + // + applyChanges = true; + this.prepareActionBarItems(); + } + } + + // + // Tag Action Provider ... + isChanged = changeKeys.includes( + nameofInstance(this, 'fileActionsProvider'), + ); + if (isChanged) { + // + if ( + isNullOrUndefined(this.fileActionsProvider) || + !isFunction(this.fileActionsProvider) + ) { + // + applyChanges = true; + this.fileActionsProvider = (f) => { + return [ + // + // Remove ... + { + ...defaultRemoveSlideOption, + icon: this.IconNames.clear, + }, + ]; + }; + } + } + + // + // Present Type ... + isChanged = changeKeys.includes(nameofInstance(this, 'presentType')); + if (isChanged) { + // + if ( + isNullOrUndefined(this.presentType) && + !isVFilesPresenterPeresentType(this.presentType) + ) { + this.presentType = VFilesPresenterPresentType.AsList; + } + + // + await this.handlePresentTypeChanged(); + } + + // + // Action Provider ... + isChanged = changeKeys.includes(nameofInstance(this, 'actionProvider')); + if (isChanged) { + // + if (isNullOrUndefined(this.actionProvider)) { + // + applyChanges = true; + this.actionProvider = new Subject(); + } + + // + this.handleRegisterActionProvider(); + } + + // + // Title Color ... + isChanged = changeKeys.includes(nameofInstance(this, 'titleColor')); + if (isChanged) { + // + if (isNullOrEmptyString(this.titleColor)) { + // + applyChanges = true; + this.titleColor = this.ColorNames.Tertiary; + } + } + + // + // File Background Color ... + isChanged = changeKeys.includes( + nameofInstance(this, 'fileBackgroundColor'), + ); + if (isChanged) { + // + if (isNullOrEmptyString(this.fileBackgroundColor)) { + // + applyChanges = true; + this.fileBackgroundColor = this.ColorNames.LightShade; + } + } + + // + if (applyChanges) { + this.detectChanges(); + } + } + //#endregion + + // + //#region Providers ... + /** + * Check Can Present Files or not ... + * + * @returns + */ + canPresent() { + return ( + !isNullOrUndefined(this.presentType) && + isVFilesPresenterPeresentType(this.presentType) + ); + } + + /** + * Check two args are same or not ... + * + * @param arg1 + * @param arg2 + * @returns + */ + isSame(arg1: XFileDto | File, arg2: XFileDto | File) { + // + let result = false; + + // + // Validate ... + result = !isNullOrUndefined(arg1) || !isNullOrUndefined(arg2); + if (!result) { + return result; + } + + // + // Validate Type ... + const isArg1FileDto = isXFileDto(arg1); + const isArg2FileDto = isXFileDto(arg2); + result = + (isArg1FileDto && isArg2FileDto) || (!isArg1FileDto && !isArg2FileDto); + if (!result) { + return result; + } + + // + // Check Same ... + if (isArg1FileDto && isArg2FileDto) { + // + const arg1Dto = arg1 as XFileDto; + const arg2Dto = arg2 as XFileDto; + + // + result = arg1Dto.id === arg2Dto.id; + } else if (!isArg1FileDto && !isArg2FileDto) { + // + const arg1File = arg1 as File; + const arg2File = arg2 as File; + + // + result = arg1File === arg2File; + } else { + result = false; + } + + // + return result; + } + //#endregion + + // + //#region Handlers ... + /** + * Reading Files List ... + * + * @returns + */ + async handleReadingFiles() { + // + if (isNullOrUndefined(this.filesReader)) { + return; + } + + // + const files = await this.getValueAsync(this.filesReader); + if (!isNullOrUndefined(files) && this.hasChild(files)) { + // + this.files = [...files]; + this.originalFiles = [...files]; + + // + await this.handlePresentTypeChanged(); + } + } + + /** + * Handle Present Type Changed ... + * + * @returns + */ + async handlePresentTypeChanged() { + // + this.filesListItems = []; + + // + if ( + !this.hasChild(this.files) || + isNullOrUndefined(this.presentType) || + !isVFilesPresenterPeresentType(this.presentType) || + this.presentType === this.FilesPresenterPresentTypes.AsNone + ) { + return; + } + + // + if (this.presentType === this.FilesPresenterPresentTypes.AsList) { + // + this.filesListItems = [ + ...this.files.map((t) => { + return { + data: t, + slideOptions: this.fileActionsProvider(t), + } as XListItem; + }), + ]; + + // + this.detectChanges(); + this.listActionProvider.next({ action: XListAction.Refresh }); + } + } + + /** + * Handle Actions ... + * + * @param tag + * @param action + */ + handleActionClicked( + file: XFileDto | File, + action: XSimpleListItemSlideOption, + ) { + // + // Validate ... + if ( + this.loading || + this.uiDisabled || + isNullOrUndefined(file) || + isNullOrUndefined(action) || + isNullOrEmptyString(action.id) + ) { + return; + } + + // + // Remove ... + if (action.id === defaultRemoveSlideOption.id) { + // + let idx = + !isNullOrUndefined(this.files) && this.hasChild(this.files) + ? this.files.findIndex((f) => this.isSame(file, f)) + : -1; + if (idx >= 0) { + // + this.files = [...this.files.filter((f) => !this.isSame(file, f))]; + + // + // Checking in List Items ... + let idx = this.filesListItems.findIndex((fli) => + this.isSame(file, fli.data), + ); + if (idx >= 0) { + // + this.filesListItems = [ + ...this.filesListItems.filter( + (fli) => !this.isSame(file, fli.data), + ), + ]; + + // + this.listActionProvider.next({ action: XListAction.Refresh }); + setTimeout( + () => this.listActionProvider.next({ action: XListAction.Refresh }), + 200, + ); + setTimeout( + () => this.listActionProvider.next({ action: XListAction.Refresh }), + 500, + ); + } + + // + this.detectChanges(); + this.handlePrepareFilesState(); + } + } + } + + /** + * Prepare and Notify Files State ... + * + * @returns + */ + handlePrepareFilesState() { + // + const hasOriginals = + !isNullOrUndefined(this.originalFiles) && + this.hasChild(this.originalFiles); + + // + // Detecting News ... + let hasNews = false; + let news: Array = []; + if (this.hasChild(this.files)) { + // + news = this.files.filter( + (f) => + !this.hasChild(this.originalFiles) || + (this.hasChild(this.originalFiles) && + this.originalFiles.findIndex((of) => { + // + if (isXFileDto(f) && isXFileDto(of)) { + return of.id === f.id; + } else if (f instanceof File && of instanceof File) { + return (of as File).name === f.name; + } else { + return false; + } + }) < 0), + ); + + // + hasNews = this.hasChild(news); + } + + // + // Detecting Removeds ... + let hasRemoveds = false; + let removeds: Array = []; + if (hasOriginals) { + // + removeds = this.originalFiles.filter( + (of) => + this.files.findIndex((f) => { + // + if (isXFileDto(f) && isXFileDto(of)) { + return f.id === of.id; + } else { + return false; + } + }) < 0, + ); + + // + hasRemoveds = this.hasChild(removeds); + } + + // + let result: VFilesPresenterModelState = { + hasNews, + hasRemoveds, + hasOriginals, + // + news, + removeds, + originals: [...this.originalFiles], + }; + + // + this.modelStateChanged.emit(result); + this.listActionProvider.next({ action: XListAction.Refresh }); + + // + return result; + } + + /** + * Handle List Item Options Clicked ... + * + * @param event + * @returns + */ + handleListItemsOptionSelected(event: XListItemOptionSelectedEvent) { + // + if (isNullOrUndefined(event)) { + return; + } + + // + if (event.id === defaultRemoveSlideOption.id) { + this.handleActionClicked(event.data.data, defaultRemoveSlideOption); + } + } + + /** + * Handle List Items Order Changed ... + */ + handleListItemOrdersChanged() { + // + const orderFiles = this.filesListItems.map((fli) => fli.data); + this.files = [...orderFiles]; + this.handlePrepareFilesState(); + } + //#endregion + + // + //#region Private ... + /** + * Preparing Action Bar ... + */ + private prepareActionBarItems() { + // + this.actionBarItems = [ + // + // Attach ... + { + index: 0, + id: 'Add', + onlyIcon: true, + icon: this.IconNames.add, + title: this.ResourceIDs.add, + slot: XActionBarItemSlot.START, + iconColor: this.ColorNames.SuccessShade, + handler: async () => { + // + let modal = await this.managerService.dialogService.presentModal({ + autoHeight: true, + fullScreen: false, + backdropDismiss: false, + component: VFileInputComponent, + componentProps: { + fileProperties: this.fileProperties, + context: VFileInputModalContext.Modal, + originalFiles: [...this.originalFiles], + toolbarTitle: this.AppResourceIDs.file, + presentType: VFilePresentType.AsFileUpload, + }, + }); + + /** + * wait for Model Result ... + */ + const data = await modal.onDidDismiss(); + modal.remove(); + modal.dismiss(); + + /** + * Handle Retrieve Data ... + */ + if (!isNullOrUndefined(data.data)) { + this.handleFileInputDialogResult(data.data); + } + }, + disabled: this.loading || this.uiDisabled || !this.canAttach, + } as XSimpleActionBarItem, + ]; + } + + /** + * handle Add Files Dialog Result ... + * + * @param result + */ + private handleFileInputDialogResult(result: Array) { + // + this.files.push(...result); + + // + if (this.presentType === this.FilesPresenterPresentTypes.AsList) { + // + this.filesListItems.push( + ...result.map((f) => { + // + return { + data: f, + slideOptions: this.fileActionsProvider(f), + } as XListItem; + }), + ); + + // + this.listActionProvider.next({ action: XListAction.Refresh }); + setTimeout( + () => this.listActionProvider.next({ action: XListAction.Refresh }), + 200, + ); + setTimeout( + () => this.listActionProvider.next({ action: XListAction.Refresh }), + 500, + ); + } + + // + this.detectChanges(); + this.handlePrepareFilesState(); + } + + // + //#region Action Provider ... + /** + * Handle Action Provider Registration ... + */ + private handleRegisterActionProvider() { + // + if (isNullOrUndefined(this.actionProvider)) { + return; + } + + // + this.applyTakeUntilWrapper(this.actionProvider.asObservable()).subscribe( + async (event) => { + // + // Validate ... + if ( + isNullOrUndefined(event) || + isNullOrEmptyString(event.action) || + !isVFilesPresenterAction(event.action) + ) { + return; + } + + // + await this.handleAction(event.action, event.payload); + }, + ); + } + + /** + * Send Specified Action through Action provider ... + * + * @param action + * @param payload + * @returns + */ + private sendAction(action: VFilesPresenterAction, payload?: any) { + // + if ( + isNullOrUndefined(this.actionProvider) || + isNullOrEmptyString(action) || + !isVFilesPresenterAction(action) + ) { + return; + } + + // + this.actionProvider.next({ action, payload }); + } + + /** + * Handle Specified Actions ... + * + * @param action + * @param payload + */ + private async handleAction(action: VFilesPresenterAction, payload?: any) { + // + // Refresh ... + if (action === VFilesPresenterAction.Refresh) { + // + if (isNullOrUndefined(payload)) { + this.detectChanges(); + } else if (!isNullOrUndefined(payload) && isNumber(payload)) { + // + this.detectChanges(); + setTimeout(() => this.detectChanges(), toNumber(payload)); + } + } + // + // Reset ... + else if (action === VFilesPresenterAction.Reset) { + // + this.files = [...this.originalFiles]; + + // + this.filesListItems = [ + ...this.originalFiles.map((of) => { + return { + data: of, + slideOptions: this.fileActionsProvider(of), + } as XListItem; + }), + ]; + + // + this.detectChanges(); + this.listActionProvider.next({ action: XListAction.Refresh }); + setTimeout( + () => this.listActionProvider.next({ action: XListAction.Refresh }), + 500, + ); + this.handlePrepareFilesState(); + } + // + // ReState ... + else if (action === VFilesPresenterAction.ReState) { + this.handlePrepareFilesState(); + } + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\files\v-files-presenter\v-files-presenter.typings.ts + +import { XFileDto } from 'x-saherelm-api-sdk'; +import { XBaseActionModel } from 'x-framework-components'; +import { isValueInEnum } from '../../tools/v-base.tools'; + +// +//#region Present Type ... +export enum VFilesPresenterPresentType { + AsNone = 'as_none', + AsList = 'as_list', + AsMedias = 'as_medias', + AsThumbnails = 'as_thumbnails', +} + +export type VFilesPresenterPresentTypeIdentfier = + | VFilesPresenterPresentType + | string; + +export function isVFilesPresenterPeresentType(value: string) { + return isValueInEnum(value, Object.assign({}, VFilesPresenterPresentType)); +} +//#endregion + +// +//#region Action Provider ... +export enum VFilesPresenterAction { + Reset = 'Reset', + Refresh = 'Refresh', + ReState = 'ReState', +} + +export type VFilesPresenterActionIdentifier = VFilesPresenterAction | string; + +export interface VFilesPresenterActionModel extends XBaseActionModel {} + +export function isVFilesPresenterAction(value: string) { + return isValueInEnum(value, Object.assign({}, VFilesPresenterAction)); +} +//#endregion + +// +//#region Files State ... +/** + * an Interface to Notify Files Changed ... + */ +export interface VFilesPresenterModelState { + hasOriginals: boolean; + hasNews: boolean; + hasRemoveds: boolean; + originals?: Array; + news?: Array; + removeds?: Array; +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\files\v-media\v-media.component.html + + + + + + + + +
+ +
+ + +
+ + + +
+
+ + + +
+ + +
+
+ + + + + + + + + + +
+ + + + + + + + + + + + {{ info }} + + + +
+
+ + + +
+ + + + + + + + + + +
+ {{ resourceProvider(thumbnailTitle) }} +
+
+ + + + + +
+ + + + + +
+
+
+
+
+
+ + +Media As Media ... + + + + +
+ + + + + + + + +
+
+ + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\files\v-media\v-media.component.scss + +:host { + --thumbnail-size: 32px; +} + +.v-media-info { + font-size: small; + width: calc(90%); + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +.v-media-thumbnail, +.v-media-thumbnail-present { + width: var(--thumbnail-size); + height: var(--thumbnail-size); +} + +.v-media-card-present { + padding: 8px; + --thumbnail-size: 128px; +} + +.v-media-action, +.v-media-card-info, +.v-media-card-actions { + padding: 3px; +} + +:host ::ng-deep .v-media-item-info-slot { + // + width: 100%; + height: 100%; + + // + padding-left: 3px; + padding-right: 3px; + + // + display: flex; + align-items: center; +} + +:host ::ng-deep .v-media-thumbnail { + .x-icon { + --size: var(--thumbnail-size) !important; + } +} + +:host ::ng-deep .v-media-card-top-slot { + .v-media-thumbnail-present { + margin-left: auto; + margin-right: auto; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\files\v-media\v-media.component.ts + +import { + isImage, + isVideo, + isAudio, + XFileBlob, + isValidURL, + isDocument, + isFunction, + isXFileBlob, + nameofInstance, + isNullOrUndefined, + isNullOrEmptyString, + XResourceIdentifier, + XColorIdentifier, +} from 'x-framework-core'; +import { + MediaType, + toMediaType, + MediaSourceType, + isMediaTypePassed, + VMediaPresentType, + getMediaSourceType, + isVMediaPresentType, + MediaSourceInputType, + isVMediaPresentTypePassed, + VMediaPresentTypeIdentifier, +} from './v-media.typings'; +import { + XSlotName, + XIconNames, + XSlotLayout, + XButtonType, + XIconIdentifier, +} from 'x-framework-components'; +import { XFileDto } from 'x-saherelm-api-sdk'; +import { Component, Input } from '@angular/core'; +import { SafeUrl } from '@angular/platform-browser'; +import { FileLocalizationsResourceIDs } from '../v-file.configs'; +import { VBaseInCardComponent } from '../../classes/v-base.component'; +import { XSimpleListItemSlideOption } from '../../typings/simple.list-item.typings'; + +@Component({ + selector: 'v-media', + templateUrl: './v-media.component.html', + styleUrls: ['./v-media.component.scss'], +}) +export class VMediaComponent extends VBaseInCardComponent { + // + //#region Props ... + + // + readonly SlotNames = Object.assign({}, XSlotName); + readonly ButtonTypes = Object.assign({}, XButtonType); + readonly SlotLayouts = Object.assign({}, XSlotLayout); + readonly MediaPresentTypes = Object.assign({}, VMediaPresentType); + + /** + * Provided Source for Presenting ... + */ + @Input() + src: MediaSourceInputType = undefined; + + /** + * CSS Classes names to Attach ... + */ + @Input() + cssClass: Array = []; + + /** + * Force to Use Thumbnails Paths if Provided XFileDto source ... + */ + @Input() + useThumbnailsIfProvided: boolean = true; + + /** + * Specify How to Represent Component ... + */ + @Input() + presentType: VMediaPresentTypeIdentifier = VMediaPresentType.AsCard; + + /** + * Thumbnail Provided Icon's Color ... + */ + @Input() + thumbnailIconColor: XColorIdentifier = this.ColorNames.TertiaryShade; + + /** + * Provides Thumbnails Icon for Use if necessary ... + * + * @param type + * @returns + */ + @Input() + thumbnailIconProvider: (type: MediaType) => XIconIdentifier = (type) => { + // + let result: XIconIdentifier = undefined; + + // + if (isMediaTypePassed(type)) { + // + switch (type) { + // + case MediaType.Image: + result = XIconNames.image; + break; + + // + case MediaType.Video: + result = XIconNames.video; + break; + + // + case MediaType.Audio: + result = XIconNames.audio; + break; + + // + case MediaType.Document: + result = XIconNames.document; + break; + } + } + + // + return result; + }; + + /** + * Provides Thumbnail Title for Use if necessary ... + * @param type + * @returns + */ + @Input() + thumbnailTitleProvider: (type: MediaType) => XResourceIdentifier = (type) => { + // + let result: XResourceIdentifier = undefined; + + // + if (isMediaTypePassed(type)) { + // + switch (type) { + // + case MediaType.Image: + result = FileLocalizationsResourceIDs.image; + break; + + // + case MediaType.Video: + result = FileLocalizationsResourceIDs.video; + break; + + // + case MediaType.Audio: + result = FileLocalizationsResourceIDs.audio; + break; + + // + case MediaType.Document: + result = FileLocalizationsResourceIDs.document; + break; + } + } + + // + return result; + }; + + /** + * Provided Actions which Used only in Card Present Type ... + */ + @Input() + providedActions: Array = []; + + // + // Required Fields For Describe Media ... + + // + url: string = undefined; + info: string = undefined; + type: MediaType = undefined; + safeUrl: SafeUrl = undefined; + inputType: MediaSourceType = undefined; + + // + thumbnailUrl: string = undefined; + thumbnailSafeUrl: SafeUrl = undefined; + thumbnailIcon: XIconIdentifier = undefined; + thumbnailTitle: XResourceIdentifier = undefined; + //#endregion + + // + //#region LifeCycles ... + afterViewInit(): void { + super.afterViewInit(); + + // + this.handlePrepareSource(); + } + + onChange(changeKeys: string[]): void { + super.onChange(changeKeys); + + // + let isChanged = false; + let applyChnages = false; + + // + // Providers Change ... + isChanged = + changeKeys.includes(nameofInstance(this, 'thumbnailIconProvider')) || + changeKeys.includes(nameofInstance(this, 'thumbnailTitleProvider')); + if (isChanged) { + // + // Icon ... + if ( + isNullOrUndefined(this.thumbnailIconProvider) || + !isFunction(this.thumbnailIconProvider) + ) { + // + this.thumbnailIconProvider = (type) => { + // + let result: XIconIdentifier = undefined; + + // + if (isMediaTypePassed(type)) { + // + switch (type) { + // + case MediaType.Image: + result = XIconNames.image; + break; + + // + case MediaType.Video: + result = XIconNames.video; + break; + + // + case MediaType.Audio: + result = XIconNames.audio; + break; + + // + case MediaType.Document: + result = XIconNames.document; + break; + } + } + + // + return result; + }; + } + + // + // Title ... + if ( + isNullOrUndefined(this.thumbnailTitleProvider) || + !isFunction(this.thumbnailTitleProvider) + ) { + // + this.thumbnailTitleProvider = (type) => { + // + let result: XResourceIdentifier = undefined; + + // + if (isMediaTypePassed(type)) { + // + switch (type) { + // + case MediaType.Image: + result = FileLocalizationsResourceIDs.image; + break; + + // + case MediaType.Video: + result = FileLocalizationsResourceIDs.video; + break; + + // + case MediaType.Audio: + result = FileLocalizationsResourceIDs.audio; + break; + + // + case MediaType.Document: + result = FileLocalizationsResourceIDs.document; + break; + } + } + + // + return result; + }; + } + + // + applyChnages = true; + this.thumbnailIcon = this.thumbnailIconProvider(this.type); + this.thumbnailTitle = this.thumbnailTitleProvider(this.type); + } + + // + // Source Change ... + isChanged = changeKeys.includes(nameofInstance(this, 'src')); + if (isChanged) { + this.handlePrepareSource(); + } + + // + // PresentType Change ... + isChanged = changeKeys.includes(nameofInstance(this, 'presentType')); + if (isChanged) { + // + if ( + isNullOrUndefined(this.presentType) || + !isVMediaPresentType(this.presentType) + ) { + // + applyChnages = true; + this.presentType = VMediaPresentType.AsCard; + } + + // + this.setAttribute(nameofInstance(this, 'presentType'), this.presentType); + } + + // + // Provided Actions ... + isChanged = changeKeys.includes(nameofInstance(this, 'providedActions')); + if (isChanged) { + // + if (isNullOrUndefined(this.providedActions)) { + // + applyChnages = true; + this.providedActions = []; + } + } + + // + // Thumbnail Icon Color ... + isChanged = changeKeys.includes(nameofInstance(this, 'thumbnailIconColor')); + if (isChanged) { + // + if (isNullOrEmptyString(this.thumbnailIconColor)) { + // + applyChnages = true; + this.thumbnailIconColor = this.ColorNames.TertiaryShade; + } + } + + // + if (applyChnages) { + this.detectChanges(); + } + } + //#endregion + + // + //#region Providers ... + /** + * Check Present Type is Allow Component Presenting ... + * + * @returns + */ + canPresent() { + return isVMediaPresentTypePassed(this.presentType); + } + + /** + * Check Conditions For Thumbnail Present Type ... + */ + canShowAsThumbnail() { + return ( + !isNullOrUndefined(this.thumbnailIcon) && + !isNullOrUndefined(this.thumbnailTitle) + ); + } + + /** + * Check Provided Actions ... + */ + hasActions() { + return ( + !isNullOrUndefined(this.providedActions) && + this.hasChild(this.providedActions) + ); + } + //#endregion + + // + //#region Handlers ... + /** + * Prepare Source and Parse data ... + * + * @returns + */ + handlePrepareSource() { + // + // Validate ... + if (isNullOrUndefined(this.src)) { + return; + } + + // + // Detect Input Type ... + this.inputType = getMediaSourceType(this.src); + switch (this.inputType) { + // + case MediaSourceType.Url: + this.handlePrepareUrlSource(); + break; + + // + case MediaSourceType.File: + this.handlePrepareFileSource(); + break; + + // + case MediaSourceType.Blob: + case MediaSourceType.XFileBlob: + this.handlePrepareBlobSource(); + break; + + // + case MediaSourceType.XFileDto: + this.handlePrepareDtoSource(); + break; + } + + // + // Preparing Thumbnail Info ... + this.thumbnailIcon = this.thumbnailIconProvider(this.type); + this.thumbnailTitle = this.thumbnailTitleProvider(this.type); + + // + // Detect Thumbnail URL ... + if (this.type === MediaType.Image) { + // + if (this.inputType === MediaSourceType.XFileDto) { + // + const value = this.src as XFileDto; + this.thumbnailUrl = !isNullOrEmptyString(value.thumbPath) + ? value.thumbPath + : value.path; + } else { + this.thumbnailUrl = this.url; + } + + // + if (isNullOrUndefined(this.thumbnailUrl)) { + this.thumbnailUrl = undefined; + } + + // + if (!isNullOrUndefined(this.safeUrl) && isNullOrEmptyString(this.url)) { + // + this.thumbnailUrl = undefined; + this.thumbnailSafeUrl = this.safeUrl; + } + } + } + + /** + * Calling Action Handler ... + * + * @param action + * @returns + */ + async handleCallingActionHandler(action: XSimpleListItemSlideOption) { + // + // Validate ... + if (isNullOrUndefined(action)) { + return; + } + + // + // Validate Handler Exists ... + if (isNullOrUndefined(action.handler) && !isFunction(action.handler)) { + return; + } + + // + // Calling Action Handler ... + await this.getValuesAsync(action.handler(this.src)); + } + //#endregion + + // + //#region Private ... + /** + * Set Thumbnail Size ... + * + * @param size + */ + private handleSetThumbnailSize(size: number) { + // + if (size < 32) { + size = 32; + } + + // + const name = 'thumbnail-size'; + const value = `${size}px;`; + + // + this.setCssVar(name, value); + this.setCssVar(name, value, document.documentElement); + } + + /** + * Extract required data when user provides string as source ... + */ + private handlePrepareUrlSource() { + // + let value = this.src as string; + + // + // Detecting Type ... + this.detectType(value); + + // + // Assign URL if has a Valid Url ... + this.url = value; + + // + if (isValidURL(value)) { + // + this.info = value; + } + + // + this.safeUrl = undefined; + this.thumbnailSafeUrl = undefined; + } + + /** + * Extract required data when user provides XFileDto as source ... + */ + private handlePrepareDtoSource() { + // + let value = this.src as XFileDto; + + // + // Validate ... + if (isNullOrUndefined(value) || isNullOrEmptyString(value.id)) { + return; + } + + // + // Detecting Type ... + this.detectType(value); + + // + // Check Can User Thumbnail ... + if (this.useThumbnailsIfProvided && !isNullOrEmptyString(value.thumbPath)) { + this.url = value.thumbPath; + } else if (!isNullOrEmptyString(value.path)) { + this.url = value.path; + } + + // + this.safeUrl = undefined; + this.thumbnailSafeUrl = undefined; + this.info = value.name || value.path || value.thumb || value.thumbPath; + } + + /** + * Extract required data when user provides Blob as source ... + */ + private handlePrepareBlobSource() { + // + // Blob ... + if (this.src instanceof Blob) { + // + let value = this.src as Blob; + this.url = undefined; + this.detectType(value); + this.safeUrl = + this.sharedService.managerService.securityService.toSafeURL( + URL.createObjectURL(value), + ); + } else if (isXFileBlob(this.src)) { + // + let value = this.src as XFileBlob; + this.detectType(value); + this.url = value.name; + this.info = value.name; + this.safeUrl = undefined; + } + } + + /** + * Extract required data when user provides File as source ... + */ + private handlePrepareFileSource() { + // + let value = this.src as File; + + // + this.detectType(value); + this.url = undefined; + this.safeUrl = this.sharedService.managerService.securityService.toSafeURL( + URL.createObjectURL(value), + ); + this.info = value.name; + } + + /** + * Detect Media Type based on Identifier ... + * + * @param identifier + * @returns + */ + private detectType(identifier: XFileDto | File | Blob | XFileBlob | string) { + // + let result: MediaType = MediaType.None; + + // + // Validate ... + if (!isNullOrUndefined(identifier)) { + // + if (this.inputType === MediaSourceType.XFileDto) { + this.type = toMediaType((identifier as XFileDto).type); + } else { + // + if (isImage(identifier as any)) { + this.type = MediaType.Image; + } else if (isVideo(identifier as any)) { + this.type = MediaType.Video; + } else if (isAudio(identifier as any)) { + this.type = MediaType.Audio; + } else if (isDocument(identifier as any)) { + this.type = MediaType.Document; + } + } + } + + // + return result; + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\files\v-media\v-media.config.ts + +// +//#region Localization Configuration ... +export enum MediaResourceIDs { + media = 'media', + medias = 'medias', +} + +// +//#region Single Resources ... +// +//#region media ... +export const media_fa = { + id: 'media', + value: 'رسانه', +}; + +export const media_en = { + id: 'media', + value: 'Media', +}; +//#endregion + +// +//#region medias ... +export const medias_fa = { + id: 'medias', + value: 'رسانه ها', +}; + +export const medias_en = { + id: 'medias', + value: 'Medias', +}; +//#endregion +//#endregion + +// +export const MediaLocalizationResourceIDs = Object.assign(MediaResourceIDs, {}); + +// +export const MediaLocalizationResources = { + media: { + fa: { ...media_fa }, + en: { ...media_en }, + }, + medias: { + fa: { ...medias_fa }, + en: { ...medias_en }, + }, +}; +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\files\v-media\v-media.typings.ts + +import { + isString, + toXString, + XFileBlob, + XFileType, + isXFileBlob, + isNullOrUndefined, + isNullOrEmptyString, +} from 'x-framework-core'; +import { isXFileDto, XFileDto } from 'x-saherelm-api-sdk'; +import { XBaseActionModel } from 'x-framework-components'; +import { isValueInEnum } from 'src/app/views/tools/v-base.tools'; + +// +//#region Media Type ... +export enum MediaType { + None = 'none', + Image = 'image', + Video = 'video', + Audio = 'audio', + Document = 'document', +} + +export function isMediaType(value: string) { + return isValueInEnum(value, Object.assign({}, MediaType)); +} + +export function isMediaTypePassed(value: string) { + return isMediaType(value) && value !== MediaType.None; +} + +export function toMediaType(type: XFileType | string) { + // + let result = MediaType.None; + + // + if (!isNullOrUndefined(type)) { + // + if (type === XFileType.Audio) { + result = MediaType.Audio; + } else if (type === XFileType.Video) { + result = MediaType.Video; + } else if (type === XFileType.Document) { + result = MediaType.Document; + } else if ( + type === XFileType.Image || + type === XFileType.CoverImage || + type === XFileType.ProfileImage + ) { + result = MediaType.Image; + } + } + + // + return result; +} +//#endregion + +// +//#region Present Type ... +export enum VMediaPresentType { + AsNone = 'as_none', + AsCard = 'as_card', + AsItem = 'as_item', + AsMedia = 'as_media', + AsThumbnail = 'as_thumbnail', +} + +export type VMediaPresentTypeIdentifier = VMediaPresentType | string; + +export function isVMediaPresentType(value: string) { + return isValueInEnum(value, Object.assign({}, VMediaPresentType)); +} + +export function isVMediaPresentTypePassed(value: string) { + return isVMediaPresentType(value) && value !== VMediaPresentType.AsNone; +} +//#endregion + +// +//#region Media Source Type ... +export enum MediaSourceType { + Url = 'Url', + None = 'none', + File = 'File', + Blob = 'Blob', + XFileDto = 'XFileDto', + XFileBlob = 'XFileBlob', +} + +export type MediaSourceTypeIdentifier = MediaSourceType | string; + +export type MediaSourceInputType = XFileDto | XFileBlob | File | Blob | string; + +export function isMediaSourceType(value: string) { + return isValueInEnum(value, Object.assign({}, MediaSourceType)); +} + +export function getMediaSourceType(input: MediaSourceInputType) { + // + let result: MediaSourceType = MediaSourceType.None; + + // + let isValid = false; + + // + // Validate input ... + isValid = !isNullOrUndefined(input); + if (!isValid) { + return result; + } + + // + // Check XFile Dto ... + isValid = isXFileDto(input); + if (isValid) { + // + result = MediaSourceType.XFileDto; + return result; + } + + // + // Check File ... + isValid = input instanceof File; + if (isValid) { + // + result = MediaSourceType.File; + return result; + } + + // + // Check XFileBlob ... + isValid = isXFileBlob(input); + if (isValid) { + // + result = MediaSourceType.XFileBlob; + return result; + } + + // + // Check Blob ... + isValid = input instanceof Blob; + if (isValid) { + // + result = MediaSourceType.Blob; + return result; + } + + // + // Check Url ... + isValid = isString(input) && !isNullOrEmptyString(toXString(input)); + if (isValid) { + // + result = MediaSourceType.Url; + return result; + } + + // + return result; +} +//#endregion + +// +//#region Action Provider ... +export enum VMediaAction { + Refresh = 'Refresh', +} + +export type VMediaActionIdentifier = VMediaAction | string; + +export interface VMediaActionModel extends XBaseActionModel {} + +export function isVMediaAction(value: string) { + return isValueInEnum(value, Object.assign({}, VMediaAction)); +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\string\v-string.tools.ts + +import { XLocale } from 'x-framework-core'; +import { AppResourceIDs } from 'src/app/config/localization.config'; + +export function getLanguageResourceID(locale: XLocale | string) { + // + if (locale === 'en-US') { + return AppResourceIDs.english; + } else if (locale === 'fa-IR') { + return AppResourceIDs.persian; + } + + // + return locale; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\string\v-string.typings.ts + +export enum StringTasks { + Add = 'add_string_resource', + Update = 'update_string_resource', + AddResource = 'add_resource_string', +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\string\v-locale\v-locale.component.html + + + + + + + + + + {{ item.data }} + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\string\v-locale\v-locale.component.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\string\v-locale\v-locale.component.ts + +import { + Component, + ViewChild, + TemplateRef, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + XLocaleResourceDto, + defaultLocaleResourceDto, + prepareLocaleResourceDtoFields, +} from 'x-saherelm-api-sdk'; +import { + nthItems, + getFieldNames, + XExceptionIDs, + XStandardType, + toNormalString, + setPropertyValue, + isNullOrUndefined, + isNullOrEmptyString, +} from 'x-framework-core'; +import { + XListItem, + XFormControlType, + XFormControlAppearance, +} from 'x-framework-components'; +import { map } from 'rxjs/operators'; +import { Validators } from '@angular/forms'; +import { + VBaseDtoComponent, + VModelFieldDescriptor, + VModelValidationResult, +} from '../../v-dto/v-dto.typings'; +import { getLanguageResourceID } from '../v-string.tools'; +import { XSimpleFormControlAutoCompleteConfig } from '../../typings/simple.form.typings'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'v-locale', + templateUrl: './v-locale.component.html', + styleUrls: ['./v-locale.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class VLocaleComponent extends VBaseDtoComponent { + // + //#region Props ... + // + // User Push Conection ID ... + connectionId: string; + availableLanguages: Array = []; + + // + readonly FormControlTypes = Object.assign({}, XFormControlType); + readonly FormControlAppearances = Object.assign({}, XFormControlAppearance); + readonly title = this.resourceProvider(this.AppResourceIDs.string_resource); + + // + defaultModel: XLocaleResourceDto = { + ...defaultLocaleResourceDto, + }; + readonly FieldNames = getFieldNames(this.defaultModel); + + // + languageValue: string = ''; + languageAutoCompleteQuery = ''; + @ViewChild('languageAutoCompleteRef') + languageAutoCompleteRef!: TemplateRef; + isLanguageAutoCompleteOpened: boolean = false; + languageAutoCompleteItems: XListItem[]; + languageAutoCompleteConfig: XSimpleFormControlAutoCompleteConfig< + XListItem + >; + + /** + * Describe Model ... + */ + defaultFieldDescriptors: VModelFieldDescriptor[] = [ + // + // Language ... + { + hide: false, + hideLabel: false, + key: this.FieldNames.language, + label: this.ResourceIDs.language, + value: this.defaultModel.language, + type: this.ModelFieldTypes.String, + valueProvider: (value) => { + return this.resourceProvider(getLanguageResourceID(value)); + }, + allowFieldForm: true, + formControlConfig: { + index: 0, + propName: this.FieldNames.language, + type: { + type: this.FormControlTypes.Text, + config: {}, + }, + validators: { + validators: [Validators.required], + }, + eventHandlers: { + onBlured: (model) => {}, + onFocused: (model) => {}, + valueChanged: (event) => { + // + if (this.languageValue === event.value) { + return; + } + + // + this.handleFilterAutoCompleteLanguage(event.value); + }, + }, + appearance: { + hidden: false, + disabled: false, + placeholder: this.ResourceIDs.language, + appearance: this.FormControlAppearances.Outline, + icons: { + prefix: { + applyStateColor: true, + name: this.IconNames.language, + color: this.ColorNames.Tertiary, + }, + }, + autoComplete: {}, + }, + }, + } as VModelFieldDescriptor, + // + // Value ... + { + hide: false, + hideLabel: false, + key: this.FieldNames.value, + value: this.defaultModel.value, + type: this.ModelFieldTypes.String, + label: this.ResourceIDs.translation, + valueProvider: (value) => { + return this.applyLocale(value); + }, + allowFieldForm: true, + formControlConfig: { + index: 1, + propName: this.FieldNames.value, + type: { + type: this.FormControlTypes.Text, + config: {}, + }, + validators: { + validators: [Validators.required], + }, + eventHandlers: {}, + appearance: { + hidden: false, + disabled: false, + placeholder: this.ResourceIDs.translation, + appearance: this.FormControlAppearances.Outline, + icons: { + prefix: { + applyStateColor: true, + name: this.IconNames.label, + color: this.ColorNames.Tertiary, + }, + }, + }, + }, + } as VModelFieldDescriptor, + ]; + //#endregion + + // + //#region LifeCycles ... + async onInit(): Promise { + super.onInit(); + } + + async onChange(changeKeys: string[]): Promise { + super.onChange(changeKeys); + + // + var isChanged = false; + + // + await this.handleLanguageAutoComplete(); + } + + /** + * Register Required Instance Subscriptions ... + */ + registerInstanceHandlers(): void { + super.registerInstanceHandlers(); + } + //#endregion + + // + //#region UI Handlers ... + //#endregion + + // + //#region Abstraction Implementation ... + /** + * Validate Model for Actions ... + * + * @param model + */ + validate( + model?: Partial, + ): VModelValidationResult { + // + let result: VModelValidationResult; + + // + let isValid = false; + const validationErrors: string[] = []; + + // + model = { + ...this.model, + ...model, + }; + const normalized = this.normalize(model); + + // + // Handle Validation ... + isValid = + !isNullOrUndefined(normalized) && !this.isSameAsDefault(normalized); + if (!isValid) { + // + validationErrors.push( + this.getExceptionMessage(XExceptionIDs.InvalidArgs), + ); + } else { + // + // Check Other Validations ... + + // + // Language ... + isValid = !isNullOrEmptyString(normalized.language); + if (!isValid) { + // + validationErrors.push( + `${this.resourceProvider( + this.ResourceIDs.language, + )}: ${this.getExceptionMessage(XExceptionIDs.InvalidArgs)}`, + ); + } + + // + // Translated Value ... + isValid = !isNullOrEmptyString(normalized.value); + if (!isValid) { + // + validationErrors.push( + `${this.resourceProvider( + this.ResourceIDs.translation, + )}: ${this.getExceptionMessage(XExceptionIDs.InvalidArgs)}`, + ); + } + } + + // + // Handle Result ... + + // + isValid = !this.hasChild(validationErrors); + + // + const isValidForAdd = false; + const isValidForUpdate = false; + + // + result = { + isValid, + normalized, + isValidForAdd, + isValidForUpdate, + validationErrors, + }; + + // + return result; + } + + /** + * Prepare Model ... + * + * @param model + */ + prepare(model?: XLocaleResourceDto): XLocaleResourceDto { + return prepareLocaleResourceDtoFields(model); + } + //#endregion + + // + //#region Overrides ... + async onFieldFormPresent(key: string) { + // + if (key === this.FieldNames.language) { + await this.handleLanguageAutoComplete(); + } + } + //#endregion + + // + //#region AutoComplete ... + /** + * Retriev Available Languages ... + * + * @returns + */ + async getAvailableLanguages() { + // + let has = + !this.hasChild(this.availableLanguages) && + (this.presentType == this.PresentTypes.AsForm || + (this.presentType == this.PresentTypes.AsView && + !isNullOrUndefined(this.inFormFieldsFormConfigs) && + this.hasChild(this.inFormFieldsFormConfigs) && + this.isFormView(this.FieldNames.language))); + if (!has) { + return; + } + + // + const request = + this.sharedService.saherElmAPIService.strings.stringProviderService + .languages() + .pipe( + map((languages) => { + // + this.availableLanguages = [...languages]; + + // + this.prepareLanguageAutoComplete(); + }), + ); + + // + await this.getValueAsync(request); + } + + /** + * Manage Auto Complete Requirement Handling ... + * + * @returns + */ + async handleLanguageAutoComplete() { + // + // Handle State When AsForm Present ... + if (this.presentType === this.PresentTypes.AsForm) { + // + if (!this.hasChild(this.availableLanguages)) { + await this.getAvailableLanguages(); + return; + } else { + // + this.prepareLanguageAutoComplete(); + return; + } + } + // + // Handle State When AsView ... + else if ( + this.showFieldsForm && + this.presentType === this.PresentTypes.AsView + ) { + // + const languageFD = this.fieldDescriptors.find( + (fd) => fd.key == this.FieldNames.language, + ); + if (languageFD.allowFieldForm) { + // + if (!this.hasChild(this.availableLanguages)) { + // + await this.getAvailableLanguages(); + return; + } else { + this.prepareLanguageAutoComplete(); + return; + } + } + } + } + + /** + * Prepare Language AutoComplete ... + * + * @returns + */ + prepareLanguageAutoComplete() { + // + if (!this.hasChild(this.availableLanguages)) { + return; + } + + // + const currentLang = this.getFieldValue(this.FieldNames.language); + if ( + !isNullOrEmptyString(currentLang) && + this.availableLanguages.findIndex((a) => a == currentLang) >= 0 + ) { + return; + } + + // + let fcIDX = -1; + let canDo = false; + let inField = false; + + // + // Field Form View ... + if (this.presentType === this.PresentTypes.AsView) { + // + // Access Field Form Index ... + inField = true; + canDo = this.isFormView(this.FieldNames.language); + fcIDX = this.inFormFieldsFormConfigs.findIndex( + (f) => f.name === this.FieldNames.language, + ); + } + // + // Form View ... + else if (this.presentType === this.PresentTypes.AsForm) { + // + // Checking Form Config ... + canDo = !isNullOrUndefined(this.modelFormConfig); + if (canDo) { + // + inField = false; + + // + fcIDX = this.modelFormConfig.controls.findIndex( + (f) => f.propName === this.FieldNames.language, + ); + if (fcIDX >= 0) { + fcIDX = this.modelFormConfig.controls[fcIDX].index; + } + } + } + + // + // Check Validation ... + canDo = fcIDX >= 0; + if (!canDo) { + return; + } + + // + // Access Controls ... + let controls = !inField + ? this.modelFormConfig.controls + : this.inFormFieldsFormConfigs[fcIDX].controls; + + // + // Access Control Appearance ... + let appearance = inField + ? controls[0].appearance + : controls[fcIDX].appearance; + + // + // Access Event Handlers ... + let eventHandlers = inField + ? controls[0].eventHandlers + : controls[fcIDX].eventHandlers; + + // + let autoCompleteConfig = appearance.autoComplete; + + // + // Update Auto Complete Items ... + this.languageAutoCompleteConfig = { + ...autoCompleteConfig, + // + // Opened ... + opened: this.isLanguageAutoCompleteOpened, + // + // Item Template ... + itemTemplate: this.languageAutoCompleteRef, + // + // Color ... + color: this.ColorNames.Tertiary, + // + // Items ... + items: () => this.languageAutoCompleteItems, + // + // On Select ... + onSelect: (item: any) => { + // + this.languageAutoCompleteItems = []; + this.isLanguageAutoCompleteOpened = false; + this.prepareLanguageAutoComplete(); + }, + // + // CSS Class ... + cssClass: ['x-auto-complete-margin'], + }; + + // + // Set Auto Complete Config to Controls ... + if (inField) { + // + controls[0] = { + ...controls[0], + appearance: { + ...appearance, + autoComplete: { + items: this.languageAutoCompleteConfig.items, + color: this.languageAutoCompleteConfig.color, + opened: this.languageAutoCompleteConfig.opened, + onSelect: this.languageAutoCompleteConfig.onSelect, + cssClass: this.languageAutoCompleteConfig.cssClass, + itemTemplate: this.languageAutoCompleteConfig.itemTemplate, + }, + }, + }; + + // + // Preparing Model ... + let iModel = this.inFormFieldsFormConfigs[fcIDX].model; + setPropertyValue(iModel, this.FieldNames.language, this.languageValue); + + // + this.inFormFieldsFormConfigs[fcIDX] = { + ...this.inFormFieldsFormConfigs[fcIDX], + model: iModel, + controls: [...controls], + }; + } else { + // + controls[fcIDX] = { + ...controls[fcIDX], + appearance: { + ...appearance, + autoComplete: { + color: this.languageAutoCompleteConfig.color, + items: this.languageAutoCompleteConfig.items, + opened: this.languageAutoCompleteConfig.opened, + onSelect: this.languageAutoCompleteConfig.onSelect, + cssClass: this.languageAutoCompleteConfig.cssClass, + itemTemplate: this.languageAutoCompleteConfig.itemTemplate, + }, + }, + }; + + // + // Preparing Model ... + let iModel = this.modelFormConfig.model; + setPropertyValue(iModel, this.FieldNames.language, this.languageValue); + + // + this.modelFormConfig = { + ...this.modelFormConfig, + model: iModel, + controls: [...controls], + }; + } + } + + /** + * Handle Apply Changes On Language Auto Complete when Query Changes ... + * + * @param value + * + * @returns + */ + handleFilterAutoCompleteLanguage(value: string) { + // + // If Values Same only Open or Close ... + if (toNormalString(value) === this.languageAutoCompleteQuery) { + // + // Check HasChild Items or not ... + this.isLanguageAutoCompleteOpened = this.hasChild( + this.languageAutoCompleteItems, + ); + + // + this.prepareLanguageAutoComplete(); + + // + return; + } + + // + this.languageValue = value; + this.languageAutoCompleteQuery = toNormalString(value); + + // + // Filter Items Based on Query ... + const filteredItems = this.availableLanguages.filter( + (i) => + toNormalString(i).includes(this.languageAutoCompleteQuery) && + toNormalString(i) !== this.languageAutoCompleteQuery, + ); + if (this.hasChild(filteredItems)) { + // + // Select Required Number of Filtered Items ... + const selectedItems = nthItems(0, 5, filteredItems); + if (this.hasChild(selectedItems)) { + // + // Create Result ... + this.languageAutoCompleteItems = selectedItems.map((i) => { + return { + data: i, + } as XListItem; + }); + } + } + + // + this.isLanguageAutoCompleteOpened = this.hasChild( + this.languageAutoCompleteItems, + ); + + // + this.prepareLanguageAutoComplete(); + } + //#endregion + + // + //#region Private ... + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\string\v-resource\v-resource.component.html + + + + + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\string\v-resource\v-resource.component.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\string\v-resource\v-resource.component.ts + +import { + Component, + ViewChild, + TemplateRef, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + getFieldNames, + XExceptionIDs, + XStandardType, + isNullOrUndefined, +} from 'x-framework-core'; +import { + XResourceDto, + XLocaleResourceDto, + defaultResourceDto, + prepareResourceDtoFields, +} from 'x-saherelm-api-sdk'; +import { + XFormControlType, + XFormControlAppearance, +} from 'x-framework-components'; +import { + VModelState, + VBaseDtoComponent, + VModelFieldDescriptor, + VModelValidationResult, +} from '../../v-dto/v-dto.typings'; +import { Validators } from '@angular/forms'; +import { StringTasks } from '../v-string.typings'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'v-resource', + templateUrl: './v-resource.component.html', + styleUrls: ['./v-resource.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class VResourceComponent extends VBaseDtoComponent { + // + //#region Props ... + // + // User Push Conection ID ... + connectionId: string; + availableLanguages: Array = []; + + // + @ViewChild('localeResourceRef') + localeResourceRef!: TemplateRef; + + // + readonly FormControlTypes = Object.assign({}, XFormControlType); + readonly FormControlAppearances = Object.assign({}, XFormControlAppearance); + readonly title = this.resourceProvider(this.AppResourceIDs.string_resource); + + // + defaultModel: XResourceDto = { + ...defaultResourceDto, + }; + readonly FieldNames = getFieldNames(this.defaultModel); + + /** + * Describe Model ... + */ + defaultFieldDescriptors: VModelFieldDescriptor[] = [ + // + // Resource ... + { + hide: false, + hideLabel: false, + key: this.FieldNames.resource, + type: this.ModelFieldTypes.String, + value: this.defaultModel.resource, + label: this.AppResourceIDs.resource_title, + allowFieldForm: true, + formControlConfig: { + index: 1, + propName: this.FieldNames.resource, + type: { + type: this.FormControlTypes.Text, + config: {}, + }, + validators: { + validators: [Validators.required], + }, + eventHandlers: {}, + appearance: { + hidden: false, + disabled: false, + placeholder: this.AppResourceIDs.resource_title, + appearance: this.FormControlAppearances.Outline, + icons: { + prefix: { + applyStateColor: true, + name: this.IconNames.label, + color: this.ColorNames.Tertiary, + }, + }, + }, + }, + } as VModelFieldDescriptor, + // + // Locales ... + { + hide: false, + hideLabel: false, + key: this.FieldNames.locales, + type: this.ModelFieldTypes.Array, + value: this.defaultModel.locales, + label: this.AppResourceIDs.locales, + allowFieldForm: false, + hasCustomTemplate: true, + showContentBelowLabel: true, + template: this.localeResourceRef, + } as VModelFieldDescriptor, + ]; + //#endregion + + // + //#region LifeCycles ... + async onInit(): Promise { + super.onInit(); + } + + async onChange(changeKeys: string[]): Promise { + super.onChange(changeKeys); + + // + var isChanged = false; + + // + this.handleAttachLocalesTemplate(); + } + + /** + * Register Required Instance Subscriptions ... + */ + registerInstanceHandlers(): void { + super.registerInstanceHandlers(); + } + //#endregion + + // + //#region UI Handlers ... + handleLocaleChanged( + event: VModelState, + key: string, + index: number, + vale: XLocaleResourceDto, + ) { + // // + // const fdIDX = this.fieldDescriptors.findIndex((f) => f.key === key); + // const obj = + // index >= 0 ? this.getFieldValue(key)[index] : this.getFieldValue(key); + // const canApply = + // !isNullOrUndefined(event) && !isSameObject(event.model, obj); + // if (!canApply) { + // return; + // } + // // + // let notify = false; + // if (index >= 0) { + // // + // notify = true; + // (this.model as any)[key][index] = event.model; + // //setPropertyValue((this.MODEL as any)[key], toXString(index), event.model); + // } else { + // // + // notify = true; + // (this.MODEL as any)[key] = event.model; + // //setPropertyValue(this.MODEL, key, event.model); + // } + // // + // if (notify) { + // // + // // this.changeSubject.next(this.MODEL); + // // + // const state = this.getState(); + // console.log('state: ', state); + // console.log('obj: ', obj); + // console.log('event model: ', event.model); + // } + } + //#endregion + + // + //#region Abstraction Implementation ... + /** + * Validate Model for Actions ... + * + * @param model + */ + validate( + model?: Partial, + ): VModelValidationResult { + // + let result: VModelValidationResult; + + // + let isValid = false; + const validationErrors: string[] = []; + + // + model = { + ...this.model, + ...model, + }; + const normalized = this.normalize(model); + + // + // Handle Validation ... + isValid = + !isNullOrUndefined(normalized) && !this.isSameAsDefault(normalized); + if (!isValid) { + // + validationErrors.push( + this.getExceptionMessage(XExceptionIDs.InvalidArgs), + ); + } else { + // + // Check Other Validations ... + } + + // + // Handle Result ... + + // + isValid = !this.hasChild(validationErrors); + + // + const isValidForAdd = false; + const isValidForUpdate = false; + + // + result = { + isValid, + normalized, + isValidForAdd, + isValidForUpdate, + validationErrors, + }; + + // + return result; + } + + /** + * Prepare Model ... + * + * @param model + */ + prepare(model?: XResourceDto): XResourceDto { + return prepareResourceDtoFields(model); + } + //#endregion + + // + //#region Overrides ... + //#endregion + + // + //#region AutoComplete ... + //#endregion + + // + //#region Private ... + handleAttachLocalesTemplate() { + // + // Check Template Exists ... + if (isNullOrUndefined(this.localeResourceRef)) { + return; + } + + // + const fdIDX = this.fieldDescriptors.findIndex( + (fd) => fd.key === this.FieldNames.locales, + ); + if (fdIDX < 0) { + return; + } + + // + // Check Has Template or not ... + if (!isNullOrUndefined(this.fieldDescriptors[fdIDX].template)) { + return; + } + + // + // Attach ... + this.fieldDescriptors[fdIDX].template = this.localeResourceRef; + + // + this.detectChanges(); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\string\v-string\v-string.component.html + + + + + + + + + + {{ item.data }} + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\string\v-string\v-string.component.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\string\v-string\v-string.component.ts + +import { + Component, + ViewChild, + TemplateRef, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + nthItems, + getFieldNames, + XExceptionIDs, + XStandardType, + toNormalString, + setPropertyValue, + isNullOrUndefined, + isNullOrEmptyString, +} from 'x-framework-core'; +import { + XStringDto, + defaultStringDto, + prepareStringDtoFields, +} from 'x-saherelm-api-sdk'; +import { + XListItem, + XFormControlType, + XFormControlAppearance, +} from 'x-framework-components'; +import { + VBaseDtoComponent, + VModelFieldDescriptor, + VModelValidationResult, +} from '../../v-dto/v-dto.typings'; +import { map } from 'rxjs/operators'; +import { Validators } from '@angular/forms'; +import { StringTasks } from '../v-string.typings'; +import { getLanguageResourceID } from '../v-string.tools'; +import { XSimpleFormControlAutoCompleteConfig } from '../../typings/simple.form.typings'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'v-string', + templateUrl: './v-string.component.html', + styleUrls: ['./v-string.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class VStringComponent extends VBaseDtoComponent { + // + //#region Props ... + // + // User Push Conection ID ... + connectionId: string; + availableLanguages: Array = []; + + // + readonly FormControlTypes = Object.assign({}, XFormControlType); + readonly FormControlAppearances = Object.assign({}, XFormControlAppearance); + readonly title = this.resourceProvider(this.AppResourceIDs.string_resource); + + // + defaultModel: XStringDto = { + ...defaultStringDto, + }; + readonly FieldNames = getFieldNames(this.defaultModel); + + // + languageValue: string = ''; + languageAutoCompleteQuery = ''; + @ViewChild('languageAutoCompleteRef') + languageAutoCompleteRef!: TemplateRef; + isLanguageAutoCompleteOpened: boolean = false; + languageAutoCompleteItems: XListItem[]; + languageAutoCompleteConfig: XSimpleFormControlAutoCompleteConfig< + XListItem + >; + + /** + * Describe Model ... + */ + defaultFieldDescriptors: VModelFieldDescriptor[] = [ + // + // Id ... + { + hide: false, + hideLabel: false, + allowFieldForm: false, + key: this.FieldNames.id, + label: this.ResourceIDs.id, + value: this.defaultModel.id, + type: this.ModelFieldTypes.Number, + } as VModelFieldDescriptor, + // + // Language ... + { + hide: false, + hideLabel: false, + key: this.FieldNames.language, + label: this.ResourceIDs.language, + value: this.defaultModel.language, + type: this.ModelFieldTypes.String, + valueProvider: (value) => { + return this.resourceProvider(getLanguageResourceID(value)); + }, + allowFieldForm: true, + formControlConfig: { + index: 0, + propName: this.FieldNames.language, + type: { + type: this.FormControlTypes.Text, + config: {}, + }, + validators: { + validators: [Validators.required], + }, + eventHandlers: { + onBlured: (model) => {}, + onFocused: (model) => {}, + valueChanged: (event) => { + // + if (this.languageValue === event.value) { + return; + } + + // + this.handleFilterAutoCompleteLanguage(event.value); + }, + }, + appearance: { + hidden: false, + disabled: false, + placeholder: this.ResourceIDs.language, + appearance: this.FormControlAppearances.Outline, + icons: { + prefix: { + applyStateColor: true, + name: this.IconNames.language, + color: this.ColorNames.Tertiary, + }, + }, + autoComplete: {}, + }, + }, + } as VModelFieldDescriptor, + // + // Resource Title ... + { + hide: false, + hideLabel: false, + type: this.ModelFieldTypes.String, + key: this.FieldNames.resourceTitle, + value: this.defaultModel.resourceTitle, + label: this.AppResourceIDs.resource_title, + allowFieldForm: true, + formControlConfig: { + index: 1, + propName: this.FieldNames.resourceTitle, + type: { + type: this.FormControlTypes.Text, + config: {}, + }, + validators: { + validators: [Validators.required], + }, + eventHandlers: {}, + appearance: { + hidden: false, + disabled: false, + placeholder: this.AppResourceIDs.resource_title, + appearance: this.FormControlAppearances.Outline, + icons: { + prefix: { + applyStateColor: true, + name: this.IconNames.label, + color: this.ColorNames.Tertiary, + }, + }, + }, + }, + } as VModelFieldDescriptor, + // + // Translated Value ... + { + hide: false, + hideLabel: false, + type: this.ModelFieldTypes.String, + label: this.ResourceIDs.translation, + key: this.FieldNames.translatedValue, + value: this.defaultModel.translatedValue, + valueProvider: (value) => { + return this.applyLocale(value); + }, + allowFieldForm: true, + formControlConfig: { + index: 2, + propName: this.FieldNames.translatedValue, + type: { + type: this.FormControlTypes.Text, + config: {}, + }, + validators: { + validators: [Validators.required], + }, + eventHandlers: {}, + appearance: { + hidden: false, + disabled: false, + placeholder: this.ResourceIDs.translation, + appearance: this.FormControlAppearances.Outline, + icons: { + prefix: { + applyStateColor: true, + name: this.IconNames.label, + color: this.ColorNames.Tertiary, + }, + }, + }, + }, + } as VModelFieldDescriptor, + ]; + //#endregion + + // + //#region LifeCycles ... + async onInit(): Promise { + super.onInit(); + } + + async onChange(changeKeys: string[]): Promise { + super.onChange(changeKeys); + + // + var isChanged = false; + + // + await this.handleLanguageAutoComplete(); + } + + /** + * Register Required Instance Subscriptions ... + */ + registerInstanceHandlers(): void { + super.registerInstanceHandlers(); + } + //#endregion + + // + //#region UI Handlers ... + //#endregion + + // + //#region Abstraction Implementation ... + /** + * Validate Model for Actions ... + * + * @param model + */ + validate(model?: Partial): VModelValidationResult { + // + let result: VModelValidationResult; + + // + let isValid = false; + const validationErrors: string[] = []; + + // + model = { + ...this.model, + ...model, + }; + const normalized = this.normalize(model); + + // + // Handle Validation ... + isValid = + !isNullOrUndefined(normalized) && !this.isSameAsDefault(normalized); + if (!isValid) { + // + validationErrors.push( + this.getExceptionMessage(XExceptionIDs.InvalidArgs), + ); + } else { + // + // Check Other Validations ... + + // + // ID ... + + // + // Language ... + isValid = !isNullOrEmptyString(normalized.language); + if (!isValid) { + // + validationErrors.push( + `${this.resourceProvider( + this.ResourceIDs.language, + )}: ${this.getExceptionMessage(XExceptionIDs.InvalidArgs)}`, + ); + } + + // + // Resource Title ... + isValid = !isNullOrEmptyString(normalized.resourceTitle); + if (!isValid) { + // + validationErrors.push( + `${this.resourceProvider( + this.AppResourceIDs.resource_title, + )}: ${this.getExceptionMessage(XExceptionIDs.InvalidArgs)}`, + ); + } + + // + // Translated Value ... + isValid = !isNullOrEmptyString(normalized.translatedValue); + if (!isValid) { + // + validationErrors.push( + `${this.resourceProvider( + this.ResourceIDs.translation, + )}: ${this.getExceptionMessage(XExceptionIDs.InvalidArgs)}`, + ); + } + } + + // + // Handle Result ... + + // + isValid = !this.hasChild(validationErrors); + + // + const isValidForAdd = + isNullOrUndefined(normalized.id) || normalized.id <= 0; + const isValidForUpdate = + !isNullOrUndefined(normalized.id) && normalized.id > 0; + + // + result = { + isValid, + normalized, + isValidForAdd, + isValidForUpdate, + validationErrors, + }; + + // + return result; + } + + /** + * Prepare Model ... + * + * @param model + */ + prepare(model?: XStringDto): XStringDto { + return prepareStringDtoFields(model); + } + //#endregion + + // + //#region Overrides ... + async onFieldFormPresent(key: string) { + // + if (key === this.FieldNames.language) { + await this.handleLanguageAutoComplete(); + } + } + //#endregion + + // + //#region AutoComplete ... + /** + * Retriev Available Languages ... + * + * @returns + */ + async getAvailableLanguages() { + // + let has = + !this.hasChild(this.availableLanguages) && + (this.presentType == this.PresentTypes.AsForm || + (this.presentType == this.PresentTypes.AsView && + !isNullOrUndefined(this.inFormFieldsFormConfigs) && + this.hasChild(this.inFormFieldsFormConfigs) && + this.isFormView(this.FieldNames.language))); + if (!has) { + return; + } + + // + const request = + this.sharedService.saherElmAPIService.strings.stringProviderService + .languages() + .pipe( + map((languages) => { + // + this.availableLanguages = [...languages]; + + // + this.prepareLanguageAutoComplete(); + }), + ); + + // + await this.getValueAsync(request); + } + + /** + * Manage Auto Complete Requirement Handling ... + * + * @returns + */ + async handleLanguageAutoComplete() { + // + // Handle State When AsForm Present ... + if (this.presentType === this.PresentTypes.AsForm) { + // + if (!this.hasChild(this.availableLanguages)) { + await this.getAvailableLanguages(); + return; + } else { + // + this.prepareLanguageAutoComplete(); + return; + } + } + // + // Handle State When AsView ... + else if ( + this.showFieldsForm && + this.presentType === this.PresentTypes.AsView + ) { + // + const languageFD = this.fieldDescriptors.find( + (fd) => fd.key == this.FieldNames.language, + ); + if (languageFD.allowFieldForm) { + // + if (!this.hasChild(this.availableLanguages)) { + // + await this.getAvailableLanguages(); + return; + } else { + this.prepareLanguageAutoComplete(); + return; + } + } + } + } + + /** + * Prepare Language AutoComplete ... + * + * @returns + */ + prepareLanguageAutoComplete() { + // + if (!this.hasChild(this.availableLanguages)) { + return; + } + + // + const currentLang = this.getFieldValue(this.FieldNames.language); + if ( + !isNullOrEmptyString(currentLang) && + this.availableLanguages.findIndex((a) => a == currentLang) >= 0 + ) { + return; + } + + // + let fcIDX = -1; + let canDo = false; + let inField = false; + + // + // Field Form View ... + if (this.presentType === this.PresentTypes.AsView) { + // + // Access Field Form Index ... + inField = true; + canDo = this.isFormView(this.FieldNames.language); + fcIDX = this.inFormFieldsFormConfigs.findIndex( + (f) => f.name === this.FieldNames.language, + ); + } + // + // Form View ... + else if (this.presentType === this.PresentTypes.AsForm) { + // + // Checking Form Config ... + canDo = !isNullOrUndefined(this.modelFormConfig); + if (canDo) { + // + inField = false; + + // + fcIDX = this.modelFormConfig.controls.findIndex( + (f) => f.propName === this.FieldNames.language, + ); + if (fcIDX >= 0) { + fcIDX = this.modelFormConfig.controls[fcIDX].index; + } + } + } + + // + // Check Validation ... + canDo = fcIDX >= 0; + if (!canDo) { + return; + } + + // + // Access Controls ... + let controls = !inField + ? this.modelFormConfig.controls + : this.inFormFieldsFormConfigs[fcIDX].controls; + + // + // Access Control Appearance ... + let appearance = inField + ? controls[0].appearance + : controls[fcIDX].appearance; + + // + // Access Event Handlers ... + let eventHandlers = inField + ? controls[0].eventHandlers + : controls[fcIDX].eventHandlers; + + // + let autoCompleteConfig = appearance.autoComplete; + + // + // Update Auto Complete Items ... + this.languageAutoCompleteConfig = { + ...autoCompleteConfig, + // + // Opened ... + opened: this.isLanguageAutoCompleteOpened, + // + // Item Template ... + itemTemplate: this.languageAutoCompleteRef, + // + // Color ... + color: this.ColorNames.Tertiary, + // + // Items ... + items: () => this.languageAutoCompleteItems, + // + // On Select ... + onSelect: (item: any) => { + // + this.languageAutoCompleteItems = []; + this.isLanguageAutoCompleteOpened = false; + this.prepareLanguageAutoComplete(); + }, + // + // CSS Class ... + cssClass: ['x-auto-complete-margin'], + }; + + // + // Set Auto Complete Config to Controls ... + if (inField) { + // + controls[0] = { + ...controls[0], + appearance: { + ...appearance, + autoComplete: { + items: this.languageAutoCompleteConfig.items, + color: this.languageAutoCompleteConfig.color, + opened: this.languageAutoCompleteConfig.opened, + onSelect: this.languageAutoCompleteConfig.onSelect, + cssClass: this.languageAutoCompleteConfig.cssClass, + itemTemplate: this.languageAutoCompleteConfig.itemTemplate, + }, + }, + }; + + // + // Preparing Model ... + let iModel = this.inFormFieldsFormConfigs[fcIDX].model; + setPropertyValue(iModel, this.FieldNames.language, this.languageValue); + + // + this.inFormFieldsFormConfigs[fcIDX] = { + ...this.inFormFieldsFormConfigs[fcIDX], + model: iModel, + controls: [...controls], + }; + } else { + // + controls[fcIDX] = { + ...controls[fcIDX], + appearance: { + ...appearance, + autoComplete: { + color: this.languageAutoCompleteConfig.color, + items: this.languageAutoCompleteConfig.items, + opened: this.languageAutoCompleteConfig.opened, + onSelect: this.languageAutoCompleteConfig.onSelect, + cssClass: this.languageAutoCompleteConfig.cssClass, + itemTemplate: this.languageAutoCompleteConfig.itemTemplate, + }, + }, + }; + + // + // Preparing Model ... + let iModel = this.modelFormConfig.model; + setPropertyValue(iModel, this.FieldNames.language, this.languageValue); + + // + this.modelFormConfig = { + ...this.modelFormConfig, + model: iModel, + controls: [...controls], + }; + } + } + + /** + * Handle Apply Changes On Language Auto Complete when Query Changes ... + * + * @param value + * + * @returns + */ + handleFilterAutoCompleteLanguage(value: string) { + // + // If Values Same only Open or Close ... + if (toNormalString(value) === this.languageAutoCompleteQuery) { + // + // Check HasChild Items or not ... + this.isLanguageAutoCompleteOpened = this.hasChild( + this.languageAutoCompleteItems, + ); + + // + this.prepareLanguageAutoComplete(); + + // + return; + } + + // + this.languageValue = value; + this.languageAutoCompleteQuery = toNormalString(value); + + // + // Filter Items Based on Query ... + const filteredItems = this.availableLanguages.filter( + (i) => + toNormalString(i).includes(this.languageAutoCompleteQuery) && + toNormalString(i) !== this.languageAutoCompleteQuery, + ); + if (this.hasChild(filteredItems)) { + // + // Select Required Number of Filtered Items ... + const selectedItems = nthItems(0, 5, filteredItems); + if (this.hasChild(selectedItems)) { + // + // Create Result ... + this.languageAutoCompleteItems = selectedItems.map((i) => { + return { + data: i, + } as XListItem; + }); + } + } + + // + this.isLanguageAutoCompleteOpened = this.hasChild( + this.languageAutoCompleteItems, + ); + + // + this.prepareLanguageAutoComplete(); + } + //#endregion + + // + //#region Private ... + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\tags\v-tag.configs.ts + +import { XIconNames } from 'x-framework-components'; +import { XSimplePage } from '../typings/simple.page.typings'; + +// +//#region Localization Configuration ... +export enum TagResourceIDs { + tag = 'tag', + tags = 'tags', + tags_page_title = 'tags_page_title', + tags_page_description = 'tags_page_description', + tags_management_page_title = 'tags_management_page_title', + tags_management_page_description = 'tags_management_page_description', + tags_explorer_page_title = 'tags_explorer_page_title', + tags_explorer_page_description = 'tags_explorer_page_description', +} + +// +//#region Single Resources ... +// +//#region tag ... +export const tag_fa = { + id: 'tag', + value: 'برچسب', +}; + +export const tag_en = { + id: 'tag', + value: 'Tag', +}; +//#endregion + +// +//#region tags ... +export const tags_fa = { + id: 'tags', + value: 'برچسب ها', +}; + +export const tags_en = { + id: 'tags', + value: 'Tags', +}; +//#endregion + +// +//#region tags_page_title ... +export const tags_page_title_fa = { + id: 'tags_page_title', + value: 'برچسب', +}; + +export const tags_page_title_en = { + id: 'tags_page_title', + value: 'Tag', +}; +//#endregion + +// +//#region tags_page_description ... +export const tags_page_description_fa = { + id: 'tags_page_description', + value: 'ابزارهای مدیریت برچسب ها', +}; + +export const tags_page_description_en = { + id: 'tags_page_description', + value: 'Tools for Tags', +}; +//#endregion + +// +//#region tags_management_page_title ... +export const tags_management_page_title_fa = { + id: 'tags_management_page_title', + value: 'مدیریت برچسب ها', +}; + +export const tags_management_page_title_en = { + id: 'tags_management_page_title', + value: 'Tags Management', +}; +//#endregion + +// +//#region tags_management_page_description ... +export const tags_management_page_description_fa = { + id: 'tags_management_page_description', + value: 'مدیریت برچسب ها', +}; + +export const tags_management_page_description_en = { + id: 'tags_management_page_description', + value: 'Tags Management', +}; +//#endregion + +// +//#region tags_explorer_page_title ... +export const tags_explorer_page_title_fa = { + id: 'tags_explorer_page_title', + value: 'برچسب ها', +}; + +export const tags_explorer_page_title_en = { + id: 'tags_explorer_page_title', + value: 'Tags', +}; +//#endregion + +// +//#region tags_explorer_page_description ... +export const tags_explorer_page_description_fa = { + id: 'tags_explorer_page_description', + value: 'مرورگر برچسب', +}; + +export const tags_explorer_page_description_en = { + id: 'tags_explorer_page_description', + value: 'Tag Explorer', +}; +//#endregion +//#endregion + +// +export const TagLocalizationResourceIDs = Object.assign({}, TagResourceIDs); + +// +export const TagLocalizationResources = { + // + tag: { + fa: { ...tag_fa }, + en: { ...tag_en }, + }, + tags: { + fa: { ...tags_fa }, + en: { ...tags_en }, + }, + tags_page_title: { + fa: { ...tags_page_title_fa }, + en: { ...tags_page_title_en }, + }, + tags_page_description: { + fa: { ...tags_page_description_fa }, + en: { ...tags_page_description_en }, + }, + tags_management_page_title: { + fa: { ...tags_management_page_title_fa }, + en: { ...tags_management_page_title_en }, + }, + tags_management_page_description: { + fa: { ...tags_management_page_description_fa }, + en: { ...tags_management_page_description_en }, + }, + tags_explorer_page_title: { + fa: { ...tags_explorer_page_title_fa }, + en: { ...tags_explorer_page_title_en }, + }, + tags_explorer_page_description: { + fa: { ...tags_explorer_page_description_fa }, + en: { ...tags_explorer_page_description_en }, + }, +}; +//#endregion + +// +//#region Page Configuration ... +// +export enum TagRoutes { + Default = 'tags', + TagsExplorer = 'tags-explorer', + TagsManagement = 'tags-management', +} + +// +export const Tags: XSimplePage = { + id: `${TagRoutes.Default}`, + name: `${TagRoutes.Default}`, + title: TagResourceIDs.tags_page_title, + description: TagResourceIDs.tags_page_description, + baseRoute: `${TagRoutes.Default}`, + route: ['/', `${TagRoutes.Default}`], + icon: XIconNames.collapse, + childs: [ + // + //#region Tags Management ... + { + id: `${TagRoutes.TagsManagement}`, + name: `${TagRoutes.TagsManagement}`, + title: TagResourceIDs.tags_management_page_title, + description: TagResourceIDs.tags_management_page_description, + baseRoute: `${TagRoutes.TagsManagement}`, + route: ['/', `${TagRoutes.Default}`, `${TagRoutes.TagsManagement}`], + icon: XIconNames.live_tv, + }, + //#endregion + + // + //#region Tags Explorer ... + { + id: `${TagRoutes.TagsExplorer}`, + name: `${TagRoutes.TagsExplorer}`, + title: TagResourceIDs.tags_explorer_page_title, + description: TagResourceIDs.tags_explorer_page_description, + baseRoute: `${TagRoutes.TagsExplorer}`, + route: ['/', `${TagRoutes.Default}`, `${TagRoutes.TagsExplorer}`], + icon: XIconNames.live_tv, + }, + //#endregion + ], +}; +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\tags\v-tag.tools.ts + +import { + nameof, + isArray, + isString, + toNumber, + toXString, + isNullOrUndefined, +} from 'x-framework-core'; +import { parseXReferences, XTagDto } from 'x-mabsut-api-sdk'; + +export function toXTagDto(value: any) { + // + let result: XTagDto = undefined; + + // + let isPassed = false; + isPassed = + !isNullOrUndefined(value) && + Object.keys(value).includes(nameof('id')) && + Object.keys(value).includes(nameof('tag')) && + Object.keys(value).includes(nameof('references')); + if (isPassed) { + // + result = { + id: toNumber(value.id), + tag: toXString(value.tag), + references: isNullOrUndefined(value.references) + ? [] + : isString(value.references) + ? parseXReferences(value.references) + : isArray(value.references) + ? [...value.references] + : [], + }; + } + + // + return result; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\tags\v-tag\v-tag.component.html + + + + +
+ +
+
+ + + + + + + + + + + + + + + + + + + + +
+ {{ getFieldValue(FieldNames.tag) }} +
+
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\tags\v-tag\v-tag.component.scss + +.v-references-list { + padding: { + top: 16px; + left: 6px; + right: 6px; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\tags\v-tag\v-tag.component.ts + +import { + Input, + Component, + ViewChild, + TemplateRef, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + getFieldNames, + XExceptionIDs, + isNullOrUndefined, + isNullOrEmptyString, + nameofInstance, +} from 'x-framework-core'; +import { + XTagDto, + defaultTagDto, + XReferenceDto, + prepareTagDtoFields, +} from 'x-saherelm-api-sdk'; +import { Subject } from 'rxjs'; +import { + VBaseDtoComponent, + VModelFieldDescriptor, + VModelValidationResult, +} from '../../v-dto/v-dto.typings'; +import { Validators } from '@angular/forms'; +import { + VTagPresentType, + isVTagPresentType, + VTagPresentTypeIdentifier, +} from './v-tag.typings'; +import { + VReferencePresentType, + isVReferencePresentType, + VReferencePresentTypeIdentifier, +} from '../../v-reference/v-reference.typings'; +import { XListAction, XListActionModel } from 'x-framework-components'; +import { XSimpleListItem } from '../../typings/simple.list-item.typings'; + +@Component({ + selector: 'v-tag', + templateUrl: './v-tag.component.html', + styleUrls: ['./v-tag.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class VTagComponent extends VBaseDtoComponent { + // + //#region Props ... + // + // Readonly Fields ... + readonly defaultModel: XTagDto = { + ...defaultTagDto, + }; + readonly FieldNames = getFieldNames(this.defaultModel); + + @Input() + presentType: VTagPresentTypeIdentifier = VTagPresentType.AsItem; + + /** + * Specifie Reference Present Type ... + */ + @Input() + referencePresentType: VReferencePresentTypeIdentifier = + VReferencePresentType.AsView; + + /** + * References List Action Provider ... + */ + referencesListActionProvider = new Subject(); + + /** + * Default Model Field Descriptors ... + */ + defaultFieldDescriptors: VModelFieldDescriptor[] = [ + // + // ID ... + { + hide: true, + hideLabel: true, + allowFieldForm: false, + key: this.FieldNames.id, + label: this.ResourceIDs.id, + value: this.defaultModel.id, + type: this.ModelFieldTypes.Number, + valueProvider: (value) => { + return this.applyLocale(value); + }, + } as VModelFieldDescriptor, + // + // Tag ... + { + hide: false, + hideLabel: false, + allowFieldForm: true, + key: this.FieldNames.tag, + value: this.defaultModel.tag, + label: this.AppResourceIDs.tag, + type: this.ModelFieldTypes.String, + valueProvider: (value) => { + return this.applyLocale(value); + }, + formControlConfig: { + index: 0, + propName: this.FieldNames.tag, + type: { + type: this.FormControlTypes.Text, + config: {}, + }, + validators: { + validators: [Validators.required], + }, + eventHandlers: {}, + appearance: { + hidden: false, + disabled: false, + placeholder: this.AppResourceIDs.tag, + appearance: this.FormControlAppearances.Outline, + icons: { + prefix: { + applyStateColor: true, + name: this.IconNames.label, + color: this.ColorNames.Tertiary, + }, + }, + }, + }, + } as VModelFieldDescriptor, + // + // References ... + { + hide: false, + hideLabel: false, + allowFieldForm: true, + key: this.FieldNames.references, + value: this.defaultModel.references, + label: this.AppResourceIDs.references, + type: this.ModelFieldTypes.Array, + showContentBelowLabel: true, + pushArrayChildsOnceToTemplate: true, + hasCustomTemplate: true, + } as VModelFieldDescriptor>>, + ]; + + @ViewChild('referencesRef') + referencesRef!: TemplateRef; + + @ViewChild('asItemTemplateRef') + asItemTemplateRef!: TemplateRef; + //#endregion + + // + //#region LifeCycles ... + afterViewInit(): void { + super.afterViewInit(); + + // + this.handleAttachTemplates(); + } + + onChange(changeKeys: string[]): void { + super.onChange(changeKeys); + + // + let isChanged = false; + let applyChanges = false; + + // + // Present Type ... + isChanged = changeKeys.includes(nameofInstance(this, 'presentType')); + if (isChanged) { + // + if (!isVTagPresentType(this.presentType)) { + // + applyChanges = true; + this.presentType = VTagPresentType.AsItem; + } + } + + // + // Reference Present Type ... + isChanged = changeKeys.includes( + nameofInstance(this, 'referencePresentType'), + ); + if (isChanged) { + // + if (!isVReferencePresentType(this.referencePresentType)) { + // + applyChanges = true; + this.referencePresentType = VReferencePresentType.AsView; + } + } + + // + if (applyChanges) { + this.detectChanges(); + } + + // + this.handleAttachTemplates(); + } + //#endregion + + // + //#region Providers ... + referencesListItems: Array> = []; + getReferencesListItems(references: Array) { + // + if (this.hasChild(this.referencesListItems)) { + return this.referencesListItems; + } + + // + this.referencesListItems = [ + ...references.map((r) => { + return { + data: { ...r }, + } as XSimpleListItem; + }), + ]; + + // + this.referencesListActionProvider.next({ action: XListAction.Refresh }); + setTimeout( + () => + this.referencesListActionProvider.next({ action: XListAction.Refresh }), + 200, + ); + setTimeout( + () => + this.referencesListActionProvider.next({ action: XListAction.Refresh }), + 500, + ); + + // + return this.referencesListItems; + } + //#endregion + + // + //#region Abstraction ... + /** + * Prepare Model Fields ... + * + * @param model + * @returns + */ + prepare(model?: XTagDto): XTagDto { + return prepareTagDtoFields(model); + } + + /** + * Handle Model Validation ... + * + * @param model + * @returns + */ + validate(model?: Partial): VModelValidationResult { + // + let result: VModelValidationResult = undefined; + + // + let isValid = false; + const validationErrors: string[] = []; + + // + model = { + ...this.model, + ...model, + }; + const normalized = this.normalize(model); + + // + // Handle Validation ... + isValid = + !isNullOrUndefined(normalized) && !this.isSameAsDefault(normalized); + if (!isValid) { + // + validationErrors.push( + this.getExceptionMessage(XExceptionIDs.InvalidArgs), + ); + } else { + // + // Check Other Validations ... + + // + // Tag ... + isValid = !isNullOrEmptyString(normalized.tag); + if (!isValid) { + // + validationErrors.push( + `${this.resourceProvider( + this.AppResourceIDs.tag, + )}: ${this.getExceptionMessage(XExceptionIDs.InvalidArgs)}`, + ); + } + } + + // + // Handle Result ... + + // + isValid = !this.hasChild(validationErrors); + + // + const isValidForAdd = + isNullOrUndefined(normalized.id) || normalized.id <= 0; + const isValidForUpdate = + !isNullOrUndefined(normalized.id) && normalized.id > 0; + + // + result = { + isValid, + normalized, + isValidForAdd, + isValidForUpdate, + validationErrors, + }; + + // + return result; + } + + onModelAssigned(): void { + super.onModelAssigned(); + + // + // Reset List Items if Exists for Renewing ... + let canReset = this.hasChild(this.referencesListItems); + if (canReset) { + this.referencesListItems = []; + } + } + + /** + * Attach Custom Present Types ... + * + * @param presentType + * @returns + */ + getCustomPresentTypeTemplate(presentType: string): TemplateRef { + // + let result: TemplateRef = undefined; + + // + if (!isNullOrEmptyString(presentType) && isVTagPresentType(presentType)) { + // + switch (presentType) { + // + case VTagPresentType.AsItem: + result = this.asItemTemplateRef; + break; + } + } + + // + return result; + } + //#endregion + + // + //#region Private ... + private handleAttachTemplates() { + // + if (isNullOrUndefined(this.referencesRef)) { + return; + } + + // + this.defaultFieldDescriptors = this.fieldDescriptors.map((fd) => { + // + return { + ...fd, + template: + fd.key === this.FieldNames.references && + fd.hasCustomTemplate && + isNullOrUndefined(fd.template) + ? this.referencesRef + : fd.template, + }; + }); + + // + if (!isNullOrUndefined(this.providedFieldDescriptors)) { + // + this.providedFieldDescriptors = this.providedFieldDescriptors.map( + (fd) => { + // + return { + ...fd, + template: + fd.key === this.FieldNames.references && + fd.hasCustomTemplate && + isNullOrUndefined(fd.template) + ? this.referencesRef + : fd.template, + }; + }, + ); + } + + // + this.resetFieldDescriptors(); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\tags\v-tag\v-tag.typings.ts + +import { XBaseActionModel } from 'x-framework-components'; +import { isValueInEnum } from '../../tools/v-base.tools'; + +// +//#region Present Type ... +export enum VTagPresentType { + // + AsNone = 'as_none', + AsForm = 'as_form', + AsView = 'as_view', + AsItem = 'as_item', +} + +export type VTagPresentTypeIdentifier = VTagPresentType | string; + +export function isVTagPresentType(value: string) { + return isValueInEnum(value, Object.assign({}, VTagPresentType)); +} +//#endregion + +// +//#region Action Provider ... +export enum VTagAction { + Refresh = 'Refresh', +} + +export type VTagActionIdentifier = VTagAction | string; + +export interface VTagActionModel extends XBaseActionModel {} + +export function isVTagAction(value: string) { + return isValueInEnum(value, Object.assign({}, VTagAction)); +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\tags\v-tag-input\v-tag-input.component.html + +
+ +
+ + + +
+ + + +
+ +
+
+ + + + +

{{ getToolbarTitle() }}

+
+
+ + + + +
+
+
+ + + +
+ + +
+ + +
+
+ + + + + + + +
+ +
+
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\tags\v-tag-input\v-tag-input.component.scss + +@import "../../../../theme/theme.scss"; + +:host { + --dialog-toolbar-height: 0px; + --background: var(--x-color-light-shade); +} + +::ng-deep.fullscreen { + --width: 100% !important; + --height: 100% !important; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + margin: 0 !important; +} + +.v-base-dialog { + width: 100%; + height: 100%; + // height: calc(100% - var(--dialog-toolbar-height)); + // margin-bottom: var(--dialog-toolbar-height); +} + +.v-base-dialog-content { + height: 100%; + + // + padding-bottom: var(--dialog-toolbar-height); +} + +.v-base-dialog-toolbar-area { + margin: 8px; +} + +.v-base-dialog-toolbar-item { + width: auto; + height: 48px; +} + +.v-base-dialog-toolbar-button { + width: 32px; + margin: { + left: 4px; + right: 4px; + } +} + +.v-webinar-screen { + padding: 16px; +} + +// +// Non Global ... +.v-avatar-image { + // + margin: auto; + display: block; + max-width: 100%; + max-height: 100%; + + // + clip-path: circle(); +} + +.v-container { + height: 100%; + padding: 16px; + overflow: scroll; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\tags\v-tag-input\v-tag-input.component.ts + +import { + Input, + ViewChild, + Component, + ElementRef, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + XColorIdentifier, + isNullOrUndefined, + isNullOrEmptyString, + XResourceIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { + VTagInputModalContext, + VTagInputModalContextIdentifier, + VTagInputReturnActionIdentifier, +} from './v-tag-input.typings'; +import { + XSlotName, + XIconNames, + XSlotLayout, + XSlotterActionModel, +} from 'x-framework-components'; +import { Subject } from 'rxjs'; +import { + VModelState, + VBaseDtoModelAction, + VBaseDtoPresentType, + VBaseDtoModelFiredActionModel, +} from '../../v-dto/v-dto.typings'; +import { XTagDto } from 'x-saherelm-api-sdk'; +import { VBaseComponent } from '../../classes/v-base.component'; + +@Component({ + selector: 'v-tag-input', + templateUrl: './v-tag-input.component.html', + styleUrls: ['./v-tag-input.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class VTagInputComponent extends VBaseComponent { + // + //#region Properties ... + // + readonly SlotNames = Object.assign({}, XSlotName); + readonly SlotLayouts = Object.assign({}, XSlotLayout); + + /** + * retrieve Toolbar container ViewChild + * to Calculate Toolbar Height ... + */ + @ViewChild('vBaseDialogToolbar') + vBaseDialogToolbarContainer!: ElementRef; + + /** + * Specified Modal Type ... + */ + @Input() + context: VTagInputModalContextIdentifier; + + @Input() + model: XTagDto; + edittedModel: XTagDto = undefined; + + @Input() + presentType: VBaseDtoPresentType; + + /** + * Background Color of Component ... + */ + @Input() + backgroundColor: XColorIdentifier = XColorWithBrightness.Light; + + @Input() + toolbarTitle: XResourceIdentifier = ''; + + /** + * Toolbar Color ... + */ + @Input() + toolbarColor: XColorIdentifier = XColorWithBrightness.PrimaryTint; + + /** + * Progressbar Color ... + */ + @Input() + progressbarColor: XColorIdentifier = XColorWithBrightness.Warning; + showProgrssbar = false; + + /** + * Action Icon Names Specifiecations ... + */ + @Input() + cancelIcon = XIconNames.close; + + @Input() + backIcon = XIconNames.arrow_back; + + // + slotterActionProvider = new Subject(); + //#endregion + + // + //#region Provider ... + getToolbarTitle() { + // + let result = ''; + + // + if (!isNullOrUndefined(this.toolbarTitle)) { + result = this.resourceProvider(this.toolbarTitle); + } + + // + return result; + } + //#endregion + + // + //#region Handlers ... + /** + * Use for Handle Modela Closing Action based on Context ... + * @returns + */ + async handleBackPressed(action?: VTagInputReturnActionIdentifier) { + // + if ( + this.loading || + this.uiDisabled || + !isNullOrUndefined(this.error) || + isNullOrUndefined(this.context) || + isNullOrEmptyString(this.context) + ) { + return; + } + + // + await this.handleCloseDialog(action); + } + + /** + * Handle Model State Changed ... + * + * @param event + */ + handleTagValueChanged(event: VModelState) { + // + this.edittedModel = { + ...(event.model as Required), + }; + } + + async handleDtoActionFired(event: VBaseDtoModelFiredActionModel) { + // + if (event.action === VBaseDtoModelAction.AddOrUpdate) { + // + let model = undefined; + if (event.payload.isValidForAdd || event.payload.isValidForUpdate) { + model = { + ...this.edittedModel, + }; + } + + // + await this.handleCloseDialog(model); + } else if (event.action === VBaseDtoModelAction.Cancel) { + // + await this.doLockBreakAction(async () => { + await this.handleBackPressed(undefined); + }); + } + } + + /** + * Use for Handle Modal Closing Action based on Context ... + * + * @returns + */ + private async handleCloseDialog( + action?: VTagInputReturnActionIdentifier | any, + ) { + // + if (isNullOrUndefined(this.context) || isNullOrEmptyString(this.context)) { + return; + } + + // + switch (this.context) { + // + case VTagInputModalContext.Modal: + await this.managerService.dialogService.modalController.dismiss(action); + break; + + // + case VTagInputModalContext.PopOver: + await this.managerService.dialogService.popoverController.dismiss( + action, + ); + break; + } + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\tags\v-tag-input\v-tag-input.typings.ts + +export enum VTagInputModalContext { + Modal = 'modal', + PopOver = 'pop_over', +} + +export type VTagInputModalContextIdentifier = VTagInputModalContext | string; + +export enum VTagInputCssVar { + DialogToolbarHeight = 'dialog-toolbar-height', +} + +export enum VTagInputReturnAction { +} + +export type VTagInputReturnActionIdentifier = VTagInputReturnAction | string; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\tags\v-tags-presenter\v-tags-presenter.component.html + + + + + + + + +
+ +
+ + +
+ + + +
+
+ + + +
+ + + + + + + + + + + + +
+
+ + + + + + + + + + +
+ +
+
+ + + +
+
+ + + + + + + +
+
+
+ + + +
+ + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + +
+ +
+
+ + + + + {{ getResourceValue(title) | async }} + + + + + +
{{ tag }}
+
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\tags\v-tags-presenter\v-tags-presenter.component.scss + +.v-tags-label { + // + &::after { + content: ":"; + } + + // + padding: { + top: 3px; + bottom: 3px; + } +} + +.v-component-wrapper { + padding: { + bottom: 6px; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\tags\v-tags-presenter\v-tags-presenter.component.ts + +import { + Input, + Output, + Component, + EventEmitter, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + isNumber, + toNumber, + isFunction, + nameofInstance, + toNormalString, + XColorIdentifier, + isNullOrUndefined, + isNullOrEmptyString, + XResourceIdentifier, +} from 'x-framework-core'; +import { + XListItem, + XListAction, + XListActionModel, + XActionBarItemSlot, + XListItemOptionSelectedEvent, +} from 'x-framework-components'; +import { + VTagsPresenterAction, + isVTagsPresenterAction, + VTagsPresenterModelState, + VTagsPresenterPresentType, + VTagsPresenterActionModel, + isVTagsPresenterPeresentType, + VTagsPresenterPresentTypeIdentfier, +} from './v-tags-presenter.typings'; +import { Observable, Subject } from 'rxjs'; +import { defaultTagDto, XTagDto } from 'x-saherelm-api-sdk'; +import { VBaseDtoPresentType } from '../../v-dto/v-dto.typings'; +import { VBaseInCardComponent } from '../../classes/v-base.component'; +import { VTagInputComponent } from '../v-tag-input/v-tag-input.component'; +import { VTagInputModalContext } from '../v-tag-input/v-tag-input.typings'; +import { XSimpleActionBarItem } from '../../typings/simple.actionbar.typings'; +import { defaultRemoveSlideOption } from '../../constants/slide.options.defaults'; +import { XSimpleListItemSlideOption } from '../../typings/simple.list-item.typings'; + +@Component({ + selector: 'v-tags-presenter', + templateUrl: './v-tags-presenter.component.html', + styleUrls: ['./v-tags-presenter.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class VTagsPresenterComponent extends VBaseInCardComponent { + // + //#region Props ... + readonly TagsPresenterPresentTypes = Object.assign( + {}, + VTagsPresenterPresentType, + ); + readonly TagPresentTypes = Object.assign({}, VBaseDtoPresentType); + + @Input() + showLabel: boolean = true; + + @Input() + cssClass: Array = []; + + @Input() + title: XResourceIdentifier = this.AppResourceIDs.tags; + + @Input() + titleColor: XColorIdentifier = this.ColorNames.Tertiary; + + @Input() + presentType: VTagsPresenterPresentTypeIdentfier = + VTagsPresenterPresentType.AsChips; + + @Input() + tagLabelColor: XColorIdentifier = this.ColorNames.TertiaryShade; + + @Input() + tagBackgroundColor: XColorIdentifier = this.ColorNames.LightShade; + + @Input() + tagsReader: Observable> = undefined; + + @Input() + tagActionsProvider: (tag: string) => Array = ( + t, + ) => { + return [ + // + // Remove ... + { + ...defaultRemoveSlideOption, + icon: this.IconNames.clear, + }, + ]; + }; + + @Input() + actionProvider = new Subject(); + + @Input() + canAttach: boolean = false; + actionBarItems: Array = []; + + /** + * Model State Changed Notifier ... + */ + @Output() + modelStateChanged = new EventEmitter(); + + // + tags: Array = []; + originalTags: Array = []; + tagsListItems: Array> = []; + listActionProvider = new Subject(); + //#endregion + + // + //#region LifeCycles ... + async afterViewInit(): Promise { + super.afterViewInit(); + + // + await this.handleReadingTags(); + this.handleRegisterActionProvider(); + } + + async onChange(changeKeys: string[]): Promise { + super.onChange(changeKeys); + + // + let isChanged = false; + let applyChanges = false; + + // + // Tags Reader ... + isChanged = changeKeys.includes(nameofInstance(this, 'tagsReader')); + if (isChanged) { + // + if (!isNullOrUndefined(this.tagsReader)) { + await this.handleReadingTags(); + } + } + + // + // Can Attach ... + isChanged = changeKeys.includes(nameofInstance(this, 'canAttach')); + if (isChanged) { + // + if (this.canAttach) { + // + applyChanges = true; + this.prepareActionBarItems(); + } + } + + // + // Tag Action Provider ... + isChanged = changeKeys.includes(nameofInstance(this, 'tagActionsProvider')); + if (isChanged) { + // + if ( + isNullOrUndefined(this.tagActionsProvider) || + !isFunction(this.tagActionsProvider) + ) { + // + applyChanges = true; + this.tagActionsProvider = (t) => { + return [ + // + // Remove ... + { + ...defaultRemoveSlideOption, + icon: this.IconNames.clear, + }, + ]; + }; + } + } + + // + // Present Type ... + isChanged = changeKeys.includes(nameofInstance(this, 'presentType')); + if (isChanged) { + // + if ( + isNullOrUndefined(this.presentType) && + !isVTagsPresenterPeresentType(this.presentType) + ) { + this.presentType = VTagsPresenterPresentType.AsItems; + } + + // + await this.handlePresentTypeChanged(); + } + + // + // Action Provider ... + isChanged = changeKeys.includes(nameofInstance(this, 'actionProvider')); + if (isChanged) { + // + if (isNullOrUndefined(this.actionProvider)) { + // + applyChanges = true; + this.actionProvider = new Subject(); + } + + // + this.handleRegisterActionProvider(); + } + + // + // Title Color ... + isChanged = changeKeys.includes(nameofInstance(this, 'titleColor')); + if (isChanged) { + // + if (isNullOrEmptyString(this.titleColor)) { + // + applyChanges = true; + this.titleColor = this.ColorNames.Tertiary; + } + } + + // + // Tag Label Color ... + isChanged = changeKeys.includes(nameofInstance(this, 'tagLabelColor')); + if (isChanged) { + // + if (isNullOrEmptyString(this.tagLabelColor)) { + // + applyChanges = true; + this.tagLabelColor = this.ColorNames.TertiaryShade; + } + } + + // + // Tag Background Color ... + isChanged = changeKeys.includes(nameofInstance(this, 'tagBackgroundColor')); + if (isChanged) { + // + if (isNullOrEmptyString(this.tagBackgroundColor)) { + // + applyChanges = true; + this.tagBackgroundColor = this.ColorNames.LightShade; + } + } + + // + if (applyChanges) { + this.detectChanges(); + } + } + //#endregion + + // + //#region Providers ... + canPresent() { + return ( + !isNullOrUndefined(this.presentType) && + isVTagsPresenterPeresentType(this.presentType) + ); + } + + hasTagActions(tag: string) { + // + let result = false; + + // + if (!isNullOrEmptyString(tag)) { + // + const actions = this.tagActionsProvider(tag); + result = !isNullOrUndefined(actions) && this.hasChild(actions); + } + + // + return result; + } + + getTagActions(tag: string) { + return this.tagActionsProvider(tag); + } + //#endregion + + // + //#region Handlers ... + /** + * Reading Tags List ... + * + * @returns + */ + async handleReadingTags() { + // + if (isNullOrUndefined(this.tagsReader)) { + return; + } + + // + const tags = await this.getValueAsync(this.tagsReader); + if (!isNullOrUndefined(tags) && this.hasChild(tags)) { + // + this.tags = [...tags]; + this.originalTags = [...tags]; + + // + await this.handlePresentTypeChanged(); + } + } + + /** + * Handle Present Type Changed ... + * + * @returns + */ + async handlePresentTypeChanged() { + // + this.tagsListItems = []; + + // + if ( + !this.hasChild(this.tags) || + isNullOrUndefined(this.presentType) || + !isVTagsPresenterPeresentType(this.presentType) || + this.presentType === this.TagsPresenterPresentTypes.AsNone + ) { + return; + } + + // + if (this.presentType === this.TagsPresenterPresentTypes.AsList) { + // + this.tagsListItems = [ + ...this.tags.map((t) => { + return { + data: t, + slideOptions: this.tagActionsProvider(t), + } as XListItem; + }), + ]; + + // + this.detectChanges(); + this.listActionProvider.next({ action: XListAction.Refresh }); + } + } + + /** + * Handle Adding new Tag ... + * + * @param tag + * @returns + */ + handleAddTag(tag: XTagDto) { + // + // Validate ... + if (isNullOrUndefined(tag) && isNullOrEmptyString(tag.tag)) { + return; + } + + // + // Check Tag Exists ... + var isExists = + !isNullOrUndefined(this.tags) && + this.hasChild(this.tags) && + this.tags.findIndex( + (t) => toNormalString(t) === toNormalString(tag.tag), + ) >= 0; + if (isExists) { + return; + } + + // + // Update Lists ... + + // + this.tags = [...this.tags, tag.tag]; + + // + if (this.presentType === this.TagsPresenterPresentTypes.AsList) { + // + this.tagsListItems = [ + ...this.tagsListItems, + { + data: tag.tag, + slideOptions: this.tagActionsProvider(tag.tag), + }, + ]; + + // + this.listActionProvider.next({ action: XListAction.Refresh }); + setTimeout( + () => this.listActionProvider.next({ action: XListAction.Refresh }), + 200, + ); + setTimeout( + () => this.listActionProvider.next({ action: XListAction.Refresh }), + 500, + ); + } + + // + this.detectChanges(); + this.handlePrepareTagsState(); + } + + /** + * Handle Actions ... + * + * @param tag + * @param action + */ + handleActionClicked(tag: string, action: XSimpleListItemSlideOption) { + // + // Validate ... + if ( + this.loading || + this.uiDisabled || + isNullOrEmptyString(tag) || + isNullOrUndefined(action) || + isNullOrEmptyString(action.id) + ) { + return; + } + + // + // Remove ... + if (action.id === defaultRemoveSlideOption.id) { + // + let tagIDX = + !isNullOrUndefined(this.tags) && this.hasChild(this.tags) + ? this.tags.findIndex( + (t) => toNormalString(t) === toNormalString(tag), + ) + : -1; + let isExists = tagIDX >= 0; + if (isExists) { + // + this.tags = [ + ...this.tags.filter((t) => toNormalString(t) !== toNormalString(tag)), + ]; + + // + // Checking in List Items ... + let tagIDX = this.tagsListItems.findIndex( + (tli) => toNormalString(tli.data) === toNormalString(tag), + ); + if (tagIDX >= 0) { + // + this.tagsListItems = [ + ...this.tagsListItems.filter( + (tli) => toNormalString(tli.data) !== toNormalString(tag), + ), + ]; + + // + this.listActionProvider.next({ action: XListAction.Refresh }); + setTimeout( + () => this.listActionProvider.next({ action: XListAction.Refresh }), + 200, + ); + setTimeout( + () => this.listActionProvider.next({ action: XListAction.Refresh }), + 500, + ); + } + + // + this.detectChanges(); + this.handlePrepareTagsState(); + } + } + } + + /** + * Prepare and Notify Tag State ... + * + * @returns + */ + handlePrepareTagsState() { + // + const hasOriginals = + !isNullOrUndefined(this.originalTags) && this.hasChild(this.originalTags); + + // + // Detecting News ... + let hasNews = false; + let news: Array = []; + if (this.hasChild(this.tags)) { + // + news = this.tags.filter( + (t) => + !this.hasChild(this.originalTags) || + (this.hasChild(this.originalTags) && + this.originalTags.findIndex( + (ot) => toNormalString(t) === toNormalString(ot), + ) < 0), + ); + + // + hasNews = this.hasChild(news); + } + + // + // Detecting Removeds ... + let hasRemoveds = false; + let removeds: Array = []; + if (hasOriginals) { + // + removeds = this.originalTags.filter( + (ot) => + this.tags.findIndex((t) => toNormalString(ot) === toNormalString(t)) < + 0, + ); + + // + hasRemoveds = this.hasChild(removeds); + } + + // + let result: VTagsPresenterModelState = { + hasNews, + hasRemoveds, + hasOriginals, + // + news, + removeds, + originals: [...this.originalTags], + }; + + // + this.modelStateChanged.emit(result); + this.listActionProvider.next({ action: XListAction.Refresh }); + + // + return result; + } + + /** + * Handle List Item Options Clicked ... + * + * @param event + * @returns + */ + handleListItemsOptionSelected(event: XListItemOptionSelectedEvent) { + // + if (isNullOrUndefined(event)) { + return; + } + + // + if (event.id === defaultRemoveSlideOption.id) { + this.handleActionClicked(event.data.data, defaultRemoveSlideOption); + } + } + //#endregion + + // + //#region Private ... + /** + * Preparing Action Bar ... + */ + private prepareActionBarItems() { + // + this.actionBarItems = [ + // + // Attach ... + { + index: 0, + id: 'Add', + onlyIcon: true, + icon: this.IconNames.add, + title: this.ResourceIDs.add, + slot: XActionBarItemSlot.START, + iconColor: this.ColorNames.SuccessShade, + handler: async () => { + // + let modal = await this.managerService.dialogService.presentModal({ + autoHeight: true, + fullScreen: false, + backdropDismiss: false, + component: VTagInputComponent, + componentProps: { + context: VTagInputModalContext.Modal, + model: { + ...defaultTagDto, + }, + toolbarTitle: this.AppResourceIDs.tag, + presentType: VBaseDtoPresentType.AsForm, + }, + }); + + /** + * wait for Model Result ... + */ + const data = await modal.onDidDismiss(); + modal.remove(); + modal.dismiss(); + + /** + * Handle Retrieve Data ... + */ + if (!isNullOrUndefined(data.data)) { + this.handleAddTag(data.data); + } + }, + disabled: this.loading || this.uiDisabled || !this.canAttach, + } as XSimpleActionBarItem, + ]; + } + + // + //#region Action Provider ... + /** + * Handle Action Provider Registration ... + */ + private handleRegisterActionProvider() { + // + if (isNullOrUndefined(this.actionProvider)) { + return; + } + + // + this.applyTakeUntilWrapper(this.actionProvider.asObservable()).subscribe( + async (event) => { + // + // Validate ... + if ( + isNullOrUndefined(event) || + isNullOrEmptyString(event.action) || + !isVTagsPresenterAction(event.action) + ) { + return; + } + + // + await this.handleAction(event.action, event.payload); + }, + ); + } + + /** + * Send Specified Action through Action provider ... + * + * @param action + * @param payload + * @returns + */ + private sendAction(action: VTagsPresenterAction, payload?: any) { + // + if ( + isNullOrUndefined(this.actionProvider) || + isNullOrEmptyString(action) || + !isVTagsPresenterAction(action) + ) { + return; + } + + // + this.actionProvider.next({ action, payload }); + } + + /** + * Handle Specified Actions ... + * + * @param action + * @param payload + */ + private async handleAction(action: VTagsPresenterAction, payload?: any) { + // + // Refresh ... + if (action === VTagsPresenterAction.Refresh) { + // + if (isNullOrUndefined(payload)) { + this.detectChanges(); + } else if (!isNullOrUndefined(payload) && isNumber(payload)) { + // + this.detectChanges(); + setTimeout(() => this.detectChanges(), toNumber(payload)); + } + } + // + // Reset ... + else if (action === VTagsPresenterAction.Reset) { + // + this.tags = [...this.originalTags]; + + // + this.tagsListItems = [ + ...this.originalTags.map((ot) => { + return { + data: ot, + slideOptions: this.tagActionsProvider(ot), + } as XListItem; + }), + ]; + + // + this.detectChanges(); + this.listActionProvider.next({ action: XListAction.Refresh }); + setTimeout( + () => this.listActionProvider.next({ action: XListAction.Refresh }), + 500, + ); + this.handlePrepareTagsState(); + } + // + // ReState ... + else if (action === VTagsPresenterAction.ReState) { + this.handlePrepareTagsState(); + } + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\tags\v-tags-presenter\v-tags-presenter.typings.ts + +import { isValueInEnum } from '../../tools/v-base.tools'; +import { XBaseActionModel } from 'x-framework-components'; + +// +//#region Present Type ... +export enum VTagsPresenterPresentType { + AsNone = 'as_none', + AsList = 'as_list', + AsItems = 'as_items', + AsChips = 'as_chips', +} + +export type VTagsPresenterPresentTypeIdentfier = + | VTagsPresenterPresentType + | string; + +export function isVTagsPresenterPeresentType(value: string) { + return isValueInEnum(value, Object.assign({}, VTagsPresenterPresentType)); +} +//#endregion + +// +//#region Action Provider ... +export enum VTagsPresenterAction { + Reset = 'Reset', + Refresh = 'Refresh', + ReState = 'ReState', +} + +export type VTagsPresenterActionIdentifier = VTagsPresenterAction | string; + +export interface VTagsPresenterActionModel + extends XBaseActionModel {} + +export function isVTagsPresenterAction(value: string) { + return isValueInEnum(value, Object.assign({}, VTagsPresenterAction)); +} +//#endregion + +// +//#region Tags State ... +/** + * an Interface to Notify Tags Changed ... + */ +export interface VTagsPresenterModelState { + hasOriginals: boolean; + hasNews: boolean; + hasRemoveds: boolean; + originals?: Array; + news?: Array; + removeds?: Array; +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\tests\test-video-player.html + + + + + + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\tests\v-icons-presenter\template.html + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\tests\v-icons-presenter\v-icons-presenter.component.html + + + + + + + + +
+ +
+ + +
+ + + +
+
+ + + +
+ + + +
+
+ + + +
+ + {{ item.data }} +
+
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\tests\v-icons-presenter\v-icons-presenter.component.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\tests\v-icons-presenter\v-icons-presenter.component.ts + +import { XIconIdentifier, XListItem } from 'x-framework-components'; +import { VBaseInCardComponent } from '../../classes/v-base.component'; +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; + +@Component({ + selector: 'v-icons-presenter', + templateUrl: './v-icons-presenter.component.html', + styleUrls: ['./v-icons-presenter.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class VIconsPresenterComponent extends VBaseInCardComponent { + // + //#region Props ... + @Input() + cssClass: Array; + + iconListItems: XListItem[] = []; + //#endregion + + // + //#region LifeCycles ... + afterViewInit(): void { + super.afterViewInit(); + + // + this.handlePrepareIcons(); + } + //#endregion + + // + //#region Handlers ... + handlePrepareIcons() { + // + let iconNames = Object.keys(this.IconNames); + iconNames = iconNames.sort((a,b) => a.localeCompare(b)); + console.log('iconNames: ', iconNames); + + // + let items = iconNames.map((i) => { + return { + data: i, + } as XListItem; + }); + + // + this.iconListItems = [...items]; + this.detectChanges(); + } + + async handleIconSelected(item: XListItem) { + // + // text: this.resourceProvider(XResourceIDs.dismiss), + const iconText = `<x-icon name='${item.data}'></x-icon>`; + const iconCode = ``; + await this.managerService.notificationService.presentInfoNotification({ + message: iconText, + dissmissable: true, + opt: { + duration: 4000, + buttons: [ + { + icon: this.IconNames.clipboard, + side: 'end', + role: 'set', + handler: () => { + // + console.log(iconCode); + const isSaved = this.managerService.saveToClipboard(iconCode); + if (isSaved) { + this.managerService.notificationService.presentDefaultSuccessNotification(); + } + }, + }, + { + icon: this.IconNames.close, + side: 'end', + role: 'cancel', + }, + ], + }, + }); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\tools\action-provider.tools.ts + +import { Subject } from 'rxjs'; +import { XBaseActionModel } from 'x-framework-components'; +import { isNullOrEmptyString, isNullOrUndefined } from 'x-framework-core'; + +export function sendAction(arg: { + provider: Subject>; + action: string; + payload?: any; + tries?: number; + delay?: number; +}) { + // + // Validate ... + let isValid = + !isNullOrUndefined(arg) && + !isNullOrUndefined(arg.provider) && + !isNullOrEmptyString(arg.action); + if (!isValid) { + return; + } + + // + // Normalize Args ... + + // + if (isNullOrUndefined(arg.tries)) { + arg.tries = 3; + } + + // + if (isNullOrUndefined(arg.delay)) { + arg.delay = 200; + } + + // + arg.provider.next({ action: arg.action, payload: arg.payload }); + for (let i = 0; i < arg.tries; i++) { + // + let delay = (i + 1) * arg.delay; + setTimeout(() => { + arg.provider.next({ action: arg.action, payload: arg.payload }); + }, delay); + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\tools\date.tools.ts + +import { + XLocale, + formatDate, + applyLocale, + getErrorDateFormatString, + getRegularDateFormatString, + getDateOfBirthDateFormatString, +} from 'x-framework-core'; +import { XManagerService } from 'x-framework-services'; + +export enum XDateFormat { + ErroDate = 'error', + RegularDate = 'regular', + EventDate = 'event_date', + DateOfBirth = 'dateofbirth', +} + +export type XDateFormatIdentifier = XDateFormat | string; + +export function getDateFormatter( + type: XDateFormatIdentifier, + locale?: XLocale +) { + // + let result: string = getRegularDateFormatString(locale); + + // + switch (type) { + // + case XDateFormat.RegularDate: + result = getRegularDateFormatString(locale); + break; + + // + case XDateFormat.ErroDate: + result = getErrorDateFormatString(locale); + break; + + // + case XDateFormat.EventDate: + result = getEventDateTimeFromatString(locale); + break; + + // + case XDateFormat.DateOfBirth: + result = getDateOfBirthDateFormatString(locale); + break; + } + + // + return result; +} + +export function applyFormatDate( + value: Date, + format: XDateFormatIdentifier, + managerService: XManagerService, + localized = true, + isUTC = true +) { + // + const locale = managerService.currentLocale; + const formatter = getDateFormatter(format, locale); + let result = formatDate(value, locale, formatter, isUTC); + + // + if (!!localized) { + result = applyLocale(result, locale); + } + + // + return result; +} + +export function getEventDateTimeFromatString(locale?: XLocale) { + // + if (locale === 'fa-IR') { + return 'HH:mm jYYYY/jMM/jDD'; + } + + // + return 'YYYY-MM-DD HH:mm'; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\tools\v-base.tools.ts + +import { + XLocale, + toXString, + isValidEmail, + isNullOrUndefined, + isValidPhoneNumber, + isNullOrEmptyString, + keys, + isString, +} from 'x-framework-core'; + +/** + * generate an href string for linking to email addresses ... + * + * @param email the propper email address ... + */ +export function getEmailHref(email: string) { + // + if (isNullOrEmptyString(email) || !isValidEmail(email)) { + return ''; + } + + // + return `mailto:${email}`; +} + +/** + * generate an href string for linking to phoneNumber ... + * + * @param phoneNumber the propper phone number ... + */ +export function getPhoneNumberHref(phoneNumber: string) { + // + if (isNullOrEmptyString(phoneNumber) || !isValidPhoneNumber(phoneNumber)) { + return ''; + } + + // + return `tel:${phoneNumber}`; +} + +export function getEventDateFormatString(locale?: XLocale) { + // + if (locale === 'fa-IR') { + return 'HH:mm jYYYY/jMM/jDD'; + } + + // + return 'YYYY/MM/DD HH:mm'; +} + +export function getKeys(object: any) { + // + let result: Array = []; + + // + keys(object).forEach(key => { + // + if (isString(key)) { + result.push(key); + } + }); + + // + return result; +} + +/** + * Generic Tools Function to Check A Value inside enum Values or not ... + * + * @param value + * @param values + * @param forceExact + */ +export function isValueInEnum( + value: TValue, + values: Object, + forceExact: boolean = true +) { + // + let result = false; + + // + if ( + !isNullOrUndefined(value) && + !isNullOrUndefined(values) && + !isNullOrEmptyString(toXString(value)) + ) { + // + const tVals = Object.values(values).map((tv) => tv as TValue); + + // + result = tVals.includes(value); + + // + // Check Includes ... + if (!forceExact && !result) { + // + result = + tVals.findIndex((t) => toXString(value).includes(toXString(t))) >= 0; + } + } + + // + return result; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\typings\common.typings.ts + +import { + XKey, + isXKeyOf, + nameofType, + getComponentSelector, +} from 'x-framework-core'; +import { ComponentRef, InjectionToken, Injector, Type } from '@angular/core'; + +export const markdownImagePattern = /(?:[!]\[(.*?)\]\(.*?\))/g; + +export const X_COMPONENT_PROP_TOKEN = new InjectionToken('x_component_props'); + +export type TXComponentProps = { + [key in keyof T]?: any; +}; + +/** + * Describe Specific Component ... + */ +export interface XComponentDescriptor { + /** + * Use as Identifier ... + */ + payload?: any; + + /** + * Component Type ... + */ + type: Type; + + /** + * Component Props ... + */ + inputs: TXComponentProps; + + /** + * Component Property Injector ... + */ + injector?: Injector; + + /** + * Component Ref ... + */ + componentRef?: ComponentRef; +} + +// +// Extract a Query Selector of Specified Component which +// has Specified Attribute ... +export function getQuerySelectorOfSpecifiedAttribute( + type: Type, + attributeName: XKey | string, + attributeValue: string +) { + // + let attributeNameString = isXKeyOf(type, attributeName) + ? nameofType(type, attributeName as XKey) + : attributeName.toString(); + + // + let result = `${getComponentSelector( + type + )}[${attributeNameString}="${attributeValue}"]`; + + // + return result; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\typings\dto.common.typings.ts + +import { IVBaseDtoContainer } from "../v-dto/v-dto.typings"; + +/** + * Commonly Used Properties in DTO Views ... + */ +export type VDtoCommonProperties = Partial< + Pick< + IVBaseDtoContainer, + | 'labelColor' + | 'valueColor' + | 'fieldFormButtonPosition' + | 'fieldFormIcon' + | 'fieldFormIconColor' + | 'fieldFormApplyIcon' + | 'fieldFormApplyIconColor' + | 'fieldFormCancelIcon' + | 'fieldFormCancelIconColor' + | 'isInPage' + | 'wrapWithCard' + | 'cardShowFooter' + | 'cardShowActions' + | 'removeCardMargin' + | 'cardForegroundColor' + | 'cardTitle' + | 'cardSubTitle' + | 'cardCssClass' + | 'cardColor' + | 'cardActionProvider' + | 'formAppearance' + | 'formActionProvider' + | 'formRegularColor' + | 'formAccentColor' + | 'formHintColor' + | 'formActiveColor' + | 'formValidColor' + | 'formInvalidColor' + | 'formPendingColor' + | 'formFillColor' + | 'formDisabledColor' + | 'formDisabledFillColor' + | 'showFormCancelButton' + | 'formCancelButtonResource' + | 'formCancelButtonColor' + | 'formResetButtonResource' + | 'formResetButtonColor' + | 'formActionButtonAddResource' + | 'formActionButtonUpdateResource' + | 'formActionButtonColor' + > +>; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\typings\simple.actionbar.typings.ts + +import { + XStandardType, + XColorIdentifier, + XResourceIdentifier, +} from 'x-framework-core'; +import { + XIconIdentifier, + XActionBarActionFiredModel, + XActionBarItemSlotIdentifier, +} from 'x-framework-components'; + +export interface XSimpleActionBarItem { + index?: number; + id: string; + hidden?: boolean; + onlyIcon?: boolean; + disabled?: boolean; + icon: XIconIdentifier; + color?: XColorIdentifier; + title?: XResourceIdentifier; + iconColor?: XColorIdentifier; + titleColor?: XColorIdentifier; + cssClass?: Array; + slot: XActionBarItemSlotIdentifier; + handler?: (model?: XActionBarActionFiredModel) => XStandardType; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\typings\simple.fab.typings.ts + +import { + hasChild, + toPromise, + XStandardType, + XColorIdentifier, + XRouterDirection, + isNullOrUndefined, + XResourceIdentifier, + XRouterTargetIdentifier, +} from 'x-framework-core'; +import { + XFabItem, + XFabButton, + XIconIdentifier, + XFabButtonSideIdentifier, + XFabButtonSizeIdentifier, + XFabVerticalSlotIdentifier, + XFabHorizontalSlotIdentifier, +} from 'x-framework-components'; + +export interface XSimpleFabButton { + id: string; + index: number; + href?: string; + hidden?: boolean; + download?: string; + disabled?: boolean; + icon: XIconIdentifier; + color: XColorIdentifier; + tooltip?: XResourceIdentifier; + side: XFabButtonSideIdentifier; + size?: XFabButtonSizeIdentifier; + routerDirection?: XRouterDirection; + routerTarget?: XRouterTargetIdentifier; + handler?: (id?: string) => XStandardType; +} +declare type FAB_MAIN_BUTTON = Pick< + XSimpleFabButton, + | 'id' + | 'icon' + | 'size' + | 'color' + | 'hidden' + | 'handler' + | 'tooltip' + | 'disabled' + | 'href' + | 'download' + | 'routerDirection' + | 'routerTarget' +>; +export interface XSimpleFabItem extends FAB_MAIN_BUTTON { + vertical: XFabVerticalSlotIdentifier; + horizontal: XFabHorizontalSlotIdentifier; + fixed?: boolean; + edged?: boolean; + activated?: boolean; + childs?: Array; +} + +export async function toSimpleFabButton(item: XFabButton) { + // + let result: XSimpleFabButton = undefined; + if (isNullOrUndefined(item)) { + return result; + } + + // + result = { + id: item.id, + index: item.index, + handler: item.handler, + icon: await toPromise(item.icon), + side: await toPromise(item.side), + size: await toPromise(item.size), + disabled: await toPromise(item.disabled), + hidden: await toPromise(item.hidden), + color: await toPromise(item.color), + tooltip: await toPromise(item.tooltip), + href: await toPromise(item.href), + download: await toPromise(item.download), + routerDirection: await toPromise(item.routerDirection), + routerTarget: await toPromise(item.routerTarget), + }; + + // + return result; +} + +export async function toSimpleFabItems( + item: XFabItem | XStandardType +) { + // + let result: XSimpleFabItem = undefined; + if (isNullOrUndefined(item)) { + return result; + } + + // + const itemP = await toPromise(item); + if (isNullOrUndefined(itemP) || !hasChild(Object.keys(itemP))) { + return result; + } + + // + result = { + handler: itemP.handler, + id: await toPromise(itemP.id), + icon: await toPromise(itemP.icon), + size: await toPromise(itemP.size), + color: await toPromise(itemP.color), + hidden: await toPromise(itemP.hidden), + tooltip: await toPromise(itemP.tooltip), + disabled: await toPromise(itemP.disabled), + href: await toPromise(itemP.href), + download: await toPromise(itemP.download), + routerDirection: await toPromise(itemP.routerDirection), + routerTarget: await toPromise(itemP.routerTarget), + vertical: await toPromise(itemP.vertical), + horizontal: await toPromise(itemP.horizontal), + fixed: await toPromise(itemP.fixed), + edged: await toPromise(itemP.edged), + activated: await toPromise(itemP.activated), + childs: isNullOrUndefined(itemP.childs) + ? undefined + : await Promise.all( + ( + await toPromise(itemP.childs) + ).map(async (i) => await toSimpleFabButton(i)) + ), + }; + + // + return result; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\typings\simple.form.typings.ts + +import { + XLocale, + XBaseDto, + XColorIdentifier, + XResourceIdentifier, +} from 'x-framework-core'; +import { + XFormInputFormatterIdentifier, + XStringInputFormatTypeIdentifier, +} from 'x-framework-services'; +import { Subject } from 'rxjs'; +import { + XIconIdentifier, + XFormControlTypeConfig, + XFormUpdateOnIdentifier, + XFormValidatorDescriptor, + XFormControlTypeIdentifier, + XFormControlEventsDescriptor, + XControlIconPositionIdentifier, + XFormControlActionProviderModel, + XFormControlAppearanceIdentifier, + XFormControlLabelPositionIdentifier, +} from 'x-framework-components'; +import { TemplateRef } from '@angular/core'; + +export interface XSimpleFormErrorHandler { + errorName: string; + errorMessage: XResourceIdentifier; + errorDataProvider?: (errorObject: any) => XResourceIdentifier; +} + +export interface XSimpleFormControlTypeDescriptor { + type: XFormControlTypeIdentifier; + config?: XFormControlTypeConfig; +} + +export interface XSimpleControlIconDescriptor { + handler?: () => void; + name?: XIconIdentifier; + color?: XColorIdentifier; + applyStateColor?: boolean; + tooltip?: XResourceIdentifier; + position?: XControlIconPositionIdentifier; +} + +export interface XSimpleFormIconDescriptor + extends Pick< + Partial, + 'handler' | 'name' | 'color' | 'applyStateColor' | 'tooltip' + > {} + +export interface XSimpleFormControlIconDescriptor { + suffix?: XSimpleFormIconDescriptor; + prefix?: XSimpleFormIconDescriptor; +} + +export interface XSimpleFormControlFormatterConfig { + locale?: XLocale; + seperator?: string; + type: XFormInputFormatterIdentifier; + inputType?: XStringInputFormatTypeIdentifier; + currencyLable?: XResourceIdentifier; + stringFormatter?: Array; +} + +export interface XSimpleFormControlProgressbarConfig { + color: XColorIdentifier; +} + +export interface XSimpleFormControlAutoCompleteConfig< + T extends XBaseDto = any +> { + opened?: boolean; + color?: XColorIdentifier; + onSelect?: (item: T) => void; + items: () => Array; + itemTemplate: TemplateRef; + cssClass?: Array; + onEscape?: () => void; + onOutsideClicked?: () => void; +} + +export interface XSimpleFormControlAppearanceDescriptor { + hidden?: boolean; + disabled?: boolean; + hint?: XResourceIdentifier; + label: XResourceIdentifier; + tooltip?: XResourceIdentifier; + placeholder?: XResourceIdentifier; + cssClass?: Array; + icons?: XSimpleFormControlIconDescriptor; + formatter?: XSimpleFormControlFormatterConfig; + progressBar?: XSimpleFormControlProgressbarConfig; + autoComplete?: XSimpleFormControlAutoCompleteConfig; + appearance?: XFormControlAppearanceIdentifier; + labelPosition?: XFormControlLabelPositionIdentifier; +} + +// +//#region Controls Simple Config ... +// +//#region Select ... +export interface XSimpleFormSelectControlOption { + value: T; + viewValue: string; +} +export interface XSimpleFormSelectControlConfig { + options?: Array>; + multiple?: boolean; +} +//#endregion +//#endregion + +export interface XSimpleFormControlConfig { + index: number; + propName: keyof Partial; + focusHandler?: Subject; + type: XSimpleFormControlTypeDescriptor; + updateOn?: XFormUpdateOnIdentifier; + validators?: XFormValidatorDescriptor; + actionProvider?: Subject; + eventHandlers?: XFormControlEventsDescriptor; + appearance: XSimpleFormControlAppearanceDescriptor; + errorHandlers?: Array; +} + +export interface XSimpleFormConfig { + name: string; + model?: Partial; + updateOn?: XFormUpdateOnIdentifier; + validators?: XFormValidatorDescriptor; + controls: Array>; + errorHandlers?: Array; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\typings\simple.list-item.typings.ts + +import { + toArray, + hasChild, + toPromise, + XStandardType, + XColorIdentifier, + isNullOrUndefined, + XResourceIdentifier, +} from 'x-framework-core'; +import { + XListItem, + XIconIdentifier, + XListItemOptionSlot, + XListItemSlideOption, +} from 'x-framework-components'; + +/** + * Simple List Item ... + */ +export interface XSimpleListItem { + data: T; + hide?: boolean; + disabled?: boolean; + isSelected?: boolean; + href?: Array; + showDetailedIcon?: boolean; + slideOptions?: Array; +} + +/** + * Simple List Item ... + */ +export interface XSimpleListItemSlideOption { + id: string; + icon?: XIconIdentifier; + title?: XResourceIdentifier; + color?: XColorIdentifier; + onlyIcon?: boolean; + slot: XListItemOptionSlot; + handler?: (info?: any) => XStandardType; + disabled?: boolean; +} + +/** + * Converts a ListItemSlideOption to Simple Mode ... + * + * @param option + * @returns + */ +export async function toSimpleListItemSlideOption( + option: XListItemSlideOption +) { + // + let result: XSimpleListItemSlideOption = undefined; + + // + if (!isNullOrUndefined(option)) { + // + result = { + id: option.id, + handler: option.handler, + icon: await toPromise(option.icon), + slot: await toPromise(option.slot), + title: await toPromise(option.title), + color: await toPromise(option.color), + disabled: await toPromise(option.disabled), + onlyIcon: await toPromise(option.onlyIcon), + }; + } + + // + return result; +} + +/** + * Converts a ListItem to Simple Mode ... + * + * @param item + * @returns + */ +export async function toSimpleListItem(item: XListItem) { + // + let result: XSimpleListItem = undefined; + + // + if (!isNullOrUndefined(item)) { + // + result = { + data: await toPromise(item.data), + hide: await toPromise(item.hide), + disabled: await toPromise(item.disabled), + isSelected: await toPromise(item.isSelected), + showDetailedIcon: await toPromise(item.showDetailedIcon), + href: !isNullOrUndefined(item.href) ? toArray(item.href) : undefined, + slideOptions: + isNullOrUndefined(item.slideOptions) || !hasChild(item.slideOptions) + ? [] + : await Promise.all( + item.slideOptions.map( + async (sl) => await toSimpleListItemSlideOption(sl) + ) + ), + }; + } + + // + return result; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\typings\simple.page.typings.ts + +import { XResourceIdentifier } from "x-framework-core"; + +export interface XSimplePage { + id: string; + name: string; + icon?: string; + baseRoute: string; + route: Array; + childs?: Array; + title: XResourceIdentifier; + description?: XResourceIdentifier; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\typings\v-base-form.typings.ts + +import { XBaseActionModel } from 'x-framework-components'; + +export enum VBaseFormViewPresentType { + AsNone = 'as_none', + AsForm = 'as_form', + AsDescriptionList = 'as_description_list', +} + +export type VBaseFormViewPresentTypeIdentifier = + | VBaseFormViewPresentType + | string; + +export enum VBaseFormViewAction { + // + Refresh = 'refresh', + SetError = 'set_error', + ClearError = 'clear_error', + ClearForm = 'clear_form', + ResetForm = 'reset_form', +} + +export type VBaseFormViewActionIdentifier = VBaseFormViewAction | string; + +export interface VBaseFormViewActionModel + extends XBaseActionModel {} + +export interface VBaseFormViewCustomStates { + /** + * State Management Result ... + */ + isBase: boolean; + isValid: boolean; + isDefault: boolean; + isChanged: boolean; + + /** + * payload carry changed value ... + */ + payload?: any; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\typings\x.state.enum.ts + +export enum XAppState { + UserProfile = 'user_profile', +} + +export type AppStateIdentifier = XAppState | string; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\v-dto\v-dto.component.html + + + + + + + + +
+ +
+ + +
+ + + +
+
+ + + +
+ + + + + + +
+ +
+ + +
+ +
+ + +
+ +
+
+
+ + + + + + + +
+ + +
+ + + + + + + + + + + + + + + + +
+ + + + + + + + + + {{ dtoContainer.getFieldProvidedValue(key) }} + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+ + + +
+ + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + + + + + + + + + + {{ dtoContainer.getFieldProvidedLabel(key) }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ {{ err }} +
+
+
+
+ + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\v-dto\v-dto.component.scss + +:host { + --applied-color: var(--x-color-primary-contrast); +} + +.v-dto-field { + width: 100%; + max-width: 100%; + display: block; + overflow: hidden; + white-space: nowrap; + + margin: { + top: 6px; + bottom: 6px; + } +} + +.v-dto-field-label::after { + content: ":"; +} + +.v-dto-field-form, +.v-dto-field-label, +.v-dto-field-value, +.v-dto-field-pesent-as-form { + // + display: inline-block; + + // + margin: { + left: 3px; + right: 3px; + } +} + +.v-dto-field-value, +.v-dto-field-label { + height: 100%; + line-height: 100%; + font-size: small; +} + +.v-dto-field-value { + width: calc(90%); + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +.v-dto-field-form-slot { + width: 100%; +} + +.v-dto-field-form-slot:not(.rtl) { + padding-right: 60px; +} + +.v-dto-field-form-slot.rtl { + padding-left: 6px; +} + +.v-dto-actions, +.v-dto-validation-errors { + padding: 16px; +} + +.v-dto-validation-error { + padding-bottom: 8px; +} + +// +:host ::ng-deep .v-dto-field-present-toggelr { + + *, + .x-button { + width: 16px; + height: 16px; + line-height: 16px; + } + + x-icon { + --size: 14px; + } +} + +// +::ng-deep.mat-datepicker-popup { + .x-form-control-date-picker { + + // + &.mat-calendar { + // + color: var(--applied-color) !important; + + // + .mat-calendar-body-cell-content { + color: var(--applied-color) !important; + } + } + } +} + +// +::ng-deep.mat-select-panel-wrap { + .x-form-select-control { + + // + .x-select-option { + // + color: var(--applied-color) !important; + background-color: var(--fill-color) !important; + + // + .mat-option-pseudo-checkbox { + color: var(--applied-color) !important; + } + + // + &.mat-active { + color: var(--active-color) !important; + } + + // + &.mat-selected { + // + color: var(--active-color) !important; + + // + .mat-pseudo-checkbox-checked { + background: var(--active-color) !important; + } + + // + .mat-pseudo-checkbox::after { + color: var(--accent-color) !important; + } + } + + // + &:hover { + $level: 0.5; + -webkit-filter: brightness($level); + filter: brightness($level); + color: var(--active-color) !important; + } + } + } +} + +/** +* this Styling used for Ignore Mat Form Field Outline Gap Issues ... +**/ +:host ::ng-deep .mat-form-field-appearance-outline.mat-form-field { + + // + .mat-form-field-outline-start { + width: 0 !important; + } + + // + .mat-form-field-outline-end { + width: 0 !important; + } + + // + .mat-form-field-outline-gap { + width: 0 !important; + } + + // + mat-label.x-field-label { + // + padding-left: 3px !important; + padding-right: 3px !important; + background-color: var(--outline-bg-color) !important; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\v-dto\v-dto.component.ts + +import { + Input, + ViewChild, + Component, + TemplateRef, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + VModelFieldTypes, + isVBaseDtoAction, + VBaseDtoComponent, + VBaseDtoPresentType, + VBaseDtoModelAction, + VBaseDtoFieldViewAction, + VBaseDtoFieldFormPresentationIconPosition, +} from './v-dto.typings'; +import { + nameof, + parseXColor, + isNullOrUndefined, + isNullOrEmptyString, + getXColorCssVariableName, +} from 'x-framework-core'; +import { + XSlotName, + XButtonType, + XSlotLayout, + XButtonComponent, +} from 'x-framework-components'; +import { VBaseComponent } from '../classes/v-base.component'; + +@Component({ + selector: 'v-dto', + templateUrl: './v-dto.component.html', + styleUrls: ['./v-dto.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class VDto extends VBaseComponent { + // + //#region Props ... + readonly SlotNames = Object.assign({}, XSlotName); + readonly FieldFormButtonPositions = Object.assign( + {}, + VBaseDtoFieldFormPresentationIconPosition + ); + readonly ButtonTypes = Object.assign({}, XButtonType); + readonly SlotLayouts = Object.assign({}, XSlotLayout); + readonly PresentTypes = Object.assign({}, VBaseDtoPresentType); + readonly ModelFieldTypes = Object.assign({}, VModelFieldTypes); + readonly FieldViewActions = Object.assign({}, VBaseDtoFieldViewAction); + + @Input() + dtoContainer: VBaseDtoComponent; + + @ViewChild('btnAddOrUpdateDto') + btnAddOrUpdateDto: XButtonComponent; + + @ViewChild('btnCancelModelEdit') + btnCancelModelEdit: XButtonComponent; + + @ViewChild('nothingPresentTypeRef') + nothingPresentTypeRef: TemplateRef; + //#endregion + + // + //#region LifeCycles ... + onInit(): void { + super.onInit(); + } + + onChange(changeKeys: string[]): void { + super.onChange(changeKeys); + + // + let isChanged = false; + + // + isChanged = changeKeys.includes(nameof('dtoContainer')); + if (isChanged) { + // + this.handleSetCSSVars(); + this.handleSubscribeToDtoFormAction(); + this.handleSubscribeToChangeNotifier(); + this.registerDtoContainerActionProvider(); + } + } + + afterViewInit(): void { + super.afterViewInit(); + + // + this.handleSetCSSVars(); + this.handleSubscribeToDtoFormAction(); + this.handleSubscribeToChangeNotifier(); + } + //#endregion + + // + //#region UI Handlers ... + /** + * Handle Enter Pressed ... + * + * @param event + */ + handleEnterPressed(event: any) { + // + if ( + isNullOrUndefined(this.dtoContainer) || + isNullOrUndefined(this.btnAddOrUpdateDto) || + this.btnAddOrUpdateDto.loading || + this.btnAddOrUpdateDto.uiDisabled + ) { + return; + } + + // + this.dtoContainer.lock = false; + this.btnAddOrUpdateDto.clicked.emit(); + } + + /** + * Handle Escape Pressed ... + * + * @param event + */ + handleEscapePressed(event: any) { + // + if ( + isNullOrUndefined(this.dtoContainer) || + this.dtoContainer.loading || + this.dtoContainer.uiDisabled + ) { + return; + } + + // + if (isNullOrUndefined(this.btnCancelModelEdit)) { + // + this.dtoContainer.handleCancelEditModel(); + return; + } + + // + if (this.btnCancelModelEdit.loading || this.btnCancelModelEdit.uiDisabled) { + return; + } + + // + this.btnCancelModelEdit.clicked.emit(); + } + + /** + * Handle Add or Update ... + * + * @returns + */ + handleAddOrUpdateClicked() { + // + if ( + isNullOrUndefined(this.dtoContainer) || + isNullOrUndefined(this.btnAddOrUpdateDto) || + this.btnAddOrUpdateDto.loading || + this.btnAddOrUpdateDto.uiDisabled + ) { + return; + } + + // + this.dtoContainer.lock = false; + + // + const action = VBaseDtoModelAction.AddOrUpdate; + this.dtoContainer.onDtoModelAction(VBaseDtoModelAction.AddOrUpdate); + this.dtoContainer.dtoModelActionFired.emit({ + action, + payload: { ...this.dtoContainer.modelState }, + }); + } + + /** + * Select Custom Present Type Templates ... + */ + handleSelectCustomPresentTypeTemplate(presentType: string) { + // + let result = this.nothingPresentTypeRef; + + // + // Check Validate Present type and also DTO Container Exists ... + // then Select Present Type ... + if ( + !isNullOrUndefined(this.dtoContainer) && + !isNullOrEmptyString(presentType) + ) { + // + result = this.dtoContainer.getCustomPresentTypeTemplate(presentType); + + // + // Validate Template ... + if (isNullOrUndefined(result)) { + result = this.nothingPresentTypeRef; + } + } + + // + return result; + } + //#endregion + + // + //#region Private ... + private handleSetCSSVars() { + // + if (isNullOrUndefined(this.dtoContainer)) { + return; + } + + // + const regularColorDescriptor = parseXColor( + this.dtoContainer.formRegularColor + ); + + // + const outlineBGVarName = 'outline-bg-color'; + const outlineBGVarValue = getXColorCssVariableName( + regularColorDescriptor.name, + true, + regularColorDescriptor.brightnessLevel + ); + + // + this.setCssVar(outlineBGVarName, `var(${outlineBGVarValue})`); + this.setCssVar( + outlineBGVarName, + `var(${outlineBGVarValue})`, + document.documentElement + ); + } + + private handleSubscribeToChangeNotifier() { + // + if (isNullOrUndefined(this.dtoContainer)) { + return; + } + + // + this.applyTakeUntilWrapper( + this.dtoContainer.changeNotifier.asObservable() + ).subscribe((re) => { + this.detectChanges(); + }); + } + + private handleSubscribeToDtoFormAction() { + // + if (isNullOrUndefined(this.dtoContainer)) { + return; + } + + // + this.applyTakeUntilWrapper( + this.dtoContainer.dtoModelActionFired.asObservable() + ).subscribe((res) => {}); + } + + private registerDtoContainerActionProvider() { + // + if ( + isNullOrUndefined(this.dtoContainer) || + isNullOrUndefined(this.dtoContainer.actionProvider) + ) { + return; + } + + // + this.applyTakeUntilWrapper(this.dtoContainer.actionProvider).subscribe( + (event) => { + // + if ( + isNullOrUndefined(event) || + isNullOrEmptyString(event.action) || + !isVBaseDtoAction(event.action) + ) { + return; + } + + // + this.detectChanges(); + this.dtoContainer.detectChanges(); + } + ); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\v-dto\v-dto.typings.ts + +import { + Inject, + Input, + NgZone, + Output, + Renderer2, + Component, + ElementRef, + Injectable, + TemplateRef, + EventEmitter, + ChangeDetectorRef, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + XKey, + toMd5, + nameof, + toArray, + isNumber, + isBoolean, + toBoolean, + XKeyValue, + isFunction, + isSameObject, + XResourceIDs, + XExceptionIDs, + nameofInstance, + XColorIdentifier, + setPropertyValue, + isNullOrUndefined, + XResourceIdentifier, + isNullOrEmptyString, + XColorWithBrightness, + XPartialModelFieldNames, +} from 'x-framework-core'; +import { + XSlotName, + XIconNames, + XButtonType, + XSlotLayout, + XCardAction, + XFormUpdateOn, + XFormBaseAction, + XPopoverService, + XIconIdentifier, + XCardActionModel, + XFormActionModel, + XBaseActionModel, + XFormControlType, + XFormControlAction, + XFormControlAppearance, + XFormValueChangeEventModel, + XFormControlAppearanceIdentifier, + XFormControlValueChangeEventModel, +} from 'x-framework-components'; +import { map } from 'rxjs/operators'; +import { + XSimpleFormConfig, + XSimpleFormControlConfig, +} from '../typings/simple.form.typings'; +import { Subject, Subscription } from 'rxjs'; +import { X_CONFIG } from 'src/app/config/x.config'; +import { XConfig } from 'src/app/config/app.config'; +import { isValueInEnum } from '../tools/v-base.tools'; +import { ViewportRuler } from '@angular/cdk/scrolling'; +import { XManagerService } from 'x-framework-services'; +import { VBaseComponent } from '../classes/v-base.component'; +import { AppResourceIDs } from 'src/app/config/localization.config'; +import { SharedService } from 'src/app/shared/services/shared.service'; + +// +//#region Model Field Type ... +/** + * All available filed Types of Models ... + */ +export enum VModelFieldTypes { + Date = 'date', + Array = 'array', + String = 'string', + Number = 'number', + Object = 'object', + Boolean = 'boolean', + Function = 'function', +} + +/** + * retrieve specified Field Type's default value ... + * + * @param type VmodelFieldTypes member ... + * @returns + */ +export function getDefaultTypeValue(type: VModelFieldTypes) { + // + let result = undefined; + + // + switch (type) { + // + case VModelFieldTypes.String: + result = ''; + break; + + // + case VModelFieldTypes.Number: + result = 0; + break; + + // + case VModelFieldTypes.Boolean: + result = false; + break; + + // + case VModelFieldTypes.Date: + case VModelFieldTypes.Function: + result = null; + break; + + // + case VModelFieldTypes.Object: + result = undefined; + break; + + // + case VModelFieldTypes.Array: + result = []; + break; + + // + default: + result = undefined; + break; + } + + // + return result; +} +//#endregion + +// +//#region Definitions ... +/** + * each fields of a model described using this ... + */ +export interface VModelFieldDescriptor { + /** + * Default Field Value ... + */ + value: T; + + /** + * Field Name on Model ... + */ + key: string; + + /** + * Hide Key/Value ... + */ + hide?: boolean; + + /** + * Hide Label ... + */ + hideLabel?: boolean; + + /** + * Specified Field Data Type ... + */ + type: VModelFieldTypes; + + /** + * Field Label Resource Identifier ... + */ + label?: XResourceIdentifier; + + /** + * Field Label Value Provider ... + * @returns + */ + valueProvider?: (value: T) => any; + + /** + * allow this field to Edit as Field Form ... + */ + allowFieldForm?: boolean; + + /** + * Field Form Control Config ... + */ + formControlConfig?: XSimpleFormControlConfig; + + /** + * this specified the content of + * field shown on below of label or not ... + */ + showContentBelowLabel?: boolean; + + /** + * in some cases in custom template providing for Arrays ... + * maybe we have to use a List or etc component ... + * in this cases instead of looping arround array childs and passed + * each one separately to template, passed all childs to custom template ... + */ + pushArrayChildsOnceToTemplate?: boolean; + + /** + * a View Template for a Key to + * provides Custom View for it ... + * this passed three variable: + * + * key: field name ... + * value: field value ... + * presentType: current context present type ... + */ + hasCustomTemplate?: boolean; + template?: TemplateRef; +} + +/** + * Holds Model state as this interface ... + */ +export interface VModelState { + isBase: boolean; + isValid: boolean; + model: Partial; + base?: Partial; + isDefault: boolean; + isChanged: boolean; + default?: Partial; + errors?: Array; + isValidForAdd: boolean; + isValidForUpdate: boolean; + hasOpenFormField?: boolean; +} + +/** + * Check a Model State Can Locked or not ... + * + * @param state + */ +export function isModelStateLocked(state: VModelState) { + // + let result = false; + if (isNullOrUndefined(state)) { + return result; + } + + // + result = + !state || + !state.isValid || + ((state.isValidForAdd || state.isValidForUpdate) && !state.isBase); + + // + return result; +} + +/** + * Check Spacified State is Valid for Add Action ... + * + * @param state + * @returns + */ +export function canAddModelState(state: VModelState) { + // + let result = false; + + // + if (isNullOrUndefined(state)) { + return result; + } + + // + result = + state.isValid && + !state.isBase && + state.isChanged && + !state.isDefault && + !isNullOrUndefined(state.base) && + !isNullOrUndefined(state.default) && + isSameObject(state.default, state.base); + + // + return result; +} + +/** + * Check Specified State is Valid for Update Action ... + * + * @param state + * @returns + */ +export function canUpdateModelState(state: VModelState) { + // + let result = false; + + // + if (isNullOrUndefined(state)) { + return result; + } + + // + result = + state.isValid && + state.isChanged && + !state.isDefault && + !state.isBase && + !isNullOrUndefined(state.base) && + !isNullOrUndefined(state.default) && + !isSameObject(state.default, state.base); + + // + return result; +} + +/** + * Modle must Validate using this interface ... + */ +export interface VModelValidationResult { + isValid: boolean; + isValidForAdd: boolean; + normalized: Partial; + isValidForUpdate: boolean; + validationErrors?: Array; +} +//#endregion + +// +//#region Present Type ... +/** + * available types of view ... + */ +export enum VBaseDtoPresentType { + // + AsNone = 'as_none', + AsForm = 'as_form', + AsView = 'as_view', +} + +export type TVBaseDtoPresentTypeIdentifier = VBaseDtoPresentType | string; + +export function isVBaseDtoPresentType(value: string) { + return isValueInEnum(value, Object.assign({}, VBaseDtoPresentType)); +} + +/** + * Define Sections Type for Reloading Implementation ... + */ +export type TVDtoSections = XKey | 'All'; +//#endregion + +// +//#region Field View Actions ... +/** + * Form Filed Available Actions ... + */ +export enum VBaseDtoFieldViewAction { + Apply = 'apply', + Cancel = 'cancel', + ShowForm = 'showForm', +} + +export type VBaseDtoFieldViewActionIdentifier = + | VBaseDtoFieldViewAction + | string; + +export interface VModelFieldViewActionModel { + key: string; + action: VBaseDtoFieldViewActionIdentifier; +} + +export function isVBaseDtoFieldViewAction(value: string) { + return isValueInEnum(value, Object.assign({}, VBaseDtoFieldViewAction)); +} +//#endregion + +// +//#region VBaseDtoFieldFormPresentationIconPosition ... +/** + * field form Presentation Side ... + */ +export enum VBaseDtoFieldFormPresentationIconPosition { + BeforeLabel = 'before_label', + BeforeValue = 'before_value', + AfterValue = 'after_value', +} + +export type VBaseDtoFieldFormPresentationIconPositionIdentifier = + | VBaseDtoFieldFormPresentationIconPosition + | string; + +export function isVBaseDtoFieldFormPresentationIconPosition(value: string) { + return isValueInEnum( + value, + Object.assign({}, VBaseDtoFieldFormPresentationIconPosition) + ); +} +//#endregion + +// +//#region VBaseDtoModelAction ... +/** + * Base Allowed Dto Actions ... + */ +export enum VBaseDtoModelAction { + Reset = 'reset', + Cancel = 'cancel', + AddOrUpdate = 'addOrUpdate', +} + +export type VBaseDtoModelActionIdentifier = VBaseDtoModelAction | string; + +export interface VBaseDtoModelFiredActionModel { + action: VBaseDtoModelActionIdentifier; + payload?: VModelState; +} + +export function isBaseDtoModelAction(value: string) { + return isValueInEnum(value, Object.assign({}, VBaseDtoModelAction)); +} +//#endregion + +// +//#region Container ... +/** + * Define all required properties here + * just as variables ... + */ +export interface IVBaseDtoContainer { + // + // Self Props ... + /** + * Specified Component present type ... + */ + presentType: TVBaseDtoPresentTypeIdentifier; + + /** + * Specified Card Section css class names ... + */ + cssClass: Array; + + /** + * Labels Color ... + * usually used in View Presentation ... + */ + labelColor: XColorIdentifier; + + /** + * Values Color ... + * usually used in View Presentation ... + */ + valueColor: XColorIdentifier; + + /** + * hide optional values on view mode + * if it's not provide as FormField ... + */ + hideOptionalFieldsOnView: boolean; + + /** + * show configured descriptor keys + * as inline form fields ... + */ + showFieldsForm: boolean; + + /** + * Where to Show Form Field Prentation Button ... + */ + fieldFormButtonPosition: VBaseDtoFieldFormPresentationIconPositionIdentifier; + + /** + * Specified Field Form View Toggeler ... + */ + fieldFormIcon: XIconIdentifier; + fieldFormIconColor: XColorIdentifier; + + /** + * Specified Field Form Apply ... + */ + fieldFormApplyIcon: XIconIdentifier; + fieldFormApplyIconColor: XColorIdentifier; + + /** + * Specified Field Form Cancel ... + */ + fieldFormCancelIcon: XIconIdentifier; + fieldFormCancelIconColor: XColorIdentifier; + + /** + * Specify Force to Show Validation Errors or not ... + */ + showValidationErrors: boolean; + + // + // Card Properties ... + /** + * Card Show as In Page ... + */ + isInPage: boolean; + + /** + * Wrap Component with Card ... + */ + wrapWithCard: boolean; + + /** + * Show Card Footrer Section ... + */ + cardShowFooter: boolean; + + /** + * Show Card Actions Section ... + */ + cardShowActions: boolean; + + /** + * Specify to Remove Card Marigin or not ... + */ + removeCardMargin: boolean; + + /** + * Specified Card Color apply as Foreground ... + */ + cardForegroundColor: boolean; + + /** + * Specified Card Title Resource Id ... + */ + cardTitle: XResourceIdentifier; + + /** + * Specified Card Subtitle Resource Id ... + */ + cardSubTitle: XResourceIdentifier; + + /** + * Specified Card Section css class names ... + */ + cardCssClass: Array; + + /** + * Specified Card Color ... + */ + cardColor: XColorIdentifier; + + /** + * Card Action Provider Implementation ... + */ + cardActionProvider: Subject; + + // + // Form Props ... + /** + * Form Appearance ... + */ + formAppearance: XFormControlAppearanceIdentifier; + + /** + * Forms Action Provider ... + */ + formActionProvider: Subject; + + /** + * Form Regular Color ... + */ + formRegularColor: XColorIdentifier; + + /** + * Form Accent Color ... + */ + formAccentColor: XColorIdentifier; + + /** + * Form Hint Color ... + */ + formHintColor: XColorIdentifier; + + /** + * Form Active Color ... + */ + formActiveColor: XColorIdentifier; + + /** + * Form Valid Color ... + */ + formValidColor: XColorIdentifier; + + /** + * Form Invalid Color ... + */ + formInvalidColor: XColorIdentifier; + + /** + * Form Pending Color ... + */ + formPendingColor: XColorIdentifier; + + /** + * Form Fill Color ... + */ + formFillColor: XColorIdentifier; + + /** + * Form Disabled Color ... + */ + formDisabledColor: XColorIdentifier; + + /** + * Form Disabled Fill Color ... + */ + formDisabledFillColor: XColorIdentifier; + + /** + * Specifie to Show Form Cancel Button or not ... + */ + showFormCancelButton: boolean; + + /** + * Form Cancel Button Locale Resource ... + */ + formCancelButtonResource: XResourceIdentifier; + + /** + * Form Cancel Button Color ... + */ + formCancelButtonColor: XColorIdentifier; + + /** + * Show Form Reset Button or not ... + */ + showFormResetButton: boolean; + + /** + * Form Reset Button Locale Resource ... + */ + formResetButtonResource: XResourceIdentifier; + + /** + * Form Reset Button Color ... + */ + formResetButtonColor: XColorIdentifier; + + /** + * Show Form Action Button ... + */ + showFormActionButton: boolean; + + /** + * Form Add Action Button Locale Resource ... + */ + formActionButtonAddResource: XResourceIdentifier; + + /** + * Form Update Action Button Locale Resource ... + */ + formActionButtonUpdateResource: XResourceIdentifier; + + /** + * Form Action Button Color ... + */ + formActionButtonColor: XColorIdentifier; +} + +/** + * this is a service which we called it Property Provider ... + * all variables defined here as readonly variable with + * default allowed values ... + */ +@Injectable({ + providedIn: 'root', +}) +export class VBaseDtoContainer implements Readonly { + // + // Self Props ... + readonly presentType: TVBaseDtoPresentTypeIdentifier = + VBaseDtoPresentType.AsNone; + readonly cssClass: Array = []; + readonly labelColor: XColorIdentifier = XColorWithBrightness.Tertiary; + readonly valueColor: XColorIdentifier = XColorWithBrightness.Dark; + readonly hideOptionalFieldsOnView: boolean = false; + readonly showFieldsForm: boolean = false; + readonly fieldFormButtonPosition: VBaseDtoFieldFormPresentationIconPositionIdentifier = + VBaseDtoFieldFormPresentationIconPosition.AfterValue; + readonly fieldFormIcon: XIconIdentifier = XIconNames.edit; + readonly fieldFormIconColor: XColorIdentifier = XColorWithBrightness.Tertiary; + readonly fieldFormApplyIcon: XIconIdentifier = XIconNames.checkmark; + readonly fieldFormApplyIconColor: XColorIdentifier = + XColorWithBrightness.Success; + readonly fieldFormCancelIcon: XIconIdentifier = XIconNames.cancel; + readonly fieldFormCancelIconColor: XColorIdentifier = + XColorWithBrightness.Danger; + readonly showValidationErrors: boolean = true; + + // + // Card Props ... + readonly isInPage: boolean = false; + readonly wrapWithCard: boolean = false; + readonly cardShowFooter: boolean = false; + readonly cardShowActions: boolean = false; + readonly removeCardMargin: boolean = false; + readonly cardForegroundColor: boolean = false; + readonly cardTitle: XResourceIdentifier = ''; + readonly cardSubTitle: XResourceIdentifier = ''; + readonly cardCssClass: Array = []; + readonly cardColor: string = XColorWithBrightness.Light; + readonly cardActionProvider: Subject = + new Subject(); + + // + readonly formAppearance: XFormControlAppearanceIdentifier = + XFormControlAppearance.Outline; + readonly formActionProvider: Subject = + new Subject(); + + // + readonly formRegularColor: XColorIdentifier = XColorWithBrightness.Dark; + readonly formAccentColor: XColorIdentifier = XColorWithBrightness.DangerShade; + readonly formHintColor: XColorIdentifier = XColorWithBrightness.DangerShade; + readonly formActiveColor: XColorIdentifier = + XColorWithBrightness.WarningShade; + readonly formValidColor: XColorIdentifier = XColorWithBrightness.Success; + readonly formInvalidColor: XColorIdentifier = XColorWithBrightness.Danger; + readonly formPendingColor: XColorIdentifier = XColorWithBrightness.Warning; + readonly formFillColor: XColorIdentifier = XColorWithBrightness.Primary; + readonly formDisabledColor: XColorIdentifier = XColorWithBrightness.LightTint; + readonly formDisabledFillColor: XColorIdentifier = + XColorWithBrightness.LightShade; + + // + readonly showFormCancelButton: boolean = true; + readonly formCancelButtonResource: XResourceIdentifier = XResourceIDs.cancel; + readonly formCancelButtonColor: XColorIdentifier = + XColorWithBrightness.Danger; + readonly showFormResetButton: boolean = true; + readonly formResetButtonResource: XResourceIdentifier = XResourceIDs.reset; + readonly formResetButtonColor: XColorIdentifier = + XColorWithBrightness.Warning; + readonly showFormActionButton: boolean = true; + readonly formActionButtonAddResource: XResourceIdentifier = XResourceIDs.add; + readonly formActionButtonUpdateResource: XResourceIdentifier = + XResourceIDs.update; + readonly formActionButtonColor: XColorIdentifier = + XColorWithBrightness.Primary; +} +//#endregion + +// +//#region Action Provider ... +export enum VBaseDtoAction { + /** + * Calling Change Detection ... + */ + Refresh = 'Refresh', + + /** + * Reloading Component ... + */ + Reload = 'Reload', + + /** + * ReState Model ... + */ + ReState = 'ReState', + + /** + * Rene Model by Provided payload ... + */ + RenewModel = 'RenewModel', + + /** + * Sep Properties ... + * payload.props: Partial => Specified Properties Object to Set ... + * payload.section: VDtoSections => Specified Which Section to Reload (default: 'AsNone') ... + */ + SetProperties = 'SetProperties', + + /** + * Focus on Specified Form Control ... + */ + FocusOnFormControl = 'FocusOnFormControl', +} + +export type VBaseDtoActionIdentifier = VBaseDtoAction | string; + +export interface VBaseDtoActionModel extends XBaseActionModel {} + +export function isVBaseDtoAction(value: string) { + return isValueInEnum(value, Object.assign({}, VBaseDtoAction)); +} +//#endregion + +// +//#region VBaseDtoComponent ... +/** + * a main component class wich provides all properties with their + * anotations and has a base constructor by inject Property Provider service ... + * Must Important things in this class is it provides an Actions called + * extractProps + */ +@Injectable() +@Component({ + template: ``, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export abstract class VBaseDtoComponent + extends VBaseComponent + implements IVBaseDtoContainer +{ + // + //#region Props ... + // + //#region Readonly Fields ... + // + readonly SlotNames = Object.assign({}, XSlotName); + readonly IconNames = Object.assign({}, XIconNames); + readonly FieldFormButtonPositions = Object.assign( + {}, + VBaseDtoFieldFormPresentationIconPosition + ); + readonly ButtonTypes = Object.assign({}, XButtonType); + readonly SlotLayouts = Object.assign({}, XSlotLayout); + readonly ResourceIDs = Object.assign({}, XResourceIDs); + readonly AppResourceIDs = Object.assign({}, AppResourceIDs); + readonly ColorNames = Object.assign({}, XColorWithBrightness); + readonly ModelFieldTypes = Object.assign({}, VModelFieldTypes); + readonly PresentTypes = Object.assign({}, VBaseDtoPresentType); + readonly FormControlTypes = Object.assign({}, XFormControlType); + readonly FieldViewActions = Object.assign({}, VBaseDtoFieldViewAction); + readonly FormControlAppearances = Object.assign({}, XFormControlAppearance); + //#endregion + + // + //#region Inputs ... + // + //#region Custom ... + @Input() + providedFieldDescriptors: VModelFieldDescriptor[]; + + // + private PRESENT_TYPE: string; + + @Input() + presentType: string = this.propertyProvider.presentType; + + // + @Input() + labelColor: XColorIdentifier = this.propertyProvider.labelColor; + + // + @Input() + valueColor: XColorIdentifier = this.propertyProvider.valueColor; + + // + @Input() + hideOptionalFieldsOnView: boolean = + this.propertyProvider.hideOptionalFieldsOnView; + + // + @Input() + showFieldsForm: boolean = this.propertyProvider.showFieldsForm; + @Input() + fieldFormButtonPosition: VBaseDtoFieldFormPresentationIconPositionIdentifier = + this.propertyProvider.fieldFormButtonPosition; + + // + @Input() + fieldFormIcon = this.propertyProvider.fieldFormIcon; + @Input() + fieldFormIconColor = this.propertyProvider.fieldFormIconColor; + + // + @Input() + fieldFormApplyIcon = this.propertyProvider.fieldFormApplyIcon; + @Input() + fieldFormApplyIconColor = this.propertyProvider.fieldFormApplyIconColor; + + // + @Input() + fieldFormCancelIcon = this.propertyProvider.fieldFormCancelIcon; + @Input() + fieldFormCancelIconColor = this.propertyProvider.fieldFormCancelIconColor; + + // + @Input() + showValidationErrors = this.propertyProvider.showValidationErrors; + + // + @Input() + cssClass: Array = this.propertyProvider.cssClass; + //#endregion + + // + //#region Card Props ... + @Input() + isInPage: boolean = this.propertyProvider.isInPage; + + // + @Input() + wrapWithCard: boolean = this.propertyProvider.wrapWithCard; + + // + @Input() + cardShowFooter: boolean = this.propertyProvider.cardShowFooter; + + // + @Input() + cardShowActions: boolean = this.propertyProvider.cardShowActions; + + // + @Input() + cardColor: string = this.propertyProvider.cardColor; + + // + @Input() + cardForegroundColor: boolean = this.propertyProvider.cardForegroundColor; + + // + @Input() + cardTitle: XResourceIdentifier = this.propertyProvider.cardTitle; + + // + @Input() + cardSubTitle: XResourceIdentifier = this.propertyProvider.cardSubTitle; + + // + @Input() + cardCssClass: Array = this.propertyProvider.cardCssClass; + + /** + * Remove Card Margin ... + */ + @Input() + removeCardMargin: boolean = this.propertyProvider.removeCardMargin; + + // + @Input() + cardActionProvider: Subject = + this.propertyProvider.cardActionProvider; + //#endregion + + // + //#region Form Props ... + @Input() + formAppearance: XFormControlAppearanceIdentifier = + this.propertyProvider.formAppearance; + + // + @Input() + formActionProvider: Subject = + this.propertyProvider.formActionProvider; + + /** + * + */ + @Input() + formRegularColor: XColorIdentifier = this.propertyProvider.formRegularColor; + + @Input() + formAccentColor: XColorIdentifier = this.propertyProvider.formAccentColor; + + @Input() + formHintColor: XColorIdentifier = this.propertyProvider.formHintColor; + + @Input() + formActiveColor: XColorIdentifier = this.propertyProvider.formActiveColor; + + @Input() + formValidColor: XColorIdentifier = this.propertyProvider.formValidColor; + + @Input() + formInvalidColor: XColorIdentifier = this.propertyProvider.formInvalidColor; + + @Input() + formPendingColor: XColorIdentifier = this.propertyProvider.formPendingColor; + + @Input() + formFillColor: XColorIdentifier = this.propertyProvider.formFillColor; + + @Input() + formDisabledColor: XColorIdentifier = this.propertyProvider.formDisabledColor; + + @Input() + formDisabledFillColor: XColorIdentifier = + this.propertyProvider.formDisabledFillColor; + //#endregion + + // + //#region Form Actions Buttons Config ... + // + // Cancel Button ... + @Input() + showFormCancelButton: boolean = this.propertyProvider.showFormCancelButton; + + @Input() + formCancelButtonResource: XResourceIdentifier = + this.propertyProvider.formCancelButtonResource; + + @Input() + formCancelButtonColor: XColorIdentifier = + this.propertyProvider.formCancelButtonColor; + + // + // Reset Button ... + @Input() + showFormResetButton: boolean = this.propertyProvider.showFormResetButton; + + @Input() + formResetButtonResource: XResourceIdentifier = + this.propertyProvider.formResetButtonResource; + + @Input() + formResetButtonColor: XColorIdentifier = + this.propertyProvider.formResetButtonColor; + + // + // Action Button Button ... + @Input() + showFormActionButton: boolean = this.propertyProvider.showFormActionButton; + + @Input() + formActionButtonAddResource: XResourceIdentifier = + this.propertyProvider.formActionButtonAddResource; + + @Input() + formActionButtonUpdateResource: XResourceIdentifier = + this.propertyProvider.formActionButtonUpdateResource; + + @Input() + formActionButtonColor: XColorIdentifier = + this.propertyProvider.formActionButtonColor; + //#endregion + //#endregion + + // + //#region Events Emitters ... + changeSubject = new Subject(); + + /** + * Model Changed Event Emitter ... + */ + @Output() + changeNotifier: EventEmitter> = new EventEmitter< + VModelState + >(); + + /** + * Notify Base Dto Actions (Form Action Buttons ...) Fired ... + */ + @Output() + dtoModelActionFired = new EventEmitter(); + + /** + * Field Presenter Type Change ... + */ + @Output() + fieldPresentTypeChanged = new EventEmitter(); + //#endregion + + // + @Input() + actionProvider = new Subject(); + + // + public inFormFields: Array = []; + public inFormFieldsFormConfigs: Array> = []; + public inFormFieldValues: Array> = []; + + // + // Provide Filed Descriptors ... + private FIELD_DESCRIPTORS: Array> = []; + get fieldDescriptors() { + // + let result: Array> = []; + + // + if (this.hasChild(this.FIELD_DESCRIPTORS)) { + result = [...this.FIELD_DESCRIPTORS]; + } else { + // + if ( + !isNullOrUndefined(this.providedFieldDescriptors) && + this.hasChild(this.providedFieldDescriptors) + ) { + // + result = [...this.providedFieldDescriptors]; + } else { + result = [...this.defaultFieldDescriptors]; + } + + // + this.onFieldDescriptorsReady(); + } + + // + return result; + } + + // + //#region Model ... + // + // Model State Used to Provided Changes Happens ... + private MODEL_STATE: VModelState; + get modelState() { + return this.MODEL_STATE; + } + + // + // Base Model used to Check Changes ... + private BASE_MODEL: T = undefined; + get baseModel() { + return this.BASE_MODEL; + } + + // + private MODEL: T = undefined; + + // + // Specified Model Unique Identifier ... + modelUniqueIdentifier: string = 'id'; + getModelUniqueIdentifierValue() { + // + let result: string = undefined; + + // + if ( + !isNullOrUndefined(this.model) && + !isNullOrEmptyString(this.modelUniqueIdentifier) + ) { + result = (this.model as any)[this.modelUniqueIdentifier]; + } + + // + return result; + } + + // + @Input() + set model(v: T) { + // + const preparedModel = this.prepare(v); + this.MODEL = { + ...preparedModel, + }; + this.BASE_MODEL = { + ...preparedModel, + }; + + // + this.onModelAssigned(); + this.setAttribute( + this.modelUniqueIdentifier, + this.getModelUniqueIdentifierValue() + ); + } + + get model() { + return this.MODEL; + } + //#endregion + //#endregion + + // + //#region Reloading Mechanism ... + showComponentCard: boolean = true; + showComponentPresentType: boolean = true; + showComponentViewPresentType: boolean = true; + showComponentFormPresentType: boolean = true; + + // + // Since We Have Implement new Reloading mechanism ... + // here we create a new Reload for VDto ... + async reloadDtoComponent(section: TVDtoSections): Promise { + // + let task: Promise = undefined; + switch (section) { + // + case 'All': + // + this.showComponent = false; + this.showComponentCard = false; + this.showComponentPresentType = false; + this.showComponentViewPresentType = false; + this.showComponentFormPresentType = false; + + // + task = this.waitForElement(this.element.nativeElement.tagName).then( + () => { + // + this.showComponent = true; + this.showComponentCard = true; + this.showComponentPresentType = true; + this.showComponentViewPresentType = true; + this.showComponentFormPresentType = true; + } + ); + break; + + // + case 'AsForm': + // + this.showComponentPresentType = false; + this.showComponentFormPresentType = false; + + // + task = this.waitForElement(this.element.nativeElement.tagName).then( + () => { + // + this.showComponent = true; + this.showComponentPresentType = true; + this.showComponentFormPresentType = true; + } + ); + break; + + // + case 'AsView': + // + this.showComponentPresentType = false; + this.showComponentViewPresentType = false; + + // + task = this.waitForElement(this.element.nativeElement.tagName).then( + () => { + // + this.showComponent = true; + this.showComponentPresentType = true; + this.showComponentViewPresentType = true; + } + ); + break; + + // + case 'AsNone': + // + // Do Nothisn ... + task = undefined; + break; + } + + // + if (section !== 'AsNone') { + // + if (!isNullOrUndefined(task)) { + await task; + } + + // + this.handleChangeDetection(); + } + } + //#endregion + + // + //#region Constructor ... + public constructor( + public zone: NgZone, + @Inject(X_CONFIG) + public config: XConfig, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public sharedService: SharedService, + public managerService: XManagerService, + public changeDetector: ChangeDetectorRef, + protected popoverService: XPopoverService, + protected propertyProvider: VBaseDtoContainer + ) { + super( + zone, + config, + element, + renderer, + ruler, + sharedService, + managerService, + changeDetector, + popoverService + ); + } + //#endregion + + // + //#region LifeCycles ... + onChange(changeKeys: string[]): void { + super.onChange(changeKeys); + + // + let isChanged = false; + let applyChange = false; + + // + // Check Action Provider Changes ... + isChanged = changeKeys.includes( + nameof>('actionProvider') + ); + if (isChanged) { + // + // Check Validation ... + if (isNullOrUndefined(this.actionProvider)) { + // + applyChange = true; + this.actionProvider = new Subject(); + this.registerActionProvider(); + } + } + + // + // Check Form Action Provider Changes ... + isChanged = changeKeys.includes( + nameof>('formActionProvider') + ); + if (isChanged) { + // + if (isNullOrUndefined(this.formActionProvider)) { + // + applyChange = true; + this.formActionProvider = this.propertyProvider.formActionProvider; + this.registerActionProvider(); + } + } + + // + // Check Card Action Provider Changes ... + isChanged = changeKeys.includes( + nameof>('cardActionProvider') + ); + if (isChanged) { + // + if (isNullOrUndefined(this.cardActionProvider)) { + // + applyChange = true; + this.cardActionProvider = this.propertyProvider.cardActionProvider; + this.registerActionProvider(); + } + } + + // + // Check Present Type Changed ... + isChanged = changeKeys.includes( + nameof>('presentType') + ); + if (isChanged) { + // + if (isNullOrEmptyString(this.presentType)) { + // + applyChange = true; + this.presentType = this.propertyProvider.presentType; + } + + // + if (this.PRESENT_TYPE !== this.presentType) { + // + this.onPresentTypeChanged(this.presentType, this.PRESENT_TYPE); + this.PRESENT_TYPE = this.presentType; + this.setAttribute( + nameofInstance(this, 'presentType'), + this.presentType + ); + } + } + + // + if (applyChange) { + this.handleChangeDetection(); + } + } + + afterViewInit(): void { + super.afterViewInit(); + + // + this.registerChangeSubject(); + this.registerActionProvider(); + + // + this.onPresentTypeChanged(this.presentType, this.PRESENT_TYPE); + } + //#endregion + + // + //#region Abstractions ... + /** + * Used to Handle Model Assigned ... + */ + onModelAssigned() {} + + /** + * When a Field Present As Form ... + * + * @param key + */ + onFieldFormPresent(key: string) {} + + /** + * Calls When Filed Form Action Calling ... + * + * @param model + */ + onFieldPresentTypeChanged(model: VModelFieldViewActionModel) {} + + /** + * When Present Type Changed ... + * + * @param to new Present Type ... + * @param from old Present Type ... + */ + onPresentTypeChanged(to: string, from: string) {} + + /** + * Calls When FormModel Config Prepared ... + */ + onFormModelConfigPrepared() {} + + /** + * Notify Prepare Field Descriptors Ready ... + */ + onFieldDescriptorsReady() {} + + /** + * Calls when a DTO Action done ... + * fired Event ... + * + * @param action + */ + onDtoModelAction(action: VBaseDtoModelActionIdentifier) {} + + /** + * Handle DTO State Management ... + */ + handleDtoStateManagement() { + // + const state = this.getState(); + this.MODEL_STATE = { + ...state, + }; + + // + this.lock = isModelStateLocked({ ...this.modelState }); + this.detectChanges(); + + // + this.changeNotifier.emit(this.MODEL_STATE); + } + + /** + * a function to prepare Model ... + * + * @param model + */ + abstract prepare(model?: T): T; + + /** + * Provide Default Model ... + */ + abstract defaultModel: T; + + /** + * Check Model Validation and Normalized it ... + */ + abstract validate(model?: Partial): VModelValidationResult; + + /** + * Default Field Descriptors ... + */ + abstract defaultFieldDescriptors: Array>; + //#endregion + + // + //#region Properties ... + /** + * Collect Properties ... + * + * @returns + */ + getProperties() { + // + let result: Partial = {}; + + // + const keys = Object.keys(this.propertyProvider); + keys.forEach((k) => { + // + const value = (this as any)[k]; + if (!isNullOrUndefined(value)) { + (result as any)[k] = value; + } + }); + + // + return result; + } + + /** + * Set Properties ... + * + * @param props + * @returns + */ + setProperties( + props: Partial, + section: TVDtoSections = 'AsNone' + ) { + // + if (isNullOrUndefined(props)) { + return; + } + + // + let isChanged = false; + const keys = Object.keys(props); + keys.forEach((key) => { + // + let propertyValue = (props as any)[key]; + let currentValue = (this as any)[key]; + let propertyDefaultValue = (this.propertyProvider as any)[key]; + let selectedValue = + !isNullOrUndefined(propertyValue) && propertyValue !== currentValue + ? propertyValue + : isNullOrUndefined(propertyValue) && isNullOrUndefined(currentValue) + ? propertyDefaultValue + : currentValue; + if (!isChanged) { + isChanged = + !isNullOrUndefined(selectedValue) && selectedValue !== currentValue; + } + setPropertyValue(this, key, selectedValue); + }); + + // + if (isChanged) { + // + if (section === 'AsNone') { + this.handleChangeDetection(); + } else { + this.reloadDtoComponent(section).then(); + } + } + } + + /** + * Validate Changed Properties ... + */ + handleValidateChangedProperties() { + // + let applyChanges = false; + const keys = Object.keys(this.propertyProvider); + keys.forEach((k) => { + // + const value = (this as any)[k]; + const defValue = (this.propertyProvider as any)[k]; + if (isNullOrUndefined(value)) { + // + if (!applyChanges) { + applyChanges = true; + } + + // + (this as any)[k] = defValue; + } + }); + + // + if (applyChanges) { + this.handleChangeDetection(); + } + } + //#endregion + + // + //#region Providers ... + /** + * Generate Unique Identifier MD5 string based on keys of Filed ... + * + * @returns + */ + generateFormName() { + // + const source = this.extractDescribedKeys() + .map((k) => `${k}`) + .join('|'); + const result = toMd5(source); + + // + return result; + } + + /** + * Extract Avavilable Described Keys ... + */ + extractDescribedKeys() { + return this.fieldDescriptors.map((i) => i.key); + } + + /** + * extract a Partial which filled by provided object ... + * + * @returns + */ + extractDecribedModel() { + // + let result: Partial = {}; + + // + // Validate Field Descriptors Exists or not ... + if (!this.hasChild(this.fieldDescriptors)) { + return result; + } + + // + for (var fd of this.fieldDescriptors) { + // + if (isNullOrUndefined(fd)) { + continue; + } + + // + let fdValue = getDefaultTypeValue(fd.type); + let mdValue = (this.MODEL as any)[fd.key]; + (result as any)[fd.key] = !isNullOrUndefined(mdValue) ? mdValue : fdValue; + } + + // + return result; + } + + /** + * extract a Partial which filled by provided base object ... + * + * @returns + */ + extractDecribedBaseModel() { + // + let result: Partial = {}; + + // + // Validate Field Descriptors Exists or not ... + if (!this.hasChild(this.fieldDescriptors)) { + return result; + } + + // + for (var fd of this.fieldDescriptors) { + // + if (isNullOrUndefined(fd)) { + continue; + } + + // + let fdValue = getDefaultTypeValue(fd.type); + let mdValue = (this.BASE_MODEL as any)[fd.key]; + (result as any)[fd.key] = !isNullOrUndefined(mdValue) ? mdValue : fdValue; + } + + // + return result; + } + + /** + * extract a Partial which filled by provided default object ... + * + * @returns + */ + extractDecribedDefaultModel() { + // + let result: Partial = {}; + + // + // Validate Field Descriptors Exists or not ... + if (!this.hasChild(this.fieldDescriptors)) { + return result; + } + + // + for (var fd of this.fieldDescriptors) { + // + if (isNullOrUndefined(fd)) { + continue; + } + + // + let fdValue = getDefaultTypeValue(fd.type); + let mdValue = (this.defaultModel as any)[fd.key]; + (result as any)[fd.key] = !isNullOrUndefined(mdValue) ? mdValue : fdValue; + } + + // + return result; + } + + /** + * get current Model State ... + * + * @returns + */ + getState() { + // + // Extract required Models ... + const model = { + ...this.MODEL, + ...this.extractDecribedModel(), + }; + const baseModel = { + ...this.BASE_MODEL, + ...this.extractDecribedBaseModel(), + }; + const defaultModel = { + ...this.defaultModel, + ...this.extractDecribedDefaultModel(), + }; + + // + // Check Compared States ... + const isBase = this.isSame(model, baseModel); + const isDefault = this.isSame(model, defaultModel); + const isChanged = !isBase && !isDefault; + + // + const validatedResult = this.validate(); + + // + const isValid = !isChanged ? true : validatedResult.isValid; + const payload = { + ...(isValid ? model : validatedResult.normalized), + }; + + // + const hasOpenFormField = this.handleFormFieldStates(); + + // + let result: VModelState = { + isBase, + isValid, + isDefault, + isChanged, + hasOpenFormField, + model: { ...payload }, + base: { ...this.BASE_MODEL }, + default: { ...this.defaultModel }, + isValidForAdd: validatedResult.isValidForAdd, + errors: [...validatedResult.validationErrors], + isValidForUpdate: validatedResult.isValidForUpdate, + }; + + // + this.MODEL_STATE = { + ...result, + }; + + // + return result; + } + + /** + * get FieldType of Specified Key ... + * + * @param key + * @returns + */ + getFieldType(key: string) { + // + const fd = this.fieldDescriptors.find((f) => f.key === key); + if (isNullOrUndefined(fd)) { + return undefined; + } + + // + return fd.type; + } + + /** + * get Field Value based on Specified Key ... + * + * @param key + * @returns + */ + getFieldValue(key: string) { + // + const fd = this.fieldDescriptors.find((f) => f.key === key); + let result = undefined; + if (isNullOrUndefined(fd)) { + return result; + } + + // + const descModel: any = { + ...this.extractDecribedModel(), + }; + + // + if ( + fd.type !== VModelFieldTypes.Array && + fd.type !== VModelFieldTypes.Object + ) { + result = descModel[key]; + } else if (fd.type === VModelFieldTypes.Object) { + result = { + ...descModel[key], + }; + } else if (fd.type === VModelFieldTypes.Array) { + result = [...descModel[key]]; + } + + // + return result; + } + + /** + * get fields provided value ... + * + * @param key + * @param managerService + * @returns + */ + getFieldProvidedValue(key: string) { + // + let result: any; + + // + let value = this.getFieldValue(key); + const fd = this.fieldDescriptors.find((i) => i.key === key); + result = + !isNullOrUndefined(fd) && + !isNullOrUndefined(fd.valueProvider) && + isFunction(fd.valueProvider) + ? fd.valueProvider(value) + : this.getFieldValue(key); + + // + return result; + } + + /** + * get field label translation ... + * + * @param key + * @param managerService + * @returns + */ + getFieldProvidedLabel(key: string) { + // + let result: any; + + // + const fd = this.fieldDescriptors.find((i) => i.key === key); + result = + !isNullOrUndefined(fd) && !isNullOrUndefined(fd.label) + ? this.resourceProvider(fd.label) + : key; + + // + return result; + } + + /** + * Check a Key has Custom Template or not ... + * + * @param key + * @returns + */ + hasCustomTemplate(key: string) { + // + let result = false; + + // + const fd = this.fieldDescriptors.find((f) => f.key === key); + if (isNullOrUndefined(fd)) { + return result; + } + + // + result = !isNullOrUndefined(fd.hasCustomTemplate) && !!fd.hasCustomTemplate; + return result; + } + + /** + * Extract Only Fields Key which Has Custom Templates ... + * @returns + */ + getCustomTemplateKeys() { + return this.fieldDescriptors + .filter( + (fd) => + fd.hasCustomTemplate && + !isNullOrUndefined(fd.template) && + !isNullOrUndefined(fd.formControlConfig) + ) + .map((fd) => fd.key); + } + + /** + * Retrieve Specified Field's Template ... + * + * @param key + * @returns + */ + getFieldTemplate(key: string) { + // + const fd = this.fieldDescriptors.find((f) => f.key == key); + if (isNullOrUndefined(fd)) { + return undefined; + } + + // + return fd.template; + } + + /** + * retrieve Specified Field's Form Control Config ... + * + * @param key + */ + getFieldFormControlConfig(key: string) { + // + if (!this.isFormView(key)) { + return undefined; + } + + // + const fd = this.fieldDescriptors.find((i) => i.key === key); + if (isNullOrUndefined(fd)) { + return undefined; + } + + // + let result = this.inFormFieldsFormConfigs.find((f) => f.name === key); + + // + return result; + } + + /** + * Get Specified Exception Message ... + * + * @param ex + * @returns + */ + getExceptionMessage(ex: XExceptionIDs) { + return this.managerService.localizationService.getErrorMessage(ex); + } + + // + // Form Model ... + + /** + * retrieve Model Form Config ... + */ + modelFormConfig: XSimpleFormConfig; + + /** + * get Current Model form Config ... + * + * @returns + */ + getDtoFormModelConfig(ignoreCustomTemplates: boolean = true) { + // + const model = { + ...this.extractDecribedModel(), + }; + const name = this.generateFormName(); + const controls = this.fieldDescriptors + .filter( + (i) => + !isNullOrUndefined(i.formControlConfig) && + (ignoreCustomTemplates ? true : !i.hasCustomTemplate) + ) + .map((i) => i.formControlConfig) + .sort((i) => i.index); + + // + // Generate Form Config ... + const result: XSimpleFormConfig> = { + name, + model, + controls, + }; + + // + return result; + } + + /** + * Check Form Model Config ... + * + * @returns + */ + getFormModelConfig() { + // + if (isNullOrUndefined(this.modelFormConfig)) { + // + const m = this.getDtoFormModelConfig(); + if (!isNullOrUndefined(m)) { + // + this.modelFormConfig = { + ...m, + }; + + // + this.prepareFormModelConfig(); + } + } + + // + return this.modelFormConfig; + } + + /** + * if there is any Custom Present Types ... + * we have to override this for Implementation ... + * + * @param presentType + * @returns + */ + getCustomPresentTypeTemplate(presentType: string): TemplateRef { + return null; + } + + // + //#region Can ... + /** + * Check a Filed can Shown or not ... + * + * @param key + * @returns + */ + canShowField(key: string) { + // + let result = false; + + // + const value = this.getFieldValue(key); + const fd = this.fieldDescriptors.find((i) => i.key === key); + result = !isNullOrUndefined(fd) + ? isNullOrUndefined(fd.hide) || !fd.hide + : false; + result = + result && + (this.hideOptionalFieldsOnView + ? this.showFieldsForm && !!fd.allowFieldForm + ? true + : !isNullOrEmptyString(value) && !isNullOrUndefined(value) + : true); + + // + return result; + } + + /** + * Check a Field Label can Show or not ... + * + * @param key + * @returns + */ + canShowFieldLabel(key: string) { + // + let result = false; + + // + const fd = this.fieldDescriptors.find((i) => i.key === key); + result = !isNullOrUndefined(fd) + ? isNullOrUndefined(fd.hideLabel) || !fd.hideLabel + : false; + + // + return result; + } + + /** + * Check can Present Field as Form or not ... + * @param key + */ + canPresentFieldForm(key: string) { + // + let result = false; + + // + const fd = this.fieldDescriptors.find((i) => i.key === key); + result = + !isNullOrUndefined(fd) && + !isNullOrUndefined(fd.allowFieldForm) && + fd.allowFieldForm && + ![ + // + VModelFieldTypes.Object, + VModelFieldTypes.Function, + ].includes(fd.type); + + // + const isInFormView = + this.hasChild(this.inFormFields) && this.inFormFields.includes(key); + + // + result = result && !isInFormView; + + // + return result; + } + + /** + * Check can Present Field as View or not ... + * @param key + */ + canPresentFieldView(key: string) { + // + let result = false; + + // + const fd = this.fieldDescriptors.find((i) => i.key === key); + result = + !isNullOrUndefined(fd) && + !isNullOrUndefined(fd.allowFieldForm) && + !!fd.allowFieldForm; + + // + const isInFormView = + this.hasChild(this.inFormFields) && this.inFormFields.includes(key); + + // + result = result && isInFormView; + + // + return result; + } + + /** + * Check Can Show Specified Key's Content below + * Label ... + * + * @param key + * @returns + */ + canBreakContent(key: string) { + // + let result = false; + + // + const fd = this.fieldDescriptors.find((f) => f.key === key); + if (isNullOrUndefined(fd)) { + return result; + } + + // + result = + !isNullOrUndefined(fd.showContentBelowLabel) && + !!fd.showContentBelowLabel; + return result; + } + + /** + * Push All Childs once ... + * + * @param key + * @returns + */ + canPushChilds(key: string) { + // + let result = false; + + // + const fd = this.fieldDescriptors.find((f) => f.key === key); + if (isNullOrUndefined(fd)) { + return result; + } + + // + result = + fd.type === VModelFieldTypes.Array && + !isNullOrUndefined(fd.pushArrayChildsOnceToTemplate) && + !!fd.pushArrayChildsOnceToTemplate; + return result; + } + + /** + * Check Specified Described Key has Custom Template Provided ... + * + * @param key + * @returns + */ + canShowCustomTemplate(key: string) { + // + return this.getValue(this.fieldDescriptors).pipe( + map((fds) => fds.find((fd) => fd.key === key)), + map((fd) => fd.hasCustomTemplate && !isNullOrUndefined(fd.template)) + ); + } + + /** + * Check Can Reset Model or not ... + * + * @returns + */ + canReset() { + return this.isModelStateChanged(); + } + + /** + * Check Can Add or Update Model or not ... + * + * @returns + */ + canAddOrUpdate() { + return this.isModelStateHasValidChanges(); + } + + /** + * Check can Show Dto CRUD actions or not ... + * + * @returns + */ + canShowDtoActions() { + return this.getValue(this.lock); + } + + /** + * Can Show Validation Errors ... + * + * @returns + */ + canShowValidationErros() { + return this.showValidationErrors && !isNullOrUndefined(this.model); + } + //#endregion + + // + //#region is ... + /** + * Check a Field is in Field View ... + * + * @param key + * @returns + */ + isFieldView(key: string) { + // + let result = false; + + // + const fd = this.fieldDescriptors.find((i) => i.key === key); + result = + !isNullOrUndefined(fd) && + (!this.hasChild(this.inFormFields) || !this.inFormFields.includes(key)); + + // + return result; + } + + /** + * Check a Filed is in Form View or not ... + * + * @param key + * @returns + */ + isFormView(key: string) { + // + let result = false; + + // + const fd = this.fieldDescriptors.find((i) => i.key === key); + result = + !isNullOrUndefined(fd) && + this.hasChild(this.inFormFields) && + this.inFormFields.includes(key); + + // + return result; + } + + /** + * compare to object is same or not ... + * + * @param model + * @param compareWith + * @returns + */ + isSame(model?: Partial, compareWith?: Partial) { + return isSameObject(this.normalize(model), this.normalize(compareWith)); + } + + /** + * Check current Described Fields are same as base ... + * + * @param model + * @returns + */ + isSameAsBase(model?: Partial) { + return this.isSame(this.normalize(model), this.extractDecribedBaseModel()); + } + + /** + * Check current Described Fields are same as default ... + * + * @param model + * @returns + */ + isSameAsDefault(model?: Partial) { + return this.isSame( + this.normalize(model), + this.extractDecribedDefaultModel() + ); + } + + /** + * Check Model is Changed or not ... + * + * @returns + */ + isModelStateChanged() { + // + const result = + !isNullOrUndefined(this.modelState) && this.modelState.isChanged; + return result; + } + + /** + * Check Model is Changed and the Changes are Valid or not ... + * + * @returns + */ + isModelStateHasValidChanges() { + // + const result = this.isModelStateChanged() && this.modelState.isValid; + return result; + } + //#endregion + //#endregion + + // + //#region Handlers ... + /** + * Handle XChanges on Form Field ... + * + * @param model + * @returns + */ + handleInFormFieldValueChanged(model: XFormControlValueChangeEventModel) { + // + // Validate Model ... + if (isNullOrUndefined(model)) { + return; + } + + // + // Check Descriptor Field Exists ... + const fd = this.fieldDescriptors.find((f) => f.key === model.name); + if (isNullOrUndefined(fd)) { + return; + } + + // + // Check Field is in Form View ... + if (!this.isFormView(fd.key)) { + return; + } + + // + // Check Model Exists or not ... + const index = this.inFormFieldValues.findIndex((f) => f.key === fd.key); + if (index >= 0) { + // + // Update Exists Model ... + this.inFormFieldValues[index].value = model.value; + } else { + // + // Add New Changed Model ... + this.inFormFieldValues.push({ + key: fd.key, + value: model.value, + }); + } + + // + this.changeSubject.next({ ...this.MODEL }); + } + + /** + * Apply Specified Value Form Changes to Model ... + */ + handleApplyFormFieldChanges(key: string) { + // + const fd = this.fieldDescriptors.find((f) => f.key === key); + if (isNullOrUndefined(fd)) { + return; + } + + // + if (!this.isFormView(key)) { + return; + } + + // + const formValueControl = this.inFormFieldValues.find((f) => f.key === key); + if (!isNullOrUndefined(formValueControl)) { + // + setPropertyValue(this.MODEL, key, formValueControl.value); + + // + this.changeSubject.next({ ...this.MODEL }); + } + } + + /** + * Handle Change Detection ... + * + * @param force + * @param delay + */ + handleChangeDetection(force: boolean = false) { + // + this.sendCardAction(XCardAction.Refresh); + this.sendFormAction(XFormBaseAction.Refresh); + + // + this.detectChanges(); + + // + if (force) { + this.reloadComponent().then(); + } + } + + /** + * Check Current Model State based on informView Fields and + * notify What to Do on Each State ... + */ + handleFormFieldStates() { + // + const result = this.hasChild(this.inFormFields); + return result; + } + + /** + * Apply Form Modle Changes ... + * whole described form controls ... + * + * @param model + * @returns + */ + handleDtoFormValueChanged(model: Partial) { + // + if (isNullOrUndefined(model)) { + return; + } + + // + const keys = Object.keys(model); + if (!this.hasChild(keys)) { + return; + } + + // + for (const key of keys) { + // + const fd = this.fieldDescriptors.find((f) => f.key === key); + if (isNullOrUndefined(fd)) { + continue; + } + + // + setPropertyValue(this.MODEL, key, (model as any)[key]); + } + + // + this.changeSubject.next({ ...this.MODEL }); + } + + /** + * Handle Disbaled Requirements ... + */ + handleEnabled() { + this.handleStateOfViews(false); + } + + /** + * Handle Enabled Requirements ... + */ + handleDisabled() { + this.handleStateOfViews(true); + } + + /** + * Form Field Action Handlers ... + * + * @param key + * @param action + */ + handleFormFieldAction( + key: string, + action: VBaseDtoFieldViewActionIdentifier + ) { + // + switch (action) { + // + case this.FieldViewActions.ShowForm: + this.toggleFieldView(key); + this.onFieldFormPresent(key); + break; + + // + case this.FieldViewActions.Apply: + case this.FieldViewActions.Cancel: + if (action === this.FieldViewActions.Apply) { + this.handleApplyFormFieldChanges(key); + } else { + } + this.toggleFieldView(key); + break; + } + + // + const actionModel: VModelFieldViewActionModel = { + key: key, + action: action, + }; + this.onFieldPresentTypeChanged(actionModel); + this.fieldPresentTypeChanged.emit(actionModel); + } + + /** + * reset all changes in model + * close all open FormFields and etc ... + */ + async handleResetModel() { + // + this.doLockBreakAction(() => { + // + this.formActionProvider.next({ + action: XFormBaseAction.ClearData, + }); + + // + this.resetFieldDescriptors(); + this.modelFormConfig = undefined; + + // + this.reset(); + this.detectChanges(); + + // + const action = VBaseDtoModelAction.Reset; + + // + this.onDtoModelAction(action); + + // + // Emit Event Handler ... + this.dtoModelActionFired.emit({ + action, + payload: { ...this.modelState }, + }); + }); + } + + /** + * Cancel Form/Field Form(s) View ... + * + * @param toPresentType a View to change Present Type ... + */ + async handleCancelEditModel( + toPresentType: string = VBaseDtoPresentType.AsView + ) { + // + await this.doLockBreakAction((toPresentType) => { + // + if (this.presentType === this.PresentTypes.AsForm) { + this.handleResetFormView(this.presentType); + } else { + this.handleResetFormView(toPresentType); + } + + // + const action = VBaseDtoModelAction.Cancel; + + // + this.onDtoModelAction(action); + + // + // Emit Event Handler ... + this.dtoModelActionFired.emit({ + action, + payload: { ...this.modelState }, + }); + }); + } + + /** + * handle form changes on FormView present type ... + * + * @param event + * @returns + */ + handleFormValueChanged(event: XFormValueChangeEventModel) { + // + if (this.presentType !== VBaseDtoPresentType.AsForm) { + return; + } + + // + this.handleDtoFormValueChanged(event.value); + } + + // + //#region Do ... + /** + * Hide and Show Fileds ... + * + * @param keys + * @param hide + * @returns + */ + hideFields(keys: XPartialModelFieldNames, hide: boolean) { + // + // Validate Args ... + if ( + isNullOrUndefined(keys) || + !this.hasChild(keys) || + !this.hasChild(this.fieldDescriptors) + ) { + return; + } + + // + const hideValue = !!hide; + const keysArr = toArray(keys).map((k) => k.toString()); + + // + this.handleResetFieldStates(); + + // + // Update Field Descriptor(s) ... + this.FIELD_DESCRIPTORS = [ + ...this.FIELD_DESCRIPTORS.map((fd) => { + // + const model: VModelFieldDescriptor = { + ...fd, + hide: keysArr.includes(fd.key) ? hideValue : fd.hide, + }; + + // + return model; + }), + ]; + + // + this.detectChanges(); + } + + /** + * Hide and Show Filed Labels ... + * + * @param keys + * @param hide + * @returns + */ + hideFieldLabels(keys: XPartialModelFieldNames, hide: boolean) { + // + // Validate Args ... + if ( + isNullOrUndefined(keys) || + !this.hasChild(keys) || + !this.hasChild(this.fieldDescriptors) + ) { + return; + } + + // + const hideValue = !!hide; + const keysArr = toArray(keys).map((k) => k.toString()); + + // + this.handleResetFieldStates(); + + // + // Update Field Descriptor(s) ... + + this.FIELD_DESCRIPTORS = [ + ...this.FIELD_DESCRIPTORS.map((fd) => { + // + const model: VModelFieldDescriptor = { + ...fd, + hideLabel: keysArr.includes(fd.key) ? hideValue : fd.hideLabel, + }; + + // + return model; + }), + ]; + + // + this.detectChanges(); + } + + /** + * Allow / Disallow Field Form Edit ... + * + * @param keys + * @param disable + * @returns + */ + disableFieldEdits(keys: XPartialModelFieldNames, disable: boolean) { + // + // Validate Args ... + if ( + isNullOrUndefined(keys) || + !this.hasChild(keys) || + !this.hasChild(this.fieldDescriptors) + ) { + return; + } + + // + const disableValue = !!!disable; + const keysArr = toArray(keys).map((k) => k.toString()); + + // + this.handleResetFieldStates(); + + // + this.FIELD_DESCRIPTORS = [ + ...this.FIELD_DESCRIPTORS.map((fd) => { + // + // Renew Field Descriptor ... + const model: VModelFieldDescriptor = { + ...fd, + allowFieldForm: keysArr.includes(fd.key) + ? disableValue + : fd.allowFieldForm, + }; + + // + return model; + }), + ]; + + // + this.detectChanges(); + } + + /** + * Reproduce Field Descriptors ... + */ + resetFieldDescriptors() { + // + if (!this.hasChild(this.FIELD_DESCRIPTORS)) { + return; + } + + // + this.FIELD_DESCRIPTORS = []; + this.detectChanges(); + if ( + !isNullOrUndefined(this.providedFieldDescriptors) && + this.hasChild(this.providedFieldDescriptors) + ) { + // + this.FIELD_DESCRIPTORS = [...this.providedFieldDescriptors]; + } else { + // + this.FIELD_DESCRIPTORS = [...this.defaultFieldDescriptors]; + } + // + this.onFieldDescriptorsReady(); + this.detectChanges(); + } + + /** + * Toggle Field View ... + * + * @param key + * @returns + */ + toggleFieldView(key: string) { + // + const fd = this.fieldDescriptors.find((i) => i.key === key); + if (isNullOrUndefined(fd)) { + return; + } + + // + // Check Can Toggle ... + if (isNullOrUndefined(fd.allowFieldForm) || !fd.allowFieldForm) { + return; + } + + // + const isFieldView = this.isFieldView(key); + // + // Show Field Form ... + if (isFieldView) { + // + this.inFormFields.push(key); + let value = this.getFieldValue(key); + let fieldFormModel = {} as any; + fieldFormModel[key] = value; + + // + // Here we Apply Some Changes for Handling Form Field ... + const fieldFormControlConfig = fd.formControlConfig; + if (!isNullOrUndefined(fieldFormControlConfig)) { + // + fieldFormControlConfig.updateOn = XFormUpdateOn.CHANGE; + fieldFormControlConfig.eventHandlers = { + valueChanged: (model) => { + this.handleInFormFieldValueChanged(model); + }, + }; + + // + let fieldFormConfig: XSimpleFormConfig = { + name: key, + model: fieldFormModel, + controls: [{ ...fieldFormControlConfig }], + }; + this.inFormFieldsFormConfigs.push(fieldFormConfig); + } + } + // + // Show View Filed ... + else { + // + this.inFormFieldValues = this.inFormFieldValues.filter( + (k) => k.key !== key + ); + this.inFormFields = this.inFormFields.filter((k) => k !== key); + this.inFormFieldsFormConfigs = this.inFormFieldsFormConfigs.filter( + (k) => k.name !== key + ); + } + + // + this.changeSubject.next({ ...this.MODEL }); + } + + /** + * prepare a provided object using Described fields ... + * + * @param model + */ + normalize(model?: Partial) { + // + const defaultModel = { + ...this.defaultModel, + ...this.extractDecribedDefaultModel(), + }; + + // + if (isNullOrUndefined(model)) { + // + model = { + ...defaultModel, + }; + } + + // + for (const fd of this.fieldDescriptors) { + // + const mdValue = (model as any)[fd.key]; + (model as any)[fd.key] = !isNullOrUndefined(mdValue) ? mdValue : fd.value; + } + + // + return model; + } + + /** + * force Cancel Edit Mode ... + */ + cancelEdit() { + // + if (this.presentType === this.PresentTypes.AsForm) { + this.reset(); + } else { + // + for (let key of this.inFormFields) { + this.toggleFieldView(key); + } + } + + // + this.handleChangeDetection(); + } + + /** + * Do Focus On Specified Key Control ... + * only on Form Presenting ... + * + * @param key + */ + focusOnFormControl(key: string) { + // + if (isNullOrEmptyString(key)) { + return; + } else if ( + this.presentType === this.PresentTypes.AsView && + !this.isFormView(key) + ) { + return; + } else if (this.presentType !== this.PresentTypes.AsForm) { + return; + } + + // + let cIdx = this.fieldDescriptors.findIndex((c) => c.key === key); + if (cIdx < 0) { + return; + } + + // + if ( + isNullOrUndefined( + this.fieldDescriptors[cIdx].formControlConfig.focusHandler + ) + ) { + return; + } + + // + this.fieldDescriptors[cIdx].formControlConfig.focusHandler.next(); + } + + /** + * Re Produce Component model ... + * + * @param force + * @param model + */ + reNewModel(force = false, model?: T) { + // + if (force) { + // + if (isNullOrUndefined(model)) { + this.model = model; + } else { + // + this.model = { + ...model, + }; + } + } + + // + this.resetFieldDescriptors(); + this.handleResetFieldStates(); + this.reloadComponent().then(() => (this.showComponent = true)); + } + + /** + * reset all Changes ... + */ + reset() { + // + this.handleResetFieldStates(); + + // + this.MODEL = { + ...this.BASE_MODEL, + }; + + // + this.changeSubject.next({ ...this.MODEL }); + } + //#endregion + //#endregion + + // + //#region Focus Handler ... + private focusHandlerIntervalId: NodeJS.Timeout = undefined; + public registerFocusHandler(formName: string, key: string) { + // + this.unregisterFocusHandler(); + if (isNullOrEmptyString(formName) || isNullOrEmptyString(key)) { + return; + } + + // + let className = `x-form-${formName}`; + this.focusHandlerIntervalId = setInterval(() => { + // + const target = document.querySelector(`.${className}`) as HTMLElement; + if (!isNullOrUndefined(target)) { + // + this.focusOnFormControl(key); + this.unregisterFocusHandler(); + } + }, 100); + } + private unregisterFocusHandler() { + // + if (isNullOrUndefined(this.focusHandlerIntervalId)) { + return; + } + + // + clearInterval(this.focusHandlerIntervalId); + this.focusHandlerIntervalId = undefined; + } + //#endregion + + // + //#region Action Provider ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription = undefined; + private FORM_ACTION_PROVIDER_SUBSCRIPTION: Subscription = undefined; + private CARD_ACTION_PROVIDER_SUBSCRIPTION: Subscription = undefined; + + // + private registerActionProvider() { + // + this.unregisterActionProviders(); + + // + if (!isNullOrUndefined(this.actionProvider)) { + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (event) => { + // + if ( + isNullOrUndefined(event) || + isNullOrEmptyString(event.action) || + !isVBaseDtoAction(event.action) + ) { + return; + } + + // + await this.handleAction(event.action, event.payload); + }); + } + + // + if (!isNullOrUndefined(this.formActionProvider)) { + // + this.FORM_ACTION_PROVIDER_SUBSCRIPTION = + this.formActionProvider.subscribe(async (event) => { + // + if ( + isNullOrUndefined(event) || + isNullOrEmptyString(event.action) || + !isVBaseDtoAction(event.action) + ) { + return; + } + + // + await this.handleFormAction(event.action, event.payload); + }); + } + + // + if (!isNullOrUndefined(this.cardActionProvider)) { + // + this.CARD_ACTION_PROVIDER_SUBSCRIPTION = + this.cardActionProvider.subscribe(async (event) => { + // + if ( + isNullOrUndefined(event) || + isNullOrEmptyString(event.action) || + !isVBaseDtoAction(event.action) + ) { + return; + } + + // + await this.handleCardAction(event.action, event.payload); + }); + } + } + + // + private unregisterActionProviders() { + // + if (!isNullOrUndefined(this.ACTION_PROVIDER_SUBSCRIPTION)) { + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = undefined; + } + + // + if (!isNullOrUndefined(this.FORM_ACTION_PROVIDER_SUBSCRIPTION)) { + // + this.FORM_ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.FORM_ACTION_PROVIDER_SUBSCRIPTION = undefined; + } + + // + if (!isNullOrUndefined(this.CARD_ACTION_PROVIDER_SUBSCRIPTION)) { + // + this.CARD_ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.CARD_ACTION_PROVIDER_SUBSCRIPTION = undefined; + } + } + + // + sendAction(action: VBaseDtoAction, payload?: any) { + // + if (isNullOrUndefined(this.actionProvider)) { + return; + } + + // + this.actionProvider.next({ action, payload }); + } + + // + async handleAction(action: VBaseDtoAction, payload?: any) { + // + // Refresh ... + if (action === VBaseDtoAction.Refresh) { + // + if (isNullOrUndefined(payload)) { + this.handleChangeDetection(); + } else { + // + if (isNumber(payload)) { + this.runTimedOut(() => this.handleChangeDetection(), payload); + } else if ( + isBoolean(payload.force) && + toBoolean(payload.force) === true + ) { + this.handleChangeDetection(true); + } + } + } + + // + // Reload ... + if (action === VBaseDtoAction.Reload) { + await this.reloadComponent(); + } + + // + // Renew Model ... + if (action === VBaseDtoAction.RenewModel && !isNullOrUndefined(payload)) { + this.reNewModel(true, { ...payload }); + } + + // + // Set Properties ... + if ( + action === VBaseDtoAction.SetProperties && + !isNullOrUndefined(payload) + ) { + this.setProperties(payload.props, payload.section); + } + + // + // Focus On Form Control ... + if ( + action === VBaseDtoAction.FocusOnFormControl && + !isNullOrUndefined(payload) + ) { + this.focusOnFormControl(payload); + } + + // + // ReState ... + if (action === VBaseDtoAction.ReState) { + this.handleDtoStateManagement(); + } + } + + // + sendFormAction(action: XFormBaseAction | XFormControlAction, payload?: any) { + // + if (isNullOrUndefined(this.formActionProvider)) { + return; + } + + // + this.formActionProvider.next({ action, payload }); + } + + // + async handleFormAction( + action: XFormBaseAction | XFormControlAction | string, + payload?: any + ) {} + + // + sendCardAction(action: XCardAction, payload?: any) { + // + if (isNullOrUndefined(this.cardActionProvider)) { + return; + } + + // + this.cardActionProvider.next({ action, payload }); + } + + // + async handleCardAction(action: XCardAction | string, payload?: any) {} + //#endregion + + // + //#region Private ... + private registerChangeSubject() { + // + this.applyTakeUntilWrapper(this.changeSubject).subscribe((event) => { + this.handleDtoStateManagement(); + }); + } + + // + private handleResetFormView(toPresentType: string) { + // + this.cancelEdit(); + const isFormView = this.presentType === this.PresentTypes.AsForm; + if (isFormView && this.presentType !== toPresentType) { + this.presentType = toPresentType; + } + + // + this.detectChanges(); + } + + /** + * Clean All Open Field Edits and Form Configs ... + */ + private handleResetFieldStates() { + // + this.inFormFields = []; + this.inFormFieldValues = []; + this.modelFormConfig = undefined; + this.inFormFieldsFormConfigs = []; + } + + /** + * Handle State of Views ... + * + * @param disabled + */ + private async handleStateOfViews(disabled: boolean) { + // + // Check State of Field Descriptors ... + if (this.hasChild(this.fieldDescriptors)) { + // + // Loop through Descriptors ... + const fds = await Promise.all( + this.fieldDescriptors.map(async (fd) => { + // + // Check has Form Control Config ... + if (!isNullOrUndefined(fd.formControlConfig)) { + // + const appearance = await this.getValueAsync( + fd.formControlConfig.appearance + ); + appearance.disabled = disabled; + + // + fd.formControlConfig.appearance = { + ...appearance, + }; + } + + // + return fd; + }) + ); + + // + // Renew Field Descriptors ... + this.FIELD_DESCRIPTORS = [...fds]; + } + + // + // Check All Open Filed Form Configs ... + if (this.hasChild(this.inFormFieldsFormConfigs)) { + // + const fcs = await Promise.all( + this.inFormFieldsFormConfigs.map(async (fc) => { + // + if (!isNullOrUndefined(fc.controls)) { + // + let controls = await this.getArrayValueAsync(fc.controls); + if (this.hasChild(controls)) { + // + controls = await Promise.all([ + ...controls.map(async (c) => { + // + if (!isNullOrUndefined(c.appearance)) { + // + const appearance = await this.getValueAsync(c.appearance); + appearance.disabled = disabled; + c.appearance = { + ...appearance, + }; + } + + // + return c; + }), + ]); + + // + fc.controls = [...controls]; + } + } + + // + return fc; + }) + ); + + // + this.inFormFieldsFormConfigs = [...fcs]; + } + } + + /** + * Handle Prepare Form Model Config ... + * + * @returns + */ + private async prepareFormModelConfig() { + // + // Valide Model Form Config ... + if ( + isNullOrUndefined(this.modelFormConfig) || + isNullOrUndefined(this.modelFormConfig.controls) + ) { + return; + } + + // + let updated = false; + const controls = await this.getArrayValueAsync( + this.modelFormConfig.controls + ); + for (let control of controls) { + // + const appearance = await this.getValueAsync(control.appearance); + if (!isNullOrUndefined(appearance)) { + // + if ( + isNullOrUndefined(appearance.label) && + isNullOrUndefined(appearance.placeholder) + ) { + // + // Detect Field Descriptor ... + const fd = this.fieldDescriptors.find( + (f) => f.key === control.propName + ); + if (!isNullOrUndefined(fd)) { + // + // Set Label ... + appearance.label = fd.label; + // appearance.appearance = XFormControlAppearance.Outline; + + // + // Update Controle appearance ... + control.appearance = { + ...appearance, + }; + + // + if (!updated) { + updated = true; + } + } + } + } + } + + // + if (updated) { + // + this.modelFormConfig = { + ...this.modelFormConfig, + controls: [...controls], + }; + + // + this.detectChanges(); + } + + // + this.onFormModelConfigPrepared(); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\v-error\v-error.component.html + + + + + + + + +
+ +
+ + + +
+
+ + + +
+ + + + + + + + + +
+ +
+
+
+
+ + + +
+ {{ getErrorTitle() | async }} +
+
+ + + +
+ + {{ getSingleErrorMessage() | async }} + +
+
+ + + +
+
    +
  • + {{ error }} +
  • +
+
+
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\v-error\v-error.component.scss + +.v-error-title-text::after { + content: ":"; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\v-error\v-error.component.ts + +import { + isNullOrEmptyString, + XColorIdentifier, + XOneOrManyType, + XResourceIdentifier, + XStandardType, +} from 'x-framework-core'; +import { VBaseInCardComponent } from '../classes/v-base.component'; +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; +import { map } from 'rxjs/operators'; + +@Component({ + selector: 'v-error', + templateUrl: './v-error.component.html', + styleUrls: ['./v-error.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class VErrorComponent extends VBaseInCardComponent { + // + //#region Props ... + // + cardColor: XStandardType = this.ColorNames.DangerShade; + + // + @Input() + cssClass: XOneOrManyType; + + @Input() + errorTitle: XResourceIdentifier = this.ResourceIDs.error; + + @Input() + errorTitleColor: XColorIdentifier = this.ColorNames.Light; + + @Input() + errors: XOneOrManyType; + + @Input() + color: XColorIdentifier = this.ColorNames.Warning; + + @Input() + backgroundColor: XColorIdentifier = this.ColorNames.DangerShade; + //#endregion + + // + //#region Constructor ... + //#endregion + + // + //#region LifeCycles ... + //#endregion + + // + //#region Register Handlers ... + //#endregion + + // + //#region Task Handlers ... + //#endregion + + // + //#region UI Providers ... + getColor() { + return this.getValue(this.color).pipe( + map((color) => + !isNullOrEmptyString(color) ? color : this.ColorNames.Warning + ) + ); + } + + getErrorTitle() { + return this.getResourceValue( + isNullOrEmptyString(this.errorTitle.toString()) + ? this.errorTitle + : this.ResourceIDs.error + ); + } + + getErrorTitleColor() { + return this.getValue(this.errorTitleColor).pipe( + map((color) => + !isNullOrEmptyString(color) ? color : this.ColorNames.Light + ) + ); + } + + getBackgroundColorValue() { + return this.getValue(this.backgroundColor).pipe( + map((color) => + !isNullOrEmptyString(color) ? color : this.ColorNames.DangerShade + ) + ); + } + + getSingleErrorMessage() { + return this.getArrayValue(this.errors).pipe( + map((errors) => errors[0] || '') + ); + } + //#endregion + + // + //#region UI Handlers ... + //#endregion + + // + //#region Actions ... + //#endregion + + // + //#region Abstracts ... + //#endregion + + // + //#region Private ... + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\v-lazy-view\v-lazy-view.component.html + + + + + + + + +
+ +
+ + + +
+
+ + + +
+ + + + +
+
+
+ + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+ + + + +
+
+
+ + + Nothing ... + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\v-lazy-view\v-lazy-view.component.scss + +.v-lazy-view-loader { + text-align: center; + min-height: 50px; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\v-lazy-view\v-lazy-view.component.ts + +import { + Type, + Input, + NgZone, + Inject, + Output, + Component, + Renderer2, + ElementRef, + EventEmitter, + ChangeDetectorRef, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + XTask, + nameof, + XSpinner, + setTaskId, + generateUuid, + getFieldNames, + XStandardType, + XTaskPriority, + XOneOrManyType, + XColorIdentifier, + isNullOrUndefined, + XSpinnerIdentifier, + isNullOrEmptyString, + XResourceIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { + XEmptyAction, + XPopoverService, + XIconIdentifier, + XProgressBarType, + XEmptyActionModel, + XComponentCardContainer, + XComponentEmptyContainer, + IXComponentEmptyContainer, +} from 'x-framework-components'; +import { + VLazyViewLoadingType, + isLazyViewLoadingType, + VLazyViewLoadingTypeIdentifier, +} from './v-lazy-view.typings'; +import { Observable, Subject } from 'rxjs'; +import { X_CONFIG } from 'src/app/config/x.config'; +import { XConfig } from 'src/app/config/app.config'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { VBaseInCardComponent } from '../classes/v-base.component'; +import { SharedService } from 'src/app/shared/services/shared.service'; + +@Component({ + selector: 'v-lazy-view', + templateUrl: './v-lazy-view.component.html', + styleUrls: ['./v-lazy-view.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class VLazyView + extends VBaseInCardComponent + implements IXComponentEmptyContainer +{ + // + //#region Props ... + // + readonly LoaderSpinners = Object.assign({}, XSpinner); + readonly LoaderTypes = Object.assign({}, VLazyViewLoadingType); + readonly ProgressBarTypes = Object.assign({}, XProgressBarType); + + // + // Set a UUID for Task Handling ... + private UNIQUE_ID: string = generateUuid('VLazyView'); + get UUID() { + return this.UNIQUE_ID; + } + + /** + * a Data Model which required to Load ... + */ + model: TModel = undefined; + + /** + * Component Css Classes ... + */ + @Input() + cssClass: XOneOrManyType; + + /** + * Specified Lazy Loaded Component Type ... + */ + @Input() + componentType: Type; + + /** + * a Task Which Load the Data Model ... + */ + @Input() + modelLoaderTask: Observable; + + /** + * a Property to Set Data Model to Component ... + */ + @Input() + modelAssignOnComponentPropKey: keyof TView; + + /** + * Lazy Loaded Component Inputs ... + */ + @Input() + componentInputs: { + [key in keyof TView]?: any; + }; + + // + //#region Empty Props ... + // + showEmpty: XStandardType = true; + + // + emptyHasRefresh: XStandardType = true; + + // + @Output() + emptyRefresh: EventEmitter = this.emptyPropertyProvider.emptyRefresh; + + @Input() + emptyColor: XStandardType = + this.emptyPropertyProvider.emptyColor; + + @Input() + emptyMessage: XStandardType = + this.emptyPropertyProvider.emptyMessage; + + @Input() + emptyCssClass: XStandardType> = + this.emptyPropertyProvider.emptyCssClass; + + @Input() + emptyRefreshIcon: XStandardType = + this.emptyPropertyProvider.emptyRefreshIcon; + + @Input() + emptyMessageColor: XStandardType = + this.emptyPropertyProvider.emptyMessageColor; + + @Input() + emptyActionProvider: Subject = + this.emptyPropertyProvider.emptyActionProvider; + + @Input() + emptyForegroundColor: XStandardType = + this.emptyPropertyProvider.emptyForegroundColor; + + @Input() + emptyRefreshButtonColor: XStandardType = + this.emptyPropertyProvider.emptyRefreshButtonColor; + + @Input() + emptyForegroundMessageColor: XStandardType = + this.emptyPropertyProvider.emptyForegroundMessageColor; + + @Input() + emptyForegroundRefreshButtonColor: XStandardType = + this.emptyPropertyProvider.emptyForegroundRefreshButtonColor; + //#endregion + + // + // TODO: Add Props for Loading Support ... + @Input() + loaderColor: XColorIdentifier = XColorWithBrightness.Warning; + + @Input() + loaderType: VLazyViewLoadingTypeIdentifier = this.LoaderTypes.Spinner; + + @Input() + loaderSpinner: XSpinnerIdentifier = this.LoaderSpinners.Lines; + + // + @Output() + onComponentLoadingFinished = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + public constructor( + public zone: NgZone, + @Inject(X_CONFIG) + public config: XConfig, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public sharedService: SharedService, + public managerService: XManagerService, + public changeDetector: ChangeDetectorRef, + protected popoverService: XPopoverService, + protected propertyProvider: XComponentCardContainer, + protected emptyPropertyProvider: XComponentEmptyContainer + ) { + super( + zone, + config, + element, + renderer, + ruler, + sharedService, + managerService, + changeDetector, + popoverService, + propertyProvider + ); + } + //#endregion + + // + //#region LifeCycles ... + afterViewInit(): void { + super.afterViewInit(); + + // + this.handleProcessModel(); + } + + /** + * Process Binding Changes ... + * + * @param changeKeys + */ + onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + let isChanged = false; + let applyChanges = false; + + // + // Check Empty Props Changes ... + const emptyProps = [ + nameof('emptyColor'), + nameof('emptyMessage'), + nameof('emptyRefresh'), + nameof('emptyCssClass'), + nameof('emptyRefreshIcon'), + nameof('emptyMessageColor'), + nameof('emptyActionProvider'), + nameof('emptyForegroundColor'), + nameof('emptyRefreshButtonColor'), + nameof('emptyForegroundMessageColor'), + nameof('emptyForegroundRefreshButtonColor'), + ]; + isChanged = emptyProps.findIndex((ep) => changeKeys.includes(ep)) >= 0; + if (isChanged) { + // + if (this.handleEmptyPropertiesChanged()) { + if (!applyChanges) { + applyChanges = true; + } + } + } + + // + // Loader Color ... + isChanged = changeKeys.includes(nameof('loaderColor')); + if (isChanged) { + // + if ( + isNullOrUndefined(this.loaderColor) || + isNullOrEmptyString(this.loaderColor) + ) { + // + this.loaderColor = XColorWithBrightness.Warning; + if (!applyChanges) { + applyChanges = true; + } + } + } + + // + // Loader Type ... + isChanged = changeKeys.includes(nameof('loaderType')); + if (isChanged) { + // + if (!isLazyViewLoadingType(this.loaderType)) { + // + this.loaderType = this.LoaderTypes.Spinner; + if (!applyChanges) { + applyChanges = true; + } + } + } + + // + // Loader Spinner ... + isChanged = changeKeys.includes(nameof('loaderSpinner')); + if (isChanged) { + // + if ( + isNullOrUndefined(this.loaderSpinner) || + !isNullOrEmptyString(this.loaderSpinner) + ) { + // + this.loaderSpinner = this.LoaderSpinners.Lines; + if (!applyChanges) { + applyChanges = true; + } + } + } + + // + // Check Requirements for Handling Lazy Load ... + const modelProps = [ + nameof('componentType'), + nameof('modelLoaderTask'), + nameof('componentInputs'), + nameof('modelAssignOnComponentPropKey'), + ]; + isChanged = modelProps.findIndex((mp) => changeKeys.includes(mp)) >= 0; + if (isChanged) { + this.handleProcessModel(); + } + + // + if (applyChanges) { + this.handleDetectChanges(); + } + } + + /** + * Register Requirements ... + */ + registerViewHandlers(): void { + super.registerViewHandlers(); + } + //#endregion + + // + //#region Handlers ... + /** + * Handle Detect Changes ... + */ + handleDetectChanges() { + // + this.detectChanges(); + this.emptyActionProvider.next({ action: XEmptyAction.Refresh }); + + // + this.runTimedOut(() => this.handleDetectChanges(), 1000); + } + + /** + * Handle Process Model and Lazy Loading ... + */ + handleProcessModel() { + // + // Check Requirements ... + const isValid = + !this.loading && + !this.uiDisabled && + isNullOrUndefined(this.model) && + !isNullOrEmptyString(this.UUID) && + !isNullOrUndefined(this.componentType) && + !isNullOrUndefined(this.modelLoaderTask) && + !isNullOrUndefined(this.modelAssignOnComponentPropKey); + if (!isValid) { + return; + } + + // + this.applyLoadingWrapper(this.modelLoaderTask).subscribe((model) => { + // + if (!isNullOrUndefined(model)) { + // + this.model = { + ...model, + }; + } else { + this.model = undefined; + } + + // + // Prepare Inputs ... + if (isNullOrUndefined(this.componentInputs)) { + this.componentInputs = {}; + } + + // + // Set Model to Inputs ... + (this.componentInputs as any)[this.modelAssignOnComponentPropKey] = { + ...model, + }; + + // + this.handleDetectChanges(); + }); + } + + /** + * When Refresh Button Clicked ... + */ + handleRefreshTask() { + // + this.handleProcessModel(); + this.emptyRefresh.emit(); + } + + /** + * Provides Component instance for attaching + * Event Listeners or do anything else ... + * + * @param event + */ + handleComponentRendered(event: TView) { + this.onComponentLoadingFinished.emit(event); + } + //#endregion + + // + //#region Private ... + private handleEmptyPropertiesChanged() { + // + let result = false; + + // + if (isNullOrUndefined(this.emptyColor)) { + // + this.emptyColor = this.emptyPropertyProvider.emptyColor; + if (!result) { + result = true; + } + } + + // + if (isNullOrUndefined(this.emptyMessage)) { + // + this.emptyMessage = this.emptyPropertyProvider.emptyMessage; + if (!result) { + result = true; + } + } + + // + if (isNullOrUndefined(this.emptyCssClass)) { + // + this.emptyCssClass = this.emptyPropertyProvider.emptyCssClass; + if (!result) { + result = true; + } + } + + // + if (isNullOrUndefined(this.emptyRefreshIcon)) { + // + this.emptyRefreshIcon = this.emptyPropertyProvider.emptyRefreshIcon; + if (!result) { + result = true; + } + } + + // + if (isNullOrUndefined(this.emptyMessageColor)) { + // + this.emptyMessageColor = this.emptyPropertyProvider.emptyMessageColor; + if (!result) { + result = true; + } + } + + // + if (isNullOrUndefined(this.emptyActionProvider)) { + // + this.emptyActionProvider = this.emptyPropertyProvider.emptyActionProvider; + if (!result) { + result = true; + } + } + + // + if (isNullOrUndefined(this.emptyForegroundColor)) { + // + this.emptyForegroundColor = + this.emptyPropertyProvider.emptyForegroundColor; + if (!result) { + result = true; + } + } + + // + if (isNullOrUndefined(this.emptyRefreshButtonColor)) { + // + this.emptyRefreshButtonColor = + this.emptyPropertyProvider.emptyRefreshButtonColor; + if (!result) { + result = true; + } + } + + // + if (isNullOrUndefined(this.emptyForegroundMessageColor)) { + // + this.emptyForegroundMessageColor = + this.emptyPropertyProvider.emptyForegroundMessageColor; + if (!result) { + result = true; + } + } + + // + if (isNullOrUndefined(this.emptyForegroundRefreshButtonColor)) { + // + this.emptyForegroundRefreshButtonColor = + this.emptyPropertyProvider.emptyForegroundRefreshButtonColor; + if (!result) { + result = true; + } + } + + // + if (isNullOrUndefined(this.emptyRefresh)) { + // + this.emptyRefresh = this.emptyPropertyProvider.emptyRefresh; + if (!result) { + result = true; + } + } + + // + return result; + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\v-lazy-view\v-lazy-view.typings.ts + +import { isValueInEnum } from "../tools/v-base.tools"; + +export enum VLazyViewLoadingType { + Spinner = 'spinner', + Progress = 'progress', +} + +export type VLazyViewLoadingTypeIdentifier = VLazyViewLoadingType | string; + +export function isLazyViewLoadingType(value: VLazyViewLoadingTypeIdentifier) { + return isValueInEnum( + value, + Object.assign({}, VLazyViewLoadingType) + ); +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\v-page\v-page.component.html + + + +
+ +
+ + +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\v-page\v-page.component.scss + +// :host ::ng-deep .app-page-content { +// overflow: auto; +// height: calc(100% + (var(--content-offset-top) + var(--content-offset-bottom))); +// } + +// :host ::ng-deep x-fab { +// .x-fab { +// margin-bottom: calc(var(--content-offset-bottom)); +// } +// } + +.x-footer-content { + margin: 0; + padding: 0; +} + +:host ::ng-deep { + .x-toolbar-end-slot { + flex-shrink: 0 !important; + justify-content: flex-end; + } + + .x-toolbar-start-slot { + flex-shrink: 1 !important; + flex-grow: 7 !important; + justify-content: flex-start; + overflow: hidden; + text-overflow: ellipsis; + } + + x-expandable-list { + &.x-disabled { + // + // .item-parent, + // .item-child, + .expandable-item-parent { + background-color: var(--x-color-medium-shade) !important; + } + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\v-page\v-page.component.ts + +import { + Input, + Inject, + Output, + NgZone, + Renderer2, + Component, + ViewChild, + ElementRef, + TemplateRef, + EventEmitter, + ChangeDetectorRef, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + XColor, + nameof, + XLocale, + notValue, + formatDate, + XResourceIDs, + XPickerColumn, + XStandardType, + XOneOrManyType, + XBehaviourEvent, + XColorIdentifier, + XModalButtonRole, + getDateFormatString, + XPickerColumnOption, + XLanguageDescriptor, + isNullOrEmptyString, + XBehaviourEventType, + XColorWithBrightness, +} from 'x-framework-core'; +import { + XMenuSlot, + XLogoSlot, + XBackSlot, + XSideType, + XSideSlot, + XTitleSlot, + XIconNames, + XMenuState, + XContentSlot, + XPageContainer, + XPageComponent, + XPopoverOption, + XPopoverService, + XNavigatorListItem, + XBackSlotIdentifier, + XLogoSlotIdentifier, + XMenuSlotIdentifier, + XPageSizeIdentifier, + XSideSlotIdentifier, + XSideTypeIdentifier, + XTitleSlotIdentifier, + XContentSlotIdentifier, + XNavigatorStartupState, +} from 'x-framework-components'; +import { + XUserProfileDto, + isDefaultUserProfileDto, +} from 'x-framework-identity-sdk'; +import { map } from 'rxjs/operators'; +import { Subject, Subscription } from 'rxjs'; +import { MenuController } from '@ionic/angular'; +import { X_CONFIG } from 'src/app/config/x.config'; +import { NavigationExtras } from '@angular/router'; +import { XConfig } from 'src/app/config/app.config'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { + VBasePageAction, + VBasePageActionModel, +} from '../v-page-presenter/v-page-presenter.typings'; +import { XAppState } from './../typings/x.state.enum'; +import { XManagerService } from 'x-framework-services'; +import { isNullOrUndefined, hasChild } from 'x-framework-core'; +import { getNavPages, Pages } from 'src/app/config/page.config'; +import { AppResourceIDs } from 'src/app/config/localization.config'; +import { VUserPresentType } from '../account/v-user/v-user.typings'; +import { SharedService } from 'src/app/shared/services/shared.service'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'v-page', + templateUrl: './v-page.component.html', + styleUrls: ['./v-page.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class VPageComponent extends XPageComponent { + // + //#region Props ... + // + isMobileUi$ = this.managerService.isMobileUi$; + isNotMobileUi$ = this.managerService.isNotMobileUi$; + isAuthenticated$ = this.sharedService.isAuthenticated$; + + // + readonly IconNames = Object.assign({}, XIconNames); + readonly AppResourceIDs = Object.assign({}, AppResourceIDs); + readonly ColorNames = Object.assign({}, XColorWithBrightness); + readonly UserPresentTypes = Object.assign({}, VUserPresentType); + readonly NavigatorStartupStates = Object.assign({}, XNavigatorStartupState); + + // + //#region CaptureMode ... + @Input() + enableCaptureMode: XStandardType = false; + + @Input() + downloadCapturedImage: XStandardType = true; + + @Input() + captureTopOffset: XStandardType = 0; + + @Input() + captureBottomOffset: XStandardType = 0; + + @Output() + imageCaptured = new EventEmitter(); + //#endregion + + // + //#region Page Props ... + // + //#region Toolbar Content ... + @Input() + showToolbarContent = true; + + private TOOLBAR_CONTENT_TEMPLATE: TemplateRef; + + /** + * the toolbar content ... + */ + @ViewChild('toolbarContentTemplateRef', { static: false }) + set toolbarContentTemplateRef(v: TemplateRef) { + // + if (isNullOrUndefined(v)) { + return; + } + + // + this.TOOLBAR_CONTENT_TEMPLATE = v; + } + + get toolbarContentTemplateRef() { + return this.TOOLBAR_CONTENT_TEMPLATE; + } + //#endregion + + // + //#region Toolbar End Slot ... + @Input() + showToolbarEndSlot = true; + //#endregion + + // + @Input() + color: XStandardType = 'light-shade'; + + @Input() + cssClass: XStandardType>; + //#endregion + + // + //#region Toolbar Props ... + // + //#region Toolbar Boundary ... + // + @Input() + showToolbar: XStandardType = true; + + // + @Input() + toolbarColor: XStandardType = XColor.Primary; + //#endregion + + // + //#region Toolbar ProgressBar ... + @Input() + toolbarProgressBarColor: XStandardType = XColor.Warning; + + @Input() + toolbarShowProgressBar: XStandardType = false; + //#endregion + + // + //#region Toolbar Title and SubTitle ... + // + // Toolbar Title ... + @Input() + toolbarTitleSlot: XStandardType = XTitleSlot.End; + + @Input() + toolbarShowTitle: XStandardType = true; + + @Input() + toolbarTitle: XStandardType = super.resourceProvider( + this.ResourceIDs.app_name, + ); + //#endregion + + // + //#region Toolbar Logo ... + @Input() + toolbarLogoSlot: XStandardType = XLogoSlot.End; + + @Input() + toolbarShowLogo: XStandardType = true; + + @Input() + toolbarLogoUrl: XStandardType = './assets/image/logo.png'; + //#endregion + + // + //#region Toolbar Back ... + @Input() + toolbarBackSlot: XStandardType = XBackSlot.Start; + + @Input() + toolbarShowBack: XStandardType = false; + + @Input() + toolbarHasBack: XStandardType = true; + + @Input() + toolbarDefaultHref: string; + //#endregion + + // + //#region Toolbar Menu ... + @Input() + toolbarMenuSlot: XStandardType = XMenuSlot.Start; + + // + @Input() + menuState: XStandardType; + + // + @Input() + toolbarShowMenu: XStandardType = true; + + // + @Input() + toolbarHasMenu: XStandardType = true; + + @Input() + toolbarAutoHideMenu: XStandardType = true; + //#endregion + + // + //#region Toolbar Templates ... + @Input() + toolbarContentSlot: XStandardType = + XContentSlot.Start; + //#endregion + //#endregion + + // + //#region Scrolling Handlers ... + @Input() + provideScrollEvents: XStandardType = true; + + @Input() + scrollBehaviour: XOneOrManyType = ['x-fab']; + + @Input() + scrollStartClasses: XOneOrManyType = 'start-scroll'; + + @Input() + scrollEndClasses: XOneOrManyType = 'end-scroll'; + + @Input() + scrollEventActionProvider: Subject = + new Subject(); + //#endregion + + // + //#region Footer Props ... + // + @Input() + footerColor: XStandardType = XColor.Primary; + //#endregion + + // + //#region Side Props ... + // + @Input() + toggleMenuWhen: XStandardType = 'md'; + + @Input() + hasSide: XStandardType = true; + + @Input() + sideSlot: XStandardType = XSideSlot.Start; + + @Input() + sideType: XStandardType = XSideType.Overlay; + + @Input() + sideColor: XStandardType = XColor.Primary; + //#endregion + + // + navPages: XNavigatorListItem[] = []; + //#endregion + + // + //#region Constructor ... + public constructor( + public zone: NgZone, + @Inject(X_CONFIG) + public config: XConfig, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public sharedService: SharedService, + public menuController: MenuController, + public managerService: XManagerService, + public changeDetector: ChangeDetectorRef, + protected popoverService: XPopoverService, + protected propertyProvider: XPageContainer, + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + menuController, + propertyProvider, + ); + + // + this.isMobileUi$ = this.managerService.isMobileUi$; + this.isNotMobileUi$ = notValue(this.isMobileUi$); + } + //#endregion + + // + //#region LifeCycle ... + onInit() { + super.onInit(); + + // + this.menuState = this.isMobileUi$.pipe( + map((isMobile) => (isMobile ? XMenuState.WillClose : XMenuState.Opened)), + ); + } + + async afterViewInit() { + super.afterViewInit(); + + // + const showToolbar = await this.getValueAsync(this.showToolbar); + if (showToolbar) { + this.detectChanges(); + } + + // + this.managerService.dispatchResizeEvent(); + } + + afterViewChecked() { + super.afterViewChecked(); + + // + this.changeDetector.detectChanges(); + } + + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + const isHasSideChanged = changeKeys.includes( + nameof('hasSide'), + ); + if (isHasSideChanged) { + await this.handleHasSideEffect(); + } + + // + const isToolbarHasMenuChanged = changeKeys.includes( + nameof('toolbarHasMenu'), + ); + const isToolbarShowMenuChanged = changeKeys.includes( + nameof('toolbarShowMenu'), + ); + if (isToolbarHasMenuChanged || isToolbarShowMenuChanged) { + this.detectChanges(); + } + + // + let isChanged = changeKeys.includes( + nameof('actionProvider'), + ); + if (isChanged) { + this.subscribeActionProvider(); + } + + // + isChanged = changeKeys.includes( + nameof('toolbarShowProgressBar'), + ); + if (isChanged) { + // + this.detectChanges(); + this.actionProvider.next({ action: VBasePageAction.RefreshContainer }); + } + } + //#endregion + + // + //#region Register Handlers ... + registerViewHandlers() { + super.registerViewHandlers(); + + // + //#region User Handlers ... + this.prepareNavPages(null).then(() => { + this.detectChanges(); + }); + + // + // tslint:disable-next-line: deprecation + this.applyTakeUntilWrapper(this.sharedService.user$).subscribe( + async (user) => { + // + if (isNullOrUndefined(user) || isDefaultUserProfileDto(user)) { + return; + } + + // + await this.prepareNavPages(user); + }, + ); + //#endregion + + // + //#region Register ManagerService Loading Handler ... + // tslint:disable-next-line: deprecation + this.managerService.isLoading$.subscribe((loading) => { + this.toolbarShowProgressBar = loading; + }); + //#endregion + + // + //#region Register Scrolled Event Listener ... + this.applyTakeUntilWrapper(this.scrolledEvent.asObservable()).subscribe( + async (event) => { + await this.onScrolledEvent(event); + }, + ); + //#endregion + } + + registerInstanceHandlers(): void { + super.registerInstanceHandlers(); + } + //#endregion + + // + //#region Handlers ... + //#endregion + + // + //#region UI Providers ... + canShowUser(user: XUserProfileDto) { + // + const result = !isDefaultUserProfileDto(user); + return result; + } + //#endregion + + // + //#region UI Handlers ... + async handleNavigatorItemSelected(event: XNavigatorListItem) { + // + if (!event) { + return; + } + + // + // Check for Logout ... + if (event.id && event.id.includes('logout')) { + await this.handleLogout(); + } + } + + async handleMoreButtonClicked(event: any) { + // + const onlyIcon = false; + const isLoggedIn = await this.sharedService.authService.isLoggedIn(); + const currentRoute = decodeURI( + decodeURIComponent( + this.managerService.getFullUrl(this.managerService.getRoute()), + ), + ); + + // + const options: XPopoverOption[] = []; + + // + // Check Landing Page ... + const landingPageUrl = this.managerService.getFullUrl( + this.managerService.getPageRoute(Pages.Landing), + ); + const isInLandingPage = currentRoute == landingPageUrl; + if (!isInLandingPage) { + // + options.push({ + id: 'go_landing_page', + icon: XIconNames.locate, + title: AppResourceIDs.landing_page, + color: XColor.Tertiary, + slot: 'start', + onlyIcon, + handler: async () => { + await this.managerService.navigateByPageReplace(Pages.Landing); + }, + }); + } + + // + // Landing Page ... + // const landingPageUrl = this.managerService.getFullUrl( + // this.managerService.getPageRoute(Pages.Landing) + // ); + // const isInLandingPage = currentRoute === landingPageUrl; + // if (!isInLandingPage) { + // // + // if (!isNullOrUndefined(this.config.defaultLandingPage)) { + // options.push({ + // id: 'go_landing', + // icon: XIconNames.refresh, + // title: AppResourceIDs.landing_page_title, + // color: XColor.Tertiary, + // slot: 'start', + // onlyIcon, + // handler: async () => { + // await this.managerService.navigateByPageReplace(Pages.Landing); + // }, + // }); + // } + // } + + // + // Content Explorer Page ... + // const contentExplorerPageUrl = this.managerService.getFullUrl( + // this.managerService.getPageRoute(Pages.ContentExplorer) + // ); + // const isInContentExplorerPage = currentRoute === contentExplorerPageUrl; + // if (!isInContentExplorerPage) { + // // + // options.push({ + // id: 'go_content_explorer', + // icon: XIconNames.posts, + // title: AppResourceIDs.content_explorer_page_title, + // color: XColor.Tertiary, + // slot: 'start', + // onlyIcon, + // handler: async () => { + // await this.managerService.navigateByPageReplace( + // Pages.ContentExplorer + // ); + // }, + // }); + // } + + // + // Dashboard Page ... + if (isLoggedIn) { + // + const startupUrl = this.managerService.getFullUrl( + this.managerService.getPageRoute(Pages.Startup), + ); + const landingUrl = this.managerService.getFullUrl( + this.managerService.getPageRoute(Pages.Landing), + ); + const registerUrl = this.managerService.getFullUrl( + this.managerService.getPageRoute(Pages.Register), + ); + const notAuthorizedUrl = this.managerService.getFullUrl( + this.managerService.getPageRoute(Pages.NotAuthorized), + ); + const errorUrl = this.managerService.getFullUrl( + this.managerService.getPageRoute(Pages.Error), + ); + const isInOpenPages = + currentRoute === errorUrl || + currentRoute === startupUrl || + currentRoute === landingUrl || + currentRoute === registerUrl || + currentRoute === notAuthorizedUrl; + + // + if (isInOpenPages) { + options.push({ + id: 'go_dashboard', + icon: XIconNames.home, + title: AppResourceIDs.management_panel, + color: XColor.Tertiary, + slot: 'start', + onlyIcon, + handler: async () => { + await this.managerService.navigateByPageReplace(Pages.Home); + }, + }); + } + } + + // + // Change Language ... + options.push({ + id: 'change_language', + icon: XIconNames.language, + title: XResourceIDs.language, + color: XColor.Tertiary, + slot: 'start', + onlyIcon, + handler: async () => { + await this.handleChangeLocale(); + }, + }); + + // + // CaptureMode ... + // if (isLoggedIn) { + // const isCaptureModeEnabled = await this.getValueAsync( + // this.enableCaptureMode + // ); + // if (isCaptureModeEnabled) { + // // + // // Capture Mode ... + // options.push({ + // id: 'disable_capture_mode', + // icon: XIconNames.crop, + // title: XResourceIDs.disable_capture_mode, + // color: XColor.Danger, + // slot: 'start', + // onlyIcon, + // handler: async () => { + // // + // this.enableCaptureMode = false; + // this.detectChanges(); + // }, + // }); + // } else { + // // + // // Capture Mode ... + // options.push({ + // id: 'enable_capture_mode', + // icon: XIconNames.crop, + // title: XResourceIDs.enable_capture_mode, + // color: XColor.Tertiary, + // slot: 'start', + // onlyIcon, + // handler: async () => { + // // + // this.enableCaptureMode = true; + // this.detectChanges(); + // }, + // }); + // } + // } + + // + // Theme Manager ... + // if (isLoggedIn) { + // options.push({ + // id: 'theme_manager', + // icon: XIconNames.color_palette, + // title: AppResourceIDs.theme_manager_page_title, + // color: XColor.Tertiary, + // slot: 'start', + // onlyIcon, + // handler: async () => { + // await this.managerService.navigateByPageReplace(Pages.ThemeManager); + // }, + // }); + // } + + // + // Login/Logout/Dashboard ... + if (isLoggedIn) { + // + // Logout ... + options.push({ + id: 'logout', + icon: XIconNames.logout, + title: AppResourceIDs.logout, + color: XColor.Danger, + slot: 'start', + onlyIcon, + handler: async () => { + await this.handleLogout(); + }, + }); + } else { + // + options.push({ + id: 'login', + icon: XIconNames.login, + title: AppResourceIDs.login, + color: XColor.Tertiary, + slot: 'start', + onlyIcon, + handler: async () => { + await this.managerService.navigateByPageReplace(Pages.Login); + }, + }); + } + + // + // Present Context Menu ... + await this.popoverService.presentContextMenu(options, event); + } + + async handleImageCaptured(image: string | Blob) { + // + if (image) { + this.imageCaptured.emit(image); + } + + // + this.enableCaptureMode = false; + this.detectChanges(); + } + + async handleUserSelected(user: XUserProfileDto) { + // + const navigationExtras: NavigationExtras = { + state: {}, + }; + navigationExtras.state[XAppState.UserProfile] = user; + + // + await this.managerService.navigateByPageReplace( + Pages.ProfileView, + navigationExtras, + ); + } + //#endregion + + // + //#region Provided Actions ... + async presentPageLockDialog() { + return new Promise(async (res) => { + await this.managerService.dialogService.presentActionModal({ + header: XResourceIDs.locked_msg, + buttons: [], + keyboardClose: true, + backdropDismiss: true, + hasNo: true, + noRole: XModalButtonRole.Destructive, + noHandler: () => { + res(false); + }, + hasYes: true, + yesRole: XModalButtonRole.Selected, + yesHandler: () => { + res(true); + }, + }); + }); + } + + applyDefaultDate(value: Date) { + // + let result = ''; + + // + if (!isNullOrUndefined(value)) { + result = formatDate( + value, + this.managerService.currentLocale, + getDateFormatString(this.managerService.currentLocale), + true, + ); + } + // + return result; + } + + /** + * do LockBreak action ... + * + * @param action a callback function ... + */ + async doLockBreakAction(action: (payload?: any) => void) { + // + // Check is Locked or not ... + if (this.lock) { + // + // if Locked, Present a Dialog for Lock Break ... + await this.managerService.dialogService.presentAreYouSureYesNoDialog( + async () => { + // + // if User Confirmed Lock Break, do Action ... + action(); + }, + ); + } else { + // + // do Action if there isn't any Lock ... + action(); + } + } + + /** + * do an Action which required to confirmed by user ... + * + * @param action + */ + async doConfirmedAction(action: (payload?: any) => void) { + await this.managerService.dialogService.presentAreYouSureYesNoDialog( + async () => { + // + // if User Confirmed Lock Break, do Action ... + action(); + }, + ); + } + //#endregion + + // + //#region Protected ... + //#endregion + + // + //#region Overrides ... + //#endregion + + // + //#region Abstraction ... + /** + * Calls When an Scroll Event Happens ... + * + * @param event + */ + async onScrolledEvent(event: XBehaviourEvent) {} + //#endregion + + // + //#region Action Provider ... + @Input() + public actionProvider: Subject = + new Subject(); + private ACTION_PROVIDER_SUBSCRIPTION: Subscription = undefined; + public unsubscribeActionProvider() { + // + if (isNullOrUndefined(this.ACTION_PROVIDER_SUBSCRIPTION)) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = undefined; + } + public subscribeActionProvider() { + // + this.unsubscribeActionProvider(); + if (isNullOrUndefined(this.actionProvider)) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe((res) => { + // + if (isNullOrUndefined(res) || isNullOrEmptyString(res.action)) { + return; + } + + // + if ( + res.action == VBasePageAction.Refresh || + res.action == VBasePageAction.RefreshContainer + ) { + this.detectChanges(); + } + }); + } + //#endregion + + // + //#region Private ... + private async handleHasSideEffect() { + // + const hasSide = await this.getValueAsync(this.hasSide); + if (!hasSide) { + // + this.toolbarHasMenu = false; + this.toolbarShowMenu = false; + this.toggleMenuWhen = ''; + } + } + + private async handleChangeLocale() { + // + const currentLocale = this.managerService.currentLocale; + const availableLanguages = this.config.availableLanguages.map( + (ai: XLanguageDescriptor) => { + return { + name: ai.name, + locale: ai.locale, + }; + }, + ); + if (!hasChild(availableLanguages)) { + return; + } + + // + const localOptions: XPickerColumnOption[] = availableLanguages.map((al) => { + // + const op: XPickerColumnOption = { + text: al.name, + value: al.locale, + disabled: al.locale === currentLocale, + }; + + // + return op; + }); + const localColumn: XPickerColumn = { + name: 'locales', + options: localOptions, + }; + + // + await this.managerService.dialogService.presentPicker({ + buttons: [ + { + text: XResourceIDs.ok, + role: XModalButtonRole.Selected, + cssClass: ['btn-selected'], + handler: async (value) => { + // + const selectedLocale: XLocale = value?.locales?.value; + if (!selectedLocale) { + return; + } + + // + await this.managerService.settingsService.changeLocale( + selectedLocale, + true, + ); + }, + }, + { + text: XResourceIDs.cancel, + role: XModalButtonRole.Cancel, + cssClass: ['btn-cancel'], + handler: () => {}, + }, + ], + columns: [localColumn], + }); + } + + private async handleLogout() { + // + await this.managerService.dialogService.presentAreYouSureYesNoDialog( + async () => { + await this.managerService.navigateByPageReplace(Pages.Logout); + }, + ); + } + + private async prepareNavPages(user: XUserProfileDto) { + // + this.navPages = []; + + // + if (!user) { + this.navPages = [...(await getNavPages(null))]; + this.detectChanges(); + return; + } + + // + const roleInfo = await this.getValueAsync(this.sharedService.userRoleInfo$); + if (!roleInfo) { + this.navPages = []; + this.detectChanges(); + return; + } + + // + this.navPages = [...(await getNavPages(roleInfo))]; + + // + this.detectChanges(); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\v-page-presenter\v-page-presenter.component.html + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\v-page-presenter\v-page-presenter.component.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\v-page-presenter\v-page-presenter.component.ts + +import { + Input, + Component, + TemplateRef, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + nameof, + isNullOrUndefined, + isNullOrEmptyString, +} from 'x-framework-core'; +import { + VBasePageAction, + VBasePageActionModel, +} from './v-page-presenter.typings'; +import { Subject, Subscription } from 'rxjs'; +import { VPageComponent } from '../v-page/v-page.component'; +import { VBaseComponent } from '../classes/v-base.component'; + +@Component({ + selector: 'v-page-presenter', + templateUrl: './v-page-presenter.component.html', + styleUrls: ['./v-page-presenter.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class VPagePresenter extends VBaseComponent { + // + //#region Props ... + @Input() + containerBase: VPageComponent; + + @Input() + contentTemplate: TemplateRef; + //#endregion + + // + //#region LifeCycles ... + onInit(): void { + super.onInit(); + } + + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + let isChanged = false; + let applyChanges = false; + + // + isChanged = changeKeys.includes(nameof('actionProvider')); + if (isChanged) { + this.subscribeActionProvider(); + } + + // + if (applyChanges) { + // + if (!isNullOrUndefined(this.containerBase)) { + this.containerBase.detectChanges(); + } + + // + this.detectChanges(); + } + } + //#endregion + + // + //#region Hnadlers ... + //#endregion + + // + //#region Action Provider ... + @Input() + public actionProvider: Subject = + new Subject(); + private ACTION_PROVIDER_SUBSCRIPTION: Subscription = undefined; + public unsubscribeActionProvider() { + // + if (isNullOrUndefined(this.ACTION_PROVIDER_SUBSCRIPTION)) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = undefined; + } + public subscribeActionProvider() { + // + this.unsubscribeActionProvider(); + if (isNullOrUndefined(this.actionProvider)) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe((res) => { + // + if (isNullOrUndefined(res) || isNullOrEmptyString(res.action)) { + return; + } + + // + if ( + res.action == VBasePageAction.Refresh || + res.action == VBasePageAction.RefreshHost + ) { + this.detectChanges(); + } + }); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\v-page-presenter\v-page-presenter.typings.ts + +import { isValueInEnum } from '../tools/v-base.tools'; +import { XBaseActionModel } from 'x-framework-components'; + +// +//#region Action Provider ... +export enum VBasePageAction { + Refresh = 'Refresh', + RefreshHost = 'RefreshHost', + RefreshContainer = 'RefreshContainer', +} + +export type VBasePageActionIdentifier = VBasePageAction | string; + +export function isVBasePageAction(value: string) { + return isValueInEnum(value, Object.assign(VBasePageAction, {})); +} + +export interface VBasePageActionModel + extends XBaseActionModel {} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\v-paginator\v-paginator.component.html + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + {{ + options.pageInfoProvider( + current, + options.pageSize, + totalPages, + totalItems + ) + }} + + + + +
+ + +
+ + {{ applyLocale(options.pageSize) }} + +
+
+
+ + + + + + + + + + + + + +
+ + + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\v-paginator\v-paginator.component.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\v-paginator\v-paginator.component.ts + +import { + isNumber, + isFunction, + getFieldNames, + isNullOrUndefined, + XColorIdentifier, +} from 'x-framework-core'; +import { + VPaginatorAction, + VPaginatorOptions, + isVPaginatorAction, + VPaginatorActionModel, + VPaginatorActionButton, +} from './v-paginator.typings'; +import { Subject, Subscription } from 'rxjs'; +import { VBaseComponent } from '../classes/v-base.component'; +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; + +@Component({ + selector: 'v-paginator', + templateUrl: './v-paginator.component.html', + styleUrls: ['./v-paginator.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class VPaginatorComponent extends VBaseComponent { + // + //#region Props ... + + // + readonly ComponentFiledNames = getFieldNames(this); + + @Input() + actionProvider: Subject = undefined; + + @Input() + showPageNumberInput = false; + + @Input() + current: number = undefined; + + @Input() + totalPages: number = undefined; + + @Input() + totalItems: number = undefined; + + @Input() + color: XColorIdentifier = undefined; + + @Input() + firstButtonDescriptor: VPaginatorActionButton = undefined; + + @Input() + nextButtonDescriptor: VPaginatorActionButton = undefined; + + @Input() + prevButtonDescriptor: VPaginatorActionButton = undefined; + + @Input() + lastButtonDescriptor: VPaginatorActionButton = undefined; + + // + @Input() + pageSizeProvider: () => number = undefined; + + @Input() + pageSizesProvider: () => Array = undefined; + + @Input() + pageInfoProvider: ( + page: number, + pageSize: number, + totalPages: number, + totalItems: number + ) => string = undefined; + + // + private OPTIONS: VPaginatorOptions = undefined; + get options() { + // + // Do Options Preparation ... + this.OPTIONS = { + // + first: { + ...this.firstButtonDescriptor, + }, + + // + last: { + ...this.lastButtonDescriptor, + }, + + // + next: { + ...this.nextButtonDescriptor, + }, + + // + prev: { + ...this.prevButtonDescriptor, + }, + + // + pageSize: this.pageSizeProvider(), + pageInfoProvider: this.pageInfoProvider, + pageSizes: [...this.pageSizesProvider()], + }; + + // + return this.OPTIONS; + } + //#endregion + + // + //#region LifeCycles ... + onInit(): void { + super.onInit(); + } + + afterViewInit(): void { + super.afterViewInit(); + + this.applyButtonDescriptors(); + } + + registerViewHandlers(): void { + super.registerViewHandlers(); + + // + this.subscribeActionProvider(); + } + + onChange(changeKeys: string[]): void { + super.onChange(changeKeys); + + // + let isChanged = false; + let applyChanges = false; + + // + // Action Provider ... + isChanged = changeKeys.includes(this.ComponentFiledNames.actionProvider); + if (isChanged) { + // + if (isNullOrUndefined(this.actionProvider)) { + // + applyChanges = true; + this.actionProvider = new Subject(); + } + + // + this.subscribeActionProvider(); + } + + // + // Color ... + isChanged = changeKeys.includes(this.ComponentFiledNames.color); + if (isChanged) { + // + if (isNullOrUndefined(this.color)) { + // + applyChanges = true; + this.color = this.ColorNames.Primary; + } + } + + // + // Page ... + isChanged = changeKeys.includes(this.ComponentFiledNames.pageSizeProvider); + if (isChanged) { + // + if ( + isNullOrUndefined(this.pageSizeProvider) || + !isFunction(this.pageSizeProvider) + ) { + // + applyChanges = true; + this.pageSizeProvider = () => { + return 0; + }; + } + } + + // + // Page Sizes ... + isChanged = changeKeys.includes(this.ComponentFiledNames.pageSizesProvider); + if (isChanged) { + // + if ( + isNullOrUndefined(this.pageSizesProvider) || + !isFunction(this.pageSizesProvider) + ) { + // + applyChanges = true; + this.pageSizesProvider = () => { + return [0]; + }; + } + } + + // + // Page Info Provider ... + isChanged = changeKeys.includes(this.ComponentFiledNames.pageInfoProvider); + if (isChanged) { + // + if ( + isNullOrUndefined(this.pageInfoProvider) || + !isFunction(this.pageInfoProvider) + ) { + // + applyChanges = true; + this.pageInfoProvider = ( + page: number, + pageSize: number, + totalPages: number, + totalItems: number + ) => { + return this.applyLocale(`${page}/${totalPages}`); + }; + } + } + + // + // Button Descriptors Change ... + isChanged = + changeKeys.includes(this.ComponentFiledNames.firstButtonDescriptor) || + changeKeys.includes(this.ComponentFiledNames.lastButtonDescriptor) || + changeKeys.includes(this.ComponentFiledNames.nextButtonDescriptor) || + changeKeys.includes(this.ComponentFiledNames.prevButtonDescriptor); + if (isChanged) { + applyChanges = this.applyButtonDescriptors(); + } + + // + if (applyChanges) { + this.detectChanges(); + } + } + //#endregion + + // + //#region Actions ... + firstPage() { + this.sendActionProviderAction(VPaginatorAction.First); + } + + lastPage() { + this.sendActionProviderAction(VPaginatorAction.Last); + } + + nextPage() { + this.sendActionProviderAction(VPaginatorAction.Next, this.current); + } + + previousPage() { + this.sendActionProviderAction(VPaginatorAction.Prev, this.current); + } + //#endregion + + // + //#region Handlers ... + async handlePageChange(event: CustomEvent | any) { + // + const selectedPage = (event.detail.value as number) || undefined; + if (!selectedPage) { + return; + } + + // + this.sendActionProviderAction(VPaginatorAction.To, selectedPage); + } + + async handlePageSizeChange(event?: any) { + this.sendActionProviderAction(VPaginatorAction.SelectPageSizeRequest, { + current: this.pageSizeProvider(), + allowed: this.pageSizesProvider(), + }); + } + //#endregion + + // + //#region Action Provider ... + private ACTION_PROVIDER_SUBSCRIPTION: Subscription = undefined; + private unsubscribeActionProvider() { + // + if (isNullOrUndefined(this.ACTION_PROVIDER_SUBSCRIPTION)) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = undefined; + } + private subscribeActionProvider() { + // + if (isNullOrUndefined(this.actionProvider)) { + return; + } + + // + this.unsubscribeActionProvider(); + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider.subscribe( + (action) => { + // + if (isNullOrUndefined(action)) { + return; + } + + // + // Handle Different Actions ... + if (action.action === VPaginatorAction.Refresh) { + // + if (!isNullOrUndefined(action.payload) && isNumber(action.payload)) { + this.runTimedOut(() => this.detectChanges(), action.payload); + } else { + this.detectChanges(); + } + } + } + ); + } + private sendActionProviderAction(action: VPaginatorAction, payload?: any) { + // + // Validate ... + if (!isVPaginatorAction(action) || isNullOrUndefined(this.actionProvider)) { + return; + } + + // + this.actionProvider.next({ action, payload }); + } + //#endregion + + // + //#region Private ... + private applyButtonDescriptors() { + // + let result = true; + + // + //#region First ... + // + // Object ... + if (isNullOrUndefined(this.firstButtonDescriptor)) { + this.firstButtonDescriptor = {}; + } + + // + // Color ... + if (isNullOrUndefined(this.firstButtonDescriptor.color)) { + this.firstButtonDescriptor.color = this.ColorNames.Light; + } + + // + // Icon .. + if (isNullOrUndefined(this.firstButtonDescriptor.icon)) { + this.firstButtonDescriptor.icon = this.IconNames.first; + } + + // + // Title .. + if (isNullOrUndefined(this.firstButtonDescriptor.title)) { + this.firstButtonDescriptor.title = this.ResourceIDs.first; + } + //#endregion + + // + //#region Last ... + // + // Object ... + if (isNullOrUndefined(this.lastButtonDescriptor)) { + this.lastButtonDescriptor = {}; + } + + // + // Color ... + if (isNullOrUndefined(this.lastButtonDescriptor.color)) { + this.lastButtonDescriptor.color = this.ColorNames.Light; + } + + // + // Icon .. + if (isNullOrUndefined(this.lastButtonDescriptor.icon)) { + this.lastButtonDescriptor.icon = this.IconNames.last; + } + + // + // Title .. + if (isNullOrUndefined(this.lastButtonDescriptor.title)) { + this.lastButtonDescriptor.title = this.ResourceIDs.last; + } + //#endregion + + // + //#region Next ... + // + // Object ... + if (isNullOrUndefined(this.nextButtonDescriptor)) { + this.nextButtonDescriptor = {}; + } + + // + // Color ... + if (isNullOrUndefined(this.nextButtonDescriptor.color)) { + this.nextButtonDescriptor.color = this.ColorNames.Light; + } + + // + // Icon .. + if (isNullOrUndefined(this.nextButtonDescriptor.icon)) { + this.nextButtonDescriptor.icon = this.IconNames.next; + } + + // + // Title .. + if (isNullOrUndefined(this.nextButtonDescriptor.title)) { + this.nextButtonDescriptor.title = this.ResourceIDs.next; + } + //#endregion + + // + //#region Prev ... + // + // Object ... + if (isNullOrUndefined(this.prevButtonDescriptor)) { + this.prevButtonDescriptor = {}; + } + + // + // Color ... + if (isNullOrUndefined(this.prevButtonDescriptor.color)) { + this.prevButtonDescriptor.color = this.ColorNames.Light; + } + + // + // Icon .. + if (isNullOrUndefined(this.prevButtonDescriptor.icon)) { + this.prevButtonDescriptor.icon = this.IconNames.previous; + } + + // + // Title .. + if (isNullOrUndefined(this.prevButtonDescriptor.title)) { + this.prevButtonDescriptor.title = this.ResourceIDs.previous; + } + //#endregion + + // + this.sendActionProviderAction(VPaginatorAction.UpdateButtonDescriptors, { + first: this.firstButtonDescriptor, + last: this.lastButtonDescriptor, + next: this.nextButtonDescriptor, + prev: this.prevButtonDescriptor, + }); + + // + return result; + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\v-paginator\v-paginator.typings.ts + +import { + XLocale, + XStandardType, + XColorIdentifier, + XResourceIdentifier, +} from 'x-framework-core'; +import { isValueInEnum } from '../tools/v-base.tools'; +import { XBaseActionModel, XIconIdentifier } from 'x-framework-components'; + +export interface VPaginatorActionButton { + hidden?: boolean; + disabled?: boolean; + icon?: XIconIdentifier; + color?: XColorIdentifier; + title?: XResourceIdentifier; + handler?: (item?: any) => XStandardType; +} + +// +//#region Action Provider ... +export enum VPaginatorAction { + Refresh = 'refresh', + To = 'To', + Last = 'Last', + Next = 'Next', + Prev = 'Prev', + First = 'First', + SelectPageSizeRequest = 'SelectPageSizeRequest', + UpdateButtonDescriptors = 'UpdateButtonDescriptors', +} +export type VPaginatorActionIdentifier = VPaginatorAction | string; +export function isVPaginatorAction(value: string) { + return isValueInEnum(value, Object.assign(VPaginatorAction, {})); +} +export interface VPaginatorActionModel + extends XBaseActionModel {} +//#endregion + +export interface VPaginatorOptions { + pageSizes: number[]; + pageSize: number; + first?: VPaginatorActionButton; + last?: VPaginatorActionButton; + next?: VPaginatorActionButton; + prev?: VPaginatorActionButton; + pageInfoProvider: ( + currentPage: number, + pageSize: number, + totalPages: number, + totalItems: number + ) => string; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\v-person\v-person.component.html + + + + + + + + + + +
+ +
+ + +
+ + + +
+
+ + + +
+ + + + +
+
+
+ + + + + + + + + + + + + + + + + +
+ {{ getFullName() }} +
+
+ + + +
+ +
+
+ + + +
+ +
+ +
+ +
+
+ {{ getFieldValue(FieldNames.username) }} +
+
+
+
+ + + +
+ + + + + + + + + + + + + + + +
+
+ + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\v-person\v-person.component.scss + +.v-avatar-box, +.v-avatar-image { + width: 32px; + height: 32px; +} + +.v-avatar-image { + // + margin: auto; + display: block; + max-width: 100%; + max-height: 100%; + + // + clip-path: circle(); +} + +.v-person-full-name { + font-size: small; +} +.v-person-user-name { + font-size: x-small; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\v-person\v-person.component.ts + +import { + XPersonDto, + defaultPersonDto, + preparePersonDtoFields, +} from 'x-saherelm-api-sdk'; +import { + PersonPresentType, + isPersonPresentType, + PersonPresentTypeIdentifier, +} from './v-person.typings'; +import { + XSlotName, + XSlotLayout, + XSlotNameIdentifier, +} from 'x-framework-components'; +import { + VBaseDtoComponent, + VModelFieldDescriptor, + VModelValidationResult, +} from '../v-dto/v-dto.typings'; +import { getPersonAvatar, getPersonFullName } from './v-person.tools'; +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; +import { nameof, getFieldNames, isNullOrEmptyString } from 'x-framework-core'; + +@Component({ + selector: 'v-person', + templateUrl: './v-person.component.html', + styleUrls: ['./v-person.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class VPersonComponent extends VBaseDtoComponent { + // + //#region Props ... + addDtoTaskName: string; + updateDtoTaskName: string; + + // + readonly SlotNames = Object.assign(XSlotName, {}); + readonly SlotLayouts = Object.assign(XSlotLayout, {}); + readonly PersonPresentTypes = Object.assign(PersonPresentType, {}); + + // + readonly defaultModel: XPersonDto = { ...defaultPersonDto }; + readonly FieldNames = getFieldNames(this.defaultModel); + + // + hideOptionalFieldsOnView: boolean = false; + + // + defaultFieldDescriptors: VModelFieldDescriptor[] = [ + // + // User Name ... + { + hide: false, + hideLabel: false, + allowFieldForm: false, + key: this.FieldNames.username, + value: this.defaultModel.username, + label: this.ResourceIDs.username, + type: this.ModelFieldTypes.String, + }, + // + // First Name ... + { + hide: false, + hideLabel: false, + allowFieldForm: false, + key: this.FieldNames.firstname, + value: this.defaultModel.firstname, + label: this.ResourceIDs.first_name, + type: this.ModelFieldTypes.String, + }, + // + // Last Name ... + { + hide: false, + hideLabel: false, + allowFieldForm: false, + key: this.FieldNames.lastname, + value: this.defaultModel.lastname, + label: this.ResourceIDs.last_name, + type: this.ModelFieldTypes.String, + }, + // + // Avatar ... + { + hide: false, + hideLabel: false, + allowFieldForm: false, + key: this.FieldNames.avatar, + value: this.defaultModel.avatar, + label: this.ResourceIDs.avatar, + type: this.ModelFieldTypes.String, + }, + ]; + + // + @Input() + presentType: PersonPresentTypeIdentifier = this.PersonPresentTypes.AsView; + + @Input() + avatarSlotInCardView: XSlotNameIdentifier = this.SlotNames.START; + + @Input() + showAvatarInCard: boolean = false; + //#endregion + + // + //#region LifeCycles ... + onChange(changeKeys: string[]): void { + super.onChange(changeKeys); + + // + let isChanged = false; + let applyChanges = false; + + // + // Handle Preset Type Changes ... + isChanged = changeKeys.includes(nameof('presentType')); + if (isChanged) { + // + if (!isPersonPresentType(this.presentType)) { + // + applyChanges = true; + this.presentType = this.PersonPresentTypes.AsView; + } + } + + // + // Handle Avatar Slot Changed ... + isChanged = changeKeys.includes( + nameof('avatarSlotInCardView'), + ); + if (isChanged) { + // + if (isNullOrEmptyString(this.avatarSlotInCardView)) { + applyChanges = true; + this.avatarSlotInCardView = this.SlotNames.START; + } + } + + // + if (applyChanges) { + this.detectChanges(); + } + } + //#endregion + + // + //#region UI Providers ... + getFullName() { + return getPersonFullName(this.model); + } + + getAvatar() { + return getPersonAvatar(this.model); + } + + getCardAvatar() { + // + if (!this.showAvatarInCard) { + return undefined; + } + + // + return this.getAvatar(); + } + //#endregion + + // + //#region Abstract ... + validate(model?: Partial): VModelValidationResult { + throw new Error('Method not implemented.'); + } + prepare(model?: XPersonDto): XPersonDto { + return preparePersonDtoFields(model); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\v-person\v-person.tools.ts + +import { XPersonDto } from 'x-saherelm-api-sdk'; +import { defaultPersonAvatar, defaultPersonFullName } from './v-person.typings'; +import { isNullOrEmptyString, isNullOrUndefined } from 'x-framework-core'; + +export function getPersonFullName(model: XPersonDto) { + // + let result = defaultPersonFullName; + + // + if ( + !isNullOrUndefined(model) && + (!isNullOrEmptyString(model.firstname) || + !isNullOrEmptyString(model.lastname)) + ) { + result = `${model.firstname} ${model.lastname}`; + } + + // + return result; +} + +export function getPersonAvatar(model: XPersonDto) { + // + let result = defaultPersonAvatar; + + // + if (!isNullOrUndefined(model) && !isNullOrEmptyString(model.avatar)) { + result = model.avatar; + } + + // + return result; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\v-person\v-person.typings.ts + +import { isValueInEnum } from "../public-api"; + +export enum PersonPresentType { + AsView = 'as_view', + AsName = 'as_name', + AsInfo = 'as_info', + AsCard = 'as_card', + AsAvatar = 'as_avatar', +} +export type PersonPresentTypeIdentifier = PersonPresentType | string; +export function isPersonPresentType(value: string) { + return isValueInEnum(value, Object.assign(PersonPresentType, {})); +} + +// +export const defaultPersonFullName = '...'; +export const defaultPersonAvatar = '/assets/image/default-user-image.png'; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\v-query-presenter\v-query-presenter.base.page.ts + +import { + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + TemplateRef, + ChangeDetectorRef, +} from '@angular/core'; +import { + XStandardType, + XBehaviourEvent, + XColorIdentifier, + isNullOrUndefined, + isNullOrEmptyString, + XResourceIdentifier, +} from 'x-framework-core'; +import { Subject } from 'rxjs'; +import { + XSlotName, + XListItem, + XSlotLayout, + XFabButtonSide, + XPageContainer, + XIconIdentifier, + XPopoverService, + XFormControlType, + XFormControlAppearance, + XSearchBarTypeIdentifier, + XSearchBarShowButtonIdentifier, + XSearchBarEnterKeyHintIdentifier, +} from 'x-framework-components'; +import { + defaultAddFabButton, + defaultEditFabButton, + defaultOptionsFabItem, + defaultRemoveFabButton, + defaultRefreshFabButton, +} from '../constants/fab.defaults'; +import { + VQueryPresenterTabs, + VQueryPresenterAction, + isVQueryPresenterAction, + VQueryPresenterContainer, + IVQueryPresenterContainer, + VQueryPresenterActionModel, + VQueryPresenterActionIdentifier, + VQueryPresenterPaginatorPosition, + VQueryFilledItemsProcessingResult, +} from './v-query-presenter.typings'; +import { + XSimpleFabItem, + toSimpleFabItems, +} from '../typings/simple.fab.typings'; +import { + defaultConnectSlideOption, + defaultDetailsSlideOption, +} from './../constants/slide.options.defaults'; +import { MenuController } from '@ionic/angular'; +import { X_CONFIG } from 'src/app/config/x.config'; +import { XConfig } from 'src/app/config/app.config'; +import { ViewportRuler } from '@angular/cdk/scrolling'; +import { XManagerService } from 'x-framework-services'; +import { VPageComponent } from '../v-page/v-page.component'; +import { XQueryDto, XQueryResultDto } from 'x-framework-identity-sdk'; +import { SharedService } from 'src/app/shared/services/shared.service'; +import { XScrollingHoldInfo } from 'src/app/shared/services/x-scrolling-provider.service'; + +/** + * a Base Implementation of VQueryPresenter Page ... + */ +@Component({ + template: '', +}) +export abstract class VQueryPresenterBasePage + extends VPageComponent + implements IVQueryPresenterContainer +{ + // + //#region Readonly Fields ... + // + readonly PaginatorPositions = Object.assign( + VQueryPresenterPaginatorPosition, + {} + ); + readonly SlotNames = Object.assign({}, XSlotName); + readonly SlotLayouts = Object.assign({}, XSlotLayout); + readonly FormControlTypes = Object.assign(XFormControlType, {}); + readonly VQueryPresenterActions = Object.assign(VQueryPresenterAction, {}); + readonly FormControlAppearances = Object.assign(XFormControlAppearance, {}); + //#endregion + + // + //#region Page Props ... + infinityScrollThreshold: XStandardType = '10px'; + //#endregion + + // + //#region Query Presenter Props ... + tabsCssClass: string[] = this.queryPresenterPropertyProvider.tabsCssClass; + tabsSelectedIndex: number = + this.queryPresenterPropertyProvider.tabsSelectedIndex; + tabsShowHeader: boolean = this.queryPresenterPropertyProvider.tabsShowHeader; + tabsDisablePagination: boolean = + this.queryPresenterPropertyProvider.tabsDisablePagination; + tabsAlignTabs: string = this.queryPresenterPropertyProvider.tabsAlignTabs; + tabsDynamicHeight: boolean = + this.queryPresenterPropertyProvider.tabsDynamicHeight; + tabsAnimationDuration: string = + this.queryPresenterPropertyProvider.tabsAnimationDuration; + tabsDisableRipple: boolean = + this.queryPresenterPropertyProvider.tabsDisableRipple; + tabsHeaderPosition: string = + this.queryPresenterPropertyProvider.tabsHeaderPosition; + tabsLoopTabs: boolean = this.queryPresenterPropertyProvider.tabsLoopTabs; + tabsSwipable: boolean = this.queryPresenterPropertyProvider.tabsSwipable; + tabsLock: boolean = this.queryPresenterPropertyProvider.tabsLock; + tabsColor: string = this.queryPresenterPropertyProvider.tabsColor; + tabsHeaderColor: string = this.queryPresenterPropertyProvider.tabsHeaderColor; + tabsActiveTabColor: string = + this.queryPresenterPropertyProvider.tabsActiveTabColor; + tabsTabIndicatorColor: string = + this.queryPresenterPropertyProvider.tabsTabIndicatorColor; + tabsTabPaginatorColor: string = + this.queryPresenterPropertyProvider.tabsTabPaginatorColor; + showPaginator: boolean = this.queryPresenterPropertyProvider.showPaginator; + paginatorPosition: string = + this.queryPresenterPropertyProvider.paginatorPosition; + paginatorColor: string = this.queryPresenterPropertyProvider.paginatorColor; + listTabTitle: XResourceIdentifier = + this.queryPresenterPropertyProvider.listTabTitle; + showItemsAsList: boolean = + this.queryPresenterPropertyProvider.showItemsAsList; + modelIdentifier: string = this.queryPresenterPropertyProvider.modelIdentifier; + wrapListTabWithCard: boolean = + this.queryPresenterPropertyProvider.wrapListTabWithCard; + listTabCardIsInPage: boolean = + this.queryPresenterPropertyProvider.listTabCardIsInPage; + listTabCardTitle: XResourceIdentifier = + this.queryPresenterPropertyProvider.listTabCardTitle; + listTabCardSubTitle: XResourceIdentifier = + this.queryPresenterPropertyProvider.listTabCardSubTitle; + listTabCardColor: string = + this.queryPresenterPropertyProvider.listTabCardColor; + showEmpty: boolean = this.queryPresenterPropertyProvider.showEmpty; + emptyCssClass: string[] = this.queryPresenterPropertyProvider.emptyCssClass; + emptyColor: string = this.queryPresenterPropertyProvider.emptyColor; + emptyForegroundColor: boolean = + this.queryPresenterPropertyProvider.emptyForegroundColor; + emptyMessage: XResourceIdentifier = + this.queryPresenterPropertyProvider.emptyMessage; + emptyMessageColor: string = + this.queryPresenterPropertyProvider.emptyMessageColor; + emptyForegroundMessageColor: boolean = + this.queryPresenterPropertyProvider.emptyForegroundMessageColor; + emptyHasRefresh: boolean = + this.queryPresenterPropertyProvider.emptyHasRefresh; + emptyRefreshButtonColor: string = + this.queryPresenterPropertyProvider.emptyRefreshButtonColor; + emptyForegroundRefreshButtonColor: boolean = + this.queryPresenterPropertyProvider.emptyForegroundRefreshButtonColor; + emptyRefreshIcon: XIconIdentifier = + this.queryPresenterPropertyProvider.emptyRefreshIcon; + detailsTabTitle: XResourceIdentifier = + this.queryPresenterPropertyProvider.detailsTabTitle; + addTabTitle: XResourceIdentifier = + this.queryPresenterPropertyProvider.addTabTitle; + updateTabTitle: XResourceIdentifier = + this.queryPresenterPropertyProvider.updateTabTitle; + showSearchBar: boolean = this.queryPresenterPropertyProvider.showSearchBar; + searchBarValue: string = this.queryPresenterPropertyProvider.searchBarValue; + searchBarDebounce: number = + this.queryPresenterPropertyProvider.searchBarDebounce; + searchBarSpellcheck: boolean = + this.queryPresenterPropertyProvider.searchBarSpellcheck; + searchBarCssClass: Array = + this.queryPresenterPropertyProvider.searchBarCssClass; + searchBarColor: XColorIdentifier = + this.queryPresenterPropertyProvider.searchBarColor; + searchBarForegroundColor: boolean = + this.queryPresenterPropertyProvider.searchBarForegroundColor; + searchBarTitle: XResourceIdentifier = + this.queryPresenterPropertyProvider.searchBarTitle; + searchBarType: XSearchBarTypeIdentifier = + this.queryPresenterPropertyProvider.searchBarType; + searchBarClearButtonIcon: XIconIdentifier = + this.queryPresenterPropertyProvider.searchBarClearButtonIcon; + searchBarCancelButtonIcon: XIconIdentifier = + this.queryPresenterPropertyProvider.searchBarCancelButtonIcon; + searchBarSearchButtonIcon: XIconIdentifier = + this.queryPresenterPropertyProvider.searchBarSearchButtonIcon; + searchBarSearchInputColor: XColorIdentifier = + this.queryPresenterPropertyProvider.searchBarSearchInputColor; + searchBarForegroundSearchInputColor: boolean = + this.queryPresenterPropertyProvider.searchBarForegroundSearchInputColor; + searchBarKeyboardType: XSearchBarTypeIdentifier = + this.queryPresenterPropertyProvider.searchBarKeyboardType; + searchBarCancelButtonTitle: XResourceIdentifier = + this.queryPresenterPropertyProvider.searchBarCancelButtonTitle; + searchBarClearButtonIconColor: XColorIdentifier = + this.queryPresenterPropertyProvider.searchBarClearButtonIconColor; + searchBarCancelButtonIconColor: XColorIdentifier = + this.queryPresenterPropertyProvider.searchBarCancelButtonIconColor; + searchBarSearchButtonIconColor: XColorIdentifier = + this.queryPresenterPropertyProvider.searchBarSearchButtonIconColor; + searchBarEnterKeyHint: XSearchBarEnterKeyHintIdentifier = + this.queryPresenterPropertyProvider.searchBarEnterKeyHint; + searchBarShowClearButton: XSearchBarShowButtonIdentifier = + this.queryPresenterPropertyProvider.searchBarShowClearButton; + searchBarShowCancelButton: XSearchBarShowButtonIdentifier = + this.queryPresenterPropertyProvider.searchBarShowCancelButton; + + // + //#region Abstracts ... + /** + * An Action Provider for Handle Connection Between Page and Component ... + */ + abstract queryPresenterActionProvider: Subject; + + /** + * if showItemsAsList false, use this template for Present Query items ... + */ + abstract itemTemplate: TemplateRef; + + /** + * if showItemsAsList true, use this template for Present Query items in List ... + */ + abstract listItemTemplate: TemplateRef; + + /** + * Specified Template for Adding Item ... + */ + abstract addItemTemplate: TemplateRef; + + /** + * Specified Template for Update Item ... + */ + abstract updateItemTemplate: TemplateRef; + + /** + * Specified Template for Details Item ... + */ + abstract detailsItemTemplate: TemplateRef; + + /** + * Provide Query ... + */ + abstract query: XQueryDto; + + /** + * holding dto's based query result ... + */ + abstract dtoQueryResult: XQueryResultDto; + //#endregion + //#endregion + + // + activeTab: number; + prevTab: number; + + /** + * Override Back Handling Mechanism for Toolbar ... + */ + toolbarBackHandler?: () => XStandardType = async () => { + // + // Check Params ... + const hasIdentifierParam = this.managerService.isContainQueryParam( + this.modelIdentifier + ); + if (!this.lock && hasIdentifierParam) { + // + if (this.activeTab !== VQueryPresenterTabs.List) { + // + this.sendQueryPresenterAction( + VQueryPresenterAction.ChangeTab, + VQueryPresenterTabs.List + ); + await this.managerService.navigate([], { + queryParams: { + id: null, + a: null, + }, + queryParamsHandling: 'merge', + }); + return; + } + } + + // + switch (this.activeTab) { + // + // Add ... + case VQueryPresenterTabs.Add: + // + // Go Back to List ... + await this.doLockBreakAction(() => { + // + // Send Change Tab ... + this.sendQueryPresenterAction( + VQueryPresenterAction.ChangeTab, + this.prevTab || VQueryPresenterTabs.List + ); + }); + break; + + // + // Update ... + case VQueryPresenterTabs.Update: + // + // Go Back to List ... + await this.doLockBreakAction(() => { + // + // Send Change Tab ... + this.sendQueryPresenterAction( + VQueryPresenterAction.ChangeTab, + this.prevTab || VQueryPresenterTabs.List + ); + }); + break; + + // + // Details ... + case VQueryPresenterTabs.Details: + // + // Send Change Tab ... + let tab = this.prevTab; + if (this.prevTab === VQueryPresenterTabs.Update) { + tab = VQueryPresenterTabs.List; + } + + // + this.sendQueryPresenterAction(VQueryPresenterAction.ChangeTab, tab); + break; + } + }; + + // + //#region Constructor ... + public constructor( + public zone: NgZone, + @Inject(X_CONFIG) + public config: XConfig, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public sharedService: SharedService, + public menuController: MenuController, + public managerService: XManagerService, + public changeDetector: ChangeDetectorRef, + protected popoverService: XPopoverService, + protected propertyProvider: XPageContainer, + protected queryPresenterPropertyProvider: VQueryPresenterContainer + ) { + super( + zone, + config, + element, + renderer, + ruler, + sharedService, + menuController, + managerService, + changeDetector, + popoverService, + propertyProvider + ); + } + //#endregion + + // + //#region LifeCycles ... + onInit(): void { + super.onInit(); + } + + async onChange(changeKeys: string[]): Promise { + await super.onChange(changeKeys); + + // + let isChanged = false; + let applyChanges = false; + + // + if (applyChanges) { + this.detectChanges(); + } + } + + async afterViewInit(): Promise { + await super.afterViewInit(); + + // + this.handleRegisterListeners(); + } + + onDestroy(): void { + super.onDestroy(); + + // + this.removeScrollingInfo(); + } + + async onLoading(state: boolean): Promise { + super.onLoading(state); + + // + await this.handleStateChanged(state); + } + + async onUiDisabled(state: boolean): Promise { + super.onUiDisabled(state); + + // + await this.handleStateChanged(state); + } + //#endregion + + // + //#region Predefine Configurations ... + configureAsExplorerPage() { + // + this.handleParams = true; + this.showSearchBar = false; + this.showPaginator = false; + this.tabsShowHeader = false; + this.showItemsAsList = false; + this.showPageRefresher = true; + this.listTabCardIsInPage = false; + this.wrapListTabWithCard = false; + this.tabsDisablePagination = true; + this.showPageInfinityScroller = true; + } + + configureAsManagementPage() { + // + this.handleParams = false; + this.showSearchBar = true; + this.showPaginator = true; + this.tabsShowHeader = false; + this.showItemsAsList = true; + this.showPageRefresher = false; + this.listTabCardIsInPage = true; + this.wrapListTabWithCard = true; + this.tabsDisablePagination = true; + this.showPageInfinityScroller = false; + } + //#endregion + + // + //#region Params Handling ... + /** + * a Flag for Enable/Disable Params Processing ... + */ + private handleParams = false; + protected hasParams = false; + + /** + * Parse Params and Request to Do it, if detected ... + * + * @returns + */ + async parseParams(): Promise { + super.parseParams(); + + // + if (!this.handleParams) { + // + await this.managerService.navigate([], { + queryParams: { + id: null, + a: null, + }, + queryParamsHandling: 'merge', + }); + return; + } + + // + // Handle Parse Param ... + const hasIdentifierParam = this.managerService.isContainQueryParam( + this.modelIdentifier + ); + + // + if (hasIdentifierParam) { + // + const identifier = this.managerService.getQueryParamValOfType( + this.modelIdentifier + ); + + // + let action = 'd'; + if (this.managerService.isContainQueryParam('a')) { + action = this.managerService.getQueryParamValOfType('a'); + } + + // + if (!isNullOrEmptyString(action)) { + // + let actionId = + action === 'd' + ? defaultDetailsSlideOption.id + : action === 'c' + ? defaultConnectSlideOption.id + : undefined; + + // + if ( + !isNullOrEmptyString(actionId) && + !isNullOrEmptyString(identifier) + ) { + // + this.hasParams = true; + await this.handleParamAction(identifier, actionId); + } + } + } + } + //#endregion + + // + //#region Abstraction ... + /** + * Run Provided Query and Retrieve Query Result ... + * + * @param query + */ + abstract handleGetQueryResult( + query: XQueryDto + ): Promise>; + + /** + * Retrieve Provided List Items and Validate them and also add Required Options + * then return validated list ... + * + * @param items + */ + abstract validateAndPrepareListItems( + items: Array> + ): Promise>>; + + /** + * Handle Request for Non as List Items Presenting ... + * + * @param current + * @param recieved + */ + abstract requestFillingItems( + exists: Array, + recieved: Array + ): Promise>; + + /** + * When Non as List Items Filled, this method call for Change Detection ... + * + * @param ids + */ + async handleRefreshViews(ids: Array) {} + + /** + * Handle all Provided actions for Specified Model ... + * + * @param info + */ + abstract doItemActions(info: { id: string; data: any }): Promise; + + /** + * a Flag which Specified has Query Result Error ... + */ + abstract hasQueryResultError: boolean; + + /** + * Handle Custo Error States ... + * + * @param error + */ + abstract handleCustomError( + error?: VQueryPresenterActionIdentifier + ): Promise; + + /** + * if a Params Recieved, Handle it ... + * + * @param identifier + * @param acttion + */ + async handleParamAction(identifier: string, action: string) {} + + /** + * State Management after Tab Open ... + * + * @param tab + */ + async handleStateAfterTabOpen(tab: number) { + // + this.prevTab = this.activeTab; + this.activeTab = tab; + this.closeActions(); + + // + this.toolbarHasBack = false; + this.toolbarShowBack = false; + let actions: XSimpleFabItem = undefined; + + // + const isAdmin = await this.getValueAsync(this.sharedService.isAdmin$); + const isAgent = await this.getValueAsync(this.sharedService.isAgent$); + const isAdminAgent = isAdmin || isAgent; + + // + switch (tab) { + // + // List ... + case VQueryPresenterTabs.List: + // + actions = { + ...defaultOptionsFabItem, + childs: [ + // + // Add ... + { + ...defaultAddFabButton, + handler: async () => { + // + this.sendQueryPresenterAction( + VQueryPresenterAction.ChangeTab, + VQueryPresenterTabs.Add + ); + + // + this.closeActions(); + }, + disabled: this.loading || this.uiDisabled || !isAdminAgent, + }, + // + // Refresh ... + { + ...defaultRefreshFabButton, + handler: async () => { + // + this.sendQueryPresenterAction(VQueryPresenterAction.ResetQuery); + + // + this.closeActions(); + }, + disabled: this.loading || this.uiDisabled, + }, + ], + }; + + // + setTimeout(() => { + this.infinityScrollDisabled = false; + }, 1000); + await this.handleScrollingState(500); + break; + + // + // Add ... + case VQueryPresenterTabs.Add: + // + this.toolbarHasBack = true; + this.toolbarShowBack = true; + break; + + // + // Details ... + case VQueryPresenterTabs.Details: + // + this.toolbarHasBack = true; + this.toolbarShowBack = true; + + // + actions = { + ...defaultOptionsFabItem, + childs: [ + // + // Remove ... + { + ...defaultRemoveFabButton, + index: 0, + side: XFabButtonSide.START, + handler: async () => { + // + await this.doItemActions({ + id: defaultRemoveFabButton.id, + data: undefined, + }); + + // + this.closeActions(); + }, + disabled: this.loading || this.uiDisabled, + }, + // + // Edit ... + { + ...defaultEditFabButton, + index: 1, + handler: async () => { + // + await this.doItemActions({ + id: defaultEditFabButton.id, + data: undefined, + }); + + // + this.closeActions(); + }, + disabled: this.loading || this.uiDisabled, + }, + ], + }; + break; + + // + // Update ... + case VQueryPresenterTabs.Update: + // + this.toolbarHasBack = true; + this.toolbarShowBack = true; + break; + } + + // + if (!isNullOrUndefined(actions)) { + // + this.actions = { + ...actions, + }; + } else { + this.actions = undefined; + } + + // + this.detectChanges(); + } + + /** + * State Management before Tab Changed + * + * @param from + * @param to + */ + async handleStateBeforeTabOpen(from: number, to: number) { + // + if (from === VQueryPresenterTabs.List) { + this.infinityScrollDisabled = true; + } + } + //#endregion + + // + //#region Private ... + private async handleStateChanged(state: boolean) { + // + // Handle Fab Actions State Management ... + if (!isNullOrUndefined(this.actions)) { + // + let actions: XSimpleFabItem = await toSimpleFabItems(this.actions); + + // + this.actions = { + ...actions, + disabled: actions.disabled || this.loading || this.uiDisabled, + childs: [ + ...actions.childs.map((i) => { + return { + ...i, + disabled: i.disabled || this.loading || this.uiDisabled, + }; + }), + ], + }; + } + + // + const prevTab = this.prevTab; + await this.handleStateAfterTabOpen(this.activeTab); + this.prevTab = prevTab; + } + //#endregion + + // + //#region Scroller Handler ... + /** + * Handling Scroll Event ... + * + * @param event + */ + async onScrolledEvent(event: XBehaviourEvent): Promise { + await super.onScrolledEvent(event); + + // + if ( + event.type === 'scrolling' && + this.activeTab === VQueryPresenterTabs.List + ) { + // + const identifier = this.constructor.name; + const yOffset = event.event.detail.scrollTop as number; + const xOffset = event.event.detail.scrollLeft as number; + + // + if (yOffset > 0) { + // + const infoModel: XScrollingHoldInfo = { + identifier, + yOffset, + xOffset, + }; + + // + this.sharedService.scrollingInfoProvider.set(infoModel); + } + } + } + + /** + * Handle Scrolling to last Saved State ... + */ + async handleScrollingState(delay?: number) { + // + const identifier = this.constructor.name; + const infoModel = this.sharedService.scrollingInfoProvider.get(identifier); + + // + // Validate ... + if ( + !isNullOrUndefined(infoModel) && + infoModel.yOffset > 0 && + this.activeTab === VQueryPresenterTabs.List + ) { + // + let identifier: string = undefined; + if ( + !isNullOrUndefined(this.dtoQueryResult) && + this.hasChild(this.dtoQueryResult.items) + ) { + identifier = ( + this.dtoQueryResult.items[this.dtoQueryResult.items.length - 1] as any + )[this.modelIdentifier]; + } + + // + if (isNullOrEmptyString(identifier)) { + return; + } + + // + await this.waitForElement( + `[${this.modelIdentifier}="${identifier}"]` + ).then(async (el) => { + // + this.infinityScrollDisabled = true; + + // + setTimeout(async () => { + // + await this.scrollByPoint(infoModel.xOffset, infoModel.yOffset, delay); + setTimeout(() => { + this.infinityScrollDisabled = false; + }, 1000); + }, delay); + }); + } + } + + /** + * Remove Scrolling Info ... + */ + removeScrollingInfo() { + // + const identifier = this.constructor.name; + if (this.sharedService.scrollingInfoProvider.has(identifier)) { + this.sharedService.scrollingInfoProvider.remove(identifier); + } + } + //#endregion + + // + //#region Action Provider ... + private handleRegisterListeners() { + // + // Register Query Presenter Action Provider ... + if (!isNullOrUndefined(this.queryPresenterActionProvider)) { + // + this.applyTakeUntilWrapper(this.queryPresenterActionProvider).subscribe( + async (event) => { + // + if ( + isNullOrUndefined(event) || + isNullOrEmptyString(event.action) || + !isVQueryPresenterAction(event.action) + ) { + return; + } + + // + await this.handleQueryPresenterAction(event.action, event.payload); + } + ); + } + + // + // Register Load More Event Listener ... + this.applyTakeUntilWrapper(this.loadMore.asObservable()).subscribe( + (res) => { + // + // Setting Timout for Cancel Loading ... + setTimeout(async () => { + await this.cancelLoading(res.event); + }, 500); + + // + this.sendQueryPresenterAction( + VQueryPresenterAction.LoadMore, + this.hasQueryResultError + ); + } + ); + + // + // Register Page Refresh Event Listener ... + this.applyTakeUntilWrapper(this.refreshPage.asObservable()).subscribe( + (res) => { + // + // Setting Timout for Cancel Loading ... + setTimeout(async () => { + await this.cancelLoading(res.event); + }, 500); + + // + this.sendQueryPresenterAction(VQueryPresenterAction.ResetQuery); + } + ); + } + + protected sendQueryPresenterAction( + action: VQueryPresenterAction, + payload?: any + ) { + // + if (isNullOrUndefined(this.queryPresenterActionProvider)) { + return; + } + + // + this.queryPresenterActionProvider.next({ action, payload }); + } + + protected async handleQueryPresenterAction( + action: VQueryPresenterActionIdentifier, + payload?: any + ) { + // + // TabOpenedState ... + if ( + action === VQueryPresenterAction.TabOpenedState && + !isNullOrUndefined(payload) + ) { + await this.handleStateAfterTabOpen(payload); + } + + // + // TabRequestState ... + if ( + action === VQueryPresenterAction.TabRequestState && + !isNullOrUndefined(payload) + ) { + await this.handleStateBeforeTabOpen(payload.from, payload.to); + } + + // + // RequestQueryResult ... + if (action === VQueryPresenterAction.RequestQueryResult) { + // + try { + // + // Normalize Payload ... + if (isNullOrUndefined(payload)) { + // + payload = { + ...this.query, + }; + } + + // + this.activeTab = VQueryPresenterTabs.List; + const result = await this.handleGetQueryResult(payload); + + // + // Validate Result ... + if (!isNullOrUndefined(result)) { + // + this.sendQueryPresenterAction( + VQueryPresenterAction.ResponseQueryResult, + { + query: payload, + result, + } + ); + } + } catch { + await this.handleCustomError(action); + } + } + + // + // ValidateListItems ... + if (action === VQueryPresenterAction.ValidateListItems) { + // + const items = await this.validateAndPrepareListItems(payload); + this.sendQueryPresenterAction( + VQueryPresenterAction.ResponseListItems, + items + ); + } + + // + // Request Item Action ... + if ( + action === VQueryPresenterAction.RequestItemAction && + !isNullOrUndefined(payload) + ) { + await this.doItemActions(payload); + } + + // + // Request Filling Items ... + if ( + action === VQueryPresenterAction.RequestFillingItems && + !isNullOrUndefined(payload) + ) { + // + const model = await this.requestFillingItems( + [...payload.exists], + [...payload.recieved] + ); + + // + this.sendQueryPresenterAction( + VQueryPresenterAction.ResponseFillingItems, + model + ); + this.infinityScrollDisabled = true; + } + + // + // Refresh Views ... + if ( + action === VQueryPresenterAction.RefreshViews && + !isNullOrUndefined(payload) + ) { + await this.handleRefreshViews(payload); + } + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\v-query-presenter\v-query-presenter.component.html + + + + + + + + +
{{ resourceProvider(listTabTitle) }}
+ + +
+ +
+
+ + + + +
{{ resourceProvider(detailsTabTitle) }}
+ + +
+ +
+
+ + + + +
{{ resourceProvider(addTabTitle) }}
+ + +
+ +
+
+ + + + +
{{ resourceProvider(updateTabTitle) }}
+ + +
+ +
+
+
+ + + + + + + + + + + + + +
+ + + + + + + + + + + +
+
+ + + + + + +
+ + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ + + + + + +
+ +
+
+ + + + + + +
+ +
+
+ + + + + + + +
List ...
+
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\v-query-presenter\v-query-presenter.component.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\v-query-presenter\v-query-presenter.component.ts + +import { + Input, + NgZone, + Inject, + Output, + Renderer2, + Component, + ElementRef, + TemplateRef, + EventEmitter, + ChangeDetectorRef, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + nameof, + getKeys, + isNumber, + toNumber, + isBoolean, + toBoolean, + XColorIdentifier, + XModalButtonRole, + isNullOrUndefined, + isNullOrEmptyString, + XPickerColumnOption, + XResourceIdentifier, +} from 'x-framework-core'; +import { Subject } from 'rxjs'; +import { + XCardAction, + XListAction, + XTabsAction, + XIconIdentifier, + XPopoverService, + XTabChangeEvent, + XCardActionModel, + XListActionModel, + XSearchBarAction, + XTabsActionModel, + XTabAlignIdentifier, + XListActionIdentifier, + XSearchBarActionModel, + XTabsActionIdentifier, + XSearchBarTypeIdentifier, + XSearchBarActionIdentifier, + XListItemOptionSelectedEvent, + XTabsHeaderPositionIdentifier, + XSearchBarShowButtonIdentifier, + XSearchBarEnterKeyHintIdentifier, +} from 'x-framework-components'; +import { + VQueryPresenterTabs, + VQueryPresenterAction, + isVQueryPresenterAction, + VQueryPresenterContainer, + IVQueryPresenterContainer, + VQueryPresenterActionModel, + VQueryPresenterActionIdentifier, + VQueryPresenterPaginatorPosition, + VQueryFilledItemsProcessingResult, + VQueryPresenterPaginatorPositionIdentifier, +} from './v-query-presenter.typings'; +import { + VPaginatorAction, + VPaginatorActionModel, + VPaginatorActionButton, + VPaginatorActionIdentifier, +} from '../v-paginator/v-paginator.typings'; +import { X_CONFIG } from 'src/app/config/x.config'; +import { XConfig } from 'src/app/config/app.config'; +import { isValueInEnum } from '../tools/v-base.tools'; +import { ViewportRuler } from '@angular/cdk/scrolling'; +import { XManagerService } from 'x-framework-services'; +import { VBaseComponent } from '../classes/v-base.component'; +import { XQueryDto, XQueryResultDto } from 'x-framework-identity-sdk'; +import { XSimpleListItem } from '../typings/simple.list-item.typings'; +import { SharedService } from 'src/app/shared/services/shared.service'; + +/** + * a Component for Presenting items based on Query Model ... + */ +@Component({ + selector: 'v-query-presenter', + templateUrl: 'v-query-presenter.component.html', + styleUrls: ['v-query-presenter.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class VQueryPresenterComponent + extends VBaseComponent + implements IVQueryPresenterContainer +{ + // + //#region Props ... + readonly PaginatorPositions = Object.assign( + VQueryPresenterPaginatorPosition, + {} + ); + + // + //#region Tabs Presenter Props ... + @Input() + tabsCssClass: Array = this.propertyProvider.tabsCssClass; + + @Input() + tabsSelectedIndex: number = this.propertyProvider.tabsSelectedIndex; + + @Input() + tabsShowHeader: boolean = this.propertyProvider.tabsShowHeader; + + @Input() + tabsDisablePagination: boolean = this.propertyProvider.tabsDisablePagination; + + @Input() + tabsAlignTabs: XTabAlignIdentifier = this.propertyProvider.tabsAlignTabs; + + @Input() + tabsDynamicHeight: boolean = this.propertyProvider.tabsDynamicHeight; + + @Input() + tabsAnimationDuration: string = this.propertyProvider.tabsAnimationDuration; + + @Input() + tabsDisableRipple: boolean = this.propertyProvider.tabsDisableRipple; + + @Input() + tabsHeaderPosition: XTabsHeaderPositionIdentifier = + this.propertyProvider.tabsHeaderPosition; + + @Input() + tabsLoopTabs: boolean = this.propertyProvider.tabsLoopTabs; + + @Input() + tabsSwipable: boolean = this.propertyProvider.tabsSwipable; + + @Input() + tabsLock: boolean = this.propertyProvider.tabsLock; + + @Input() + tabsColor: XColorIdentifier = this.propertyProvider.tabsColor; + + @Input() + tabsHeaderColor: XColorIdentifier = this.propertyProvider.tabsHeaderColor; + + @Input() + tabsActiveTabColor: XColorIdentifier = + this.propertyProvider.tabsActiveTabColor; + + @Input() + tabsTabIndicatorColor: XColorIdentifier = + this.propertyProvider.tabsTabIndicatorColor; + + @Input() + tabsTabPaginatorColor: XColorIdentifier = + this.propertyProvider.tabsTabPaginatorColor; + //#endregion + + // + //#region Non Inputs ... + // + // Action Providers ... + listActionProvider = new Subject(); + tabsActionProvider = new Subject(); + listTabCardActionProvider = new Subject(); + searchBarActionProvider = new Subject(); + paginatorActionProvider = new Subject(); + + @Output() + tabsAnimationDone = new EventEmitter(); + + @Output() + tabsSelectedIndexChange = new EventEmitter(); + + @Output() + tabsSelectedTabChange = new EventEmitter(); + //#endregion + + @Input() + actionProvider = new Subject(); + //#endregion + + // + //#region Constructor ... + public constructor( + public zone: NgZone, + @Inject(X_CONFIG) + public config: XConfig, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public sharedService: SharedService, + public managerService: XManagerService, + public changeDetector: ChangeDetectorRef, + protected popoverService: XPopoverService, + public propertyProvider: VQueryPresenterContainer + ) { + super( + zone, + config, + element, + renderer, + ruler, + sharedService, + managerService, + changeDetector, + popoverService + ); + } + //#endregion + + // + //#region LifeCycles ... + afterViewInit(): void { + super.afterViewInit(); + + // + this.handleRegisterListeners(); + } + + onChange(changeKeys: string[]): void { + super.onChange(changeKeys); + + // + let isChanged = changeKeys.includes( + nameof('query') + ); + if (isChanged) { + this.resetQuery(); + } + + // + this.validateProperties(); + } + + async onLoading(state: boolean): Promise { + super.onLoading(state); + + // + await this.handleStateChanges(state); + } + + async onUiDisabled(state: boolean): Promise { + super.onUiDisabled(state); + + // + await this.handleStateChanges(state); + } + //#endregion + + // + //#region Properties ... + /** + * Extract Current Properties ... + * + * @returns + */ + getProperties() { + // + let keys = getKeys(this.propertyProvider); + let result: Partial = {}; + + // + keys.forEach((k) => { + (result as any)[k] = (this as any)[k]; + }); + + // + return result; + } + + /** + * Set Properties ... + * + * @param props + */ + setProperties(props: Partial) { + // + let applyChanges = false; + let keys = Object.keys(props); + + // + keys.forEach((k) => { + // + const value = (props as any)[k]; + const currentValue = (this as any)[k]; + const defaultValue = (this.propertyProvider as any)[k]; + const selectedValue = !isNullOrUndefined(value) ? value : defaultValue; + + // + (this as any)[k] = selectedValue; + + // + if (currentValue !== selectedValue && !applyChanges) { + applyChanges = true; + } + }); + + // + if (applyChanges) { + this.handleDetecteChanges(); + } + } + + /** + * Validate Properties ... + */ + validateProperties() { + // + var props = this.getProperties(); + this.setProperties(props); + } + //#endregion + + // + //#region Separate ... + // + //#region List ... + @Input() + listTabTitle: XResourceIdentifier = this.propertyProvider.listTabTitle; + + /** + * When Items Prepared, Show them as a List ... + */ + @Input() + showItemsAsList: boolean = this.propertyProvider.showItemsAsList; + + /** + * When Provides items as Page and Non as List, this used to track items ... + */ + @Input() + modelIdentifier: string = this.propertyProvider.modelIdentifier; + + /** + * if set showItemsAsList equals to false ... + * all retrieved items shown as standalone item ... + * this property prepared template for each item ... + * NOTE: let-item="item" attached item to template ... + */ + @Input() + itemTemplate: TemplateRef = this.propertyProvider.itemTemplate; + + /** + * if set showItemsAsList equals to true ... + * all retrieved items shown as a list ... + * this property prepared template for each list item ... + * NOTE: let-item="item" attached item to template ... + */ + @Input() + listItemTemplate: TemplateRef = this.propertyProvider.listItemTemplate; + + @Input() + wrapListTabWithCard = this.propertyProvider.wrapListTabWithCard; + + @Input() + listTabCardIsInPage = this.propertyProvider.listTabCardIsInPage; + + @Input() + listTabCardTitle: XResourceIdentifier = + this.propertyProvider.listTabCardTitle; + + @Input() + listTabCardSubTitle: XResourceIdentifier = + this.propertyProvider.listTabCardSubTitle; + + @Input() + listTabCardColor: XColorIdentifier = this.propertyProvider.listTabCardColor; + + // + //#region Empty ... + @Input() + showEmpty: boolean = this.propertyProvider.showEmpty; + + @Input() + emptyCssClass: Array = this.propertyProvider.emptyCssClass; + + @Input() + emptyColor: XColorIdentifier = this.propertyProvider.emptyColor; + + @Input() + emptyForegroundColor: boolean = this.propertyProvider.emptyForegroundColor; + + @Input() + emptyMessage: XResourceIdentifier = this.propertyProvider.emptyMessage; + + @Input() + emptyMessageColor: XColorIdentifier = this.propertyProvider.emptyMessageColor; + + @Input() + emptyForegroundMessageColor: boolean = + this.propertyProvider.emptyForegroundMessageColor; + + @Input() + emptyHasRefresh: boolean = this.propertyProvider.emptyHasRefresh; + + @Input() + emptyRefreshButtonColor: XColorIdentifier = + this.propertyProvider.emptyRefreshButtonColor; + + @Input() + emptyForegroundRefreshButtonColor: boolean = + this.propertyProvider.emptyForegroundRefreshButtonColor; + + @Input() + emptyRefreshIcon: XIconIdentifier = this.propertyProvider.emptyRefreshIcon; + //#endregion + + // + @Input() + query: XQueryDto = undefined; + + // + items: Array = []; + queryResult: XQueryResultDto = undefined; + listItems: Array> = []; + + // + //#region Search Bar ... + @Input() + showSearchBar: boolean = this.propertyProvider.showSearchBar; + + @Input() + searchBarValue: string = this.propertyProvider.searchBarValue; + + @Input() + searchBarDebounce: number = this.propertyProvider.searchBarDebounce; + + @Input() + searchBarSpellcheck: boolean = this.propertyProvider.searchBarSpellcheck; + + @Input() searchBarCssClass: Array = + this.propertyProvider.searchBarCssClass; + + @Input() + searchBarColor: XColorIdentifier = this.propertyProvider.searchBarColor; + + @Input() + searchBarForegroundColor: boolean = + this.propertyProvider.searchBarForegroundColor; + + @Input() + searchBarTitle: XResourceIdentifier = this.propertyProvider.searchBarTitle; + + @Input() + searchBarType: XSearchBarTypeIdentifier = this.propertyProvider.searchBarType; + + @Input() + searchBarClearButtonIcon: XIconIdentifier = + this.propertyProvider.searchBarClearButtonIcon; + + @Input() + searchBarCancelButtonIcon: XIconIdentifier = + this.propertyProvider.searchBarCancelButtonIcon; + + @Input() + searchBarSearchButtonIcon: XIconIdentifier = + this.propertyProvider.searchBarSearchButtonIcon; + + @Input() + searchBarSearchInputColor: XColorIdentifier = + this.propertyProvider.searchBarSearchInputColor; + + @Input() + searchBarForegroundSearchInputColor: boolean = + this.propertyProvider.searchBarForegroundSearchInputColor; + + @Input() + searchBarKeyboardType: XSearchBarTypeIdentifier = + this.propertyProvider.searchBarKeyboardType; + + @Input() + searchBarCancelButtonTitle: XResourceIdentifier = + this.propertyProvider.searchBarCancelButtonTitle; + + @Input() + searchBarClearButtonIconColor: XColorIdentifier = + this.propertyProvider.searchBarClearButtonIconColor; + + @Input() + searchBarCancelButtonIconColor: XColorIdentifier = + this.propertyProvider.searchBarCancelButtonIconColor; + + @Input() + searchBarSearchButtonIconColor: XColorIdentifier = + this.propertyProvider.searchBarSearchButtonIconColor; + + @Input() + searchBarEnterKeyHint: XSearchBarEnterKeyHintIdentifier = + this.propertyProvider.searchBarEnterKeyHint; + + @Input() + searchBarShowClearButton: XSearchBarShowButtonIdentifier = + this.propertyProvider.searchBarShowClearButton; + + @Input() + searchBarShowCancelButton: XSearchBarShowButtonIdentifier = + this.propertyProvider.searchBarShowCancelButton; + //#endregion + + // + //#region Paginator ... + @Input() + showPaginator: boolean = this.propertyProvider.showPaginator; + + @Input() + paginatorPosition: VQueryPresenterPaginatorPositionIdentifier = + this.propertyProvider.paginatorPosition; + + @Input() + paginatorColor: XColorIdentifier = this.propertyProvider.paginatorColor; + //#endregion + + // + showListTabContent: boolean = false; + + /** + * Non As List Item's Presenting Tracking ... + */ + itemsTrackBy(index: number, item: any) { + // + if (!isNullOrEmptyString(this.modelIdentifier)) { + return index; + } else { + return item[this.modelIdentifier]; + } + } + + /** + * Check Can Show Search Bar or not ... + * @returns + */ + canShowSearchBar() { + // + return ( + this.showSearchBar && + !isNullOrUndefined(this.query) && + !isNullOrUndefined(this.queryResult) + ); + } + + // + paginatorPageSizeProvider = () => { + return this.queryResult && + this.queryResult.pageSize && + this.queryResult.pageSize > 0 + ? this.queryResult.pageSize + : undefined; + }; + + paginatorPageSizesProvider = () => { + return [...this.config.pageSizes]; + }; + + paginatorPInfoProvider = ( + currentPage: number, + pageSize: number, + totalPages: number, + totalItems: number + ) => { + // + if (isNullOrUndefined(currentPage) || isNullOrUndefined(totalPages)) { + return ''; + } + + // + return this.applyLocale(`${currentPage}/${totalPages}`); + }; + + // + paginatorCurrent: number = undefined; + paginatorTotalPages: number = undefined; + paginatorTotalItems: number = undefined; + nextButtonDescriptor: VPaginatorActionButton = undefined; + prevButtonDescriptor: VPaginatorActionButton = undefined; + lastButtonDescriptor: VPaginatorActionButton = undefined; + firstButtonDescriptor: VPaginatorActionButton = undefined; + + /** + * Handle Page Size Change ... + * + * @param current + * @param allowed + */ + handleChangePageSize = async (current: number, allowed: Array) => { + // + if (!allowed.includes(current)) { + // + allowed.push(current); + + // + allowed.sort((a, b) => (a > b ? 1 : a === b ? 0 : -1)); + } + + // + const options: XPickerColumnOption[] = allowed.map((ps) => { + // + const opt: XPickerColumnOption = { + text: this.applyLocale(ps.toString()), + value: ps, + disabled: ps === current, + }; + + // + return opt; + }); + + // + let selected = current; + const modal = await this.managerService.dialogService.presentPicker({ + buttons: [ + { + text: this.resourceProvider(this.ResourceIDs.accept), + role: XModalButtonRole.Selected, + handler: (value) => { + if (!value) { + return; + } + + // + const pageSize = value.pageSize.value; + selected = pageSize; + }, + }, + { + text: this.resourceProvider(this.ResourceIDs.cancel), + role: XModalButtonRole.Cancel, + }, + ], + columns: [{ name: 'pageSize', options }], + }); + + // + await modal.onWillDismiss(); + + // + if (this.query && selected !== this.query.pageSize) { + // + this.query.pageSize = selected; + this.resetQuery(); + } + + // + return Promise.resolve(selected); + }; + + /** + * Apply Search Query and renew Items ... + * + * @param searchValue + * @returns + */ + handleSearchQuryChanged(searchValue: string) { + // + if ( + searchValue === this.searchBarValue && + (searchValue === this.query?.filter || '') + ) { + return; + } + + // + this.query.page = 1; + this.query.filter = searchValue; + this.sendAction(VQueryPresenterAction.RequestQueryResult, this.query); + } + + /** + * Reset Page and Filter and Try to Refetch Items ... + */ + resetQuery() { + // + if (!isNullOrUndefined(this.query)) { + this.query.page = 1; + this.query.filter = ''; + } + + // + this.items = []; + + // + this.sendAction(VQueryPresenterAction.RequestQueryResult, this.query); + } + + /** + * Handle Request Next Page ... + * + * @param forceCurrent Specified Reload current if true ... + */ + async handleLoadMore(forceCurrent: boolean = false) { + // + if ( + isNullOrUndefined(this.query) || + isNullOrUndefined(this.queryResult) || + (!forceCurrent && + this.queryResult.page === this.queryResult.totalFilteredPages) + ) { + return; + } + + // + let page = this.query.page; + if (!forceCurrent) { + page = page + 1; + } else { + this.query.page = this.query.page - 1; + } + + // + await this.handleLoadSpecifiedPage(page); + } + + /** + * Handle Load Specified Page ... + * + * @param page + * @returns + */ + async handleLoadSpecifiedPage(page: number) { + // + // Validate ... + if ( + !page || + page <= 0 || + isNullOrUndefined(this.query) || + isNullOrUndefined(this.queryResult) || + page === this.query.page || + page > this.queryResult.totalFilteredPages + ) { + return; + } + + // + this.query.page = page; + this.sendAction(VQueryPresenterAction.RequestQueryResult, this.query); + } + + /** + * What to Do when List Tab Opened ... + */ + async handleListTabOpenState() { + // + const requestQueryResult = + !this.showListTabContent && isNullOrUndefined(this.queryResult); + if (requestQueryResult) { + // + this.resetQuery(); + return; + } + } + + /** + * Handle Query Result Recieved ... + */ + async handleQueryResultRecieved( + query: XQueryDto, + result: XQueryResultDto + ) { + // + this.query = query; + + // + // Validate ... + if (isNullOrUndefined(result)) { + return; + } + + // + // Renew Query Result ... + this.queryResult = { + ...result, + }; + + // + // Change List Tab Visibility ... + if (!this.isTabVisible(VQueryPresenterTabs.List)) { + this.changeTabVisibility(VQueryPresenterTabs.List, true); + } + + // + this.preparePaginator(); + + // + //#region Preparing Items ... + // + // List Items ... + if (this.showItemsAsList) { + // + // Preparing List Items ... + const items = this.queryResult.items.map((i) => { + return { + data: i, + } as XSimpleListItem; + }); + + // + // Send Request to Validate List Items and also Attach Actions ... + this.sendAction(VQueryPresenterAction.ValidateListItems, items); + } + // + // Non As List Items ... + else { + // + this.listItems = []; + + // + if (this.searchBarValue !== query.filter) { + // + this.items = []; + this.detectChanges(); + this.searchBarValue = query.filter; + } + + // + // Request for Filling Items ... + this.sendAction(VQueryPresenterAction.RequestFillingItems, { + exists: [...this.items], + recieved: [...this.queryResult.items], + }); + } + //#endregion + } + + /** + * Preparing Paginator ... + * + * @returns + */ + preparePaginator() { + // + // Validate ... + if (isNullOrUndefined(this.query) || isNullOrUndefined(this.queryResult)) { + // + // Filling Paginator items ... + this.paginatorCurrent = undefined; + this.paginatorTotalItems = undefined; + this.paginatorTotalPages = undefined; + return; + } + + // + // Check If Hasnt Child Means Remove all Childs ... + if (!this.hasChild(this.queryResult.items)) { + // + // here we Must Remove Data ... + this.paginatorCurrent = undefined; + this.paginatorTotalItems = undefined; + this.paginatorTotalPages = undefined; + return; + } + + // + //#region Prepare Paginator ... + // + // Filling Paginator items ... + this.paginatorCurrent = this.queryResult.page; + this.paginatorTotalItems = this.queryResult.totalFilteredItems; + this.paginatorTotalPages = this.queryResult.totalFilteredPages; + + // + //#region Preparing Paginator Buttons ... + // + // First ... + if (isNullOrUndefined(this.firstButtonDescriptor)) { + this.firstButtonDescriptor = {}; + } + this.firstButtonDescriptor = { + ...this.firstButtonDescriptor, + disabled: this.queryResult.page === 1, + }; + + // + // Prev ... + if (isNullOrUndefined(this.prevButtonDescriptor)) { + this.prevButtonDescriptor = {}; + } + this.prevButtonDescriptor = { + ...this.prevButtonDescriptor, + disabled: this.queryResult.page === 1, + }; + + // + // Next ... + if (isNullOrUndefined(this.nextButtonDescriptor)) { + this.nextButtonDescriptor = {}; + } + this.nextButtonDescriptor = { + ...this.nextButtonDescriptor, + disabled: this.queryResult.page >= this.queryResult.totalFilteredPages, + }; + + // + // Last ... + if (isNullOrUndefined(this.lastButtonDescriptor)) { + this.lastButtonDescriptor = {}; + } + this.lastButtonDescriptor = { + ...this.lastButtonDescriptor, + disabled: this.queryResult.page >= this.queryResult.totalFilteredPages, + }; + //#endregion + //#endregion + } + + /** + * Handle Prepared List Items Recieved ... + * @param result + */ + async handleListItemsRecieved(result: Array>) { + // + this.listItems = undefined; + this.detectChanges(); + + // + this.listItems = [...result]; + this.detectChanges(); + + // + if (this.showItemsAsList && !this.isTabVisible(VQueryPresenterTabs.List)) { + this.changeTabVisibility(VQueryPresenterTabs.List, true); + } + + // + this.detectChanges(); + this.preparePaginator(); + this.sendListAction(XListAction.Refresh); + this.runTimedOut(() => this.sendListAction(XListAction.Refresh), 100); + this.runTimedOut(() => this.sendListAction(XListAction.Refresh), 300); + this.runTimedOut(() => this.sendListAction(XListAction.Refresh), 500); + this.handleDetecteChanges(); + } + + /** + * Handle Filling Items Response ... + * + * @param items + */ + async handleFillingItemsRecieved( + model: VQueryFilledItemsProcessingResult + ) { + // + if (isNullOrUndefined(model)) { + return; + } + + // + let applyChanges = false; + + // + // Simplest Mode ... + if ( + (isNullOrUndefined(model.changed) || !this.hasChild(model.changed)) && + (isNullOrUndefined(model.exists) || !this.hasChild(model.exists)) + ) { + // + applyChanges = true; + this.items = [...model.news]; + } else { + // + // Has Change Items ... + + // + // Apply Changes ... + if (!isNullOrUndefined(model.changed) && this.hasChild(model.changed)) { + // + model.changed.forEach((changeModel) => { + // + const idx = this.items.findIndex( + (i) => i[model.identifier] === changeModel.identifier + ); + if (idx >= 0) { + // + if (!applyChanges) { + applyChanges = true; + } + + // + // Change Model ... + this.items[idx] = { + ...changeModel.data, + }; + } + }); + } + + // + // Push New Items ... + if (!isNullOrUndefined(model.news) && this.hasChild(model.news)) { + // + if (!applyChanges) { + applyChanges = true; + } + + // + this.items.push(...model.news); + } + } + + // + if (applyChanges) { + // + this.preparePaginator(); + this.handleDetecteChanges(); + + // + if (!isNullOrUndefined(this.items) && this.hasChild(this.items)) { + // + const ids = this.items.map((i) => i[this.modelIdentifier]); + this.sendAction(VQueryPresenterAction.RefreshViews, ids); + } + } + } + + /** + * Request to Do Specified Action based on item ... + * + * @param event + */ + handleListItemOptionsSelected(event: XListItemOptionSelectedEvent) { + // + // Replacing Data by Exact Model ... + event.data = event.data.data; + this.sendAction(VQueryPresenterAction.RequestItemAction, event); + } + //#endregion + + // + //#region Details ... + @Input() + detailsTabTitle: XResourceIdentifier = this.propertyProvider.detailsTabTitle; + + @Input() + detailsItemTemplate: TemplateRef = + this.propertyProvider.detailsItemTemplate; + + showDetailsTabContent: boolean = false; + //#endregion + + // + //#region Add ... + @Input() + addTabTitle: XResourceIdentifier = this.propertyProvider.addTabTitle; + + @Input() + addItemTemplate: TemplateRef = this.propertyProvider.addItemTemplate; + + showAddTabContent: boolean = false; + //#endregion + + // + //#region Update ... + @Input() + updateTabTitle: XResourceIdentifier = this.propertyProvider.updateTabTitle; + + @Input() + updateItemTemplate: TemplateRef = + this.propertyProvider.updateItemTemplate; + + // + showUpdateTabContent: boolean = false; + //#endregion + //#endregion + + // + //#region Handlers ... + async handleStateBeforeTabChange(to: VQueryPresenterTabs) { + // + this.sendAction(VQueryPresenterAction.TabRequestState, { + from: this.tabsSelectedIndex, + to: to, + }); + } + + async handleStateAfterTabChanged() { + // + switch (this.tabsSelectedIndex) { + // + // List ... + case VQueryPresenterTabs.List: + await this.handleListTabOpenState(); + break; + + // + // Add ... + case VQueryPresenterTabs.Add: + // + // Make Tab Visible if not ... + if (!this.isTabVisible(this.tabsSelectedIndex)) { + this.changeTabVisibility(this.tabsSelectedIndex, true); + } + break; + + // + // Update ... + case VQueryPresenterTabs.Update: + // + // Make Tab Visible if not ... + if (!this.isTabVisible(this.tabsSelectedIndex)) { + this.changeTabVisibility(this.tabsSelectedIndex, true); + } + break; + + // + // Details ... + case VQueryPresenterTabs.Details: + // + // Make Tab Visible if not ... + if (!this.isTabVisible(this.tabsSelectedIndex)) { + this.changeTabVisibility(this.tabsSelectedIndex, true); + } + break; + } + + // + this.sendAction( + VQueryPresenterAction.TabOpenedState, + this.tabsSelectedIndex + ); + } + + /** + * Handle Change Tab ... + * + * @param to + * @returns + */ + async changeTab(to: VQueryPresenterTabs) { + // + if (to === this.tabsSelectedIndex) { + return; + } + + // + // Handle State Before Tabs Open ... + await this.handleStateBeforeTabChange(to); + + // + // Since Tab Open State Handled using Event Emitter, here we ignore it ... + this.tabsSelectedIndex = to; + this.sendTabsAction(XTabsAction.TO, to); + this.sendTabsAction(XTabsAction.Refresh); + } + + /** + * Hide other Tabs ... + * @param tab + * @returns + */ + hideOtherTabs(tab: number) { + // + if (isNullOrUndefined(tab) || tab !== this.tabsSelectedIndex) { + return; + } + + // + switch (tab) { + // + case VQueryPresenterTabs.List: + // + // this.changeTabVisibility(VQueryPresenterTabs.List, true); + + // + this.changeTabVisibility(VQueryPresenterTabs.Add, false); + this.changeTabVisibility(VQueryPresenterTabs.Update, false); + this.changeTabVisibility(VQueryPresenterTabs.Details, false); + break; + + // + case VQueryPresenterTabs.Add: + // + this.changeTabVisibility(VQueryPresenterTabs.Add, true); + + // + // this.changeTabVisibility(VQueryPresenterTabs.List, false); + this.changeTabVisibility(VQueryPresenterTabs.Update, false); + this.changeTabVisibility(VQueryPresenterTabs.Details, false); + break; + + // + case VQueryPresenterTabs.Update: + // + this.changeTabVisibility(VQueryPresenterTabs.Update, true); + + // + this.changeTabVisibility(VQueryPresenterTabs.Add, false); + // this.changeTabVisibility(VQueryPresenterTabs.List, false); + this.changeTabVisibility(VQueryPresenterTabs.Details, false); + break; + + // + case VQueryPresenterTabs.Details: + // + this.changeTabVisibility(VQueryPresenterTabs.Details, true); + + // + this.changeTabVisibility(VQueryPresenterTabs.Add, false); + // this.changeTabVisibility(VQueryPresenterTabs.List, false); + this.changeTabVisibility(VQueryPresenterTabs.Update, false); + break; + } + } + //#endregion + + // + //#region Private ... + /** + * Handle Detect Changes ... + */ + private handleDetecteChanges() { + // + this.sendTabsAction(XTabsAction.Refresh); + this.sendListAction(XListAction.Refresh); + this.sendListTabCardAction(XCardAction.Refresh); + this.sendSearchBarAction(XSearchBarAction.Refresh); + this.sendPaginatorAction(VPaginatorAction.Refresh); + + // + this.detectChanges(); + } + + /** + * Check Specified Tab is Visible or not ... + * + * @param tab + * @returns + */ + private isTabVisible(tab: VQueryPresenterTabs) { + // + let result = false; + + // + switch (tab) { + // + // List ... + case VQueryPresenterTabs.List: + result = this.showListTabContent; + break; + + // + // Details ... + case VQueryPresenterTabs.Details: + result = this.showDetailsTabContent; + break; + + // + // Add ... + case VQueryPresenterTabs.Add: + result = this.showAddTabContent; + break; + + // + // Update ... + case VQueryPresenterTabs.Update: + result = this.showUpdateTabContent; + break; + } + + // + return result; + } + + /** + * Change Tabs Visibility ... + * + * @param tab + * @param visible + */ + private changeTabVisibility(tab: VQueryPresenterTabs, visible: boolean) { + // + let applyChange = false; + + // + switch (tab) { + // + // List ... + case VQueryPresenterTabs.List: + // + applyChange = visible != this.showListTabContent; + this.showListTabContent = visible; + break; + + // + // Details ... + case VQueryPresenterTabs.Details: + // + applyChange = visible != this.showDetailsTabContent; + this.showDetailsTabContent = visible; + break; + + // + // Add ... + case VQueryPresenterTabs.Add: + // + applyChange = visible != this.showAddTabContent; + this.showAddTabContent = visible; + break; + + // + // Update ... + case VQueryPresenterTabs.Update: + // + applyChange = visible != this.showUpdateTabContent; + this.showUpdateTabContent = visible; + break; + } + + // + if (applyChange) { + this.handleDetecteChanges(); + } + } + + /** + * Mnaging State (loading/disabled) ... + */ + private prevState = false; + private async handleStateChanges(state: boolean) { + // + if (state === this.prevState) { + return; + } + + // + if (!isNullOrUndefined(this.listItems) && this.hasChild(this.listItems)) { + // + this.listItems = [ + ...this.listItems.map((li) => { + // + return { + ...li, + slideOptions: [ + ...(isNullOrUndefined(li.slideOptions) || + !this.hasChild(li.slideOptions) + ? [] + : li.slideOptions.map((so) => { + // + return { + ...so, + disabled: so.disabled || state, + }; + })), + ], + }; + }), + ]; + + // + this.handleDetecteChanges(); + } + + // + this.prevState = state; + this.sendListAction(XListAction.Refresh); + } + //#endregion + + // + //#region Action Providers ... + private handleRegisterListeners() { + // + //#region Tabs Action Provider ... + this.applyTakeUntilWrapper(this.tabsActionProvider).subscribe( + async (event) => { + // + if ( + isNullOrUndefined(event) || + isNullOrEmptyString(event.action) || + !isValueInEnum(event.action, Object.assign({}, XTabsAction)) + ) { + return; + } + + // + await this.handleTabsAction(event.action, event.payload); + } + ); + //#endregion + + // + //#region List Action Provider ... + this.applyTakeUntilWrapper(this.listActionProvider).subscribe( + async (event) => { + // + if ( + isNullOrUndefined(event) || + isNullOrEmptyString(event.action) || + !isValueInEnum(event.action, Object.assign({}, XListAction)) + ) { + return; + } + + // + await this.handleListTabCardAction(event.action, event.payload); + } + ); + //#endregion + + // + //#region SearchBar ... + this.applyTakeUntilWrapper(this.searchBarActionProvider).subscribe( + async (event) => { + // + if ( + isNullOrUndefined(event) || + isNullOrEmptyString(event.action) || + !isValueInEnum(event.action, Object.assign({}, XSearchBarAction)) + ) { + return; + } + + // + await this.handleSearchBarAction(event.action, event.payload); + } + ); + //#endregion + + // + //#region Paginator ... + this.applyTakeUntilWrapper(this.paginatorActionProvider).subscribe( + async (event) => { + // + if ( + isNullOrUndefined(event) || + isNullOrEmptyString(event.action) || + !isValueInEnum(event.action, Object.assign({}, VPaginatorAction)) + ) { + return; + } + + // + await this.handlePaginatorAction(event.action, event.payload); + } + ); + //#endregion + + // + //#region List Tab Card ... + this.applyTakeUntilWrapper(this.listTabCardActionProvider).subscribe( + async (event) => { + // + if ( + isNullOrUndefined(event) || + isNullOrEmptyString(event.action) || + !isValueInEnum(event.action, Object.assign({}, XCardAction)) + ) { + return; + } + + // + await this.handleListTabCardAction(event.action, event.payload); + } + ); + //#endregion + + // + //#region Tab Changed Event Emitter ... + this.applyTakeUntilWrapper( + this.tabsSelectedTabChange.asObservable() + ).subscribe(async (event: XTabChangeEvent) => { + // + this.tabsSelectedIndex = event.index; + await this.handleStateAfterTabChanged(); + + // + // if Changed Tab is List Tab ... + // Handle Permissions or Actions State ... + if (this.tabsSelectedIndex === VQueryPresenterTabs.List) { + // + // If Show Items as List ... + if (this.showItemsAsList && this.hasChild(this.listItems)) { + // + // Re Validate List Items Actions ... + this.sendAction(VQueryPresenterAction.ValidateListItems, [ + ...this.listItems, + ]); + } + } + }); + //#endregion + + // + //#region Action Provider ... + this.applyTakeUntilWrapper(this.actionProvider).subscribe(async (event) => { + // + if ( + isNullOrUndefined(event) || + isNullOrEmptyString(event.action) || + !isVQueryPresenterAction(event.action) + ) { + return; + } + + // + await this.handleAction(event.action, event.payload); + }); + //#endregion + } + + // + //#region Tabs ... + /** + * Send Tabs Action ... + * + * @param action + * @param payload + * @returns + */ + private sendTabsAction(action: XTabsAction, payload?: any) { + // + if (isNullOrUndefined(this.tabsActionProvider)) { + return; + } + + // + this.tabsActionProvider.next({ action, payload }); + } + + /** + * Handle Specified Recieved Actions Of Tabs Action Provider ... + * @param action + * @param payload + */ + private async handleTabsAction( + action: XTabsActionIdentifier, + payload?: any + ) {} + //#endregion + + // + //#region List ... + /** + * Send List Action ... + * + * @param action + * @param payload + * @returns + */ + private sendListAction(action: XListAction, payload?: any) { + // + if (isNullOrUndefined(this.listActionProvider)) { + return; + } + + // + this.listActionProvider.next({ action, payload }); + } + + /** + * Handle Specified Recieved Actions Of List Action Provider ... + * + * @param action + * @param payload + */ + private async handleListAction( + action: XListActionIdentifier, + payload?: any + ) {} + //#endregion + + // + //#region SearchBar ... + /** + * Send SearchBar Action ... + * + * @param action + * @param payload + */ + private sendSearchBarAction(action: XSearchBarAction, payload?: any) { + // + if (isNullOrUndefined(this.searchBarActionProvider)) { + return; + } + + // + this.searchBarActionProvider.next({ action, payload }); + } + + /** + * Handle SearchBar Action ... + * + * @param action + * @param payload + */ + private async handleSearchBarAction( + action: XSearchBarActionIdentifier, + payload?: any + ) {} + //#endregion + + // + //#region Paginator ... + /** + * Send Paginator Action ... + * + * @param action + * @param payload + */ + private sendPaginatorAction(action: VPaginatorAction, payload?: any) { + // + if (isNullOrUndefined(this.paginatorActionProvider)) { + return; + } + + // + this.paginatorActionProvider.next({ action, payload }); + } + + /** + * Handle Paginator Action ... + * + * @param action + * @param payload + */ + private async handlePaginatorAction( + action: VPaginatorActionIdentifier, + payload?: any + ) { + // + // Update Buttons ... + if ( + action === VPaginatorAction.UpdateButtonDescriptors && + !isNullOrUndefined(payload) + ) { + // + // First ... + if (!isNullOrUndefined(payload.first)) { + // + this.firstButtonDescriptor = { + ...this.firstButtonDescriptor, + ...payload.first, + }; + } + + // + // Last ... + if (!isNullOrUndefined(payload.last)) { + // + this.lastButtonDescriptor = { + ...this.lastButtonDescriptor, + ...payload.last, + }; + } + + // + // Next ... + if (!isNullOrUndefined(payload.next)) { + // + this.nextButtonDescriptor = { + ...this.nextButtonDescriptor, + ...payload.next, + }; + } + + // + // Prev ... + if (!isNullOrUndefined(payload.prev)) { + // + this.prevButtonDescriptor = { + ...this.prevButtonDescriptor, + ...payload.prev, + }; + } + } + + // + // Select Page Size ... + if (action === VPaginatorAction.SelectPageSizeRequest) { + await this.handleChangePageSize(payload.current, payload.allowed); + } + + // + // To Specified Page ... + if ( + action === VPaginatorAction.To && + !isNullOrUndefined(payload) && + isNumber(payload) + ) { + // + // Validate Payload ... + const toPage = toNumber(payload); + if ( + !isNullOrUndefined(this.queryResult) && + toPage !== this.queryResult.page && + toPage >= 1 && + toPage <= this.queryResult.totalFilteredPages + ) { + this.handleLoadSpecifiedPage(toPage); + } + } + + // + // First ... + if (action === VPaginatorAction.First) { + this.handleLoadSpecifiedPage(1); + } + + // + // Last ... + if (action === VPaginatorAction.Last) { + // + if (!isNullOrUndefined(this.queryResult)) { + // + const page = this.queryResult.totalFilteredPages; + this.handleLoadSpecifiedPage(page); + } + } + + // + // Next ... + if (action === VPaginatorAction.Next) { + // + if (!isNullOrUndefined(this.queryResult)) { + // + const page = this.queryResult.page + 1; + this.handleLoadSpecifiedPage(page); + } + } + + // + // Prev ... + if (action === VPaginatorAction.Prev) { + // + if (!isNullOrUndefined(this.queryResult)) { + // + const page = this.queryResult.page - 1; + this.handleLoadSpecifiedPage(page); + } + } + } + //#endregion + + // + //#region ListTabCard ... + /** + * Send ListTabCard Action ... + * + * @param action + * @param payload + */ + private sendListTabCardAction(action: XCardAction, payload?: any) { + // + if (isNullOrUndefined(this.listTabCardActionProvider)) { + return; + } + + // + this.listTabCardActionProvider.next({ action, payload }); + } + + /** + * Handle ListTabCard Action ... + * + * @param action + * @param payload + */ + private async handleListTabCardAction( + action: XListActionIdentifier, + payload?: any + ) {} + //#endregion + + // + //#region Action Provider ... + /** + * Send Action ... + * + * @param action + * @param payload + */ + private sendAction(action: VQueryPresenterAction, payload?: any) { + // + if (isNullOrUndefined(this.actionProvider)) { + return; + } + + // + this.actionProvider.next({ action, payload }); + } + + /** + * Handle Action ... + * + * @param action + * @param payload + */ + private async handleAction( + action: VQueryPresenterActionIdentifier, + payload?: any + ) { + // + // Refresh ... + if (action === VQueryPresenterAction.Refresh) { + // + if (!isNullOrUndefined(payload) && isNumber(payload)) { + this.runTimedOut(() => this.handleDetecteChanges(), toNumber(payload)); + } else { + this.handleDetecteChanges(); + } + } + + // + // LoadMore ... + if (action === VQueryPresenterAction.LoadMore) { + // + const forceCurrent = + !isNullOrUndefined(payload) && isBoolean(payload) + ? toBoolean(payload) + : false; + + // + await this.handleLoadMore(forceCurrent); + } + + // + // RefreshTab ... + if ( + action === VQueryPresenterAction.RefreshTab && + !isNullOrUndefined(payload) && + isNumber(payload) + ) { + // + this.changeTabVisibility(toNumber(payload), false); + this.runTimedOut( + () => this.changeTabVisibility(toNumber(payload), true), + 500 + ); + } + + // + // Add Model ... + if ( + action === VQueryPresenterAction.AddModel && + !isNullOrUndefined(payload) + ) { + // + // Try to Update Model in Context ... + let isChanged = false; + + // + if ( + !isNullOrUndefined(this.query) && + !isNullOrUndefined(this.queryResult) && + isNullOrEmptyString(this.query.filter) && + !this.hasChild(this.queryResult.items) && + this.queryResult.page === 1 && + this.queryResult.totalFilteredItems < this.queryResult.pageSize + ) { + // + this.queryResult.items = [ + ...this.queryResult.items, + { ...payload.model }, + ]; + + // + if (this.showItemsAsList) { + // + this.listItems = [ + ...this.listItems, + { + data: payload.model, + slideOptions: payload.actions, + }, + ]; + } else { + this.sendAction(VQueryPresenterAction.RequestFillingItems, { + exists: [...this.items], + recieved: [{ ...payload.model }], + }); + } + + // + isChanged = true; + } + + // + if (isChanged) { + // + this.handleDetecteChanges(); + this.runTimedOut(() => this.handleDetecteChanges(), 100); + this.runTimedOut(() => this.handleDetecteChanges(), 300); + this.runTimedOut(() => this.handleDetecteChanges(), 500); + } + } + + // + // Update Model ... + if ( + action === VQueryPresenterAction.UpdateModel && + !isNullOrUndefined(payload) + ) { + // + // Try to Update Model in Context ... + let idx = -1; + let isChanged = false; + + // + if ( + !isNullOrUndefined(this.queryResult) && + this.hasChild(this.queryResult.items) + ) { + // + idx = this.queryResult.items.findIndex( + (i) => i[payload.key] === payload.model[payload.key] + ); + if (idx >= 0) { + // + isChanged = true; + this.queryResult.items[idx] = { + ...payload.model, + }; + } + } + + // + if (!isNullOrUndefined(this.listItems) && this.hasChild(this.listItems)) { + // + idx = this.listItems.findIndex( + (i) => i.data[payload.key] === payload.model[payload.key] + ); + if (idx >= 0) { + // + isChanged = true; + this.listItems[idx] = { + ...this.listItems[idx], + data: { ...payload.model }, + slideOptions: [...payload.actions], + }; + } + } + + // + if (!isNullOrUndefined(this.items) && this.hasChild(this.items)) { + // + isChanged = true; + this.sendAction(VQueryPresenterAction.RequestFillingItems, { + exists: [...this.items], + recieved: [{ ...payload.model }], + }); + } + + // + if (isChanged) { + // + this.handleDetecteChanges(); + this.runTimedOut(() => this.handleDetecteChanges(), 100); + this.runTimedOut(() => this.handleDetecteChanges(), 300); + this.runTimedOut(() => this.handleDetecteChanges(), 500); + } + } + + // + // Remove Model ... + if ( + action === VQueryPresenterAction.RemoveModel && + !isNullOrUndefined(payload) + ) { + // + // Try to Update Model in Context ... + let idx = -1; + let isChanged = false; + + // + if ( + !isNullOrUndefined(this.queryResult) && + this.hasChild(this.queryResult.items) + ) { + // + idx = this.queryResult.items.findIndex( + (i) => i[payload.key] === payload.model[payload.key] + ); + if (idx >= 0) { + // + isChanged = true; + this.queryResult.items = this.queryResult.items.filter( + (i) => i[payload.key] !== payload.model[payload.key] + ); + } + } + + // + if (!isNullOrUndefined(this.listItems) && this.hasChild(this.listItems)) { + // + idx = this.listItems.findIndex( + (i) => i.data[payload.key] === payload.model[payload.key] + ); + if (idx >= 0) { + // + isChanged = true; + this.listItems = this.listItems.filter( + (i) => i.data[payload.key] !== payload.model[payload.key] + ); + } + } + + // + if (!isNullOrUndefined(this.items) && this.hasChild(this.items)) { + // + idx = this.items.findIndex( + (i) => i[payload.key] === payload.model[payload.key] + ); + if (idx >= 0) { + // + isChanged = true; + this.items = this.items.filter( + (i) => i[payload.key] !== payload.model[payload.key] + ); + } + } + + // + if (isChanged) { + // + this.preparePaginator(); + this.handleDetecteChanges(); + + // + // Re Validate List Items Actions ... + this.sendAction(VQueryPresenterAction.ValidateListItems, [ + ...this.listItems, + ]); + + // + if (!isNullOrUndefined(this.items) && this.hasChild(this.items)) { + // + this.sendAction(VQueryPresenterAction.RefreshViews, [ + ...this.items.map((i) => i[this.modelIdentifier]), + ]); + } + } + } + + // + // Response Query Result ... + if ( + action === VQueryPresenterAction.ResponseQueryResult && + !isNullOrUndefined(payload) + ) { + await this.handleQueryResultRecieved(payload.query, payload.result); + } + + // + // Response List Items ... + if (action === VQueryPresenterAction.ResponseListItems) { + await this.handleListItemsRecieved(payload); + } + + // + // Change Tab ... + if ( + action === VQueryPresenterAction.ChangeTab && + !isNullOrUndefined(payload) + ) { + await this.changeTab(payload); + } + + // + // Reset Query ... + if (action === VQueryPresenterAction.ResetQuery) { + // + if ( + !isNullOrUndefined(payload) && + isBoolean(payload) && + toBoolean(payload) === true + ) { + await this.changeTab(VQueryPresenterTabs.List); + } + + // + this.resetQuery(); + } + + // + // Response Filling Items ... + if (action === VQueryPresenterAction.ResponseFillingItems) { + await this.handleFillingItemsRecieved(payload); + } + + // + // HideOtherTabs ... + if ( + action === VQueryPresenterAction.HideOtherTabs && + !isNullOrUndefined(payload) + ) { + this.hideOtherTabs(payload); + } + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\v-query-presenter\v-query-presenter.typings.ts + +import { + hasChild, + XResourceIDs, + XColorIdentifier, + isNullOrUndefined, + XResourceIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { + XTabAlign, + XIconNames, + XSearchBarType, + XIconIdentifier, + XBaseActionModel, + XTabAlignIdentifier, + XTabsHeaderPosition, + XSearchBarShowButton, + XSearchBarEnterKeyHint, + XSearchBarTypeIdentifier, + XTabsHeaderPositionIdentifier, + XSearchBarShowButtonIdentifier, + XSearchBarEnterKeyHintIdentifier, +} from 'x-framework-components'; +import { isValueInEnum } from '../tools/v-base.tools'; +import { Injectable, TemplateRef } from '@angular/core'; + +/** + * All Aawailable Tabs ... + */ +export enum VQueryPresenterTabs { + List = 0, + Details = 1, + Add = 2, + Update = 3, +} + +// +//#region Paginator Position ... +export enum VQueryPresenterPaginatorPosition { + Above = 'above', + Below = 'below', + Both = 'both', +} +export type VQueryPresenterPaginatorPositionIdentifier = + | VQueryPresenterPaginatorPosition + | string; +export function isVQueryPresenterPaginatorPosition(value: string) { + return isValueInEnum( + value, + Object.assign(VQueryPresenterPaginatorPosition, {}) + ); +} +//#endregion ... + +// +//#region Processing Model ... +/** + * a Changed Items in Processing ... + */ +export interface VQueryFilledItemsProcessingChangedItems { + /** + * item identifier value ... + */ + identifier: any; + + /** + * Item ... + */ + data: T; +} + +/** + * Items Processing Result ... + */ +export interface VQueryFilledItemsProcessingResult { + /** + * Exists Items ... + */ + exists: Array; + + /** + * Recieved Items ... + */ + recieved: Array; + + /** + * Items Uinque Identifier Field Name ... + */ + identifier: keyof T; + + /** + * Change Items from Exists ... + */ + changed: Array>; + + /** + * New Items to Push ... + */ + news: Array; +} + +/** + * Process Items based ... + * + * @param model + * @returns + */ +export function processFilledItems( + exists: Array, + recieved: Array, + identifier: keyof T +) { + // + let result: VQueryFilledItemsProcessingResult = undefined; + + // + // Validate Args ... + if ( + isNullOrUndefined(identifier) || + isNullOrUndefined(recieved) || + !hasChild(recieved) + ) { + return result; + } + + // + // Simplest Mode ... + if (isNullOrUndefined(exists) || !hasChild(exists)) { + // + // Has not Exists items ... + result = { + exists, + recieved, + identifier, + changed: [], + news: [...recieved], + }; + } + // + // Complex Mode ... + else { + // + // Has Exists Items ... + const news: Array = []; + const changed: Array> = []; + + // + // Process Exists items ... + recieved.forEach((rItem) => { + // + // Recieved Item ... + const isExists = + exists.findIndex((eItem) => eItem[identifier] === rItem[identifier]) >= + 0; + + // + // If new Item Recieveds ... + if (!isExists) { + news.push(rItem); + } + // + // If Exists ... + else { + // + const identifierValue = rItem[identifier]; + const data: T = { + ...rItem, + }; + + // + changed.push({ + identifier: identifierValue, + data, + }); + } + }); + + // + // Changed Model ... + result = { + exists, + recieved, + identifier, + changed, + news, + }; + } + + // + return result; +} +//#endregion + +// +//#region Action Provider ... +/** + * All Actions/Events of Component ... + */ +export enum VQueryPresenterAction { + /** + * Refresh and Detect Change ... + */ + Refresh = 'refresh', + + /** + * Call when we Have to Load More ... + * as Ususall Action, loading More means request next page if Exists ... + * except has an error on Previous loading ... + */ + LoadMore = 'LoadMore', + + /** + * Refresh Specified Tab ... + */ + RefreshTab = 'RefreshTab', + + /** + * Refreshing Views After Update ... + */ + RefreshViews = 'RefreshViews', + + /** + * Change Tab ... + */ + ChangeTab = 'ChangeTab', + + /** + * Reset Query ... + * if pass true as payload also Change Tab to List ... + */ + ResetQuery = 'ResetQuery', + + /** + * Add a Model ... + * payload: { + * model: any; // Data to Update ... + * key: string; // a Field Name to Detect model to Update based on it ... + * actions: XListItemSlideOptions[]; // Provided Slide Options ... + * } + */ + AddModel = 'AddModel', + + /** + * Update a Model ... + * payload: { + * model: any; // Data to Update ... + * key: string; // a Field Name to Detect model to Update based on it ... + * actions: XListItemSlideOptions[]; // Provided Slide Options ... + * } + */ + UpdateModel = 'UpdateModel', + + /** + * Remove a Model ... + * payload: { + * model: any; // Data to Update ... + * key: string; // a Field Name to Detect model to Update based on it ... + * actions: XListItemSlideOptions[]; // Provided Slide Options ... + * } + */ + RemoveModel = 'RemoveModel', + + /** + * Tab Open State ... + */ + TabOpenedState = 'TabOpenedState', + + /** + * Request Handle State Before Specified Tabs Open ... + * from: points to current ... + * to: points to next ... + */ + TabRequestState = 'TabRequestState', + + /** + * Request Item Action to Done ... + */ + RequestItemAction = 'RequestItemAction', + + /** + * Request Execute Query and Retrieve Query Result ... + */ + RequestQueryResult = 'RequestQueryResult', + + /** + * Response the Query Result ... + */ + ResponseQueryResult = 'ResponseQueryResult', + + /** + * Validate and Provide List Item Options ... + */ + ValidateListItems = 'ValidateListItems', + + /** + * Response Validated List Items ... + */ + ResponseListItems = 'ResponseListItems', + + /** + * for Non As List Items ... + * payload: { + * current: Array => current Items ... + * recieved: Array => recieved Items ... + * } + */ + RequestFillingItems = 'RequestFillingItems', + ResponseFillingItems = 'ResponseFillingItems', + + /** + * invisible tabs ... + * payload: number ... + */ + HideOtherTabs = 'HideOtherTabs', +} + +export interface VQueryPresenterActionModel + extends XBaseActionModel {} + +export type VQueryPresenterActionIdentifier = VQueryPresenterAction | string; + +export function isVQueryPresenterAction(value: string) { + return isValueInEnum(value, Object.assign({}, VQueryPresenterAction)); +} +//#endregion + +// +//#region Container ... +export interface IVQueryPresenterContainer { + // + //#region Tabs Presenter Props ... + tabsCssClass: Array; + tabsSelectedIndex: number; + tabsShowHeader: boolean; + tabsDisablePagination: boolean; + tabsAlignTabs: XTabAlignIdentifier; + tabsDynamicHeight: boolean; + tabsAnimationDuration: string; + tabsDisableRipple: boolean; + tabsHeaderPosition: XTabsHeaderPositionIdentifier; + tabsLoopTabs: boolean; + tabsSwipable: boolean; + tabsLock: boolean; + tabsColor: XColorIdentifier; + tabsHeaderColor: XColorIdentifier; + tabsActiveTabColor: XColorIdentifier; + tabsTabIndicatorColor: XColorIdentifier; + tabsTabPaginatorColor: XColorIdentifier; + //#endregion + + // + //#region Search Bar ... + showSearchBar: boolean; + searchBarValue: string; + searchBarDebounce: number; + searchBarSpellcheck: boolean; + searchBarCssClass: Array; + searchBarColor: XColorIdentifier; + searchBarForegroundColor: boolean; + searchBarTitle: XResourceIdentifier; + searchBarType: XSearchBarTypeIdentifier; + searchBarClearButtonIcon: XIconIdentifier; + searchBarCancelButtonIcon: XIconIdentifier; + searchBarSearchButtonIcon: XIconIdentifier; + searchBarSearchInputColor: XColorIdentifier; + searchBarForegroundSearchInputColor: boolean; + searchBarKeyboardType: XSearchBarTypeIdentifier; + searchBarCancelButtonTitle: XResourceIdentifier; + searchBarClearButtonIconColor: XColorIdentifier; + searchBarCancelButtonIconColor: XColorIdentifier; + searchBarSearchButtonIconColor: XColorIdentifier; + searchBarEnterKeyHint: XSearchBarEnterKeyHintIdentifier; + searchBarShowClearButton: XSearchBarShowButtonIdentifier; + searchBarShowCancelButton: XSearchBarShowButtonIdentifier; + //#endregion + + // + //#region Paginator ... + showPaginator: boolean; + paginatorPosition: VQueryPresenterPaginatorPositionIdentifier; + paginatorColor: XColorIdentifier; + //#endregion + + // + //#region Tabs ... + // + //#region List ... + listTabTitle: XResourceIdentifier; + showItemsAsList: boolean; + modelIdentifier: string; + itemTemplate: TemplateRef; + listItemTemplate: TemplateRef; + wrapListTabWithCard: boolean; + listTabCardIsInPage: boolean; + listTabCardTitle: XResourceIdentifier; + listTabCardSubTitle: XResourceIdentifier; + listTabCardColor: XColorIdentifier; + // + //#region Empty ... + showEmpty: boolean; + emptyCssClass: Array; + emptyColor: XColorIdentifier; + emptyForegroundColor: boolean; + emptyMessage: XResourceIdentifier; + emptyMessageColor: XColorIdentifier; + emptyForegroundMessageColor: boolean; + emptyHasRefresh: boolean; + emptyRefreshButtonColor: XColorIdentifier; + emptyForegroundRefreshButtonColor: boolean; + emptyRefreshIcon: XIconIdentifier; + //#endregion + //#endregion + + // + //#region Details ... + detailsTabTitle: XResourceIdentifier; + detailsItemTemplate: TemplateRef; + //#endregion + + // + //#region Add ... + addTabTitle: XResourceIdentifier; + addItemTemplate: TemplateRef; + //#endregion + + // + //#region Update ... + updateTabTitle: XResourceIdentifier; + updateItemTemplate: TemplateRef; + //#endregion + //#endregion +} + +@Injectable({ + providedIn: 'root', +}) +export class VQueryPresenterContainer + implements Readonly +{ + // + //#region Tabs Presenter Props ... + readonly tabsCssClass: Array = []; + readonly tabsSelectedIndex: number = VQueryPresenterTabs.List; + readonly tabsShowHeader: boolean = false; + readonly tabsDisablePagination: boolean = false; + readonly tabsAlignTabs: XTabAlignIdentifier = XTabAlign.STRETCH; + readonly tabsDynamicHeight: boolean = true; + readonly tabsAnimationDuration: string = '500ms'; + readonly tabsDisableRipple: boolean = false; + readonly tabsHeaderPosition: XTabsHeaderPositionIdentifier = + XTabsHeaderPosition.ABOVE; + readonly tabsLoopTabs: boolean = true; + readonly tabsSwipable: boolean = false; + readonly tabsLock: boolean = false; + readonly tabsColor: XColorIdentifier = XColorWithBrightness.Transparent; + readonly tabsHeaderColor: XColorIdentifier = XColorWithBrightness.Primary; + readonly tabsActiveTabColor: XColorIdentifier = + XColorWithBrightness.Secondary; + readonly tabsTabIndicatorColor: XColorIdentifier = + XColorWithBrightness.SecondaryTint; + readonly tabsTabPaginatorColor: XColorIdentifier = + XColorWithBrightness.SecondaryShade; + //#endregion + + // + //#region Search Bar ... + readonly showSearchBar: boolean = false; + readonly searchBarValue: string = ''; + readonly searchBarDebounce: number = 1000; + readonly searchBarSpellcheck: boolean = false; + readonly searchBarCssClass: Array = []; + readonly searchBarColor: XColorIdentifier = XColorWithBrightness.Primary; + readonly searchBarForegroundColor: boolean = false; + readonly searchBarTitle: XResourceIdentifier = XResourceIDs.search; + readonly searchBarType: XSearchBarTypeIdentifier = XSearchBarType.Search; + readonly searchBarClearButtonIcon: XIconIdentifier; + readonly searchBarCancelButtonIcon: XIconIdentifier; + readonly searchBarSearchButtonIcon: XIconIdentifier = XIconNames.search; + readonly searchBarSearchInputColor: XColorIdentifier = + XColorWithBrightness.PrimaryShade; + readonly searchBarForegroundSearchInputColor: boolean = false; + readonly searchBarKeyboardType: XSearchBarTypeIdentifier = + XSearchBarType.Search; + readonly searchBarCancelButtonTitle: XResourceIdentifier = + XResourceIDs.cancel; + readonly searchBarClearButtonIconColor: XColorIdentifier = + XColorWithBrightness.Danger; + readonly searchBarCancelButtonIconColor: XColorIdentifier = + XColorWithBrightness.Danger; + readonly searchBarSearchButtonIconColor: XColorIdentifier = + XColorWithBrightness.Warning; + readonly searchBarEnterKeyHint: XSearchBarEnterKeyHintIdentifier = + XSearchBarEnterKeyHint.Search; + readonly searchBarShowClearButton: XSearchBarShowButtonIdentifier = + XSearchBarShowButton.Always; + readonly searchBarShowCancelButton: XSearchBarShowButtonIdentifier = + XSearchBarShowButton.Never; + //#endregion + + // + //#region Paginator ... + readonly showPaginator: boolean = true; + readonly paginatorPosition: VQueryPresenterPaginatorPositionIdentifier = + VQueryPresenterPaginatorPosition.Below; + readonly paginatorColor: XColorIdentifier = XColorWithBrightness.PrimaryTint; + //#endregion + + // + //#region Tabs ... + // + //#region List ... + readonly listTabTitle: XResourceIdentifier = XResourceIDs.list; + readonly showItemsAsList: boolean = true; + readonly modelIdentifier: string = ''; + readonly itemTemplate: TemplateRef = undefined; + readonly listItemTemplate: TemplateRef = undefined; + readonly wrapListTabWithCard: boolean = true; + readonly listTabCardIsInPage: boolean = true; + readonly listTabCardTitle: XResourceIdentifier = undefined; + readonly listTabCardSubTitle: XResourceIdentifier = undefined; + readonly listTabCardColor: XColorIdentifier = XColorWithBrightness.Light; + // + //#region Empty ... + readonly showEmpty: boolean = true; + readonly emptyCssClass: Array = []; + readonly emptyColor: XColorIdentifier = XColorWithBrightness.Light; + readonly emptyForegroundColor: boolean = false; + readonly emptyMessage: XResourceIdentifier = XResourceIDs.no_item; + readonly emptyMessageColor: XColorIdentifier = XColorWithBrightness.Dark; + readonly emptyForegroundMessageColor: boolean = true; + readonly emptyHasRefresh: boolean = true; + readonly emptyRefreshButtonColor: XColorIdentifier = + XColorWithBrightness.Dark; + readonly emptyForegroundRefreshButtonColor: boolean = true; + readonly emptyRefreshIcon: XIconIdentifier = XIconNames.refresh; + //#endregion + //#endregion + + // + //#region Details ... + readonly detailsTabTitle: XResourceIdentifier = XResourceIDs.details; + readonly detailsItemTemplate: TemplateRef = undefined; + //#endregion + + // + //#region Add ... + readonly addTabTitle: XResourceIdentifier = XResourceIDs.add; + readonly addItemTemplate: TemplateRef = undefined; + //#endregion + + // + //#region Update ... + readonly updateTabTitle: XResourceIdentifier = XResourceIDs.update; + readonly updateItemTemplate: TemplateRef = undefined; + //#endregion + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\v-reference\v-reference.component.html + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\v-reference\v-reference.component.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\v-reference\v-reference.component.ts + +import { + toXString, + getFieldNames, + XExceptionIDs, + nameofInstance, + isNullOrUndefined, + isNullOrEmptyString, +} from 'x-framework-core'; +import { + XReferenceDto, + defaultReferenceDto, + prepareReferenceDtoFields, +} from 'x-saherelm-api-sdk'; +import { + VReferencePresentType, + isVReferencePresentType, + VReferencePresentTypeIdentifier, +} from './v-reference.typings'; +import { + VBaseDtoComponent, + VModelFieldDescriptor, + VModelValidationResult, +} from '../v-dto/v-dto.typings'; +import { Component, Input } from '@angular/core'; + +@Component({ + selector: 'v-reference', + templateUrl: './v-reference.component.html', + styleUrls: ['./v-reference.component.scss'], +}) +export class VReferenceComponent extends VBaseDtoComponent> { + // + //#region Props ... + readonly defaultModel: XReferenceDto = { + ...defaultReferenceDto, + }; + readonly FieldNames = getFieldNames(this.defaultModel); + readonly ReferencePresentTypes = Object.assign({}, VReferencePresentType); + + // + @Input() + presentType: VReferencePresentTypeIdentifier = + this.ReferencePresentTypes.AsView; + + // + defaultFieldDescriptors: VModelFieldDescriptor[] = [ + // + // Index ... + { + hide: false, + hideLabel: false, + allowFieldForm: false, + key: this.FieldNames.index, + value: this.defaultModel.index, + label: this.AppResourceIDs.index, + type: this.ModelFieldTypes.Number, + valueProvider: (value) => { + return this.applyLocale(value); + }, + } as VModelFieldDescriptor, + // + // ProvidedFor ... + { + hide: false, + hideLabel: false, + allowFieldForm: false, + key: this.FieldNames.providedFor, + type: this.ModelFieldTypes.String, + value: this.defaultModel.providedFor, + label: this.AppResourceIDs.provided_for, + } as VModelFieldDescriptor, + // + // ReferenceTo ... + { + hide: false, + hideLabel: false, + allowFieldForm: false, + key: this.FieldNames.referencedTo, + type: this.ModelFieldTypes.String, + value: this.defaultModel.referencedTo, + label: this.AppResourceIDs.referenced_to, + valueProvider: (value) => { + return toXString(value); + }, + } as VModelFieldDescriptor, + ]; + //#endregion + + // + //#region LifeCycles ... + onChange(changeKeys: string[]): void { + super.onChange(changeKeys); + + // + let isChanged = false; + let applyChanges = false; + + // + // Present Type ... + isChanged = changeKeys.includes(nameofInstance(this, 'presentType')); + if (isChanged) { + // + if (!isVReferencePresentType(this.presentType)) { + // + applyChanges = true; + this.presentType = this.ReferencePresentTypes.AsView; + } + } + + // + if (applyChanges) { + this.detectChanges(); + } + } + //#endregion + + // + //#region Abstraction ... + /** + * Prepare Model Fields ... + * + * @param model + * @returns + */ + prepare(model?: XReferenceDto): XReferenceDto { + return prepareReferenceDtoFields(model); + } + + /** + * Handle Model Validation ... + * + * @param model + * @returns + */ + validate( + model?: Partial>, + ): VModelValidationResult> { + // + let result: VModelValidationResult> = undefined; + + // + let isValid = false; + const validationErrors: string[] = []; + + // + model = { + ...this.model, + ...model, + }; + const normalized = this.normalize(model); + + // + // Handle Validation ... + isValid = + !isNullOrUndefined(normalized) && !this.isSameAsDefault(normalized); + if (!isValid) { + // + validationErrors.push( + this.getExceptionMessage(XExceptionIDs.InvalidArgs), + ); + } else { + // + // Check Other Validations ... + + // + // Index ... + isValid = !isNullOrUndefined(normalized.index) && normalized.index >= 0; + if (!isValid) { + // + validationErrors.push( + `${this.resourceProvider( + this.AppResourceIDs.index, + )}: ${this.getExceptionMessage(XExceptionIDs.InvalidArgs)}`, + ); + } + + // + // ProvidedFor ... + isValid = !isNullOrEmptyString(normalized.providedFor); + if (!isValid) { + // + validationErrors.push( + `${this.resourceProvider( + this.AppResourceIDs.provided_for, + )}: ${this.getExceptionMessage(XExceptionIDs.InvalidArgs)}`, + ); + } + + // + // ReferenceTo ... + isValid = !isNullOrUndefined(normalized.referencedTo); + if (!isValid) { + // + validationErrors.push( + `${this.resourceProvider( + this.AppResourceIDs.referenced_to, + )}: ${this.getExceptionMessage(XExceptionIDs.InvalidArgs)}`, + ); + } + } + + // + // Handle Result ... + + // + isValid = !this.hasChild(validationErrors); + + // + const isValidForAdd = + isNullOrUndefined(normalized.index) || normalized.index < 0; + const isValidForUpdate = + !isNullOrUndefined(normalized.index) && normalized.index > 0; + + // + result = { + isValid, + normalized, + isValidForAdd, + isValidForUpdate, + validationErrors, + }; + + // + return result; + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\v-reference\v-reference.configs.ts + +// +//#region Localization Configuration ... +export enum ReferenceResourceIDs { + reference = 'reference', + references = 'references', + provided_for = 'provided_for', + referenced_to = 'referenced_to', +} + +// +//#region Single Resources ... +// +//#region reference ... +export const reference_fa = { + id: 'reference', + value: 'مرجع', +}; + +export const reference_en = { + id: 'reference', + value: 'Reference', +}; +//#endregion + +// +//#region references ... +export const references_fa = { + id: 'references', + value: 'مراجع', +}; + +export const references_en = { + id: 'references', + value: 'References', +}; +//#endregion + +// +//#region provided_for ... +export const provided_for_fa = { + id: 'provided_for', + value: 'متصل به', +}; + +export const provided_for_en = { + id: 'provided_for', + value: 'Provided To', +}; +//#endregion + +// +//#region referenced_to ... +export const referenced_to_fa = { + id: 'referenced_to', + value: 'شناسه مرجع', +}; + +export const referenced_to_en = { + id: 'referenced_to', + value: 'Referenced Identifier', +}; +//#endregion +//#endregion + +// +export const ReferenceLocalizationResourceIDs = Object.assign( + {}, + ReferenceResourceIDs +); + +// +export const ReferenceLocalizationResources = { + // + reference: { + fa: { ...reference_fa }, + en: { ...reference_en }, + }, + references: { + fa: { ...references_fa }, + en: { ...references_en }, + }, + provided_for: { + fa: { ...provided_for_fa }, + en: { ...provided_for_en }, + }, + referenced_to: { + fa: { ...referenced_to_fa }, + en: { ...referenced_to_en }, + }, +}; +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\v-reference\v-reference.typings.ts + +import { isValueInEnum } from '../tools/v-base.tools'; +import { isNullOrUndefined, isString } from 'x-framework-core'; +import { isXReferenceDto, XReferenceDto } from 'x-saherelm-api-sdk'; + +// +//#region ReferenceType ... +export type XReferenceType = XReferenceDto | string; + +export function isReferenceStringType(reference: XReferenceType) { + // + let result = !isNullOrUndefined(reference) && isString(reference); + + // + return result; +} + +export function isReferenceSDtoType(reference: XReferenceType) { + // + let result = !isNullOrUndefined(reference) && isXReferenceDto(reference); + + // + return result; +} +//#endregion + +// +//#region Present Type ... +export enum VReferencePresentType { + AsNone = 'as_none', + AsView = 'as_view', + AsItem = 'as_item', +} + +export type VReferencePresentTypeIdentifier = VReferencePresentType | string; + +export function isVReferencePresentType(value: string) { + return isValueInEnum(value, Object.assign({}, VReferencePresentType)); +} + +export function isVisibleVReferencePresentType( + value: VReferencePresentTypeIdentifier, +) { + return ( + isVReferencePresentType(value) && value !== VReferencePresentType.AsNone + ); +} +//#endregion + +// +//#region Action Provider ... +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\v-video\v-video.component.html + + + + + + + + +
+ +
+ + +
+ + + +
+
+ + + +
+ + + + +
+ +
+ + +
+
+ {{ applyLocale(title) }} +
+
+ + +
+ + + + +
+ + +
+ +
+ + +
+ +
+
+
+
+
+
+ {{ formatVideoTime(currentTime) }} / {{ formatVideoTime(duration) }} +
+
+ + +
+ +
+ + + + + + + + + + + + + + +
+ + +
+ +
+ {{ getPlaybackRatesTitle() }} +
+ + + + + + + +
+
+ + +
+ + +
+
+
+
+
+ + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\v-video\v-video.component.scss + +.v-card { + border-radius: 8px; + overflow: hidden; +} + +.v-video-container { + position: relative; + width: 100%; + max-width: 960px; + margin: 0 auto; + aspect-ratio: 16 / 9; + background: #000; +} + +.video { + width: 100%; + height: 100%; + object-fit: contain; + display: block; + background: #000; +} + +.overlay.top { + position: absolute; + top: 8px; + left: 12px; + right: 12px; + display: flex; + justify-content: space-between; + pointer-events: none; +} + +.title { + font-size: 14px; + opacity: 0.8; +} + +.overlay.center { + position: absolute; + inset: 0; + margin: auto; + width: 64px; + height: 64px; + color: #fff; + opacity: 0.85; +} + +// +// Controls Styles ... +.v-speech-recognition-container { + width: 100%; + min-height: 50px; + max-height: 200px; + overflow-y: scroll; + font-size: small; +} + +.overlay.bottom { + position: absolute; + left: 0; + right: 0; + bottom: 0; + padding: 8px 12px; + background: linear-gradient(to top, rgba(0, 0, 0, 0.6), transparent); + transition: opacity 180ms ease-in-out; +} + +.hidable.hidden { + opacity: 0; + pointer-events: none; +} + +.v-video-progress-row { + display: flex; + align-items: center; + gap: 12px; +} +.v-video-progress-track { + flex: 1; + height: 4px; + border-radius: 2px; + background: rgba(255, 255, 255, 0.25); + cursor: pointer; + position: relative; +} +.v-video-progress-buffered, +.v-video-progress-current { + position: absolute; + left: 0; + top: 0; + height: 100%; + border-radius: 2px; +} +.v-video-progress-buffered { + background: rgba(255, 255, 255, 0.4); + width: 0%; +} +.v-video-progress-current { + background: #ff4081; /* Angular Material pink accent */ + width: 0%; +} +.v-video-time { + font-size: 12px; + opacity: 0.85; + white-space: nowrap; +} + +.v-video-actions-row { + margin-top: 6px; + display: flex; + align-items: center; + justify-content: space-between; +} +.v-video-actions-row .left, +.v-video-actions-row .right { + display: flex; + align-items: center; + gap: 8px; +} +.v-video-volume { + width: 140px; +} + +.v-action-bar { + width: 100%; +} + +.v-second-video-draggable { + right: 16px; + width: 200px; + bottom: 16px; + resize: both; + height: 120px; + overflow: hidden; + border-radius: 8px; + position: absolute; + border: 2px solid white; + background: black; + cursor: move; + z-index: 9999; + + video { + width: 100%; + height: 100%; + object-fit: contain; // 👈 show the entire shared window + background: black; // 👈 fill empty space with black bars + } +} + +// +// +// + +:host ::ng-deep .v-action-bar { + ion-toolbar { + --background: transparent !important; + background: transparent !important; + background-color: transparent !important; + } +} + +:host ::ng-deep .v-recording-timer { + // + button { + // + background: transparent !important; + .mat-button-wrapper > .x-action-bar-item-inner { + x-icon { + display: none !important; + } + } + } + + // + --background: transparent !important; + background: transparent !important; + background-color: transparent !important; +} + +:host ::ng-deep .v-video-volume-form, +:host ::ng-deep .v-video-volume-slider { + padding: 0 !important; + margin: 0 !important; + + min-width: 100px !important; + max-width: 200px !important; + + * { + padding: 0 !important; + margin: 0 !important; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\v-video\v-video.component.ts + +import { + Input, + NgZone, + Inject, + Component, + ViewChild, + Renderer2, + ElementRef, + HostListener, + ChangeDetectorRef, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + nameof, + isNumber, + XSpinner, + toNumber, + generateUuid, + XStandardType, + getFieldNames, + XOneOrManyType, + setPropertyValue, + getPropertyValue, + XColorIdentifier, + isNullOrUndefined, + XSpinnerIdentifier, + XResourceIdentifier, + isNullOrEmptyString, + XVideoFileExtensions, + downloadArrayBufferedFile, +} from 'x-framework-core'; +import { + IVVideoFlags, + IVVideoState, + VVideoAction, + isVVideoAction, + VVideoActionModel, + MediaRecorderType, + defaultAudioPoster, + VVideoVolumeSelectModel, + VVideoComponentContainer, + IVVideoComponentContainer, +} from './v-video.typings'; +import { + XCardAction, + XButtonType, + XPopoverOption, + XPopoverService, + XFormBaseAction, + XIconIdentifier, + XFormActionModel, + XFormControlType, + XCardActionModel, + XActionBarAction, + XActionBarItemSlot, + XActionBarActionModel, + XFormControlAppearance, + XComponentCardContainer, + XFormSliderControlConfig, + XActionBarActionFiredModel, + XFormControlValueChangeEventModel, +} from 'x-framework-components'; +import { + defaultMuteActionBar, + defaultMicOnActionBar, + defaultUnMuteActionBar, + defaultMicOffActionBar, + defaultVideoOnActionBar, + defaultCallEndActionBar, + defaultVideoOffActionBar, + defaultFullscreenActionBar, + defaultStopRecordActionBar, + defaultStartRecordActionBar, + defaultCallRequestActionBar, + defaultUnfullscreenActionBar, + defaultStopScreenShareActionBar, + defaultStartScreenShareActionBar, + defaultSwitchToBackCameraActionBar, + defaultSwitchToFrontCameraActionBar, +} from '../constants/actionbar.defaults'; +import { Subject, Subscription } from 'rxjs'; +import { CdkDrag } from '@angular/cdk/drag-drop'; +import { X_CONFIG } from 'src/app/config/x.config'; +import { XConfig } from 'src/app/config/app.config'; +import { ViewportRuler } from '@angular/cdk/scrolling'; +import { XManagerService } from 'x-framework-services'; +import { VBaseInCardComponent } from '../classes/v-base.component'; +import { XSimpleFormConfig } from '../typings/simple.form.typings'; +import { SharedService } from 'src/app/shared/services/shared.service'; +import { XSimpleActionBarItem } from '../typings/simple.actionbar.typings'; +import { XCameraFacing } from 'src/app/shared/services/x-media-devices.service'; + +/** + * a Media Presenter Component ... + */ +@Component({ + selector: 'v-video', + templateUrl: './v-video.component.html', + styleUrls: ['./v-video.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class VVideoComponent + extends VBaseInCardComponent + implements IVVideoComponentContainer +{ + // + //#region Props ... + readonly Spinners = Object.assign(XSpinner, {}); + readonly ButtonTypes = Object.assign(XButtonType, {}); + readonly Properties = getFieldNames(this.videoPropertyProvider); + //#endregion + + // + //#region Constructor ... + constructor( + public zone: NgZone, + @Inject(X_CONFIG) + public config: XConfig, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public sharedService: SharedService, + public managerService: XManagerService, + public changeDetector: ChangeDetectorRef, + protected popoverService: XPopoverService, + protected propertyProvider: XComponentCardContainer, + protected videoPropertyProvider: VVideoComponentContainer + ) { + super( + zone, + config, + element, + renderer, + ruler, + sharedService, + managerService, + changeDetector, + popoverService, + propertyProvider + ); + } + //#endregion + + // + //#region LifeCycles ... + async afterViewInit() { + super.afterViewInit(); + + // + // Initialize defaults + this.video.nativeElement.volume = this.volume; + this.video.nativeElement.playbackRate = this.playbackRate; + + // + await this.handleAutoPlay(); + + // + // Handle Loading State ... + if ( + !this.loading && + this.hasDataSource() && + (this.preload === 'auto' || this.preload === 'metadata') + ) { + await this.handleLoadingState(true); + } + + // + // Attach Event Listener for Detect Fullscreen Mode Changed ... + window.addEventListener('resize', this.windowResizeEventListener); + + // + // Detecting Devices States and Capabilities ... + + // + this.hasFrontCamera = true; + this.hasBackCamera = false; + this.canSwitchCameras = false; + + // + this.subscribeActionProvider(); + this.handlePrepareForImageView(); + this.handleSourceObjectRecieved(); + this.handlePrepareCallingActions(); + this.handlePrepareVolumeChangerForm(); + this.sendAction(this.ProvidedActions.Inited); + } + + async onDestroy(): Promise { + super.onDestroy(); + + // + if (this.hideTimeout) { + window.clearTimeout(this.hideTimeout); + } + + // + // Remove Full Screen Recognizer Event Listener ... + window.removeEventListener('resize', this.windowResizeEventListener); + + // + // Stop Playing ... + if (this.isPlaying) { + await this.togglePlay(); + } + + // + // Fullscreen Mode ... + if (this.fullscreenMode) { + this.toggleFullscreen(); + } + + // + // Destroy src Object if Exists ... + if (!isNullOrUndefined(this.srcObject)) { + // + this.srcObject.getTracks().forEach((track) => track.stop()); + this.srcObject = undefined; + } + + // + // Destory Screen Share if it's Exists ... + if (!isNullOrUndefined(this.screenShareStream)) { + // + this.screenShareStream.getTracks().forEach((track) => track.stop()); + this.screenShareStream = null; + } + + // + // Destroy Strem if Exists ... + if (!isNullOrUndefined(this.srcObject)) { + // + this.srcObject.getTracks().forEach((track) => track.stop()); + this.srcObject = null; + } + + // + // Destroy Second Video Stream if Exists ... + if (!isNullOrUndefined(this.pictureInPictureStream)) { + // + this.pictureInPictureStream.getTracks().forEach((track) => track.stop()); + this.pictureInPictureStream = null; + } + + // + // Unsubscribe Action Provider ... + this.unsubscribeActionProvider(); + } + + async onChange(changeKeys: string[]): Promise { + super.onChange(changeKeys); + + // + let isChanged = false; + let applyChanges = false; + + // + // Default Property Handlers ... + if (this.hasChild(changeKeys)) { + this.validateChangedProperties(changeKeys); + } + + // + // Loading ... + isChanged = changeKeys.includes(nameof('loading')); + if (isChanged) { + } + + // + // Ui Disabled ... + isChanged = changeKeys.includes(nameof('uiDisabled')); + if (isChanged) { + } + + // + // Title ... + isChanged = changeKeys.includes(nameof('title')); + if (isChanged) { + applyChanges = true; + this.cardTitle = this.title; + } + + // + // Poster Changed ... + isChanged = changeKeys.includes(nameof('poster')); + if (isChanged) { + // + const isOnlyImage = + !isNullOrEmptyString(this.poster) && + isNullOrEmptyString(this.src) && + isNullOrUndefined(this.srcObject); + if (isOnlyImage) { + // + applyChanges = true; + await this.handlePrepareForImageView(); + } + } + + // + // Calling Actions ... + isChanged = changeKeys.includes( + nameof('showCallingActions') + ); + if (isChanged) { + this.handlePrepareCallingActions(); + } + + // + // srcObject ... + isChanged = changeKeys.includes(nameof('srcObject')); + if (isChanged) { + // + if (!isNullOrUndefined(this.srcObject)) { + this.handleSourceObjectRecieved(); + } + } + + // + // Volume Change Control ... + isChanged = changeKeys.includes( + nameof('showVolumeChangerControl') + ); + if (isChanged && !!this.showVolumeChangerControl) { + // + applyChanges = true; + this.handlePrepareVolumeChangerForm(); + } + + // + // Action Provider ... + isChanged = changeKeys.includes(nameof('actionProvider')); + if (isChanged) { + // + if (isNullOrUndefined(this.actionProvider)) { + this.actionProvider = new Subject(); + } + + // + if (!isNullOrUndefined(this.actionProvider)) { + this.subscribeActionProvider(); + } + } + + // + if (applyChanges) { + this.handleChangeDetection(); + } + + // + this.sendAction(this.ProvidedActions.PropertyChanged, changeKeys); + } + //#endregion + + // + //#region Properties ... + /** + * Retrieve Component State ... + * + * @returns + */ + getState() { + // + let result: IVVideoState = {}; + + // + const keys = Object.keys(result); + keys.forEach((k) => { + // + const value = getPropertyValue(this, k); + setPropertyValue(result, k, value); + }); + + // + return result; + } + + /** + * Set Component State ... + * only Apply not Null or Undefined Values per Keys ... + * + * @param state + * @returns + */ + setState(state: IVVideoState) { + // + if (isNullOrUndefined(state)) { + return; + } + + // + let applyChanges = false; + const keys = Object.keys(state); + keys.forEach((k) => { + // + const value = (state as any)[k]; + if (!isNullOrUndefined(value)) { + // + applyChanges = true; + setPropertyValue(this, k, value); + } + }); + + // + if (applyChanges) { + this.handleChangeDetection(); + } + } + + /** + * Reading Flags ... + */ + getFlags() { + // + var result: IVVideoFlags = { + muted: this.muted, + isInCall: this.isInCall, + isPlaying: this.isPlaying, + micEnabled: this.micEnabled, + videoEnabled: this.videoEnabled, + hasBackCamera: this.hasBackCamera, + hasFrontCamera: this.hasFrontCamera, + fullscreenMode: this.fullscreenMode, + isScreenShared: this.isScreenShared, + canSwitchCameras: this.canSwitchCameras, + currentUsingMediaDevice: this.currentUsingMediaDevice, + }; + + // + return result; + } + + /** + * Set Flags ... + * + * @param flags + * @returns + */ + setFlags(flags: IVVideoFlags) { + // + let applyChange = false; + + // + if (isNullOrUndefined(flags)) { + return; + } + + // + const keys = Object.keys(flags); + if (!this.hasChild(keys)) { + return; + } + + // + keys.forEach((k) => { + // + const value = (flags as any)[k]; + if (!isNullOrUndefined(value)) { + // + applyChange = true; + setPropertyValue(this, k, value); + } + }); + + // + if (applyChange) { + this.handleChangeDetection(); + } + } + + /** + * Retrieve All Setted Properties ... + * + * @returns + */ + getProperties() { + // + let result: Partial = {}; + + // + const keys = Object.keys(this.Properties); + keys.forEach((k) => { + // + const value = getPropertyValue(this, k); + setPropertyValue(result, k, value); + }); + + // + return result; + } + + /** + * Set Provided Properties ... + * + * @param properties + */ + setProperties(properties: Partial) { + // + let applyChanges = false; + const keys = Object.keys(properties); + keys.forEach((k) => { + // + const value = getPropertyValue(properties, k); + if (!isNullOrUndefined(value) && !applyChanges) { + applyChanges = true; + } + const defaultValue = getPropertyValue(this.videoPropertyProvider, k); + const applyedValue = !isNullOrUndefined(value) ? value : defaultValue; + setPropertyValue(this, k, applyedValue); + }); + + // + if (applyChanges) { + this.handleChangeDetection(); + } + } + //#endregion + + // + // + // Implementing Separation of Concerns ... + // + // + + // + //#region Views ... + /** + * Video Element ... + */ + @ViewChild('videoRef') + video!: ElementRef; + + /** + * Picture in Picture Video Element ... + */ + @ViewChild('secondVideo') + secondVideo!: ElementRef; + + /** + * Picture in Picture Video Container Element ... + */ + @ViewChild('pictureInPictureContainer', { read: CdkDrag }) + pictureInPictureCDKDrag!: CdkDrag; + + /** + * Picture in Picture Video Container Element ... + */ + @ViewChild('pictureInPictureContainer') + pictureInPictureContainer!: ElementRef; + + /** + * Speech Recognition Container ... + */ + @ViewChild('speechRecognitionContainer') + speechRecognitionContainer!: ElementRef; + //#endregion + + // + //#region Common ... + // These Props Doesnt Exists on Property Provider ... + // + @Input() + src = ''; + + @Input() + poster = ''; + + @Input() + title = ''; + + @Input() + preload: 'auto' | 'metadata' | 'none' = 'metadata'; + + @Input() + crossOrigin?: 'anonymous' | 'use-credentials'; + + @Input() + srcObject: MediaStream | null | undefined = undefined; + //#endregion + + // + //#region Card ... + @Input() + isInPage: XStandardType = this.videoPropertyProvider.isInPage; + + @Input() + wrapWithCard: XStandardType = + this.videoPropertyProvider.wrapWithCard; + + @Input() + cardCssClass: XStandardType> = + this.videoPropertyProvider.cardCssClass; + + @Input() + cardColor: XStandardType = + this.videoPropertyProvider.cardColor; + + @Input() + cardForegroundColor: XStandardType = + this.videoPropertyProvider.cardForegroundColor; + + @Input() + cardTitle: XStandardType = + this.videoPropertyProvider.cardTitle; + + @Input() + cardSubTitle: XStandardType = + this.videoPropertyProvider.cardSubTitle; + + @Input() + cardShowFooter: XStandardType = + this.videoPropertyProvider.cardShowFooter; + + @Input() + cardShowActions: XStandardType = + this.videoPropertyProvider.cardShowActions; + //#endregion + + // + //#region Component Styles ... + /** + * Providing CSS Classes ... + */ + @Input() + cssClass: Array = this.videoPropertyProvider.cssClass; + + /** + * Auto Play Media ... + */ + @Input() + autoPlay: boolean = this.videoPropertyProvider.autoPlay; + + /** + * Continues Play Media ... + */ + @Input() + continuesPlay: boolean = this.videoPropertyProvider.continuesPlay; + + /** + * Specified Loading Spinner Type ... + */ + @Input() + loadingSpinner: XSpinnerIdentifier = + this.videoPropertyProvider.loadingSpinner; + + /** + * Specified Loading Spinner Color ... + */ + @Input() + loadingSpinnerColor: XColorIdentifier = + this.videoPropertyProvider.loadingSpinnerColor; + + /** + * Show Video Title ... + */ + @Input() + showTitle: boolean = this.videoPropertyProvider.showTitle; + //#endregion + + // + //#region Video ... + /** + * Show Play Pause Toggle Button on Center of Screen ... + */ + @Input() + showPlayPauseToggler: boolean = + this.videoPropertyProvider.showPlayPauseToggler; + + /** + * Show Video Progress Bar + * including Seeking Bar and Video Time ... + */ + @Input() + showProgressBar: boolean = this.videoPropertyProvider.showProgressBar; + + /** + * Show Vide Seeking Bar ... + */ + @Input() + showSeekingBar: boolean = this.videoPropertyProvider.showSeekingBar; + + /** + * Steps to Move Forard/Rewind ... + */ + @Input() + seekingStep: number = this.videoPropertyProvider.seekingStep; + + /** + * Show Vide Time ... + */ + @Input() + showVideoTime: boolean = this.videoPropertyProvider.showVideoTime; + + /** + * Show Video Actions ... + */ + @Input() + showActions: boolean = this.videoPropertyProvider.showActions; + + /** + * Enable Handling Keyboard Events ... + */ + @Input() + enableKeyboardEvents: boolean = + this.videoPropertyProvider.enableKeyboardEvents; + + /** + * Show Calling Actions ... + */ + @Input() + showCallingActions: boolean = this.videoPropertyProvider.showCallingActions; + + /** + * Video Duration ... + */ + duration = 0; + + /** + * Current Video Time ... + */ + currentTime = 0; + + /** + * Current Loaded Percent ... + */ + currentPercent = 0; + + /** + * Buffered Percent ... + */ + bufferedPercent = 0; + + /** + * Calling Actions Items ... + */ + callingActions: Array = []; + + /** + * Seeking Media Relative to Current Time ... + * + * @param deltaSeconds + */ + seekRelative(deltaSeconds: number): void { + // + const el = this.video.nativeElement; + el.currentTime = Math.min( + this.duration, + Math.max(0, el.currentTime + deltaSeconds) + ); + } + + /** + * Seeking Media based on Where user Clicked on Progressbar ... + * + * @param ev + */ + seekByClick(ev: MouseEvent): void { + // + if (this.loading || this.uiDisabled) { + return; + } + + // + const track = ev.currentTarget as HTMLElement; + const rect = track.getBoundingClientRect(); + const pct = (ev.clientX - rect.left) / rect.width; + const el = this.video.nativeElement; + el.currentTime = pct * this.duration; + } + //#endregion + + // + //#region Controls ... + // + controlsHidden = false; + private hideTimeout?: number; + + // + //#region Play / Pause Control ... + /** + * Show Play/Pause Control on Video Actions ... + */ + @Input() + showPlayPauseControl: boolean = + this.videoPropertyProvider.showPlayPauseControl; + + /** + * Play Control Resource ID ... + */ + @Input() + playControlTitle: XResourceIdentifier = + this.videoPropertyProvider.playControlTitle; + + /** + * Play Control Icon Name ... + */ + @Input() + playControlIcon: XIconIdentifier = this.videoPropertyProvider.playControlIcon; + + /** + * Pause Control Resource ID ... + */ + @Input() + pauseControlTitle: XResourceIdentifier = + this.videoPropertyProvider.pauseControlTitle; + + /** + * Pause Control Icon Name ... + */ + @Input() + pauseControlIcon: XIconIdentifier = + this.videoPropertyProvider.pauseControlIcon; + + /** + * Play / Pause Flag ... + */ + isPlaying = false; + + /** + * Format Video Time ... + * + * @param seconds + * @returns + */ + formatVideoTime(seconds: number): string { + // + if (!isFinite(seconds)) { + return '00:00'; + } + + // + const h = Math.floor(seconds / 3600); + const m = Math.floor((seconds % 3600) / 60); + const s = Math.floor(seconds % 60); + const hh = h > 0 ? `${h}:` : ''; + const mm = h > 0 ? String(m).padStart(2, '0') : String(m); + const ss = String(s).padStart(2, '0'); + + // + let result = `${hh}${mm}:${ss}`; + result = this.applyLocale(result); + + // + return result; + } + + /** + * Get Play / Pause Toggle Icon ... + */ + getPlayPauseIcon() { + return this.isPlaying ? this.pauseControlIcon : this.playControlIcon; + } + + /** + * Get Play / Pause Toggle Flag ... + */ + getPlayPauseTitle() { + return this.isPlaying ? this.pauseControlTitle : this.playControlTitle; + } + + /** + * Handling Auto Play ... + * + * @returns + */ + async handleAutoPlay() { + // + if ( + !this.autoPlay || + this.isPlaying || + !this.hasDataSource() || + isNullOrUndefined(this.video) + ) { + return; + } + + // + await this.togglePlay(); + } + + /** + * Toggle Video Play/Pause ... + */ + async togglePlay(): Promise { + // + const el = this.video.nativeElement; + if (el.paused) { + // + try { + // + await el.play(); + this.isPlaying = true; + this.startRecognition(); + this.sendAction(this.ProvidedActions.Played); + this.handlePrepareCallingActions(); + } catch { + // Autoplay blocked or error + this.isPlaying = false; + this.stopRecognition(); + this.sendAction(this.ProvidedActions.Paused); + this.handlePrepareCallingActions(); + } + } else { + // + el.pause(); + this.isPlaying = false; + this.stopRecognition(); + this.sendAction(this.ProvidedActions.Paused); + this.handlePrepareCallingActions(); + } + } + //#endregion + + // + //#region Rewind Control ... + /** + * Show Video Rewind Control ... + */ + @Input() + showRewindControl: boolean = this.videoPropertyProvider.showRewindControl; + + /** + * Rewind Control Resource ID ... + */ + @Input() + rewindControlTitle: XResourceIdentifier = + this.videoPropertyProvider.rewindControlTitle; + + /** + * Rewind Control Icon Name ... + */ + @Input() + rewindControlIcon: XIconIdentifier = + this.videoPropertyProvider.rewindControlIcon; + //#endregion + + // + //#region Forward Control ... + /** + * Show Video Forward Control ... + */ + @Input() + showForwardControl: boolean = this.videoPropertyProvider.showForwardControl; + + /** + * Forward Control Resource ID ... + */ + @Input() + forwardControlTitle: XResourceIdentifier = + this.videoPropertyProvider.forwardControlTitle; + + /** + * Forward Control Icon Name ... + */ + @Input() + forwardControlIcon: XIconIdentifier = + this.videoPropertyProvider.forwardControlIcon; + //#endregion + + // + //#region Mute / UnMute Control ... + /** + * Show Volume Mute/Unmute Toggler Control ... + */ + @Input() + showVolumeTogglerControl: boolean = + this.videoPropertyProvider.showVolumeTogglerControl; + + /** + * Unmute Control Resource ID ... + */ + @Input() + unmuteControlTitle: XResourceIdentifier = + this.videoPropertyProvider.unmuteControlTitle; + + /** + * Unmute Control Icon Name ... + */ + @Input() + unmuteControlIcon: XIconIdentifier = + this.videoPropertyProvider.unmuteControlIcon; + + /** + * Mute Control Resource ID ... + */ + @Input() + muteControlTitle: XResourceIdentifier = + this.videoPropertyProvider.muteControlTitle; + + /** + * Mute Control Icon Name ... + */ + @Input() + muteControlIcon: XIconIdentifier = this.videoPropertyProvider.muteControlIcon; + + /** + * Current Mute State ... + */ + muted = false; + + /** + * Toggle Mute / Unmute ... + */ + toggleMute(): void { + // + const el = this.video.nativeElement; + el.muted = !el.muted; + this.onVolumeChange(); + } + + /** + * Get Mute / Unmute Toggle Icon ... + * + * @returns + */ + getMuteUnMuteToggleIcon() { + return this.muted ? this.unmuteControlIcon : this.muteControlIcon; + } + + /** + * Get Mute / UnMute Toggle Title ... + * + * @returns + */ + getMuteUnMuteToggleTitle() { + return this.muted ? this.unmuteControlTitle : this.muteControlTitle; + } + //#endregion + + // + //#region Volume Change Control ... + /** + * Show Volume Changer Control ... + */ + @Input() + showVolumeChangerControl: boolean = + this.videoPropertyProvider.showVolumeChangerControl; + + /** + * Volume Changer Control Resource ID ... + */ + @Input() + volumeChangerControlTitle: XResourceIdentifier = + this.videoPropertyProvider.volumeChangerControlTitle; + + /** + * Current Volume ... + */ + volume = 1; + + /** + * Volume Form Action ... + */ + volumeFormConfig: XSimpleFormConfig = undefined; + + /** + * Set Volume ... + * + * @param v + * @returns + */ + setVolume(v: number | null): void { + // + if (v == null) { + return; + } + + // + const el = this.video.nativeElement; + el.volume = Math.min(1, Math.max(0, v)); + el.muted = el.volume === 0; + this.onVolumeChange(); + } + //#endregion + + // + //#region Fullscreen Toggler Control ... + /** + * Show Fullscreen Toggler Control ... + */ + @Input() + showFullscreenTogglerControl: boolean = + this.videoPropertyProvider.showFullscreenTogglerControl; + + /** + * Fullscreen Control Resource ID ... + */ + @Input() + fullscreenControlTitle: XResourceIdentifier = + this.videoPropertyProvider.fullscreenControlTitle; + + /** + * Fullscreen Control Icon Name ... + */ + @Input() + fullscreenControlIcon: XIconIdentifier = + this.videoPropertyProvider.fullscreenControlIcon; + + /** + * Unfullscreen Control Resource ID ... + */ + @Input() + unfullscreenControlTitle: XResourceIdentifier = + this.videoPropertyProvider.unfullscreenControlTitle; + + /** + * Unfullscreen Control Icon Name ... + */ + @Input() + unfullscreenControlIcon: XIconIdentifier = + this.videoPropertyProvider.unfullscreenControlIcon; + + /** + * Full Screen Model Flag ... + */ + fullscreenMode = false; + + /** + * Handle Change fullscreenmode flag when user manually close the fullscreen mode ... + * @param event + */ + windowResizeEventListener: (event: Event) => void = (event) => { + // + const doc: any = document; + const isFullScreen = !isNullOrUndefined(doc.fullscreenElement); + const isChanged = !!this.fullscreenMode && !isFullScreen; + if (isChanged) { + // + this.fullscreenMode = false; + this.sendAction(this.ProvidedActions.Defaultscreened); + if ( + this.enablePictureInPicture && + !isNullOrUndefined(this.pictureInPictureContainer) + ) { + // + this.pictureInPictureContainer.nativeElement.style.width = ''; + this.pictureInPictureContainer.nativeElement.style.height = ''; + this.pictureInPictureContainer.nativeElement.style.transform = ''; + this.pictureInPictureCDKDrag.reset(); + } + this.handleChangeDetection(); + } + }; + + /** + * Toggle Media Fullscreen / Default Screen ... + */ + toggleFullscreen(): void { + // + const container = + (this.video.nativeElement.parentElement as HTMLElement) || + this.video.nativeElement; + const doc: any = document; + const el: any = container; + + // + if (!doc.fullscreenElement && el.requestFullscreen) { + // + try { + // + el.requestFullscreen(); + this.fullscreenMode = true; + this.sendAction(this.ProvidedActions.Fullscreened); + } catch { + this.fullscreenMode = false; + } + } else if (doc.exitFullscreen) { + // + try { + // + doc.exitFullscreen(); + this.fullscreenMode = false; + if ( + this.enablePictureInPicture && + !isNullOrUndefined(this.pictureInPictureContainer) + ) { + // + this.pictureInPictureContainer.nativeElement.style.width = ''; + this.pictureInPictureContainer.nativeElement.style.height = ''; + this.pictureInPictureContainer.nativeElement.style.transform = ''; + this.pictureInPictureCDKDrag.reset(); + + // + this.detectChanges(); + } + this.sendAction(this.ProvidedActions.Defaultscreened); + } catch { + this.fullscreenMode = true; + } + } + } + + /** + * Get Fullscreen Toggle Icon ... + * + * @returns + */ + getFullscreenToggleIcon() { + return this.fullscreenMode + ? this.unfullscreenControlIcon + : this.fullscreenControlIcon; + } + + /** + * Get Fullscreen Toggle Title ... + * + * @returns + */ + getFullscreenToggleTitle() { + // + return this.fullscreenMode + ? this.unfullscreenControlTitle + : this.fullscreenControlTitle; + } + //#endregion + + // + //#region Playback Rate(s) Control ... + /** + * Show Current Playback Rate ... + */ + @Input() + showCurrentPlaybackRate: boolean = + this.videoPropertyProvider.showCurrentPlaybackRate; + + /** + * Show Playback Rates Control ... + */ + @Input() + showPlaybackRatesControl: boolean = + this.videoPropertyProvider.showPlaybackRatesControl; + + /** + * Current Playback Rate ... + */ + playbackRate = 1; + + /** + * Allowed Playback Rates ... + */ + playbackRates = [0.5, 0.75, 1, 1.25, 1.5, 2]; + + /** + * Set Playback Rate ... + * + * @param rate + */ + setPlaybackRate(rate: number): void { + // + const el = this.video.nativeElement; + this.playbackRate = rate; + el.playbackRate = rate; + + // + this.detectChanges(); + } + + /** + * Select Media Playback Rates ... + * + * @param event + * @returns + */ + async selectPlaybackRates(event: Event) { + // + if (isNullOrUndefined(event)) { + return; + } + + // + console.log('s'); + + // + let options: XPopoverOption[] = []; + + // + // Prepare Context Menu ... + options = this.playbackRates.map((pr) => { + // + return { + id: `PlayBackRates_${pr}`, + title: ` ${pr} `, + color: this.ColorNames.Dark, + slot: 'start', + handler: () => { + this.setPlaybackRate(pr); + }, + } as XPopoverOption; + }); + + // + // Present Context Menu ... + await this.popoverService.presentContextMenu(options, event); + } + + /** + * Preparing Playback Rates Title ... + * + * @returns + */ + getPlaybackRatesTitle() { + // + let result = `${this.playbackRate}x`; + + // + return this.applyLocale(result); + } + //#endregion + + // + //#region Camera Switch Control ... + /** + * Show Camera Switch if Device Supported ... + */ + @Input() + showCameraSwitchControl: boolean = + this.videoPropertyProvider.showCameraSwitchControl; + + /** + * Front Camera Control Resource ID ... + */ + @Input() + frontCameraTitle: XResourceIdentifier = + this.videoPropertyProvider.frontCameraTitle; + + /** + * Front Camera Control Icon Name ... + */ + @Input() + frontCameraIcon: XIconIdentifier = this.videoPropertyProvider.frontCameraIcon; + + /** + * Back Camera Control Resource ID ... + */ + @Input() + backCameraTitle: XResourceIdentifier = + this.videoPropertyProvider.backCameraTitle; + + /** + * Back Camera Control Icon Name ... + */ + @Input() + backCameraIcon: XIconIdentifier = this.videoPropertyProvider.backCameraIcon; + + /** + * Has Back Camera ... + */ + hasBackCamera = false; + + /** + * Has Front Camera ... + */ + hasFrontCamera = false; + + /** + * Check Can Switch Camera or not ... + */ + canSwitchCameras = false; + + /** + * Current Using Camera Facing Mode Device ... + */ + currentUsingMediaDevice: XCameraFacing = XCameraFacing.User; + + /** + * Switch Media Device ... + * + * @param type + * @returns + */ + switchMediaDevice(type: XCameraFacing) { + // + if (!this.canSwitchCameras || isNullOrUndefined(type) || this.currentUsingMediaDevice === type) { + return; + } + + // + this.sendAction(this.ProvidedActions.RequestSwitchCamera, type); + } + //#endregion + + // + //#region Call Control ... + /** + * Show Call Control ... + * Request/End ... + */ + @Input() + showCallControl: boolean = this.videoPropertyProvider.showCallControl; + + /** + * Call Request Control Resource ID ... + */ + @Input() + callRequestControlTitle: XResourceIdentifier = + this.videoPropertyProvider.callRequestControlTitle; + + /** + * Call Request Control Icon Name ... + */ + @Input() + callRequestControlIcon: XIconIdentifier = + this.videoPropertyProvider.callRequestControlIcon; + + /** + * Call End Control Resource ID ... + */ + @Input() + callEndControlTitle: XResourceIdentifier = + this.videoPropertyProvider.callEndControlTitle; + + /** + * Cal End Control Icon Name ... + */ + @Input() + callEndControlIcon: XIconIdentifier = + this.videoPropertyProvider.callEndControlIcon; + + /** + * Is In Call Flag ... + */ + isInCall = false; + + /** + * Send Call Request ... + */ + requestCall() { + // + // Validate ... + if (this.isInCall || this.hasDataSource()) { + return; + } + + // + this.isInCall = false; + this.sendAction(this.ProvidedActions.RequestCall); + } + + /** + * End Active Call ... + */ + endCall() { + // + if (!this.isInCall || !this.hasDataSource()) { + return; + } + + // + this.sendAction(this.ProvidedActions.EndCall); + } + //#endregion + + // + //#region Video Call Control ... + /** + * Show Video Call Control ... + */ + @Input() + showVideoCallControl: boolean = + this.videoPropertyProvider.showVideoCallControl; + + /** + * Enable Video Control Resource ID ... + */ + @Input() + enableVideoControlTitle: XResourceIdentifier = + this.videoPropertyProvider.enableVideoControlTitle; + + /** + * Enable Video Control Icon Name ... + */ + @Input() + enableVideoControlIcon: XIconIdentifier = + this.videoPropertyProvider.enableVideoControlIcon; + + /** + * Disable Video Control Resource ID ... + */ + @Input() + disableVideoControlTitle: XResourceIdentifier = + this.videoPropertyProvider.disableVideoControlTitle; + + /** + * Disable Video Control Icon ... + */ + @Input() + disableVideoControlIcon: XIconIdentifier = + this.videoPropertyProvider.disableVideoControlIcon; + + /** + * Video Enabled Flag ... + */ + videoEnabled = true; + + /** + * Toggle Video ... + */ + toggleVideo() { + // + if (isNullOrUndefined(this.srcObject)) { + return; + } + + // + if (this.videoEnabled) { + // + this.videoEnabled = false; + this.srcObject.getVideoTracks().forEach((track) => { + track.enabled = false; + }); + } else { + // + this.videoEnabled = true; + this.srcObject.getVideoTracks().forEach((track) => { + track.enabled = true; + }); + } + + // + this.handleChangeDetection(); + this.sendAction(this.ProvidedActions.VideoStateChanged, this.videoEnabled); + } + //#endregion + + // + //#region Microphone Control ... + /** + * Show Microphone Control ... + */ + @Input() + showMicorphoneControl: boolean = + this.videoPropertyProvider.showMicorphoneControl; + + /** + * Enable Microphone Control Resource ID ... + */ + @Input() + enableMicrophoneControlTitle: XResourceIdentifier = + this.videoPropertyProvider.enableMicrophoneControlTitle; + + /** + * Enable Microphone Control Icon Name ... + */ + @Input() + enableMicrophoneControlIcon: XIconIdentifier = + this.videoPropertyProvider.enableMicrophoneControlIcon; + + /** + * Disable Microphone Control Resource ID ... + */ + @Input() + disableMicrophoneControlTitle: XResourceIdentifier = + this.videoPropertyProvider.disableMicrophoneControlTitle; + + /** + * Disable Microphone Control Icon ... + */ + @Input() + disableMicrophoneControlIcon: XIconIdentifier = + this.videoPropertyProvider.disableMicrophoneControlIcon; + + /** + * Microphone is Enabled Flag ... + */ + micEnabled = true; + + /** + * Toggle Microphone ... + * + * @returns + */ + toggleMic() { + // + if (isNullOrUndefined(this.srcObject)) { + return; + } + + // + if (this.micEnabled) { + // + this.micEnabled = false; + this.srcObject.getAudioTracks().forEach((track) => { + track.enabled = false; + }); + } else { + // + this.micEnabled = true; + this.srcObject.getAudioTracks().forEach((track) => { + track.enabled = true; + }); + } + + // + this.handleChangeDetection(); + this.sendAction(this.ProvidedActions.MicStateChanged, this.micEnabled); + } + //#endregion + + // + //#region ScreenShare Control ... + /** + * Show Share Screen Control ... + */ + @Input() + showShareScreenControl: boolean = + this.videoPropertyProvider.showShareScreenControl; + + /** + * Share Screen Start Control Resource ID ... + */ + @Input() + startShareScreenControlTitle: XResourceIdentifier = + this.videoPropertyProvider.startShareScreenControlTitle; + + /** + * Share Screen Start Control Icon Name ... + */ + @Input() + startShareScreenControlIcon: XIconIdentifier = + this.videoPropertyProvider.startShareScreenControlIcon; + + /** + * Share Screen Stop Control Resource ID ... + */ + @Input() + stopShareScreenControlTitle: XResourceIdentifier = + this.videoPropertyProvider.stopShareScreenControlTitle; + + /** + * Share Screen Stop Icon Name ... + */ + @Input() + stopShareScreenControlIcon: XIconIdentifier = + this.videoPropertyProvider.stopShareScreenControlIcon; + + /** + * Is on Screen Shared Mode ... + */ + isScreenShared = false; + + /** + * Screen Share Mode Media Stream ... + */ + screenShareStream: MediaStream = undefined; + + /** + * Stop Screen Sharing ... + */ + stopScreenShare() { + // + if (!this.isScreenShared) { + return; + } + + // + this.screenShareStream.getTracks().forEach((track) => track.stop()); + this.screenShareStream = undefined; + + // + // Handle Screen Share Stop ... + this.isScreenShared = false; + this.sendAction( + this.ProvidedActions.ScreenShareStateChanged, + this.isScreenShared + ); + } + + /** + * Start Screen Sharing ... + * + * @returns + */ + async startScreenShare() { + // + if (this.isScreenShared) { + return; + } + + // + // Initialize Screen Share Stream ... + if (isNullOrUndefined(this.screenShareStream)) { + // + // Access Display Media Stream ... + this.screenShareStream = + await this.sharedService.mediaDevicesService.getDisplayMediaDevice(); + + // + this.screenShareStream.getVideoTracks()[0].onended = () => { + // + // Force Handle Stop Sharing Screen ... + this.stopScreenShare(); + + // + // Update Calling Action ... + this.handlePrepareCallingActions(); + }; + } + + // + // Handle Screen Share Stop ... + this.isScreenShared = true; + this.sendAction( + this.ProvidedActions.ScreenShareStateChanged, + this.isScreenShared + ); + } + + /** + * Toggle Screen Sharing ... + */ + async toggleScreenShare() { + // + if (this.isScreenShared) { + this.stopScreenShare(); + } else { + await this.startScreenShare(); + } + } + + /** + * Request Stop Screen Share ... + */ + async requestStopScreenshare() { + // + if (this.isScreenShared) { + this.sendAction(this.ProvidedActions.RequestStopScreenShare); + } + } + + /** + * Request Start Screen Share ... + */ + async requestStartScreenshare() { + // + if (!this.isScreenShared) { + this.sendAction(this.ProvidedActions.RequestStartScreenShare); + } + } + //#endregion + + // + //#region Record Control ... + /** + * Show Record Control ... + */ + @Input() + showRecordControl: boolean = this.videoPropertyProvider.showRecordControl; + + /** + * Start Record Control Resource ID ... + */ + @Input() + startRecordControlTitle: XResourceIdentifier = + this.videoPropertyProvider.startRecordControlTitle; + + /** + * Start Record Control Icon Name ... + */ + @Input() + startRecordControlIcon: XIconIdentifier = + this.videoPropertyProvider.startRecordControlIcon; + + /** + * Stop Record Control Resource ID ... + */ + @Input() + stopRecordControlTitle: XResourceIdentifier = + this.videoPropertyProvider.stopRecordControlTitle; + + /** + * Stop Record Control Icon Name ... + */ + @Input() + stopRecordControlIcon: XIconIdentifier = + this.videoPropertyProvider.stopRecordControlIcon; + + /** + * Recording Timer Color ... + */ + @Input() + recordingTimerColor: XColorIdentifier = + this.videoPropertyProvider.recordingTimerColor; + + /** + * Is Recording Flag ... + */ + isRecording = false; + + /** + * Media Recorder Class ... + */ + mediaRecorder: any = null; + + /** + * Recorded Chuncs ... + */ + recordedChunks: Array = []; + + /** + * Recording Elappsed Time String ... + */ + recordingTimeElappsed: string = ''; + + /** + * Recording Start Time ... + */ + startRecordingTime: number = undefined; + + /** + * Recording Started Timer Intervale ... + */ + recordingIntervale: NodeJS.Timeout = undefined; + + /** + * Start Recording ... + * + * @returns + */ + startRecord() { + // + // Prevent if Recorder Control not Supported ... + if ( + !this.isPlaying || + !this.showRecordControl || + isNullOrUndefined(this.video) + ) { + return; + } + + // + // Initialize Media Recorder ... + if (isNullOrUndefined(this.mediaRecorder)) { + // + const stream = (this.video.nativeElement as any).captureStream(); + if (!isNullOrUndefined(stream)) { + // + this.mediaRecorder = new (MediaRecorder as MediaRecorderType)(stream, { + mimeType: 'video/webm', + }); + + // + if (!isNullOrUndefined(this.mediaRecorder)) { + // + // Filling Recorded Chunks ... + this.mediaRecorder.ondataavailable = (event: any) => { + // + if (event.data.size > 0) { + this.recordedChunks.push(event.data); + } + }; + + // + // Attach Stop Listener ... + this.mediaRecorder.onstop = () => { + // + // Destroy Intervale ... + clearInterval(this.recordingIntervale); + this.recordingIntervale = undefined; + + // + const blob = new Blob(this.recordedChunks, { type: 'video/webm' }); + const fileName = + generateUuid(this.config.appInstanceName) + + XVideoFileExtensions.Webm; + downloadArrayBufferedFile(fileName, blob); + }; + } + } + } + + // + if (isNullOrUndefined(this.mediaRecorder)) { + return; + } + + // + // Check Recording State ... + if (this.isRecording) { + return; + } + + // + // Start Recording ... + + // + this.isRecording = true; + this.mediaRecorder.start(); + this.startRecordingTime = Date.now(); + this.recordingIntervale = setInterval(() => { + // + const elapsed = Date.now() - this.startRecordingTime; + this.recordingTimeElappsed = this.formatRecordingTime(elapsed); + + // + // Handling apply Changes on Action Bar ... + if ( + !isNullOrUndefined(this.callingActions) && + this.hasChild(this.callingActions) + ) { + // + const idx = this.callingActions.findIndex( + (ca) => ca.id === 'RecordingTime' + ); + if (idx >= 0) { + // + this.callingActions[idx].title = this.recordingTimeElappsed; + this.callingActionsActionProvider.next({ + action: XActionBarAction.Refresh, + }); + } + } + }, 1000); + + // + this.sendAction( + this.ProvidedActions.RecordingStateChanged, + this.isRecording + ); + } + + /** + * Stop Recording ... + * + * @returns + */ + stopRecord() { + // + // Validate Recording State ... + if ( + !this.isPlaying || + !this.isRecording || + !this.showRecordControl || + isNullOrUndefined(this.mediaRecorder) + ) { + return; + } + + // + // Handle Stop Recording ... + + // + this.isRecording = false; + this.mediaRecorder.stop(); + this.recordedChunks = []; + + // + this.sendAction( + this.ProvidedActions.RecordingStateChanged, + this.isRecording + ); + } + + /** + * Toggle Record ... + * + * @returns + */ + toggleRecord() { + // + // Validate ... + if (!this.showRecordControl) { + return; + } + + // + if (this.isRecording) { + this.stopRecord(); + } else { + this.startRecord(); + } + } + + /** + * Format Recording Times ... + * + * @param ellapsed + * @returns + */ + private formatRecordingTime(ellapsed: number) { + // + const totalSeconds = Math.floor(ellapsed / 1000); + const minutes = String(Math.floor(totalSeconds / 60)).padStart(2, '0'); + const seconds = String(totalSeconds % 60).padStart(2, '0'); + + // + const result: string = this.applyLocale(`${minutes}:${seconds}`); + return result; + } + //#endregion + //#endregion + + // + //#region Abilities ... + // + //#region Speech Recognition ... + /** + * Enable Speech Recognition if Supported ... + */ + @Input() + enableSpeechRecognition: boolean = + this.videoPropertyProvider.enableSpeechRecognition; + + /** + * Speech Recognition Language ... + */ + @Input() + speechRecognitionLanguage: string = + this.videoPropertyProvider.speechRecognitionLanguage; + + /** + * Speech Recognition Transcripted Text Colors ... + */ + @Input() + speechRecognitionColor: XColorIdentifier = + this.videoPropertyProvider.speechRecognitionColor; + + /** + * Speech Recognition Flag ... + */ + speechRecognizerEnabled = false; + + /** + * Speech Recognizer Class ... + */ + speechRecognizer: any = undefined; + + /** + * Stop Speech Recognizer if provided ... + * + * @returns + */ + stopRecognition() { + // + if ( + !this.enableSpeechRecognition || + isNullOrUndefined(this.speechRecognizer) + ) { + return; + } + + // + this.speechRecognizer.stop(); + this.speechRecognizerEnabled = false; + this.sendAction( + this.ProvidedActions.SpeechRecognitionStateChanged, + this.speechRecognizerEnabled + ); + } + + /** + * Start Speech Recognizer if provided ... + * + * @returns + */ + startRecognition() { + // + if (!this.enableSpeechRecognition) { + return; + } + + // + // Initialize Speech Recognizer ... + if (isNullOrUndefined(this.speechRecognizer)) { + // + // Normalize Recognition Language ... + if (isNullOrEmptyString(this.speechRecognitionLanguage)) { + this.speechRecognitionLanguage = this.managerService.currentLocale; + } + + // + // Initialize speech recognition + const windowAny = window as any; + this.speechRecognizer = new (windowAny.SpeechRecognition || + windowAny.webkitSpeechRecognition)(); + if (isNullOrUndefined(this.speechRecognizer)) { + return; + } + + // + // Setting Speech Recognizer Properties ... + this.speechRecognizer.continuous = true; + this.speechRecognizer.interimResults = true; + this.speechRecognizer.lang = this.speechRecognitionLanguage; + + // + // Attach On Result Event Listener ... + this.speechRecognizer.onresult = (event: any) => { + // + let text = ''; + for (let i = event.resultIndex; i < event.results.length; i++) { + text += event.results[i][0].transcript; + } + + // + this.speechRecognitionContainer.nativeElement.innerText = text; + this.detectChanges(); + }; + + // + } + + // + this.speechRecognizer.start(); + this.speechRecognizerEnabled = true; + this.sendAction( + this.ProvidedActions.SpeechRecognitionStateChanged, + this.speechRecognizerEnabled + ); + } + + /** + * Toggle Speech Recognizer if provided ... + * + * @returns + */ + toggleRecognition() { + // + if ( + isNullOrUndefined(this.speechRecognizer) || + !this.enableSpeechRecognition + ) { + return; + } + + // + if (this.speechRecognizerEnabled) { + this.stopRecognition(); + } else { + this.startRecognition(); + } + } + //#endregion + + // + //#region Picture in Picture ... + /** + * Enable Picture in Picture Mode ... + */ + @Input() + enablePictureInPicture: boolean = + this.videoPropertyProvider.enablePictureInPicture; + + /** + * Picture In Picture Element Source Url ... + */ + @Input() + pictureInPictureSrc: string = this.videoPropertyProvider.pictureInPictureSrc; + + /** + * Picutre in Picture Element Source Strem ... + */ + @Input() + pictureInPictureStream: MediaStream = + this.videoPropertyProvider.pictureInPictureStream; + //#endregion + //#endregion + + // + //#region Action Providers ... + /** + * Card Action Provider ... + */ + cardActionProvider = new Subject(); + + /** + * Volume Form Action Provider ... + */ + volumeFormActionProvider = new Subject(); + + /** + * Calling Actions Action Provider ... + */ + callingActionsActionProvider = new Subject(); + //#endregion + + // + //#region Handlers ... + // + //#region Video Element Event Handlers ... + /** + * Handle Can Play State ... + */ + async onCanPlay() { + // + if (this.loading) { + await this.handleLoadingState(false); + } + + // + // Handle Auto Play ... + await this.handleAutoPlay(); + } + + /** + * Calls when Video Element Start Loadng ... + */ + async onWaiting() { + // + if (this.hasDataSource()) { + await this.handleLoadingState(true); + } + } + + /** + * Calls When Data Loaded ... + */ + async onLoadedData() { + // + if (this.hasDataSource() && this.loading) { + await this.handleLoadingState(false); + } + } + + /** + * Calls When Metadata Loaded ... + */ + async onLoadedMetadata(): Promise { + // + const el = this.video.nativeElement; + this.duration = el.duration || 0; + + // + if (this.loading) { + await this.handleLoadingState(false); + } + + // + // Set Audio Default Poster if Provided Data is Audio ... + const isAudio = !this.isVideoProvided(); + if (isAudio && isNullOrEmptyString(this.poster)) { + // + this.poster = defaultAudioPoster; + this.detectChanges(); + } + } + + /** + * Calls When Time Update Video ... + */ + onTimeUpdate(): void { + // + const el = this.video.nativeElement; + this.currentTime = el.currentTime || 0; + this.currentPercent = this.duration + ? (this.currentTime / this.duration) * 100 + : 0; + + // + this.sendAction(this.ProvidedActions.TimeUpdated, { + currentTime: this.currentTime, + currentPercent: this.currentPercent, + duration: this.duration, + }); + } + + /** + * Calls When Video Progressing ... + */ + onProgress(): void { + // + const el = this.video.nativeElement; + + // + // Use last buffered range as reference + const buf = el.buffered; + if (buf && buf.length) { + // + const end = buf.end(buf.length - 1); + this.bufferedPercent = this.duration ? (end / this.duration) * 100 : 0; + + // + this.sendAction(this.ProvidedActions.Progressed, { + end, + bufferedPercent: this.bufferedPercent, + }); + } + } + + /** + * Calls When Volume Changes ... + */ + onVolumeChange(): void { + // + const el = this.video.nativeElement; + this.volume = el.volume; + this.muted = el.muted || this.volume === 0; + + // + this.handleChangeDetection(); + + // + this.sendAction(this.ProvidedActions.VolumeChanged, this.volume); + + // + if (this.muted) { + this.sendAction(this.ProvidedActions.Muted); + } + } + + /** + * Calls When Play Ended ... + */ + onEnded(): void { + // + this.isPlaying = false; + this.currentPercent = 100; + + // + if (this.continuesPlay) { + this.togglePlay(); + } + + // + this.sendAction(this.ProvidedActions.Ended); + } + //#endregion + + /** + * Detect Loaded Data is Video or Not ... + * + * @returns + */ + isVideoProvided() { + // + let result = false; + + // + if ( + !isNullOrUndefined(this.video) && + !isNullOrUndefined(this.video.nativeElement) + ) { + // + const el = this.video.nativeElement; + result = el.videoWidth > 0 && el.videoHeight > 0; + } + + // + return result; + } + + /** + * Check Has Data Source ... + * + * @returns + */ + hasDataSource() { + return !isNullOrEmptyString(this.src) || !isNullOrUndefined(this.srcObject); + } + + /** + * Show Action Controls Temparory ... + */ + showControlsTemporarily() { + // + this.controlsHidden = false; + if (this.hideTimeout) { + window.clearTimeout(this.hideTimeout); + } + this.hideTimeout = window.setTimeout( + () => (this.controlsHidden = true), + 2000 + ); + } + + /** + * Handle Keyboard Events to Controlling Over Video Element ... + * + * @param event + * @returns + */ + @HostListener('window:keydown', ['$event']) + onKeydown(event: KeyboardEvent) { + // + let applyChange = false; + if (!this.enableKeyboardEvents) { + event.preventDefault(); + return; + } + + // + // Space: play/pause, arrows: seek, up/down: volume + switch (event.key) { + case ' ': + case 'k': + // + applyChange = true; + event.preventDefault(); + this.togglePlay(); + break; + case 'ArrowLeft': + // + applyChange = true; + event.preventDefault(); + this.seekRelative(-1 * this.seekingStep); + break; + case 'ArrowRight': + // + applyChange = true; + event.preventDefault(); + this.seekRelative(1 * this.seekingStep); + break; + case 'ArrowUp': + // + applyChange = true; + event.preventDefault(); + this.setVolume(Math.min(1, this.volume + 0.05)); + break; + case 'ArrowDown': + // + applyChange = true; + event.preventDefault(); + this.setVolume(Math.max(0, this.volume - 0.05)); + break; + case 'm': + // + applyChange = true; + event.preventDefault(); + this.toggleMute(); + break; + case 'f': + // + applyChange = true; + event.preventDefault(); + this.toggleFullscreen(); + break; + } + + // + if (applyChange) { + this.showControlsTemporarily(); + } + } + + /** + * Handle Loading Change ... + * + * @param state + * @returns + */ + async handleLoadingState(state: boolean) { + // + // Validate new State ... + if (this.loading === state) { + return; + } + + // + // Set Loading ... + await this.setLoadingState(state); + + // + // TODO ... + // Implement Required Preparation Actions here ... + + // + // Notify Change State ... + this.sendAction(this.ProvidedActions.LoadingStateChanged, this.loading); + } + + /** + * Handle Change Detection ... + */ + handleChangeDetection() { + // + this.callingActionsActionProvider.next({ + action: XActionBarAction.Refresh, + }); + this.cardActionProvider.next({ action: XCardAction.Refresh }); + + // + this.handlePrepareCallingActions(); + this.handlePrepareVolumeChangerForm(); + this.volumeFormActionProvider.next({ action: XFormBaseAction.Refresh }); + + // + this.detectChanges(); + } + + /** + * Validate Changed Properties ... + * + * @param keys + */ + private validateChangedProperties(keys: string[]) { + // + let applyChanges = false; + const allowedKeys = Object.keys(this.Properties); + keys.forEach((k) => { + // + const isExists = allowedKeys.includes(k); + if (isExists) { + // + const value = (this as any)[k]; + const defaultValue = (this.videoPropertyProvider as any)[k]; + if (isNullOrUndefined(value)) { + // + applyChanges = true; + setPropertyValue(this, k, defaultValue); + } + } + }); + + // + if (applyChanges) { + this.detectChanges(); + } + } + + /** + * Handle Volume Change Form Config ... + */ + private handlePrepareVolumeChangerForm() { + // + if (!this.showVolumeChangerControl) { + return; + } + + // + this.volumeFormConfig = undefined; + this.detectChanges(); + + // + this.volumeFormConfig = { + name: 'Volume_Form', + model: { + volume: this.volume, + }, + controls: [ + // + // Volume Control ... + { + index: 0, + propName: 'volume', + type: { + type: XFormControlType.Slider, + config: { + min: 0, + max: 1, + step: 0.05, + thumbLabel: false, + vertical: this.showCallingActions, + formatter: (value: number) => + this.applyLocale((value * 100).toFixed()), + } as XFormSliderControlConfig, + }, + appearance: { + label: '', + disabled: this.muted, + cssClass: ['v-video-volume-slider'], + tooltip: this.volumeChangerControlTitle, + appearance: XFormControlAppearance.None, + }, + eventHandlers: { + valueChanged: (event: XFormControlValueChangeEventModel) => { + // + // Check Value Must Different to Current ... + if (event.value !== this.volume) { + // + // Handle Change Volume ... + this.setVolume(event.value); + } + }, + }, + }, + ], + }; + + // + this.detectChanges(); + } + + /** + * Prepare Component States as an Image Viewer ... + * Note: for this purpose you had to only Provides poster input ... + * + * @returns + */ + private async handlePrepareForImageView() { + // + // Validate State ... + if ( + !isNullOrUndefined(this.srcObject) || + !isNullOrEmptyString(this.src) || + isNullOrEmptyString(this.poster) + ) { + return; + } + + // + if (this.loading) { + // + await this.setLoadingState(false); + this.sendAction(this.ProvidedActions.LoadingStateChanged, false); + } + + // + this.showActions = false; + this.showProgressBar = false; + this.showCallingActions = false; + + // + this.handleChangeDetection(); + } + + /** + * Preparing Calling Actions ... + */ + private handlePrepareCallingActions() { + // + if (!this.showCallingActions) { + return; + } + + // + this.callingActions = []; + + // + // Preparing Calling Actions ... + + // + const actionBarItems: Array = []; + + // + // Connect / Disconnect Call ... + let startIDX = 1; + + // + // Start Slot ... + + // + // Call Request / End ... + if (this.showCallControl) { + // + // End Call ... + if (this.isInCall) { + // + actionBarItems.push({ + ...defaultCallEndActionBar, + index: startIDX, + icon: this.callEndControlIcon, + title: this.callEndControlTitle, + disabled: this.loading || this.uiDisabled, + }); + + // + startIDX++; + } + // + // Request Call ... + else { + // + actionBarItems.push({ + ...defaultCallRequestActionBar, + index: startIDX, + icon: this.callRequestControlIcon, + title: this.callRequestControlTitle, + disabled: this.loading || this.uiDisabled, + }); + + // + startIDX++; + } + } + + // + // Mute / UnMute ... + if (this.showVolumeTogglerControl) { + // + // UnMute ... + if (this.muted) { + // + actionBarItems.push({ + ...defaultUnMuteActionBar, + index: startIDX, + icon: this.unmuteControlIcon, + title: this.unmuteControlTitle, + disabled: this.loading || this.uiDisabled, + }); + + // + startIDX++; + } + // + // Unmute ... + else { + // + actionBarItems.push({ + ...defaultMuteActionBar, + index: startIDX, + icon: this.muteControlIcon, + title: this.muteControlTitle, + disabled: this.loading || this.uiDisabled, + }); + + // + startIDX++; + } + } + + // + // Camera Switch Button ... + if (this.showCameraSwitchControl && this.canSwitchCameras) { + // + // Check Current Camera In Used ... + const isFrontCameraInUse = + this.currentUsingMediaDevice === XCameraFacing.User; + const isBackCameraInUse = + this.currentUsingMediaDevice === XCameraFacing.Environment; + + // + // Switch to Back Camera ... + if (isFrontCameraInUse) { + // + actionBarItems.push({ + ...defaultSwitchToBackCameraActionBar, + index: startIDX, + icon: this.backCameraIcon, + title: this.backCameraTitle, + disabled: this.loading || this.uiDisabled, + }); + + // + startIDX++; + } + // + // Switch to Front Camera ... + if (isBackCameraInUse) { + // + actionBarItems.push({ + ...defaultSwitchToFrontCameraActionBar, + index: startIDX, + icon: this.frontCameraIcon, + title: this.frontCameraTitle, + disabled: this.loading || this.uiDisabled, + }); + + // + startIDX++; + } + } + + // + // Screen Share ... + if (this.showShareScreenControl) { + // + // Disable ... + if (this.isScreenShared) { + // + actionBarItems.push({ + ...defaultStopScreenShareActionBar, + index: startIDX, + icon: this.stopShareScreenControlIcon, + title: this.stopShareScreenControlTitle, + disabled: this.loading || this.uiDisabled, + }); + + // + startIDX++; + } + // + // Enable ... + else { + // + actionBarItems.push({ + ...defaultStartScreenShareActionBar, + index: startIDX, + icon: this.startShareScreenControlIcon, + title: this.startShareScreenControlTitle, + disabled: this.loading || this.uiDisabled, + }); + + // + startIDX++; + } + } + + // + // End Slot ... + let endIDX = 1; // startIDX; + + // + // Recorder ... + if (this.showRecordControl) { + // + // Recording Time ... + if (this.isRecording) { + // + actionBarItems.push({ + ...defaultStopRecordActionBar, + index: endIDX, + icon: undefined, + onlyIcon: false, + id: 'RecordingTime', + slot: XActionBarItemSlot.END, + cssClass: ['v-recording-timer'], + color: this.recordingTimerColor, + title: this.recordingTimeElappsed, + titleColor: this.ColorNames.Warning, + disabled: this.loading || this.uiDisabled, + }); + + // + endIDX++; + } + + // + // Stop Record ... + if (this.isRecording) { + // + actionBarItems.push({ + ...defaultStopRecordActionBar, + index: endIDX, + slot: XActionBarItemSlot.END, + icon: this.stopRecordControlIcon, + title: this.stopRecordControlTitle, + disabled: this.loading || this.uiDisabled, + }); + + // + endIDX++; + } + // + // Start Record ... + else { + // + actionBarItems.push({ + ...defaultStartRecordActionBar, + index: endIDX, + slot: XActionBarItemSlot.END, + icon: this.startRecordControlIcon, + title: this.startRecordControlTitle, + disabled: this.loading || this.uiDisabled, + }); + + // + endIDX++; + } + } + + // + // Video On / Video off ... + if (this.showVideoCallControl) { + // + // Video Off ... + if (this.videoEnabled) { + // + actionBarItems.push({ + ...defaultVideoOffActionBar, + index: endIDX, + slot: XActionBarItemSlot.END, + icon: this.disableVideoControlIcon, + title: this.disableVideoControlTitle, + disabled: this.loading || this.uiDisabled, + }); + + // + endIDX++; + } + // + // Video On ... + else { + // + actionBarItems.push({ + ...defaultVideoOnActionBar, + index: endIDX, + slot: XActionBarItemSlot.END, + icon: this.enableVideoControlIcon, + title: this.enableVideoControlTitle, + disabled: this.loading || this.uiDisabled, + }); + + // + endIDX++; + } + } + + // + // Mic On / Mic off ... + if (this.showMicorphoneControl) { + // + // Mic Off ... + if (this.micEnabled) { + // + actionBarItems.push({ + ...defaultMicOffActionBar, + index: endIDX, + slot: XActionBarItemSlot.END, + icon: this.disableMicrophoneControlIcon, + title: this.disableMicrophoneControlTitle, + disabled: this.loading || this.uiDisabled, + }); + + // + endIDX++; + } + // + // Mic On ... + else { + // + actionBarItems.push({ + ...defaultMicOnActionBar, + index: endIDX, + slot: XActionBarItemSlot.END, + icon: this.enableMicrophoneControlIcon, + title: this.enableMicrophoneControlTitle, + disabled: this.loading || this.uiDisabled, + }); + + // + endIDX++; + } + } + + // + // Full Screen Toggler ... + if (this.showFullscreenTogglerControl) { + // + // Defaultscreen ... + if (this.fullscreenMode) { + // + actionBarItems.push({ + ...defaultUnfullscreenActionBar, + index: endIDX, + slot: XActionBarItemSlot.END, + icon: this.unfullscreenControlIcon, + title: this.unfullscreenControlTitle, + disabled: this.loading || this.uiDisabled, + }); + + // + endIDX++; + } + // + // Fullscreen ... + else { + // + actionBarItems.push({ + ...defaultFullscreenActionBar, + index: endIDX, + slot: XActionBarItemSlot.END, + icon: this.fullscreenControlIcon, + title: this.fullscreenControlTitle, + disabled: this.loading || this.uiDisabled, + }); + + // + endIDX++; + } + } + + // + this.callingActions = [...actionBarItems]; + + // + this.detectChanges(); + } + + /** + * When Source Object Recieved ... + * + * @returns + */ + private handleSourceObjectRecieved() { + // + // Validate ... + if (isNullOrUndefined(this.video) || isNullOrUndefined(this.srcObject)) { + return; + } + + // + // Here we are going to Handle Microphone ... + const audioTracks = this.srcObject.getAudioTracks(); + const hasAudioTrack = + !isNullOrUndefined(audioTracks) && this.hasChild(audioTracks); + if (hasAudioTrack) { + this.micEnabled = true; + } + + // + // Check Video Tracks ... + const videoTracks = this.srcObject.getVideoTracks(); + const hasVideoTrack = + !isNullOrUndefined(videoTracks) && this.hasChild(videoTracks); + if (hasVideoTrack) { + this.videoEnabled = true; + } + + // + this.isInCall = this.micEnabled || this.videoEnabled; + + // + this.handleChangeDetection(); + } + + /** + * Handle Action Bar Calling Actions ... + * + * @param event + */ + async handleCallingActionFired(event: XActionBarActionFiredModel) { + // + console.log('Action Bar Action Fired: ', event); + + // + let isKnownAction = false; + + // + //#region Request Call ... + isKnownAction = event.action === defaultCallRequestActionBar.id; + if (isKnownAction) { + this.requestCall(); + } + //#endregion + + // + //#region End Call ... + isKnownAction = event.action === defaultCallEndActionBar.id; + if (isKnownAction) { + this.endCall(); + } + //#endregion + + // + //#region Mute ... + isKnownAction = event.action === defaultMuteActionBar.id; + if (isKnownAction) { + // + if (!this.muted) { + this.toggleMute(); + } + } + //#endregion + + // + //#region UnMute ... + isKnownAction = event.action === defaultUnMuteActionBar.id; + if (isKnownAction) { + // + if (this.muted) { + this.toggleMute(); + } + } + //#endregion + + // + //#region Fullscreen ... + isKnownAction = event.action === defaultFullscreenActionBar.id; + if (isKnownAction) { + // + if (!this.fullscreenMode) { + this.toggleFullscreen(); + } + } + //#endregion + + // + //#region UnFullscreen ... + isKnownAction = event.action === defaultUnfullscreenActionBar.id; + if (isKnownAction) { + // + if (this.fullscreenMode) { + this.toggleFullscreen(); + } + } + //#endregion + + // + //#region MicOn ... + isKnownAction = event.action === defaultMicOnActionBar.id; + if (isKnownAction) { + // + if (!this.micEnabled) { + this.toggleMic(); + } + } + //#endregion + + // + //#region MicOff ... + isKnownAction = event.action === defaultMicOffActionBar.id; + if (isKnownAction) { + // + if (this.micEnabled) { + this.toggleMic(); + } + } + //#endregion + + // + //#region VideoOn ... + isKnownAction = event.action === defaultVideoOnActionBar.id; + if (isKnownAction) { + // + if (!this.videoEnabled) { + this.toggleVideo(); + } + } + //#endregion + + // + //#region VideoOff ... + isKnownAction = event.action === defaultVideoOffActionBar.id; + if (isKnownAction) { + // + if (this.videoEnabled) { + this.toggleVideo(); + } + } + //#endregion + + // + //#region Switch To Front Camera ... + isKnownAction = event.action === defaultSwitchToFrontCameraActionBar.id; + if (isKnownAction) { + this.switchMediaDevice(XCameraFacing.User); + } + //#endregion + + // + //#region Switch To Back Camera ... + isKnownAction = event.action === defaultSwitchToBackCameraActionBar.id; + if (isKnownAction) { + this.switchMediaDevice(XCameraFacing.Environment); + } + //#endregion + + // + //#region Start Recording ... + isKnownAction = event.action === defaultStartRecordActionBar.id; + if (isKnownAction) { + // + if (!this.isRecording) { + this.toggleRecord(); + } + } + //#endregion + + // + //#region Stop Recording ... + isKnownAction = event.action === defaultStopRecordActionBar.id; + if (isKnownAction) { + // + if (this.isRecording) { + this.toggleRecord(); + } + } + //#endregion + + // + //#region Start Screen Share ... + isKnownAction = event.action === defaultStartScreenShareActionBar.id; + if (isKnownAction) { + // await this.startScreenShare(); + this.requestStartScreenshare(); + } + //#endregion + + // + //#region Stop Screen Share ... + isKnownAction = event.action === defaultStopScreenShareActionBar.id; + if (isKnownAction) { + // this.stopScreenShare(); + this.requestStopScreenshare(); + } + //#endregion + + // + this.handlePrepareCallingActions(); + } + + // + //#region Action Provider ... + @Input() + actionProvider: Subject; + readonly ProvidedActions = Object.assign(VVideoAction, {}); + private ACTION_PROVDER_SUBSCRIPTION: Subscription = undefined; + private unsubscribeActionProvider() { + // + if (isNullOrUndefined(this.ACTION_PROVDER_SUBSCRIPTION)) { + return; + } + + // + this.ACTION_PROVDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVDER_SUBSCRIPTION = undefined; + } + private subscribeActionProvider() { + // + this.unsubscribeActionProvider(); + if (isNullOrUndefined(this.actionProvider)) { + return; + } + + // + this.ACTION_PROVDER_SUBSCRIPTION = this.actionProvider.subscribe( + (action) => { + // + if (isNullOrUndefined(action) || !isVVideoAction(action.action)) { + return; + } + + // + // Handle Actions ... + + // + // Refresh ... + // Change Detection ... + if (action.action === this.ProvidedActions.Refresh) { + // + if (!isNullOrUndefined(action.payload) && isNumber(action.payload)) { + // + this.runTimedOut( + () => this.handleChangeDetection(), + toNumber(action.payload) + ); + } else { + this.handleChangeDetection(); + } + } + // + // Set State ... + else if (action.action === this.ProvidedActions.SetState) { + // + // Validate Payload ... + if (!isNullOrUndefined(action.payload)) { + this.setState(action.payload); + } + } + // + // Set Flags ... + else if (action.action === this.ProvidedActions.SetFlags) { + // + // Validate Payload ... + if (!isNullOrUndefined(action.payload)) { + this.setFlags(action.payload); + } + } + // + // Mute ... + else if (action.action === this.ProvidedActions.Mute) { + // + if (!this.muted) { + this.toggleMute(); + } + } + // + // UnMute ... + else if (action.action === this.ProvidedActions.UnMute) { + // + if (this.muted) { + this.toggleMute(); + } + } + // + // Play ... + else if (action.action === this.ProvidedActions.Play) { + // + if (!this.isPlaying) { + this.togglePlay(); + } + } + // + // Pause ... + else if (action.action === this.ProvidedActions.Pause) { + // + if (this.isPlaying) { + this.togglePlay(); + } + } + // + // Rewind ... + else if (action.action === this.ProvidedActions.Rewind) { + this.seekRelative(-1 * this.seekingStep); + } + // + // Forward ... + else if (action.action === this.ProvidedActions.Forward) { + this.seekRelative(1 * this.seekingStep); + } + // + // IncreaseVolume ... + else if (action.action === this.ProvidedActions.IncreaseVolume) { + this.setVolume(Math.min(1, this.volume + 0.05)); + } + // + // DecreaseVolume ... + else if (action.action === this.ProvidedActions.DecreaseVolume) { + this.setVolume(Math.max(0, this.volume - 0.05)); + } + // + // TogglePlayPause ... + else if (action.action === this.ProvidedActions.TogglePlayPause) { + this.togglePlay(); + } + // + // ToggleMuteUnmute ... + else if (action.action === this.ProvidedActions.ToggleMuteUnmute) { + this.toggleMute(); + } + // + // ToggleFullDefaultScreen ... + else if ( + action.action === this.ProvidedActions.ToggleFullDefaultScreen + ) { + this.toggleFullscreen(); + } + // + // Start Record ... + else if (action.action === this.ProvidedActions.StartRecord) { + this.startRecord(); + } + // + // Stop Record ... + else if (action.action === this.ProvidedActions.StopRecord) { + this.stopRecord(); + } + // + // Requests ... + // + // Flags ... + else if (action.action === this.ProvidedActions.RequestFlags) { + // + var flags = this.getFlags(); + this.sendAction(this.ProvidedActions.ResponseFlags, flags); + } + // + // State ... + else if (action.action === this.ProvidedActions.RequestState) { + // + var state = this.getState(); + this.sendAction(this.ProvidedActions.ResponseState, state); + } + // + // Properties ... + else if (action.action === this.ProvidedActions.RequestProperties) { + // + var props = this.getProperties(); + this.sendAction(this.ProvidedActions.ResponseProperties, props); + } + } + ); + } + private sendAction(action: VVideoAction, payload?: any) { + // + if (!isVVideoAction(action) || isNullOrUndefined(this.actionProvider)) { + return; + } + + // + this.actionProvider.next({ action, payload }); + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\v-video\v-video.configs.ts + +export enum VideoResourceIDs { + play = 'play', + mute = 'mute', + pause = 'pause', + unmute = 'unmute', + volume = 'volume', + rewind = 'rewind', + forward = 'forward', + fullscreen = 'fullscreen', + unfullscreen = 'unfullscreen', + change_volume = 'change_volume', + // + // Calling Actions ... + mic_on = 'mic_on', + mic_off = 'mic_off', + video_on = 'video_on', + call_end = 'call_end', + video_off = 'video_off', + stop_record = 'stop_record', + start_record = 'start_record', + call_request = 'call_request', + stop_share_screen = 'stop_share_screen', + start_share_screen = 'start_share_screen', + switch_to_back_camera = 'switch_to_back_camera', + switch_to_front_camera = 'switch_to_front_camera', +} + +// +//#region Single Resources ... +// +export const mute_fa = { + id: 'mute', + value: 'سکوت', +}; +export const unmute_fa = { + id: 'unmute', + value: 'غیرفعال کردن حالت سکوت', +}; +export const play_fa = { + id: 'play', + value: 'پخش', +}; +export const pause_fa = { + id: 'pause', + value: 'توقف', +}; +export const volume_fa = { + id: 'volume', + value: 'حجم صدا', +}; +export const rewind_fa = { + id: 'rewind', + value: 'رو به عقب', +}; +export const forward_fa = { + id: 'forward', + value: 'رو به جلو', +}; +export const fullscreen_fa = { + id: 'fullscreen', + value: 'نمایش تمام صفحه', +}; +export const unfullscreen_fa = { + id: 'unfullscreen', + value: 'نمایش پیش فرض', +}; +export const change_volume_fa = { + id: 'change_volume', + value: 'تغییر حجم صدا', +}; +// +// Call Actions ... +export const mic_on_fa = { + id: 'mic_on', + value: 'ارسال صدا', +}; +export const mic_off_fa = { + id: 'mic_off', + value: 'قطع ارسال صدا', +}; +export const video_on_fa = { + id: 'video_on', + value: 'ارسال تصویر', +}; +export const video_off_fa = { + id: 'video_off', + value: 'قطع ارسال تصویر', +}; +export const stop_record_fa = { + id: 'stop_record', + value: 'اتمام ضبط', +}; +export const start_record_fa = { + id: 'start_record', + value: 'ضبط', +}; +export const call_end_fa = { + id: 'call_end', + value: 'قطع تماس', +}; +export const call_request_fa = { + id: 'call_request', + value: 'برقراری تماس', +}; +export const stop_share_screen_fa = { + id: 'stop_share_screen', + value: 'اتمام اشتراک گذاری صفحه', +}; +export const start_share_screen_fa = { + id: 'start_share_screen', + value: 'اشتراک گذاری صفحه', +}; +export const switch_to_back_camera_fa = { + id: 'switch_to_back_camera', + value: 'فعالسازی دوربین محیط', +}; +export const switch_to_front_camera_fa = { + id: 'switch_to_front_camera', + value: 'فعالسازی دوربین مقابل', +}; + +// +export const mute_en = { + id: 'mute', + value: 'Mute', +}; +export const unmute_en = { + id: 'unmute', + value: 'Unmute', +}; +export const play_en = { + id: 'play', + value: 'Play', +}; +export const pause_en = { + id: 'pause', + value: 'Pause', +}; +export const volume_en = { + id: 'volume', + value: 'Volume', +}; +export const rewind_en = { + id: 'rewind', + value: 'Rewind', +}; +export const forward_en = { + id: 'forward', + value: 'Forward', +}; +export const fullscreen_en = { + id: 'fullscreen', + value: 'Fullscreen', +}; +export const unfullscreen_en = { + id: 'unfullscreen', + value: 'Default Size', +}; +export const change_volume_en = { + id: 'change_volume', + value: 'Change Volume', +}; +// +// Call Actions ... +export const mic_on_en = { + id: 'mic_on', + value: 'Mic On', +}; +export const mic_off_en = { + id: 'mic_off', + value: 'Mic Off', +}; +export const video_on_en = { + id: 'video_on', + value: 'Video On', +}; +export const video_off_en = { + id: 'video_off', + value: 'Video Off', +}; +export const stop_record_en = { + id: 'stop_record', + value: 'Stop Record', +}; +export const start_record_en = { + id: 'start_record', + value: 'Record', +}; +export const call_end_en = { + id: 'call_end', + value: 'Call End', +}; +export const call_request_en = { + id: 'call_request', + value: 'Call', +}; +export const stop_share_screen_en = { + id: 'stop_share_screen', + value: 'Stop Screen Share', +}; +export const start_share_screen_en = { + id: 'start_share_screen', + value: 'Start Screen Share', +}; +export const switch_to_back_camera_en = { + id: 'switch_to_back_camera', + value: 'Switch to Back Camera', +}; +export const switch_to_front_camera_en = { + id: 'switch_to_front_camera', + value: 'Switch to Front Camera', +}; +//#endregion + +// +export const VideoLocalizationResourceIDs = Object.assign(VideoResourceIDs, {}); + +// +export const VideoLocalizationResources = { + mute: { + fa: { ...mute_fa }, + en: { ...mute_en }, + }, + play: { + fa: { ...play_fa }, + en: { ...play_en }, + }, + pause: { + fa: { ...pause_fa }, + en: { ...pause_en }, + }, + unmute: { + fa: { ...unmute_fa }, + en: { ...unmute_en }, + }, + volume: { + fa: { ...volume_fa }, + en: { ...volume_en }, + }, + rewind: { + fa: { ...rewind_fa }, + en: { ...rewind_en }, + }, + forward: { + fa: { ...forward_fa }, + en: { ...forward_en }, + }, + fullscreen: { + fa: { ...fullscreen_fa }, + en: { ...fullscreen_en }, + }, + unfullscreen: { + fa: { ...unfullscreen_fa }, + en: { ...unfullscreen_en }, + }, + change_volume: { + fa: { ...change_volume_fa }, + en: { ...change_volume_en }, + }, + // + // Calling Actions ... + mic_on: { + fa: { ...mic_on_fa }, + en: { ...mic_on_en }, + }, + mic_off: { + fa: { ...mic_off_fa }, + en: { ...mic_off_en }, + }, + video_on: { + fa: { ...video_on_fa }, + en: { ...video_on_en }, + }, + video_off: { + fa: { ...video_off_fa }, + en: { ...video_off_en }, + }, + stop_record: { + fa: { ...stop_record_fa }, + en: { ...stop_record_en }, + }, + start_record: { + fa: { ...start_record_fa }, + en: { ...start_record_en }, + }, + call_end: { + fa: { ...call_end_fa }, + en: { ...call_end_en }, + }, + call_request: { + fa: { ...call_request_fa }, + en: { ...call_request_en }, + }, + stop_share_screen: { + fa: { ...stop_share_screen_fa }, + en: { ...stop_share_screen_en }, + }, + start_share_screen: { + fa: { ...start_share_screen_fa }, + en: { ...start_share_screen_en }, + }, + switch_to_back_camera: { + fa: { ...switch_to_back_camera_fa }, + en: { ...switch_to_back_camera_en }, + }, + switch_to_front_camera: { + fa: { ...switch_to_front_camera_fa }, + en: { ...switch_to_front_camera_en }, + }, +}; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\v-video\v-video.typings.ts + +import { + XSpinner, + XStandardType, + XOneOrManyType, + XColorIdentifier, + XSpinnerIdentifier, + XResourceIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { + XIconNames, + XIconIdentifier, + XBaseActionModel, +} from 'x-framework-components'; +import { Injectable } from '@angular/core'; +import { VideoResourceIDs } from './v-video.configs'; +import { BooleanInput } from '@angular/cdk/coercion'; +import { isValueInEnum } from '../tools/v-base.tools'; +import { SharedService } from 'src/app/shared/services/shared.service'; +import { XCameraFacing } from 'src/app/shared/services/x-media-devices.service'; + +/** + * Default Audio Poster ... + */ +export const defaultAudioPoster = '/assets/image/audio-poster-3.gif'; + +/** + * Volume Changer Form's Model ... + */ +export interface VVideoVolumeSelectModel { + volume: number; +} + +// +//#region Action Provider ... +export enum VVideoAction { + // + // Actions ... + /** + * Play Video if Provided ... + */ + Play = 'Play', + + /** + * Pause Video if Played ... + */ + Pause = 'Pause', + + /** + * Rewind Video ... + */ + Rewind = 'Rewind', + + /** + * Forward Video ... + */ + Forward = 'Forward', + + /** + * Refresh Component ... + */ + Refresh = 'refresh', + + /** + * Mute ... + */ + Mute = 'Mute', + + /** + * UnMute ... + */ + UnMute = 'UnMute', + + /** + * Increase Volume ... + */ + IncreaseVolume = 'IncreaseVolume', + + /** + * Decrease Volume ... + */ + DecreaseVolume = 'DecreaseVolume', + + /** + * Toggle Play / Pause Video Action ... + */ + TogglePlayPause = 'TogglePlayPause', + + /** + * Toggle Mute / UnMute Action ... + */ + ToggleMuteUnmute = 'ToggleMuteUnmute', + + /** + * Request Switch Device Camera by Providing Facing Mode ... + * XCameraFacing: + * User: Request Default Media Device (Front) ... + * Screen: Request Screen Sharing Media Device ... + * Environment: Request Back Camera Device (if Provided) ... + */ + SwitchMediaDevices = 'SwitchMediaDevices', + + /** + * Toggle Fullscreen / Defaultscreen ... + */ + ToggleFullDefaultScreen = 'ToggleFullDefaultScreen', + + /** + * Request Call ... + */ + RequestCall = 'RequestCall', + + /** + * End Call ... + */ + EndCall = 'EndCall', + + /** + * Start Media Recording ... + */ + StartRecord = 'StartRecord', + + /** + * Stop Media Recording ... + */ + StopRecord = 'StopRecord', + + /** + * Request Switch Camera ... + */ + RequestSwitchCamera = 'RequestSwitchCamera', + + /** + * Request Start Screen Share ... + */ + RequestStartScreenShare = 'RequestStartScreenShare', + + /** + * Request Stop Screen Share ... + */ + RequestStopScreenShare = 'RequestStopScreenShare', + + /** + * Set Component State by Providing IVVideoState ... + */ + SetState = 'SetState', + + /** + * Set Component Flags by Providing IVVideoFlag ... + */ + SetFlags = 'SetFlags', + + // + RequestFlags = 'RequestFlags', + RequestState = 'RequestState', + RequestProperties = 'RequestProperties', + + // + ResponseFlags = 'ResponseFlags', + ResponseState = 'ResponseState', + ResponseProperties = 'ResponseProperties', + + // + // Events ... + Ended = 'Ended', + Muted = 'Muted', + Inited = 'Inited', + Played = 'Played', + Paused = 'Paused', + Progressed = 'Progressed', + TimeUpdated = 'TimeUpdated', + Fullscreened = 'Fullscreened', + VolumeChanged = 'VolumeChanged', + PropertyChanged = 'PropertyChanged', + Defaultscreened = 'Defaultscreened', + MicStateChanged = 'MicStateChanged', + VideoStateChanged = 'VideoStateChanged', + LoadingStateChanged = 'LoadingStateChanged', + RecordingStateChanged = 'RecordingStateChanged', + ScreenShareStateChanged = 'ScreenShareStateChanged', + SpeechRecognitionStateChanged = 'SpeechRecognitionStateChanged', +} + +export type VVideoActionIdentifier = VVideoAction | 'refresh' | string; + +export function isVVideoAction(value: string) { + return isValueInEnum(value, Object.assign(VVideoAction, {})); +} + +export interface VVideoActionModel extends XBaseActionModel {} +//#endregion + +// +//#region Medi Recorder ... +export type MediaRecorderType = { + new (stream: MediaStream, options?: MediaRecorderOptions): any; + isTypeSupported?(type: string): boolean; +}; + +export interface MediaRecorderOptions { + mimeType?: string; + audioBitsPerSecond?: number; + videoBitsPerSecond?: number; + bitsPerSecond?: number; +} + +export interface BlobEvent extends Event { + data: Blob; +} +//#endregion + +// +//#region VVideo Container ... + +export interface IVVideoFlags { + muted?: boolean; + isInCall?: boolean; + isPlaying?: boolean; + micEnabled?: boolean; + videoEnabled?: boolean; + hasBackCamera?: boolean; + hasFrontCamera?: boolean; + fullscreenMode?: boolean; + isScreenShared?: boolean; + canSwitchCameras?: boolean; + currentUsingMediaDevice?: XCameraFacing; +} + +/** + * All Available Controls Properties Based on VVideo Component ... + */ +export interface IVVideoControlsContainer { + // + //#region Play / Pause Control ... + /** + * Show Play/Pause Control on Video Actions ... + */ + showPlayPauseControl: boolean; + + /** + * Play Control Resource ID ... + */ + playControlTitle: XResourceIdentifier; + + /** + * Play Control Icon Name ... + */ + playControlIcon: XIconIdentifier; + + /** + * Pause Control Resource ID ... + */ + pauseControlTitle: XResourceIdentifier; + + /** + * Pause Control Icon Name ... + */ + pauseControlIcon: XIconIdentifier; + //#endregion + + // + //#region Rewind Control ... + /** + * Show Video Rewind Control ... + */ + showRewindControl: boolean; + + /** + * Rewind Control Resource ID ... + */ + rewindControlTitle: XResourceIdentifier; + + /** + * Rewind Control Icon Name ... + */ + rewindControlIcon: XIconIdentifier; + //#endregion + + // + //#region Forward Control ... + /** + * Show Video Forward Control ... + */ + showForwardControl: boolean; + + /** + * Forward Control Resource ID ... + */ + forwardControlTitle: XResourceIdentifier; + + /** + * Forward Control Icon Name ... + */ + forwardControlIcon: XIconIdentifier; + //#endregion + + // + //#region Mute / UnMute Control ... + /** + * Show Volume Mute/Unmute Toggler Control ... + */ + showVolumeTogglerControl: BooleanInput; + + /** + * Unmute Control Resource ID ... + */ + unmuteControlTitle: XResourceIdentifier; + + /** + * Unmute Control Icon Name ... + */ + unmuteControlIcon: XIconIdentifier; + + /** + * Mute Control Resource ID ... + */ + muteControlTitle: XResourceIdentifier; + + /** + * Mute Control Icon Name ... + */ + muteControlIcon: XIconIdentifier; + //#endregion + + // + //#region Volume Change Control ... + /** + * Show Volume Changer Control ... + */ + showVolumeChangerControl: boolean; + + /** + * Volume Changer Control Resource ID ... + */ + volumeChangerControlTitle: XResourceIdentifier; + //#endregion + + // + //#region Fullscreen Toggler Control ... + /** + * Show Fullscreen Toggler Control ... + */ + showFullscreenTogglerControl: boolean; + + /** + * Fullscreen Control Resource ID ... + */ + fullscreenControlTitle: XResourceIdentifier; + + /** + * Fullscreen Control Icon Name ... + */ + fullscreenControlIcon: XIconIdentifier; + + /** + * Unfullscreen Control Resource ID ... + */ + unfullscreenControlTitle: XResourceIdentifier; + + /** + * Unfullscreen Control Icon Name ... + */ + unfullscreenControlIcon: XIconIdentifier; + //#endregion + + // + //#region Playback Rate(s) Control ... + /** + * Show Current Playback Rate ... + */ + showCurrentPlaybackRate: boolean; + + /** + * Show Playback Rates Control ... + */ + showPlaybackRatesControl: Boolean; + //#endregion + + // + //#region Camera Switch Control ... + /** + * Show Camera Switch if Device Supported ... + */ + showCameraSwitchControl: boolean; + + /** + * Front Camera Control Resource ID ... + */ + frontCameraTitle: XResourceIdentifier; + + /** + * Front Camera Control Icon Name ... + */ + frontCameraIcon: XIconIdentifier; + + /** + * Back Camera Control Resource ID ... + */ + backCameraTitle: XResourceIdentifier; + + /** + * Back Camera Control Icon Name ... + */ + backCameraIcon: XIconIdentifier; + //#endregion + + // + //#region Call Control ... + /** + * Show Call Control ... + * Request/End ... + */ + showCallControl: boolean; + + /** + * Call Request Control Resource ID ... + */ + callRequestControlTitle: XResourceIdentifier; + + /** + * Call Request Control Icon Name ... + */ + callRequestControlIcon: XIconIdentifier; + + /** + * Call End Control Resource ID ... + */ + callEndControlTitle: XResourceIdentifier; + + /** + * Cal End Control Icon Name ... + */ + callEndControlIcon: XIconIdentifier; + //#endregion + + // + //#region Video Call Control ... + /** + * Show Video Call Control ... + */ + showVideoCallControl: boolean; + + /** + * Enable Video Control Resource ID ... + */ + enableVideoControlTitle: XResourceIdentifier; + + /** + * Enable Video Control Icon Name ... + */ + enableVideoControlIcon: XIconIdentifier; + + /** + * Disable Video Control Resource ID ... + */ + disableVideoControlTitle: XResourceIdentifier; + + /** + * Disable Video Control Icon ... + */ + disableVideoControlIcon: XIconIdentifier; + //#endregion + + // + //#region Microphone Control ... + showMicorphoneControl: boolean; + + /** + * Enable Microphone Control Resource ID ... + */ + enableMicrophoneControlTitle: XResourceIdentifier; + + /** + * Enable Microphone Control Icon Name ... + */ + enableMicrophoneControlIcon: XIconIdentifier; + + /** + * Disable Microphone Control Resource ID ... + */ + disableMicrophoneControlTitle: XResourceIdentifier; + + /** + * Disable Microphone Control Icon ... + */ + disableMicrophoneControlIcon: XIconIdentifier; + //#endregion + + // + //#region ScreenShare Control ... + /** + * Show Share Screen Control ... + */ + showShareScreenControl: boolean; + + /** + * Share Screen Start Control Resource ID ... + */ + startShareScreenControlTitle: XResourceIdentifier; + + /** + * Share Screen Start Control Icon Name ... + */ + startShareScreenControlIcon: XIconIdentifier; + + /** + * Share Screen Stop Control Resource ID ... + */ + stopShareScreenControlTitle: XResourceIdentifier; + + /** + * Share Screen Stop Icon Name ... + */ + stopShareScreenControlIcon: XIconIdentifier; + //#endregion + + // + //#region Record Control ... + /** + * Show Record Control ... + */ + showRecordControl: boolean; + + /** + * Start Record Control Resource ID ... + */ + startRecordControlTitle: XResourceIdentifier; + + /** + * Start Record Control Icon Name ... + */ + startRecordControlIcon: XIconIdentifier; + + /** + * Stop Record Control Resource ID ... + */ + stopRecordControlTitle: XResourceIdentifier; + + /** + * Stop Record Control Icon Name ... + */ + stopRecordControlIcon: XIconIdentifier; + + /** + * Recording Timer Color ... + */ + recordingTimerColor: XColorIdentifier; + //#endregion +} + +/** + * Abilities of IVVideo Component ... + */ +export interface IVVideoAbilitiesContainer { + // + //#region Speech Recognition ... + /** + * Enable Speech Recognition if Supported ... + */ + enableSpeechRecognition: boolean; + + /** + * Speech Recognition Language ... + */ + speechRecognitionLanguage: string; + + /** + * Speech Recognition Transcripted Text Colors ... + */ + speechRecognitionColor: XColorIdentifier; + //#endregion + + // + //#region Picture in Picture ... + /** + * Enable Picture in Picture Mode ... + */ + enablePictureInPicture: boolean; + + /** + * Picture In Picture Element Source Url ... + */ + pictureInPictureSrc: string; + + /** + * Picutre in Picture Element Source Strem ... + */ + pictureInPictureStream: MediaStream; + //#endregion +} + +/** + * Video Component Card Properties ... + */ +export interface IVVideoCardContainer { + /** + * Specified Component's Card is In Page or not ... + */ + isInPage: XStandardType; + + /** + * Specified Wrap Component by Card or not ... + */ + wrapWithCard: XStandardType; + + /** + * Specified Card's Component CSS Classes ... + */ + cardCssClass: XStandardType>; + + /** + * Specified Component's Card Color ... + */ + cardColor: XStandardType; + + /** + * Specified Color Applies to Component's Card Foreground or not ... + */ + cardForegroundColor: XStandardType; + + /** + * Specified Component's Cards Title Resource ... + */ + cardTitle: XStandardType; + + /** + * Specified Component's Cards SubTitle Resource ... + */ + cardSubTitle: XStandardType; + + /** + * Specified Component's Card Show Footer or not ... + */ + cardShowFooter: XStandardType; + + /** + * Specified Component's Card Show Actions or not ... + */ + cardShowActions: XStandardType; +} + +/** + * Specified Type of VVideo Component State ... + */ +interface VVideoStateType + extends IVVideoControlsContainer, + IVVideoAbilitiesContainer {} +export interface IVVideoState extends Partial {} + +/** + * Provides all Available Properties ... + */ +export interface IVVideoComponentContainer + extends IVVideoCardContainer, + IVVideoControlsContainer, + IVVideoAbilitiesContainer { + // + //#region Component Styles ... + /** + * Providing CSS Classes ... + */ + cssClass: Array; + + /** + * Auto Play Media ... + */ + autoPlay: boolean; + + /** + * Continues Play Media ... + */ + continuesPlay: boolean; + + /** + * Specified Loading Spinner Type ... + */ + loadingSpinner: XSpinnerIdentifier; + + /** + * Specified Loading Spinner Color ... + */ + loadingSpinnerColor: XColorIdentifier; + + /** + * Show Video Title ... + */ + showTitle: boolean; + //#endregion + + // + //#region Video ... + /** + * Show Play Pause Toggle Button on Center of Screen ... + */ + showPlayPauseToggler: boolean; + + /** + * Show Video Progress Bar + * including Seeking Bar and Video Time ... + */ + showProgressBar: boolean; + + /** + * Show Vide Seeking Bar ... + */ + showSeekingBar: boolean; + + /** + * Steps to Move Forard/Rewind ... + */ + seekingStep: number; + + /** + * Show Vide Time ... + */ + showVideoTime: boolean; + + /** + * Show Video Actions ... + */ + showActions: boolean; + + /** + * Enable Handling Keyboard Events ... + */ + enableKeyboardEvents: boolean; + + /** + * Show Calling Actions ... + */ + showCallingActions: boolean; + //#endregion + + // +} + +/** + * All Default Property Values Provided Here ... + */ +@Injectable({ + providedIn: 'root', +}) +export class VVideoComponentContainer + implements Readonly +{ + // + //#region Constructor ... + constructor(private sharedService: SharedService) {} + //#endregion + + // + //#region Card ... + /** + * Specified Component's Card is In Page or not ... + */ + readonly isInPage: XStandardType = false; + + /** + * Specified Wrap Component by Card or not ... + */ + readonly wrapWithCard: XStandardType = false; + + /** + * Specified Card's Component CSS Classes ... + */ + readonly cardCssClass: XStandardType> = []; + + /** + * Specified Component's Card Color ... + */ + readonly cardColor: XStandardType = + XColorWithBrightness.Dark; + + /** + * Specified Color Applies to Component's Card Foreground or not ... + */ + readonly cardForegroundColor: XStandardType = false; + + /** + * Specified Component's Cards Title Resource ... + */ + readonly cardTitle: XStandardType = ''; + + /** + * Specified Component's Cards SubTitle Resource ... + */ + readonly cardSubTitle: XStandardType = ''; + + /** + * Specified Component's Card Show Footer or not ... + */ + readonly cardShowFooter: XStandardType = false; + + /** + * Specified Component's Card Show Actions or not ... + */ + readonly cardShowActions: XStandardType = false; + //#endregion + + // + //#region Component Styles ... + /** + * Providing CSS Classes ... + */ + readonly cssClass: Array = []; + + /** + * Auto Play Media ... + */ + readonly autoPlay: boolean = false; + + /** + * Continues Play Media ... + */ + readonly continuesPlay: boolean = false; + + /** + * Specified Loading Spinner Type ... + */ + readonly loadingSpinner: XSpinnerIdentifier = XSpinner.Circles; + + /** + * Specified Loading Spinner Color ... + */ + readonly loadingSpinnerColor: XColorIdentifier = XColorWithBrightness.Warning; + + /** + * Show Video Title ... + */ + readonly showTitle: boolean = true; + //#endregion + + // + //#region Video ... + /** + * Show Play Pause Toggle Button on Center of Screen ... + */ + readonly showPlayPauseToggler: boolean = false; + + /** + * Show Video Progress Bar + * including Seeking Bar and Video Time ... + */ + readonly showProgressBar: boolean = true; + + /** + * Show Vide Seeking Bar ... + */ + readonly showSeekingBar: boolean = true; + + /** + * Steps to Move Forard/Rewind ... + */ + readonly seekingStep: number = 10; + + /** + * Show Vide Time ... + */ + readonly showVideoTime: boolean = true; + + /** + * Show Video Actions ... + */ + readonly showActions: boolean = true; + + /** + * Enable Handling Keyboard Events ... + */ + readonly enableKeyboardEvents: boolean = false; + + /** + * Show Calling Actions ... + */ + readonly showCallingActions: boolean = false; + //#endregion + + // + //#region Controls ... + // + //#region Play / Pause Control ... + /** + * Show Play/Pause Control on Video Actions ... + */ + readonly showPlayPauseControl: boolean = true; + + /** + * Play Control Resource ID ... + */ + readonly playControlTitle: XResourceIdentifier = VideoResourceIDs.play; + + /** + * Play Control Icon Name ... + */ + readonly playControlIcon: XIconIdentifier = XIconNames.play; + + /** + * Pause Control Resource ID ... + */ + readonly pauseControlTitle: XResourceIdentifier = VideoResourceIDs.pause; + + /** + * Pause Control Icon Name ... + */ + readonly pauseControlIcon: XIconIdentifier = XIconNames.pause; + //#endregion + + // + //#region Rewind Control ... + /** + * Show Video Rewind Control ... + */ + readonly showRewindControl: boolean = true; + + /** + * Rewind Control Resource ID ... + */ + readonly rewindControlTitle: XResourceIdentifier = VideoResourceIDs.rewind; + + /** + * Rewind Control Icon Name ... + */ + readonly rewindControlIcon: XIconIdentifier = XIconNames.fast_rewind; + //#endregion + + // + //#region Forward Control ... + /** + * Show Video Forward Control ... + */ + readonly showForwardControl: boolean = true; + + /** + * Forward Control Resource ID ... + */ + readonly forwardControlTitle: XResourceIdentifier = VideoResourceIDs.forward; + + /** + * Forward Control Icon Name ... + */ + readonly forwardControlIcon: XIconIdentifier = XIconNames.fast_forward; + //#endregion + + // + //#region Mute / UnMute Control ... + /** + * Show Volume Mute/Unmute Toggler Control ... + */ + readonly showVolumeTogglerControl: boolean = true; + + /** + * Unmute Control Resource ID ... + */ + readonly unmuteControlTitle: XResourceIdentifier = VideoResourceIDs.unmute; + + /** + * Unmute Control Icon Name ... + */ + readonly unmuteControlIcon: XIconIdentifier = XIconNames.volume_high; + + /** + * Mute Control Resource ID ... + */ + readonly muteControlTitle: XResourceIdentifier = VideoResourceIDs.mute; + + /** + * Mute Control Icon Name ... + */ + readonly muteControlIcon: XIconIdentifier = XIconNames.volume_mute; + //#endregion + + // + //#region Volume Change Control ... + /** + * Show Volume Changer Control ... + */ + readonly showVolumeChangerControl: boolean = true; + + /** + * Volume Changer Control Resource ID ... + */ + readonly volumeChangerControlTitle: XResourceIdentifier = + VideoResourceIDs.change_volume; + //#endregion + + // + //#region Fullscreen Toggler Control ... + /** + * Show Fullscreen Toggler Control ... + */ + readonly showFullscreenTogglerControl: boolean = true; + + /** + * Fullscreen Control Resource ID ... + */ + readonly fullscreenControlTitle: XResourceIdentifier = + VideoResourceIDs.fullscreen; + + /** + * Fullscreen Control Icon Name ... + */ + readonly fullscreenControlIcon: XIconIdentifier = XIconNames.fullscreen; + + /** + * Unfullscreen Control Resource ID ... + */ + readonly unfullscreenControlTitle: XResourceIdentifier = + VideoResourceIDs.unfullscreen; + + /** + * Unfullscreen Control Icon Name ... + */ + readonly unfullscreenControlIcon: XIconIdentifier = + XIconNames.close_fullscreen; + //#endregion + + // + //#region Playback Rate(s) Control ... + /** + * Show Current Playback Rate ... + */ + readonly showCurrentPlaybackRate: boolean = true; + + /** + * Show Playback Rates Control ... + */ + readonly showPlaybackRatesControl: boolean = true; + //#endregion + + // + //#region Camera Switch Control ... + /** + * Show Camera Switch if Device Supported ... + */ + readonly showCameraSwitchControl: boolean = true; + + /** + * Front Camera Control Resource ID ... + */ + readonly frontCameraTitle: XResourceIdentifier = + VideoResourceIDs.switch_to_front_camera; + + /** + * Front Camera Control Icon Name ... + */ + readonly frontCameraIcon: XIconIdentifier = XIconNames.video_camera_front; + + /** + * Back Camera Control Resource ID ... + */ + readonly backCameraTitle: XResourceIdentifier = + VideoResourceIDs.switch_to_back_camera; + + /** + * Back Camera Control Icon Name ... + */ + readonly backCameraIcon: XIconIdentifier = XIconNames.video_camera_back; + //#endregion + + // + //#region Call Control ... + /** + * Show Call Control ... + * Request/End ... + */ + readonly showCallControl: boolean = true; + + /** + * Call Request Control Resource ID ... + */ + readonly callRequestControlTitle: XResourceIdentifier = + VideoResourceIDs.call_request; + + /** + * Call Request Control Icon Name ... + */ + readonly callRequestControlIcon: XIconIdentifier = XIconNames.phone_enabled; + + /** + * Call End Control Resource ID ... + */ + readonly callEndControlTitle: XResourceIdentifier = VideoResourceIDs.call_end; + + /** + * Cal End Control Icon Name ... + */ + readonly callEndControlIcon: XIconIdentifier = XIconNames.phone_disabled; + //#endregion + + // + //#region Video Call Control ... + /** + * Show Video Call Control ... + */ + readonly showVideoCallControl: boolean = true; + + /** + * Enable Video Control Resource ID ... + */ + readonly enableVideoControlTitle: XResourceIdentifier = + VideoResourceIDs.video_on; + + /** + * Enable Video Control Icon Name ... + */ + readonly enableVideoControlIcon: XIconIdentifier = XIconNames.videocam; + + /** + * Disable Video Control Resource ID ... + */ + readonly disableVideoControlTitle: XResourceIdentifier = + VideoResourceIDs.video_off; + + /** + * Disable Video Control Icon ... + */ + readonly disableVideoControlIcon: XIconIdentifier = + XIconNames.video_camera_front_off; + //#endregion + + // + //#region Microphone Control ... + readonly showMicorphoneControl: boolean = true; + + /** + * Enable Microphone Control Resource ID ... + */ + readonly enableMicrophoneControlTitle: XResourceIdentifier = + VideoResourceIDs.mic_on; + + /** + * Enable Microphone Control Icon Name ... + */ + readonly enableMicrophoneControlIcon: XIconIdentifier = XIconNames.mic; + + /** + * Disable Microphone Control Resource ID ... + */ + readonly disableMicrophoneControlTitle: XResourceIdentifier = + VideoResourceIDs.mic_off; + + /** + * Disable Microphone Control Icon ... + */ + readonly disableMicrophoneControlIcon: XIconIdentifier = XIconNames.mic_off; + //#endregion + + // + //#region ScreenShare Control ... + /** + * Show Share Screen Control ... + */ + readonly showShareScreenControl: boolean = true; + + /** + * Share Screen Start Control Resource ID ... + */ + readonly startShareScreenControlTitle: XResourceIdentifier = + VideoResourceIDs.start_share_screen; + + /** + * Share Screen Start Control Icon Name ... + */ + readonly startShareScreenControlIcon: XIconIdentifier = + XIconNames.screen_share; + + /** + * Share Screen Stop Control Resource ID ... + */ + readonly stopShareScreenControlTitle: XResourceIdentifier = + VideoResourceIDs.stop_share_screen; + + /** + * Share Screen Stop Icon Name ... + */ + readonly stopShareScreenControlIcon: XIconIdentifier = + XIconNames.stop_screen_share; + //#endregion + + // + //#region Record Control ... + /** + * Show Record Control ... + */ + readonly showRecordControl: boolean = false; + + /** + * Start Record Control Resource ID ... + */ + readonly startRecordControlTitle: XResourceIdentifier = + VideoResourceIDs.start_record; + + /** + * Start Record Control Icon Name ... + */ + readonly startRecordControlIcon: XIconIdentifier = XIconNames.screen_record; + + /** + * Stop Record Control Resource ID ... + */ + readonly stopRecordControlTitle: XResourceIdentifier = + VideoResourceIDs.stop_record; + + /** + * Stop Record Control Icon Name ... + */ + readonly stopRecordControlIcon: XIconIdentifier = XIconNames.stop; + + /** + * Recording Timer Color ... + */ + recordingTimerColor: XColorIdentifier = XColorWithBrightness.Warning; + //#endregion + //#endregion + + // + //#region Abilities ... + // + //#region Speech Recognition ... + /** + * Enable Speech Recognition if Supported ... + */ + readonly enableSpeechRecognition: boolean = false; + + /** + * Speech Recognition Language ... + */ + readonly speechRecognitionLanguage: string = + this.sharedService.managerService.currentLocale; + + /** + * Speech Recognition Transcripted Text Colors ... + */ + readonly speechRecognitionColor: XColorIdentifier = + XColorWithBrightness.Warning; + //#endregion + + // + //#region Picture in Picture ... + /** + * Enable Picture in Picture Mode ... + */ + readonly enablePictureInPicture: boolean = false; + + /** + * Picture In Picture Element Source Url ... + */ + readonly pictureInPictureSrc: string = ''; + + /** + * Picutre in Picture Element Source Strem ... + */ + readonly pictureInPictureStream: MediaStream = undefined; + //#endregion + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\webinar\v-webinar.configs.ts + +import { XIconNames } from 'x-framework-components'; +import { isValueInEnum } from '../tools/v-base.tools'; +import { XSimplePage } from '../typings/simple.page.typings'; +import { getXException, XException, XExceptionIDs } from 'x-framework-core'; + +// +//#region Localization Configuration ... +export enum WebinarResourceIDs { + webinar = 'webinar', + webinars = 'webinars', + close_webinar = 'close_webinar', + webinar_owner = 'webinar_owner', + webinar_joiner = 'webinar_joiner', + new_webinar_created = 'new_webinar_created', + webinars_page_title = 'webinars_page_title', + visitor_joined_message = 'visitor_joined_message', + visitor_leaved_message = 'visitor_leaved_message', + webinar_closed_message = 'webinar_closed_message', + webinars_page_description = 'webinars_page_description', + webinars_explorer_page_title = 'webinars_explorer_page_title', + webinars_management_page_title = 'webinars_management_page_title', + webinars_explorer_page_description = 'webinars_explorer_page_description', + webinars_management_page_description = 'webinars_management_page_description', +} + +// +//#region Single Resources ... +// +//#region webinar ... +export const webinar_fa = { + id: 'webinar', + value: 'وبینار', +}; + +export const webinar_en = { + id: 'webinar', + value: 'Webinar', +}; +//#endregion + +// +//#region webinars ... +export const webinars_fa = { + id: 'webinars', + value: 'وبینارها', +}; + +export const webinars_en = { + id: 'webinars', + value: 'Webinars', +}; +//#endregion + +// +//#region close_webinar ... +export const close_webinar_fa = { + id: 'close_webinar', + value: 'بستن وبینار', +}; + +export const close_webinar_en = { + id: 'close_webinar', + value: 'Close Webinar', +}; +//#endregion + +// +//#region webinar_owner +export const webinar_owner_fa = { + id: 'webinar_owner', + value: 'برگزار کننده', +}; + +export const webinar_owner_en = { + id: 'webinar_owner', + value: 'Presenter', +}; +//#endregion + +// +//#region webinar_joiner +export const webinar_joiner_fa = { + id: 'webinar_joiner', + value: 'مشاهده کننده', +}; + +export const webinar_joiner_en = { + id: 'webinar_joiner', + value: 'Viewer', +}; +//#endregion + +// +//#region new_webinar_created ... +export const new_webinar_created_fa = { + id: 'new_webinar_created', + value: 'وبینار جدیدی ایجاد شده است ...', +}; + +export const new_webinar_created_en = { + id: 'new_webinar_created', + value: 'new Webinar Created ...', +}; +//#endregion + +// +// #region webinars_page_title ... +export const webinars_page_title_fa = { + id: 'webinars_page_title', + value: 'وبینار', +}; + +export const webinars_page_title_en = { + id: 'webinars_page_title', + value: 'Webinars', +}; +//#endregion + +// +//#region visitor_joined_message ... +export const visitor_joined_message_fa = { + id: 'visitor_joined_message', + value: '${TMP1}& به وبینار پیوست', +}; + +export const visitor_joined_message_en = { + id: 'visitor_joined_message', + value: '${TMP1}& Joined to Webinar', +}; +//#endregion + +// +//#region visitor_leaved_message ... +export const visitor_leaved_message_fa = { + id: 'visitor_leaved_message', + value: '${TMP1}& وبینار را ترک کرد', +}; + +export const visitor_leaved_message_en = { + id: 'visitor_leaved_message', + value: '${TMP1}& Leaved Webina', +}; +//#endregion + +// +//#region webinar_closed_message ... +export const webinar_closed_message_fa = { + id: 'webinar_closed_message', + value: 'وبینار بسته شده است ...', +}; + +export const webinar_closed_message_en = { + id: 'webinar_closed_message', + value: 'Webinar is Closed ...', +}; +//#endregion + +// +//#region webinars_page_description ... +export const webinars_page_description_fa = { + id: 'webinars_page_description', + value: 'ابزارهای مدیریت وبینارها', +}; + +export const webinars_page_description_en = { + id: 'webinars_page_description', + value: 'Tools for Webinars ', +}; +//#endregion + +// +//#region webinars_management_page_title ... +export const webinars_management_page_title_fa = { + id: 'webinars_management_page_title', + value: 'مدیریت وبینارها', +}; + +export const webinars_management_page_title_en = { + id: 'webinars_management_page_title', + value: 'Webinars Management', +}; +//#endregion + +// +//#region webinars_management_page_description +export const webinars_management_page_description_fa = { + id: 'webinars_management_page_description', + value: 'مدیریت وبینارها', +}; + +export const webinars_management_page_description_en = { + id: 'webinars_management_page_description', + value: 'Webinars Management', +}; +//#endregion + +// +//#region webinars_explorer_page_title ... +export const webinars_explorer_page_title_fa = { + id: 'webinars_explorer_page_title', + value: 'وبینارها', +}; + +export const webinars_explorer_page_title_en = { + id: 'webinars_explorer_page_title', + value: 'Webinars', +}; +//#endregion + +// +//#region webinars_explorer_page_description ... +export const webinars_explorer_page_description_fa = { + id: 'webinars_explorer_page_description', + value: 'مرورگر وبینار', +}; + +export const webinars_explorer_page_description_en = { + id: 'webinars_explorer_page_description', + value: 'Webinar Explorer', +}; +//#endregion +//#endregion + +// +export const WebinarLocalizationResourceIDs = Object.assign(WebinarResourceIDs, {}); + +// +export const WebinarLocalizationResources = { + // + webinar: { + fa: { ...webinar_fa }, + en: { ...webinar_en }, + }, + webinars: { + fa: { ...webinars_fa }, + en: { ...webinars_en }, + }, + close_webinar: { + fa: { ...close_webinar_fa }, + en: { ...close_webinar_en }, + }, + webinar_owner: { + fa: { ...webinar_owner_fa }, + en: { ...webinar_owner_en }, + }, + webinar_joiner: { + fa: { ...webinar_joiner_fa }, + en: { ...webinar_joiner_en }, + }, + new_webinar_created: { + fa: { ...new_webinar_created_fa }, + en: { ...new_webinar_created_en }, + }, + webinars_page_title: { + fa: { ...webinars_page_title_fa }, + en: { ...webinars_page_title_en }, + }, + visitor_joined_message: { + fa: { ...visitor_joined_message_fa }, + en: { ...visitor_joined_message_en }, + }, + visitor_leaved_message: { + fa: { ...visitor_leaved_message_fa }, + en: { ...visitor_leaved_message_en }, + }, + webinar_closed_message: { + fa: { ...webinar_closed_message_fa }, + en: { ...webinar_closed_message_en }, + }, + webinars_page_description: { + fa: { ...webinars_page_description_fa }, + en: { ...webinars_page_description_en }, + }, + // + webinars_management_page_title: { + fa: { ...webinars_management_page_title_fa }, + en: { ...webinars_management_page_title_en }, + }, + webinars_management_page_description: { + fa: { ...webinars_management_page_description_fa }, + en: { ...webinars_management_page_description_en }, + }, + webinars_explorer_page_title: { + fa: { ...webinars_explorer_page_title_fa }, + en: { ...webinars_explorer_page_title_en }, + }, + webinars_explorer_page_description: { + fa: { ...webinars_explorer_page_description_fa }, + en: { ...webinars_explorer_page_description_en }, + }, +}; +//#endregion + +// +//#region Page Configuration ... +// +export enum WebinarRoutes { + Default = 'webinars', + WebinarsExplorer = 'webinars-explorer', + WebinarsManagement = 'webinars-management', +} + +// +export const Webinars: XSimplePage = { + id: `${WebinarRoutes.Default}`, + name: `${WebinarRoutes.Default}`, + title: WebinarResourceIDs.webinars_page_title, + description: WebinarResourceIDs.webinars_page_description, + baseRoute: `${WebinarRoutes.Default}`, + route: ['/', `${WebinarRoutes.Default}`], + icon: XIconNames.collapse, + childs: [ + // + //#region Webinars Management ... + { + id: `${WebinarRoutes.WebinarsManagement}`, + name: `${WebinarRoutes.WebinarsManagement}`, + title: WebinarResourceIDs.webinars_management_page_title, + description: WebinarResourceIDs.webinars_management_page_description, + baseRoute: `${WebinarRoutes.WebinarsManagement}`, + route: [ + '/', + `${WebinarRoutes.Default}`, + `${WebinarRoutes.WebinarsManagement}`, + ], + icon: XIconNames.live_tv, + }, + //#endregion + + // + //#region Webinars Explorer ... + { + id: `${WebinarRoutes.WebinarsExplorer}`, + name: `${WebinarRoutes.WebinarsExplorer}`, + title: WebinarResourceIDs.webinars_explorer_page_title, + description: WebinarResourceIDs.webinars_explorer_page_description, + baseRoute: `${WebinarRoutes.WebinarsExplorer}`, + route: [ + '/', + `${WebinarRoutes.Default}`, + `${WebinarRoutes.WebinarsExplorer}`, + ], + icon: XIconNames.live_tv, + }, + //#endregion + ], +}; +//#endregion + +// +//#region Exceptions ... +export enum XWebinarExceptionIDs { + CallEndError = 'CallEndError', + OpenWebinarError = 'OpenWebinarError', + JoinWebinarError = 'JoinWebinarError', + LocalStreamError = 'LocalStreamError', + UnKnownVisitorError = 'UnKnownVisitorError', + ServiceConnectError = 'ServiceConnectError', + RTCPeerConnectionError = 'RTCPeerConnectionError', +} +export type XWebinarExceptionIdentifier = + | XWebinarExceptionIDs + | keyof typeof XWebinarExceptionIDs + | string; +export function isXWebinarExceptionIDs(value: string) { + return isValueInEnum(value, Object.assign(XWebinarExceptionIDs, {})); +} +export function getXWebinarException(id: XWebinarExceptionIdentifier) { + // + // Prepare Default Exception ... + let result: XException = getXException(XExceptionIDs.ActionFailed); + + // + if (isXWebinarExceptionIDs(id)) { + // + if (id === XWebinarExceptionIDs.CallEndError) { + // + // result = { + // ...result, + // id: 99990, + // message: '', + // }; + } else if (id === XWebinarExceptionIDs.OpenWebinarError) { + // + // result = { + // ...result, + // id: 99990, + // message: '', + // }; + } else if (id === XWebinarExceptionIDs.JoinWebinarError) { + // + // result = { + // ...result, + // id: 99990, + // message: '', + // }; + } else if (id === XWebinarExceptionIDs.LocalStreamError) { + // + // result = { + // ...result, + // id: 99990, + // message: '', + // }; + } else if (id === XWebinarExceptionIDs.UnKnownVisitorError) { + // + // result = { + // ...result, + // id: 99990, + // message: '', + // }; + } else if (id === XWebinarExceptionIDs.ServiceConnectError) { + // + // result = { + // ...result, + // id: 99990, + // message: '', + // }; + } else if (id === XWebinarExceptionIDs.RTCPeerConnectionError) { + // + // result = { + // ...result, + // id: 99990, + // message: '', + // }; + } + } + + // + return result; +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\webinar\v-webinar.tools.ts + +import { + XLocale, + hasChild, + isSameObject, + isNullOrUndefined, + isNullOrEmptyString, + XResourceIdentifier, +} from 'x-framework-core'; +import { + XWebinarDto, + XWebinarType, + preparePersonDtoFields, +} from 'x-saherelm-api-sdk'; +import { VModelValidationResult } from '../v-dto/v-dto.typings'; +import { AppResourceIDs } from 'src/app/config/localization.config'; +import { defaultVWebinarViewDto, VWebinarViewDto } from './v-webinar.typings'; + +export function ToWebinarViewDto( + model: XWebinarDto, + forLanguage: XLocale | string, +) { + // + // Validate ... + if (isNullOrUndefined(model) || isNullOrEmptyString(forLanguage)) { + return undefined; + } + + // + // Detect for Language Locales ... + + // + // Title ... + const titleLocale = model.title.locales.find( + (m) => m.language == forLanguage, + ); + + // + // Description ... + const descriptionLocale = model.description.locales.find( + (m) => m.language == forLanguage, + ); + + // + const result: VWebinarViewDto = { + id: model.id, + type: model.type, + owner: model.owner, + language: forLanguage, + ownerId: model.ownerId, + enabled: model.enabled, + createdOn: model.createdOn, + title: isNullOrUndefined(titleLocale) ? '' : titleLocale.value, + description: isNullOrUndefined(descriptionLocale) + ? '' + : descriptionLocale.value, + }; + + // + return result; +} + +export function ToWebinarDto(model: VWebinarViewDto, dto?: XWebinarDto) { + // + let result: XWebinarDto = undefined; + + // + model = prepareVWebinarViewDtoFields(model); + + // + result = { + id: model.id, + type: model.type, + ownerId: model.ownerId, + enabled: model.enabled, + title: { + resource: + dto && dto.title && dto.title.resource ? dto.title.resource : '', + locales: [ + { + value: model.title, + language: model.language, + }, + ], + }, + description: { + resource: + dto && dto.description && dto.description.resource + ? dto.description.resource + : '', + locales: [ + { + value: model.description, + language: model.language, + }, + ], + }, + createdOn: model.createdOn, + }; + + // + if ( + isNullOrEmptyString(result.id) && + !isNullOrUndefined(dto) && + !isNullOrEmptyString(dto.id) + ) { + // + result.id = dto.id; + } + + // + return result; +} + +export function prepareVWebinarViewDtoFields(model?: VWebinarViewDto) { + // + if (isNullOrUndefined(model)) { + model = { + ...defaultVWebinarViewDto, + }; + } + + // + model = { + type: model.type >= 0 ? model.type : defaultVWebinarViewDto.type, + id: !isNullOrEmptyString(model.id) ? model.id : defaultVWebinarViewDto.id, + title: !isNullOrEmptyString(model.title) + ? model.title + : defaultVWebinarViewDto.title, + enabled: !isNullOrUndefined(model.enabled) + ? model.enabled + : defaultVWebinarViewDto.enabled, + ownerId: !isNullOrEmptyString(model.ownerId) + ? model.ownerId + : defaultVWebinarViewDto.ownerId, + language: !isNullOrEmptyString(model.language) + ? model.language + : defaultVWebinarViewDto.language, + description: !isNullOrEmptyString(model.description) + ? model.description + : defaultVWebinarViewDto.description, + owner: !isNullOrUndefined(model.owner) + ? preparePersonDtoFields(model.owner) + : defaultVWebinarViewDto.owner, + createdOn: !isNullOrUndefined(model.createdOn) + ? model.createdOn + : defaultVWebinarViewDto.createdOn, + }; + + // + return model; +} + +export function isDefaultVWebinarViewDto(model?: VWebinarViewDto) { + return isSameObject( + prepareVWebinarViewDtoFields(model), + defaultVWebinarViewDto, + ); +} + +export function validateVWebinarViewDtoForAction(model?: VWebinarViewDto) { + // + let result: VModelValidationResult = undefined; + + // + let isValid = false; + const validationErrors: string[] = []; + const normalized = prepareVWebinarViewDtoFields(model); + + // + // Handle Validation ... + isValid = + !isNullOrUndefined(normalized) && !isDefaultVWebinarViewDto(normalized); + if (!isValid) { + // + validationErrors.push('InvalidArgs'); + } else { + // + // Check Other Validations ... + + // + // ID ... + + // + // Language ... + isValid = !isNullOrEmptyString(normalized.language); + if (!isValid) { + // + validationErrors.push('InvalidArgs'); + } + + // + // Title ... + isValid = !isNullOrEmptyString(normalized.title); + if (!isValid) { + // + validationErrors.push('InvalidArgs'); + } + + // + // Description ... + isValid = !isNullOrEmptyString(normalized.description); + if (!isValid) { + // + validationErrors.push('InvalidArgs'); + } + + // + // Type ... + isValid = normalized.type >= 0; + if (!isValid) { + // + validationErrors.push('InvalidArgs'); + } + } + + // + // Handle Result ... + + // + isValid = !hasChild(validationErrors); + + // + const isValidForAdd = isNullOrEmptyString(normalized.id); + const isValidForUpdate = !isNullOrEmptyString(normalized.id); + + // + result = { + isValid, + normalized, + isValidForAdd, + isValidForUpdate, + validationErrors, + }; + + // + return result; +} + +export function getWebinarResourceID(value: number) { + // + let result: XResourceIdentifier = undefined; + + // + switch (value) { + // + case XWebinarType.Private: + result = AppResourceIDs.private; + break; + + // + case XWebinarType.Public: + result = AppResourceIDs.public; + break; + } + + // + return result; +} + +export function getWebinarTypeValues() { + return [XWebinarType.Public, XWebinarType.Private]; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\webinar\v-webinar.typings.ts + +import { XBaseDto } from 'x-framework-core'; +import { XPersonDto, XWebinarType } from 'x-saherelm-api-sdk'; + +export interface VWebinarViewDto extends XBaseDto { + id: string; + title: string; + ownerId: string; + createdOn: Date; + enabled: boolean; + language: string; + type: XWebinarType; + owner?: XPersonDto; + description: string; +} + +export const defaultVWebinarViewDto: VWebinarViewDto = { + id: '', + title: '', + ownerId: '', + language: '', + enabled: false, + description: '', + type: undefined, + owner: undefined, + createdOn: undefined, +}; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\webinar\v-webinar\v-webinar.component.html + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\webinar\v-webinar\v-webinar.component.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\webinar\v-webinar\v-webinar.component.ts + +import { + VBaseDtoComponent, + VModelFieldDescriptor, + VModelValidationResult, +} from '../../v-dto/v-dto.typings'; +import { getFieldNames } from 'x-framework-core'; +import { defaultWebinarDto, XWebinarDto } from 'x-mabsut-api-sdk'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'v-webinar', + templateUrl: './v-webinar.component.html', + styleUrls: ['./v-webinar.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class VWebinarComponent extends VBaseDtoComponent { + // + //#region Props ... + readonly defaultModel: XWebinarDto = { + ...defaultWebinarDto, + }; + readonly FieldNames = getFieldNames(this.defaultModel); + + defaultFieldDescriptors: VModelFieldDescriptor[]; + // + // ID ... + // + // Owner ID ... + // + // Type ... + // + // Enabled ... + // + // Title ... + // + // Description ... + //#endregion + + // + //#region Abstract ... + validate(model?: Partial): VModelValidationResult { + throw new Error('Method not implemented.'); + } + prepare(model?: XWebinarDto): XWebinarDto { + throw new Error('Method not implemented.'); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\webinar\v-webinar\v-webinar.typings.ts + +export enum VWebinarAction { + Refresh = 'Refresh', +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\webinar\v-webinar-connect\v-webinar-connect.component.html + +
+ +
+ + + +
+ + + +
+ +
+
+ + + + +

{{ getToolbarTitle() }}

+
+
+ + + + +
+
+
+ + + +
+ + +
+ + +
+
+ + + + + + + +
+ + + + + + + + + + + + + +
+ +
+
+
+
+
+
+ + + Chats ... + + + + + + + + + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\webinar\v-webinar-connect\v-webinar-connect.component.scss + +@import "../../../../theme/theme.scss"; + +:host { + --dialog-toolbar-height: 0px; + --background: var(--x-color-light); +} + +::ng-deep.fullscreen { + --width: 100% !important; + --height: 100% !important; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + margin: 0 !important; +} + +.v-base-dialog { + width: 100%; + height: 100%; + // height: calc(100% - var(--dialog-toolbar-height)); + // margin-bottom: var(--dialog-toolbar-height); +} + +.v-base-dialog-content { + height: 100%; + + // + padding-bottom: var(--dialog-toolbar-height); +} + +.v-base-dialog-toolbar-area { + margin: 8px; +} + +.v-base-dialog-toolbar-item { + width: auto; + height: 48px; +} + +.v-base-dialog-toolbar-button { + width: 32px; + margin: { + left: 4px; + right: 4px; + } +} + +.v-webinar-screen { + padding: 16px; +} + +// +// Non Global ... +.v-avatar-image { + // + margin: auto; + display: block; + max-width: 100%; + max-height: 100%; + + // + clip-path: circle(); +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\webinar\v-webinar-connect\v-webinar-connect.component.ts + +import { + Input, + NgZone, + Inject, + Renderer2, + Component, + ViewChild, + ElementRef, + ChangeDetectorRef, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + XExceptionIDs, + XColorIdentifier, + isNullOrUndefined, + isNullOrEmptyString, + XResourceIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { + XWebinarExceptionIDs, + getXWebinarException, + XWebinarExceptionIdentifier, +} from '../v-webinar.configs'; +import { Subject } from 'rxjs'; +import { + XSlotName, + XIconNames, + XSlotLayout, + XPopoverService, + XSlotterAction, + XSlotterActionModel, +} from 'x-framework-components'; +import { + VWebinarCssVar, + VWebinarSubscriber, + VWebinarModalContext, + VWebinarModalContextIdentifier, + VWebinarReturnActionIdentifier, +} from './v-webinar-connect.typings'; +import { + IVVideoState, + VVideoAction, + IVVideoFlags, + isVVideoAction, + VVideoActionModel, +} from '../../v-video/v-video.typings'; +import { ToastButton } from '@ionic/angular'; +import { map, takeUntil } from 'rxjs/operators'; +import { X_CONFIG } from 'src/app/config/x.config'; +import { XConfig } from 'src/app/config/app.config'; +import { ViewportRuler } from '@angular/cdk/scrolling'; +import { VWebinarViewDto } from '../v-webinar.typings'; +import { XManagerService } from 'x-framework-services'; +import { XHubConnectionDto } from 'x-framework-push-service'; +import { VBaseComponent } from '../../classes/v-base.component'; +import { SharedService } from 'src/app/shared/services/shared.service'; +import { + isXCameraFacing, + XCameraFacing, +} from 'src/app/shared/services/x-media-devices.service'; + +@Component({ + selector: 'v-webinar-connect', + templateUrl: './v-webinar-connect.component.html', + styleUrls: ['./v-webinar-connect.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class VWebinarConnectComponent extends VBaseComponent { + // + //#region Properties ... + // + readonly SlotNames = Object.assign({}, XSlotName); + readonly SlotLayouts = Object.assign({}, XSlotLayout); + readonly VideoProvidedActions = Object.assign({}, VVideoAction); + + /** + * retrieve Toolbar container ViewChild + * to Calculate Toolbar Height ... + */ + @ViewChild('vBaseDialogToolbar') + vBaseDialogToolbarContainer!: ElementRef; + + /** + * Specified Modal Type ... + */ + @Input() + context: VWebinarModalContextIdentifier; + + @Input() + model: VWebinarViewDto; + + /** + * Background Color of Component ... + */ + @Input() + backgroundColor: XColorIdentifier = XColorWithBrightness.Light; + + @Input() + toolbarTitle: XResourceIdentifier = ''; + + /** + * Toolbar Color ... + */ + @Input() + toolbarColor: XColorIdentifier = XColorWithBrightness.PrimaryTint; + + /** + * Progressbar Color ... + */ + @Input() + progressbarColor: XColorIdentifier = XColorWithBrightness.Warning; + showProgrssbar = false; + + /** + * Action Icon Names Specifiecations ... + */ + @Input() + cancelIcon = XIconNames.close; + + @Input() + backIcon = XIconNames.arrow_back; + + // + //#region Commone VVideo Props ... + @Input() + wrapWithCard: boolean = false; + + @Input() + isInPageCard: boolean = false; + + @Input() + showTitle: boolean = true; + //#endregion + + // + slotterActionProvider = new Subject(); + + // + //#region Custom Props ... + // + hasBackCamera = false; + webinaPushServiceUnSubscriber = new Subject(); + loadingHandlerTiemout: NodeJS.Timeout = undefined; + + // + isScreenShared = false; + screenShareStream: MediaStream = undefined; + + // + isWebinarConnected = false; + isWebinarServiceConnected = false; + + // + localStream: MediaStream; + currentMediaDevice: XCameraFacing = XCameraFacing.User; + + // + remotePeer: RTCPeerConnection = null; + visitors: Array = []; + viewerPeers = new Map(); + + // + localVideoSource: MediaStream = null; + localVideoActionProvider = new Subject(); + + // + remoteVideoSource: MediaStream = null; + remoteVideoActionProvider = new Subject(); + //#endregion + //#endregion + + // + //#region Constructor ... + public constructor( + public zone: NgZone, + @Inject(X_CONFIG) + public config: XConfig, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public sharedService: SharedService, + public managerService: XManagerService, + public changeDetector: ChangeDetectorRef, + protected popoverService: XPopoverService, + ) { + super( + zone, + config, + element, + renderer, + ruler, + sharedService, + managerService, + changeDetector, + popoverService, // + ); + } + //#endregion + + // + //#region LifeCycles ... + async afterViewInit(): Promise { + super.afterViewInit(); + + // + this.applyContentSize(); + this.registerActionProviders(); + + // + // Handle Starting Up Connection ... + await this.handlePushServiceConnection(); + if (this.isWebinarServiceConnected) { + await this.startUpWebinarConnect(); + } + } + + async onDestroy(): Promise { + super.onDestroy(); + + // + this.detachEventListeners(); + await this.destroyResources(); + this.unregisterConnectionChecker(); + + // + // Handle Webinar Close if it's Open ... + const isOwner = await this.getValueAsync(this.isOwner()); + if (isOwner) { + // + try { + // + const connectionId = + this.sharedService.saherElmAPIService.webinars.webinarPushService + .connectionId; + const entity = await this.getValueAsync( + this.sharedService.saherElmAPIService.webinars.webinarProviderService.get( + this.model.id, + ), + ); + if (!isNullOrUndefined(entity) && !!entity.enabled) { + // + entity.enabled = false; + await this.getValueAsync( + this.sharedService.saherElmAPIService.webinars.webinarProviderService.update( + entity, + connectionId, + ), + ); + } + } catch {} + } + } + //#endregion + + // + //#region UI Providers ... + getToolbarTitle() { + // + let result = ''; + + // + if (!isNullOrUndefined(this.model)) { + result = this.model.title; + } + + // + if (!isNullOrUndefined(this.toolbarTitle)) { + result = this.resourceProvider(this.toolbarTitle); + } + + // + return result; + } + + isOwner() { + return this.sharedService.userId$.pipe( + map( + (userId) => + !isNullOrEmptyString(userId) && userId === this.model.ownerId, + ), + ); + } + + isSubscriber() { + return this.sharedService.userId$.pipe( + map( + (userId) => + !isNullOrEmptyString(userId) && userId !== this.model.ownerId, + ), + ); + } + + /** + * Present Webinar Video Connection Title ... + * + * @returns + */ + getTitle() { + // + return this.isOwner().pipe( + map((isOwner) => { + // + let result = ''; + if (!this.model) { + return result; + } + + // + let type = this.resourceProvider( + isOwner + ? this.AppResourceIDs.webinar_owner + : this.AppResourceIDs.webinar_joiner, + ); + result = `${this.model.title}`; + return this.applyLocale(result); + }), + ); + } + + /** + * Present Webinar Video Connection Sub Title ... + * + * @returns + */ + getSubTitle() { + // + return this.isOwner().pipe( + map((isOwner) => { + // + let result = ''; + if (!this.model) { + return result; + } + + // + result = this.resourceProvider( + isOwner + ? this.AppResourceIDs.webinar_owner + : this.AppResourceIDs.webinar_joiner, + ); + return this.applyLocale(result); + }), + ); + } + //#endregion + + // + //#region UI Handlers ... + /** + * Use for Handle Modela Closing Action based on Context ... + * @returns + */ + async handleBackPressed(action?: VWebinarReturnActionIdentifier) { + // + if ( + this.loading || + this.uiDisabled || + !isNullOrUndefined(this.error) || + isNullOrUndefined(this.context) || + isNullOrEmptyString(this.context) + ) { + return; + } + + // + if (!this.lock && !this.isWebinarConnected) { + await this.handleCloseDialog(); + } else { + // + await this.handleEndCall(); + } + } + //#endregion + + // + //#region Handlers ... + //#endregion + + // + //#region Webinar Push Service ... + /** + * Handle Push Service Connection ... + */ + async handlePushServiceConnection(force: boolean = false) { + // + //#region Connect to Push Service ... + await this.handleLoadingState(true); + + // + if ( + force && + (this.isWebinarServiceConnected || + this.sharedService.saherElmAPIService.webinars.webinarPushService.isConnected()) + ) { + // + try { + await this.sharedService.saherElmAPIService.webinars.webinarPushService.disconnect(); + } catch {} + + // + this.isWebinarServiceConnected = false; + } + + // + if ( + this.sharedService.saherElmAPIService.webinars.webinarPushService.isConnected() + ) { + // + await this.handleLoadingState(false); + this.isWebinarServiceConnected = true; + } else { + try { + // + await this.sharedService.saherElmAPIService.webinars.webinarPushService.connect(); + this.isWebinarServiceConnected = true; + await this.handleLoadingState(false); + } catch { + // + await this.handleConnectionCheck( + XWebinarExceptionIDs.ServiceConnectError, + ); + } + } + //#endregion + + // + if (this.isWebinarServiceConnected) { + // + // Detach ... + if (force) { + this.detachEventListeners(); + } + + // + this.attachEventListeners(); + } + } + + /** + * Handle Startup Webinar Connection ... + */ + async startUpWebinarConnect(force: boolean = false) { + // + // Detect Type ... + const isOwner = await this.getValueAsync(this.isOwner()); + const isSubscriber = await this.getValueAsync(this.isSubscriber()); + if (!isOwner && !isSubscriber) { + // + await this.handleConnectionCheck( + XWebinarExceptionIDs.UnKnownVisitorError, + ); + return; + } + + // + // Handle Forces ... + if (force) { + // + // Reset Push Service Connection ... + await this.handlePushServiceConnection(force); + + // + // Reset All Exists Resources ... + await this.destroyResources(); + } + + // + // Enable Loading Mode ... + await this.handleLoadingState(true); + + // + // Register Connection Checker ... + this.registerConnectionChecker(); + + // + // Check Webinar Push Service Connected ... + if (!this.isWebinarServiceConnected) { + // + await this.handleConnectionCheck( + XWebinarExceptionIDs.ServiceConnectError, + ); + return; + } + + // + // Here we have to Start Webinar Action ... + + // + // Handle Owner based Starting ... + if (isOwner) { + // + // For Owner, we have to Prepare Local Device Stream ... + // which Sends Media Tracks through Peer Connections ... + + // + // Select Media Device ... + // for Local Stream ... + try { + // + this.localStream = + await this.sharedService.mediaDevicesService.getMediaDevice( + this.currentMediaDevice, + ); + + // + this.hasBackCamera = + await this.sharedService.mediaDevicesService.hasBackCamera(); + } catch { + // + await this.handleConnectionCheck(XWebinarExceptionIDs.LocalStreamError); + return; + } + + // + // Since we neew to Owner see himself on Video Element ... + // here we fill localVideoSource as Local Stream ... + // const localStreamTracks = this.localStream.getTracks().map(t => t.clone()); + // this.localVideoSource = new MediaStream(localStreamTracks); + this.localVideoSource = this.localStream; + this.sendLocalVideoAction(this.VideoProvidedActions.Mute); + + // + if (this.hasBackCamera) { + // + const flags: IVVideoFlags = { + hasBackCamera: this.hasBackCamera, + canSwitchCameras: this.hasBackCamera, + hasFrontCamera: !isNullOrUndefined(this.localStream), + }; + + // + console.log('hasBack: ', this.hasBackCamera); + + // + this.sendLocalVideoAction(this.VideoProvidedActions.SetFlags, flags); + } + + // + // After this, we have to Open Webinar and Notify clients which this webinar is Opened + // for Subscribers to Join ... + try { + // + this.isWebinarConnected = true; + await this.sharedService.saherElmAPIService.webinars.webinarPushService.openWebinar( + this.model.id, + ); + } catch { + // + this.isWebinarConnected = false; + await this.handleConnectionCheck(XWebinarExceptionIDs.OpenWebinarError); + return; + } + } + + // + // Handle Subscriber based Starting ... + if (isSubscriber) { + // + // Subscriber must have a Peer Connection by Owner ... + // so First step is Create Peer ... + try { + // + // Create a Remote Peer ... + this.remotePeer = + this.sharedService.saherElmAPIService.webinars.webinarPushService.rtcPeerService.createConnection(); + + // + // Then we have to Attach Peer Connection On Track Event Listener + // for Passing Recieve Tracks to remoteVideoSource ... + this.remotePeer.ontrack = (event: RTCTrackEvent) => { + // + // Validate ... + if ( + !isNullOrUndefined(event) && + !isNullOrUndefined(event.streams) && + this.hasChild(event.streams) + ) { + // + // Set Default Stream ... + if ( + isNullOrUndefined(this.remoteVideoSource) || + this.remoteVideoSource.id === event.streams[0].id + ) { + this.remoteVideoSource = event.streams[0]; + } + // + // Set Screen Stream ... + else { + // + const track = event.track; + + // + // Handle Enable Picture in Picture ... + this.screenShareStream = event.streams[0]; + + // + // Destroy Screenshare Stream ... + this.screenShareStream.onremovetrack = (event) => { + // + // Set State ... + let state: IVVideoState = { + enablePictureInPicture: false, + }; + this.sendRemoteVideoAction( + this.VideoProvidedActions.SetState, + state, + ); + + // + // Destroy Screen Share Stream ... + if (!isNullOrUndefined(this.screenShareStream)) { + this.screenShareStream.getTracks().forEach((t) => t.stop()); + } + this.screenShareStream = undefined; + }; + + // + // Set State ... + let state: IVVideoState = { + enablePictureInPicture: true, + }; + this.sendRemoteVideoAction( + this.VideoProvidedActions.SetState, + state, + ); + } + + // + this.handleDetectChanges(); + } + }; + + // + // After this we have to Attach ICE Candidate Events Handler ... + this.remotePeer.onicecandidate = async ( + event: RTCPeerConnectionIceEvent, + ) => { + // + // Validate ... + if ( + !isNullOrUndefined(event) && + !isNullOrUndefined(event.candidate) + ) { + // + // Converts Candidate to Json ... + const candidateJson = JSON.stringify(event.candidate); + + // + // Send Recieved Candidate through Signalling Service ... + // since we send only this candidate to webinar owner, there is no neew to Specified To ... + await this.sharedService.saherElmAPIService.webinars.webinarPushService.candidate( + candidateJson, + '', + ); + } + }; + } catch { + // + await this.handleConnectionCheck( + XWebinarExceptionIDs.RTCPeerConnectionError, + ); + return; + } + + // + // Final Step is Notify Clients which a Viewer Joined ... + try { + // + this.isWebinarConnected = true; + await this.sharedService.saherElmAPIService.webinars.webinarPushService.joinWebinar( + this.model.id, + ); + } catch { + // + this.isWebinarConnected = false; + await this.handleConnectionCheck(XWebinarExceptionIDs.JoinWebinarError); + return; + } + } + + // + // in Both Senarios we have to check Webinar is Connected or not ... + if (!this.isWebinarConnected) { + // + if (isOwner) { + await this.handleConnectionCheck(XWebinarExceptionIDs.OpenWebinarError); + } else { + await this.handleConnectionCheck(XWebinarExceptionIDs.JoinWebinarError); + } + + // + return; + } + + /** + * Locking Component ... + */ + this.lock = true; + + /** + * Clear any possible errors ... + */ + this.error = undefined; + + /** + * Unregister Connection Checker ... + */ + this.unregisterConnectionChecker(); + + /** + * Update Webianr Visitors ... + */ + await this.updateVisitors(); + + /** + * Disable Loading State ... + */ + await this.handleLoadingState(false); + + /** + * Preparing Views ... + */ + await this.preparingViews(); + } + + // + // + // + + /** + * Handle Viewer Joined ... + * + * @param viewerId + * @param webinarId + * @param userId + * @param username + */ + async onViewerJoined( + viewerId: string, + webinarId: string, + userId: string, + username?: string, + ) { + // + // Reading Our Connection Id ... + const connectionId = + this.sharedService.saherElmAPIService.webinars.webinarPushService + .connectionId; + + // + // Validate ... + if ( + isNullOrEmptyString(userId) || + isNullOrUndefined(this.model) || + isNullOrEmptyString(viewerId) || + isNullOrEmptyString(webinarId) || + isNullOrEmptyString(connectionId) || + isNullOrEmptyString(this.model.id) || + webinarId !== this.model.id + ) { + return; + } + + // + // Determine Current User State ... + const isOwner = await this.getValueAsync(this.isOwner()); + const isSubscriber = await this.getValueAsync(this.isSubscriber()); + if (!isOwner && !isSubscriber) { + return; + } + + // + if (isOwner) { + // + // if a Viewer Joined ... + + // + try { + // + // First ... + // we Have to Create a Peer Connection for it ... + const pc = + this.sharedService.saherElmAPIService.webinars.webinarPushService.rtcPeerService.createConnection(); + + // + // Second ... + // Add Required Event Listeners to Peer Connection ... + + // + // Signaliing Recieve ICE Candidates of Peer Connection ... + pc.onicecandidate = async (e: RTCPeerConnectionIceEvent) => { + // + // Check Candidate Exists ... + if (!isNullOrUndefined(e) && !isNullOrUndefined(e.candidate)) { + // + // Prepare Model to Signalling ... + const json = JSON.stringify(e.candidate); + await this.sharedService.saherElmAPIService.webinars.webinarPushService.candidate( + json, + viewerId, + ); + } + }; + + // + // Handle Offering ... + pc.onnegotiationneeded = async () => { + // + try { + // + const offer = await pc.createOffer(); + await pc.setLocalDescription(offer); + + // + const offerJson = JSON.stringify(offer); + await this.sharedService.saherElmAPIService.webinars.webinarPushService.offer( + offerJson, + viewerId, + ); + } catch {} + }; + + // + // Third ... + // Attach Local Streams Tracks to Peer Connection for Streaming ... + this.localStream + .getTracks() + .forEach((t) => pc.addTrack(t, this.localStream)); + + // + // if Screen Shared, Handle it's Tracks ... + if (!isNullOrUndefined(this.screenShareStream)) { + this.screenShareStream + .getTracks() + .forEach((t) => pc.addTrack(t, this.screenShareStream)); + } + + // + // Store Viewer Peer Connection ... + const subscriber: VWebinarSubscriber = { + userId, + username, + peer: pc, + webinarId, + connectionId: viewerId, + }; + this.viewerPeers.set(viewerId, subscriber); + } catch { + // + await this.handleConnectionCheck( + XWebinarExceptionIDs.RTCPeerConnectionError, + ); + return; + } + } + + // + // Update Visitors ... + await this.updateVisitors(); + + // + const message = this.applyLocale( + this.resourceProvider(this.AppResourceIDs.visitor_joined_message).replace( + '${TMP1}&', + username, + ), + ); + await this.managerService.notificationService.presentInfoNotification({ + message, + dissmissable: true, + }); + this.detectChanges(); + } + + /** + * Handle Viewer Leaved ... + * + * @param viewerId + * @param webinarId + * @param userId + * @param username + */ + async onViewerLeaved( + viewerId: string, + webinarId: string, + userId: string, + username?: string, + ) { + // + // Reading Our Connection Id ... + const connectionId = + this.sharedService.saherElmAPIService.webinars.webinarPushService + .connectionId; + + // + // Validate ... + if ( + isNullOrEmptyString(userId) || + isNullOrUndefined(this.model) || + isNullOrEmptyString(viewerId) || + isNullOrEmptyString(webinarId) || + isNullOrEmptyString(connectionId) || + isNullOrEmptyString(this.model.id) || + webinarId !== this.model.id + ) { + return; + } + + // + // Determine Current User State ... + const isOwner = await this.getValueAsync(this.isOwner()); + const isSubscriber = await this.getValueAsync(this.isSubscriber()); + if (!isOwner && !isSubscriber) { + return; + } + + // + if (isOwner) { + // + const has = this.viewerPeers.has(viewerId); + if (has) { + // + const pc = this.viewerPeers.get(viewerId).peer; + this.viewerPeers.delete(viewerId); + + // + pc.close(); + } + } + + // + // Update Visitors ... + await this.updateVisitors(); + + // + const message = this.applyLocale( + this.resourceProvider(this.AppResourceIDs.visitor_leaved_message).replace( + '${TMP1}&', + username, + ), + ); + await this.managerService.notificationService.presentInfoNotification({ + message, + dissmissable: true, + }); + this.detectChanges(); + } + + /** + * Handle Webinar Open ... + * + * @param webinarId + * @returns + */ + async onWebinarOpened(webinarId: string) { + // + if ( + isNullOrEmptyString(webinarId) || + isNullOrUndefined(this.model) || + isNullOrEmptyString(this.model.id) || + webinarId !== this.model.id + ) { + return; + } + + // + await this.startUpWebinarConnect(true); + } + + /** + * Webinar Closed Push Event Listener ... + */ + async onWebinarClosed(webinarId: string) { + // + if ( + isNullOrEmptyString(webinarId) || + isNullOrUndefined(this.model) || + isNullOrEmptyString(this.model.id) || + webinarId !== this.model.id + ) { + return; + } + + // + // Handle Close Event ... + // + const isSubscriber = await this.getValueAsync(this.isSubscriber()); + if (isSubscriber) { + // + // Destroy Remote Video Source ... + if (!isNullOrUndefined(this.remoteVideoSource)) { + // + this.remoteVideoSource.getTracks().forEach((t) => t.stop()); + this.remoteVideoSource = undefined; + } + + // + // Destroy remote Peer ... + if ( + !isNullOrUndefined(this.remotePeer) && + (this.remotePeer.connectionState === 'connected' || + this.remotePeer.connectionState === 'connecting') + ) { + this.remotePeer.close(); + } + this.remotePeer = undefined; + + // + this.isWebinarConnected = false; + + // + // Set Remote Video Flags ... + const flags: IVVideoFlags = { isInCall: false }; + this.sendRemoteVideoAction(this.VideoProvidedActions.SetFlags, flags); + this.detectChanges(); + + // + await this.managerService.notificationService.presentWarningNotification({ + dissmissable: true, + message: this.resourceProvider( + this.AppResourceIDs.webinar_closed_message, + ), + }); + await this.handleCloseDialog(); + } + } + + /** + * Handle Offer Recieved ... + * + * @param offer + * @returns + */ + async onOfferRecieved(offer: any) { + // + // Validate ... + if (isNullOrUndefined(offer)) { + return; + } + + // + // Determine Current User State ... + const isOwner = await this.getValueAsync(this.isOwner()); + const isSubscriber = await this.getValueAsync(this.isSubscriber()); + if (!isOwner && !isSubscriber) { + return; + } + + // + if (isSubscriber) { + // + if (!isNullOrUndefined(this.remotePeer)) { + // + // Retrieve Offering Model ... + const offerModel: RTCSessionDescriptionInit = JSON.parse(offer); + this.remotePeer.setRemoteDescription( + new RTCSessionDescription(offerModel), + ); + + // + // Preparing Answer ... + const answer = await this.remotePeer.createAnswer(); + await this.remotePeer.setLocalDescription(answer); + + // + // Signalling Answer ... + const answerJsno = JSON.stringify(answer); + await this.sharedService.saherElmAPIService.webinars.webinarPushService.answer( + answerJsno, + ); + } + } + } + + /** + * Handle Answer Recieved ... + * + * @param answer + * @param requesterId + * @returns + */ + async onAnswerRecieved(answer: any, requesterId: string) { + // + // Validate ... + if (isNullOrUndefined(answer)) { + return; + } + + // + // Determine Current User State ... + const isOwner = await this.getValueAsync(this.isOwner()); + const isSubscriber = await this.getValueAsync(this.isSubscriber()); + if (!isOwner && !isSubscriber) { + return; + } + + // + if (isOwner) { + // + // Validate Requester Id ... + if ( + !isNullOrEmptyString(requesterId) && + this.viewerPeers.has(requesterId) + ) { + // + const answerModel: RTCSessionDescriptionInit = JSON.parse(answer); + await this.viewerPeers + .get(requesterId) + .peer.setRemoteDescription(new RTCSessionDescription(answerModel)); + } + } + } + + /** + * Handle Candidate Recieved ... + * + * @param candidate + * @param senderId + * @returns + */ + async onCandidateRecieved(candidate: any, senderId: string) { + // + // Validate ... + if (isNullOrUndefined(candidate)) { + return; + } + + // + // Determine Current User State ... + const isOwner = await this.getValueAsync(this.isOwner()); + const isSubscriber = await this.getValueAsync(this.isSubscriber()); + if (!isOwner && !isSubscriber) { + return; + } + + // + if (isOwner) { + // + // Validate SenderId ... + if (!isNullOrEmptyString(senderId) && this.viewerPeers.has(senderId)) { + // + const candidateModel: RTCIceCandidateInit = JSON.parse(candidate); + await this.viewerPeers + .get(senderId) + .peer.addIceCandidate(candidateModel); + } + } + + // + if (isSubscriber) { + // + // Validate SenderId ... + if (!isNullOrUndefined(this.remotePeer)) { + const candidateModel: RTCIceCandidateInit = JSON.parse(candidate); + await this.remotePeer.addIceCandidate(candidateModel); + } + } + } + //#endregion + + // + //#region Private ... + /** + * Handle Loading State ... + * + * @param state + */ + private async handleLoadingState(state: boolean) { + // + this.loading = state; + this.uiDisabled = state; + this.showProgrssbar = state; + + // + this.handleDetectChanges(); + } + + /** + * Handle Change Detetction ... + */ + private handleDetectChanges() { + // + this.sendLocalVideoAction(this.VideoProvidedActions.Refresh); + this.sendRemoteVideoAction(this.VideoProvidedActions.Refresh); + this.slotterActionProvider.next({ action: XSlotterAction.Refresh }); + + // + this.detectChanges(); + } + + /** + * Calculate Toolbar Height and add a CSSVar to + * use it for Content Sizing ... + * + * @returns + */ + private async applyContentSize() { + // + const height = + this.vBaseDialogToolbarContainer.nativeElement.getBoundingClientRect() + .height; + if (height === 0) { + setTimeout(() => this.applyContentSize(), 100); + return; + } + + // + this.setCssVar(VWebinarCssVar.DialogToolbarHeight, `${height}px`); + this.detectChanges(); + } + + /** + * Preparing Views Based on Connection type ... + */ + private async preparingViews() { + // + // Determine Current User State ... + const isOwner = await this.getValueAsync(this.isOwner()); + const isSubscriber = await this.getValueAsync(this.isSubscriber()); + if (!isOwner && !isSubscriber) { + return; + } + + // + var falseState: IVVideoState = { + // + showRewindControl: false, + showForwardControl: false, + showPlayPauseControl: false, + showVolumeChangerControl: false, + showPlaybackRatesControl: false, + showFullscreenTogglerControl: false, + + // + showCallControl: false, + showRecordControl: false, + showVideoCallControl: false, + showMicorphoneControl: false, + showShareScreenControl: false, + showCameraSwitchControl: false, + + // + enablePictureInPicture: false, + enableSpeechRecognition: false, + }; + + // + if (isOwner) { + // + // Preparing Views as Owner Connection ... + + // + var state: IVVideoState = { + // + ...falseState, + + // + showCallControl: true, + showVideoCallControl: true, + showMicorphoneControl: true, + showShareScreenControl: true, + showFullscreenTogglerControl: true, + showCameraSwitchControl: this.hasBackCamera, + }; + + // + this.sendLocalVideoAction(VVideoAction.SetState, state); + + // + // We Have to Mute Local Video ... + this.sendLocalVideoAction(VVideoAction.Mute); + } else if (isSubscriber) { + // + // Preparing Views as Subscriber Connection ... + + // + var state: IVVideoState = { + // + ...falseState, + + // + showCallControl: true, + showVolumeTogglerControl: true, + showVolumeChangerControl: true, + showFullscreenTogglerControl: true, + }; + + // + this.sendRemoteVideoAction(VVideoAction.SetState, state); + } + } + + /** + * Update Webinar Visitors ... + */ + private async updateVisitors() { + // + // Validate ... + if (isNullOrUndefined(this.model) || isNullOrEmptyString(this.model.id)) { + return; + } + + // + let visitors: Array = []; + try { + visitors = + await this.sharedService.saherElmAPIService.webinars.webinarPushService.webinarVisitors( + this.model.id, + ); + } catch {} + + // + this.visitors = [...visitors]; + this.handleDetectChanges(); + } + + /** + * Check Connection Or Errors ... + * + * @param error + */ + private async handleConnectionCheck( + error: XWebinarExceptionIdentifier = undefined, + ) { + // + if (!isNullOrUndefined(this.loadingHandlerTiemout)) { + return; + } + + // + // Dispose / Unref Interval ... + this.unregisterConnectionChecker(); + + // + console.log('connection check error: ', error); + + // + this.error = getXWebinarException(error); + await this.handleLoadingState(false); + + // + await this.managerService.notificationService.presentErrorNotification({ + dissmissable: false, + exception: XExceptionIDs.ActionFailed, + opt: { + buttons: [ + { + icon: this.IconNames.refresh, + side: 'end', + handler: async () => { + await this.startUpWebinarConnect(true); + }, + } as ToastButton, + { + icon: this.IconNames.close, + side: 'end', + handler: async () => { + // + this.error = undefined; + await this.handleCloseDialog(); + }, + } as ToastButton, + ], + }, + }); + } + + /** + * Register Connection Checker ... + */ + private registerConnectionChecker() { + // + if (!isNullOrUndefined(this.loadingHandlerTiemout)) { + return; + } + + // + this.loadingHandlerTiemout = setInterval(async () => { + await this.handleConnectionCheck(); + }, 20000); + } + + /** + * Unregister Connection Checker ... + */ + private unregisterConnectionChecker() { + // + if (isNullOrUndefined(this.loadingHandlerTiemout)) { + return; + } + + // + clearInterval(this.loadingHandlerTiemout); + this.loadingHandlerTiemout = undefined; + } + + /** + * Detach Webinar Push Service Event Listeners ... + */ + private detachEventListeners() { + // + if (isNullOrUndefined(this.webinaPushServiceUnSubscriber)) { + return; + } + + // + this.webinaPushServiceUnSubscriber.next(); + this.webinaPushServiceUnSubscriber.complete(); + this.webinaPushServiceUnSubscriber = undefined; + } + + /** + * Attach Webinar Push Service Event Listeners ... + */ + private attachEventListeners() { + // + this.detachEventListeners(); + this.webinaPushServiceUnSubscriber = new Subject(); + + // + // Webinar Opened ... + this.sharedService.saherElmAPIService.webinars.webinarPushService.onWebinarOpened$ + .pipe(takeUntil(this.webinaPushServiceUnSubscriber)) + .subscribe((webinarId) => { + // + if ( + isNullOrEmptyString(webinarId) || + isNullOrUndefined(this.model) || + isNullOrEmptyString(this.model.id) || + webinarId !== this.model.id + ) { + return; + } + + // + this.onWebinarOpened(webinarId); + }); + + // + // Webinar Closed ... + this.sharedService.saherElmAPIService.webinars.webinarPushService.onWebinarClosed$ + .pipe(takeUntil(this.webinaPushServiceUnSubscriber)) + .subscribe((webinarId) => { + // + if ( + isNullOrEmptyString(webinarId) || + isNullOrUndefined(this.model) || + isNullOrEmptyString(this.model.id) || + webinarId !== this.model.id + ) { + return; + } + + // + this.onWebinarClosed(webinarId); + }); + + // + // Viewer Joined ... + this.sharedService.saherElmAPIService.webinars.webinarPushService.onViewerJoined$ + .pipe(takeUntil(this.webinaPushServiceUnSubscriber)) + .subscribe((data) => { + // + if ( + isNullOrUndefined(data) || + isNullOrEmptyString(data.viewerId) || + isNullOrEmptyString(data.webinarId) || + isNullOrEmptyString(data.userId) || + isNullOrUndefined(this.model) || + isNullOrEmptyString(this.model.id) || + data.webinarId !== this.model.id + ) { + return; + } + + // + this.onViewerJoined( + data.viewerId, + data.webinarId, + data.userId, + data.username, + ); + }); + + // + // Viewer Leaved ... + this.sharedService.saherElmAPIService.webinars.webinarPushService.onViewerLeaved$ + .pipe(takeUntil(this.webinaPushServiceUnSubscriber)) + .subscribe((data) => { + // + if ( + isNullOrUndefined(data) || + isNullOrEmptyString(data.viewerId) || + isNullOrEmptyString(data.webinarId) || + isNullOrEmptyString(data.userId) || + isNullOrUndefined(this.model) || + isNullOrEmptyString(this.model.id) || + data.webinarId !== this.model.id + ) { + return; + } + + // + this.onViewerLeaved( + data.viewerId, + data.webinarId, + data.userId, + data.username, + ); + }); + + // + // Offer Recieved ... + this.sharedService.saherElmAPIService.webinars.webinarPushService.onOfferRecieved$ + .pipe(takeUntil(this.webinaPushServiceUnSubscriber)) + .subscribe((offer) => { + // + if (isNullOrUndefined(offer)) { + return; + } + + // + this.onOfferRecieved(offer); + }); + + // + // Answer Recieved ... + this.sharedService.saherElmAPIService.webinars.webinarPushService.onAnswerRecieved$ + .pipe(takeUntil(this.webinaPushServiceUnSubscriber)) + .subscribe((data) => { + // + if (isNullOrUndefined(data) || isNullOrUndefined(data.answer)) { + return; + } + + // + this.onAnswerRecieved(data.answer, data.requesterId); + }); + + // + // Candidate Recieved ... + this.sharedService.saherElmAPIService.webinars.webinarPushService.onCandidateRecieved$ + .pipe(takeUntil(this.webinaPushServiceUnSubscriber)) + .subscribe((data) => { + // + if (isNullOrUndefined(data) || isNullOrUndefined(data.candidate)) { + return; + } + + // + this.onCandidateRecieved(data.candidate, data.senderId); + }); + } + + /** + * Destroy All Resources ... + */ + private async destroyResources() { + // + // Destroy Local Stream ... + if (!isNullOrUndefined(this.localStream)) { + this.localStream.getTracks().forEach((t) => t.stop()); + } + this.localStream = undefined; + + // + // Destroy Local Video Source ... + if (!isNullOrUndefined(this.localVideoSource)) { + this.localVideoSource.getTracks().forEach((t) => t.stop()); + } + this.localVideoSource = undefined; + + // + // Destroy Remote Video Source ... + if (!isNullOrUndefined(this.remoteVideoSource)) { + this.remoteVideoSource.getTracks().forEach((t) => t.stop()); + } + this.remoteVideoSource = undefined; + + // + // Destroy Screenshare Stream ... + if (!isNullOrUndefined(this.screenShareStream)) { + this.screenShareStream.getTracks().forEach((t) => t.stop()); + } + this.screenShareStream = undefined; + + // + // Destroy Remote Peer ... + if ( + !isNullOrUndefined(this.remotePeer) && + (this.remotePeer.connectionState === 'connected' || + this.remotePeer.connectionState === 'connecting') + ) { + try { + this.remotePeer.close(); + } catch {} + } + this.remotePeer = undefined; + + // + // Destroy Peers ... + if (!isNullOrUndefined(this.viewerPeers) && this.viewerPeers.size > 0) { + // + this.viewerPeers.forEach((i) => { + // + if ( + !isNullOrUndefined(i.peer) && + (i.peer.connectionState === 'connected' || + i.peer.connectionState === 'connecting') + ) { + try { + i.peer.close(); + } catch {} + } + }); + this.viewerPeers.clear(); + } + + // + // Destroy Viewers ... + this.visitors = []; + + // + this.handleDetectChanges(); + } + + /** + * Use for Handle Modal Closing Action based on Context ... + * + * @returns + */ + private async handleCloseDialog(action?: VWebinarReturnActionIdentifier) { + // + if (isNullOrUndefined(this.context) || isNullOrEmptyString(this.context)) { + return; + } + + // + switch (this.context) { + // + case VWebinarModalContext.Modal: + await this.managerService.dialogService.modalController.dismiss(action); + break; + + // + case VWebinarModalContext.PopOver: + await this.managerService.dialogService.popoverController.dismiss( + action, + ); + break; + } + } + + // + //#region Action Provider Handlers ... + /** + * Register Action Providers ... + */ + private registerActionProviders() { + // + // Register Action Providers ... + + // + // Local Video ... + if (!isNullOrUndefined(this.localVideoActionProvider)) { + // + this.applyTakeUntilWrapper( + this.localVideoActionProvider.asObservable(), + ).subscribe(async (event) => { + // + // Validate Action ... + if ( + isNullOrUndefined(event) || + isNullOrEmptyString(event.action) || + !isVVideoAction(event.action) + ) { + return; + } + + // + // Handle Provided Actions ... + const action = event.action; + + // + // Response ... + // + // Flags ... + if (action === this.VideoProvidedActions.ResponseFlags) { + } + // + // State ... + else if (action === this.VideoProvidedActions.ResponseState) { + } + // + // Properties ... + else if (action === this.VideoProvidedActions.ResponseProperties) { + } + // + // End Call ... + else if (action === this.VideoProvidedActions.EndCall) { + this.handleEndCall(); + } + // + // Request Call ... + else if (action === this.VideoProvidedActions.RequestCall) { + this.handleRequestCall(); + } + // + // Request Switch Camera ... + else if (action === this.VideoProvidedActions.RequestSwitchCamera) { + // + if ( + !isNullOrUndefined(event.payload) && + isXCameraFacing(event.payload) + ) { + await this.handleRequestSwitchCamera(event.payload); + } + } + // + // Request Start Screenshare ... + else if (action === this.VideoProvidedActions.RequestStartScreenShare) { + await this.handleStartScreenshare(); + } + // + // Request Stop Screenshare ... + else if (action === this.VideoProvidedActions.RequestStopScreenShare) { + await this.handleStopScreenshare(); + } + }); + } + + // + // Remote Video ... + if (!isNullOrUndefined(this.remoteVideoActionProvider)) { + // + this.applyTakeUntilWrapper( + this.remoteVideoActionProvider.asObservable(), + ).subscribe((event) => { + // + // Validate Action ... + if ( + isNullOrUndefined(event) || + isNullOrEmptyString(event.action) || + !isVVideoAction(event.action) + ) { + return; + } + + // + // Handle Provided Actions ... + + // + // Handle Provided Actions ... + const acttion = event.action; + + // + // Response ... + // + // Flags ... + if (acttion === this.VideoProvidedActions.ResponseFlags) { + } + // + // State ... + else if (acttion === this.VideoProvidedActions.ResponseState) { + } + // + // Properties ... + else if (acttion === this.VideoProvidedActions.ResponseProperties) { + } + // + // End Call ... + else if (acttion === this.VideoProvidedActions.EndCall) { + this.handleEndCall(); + } + // + // Request Call ... + else if (acttion === this.VideoProvidedActions.RequestCall) { + this.handleRequestCall(); + } + }); + } + } + + /** + * Send Actions to Local Video Component ... + * + * @param action + * @param payload + * @returns + */ + private sendLocalVideoAction(action: VVideoAction, payload?: any) { + // + if (isNullOrUndefined(this.localVideoActionProvider)) { + return; + } + + // + this.localVideoActionProvider.next({ action, payload }); + } + + /** + * Send Actions to Remote Video Component ... + * + * @param action + * @param payload + * @returns + */ + private sendRemoteVideoAction(action: VVideoAction, payload?: any) { + // + if (isNullOrUndefined(this.remoteVideoActionProvider)) { + return; + } + + // + this.remoteVideoActionProvider.next({ action, payload }); + } + + // + //#region Implemented Provided Actions ... + /** + * Handle End Call ... + * + * @returns + */ + private async handleEndCall() { + // + // Determine Current User State ... + const isOwner = await this.getValueAsync(this.isOwner()); + const isSubscriber = await this.getValueAsync(this.isSubscriber()); + if (!isOwner && !isSubscriber) { + return; + } + + // + // Check Connection Exists ... + if (!this.isWebinarConnected) { + return; + } + + // + await this.doConfirmedAction(async () => { + // + try { + // + if (isOwner) { + // + // Owner Close Webinar ... + await this.sharedService.saherElmAPIService.webinars.webinarPushService.closeWebinar( + this.model.id, + ); + + // + // Destroy Streams ... + + // + // Local ... + if (!isNullOrUndefined(this.localStream)) { + // + this.localStream.getTracks().forEach((t) => t.stop()); + this.localStream = undefined; + } + + // + // Local Video Source ... + if (!isNullOrUndefined(this.localVideoSource)) { + // + this.localVideoSource.getTracks().forEach((t) => t.stop()); + this.localVideoSource = undefined; + } + + // + // Close all Peers ... + if ( + !isNullOrUndefined(this.viewerPeers) && + this.viewerPeers.size > 0 + ) { + // + this.viewerPeers.forEach((viewer) => { + // + if ( + viewer.peer.connectionState === 'connected' || + viewer.peer.connectionState === 'connecting' + ) { + viewer.peer.close(); + } + }); + this.viewerPeers.clear(); + } + } + + // + if (isSubscriber) { + // + // Subscriber Leave Webinar ... + await this.sharedService.saherElmAPIService.webinars.webinarPushService.leaveWebinar( + this.model.id, + ); + + // + // Destroy Remote Video Source ... + if (!isNullOrUndefined(this.remoteVideoSource)) { + // + this.remoteVideoSource.getTracks().forEach((t) => t.stop()); + this.remoteVideoSource = undefined; + } + + // + // Close Remote Peer ... + if ( + !isNullOrUndefined(this.remotePeer) && + (this.remotePeer.connectionState === 'connected' || + this.remotePeer.connectionState === 'connecting') + ) { + this.remotePeer.close(); + } + this.remotePeer = undefined; + } + } catch { + // + await this.handleConnectionCheck(XWebinarExceptionIDs.CallEndError); + return; + } + + // + this.isWebinarConnected = false; + + // + const flags: IVVideoFlags = { isInCall: false }; + this.sendLocalVideoAction(this.VideoProvidedActions.SetFlags, flags); + this.sendRemoteVideoAction(this.VideoProvidedActions.SetFlags, flags); + this.detectChanges(); + + // + await this.handleCloseDialog(); + }); + } + + /** + * Handle Request Call ... + */ + private async handleRequestCall() { + // + const isOwner = await this.getValueAsync(this.isOwner()); + + // + } + + /** + * Handle Stop Screen Share ... + * + * @returns + */ + private async handleStopScreenshare() { + // + // Determine Current User State ... + const isOwner = await this.getValueAsync(this.isOwner()); + const isSubscriber = await this.getValueAsync(this.isSubscriber()); + if (!isOwner && !isSubscriber) { + return; + } + + // + // Check Connection Exists ... + if (!this.isWebinarConnected || !this.isScreenShared) { + return; + } + + // + // Disable Picture in Picture ... + let flags: IVVideoFlags = { + isScreenShared: false, + }; + let state: IVVideoState = { + enablePictureInPicture: false, + }; + + // + if (isOwner) { + // + this.sendLocalVideoAction(this.VideoProvidedActions.SetState, state); + + // + // Remove Tracks From Peers ... + for (let key of this.viewerPeers.keys()) { + // + const item = this.viewerPeers.get(key); + const peer = item.peer; + + // + this.screenShareStream.getTracks().forEach((t) => { + // + const sender = peer.getSenders().find((s) => s.track === t); + peer.removeTrack(sender); + }); + } + + // + // Destroy Stream ... + if (!isNullOrUndefined(this.screenShareStream)) { + this.screenShareStream.getTracks().forEach((t) => t.stop()); + } + this.screenShareStream = undefined; + + // + this.isScreenShared = false; + + // + this.sendLocalVideoAction(this.VideoProvidedActions.SetFlags, flags); + } + } + + /** + * Handle Start Screen Share ... + * + * @returns + */ + private async handleStartScreenshare() { + // + // Determine Current User State ... + const isOwner = await this.getValueAsync(this.isOwner()); + const isSubscriber = await this.getValueAsync(this.isSubscriber()); + if (!isOwner && !isSubscriber) { + return; + } + + // + // Only Owners can Share Screen ... + if (!isOwner) { + return; + } + + // + // Check Connection Exists ... + if (!this.isWebinarConnected) { + return; + } + + // + try { + // + this.screenShareStream = + await this.sharedService.mediaDevicesService.getDisplayMediaDevice(); + } catch { + // + // TODO: Issue Error ... + } + + // + if (!isNullOrUndefined(this.screenShareStream)) { + // + this.isScreenShared = true; + + // + // Attach Stops Event Listener ... + const track = this.screenShareStream.getVideoTracks()[0]; + if (!isNullOrUndefined(track)) { + track.onended = async () => { + // + // Handle Manually Stop Screen Share ... + await this.handleStopScreenshare(); + }; + } + + // + for (var key of this.viewerPeers.keys()) { + // + const item = this.viewerPeers.get(key); + const peer = item.peer; + + // + console.log('adding screen shared tracks to: ', item.username); + this.screenShareStream.getTracks().forEach((t) => { + peer.addTrack(t, this.screenShareStream); + }); + } + } + + // + if (this.isScreenShared) { + // + // Enable Picture in Picture ... + let state: IVVideoState = { + enablePictureInPicture: true, + }; + + // + this.sendLocalVideoAction(this.VideoProvidedActions.SetState, state); + + // + // Set Screen Shared Flag ... + let flags: IVVideoFlags = { + isScreenShared: true, + }; + + // + this.sendLocalVideoAction(this.VideoProvidedActions.SetFlags, flags); + } + + // + this.detectChanges(); + } + + /** + * Switch Camera Device ... + * + * @param type + */ + private async handleRequestSwitchCamera(type: XCameraFacing) { + // + // Determine Current User State ... + const isOwner = await this.getValueAsync(this.isOwner()); + const isSubscriber = await this.getValueAsync(this.isSubscriber()); + if (!isOwner && !isSubscriber) { + return; + } + + // + // Only Owners can Share Screen ... + if ( + !isOwner || + !this.hasBackCamera || + !isXCameraFacing(type) || + type === this.currentMediaDevice + ) { + return; + } + + // + // Get Device ... + try { + // + const stream = + await this.sharedService.mediaDevicesService.getMediaDevice(type); + + // + // Remove all Pears Tracks ... + this.viewerPeers.forEach(async (i) => { + // + this.localStream.getTracks().forEach((t) => { + // + const tSender = i.peer.getSenders().find((s) => s.track === t); + if (!isNullOrUndefined(tSender)) { + i.peer.removeTrack(tSender); + } + }); + }); + + // + // Destroy Local Stream ... + if (!isNullOrUndefined(this.localStream)) { + this.localStream.getTracks().forEach((t) => t.stop()); + } + this.localStream = undefined; + + // + // Destroy Local Video Source ... + if (!isNullOrUndefined(this.localVideoSource)) { + this.localVideoSource.getTracks().forEach((t) => t.stop()); + } + this.localVideoSource = undefined; + + // + this.localStream = stream; + this.localVideoSource = this.localStream; + + // + this.viewerPeers.forEach((i) => { + // + this.localStream.getTracks().forEach((t) => { + i.peer.addTrack(t, this.localStream); + }); + }); + + // + this.currentMediaDevice = type; + const flags: IVVideoFlags = { + currentUsingMediaDevice: type, + }; + this.sendLocalVideoAction(this.VideoProvidedActions.SetFlags, flags); + } catch {} + } + //#endregion + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\webinar\v-webinar-connect\v-webinar-connect.typings.ts + +import { isValueInEnum } from '../../tools/v-base.tools'; + +export enum VWebinarModalContext { + Modal = 'modal', + PopOver = 'pop_over', +} + +export type VWebinarModalContextIdentifier = VWebinarModalContext | string; + +export enum VWebinarCssVar { + DialogToolbarHeight = 'dialog-toolbar-height', +} + +export enum VWebinarReturnAction {} + +export interface VWebinarSubscriber { + userId: string; + username: string; + webinarId: string; + connectionId: string; + peer: RTCPeerConnection; +} + +export type VWebinarReturnActionIdentifier = VWebinarReturnAction | string; + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\webinar\v-webinar-subscriber\v-webinar-subscriber.component.html + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\webinar\v-webinar-subscriber\v-webinar-subscriber.component.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\webinar\v-webinar-subscriber\v-webinar-subscriber.component.ts + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\webinar\v-webinar-view\v-webinar-view.component.html + + + + +
+ +
+ + +
+ + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\webinar\v-webinar-view\v-webinar-view.component.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\app\views\webinar\v-webinar-view\v-webinar-view.component.ts + +import { + Input, + Component, + ViewChild, + TemplateRef, + ChangeDetectionStrategy, + Output, + EventEmitter, +} from '@angular/core'; +import { + XFileDto, + XPersonDto, + XWebinarType, + isDefaultPersonDto, +} from 'x-saherelm-api-sdk'; +import { + getFieldNames, + XExceptionIDs, + nameofInstance, + isNullOrUndefined, + XResourceIdentifier, + isNullOrEmptyString, +} from 'x-framework-core'; +import { + getWebinarResourceID, + getWebinarTypeValues, + prepareVWebinarViewDtoFields, +} from '../v-webinar.tools'; +import { Subject } from 'rxjs'; +import { + XFormDatePickerControlConfig, + XFormDatePickerControlPickerPosition, +} from 'x-framework-components'; +import { + VBaseDtoComponent, + isModelStateLocked, + VBaseDtoPresentType, + VBaseDtoModelAction, + VModelFieldDescriptor, + VModelValidationResult, + VBaseDtoModelActionIdentifier, +} from '../../v-dto/v-dto.typings'; +import { + PersonPresentType, + PersonPresentTypeIdentifier, +} from '../../v-person/v-person.typings'; +import { + XSimpleFormSelectControlConfig, + XSimpleFormSelectControlOption, +} from '../../typings/simple.form.typings'; +import { Validators } from '@angular/forms'; +import { + VTagsPresenterAction, + VTagsPresenterModelState, + VTagsPresenterPresentType, + VTagsPresenterActionModel, + isVTagsPresenterPeresentType, + VTagsPresenterPresentTypeIdentfier, +} from '../../tags/v-tags-presenter/v-tags-presenter.typings'; +import { + VFilesPresenterAction, + VFilesPresenterModelState, + VFilesPresenterActionModel, + VFilesPresenterPresentType, + isVFilesPresenterPeresentType, + VFilesPresenterPresentTypeIdentfier, +} from '../../files/v-files-presenter/v-files-presenter.typings'; +import { defaultVWebinarViewDto, VWebinarViewDto } from '../v-webinar.typings'; +import { XSimpleListItemSlideOption } from '../../typings/simple.list-item.typings'; +import { VFileInputFilePrperties } from '../../files/v-file-input/v-file-input.typings'; + +@Component({ + selector: 'v-webinar-view', + templateUrl: './v-webinar-view.component.html', + styleUrls: ['./v-webinar-view.component.html'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class VWebinarViewComponent extends VBaseDtoComponent { + // + //#region Props ... + readonly defaultModel: VWebinarViewDto = { + ...defaultVWebinarViewDto, + }; + readonly FieldNames = getFieldNames(this.defaultModel); + + // + //#region Owner ... + private OWNER_REF: TemplateRef; + @ViewChild('ownerRef') + set ownerRef(v: TemplateRef) { + // + this.OWNER_REF = v; + this.handleAttachTemplates(); + } + get ownerRef() { + return this.OWNER_REF; + } + + // + @Input() + ownerPersentType: PersonPresentTypeIdentifier = PersonPresentType.AsCard; + //#endregion + + // + //#region Files ... + // + @Input() + showFiles: boolean = false; + + // + @Input() + showFilesLabel: boolean = false; + + @Input() + filesPresentType: VFilesPresenterPresentTypeIdentfier = + VFilesPresenterPresentType.AsList; + + @Input() + fileActionsProvider: ( + t: XFileDto | File, + ) => Array; + + // + @Output() + filesStateChanged = new EventEmitter(); + + // + canAttachFile: boolean = false; + filesState: VFilesPresenterModelState; + filesPresenterFileProperties: VFileInputFilePrperties = { + fileUploadMaxAllowedFiles: 1, + }; + filesPresenterActionProvider = new Subject(); + //#endregion + + // + //#region Tags ... + // + @Input() + showTags: boolean = false; + + // + @Input() + showTagsLabel: boolean = false; + + @Input() + tagsPresentType: VTagsPresenterPresentTypeIdentfier = + VTagsPresenterPresentType.AsList; + + @Input() + tagActionsProvider: (t: string) => Array; + + @Output() + tagsStateChanged = new EventEmitter(); + + // + canAttachTag: boolean = false; + tagsState: VTagsPresenterModelState; + tagsPresenterActionProvider = new Subject(); + //#endregion + + // + defaultFieldDescriptors: VModelFieldDescriptor[] = [ + // + // ID ... + { + hide: true, + hideLabel: true, + allowFieldForm: false, + key: this.FieldNames.id, + label: this.ResourceIDs.id, + value: this.defaultModel.id, + type: this.ModelFieldTypes.String, + } as VModelFieldDescriptor, + // + // Language ... + { + hide: true, + hideLabel: true, + allowFieldForm: false, + key: this.FieldNames.language, + label: this.ResourceIDs.language, + value: this.defaultModel.language, + type: this.ModelFieldTypes.String, + } as VModelFieldDescriptor, + // + // Owner ID ... + { + hide: true, + hideLabel: true, + allowFieldForm: false, + key: this.FieldNames.ownerId, + label: this.AppResourceIDs.author_id, + value: this.defaultModel.ownerId, + type: this.ModelFieldTypes.String, + } as VModelFieldDescriptor, + // + // Type ... + { + hide: false, + hideLabel: false, + allowFieldForm: true, + key: this.FieldNames.type, + value: this.defaultModel.type, + label: this.AppResourceIDs.type, + type: this.ModelFieldTypes.Number, + valueProvider: (value) => { + return this.resourceProvider(getWebinarResourceID(value)); + }, + formControlConfig: { + index: 0, + propName: this.FieldNames.type, + type: { + type: this.FormControlTypes.Select, + config: { + multiple: false, + options: getWebinarTypeValues().map((k) => { + return { + value: k, + viewValue: this.resourceProvider(getWebinarResourceID(k)), + } as XSimpleFormSelectControlOption; + }), + } as XSimpleFormSelectControlConfig, + }, + validators: { + validators: [Validators.required], + }, + eventHandlers: {}, + appearance: { + hidden: false, + disabled: false, + label: this.AppResourceIDs.type, + placeholder: this.AppResourceIDs.type, + appearance: this.FormControlAppearances.Outline, + icons: { + prefix: { + applyStateColor: true, + name: this.IconNames.label, + color: this.ColorNames.Tertiary, + }, + }, + }, + }, + } as VModelFieldDescriptor, + // + // Title ... + { + hide: false, + hideLabel: false, + allowFieldForm: true, + key: this.FieldNames.title, + value: this.defaultModel.title, + label: this.AppResourceIDs.title, + type: this.ModelFieldTypes.String, + formControlConfig: { + index: 1, + propName: this.FieldNames.title, + type: { + type: this.FormControlTypes.Text, + config: {}, + }, + validators: { + validators: [Validators.required], + }, + eventHandlers: {}, + focusHandler: new Subject(), + appearance: { + hidden: false, + disabled: false, + placeholder: this.AppResourceIDs.title, + appearance: this.FormControlAppearances.Outline, + icons: { + prefix: { + applyStateColor: true, + name: this.IconNames.label, + color: this.ColorNames.Tertiary, + }, + }, + }, + }, + } as VModelFieldDescriptor, + // + // Description ... + { + hide: false, + hideLabel: false, + allowFieldForm: true, + key: this.FieldNames.description, + value: this.defaultModel.description, + label: this.AppResourceIDs.description, + type: this.ModelFieldTypes.String, + formControlConfig: { + index: 2, + propName: this.FieldNames.description, + type: { + type: this.FormControlTypes.Text, + config: {}, + }, + validators: { + validators: [Validators.required], + }, + eventHandlers: {}, + appearance: { + hidden: false, + disabled: false, + placeholder: this.AppResourceIDs.description, + appearance: this.FormControlAppearances.Outline, + icons: { + prefix: { + applyStateColor: true, + name: this.IconNames.label, + color: this.ColorNames.Tertiary, + }, + }, + }, + }, + } as VModelFieldDescriptor, + // + // Enabled ... + { + hide: false, + hideLabel: false, + allowFieldForm: true, + key: this.FieldNames.enabled, + value: this.defaultModel.enabled, + label: this.AppResourceIDs.state, + type: this.ModelFieldTypes.Boolean, + valueProvider: (value: boolean) => { + // + let resource: XResourceIdentifier = undefined; + if (!!value) { + resource = this.AppResourceIDs.enabled; + } else { + resource = this.AppResourceIDs.disabled; + } + + // + return this.resourceProvider(resource); + }, + formControlConfig: { + index: 3, + propName: this.FieldNames.enabled, + type: { + type: this.FormControlTypes.CheckBox, + config: {}, + }, + validators: {}, + eventHandlers: {}, + appearance: { + hidden: false, + disabled: false, + label: this.AppResourceIDs.state, + placeholder: this.AppResourceIDs.state, + appearance: this.FormControlAppearances.Outline, + icons: {}, + }, + }, + } as VModelFieldDescriptor, + // + // Created On ... + { + hide: false, + hideLabel: false, + allowFieldForm: true, + key: this.FieldNames.createdOn, + value: this.defaultModel.createdOn, + label: this.AppResourceIDs.created_on, + type: this.ModelFieldTypes.Date, + valueProvider: (value: Date) => { + // + if (isNullOrUndefined(value)) { + return ''; + } + + // + return this.applyDefaultDate(value); + }, + formControlConfig: { + index: 2, + propName: this.FieldNames.createdOn, + type: { + type: this.FormControlTypes.Date, + config: { + iconColor: this.ColorNames.Tertiary, + toggleIcon: this.IconNames.calendar, + applyStateColor: true, + datePickerPosition: XFormDatePickerControlPickerPosition.Prefix, + } as XFormDatePickerControlConfig, + }, + validators: { + validators: [Validators.required], + }, + eventHandlers: {}, + appearance: { + hidden: false, + disabled: false, + placeholder: this.AppResourceIDs.created_on, + appearance: this.FormControlAppearances.Outline, + icons: { + prefix: { + applyStateColor: true, + name: this.IconNames.calendar, + color: this.ColorNames.Tertiary, + }, + }, + }, + }, + } as VModelFieldDescriptor, + // + // Owner ... + { + hide: false, + hideLabel: false, + showContentBelowLabel: true, + key: this.FieldNames.owner, + value: this.defaultModel.owner, + label: this.AppResourceIDs.author, + type: this.ModelFieldTypes.Object, + hasCustomTemplate: true, + template: undefined, + } as VModelFieldDescriptor, + ]; + //#endregion + + // + //#region LifeCycles ... + afterViewInit(): void { + super.afterViewInit(); + + // + this.handleAttachTemplates(); + this.handleCanAttachTagState(); + this.handleCanAttachFileState(); + } + + onChange(changeKeys: string[]): void { + super.onChange(changeKeys); + + // + let isChanged = false; + let applyChanges = false; + + // + // Model Changed ... + isChanged = changeKeys.includes(nameofInstance(this, 'model')); + if (isChanged) { + // + if (!isNullOrUndefined(this.model)) { + // + applyChanges = true; + this.handleCanAttachTagState(); + this.handleCanAttachFileState(); + } + } + + // + this.handleAttachTemplates(); + + // + if (applyChanges) { + this.detectChanges(); + } + } + //#endregion + + // + //#region UI Provider ... + canShowOwner(owner: any) { + return !isNullOrUndefined(owner) && !isDefaultPersonDto(owner); + } + + canShowCustomContent() { + return this.canShowTags() || this.canShowFiles(); + } + + canShowTags() { + return ( + !isNullOrUndefined(this.tagsPresentType) && + isVTagsPresenterPeresentType(this.tagsPresentType) && + this.tagsPresentType !== VTagsPresenterPresentType.AsNone + ); + } + + canShowFiles() { + return ( + !isNullOrUndefined(this.filesPresentType) && + isVFilesPresenterPeresentType(this.filesPresentType) && + this.filesPresentType !== VFilesPresenterPresentType.AsNone + ); + } + + /** + * Tags Reader Task ... + */ + getTagsReaderTask() { + // + let result = undefined; + + // + if (!isNullOrUndefined(this.model) && !isNullOrEmptyString(this.model.id)) { + result = + this.sharedService.saherElmAPIService.webinars.webinarProviderService.getTags( + this.model.id, + ); + } + + // + return result; + } + + /** + * Files Reader Task ... + */ + getFilesReaderTask() { + // + let result = undefined; + + // + if (!isNullOrUndefined(this.model) && !isNullOrEmptyString(this.model.id)) { + result = + this.sharedService.saherElmAPIService.webinars.webinarProviderService.getFiles( + this.model.id, + ); + } + + // + return result; + } + + /** + * in this special case we have to restrict + * attached files to 1 ... + * + * @returns + */ + getCanAttachFile() { + // + let result = this.canAttachFile; + + // + if (!isNullOrUndefined(this.filesState)) { + // + let filesCount = 0; + + // + if (this.filesState.hasOriginals) { + filesCount += this.filesState.originals!.length; + } + + // + if (this.filesState.hasNews) { + filesCount += this.filesState.news!.length; + } + + // + if (this.filesState.hasRemoveds) { + filesCount -= this.filesState.removeds!.length; + } + + // + result = result && filesCount < 1 && filesCount >= 0; + } + + // + return result; + } + //#endregion + + // + //#region Abstracts ... + /** + * Validate Webinar Model ... + * + * @param model + * @returns + */ + validate( + model?: Partial, + ): VModelValidationResult { + // + let result: VModelValidationResult = undefined; + + // + let isValid = false; + const validationErrors: string[] = []; + + // + model = { + ...this.model, + ...model, + }; + const normalized = this.normalize(model); + + // + // Handle Validation ... + isValid = + !isNullOrUndefined(normalized) && !this.isSameAsDefault(normalized); + if (!isValid) { + // + validationErrors.push( + this.getExceptionMessage(XExceptionIDs.InvalidArgs), + ); + } else { + // + // Check Other Validations ... + + // + // ID ... + + // + // Language ... + isValid = !isNullOrEmptyString(normalized.language); + if (!isValid) { + // + validationErrors.push( + `${this.resourceProvider( + this.ResourceIDs.language, + )}: ${this.getExceptionMessage(XExceptionIDs.InvalidArgs)}`, + ); + } + + // + // Title ... + isValid = !isNullOrEmptyString(normalized.title); + if (!isValid) { + // + validationErrors.push( + `${this.resourceProvider( + this.AppResourceIDs.title, + )}: ${this.getExceptionMessage(XExceptionIDs.InvalidArgs)}`, + ); + } + + // + // Description ... + isValid = !isNullOrEmptyString(normalized.description); + if (!isValid) { + // + validationErrors.push( + `${this.resourceProvider( + this.AppResourceIDs.description, + )}: ${this.getExceptionMessage(XExceptionIDs.InvalidArgs)}`, + ); + } + + // + // Type ... + isValid = normalized.type >= 0; + if (!isValid) { + // + validationErrors.push( + `${this.resourceProvider( + this.ResourceIDs.type, + )}: ${this.getExceptionMessage(XExceptionIDs.InvalidArgs)}`, + ); + } + } + + // + // Handle Result ... + + // + isValid = !this.hasChild(validationErrors); + + // + const isValidForAdd = isNullOrEmptyString(normalized.id); + const isValidForUpdate = !isNullOrEmptyString(normalized.id); + + // + result = { + isValid, + normalized, + isValidForAdd, + isValidForUpdate, + validationErrors, + }; + + // + return result; + } + + /** + * Prepare Specified Model ... + * + * @param model + * @returns + */ + prepare(model?: VWebinarViewDto): VWebinarViewDto { + return prepareVWebinarViewDtoFields(model); + } + + /** + * Handle Register Focus Handler for User Select By ... + * + * @param to + * @param from + */ + onPresentTypeChanged(to: string, from: string): void { + super.onPresentTypeChanged(to, from); + + // + this.handleCanAttachTagState(); + this.handleCanAttachFileState(); + + // + if (to !== this.PresentTypes.AsForm) { + this.isFocusedHandlerRegistered = false; + } + + // + this.registerFieldFocusHandler(this.FieldNames.title); + } + + /** + * Handle Register Field Focus Handler ... + */ + onFieldDescriptorsReady(): void { + super.onFieldDescriptorsReady(); + + // + this.registerFieldFocusHandler(this.FieldNames.title); + } + + /** + * Try to Override Model Actions on Tags Presenter ... + * + * @param action + */ + onDtoModelAction(action: VBaseDtoModelActionIdentifier): void { + // + // Validate ... + if (isNullOrEmptyString(action)) { + return; + } + + // + // Cancel ... + if (action === VBaseDtoModelAction.Cancel) { + return; + } + + // + // Reset ... + if (action === VBaseDtoModelAction.Reset) { + // + // Reset Files ... + this.filesPresenterActionProvider.next({ + action: VFilesPresenterAction.Reset, + }); + + // + // Reset Tags ... + this.tagsPresenterActionProvider.next({ + action: VTagsPresenterAction.Reset, + }); + return; + } + } + + /** + * Check Can Reset Model or not ... + * Override ... + * + * @returns + */ + canReset() { + // + const modelStateChanged = this.isModelStateChanged(); + const filesStateChanged = + !isNullOrUndefined(this.filesState) && + (this.filesState.hasNews || this.filesState.hasRemoveds); + const tagsStateChanged = + !isNullOrUndefined(this.tagsState) && + (this.tagsState.hasNews || this.tagsState.hasRemoveds); + + // + const result = modelStateChanged || filesStateChanged || tagsStateChanged; + + // + return result; + } + + /** + * Check Can Add or Update Model or not ... + * Override ... + * + * @returns + */ + canAddOrUpdate() { + // + const state = this.getState(); + const modelStateChanged = this.isModelStateHasValidChanges(); + const filesStateChanged = + !isNullOrUndefined(this.filesState) && + (this.filesState.hasNews || this.filesState.hasRemoveds); + const tagsStateChanged = + !isNullOrUndefined(this.tagsState) && + (this.tagsState.hasNews || this.tagsState.hasRemoveds); + + // + let result = false; + if (modelStateChanged) { + result = modelStateChanged; + } else { + // + result = + state.isValid && + !state.isDefault && + !this.hasChild(state.errors) && + ((!isNullOrUndefined(this.tagsState) && tagsStateChanged) || + (!isNullOrUndefined(this.filesState) && filesStateChanged)); + } + + // + return result; + } + + /** + * Handle DTO State Management ... + */ + handleDtoStateManagement(): void { + // + // By Calling this method MODEL_STATE Filled automatically ... + const state = this.getState(); + + // + // Check DTO State Has Locked or not ... + const filesHasLock = + !isNullOrUndefined(this.filesState) && + (this.filesState.hasNews || this.filesState.hasRemoveds); + const tagsHasLock = + !isNullOrUndefined(this.tagsState) && + (this.tagsState.hasNews || this.tagsState.hasRemoveds); + const modelHasLock = isModelStateLocked({ ...this.modelState }); + + // + const isLocked = filesHasLock || tagsHasLock || modelHasLock; + + // + this.lock = isLocked; + this.detectChanges(); + + // + this.changeNotifier.emit(this.modelState); + } + //#endregion + + // + //#region Handlers ... + /** + * Handle Tags Presenter State Changed ... + * + * @param event + * @returns + */ + handleTagsModelStateChanged(event: VTagsPresenterModelState) { + // + if (isNullOrUndefined(event)) { + return; + } + + // + this.tagsState = { + ...event, + }; + + // + // Notify Change Happens ... + this.changeSubject.next(); + this.tagsStateChanged.emit(this.tagsState); + } + + /** + * Handle Files Presenter State Changed ... + * + * @param event + * @returns + */ + handleFilesModelStateChanged(event: VFilesPresenterModelState) { + // + if (isNullOrUndefined(event)) { + return; + } + + // + this.filesState = { + ...event, + }; + + // + // Notify Change Happens ... + this.changeSubject.next(); + this.filesStateChanged.emit(this.filesState); + } + //#endregion + + // + //#region Private ... + /** + * Handle Attach Templates to Field Descriptors ... + * + * @returns + */ + private handleAttachTemplates() { + // + let applyChanges = false; + + // + if (isNullOrUndefined(this.ownerRef)) { + // + // setTimeout(() => { + // this.handleAttachTemplates(); + // }, 100); + return; + } + + // + this.defaultFieldDescriptors = this.fieldDescriptors.map((fd) => { + // + return { + ...fd, + template: + fd.key === this.FieldNames.owner && + fd.hasCustomTemplate && + isNullOrUndefined(fd.template) + ? this.ownerRef + : fd.template, + }; + }); + + // + if (!isNullOrUndefined(this.providedFieldDescriptors)) { + // + this.providedFieldDescriptors = this.providedFieldDescriptors.map( + (fd) => { + // + return { + ...fd, + template: + fd.key === this.FieldNames.owner && + fd.hasCustomTemplate && + isNullOrUndefined(fd.template) + ? this.ownerRef + : fd.template, + }; + }, + ); + } + + // + this.resetFieldDescriptors(); + } + + /** + * Handle Check Can Attach Tags or not ... + */ + private handleCanAttachTagState() { + // + let result = false; + + // + if (!isNullOrUndefined(this.model)) { + // + // Check Present Type ... + if (this.presentType === VBaseDtoPresentType.AsForm) { + result = true; + } + } + + // + this.canAttachTag = result; + } + + /** + * Handle Check Can Attach Files or not ... + */ + private handleCanAttachFileState() { + // + let result = false; + + // + if (!isNullOrUndefined(this.model)) { + // + // Check Present Type ... + if (this.presentType === VBaseDtoPresentType.AsForm) { + result = true; + } + } + + // + this.canAttachFile = result; + } + + // + //#region Focus Handler ... + // + isFocusedHandlerRegistered = false; + + /** + * Register Focus Handler ... + * + * @param field + * @returns + */ + private registerFieldFocusHandler(field: string) { + // + if (this.isFocusedHandlerRegistered) { + return; + } + + // + if (this.presentType === this.PresentTypes.AsForm) { + // + // Make Sure runs when Model Form Config Prepared ... + if (isNullOrUndefined(this.modelFormConfig)) { + // + setTimeout(() => this.registerFieldFocusHandler(field), 100); + return; + } + + // + this.isFocusedHandlerRegistered = true; + this.registerFocusHandler(this.modelFormConfig.name, field); + } + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\environments\environment.prod.ts + +import { XConfig, xSharedConfig } from '../app/config/app.config'; + +export const environment = { + production: true, + configurations: { + // + // Attach Shared Config ... + ...xSharedConfig, + + // + // Disable SSL Check ... + rejectUnauthorized: true, + + // + // Set Base Api Path ... + baseUrl: 'https://api.mabsut.ir/', + + // + hubServerRoute: 'https://api.mabsut.ir/', + + // + // Set Production State ... + isProd: true, + + // + // Set Logging state ... + enableLogging: false, + baseLogTag: 'xSaherElmClient', + } as XConfig +}; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\environments\environment.ts + +import { XConfig, xSharedConfig } from '../app/config/app.config'; + +export const environment = { + production: false, + configurations: { + // + // Attach Shared Config ... + ...xSharedConfig, + + // + // Disable SSL Check ... + rejectUnauthorized: true, + + // + // Set Base Api Path ... + baseUrl: 'https://localhost:5001/', + // baseUrl: 'https://192.168.1.110:5001/', + + // + hubServerRoute: 'https://localhost:5001/', + // hubServerRoute: 'https://192.168.1.110:5001/', + + // + // Set Production State ... + isProd: false, + + // + // Set Logging state ... + enableLogging: true, + baseLogTag: 'xSaherElmClient', + } as XConfig, +}; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\xClient\src\theme\theme.scss + +// +// XFramework Color Mechanism ... +:root { + /** Primary **/ + --x-color-primary: #3880ff; + --x-color-primary-shade: #3171e0; + --x-color-primary-tint: #4c8dff; + + --x-color-primary-contrast: #ffffff; + --x-color-primary-shade-contrast: #ffffff; + --x-color-primary-tint-contrast: #ffffff; + + /** Secondary **/ + --x-color-secondary: #0cd1e8; + --x-color-secondary-shade: #0bb8cc; + --x-color-secondary-tint: #24d6ea; + + --x-color-secondary-contrast: #ffffff; + --x-color-secondary-shade-contrast: #ffffff; + --x-color-secondary-tint-contrast: #ffffff; + + /** Tertiary **/ + --x-color-tertiary: #7044ff; + --x-color-tertiary-shade: #633ce0; + --x-color-tertiary-tint: #7e57ff; + + --x-color-tertiary-contrast: #ffffff; + --x-color-tertiary-shade-contrast: #ffffff; + --x-color-tertiary-tint-contrast: #ffffff; + + /** Success **/ + --x-color-success: #10dc60; + --x-color-success-shade: #0ec254; + --x-color-success-tint: #28e070; + + --x-color-success-contrast: #ffffff; + --x-color-success-shade-contrast: #ffffff; + --x-color-success-tint-contrast: #ffffff; + + /** Warning **/ + --x-color-warning: #ffce00; + --x-color-warning-shade: #e0b500; + --x-color-warning-tint: #ffd31a; + + --x-color-warning-contrast: #ffffff; + --x-color-warning-shade-contrast: #ffffff; + --x-color-warning-tint-contrast: #ffffff; + + /** Danger **/ + --x-color-danger: #f04141; + --x-color-danger-shade: #d33939; + --x-color-danger-tint: #f25454; + + --x-color-danger-contrast: #ffffff; + --x-color-danger-shade-contrast: #ffffff; + --x-color-danger-tint-contrast: #ffffff; + + /** Dark **/ + --x-color-dark: #222428; + --x-color-dark-shade: #1e2023; + --x-color-dark-tint: #383a3e; + + --x-color-dark-contrast: #ffffff; + --x-color-dark-shade-contrast: #ffffff; + --x-color-dark-tint-contrast: #ffffff; + + /** Medium **/ + --x-color-medium: #989aa2; + --x-color-medium-shade: #86888f; + --x-color-medium-tint: #a2a4ab; + + --x-color-medium-contrast: #ffffff; + --x-color-medium-shade-contrast: #ffffff; + --x-color-medium-tint-contrast: #ffffff; + + /** Light **/ + --x-color-light: #f4f5f8; + --x-color-light-shade: #d7d8da; + --x-color-light-tint: #f5f6f9; + + --x-color-light-contrast: #000000; + --x-color-light-shade-contrast: #000000; + --x-color-light-tint-contrast: #000000; +} + +/* Core CSS required for Ionic components to work properly */ +@import "~@ionic/angular/css/core.css"; + +/* Basic CSS for apps built with Ionic */ +@import "~@ionic/angular/css/normalize.css"; +@import "~@ionic/angular/css/structure.css"; +@import "~@ionic/angular/css/typography.css"; +@import "~@ionic/angular/css/display.css"; + +/* Optional CSS utils that can be commented out */ +@import "~@ionic/angular/css/padding.css"; +@import "~@ionic/angular/css/float-elements.css"; +@import "~@ionic/angular/css/text-alignment.css"; +@import "~@ionic/angular/css/text-transformation.css"; +@import "~@ionic/angular/css/flex-utils.css"; + +/* XFramework Theme */ +@import "~x-framework-components/theme/theme.scss"; + +/* Override Default XFramework's Body Background Color */ +body { + background-color: #ffffff; +} + +.code-block { + direction: ltr; + color: white; + margin: { + top: 6px; + bottom: 8px; + } + // white-space: pre; + word-spacing: normal; + word-break: normal; + background-color: black; + + // + overflow: hidden; + + // + .x-header { + direction: var(--ui-direction) !important; + } +} + +// +//#region Card .. +$border-radius: 8px; + +x-card.v-card, +.v-card > x-card { + .x-title { + margin-bottom: 8px; + font-size: 16px !important; + + color: var(--x-color-primary-tint-contrast); + } + + .x-sub-title { + margin-bottom: 0; + font-size: 12px !important; + + color: var(--x-color-primary-tint-contrast); + } + + mat-card { + border-top-left-radius: $border-radius; + border-top-right-radius: $border-radius; + } + + mat-card.mobile { + .mat-card-header { + border-top-left-radius: 0; + border-top-right-radius: 0; + } + } + + mat-card-header { + @include transition-duration(); + + // + background-color: var(--x-color-primary-tint); + + // + border-top-left-radius: $border-radius; + border-top-right-radius: $border-radius; + } + + mat-card-header:hover { + background-color: var(--x-color-primary-shade); + } +} + +x-card.v-card.v-no-margin-card, +.v-card.v-no-margin-card > x-card { + mat-card-content { + padding: 0 !important; + margin: 0 !important; + } +} +//#endregion + +.unselecteable { + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + + // + * { + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + } +} + +.x-auto-complete-margin { + margin-top: -1.34375em; +} + +.center-vertical { + position: relative; + top: 50%; + transform: translateY(-50%); +} + +.text-truncate { + display: inline-block; + overflow-x: hidden !important; + text-overflow: ellipsis !important; + white-space: nowrap; + min-width: 0; +} + +.x-dynamic-slot { + flex: 0 auto !important; +} + +x-button.x-disabled, +ion-button.button-disabled { + pointer-events: none; + opacity: 0.8; + filter: grayscale(100%); + cursor: not-allowed; +} diff --git a/Documents/GeneratedContext/xDashboard-xFrameworkComponentsHolder-20260809_235642.txt b/Documents/GeneratedContext/xDashboard-xFrameworkComponentsHolder-20260809_235642.txt new file mode 100644 index 0000000..632aa28 --- /dev/null +++ b/Documents/GeneratedContext/xDashboard-xFrameworkComponentsHolder-20260809_235642.txt @@ -0,0 +1,96003 @@ + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\public-api.ts + +/* + * Public API Surface of x-framework-components + */ + +// +//#region Export Components ... +// +//#region Component Modules ... +export * from './lib/x-ide/x-ide.module'; +export * from './lib/x-fab/x-fab.module'; +export * from './lib/x-map/x-map.module'; +export * from './lib/x-icon/x-icon.module'; +export * from './lib/x-card/x-card.module'; +export * from './lib/x-list/x-list.module'; +export * from './lib/x-form/x-form.module'; +export * from './lib/x-grid/x-grid.module'; +export * from './lib/x-page/x-page.module'; +export * from './lib/x-tabs/x-tabs.module'; +export * from './lib/x-tree/x-tree.module'; +export * from './lib/x-theme/x-theme.module'; +export * from './lib/x-table/x-table.module'; +export * from './lib/x-frame/x-frame.module'; +export * from './lib/x-empty/x-empty.module'; +export * from './lib/x-color/x-color.module'; +export * from './lib/dialogs/x-dialog.module'; +export * from './lib/x-button/x-button.module'; +export * from './lib/x-slider/x-slider.module'; +export * from './lib/x-slotter/x-slotter.module'; +export * from './lib/x-spinner/x-spinner.module'; +export * from './lib/x-stepper/x-stepper.module'; +export * from './lib/x-counter/x-counter.module'; +export * from './lib/x-markdown/x-markdown.module'; +export * from './lib/x-thumbnail/x-thumbnail.module'; +export * from './lib/x-data-pager/x-data-pager.module'; +export * from './lib/x-search-bar/x-search-bar.module'; +export * from './lib/x-action-bar/x-action-bar.module'; +export * from './lib/x-file-upload/x-file-upload.module'; +export * from './lib/x-progress-bar/x-progress-bar.module'; +export * from './lib/x-color-picker/x-color-picker.module'; +export * from './lib/x-data-scroller/x-data-scroller.module'; +export * from './lib/x-image-cropper/x-image-cropper.module'; +export * from './lib/x-tab-navigator/x-tab-navigator.module'; +export * from './lib/x-avatar-upload/x-avatar-upload.module'; +export * from './lib/x-file-drop-area/x-file-drop-area.module'; +export * from './lib/x-screen-capture/x-screen-capture.module'; +export * from './lib/x-model-presenter/x-model-presenter.module'; +export * from './lib/x-expandable-list/x-expandable-list.module'; +export * from './lib/x-expandable-panel/x-expandable-panel.module'; +export * from './lib/x-description-list/x-description-list.module'; +//#endregion + +// +//#region Component ItSelfd ... +export * from './lib/x-ide/x-ide.component'; +export * from './lib/x-fab/x-fab.component'; +export * from './lib/x-map/x-map.component'; +export * from './lib/x-form/x-form.component'; +export * from './lib/x-icon/x-icon.component'; +export * from './lib/x-card/x-card.component'; +export * from './lib/x-list/x-list.component'; +export * from './lib/x-grid/x-grid.component'; +export * from './lib/x-page/x-page.component'; +export * from './lib/x-tabs/x-tabs.component'; +export * from './lib/x-tree/x-tree.component'; +export * from './lib/x-color/x-color.component'; +export * from './lib/x-empty/x-empty.component'; +export * from './lib/x-frame/x-frame.component'; +export * from './lib/x-table/x-table.component'; +export * from './lib/x-button/x-button.component'; +export * from './lib/x-slider/x-slider.component'; +export * from './lib/x-tabs/x-tab/x-tab.component'; +export * from './lib/x-spinner/x-spinner.component'; +export * from './lib/x-slotter/x-slotter.component'; +export * from './lib/x-stepper/x-stepper.component'; +export * from './lib/x-counter/x-counter.component'; +export * from './lib/x-markdown/x-markdown.component'; +export * from './lib/x-stepper/x-step/x-step.component'; +export * from './lib/x-slotter/x-slot/x-slot.component'; +export * from './lib/x-thumbnail/x-thumbnail.component'; +export * from './lib/x-slider/x-slide/x-slide.component'; +export * from './lib/x-page/x-footer/x-footer.component'; +export * from './lib/x-data-pager/x-data-pager.component'; +export * from './lib/x-search-bar/x-search-bar.component'; +export * from './lib/x-action-bar/x-action-bar.component'; +export * from './lib/x-page/x-toolbar/x-toolbar.component'; +export * from './lib/dialogs/x-popover/x-popover.component'; +export * from './lib/x-file-upload/x-file-upload.component'; +export * from './lib/x-progress-bar/x-progress-bar.component'; +export * from './lib/x-color-picker/x-color-picker.component'; +export * from './lib/x-page/x-navigator/x-navigator.component'; +export * from './lib/x-data-scroller/x-data-scroller.component'; +export * from './lib/x-map/x-map-dialog/x-map-dialog.component'; +export * from './lib/x-image-cropper/x-image-cropper.component'; +export * from './lib/x-tab-navigator/x-tab-navigator.component'; +export * from './lib/x-avatar-upload/x-avatar-upload.component'; +export * from './lib/x-file-drop-area/x-file-drop-area.component'; +export * from './lib/x-screen-capture/x-screen-capture.component'; +export * from './lib/x-expandable-panel/x-panel/x-panel.component'; +export * from './lib/x-expandable-list/x-expandable-list.component'; +export * from './lib/x-model-presenter/x-model-presenter.component'; +export * from './lib/x-expandable-panel/x-expandable-panel.component'; +export * from './lib/x-description-list/x-description-list.component'; +export * from './lib/x-theme/x-theme-manager/x-theme-manager.component'; +export * from './lib/dialogs/x-selector-dialog/x-selector-dialog.component'; +export * from './lib/x-color-picker/x-color-picker-dialog/x-color-picker-dialog.component'; +export * from './lib/x-model-presenter/x-model-presenter-item/x-model-presenter-item.component'; +export * from './lib/x-description-list/x-description-list-item/x-description-list-item.component'; +//#endregion + +// +//#region Component Typings ... +export * from './lib/x-ide/x-ide.typings'; +export * from './lib/x-fab/x-fab.typings'; +export * from './lib/x-map/x-map.typings'; +export * from './lib/x-grid/x-grid.typings'; +export * from './lib/x-form/x-form.typings'; +export * from './lib/x-icon/x-icon.typings'; +export * from './lib/x-card/x-card.typings'; +export * from './lib/x-list/x-list.typings'; +export * from './lib/x-page/x-page.typings'; +export * from './lib/x-tabs/x-tabs.typings'; +export * from './lib/x-tree/x-tree.typings'; +export * from './lib/x-empty/x-empty.typings'; +export * from './lib/x-frame/x-frame.typings'; +export * from './lib/x-page/x-page.container'; +export * from './lib/x-color/x-color.typings'; +export * from './lib/x-theme/x-theme-typings'; +export * from './lib/x-table/x-table.typings'; +export * from './lib/x-button/x-button.typings'; +export * from './lib/x-slider/x-slider.typings'; +export * from './lib/x-stepper/x-stepper.typings'; +export * from './lib/x-slotter/x-slotter.typings'; +export * from './lib/x-counter/x-counter.typings'; +export * from './lib/x-markdown/x-markdown.typings'; +export * from './lib/x-thumbnail/x-thumbnail.typings'; +export * from './lib/x-page/x-footer/x-footer.typings'; +export * from './lib/x-data-pager/x-data-pager.typings'; +export * from './lib/x-action-bar/x-action-bar.typings'; +export * from './lib/x-search-bar/x-search-bar.typings'; +export * from './lib/x-page/x-toolbar/x-toolbar.typings'; +export * from './lib/dialogs/x-popover/x-popover.typings'; +export * from './lib/x-file-upload/x-file-upload.typings'; +export * from './lib/x-progress-bar/x-progress-bar.typings'; +export * from './lib/x-color-picker/x-color-picker.typings'; +export * from './lib/x-page/x-navigator/x-navigator.typings'; +export * from './lib/x-data-scroller/x-data-scroller.typings'; +export * from './lib/x-image-cropper/x-image-cropper.typings'; +export * from './lib/x-tab-navigator/x-tab-navigator.typings'; +export * from './lib/x-avatar-upload/x-avatar-upload.typings'; +export * from './lib/x-map/x-map-dialog/x-map-dialog.typings'; +export * from './lib/x-file-drop-area/x-file-drop-area.typings'; +export * from './lib/x-screen-capture/x-screen-capture.typings'; +export * from './lib/x-screen-capture/x-screen-capture.container'; +export * from './lib/x-expandable-list/x-expandable-list.typings'; +export * from './lib/x-model-presenter/x-model-presenter.typings'; +export * from './lib/x-expandable-panel/x-expandable-panel.typings'; +export * from './lib/x-description-list/x-description-list.typings'; +export * from './lib/x-theme/x-theme-manager/x-theme-manager.typings'; +export * from './lib/dialogs/x-selector-dialog/x-selector-dialog.typings'; +export * from './lib/x-color-picker/x-color-picker-dialog/x-color-picker-dialog.typings'; +export * from './lib/x-model-presenter/x-model-presenter-item/x-model-presenter-item.typings'; +export * from './lib/x-description-list/x-description-list-item/x-description-list-item.typings'; +//#endregion + +// +//#region Component Tools ... +export * from './lib/x-map/x-map.tools'; +export * from './lib/x-form/x-form.tools'; +export * from './lib/x-theme/x-theme.tools'; +export * from './lib/dialogs/x-popover/x-popover.tools'; +export * from './lib/x-form/validators/x-form-validators'; +export * from './lib/dialogs/x-popover/x-popover.service'; +export * from './lib/x-slider/x-slide.animation.providers'; +export * from './lib/x-page/x-navigator/x-navigator.tools'; +export * from './lib/x-tab-navigator/x-tab-navigator.tools'; +export * from './lib/x-map/x-map-dialog/x-map-dialog.service'; +export * from './lib/x-image-cropper/x-image-cropper.service'; +export * from './lib/x-screen-capture/x-screen-capture.service'; +export * from './lib/x-expandable-list/x-expandable-list.tools'; +export * from './lib/x-color-picker/directives/x-color-text.directive'; +export * from './lib/dialogs/x-selector-dialog/x-selector-dialog.service'; +export * from './lib/x-color-picker/x-color-picker-dialog/x-color-picker-dialog.service'; +//#endregion +//#endregion + +// +//#region Export Classes ... +// +export * from './lib/classes/x-base-page'; +export * from './lib/classes/x-base-component'; +export * from './lib/classes/i-x-base.container'; +export * from './lib/classes/i-x-file.container'; +export * from './lib/classes/i-x-toolbar-footer'; +export * from './lib/classes/x-base-action.model'; +export * from './lib/classes/x-base-dialog.service'; +export * from './lib/classes/x-base.dialog.component'; +export * from './lib/classes/x-base.observable.tools'; +export * from './lib/classes/x-base.dialog.component'; +export * from './lib/classes/i-x-card-empty.container'; +export * from './lib/classes/i-x-base-dialog.container'; +export * from './lib/classes/i-x-next-prev-reset.container'; +// +export * from './lib/dialogs/x-dialog/x-dialog.typings'; +export * from './lib/dialogs/x-dialog/x-dialog.component'; +//#endregion + +// +//#region Tokens ... +export * from './lib/tokens/x-injectable-tokens'; +//#endregion + +// +//#region Export Config ... +export * from './lib/config/x-framework-components.config'; +//#endregion + +// +//#region Export Modules ... +export * from './lib/modules/x-material.module'; +export * from './lib/x-framework-components.module'; +export * from './lib/modules/x-base-component.module'; +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\test.ts + +// This file is required by karma.conf.js and loads recursively all the .spec and framework files + +import 'zone.js'; +import 'zone.js/testing'; +import { getTestBed } from '@angular/core/testing'; +import { + BrowserDynamicTestingModule, + platformBrowserDynamicTesting +} from '@angular/platform-browser-dynamic/testing'; + +declare const require: { + context(path: string, deep?: boolean, filter?: RegExp): { + keys(): string[]; + (id: string): T; + }; +}; + +// First, initialize the Angular testing environment. +getTestBed().initTestEnvironment( + BrowserDynamicTestingModule, + platformBrowserDynamicTesting() +); +// Then we find all the tests. +const context = require.context('./', true, /\.spec\.ts$/); +// And load the modules. +context.keys().map(context); + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-framework-components.module.ts + +import { NgModule } from '@angular/core'; +import { IonicModule } from '@ionic/angular'; +import { CommonModule } from '@angular/common'; +import { RouterModule } from '@angular/router'; +import { XIDEModule } from './x-ide/x-ide.module'; +import { XFabModule } from './x-fab/x-fab.module'; +import { XMapModule } from './x-map/x-map.module'; +import { XIconModule } from './x-icon/x-icon.module'; +import { XCardModule } from './x-card/x-card.module'; +import { XListModule } from './x-list/x-list.module'; +import { XFormModule } from './x-form/x-form.module'; +import { XGridModule } from './x-grid/x-grid.module'; +import { XPageModule } from './x-page/x-page.module'; +import { XTabsModule } from './x-tabs/x-tabs.module'; +import { XFrameModule } from './x-frame/x-frame.module'; +import { XEmptyModule } from './x-empty/x-empty.module'; +import { XTableModule } from './x-table/x-table.module'; +import { XFrameworkCoreModule } from 'x-framework-core'; +import { XThemeModule } from './x-theme/x-theme.module'; +import { XColorModule } from './x-color/x-color.module'; +import { XSliderModule } from './x-slider/x-slider.module'; +import { XButtonModule } from './x-button/x-button.module'; +import { XDialogsModule } from './dialogs/x-dialog.module'; +import { XMaterialModule } from './modules/x-material.module'; +import { XSlotterModule } from './x-slotter/x-slotter.module'; +import { XSpinnerModule } from './x-spinner/x-spinner.module'; +import { XStepperModule } from './x-stepper/x-stepper.module'; +import { XCounterModule } from './x-counter/x-counter.module'; +import { XFrameworkServicesModule } from 'x-framework-services'; +import { XMarkdownModule } from './x-markdown/x-markdown.module'; +import { XThumbnailModule } from './x-thumbnail/x-thumbnail.module'; +import { XActionBarModule } from './x-action-bar/x-action-bar.module'; +import { XSearchBarModule } from './x-search-bar/x-search-bar.module'; +import { XDataPagerModule } from './x-data-pager/x-data-pager.module'; +import { XBaseComponentModule } from './modules/x-base-component.module'; +import { XFileUploadModule } from './x-file-upload/x-file-upload.module'; +import { XColorPickerModule } from './x-color-picker/x-color-picker.module'; +import { XProgressBarModule } from './x-progress-bar/x-progress-bar.module'; +import { XImageCropperModule } from './x-image-cropper/x-image-cropper.module'; +import { XTabNavigatorModule } from './x-tab-navigator/x-tab-navigator.module'; +import { XAvatarUploadModule } from './x-avatar-upload/x-avatar-upload.module'; +import { XDataScrollerModule } from './x-data-scroller/x-data-scroller.module'; +import { XFileDropAreaModule } from './x-file-drop-area/x-file-drop-area.module'; +import { XScreenCaptureModule } from './x-screen-capture/x-screen-capture.module'; +import { XExpandableListModule } from './x-expandable-list/x-expandable-list.module'; +import { XModelPresenterModule } from './x-model-presenter/x-model-presenter.module'; +import { XExpandablePanelModule } from './x-expandable-panel/x-expandable-panel.module'; +import { XDescriptionListModule } from './x-description-list/x-description-list.module'; + +@NgModule({ + declarations: [], + imports: [ + // + IonicModule, + RouterModule, + CommonModule, + + // + XDialogsModule, + + // + XMaterialModule, + XBaseComponentModule, + + // + XFrameworkCoreModule, + XFrameworkServicesModule, + + // + XIDEModule, + XFabModule, + XMapModule, + XPageModule, + XIconModule, + XFormModule, + XCardModule, + XListModule, + XTabsModule, + XGridModule, + XFrameModule, + XEmptyModule, + XColorModule, + XTableModule, + XThemeModule, + XButtonModule, + XSliderModule, + XSlotterModule, + XSpinnerModule, + XStepperModule, + XCounterModule, + XMarkdownModule, + XDataPagerModule, + XSearchBarModule, + XThumbnailModule, + XActionBarModule, + XFileUploadModule, + XProgressBarModule, + XColorPickerModule, + XDataScrollerModule, + XTabNavigatorModule, + XImageCropperModule, + XFileDropAreaModule, + XAvatarUploadModule, + XScreenCaptureModule, + XExpandableListModule, + XModelPresenterModule, + XExpandablePanelModule, + XDescriptionListModule, + ], + exports: [ + // + IonicModule, + RouterModule, + CommonModule, + + // + XDialogsModule, + + // + XMaterialModule, + XBaseComponentModule, + + // + XFrameworkCoreModule, + XFrameworkServicesModule, + + // + XIDEModule, + XFabModule, + XMapModule, + XPageModule, + XIconModule, + XFormModule, + XCardModule, + XListModule, + XTabsModule, + XGridModule, + XEmptyModule, + XFrameModule, + XColorModule, + XTableModule, + XThemeModule, + XButtonModule, + XSliderModule, + XSlotterModule, + XSpinnerModule, + XStepperModule, + XCounterModule, + XMarkdownModule, + XDataPagerModule, + XSearchBarModule, + XActionBarModule, + XThumbnailModule, + XFileUploadModule, + XProgressBarModule, + XColorPickerModule, + XDataScrollerModule, + XTabNavigatorModule, + XImageCropperModule, + XFileDropAreaModule, + XAvatarUploadModule, + XScreenCaptureModule, + XExpandableListModule, + XModelPresenterModule, + XExpandablePanelModule, + XDescriptionListModule, + ], +}) +export class XFrameworkComponentsModule { } + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\classes\i-x-base-dialog.container.ts + +import { XResourceIdentifier, XStandardType } from 'x-framework-core'; + +export interface XBaseDialogConfig { + title?: XStandardType; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\classes\i-x-base.container.ts + +import { Observable } from 'rxjs'; +import { Component } from '@angular/core'; +import { XBaseComponent } from './x-base-component'; + +// +//#region Container ... +export interface IXBaseContainer {} + +export abstract class XBaseContainer implements IXBaseContainer {} + +@Component({ + template: '', +}) +export abstract class XBaseContainerComponent extends XBaseComponent { + // + //#region Abstracts ... + public abstract extractProps(container?: any): Observable; + //#endregion + + // + //#region Props Actions ... + public async extractPropsAsync(container?: any): Promise { + return await this.getValueAsync(this.extractProps(container)); + } + //#endregion +} +//#endregion + +// +//#region ComponentContainer ... +export interface IXBaseComponentContainer {} + +export abstract class XBaseComponentContainer + implements IXBaseComponentContainer {} + +@Component({ + template: '', +}) +export abstract class XBaseComponentContainerComponent extends XBaseComponent { + // + //#region Abstracts ... + public abstract extractProps(container?: any): Observable; + //#endregion + + // + //#region Props Actions ... + public async extractPropsAsync(container?: any): Promise { + return await this.getValueAsync(this.extractProps(container)); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\classes\i-x-card-empty.container.ts + +import { + Input, + Inject, + NgZone, + Output, + Component, + Renderer2, + ElementRef, + Injectable, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XStandardType, + XOneOrManyType, + XColorIdentifier, + XResourceIdentifier, +} from 'x-framework-core'; +import { + XBaseContainer, + IXBaseContainer, + XBaseContainerComponent, +} from './i-x-base.container'; +import { + XCardActionModel, + XComponentCardContainer, + IXComponentCardContainer, +} from '../x-card/x-card.typings'; +import { + XEmptyContainer, + XEmptyActionModel, + IXComponentEmptyContainer, +} from '../x-empty/x-empty.typings'; +import { + XActionBarItem, + XActionBarActionModel, + XActionBarActionFiredModel, + XComponentActionBarContainer, + IXComponentActionBarContainer, +} from '../x-action-bar/x-action-bar.typings'; +import { + XSearchBarActionModel, + XSearchBarTypeIdentifier, + XSearchBarEventIdentifier, + XComponentSearchBarContainer, + IXComponentSearchBarContainer, +} from '../x-search-bar/x-search-bar.typings'; +import { concatMap, map } from 'rxjs/operators'; +import { Observable, Subject, forkJoin } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XIconIdentifier } from '../x-icon/x-icon.typings'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +// +//#region XCardEmptyContainer ... +// +type TXCardEmptyContainer = IXComponentCardContainer & + IXComponentEmptyContainer; +export interface IXCardEmptyContainer extends TXCardEmptyContainer { } + +// +@Injectable({ + providedIn: 'root', +}) +export class XCardEmptyContainer + extends XBaseContainer + implements IXCardEmptyContainer { + // + //#region XComponentCard ... + readonly isInPage: XStandardType = + this.cardPropertyProvider.isInPage; + readonly wrapWithCard: XStandardType = + this.cardPropertyProvider.wrapWithCard; + readonly cardShowFooter: XStandardType = + this.cardPropertyProvider.cardShowFooter; + readonly cardShowActions: XStandardType = + this.cardPropertyProvider.cardShowActions; + readonly cardColor: XStandardType = + this.cardPropertyProvider.cardColor; + readonly cardForegroundColor: XStandardType = + this.cardPropertyProvider.cardForegroundColor; + readonly cardTitle: XStandardType = + this.cardPropertyProvider.cardTitle; + readonly cardActionProvider: Subject = + this.cardPropertyProvider.cardActionProvider; + readonly cardSubTitle: XStandardType = + this.cardPropertyProvider.cardSubTitle; + readonly cardCssClass: XStandardType> = + this.cardPropertyProvider.cardCssClass; + //#endregion + + // + //#region XComponentEmpty ... + // + readonly showEmpty: XStandardType = true; + readonly emptyHasRefresh: XStandardType = + this.emptyPropertyProvider.hasRefresh; + + // + readonly emptyActionProvider: Subject = + this.emptyPropertyProvider.actionProvider; + readonly emptyCssClass: XStandardType> = + this.emptyPropertyProvider.cssClass; + readonly emptyColor: XStandardType = + this.emptyPropertyProvider.color; + readonly emptyForegroundColor: XStandardType = + this.emptyPropertyProvider.foregroundColor; + readonly emptyMessageColor: XStandardType = + this.emptyPropertyProvider.messageColor; + readonly emptyForegroundMessageColor: XStandardType = + this.emptyPropertyProvider.foregroundMessageColor; + readonly emptyRefreshButtonColor: XStandardType = + this.emptyPropertyProvider.refreshButtonColor; + readonly emptyForegroundRefreshButtonColor: XStandardType = + this.emptyPropertyProvider.foregroundRefreshButtonColor; + readonly emptyMessage: XStandardType = + this.emptyPropertyProvider.message; + readonly emptyRefreshIcon: XStandardType = + this.emptyPropertyProvider.refreshIcon; + readonly emptyRefresh: EventEmitter = + this.emptyPropertyProvider.refresh; + //#endregion + + // + //#region Constructor ... + constructor( + protected cardPropertyProvider: XComponentCardContainer, + protected emptyPropertyProvider: XEmptyContainer + ) { + super(); + } + //#endregion +} + +// +@Injectable() +@Component({ + template: ``, +}) +export class XCardEmptyContainerComponent + extends XBaseContainerComponent + implements IXCardEmptyContainer { + // + //#region Props ... + // + //#region XComponentCard ... + @Input() + isInPage: XStandardType = this.propertyProvider.isInPage; + + @Input() + wrapWithCard: XStandardType = this.propertyProvider.wrapWithCard; + + @Input() + cardShowFooter: XStandardType = this.propertyProvider.cardShowFooter; + + @Input() + cardShowActions: XStandardType = + this.propertyProvider.cardShowActions; + + @Input() + cardColor: XStandardType = this.propertyProvider.cardColor; + + @Input() + cardForegroundColor: XStandardType = + this.propertyProvider.cardForegroundColor; + + @Input() + cardTitle: XStandardType = + this.propertyProvider.cardTitle; + + @Input() + cardActionProvider: Subject = + this.propertyProvider.cardActionProvider; + + @Input() + cardSubTitle: XStandardType = + this.propertyProvider.cardSubTitle; + + @Input() + cardCssClass: XStandardType> = + this.propertyProvider.cardCssClass; + //#endregion + + // + //#region XComponentEmpty ... + // + @Input() + showEmpty: XStandardType = this.propertyProvider.showEmpty; + + @Input() + emptyHasRefresh: XStandardType = + this.propertyProvider.emptyHasRefresh; + + @Input() + emptyActionProvider: Subject = + this.propertyProvider.emptyActionProvider; + + @Input() + emptyCssClass: XStandardType> = + this.propertyProvider.emptyCssClass; + + @Input() + emptyColor: XStandardType = + this.propertyProvider.emptyColor; + + @Input() + emptyForegroundColor: XStandardType = + this.propertyProvider.emptyForegroundColor; + + @Input() + emptyMessageColor: XStandardType = + this.propertyProvider.emptyMessageColor; + + @Input() + emptyForegroundMessageColor: XStandardType = + this.propertyProvider.emptyForegroundMessageColor; + + @Input() + emptyRefreshButtonColor: XStandardType = + this.propertyProvider.emptyRefreshButtonColor; + + @Input() + emptyForegroundRefreshButtonColor: XStandardType = + this.propertyProvider.emptyForegroundRefreshButtonColor; + + @Input() + emptyMessage: XStandardType = + this.propertyProvider.emptyMessage; + + @Input() + emptyRefreshIcon: XStandardType = + this.propertyProvider.emptyRefreshIcon; + + @Output() + emptyRefresh: EventEmitter = new EventEmitter(); + //#endregion + //#endregion + + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XCardEmptyContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXCardEmptyContainer + ): Observable { + return forkJoin({ + // + //#region XComponentCard ... + cardCssClass: this.getArrayValue( + container ? container.cardCssClass : this.cardCssClass + ), + isInPage: this.getValue(container ? container.isInPage : this.isInPage), + wrapWithCard: this.getValue( + container ? container.wrapWithCard : this.wrapWithCard + ), + cardShowFooter: this.getValue( + container ? container.cardShowFooter : this.cardShowFooter + ), + cardShowActions: this.getValue( + container ? container.cardShowActions : this.cardShowActions + ), + cardColor: this.getValue( + container ? container.cardColor : this.cardColor + ), + cardForegroundColor: this.getValue( + container ? container.cardForegroundColor : this.cardForegroundColor + ), + cardTitle: this.getValue( + container ? container.cardTitle : this.cardTitle + ), + cardSubTitle: this.getValue( + container ? container.cardSubTitle : this.cardSubTitle + ), + //#endregion + + // + //#region XComponentEmpty ... + // + showEmpty: this.getValue( + container ? container.showEmpty : this.showEmpty + ), + emptyHasRefresh: this.getValue( + container ? container.emptyHasRefresh : this.emptyHasRefresh + ), + + // + emptyCssClass: this.getValue( + container ? container.emptyCssClass : this.emptyCssClass + ), + emptyColor: this.getValue( + container ? container.emptyColor : this.emptyColor + ), + emptyForegroundColor: this.getValue( + container ? container.emptyForegroundColor : this.emptyForegroundColor + ), + emptyMessageColor: this.getValue( + container ? container.emptyMessageColor : this.emptyMessageColor + ), + emptyForegroundMessageColor: this.getValue( + container + ? container.emptyForegroundMessageColor + : this.emptyForegroundMessageColor + ), + emptyRefreshButtonColor: this.getValue( + container + ? container.emptyRefreshButtonColor + : this.emptyRefreshButtonColor + ), + emptyForegroundRefreshButtonColor: this.getValue( + container + ? container.emptyForegroundRefreshButtonColor + : this.emptyForegroundRefreshButtonColor + ), + emptyMessage: this.getValue( + container ? container.emptyMessage : this.emptyMessage + ), + emptyRefreshIcon: this.getValue( + container ? container.emptyRefreshIcon : this.emptyRefreshIcon + ), + //#endregion + }).pipe( + map((data) => { + return { + // + ...data, + + // + cardActionProvider: container + ? container.cardActionProvider + : this.cardActionProvider, + + // + emptyActionProvider: container + ? container.emptyActionProvider + : this.emptyActionProvider, + + // + emptyRefresh: this.emptyRefresh, + }; + }) + ); + } + //#endregion +} +//#endregion + +// +//#region XCardActionBarContainer ... +type TXCardActionBarContainer = IXComponentCardContainer & IXComponentActionBarContainer; +export interface IXCardActionBarContainer extends TXCardActionBarContainer { } + +@Injectable({ + providedIn: 'root', +}) +export class XCardActionBarContainer extends XBaseContainer implements IXCardActionBarContainer { + // + //#region Props ... + // + //#region ComponentCard Props ... + readonly isInPage: XStandardType = + this.cardPropertyProvider.isInPage; + readonly wrapWithCard: XStandardType = + this.cardPropertyProvider.wrapWithCard; + readonly cardActionProvider: Subject = + this.cardPropertyProvider.cardActionProvider; + readonly cardCssClass: XStandardType> = + this.cardPropertyProvider.cardCssClass; + readonly cardColor: XStandardType = + this.cardPropertyProvider.cardColor; + readonly cardForegroundColor: XStandardType = + this.cardPropertyProvider.cardForegroundColor; + readonly cardTitle: XStandardType = + this.cardPropertyProvider.cardTitle; + readonly cardSubTitle: XStandardType = + this.cardPropertyProvider.cardSubTitle; + readonly cardShowFooter: XStandardType = + this.cardPropertyProvider.cardShowFooter; + readonly cardShowActions: XStandardType = + this.cardPropertyProvider.cardShowActions; + //#endregion + + // + //#region ComponentActionBar Props ... + readonly showActionBar: XStandardType = + this.actionBarPropertyProvider.showActionBar; + readonly actionBarActionProvider: Subject = + this.actionBarPropertyProvider.actionBarActionProvider; + readonly actionBarCssClass: XStandardType> = + this.actionBarPropertyProvider.actionBarCssClass; + readonly actionBarColor: XStandardType = + this.actionBarPropertyProvider.actionBarColor; + readonly actionBarForegroundColor: XStandardType = + this.actionBarPropertyProvider.actionBarForegroundColor; + readonly actionBarActions: XStandardType> = + this.actionBarPropertyProvider.actionBarActions; + readonly actionBarActionFired: EventEmitter = + this.actionBarPropertyProvider.actionBarActionFired; + //#endregion + //#endregion + + // + //#region Constructor ... + constructor( + protected cardPropertyProvider: XComponentCardContainer, + protected actionBarPropertyProvider: XComponentActionBarContainer + ) { + super(); + } + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export class XCardActionBarContainerComponent + extends XBaseContainerComponent + implements IXCardActionBarContainer { + // + //#region Props ... + // + //#region ComponentCard Props ... + @Input() + isInPage: XStandardType = this.propertyProvider.isInPage; + + @Input() + wrapWithCard: XStandardType = this.propertyProvider.wrapWithCard; + + @Input() + cardCssClass: XStandardType> = + this.propertyProvider.cardCssClass; + + @Input() + cardColor: XStandardType = this.propertyProvider.cardColor; + + @Input() + cardForegroundColor: XStandardType = + this.propertyProvider.cardForegroundColor; + + @Input() + cardTitle: XStandardType = + this.propertyProvider.cardTitle; + + @Input() + cardSubTitle: XStandardType = + this.propertyProvider.cardSubTitle; + + @Input() + cardShowFooter: XStandardType = this.propertyProvider.cardShowFooter; + + @Input() + cardShowActions: XStandardType = + this.propertyProvider.cardShowActions; + + @Input() + cardActionProvider: Subject = + this.propertyProvider.cardActionProvider; + //#endregion + + // + //#region ComponentActionBar Props ... + @Input() + showActionBar: XStandardType = this.propertyProvider.showActionBar; + + @Input() + actionBarCssClass: XStandardType> = + this.propertyProvider.actionBarCssClass; + + @Input() + actionBarColor: XStandardType = + this.propertyProvider.actionBarColor; + + @Input() + actionBarForegroundColor: XStandardType = + this.propertyProvider.actionBarForegroundColor; + + @Input() + actionBarActionProvider: Subject = + this.propertyProvider.actionBarActionProvider; + + @Output() + actionBarActionFired: EventEmitter = + this.propertyProvider.actionBarActionFired; + + actionBarActions: XStandardType> = + this.propertyProvider.actionBarActions; + //#endregion + //#endregion + + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XCardActionBarContainer, + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstracts ... + public extractProps(container?: IXCardActionBarContainer): Observable { + return forkJoin({ + // + //#region XComponentCard ... + cardCssClass: this.getArrayValue( + container ? container.cardCssClass : this.cardCssClass + ), + isInPage: this.getValue(container ? container.isInPage : this.isInPage), + wrapWithCard: this.getValue( + container ? container.wrapWithCard : this.wrapWithCard + ), + cardShowFooter: this.getValue( + container ? container.cardShowFooter : this.cardShowFooter + ), + cardShowActions: this.getValue( + container ? container.cardShowActions : this.cardShowActions + ), + cardColor: this.getValue( + container ? container.cardColor : this.cardColor + ), + cardForegroundColor: this.getValue( + container ? container.cardForegroundColor : this.cardForegroundColor + ), + cardTitle: this.getValue( + container ? container.cardTitle : this.cardTitle + ), + cardSubTitle: this.getValue( + container ? container.cardSubTitle : this.cardSubTitle + ), + //#endregion + + // + //#region XComponentActionBar ... + showActionBar: this.getValue( + container ? container.showActionBar : this.showActionBar + ), + actionBarCssClass: this.getValue( + container ? container.actionBarCssClass : this.actionBarCssClass + ), + actionBarColor: this.getValue( + container ? container.actionBarColor : this.actionBarColor + ), + actionBarForegroundColor: this.getValue( + container + ? container.actionBarForegroundColor + : this.actionBarForegroundColor + ), + actionBarActions: this.getValue( + container ? container.actionBarActions : this.actionBarActions + ), + //#endregion + }).pipe( + map((data) => { + return { + // + ...data, + + // + cardActionProvider: container + ? container.cardActionProvider + : this.cardActionProvider, + + // + actionBarActionProvider: container + ? container.actionBarActionProvider + : this.actionBarActionProvider, + + // + actionBarActionFired: this.actionBarActionFired, + }; + }) + ); + } + //#endregion +} +//#endregion + +// +//#region XCardEmptyActionSearchBarContainer ... +// +type TXCardEmptyActionSearchBarContainer = IXCardEmptyContainer & + IXComponentActionBarContainer & + IXComponentSearchBarContainer; +export interface IXCardEmptyActionSearchBarContainer + extends IXBaseContainer, + TXCardEmptyActionSearchBarContainer { } + +// +@Injectable({ + providedIn: 'root', +}) +export class XCardEmptyActionSearchBarContainer + extends XCardEmptyContainer + implements IXCardEmptyActionSearchBarContainer { + // + //#region Props ... + // + //#region ActionBar ... + // + readonly showActionBar: XStandardType = + this.actionBarPropertyProvider.showActionBar; + + // + readonly actionBarActionProvider: Subject = + this.actionBarPropertyProvider.actionBarActionProvider; + readonly actionBarCssClass: XStandardType> = + this.actionBarPropertyProvider.actionBarCssClass; + + // + readonly actionBarColor: XStandardType = + this.actionBarPropertyProvider.actionBarColor; + readonly actionBarForegroundColor: XStandardType = + this.actionBarPropertyProvider.actionBarForegroundColor; + + // + readonly actionBarActions: XStandardType> = + this.actionBarPropertyProvider.actionBarActions; + + // + readonly actionBarActionFired: EventEmitter = + this.actionBarPropertyProvider.actionBarActionFired; + //#endregion + + // + //#region SearchBar ... + // + readonly showSearchBar: XStandardType = + this.searchBarPropertyProvider.showSearchBar; + + // + readonly searchBarActionProvider: Subject = + this.searchBarPropertyProvider.searchBarActionProvider; + readonly searchBarCssClass: XStandardType> = + this.searchBarPropertyProvider.searchBarCssClass; + + // + readonly searchBarColor: XStandardType = + this.searchBarPropertyProvider.searchBarColor; + readonly searchBarForegroundColor: XStandardType = + this.searchBarPropertyProvider.searchBarForegroundColor; + + // + readonly searchBarSearchInputColor: XStandardType = + this.searchBarPropertyProvider.searchBarSearchInputColor; + readonly searchBarForegroundSearchInputColor: XStandardType = + this.searchBarPropertyProvider.searchBarForegroundSearchInputColor; + + // + readonly searchBarType: XStandardType = + this.searchBarPropertyProvider.searchBarType; + readonly searchBarTitle: XStandardType = + this.searchBarPropertyProvider.searchBarTitle; + readonly searchBarValue: XStandardType = + this.searchBarPropertyProvider.searchBarValue; + readonly searchBarEnterKeyHint: XStandardType = + this.searchBarPropertyProvider.searchBarEnterKeyHint; + readonly searchBarKeyboardType: XStandardType = + this.searchBarPropertyProvider.searchBarKeyboardType; + readonly searchBarSpellcheck: XStandardType = + this.searchBarPropertyProvider.searchBarSpellcheck; + readonly searchBarDebounce: XStandardType = + this.searchBarPropertyProvider.searchBarDebounce; + + // + readonly searchBarShowCancelButton: XStandardType = + this.searchBarPropertyProvider.searchBarShowCancelButton; + readonly searchBarCancelButtonIcon: XStandardType = + this.searchBarPropertyProvider.searchBarCancelButtonIcon; + readonly searchBarCancelButtonTitle: XStandardType = + this.searchBarPropertyProvider.searchBarCancelButtonTitle; + readonly searchBarCancelButtonIconColor: XStandardType = + this.searchBarPropertyProvider.searchBarCancelButtonIconColor; + + // + readonly searchBarShowClearButton: XStandardType = + this.searchBarPropertyProvider.searchBarShowClearButton; + readonly searchBarClearButtonIcon: XStandardType = + this.searchBarPropertyProvider.searchBarClearButtonIcon; + readonly searchBarClearButtonIconColor: XStandardType = + this.searchBarPropertyProvider.searchBarClearButtonIconColor; + + // + readonly searchBarSearchButtonIcon: XStandardType = + this.searchBarPropertyProvider.searchBarSearchButtonIcon; + readonly searchBarSearchButtonIconColor: XStandardType = + this.searchBarPropertyProvider.searchBarSearchButtonIconColor; + + // + readonly searchBarQueryChange: EventEmitter = + this.searchBarPropertyProvider.searchBarQueryChange; + readonly searchBarOnEvent: EventEmitter = + this.searchBarPropertyProvider.searchBarOnEvent; + //#endregion + //#endregion + + // + //#region Constructor ... + constructor( + protected cardPropertyProvider: XComponentCardContainer, + protected emptyPropertyProvider: XEmptyContainer, + protected actionBarPropertyProvider: XComponentActionBarContainer, + protected searchBarPropertyProvider: XComponentSearchBarContainer + ) { + super(cardPropertyProvider, emptyPropertyProvider); + } + //#endregion +} + +// +@Injectable() +@Component({ + template: ``, +}) +export class XCardEmptyActionSearchBarContainerComponent + extends XCardEmptyContainerComponent + implements IXCardEmptyActionSearchBarContainer { + // + //#region Props ... + // + //#region ActionBar ... + // + @Input() + showActionBar: XStandardType = this.propertyProvider.showActionBar; + + // + @Input() + actionBarActionProvider: Subject = + this.propertyProvider.actionBarActionProvider; + + @Input() + actionBarCssClass: XStandardType> = + this.propertyProvider.actionBarCssClass; + + // + @Input() + actionBarColor: XStandardType = + this.propertyProvider.actionBarColor; + + @Input() + actionBarForegroundColor: XStandardType = + this.propertyProvider.actionBarForegroundColor; + + // + @Input() + actionBarActions: XStandardType> = + this.propertyProvider.actionBarActions; + + // + @Output() + actionBarActionFired: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region SearchBar ... + // + @Input() + showSearchBar: XStandardType = this.propertyProvider.showSearchBar; + + // + @Input() + searchBarActionProvider: Subject = + this.propertyProvider.searchBarActionProvider; + + @Input() + searchBarCssClass: XStandardType> = + this.propertyProvider.searchBarCssClass; + + // + @Input() + searchBarColor: XStandardType = + this.propertyProvider.searchBarColor; + + @Input() + searchBarForegroundColor: XStandardType = + this.propertyProvider.searchBarForegroundColor; + + // + @Input() + searchBarSearchInputColor: XStandardType = + this.propertyProvider.searchBarSearchInputColor; + + @Input() + searchBarForegroundSearchInputColor: XStandardType = + this.propertyProvider.searchBarForegroundSearchInputColor; + + // + @Input() + searchBarType: XStandardType = + this.propertyProvider.searchBarType; + + @Input() + searchBarTitle: XStandardType = + this.propertyProvider.searchBarTitle; + + @Input() + searchBarValue: XStandardType = this.propertyProvider.searchBarValue; + + @Input() + searchBarEnterKeyHint: XStandardType = + this.propertyProvider.searchBarEnterKeyHint; + + @Input() + searchBarKeyboardType: XStandardType = + this.propertyProvider.searchBarKeyboardType; + + @Input() + searchBarSpellcheck: XStandardType = + this.propertyProvider.searchBarSpellcheck; + + @Input() + searchBarDebounce: XStandardType = + this.propertyProvider.searchBarDebounce; + + // + @Input() + searchBarShowCancelButton: XStandardType = + this.propertyProvider.searchBarShowCancelButton; + + @Input() + searchBarCancelButtonIcon: XStandardType = + this.propertyProvider.searchBarCancelButtonIcon; + + @Input() + searchBarCancelButtonTitle: XStandardType = + this.propertyProvider.searchBarCancelButtonTitle; + + @Input() + searchBarCancelButtonIconColor: XStandardType = + this.propertyProvider.searchBarCancelButtonIconColor; + + // + @Input() + searchBarClearButtonIcon: XStandardType = + this.propertyProvider.searchBarClearButtonIcon; + + @Input() + searchBarClearButtonIconColor: XStandardType = + this.propertyProvider.searchBarClearButtonIconColor; + + // + @Input() + searchBarShowClearButton: XStandardType = + this.propertyProvider.searchBarShowClearButton; + + @Input() + searchBarSearchButtonIcon: XStandardType = + this.propertyProvider.searchBarSearchButtonIcon; + + @Input() + searchBarSearchButtonIconColor: XStandardType = + this.propertyProvider.searchBarSearchButtonIconColor; + + // + @Output() + searchBarQueryChange: EventEmitter = new EventEmitter(); + + @Output() + searchBarOnEvent: EventEmitter = new EventEmitter(); + //#endregion + //#endregion + + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XCardEmptyActionSearchBarContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXCardEmptyActionSearchBarContainer + ): Observable { + return super + .extractProps(container ? container : this) + .pipe( + concatMap((superData) => { + return forkJoin({ + // + // + //#region ActionBar ... + // + showActionBar: this.getValue( + container ? container.showActionBar : this.showActionBar + ), + actionBarCssClass: this.getValue( + container ? container.actionBarCssClass : this.actionBarCssClass + ), + actionBarColor: this.getValue( + container ? container.actionBarColor : this.actionBarColor + ), + actionBarForegroundColor: this.getValue( + container + ? container.actionBarForegroundColor + : this.actionBarForegroundColor + ), + actionBarActions: this.getValue( + container ? container.actionBarActions : this.actionBarActions + ), + //#endregion + + // + //#region SearchBar ... + // + showSearchBar: this.getValue( + container ? container.showSearchBar : this.showSearchBar + ), + searchBarCssClass: this.getValue( + container ? container.searchBarCssClass : this.searchBarCssClass + ), + searchBarColor: this.getValue( + container ? container.searchBarColor : this.searchBarColor + ), + searchBarForegroundColor: this.getValue( + container + ? container.searchBarForegroundColor + : this.searchBarForegroundColor + ), + searchBarSearchInputColor: this.getValue( + container + ? container.searchBarSearchInputColor + : this.searchBarSearchInputColor + ), + searchBarForegroundSearchInputColor: this.getValue( + container + ? container.searchBarForegroundSearchInputColor + : this.searchBarForegroundSearchInputColor + ), + + // + searchBarType: this.getValue( + container ? container.searchBarType : this.searchBarType + ), + searchBarTitle: this.getValue( + container ? container.searchBarTitle : this.searchBarTitle + ), + searchBarValue: this.getValue( + container ? container.searchBarValue : this.searchBarValue + ), + searchBarEnterKeyHint: this.getValue( + container + ? container.searchBarEnterKeyHint + : this.searchBarEnterKeyHint + ), + searchBarKeyboardType: this.getValue( + container + ? container.searchBarKeyboardType + : this.searchBarKeyboardType + ), + searchBarSpellcheck: this.getValue( + container + ? container.searchBarSpellcheck + : this.searchBarSpellcheck + ), + searchBarDebounce: this.getValue( + container ? container.searchBarDebounce : this.searchBarDebounce + ), + + // + searchBarShowCancelButton: this.getValue( + container + ? container.searchBarShowCancelButton + : this.searchBarShowCancelButton + ), + searchBarCancelButtonIcon: this.getValue( + container + ? container.searchBarCancelButtonIcon + : this.searchBarCancelButtonIcon + ), + searchBarCancelButtonTitle: this.getValue( + container + ? container.searchBarCancelButtonTitle + : this.searchBarCancelButtonTitle + ), + searchBarCancelButtonIconColor: this.getValue( + container + ? container.searchBarCancelButtonIconColor + : this.searchBarCancelButtonIconColor + ), + + // + searchBarShowClearButton: this.getValue( + container + ? container.searchBarShowClearButton + : this.searchBarShowClearButton + ), + searchBarClearButtonIcon: this.getValue( + container + ? container.searchBarClearButtonIcon + : this.searchBarClearButtonIcon + ), + searchBarClearButtonIconColor: this.getValue( + container + ? container.searchBarClearButtonIconColor + : this.searchBarClearButtonIconColor + ), + + // + searchBarSearchButtonIcon: this.getValue( + container + ? container.searchBarSearchButtonIcon + : this.searchBarSearchButtonIcon + ), + searchBarSearchButtonIconColor: this.getValue( + container + ? container.searchBarSearchButtonIconColor + : this.searchBarSearchButtonIconColor + ), + //#endregion + }).pipe( + map((data) => { + return { + superData, + data, + }; + }) + ); + }) + ) + .pipe( + map((info) => { + return { + // + ...info.superData, + + // + ...info.data, + + // + actionBarActionProvider: container + ? container.actionBarActionProvider + : this.actionBarActionProvider, + actionBarActionFired: this.actionBarActionFired, + + // + searchBarActionProvider: container + ? container.searchBarActionProvider + : this.searchBarActionProvider, + + // + searchBarQueryChange: this.searchBarQueryChange, + searchBarOnEvent: this.searchBarOnEvent, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\classes\i-x-file.container.ts + +import { + Input, + Inject, + NgZone, + Output, + Component, + Renderer2, + Injectable, + ElementRef, + TemplateRef, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XExceptionIDs, + XStandardType, + XOneOrManyType, + XColorIdentifier, + XResourceIdentifier, + XExceptionIdentifier, +} from 'x-framework-core'; +import { + XCardActionModel, + XComponentCardContainer, + IXComponentCardContainer, +} from '../x-card/x-card.typings'; +import { map } from 'rxjs/operators'; +import { forkJoin, Observable, Subject } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { + XFileDropAreaActionModel, + XComponentFileDropAreaContainer, + IXComponentFileDropAreaContainer, +} from '../x-file-drop-area/x-file-drop-area.typings'; +import { XIconIdentifier } from '../x-icon/x-icon.typings'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XBaseContainer, XBaseContainerComponent } from './i-x-base.container'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +// +//#region Max Allowed Files ... +export interface IXMaxAllowedFilesContainer { + maxAllowedFiles: XStandardType; + maxAllowedFilesException: XStandardType; +} + +@Injectable({ + providedIn: 'root', +}) +export class XMaxAllowedFilesContainer + implements Readonly +{ + // + //#region Props ... + readonly maxAllowedFiles: XStandardType = this.config.maxUploadFiles; + + readonly maxAllowedFilesException: XStandardType = + XExceptionIDs.MaxAllowedFilesExceeded; + //#endregion + + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + private config: XFrameworkComponentsConfig + ) {} + //#endregion +} +//#endregion + +// +//#region Max Allowed File Size ... +export interface IXMaxAllowedSizeContainer { + maxAllowedSize: XStandardType; + maxAllowedSizeException: XStandardType; +} + +@Injectable({ + providedIn: 'root', +}) +export class XMaxAllowedSizeContainer + implements Readonly +{ + // + //#region Props ... + readonly maxAllowedSize: XStandardType = this.config.maxAllowedSize; + + readonly maxAllowedSizeException: XStandardType = + XExceptionIDs.MaxFilesSizeExceeded; + //#endregion + + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + private config: XFrameworkComponentsConfig + ) {} + //#endregion +} +//#endregion + +// +//#region Min Allowed File Size ... +export interface IXMinAllowedFileSizeContainer { + minAllowedFileSize: XStandardType; + minAllowedFileSizeException: XStandardType; +} + +@Injectable({ + providedIn: 'root', +}) +export class XMinAllowedFileSizeContainer + implements Readonly +{ + // + //#region Props ... + readonly minAllowedFileSize: XStandardType = + this.config.minAllowedFileSize; + + readonly minAllowedFileSizeException: XStandardType = + XExceptionIDs.MinFilesSizeExceeded; + //#endregion + + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + private config: XFrameworkComponentsConfig + ) {} + //#endregion +} +//#endregion + +// +//#region Max Allowed Size ... +export interface IXMaxAllowedFileSizeContainer { + maxAllowedFileSize: XStandardType; + maxAllowedFileSizeException: XStandardType; +} + +@Injectable({ + providedIn: 'root', +}) +export class XMaxAllowedFileSizeContainer + implements Readonly +{ + // + //#region Props ... + readonly maxAllowedFileSize: XStandardType = + this.config.maxAllowedFileSize; + + readonly maxAllowedFileSizeException: XStandardType = + XExceptionIDs.MaxFilesSizeExceeded; + //#endregion + + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + private config: XFrameworkComponentsConfig + ) {} + //#endregion +} +//#endregion + +// +//#region Regular File ... +export interface IXFileRegularContainer { + emptyFileException: XStandardType; + notAllowedFileException: XStandardType; + allowedExtensions: XStandardType>; +} + +@Injectable({ + providedIn: 'root', +}) +export class XFileRegularContainer implements Readonly { + // + //#region Props ... + readonly emptyFileException: XStandardType = + XExceptionIDs.EmptyFile; + + readonly notAllowedFileException: XStandardType = + XExceptionIDs.InavlidFile; + + readonly allowedExtensions: XStandardType> = []; + //#endregion + + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + private config: XFrameworkComponentsConfig + ) {} + //#endregion +} +//#endregion + +// +//#region Combine ... +type XCombinedType = IXFileRegularContainer & + IXMaxAllowedFilesContainer & + IXMaxAllowedSizeContainer & + IXMinAllowedFileSizeContainer & + IXMaxAllowedFileSizeContainer; + +// tslint:disable-next-line:no-empty-interface +export interface IXFileContainer extends XCombinedType {} + +@Injectable({ + providedIn: 'root', +}) +export class XFileContainer implements Readonly { + // + //#region Props ... + // + readonly emptyFileException: XStandardType = + XExceptionIDs.EmptyFile; + + readonly notAllowedFileException: XStandardType = + XExceptionIDs.InavlidFile; + + readonly allowedExtensions: XStandardType> = []; + + // + readonly maxAllowedFiles: XStandardType = this.config.maxUploadFiles; + + readonly maxAllowedFilesException: XStandardType = + XExceptionIDs.MaxAllowedFilesExceeded; + + // + readonly maxAllowedSize: XStandardType = this.config.maxAllowedSize; + + readonly maxAllowedSizeException: XStandardType = + XExceptionIDs.MaxFilesSizeExceeded; + + // + readonly minAllowedFileSize: XStandardType = + this.config.minAllowedFileSize; + + readonly minAllowedFileSizeException: XStandardType = + XExceptionIDs.MinFilesSizeExceeded; + + // + readonly maxAllowedFileSize: XStandardType = + this.config.maxAllowedFileSize; + + readonly maxAllowedFileSizeException: XStandardType = + XExceptionIDs.MaxFilesSizeExceeded; + //#endregion + + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + private config: XFrameworkComponentsConfig + ) {} + //#endregion +} +//#endregion + +// +//#region XFileContainerComponent ... +@Injectable({ + providedIn: 'root', +}) +@Component({ + template: ` + + `, +}) +export abstract class XFileContainerComponent + extends XBaseContainerComponent + implements IXFileContainer +{ + // + //#region Props ... + // + @Input() + emptyFileException: XStandardType = + this.propertyProvider.emptyFileException; + + @Input() + notAllowedFileException: XStandardType = + this.propertyProvider.notAllowedFileException; + + @Input() + allowedExtensions: XStandardType> = + this.propertyProvider.allowedExtensions; + + // + @Input() + maxAllowedFiles: XStandardType = + this.propertyProvider.maxAllowedFiles; + + @Input() + maxAllowedFilesException: XStandardType = + this.propertyProvider.maxAllowedFilesException; + + // + @Input() + maxAllowedSize: XStandardType = this.propertyProvider.maxAllowedSize; + + @Input() + maxAllowedSizeException: XStandardType = + this.propertyProvider.maxAllowedSizeException; + + // + @Input() + minAllowedFileSize: XStandardType = + this.propertyProvider.minAllowedFileSize; + + @Input() + minAllowedFileSizeException: XStandardType = + this.propertyProvider.minAllowedFileSizeException; + + // + @Input() + maxAllowedFileSize: XStandardType = + this.propertyProvider.maxAllowedFileSize; + + @Input() + maxAllowedFileSizeException: XStandardType = + this.propertyProvider.maxAllowedFileSizeException; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XFileContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXFileContainer + ): Observable { + return forkJoin({ + // + emptyFileException: this.getValue( + container ? container.emptyFileException : this.emptyFileException + ), + notAllowedFileException: this.getValue( + container + ? container.notAllowedFileException + : this.notAllowedFileException + ), + allowedExtensions: this.getValue( + container ? container.allowedExtensions : this.allowedExtensions + ), + maxAllowedFiles: this.getValue( + container ? container.maxAllowedFiles : this.maxAllowedFiles + ), + maxAllowedFilesException: this.getValue( + container + ? container.maxAllowedFilesException + : this.maxAllowedFilesException + ), + maxAllowedSize: this.getValue( + container ? container.maxAllowedSize : this.maxAllowedSize + ), + maxAllowedSizeException: this.getValue( + container + ? container.maxAllowedSizeException + : this.maxAllowedSizeException + ), + minAllowedFileSize: this.getValue( + container ? container.minAllowedFileSize : this.minAllowedFileSize + ), + minAllowedFileSizeException: this.getValue( + container + ? container.minAllowedFileSizeException + : this.minAllowedFileSizeException + ), + maxAllowedFileSize: this.getValue( + container ? container.maxAllowedFileSize : this.maxAllowedFileSize + ), + maxAllowedFileSizeException: this.getValue( + container + ? container.maxAllowedFileSizeException + : this.maxAllowedFileSizeException + ), + }).pipe( + map((data) => { + return { + // + ...data, + + // + }; + }) + ); + } + //#endregion +} +//#endregion + +// +//#region XCardFileContainer ... +// +type TXCardFileContainer = IXComponentCardContainer & IXFileContainer; +export interface IXCardFileContainer extends TXCardFileContainer {} + +// +@Injectable({ + providedIn: 'root', +}) +export class XCardFileContainer + extends XBaseContainer + implements Readonly +{ + // + //#region XComponentCard ... + readonly isInPage: XStandardType = + this.cardPropertyProvider.isInPage; + readonly wrapWithCard: XStandardType = + this.cardPropertyProvider.wrapWithCard; + readonly cardShowFooter: XStandardType = + this.cardPropertyProvider.cardShowFooter; + readonly cardShowActions: XStandardType = + this.cardPropertyProvider.cardShowActions; + readonly cardColor: XStandardType = + this.cardPropertyProvider.cardColor; + readonly cardForegroundColor: XStandardType = + this.cardPropertyProvider.cardForegroundColor; + readonly cardTitle: XStandardType = + this.cardPropertyProvider.cardTitle; + readonly cardActionProvider: Subject = + this.cardPropertyProvider.cardActionProvider; + readonly cardSubTitle: XStandardType = + this.cardPropertyProvider.cardSubTitle; + readonly cardCssClass: XStandardType> = + this.cardPropertyProvider.cardCssClass; + //#endregion + + // + //#region XFile ... + // + readonly emptyFileException: XStandardType = + this.filePropertyProvider.emptyFileException; + + readonly notAllowedFileException: XStandardType = + this.filePropertyProvider.notAllowedFileException; + + readonly allowedExtensions: XStandardType> = + this.filePropertyProvider.allowedExtensions; + + // + readonly maxAllowedFiles: XStandardType = + this.filePropertyProvider.maxAllowedFiles; + + readonly maxAllowedFilesException: XStandardType = + this.filePropertyProvider.maxAllowedFilesException; + + // + readonly maxAllowedSize: XStandardType = + this.filePropertyProvider.maxAllowedSize; + + readonly maxAllowedSizeException: XStandardType = + this.filePropertyProvider.maxAllowedSizeException; + + // + readonly minAllowedFileSize: XStandardType = + this.filePropertyProvider.minAllowedFileSize; + + readonly minAllowedFileSizeException: XStandardType = + this.filePropertyProvider.minAllowedFileSizeException; + + // + readonly maxAllowedFileSize: XStandardType = + this.filePropertyProvider.maxAllowedFileSize; + + readonly maxAllowedFileSizeException: XStandardType = + this.filePropertyProvider.maxAllowedFileSizeException; + //#endregion + + // + //#region Constructor ... + constructor( + protected cardPropertyProvider: XComponentCardContainer, + protected filePropertyProvider: XFileContainer + ) { + super(); + } + //#endregion +} + +// +@Injectable() +@Component({ + template: ``, +}) +export class XCardFileContainerComponent + extends XBaseContainerComponent + implements IXCardFileContainer +{ + // + //#region Props ... + // + //#region XComponentCard ... + @Input() + isInPage: XStandardType = this.propertyProvider.isInPage; + + @Input() + wrapWithCard: XStandardType = this.propertyProvider.wrapWithCard; + + @Input() + cardShowFooter: XStandardType = this.propertyProvider.cardShowFooter; + + @Input() + cardShowActions: XStandardType = + this.propertyProvider.cardShowActions; + + @Input() + cardColor: XStandardType = this.propertyProvider.cardColor; + + @Input() + cardForegroundColor: XStandardType = + this.propertyProvider.cardForegroundColor; + + @Input() + cardTitle: XStandardType = + this.propertyProvider.cardTitle; + + @Input() + cardActionProvider: Subject = + this.propertyProvider.cardActionProvider; + + @Input() + cardSubTitle: XStandardType = + this.propertyProvider.cardSubTitle; + + @Input() + cardCssClass: XStandardType> = + this.propertyProvider.cardCssClass; + //#endregion + + // + //#region File ... + // + @Input() + emptyFileException: XStandardType = + this.propertyProvider.emptyFileException; + + @Input() + notAllowedFileException: XStandardType = + this.propertyProvider.notAllowedFileException; + + @Input() + allowedExtensions: XStandardType> = + this.propertyProvider.allowedExtensions; + + // + @Input() + maxAllowedFiles: XStandardType = + this.propertyProvider.maxAllowedFiles; + + @Input() + maxAllowedFilesException: XStandardType = + this.propertyProvider.maxAllowedFilesException; + + // + @Input() + maxAllowedSize: XStandardType = this.propertyProvider.maxAllowedSize; + + @Input() + maxAllowedSizeException: XStandardType = + this.propertyProvider.maxAllowedSizeException; + + // + @Input() + minAllowedFileSize: XStandardType = + this.propertyProvider.minAllowedFileSize; + + @Input() + minAllowedFileSizeException: XStandardType = + this.propertyProvider.minAllowedFileSizeException; + + // + @Input() + maxAllowedFileSize: XStandardType = + this.propertyProvider.maxAllowedFileSize; + + @Input() + maxAllowedFileSizeException: XStandardType = + this.propertyProvider.maxAllowedFileSizeException; + //#endregion + //#endregion + + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XCardFileContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXCardFileContainer + ): Observable { + return forkJoin({ + // + //#region XComponentCard ... + cardCssClass: this.getArrayValue( + container ? container.cardCssClass : this.cardCssClass + ), + isInPage: this.getValue(container ? container.isInPage : this.isInPage), + wrapWithCard: this.getValue( + container ? container.wrapWithCard : this.wrapWithCard + ), + cardShowFooter: this.getValue( + container ? container.cardShowFooter : this.cardShowFooter + ), + cardShowActions: this.getValue( + container ? container.cardShowActions : this.cardShowActions + ), + cardColor: this.getValue( + container ? container.cardColor : this.cardColor + ), + cardForegroundColor: this.getValue( + container ? container.cardForegroundColor : this.cardForegroundColor + ), + cardTitle: this.getValue( + container ? container.cardTitle : this.cardTitle + ), + cardSubTitle: this.getValue( + container ? container.cardSubTitle : this.cardSubTitle + ), + //#endregion + + // + //#region File ... + emptyFileException: this.getValue( + container ? container.emptyFileException : this.emptyFileException + ), + notAllowedFileException: this.getValue( + container + ? container.notAllowedFileException + : this.notAllowedFileException + ), + allowedExtensions: this.getValue( + container ? container.allowedExtensions : this.allowedExtensions + ), + maxAllowedFiles: this.getValue( + container ? container.maxAllowedFiles : this.maxAllowedFiles + ), + maxAllowedFilesException: this.getValue( + container + ? container.maxAllowedFilesException + : this.maxAllowedFilesException + ), + maxAllowedSize: this.getValue( + container ? container.maxAllowedSize : this.maxAllowedSize + ), + maxAllowedSizeException: this.getValue( + container + ? container.maxAllowedSizeException + : this.maxAllowedSizeException + ), + minAllowedFileSize: this.getValue( + container ? container.minAllowedFileSize : this.minAllowedFileSize + ), + minAllowedFileSizeException: this.getValue( + container + ? container.minAllowedFileSizeException + : this.minAllowedFileSizeException + ), + maxAllowedFileSize: this.getValue( + container ? container.maxAllowedFileSize : this.maxAllowedFileSize + ), + maxAllowedFileSizeException: this.getValue( + container + ? container.maxAllowedFileSizeException + : this.maxAllowedFileSizeException + ), + //#endregion + }).pipe( + map((data) => { + return { + // + ...data, + + // + cardActionProvider: container + ? container.cardActionProvider + : this.cardActionProvider, + }; + }) + ); + } + //#endregion +} +//#endregion + +// +//#region XCardFileFileDropAreaContainer ... +// +type TXCardFileFileDropContainer = IXComponentCardContainer & + IXFileContainer & + IXComponentFileDropAreaContainer; +export interface IXCardFileFileDropContainer + extends TXCardFileFileDropContainer {} + +// +@Injectable({ + providedIn: 'root', +}) +export class XCardFileFileDropContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + // + //#region ComponentCard ... + readonly isInPage: XStandardType = + this.cardPropertyProvider.isInPage; + readonly wrapWithCard: XStandardType = + this.cardPropertyProvider.wrapWithCard; + readonly cardShowFooter: XStandardType = + this.cardPropertyProvider.cardShowFooter; + readonly cardShowActions: XStandardType = + this.cardPropertyProvider.cardShowActions; + readonly cardColor: XStandardType = + this.cardPropertyProvider.cardColor; + readonly cardForegroundColor: XStandardType = + this.cardPropertyProvider.cardForegroundColor; + readonly cardTitle: XStandardType = + this.cardPropertyProvider.cardTitle; + readonly cardActionProvider: Subject = + this.cardPropertyProvider.cardActionProvider; + readonly cardSubTitle: XStandardType = + this.cardPropertyProvider.cardSubTitle; + readonly cardCssClass: XStandardType> = + this.cardPropertyProvider.cardCssClass; + //#endregion + + // + //#region File ... + // + readonly emptyFileException: XStandardType = + this.filePropertyProvider.emptyFileException; + + readonly notAllowedFileException: XStandardType = + this.filePropertyProvider.notAllowedFileException; + + readonly allowedExtensions: XStandardType> = + this.filePropertyProvider.allowedExtensions; + + // + readonly maxAllowedFiles: XStandardType = + this.filePropertyProvider.maxAllowedFiles; + + readonly maxAllowedFilesException: XStandardType = + this.filePropertyProvider.maxAllowedFilesException; + + // + readonly maxAllowedSize: XStandardType = + this.filePropertyProvider.maxAllowedSize; + + readonly maxAllowedSizeException: XStandardType = + this.filePropertyProvider.maxAllowedSizeException; + + // + readonly minAllowedFileSize: XStandardType = + this.filePropertyProvider.minAllowedFileSize; + + readonly minAllowedFileSizeException: XStandardType = + this.filePropertyProvider.minAllowedFileSizeException; + + // + readonly maxAllowedFileSize: XStandardType = + this.filePropertyProvider.maxAllowedFileSize; + + readonly maxAllowedFileSizeException: XStandardType = + this.filePropertyProvider.maxAllowedFileSizeException; + //#endregion + + // + //#region File Drop ... + // + readonly showFileDropArea: XStandardType = + this.fileDropPropertyProvider.showFileDropArea; + + // + readonly fileDropAreaActionProvider: Subject = + this.fileDropPropertyProvider.fileDropAreaActionProvider; + + readonly fileDropAreaCssClass: XStandardType> = + this.fileDropPropertyProvider.fileDropAreaCssClass; + + // + readonly fileDropAreaColor: XStandardType = + this.fileDropPropertyProvider.fileDropAreaColor; + + readonly fileDropAreaForegroundColor: XStandardType = + this.fileDropPropertyProvider.fileDropAreaForegroundColor; + + // + readonly fileDropAreaIcon: XStandardType = + this.fileDropPropertyProvider.fileDropAreaIcon; + + readonly fileDropAreaMessage: XStandardType = + this.fileDropPropertyProvider.fileDropAreaMessage; + + // + readonly fileDropAreaDisableFileCheck: XStandardType = + this.fileDropPropertyProvider.fileDropAreaDisableFileCheck; + + // + readonly fileDropAreaSupportsMultipleFiles: XStandardType = + this.fileDropPropertyProvider.fileDropAreaSupportsMultipleFiles; + + readonly fileDropAreaAllowedExtensions: XStandardType< + XOneOrManyType + > = this.fileDropPropertyProvider.fileDropAreaAllowedExtensions; + + // + readonly fileDropAreaDragDisabled: XStandardType = + this.fileDropPropertyProvider.fileDropAreaDragDisabled; + + readonly fileDropAreaContentTemplate: TemplateRef = + this.fileDropPropertyProvider.fileDropAreaContentTemplate; + + readonly fileDropAreaFilesChangeEmiter: EventEmitter> = + this.fileDropPropertyProvider.fileDropAreaFilesChangeEmiter; + + // + readonly fileDropAreaSuccessColor: XStandardType = + this.fileDropPropertyProvider.fileDropAreaSuccessColor; + + readonly fileDropAreaErrorColor: XStandardType = + this.fileDropPropertyProvider.fileDropAreaErrorColor; + + readonly fileDropAreaAccentColor: XStandardType = + this.fileDropPropertyProvider.fileDropAreaAccentColor; + + // + //#region ComponentsCard ... + readonly fileDropAreaIsInPage: XStandardType = + this.fileDropPropertyProvider.fileDropAreaIsInPage; + readonly fileDropAreaWrapWithCard: XStandardType = + this.fileDropPropertyProvider.fileDropAreaWrapWithCard; + readonly fileDropAreaCardShowFooter: XStandardType = + this.fileDropPropertyProvider.fileDropAreaCardShowFooter; + readonly fileDropAreaCardShowActions: XStandardType = + this.fileDropPropertyProvider.fileDropAreaCardShowActions; + readonly fileDropAreaCardColor: XStandardType = + this.fileDropPropertyProvider.fileDropAreaCardColor; + readonly fileDropAreaCardForegroundColor: XStandardType = + this.fileDropPropertyProvider.fileDropAreaCardForegroundColor; + readonly fileDropAreaCardTitle: XStandardType = + this.fileDropPropertyProvider.fileDropAreaCardTitle; + readonly fileDropAreaCardActionProvider: Subject = + this.fileDropPropertyProvider.fileDropAreaCardActionProvider; + readonly fileDropAreaCardSubTitle: XStandardType = + this.fileDropPropertyProvider.fileDropAreaCardSubTitle; + readonly fileDropAreaCardCssClass: XStandardType> = + this.fileDropPropertyProvider.fileDropAreaCardCssClass; + //#endregion + //#endregion + //#endregion + + // + //#region Constructor ... + constructor( + protected cardPropertyProvider: XComponentCardContainer, + protected fileDropPropertyProvider: XComponentFileDropAreaContainer, + protected filePropertyProvider: XFileContainer + ) { + super(); + } + //#endregion +} + +// +@Injectable() +@Component({ + template: ``, +}) +export class XCardFileFileDropContainerComponent + extends XBaseContainerComponent + implements IXCardFileContainer +{ + // + //#region Props ... + // + //#region XComponentCard ... + @Input() + isInPage: XStandardType = this.propertyProvider.isInPage; + + @Input() + wrapWithCard: XStandardType = this.propertyProvider.wrapWithCard; + + @Input() + cardShowFooter: XStandardType = this.propertyProvider.cardShowFooter; + + @Input() + cardShowActions: XStandardType = + this.propertyProvider.cardShowActions; + + @Input() + cardColor: XStandardType = this.propertyProvider.cardColor; + + @Input() + cardForegroundColor: XStandardType = + this.propertyProvider.cardForegroundColor; + + @Input() + cardTitle: XStandardType = + this.propertyProvider.cardTitle; + + @Input() + cardActionProvider: Subject = + this.propertyProvider.cardActionProvider; + + @Input() + cardSubTitle: XStandardType = + this.propertyProvider.cardSubTitle; + + @Input() + cardCssClass: XStandardType> = + this.propertyProvider.cardCssClass; + //#endregion + + // + //#region File ... + // + @Input() + emptyFileException: XStandardType = + this.propertyProvider.emptyFileException; + + @Input() + notAllowedFileException: XStandardType = + this.propertyProvider.notAllowedFileException; + + @Input() + allowedExtensions: XStandardType> = + this.propertyProvider.allowedExtensions; + + // + @Input() + maxAllowedFiles: XStandardType = + this.propertyProvider.maxAllowedFiles; + + @Input() + maxAllowedFilesException: XStandardType = + this.propertyProvider.maxAllowedFilesException; + + // + @Input() + maxAllowedSize: XStandardType = this.propertyProvider.maxAllowedSize; + + @Input() + maxAllowedSizeException: XStandardType = + this.propertyProvider.maxAllowedSizeException; + + // + @Input() + minAllowedFileSize: XStandardType = + this.propertyProvider.minAllowedFileSize; + + @Input() + minAllowedFileSizeException: XStandardType = + this.propertyProvider.minAllowedFileSizeException; + + // + @Input() + maxAllowedFileSize: XStandardType = + this.propertyProvider.maxAllowedFileSize; + + @Input() + maxAllowedFileSizeException: XStandardType = + this.propertyProvider.maxAllowedFileSizeException; + //#endregion + + // + //#region FileDrop ... + // + @Input() + fileDropAreaActionProvider: Subject = + this.propertyProvider.fileDropAreaActionProvider; + + @Input() + fileDropAreaCssClass: XStandardType> = + this.propertyProvider.fileDropAreaCssClass; + + // + @Input() + fileDropAreaColor: XStandardType = + this.propertyProvider.fileDropAreaColor; + + @Input() + fileDropAreaForegroundColor: XStandardType = + this.propertyProvider.fileDropAreaForegroundColor; + + // + @Input() + fileDropAreaIcon: XStandardType = + this.propertyProvider.fileDropAreaIcon; + + @Input() + fileDropAreaMessage: XStandardType = + this.propertyProvider.fileDropAreaMessage; + + // + @Input() + fileDropAreaDisableFileCheck: XStandardType = + this.propertyProvider.fileDropAreaDisableFileCheck; + + // + @Input() + fileDropAreaSupportsMultipleFiles: XStandardType = + this.propertyProvider.fileDropAreaSupportsMultipleFiles; + + @Input() + fileDropAreaAllowedExtensions: XStandardType> = + this.propertyProvider.fileDropAreaAllowedExtensions; + + // + @Input() + fileDropAreaDragDisabled: XStandardType = + this.propertyProvider.fileDropAreaDragDisabled; + + @Input() + fileDropAreaContentTemplate: TemplateRef = + this.propertyProvider.fileDropAreaContentTemplate; + + @Output() + fileDropAreaFilesChangeEmiter: EventEmitter> = + new EventEmitter(); + + // + @Input() + fileDropAreaSuccessColor: XStandardType = + this.propertyProvider.fileDropAreaSuccessColor; + + @Input() + fileDropAreaErrorColor: XStandardType = + this.propertyProvider.fileDropAreaErrorColor; + + @Input() + fileDropAreaAccentColor: XStandardType = + this.propertyProvider.fileDropAreaAccentColor; + + // + @Input() + showFileDropArea: XStandardType = + this.propertyProvider.showFileDropArea; + + // + //#region ComponentsCard ... + @Input() + fileDropAreaIsInPage: XStandardType = + this.propertyProvider.fileDropAreaIsInPage; + + @Input() + fileDropAreaWrapWithCard: XStandardType = + this.propertyProvider.fileDropAreaWrapWithCard; + + @Input() + fileDropAreaCardShowFooter: XStandardType = + this.propertyProvider.fileDropAreaCardShowFooter; + + @Input() + fileDropAreaCardShowActions: XStandardType = + this.propertyProvider.fileDropAreaCardShowActions; + + @Input() + fileDropAreaCardColor: XStandardType = + this.propertyProvider.fileDropAreaCardColor; + + @Input() + fileDropAreaCardForegroundColor: XStandardType = + this.propertyProvider.fileDropAreaCardForegroundColor; + + @Input() + fileDropAreaCardTitle: XStandardType = + this.propertyProvider.fileDropAreaCardTitle; + + @Input() + fileDropAreaCardActionProvider: Subject = + this.propertyProvider.fileDropAreaCardActionProvider; + + @Input() + fileDropAreaCardSubTitle: XStandardType = + this.propertyProvider.fileDropAreaCardSubTitle; + + @Input() + fileDropAreaCardCssClass: XStandardType> = + this.propertyProvider.fileDropAreaCardCssClass; + //#endregion + //#endregion + //#endregion + + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XCardFileFileDropContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXCardFileFileDropContainer + ): Observable { + return forkJoin({ + // + //#region XComponentCard ... + cardCssClass: this.getArrayValue( + container ? container.cardCssClass : this.cardCssClass + ), + isInPage: this.getValue(container ? container.isInPage : this.isInPage), + wrapWithCard: this.getValue( + container ? container.wrapWithCard : this.wrapWithCard + ), + cardShowFooter: this.getValue( + container ? container.cardShowFooter : this.cardShowFooter + ), + cardShowActions: this.getValue( + container ? container.cardShowActions : this.cardShowActions + ), + cardColor: this.getValue( + container ? container.cardColor : this.cardColor + ), + cardForegroundColor: this.getValue( + container ? container.cardForegroundColor : this.cardForegroundColor + ), + cardTitle: this.getValue( + container ? container.cardTitle : this.cardTitle + ), + cardSubTitle: this.getValue( + container ? container.cardSubTitle : this.cardSubTitle + ), + //#endregion + + // + //#region File ... + emptyFileException: this.getValue( + container ? container.emptyFileException : this.emptyFileException + ), + notAllowedFileException: this.getValue( + container + ? container.notAllowedFileException + : this.notAllowedFileException + ), + allowedExtensions: this.getValue( + container ? container.allowedExtensions : this.allowedExtensions + ), + maxAllowedFiles: this.getValue( + container ? container.maxAllowedFiles : this.maxAllowedFiles + ), + maxAllowedFilesException: this.getValue( + container + ? container.maxAllowedFilesException + : this.maxAllowedFilesException + ), + maxAllowedSize: this.getValue( + container ? container.maxAllowedSize : this.maxAllowedSize + ), + maxAllowedSizeException: this.getValue( + container + ? container.maxAllowedSizeException + : this.maxAllowedSizeException + ), + minAllowedFileSize: this.getValue( + container ? container.minAllowedFileSize : this.minAllowedFileSize + ), + minAllowedFileSizeException: this.getValue( + container + ? container.minAllowedFileSizeException + : this.minAllowedFileSizeException + ), + maxAllowedFileSize: this.getValue( + container ? container.maxAllowedFileSize : this.maxAllowedFileSize + ), + maxAllowedFileSizeException: this.getValue( + container + ? container.maxAllowedFileSizeException + : this.maxAllowedFileSizeException + ), + //#endregion + + // + //#region FileDrop ... + // + fileDropAreaCssClass: this.getValue( + container ? container.fileDropAreaCssClass : this.fileDropAreaCssClass + ), + fileDropAreaColor: this.getValue( + container ? container.fileDropAreaColor : this.fileDropAreaColor + ), + fileDropAreaForegroundColor: this.getValue( + container + ? container.fileDropAreaForegroundColor + : this.fileDropAreaForegroundColor + ), + fileDropAreaIcon: this.getValue( + container ? container.fileDropAreaIcon : this.fileDropAreaIcon + ), + fileDropAreaMessage: this.getValue( + container ? container.fileDropAreaMessage : this.fileDropAreaMessage + ), + fileDropAreaDisableFileCheck: this.getValue( + container + ? container.fileDropAreaDisableFileCheck + : this.fileDropAreaDisableFileCheck + ), + fileDropAreaSupportsMultipleFiles: this.getValue( + container + ? container.fileDropAreaSupportsMultipleFiles + : this.fileDropAreaSupportsMultipleFiles + ), + fileDropAreaAllowedExtensions: this.getValue( + container + ? container.fileDropAreaAllowedExtensions + : this.fileDropAreaAllowedExtensions + ), + fileDropAreaDragDisabled: this.getValue( + container + ? container.fileDropAreaDragDisabled + : this.fileDropAreaDragDisabled + ), + fileDropAreaContentTemplate: this.getValue( + container + ? container.fileDropAreaContentTemplate + : this.fileDropAreaContentTemplate + ), + fileDropAreaSuccessColor: this.getValue( + container + ? container.fileDropAreaSuccessColor + : this.fileDropAreaSuccessColor + ), + fileDropAreaErrorColor: this.getValue( + container + ? container.fileDropAreaErrorColor + : this.fileDropAreaErrorColor + ), + fileDropAreaAccentColor: this.getValue( + container + ? container.fileDropAreaAccentColor + : this.fileDropAreaAccentColor + ), + showFileDropArea: this.getValue( + container ? container.showFileDropArea : this.showFileDropArea + ), + fileDropAreaIsInPage: this.getValue( + container ? container.fileDropAreaIsInPage : this.fileDropAreaIsInPage + ), + fileDropAreaWrapWithCard: this.getValue( + container + ? container.fileDropAreaWrapWithCard + : this.fileDropAreaWrapWithCard + ), + fileDropAreaCardShowFooter: this.getValue( + container + ? container.fileDropAreaCardShowFooter + : this.fileDropAreaCardShowFooter + ), + fileDropAreaCardShowActions: this.getValue( + container + ? container.fileDropAreaCardShowActions + : this.fileDropAreaCardShowActions + ), + fileDropAreaCardColor: this.getValue( + container ? container.fileDropAreaCardColor : this.fileDropAreaCardColor + ), + fileDropAreaCardForegroundColor: this.getValue( + container + ? container.fileDropAreaCardForegroundColor + : this.fileDropAreaCardForegroundColor + ), + fileDropAreaCardTitle: this.getValue( + container ? container.fileDropAreaCardTitle : this.fileDropAreaCardTitle + ), + fileDropAreaCardSubTitle: this.getValue( + container + ? container.fileDropAreaCardSubTitle + : this.fileDropAreaCardSubTitle + ), + fileDropAreaCardCssClass: this.getValue( + container + ? container.fileDropAreaCardCssClass + : this.fileDropAreaCardCssClass + ), + //#endregion + }).pipe( + map((data) => { + return { + // + ...data, + + // + cardActionProvider: container + ? container.cardActionProvider + : this.cardActionProvider, + + // + fileDropAreaActionProvider: container + ? container.fileDropAreaActionProvider + : this.fileDropAreaActionProvider, + + // + fileDropAreaCardActionProvider: container + ? container.fileDropAreaCardActionProvider + : this.fileDropAreaCardActionProvider, + + // + fileDropAreaFilesChangeEmiter: this.fileDropAreaFilesChangeEmiter, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\classes\i-x-next-prev-reset.container.ts + +import { + Input, + Inject, + Output, + NgZone, + Renderer2, + Component, + Injectable, + ElementRef, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XResourceIDs, + XStandardType, + XOneOrManyType, + XColorIdentifier, + XResourceIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { + XBaseContainer, + IXBaseContainer, + XBaseContainerComponent, +} from './i-x-base.container'; +import { + XCardActionModel, + XComponentCardContainer, + IXComponentCardContainer, +} from '../x-card/x-card.typings'; +import { map } from 'rxjs/operators'; +import { Observable, forkJoin, Subject } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { IXButtonContainer, XButtonType } from '../x-button/x-button.typings'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XSlotNameIdentifier, XSlotName } from '../x-slotter/x-slotter.typings'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +// +export enum XNPRButtonType { + Next = 'next', + Prev = 'prev', + Reset = 'reset', +} + +// +export type XNPRButtonTypeIdentifier = XNPRButtonType | string; + +// +//#region Next ... +export interface IXNextHost { + canNext: XStandardType; + onNext: EventEmitter; +} + +export interface IXNextContainer extends IXBaseContainer { + // + //#region Next ... + hasNext: XStandardType; + disableNext: XStandardType; + nextConfirmer: () => XStandardType; + nextPosition: XStandardType; + nextCssClass: XStandardType>; + nextButton: XStandardType>; + //#endregion +} + +export const defaultNextButton: Partial = { + title: XResourceIDs.next, + type: XButtonType.Raised, + color: XColorWithBrightness.Primary, +}; + +@Injectable({ + providedIn: 'root', +}) +export abstract class XNextContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Next ... + readonly hasNext: XStandardType; + readonly disableNext: XStandardType; + readonly nextConfirmer: () => XStandardType; + readonly nextPosition: XStandardType = XSlotName.START; + readonly nextCssClass: XStandardType> = + 'ion-text-start'; + readonly nextButton: XStandardType> = { + ...defaultNextButton, + }; + //#endregion +} +//#endregion + +// +//#region Prev ... +export interface IXPrevHost { + canPrev: XStandardType; + onPrev: EventEmitter; +} + +export interface IXPrevContainer extends IXBaseContainer { + // + //#region Prev ... + hasPrev: XStandardType; + disablePrev: XStandardType; + prevConfirmer: () => XStandardType; + prevPosition: XStandardType; + prevCssClass: XStandardType>; + prevButton: XStandardType>; + //#endregion +} + +export const defaultPrevButton: Partial = { + type: XButtonType.Raised, + title: XResourceIDs.previous, + color: XColorWithBrightness.Primary, +}; + +@Injectable({ + providedIn: 'root', +}) +export abstract class XPrevContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Prev ... + readonly hasPrev: XStandardType; + readonly disablePrev: XStandardType; + readonly prevConfirmer: () => XStandardType; + readonly prevPosition: XStandardType = XSlotName.END; + readonly prevCssClass: XStandardType> = 'ion-text-end'; + readonly prevButton: XStandardType> = { + ...defaultPrevButton, + }; + //#endregion +} +//#endregion + +// +//#region Reset ... +export interface IXResetHost { + canReset: XStandardType; + onReset: EventEmitter; +} + +export interface IXResetContainer extends IXBaseContainer { + // + //#region Reset ... + hasReset: XStandardType; + disableReset: XStandardType; + resetConfirmer: () => XStandardType; + resetPosition: XStandardType; + resetCssClass: XStandardType>; + resetButton: XStandardType>; + //#endregion +} + +export const defaultResetButton: Partial = { + type: XButtonType.Raised, + title: XResourceIDs.reset, + color: XColorWithBrightness.Warning, +}; + +@Injectable({ + providedIn: 'root', +}) +export abstract class XResetContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Reset ... + readonly hasReset: XStandardType; + readonly disableReset: XStandardType; + readonly resetConfirmer: () => XStandardType; + readonly resetPosition: XStandardType = XSlotName.CENTER; + readonly resetCssClass: XStandardType> = + 'ion-text-center'; + readonly resetButton: XStandardType> = { + ...defaultResetButton, + }; + //#endregion +} +//#endregion + +// +//#region Combined ... +// +//#region Host ... +// +type XCompleteHostInterface = IXNextHost & IXPrevHost & IXResetHost; +// tslint:disable-next-line:no-empty-interface +export interface IXNextPrevResetHostContainer extends XCompleteHostInterface {} + +@Injectable({ + providedIn: 'root', +}) +export class XNextPrevResetHostContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + // + //#region Next ... + readonly canNext: XStandardType = true; + readonly onNext: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Prev ... + readonly canPrev: XStandardType = true; + readonly onPrev: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Reset ... + readonly canReset: XStandardType = true; + readonly onReset: EventEmitter = new EventEmitter(); + //#endregion + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XNextPrevResetHostContainerComponent + extends XBaseContainerComponent + implements IXNextPrevResetHostContainer +{ + // + //#region Props ... + // + //#region Next ... + @Input() + canNext: XStandardType = this.propertyProvider.canNext; + + @Output() + onNext: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Prev ... + @Input() + canPrev: XStandardType = this.propertyProvider.canPrev; + + @Output() + onPrev: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Reset ... + @Input() + canReset: XStandardType = this.propertyProvider.canReset; + + @Output() + onReset: EventEmitter = new EventEmitter(); + //#endregion + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XNextPrevResetHostContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXNextPrevResetHostContainer + ): Observable { + return forkJoin({ + // + canNext: this.getValue(container ? container.canNext : this.canNext), + canPrev: this.getValue(container ? container.canPrev : this.canPrev), + canReset: this.getValue(container ? container.canReset : this.canReset), + }).pipe( + map((data) => { + return { + // + ...data, + + // + onNext: this.onNext, + onPrev: this.onPrev, + onReset: this.onReset, + }; + }) + ); + } + //#endregion +} +//#endregion + +// +//#region XCardHost +// +type TXCardEmptyContainer = IXComponentCardContainer & + IXNextPrevResetHostContainer; +export interface IXCardNextPrevResetHostContainer + extends TXCardEmptyContainer {} + +// +@Injectable({ + providedIn: 'root', +}) +export class XCardNextPrevResetHostContainer + extends XBaseContainer + implements IXCardNextPrevResetHostContainer +{ + // + //#region Props ... + // + //#region XComponentCard ... + readonly isInPage: XStandardType = + this.cardPropertyProvider.isInPage; + readonly wrapWithCard: XStandardType = + this.cardPropertyProvider.wrapWithCard; + readonly cardShowFooter: XStandardType = + this.cardPropertyProvider.cardShowFooter; + readonly cardShowActions: XStandardType = + this.cardPropertyProvider.cardShowActions; + readonly cardColor: XStandardType = + this.cardPropertyProvider.cardColor; + readonly cardForegroundColor: XStandardType = + this.cardPropertyProvider.cardForegroundColor; + readonly cardTitle: XStandardType = + this.cardPropertyProvider.cardTitle; + readonly cardActionProvider: Subject = + this.cardPropertyProvider.cardActionProvider; + readonly cardSubTitle: XStandardType = + this.cardPropertyProvider.cardSubTitle; + readonly cardCssClass: XStandardType> = + this.cardPropertyProvider.cardCssClass; + //#endregion + + // + //#region XNextPrevResetHost ... + // + //#region Next ... + readonly canNext: XStandardType = + this.nextPrevResetHostPropertyProvider.canNext; + readonly onNext: EventEmitter = + this.nextPrevResetHostPropertyProvider.onNext; + //#endregion + + // + //#region Prev ... + readonly canPrev: XStandardType = + this.nextPrevResetHostPropertyProvider.canPrev; + readonly onPrev: EventEmitter = + this.nextPrevResetHostPropertyProvider.onPrev; + //#endregion + + // + //#region Reset ... + readonly canReset: XStandardType = + this.nextPrevResetHostPropertyProvider.canReset; + readonly onReset: EventEmitter = + this.nextPrevResetHostPropertyProvider.onReset; + //#endregion + //#endregion + //#endregion + + // + //#region Constructor ... + constructor( + protected cardPropertyProvider: XComponentCardContainer, + protected nextPrevResetHostPropertyProvider: XNextPrevResetHostContainer + ) { + super(); + } + //#endregion +} + +// +@Injectable() +@Component({ + template: ``, +}) +export class XCardNextPrevResetHostContainerComponent + extends XBaseContainerComponent + implements IXCardNextPrevResetHostContainer +{ + // + //#region Props ... + // + //#region XComponentCard ... + @Input() + isInPage: XStandardType = this.propertyProvider.isInPage; + + @Input() + wrapWithCard: XStandardType = this.propertyProvider.wrapWithCard; + + @Input() + cardShowFooter: XStandardType = this.propertyProvider.cardShowFooter; + + @Input() + cardShowActions: XStandardType = + this.propertyProvider.cardShowActions; + + @Input() + cardColor: XStandardType = this.propertyProvider.cardColor; + + @Input() + cardForegroundColor: XStandardType = + this.propertyProvider.cardForegroundColor; + + @Input() + cardTitle: XStandardType = + this.propertyProvider.cardTitle; + + @Input() + cardActionProvider: Subject = + this.propertyProvider.cardActionProvider; + + @Input() + cardSubTitle: XStandardType = + this.propertyProvider.cardSubTitle; + + @Input() + cardCssClass: XStandardType> = + this.propertyProvider.cardCssClass; + //#endregion + + // + //#region XNextPrevResetHost ... + // + //#region Next ... + @Input() + canNext: XStandardType = this.propertyProvider.canNext; + + @Output() + onNext: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Prev ... + @Input() + canPrev: XStandardType = this.propertyProvider.canPrev; + + @Output() + onPrev: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Reset ... + @Input() + canReset: XStandardType = this.propertyProvider.canReset; + + @Output() + onReset: EventEmitter = new EventEmitter(); + //#endregion + //#endregion + //#endregion + + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XCardNextPrevResetHostContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXCardNextPrevResetHostContainer + ): Observable { + return forkJoin({ + // + //#region XComponentCard ... + cardCssClass: this.getArrayValue( + container ? container.cardCssClass : this.cardCssClass + ), + isInPage: this.getValue(container ? container.isInPage : this.isInPage), + wrapWithCard: this.getValue( + container ? container.wrapWithCard : this.wrapWithCard + ), + cardShowFooter: this.getValue( + container ? container.cardShowFooter : this.cardShowFooter + ), + cardShowActions: this.getValue( + container ? container.cardShowActions : this.cardShowActions + ), + cardColor: this.getValue( + container ? container.cardColor : this.cardColor + ), + cardForegroundColor: this.getValue( + container ? container.cardForegroundColor : this.cardForegroundColor + ), + cardTitle: this.getValue( + container ? container.cardTitle : this.cardTitle + ), + cardSubTitle: this.getValue( + container ? container.cardSubTitle : this.cardSubTitle + ), + //#endregion + + // + //#region XNextPrevResetHost ... + // + canNext: this.getValue(container ? container.canNext : this.canNext), + canPrev: this.getValue(container ? container.canPrev : this.canPrev), + canReset: this.getValue(container ? container.canReset : this.canReset), + //#endregion + }).pipe( + map((data) => { + return { + // + ...data, + + // + cardActionProvider: container + ? container.cardActionProvider + : this.cardActionProvider, + + // + onNext: this.onNext, + onPrev: this.onPrev, + onReset: this.onReset, + }; + }) + ); + } + //#endregion +} +//#endregion + +// +//#region Interface ... +// +type TXNextPrevResetContainer = IXNextContainer & + IXPrevContainer & + IXResetContainer; +// tslint:disable-next-line:no-empty-interface +export interface IXNextPrevResetContainer + extends IXBaseContainer, + TXNextPrevResetContainer {} + +@Injectable({ + providedIn: 'root', +}) +export class XNextPrevResetContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + // + //#region Next ... + hasNext: XStandardType = this.nextPropertyProvider.hasNext; + disableNext: XStandardType = this.nextPropertyProvider.disableNext; + nextConfirmer: () => XStandardType = + this.nextPropertyProvider.nextConfirmer; + nextPosition: XStandardType = + this.nextPropertyProvider.nextPosition; + nextCssClass: XStandardType> = + this.nextPropertyProvider.nextCssClass; + nextButton: XStandardType> = + this.nextPropertyProvider.nextButton; + //#endregion + + // + //#region Prev ... + hasPrev: XStandardType = this.prevPropertyProvider.hasPrev; + disablePrev: XStandardType = this.prevPropertyProvider.disablePrev; + prevConfirmer: () => XStandardType = + this.prevPropertyProvider.prevConfirmer; + prevPosition: XStandardType = + this.prevPropertyProvider.prevPosition; + prevCssClass: XStandardType> = + this.prevPropertyProvider.prevCssClass; + prevButton: XStandardType> = + this.prevPropertyProvider.prevButton; + //#endregion + + // + //#region Reset ... + hasReset: XStandardType = this.resetpropertyProvider.hasReset; + disableReset: XStandardType = + this.resetpropertyProvider.disableReset; + resetConfirmer: () => XStandardType = + this.resetpropertyProvider.resetConfirmer; + resetPosition: XStandardType = + this.resetpropertyProvider.resetPosition; + resetCssClass: XStandardType> = + this.resetpropertyProvider.resetCssClass; + resetButton: XStandardType> = + this.resetpropertyProvider.resetButton; + //#endregion + //#endregion + + // + //#region Constructor ... + constructor( + protected prevPropertyProvider: XPrevContainer, + protected nextPropertyProvider: XNextContainer, + protected resetpropertyProvider: XResetContainer + ) { + super(); + } + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XNextPrevResetContainerComponent + extends XBaseContainerComponent + implements IXNextPrevResetContainer +{ + // + //#region Props ... + // + //#region Next ... + @Input() + hasNext: XStandardType = this.propertyProvider.hasNext; + + @Input() + disableNext: XStandardType = this.propertyProvider.disableNext; + + @Input() + nextConfirmer: () => XStandardType = + this.propertyProvider.nextConfirmer; + + @Input() + nextPosition: XStandardType = + this.propertyProvider.nextPosition; + + @Input() + nextCssClass: XStandardType> = + this.propertyProvider.nextCssClass; + + @Input() + nextButton: XStandardType> = + this.propertyProvider.nextButton; + //#endregion + + // + //#region Prev ... + @Input() + hasPrev: XStandardType = this.propertyProvider.hasPrev; + + @Input() + disablePrev: XStandardType = this.propertyProvider.disablePrev; + + @Input() + prevConfirmer: () => XStandardType = + this.propertyProvider.prevConfirmer; + + @Input() + prevPosition: XStandardType = + this.propertyProvider.prevPosition; + + @Input() + prevCssClass: XStandardType> = + this.propertyProvider.prevCssClass; + + @Input() + prevButton: XStandardType> = + this.propertyProvider.prevButton; + //#endregion + + // + //#region Reset ... + @Input() + hasReset: XStandardType = this.propertyProvider.hasReset; + + @Input() + disableReset: XStandardType = this.propertyProvider.disableReset; + + @Input() + resetConfirmer: () => XStandardType = + this.propertyProvider.resetConfirmer; + + @Input() + resetPosition: XStandardType = + this.propertyProvider.resetPosition; + + @Input() + resetCssClass: XStandardType> = + this.propertyProvider.resetCssClass; + + @Input() + resetButton: XStandardType> = + this.propertyProvider.resetButton; + //#endregion + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XNextPrevResetContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXNextPrevResetContainer + ): Observable { + return forkJoin({ + // + hasNext: this.getValue(container ? container.hasNext : this.hasNext), + disableNext: this.getValue( + container ? container.disableNext : this.disableNext + ), + nextConfirmer: this.getValue( + container ? container.nextConfirmer : this.nextConfirmer + ), + nextPosition: this.getValue( + container ? container.nextPosition : this.nextPosition + ), + nextCssClass: this.getValue( + container ? container.nextCssClass : this.nextCssClass + ), + nextButton: this.getValue( + container ? container.nextButton : this.nextButton + ), + hasPrev: this.getValue(container ? container.hasPrev : this.hasPrev), + disablePrev: this.getValue( + container ? container.disablePrev : this.disablePrev + ), + prevConfirmer: this.getValue( + container ? container.prevConfirmer : this.prevConfirmer + ), + prevPosition: this.getValue( + container ? container.prevPosition : this.prevPosition + ), + prevCssClass: this.getValue( + container ? container.prevCssClass : this.prevCssClass + ), + prevButton: this.getValue( + container ? container.prevButton : this.prevButton + ), + hasReset: this.getValue(container ? container.hasReset : this.hasReset), + disableReset: this.getValue( + container ? container.disableReset : this.disableReset + ), + resetConfirmer: this.getValue( + container ? container.resetConfirmer : this.resetConfirmer + ), + resetPosition: this.getValue( + container ? container.resetPosition : this.resetPosition + ), + resetCssClass: this.getValue( + container ? container.resetCssClass : this.resetCssClass + ), + resetButton: this.getValue( + container ? container.resetButton : this.resetButton + ), + }).pipe( + map((data) => { + return { + // + ...data, + + // + }; + }) + ); + } + //#endregion +} +//#endregion + +// +//#region IXNextPrevResetAndHostContainer ... +type TXNextPrevResetAndHostContainer = IXNextPrevResetContainer & + IXCardNextPrevResetHostContainer; +export interface IXNextPrevResetAndHostContainer + extends IXBaseContainer, + TXNextPrevResetAndHostContainer {} + +@Injectable({ + providedIn: 'root', +}) +export class XNextPrevResetAndHostContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + // + //#region Inteface ... + // + //#region Next ... + hasNext: XStandardType = this.nextPrevResetPropertyProvider.hasNext; + disableNext: XStandardType = + this.nextPrevResetPropertyProvider.disableNext; + nextConfirmer: () => XStandardType = + this.nextPrevResetPropertyProvider.nextConfirmer; + nextPosition: XStandardType = + this.nextPrevResetPropertyProvider.nextPosition; + nextCssClass: XStandardType> = + this.nextPrevResetPropertyProvider.nextCssClass; + nextButton: XStandardType> = + this.nextPrevResetPropertyProvider.nextButton; + //#endregion + + // + //#region Prev ... + hasPrev: XStandardType = this.nextPrevResetPropertyProvider.hasPrev; + disablePrev: XStandardType = + this.nextPrevResetPropertyProvider.disablePrev; + prevConfirmer: () => XStandardType = + this.nextPrevResetPropertyProvider.prevConfirmer; + prevPosition: XStandardType = + this.nextPrevResetPropertyProvider.prevPosition; + prevCssClass: XStandardType> = + this.nextPrevResetPropertyProvider.prevCssClass; + prevButton: XStandardType> = + this.nextPrevResetPropertyProvider.prevButton; + //#endregion + + // + //#region Reset ... + hasReset: XStandardType = + this.nextPrevResetPropertyProvider.hasReset; + disableReset: XStandardType = + this.nextPrevResetPropertyProvider.disableReset; + resetConfirmer: () => XStandardType = + this.nextPrevResetPropertyProvider.resetConfirmer; + resetPosition: XStandardType = + this.nextPrevResetPropertyProvider.resetPosition; + resetCssClass: XStandardType> = + this.nextPrevResetPropertyProvider.resetCssClass; + resetButton: XStandardType> = + this.nextPrevResetPropertyProvider.resetButton; + //#endregion + //#endregion + + // + //#region Host ... + // + //#region Next ... + readonly canNext: XStandardType = + this.cardNextPrevResetPropertyProvider.canNext; + readonly onNext: EventEmitter = + this.cardNextPrevResetPropertyProvider.onNext; + //#endregion + + // + //#region Prev ... + readonly canPrev: XStandardType = + this.cardNextPrevResetPropertyProvider.canPrev; + readonly onPrev: EventEmitter = + this.cardNextPrevResetPropertyProvider.onPrev; + //#endregion + + // + //#region Reset ... + readonly canReset: XStandardType = + this.cardNextPrevResetPropertyProvider.canReset; + readonly onReset: EventEmitter = + this.cardNextPrevResetPropertyProvider.onReset; + //#endregion + //#endregion + + // + //#region XComponentCard ... + readonly isInPage: XStandardType = + this.cardNextPrevResetPropertyProvider.isInPage; + readonly wrapWithCard: XStandardType = + this.cardNextPrevResetPropertyProvider.wrapWithCard; + readonly cardShowFooter: XStandardType = + this.cardNextPrevResetPropertyProvider.cardShowFooter; + readonly cardShowActions: XStandardType = + this.cardNextPrevResetPropertyProvider.cardShowActions; + readonly cardColor: XStandardType = + this.cardNextPrevResetPropertyProvider.cardColor; + readonly cardForegroundColor: XStandardType = + this.cardNextPrevResetPropertyProvider.cardForegroundColor; + readonly cardTitle: XStandardType = + this.cardNextPrevResetPropertyProvider.cardTitle; + readonly cardActionProvider: Subject = + this.cardNextPrevResetPropertyProvider.cardActionProvider; + readonly cardSubTitle: XStandardType = + this.cardNextPrevResetPropertyProvider.cardSubTitle; + readonly cardCssClass: XStandardType> = + this.cardNextPrevResetPropertyProvider.cardCssClass; + //#endregion + //#endregion + + // + //#region Constructor ... + constructor( + protected nextPrevResetPropertyProvider: XNextPrevResetContainer, + protected cardNextPrevResetPropertyProvider: XCardNextPrevResetHostContainer + ) { + super(); + } + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export class XNextPrevResetAndHostContainerComponent + extends XBaseContainerComponent + implements IXNextPrevResetAndHostContainer +{ + // + //#region Props ... + // + //#region XComponentCard ... + @Input() + isInPage: XStandardType = this.propertyProvider.isInPage; + + @Input() + wrapWithCard: XStandardType = this.propertyProvider.wrapWithCard; + + @Input() + cardShowFooter: XStandardType = this.propertyProvider.cardShowFooter; + + @Input() + cardShowActions: XStandardType = + this.propertyProvider.cardShowActions; + + @Input() + cardColor: XStandardType = this.propertyProvider.cardColor; + + @Input() + cardForegroundColor: XStandardType = + this.propertyProvider.cardForegroundColor; + + @Input() + cardTitle: XStandardType = + this.propertyProvider.cardTitle; + + @Input() + cardActionProvider: Subject = + this.propertyProvider.cardActionProvider; + + @Input() + cardSubTitle: XStandardType = + this.propertyProvider.cardSubTitle; + + @Input() + cardCssClass: XStandardType> = + this.propertyProvider.cardCssClass; + //#endregion + + // + //#region XNextPrevResetHost ... + // + //#region Next ... + @Input() + canNext: XStandardType = this.propertyProvider.canNext; + + @Output() + onNext: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Prev ... + @Input() + canPrev: XStandardType = this.propertyProvider.canPrev; + + @Output() + onPrev: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Reset ... + @Input() + canReset: XStandardType = this.propertyProvider.canReset; + + @Output() + onReset: EventEmitter = new EventEmitter(); + //#endregion + //#endregion + + // + //#region Props ... + // + //#region Next ... + @Input() + hasNext: XStandardType = this.propertyProvider.hasNext; + + @Input() + disableNext: XStandardType = this.propertyProvider.disableNext; + + @Input() + nextConfirmer: () => XStandardType = + this.propertyProvider.nextConfirmer; + + @Input() + nextPosition: XStandardType = + this.propertyProvider.nextPosition; + + @Input() + nextCssClass: XStandardType> = + this.propertyProvider.nextCssClass; + + @Input() + nextButton: XStandardType> = + this.propertyProvider.nextButton; + //#endregion + + // + //#region Prev ... + @Input() + hasPrev: XStandardType = this.propertyProvider.hasPrev; + + @Input() + disablePrev: XStandardType = this.propertyProvider.disablePrev; + + @Input() + prevConfirmer: () => XStandardType = + this.propertyProvider.prevConfirmer; + + @Input() + prevPosition: XStandardType = + this.propertyProvider.prevPosition; + + @Input() + prevCssClass: XStandardType> = + this.propertyProvider.prevCssClass; + + @Input() + prevButton: XStandardType> = + this.propertyProvider.prevButton; + //#endregion + + // + //#region Reset ... + @Input() + hasReset: XStandardType = this.propertyProvider.hasReset; + + @Input() + disableReset: XStandardType = this.propertyProvider.disableReset; + + @Input() + resetConfirmer: () => XStandardType = + this.propertyProvider.resetConfirmer; + + @Input() + resetPosition: XStandardType = + this.propertyProvider.resetPosition; + + @Input() + resetCssClass: XStandardType> = + this.propertyProvider.resetCssClass; + + @Input() + resetButton: XStandardType> = + this.propertyProvider.resetButton; + //#endregion + //#endregion + //#endregion + + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XNextPrevResetAndHostContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXNextPrevResetAndHostContainer + ): Observable { + return forkJoin({ + // + //#region XComponentCard ... + cardCssClass: this.getArrayValue( + container ? container.cardCssClass : this.cardCssClass + ), + isInPage: this.getValue(container ? container.isInPage : this.isInPage), + wrapWithCard: this.getValue( + container ? container.wrapWithCard : this.wrapWithCard + ), + cardShowFooter: this.getValue( + container ? container.cardShowFooter : this.cardShowFooter + ), + cardShowActions: this.getValue( + container ? container.cardShowActions : this.cardShowActions + ), + cardColor: this.getValue( + container ? container.cardColor : this.cardColor + ), + cardForegroundColor: this.getValue( + container ? container.cardForegroundColor : this.cardForegroundColor + ), + cardTitle: this.getValue( + container ? container.cardTitle : this.cardTitle + ), + cardSubTitle: this.getValue( + container ? container.cardSubTitle : this.cardSubTitle + ), + //#endregion + + // + //#region XNextPrevResetHost ... + // + canNext: this.getValue(container ? container.canNext : this.canNext), + canPrev: this.getValue(container ? container.canPrev : this.canPrev), + canReset: this.getValue(container ? container.canReset : this.canReset), + //#endregion + + // + //#region Interface ... + // + hasNext: this.getValue(container ? container.hasNext : this.hasNext), + disableNext: this.getValue( + container ? container.disableNext : this.disableNext + ), + nextConfirmer: this.getValue( + container ? container.nextConfirmer : this.nextConfirmer + ), + nextPosition: this.getValue( + container ? container.nextPosition : this.nextPosition + ), + nextCssClass: this.getValue( + container ? container.nextCssClass : this.nextCssClass + ), + nextButton: this.getValue( + container ? container.nextButton : this.nextButton + ), + hasPrev: this.getValue(container ? container.hasPrev : this.hasPrev), + disablePrev: this.getValue( + container ? container.disablePrev : this.disablePrev + ), + prevConfirmer: this.getValue( + container ? container.prevConfirmer : this.prevConfirmer + ), + prevPosition: this.getValue( + container ? container.prevPosition : this.prevPosition + ), + prevCssClass: this.getValue( + container ? container.prevCssClass : this.prevCssClass + ), + prevButton: this.getValue( + container ? container.prevButton : this.prevButton + ), + hasReset: this.getValue(container ? container.hasReset : this.hasReset), + disableReset: this.getValue( + container ? container.disableReset : this.disableReset + ), + resetConfirmer: this.getValue( + container ? container.resetConfirmer : this.resetConfirmer + ), + resetPosition: this.getValue( + container ? container.resetPosition : this.resetPosition + ), + resetCssClass: this.getValue( + container ? container.resetCssClass : this.resetCssClass + ), + resetButton: this.getValue( + container ? container.resetButton : this.resetButton + ), + //#endregion + }).pipe( + map((data) => { + return { + // + ...data, + + // + cardActionProvider: container + ? container.cardActionProvider + : this.cardActionProvider, + + // + onNext: this.onNext, + onPrev: this.onPrev, + onReset: this.onReset, + }; + }) + ); + } + //#endregion +} +//#endregion +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\classes\i-x-toolbar-footer.ts + +import { + Input, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + TemplateRef, + ChangeDetectorRef, +} from '@angular/core'; +import { + XStandardType, + XOneOrManyType, + XColorIdentifier, +} from 'x-framework-core'; +import { + XBackSlotIdentifier, + XLogoSlotIdentifier, + XMenuSlotIdentifier, + XTitleSlotIdentifier, + XContentSlotIdentifier, +} from '../x-page/x-page.typings'; +import { map } from 'rxjs/operators'; +import { + XFooterActionModel, + XComponentFooterContainer, + IXComponentFooterContainer, +} from '../x-page/x-footer/x-footer.typings'; +import { + XToolbarActionModel, + XComponentToolbarContainer, + IXComponentToolbarContainer, +} from '../x-page/x-toolbar/x-toolbar.typings'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { forkJoin, Observable, Subject } from 'rxjs'; +import { XManagerService } from 'x-framework-services'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XBaseContainer, XBaseContainerComponent } from './i-x-base.container'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +// +//#region Container ... +type TXToolbarFooterContainer = IXComponentToolbarContainer & + IXComponentFooterContainer; +export interface IXToolbarFooterContainer extends TXToolbarFooterContainer {} + +@Injectable({ + providedIn: 'root', +}) +export class XToolbarFooterContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + // + //#region Toolbar ... + // + readonly showToolbar: XStandardType = + this.toolbarPropertyProvider.showToolbar; + + // + readonly toolbarActionProvider: Subject = + this.toolbarPropertyProvider.toolbarActionProvider; + readonly toolbarCssClass: XStandardType> = + this.toolbarPropertyProvider.toolbarCssClass; + + // + readonly toolbarColor: XStandardType = + this.toolbarPropertyProvider.toolbarColor; + + // + readonly toolbarShowProgressBar: XStandardType = + this.toolbarPropertyProvider.toolbarShowProgressBar; + readonly toolbarProgressBarColor: XStandardType = + this.toolbarPropertyProvider.toolbarProgressBarColor; + + // + readonly toolbarShowTitle: XStandardType = + this.toolbarPropertyProvider.toolbarShowTitle; + readonly toolbarTitleSlot: XStandardType = + this.toolbarPropertyProvider.toolbarTitleSlot; + readonly toolbarTitle: XStandardType = + this.toolbarPropertyProvider.toolbarTitle; + + // + readonly toolbarShowSubTitle: XStandardType = + this.toolbarPropertyProvider.toolbarShowSubTitle; + readonly toolbarSubTitle: XStandardType = + this.toolbarPropertyProvider.toolbarSubTitle; + + // + readonly toolbarShowLogo: XStandardType = + this.toolbarPropertyProvider.toolbarShowLogo; + readonly toolbarLogoSlot: XStandardType = + this.toolbarPropertyProvider.toolbarLogoSlot; + readonly toolbarLogoUrl: XStandardType = + this.toolbarPropertyProvider.toolbarLogoUrl; + + // + readonly toolbarHasBack: XStandardType = + this.toolbarPropertyProvider.toolbarHasBack; + readonly toolbarShowBack: XStandardType = + this.toolbarPropertyProvider.toolbarShowBack; + readonly toolbarBackSlot: XStandardType = + this.toolbarPropertyProvider.toolbarBackSlot; + readonly toolbarBackHandler?: () => XStandardType = + this.toolbarPropertyProvider.toolbarBackHandler; + readonly toolbarDefaultHref: string = + this.toolbarPropertyProvider.toolbarDefaultHref; + + // + readonly toolbarHasMenu: XStandardType = + this.toolbarPropertyProvider.toolbarHasMenu; + readonly toolbarShowMenu: XStandardType = + this.toolbarPropertyProvider.toolbarShowMenu; + readonly toolbarAutoHideMenu: XStandardType = + this.toolbarPropertyProvider.toolbarAutoHideMenu; + readonly toolbarMenuSlot: XStandardType = + this.toolbarPropertyProvider.toolbarMenuSlot; + + // + readonly toolbarStartSlotButtonsTemplate: TemplateRef = + this.toolbarPropertyProvider.toolbarStartSlotButtonsTemplate; + readonly toolbarEndSlotButtonsTemplate: TemplateRef = + this.toolbarPropertyProvider.toolbarEndSlotButtonsTemplate; + + // + readonly toolbarContentSlot: XStandardType = + this.toolbarPropertyProvider.toolbarContentSlot; + readonly toolbarContentTemplate: TemplateRef = + this.toolbarPropertyProvider.toolbarContentTemplate; + //#endregion + + // + //#region Footer ... + // + readonly showFooter: XStandardType = + this.footerPropertyProvider.showFooter; + + // + readonly footerActionProvider: Subject = + this.footerPropertyProvider.footerActionProvider; + readonly footerCssClass: XStandardType> = + this.footerPropertyProvider.footerCssClass; + + // + readonly footerColor: XStandardType = + this.footerPropertyProvider.footerColor; + readonly footerContentTemplate: TemplateRef = + this.footerPropertyProvider.footerContentTemplate; + //#endregion + //#endregion + + // + //#region Constructor ... + constructor( + protected toolbarPropertyProvider: XComponentToolbarContainer, + protected footerPropertyProvider: XComponentFooterContainer + ) { + super(); + } + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XToolbarFooterContainerComponent + extends XBaseContainerComponent + implements IXToolbarFooterContainer +{ + // + //#region Props ... + // + //#region Toolbar ... + // + @Input() + showToolbar: XStandardType = this.propertyProvider.showToolbar; + + // + @Input() + toolbarActionProvider: Subject = + this.propertyProvider.toolbarActionProvider; + + @Input() + toolbarCssClass: XStandardType> = + this.propertyProvider.toolbarCssClass; + + // + @Input() + toolbarColor: XStandardType = + this.propertyProvider.toolbarColor; + + // + @Input() + toolbarShowProgressBar: XStandardType = + this.propertyProvider.toolbarShowProgressBar; + + @Input() + toolbarProgressBarColor: XStandardType = + this.propertyProvider.toolbarProgressBarColor; + + // + @Input() + toolbarShowTitle: XStandardType = + this.propertyProvider.toolbarShowTitle; + + @Input() + toolbarTitleSlot: XStandardType = + this.propertyProvider.toolbarTitleSlot; + + @Input() + toolbarTitle: XStandardType = this.propertyProvider.toolbarTitle; + + // + @Input() + toolbarShowSubTitle: XStandardType = + this.propertyProvider.toolbarShowSubTitle; + + @Input() + toolbarSubTitle: XStandardType = + this.propertyProvider.toolbarSubTitle; + + // + @Input() + toolbarShowLogo: XStandardType = + this.propertyProvider.toolbarShowLogo; + + @Input() + toolbarLogoSlot: XStandardType = + this.propertyProvider.toolbarLogoSlot; + + @Input() + toolbarLogoUrl: XStandardType = this.propertyProvider.toolbarLogoUrl; + + // + @Input() + toolbarHasBack: XStandardType = this.propertyProvider.toolbarHasBack; + + @Input() + toolbarShowBack: XStandardType = + this.propertyProvider.toolbarShowBack; + + @Input() + toolbarBackSlot: XStandardType = + this.propertyProvider.toolbarBackSlot; + + @Input() + toolbarBackHandler?: () => XStandardType = + this.propertyProvider.toolbarBackHandler; + + @Input() + toolbarDefaultHref: string = this.propertyProvider.toolbarDefaultHref; + + // + @Input() + toolbarHasMenu: XStandardType = this.propertyProvider.toolbarHasMenu; + + @Input() + toolbarShowMenu: XStandardType = + this.propertyProvider.toolbarShowMenu; + + @Input() + toolbarAutoHideMenu: XStandardType = + this.propertyProvider.toolbarAutoHideMenu; + + @Input() + toolbarMenuSlot: XStandardType = + this.propertyProvider.toolbarMenuSlot; + + // + @Input() + toolbarStartSlotButtonsTemplate: TemplateRef = + this.propertyProvider.toolbarStartSlotButtonsTemplate; + + @Input() + toolbarEndSlotButtonsTemplate: TemplateRef = + this.propertyProvider.toolbarEndSlotButtonsTemplate; + + // + @Input() + toolbarContentSlot: XStandardType = + this.propertyProvider.toolbarContentSlot; + + @Input() + toolbarContentTemplate: TemplateRef = + this.propertyProvider.toolbarContentTemplate; + //#endregion + + // + //#region Footer ... + // + @Input() + showFooter: XStandardType = this.propertyProvider.showFooter; + + // + @Input() + footerActionProvider: Subject = + this.propertyProvider.footerActionProvider; + + @Input() + footerCssClass: XStandardType> = + this.propertyProvider.footerCssClass; + + // + @Input() + footerColor: XStandardType = + this.propertyProvider.footerColor; + + @Input() + footerContentTemplate: TemplateRef = + this.propertyProvider.footerContentTemplate; + //#endregion + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XToolbarFooterContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXToolbarFooterContainer + ): Observable { + return forkJoin({ + // + //#region Toolbar ... + showToolbar: this.getValue( + container ? container.showToolbar : this.showToolbar + ), + toolbarCssClass: this.getValue( + container ? container.toolbarCssClass : this.toolbarCssClass + ), + toolbarColor: this.getValue( + container ? container.toolbarColor : this.toolbarColor + ), + toolbarShowProgressBar: this.getValue( + container + ? container.toolbarShowProgressBar + : this.toolbarShowProgressBar + ), + toolbarProgressBarColor: this.getValue( + container + ? container.toolbarProgressBarColor + : this.toolbarProgressBarColor + ), + toolbarShowTitle: this.getValue( + container ? container.toolbarShowTitle : this.toolbarShowTitle + ), + toolbarTitleSlot: this.getValue( + container ? container.toolbarTitleSlot : this.toolbarTitleSlot + ), + toolbarTitle: this.getValue( + container ? container.toolbarTitle : this.toolbarTitle + ), + toolbarShowSubTitle: this.getValue( + container ? container.toolbarShowSubTitle : this.toolbarShowSubTitle + ), + toolbarSubTitle: this.getValue( + container ? container.toolbarSubTitle : this.toolbarSubTitle + ), + toolbarShowLogo: this.getValue( + container ? container.toolbarShowLogo : this.toolbarShowLogo + ), + toolbarLogoSlot: this.getValue( + container ? container.toolbarLogoSlot : this.toolbarLogoSlot + ), + toolbarLogoUrl: this.getValue( + container ? container.toolbarLogoUrl : this.toolbarLogoUrl + ), + toolbarHasBack: this.getValue( + container ? container.toolbarHasBack : this.toolbarHasBack + ), + toolbarShowBack: this.getValue( + container ? container.toolbarShowBack : this.toolbarShowBack + ), + toolbarBackSlot: this.getValue( + container ? container.toolbarBackSlot : this.toolbarBackSlot + ), + toolbarBackHandler: this.getValue( + container ? container.toolbarBackHandler : this.toolbarBackHandler + ), + toolbarDefaultHref: this.getValue( + container ? container.toolbarDefaultHref : this.toolbarDefaultHref + ), + toolbarHasMenu: this.getValue( + container ? container.toolbarHasMenu : this.toolbarHasMenu + ), + toolbarShowMenu: this.getValue( + container ? container.toolbarShowMenu : this.toolbarShowMenu + ), + toolbarAutoHideMenu: this.getValue( + container ? container.toolbarAutoHideMenu : this.toolbarAutoHideMenu + ), + toolbarMenuSlot: this.getValue( + container ? container.toolbarMenuSlot : this.toolbarMenuSlot + ), + toolbarStartSlotButtonsTemplate: this.getValue( + container + ? container.toolbarStartSlotButtonsTemplate + : this.toolbarStartSlotButtonsTemplate + ), + toolbarEndSlotButtonsTemplate: this.getValue( + container + ? container.toolbarEndSlotButtonsTemplate + : this.toolbarEndSlotButtonsTemplate + ), + toolbarContentSlot: this.getValue( + container ? container.toolbarContentSlot : this.toolbarContentSlot + ), + toolbarContentTemplate: this.getValue( + container + ? container.toolbarContentTemplate + : this.toolbarContentTemplate + ), + //#endregion + + // + //#region Footer ... + showFooter: this.getValue( + container ? container.showFooter : this.showFooter + ), + footerCssClass: this.getValue( + container ? container.footerCssClass : this.footerCssClass + ), + footerColor: this.getValue( + container ? container.footerColor : this.footerColor + ), + footerContentTemplate: this.getValue( + container ? container.footerContentTemplate : this.footerContentTemplate + ), + //#endregion + }).pipe( + map((data) => { + return { + // + ...data, + + // + toolbarActionProvider: container + ? container.toolbarActionProvider + : this.toolbarActionProvider, + + // + footerActionProvider: container + ? container.footerActionProvider + : this.footerActionProvider, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\classes\x-base-action.model.ts + +export interface XBaseActionModel { + action: T; + payload?: any; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\classes\x-base-component.ts + +import { + Input, + OnInit, + Inject, + Output, + NgZone, + OnDestroy, + Component, + Renderer2, + OnChanges, + ElementRef, + EventEmitter, + AfterViewInit, + SimpleChanges, + AfterContentInit, + AfterViewChecked, + ChangeDetectorRef, + AfterContentChecked, +} from '@angular/core'; +import { + tap, + map, + first, + filter, + takeUntil, + concatMap, + catchError, +} from 'rxjs/operators'; +import { + keys, + XTask, + XColor, + XParam, + toArray, + getValue, + notValue, + isNumber, + XUiEvent, + hasChild, + XRenderer, + isFunction, + XException, + formatDate, + isValueDate, + applyLocale, + toObservable, + XResourceIDs, + XExceptionIDs, + XStandardType, + fromUtcString, + XOneOrManyType, + toNormalString, + stringFormatter, + getDefaultValue, + XFormControlState, + isNullOrUndefined, + getErrorException, + getDateFormatString, + isNullOrEmptyString, + XResourceIdentifier, + XExceptionIdentifier, + XModalButtonRoleIdentifier, + nameof, + generateUuid, + XKey, +} from 'x-framework-core'; +import { NgModel } from '@angular/forms'; +import { NavigationExtras } from '@angular/router'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { IXBaseObservableTools } from './x-base.observable.tools'; +import { Subject, of, Observable, throwError, forkJoin } from 'rxjs'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; +import { XBaseActionModel } from './x-base-action.model'; + +@Component({ + template: '', +}) +export abstract class XBaseComponent + extends XRenderer + implements + OnInit, + OnDestroy, + OnChanges, + AfterViewInit, + AfterViewChecked, + AfterContentInit, + AfterContentChecked, + IXBaseObservableTools +{ + // + //#region Props ... + // + public readonly isMobileUi$ = this.managerService.isMobileUi$; + public readonly isNotMobileUi$ = this.managerService.isNotMobileUi$; + + // + public readonly ResourceIDs = Object.assign({}, XResourceIDs); + public readonly ExceptionIDs = Object.assign({}, XExceptionIDs); + + // + public TaskRepo: XTask[] = []; + public RunningTasks: XTask[] = []; + + // + public unsubscriber = new Subject(); + + // + protected applyThemeColors = false; + + /** + * Unique Component Identifier ... + */ + readonly uuid = generateUuid('X[' + this.constructor.name + ']'); + + // + //#region Component reload ... + private SHOW_COMPONENT: boolean = true; + set showComponent(v: boolean) { + // + if (this.SHOW_COMPONENT !== v) { + this.onComponentVisibilityChanged(v); + } + + // + this.SHOW_COMPONENT = v; + } + get showComponent() { + return this.SHOW_COMPONENT; + } + async reloadComponent(delay?: number) { + // + if (!isNullOrUndefined(delay) && delay < 100) { + delay = 100; + } + + // + this.showComponent = false; + this.detectChanges(); + return this.waitForElement(this.element.nativeElement.tagName).then( + (el) => { + // + setTimeout(() => { + // + this.showComponent = true; + this.detectChanges(); + }, delay); + } + ); + } + onComponentVisibilityChanged(visible: boolean) {} + //#endregion + + // + //#region Loading ... + /** + * Notifiy Loading Changed ... + */ + @Output() + loadingNotifier = new EventEmitter<{ id: string; state: boolean }>(); + + private LOADING: boolean = false; + + /** + * Loading State of Component Representation ... + */ + @Input() + set loading(v: boolean) { + // + this.LOADING = v; + this.onLoading(v); + this.loadingNotifier.emit({ id: this.uuid, state: v }); + } + get loading() { + return this.LOADING; + } + + onLoading(state: boolean) {} + //#endregion + + // + //#region Ui Disabled ... + /** + * Notifiy Ui Disabled Changed ... + */ + @Output() + uiDisabledNotifier = new EventEmitter<{ id: string; state: boolean }>(); + + private UI_DISABLED: boolean = false; + + /** + * Set UI State Disables / Enabled of Component ... + */ + @Input() + set uiDisabled(v: boolean) { + // + this.UI_DISABLED = v; + this.onUiDisabled(v); + this.uiDisabledNotifier.emit({ id: this.uuid, state: v }); + } + get uiDisabled() { + return this.UI_DISABLED; + } + + onUiDisabled(state: boolean) {} + //#endregion + + // + //#region Error ... + /** + * Notify Error Changed ... + */ + errorNotifier = new EventEmitter<{ id: string; state: boolean }>(); + + private ERROR: XException = undefined; + + /** + * Set Specified Error of Component if Exists ... + */ + @Input() + set error(v: XException) { + // + this.ERROR = v; + this.onError(v); + + // + const hasError = isNullOrUndefined(v); + this.errorNotifier.emit({ id: this.uuid, state: hasError }); + } + get error() { + return this.ERROR; + } + + onError(error: XException) {} + //#endregion + + // + //#region Lock ... + /** + * Notify Lock Changed ... + */ + @Output() + lockNotifier = new EventEmitter<{ id: string; state: boolean }>(); + + private LOCK: boolean = false; + + /** + * Specified Component Locked or not ... + */ + @Input() + set lock(v: boolean) { + // + this.LOCK = v; + this.onLock(v); + this.lockNotifier.emit({ id: this.uuid, state: v }); + } + get lock() { + return this.LOCK; + } + + onLock(state: boolean) {} + //#endregion + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef + ) { + super(config, element, renderer, ruler, zone); + } + //#endregion + + // + //#region Abstracted ... + //#endregion + + // + //#region LifeCycle ... + ngOnInit(): void { + // + this.clearTasks(); + this.registerComponentHandlers(); + + // + this.onInit(); + } + + ngAfterViewInit(): void { + // + this.registerViewHandlers(); + + // + // Apply theme Colors only when it's required ... + if (this.applyThemeColors) { + this.managerService.themeManagerService + .applyCurrentTheme(this.element) + .then(() => this.detectChanges()); + } + + // + // Handle Apply Manager Service UI State Changed Events is Base Components ... + this.applyTakeUntilWrapper( + this.managerService.uiStateChanged.asObservable() + ).subscribe((state) => { + // + if (state === XUiEvent.StartLoading && !this.loading) { + this.loading = true; + } else if (state === XUiEvent.StopLoading && this.loading) { + this.loading = false; + } else if (state === XUiEvent.Disabled && !this.uiDisabled) { + this.uiDisabled = true; + } else if (state === XUiEvent.Enabled && this.uiDisabled) { + this.uiDisabled = false; + } + + // + this.detectChanges(); + }); + + // + // Set UUID attribute ... + this.setAttribute('xFramework', this.uuid); + + // + this.afterViewInit(); + } + + ngAfterViewChecked(): void { + this.afterViewChecked(); + } + + ngAfterContentInit(): void { + this.afterContentInit(); + } + + ngAfterContentChecked(): void { + this.afterContentChecked(); + } + + ngOnDestroy(): void { + // + this.clearTasks(); + this.unregisterHandlers(); + + // + this.onDestroy(); + } + + ngOnChanges(changes: SimpleChanges): void { + // + const changeKeys = keys(changes); + + // + let isChanged = false; + let applyChanges = false; + + // + // Error Changes ... + isChanged = changeKeys.includes(nameof('error')); + if (isChanged) { + // + const hasError = !isNullOrUndefined(this.error); + if (hasError) { + this.addClass('x-has-error'); + } else { + this.removeClass('x-has-error'); + } + + // + applyChanges = true; + this.onError(this.error); + this.errorNotifier.emit({ id: this.uuid, state: hasError }); + } + + // + // Loading Changes ... + isChanged = changeKeys.includes(nameof('loading')); + if (isChanged) { + // + if (this.loading) { + this.addClass('x-loading'); + } else { + this.removeClass('x-loading'); + } + + // + applyChanges = true; + this.onLoading(this.loading); + this.loadingNotifier.emit({ id: this.uuid, state: this.loading }); + } + + // + // uiDisabled Changes ... + isChanged = changeKeys.includes(nameof('uiDisabled')); + if (isChanged) { + // + if (this.uiDisabled) { + this.addClass('x-disabled'); + } else { + this.removeClass('x-disabled'); + } + + // + applyChanges = true; + this.onUiDisabled(this.uiDisabled); + this.uiDisabledNotifier.emit({ id: this.uuid, state: this.uiDisabled }); + } + + // + // Lock Change ... + isChanged = changeKeys.includes(nameof('lock')); + if (isChanged) { + // + if (this.lock) { + this.addClass('x-locked'); + } else { + this.removeClass('x-locked'); + } + + // + applyChanges = true; + this.onLock(this.lock); + this.lockNotifier.emit({ id: this.uuid, state: this.lock }); + } + + // + if (applyChanges) { + this.detectChanges(); + } + + // + this.onChange(changeKeys); + } + + // + //#region Ionic LifeCycles ... + ionViewWillEnter() { + this.willEnter(); + } + + ionViewDidEnter() { + this.didEnter(); + } + + ionViewWillLeave() { + this.willLeave(); + } + + ionViewDidLeave() { + this.didLeave(); + } + //#endregion + + // + //#region Abstracted LifeCycles ... + public onInit() {} + + public willEnter() {} + + public didEnter() {} + + public afterViewInit() {} + + public afterViewChecked() {} + + public afterContentInit() {} + + public afterContentChecked() {} + + public onChange(changeKeys: string[]) {} + + public willLeave() {} + + public didLeave() {} + + public onDestroy() {} + //#endregion + //#endregion + + // + //#region Providers ... + public registerInstanceHandlers(): void {} + public registerViewHandlers(): void {} + + /** + * Retrieve Specified Property Names ... + */ + getFieldName(obj: T, name: XKey): string { + return name.toString(); + } + //#endregion + + // + //#region Notifier Handlers ... + public onResize(event?: Event) {} + + /** + * notify a Task Start ... + * + * @param name task name + */ + public onTaskStart(name: string, task: XTask) {} + + /** + * notify an error on running task ... + * + * @param name task name + * @param task task object + */ + public onTaskFailed(name: string, task: XTask) {} + + /** + * notify a task finished successfully ... + * + * @param name task name + * @param task task object + */ + public onTaskFinished(name: string, task: XTask) {} + //#endregion + + // + //#region Handlers ... + /** + * Clear Error of Component ... + */ + public clearError() { + // + setTimeout(() => { + // + this.error = null; + this.managerService.error = null; + }, 500); + } + + /** + * show error ... + */ + public showError( + exception: + | Error + | string + | XException + | XExceptionIDs + | XExceptionIdentifier + | any = undefined, + next?: () => void + ) { + // + let mException: any = getErrorException(exception); + if (isNullOrUndefined(mException) && !isNullOrUndefined(this.error)) { + mException = this.error; + } else if (isNullOrUndefined(mException)) { + mException = XExceptionIDs.ActionFailed; + } + + // + this.managerService.notificationService.presentErrorNotification({ + exception: mException, + dissmissable: true, + onWillDismiss: () => { + // + this.clearError(); + + // + if (next && isFunction(next)) { + next(); + } + }, + }); + } + + /** + * handle error state ... + */ + public async handleErrorState( + exception?: XExceptionIdentifier, + presentNotification: boolean = true, + redirectToUrl?: string, + returnUrl?: string, + params?: NavigationExtras, + duration: number = this.config.defaultNotificationDelay, + replaceUrl = true + ) { + // + //#region Inside Content ... + let errorMessage = + this.managerService.localizationService.getErrorMessage(exception); + + // + if ( + exception.hasOwnProperty('payload') && + hasChild((exception as any)['payload']) + ) { + // + const payload = (exception as any)['payload'] as Array; + + // + const localizedPayload = payload.map((pl) => { + // + if (isValueDate(pl)) { + // + const plDate = fromUtcString(pl); + const locale = this.managerService.currentLocale; + + // + const result = formatDate( + plDate, + locale, + getDateFormatString(locale) + ); + + // + return result; + } else { + return this.applyLocale(pl); + } + }); + + // + errorMessage = stringFormatter(errorMessage, localizedPayload); + } + + // + if (!params || !params.state) { + params = { + ...params, + state: {}, + }; + } + + // + params.state[XParam.ReturnUrl] = returnUrl; + params.state[XParam.Exception] = exception; + + // + if (presentNotification) { + // + if (duration < 500 || duration > 10000) { + duration = this.config.defaultNotificationDelay; + } + + // + await this.managerService.notificationService.presentDangerNotification({ + message: errorMessage, + dissmissable: true, + opt: { + duration, + }, + }); + } + + // + if (redirectToUrl && !isNullOrEmptyString(redirectToUrl)) { + await this.managerService.navigateByUrlReplace( + redirectToUrl, + params, + replaceUrl + ); + } + //#endregion + } + + /** + * show success ... + */ + public showSuccess(message?: XResourceIdentifier, next?: () => void) { + // + if (!message) { + message = XResourceIDs.success_msg; + } + + // + this.managerService.notificationService.presentSuccessNotification({ + message, + dissmissable: true, + onWillDismiss: () => { + // + if (next && isFunction(next)) { + next(); + } + }, + }); + } + + /** + * present an action modal with yes and no button ... + * + * @param header the action title resource/msg ... + * @param yesHandler the handler which must run when user accept action ... + * @param yesRole the yes button role ... + * @param noHandler the handler which must run when user reject action ... + * @param noRole the no button role ... + */ + public async presentYesNoDialog( + header: XResourceIdentifier, + yesHandler: () => void, + yesRole?: XModalButtonRoleIdentifier, + noHandler?: () => void, + noRole?: XModalButtonRoleIdentifier + ) { + // + if (isNullOrUndefined(header)) { + header = this.ResourceIDs.are_you_sure; + } + + // + await this.managerService.dialogService.presentActionModal({ + header, + buttons: [], + keyboardClose: true, + backdropDismiss: true, + hasNo: true, + noRole, + noHandler, + hasYes: true, + yesRole, + yesHandler, + }); + } + + /** + * present an action modal with yes and no button ... + * + * @param yesHandler the handler which must run when user accept action ... + * @param yesRole the yes button role ... + * @param noHandler the handler which must run when user reject action ... + * @param noRole the no button role ... + */ + public async presentDefaultYesNoDialog( + yesHandler: () => void, + yesRole?: XModalButtonRoleIdentifier, + noHandler?: () => void, + noRole?: XModalButtonRoleIdentifier + ) { + // + await this.managerService.dialogService.presentActionModal({ + header: this.ResourceIDs.are_you_sure, + buttons: [], + keyboardClose: true, + backdropDismiss: true, + hasNo: true, + noRole, + noHandler, + hasYes: true, + yesRole, + yesHandler, + }); + } + + /** + * lock page for an action + * + * @param isLoading specify state + */ + public async setLoadingState(isLoading: boolean) { + // + this.loading = isLoading; + this.uiDisabled = isLoading; + + // + this.detectChanges(); + } + + /** + * handle Default Action for Closing open Dialog ... + */ + public handleDefaultDialogCloseAction() { + // + this.managerService.dialogService.modalController.dismiss({ + dismissed: true, + }); + } + + /** + * Release all Subscription ... + */ + public releaseUnsubscriber() { + this.unregisterHandlers(); + } + + /** + * apply localization on numbers of a text ... + * + * @param content the text + */ + public applyLocale(content: string | any) { + return applyLocale( + content || isNumber(content) ? content.toString() : '', + this.managerService.localizationService.locale + ); + } + + /** + * apply locale and get current resource translated value ... + * + * @param id tes resource id + */ + public resourceProvider(id: XResourceIdentifier): string { + return ( + this.managerService.localizationService.getValue(id) || id.toString() + ); + } + + /** + * default join strings ... + */ + public combinedResourceProvider(...ids: XResourceIdentifier[]) { + return this.managerService.localizationService.combine( + this.managerService.currentLocale, + ' ', + ...ids + ); + } + + /** + * get translation of exception id ... + * + * @param id exception id + */ + public exceptionResourceProvider(id: XExceptionIdentifier) { + return this.managerService.localizationService.getErrorMessage(id); + } + + /** + * retrieve property value ... + * + * @param object the context object ... + * @param prop the property name ... + */ + public getPropValue(object: T, prop: keyof T) { + return getValue(object, prop); + } + + /** + * retrieve desiered color name based on form control state ... + * + * @param status form status + */ + public getIconColor(status: XFormControlState) { + switch (status) { + case 'DISABLED': + case 'PENDING': + default: + return XColor.Warning; + + case 'VALID': + return XColor.Primary; + + case 'INVALID': + return XColor.Danger; + } + } + + /** + * determines an element contains child nodes or not ... + * + * @param ref the element refrence + */ + public hasChildNodes(ref: ElementRef) { + return ref && (ref.nativeElement as HTMLElement).children.length > 0; + } + + public getLengthError(model: NgModel, identifier: string) { + // + if (!model || !model.errors) { + return ''; + } + + // + const errorObject = model.getError(identifier); + const requiredLength = errorObject.requiredLength; + const actualLength = errorObject.actualLength; + + // + return `(${actualLength}/${requiredLength})`; + } + + /** + * usefull for doing nothing ... + */ + public nothing() {} + //#endregion + + // + //#region Task Methods ... + /** + * check a task is Exists or not ... + * + * @param name task name + */ + public async hasTask(name: string) { + // + const hasTask = this.TaskRepo.findIndex((t) => t.name === name) > -1; + return hasTask; + } + + /** + * retrieve a task by it's name ... + * + * @param name task name + */ + public async getTask(name: string) { + // + const isExists = await this.hasTask(name); + if (!isExists) { + return null; + } + + // + const task = this.TaskRepo.find((t) => t.name === name); + return task; + } + + /** + * add or update a task in Task Repo ... + * + * @param task the task object + */ + public async addOrUpdateTask(task: XTask) { + // + if (!task) { + return; + } + + // + const isExists = await this.hasTask(task.name); + if (!isExists) { + this.TaskRepo.push(task); + } else { + await this.updateTask(task); + } + } + + /** + * Add a New Task to Task Repo ... + * + * @param task task object + */ + public async addTask(task: XTask) { + // + if (!task) { + return; + } + + // + const isExists = await this.hasTask(task.name); + if (isExists) { + return; + } + + // + this.TaskRepo.push(task); + } + + /** + * Update a Task in Task Repo ... + * + * @param task the task object + */ + public async updateTask(task: XTask) { + // + const isExists = await this.hasTask(task.name); + if (!isExists) { + return; + } + + // + await this.removeTask(task.name); + this.TaskRepo.push(task); + } + + /** + * remove a task by it's name ... + * + * @param name the task name + */ + public async removeTask(name: string) { + // + const isExists = await this.hasTask(name); + if (!isExists) { + return; + } + + // + if (this.managerService.taskManagerService.has(name)) { + this.managerService.taskManagerService.delete(name); + } + + // + this.TaskRepo = this.TaskRepo.filter((t) => t.name !== name); + } + + /** + * call a task for running on task manager ... + * + * @param name task name + */ + public async dispatchTask(name: string) { + // + if (isNullOrEmptyString(name)) { + return; + } + + // + const task = await this.getTask(name); + if (!task) { + return; + } + + // + this.managerService.taskManagerService.add(task); + } + + /** + * check a Task is running or not by it's name + * + * @param name task name + */ + public async isTaskRunning(name: string) { + // + if (isNullOrEmptyString(name)) { + return false; + } + + // + name = toNormalString(name); + + // + return ( + this.RunningTasks.findIndex((rt) => name === toNormalString(rt.name)) > -1 + ); + } + + /** + * clear all tasks for component ... + */ + public async clearTasks() { + // + this.TaskRepo = []; + this.RunningTasks = []; + } + + /** + * do some actions related to Task Repo when a Task Start ... + * + * @param name task name + * @param task task object + */ + private async handlTaskStart(name: string, task: XTask) { + // + const isRunning = await this.isTaskRunning(name); + if (isRunning) { + return; + } + + // + this.RunningTasks.push(task); + } + + /** + * do some actions related to Task Repo when a Task Failed ... + * + * @param name task name + * @param task task object + */ + private async handleTaskFailed(name: string, task: XTask) { + // + const isRunning = await this.isTaskRunning(name); + if (!isRunning) { + return; + } + + // + const tName = toNormalString(task.name); + this.RunningTasks = this.RunningTasks.filter( + (t) => tName !== toNormalString(t.name) + ); + } + + /** + * do some actions related to Task Repo when a Task Finished ... + * + * @param name task name + * @param task task object + */ + private async handleTaskFinished(name: string, task: XTask) { + // + const isRunning = await this.isTaskRunning(name); + if (!isRunning) { + return; + } + + // + const tName = toNormalString(task.name); + this.TaskRepo = this.TaskRepo.filter( + (t) => tName !== toNormalString(t.name) + ); + this.RunningTasks = this.RunningTasks.filter( + (t) => tName !== toNormalString(t.name) + ); + } + //#endregion + + // + //#region Observable Actions ... + /** + * Check if items has childs or not ... + * + * @param items a list item ... + */ + public hasChild(items: XOneOrManyType) { + return hasChild(items); + } + + /** + * convert an standard UI Value to Observable for consuming ... + * + * @param value the standard UI Value + */ + public getValue(value: XStandardType) { + return toObservable(value); + } + + /** + * get stream Observble of specified standard value + */ + public getValueStream(value: XStandardType) { + return this.applyTakeUntilWrapper(this.getValue(value)); + } + + /** + * retrieve an standard type var in promise + * + * @param value an standard type var + */ + public async getValueAsync(value: XStandardType) { + // + if (!value) { + return null; + } + + // + return await this.toPromise(this.getValue(value)); + } + + /** + * convert to array ... + * + * @param value an standard type one or many arg ... + */ + public getArrayValue(value: XStandardType>) { + return this.getValue(value).pipe(map((valueType) => toArray(valueType))); + } + + /** + * convert to array ... + * + * @param value an standard type one or many arg ... + */ + public async getArrayValueAsync(value: XStandardType>) { + return await this.getValueAsync(this.getArrayValue(value)); + } + + /** + * check a value has child or not ... + * + * @param value an standard one or many of type + */ + public hasChildValue(value: XStandardType>) { + // + return this.getValue(value).pipe( + map((valueType) => hasChild(toArray(valueType))) + ); + } + + /** + * check a value has child or not ... + * + * @param value an standard one or many of type + */ + public async hasChildValueAsync(value: XStandardType>) { + // + const valueType = await this.getValueAsync(value); + if (!valueType) { + return false; + } + + // + return hasChild(toArray(valueType)); + } + + /** + * count a value's child ... + * + * @param value an standard one or many of type + */ + public countChildsValue(value: XStandardType>) { + return this.getValue(value).pipe( + map((valueType) => toArray(valueType).length) + ); + } + + /** + * count a value's child ... + * + * @param value an standard one or many of type + */ + public async countChildsValueAsync( + value: XStandardType> + ) { + // + const valueType = await this.getValueAsync(value); + if (!valueType) { + return 0; + } + + // + return toArray(valueType).length; + } + + /** + * providing string resources ... + * + * @param value an standard one or many of type + */ + public getResourceValue(value: XStandardType) { + // + return !value + ? of('') + : this.getValue(value).pipe( + map((val) => { + return this.resourceProvider(val) || (value as string); + }) + ); + } + + /** + * providing string resources ... + * + * @param value an standard one or many of type + */ + public async getResourceValueAsync( + value: XStandardType + ) { + // + if (!value) { + return ''; + } + + // + const val = await this.getValueAsync(value); + return this.resourceProvider(val) || (val as string); + } + + /** + * determines a given value has content and not empty + * + * @param value an Standard type of string + */ + public notEmptyValue(value: XStandardType) { + return this.getValue(value).pipe(map((val) => !isNullOrEmptyString(val))); + } + + /** + * determines a given value has content and not empty + * + * @param value an Standard type of string + */ + public async notEmptyValueAsync(value: any) { + return await this.toPromise(this.notEmptyValue(value)); + } + + /** + * determines a given value has not content and empty + * + * @param value an Standard type of string + */ + public emptyValue(value: XStandardType) { + return this.getValue(value).pipe(map((val) => isNullOrEmptyString(val))); + } + + /** + * determines a given value has not content and empty + * + * @param value an Standard type of string + */ + public async emptyValueAsync(value: XStandardType) { + return await this.toPromise(this.emptyValue(value)); + } + + /** + * apply not operator to a boolean ... + */ + public notValue(value: XStandardType) { + return notValue(this.getValue(value)); + } + + /** + * apply not operator to a boolean ... + */ + public async notValueAsync(value: XStandardType) { + return await this.getValueAsync(this.notValue(value)); + } + + /** + * check all input args be same as value ... + * + * @param value any value which going to check with all args to be same as them ... + * @param values the input args which be checked by value ... + */ + public mustBeValue(value: any, ...values: XStandardType[]) { + // + return forkJoin(values.map((v) => toObservable(v))).pipe( + map((res) => { + return res.every((i) => i === value); + }) + ); + } + + /** + * check all input args be same as value ... + * + * @param value any value which going to check with all args to be same as them ... + * @param values the input args which be checked by value ... + */ + public async mustBeValueAsync(value: any, ...values: XStandardType[]) { + // + return ( + await Promise.all(values.map(async (v) => await this.getValueAsync(v))) + ).every((v) => v === value); + } + + /** + * check all input args contains at least one value ... + * + * @param value any value which going to check with all args to find one same ... + * @param values the input args which be checked by value ... + */ + public atLeastHasOneValue(value: any, ...values: XStandardType[]) { + // + return forkJoin(values.map((v) => toObservable(v))).pipe( + map((res) => { + return res.includes(value); + }) + ); + } + + /** + * check all input args contains at least one value ... + * + * @param value any value which going to check with all args to find one same ... + * @param values the input args which be checked by value ... + */ + public async atLeastHasOneValueAsync( + value: any, + ...values: XStandardType[] + ) { + // + return ( + await Promise.all(values.map(async (v) => await this.getValueAsync(v))) + ).includes(value); + } + + /** + * determines a given value has content and not empty + * + * @param value an Standard type of any + */ + public notNullOrUndefinedValue(value: XStandardType) { + return this.getValue(value).pipe(map((val) => !isNullOrUndefined(val))); + } + + /** + * determines a given value has content and not empty + * + * @param value an Standard type of any + */ + public async notNullOrUndefinedValueAsync(value: XStandardType) { + return await this.toPromise(this.notNullOrUndefinedValue(value)); + } + + /** + * determines a given value content must empty + * + * @param value an Standard type of any + */ + public nullOrUndefinedValue(value: XStandardType) { + return this.getValue(value).pipe(map((val) => isNullOrUndefined(val))); + } + + /** + * determines a given value content must empty + * + * @param value an Standard type of any + */ + public async nullOrUndefinedValueAsync(value: XStandardType) { + return await this.toPromise(this.nullOrUndefinedValue(value)); + } + + /** + * retrieve an optional value ... + * + * @param value the optional standard value ... + */ + public getOptionalValue(value?: XStandardType): Observable { + // + if (value) { + return this.getValue(value); + } + + // + return this.getValue(getDefaultValue(value)); + } + + /** + * retrieve an optional value ... + * + * @param value the optional standard value ... + */ + public async getOptionalValueAsync(value?: XStandardType) { + return await this.toPromise(this.getOptionalValue(value)); + } + + /** + * convert all values as observable of them ... + * + * @param values the standard list of specific type ... + * @returns observable of values array of specific type ... + */ + public getValues(values?: XOneOrManyType>) { + // + interface ForkSourceType { + [index: number]: XStandardType; + } + + // + return this.getArrayValue(values).pipe( + concatMap((values) => { + // + if (!hasChild(values)) { + return this.getValue([] as T[]); + } else { + // + const forkSource: ForkSourceType = {}; + values.forEach((value, index) => { + forkSource[index] = this.getValue(value); + }); + + // + return forkJoin(forkSource).pipe( + map((forkResult) => { + // + const respObj = forkResult as any; + const mKeys = Object.keys(forkResult) + .map((key) => key) + .sort((a, b) => +a - +b); + const result = mKeys.map((mKey: any) => respObj[mKey] as T); + + // + return result; + }) + ); + } + }) + ); + } + + /** + * convert all values as observable of them ... + * + * @param values the standard list of specific type ... + * @returns observable of values array of specific type ... + */ + public async getValuesAsync(values?: XOneOrManyType>) { + return await this.toPromise(this.getValues(values)); + } + + /** + * apply take until wrapper arround an observable ... + * + * @param task the observable object + */ + public applyTakeUntilWrapper(task: Observable): Observable { + return task.pipe(takeUntil(this.unsubscriber)); + } + + /** + * apply inside component loading wrapper arround an observable ... + * + * @param task the observable object + */ + public applyLoadingWrapper(task: Observable): Observable { + return of(null).pipe( + tap((_) => { + this.uiDisabled = true; + this.loading = true; + }), + concatMap((_) => + task.pipe( + tap(() => { + this.uiDisabled = false; + this.loading = false; + }) + ) + ), + catchError((err) => { + // + this.uiDisabled = false; + this.loading = false; + return throwError(err); + }) + ); + } + + /** + * apply get first result of an observable ... + * + * @param task the observable object + */ + public applyFirstWrapper(task: Observable): Observable { + return task.pipe(first()); + } + + /** + * convert an observable to promise object ... + * + * @param task the observable object + */ + public toPromise(task: Observable): Promise { + return this.applyFirstWrapper(task).toPromise(); + } + //#endregion + + // + //#region Actions ... + /** + * Call Change Detector's Detect Change method ... + */ + detectChanges() { + this.changeDetector.detectChanges(); + } + //#endregion + + // + //#region Private ... + /** + * Register All Required Subscriptions ... + */ + private registerComponentHandlers() { + // + this.registerUiHandlers(); + this.registerTaskManager(); + this.registerInstanceHandlers(); + } + + /** + * Release all Subscriptions based on the Component ... + */ + private unregisterHandlers() { + // + this.unsubscriber.next(); + this.unsubscriber.complete(); + } + + /** + * register Ui Event Handlers ... + */ + private registerUiHandlers() { + // + this.applyTakeUntilWrapper(this.ruler.change()).subscribe((resizeEvent) => + this.onResize(resizeEvent) + ); + + // + this.applyTakeUntilWrapper( + this.managerService.themeManagerService.notifyChanges.asObservable() + ).subscribe(() => { + this.detectChanges(); + }); + } + + /** + * Register Task Handlers for Finish and Error of Tasks ... + */ + private registerTaskManager() { + // + // Task Start ... + this.applyTakeUntilWrapper( + this.managerService.taskManagerService.taskStart.asObservable() + ) + .pipe(filter((task) => !isNullOrUndefined(task))) + .subscribe((task) => { + // + this.onTaskStart(task.name, task); + + // + this.handlTaskStart(task.name, task); + }); + + // + // Task Error ... + this.applyTakeUntilWrapper( + this.managerService.taskManagerService.taskError.asObservable() + ) + .pipe(filter((task) => !isNullOrUndefined(task))) + .subscribe((task) => { + // + this.onTaskFailed(task.name, task); + + // + this.handleTaskFailed(task.name, task); + }); + + // + // Task Finished ... + this.applyTakeUntilWrapper( + this.managerService.taskManagerService.taskFinished.asObservable() + ) + .pipe(filter((task) => !isNullOrUndefined(task))) + .subscribe((task) => { + // + this.onTaskFinished(task.name, task); + + // + this.handleTaskFinished(task.name, task); + }); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\classes\x-base-dialog.service.ts + +import { + XBaseService, + XDialogResult, + isNullOrUndefined, + XResourceIdentifier, +} from 'x-framework-core'; +import { + MatDialog, + MatDialogRef, + MatDialogConfig, +} from '@angular/material/dialog'; +import { + IXBaseDialogConfig, + IXBaseDialogResult, + XBaseDialogComponent, + IXBaseDialogActionModel, +} from './x-base.dialog.component'; +import { XManagerService } from 'x-framework-services'; +import { EventEmitter, Inject, Injectable, Output } from '@angular/core'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +@Injectable({ + providedIn: 'root' +}) +export abstract class XBaseDialogService< + TItem, + TActionProviderModel extends IXBaseDialogActionModel, + TConfig extends IXBaseDialogConfig, + TDialogCompnent extends XBaseDialogComponent< + TItem, + TActionProviderModel, + TConfig + > +> extends XBaseService { + // + //#region Props ... + private MOBILE_CLASS_NAME = 'mobile'; + private DIALOG_REF: MatDialogRef; + + // + isMobile$ = this.managerService.isMobileUi$; + + // tslint:disable-next-line:no-output-on-prefix + @Output() + onResult = new EventEmitter(); + //#endregion + + // + //#region Abstract ... + abstract defaultTitleResourceIdentifier: XResourceIdentifier; + + abstract openDialogAndReturnRef( + config: MatDialogConfig + ): MatDialogRef; + //#endregion + + // + //#region Constructor ... + constructor( + protected dialog: MatDialog, + protected managerService: XManagerService, + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig + ) { + super(config); + } + //#endregion + + // + //#region Actions ... + /** + * Open Select Map Dialog ... + */ + async openDialog(config?: TConfig) { + // + // Prevent Double Bubble ... + if (this.DIALOG_REF) { + return; + } + + // + // Prepare Config ... + if (!config) { + config = {} as TConfig; + } + + // + //#region Default Config for Map Component ... + // + // Set Default Title ... + if (config && !config.title) { + config.title = this.defaultTitleResourceIdentifier; + } + //#endregion + + // + // Prepare Dialog Config ... + const data: MatDialogConfig = { + data: config, + }; + + // + data.panelClass = 'fullscreen-dialog'; + + // + // Show Cropper inside a Dialog ... + this.DIALOG_REF = this.openDialogAndReturnRef(data); + this.DIALOG_REF.disableClose = true; + + // + // Attach Mobile Class ... + this.isMobile$.subscribe((isMobile) => { + // + if (isMobile) { + if (this.DIALOG_REF) { + this.DIALOG_REF.addPanelClass(this.MOBILE_CLASS_NAME); + } + } else { + if (this.DIALOG_REF) { + this.DIALOG_REF.removePanelClass(this.MOBILE_CLASS_NAME); + } + } + }); + + // + // Handle Backdrop ... + this.DIALOG_REF.backdropClick().subscribe(() => { + // + // Cancel Dialog ... + this.onResult.emit({ result: XDialogResult.Cancel }); + + // + this.closeDialog(); + }); + + // + // Handle Dialog Result ... + this.DIALOG_REF.afterClosed().subscribe((model: IXBaseDialogResult) => { + // + if (isNullOrUndefined(model) || model.result !== XDialogResult.Ok) { + // + // Cancel Dialog ... + this.onResult.emit({ result: XDialogResult.Cancel }); + } else { + // + // Handle Results ... + this.onResult.emit(model); + } + + // + this.closeDialog(); + }); + } + + /** + * Close Select Map Dialog ... + */ + closeDialog() { + // + // Prevent Go Forward ... + if (!this.DIALOG_REF) { + return; + } + + // + // Close Dialog ... + this.DIALOG_REF.close(); + this.DIALOG_REF = null; + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\classes\x-base-page.ts + +import { + NgZone, + Inject, + Output, + Renderer2, + Component, + ElementRef, + EventEmitter, + HostListener, + ChangeDetectorRef, +} from '@angular/core'; +import { + nameof, + XResourceIDs, + XStandardType, + XCanDeactivate, + XResourceIdentifier, + XBehaviourEventType, + isNullOrUndefined, +} from 'x-framework-core'; +import { Subject, Observable } from 'rxjs'; +import { XBaseComponent } from './x-base-component'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +@Component({ + template: '', +}) +export abstract class XBasePage + extends XBaseComponent + implements XCanDeactivate +{ + /** + * Since Page Components Manipulate Angular Router ... + * and Angular Router has Deactivate Guards ... + * and it Works where component is Locked ... + * this Param Specified for Deactivation Guard to Do Unlock by Confirmation or not ... + */ + protected confirmUnlock: boolean = true; + + /** + * Specified when Page Component is Locked, make Guards available for Prevent Back ... + */ + protected preventBackHandlingWhenHasLock: boolean = true; + + // + @Output() + backPreventNotifier = new EventEmitter<{ id: string; state: boolean }>(); + + // + private PREVENT_BACK = false; + get isBackPreveted() { + return this.PREVENT_BACK; + } + set preventBack(v: boolean) { + // + if (v && !this.PREVENT_BACK) { + this.managerService.historyService.pushEmptyState(); + } else if (v && this.PREVENT_BACK) { + this.managerService.historyService.replaceEmptyState(); + } + + // + this.PREVENT_BACK = v; + + // + if (v) { + this.addClass('x-back-prevented'); + } else { + this.addClass('x-back-prevented'); + } + + // + this.detectChanges(); + this.onBackPrevent(v); + this.backPreventNotifier.emit({ id: this.uuid, state: v }); + } + + // + onBackPrevent(state: boolean) {} + + // + private IS_PARSING_PARAMS = false; + private NOT_PREVENT_REFRESH = true; + + // + public titleRes?: XResourceIdentifier; + + // + public scrollEventSimulator = new Subject(); + + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Guards ... + public canDeactivate( + component: XCanDeactivate, + route: ActivatedRouteSnapshot, + state: RouterStateSnapshot + ): boolean | Observable | Promise { + return this.handleCanDeactivateGuard(component, route, state); + } + + @HostListener('window:beforeunload', ['$event']) + beforeUnloadHandler($event: any) { + // + if (!this.NOT_PREVENT_REFRESH) { + $event.returnValue = true; + } + } + //#endregion + + // + //#region LifeCycle ... + onInit() { + super.onInit(); + + // + this.handleParseParams(); + this.registerPageHandlers(); + } + + public afterViewInit(): void { + super.afterViewInit(); + + // + // Register Lock Notifier for Prevent Handling ... + this.applyTakeUntilWrapper(this.lockNotifier.asObservable()).subscribe( + (event) => { + // + if (isNullOrUndefined(event)) { + return; + } + + // + if (this.preventBackHandlingWhenHasLock) { + this.preventBack = event.state; + } + + // + this.detectChanges(); + } + ); + } + + public onChange(changeKeys: string[]): void { + super.onChange(changeKeys); + + // + let isChanged = false; + let applyChanges = false; + + // + // Looking for Lock Changes ... + isChanged = changeKeys.includes(nameof('lock')); + if (isChanged) { + // + applyChanges = true; + if (this.preventBackHandlingWhenHasLock) { + this.preventBack = this.lock; + } + } + + // + if (applyChanges) { + this.detectChanges(); + } + } + + public onBack() {} + + didEnter() { + super.didEnter(); + + // + this.handleResourcePresenting(); + } + + willLeave() { + super.willLeave(); + + // + this.clearError(); + this.clearTasks(); + } + + public parseParams() {} + //#endregion + + // + //#region Notifiers ... + public simulateScrollStart() { + this.scrollEventSimulator.next('scrollStart'); + } + + public simulateScrollEnd() { + this.scrollEventSimulator.next('scrollEnd'); + } + + public simulateScrolling() { + this.scrollEventSimulator.next('scrolling'); + } + //#endregion + + // + //#region Protected ... + /** + * Do Lock Breakes Works ... + * + * @param acceptCb + * @param cancelCb + */ + protected async presentDeactiveGuardDialog( + acceptCb?: () => XStandardType, + cancelCb?: () => XStandardType + ) { + if (this.lock) { + // + const a = await this.managerService.dialogService.presentActionModal({ + header: XResourceIDs.locked_msg, + hasAccept: true, + acceptHandler: () => { + // + if (acceptCb) { + acceptCb(); + } + + // + this.lock = false; + }, + hasCancel: true, + cancelHandler: () => { + // + if (cancelCb) { + cancelCb(); + } + + // + this.lock = true; + }, + buttons: [], + }); + + // + await a.onDidDismiss(); + } + } + //#endregion + + // + //#region Private ... + private handleParseParams() { + // + if (this.IS_PARSING_PARAMS) { + return; + } + + // + this.IS_PARSING_PARAMS = true; + + // + this.getValueAsync(this.parseParams()) + .then(() => (this.IS_PARSING_PARAMS = false)) + .catch( + () => + // + (this.IS_PARSING_PARAMS = false) + ); + } + + private registerPageHandlers() { + // + this.registerBackHandler(); + this.registerParamsHandler(); + } + + private handleResourcePresenting() { + // + if (!this.titleRes) { + return; + } + + // + this.managerService.settingsService.setTitle( + this.managerService.localizationService.getValue(this.titleRes) + ); + } + + private async handleCanDeactivateGuard( + component: XCanDeactivate, + route: ActivatedRouteSnapshot, + state: RouterStateSnapshot + ) { + // + let result = !this.lock; + if (this.lock && this.confirmUnlock) { + // + await this.presentDeactiveGuardDialog( + () => { + result = true; + }, + () => { + result = false; + } + ); + } + + // + return result; + } + + private registerBackHandler() { + // + this.managerService.historyService.platformLocation.onPopState(() => { + // + if (this.PREVENT_BACK) { + this.managerService.historyService.pushEmptyState(); + } + }); + + // + this.applyTakeUntilWrapper( + this.managerService.historyService.backHandler.asObservable() + ).subscribe(() => { + this.onBack(); + }); + } + + private registerParamsHandler() { + // + this.applyTakeUntilWrapper(this.managerService.routeDataChanged$).subscribe( + (res) => { + this.handleParseParams(); + } + ); + + // + this.applyTakeUntilWrapper(this.managerService.routeParmChanged$).subscribe( + (res) => { + this.handleParseParams(); + } + ); + + // + this.applyTakeUntilWrapper( + this.managerService.routeQueryParmChanged$ + ).subscribe((res) => { + this.handleParseParams(); + }); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\classes\x-base.dialog.component.html + +
+ + + + + +
+ + + +
+ +
+
+ + + + +

+ {{ title | async }} +

+
+
+ + + + +
+ +
+
+
+
+ + + +
+
+ + + + +
+ + + + {{ item.data | json }} + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\classes\x-base.dialog.component.scss + +::ng-deep.fullscreen-dialog { + // + width: 80vw !important; + max-width: 80vw !important; + + // + height: 80vh !important; + max-height: 80vh !important; + + // + &.mobile { + // + width: 100vw !important; + max-width: 100vw !important; + + // + height: 100vh !important; + max-height: 100vh !important; + } + + // + .mat-dialog-container { + padding: 0 !important; + } +} + +.x-dialog-wrapper { + width: 100% !important; + height: 100% !important; + position: relative !important; +} + +.x-dialog-header { + position: sticky !important; + top: 0; + z-index: 1000; +} + +.x-dialog-content-wrapper { + height: calc(100%); + + &.has-toolbar { + height: calc(100% - 56px); + } +} + +x-icon { + --size: 24px; +} + +.toolbar-area { + margin: 0px; +} + +.toolbar-item { + width: auto; + height: 48px; +} + +.toolbar-inline-item { + display: inline-flex !important; +} + +.toolbar-button { + width: 32px; + margin: { + left: 4px; + right: 4px; + } +} + +h1, +h2, +h3, +h4, +h5, +h6 { + margin: 0 !important; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\classes\x-base.dialog.component.ts + +import { + Inject, + NgZone, + Output, + Renderer2, + Component, + ElementRef, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XTask, + XColor, + toArray, + isFunction, + getTaskName, + XStandardType, + XDialogResult, + XOneOrManyType, + XColorIdentifier, + isNullOrUndefined, + isNullOrEmptyString, + XResourceIdentifier, +} from 'x-framework-core'; +import { of, Subject } from 'rxjs'; +import { concatMap, map } from 'rxjs/operators'; +import { XBaseComponent } from './x-base-component'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XIconNames } from '../x-icon/x-icon.typings'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from './x-base-action.model'; +import { XButtonType } from '../x-button/x-button.typings'; +import { XSlotLayout, XSlotName } from '../x-slotter/x-slotter.typings'; +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export enum XBaseDialogAction { + Refresh = 'refresh', +} + +export type XBaseDialogActionIdentifier = XBaseDialogAction | string; + +export interface IXBaseDialogActionModel + extends XBaseActionModel {} + +export interface IXBaseDialogConfig< + TActionProviderModel extends IXBaseDialogActionModel +> { + // + showToolbar?: boolean; + + // + title?: XResourceIdentifier; + cssClass?: XOneOrManyType; + progressBarColor?: XColorIdentifier; + + // + showSearchBar?: boolean; + searchBarColor?: XColorIdentifier; + searchBarInputColor?: XColorIdentifier; + + // + actionProvider?: Subject; + + // + canMultipleSelect?: boolean; + + // + onItemSelect?: (item: XOneOrManyType) => XStandardType; + + // + onTaskStart?: (name: string) => XStandardType; + onTaskFinished?: (name: string) => XStandardType; + onTaskFailed?: (name: string) => XStandardType; +} + +export interface IXBaseDialogResult { + payload?: any; + result: XDialogResult; +} + +@Component({ + template: '', +}) +export abstract class XBaseDialogComponent< + TItem, + TActionProviderModel extends IXBaseDialogActionModel, + TConfig extends IXBaseDialogConfig +> extends XBaseComponent { + // + //#region Props ... + // + public cancelIcon = XIconNames.close; + public selectIcon = XIconNames.checked; + public backIcon = XIconNames.arrow_back; + + // + readonly SlotNames = Object.assign({}, XSlotName); + readonly ButtonTypes = Object.assign({}, XButtonType); + readonly SlotLayouts = Object.assign({}, XSlotLayout); + + // + readonly isMobile$ = this.managerService.isMobileUi$; + readonly isNotMobile$ = this.managerService.isNotMobileUi$; + + // + public selectedItem: XOneOrManyType; + + // + //#region Prevent Back ... + @Output() + backPreventNotifier = new EventEmitter<{ id: string; state: boolean }>(); + + // + private PREVENT_BACK = false; + get isBackPreveted() { + return this.PREVENT_BACK; + } + set preventBack(v: boolean) { + // + if (v && !this.PREVENT_BACK) { + this.managerService.historyService.pushEmptyState(); + } else if (v && this.PREVENT_BACK) { + this.managerService.historyService.replaceEmptyState(); + } + + // + this.PREVENT_BACK = v; + + // + if (v) { + this.addClass('x-back-prevented'); + } else { + this.addClass('x-back-prevented'); + } + + // + this.detectChanges(); + this.onBackPrevent(v); + this.backPreventNotifier.emit({ id: this.uuid, state: v }); + } + + // + onBackPrevent(state: boolean) {} + //#endregion + + // + public baseActionProvider = new Subject(); + //#endregion + + // + //#region Constructor ... + constructor( + public zone: NgZone, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public managerService: XManagerService, + public changeDetector: ChangeDetectorRef, + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + @Inject(MAT_DIALOG_DATA) public data: any, + public dialogRef: MatDialogRef< + XBaseDialogComponent + > + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region LifeCycles ... + async onInit() { + super.onInit(); + + // + this.preventBack = true; + } + + public afterViewInit(): void { + super.afterViewInit(); + + // + // Register Lock Notifier for Prevent Handling ... + this.applyTakeUntilWrapper(this.lockNotifier.asObservable()).subscribe( + (event) => { + // + if (isNullOrUndefined(event)) { + return; + } + + // + this.preventBack = event.state; + this.detectChanges(); + } + ); + } + //#endregion + + // + //#region Task Handlers ... + async onTaskStart(name: string, task: XTask) { + super.onTaskStart(name, task); + + // + const isDialogTask = await this.isDialogTask(name); + + // + if (isDialogTask) { + // + this.setLoadingState(true); + await this.notifyTaskStartEvent(name); + } + } + + async onTaskFinished(name: string, task: XTask) { + super.onTaskFinished(name, task); + + // + const isDialogTask = await this.isDialogTask(name); + + // + if (isDialogTask) { + // + this.setLoadingState(false); + await this.notifyTaskFinishedEvent(name); + } + } + + async onTaskFailed(name: string, task: XTask) { + super.onTaskFailed(name, task); + + // + const isDialogTask = await this.isDialogTask(name); + + // + if (isDialogTask) { + // + this.showError(task.error); + this.setLoadingState(false); + await this.notifyTaskFailedEvent(name); + } + } + //#endregion + + // + //#region Handlers ... + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerBackHandler(); + } + //#endregion + + // + //#region UI Providers ... + /** + * retrieve map dialog config ... + */ + public getData() { + return this.getValue(this.data as TConfig); + } + + /** + * determines map dialog config exists or not ... + */ + public hasData() { + return !isNullOrUndefined(this.getData()); + } + + /** + * determines dialog can select result or not ... + */ + public dialogCanSelect() { + return this.hasChild(this.selectedItem); + } + + /** + * retrieve dialog title ... + */ + public get title() { + return this.getData().pipe( + concatMap((config) => this.getResourceValue(config ? config.title : '')) + ); + } + + /** + * retrieve dialog showTollbar ... + */ + public get showToolbar() { + return this.getData().pipe( + concatMap((config) => + !config || isNullOrUndefined(config.showToolbar) + ? of(true) + : this.getOptionalValue(config.showToolbar) + ) + ); + } + + public get cssClass() { + return this.getData().pipe( + concatMap((config) => this.getArrayValue(config ? config.cssClass : [])) + ); + } + + public get progressBarColor() { + return this.getData().pipe( + concatMap((config) => + this.getValue(config ? config.progressBarColor : XColor.Warning) + ), + map((color) => (!isNullOrEmptyString(color) ? color : XColor.Warning)) + ); + } + + public get actionProvider() { + return this.getData().pipe( + map((config) => + config ? config.actionProvider : this.baseActionProvider + ), + map((actionProvider) => + !isNullOrUndefined(actionProvider) + ? actionProvider + : this.baseActionProvider + ) + ); + } + + public get canMultipleSelect() { + return this.getData().pipe( + concatMap((config) => + this.getOptionalValue(config ? config.canMultipleSelect : false) + ) + ); + } + + // + //#region SearchBar ... + public get showSearchBar() { + return this.getData().pipe( + concatMap((config) => + this.getOptionalValue(config ? config.showSearchBar : false) + ) + ); + } + + public get searchBarColor() { + return this.getData().pipe( + concatMap((config) => + this.getValue(config ? config.searchBarColor : XColor.Primary) + ), + map((color) => (!isNullOrEmptyString(color) ? color : XColor.Primary)) + ); + } + + public get searchBarInputColor() { + return this.getData().pipe( + concatMap((config) => + this.getValue(config ? config.searchBarInputColor : XColor.Primary) + ), + map((color) => (!isNullOrEmptyString(color) ? color : XColor.Primary)) + ); + } + //#endregion + //#endregion + + // + //#region UI Handlers ... + async handleBack() { + this.dialogRef.close(); + } + + async handleSelect() { + // + if (!this.dialogCanSelect()) { + return; + } + + // + this.dialogRef.close({ + result: XDialogResult.Ok, + payload: this.selectedItem, + }); + } + + async handleDeselect() { + // + if (!this.dialogCanSelect()) { + return; + } + + // + const canSelectMultipleItems = await this.getValueAsync( + this.canMultipleSelect + ); + if (canSelectMultipleItems) { + await this.deselectAllListItems(); + } else { + // + const selectedItem = toArray(this.selectedItem)[0]; + if (!isNullOrUndefined(selectedItem)) { + await this.deselectItemInList(selectedItem); + } + } + + // + this.selectedItem = undefined; + this.detectChanges(); + } + //#endregion + + // + //#region Abstract ... + abstract getTaskNames(): Promise>; + + abstract deselectAllListItems(): Promise; + + abstract deselectItemInList(item: TItem): Promise; + + abstract handleSearchbarQuryChanged(query: string): Promise; + //#endregion + + // + //#region Protected ... + public async onBack() {} + //#endregion + + // + //#region Private ... + private async isDialogTask(name: string) { + // + const pageTasks = await this.getTaskNames(); + const mTaskKey = getTaskName(name); + const result = pageTasks.includes(mTaskKey); + + // + return result; + } + + private async notifyTaskStartEvent(name: string) { + // + if (isNullOrEmptyString(name)) { + return; + } + + // + const config = await this.getValueAsync(this.getData()); + if (config && config.onTaskStart && isFunction(config.onTaskStart)) { + await this.getValueAsync(config.onTaskStart(name)); + } + } + + private async notifyTaskFinishedEvent(name: string) { + // + if (isNullOrEmptyString(name)) { + return; + } + + // + const config = await this.getValueAsync(this.getData()); + if (config && config.onTaskFinished && isFunction(config.onTaskFinished)) { + await this.getValueAsync(config.onTaskFinished(name)); + } + } + + private async notifyTaskFailedEvent(name: string) { + // + if (isNullOrEmptyString(name)) { + return; + } + + // + const config = await this.getValueAsync(this.getData()); + if (config && config.onTaskFailed && isFunction(config.onTaskFailed)) { + await this.getValueAsync(config.onTaskFailed(name)); + } + } + + private registerBackHandler() { + // + this.managerService.historyService.platformLocation.onPopState(() => { + // + if (this.PREVENT_BACK) { + this.managerService.historyService.pushEmptyState(); + } + }); + + // + this.applyTakeUntilWrapper( + this.managerService.historyService.backHandler.asObservable() + ).subscribe(async () => { + await this.onBack(); + }); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\classes\x-base.observable.tools.ts + +import { + toArray, + hasChild, + notValue, + toPromise, + toObservable, + XStandardType, + XOneOrManyType, + getDefaultValue, + isNullOrUndefined, + XResourceIdentifier, + isNullOrEmptyString, +} from 'x-framework-core'; +import { concatMap, map } from 'rxjs/operators'; +import { Observable, forkJoin, of } from 'rxjs'; + +export interface IXBaseObservableTools { + // + //#region Action ... + hasChild: (items: XOneOrManyType) => boolean; + //#endregion + + // + //#region Observables ... + getValue: (value: XStandardType) => Observable; + + getArrayValue: ( + value: XStandardType> + ) => Observable; + + hasChildValue: ( + value: XStandardType> + ) => Observable; + + countChildsValue: ( + value: XStandardType> + ) => Observable; + + getResourceValue: ( + value: XStandardType + ) => Observable; + + notEmptyValue: (value: XStandardType) => Observable; + + emptyValue: (value: XStandardType) => Observable; + + notValue: (value: XStandardType) => Observable; + + mustBeValue: ( + value: any, + ...values: XStandardType[] + ) => Observable; + + atLeastHasOneValue: ( + value: any, + ...values: XStandardType[] + ) => Observable; + + notNullOrUndefinedValue: (value: XStandardType) => Observable; + + nullOrUndefinedValue: (value: XStandardType) => Observable; + + getOptionalValue: (value?: XStandardType) => Observable; + + getValues: (values?: XOneOrManyType>) => Observable; + //#endregion + + // + //#region Async of Observables ... + getValueAsync: (value: XStandardType) => Promise; + + getArrayValueAsync: ( + value: XStandardType> + ) => Promise; + + hasChildValueAsync: ( + value: XStandardType> + ) => Promise; + + countChildsValueAsync: ( + value: XStandardType> + ) => Promise; + + getResourceValueAsync: ( + value: XStandardType + ) => Promise; + + notEmptyValueAsync: (value: XStandardType) => Promise; + + emptyValueAsync: (value: XStandardType) => Promise; + + notValueAsync: (value: XStandardType) => Promise; + + mustBeValueAsync: ( + value: any, + ...values: XStandardType[] + ) => Promise; + + atLeastHasOneValueAsync: ( + value: any, + ...values: XStandardType[] + ) => Promise; + + notNullOrUndefinedValueAsync: (value: XStandardType) => Promise; + + nullOrUndefinedValueAsync: (value: XStandardType) => Promise; + + getOptionalValueAsync: (value?: XStandardType) => Promise; + + getValuesAsync: ( + values?: XOneOrManyType> + ) => Promise; + //#endregion +} + +export abstract class XBaseObservableTools implements IXBaseObservableTools { + // + //#region Actions ... + // + //#region Observable Actions ... + /** + * Check if items has childs or not ... + * + * @param items a list item ... + */ + public hasChild(items: XOneOrManyType) { + return hasChild(items); + } + + /** + * convert an standard UI Value to Observable for consuming ... + * + * @param value the standard UI Value + */ + public getValue(value: XStandardType) { + return toObservable(value); + } + + /** + * retrieve an standard type var in promise + * + * @param value an standard type var + */ + public async getValueAsync(value: XStandardType) { + // + if (!value) { + return null; + } + + // + return await toPromise(this.getValue(value)); + } + + /** + * convert to array ... + * + * @param value an standard type one or many arg ... + */ + public getArrayValue(value: XStandardType>) { + return this.getValue(value).pipe(map((valueType) => toArray(valueType))); + } + + /** + * convert to array ... + * + * @param value an standard type one or many arg ... + */ + public async getArrayValueAsync(value: XStandardType>) { + return this.getValueAsync(value).then((valueType) => toArray(valueType)); + } + + /** + * check a value has child or not ... + * + * @param value an standard one or many of type + */ + public hasChildValue(value: XStandardType>) { + // + return this.getValue(value).pipe( + map((valueType) => hasChild(toArray(valueType))) + ); + } + + /** + * check a value has child or not ... + * + * @param value an standard one or many of type + */ + public async hasChildValueAsync(value: XStandardType>) { + // + const valueType = await this.getValueAsync(value); + if (!valueType) { + return false; + } + + // + return hasChild(toArray(valueType)); + } + + /** + * count a value's child ... + * + * @param value an standard one or many of type + */ + public countChildsValue(value: XStandardType>) { + return this.getValue(value).pipe( + map((valueType) => toArray(valueType).length) + ); + } + + /** + * count a value's child ... + * + * @param value an standard one or many of type + */ + public async countChildsValueAsync( + value: XStandardType> + ) { + // + const valueType = await this.getValueAsync(value); + if (!valueType) { + return 0; + } + + // + return toArray(valueType).length; + } + + /** + * providing string resources ... + * + * @param value an standard one or many of type + */ + public getResourceValue(value: XStandardType) { + // + return !value + ? of('') + : this.getValue(value).pipe( + map((val) => { + return val.toString(); + // return this.resourceProvider(val) || (value as string); + }) + ); + } + + /** + * providing string resources ... + * + * @param value an standard one or many of type + */ + public async getResourceValueAsync( + value: XStandardType + ) { + // + if (!value) { + return ''; + } + + // + const val = await this.getValueAsync(value); + return val.toString(); + } + + /** + * determines a given value has content and not empty + * + * @param value an Standard type of string + */ + public notEmptyValue(value: XStandardType) { + return this.getValue(value).pipe(map((val) => !isNullOrEmptyString(val))); + } + + /** + * determines a given value has content and not empty + * + * @param value an Standard type of string + */ + public async notEmptyValueAsync(value: any) { + return await toPromise(this.notEmptyValue(value)); + } + + /** + * determines a given value has not content and empty + * + * @param value an Standard type of string + */ + public emptyValue(value: XStandardType) { + return this.getValue(value).pipe(map((val) => isNullOrEmptyString(val))); + } + + /** + * determines a given value has not content and empty + * + * @param value an Standard type of string + */ + public async emptyValueAsync(value: XStandardType) { + return await toPromise(this.emptyValue(value)); + } + + /** + * apply not operator to a boolean ... + */ + public notValue(value: XStandardType) { + return notValue(this.getValue(value)); + } + + /** + * apply not operator to a boolean ... + */ + public async notValueAsync(value: XStandardType) { + return await this.getValueAsync(this.notValue(value)); + } + + /** + * check all input args be same as value ... + * + * @param value any value which going to check with all args to be same as them ... + * @param values the input args which be checked by value ... + */ + public mustBeValue(value: any, ...values: XStandardType[]) { + // + return forkJoin(values.map((v) => toObservable(v))).pipe( + map((res) => { + return res.every((i) => i === value); + }) + ); + } + + /** + * check all input args be same as value ... + * + * @param value any value which going to check with all args to be same as them ... + * @param values the input args which be checked by value ... + */ + public async mustBeValueAsync(value: any, ...values: XStandardType[]) { + // + return ( + await Promise.all(values.map(async (v) => await this.getValueAsync(v))) + ).every((v) => v === value); + } + + /** + * check all input args contains at least one value ... + * + * @param value any value which going to check with all args to find one same ... + * @param values the input args which be checked by value ... + */ + public atLeastHasOneValue(value: any, ...values: XStandardType[]) { + // + return forkJoin(values.map((v) => toObservable(v))).pipe( + map((res) => { + return res.includes(value); + }) + ); + } + + /** + * check all input args contains at least one value ... + * + * @param value any value which going to check with all args to find one same ... + * @param values the input args which be checked by value ... + */ + public async atLeastHasOneValueAsync( + value: any, + ...values: XStandardType[] + ) { + // + return ( + await Promise.all(values.map(async (v) => await this.getValueAsync(v))) + ).includes(value); + } + + /** + * determines a given value has content and not empty + * + * @param value an Standard type of any + */ + public notNullOrUndefinedValue(value: XStandardType) { + return this.getValue(value).pipe(map((val) => !isNullOrUndefined(val))); + } + + /** + * determines a given value has content and not empty + * + * @param value an Standard type of any + */ + public async notNullOrUndefinedValueAsync(value: XStandardType) { + return await toPromise(this.notNullOrUndefinedValue(value)); + } + + /** + * determines a given value content must empty + * + * @param value an Standard type of any + */ + public nullOrUndefinedValue(value: XStandardType) { + return this.getValue(value).pipe(map((val) => isNullOrUndefined(val))); + } + + /** + * determines a given value content must empty + * + * @param value an Standard type of any + */ + public async nullOrUndefinedValueAsync(value: XStandardType) { + return await toPromise(this.nullOrUndefinedValue(value)); + } + + /** + * retrieve an optional value ... + * + * @param value the optional standard value ... + */ + public getOptionalValue(value?: XStandardType): Observable { + // + if (value) { + return this.getValue(value); + } + + // + return this.getValue(getDefaultValue(value)); + } + + /** + * retrieve an optional value ... + * + * @param value the optional standard value ... + */ + public async getOptionalValueAsync(value?: XStandardType) { + return await toPromise(this.getOptionalValue(value)); + } + + /** + * convert all values as observable of them ... + * + * @param values the standard list of specific type ... + * @returns observable of values array of specific type ... + */ + public getValues(values?: XOneOrManyType>) { + // + interface ForkSourceType { + [index: number]: XStandardType; + } + + // + return this.getArrayValue(values).pipe( + concatMap((values) => { + // + if (!hasChild(values)) { + return this.getValue([] as T[]); + } else { + // + const forkSource: ForkSourceType = {}; + values.forEach((value, index) => { + forkSource[index] = this.getValue(value); + }); + + // + return forkJoin(forkSource).pipe( + map((forkResult) => { + // + const respObj = forkResult as any; + const mKeys = Object.keys(forkResult) + .map((key) => key) + .sort((a, b) => +a - +b); + const result = mKeys.map((mKey: any) => respObj[mKey] as T); + + // + return result; + }) + ); + } + }) + ); + } + + /** + * convert all values as observable of them ... + * + * @param values the standard list of specific type ... + * @returns observable of values array of specific type ... + */ + public async getValuesAsync(values?: XOneOrManyType>) { + return await toPromise(this.getValues(values)); + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\component-templates.module.ts + +import { + XCardTestComponent, + XComponentCardTestComponent, +} from './x-card.component.templates'; +import { + XListTestComponent, + XComponentListTestComponent, +} from './x-list.component.templates'; +import { + XTabTestComponent, + XTabsTestComponent, +} from './x-tabs.component.templates'; +import { + XEmptyTestComponent, + XComponentEmptyTestComponent, +} from './x-empty.component.templates'; +import { + XSlideTestComponent, + XSliderTestComponent, +} from './x-slider.component.templates'; +import { + XSlotTestComponent, + XSlotterTestComponent, +} from './x-slotter.component.templates'; +import { + XStepTestComponent, + XStepperTestComponent, +} from './x-stepper.component.templates'; +import { NgModule } from '@angular/core'; +import { + XActionBarTestComponent, + XComponentActionBarTestComponent, +} from './x-action-bar.component.templates'; +import { + XSearchBarTestComponent, + XComponentSearchBarTestComponent, +} from './x-search-bar.component.templates'; +import { CommonModule } from '@angular/common'; +import { + XDescriptionListTestComponent, + XDescriptionListItemTestComponent, +} from './x-description-list.component.templates'; +import { XMapModule } from '../x-map/x-map.module'; +import { XFabModule } from '../x-fab/x-fab.module'; +import { XFormModule } from '../x-form/x-form.module'; +import { XGridModule } from '../x-grid/x-grid.module'; +import { XIconModule } from '../x-icon/x-icon.module'; +import { XListModule } from '../x-list/x-list.module'; +import { XPageModule } from '../x-page/x-page.module'; +import { XTabsModule } from '../x-tabs/x-tabs.module'; +import { XCardModule } from '../x-card/x-card.module'; +import { XTableModule } from '../x-table/x-table.module'; +import { XThemeModule } from '../x-theme/x-theme.module'; +import { XColorModule } from '../x-color/x-color.module'; +import { XEmptyModule } from '../x-empty/x-empty.module'; +import { XSliderModule } from '../x-slider/x-slider.module'; +import { XButtonModule } from '../x-button/x-button.module'; +import { XCounterModule } from '../x-counter/x-counter.module'; +import { XSlotterModule } from '../x-slotter/x-slotter.module'; +import { XSpinnerModule } from '../x-spinner/x-spinner.module'; +import { XStepperModule } from '../x-stepper/x-stepper.module'; +import { XMapTestComponent } from './x-map.component.templates'; +import { XFabTestComponent } from './x-fab.component.templates'; +import { XPageTestComponent } from './x-page.component.templates'; +import { XMarkdownModule } from '../x-markdown/x-markdown.module'; +import { XFormTestComponent } from './x-form.component.templates'; +import { XGridTestComponent } from './x-grid.component.templates'; +import { XIconTestComponent } from './x-icon.component.templates'; +import { XTableTestComponent } from './x-table.component.templates'; +import { XColorTestComponent } from './x-color.component.templates'; +import { XThumbnailModule } from '../x-thumbnail/x-thumbnail.module'; +import { XFooterTestComponent } from './x-footer.component.templates'; +import { XButtonTestComponent } from './x-button.component.templates'; +import { XSearchBarModule } from '../x-search-bar/x-search-bar.module'; +import { XActionBarModule } from '../x-action-bar/x-action-bar.module'; +import { XToolbarTestComponent } from './x-toolbar.component.templates'; +import { XSpinnerTestComponent } from './x-spinner.component.templates'; +import { XCounterTestComponent } from './x-counter.component.templates'; +import { XDataPagerModule } from './../x-data-pager/x-data-pager.module'; +import { XMarkdownTestComponent } from './x-markdown.component.templates'; +import { XFileUploadModule } from '../x-file-upload/x-file-upload.module'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; +import { XThumbnailTestComponent } from './x-thumbnail.component.templates'; +import { XNavigatorTestComponent } from './x-navigator.component.templates'; +import { XDataPagerTestComponent } from './x-data-pager.component.templates'; +import { XColorPickerModule } from '../x-color-picker/x-color-picker.module'; +import { XFileUploadTestComponent } from './x-file-upload.component.templates'; +import { XImageCropperModule } from '../x-image-cropper/x-image-cropper.module'; +import { XAvatarUploadModule } from '../x-avatar-upload/x-avatar-upload.module'; +import { XTabNavigatorModule } from '../x-tab-navigator/x-tab-navigator.module'; +import { XDataScrollerModule } from '../x-data-scroller/x-data-scroller.module'; +import { XColorPickerTestComponent } from './x-color-picker.component.templates'; +import { XFileDropAreaModule } from '../x-file-drop-area/x-file-drop-area.module'; +import { XTabNavigatorTestComponent } from './x-tab-navigator.component.templates'; +import { XThemeManagerTestComponent } from './x-theme-manager.component.templates'; +import { XAvatarUploadTestComponent } from './x-avatar-upload.component.templates'; +import { XImageCropperTestComponent } from './x-image-cropper.component.templates'; +import { XDataScrollerTestComponent } from './x-data-scroller.component.templates'; +import { XFileDropAreaTestComponent } from './x-file-drop-area.component.templates'; +import { XScreenCaptureTestComponent } from './x-screen-capture.component.templates'; +import { XModelPresenterModule } from '../x-model-presenter/x-model-presenter.module'; +import { XExpandableListModule } from '../x-expandable-list/x-expandable-list.module'; +import { XExpandableListTestComponent } from './x-expandable-list.component.templates'; +import { XModelPresenterTestComponent } from './x-model-presenter.component.templates'; +import { XDescriptionListModule } from '../x-description-list/x-description-list.module'; +import { XExpandablePanelModule } from '../x-expandable-panel/x-expandable-panel.module'; +import { XExpandablePandelTestComponent } from './x-expandable-panel.component.templates'; + +@NgModule({ + declarations: [ + // + // x-action-bar ... + XActionBarTestComponent, + XComponentActionBarTestComponent, + // + // x-avatar-upload ... + XAvatarUploadTestComponent, + // + // x-button ... + XButtonTestComponent, + // + // x-card ... + XCardTestComponent, + XComponentCardTestComponent, + // + // x-color ... + XColorTestComponent, + // + // x-color-picker ... + XColorPickerTestComponent, + // + // x-counter ... + XCounterTestComponent, + // + // x-data-pager ... + XDataPagerTestComponent, + // + // x-data-scroller ... + XDataScrollerTestComponent, + // + // x-description-list ... + XDescriptionListTestComponent, + XDescriptionListItemTestComponent, + // + // x-empty ... + XEmptyTestComponent, + XComponentEmptyTestComponent, + // + // x-expandable-list ... + XExpandableListTestComponent, + // + // x-expandable-panel ... + XExpandablePandelTestComponent, + // + // x-fab ... + XFabTestComponent, + // + // x-file-drop-area ... + XFileDropAreaTestComponent, + // + // x-file-upload ... + XFileUploadTestComponent, + // + // x-form ... + XFormTestComponent, + // + // x-grid ... + XGridTestComponent, + // + // x-icon ... + XIconTestComponent, + // + // x-image-cropper ... + XImageCropperTestComponent, + // + // x-list ... + XListTestComponent, + XComponentListTestComponent, + // + // x-map ... + XMapTestComponent, + // + // x-markdown ... + XMarkdownTestComponent, + // + // x-model-presenter ... + XModelPresenterTestComponent, + // + // x-page ... + XPageTestComponent, + // + // x-footer ... + XFooterTestComponent, + // + // x-navigator ... + XNavigatorTestComponent, + // + // x-toolbar ... + XToolbarTestComponent, + // + // x-screen-capture ... + XScreenCaptureTestComponent, + // + // x-search-bar ... + XSearchBarTestComponent, + XComponentSearchBarTestComponent, + // + // x-slider ... + XSlideTestComponent, + XSliderTestComponent, + // + // x-slotter ... + XSlotTestComponent, + XSlotterTestComponent, + // + // x-spinner ... + XSpinnerTestComponent, + // + // x-stepper ... + XStepTestComponent, + XStepperTestComponent, + // + // x-tab-navigator ... + XTabNavigatorTestComponent, + // + // x-table ... + XTableTestComponent, + // + // x-tabs ... + XTabTestComponent, + XTabsTestComponent, + // + // x-theme-manager ... + XThemeManagerTestComponent, + // + // x-thumbnail ... + XThumbnailTestComponent, + ], + imports: [ + // + CommonModule, + + // + XEmptyModule, + XBaseComponentModule, + + // + XFabModule, + XMapModule, + XPageModule, + XCardModule, + XIconModule, + XFormModule, + XTabsModule, + XTabsModule, + XListModule, + XGridModule, + XColorModule, + XTableModule, + XThemeModule, + XButtonModule, + XSliderModule, + XSpinnerModule, + XStepperModule, + XSlotterModule, + XCounterModule, + XMarkdownModule, + XDataPagerModule, + XSearchBarModule, + XThumbnailModule, + XActionBarModule, + XFileUploadModule, + XColorPickerModule, + XImageCropperModule, + XFileDropAreaModule, + XDataScrollerModule, + XTabNavigatorModule, + XAvatarUploadModule, + XExpandableListModule, + XModelPresenterModule, + XExpandablePanelModule, + XDescriptionListModule, + ], + exports: [], +}) +export class XComponentTemplatesModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-action-bar.component.templates.ts + +import { Component } from '@angular/core'; +import { + XActionBarContainerComponent, + XComponentActionBarContainerComponent, +} from '../x-action-bar/x-action-bar.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XActionBarTestComponent extends XActionBarContainerComponent {} + +@Component({ + template: ` + + + + + + `, +}) +export class XComponentActionBarTestComponent extends XComponentActionBarContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-avatar-upload.component.templates.ts + +import { Component } from '@angular/core'; +import { XAvatarUploadContainerComponent } from '../x-avatar-upload/x-avatar-upload.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XAvatarUploadTestComponent extends XAvatarUploadContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-button.component.templates.ts + +import { Component } from '@angular/core'; +import { XButtonContainerComponent } from '../x-button/x-button.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XButtonTestComponent extends XButtonContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-card.component.templates.ts + +import { Component } from '@angular/core'; +import { + XCardContainerComponent, + XComponentCardContainerComponent, +} from '../x-card/x-card.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XCardTestComponent extends XCardContainerComponent {} + +@Component({ + template: ` + + + + + + + + + + + + + + + + +
+ +
+ + +
+ +
+
+
+
+ + + + `, +}) +export class XComponentCardTestComponent extends XComponentCardContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-color-picker.component.templates.ts + +import { Component } from '@angular/core'; +import { XColorPickerContainerComponent } from '../x-color-picker/x-color-picker.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XColorPickerTestComponent extends XColorPickerContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-color.component.templates.ts + +import { Component } from "@angular/core"; +import { XColorContainerComponent } from "../x-color/x-color.typings"; + +@Component({ + template: ` + + + + + ` +}) +export class XColorTestComponent extends XColorContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-counter.component.templates.ts + + +import { Component } from "@angular/core"; +import { XCounterContainerComponent } from "../x-counter/x-counter.typings"; + +@Component({ + template: ` + + + + + ` +}) +export class XCounterTestComponent extends XCounterContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-data-pager.component.templates.ts + +import { Component } from '@angular/core'; +import { XDataPagerContainerComponent } from '../x-data-pager/x-data-pager.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XDataPagerTestComponent extends XDataPagerContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-data-scroller.component.templates.ts + +import { Component } from '@angular/core'; +import { XDataScrollerContainerComponent } from '../x-data-scroller/x-data-scroller.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XDataScrollerTestComponent extends XDataScrollerContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-description-list.component.templates.ts + +import { Component } from '@angular/core'; +import { XDescriptionListContainerComponent } from '../x-description-list/x-description-list.typings'; +import { XDescriptionListItemContainerComponent } from '../x-description-list/x-description-list-item/x-description-list-item.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XDescriptionListTestComponent extends XDescriptionListContainerComponent {} + +@Component({ + template: ` + + + + + `, +}) +export class XDescriptionListItemTestComponent extends XDescriptionListItemContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-empty.component.templates.ts + +import { Component } from '@angular/core'; +import { + XComponentEmptyContainerComponent, + XEmptyContainerComponent, +} from '../x-empty/x-empty.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XEmptyTestComponent extends XEmptyContainerComponent {} + +@Component({ + template: ` + + + + + + + `, +}) +export class XComponentEmptyTestComponent extends XComponentEmptyContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-expandable-list.component.templates.ts + +import { Component } from '@angular/core'; +import { XExpandableListContainerComponent } from '../x-expandable-list/x-expandable-list.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XExpandableListTestComponent extends XExpandableListContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-expandable-panel.component.templates.ts + +import { Component } from '@angular/core'; +import { XExpandablePandelContainerComponent } from '../x-expandable-panel/x-expandable-panel.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XExpandablePandelTestComponent extends XExpandablePandelContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-fab.component.templates.ts + +import { Component } from '@angular/core'; +import { XFabContainerComponent } from '../x-fab/x-fab.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XFabTestComponent extends XFabContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-file-drop-area.component.templates.ts + +import { Component } from '@angular/core'; +import { XFileDropAreaContainerComponent } from '../x-file-drop-area/x-file-drop-area.typings'; + +@Component({ + template: ` + + + + `, +}) +export class XFileDropAreaTestComponent extends XFileDropAreaContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-file-upload.component.templates.ts + +import { Component } from '@angular/core'; +import { XFileUploadContainerComponent } from '../x-file-upload/x-file-upload.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XFileUploadTestComponent extends XFileUploadContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-footer.component.templates.ts + +import { Component } from '@angular/core'; +import { XFooterContainerComponent } from '../x-page/x-footer/x-footer.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XFooterTestComponent extends XFooterContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-form.component.templates.ts + +import { Component } from '@angular/core'; +import { XFormContainerComponent } from '../x-form/x-form.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XFormTestComponent extends XFormContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-grid.component.templates.ts + +import { Component } from '@angular/core'; +import { XGridContainerComponent } from '../x-grid/x-grid.typings'; + +@Component({ + template: ` + + + + `, +}) +export class XGridTestComponent extends XGridContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-icon.component.templates.ts + +import { Component } from '@angular/core'; +import { XIconContainerComponent } from '../x-icon/x-icon.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XIconTestComponent extends XIconContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-image-cropper.component.templates.ts + +import { Component } from '@angular/core'; +import { XImageCropperContainerComponent } from '../x-image-cropper/x-image-cropper.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XImageCropperTestComponent extends XImageCropperContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-list.component.templates.ts + +import { Component } from '@angular/core'; +import { + XComponentListContainerComponent, + XListContainerComponent, +} from '../x-list/x-list.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XListTestComponent extends XListContainerComponent {} + +@Component({ + template: ` + + + + + + + `, +}) +export class XComponentListTestComponent extends XComponentListContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-map.component.templates.ts + +import { Component } from '@angular/core'; +import { XMapContainerComponent } from '../x-map/x-map.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XMapTestComponent extends XMapContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-markdown.component.templates.ts + +import { Component } from '@angular/core'; +import { XMarkdownContainerComponent } from '../x-markdown/x-markdown.typings'; + +@Component({ + template: ` + + + + `, +}) +export class XMarkdownTestComponent extends XMarkdownContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-model-presenter.component.templates.ts + +import { Component } from '@angular/core'; +import { XModelPresenterContainerComponent } from '../x-model-presenter/x-model-presenter.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XModelPresenterTestComponent extends XModelPresenterContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-navigator.component.templates.ts + +import { Component } from '@angular/core'; +import { XNavigatorContainerComponent } from '../x-page/x-navigator/x-navigator.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XNavigatorTestComponent extends XNavigatorContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-page.component.templates.ts + + +import { Component } from "@angular/core"; +import { XPageContainerComponent } from "../x-page/x-page.container"; + +@Component({ + template: ` + + + + + ` +}) +export class XPageTestComponent extends XPageContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-screen-capture.component.templates.ts + +import { Component } from '@angular/core'; +import { XScreenCaptureContainerComponent } from '../x-screen-capture/x-screen-capture.container'; + +@Component({ + template: ` + + + + + `, +}) +export class XScreenCaptureTestComponent extends XScreenCaptureContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-search-bar.component.templates.ts + +import { Component } from '@angular/core'; +import { + XComponentSearchBarContainerComponent, + XSearchBarContainerComponent, +} from '../x-search-bar/x-search-bar.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XSearchBarTestComponent extends XSearchBarContainerComponent {} + +@Component({ + template: ` + + + + + + + `, +}) +export class XComponentSearchBarTestComponent extends XComponentSearchBarContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-slider.component.templates.ts + +import { Component } from '@angular/core'; +import { XSliderContainerComponent } from '../x-slider/x-slider.typings'; +import { XSlideContainerComponent } from '../x-slider/x-slide/x-slide.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XSliderTestComponent extends XSliderContainerComponent {} + +@Component({ + template: ` + + + + `, +}) +export class XSlideTestComponent extends XSlideContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-slotter.component.templates.ts + +import { Component } from '@angular/core'; +import { XSlotterContainerComponent } from '../x-slotter/x-slotter.typings'; +import { XSlotContainerComponent } from '../x-slotter/x-slot/x-slot.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XSlotterTestComponent extends XSlotterContainerComponent {} + +@Component({ + template: ` + + + + + `, +}) +export class XSlotTestComponent extends XSlotContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-spinner.component.templates.ts + +import { Component } from '@angular/core'; +import { XSpinnerContainerComponent } from '../x-spinner/x-spinner.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XSpinnerTestComponent extends XSpinnerContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-stepper.component.templates.ts + +import { Component } from '@angular/core'; +import { XStepperContainerComponent } from '../x-stepper/x-stepper.typings'; +import { XStepContainerComponent } from '../x-stepper/x-step/x-step.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XStepperTestComponent extends XStepperContainerComponent {} + +@Component({ + template: ` + + + + + `, +}) +export class XStepTestComponent extends XStepContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-tab-navigator.component.templates.ts + +import { Component } from '@angular/core'; +import { XTabNavigatorContainerComponent } from '../x-tab-navigator/x-tab-navigator.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XTabNavigatorTestComponent extends XTabNavigatorContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-table.component.templates.ts + +import { Component } from '@angular/core'; +import { XTableContainerComponent } from '../x-table/x-table.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XTableTestComponent extends XTableContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-tabs.component.templates.ts + +import { Component } from '@angular/core'; +import { XTabsContainerComponent } from '../x-tabs/x-tabs.typings'; +import { XTabContainerComponent } from '../x-tabs/x-tab/x-tab.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XTabsTestComponent extends XTabsContainerComponent {} + +@Component({ + template: ` + + + + `, +}) +export class XTabTestComponent extends XTabContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-theme-manager.component.templates.ts + +import { Component } from '@angular/core'; +import { XThemeManagerContainerComponent } from '../x-theme/x-theme-manager/x-theme-manager.typings'; + +@Component({ + template: ` + + + + `, +}) +export class XThemeManagerTestComponent extends XThemeManagerContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-thumbnail.component.templates.ts + +import { Component } from '@angular/core'; +import { XThumbnailContainerComponent } from '../x-thumbnail/x-thumbnail.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XThumbnailTestComponent extends XThumbnailContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\component-templates\x-toolbar.component.templates.ts + +import { Component } from '@angular/core'; +import { XToolbarContainerComponent } from '../x-page/x-toolbar/x-toolbar.typings'; + +@Component({ + template: ` + + + + + `, +}) +export class XToolbarTestComponent extends XToolbarContainerComponent {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\config\x-framework-components.config.ts + +import { XFrameworkCoreConfig } from 'x-framework-core'; +import { XFrameworkServicesConfig } from 'x-framework-services'; + +export type XSharedConfig = XFrameworkCoreConfig & XFrameworkServicesConfig; + +// tslint:disable-next-line:no-empty-interface +export interface XFrameworkComponentsConfig extends XSharedConfig { + // + // File Config ... + maxAllowedSize: number; + maxUploadFiles: number; + minAllowedFileSize: number; + maxAllowedFileSize: number; + + // + defaultSearchDebounceTime: number; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\dialogs\x-dialog.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XListModule } from '../x-list/x-list.module'; +import { XPopoverService } from './x-popover/x-popover.service'; +import { XSlotterModule } from './../x-slotter/x-slotter.module'; +import { XPopoverComponent } from './x-popover/x-popover.component'; +import { XSearchBarModule } from '../x-search-bar/x-search-bar.module'; +import { XDataPagerModule } from '../x-data-pager/x-data-pager.module'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; +import { XSelectorDialogService } from './x-selector-dialog/x-selector-dialog.service'; +import { XSelectorDialogComponent } from './x-selector-dialog/x-selector-dialog.component'; + +@NgModule({ + imports: [ + XListModule, + CommonModule, + XSlotterModule, + XSearchBarModule, + XDataPagerModule, + XBaseComponentModule, + ], + exports: [ + XListModule, + CommonModule, + XSlotterModule, + XSearchBarModule, + XDataPagerModule, + XBaseComponentModule, + ], + providers: [XPopoverService, XSelectorDialogService], + declarations: [XPopoverComponent, XSelectorDialogComponent], + entryComponents: [XPopoverComponent, XSelectorDialogComponent], +}) +export class XDialogsModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\dialogs\x-dialog\x-dialog.component.html + +
+ +
+ + + +
+ + + +
+ +
+
+ + + + +

+ {{ getTitle() | async }} +

+
+
+ + + +
+ +
+
+
+
+
+ + + +
+ + +
+ +
+
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\dialogs\x-dialog\x-dialog.component.scss + +x-icon { + --size: 24px; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + margin: 0 !important; +} + +.x-base-dialog-container { + height: 100%; +} + +.x-base-dialog-content { + height: 100%; + overflow-y: scroll; +} + +.x-base-dialog-toolbar-area { + margin: 8px; +} + +.x-base-dialog-toolbar-item { + width: auto; + height: 48px; +} + +.x-base-dialog-toolbar-button { + width: 32px; + margin: { + left: 4px; + right: 4px; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\dialogs\x-dialog\x-dialog.component.ts + +import { + XDialogConfig, + XDialogContext, + defaultBaseDialogConfig, +} from './x-dialog.typings'; +import { concatMap, map } from 'rxjs/operators'; +import { Input, Component, ViewChild, ElementRef } from '@angular/core'; +import { XIconNames } from '../../x-icon/x-icon.typings'; +import { XSlotName } from '../../x-slotter/x-slotter.typings'; +import { XBaseComponent } from '../../classes/x-base-component'; +import { XStandardType, isNullOrUndefined } from 'x-framework-core'; + +@Component({ + template: '', +}) +export abstract class XDialogComponent extends XBaseComponent { + // + //#region Props ... + // + //#region Private/Readonly ... + readonly SlotNames = Object.assign({}, XSlotName); + //#endregion + + // + //#region Self Props ... + // + @Input() + context: XStandardType; + //#endregion + + // + //#region ViewChilds ... + @ViewChild('xBaseDialogToolbar') + dialogToolbar: ElementRef; + //#endregion + + // + selectedValue: any; + applyThemeColors = true; + selectIcon = XIconNames.checked; + backIcon = XIconNames.arrow_back; + //#endregion + + // + //#region Constructor ... + //#endregion + + // + //#region LifeCycles ... + //#endregion + + // + //#region Register Handlers ... + //#endregion + + // + //#region Task Handlers ... + //#endregion + + // + //#region UI Providers ... + // + //#region Context Options UI Providers ... + /** + * retrieve id of dialog ... + * + * @returns Observable of Id ... + */ + getId() { + return this.getData().pipe(map((config) => config.id)); + } + + /** + * retrieve map dialog config ... + */ + getData() { + return this.getValue(this.context); + } + + /** + * retrieve dialog title ... + */ + getTitle() { + return this.getData().pipe( + concatMap((config) => this.getResourceValue(config.title)) + ); + } + + /** + * retrieve dialog can Select values or not ... + * + * @returns observable of boolean ... + */ + canSelect() { + return this.getData().pipe(map((config) => !!config.canSelect)); + } + + /** + * retrieve payload of data for dialog ... + * + * @returns observable of any ... + */ + getPayload() { + return this.getData().pipe(map((config) => config.payload)); + } + + /** + * retrieve toolbar color ... + * + * @returns observable of XColorIdentifier ... + */ + getToolbarColor() { + return this.getData().pipe( + map( + (config) => config.toolbarColor || defaultBaseDialogConfig.toolbarColor + ) + ); + } + + /** + * retrieve background color ... + * + * @returns observable of XColorIdentifier ... + */ + getBackgroundColor() { + return this.getData().pipe( + map( + (config) => + config.backgroundColor || defaultBaseDialogConfig.backgroundColor + ) + ); + } + + /** + * retrieve progressBar color ... + * + * @returns observable of XColorIdentifier ... + */ + getProgressBarColor() { + return this.getData().pipe( + map( + (config) => + config.progressBarColor || defaultBaseDialogConfig.progressBarColor + ) + ); + } + //#endregion + + // + //#region Class Providers ... + getDialogToolbarClasses() { + return this.getValue(this.getId()).pipe( + map((id) => { + return ['x-base-dialog-toolbar', `x-base-dialog-${id}-toolbar`]; + }) + ); + } + + getDialogContainerClasses() { + return this.getValue(this.getId()).pipe( + map((id) => { + return ['x-base-dialog-container', `x-base-dialog-${id}-container`]; + }) + ); + } + //#endregion + + hasSelectedValue() { + return this.notNullOrUndefinedValue(this.selectedValue); + } + + canShowProgressBar() { + return this.getValue(this.loading || this.uiDisabled); + } + //#endregion + + // + //#region UI Handlers ... + async handleClosePopup(payload: any = undefined) { + // + const id = await this.getValueAsync(this.getId()); + const context = await this.getValueAsync(this.context); + if (isNullOrUndefined(context)) { + return; + } + + // + switch (context.type) { + // + case XDialogContext.Modal: + await this.managerService.dialogService.modalController.dismiss( + payload, + undefined, + id + ); + break; + + // + case XDialogContext.PopOver: + await this.managerService.dialogService.popoverController.dismiss( + payload, + undefined, + id + ); + break; + } + } + + async handleSelect(payload: any) { + // + if (isNullOrUndefined(payload)) { + return; + } + + // + const hasSelectedValue = await this.getValueAsync(this.hasSelectedValue()); + if (!hasSelectedValue) { + return; + } + + // + await this.handleClosePopup(payload); + } + //#endregion + + // + //#region Actions ... + //#endregion + + // + //#region Abstracts ... + + //#endregion + + // + //#region Private ... + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\dialogs\x-dialog\x-dialog.typings.ts + +import { XColorIdentifier, XColorWithBrightness } from 'x-framework-core'; + +export type XDialogConfigKeys = 'title' | string; + +export type XDialogConfigType = { + [key in XDialogConfigKeys]: any; +}; + +export enum XDialogContext { + Modal = 'modal', + PopOver = 'pop_over', +} + +export interface XDialogConfig extends XDialogConfigType { + id: string; + title: string; + payload?: any; + canSelect?: boolean; + type: XDialogContext; + toolbarColor?: XColorIdentifier; + backgroundColor?: XColorIdentifier; + progressBarColor?: XColorIdentifier; +} + +export const defaultBaseDialogConfig: Partial = { + // + toolbarColor: XColorWithBrightness.Primary, + backgroundColor: XColorWithBrightness.Light, + progressBarColor: XColorWithBrightness.Warning, +}; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\dialogs\x-popover\x-popover.component.html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ + getResourceValue(title) | async + }} + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\dialogs\x-popover\x-popover.component.scss + +:host { + --max-popover-height: 260px; + --background-color: var(--x-color-light); + --divider-color: var(--x-color-dark-shade); +} + +:host::ng-deep { + mat-card { + background-color: var(--background-color) !important; + } +} + +:host::ng-deep.align-self-center { + align-self: center !important; +} + +ion-list, +ion-item.item, +ion-item-divider, +ion-list-header { + --background: var(--background-color); +} + +ion-item-divider { + --color: var(--divider-color); +} + +ion-list-header.x-list-header { + text-align: center !important; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + margin: 0; +} + +.x-popover-wrapper { + max-height: var(--max-popover-height); + overflow-y: scroll; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\dialogs\x-popover\x-popover.component.ts + +import { + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + ChangeDetectorRef, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + nameof, + isFunction, + parseXColor, + XExceptionIDs, + XOneOrManyType, + XColorIdentifier, + isNullOrUndefined, + XColorWithBrightness, + isNullOrEmptyString, + getXColorCssVariableName, +} from 'x-framework-core'; +import { + XPopoverItem, + XPopoverCssVar, + XPopoverOption, + XPopoverResult, +} from './x-popover.typings'; +import { NavParams } from '@ionic/angular'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XPopoverService } from './x-popover.service'; +import { generatePopoverId } from './x-popover.tools'; +import { XManagerService } from 'x-framework-services'; +import { XBaseComponent } from '../../classes/x-base-component'; +import { XSlotLayout, XSlotName } from '../../x-slotter/x-slotter.typings'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../../config/x-framework-components.config'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-popover', + templateUrl: './x-popover.component.html', + styleUrls: ['./x-popover.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XPopoverComponent extends XBaseComponent { + // + //#region Props ... + // + readonly SlotNames = Object.assign({}, XSlotName); + readonly SlotLayouts = Object.assign({}, XSlotLayout); + readonly ColorNames = Object.assign({}, XColorWithBrightness); + + // + items: XOneOrManyType; + options: XOneOrManyType; + + // + height: string; + dividerColor: XColorIdentifier; + backgroundColor: XColorIdentifier; + //#endregion + + // + //#region Constructor ... + constructor( + public zone: NgZone, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public navParams: NavParams, + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public changeDetector: ChangeDetectorRef, + protected popOverService: XPopoverService + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + + // + // Options ... + const options = this.navParams.get( + nameof('options') + ) as XPopoverOption[]; + this.options = options; + + // + // Items ... + const items = this.navParams.get( + nameof('items') + ) as XPopoverItem[]; + this.items = items; + + // + // Error if not Options or Items ... + if (!this.hasChild(options) && !this.hasChild(items)) { + this.showError(XExceptionIDs.InvalidArgs, () => { + this.managerService.dialogService.popoverController.dismiss(); + }); + } + } + //#endregion + + // + //#region LifeCycles ... + public afterViewInit(): void { + super.afterViewInit(); + + // + // Height ... + this.height = this.navParams.get(nameof('height')); + if (!isNullOrEmptyString(this.height)) { + this.applyHeight(); + } + + // + // DividerColor ... + this.dividerColor = this.navParams.get( + nameof('dividerColor') + ); + if (!isNullOrEmptyString(this.dividerColor)) { + this.applyDividerColor(); + } + + // + // BackgroundColor ... + this.backgroundColor = this.navParams.get( + nameof('backgroundColor') + ); + if (!isNullOrEmptyString(this.backgroundColor)) { + this.applyBackgroundColor(); + } + } + //#endregion + + // + //#region UI Providers ... + //#endregion + + // + //#region UI Handlers ... + handleMenuSelected( + isItem: boolean, + item: XPopoverItem | XPopoverOption, + event: Event + ) { + // + if (!!isItem) { + this.handleItemSelected(item as XPopoverItem, event); + } else { + this.handleOptionSelected(item as XPopoverOption); + } + } + //#endregion + + // + //#region Privates ... + private async handleItemSelected(item: XPopoverItem, event: Event) { + // + const itemResult = + !isNullOrUndefined(item) && + !isNullOrUndefined(item.handler) && + isFunction(item.handler) + ? await this.getValueAsync(item.handler()) + : undefined; + + // + const optionsResultObj = !this.hasChild(item.options) + ? undefined + : await this.popOverService.presentContextMenu(item.options, event); + + // + if ( + isNullOrUndefined( + optionsResultObj || isNullOrUndefined(optionsResultObj.option) + ) + ) { + return; + } + + // + const popoverId = generatePopoverId(this.items); + + // + await this.managerService.dialogService.popoverController.dismiss( + { + item, + itemResult, + ...optionsResultObj, + } as XPopoverResult, + undefined, + popoverId + ); + } + + private async handleOptionSelected(option: XPopoverOption) { + // + const optionResult = + !isNullOrUndefined(option) && + !isNullOrUndefined(option.handler) && + isFunction(option.handler) + ? await this.getValueAsync(option.handler()) + : undefined; + + // + await this.managerService.dialogService.popoverController.dismiss({ + option, + optionResult, + } as XPopoverResult); + } + + private applyHeight() { + // + if (isNullOrEmptyString(this.height)) { + return; + } + + // + this.setCssVar(XPopoverCssVar.MaxPopoverHeight, this.height); + this.detectChanges(); + } + + private applyDividerColor() { + // + if (isNullOrEmptyString(this.dividerColor)) { + return; + } + + // + const color = this.dividerColor; + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XPopoverCssVar.DividerColor, `var(${bgColor})`); + this.detectChanges(); + } + + private applyBackgroundColor() { + // + if (isNullOrEmptyString(this.backgroundColor)) { + return; + } + + // + const color = this.backgroundColor; + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XPopoverCssVar.BackgroundColor, `var(${bgColor})`); + this.detectChanges(); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\dialogs\x-popover\x-popover.service.ts + +import { + toArray, + hasChild, + XBaseService, + XOneOrManyType, + isNullOrUndefined, +} from 'x-framework-core'; +import { + XPopoverItem, + XPopoverOption, + XPopoverResult, + XPopoverComponentProps, + XPopoverPresentOptions, + XPopoverPresenterOptions, +} from './x-popover.typings'; +import { Inject, Injectable } from '@angular/core'; +import { generatePopoverId } from './x-popover.tools'; +import { XManagerService } from 'x-framework-services'; +import { XPopoverComponent } from './x-popover.component'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../../config/x-framework-components.config'; + +@Injectable({ + providedIn: 'root', +}) +export class XPopoverService extends XBaseService { + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService + ) { + super(config); + } + //#endregion + + // + //#region Actions ... + /** + * Present Context Menu by Providinf XPopOverOption instance array ... + * + * @param options menu options which going to show to user ... + * @param event click event for positioning ... + */ + public async presentContextMenu( + options: XOneOrManyType, + event: Event, + props?: XPopoverPresentOptions, + presentProps?: XPopoverPresenterOptions + ) { + // + if (!hasChild(options)) { + return null; + } + + // + const overrideWidth = toArray(options).every((option) => !!option.onlyIcon); + if (overrideWidth) { + // + let currentCssClass = !isNullOrUndefined(presentProps) + ? toArray(presentProps.cssClass).join(' ') + : ''; + currentCssClass += `${ + currentCssClass.length > 0 ? ' ' : '' + }x-minimal-popover`; + + // + presentProps = { + ...presentProps, + cssClass: currentCssClass, + }; + } + + // + const mProps: XPopoverComponentProps = { + options, + ...props, + }; + + // + // Present Popover as Context Menu ... + const popOver = await this.managerService.dialogService.presentPopover({ + // + ...presentProps, + // + event, + translucent: true, + showBackdrop: true, + componentProps: mProps, + component: XPopoverComponent, + }); + + // + // Extract Data from PopOver ... + const { data } = await popOver.onWillDismiss(); + if (!data) { + return undefined; + } + + // + const result = data as XPopoverResult; + return result; + } + + /** + * Present Context Menu by Providinf XPopoverItem instance array ... + * + * @param items + * @param event + */ + public async presentContextMenuItems( + items: XOneOrManyType, + event: Event, + props?: XPopoverPresentOptions, + presentProps?: XPopoverPresenterOptions + ) { + // + if (!hasChild(items)) { + return null; + } + + // + const overrideWidth = toArray(items).every((i) => !!i.onlyIcon); + if (overrideWidth) { + // + let currentCssClass = !isNullOrUndefined(presentProps) + ? toArray(presentProps.cssClass).join(' ') + : ''; + currentCssClass += `${ + currentCssClass.length > 0 ? ' ' : '' + }x-minimal-popover`; + + // + presentProps = { + ...presentProps, + cssClass: currentCssClass, + }; + } + + // + // Generate unique id from items ... + const popoverId = generatePopoverId(items); + + // + const mProps: XPopoverComponentProps = { + items, + ...props, + }; + + // + // Present Popover as Context Menu ... + const popOver = await this.managerService.dialogService.presentPopover({ + // + ...presentProps, + // + event, + id: popoverId, + translucent: true, + showBackdrop: true, + componentProps: mProps, + component: XPopoverComponent, + }); + + // + // Extract Data from PopOver ... + const { data } = await popOver.onWillDismiss(); + if (!data) { + return null; + } + + // + const result = data as XPopoverResult; + return result; + } + //#endregion + + // + //#region Private ... + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\dialogs\x-popover\x-popover.tools.ts + +import { XListItem } from '../../x-list/x-list.typings'; +import { + toMd5, + toArray, + hasChild, + XOneOrManyType, + isNullOrUndefined, +} from 'x-framework-core'; +import { XPopoverItem, XPopoverOption } from './x-popover.typings'; + +export const generatePopoverId: ( + items: XOneOrManyType +) => string = (items) => { + // + const itemIds = toArray(items) + .map((i) => i.id) + .join('|'); + const result = toMd5(itemIds); + + // + return result; +}; + +export const toPopoverOptions: ( + item: XListItem +) => XOneOrManyType = (item) => { + // + let result: XPopoverOption[] = []; + + // + if (isNullOrUndefined(item) || !hasChild(item.slideOptions)) { + return result; + } + + // + result = item.slideOptions.map((so) => { + return { + id: so.id, + onlyIcon: so.onlyIcon, + disabled: so.disabled, + icon: so.icon, + color: so.color, + title: so.title, + slot: so.slot, + handler: so.handler, + } as XPopoverOption; + }); + + // + return result; +}; + +export const defaultPopoverItem: XPopoverItem = { + id: '', + icon: '', + color: '', + title: '', + options: [], + iconColor: '', + iconSlot: 'end', + onlyIcon: false, + disabled: false, + handler: () => {}, + showDetailedIcon: false, +}; + +export const defaultPopoverOption: XPopoverOption = { + id: '', + icon: '', + color: '', + title: '', + slot: 'end', + onlyIcon: false, + disabled: false, + handler: () => {}, +}; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\dialogs\x-popover\x-popover.typings.ts + +import { + XStandardType, + XOneOrManyType, + XPopoverOptions, + XColorIdentifier, + XResourceIdentifier, +} from 'x-framework-core'; +import { XPopoverComponent } from './x-popover.component'; +import { XIconIdentifier } from '../../x-icon/x-icon.typings'; + +export type XPopoverItemOptionSlot = 'end' | 'start'; + +export enum XPopoverCssVar { + DividerColor = 'divider-color', + BackgroundColor = 'background-color', + MaxPopoverHeight = 'max-popover-height', +} + +export type XPopoverCssVarIdentifier = XPopoverCssVar | string; + +export interface XPopoverOption { + id: string; + onlyIcon?: boolean; + disabled?: boolean; + icon?: XIconIdentifier; + color?: XColorIdentifier; + title?: XResourceIdentifier; + slot?: XPopoverItemOptionSlot; + handler?: () => XStandardType; +} + +export interface XPopoverItem { + id: string; + disabled?: boolean; + onlyIcon?: boolean; + icon?: XIconIdentifier; + color: XColorIdentifier; + title: XResourceIdentifier; + showDetailedIcon?: boolean; + iconColor?: XColorIdentifier; + options?: XPopoverOption[]; + iconSlot?: XPopoverItemOptionSlot; + handler?: () => XStandardType; +} + +export interface XPopoverResult { + // + item?: XPopoverItem; + itemResult?: any; + // + option?: XPopoverOption; + optionResult?: any; +} + +type SelectedKeysOfXpopoverComponent = Pick< + Partial, + 'items' | 'options' | 'height' | 'dividerColor' | 'backgroundColor' +>; +export type XPopoverComponentPropsType = { + [key in keyof SelectedKeysOfXpopoverComponent]: any; +}; + +export interface XPopoverComponentProps extends XPopoverComponentPropsType { + height?: string; + dividerColor?: XColorIdentifier; + backgroundColor?: XColorIdentifier; + items?: XOneOrManyType; + options?: XOneOrManyType; +} + +export type XPopoverPresentOptions = Pick< + Partial, + 'height' | 'dividerColor' | 'backgroundColor' +>; + +export type XPopoverPresenterOptions = Partial< + Omit< + XPopoverOptions, + | 'backdropDismiss' + | 'componentProps' + | 'component' + | 'id' + | 'translucent' + | 'showBackdrop' + | 'event' + > +>; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\dialogs\x-selector-dialog\x-selector-dialog.component.html + +
+ + + + + +
+ + + +
+ +
+
+ + + + +

+ {{ title | async }} +

+
+
+ + + + +
+ +
+ + +
+ +
+
+
+
+ + + +
+
+ + + + + + + + + +
+ + + + + + + + + + + {{ item.data | json }} + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\dialogs\x-selector-dialog\x-selector-dialog.component.scss + +::ng-deep.fullscreen-dialog { + // + width: 80vw !important; + max-width: 80vw !important; + + // + height: 80vh !important; + max-height: 80vh !important; + + // + &.mobile { + // + width: 100vw !important; + max-width: 100vw !important; + + // + height: 100vh !important; + max-height: 100vh !important; + } + + // + .mat-dialog-container { + padding: 0 !important; + } +} + +.x-dialog-wrapper { + width: 100% !important; + height: 100% !important; + position: relative !important; +} + +.x-dialog-header { + position: sticky !important; + top: 0; + z-index: 1000; +} + +.x-dialog-content-wrapper { + height: calc(100%); + + &.has-toolbar { + height: calc(100% - 56px); + } +} + +x-icon { + --size: 24px; +} + +.toolbar-area { + margin: 0px; +} + +.toolbar-item { + width: auto; + height: 48px; +} + +.toolbar-inline-item { + display: inline-flex !important; +} + +.toolbar-button { + width: 32px; + margin: { + left: 4px; + right: 4px; + } +} + +h1, +h2, +h3, +h4, +h5, +h6 { + margin: 0 !important; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\dialogs\x-selector-dialog\x-selector-dialog.component.ts + +import { + Inject, + NgZone, + Renderer2, + Component, + ElementRef, + ChangeDetectorRef, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + XTask, + XColor, + toArray, + isFunction, + generateUuid, + XOneOrManyType, + isNullOrUndefined, + isNullOrEmptyString, + XBaseDataPagerQuery, + XBaseDataPagerQueryResult, +} from 'x-framework-core'; +import { Observable, Subject } from 'rxjs'; +import { concatMap, map } from 'rxjs/operators'; +import { + XDataPagerActionModel, + XDataPagerPresentType, + XDataPagerPaginatorPosition, +} from '../../x-data-pager/x-data-pager.typings'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XSelectorDialogConfig } from './x-selector-dialog.typings'; +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { XListItem, XListActionModel } from '../../x-list/x-list.typings'; +import { XBaseDialogComponent } from '../../classes/x-base.dialog.component'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../../config/x-framework-components.config'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-selector-dialog', + templateUrl: './x-selector-dialog.component.html', + styleUrls: ['./x-selector-dialog.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XSelectorDialogComponent extends XBaseDialogComponent< + any, + XDataPagerActionModel, + XSelectorDialogConfig +> { + // + //#region Props ... + // + uuid = generateUuid('XFormSelector'); + + // + canSearch: boolean; + dataPagerQuery: XBaseDataPagerQuery = { + ...this.getDataPagerQuery(), + }; + showDataPagerPaginator: boolean = true; + dataPagerResultListItems: XListItem[] = []; + dataPagerResult: XBaseDataPagerQueryResult; + dataPagerActionProvider = new Subject(); + + // + pageListActionProvider = new Subject(); + + // + readonly DataPagerPresentTypes = Object.assign({}, XDataPagerPresentType); + //#endregion + + // + //#region Constructor ... + constructor( + public zone: NgZone, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public managerService: XManagerService, + public changeDetector: ChangeDetectorRef, + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + @Inject(MAT_DIALOG_DATA) public data: any, + public dialogRef: MatDialogRef + ) { + super( + zone, + element, + renderer, + ruler, + managerService, + changeDetector, + config, + data, + dialogRef + ); + } + //#endregion + + // + //#region Task Handlers ... + async onTaskFinished(name: string, task: XTask) { + await super.onTaskFinished(name, task); + + // + const tName = await this.getValueAsync(this.taskName); + if (name === tName) { + // + this.setLoadingState(false); + await this.handleDataPagerTaskFinished(task.result); + } + } + //#endregion + + // + //#region UI Providers ... + // + get taskName() { + return this.getData().pipe( + concatMap((config) => + this.getValue(config ? config.taskName : this.uuid) + ), + map((name) => (!isNullOrEmptyString(name) ? name : this.uuid)) + ); + } + + get queryFetcher() { + return this.getData().pipe( + concatMap((config) => + this.getValue(config ? config.queryFetcher : undefined) + ) + ); + } + + get itemTemplate() { + return this.getData().pipe( + concatMap((config) => + this.getValue(config ? config.itemTemplate : undefined) + ) + ); + } + + // + //#region Data Pager Paginator ... + get showPaginator() { + return this.getData().pipe( + concatMap((config) => + this.getOptionalValue(config ? config.showPaginator : false) + ) + ); + } + + get paginatorColor() { + return this.getData().pipe( + concatMap((config) => + this.getValue(config ? config.paginatorColor : XColor.Primary) + ), + map((color) => (!isNullOrEmptyString(color) ? color : XColor.Primary)) + ); + } + + get paginatorPosition() { + return this.getData().pipe( + concatMap((config) => + this.getValue( + config ? config.paginatorPosition : XDataPagerPaginatorPosition.Above + ) + ), + map((position) => + !isNullOrEmptyString(position) + ? position + : XDataPagerPaginatorPosition.Above + ) + ); + } + //#endregion + //#endregion + + // + //#region UI Handlers ... + async handleResetDataPagerQuery() { + // + this.dataPagerQuery = { + ...this.getDataPagerQuery(), + }; + + // + this.detectChanges(); + } + + async handleDataPagerTaskReady( + task: Observable> + ) { + // + // XBaseDataPagerQueryResult ... + if (!task || this.uiDisabled || this.loading) { + return; + } + + // + await this.issueDataPagerTask(task); + } + + async handleDataPagerQueryChanged(query: XBaseDataPagerQuery) { + // + if (!query) { + return; + } + + // + const config = await this.getValueAsync(this.getData()); + if (config && config.onQueryChanged && isFunction(config.onQueryChanged)) { + await this.getValueAsync(config.onQueryChanged(query)); + } + } + + async handleListItemSelected(event: XListItem) { + // + const canSelectMultipleItems = await this.getValueAsync( + this.canMultipleSelect + ); + + // + if (!event || !event.data || canSelectMultipleItems) { + return; + } + + // + const index = this.dataPagerResultListItems.findIndex((i) => + Object.is(i, event) + ); + if (index > -1) { + // + // Cancel Previous Selected Item(s) ... + this.dataPagerResultListItems.forEach((i) => { + i.disabled = false; + }); + + // + // Disable Current Selected Item ... + this.dataPagerResultListItems[index].disabled = true; + + // + this.detectChanges(); + } + + // + await this.handleSelectedItems(event); + } + + async handleSelectedItems(event: XOneOrManyType>) { + // + this.selectedItem = toArray(event).map((i) => i.data); + + // + const config = await this.getValueAsync(this.getData()); + if (config && config.onItemSelect && isFunction(config.onItemSelect)) { + await this.getValueAsync(config.onItemSelect(event)); + } + + // + this.detectChanges(); + } + + async handleListItemsSelected(event: XListItem[]) { + // + const canSelectMultipleItems = await this.getValueAsync( + this.canMultipleSelect + ); + + // + if (!this.hasChild(event) || !canSelectMultipleItems) { + return; + } + + // + await this.handleSelectedItems(event); + } + //#endregion + + // + //#region Abstracts ... + async getTaskNames(): Promise { + return [await this.getValueAsync(this.taskName)]; + } + + async deselectAllListItems(): Promise { + // + if (!this.hasChild(this.dataPagerResultListItems)) { + return; + } + + // + this.dataPagerResultListItems.forEach((i) => { + i.disabled = false; + }); + + // + this.dataPagerResultListItems = [...this.dataPagerResultListItems]; + + // + this.detectChanges(); + } + + async deselectItemInList(item: any): Promise { + // + if ( + isNullOrUndefined(item) || + !this.hasChild(this.dataPagerResultListItems) + ) { + return; + } + + // + const index = this.dataPagerResultListItems.findIndex((i) => + Object.is(i.data, item) + ); + if (index > -1) { + // + this.dataPagerResultListItems[index].disabled = false; + + // + this.dataPagerResultListItems = [...this.dataPagerResultListItems]; + + // + this.detectChanges(); + } + } + + async handleSearchbarQuryChanged(query: string): Promise { + // + this.dataPagerQuery = { + ...this.dataPagerQuery, + filter: query, + }; + + // + this.detectChanges(); + } + //#endregion + + // + //#region Private ... + private getDataPagerQuery(): XBaseDataPagerQuery { + return { + filter: '', + pageNumber: 1, + rowsInPage: this.config.defaultPageSize, + } as XBaseDataPagerQuery; + } + + private async issueDataPagerTask( + task: Observable> + ) { + // + if (!task) { + return; + } + + // + const mTaskName = await this.getValueAsync(this.taskName); + const mTask = task; + + // + await this.addOrUpdateTask({ + priority: 0, + affectLoading: true, + name: mTaskName, + task: mTask, + }); + + // + await this.dispatchTask(mTaskName); + } + + private async handleDataPagerTaskFinished( + result: XBaseDataPagerQueryResult + ) { + // + this.dataPagerResult = null; + this.dataPagerResultListItems = []; + this.detectChanges(); + + // + if (!result) { + return; + } + + // + this.dataPagerResult = { + ...result, + }; + + // + this.canSearch = + (this.hasChild(this.dataPagerResult.items) && + this.dataPagerResult.items.length > 1) || + !isNullOrEmptyString(this.dataPagerQuery.filter); + + // + this.showDataPagerPaginator = + this.hasChild(this.dataPagerResult.items) && + this.dataPagerResult.items.length > 1; + + // + const config = await this.getValueAsync(this.getData()); + if (config && config.onPageLoad && isFunction(config.onPageLoad)) { + await this.getValueAsync(config.onPageLoad(this.dataPagerResult)); + } + + // + this.dataPagerResultListItems = await this.prepareListItems(); + + // + this.detectChanges(); + } + + private async prepareListItems() { + // + if (!this.dataPagerResult || !this.hasChild(this.dataPagerResult.items)) { + return []; + } + + // + return this.dataPagerResult.items.map((sr) => { + return { + data: sr, + } as XListItem; + }); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\dialogs\x-selector-dialog\x-selector-dialog.service.ts + +import { + MatDialog, + MatDialogRef, + MatDialogConfig, +} from '@angular/material/dialog'; +import { Inject, Injectable } from '@angular/core'; +import { XResourceIdentifier } from 'x-framework-core'; +import { XManagerService } from 'x-framework-services'; +import { XSelectorDialogConfig } from './x-selector-dialog.typings'; +import { XSelectorDialogComponent } from './x-selector-dialog.component'; +import { XBaseDialogService } from '../../classes/x-base-dialog.service'; +import { XDataPagerActionModel } from '../../x-data-pager/x-data-pager.typings'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../../config/x-framework-components.config'; + +@Injectable({ + providedIn: 'root', +}) +export class XSelectorDialogService extends XBaseDialogService< + any, + XDataPagerActionModel, + XSelectorDialogConfig, + XSelectorDialogComponent +> { + // + defaultTitleResourceIdentifier: XResourceIdentifier; + + // + //#region Constructor ... + constructor( + protected dialog: MatDialog, + protected managerService: XManagerService, + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig + ) { + super(dialog, managerService, config); + } + //#endregion + + // + openDialogAndReturnRef( + config: MatDialogConfig + ): MatDialogRef { + return this.dialog.open(XSelectorDialogComponent, config); + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\dialogs\x-selector-dialog\x-selector-dialog.typings.ts + +import { + XStandardType, + XColorIdentifier, + XBaseDataPagerQuery, + XBaseDataPagerQueryResult, +} from 'x-framework-core'; +import { Observable } from 'rxjs'; +import { TemplateRef } from '@angular/core'; +import { + IXBaseDialogConfig, + IXBaseDialogResult, +} from '../../classes/x-base.dialog.component'; +import { + XDataPagerActionModel, + XDataPagerPaginatorPositionIdentifier, +} from '../../x-data-pager/x-data-pager.typings'; + +export interface XSelectorDialogResult extends IXBaseDialogResult {} + +export interface XSelectorDialogConfig + extends IXBaseDialogConfig { + // + showPaginator?: XStandardType; + paginatorColor?: XStandardType; + paginatorPosition?: XStandardType; + + // + queryFetcher?: ( + query: XBaseDataPagerQuery + ) => Observable>; + onQueryChanged?: (query: XBaseDataPagerQuery) => XStandardType; + onPageLoad?: (result: XBaseDataPagerQueryResult) => XStandardType; + itemTemplate?: XStandardType>; + + // + taskName?: XStandardType; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\modules\x-base-component.module.ts + +import { NgModule } from '@angular/core'; +import { IonicModule } from '@ionic/angular'; +import { CommonModule } from '@angular/common'; +import { RouterModule } from '@angular/router'; +import { XMaterialModule } from './x-material.module'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { XDirectivesModule as XFrameworkCoreDirectivesModule } from 'x-framework-core'; +import { XDirectivesModule as XFrameworkServicesDirectivesModule } from 'x-framework-services'; + +@NgModule({ + imports: [ + IonicModule, + FormsModule, + CommonModule, + RouterModule, + XMaterialModule, + ReactiveFormsModule, + XFrameworkCoreDirectivesModule, + XFrameworkServicesDirectivesModule, + ], + exports: [ + FormsModule, + IonicModule, + RouterModule, + CommonModule, + XMaterialModule, + ReactiveFormsModule, + XFrameworkCoreDirectivesModule, + XFrameworkServicesDirectivesModule, + ], +}) +export class XBaseComponentModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\modules\x-material.module.ts + +// +import { NgModule } from '@angular/core'; + +// +import { DragDropModule } from '@angular/cdk/drag-drop'; +import { ScrollingModule } from '@angular/cdk/scrolling'; +import { CdkTableModule } from '@angular/cdk/table'; +import { CdkTreeModule } from '@angular/cdk/tree'; + +// +// Form Controls ... +import { MatAutocompleteModule } from '@angular/material/autocomplete'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatDatepickerModule } from '@angular/material/datepicker'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatInputModule } from '@angular/material/input'; +import { MatRadioModule } from '@angular/material/radio'; +import { MatSelectModule } from '@angular/material/select'; +import { MatSliderModule } from '@angular/material/slider'; +import { MatSlideToggleModule } from '@angular/material/slide-toggle'; + +// +// Navigations / Menus ... +import { MatMenuModule } from '@angular/material/menu'; +import { MatSidenavModule } from '@angular/material/sidenav'; +import { MatToolbarModule } from '@angular/material/toolbar'; + +// +// Layout ... +import { MatCardModule } from '@angular/material/card'; +import { MatDividerModule } from '@angular/material/divider'; +import { MatExpansionModule } from '@angular/material/expansion'; +import { MatGridListModule } from '@angular/material/grid-list'; +import { MatListModule } from '@angular/material/list'; +import { MatStepperModule } from '@angular/material/stepper'; +import { MatTabsModule } from '@angular/material/tabs'; +import { MatTreeModule } from '@angular/material/tree'; + +// +// Buttons and Indicators ... +import { MatButtonModule } from '@angular/material/button'; +import { MatButtonToggleModule } from '@angular/material/button-toggle'; +import { MatBadgeModule } from '@angular/material/badge'; +import { MatChipsModule } from '@angular/material/chips'; +import { MatIconModule } from '@angular/material/icon'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; +import { MatProgressBarModule } from '@angular/material/progress-bar'; +import { MatRippleModule } from '@angular/material/core'; +import { MatNativeDateModule } from '@angular/material/core'; + +// +// Popups and Dialogs ... +import { MatBottomSheetModule } from '@angular/material/bottom-sheet'; +import { MatDialogModule } from '@angular/material/dialog'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { MatTooltipModule } from '@angular/material/tooltip'; + +// +// Data Table ... +import { MatPaginatorModule } from '@angular/material/paginator'; +import { MatSortModule } from '@angular/material/sort'; +import { MatTableModule } from '@angular/material/table'; + +// +import { + MomentDateModule, + MatMomentDateModule, +} from '@angular/material-moment-adapter'; +import { LayoutModule } from '@angular/cdk/layout'; +import { FlexLayoutModule } from '@angular/flex-layout'; +import { CommonModule } from '@angular/common'; + +@NgModule({ + imports: [ + CommonModule, + + // + CdkTableModule, + CdkTreeModule, + + // + DragDropModule, + ScrollingModule, + + // + LayoutModule, + FlexLayoutModule, + + // + MatAutocompleteModule, + MatBadgeModule, + MatBottomSheetModule, + MatButtonModule, + MatButtonToggleModule, + MatCardModule, + MatCheckboxModule, + MatChipsModule, + MatStepperModule, + MatDatepickerModule, + MatDialogModule, + MatDividerModule, + MatExpansionModule, + MatGridListModule, + MatIconModule, + MatInputModule, + MatListModule, + MatNativeDateModule, + MatPaginatorModule, + MatProgressBarModule, + MatProgressSpinnerModule, + MatRadioModule, + MatRippleModule, + MatSelectModule, + MatSidenavModule, + MatSliderModule, + MatSlideToggleModule, + MatSnackBarModule, + MatSortModule, + MatTableModule, + MatTabsModule, + MatToolbarModule, + MatTooltipModule, + MatTreeModule, + MatFormFieldModule, + MatMenuModule, + + // + MomentDateModule, + MatMomentDateModule, + ], + exports: [ + // + CdkTableModule, + CdkTreeModule, + + // + DragDropModule, + ScrollingModule, + + // + LayoutModule, + FlexLayoutModule, + + // + MatAutocompleteModule, + MatBadgeModule, + MatBottomSheetModule, + MatButtonModule, + MatButtonToggleModule, + MatCardModule, + MatCheckboxModule, + MatChipsModule, + MatStepperModule, + MatDatepickerModule, + MatDialogModule, + MatDividerModule, + MatExpansionModule, + MatGridListModule, + MatIconModule, + MatInputModule, + MatListModule, + MatNativeDateModule, + MatPaginatorModule, + MatProgressBarModule, + MatProgressSpinnerModule, + MatRadioModule, + MatRippleModule, + MatSelectModule, + MatSidenavModule, + MatSliderModule, + MatSlideToggleModule, + MatSnackBarModule, + MatSortModule, + MatTableModule, + MatTabsModule, + MatToolbarModule, + MatTooltipModule, + MatTreeModule, + MatFormFieldModule, + MatMenuModule, + + // + MomentDateModule, + MatMomentDateModule, + ], +}) +export class XMaterialModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\theme\_tools.scss + +@import "./animate.scss"; + +// +//#region Tools ... + +// +//#region Dimensions ... +// +// These are default values which can be used for margins and +// paddings ... +$d-xsm: 2px; +$d-sm: 4px; +$d-md: 8px; +$d-l: 16px; +$d-el: 32px; +$d-exl: 48px; + +// +// Box Sizing ... +$d-min: 300px; +$d-medium: 500px; +$d-max: 800px; + +// +// Percentages ... +$d-25p: 25%; +$d-50p: 50%; +$d-75p: 75%; +$d-100p: 100%; +//#endregion + +// +//#region Margin adnd Padding mixins ... +.m-0 { + margin: 0 !important; +} + +.p-0 { + padding: 0 !important; +} + +@mixin m-rl($size: $d-sm) { + margin-left: $size; + margin-right: $size; +} + +@mixin p-rl($size: $d-sm) { + padding-left: $size; + padding-right: $size; +} + +@mixin m-tb($size: $d-sm) { + margin-top: $size; + margin-bottom: $size; +} + +@mixin p-tb($size: $d-sm) { + padding-top: $size; + padding-bottom: $size; +} + +@mixin m($size: $d-sm) { + margin: $size !important; +} + +@mixin p($size: $d-sm) { + padding: $size !important; +} + +@mixin m-full($top: 0px, $left: 0px, $bottom: 0px, $right: 0px) { + margin: { + top: $top; + left: $left; + bottom: $bottom; + right: $right; + } +} + +@mixin p-full($top: 0px, $left: 0px, $bottom: 0px, $right: 0px) { + padding: { + top: $top; + left: $left; + bottom: $bottom; + right: $right; + } +} + +@mixin push-auto { + margin: { + left: auto; + right: auto; + } +} + +.push-auto { + margin: { + left: auto; + right: auto; + } +} + +// #endregion + +// +//#region Animation mixins and classes ... +@mixin animation( + $animation-name: ".fadeIn", + $animation-duration: 500ms, + $animation-delay: "", + $animation-repeat: 1 +) { + @extend .animated; + @extend .#{$animation-name}; + animation-delay: $animation-delay; + animation-duration: $animation-duration; + animation-iteration-count: $animation-repeat; +} + +@mixin transition-duration($duration: 500ms, $delay: 0ms) { + // + transition-delay: $delay; + -webkit-transition-delay: $delay; + + // + transition-duration: $duration; + -webkit-transition-duration: $duration; +} + +@mixin transformer($transform, $duration: 500ms, $delay: 0ms) { + // + transform: $transform; + -ms-transform: $transform; + -webkit-transform: $transform; + + // + @include transition(transform, $duration, $delay); +} + +@mixin transition($transition, $duration: 500ms, $delay: 0ms) { + // + transition: $transition; + -webkit-transition: $transition; + + @include transition-duration($duration, $delay); +} + +@mixin show { + @include animation("fadeIn", "250ms"); + @include transition("height", "250ms"); +} + +@mixin hide { + @include animation("fadeOut", "250ms"); + + height: 0; + @include transition("height", "250ms", "250ms"); +} + +.show { + @include show(); +} + +.hide { + @include hide(); +} + +//#endregion + +// +//#region Width Height and Alignment ... +@mixin applyWidthHeight($size: $d-75p, $min-width: $d-min, $max-width: $d-max) { + width: calc(#{$size}); + max-width: $max-width; + min-width: $min-width; +} + +@mixin horizontal-spacer { + flex: 1 1 auto; +} + +.horizontal-spacer { + @include horizontal-spacer(); +} + +@mixin center-vertical-flex { + // + display: flex !important; + flex-direction: column !important; + justify-content: center !important; +} + +.center-vertical-flex { + @include center-vertical-flex(); +} + +@mixin center-vertical { + position: relative; + top: 50%; + transform: translateY(-50%); +} + +.center-vertical { + @include center-vertical(); +} + +@mixin center { + @extend .center; +} + +.center { + text-align: center; + margin-left: auto; + margin-right: auto; +} + +@mixin text-truncate { + @extend .text-truncate; +} + +.text-truncate { + display: inline-block; + overflow-x: hidden !important; + text-overflow: ellipsis !important; + white-space: nowrap; + min-width: 0; +} + +//#endregion + +// +//#region Shadows Tools ... +@mixin add-shadow-box($vert, $horz, $blur, $size, $color: $shadow-color) { + // + $box-shadow-data: $vert $horz $blur $size $shadow-color !important; + + // + box-shadow: $box-shadow-data; + -moz-box-shadow: $box-shadow-data; + -webkit-box-shadow: $box-shadow-data; +} + +@mixin remove-shadow-box() { + // + $box-shadow-data: none !important; + + // + box-shadow: $box-shadow-data; + -moz-box-shadow: $box-shadow-data; + -webkit-box-shadow: $box-shadow-data; +} + +@mixin filterBlur($level: 4px) { + -webkit-filter: blur($level); + filter: blur($level); +} + +@mixin filterBrightness($level: 0.3) { + -webkit-filter: brightness($level); + filter: brightness($level); +} + +@mixin filterContrast($level: 180%) { + -webkit-filter: contrast($level); + filter: contrast($level); +} + +@mixin filterGrayscale($level: 100%) { + -webkit-filter: grayscale($level); + filter: grayscale($level); +} + +@mixin filterHuerotate($level: 180deg) { + -webkit-filter: hue-rotate($level); + filter: hue-rotate($level); +} + +@mixin filterInvert($level: 100%) { + -webkit-filter: invert($level); + filter: invert($level); +} + +@mixin filterOpacity($level: 50%) { + -webkit-filter: opacity($level); + filter: opacity($level); +} + +@mixin filterSaturate($level: 7) { + -webkit-filter: saturate($level); + filter: saturate($level); +} + +@mixin filterSepia($level: 100%) { + -webkit-filter: sepia($level); + filter: sepia(100%); +} + +//#endregion + +// +//#region Other ... +@mixin disableLink { + pointer-events: none; + + * { + pointer-events: none; + } +} +//#endregion +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\theme\animate.scss + +@charset "UTF-8"; + +/*! + * animate.css -http://daneden.me/animate + * Version - 3.7.0 + * Licensed under the MIT license - http://opensource.org/licenses/MIT + * + * Copyright (c) 2018 Daniel Eden + */ + +@-webkit-keyframes bounce { + from, + 20%, + 53%, + 80%, + to { + -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + 40%, + 43% { + -webkit-animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); + animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); + -webkit-transform: translate3d(0, -30px, 0); + transform: translate3d(0, -30px, 0); + } + + 70% { + -webkit-animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); + animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); + -webkit-transform: translate3d(0, -15px, 0); + transform: translate3d(0, -15px, 0); + } + + 90% { + -webkit-transform: translate3d(0, -4px, 0); + transform: translate3d(0, -4px, 0); + } +} + +@keyframes bounce { + from, + 20%, + 53%, + 80%, + to { + -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + 40%, + 43% { + -webkit-animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); + animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); + -webkit-transform: translate3d(0, -30px, 0); + transform: translate3d(0, -30px, 0); + } + + 70% { + -webkit-animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); + animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); + -webkit-transform: translate3d(0, -15px, 0); + transform: translate3d(0, -15px, 0); + } + + 90% { + -webkit-transform: translate3d(0, -4px, 0); + transform: translate3d(0, -4px, 0); + } +} + +.bounce { + -webkit-animation-name: bounce; + animation-name: bounce; + -webkit-transform-origin: center bottom; + transform-origin: center bottom; +} + +@-webkit-keyframes flash { + from, + 50%, + to { + opacity: 1; + } + + 25%, + 75% { + opacity: 0; + } +} + +@keyframes flash { + from, + 50%, + to { + opacity: 1; + } + + 25%, + 75% { + opacity: 0; + } +} + +.flash { + -webkit-animation-name: flash; + animation-name: flash; +} + +/* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ + +@-webkit-keyframes pulse { + from { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } + + 50% { + -webkit-transform: scale3d(1.05, 1.05, 1.05); + transform: scale3d(1.05, 1.05, 1.05); + } + + to { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } +} + +@keyframes pulse { + from { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } + + 50% { + -webkit-transform: scale3d(1.05, 1.05, 1.05); + transform: scale3d(1.05, 1.05, 1.05); + } + + to { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } +} + +.pulse { + -webkit-animation-name: pulse; + animation-name: pulse; +} + +@-webkit-keyframes rubberBand { + from { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } + + 30% { + -webkit-transform: scale3d(1.25, 0.75, 1); + transform: scale3d(1.25, 0.75, 1); + } + + 40% { + -webkit-transform: scale3d(0.75, 1.25, 1); + transform: scale3d(0.75, 1.25, 1); + } + + 50% { + -webkit-transform: scale3d(1.15, 0.85, 1); + transform: scale3d(1.15, 0.85, 1); + } + + 65% { + -webkit-transform: scale3d(0.95, 1.05, 1); + transform: scale3d(0.95, 1.05, 1); + } + + 75% { + -webkit-transform: scale3d(1.05, 0.95, 1); + transform: scale3d(1.05, 0.95, 1); + } + + to { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } +} + +@keyframes rubberBand { + from { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } + + 30% { + -webkit-transform: scale3d(1.25, 0.75, 1); + transform: scale3d(1.25, 0.75, 1); + } + + 40% { + -webkit-transform: scale3d(0.75, 1.25, 1); + transform: scale3d(0.75, 1.25, 1); + } + + 50% { + -webkit-transform: scale3d(1.15, 0.85, 1); + transform: scale3d(1.15, 0.85, 1); + } + + 65% { + -webkit-transform: scale3d(0.95, 1.05, 1); + transform: scale3d(0.95, 1.05, 1); + } + + 75% { + -webkit-transform: scale3d(1.05, 0.95, 1); + transform: scale3d(1.05, 0.95, 1); + } + + to { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } +} + +.rubberBand { + -webkit-animation-name: rubberBand; + animation-name: rubberBand; +} + +@-webkit-keyframes shake { + from, + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + 10%, + 30%, + 50%, + 70%, + 90% { + -webkit-transform: translate3d(-10px, 0, 0); + transform: translate3d(-10px, 0, 0); + } + + 20%, + 40%, + 60%, + 80% { + -webkit-transform: translate3d(10px, 0, 0); + transform: translate3d(10px, 0, 0); + } +} + +@keyframes shake { + from, + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + 10%, + 30%, + 50%, + 70%, + 90% { + -webkit-transform: translate3d(-10px, 0, 0); + transform: translate3d(-10px, 0, 0); + } + + 20%, + 40%, + 60%, + 80% { + -webkit-transform: translate3d(10px, 0, 0); + transform: translate3d(10px, 0, 0); + } +} + +.shake { + -webkit-animation-name: shake; + animation-name: shake; +} + +@-webkit-keyframes headShake { + 0% { + -webkit-transform: translateX(0); + transform: translateX(0); + } + + 6.5% { + -webkit-transform: translateX(-6px) rotateY(-9deg); + transform: translateX(-6px) rotateY(-9deg); + } + + 18.5% { + -webkit-transform: translateX(5px) rotateY(7deg); + transform: translateX(5px) rotateY(7deg); + } + + 31.5% { + -webkit-transform: translateX(-3px) rotateY(-5deg); + transform: translateX(-3px) rotateY(-5deg); + } + + 43.5% { + -webkit-transform: translateX(2px) rotateY(3deg); + transform: translateX(2px) rotateY(3deg); + } + + 50% { + -webkit-transform: translateX(0); + transform: translateX(0); + } +} + +@keyframes headShake { + 0% { + -webkit-transform: translateX(0); + transform: translateX(0); + } + + 6.5% { + -webkit-transform: translateX(-6px) rotateY(-9deg); + transform: translateX(-6px) rotateY(-9deg); + } + + 18.5% { + -webkit-transform: translateX(5px) rotateY(7deg); + transform: translateX(5px) rotateY(7deg); + } + + 31.5% { + -webkit-transform: translateX(-3px) rotateY(-5deg); + transform: translateX(-3px) rotateY(-5deg); + } + + 43.5% { + -webkit-transform: translateX(2px) rotateY(3deg); + transform: translateX(2px) rotateY(3deg); + } + + 50% { + -webkit-transform: translateX(0); + transform: translateX(0); + } +} + +.headShake { + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + -webkit-animation-name: headShake; + animation-name: headShake; +} + +@-webkit-keyframes swing { + 20% { + -webkit-transform: rotate3d(0, 0, 1, 15deg); + transform: rotate3d(0, 0, 1, 15deg); + } + + 40% { + -webkit-transform: rotate3d(0, 0, 1, -10deg); + transform: rotate3d(0, 0, 1, -10deg); + } + + 60% { + -webkit-transform: rotate3d(0, 0, 1, 5deg); + transform: rotate3d(0, 0, 1, 5deg); + } + + 80% { + -webkit-transform: rotate3d(0, 0, 1, -5deg); + transform: rotate3d(0, 0, 1, -5deg); + } + + to { + -webkit-transform: rotate3d(0, 0, 1, 0deg); + transform: rotate3d(0, 0, 1, 0deg); + } +} + +@keyframes swing { + 20% { + -webkit-transform: rotate3d(0, 0, 1, 15deg); + transform: rotate3d(0, 0, 1, 15deg); + } + + 40% { + -webkit-transform: rotate3d(0, 0, 1, -10deg); + transform: rotate3d(0, 0, 1, -10deg); + } + + 60% { + -webkit-transform: rotate3d(0, 0, 1, 5deg); + transform: rotate3d(0, 0, 1, 5deg); + } + + 80% { + -webkit-transform: rotate3d(0, 0, 1, -5deg); + transform: rotate3d(0, 0, 1, -5deg); + } + + to { + -webkit-transform: rotate3d(0, 0, 1, 0deg); + transform: rotate3d(0, 0, 1, 0deg); + } +} + +.swing { + -webkit-transform-origin: top center; + transform-origin: top center; + -webkit-animation-name: swing; + animation-name: swing; +} + +@-webkit-keyframes tada { + from { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } + + 10%, + 20% { + -webkit-transform: scale3d(0.9, 0.9, 0.9) rotate3d(0, 0, 1, -3deg); + transform: scale3d(0.9, 0.9, 0.9) rotate3d(0, 0, 1, -3deg); + } + + 30%, + 50%, + 70%, + 90% { + -webkit-transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg); + transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg); + } + + 40%, + 60%, + 80% { + -webkit-transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg); + transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg); + } + + to { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } +} + +@keyframes tada { + from { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } + + 10%, + 20% { + -webkit-transform: scale3d(0.9, 0.9, 0.9) rotate3d(0, 0, 1, -3deg); + transform: scale3d(0.9, 0.9, 0.9) rotate3d(0, 0, 1, -3deg); + } + + 30%, + 50%, + 70%, + 90% { + -webkit-transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg); + transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg); + } + + 40%, + 60%, + 80% { + -webkit-transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg); + transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg); + } + + to { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } +} + +.tada { + -webkit-animation-name: tada; + animation-name: tada; +} + +/* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ + +@-webkit-keyframes wobble { + from { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + 15% { + -webkit-transform: translate3d(-25%, 0, 0) rotate3d(0, 0, 1, -5deg); + transform: translate3d(-25%, 0, 0) rotate3d(0, 0, 1, -5deg); + } + + 30% { + -webkit-transform: translate3d(20%, 0, 0) rotate3d(0, 0, 1, 3deg); + transform: translate3d(20%, 0, 0) rotate3d(0, 0, 1, 3deg); + } + + 45% { + -webkit-transform: translate3d(-15%, 0, 0) rotate3d(0, 0, 1, -3deg); + transform: translate3d(-15%, 0, 0) rotate3d(0, 0, 1, -3deg); + } + + 60% { + -webkit-transform: translate3d(10%, 0, 0) rotate3d(0, 0, 1, 2deg); + transform: translate3d(10%, 0, 0) rotate3d(0, 0, 1, 2deg); + } + + 75% { + -webkit-transform: translate3d(-5%, 0, 0) rotate3d(0, 0, 1, -1deg); + transform: translate3d(-5%, 0, 0) rotate3d(0, 0, 1, -1deg); + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes wobble { + from { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + 15% { + -webkit-transform: translate3d(-25%, 0, 0) rotate3d(0, 0, 1, -5deg); + transform: translate3d(-25%, 0, 0) rotate3d(0, 0, 1, -5deg); + } + + 30% { + -webkit-transform: translate3d(20%, 0, 0) rotate3d(0, 0, 1, 3deg); + transform: translate3d(20%, 0, 0) rotate3d(0, 0, 1, 3deg); + } + + 45% { + -webkit-transform: translate3d(-15%, 0, 0) rotate3d(0, 0, 1, -3deg); + transform: translate3d(-15%, 0, 0) rotate3d(0, 0, 1, -3deg); + } + + 60% { + -webkit-transform: translate3d(10%, 0, 0) rotate3d(0, 0, 1, 2deg); + transform: translate3d(10%, 0, 0) rotate3d(0, 0, 1, 2deg); + } + + 75% { + -webkit-transform: translate3d(-5%, 0, 0) rotate3d(0, 0, 1, -1deg); + transform: translate3d(-5%, 0, 0) rotate3d(0, 0, 1, -1deg); + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.wobble { + -webkit-animation-name: wobble; + animation-name: wobble; +} + +@-webkit-keyframes jello { + from, + 11.1%, + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + 22.2% { + -webkit-transform: skewX(-12.5deg) skewY(-12.5deg); + transform: skewX(-12.5deg) skewY(-12.5deg); + } + + 33.3% { + -webkit-transform: skewX(6.25deg) skewY(6.25deg); + transform: skewX(6.25deg) skewY(6.25deg); + } + + 44.4% { + -webkit-transform: skewX(-3.125deg) skewY(-3.125deg); + transform: skewX(-3.125deg) skewY(-3.125deg); + } + + 55.5% { + -webkit-transform: skewX(1.5625deg) skewY(1.5625deg); + transform: skewX(1.5625deg) skewY(1.5625deg); + } + + 66.6% { + -webkit-transform: skewX(-0.78125deg) skewY(-0.78125deg); + transform: skewX(-0.78125deg) skewY(-0.78125deg); + } + + 77.7% { + -webkit-transform: skewX(0.390625deg) skewY(0.390625deg); + transform: skewX(0.390625deg) skewY(0.390625deg); + } + + 88.8% { + -webkit-transform: skewX(-0.1953125deg) skewY(-0.1953125deg); + transform: skewX(-0.1953125deg) skewY(-0.1953125deg); + } +} + +@keyframes jello { + from, + 11.1%, + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + 22.2% { + -webkit-transform: skewX(-12.5deg) skewY(-12.5deg); + transform: skewX(-12.5deg) skewY(-12.5deg); + } + + 33.3% { + -webkit-transform: skewX(6.25deg) skewY(6.25deg); + transform: skewX(6.25deg) skewY(6.25deg); + } + + 44.4% { + -webkit-transform: skewX(-3.125deg) skewY(-3.125deg); + transform: skewX(-3.125deg) skewY(-3.125deg); + } + + 55.5% { + -webkit-transform: skewX(1.5625deg) skewY(1.5625deg); + transform: skewX(1.5625deg) skewY(1.5625deg); + } + + 66.6% { + -webkit-transform: skewX(-0.78125deg) skewY(-0.78125deg); + transform: skewX(-0.78125deg) skewY(-0.78125deg); + } + + 77.7% { + -webkit-transform: skewX(0.390625deg) skewY(0.390625deg); + transform: skewX(0.390625deg) skewY(0.390625deg); + } + + 88.8% { + -webkit-transform: skewX(-0.1953125deg) skewY(-0.1953125deg); + transform: skewX(-0.1953125deg) skewY(-0.1953125deg); + } +} + +.jello { + -webkit-animation-name: jello; + animation-name: jello; + -webkit-transform-origin: center; + transform-origin: center; +} + +@-webkit-keyframes heartBeat { + 0% { + -webkit-transform: scale(1); + transform: scale(1); + } + + 14% { + -webkit-transform: scale(1.3); + transform: scale(1.3); + } + + 28% { + -webkit-transform: scale(1); + transform: scale(1); + } + + 42% { + -webkit-transform: scale(1.3); + transform: scale(1.3); + } + + 70% { + -webkit-transform: scale(1); + transform: scale(1); + } +} + +@keyframes heartBeat { + 0% { + -webkit-transform: scale(1); + transform: scale(1); + } + + 14% { + -webkit-transform: scale(1.3); + transform: scale(1.3); + } + + 28% { + -webkit-transform: scale(1); + transform: scale(1); + } + + 42% { + -webkit-transform: scale(1.3); + transform: scale(1.3); + } + + 70% { + -webkit-transform: scale(1); + transform: scale(1); + } +} + +.heartBeat { + -webkit-animation-name: heartBeat; + animation-name: heartBeat; + -webkit-animation-duration: 1.3s; + animation-duration: 1.3s; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; +} + +@-webkit-keyframes bounceIn { + from, + 20%, + 40%, + 60%, + 80%, + to { + -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + } + + 0% { + opacity: 0; + -webkit-transform: scale3d(0.3, 0.3, 0.3); + transform: scale3d(0.3, 0.3, 0.3); + } + + 20% { + -webkit-transform: scale3d(1.1, 1.1, 1.1); + transform: scale3d(1.1, 1.1, 1.1); + } + + 40% { + -webkit-transform: scale3d(0.9, 0.9, 0.9); + transform: scale3d(0.9, 0.9, 0.9); + } + + 60% { + opacity: 1; + -webkit-transform: scale3d(1.03, 1.03, 1.03); + transform: scale3d(1.03, 1.03, 1.03); + } + + 80% { + -webkit-transform: scale3d(0.97, 0.97, 0.97); + transform: scale3d(0.97, 0.97, 0.97); + } + + to { + opacity: 1; + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } +} + +@keyframes bounceIn { + from, + 20%, + 40%, + 60%, + 80%, + to { + -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + } + + 0% { + opacity: 0; + -webkit-transform: scale3d(0.3, 0.3, 0.3); + transform: scale3d(0.3, 0.3, 0.3); + } + + 20% { + -webkit-transform: scale3d(1.1, 1.1, 1.1); + transform: scale3d(1.1, 1.1, 1.1); + } + + 40% { + -webkit-transform: scale3d(0.9, 0.9, 0.9); + transform: scale3d(0.9, 0.9, 0.9); + } + + 60% { + opacity: 1; + -webkit-transform: scale3d(1.03, 1.03, 1.03); + transform: scale3d(1.03, 1.03, 1.03); + } + + 80% { + -webkit-transform: scale3d(0.97, 0.97, 0.97); + transform: scale3d(0.97, 0.97, 0.97); + } + + to { + opacity: 1; + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } +} + +.bounceIn { + -webkit-animation-duration: 0.75s; + animation-duration: 0.75s; + -webkit-animation-name: bounceIn; + animation-name: bounceIn; +} + +@-webkit-keyframes bounceInDown { + from, + 60%, + 75%, + 90%, + to { + -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + } + + 0% { + opacity: 0; + -webkit-transform: translate3d(0, -3000px, 0); + transform: translate3d(0, -3000px, 0); + } + + 60% { + opacity: 1; + -webkit-transform: translate3d(0, 25px, 0); + transform: translate3d(0, 25px, 0); + } + + 75% { + -webkit-transform: translate3d(0, -10px, 0); + transform: translate3d(0, -10px, 0); + } + + 90% { + -webkit-transform: translate3d(0, 5px, 0); + transform: translate3d(0, 5px, 0); + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes bounceInDown { + from, + 60%, + 75%, + 90%, + to { + -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + } + + 0% { + opacity: 0; + -webkit-transform: translate3d(0, -3000px, 0); + transform: translate3d(0, -3000px, 0); + } + + 60% { + opacity: 1; + -webkit-transform: translate3d(0, 25px, 0); + transform: translate3d(0, 25px, 0); + } + + 75% { + -webkit-transform: translate3d(0, -10px, 0); + transform: translate3d(0, -10px, 0); + } + + 90% { + -webkit-transform: translate3d(0, 5px, 0); + transform: translate3d(0, 5px, 0); + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.bounceInDown { + -webkit-animation-name: bounceInDown; + animation-name: bounceInDown; +} + +@-webkit-keyframes bounceInLeft { + from, + 60%, + 75%, + 90%, + to { + -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + } + + 0% { + opacity: 0; + -webkit-transform: translate3d(-3000px, 0, 0); + transform: translate3d(-3000px, 0, 0); + } + + 60% { + opacity: 1; + -webkit-transform: translate3d(25px, 0, 0); + transform: translate3d(25px, 0, 0); + } + + 75% { + -webkit-transform: translate3d(-10px, 0, 0); + transform: translate3d(-10px, 0, 0); + } + + 90% { + -webkit-transform: translate3d(5px, 0, 0); + transform: translate3d(5px, 0, 0); + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes bounceInLeft { + from, + 60%, + 75%, + 90%, + to { + -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + } + + 0% { + opacity: 0; + -webkit-transform: translate3d(-3000px, 0, 0); + transform: translate3d(-3000px, 0, 0); + } + + 60% { + opacity: 1; + -webkit-transform: translate3d(25px, 0, 0); + transform: translate3d(25px, 0, 0); + } + + 75% { + -webkit-transform: translate3d(-10px, 0, 0); + transform: translate3d(-10px, 0, 0); + } + + 90% { + -webkit-transform: translate3d(5px, 0, 0); + transform: translate3d(5px, 0, 0); + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.bounceInLeft { + -webkit-animation-name: bounceInLeft; + animation-name: bounceInLeft; +} + +@-webkit-keyframes bounceInRight { + from, + 60%, + 75%, + 90%, + to { + -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + } + + from { + opacity: 0; + -webkit-transform: translate3d(3000px, 0, 0); + transform: translate3d(3000px, 0, 0); + } + + 60% { + opacity: 1; + -webkit-transform: translate3d(-25px, 0, 0); + transform: translate3d(-25px, 0, 0); + } + + 75% { + -webkit-transform: translate3d(10px, 0, 0); + transform: translate3d(10px, 0, 0); + } + + 90% { + -webkit-transform: translate3d(-5px, 0, 0); + transform: translate3d(-5px, 0, 0); + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes bounceInRight { + from, + 60%, + 75%, + 90%, + to { + -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + } + + from { + opacity: 0; + -webkit-transform: translate3d(3000px, 0, 0); + transform: translate3d(3000px, 0, 0); + } + + 60% { + opacity: 1; + -webkit-transform: translate3d(-25px, 0, 0); + transform: translate3d(-25px, 0, 0); + } + + 75% { + -webkit-transform: translate3d(10px, 0, 0); + transform: translate3d(10px, 0, 0); + } + + 90% { + -webkit-transform: translate3d(-5px, 0, 0); + transform: translate3d(-5px, 0, 0); + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.bounceInRight { + -webkit-animation-name: bounceInRight; + animation-name: bounceInRight; +} + +@-webkit-keyframes bounceInUp { + from, + 60%, + 75%, + 90%, + to { + -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + } + + from { + opacity: 0; + -webkit-transform: translate3d(0, 3000px, 0); + transform: translate3d(0, 3000px, 0); + } + + 60% { + opacity: 1; + -webkit-transform: translate3d(0, -20px, 0); + transform: translate3d(0, -20px, 0); + } + + 75% { + -webkit-transform: translate3d(0, 10px, 0); + transform: translate3d(0, 10px, 0); + } + + 90% { + -webkit-transform: translate3d(0, -5px, 0); + transform: translate3d(0, -5px, 0); + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes bounceInUp { + from, + 60%, + 75%, + 90%, + to { + -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + } + + from { + opacity: 0; + -webkit-transform: translate3d(0, 3000px, 0); + transform: translate3d(0, 3000px, 0); + } + + 60% { + opacity: 1; + -webkit-transform: translate3d(0, -20px, 0); + transform: translate3d(0, -20px, 0); + } + + 75% { + -webkit-transform: translate3d(0, 10px, 0); + transform: translate3d(0, 10px, 0); + } + + 90% { + -webkit-transform: translate3d(0, -5px, 0); + transform: translate3d(0, -5px, 0); + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.bounceInUp { + -webkit-animation-name: bounceInUp; + animation-name: bounceInUp; +} + +@-webkit-keyframes bounceOut { + 20% { + -webkit-transform: scale3d(0.9, 0.9, 0.9); + transform: scale3d(0.9, 0.9, 0.9); + } + + 50%, + 55% { + opacity: 1; + -webkit-transform: scale3d(1.1, 1.1, 1.1); + transform: scale3d(1.1, 1.1, 1.1); + } + + to { + opacity: 0; + -webkit-transform: scale3d(0.3, 0.3, 0.3); + transform: scale3d(0.3, 0.3, 0.3); + } +} + +@keyframes bounceOut { + 20% { + -webkit-transform: scale3d(0.9, 0.9, 0.9); + transform: scale3d(0.9, 0.9, 0.9); + } + + 50%, + 55% { + opacity: 1; + -webkit-transform: scale3d(1.1, 1.1, 1.1); + transform: scale3d(1.1, 1.1, 1.1); + } + + to { + opacity: 0; + -webkit-transform: scale3d(0.3, 0.3, 0.3); + transform: scale3d(0.3, 0.3, 0.3); + } +} + +.bounceOut { + -webkit-animation-duration: 0.75s; + animation-duration: 0.75s; + -webkit-animation-name: bounceOut; + animation-name: bounceOut; +} + +@-webkit-keyframes bounceOutDown { + 20% { + -webkit-transform: translate3d(0, 10px, 0); + transform: translate3d(0, 10px, 0); + } + + 40%, + 45% { + opacity: 1; + -webkit-transform: translate3d(0, -20px, 0); + transform: translate3d(0, -20px, 0); + } + + to { + opacity: 0; + -webkit-transform: translate3d(0, 2000px, 0); + transform: translate3d(0, 2000px, 0); + } +} + +@keyframes bounceOutDown { + 20% { + -webkit-transform: translate3d(0, 10px, 0); + transform: translate3d(0, 10px, 0); + } + + 40%, + 45% { + opacity: 1; + -webkit-transform: translate3d(0, -20px, 0); + transform: translate3d(0, -20px, 0); + } + + to { + opacity: 0; + -webkit-transform: translate3d(0, 2000px, 0); + transform: translate3d(0, 2000px, 0); + } +} + +.bounceOutDown { + -webkit-animation-name: bounceOutDown; + animation-name: bounceOutDown; +} + +@-webkit-keyframes bounceOutLeft { + 20% { + opacity: 1; + -webkit-transform: translate3d(20px, 0, 0); + transform: translate3d(20px, 0, 0); + } + + to { + opacity: 0; + -webkit-transform: translate3d(-2000px, 0, 0); + transform: translate3d(-2000px, 0, 0); + } +} + +@keyframes bounceOutLeft { + 20% { + opacity: 1; + -webkit-transform: translate3d(20px, 0, 0); + transform: translate3d(20px, 0, 0); + } + + to { + opacity: 0; + -webkit-transform: translate3d(-2000px, 0, 0); + transform: translate3d(-2000px, 0, 0); + } +} + +.bounceOutLeft { + -webkit-animation-name: bounceOutLeft; + animation-name: bounceOutLeft; +} + +@-webkit-keyframes bounceOutRight { + 20% { + opacity: 1; + -webkit-transform: translate3d(-20px, 0, 0); + transform: translate3d(-20px, 0, 0); + } + + to { + opacity: 0; + -webkit-transform: translate3d(2000px, 0, 0); + transform: translate3d(2000px, 0, 0); + } +} + +@keyframes bounceOutRight { + 20% { + opacity: 1; + -webkit-transform: translate3d(-20px, 0, 0); + transform: translate3d(-20px, 0, 0); + } + + to { + opacity: 0; + -webkit-transform: translate3d(2000px, 0, 0); + transform: translate3d(2000px, 0, 0); + } +} + +.bounceOutRight { + -webkit-animation-name: bounceOutRight; + animation-name: bounceOutRight; +} + +@-webkit-keyframes bounceOutUp { + 20% { + -webkit-transform: translate3d(0, -10px, 0); + transform: translate3d(0, -10px, 0); + } + + 40%, + 45% { + opacity: 1; + -webkit-transform: translate3d(0, 20px, 0); + transform: translate3d(0, 20px, 0); + } + + to { + opacity: 0; + -webkit-transform: translate3d(0, -2000px, 0); + transform: translate3d(0, -2000px, 0); + } +} + +@keyframes bounceOutUp { + 20% { + -webkit-transform: translate3d(0, -10px, 0); + transform: translate3d(0, -10px, 0); + } + + 40%, + 45% { + opacity: 1; + -webkit-transform: translate3d(0, 20px, 0); + transform: translate3d(0, 20px, 0); + } + + to { + opacity: 0; + -webkit-transform: translate3d(0, -2000px, 0); + transform: translate3d(0, -2000px, 0); + } +} + +.bounceOutUp { + -webkit-animation-name: bounceOutUp; + animation-name: bounceOutUp; +} + +@-webkit-keyframes fadeIn { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} + +@keyframes fadeIn { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} + +.fadeIn { + -webkit-animation-name: fadeIn; + animation-name: fadeIn; +} + +@-webkit-keyframes fadeInDown { + from { + opacity: 0; + -webkit-transform: translate3d(0, -100%, 0); + transform: translate3d(0, -100%, 0); + } + + to { + opacity: 1; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes fadeInDown { + from { + opacity: 0; + -webkit-transform: translate3d(0, -100%, 0); + transform: translate3d(0, -100%, 0); + } + + to { + opacity: 1; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.fadeInDown { + -webkit-animation-name: fadeInDown; + animation-name: fadeInDown; +} + +@-webkit-keyframes fadeInDownBig { + from { + opacity: 0; + -webkit-transform: translate3d(0, -2000px, 0); + transform: translate3d(0, -2000px, 0); + } + + to { + opacity: 1; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes fadeInDownBig { + from { + opacity: 0; + -webkit-transform: translate3d(0, -2000px, 0); + transform: translate3d(0, -2000px, 0); + } + + to { + opacity: 1; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.fadeInDownBig { + -webkit-animation-name: fadeInDownBig; + animation-name: fadeInDownBig; +} + +@-webkit-keyframes fadeInLeft { + from { + opacity: 0; + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + } + + to { + opacity: 1; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes fadeInLeft { + from { + opacity: 0; + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + } + + to { + opacity: 1; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.fadeInLeft { + -webkit-animation-name: fadeInLeft; + animation-name: fadeInLeft; +} + +@-webkit-keyframes fadeInLeftBig { + from { + opacity: 0; + -webkit-transform: translate3d(-2000px, 0, 0); + transform: translate3d(-2000px, 0, 0); + } + + to { + opacity: 1; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes fadeInLeftBig { + from { + opacity: 0; + -webkit-transform: translate3d(-2000px, 0, 0); + transform: translate3d(-2000px, 0, 0); + } + + to { + opacity: 1; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.fadeInLeftBig { + -webkit-animation-name: fadeInLeftBig; + animation-name: fadeInLeftBig; +} + +@-webkit-keyframes fadeInRight { + from { + opacity: 0; + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + } + + to { + opacity: 1; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes fadeInRight { + from { + opacity: 0; + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + } + + to { + opacity: 1; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.fadeInRight { + -webkit-animation-name: fadeInRight; + animation-name: fadeInRight; +} + +@-webkit-keyframes fadeInRightBig { + from { + opacity: 0; + -webkit-transform: translate3d(2000px, 0, 0); + transform: translate3d(2000px, 0, 0); + } + + to { + opacity: 1; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes fadeInRightBig { + from { + opacity: 0; + -webkit-transform: translate3d(2000px, 0, 0); + transform: translate3d(2000px, 0, 0); + } + + to { + opacity: 1; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.fadeInRightBig { + -webkit-animation-name: fadeInRightBig; + animation-name: fadeInRightBig; +} + +@-webkit-keyframes fadeInUp { + from { + opacity: 0; + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + } + + to { + opacity: 1; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes fadeInUp { + from { + opacity: 0; + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + } + + to { + opacity: 1; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.fadeInUp { + -webkit-animation-name: fadeInUp; + animation-name: fadeInUp; +} + +@-webkit-keyframes fadeInUpBig { + from { + opacity: 0; + -webkit-transform: translate3d(0, 2000px, 0); + transform: translate3d(0, 2000px, 0); + } + + to { + opacity: 1; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes fadeInUpBig { + from { + opacity: 0; + -webkit-transform: translate3d(0, 2000px, 0); + transform: translate3d(0, 2000px, 0); + } + + to { + opacity: 1; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.fadeInUpBig { + -webkit-animation-name: fadeInUpBig; + animation-name: fadeInUpBig; +} + +@-webkit-keyframes fadeOut { + from { + opacity: 1; + } + + to { + opacity: 0; + } +} + +@keyframes fadeOut { + from { + opacity: 1; + } + + to { + opacity: 0; + } +} + +.fadeOut { + -webkit-animation-name: fadeOut; + animation-name: fadeOut; +} + +@-webkit-keyframes fadeOutDown { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + } +} + +@keyframes fadeOutDown { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + } +} + +.fadeOutDown { + -webkit-animation-name: fadeOutDown; + animation-name: fadeOutDown; +} + +@-webkit-keyframes fadeOutDownBig { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(0, 2000px, 0); + transform: translate3d(0, 2000px, 0); + } +} + +@keyframes fadeOutDownBig { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(0, 2000px, 0); + transform: translate3d(0, 2000px, 0); + } +} + +.fadeOutDownBig { + -webkit-animation-name: fadeOutDownBig; + animation-name: fadeOutDownBig; +} + +@-webkit-keyframes fadeOutLeft { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + } +} + +@keyframes fadeOutLeft { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + } +} + +.fadeOutLeft { + -webkit-animation-name: fadeOutLeft; + animation-name: fadeOutLeft; +} + +@-webkit-keyframes fadeOutLeftBig { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(-2000px, 0, 0); + transform: translate3d(-2000px, 0, 0); + } +} + +@keyframes fadeOutLeftBig { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(-2000px, 0, 0); + transform: translate3d(-2000px, 0, 0); + } +} + +.fadeOutLeftBig { + -webkit-animation-name: fadeOutLeftBig; + animation-name: fadeOutLeftBig; +} + +@-webkit-keyframes fadeOutRight { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + } +} + +@keyframes fadeOutRight { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + } +} + +.fadeOutRight { + -webkit-animation-name: fadeOutRight; + animation-name: fadeOutRight; +} + +@-webkit-keyframes fadeOutRightBig { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(2000px, 0, 0); + transform: translate3d(2000px, 0, 0); + } +} + +@keyframes fadeOutRightBig { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(2000px, 0, 0); + transform: translate3d(2000px, 0, 0); + } +} + +.fadeOutRightBig { + -webkit-animation-name: fadeOutRightBig; + animation-name: fadeOutRightBig; +} + +@-webkit-keyframes fadeOutUp { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(0, -100%, 0); + transform: translate3d(0, -100%, 0); + } +} + +@keyframes fadeOutUp { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(0, -100%, 0); + transform: translate3d(0, -100%, 0); + } +} + +.fadeOutUp { + -webkit-animation-name: fadeOutUp; + animation-name: fadeOutUp; +} + +@-webkit-keyframes fadeOutUpBig { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(0, -2000px, 0); + transform: translate3d(0, -2000px, 0); + } +} + +@keyframes fadeOutUpBig { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(0, -2000px, 0); + transform: translate3d(0, -2000px, 0); + } +} + +.fadeOutUpBig { + -webkit-animation-name: fadeOutUpBig; + animation-name: fadeOutUpBig; +} + +@-webkit-keyframes flip { + from { + -webkit-transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 0) + rotate3d(0, 1, 0, -360deg); + transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 0) rotate3d(0, 1, 0, -360deg); + -webkit-animation-timing-function: ease-out; + animation-timing-function: ease-out; + } + + 40% { + -webkit-transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 150px) + rotate3d(0, 1, 0, -190deg); + transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 150px) + rotate3d(0, 1, 0, -190deg); + -webkit-animation-timing-function: ease-out; + animation-timing-function: ease-out; + } + + 50% { + -webkit-transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 150px) + rotate3d(0, 1, 0, -170deg); + transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 150px) + rotate3d(0, 1, 0, -170deg); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } + + 80% { + -webkit-transform: perspective(400px) scale3d(0.95, 0.95, 0.95) translate3d(0, 0, 0) + rotate3d(0, 1, 0, 0deg); + transform: perspective(400px) scale3d(0.95, 0.95, 0.95) translate3d(0, 0, 0) + rotate3d(0, 1, 0, 0deg); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } + + to { + -webkit-transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 0) + rotate3d(0, 1, 0, 0deg); + transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 0) rotate3d(0, 1, 0, 0deg); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } +} + +@keyframes flip { + from { + -webkit-transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 0) + rotate3d(0, 1, 0, -360deg); + transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 0) rotate3d(0, 1, 0, -360deg); + -webkit-animation-timing-function: ease-out; + animation-timing-function: ease-out; + } + + 40% { + -webkit-transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 150px) + rotate3d(0, 1, 0, -190deg); + transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 150px) + rotate3d(0, 1, 0, -190deg); + -webkit-animation-timing-function: ease-out; + animation-timing-function: ease-out; + } + + 50% { + -webkit-transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 150px) + rotate3d(0, 1, 0, -170deg); + transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 150px) + rotate3d(0, 1, 0, -170deg); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } + + 80% { + -webkit-transform: perspective(400px) scale3d(0.95, 0.95, 0.95) translate3d(0, 0, 0) + rotate3d(0, 1, 0, 0deg); + transform: perspective(400px) scale3d(0.95, 0.95, 0.95) translate3d(0, 0, 0) + rotate3d(0, 1, 0, 0deg); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } + + to { + -webkit-transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 0) + rotate3d(0, 1, 0, 0deg); + transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 0) rotate3d(0, 1, 0, 0deg); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } +} + +.animated.flip { + -webkit-backface-visibility: visible; + backface-visibility: visible; + -webkit-animation-name: flip; + animation-name: flip; +} + +@-webkit-keyframes flipInX { + from { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 90deg); + transform: perspective(400px) rotate3d(1, 0, 0, 90deg); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + opacity: 0; + } + + 40% { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -20deg); + transform: perspective(400px) rotate3d(1, 0, 0, -20deg); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } + + 60% { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 10deg); + transform: perspective(400px) rotate3d(1, 0, 0, 10deg); + opacity: 1; + } + + 80% { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -5deg); + transform: perspective(400px) rotate3d(1, 0, 0, -5deg); + } + + to { + -webkit-transform: perspective(400px); + transform: perspective(400px); + } +} + +@keyframes flipInX { + from { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 90deg); + transform: perspective(400px) rotate3d(1, 0, 0, 90deg); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + opacity: 0; + } + + 40% { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -20deg); + transform: perspective(400px) rotate3d(1, 0, 0, -20deg); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } + + 60% { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 10deg); + transform: perspective(400px) rotate3d(1, 0, 0, 10deg); + opacity: 1; + } + + 80% { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -5deg); + transform: perspective(400px) rotate3d(1, 0, 0, -5deg); + } + + to { + -webkit-transform: perspective(400px); + transform: perspective(400px); + } +} + +.flipInX { + -webkit-backface-visibility: visible !important; + backface-visibility: visible !important; + -webkit-animation-name: flipInX; + animation-name: flipInX; +} + +@-webkit-keyframes flipInY { + from { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 90deg); + transform: perspective(400px) rotate3d(0, 1, 0, 90deg); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + opacity: 0; + } + + 40% { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -20deg); + transform: perspective(400px) rotate3d(0, 1, 0, -20deg); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } + + 60% { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 10deg); + transform: perspective(400px) rotate3d(0, 1, 0, 10deg); + opacity: 1; + } + + 80% { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -5deg); + transform: perspective(400px) rotate3d(0, 1, 0, -5deg); + } + + to { + -webkit-transform: perspective(400px); + transform: perspective(400px); + } +} + +@keyframes flipInY { + from { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 90deg); + transform: perspective(400px) rotate3d(0, 1, 0, 90deg); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + opacity: 0; + } + + 40% { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -20deg); + transform: perspective(400px) rotate3d(0, 1, 0, -20deg); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } + + 60% { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 10deg); + transform: perspective(400px) rotate3d(0, 1, 0, 10deg); + opacity: 1; + } + + 80% { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -5deg); + transform: perspective(400px) rotate3d(0, 1, 0, -5deg); + } + + to { + -webkit-transform: perspective(400px); + transform: perspective(400px); + } +} + +.flipInY { + -webkit-backface-visibility: visible !important; + backface-visibility: visible !important; + -webkit-animation-name: flipInY; + animation-name: flipInY; +} + +@-webkit-keyframes flipOutX { + from { + -webkit-transform: perspective(400px); + transform: perspective(400px); + } + + 30% { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -20deg); + transform: perspective(400px) rotate3d(1, 0, 0, -20deg); + opacity: 1; + } + + to { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 90deg); + transform: perspective(400px) rotate3d(1, 0, 0, 90deg); + opacity: 0; + } +} + +@keyframes flipOutX { + from { + -webkit-transform: perspective(400px); + transform: perspective(400px); + } + + 30% { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -20deg); + transform: perspective(400px) rotate3d(1, 0, 0, -20deg); + opacity: 1; + } + + to { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 90deg); + transform: perspective(400px) rotate3d(1, 0, 0, 90deg); + opacity: 0; + } +} + +.flipOutX { + -webkit-animation-duration: 0.75s; + animation-duration: 0.75s; + -webkit-animation-name: flipOutX; + animation-name: flipOutX; + -webkit-backface-visibility: visible !important; + backface-visibility: visible !important; +} + +@-webkit-keyframes flipOutY { + from { + -webkit-transform: perspective(400px); + transform: perspective(400px); + } + + 30% { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -15deg); + transform: perspective(400px) rotate3d(0, 1, 0, -15deg); + opacity: 1; + } + + to { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 90deg); + transform: perspective(400px) rotate3d(0, 1, 0, 90deg); + opacity: 0; + } +} + +@keyframes flipOutY { + from { + -webkit-transform: perspective(400px); + transform: perspective(400px); + } + + 30% { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -15deg); + transform: perspective(400px) rotate3d(0, 1, 0, -15deg); + opacity: 1; + } + + to { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 90deg); + transform: perspective(400px) rotate3d(0, 1, 0, 90deg); + opacity: 0; + } +} + +.flipOutY { + -webkit-animation-duration: 0.75s; + animation-duration: 0.75s; + -webkit-backface-visibility: visible !important; + backface-visibility: visible !important; + -webkit-animation-name: flipOutY; + animation-name: flipOutY; +} + +@-webkit-keyframes lightSpeedIn { + from { + -webkit-transform: translate3d(100%, 0, 0) skewX(-30deg); + transform: translate3d(100%, 0, 0) skewX(-30deg); + opacity: 0; + } + + 60% { + -webkit-transform: skewX(20deg); + transform: skewX(20deg); + opacity: 1; + } + + 80% { + -webkit-transform: skewX(-5deg); + transform: skewX(-5deg); + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes lightSpeedIn { + from { + -webkit-transform: translate3d(100%, 0, 0) skewX(-30deg); + transform: translate3d(100%, 0, 0) skewX(-30deg); + opacity: 0; + } + + 60% { + -webkit-transform: skewX(20deg); + transform: skewX(20deg); + opacity: 1; + } + + 80% { + -webkit-transform: skewX(-5deg); + transform: skewX(-5deg); + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.lightSpeedIn { + -webkit-animation-name: lightSpeedIn; + animation-name: lightSpeedIn; + -webkit-animation-timing-function: ease-out; + animation-timing-function: ease-out; +} + +@-webkit-keyframes lightSpeedOut { + from { + opacity: 1; + } + + to { + -webkit-transform: translate3d(100%, 0, 0) skewX(30deg); + transform: translate3d(100%, 0, 0) skewX(30deg); + opacity: 0; + } +} + +@keyframes lightSpeedOut { + from { + opacity: 1; + } + + to { + -webkit-transform: translate3d(100%, 0, 0) skewX(30deg); + transform: translate3d(100%, 0, 0) skewX(30deg); + opacity: 0; + } +} + +.lightSpeedOut { + -webkit-animation-name: lightSpeedOut; + animation-name: lightSpeedOut; + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; +} + +@-webkit-keyframes rotateIn { + from { + -webkit-transform-origin: center; + transform-origin: center; + -webkit-transform: rotate3d(0, 0, 1, -200deg); + transform: rotate3d(0, 0, 1, -200deg); + opacity: 0; + } + + to { + -webkit-transform-origin: center; + transform-origin: center; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + opacity: 1; + } +} + +@keyframes rotateIn { + from { + -webkit-transform-origin: center; + transform-origin: center; + -webkit-transform: rotate3d(0, 0, 1, -200deg); + transform: rotate3d(0, 0, 1, -200deg); + opacity: 0; + } + + to { + -webkit-transform-origin: center; + transform-origin: center; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + opacity: 1; + } +} + +.rotateIn { + -webkit-animation-name: rotateIn; + animation-name: rotateIn; +} + +@-webkit-keyframes rotateInDownLeft { + from { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate3d(0, 0, 1, -45deg); + transform: rotate3d(0, 0, 1, -45deg); + opacity: 0; + } + + to { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + opacity: 1; + } +} + +@keyframes rotateInDownLeft { + from { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate3d(0, 0, 1, -45deg); + transform: rotate3d(0, 0, 1, -45deg); + opacity: 0; + } + + to { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + opacity: 1; + } +} + +.rotateInDownLeft { + -webkit-animation-name: rotateInDownLeft; + animation-name: rotateInDownLeft; +} + +@-webkit-keyframes rotateInDownRight { + from { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate3d(0, 0, 1, 45deg); + transform: rotate3d(0, 0, 1, 45deg); + opacity: 0; + } + + to { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + opacity: 1; + } +} + +@keyframes rotateInDownRight { + from { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate3d(0, 0, 1, 45deg); + transform: rotate3d(0, 0, 1, 45deg); + opacity: 0; + } + + to { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + opacity: 1; + } +} + +.rotateInDownRight { + -webkit-animation-name: rotateInDownRight; + animation-name: rotateInDownRight; +} + +@-webkit-keyframes rotateInUpLeft { + from { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate3d(0, 0, 1, 45deg); + transform: rotate3d(0, 0, 1, 45deg); + opacity: 0; + } + + to { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + opacity: 1; + } +} + +@keyframes rotateInUpLeft { + from { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate3d(0, 0, 1, 45deg); + transform: rotate3d(0, 0, 1, 45deg); + opacity: 0; + } + + to { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + opacity: 1; + } +} + +.rotateInUpLeft { + -webkit-animation-name: rotateInUpLeft; + animation-name: rotateInUpLeft; +} + +@-webkit-keyframes rotateInUpRight { + from { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate3d(0, 0, 1, -90deg); + transform: rotate3d(0, 0, 1, -90deg); + opacity: 0; + } + + to { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + opacity: 1; + } +} + +@keyframes rotateInUpRight { + from { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate3d(0, 0, 1, -90deg); + transform: rotate3d(0, 0, 1, -90deg); + opacity: 0; + } + + to { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + opacity: 1; + } +} + +.rotateInUpRight { + -webkit-animation-name: rotateInUpRight; + animation-name: rotateInUpRight; +} + +@-webkit-keyframes rotateOut { + from { + -webkit-transform-origin: center; + transform-origin: center; + opacity: 1; + } + + to { + -webkit-transform-origin: center; + transform-origin: center; + -webkit-transform: rotate3d(0, 0, 1, 200deg); + transform: rotate3d(0, 0, 1, 200deg); + opacity: 0; + } +} + +@keyframes rotateOut { + from { + -webkit-transform-origin: center; + transform-origin: center; + opacity: 1; + } + + to { + -webkit-transform-origin: center; + transform-origin: center; + -webkit-transform: rotate3d(0, 0, 1, 200deg); + transform: rotate3d(0, 0, 1, 200deg); + opacity: 0; + } +} + +.rotateOut { + -webkit-animation-name: rotateOut; + animation-name: rotateOut; +} + +@-webkit-keyframes rotateOutDownLeft { + from { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + opacity: 1; + } + + to { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate3d(0, 0, 1, 45deg); + transform: rotate3d(0, 0, 1, 45deg); + opacity: 0; + } +} + +@keyframes rotateOutDownLeft { + from { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + opacity: 1; + } + + to { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate3d(0, 0, 1, 45deg); + transform: rotate3d(0, 0, 1, 45deg); + opacity: 0; + } +} + +.rotateOutDownLeft { + -webkit-animation-name: rotateOutDownLeft; + animation-name: rotateOutDownLeft; +} + +@-webkit-keyframes rotateOutDownRight { + from { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + opacity: 1; + } + + to { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate3d(0, 0, 1, -45deg); + transform: rotate3d(0, 0, 1, -45deg); + opacity: 0; + } +} + +@keyframes rotateOutDownRight { + from { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + opacity: 1; + } + + to { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate3d(0, 0, 1, -45deg); + transform: rotate3d(0, 0, 1, -45deg); + opacity: 0; + } +} + +.rotateOutDownRight { + -webkit-animation-name: rotateOutDownRight; + animation-name: rotateOutDownRight; +} + +@-webkit-keyframes rotateOutUpLeft { + from { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + opacity: 1; + } + + to { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate3d(0, 0, 1, -45deg); + transform: rotate3d(0, 0, 1, -45deg); + opacity: 0; + } +} + +@keyframes rotateOutUpLeft { + from { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + opacity: 1; + } + + to { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate3d(0, 0, 1, -45deg); + transform: rotate3d(0, 0, 1, -45deg); + opacity: 0; + } +} + +.rotateOutUpLeft { + -webkit-animation-name: rotateOutUpLeft; + animation-name: rotateOutUpLeft; +} + +@-webkit-keyframes rotateOutUpRight { + from { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + opacity: 1; + } + + to { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate3d(0, 0, 1, 90deg); + transform: rotate3d(0, 0, 1, 90deg); + opacity: 0; + } +} + +@keyframes rotateOutUpRight { + from { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + opacity: 1; + } + + to { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate3d(0, 0, 1, 90deg); + transform: rotate3d(0, 0, 1, 90deg); + opacity: 0; + } +} + +.rotateOutUpRight { + -webkit-animation-name: rotateOutUpRight; + animation-name: rotateOutUpRight; +} + +@-webkit-keyframes hinge { + 0% { + -webkit-transform-origin: top left; + transform-origin: top left; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + } + + 20%, + 60% { + -webkit-transform: rotate3d(0, 0, 1, 80deg); + transform: rotate3d(0, 0, 1, 80deg); + -webkit-transform-origin: top left; + transform-origin: top left; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + } + + 40%, + 80% { + -webkit-transform: rotate3d(0, 0, 1, 60deg); + transform: rotate3d(0, 0, 1, 60deg); + -webkit-transform-origin: top left; + transform-origin: top left; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + opacity: 1; + } + + to { + -webkit-transform: translate3d(0, 700px, 0); + transform: translate3d(0, 700px, 0); + opacity: 0; + } +} + +@keyframes hinge { + 0% { + -webkit-transform-origin: top left; + transform-origin: top left; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + } + + 20%, + 60% { + -webkit-transform: rotate3d(0, 0, 1, 80deg); + transform: rotate3d(0, 0, 1, 80deg); + -webkit-transform-origin: top left; + transform-origin: top left; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + } + + 40%, + 80% { + -webkit-transform: rotate3d(0, 0, 1, 60deg); + transform: rotate3d(0, 0, 1, 60deg); + -webkit-transform-origin: top left; + transform-origin: top left; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + opacity: 1; + } + + to { + -webkit-transform: translate3d(0, 700px, 0); + transform: translate3d(0, 700px, 0); + opacity: 0; + } +} + +.hinge { + -webkit-animation-duration: 2s; + animation-duration: 2s; + -webkit-animation-name: hinge; + animation-name: hinge; +} + +@-webkit-keyframes jackInTheBox { + from { + opacity: 0; + -webkit-transform: scale(0.1) rotate(30deg); + transform: scale(0.1) rotate(30deg); + -webkit-transform-origin: center bottom; + transform-origin: center bottom; + } + + 50% { + -webkit-transform: rotate(-10deg); + transform: rotate(-10deg); + } + + 70% { + -webkit-transform: rotate(3deg); + transform: rotate(3deg); + } + + to { + opacity: 1; + -webkit-transform: scale(1); + transform: scale(1); + } +} + +@keyframes jackInTheBox { + from { + opacity: 0; + -webkit-transform: scale(0.1) rotate(30deg); + transform: scale(0.1) rotate(30deg); + -webkit-transform-origin: center bottom; + transform-origin: center bottom; + } + + 50% { + -webkit-transform: rotate(-10deg); + transform: rotate(-10deg); + } + + 70% { + -webkit-transform: rotate(3deg); + transform: rotate(3deg); + } + + to { + opacity: 1; + -webkit-transform: scale(1); + transform: scale(1); + } +} + +.jackInTheBox { + -webkit-animation-name: jackInTheBox; + animation-name: jackInTheBox; +} + +/* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ + +@-webkit-keyframes rollIn { + from { + opacity: 0; + -webkit-transform: translate3d(-100%, 0, 0) rotate3d(0, 0, 1, -120deg); + transform: translate3d(-100%, 0, 0) rotate3d(0, 0, 1, -120deg); + } + + to { + opacity: 1; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes rollIn { + from { + opacity: 0; + -webkit-transform: translate3d(-100%, 0, 0) rotate3d(0, 0, 1, -120deg); + transform: translate3d(-100%, 0, 0) rotate3d(0, 0, 1, -120deg); + } + + to { + opacity: 1; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.rollIn { + -webkit-animation-name: rollIn; + animation-name: rollIn; +} + +/* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ + +@-webkit-keyframes rollOut { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(100%, 0, 0) rotate3d(0, 0, 1, 120deg); + transform: translate3d(100%, 0, 0) rotate3d(0, 0, 1, 120deg); + } +} + +@keyframes rollOut { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(100%, 0, 0) rotate3d(0, 0, 1, 120deg); + transform: translate3d(100%, 0, 0) rotate3d(0, 0, 1, 120deg); + } +} + +.rollOut { + -webkit-animation-name: rollOut; + animation-name: rollOut; +} + +@-webkit-keyframes zoomIn { + from { + opacity: 0; + -webkit-transform: scale3d(0.3, 0.3, 0.3); + transform: scale3d(0.3, 0.3, 0.3); + } + + 50% { + opacity: 1; + } +} + +@keyframes zoomIn { + from { + opacity: 0; + -webkit-transform: scale3d(0.3, 0.3, 0.3); + transform: scale3d(0.3, 0.3, 0.3); + } + + 50% { + opacity: 1; + } +} + +.zoomIn { + -webkit-animation-name: zoomIn; + animation-name: zoomIn; +} + +@-webkit-keyframes zoomInDown { + from { + opacity: 0; + -webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(0, -1000px, 0); + transform: scale3d(0.1, 0.1, 0.1) translate3d(0, -1000px, 0); + -webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); + animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); + } + + 60% { + opacity: 1; + -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(0, 60px, 0); + transform: scale3d(0.475, 0.475, 0.475) translate3d(0, 60px, 0); + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); + } +} + +@keyframes zoomInDown { + from { + opacity: 0; + -webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(0, -1000px, 0); + transform: scale3d(0.1, 0.1, 0.1) translate3d(0, -1000px, 0); + -webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); + animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); + } + + 60% { + opacity: 1; + -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(0, 60px, 0); + transform: scale3d(0.475, 0.475, 0.475) translate3d(0, 60px, 0); + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); + } +} + +.zoomInDown { + -webkit-animation-name: zoomInDown; + animation-name: zoomInDown; +} + +@-webkit-keyframes zoomInLeft { + from { + opacity: 0; + -webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(-1000px, 0, 0); + transform: scale3d(0.1, 0.1, 0.1) translate3d(-1000px, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); + animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); + } + + 60% { + opacity: 1; + -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(10px, 0, 0); + transform: scale3d(0.475, 0.475, 0.475) translate3d(10px, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); + } +} + +@keyframes zoomInLeft { + from { + opacity: 0; + -webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(-1000px, 0, 0); + transform: scale3d(0.1, 0.1, 0.1) translate3d(-1000px, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); + animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); + } + + 60% { + opacity: 1; + -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(10px, 0, 0); + transform: scale3d(0.475, 0.475, 0.475) translate3d(10px, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); + } +} + +.zoomInLeft { + -webkit-animation-name: zoomInLeft; + animation-name: zoomInLeft; +} + +@-webkit-keyframes zoomInRight { + from { + opacity: 0; + -webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(1000px, 0, 0); + transform: scale3d(0.1, 0.1, 0.1) translate3d(1000px, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); + animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); + } + + 60% { + opacity: 1; + -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(-10px, 0, 0); + transform: scale3d(0.475, 0.475, 0.475) translate3d(-10px, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); + } +} + +@keyframes zoomInRight { + from { + opacity: 0; + -webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(1000px, 0, 0); + transform: scale3d(0.1, 0.1, 0.1) translate3d(1000px, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); + animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); + } + + 60% { + opacity: 1; + -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(-10px, 0, 0); + transform: scale3d(0.475, 0.475, 0.475) translate3d(-10px, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); + } +} + +.zoomInRight { + -webkit-animation-name: zoomInRight; + animation-name: zoomInRight; +} + +@-webkit-keyframes zoomInUp { + from { + opacity: 0; + -webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(0, 1000px, 0); + transform: scale3d(0.1, 0.1, 0.1) translate3d(0, 1000px, 0); + -webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); + animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); + } + + 60% { + opacity: 1; + -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(0, -60px, 0); + transform: scale3d(0.475, 0.475, 0.475) translate3d(0, -60px, 0); + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); + } +} + +@keyframes zoomInUp { + from { + opacity: 0; + -webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(0, 1000px, 0); + transform: scale3d(0.1, 0.1, 0.1) translate3d(0, 1000px, 0); + -webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); + animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); + } + + 60% { + opacity: 1; + -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(0, -60px, 0); + transform: scale3d(0.475, 0.475, 0.475) translate3d(0, -60px, 0); + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); + } +} + +.zoomInUp { + -webkit-animation-name: zoomInUp; + animation-name: zoomInUp; +} + +@-webkit-keyframes zoomOut { + from { + opacity: 1; + } + + 50% { + opacity: 0; + -webkit-transform: scale3d(0.3, 0.3, 0.3); + transform: scale3d(0.3, 0.3, 0.3); + } + + to { + opacity: 0; + } +} + +@keyframes zoomOut { + from { + opacity: 1; + } + + 50% { + opacity: 0; + -webkit-transform: scale3d(0.3, 0.3, 0.3); + transform: scale3d(0.3, 0.3, 0.3); + } + + to { + opacity: 0; + } +} + +.zoomOut { + -webkit-animation-name: zoomOut; + animation-name: zoomOut; +} + +@-webkit-keyframes zoomOutDown { + 40% { + opacity: 1; + -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(0, -60px, 0); + transform: scale3d(0.475, 0.475, 0.475) translate3d(0, -60px, 0); + -webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); + animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); + } + + to { + opacity: 0; + -webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(0, 2000px, 0); + transform: scale3d(0.1, 0.1, 0.1) translate3d(0, 2000px, 0); + -webkit-transform-origin: center bottom; + transform-origin: center bottom; + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); + } +} + +@keyframes zoomOutDown { + 40% { + opacity: 1; + -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(0, -60px, 0); + transform: scale3d(0.475, 0.475, 0.475) translate3d(0, -60px, 0); + -webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); + animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); + } + + to { + opacity: 0; + -webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(0, 2000px, 0); + transform: scale3d(0.1, 0.1, 0.1) translate3d(0, 2000px, 0); + -webkit-transform-origin: center bottom; + transform-origin: center bottom; + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); + } +} + +.zoomOutDown { + -webkit-animation-name: zoomOutDown; + animation-name: zoomOutDown; +} + +@-webkit-keyframes zoomOutLeft { + 40% { + opacity: 1; + -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(42px, 0, 0); + transform: scale3d(0.475, 0.475, 0.475) translate3d(42px, 0, 0); + } + + to { + opacity: 0; + -webkit-transform: scale(0.1) translate3d(-2000px, 0, 0); + transform: scale(0.1) translate3d(-2000px, 0, 0); + -webkit-transform-origin: left center; + transform-origin: left center; + } +} + +@keyframes zoomOutLeft { + 40% { + opacity: 1; + -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(42px, 0, 0); + transform: scale3d(0.475, 0.475, 0.475) translate3d(42px, 0, 0); + } + + to { + opacity: 0; + -webkit-transform: scale(0.1) translate3d(-2000px, 0, 0); + transform: scale(0.1) translate3d(-2000px, 0, 0); + -webkit-transform-origin: left center; + transform-origin: left center; + } +} + +.zoomOutLeft { + -webkit-animation-name: zoomOutLeft; + animation-name: zoomOutLeft; +} + +@-webkit-keyframes zoomOutRight { + 40% { + opacity: 1; + -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(-42px, 0, 0); + transform: scale3d(0.475, 0.475, 0.475) translate3d(-42px, 0, 0); + } + + to { + opacity: 0; + -webkit-transform: scale(0.1) translate3d(2000px, 0, 0); + transform: scale(0.1) translate3d(2000px, 0, 0); + -webkit-transform-origin: right center; + transform-origin: right center; + } +} + +@keyframes zoomOutRight { + 40% { + opacity: 1; + -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(-42px, 0, 0); + transform: scale3d(0.475, 0.475, 0.475) translate3d(-42px, 0, 0); + } + + to { + opacity: 0; + -webkit-transform: scale(0.1) translate3d(2000px, 0, 0); + transform: scale(0.1) translate3d(2000px, 0, 0); + -webkit-transform-origin: right center; + transform-origin: right center; + } +} + +.zoomOutRight { + -webkit-animation-name: zoomOutRight; + animation-name: zoomOutRight; +} + +@-webkit-keyframes zoomOutUp { + 40% { + opacity: 1; + -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(0, 60px, 0); + transform: scale3d(0.475, 0.475, 0.475) translate3d(0, 60px, 0); + -webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); + animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); + } + + to { + opacity: 0; + -webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(0, -2000px, 0); + transform: scale3d(0.1, 0.1, 0.1) translate3d(0, -2000px, 0); + -webkit-transform-origin: center bottom; + transform-origin: center bottom; + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); + } +} + +@keyframes zoomOutUp { + 40% { + opacity: 1; + -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(0, 60px, 0); + transform: scale3d(0.475, 0.475, 0.475) translate3d(0, 60px, 0); + -webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); + animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); + } + + to { + opacity: 0; + -webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(0, -2000px, 0); + transform: scale3d(0.1, 0.1, 0.1) translate3d(0, -2000px, 0); + -webkit-transform-origin: center bottom; + transform-origin: center bottom; + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); + } +} + +.zoomOutUp { + -webkit-animation-name: zoomOutUp; + animation-name: zoomOutUp; +} + +@-webkit-keyframes slideInDown { + from { + -webkit-transform: translate3d(0, -100%, 0); + transform: translate3d(0, -100%, 0); + visibility: visible; + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes slideInDown { + from { + -webkit-transform: translate3d(0, -100%, 0); + transform: translate3d(0, -100%, 0); + visibility: visible; + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.slideInDown { + -webkit-animation-name: slideInDown; + animation-name: slideInDown; +} + +@-webkit-keyframes slideInLeft { + from { + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + visibility: visible; + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes slideInLeft { + from { + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + visibility: visible; + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.slideInLeft { + -webkit-animation-name: slideInLeft; + animation-name: slideInLeft; +} + +@-webkit-keyframes slideInRight { + from { + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + visibility: visible; + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes slideInRight { + from { + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + visibility: visible; + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.slideInRight { + -webkit-animation-name: slideInRight; + animation-name: slideInRight; +} + +@-webkit-keyframes slideInUp { + from { + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + visibility: visible; + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes slideInUp { + from { + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + visibility: visible; + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.slideInUp { + -webkit-animation-name: slideInUp; + animation-name: slideInUp; +} + +@-webkit-keyframes slideOutDown { + from { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + to { + visibility: hidden; + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + } +} + +@keyframes slideOutDown { + from { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + to { + visibility: hidden; + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + } +} + +.slideOutDown { + -webkit-animation-name: slideOutDown; + animation-name: slideOutDown; +} + +@-webkit-keyframes slideOutLeft { + from { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + to { + visibility: hidden; + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + } +} + +@keyframes slideOutLeft { + from { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + to { + visibility: hidden; + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + } +} + +.slideOutLeft { + -webkit-animation-name: slideOutLeft; + animation-name: slideOutLeft; +} + +@-webkit-keyframes slideOutRight { + from { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + to { + visibility: hidden; + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + } +} + +@keyframes slideOutRight { + from { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + to { + visibility: hidden; + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + } +} + +.slideOutRight { + -webkit-animation-name: slideOutRight; + animation-name: slideOutRight; +} + +@-webkit-keyframes slideOutUp { + from { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + to { + visibility: hidden; + -webkit-transform: translate3d(0, -100%, 0); + transform: translate3d(0, -100%, 0); + } +} + +@keyframes slideOutUp { + from { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + to { + visibility: hidden; + -webkit-transform: translate3d(0, -100%, 0); + transform: translate3d(0, -100%, 0); + } +} + +.slideOutUp { + -webkit-animation-name: slideOutUp; + animation-name: slideOutUp; +} + +.animated { + -webkit-animation-duration: 1s; + animation-duration: 1s; + -webkit-animation-fill-mode: both; + animation-fill-mode: both; +} + +.animated.infinite { + -webkit-animation-iteration-count: infinite; + animation-iteration-count: infinite; +} + +.animated.delay-1s { + -webkit-animation-delay: 1s; + animation-delay: 1s; +} + +.animated.delay-2s { + -webkit-animation-delay: 2s; + animation-delay: 2s; +} + +.animated.delay-3s { + -webkit-animation-delay: 3s; + animation-delay: 3s; +} + +.animated.delay-4s { + -webkit-animation-delay: 4s; + animation-delay: 4s; +} + +.animated.delay-5s { + -webkit-animation-delay: 5s; + animation-delay: 5s; +} + +.animated.fast { + -webkit-animation-duration: 800ms; + animation-duration: 800ms; +} + +.animated.faster { + -webkit-animation-duration: 500ms; + animation-duration: 500ms; +} + +.animated.slow { + -webkit-animation-duration: 2s; + animation-duration: 2s; +} + +.animated.slower { + -webkit-animation-duration: 3s; + animation-duration: 3s; +} + +@media (prefers-reduced-motion) { + .animated { + -webkit-animation: unset !important; + animation: unset !important; + -webkit-transition: none !important; + transition: none !important; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\theme\base-theme.scss + +// Import all the theming functionality. +// We can use relative imports for imports from the cdk because we bundle everything +// up into a single flat scss file for material. +// We want overlays to always appear over user content, so set a baseline +// very high z-index for the overlay container, which is where we create the new +// stacking context for all overlays. +$cdk-z-index-overlay-container: 1000 !default; +$cdk-z-index-overlay: 1000 !default; +$cdk-z-index-overlay-backdrop: 1000 !default; + +// Background color for all of the backdrops +$cdk-overlay-dark-backdrop-background: rgba(0, 0, 0, 0.32) !default; + +// Default backdrop animation is based on the Material Design swift-ease-out. +$backdrop-animation-duration: 400ms !default; +$backdrop-animation-timing-function: cubic-bezier(0.25, 0.8, 0.25, 1) !default; + + +@mixin cdk-overlay() { + .cdk-overlay-container, .cdk-global-overlay-wrapper { + // Disable events from being captured on the overlay container. + pointer-events: none; + + // The container should be the size of the viewport. + top: 0; + left: 0; + height: 100%; + width: 100%; + } + + // The overlay-container is an invisible element which contains all individual overlays. + .cdk-overlay-container { + position: fixed; + z-index: $cdk-z-index-overlay-container; + + &:empty { + // Hide the element when it doesn't have any child nodes. This doesn't + // include overlays that have been detached, rather than disposed. + display: none; + } + } + + // We use an extra wrapper element in order to use make the overlay itself a flex item. + // This makes centering the overlay easy without running into the subpixel rendering + // problems tied to using `transform` and without interfering with the other position + // strategies. + .cdk-global-overlay-wrapper { + display: flex; + position: absolute; + z-index: $cdk-z-index-overlay; + } + + // A single overlay pane. + .cdk-overlay-pane { + // Note: it's important for this one to start off `absolute`, + // in order for us to be able to measure it correctly. + position: absolute; + pointer-events: auto; + box-sizing: border-box; + z-index: $cdk-z-index-overlay; + + // For connected-position overlays, we set `display: flex` in + // order to force `max-width` and `max-height` to take effect. + display: flex; + max-width: 100%; + max-height: 100%; + } + + .cdk-overlay-backdrop { + // TODO(jelbourn): reuse sidenav fullscreen mixin. + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + + z-index: $cdk-z-index-overlay-backdrop; + pointer-events: auto; + -webkit-tap-highlight-color: transparent; + transition: opacity $backdrop-animation-duration $backdrop-animation-timing-function; + opacity: 0; + + &.cdk-overlay-backdrop-showing { + opacity: 1; + + // In high contrast mode the rgba background will become solid so we need to fall back + // to making it opaque using `opacity`. Note that we can't use the `cdk-high-contrast` + // mixin, because we can't normalize the import path to the _a11y.scss both for the + // source and when this file is distributed. See #10908. + @media screen and (-ms-high-contrast: active) { + opacity: 0.6; + } + } + } + + .cdk-overlay-dark-backdrop { + background: $cdk-overlay-dark-backdrop-background; + } + + .cdk-overlay-transparent-backdrop { + // Note: as of Firefox 57, having the backdrop be `background: none` will prevent it from + // capturing the user's mouse scroll events. Since we also can't use something like + // `rgba(0, 0, 0, 0)`, we work around the inconsistency by not setting the background at + // all and using `opacity` to make the element transparent. + &, &.cdk-overlay-backdrop-showing { + opacity: 0; + } + } + + // Overlay parent element used with the connected position strategy. Used to constrain the + // overlay element's size to fit within the viewport. + .cdk-overlay-connected-position-bounding-box { + position: absolute; + z-index: $cdk-z-index-overlay; + + // We use `display: flex` on this element exclusively for centering connected overlays. + // When *not* centering, a top/left/bottom/right will be set which overrides the normal + // flex layout. + display: flex; + + // We use the `column` direction here to avoid some flexbox issues in Edge + // when using the "grow after open" options. + flex-direction: column; + + // Add some dimensions so the element has an `innerText` which some people depend on in tests. + min-width: 1px; + min-height: 1px; + } + + // Used when disabling global scrolling. + .cdk-global-scrollblock { + position: fixed; + + // Necessary for the content not to lose its width. Note that we're using 100%, instead of + // 100vw, because 100vw includes the width plus the scrollbar, whereas 100% is the width + // that the element had before we made it `fixed`. + width: 100%; + + // Note: this will always add a scrollbar to whatever element it is on, which can + // potentially result in double scrollbars. It shouldn't be an issue, because we won't + // block scrolling on a page that doesn't have a scrollbar in the first place. + overflow-y: scroll; + } +} + +@mixin cdk-a11y { + .cdk-visually-hidden { + border: 0; + clip: rect(0 0 0 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; + + // Avoid browsers rendering the focus ring in some cases. + outline: 0; + + // Avoid some cases where the browser will still render the native controls (see #9049). + -webkit-appearance: none; + -moz-appearance: none; + } +} + +// Applies styles for users in high contrast mode. Note that this only applies +// to Microsoft browsers. Chrome can be included by checking for the `html[hc]` +// attribute, however Chrome handles high contrast differently. +// +// @param target Which kind of high contrast setting to target. Defaults to `active`, can be +// `white-on-black` or `black-on-white`. +@mixin cdk-high-contrast($target: active) { + @media (-ms-high-contrast: $target) { + @content; + } +} + +// Core styles that enable monitoring autofill state of text fields. +@mixin cdk-text-field { + // Keyframes that apply no styles, but allow us to monitor when an text field becomes autofilled + // by watching for the animation events that are fired when they start. Note: the /*!*/ comment is + // needed to prevent LibSass from stripping the keyframes out. + // Based on: https://medium.com/@brunn/detecting-autofilled-fields-in-javascript-aed598d25da7 + @keyframes cdk-text-field-autofill-start {/*!*/} + @keyframes cdk-text-field-autofill-end {/*!*/} + + .cdk-text-field-autofill-monitored:-webkit-autofill { + animation-name: cdk-text-field-autofill-start; + } + + .cdk-text-field-autofill-monitored:not(:-webkit-autofill) { + animation-name: cdk-text-field-autofill-end; + } + + // Remove the resize handle on autosizing textareas, because whatever height + // the user resized to will be overwritten once they start typing again. + textarea.cdk-textarea-autosize { + resize: none; + } + + // This class is temporarily applied to the textarea when it is being measured. It is immediately + // removed when measuring is complete. We use `!important` rules here to make sure user-specified + // rules do not interfere with the measurement. + textarea.cdk-textarea-autosize-measuring { + height: auto !important; + overflow: hidden !important; + // Having 2px top and bottom padding seems to fix a bug where Chrome gets an incorrect + // measurement. We just have to account for it later and subtract it off the final result. + padding: 2px 0 !important; + box-sizing: content-box !important; + } +} + +// Used to generate UIDs for keyframes used to change the text field autofill styles. +$cdk-text-field-autofill-color-frame-count: 0; + +// Mixin used to apply custom background and foreground colors to an autofilled text field. +// Based on: https://stackoverflow.com/questions/2781549/ +// removing-input-background-colour-for-chrome-autocomplete#answer-37432260 +@mixin cdk-text-field-autofill-color($background, $foreground:'') { + @keyframes cdk-text-field-autofill-color-#{$cdk-text-field-autofill-color-frame-count} { + to { + background: $background; + @if $foreground != '' { color: $foreground; } + } + } + + &:-webkit-autofill { + animation-name: cdk-text-field-autofill-color-#{$cdk-text-field-autofill-color-frame-count}; + animation-fill-mode: both; + } + + &.cdk-text-field-autofill-monitored:-webkit-autofill { + animation-name: cdk-text-field-autofill-start, + cdk-text-field-autofill-color-#{$cdk-text-field-autofill-color-frame-count}; + } + + $cdk-text-field-autofill-color-frame-count: + $cdk-text-field-autofill-color-frame-count + 1 !global; +} + + +// Core styles that can be used to apply material design treatments to any element. +// Media queries +// TODO(josephperrott): Change $mat-xsmall and $mat-small usages to rely on BreakpointObserver, +$mat-xsmall: 'max-width: 599px'; +$mat-small: 'max-width: 959px'; + +// TODO: Revisit all z-indices before beta +// z-index master list + +$z-index-fab: 20 !default; +$z-index-drawer: 100 !default; + +// Global constants +$pi: 3.14159265; + +// Padding between input toggles and their labels +$mat-toggle-padding: 8px !default; +// Width and height of input toggles +$mat-toggle-size: 20px !default; + +// Easing Curves +// TODO(jelbourn): all of these need to be revisited + +// The default animation curves used by material design. +$mat-linear-out-slow-in-timing-function: cubic-bezier(0, 0, 0.2, 0.1) !default; +$mat-fast-out-slow-in-timing-function: cubic-bezier(0.4, 0, 0.2, 1) !default; +$mat-fast-out-linear-in-timing-function: cubic-bezier(0.4, 0, 1, 1) !default; + +$ease-in-out-curve-function: cubic-bezier(0.35, 0, 0.25, 1) !default; + +$swift-ease-out-duration: 400ms !default; +$swift-ease-out-timing-function: cubic-bezier(0.25, 0.8, 0.25, 1) !default; +$swift-ease-out: all $swift-ease-out-duration $swift-ease-out-timing-function !default; + +$swift-ease-in-duration: 300ms !default; +$swift-ease-in-timing-function: cubic-bezier(0.55, 0, 0.55, 0.2) !default; +$swift-ease-in: all $swift-ease-in-duration $swift-ease-in-timing-function !default; + +$swift-ease-in-out-duration: 500ms !default; +$swift-ease-in-out-timing-function: $ease-in-out-curve-function !default; +$swift-ease-in-out: all $swift-ease-in-out-duration $swift-ease-in-out-timing-function !default; + +$swift-linear-duration: 80ms !default; +$swift-linear-timing-function: linear !default; +$swift-linear: all $swift-linear-duration $swift-linear-timing-function !default; + + + +// A collection of mixins and CSS classes that can be used to apply elevation to a material +// element. +// See: https://material.io/design/environment/elevation.html +// Examples: +// +// +// .mat-foo { +// @include $mat-elevation(2); +// +// &:active { +// @include $mat-elevation(8); +// } +// } +// +//

Some content

+// +// For an explanation of the design behind how elevation is implemented, see the design doc at +// https://goo.gl/Kq0k9Z. + +// Colors for umbra, penumbra, and ambient shadows. As described in the design doc, each elevation +// level is created using a set of 3 shadow values, one for umbra (the shadow representing the +// space completely obscured by an object relative to its light source), one for penumbra (the +// space partially obscured by an object), and one for ambient (the space which contains the object +// itself). For a further explanation of these terms and their meanings, see +// https://en.wikipedia.org/wiki/Umbra,_penumbra_and_antumbra. + +// Maps for the different shadow sets and their values within each z-space. These values were +// created by taking a few reference shadow sets created by Google's Designers and interpolating +// all of the values between them. + +@function _get-umbra-map($color, $opacity) { + $shadow-color: if(type-of($color) == color, rgba($color, $opacity * 0.2), $color); + + @return ( + 0: '0px 0px 0px 0px #{$shadow-color}', + 1: '0px 2px 1px -1px #{$shadow-color}', + 2: '0px 3px 1px -2px #{$shadow-color}', + 3: '0px 3px 3px -2px #{$shadow-color}', + 4: '0px 2px 4px -1px #{$shadow-color}', + 5: '0px 3px 5px -1px #{$shadow-color}', + 6: '0px 3px 5px -1px #{$shadow-color}', + 7: '0px 4px 5px -2px #{$shadow-color}', + 8: '0px 5px 5px -3px #{$shadow-color}', + 9: '0px 5px 6px -3px #{$shadow-color}', + 10: '0px 6px 6px -3px #{$shadow-color}', + 11: '0px 6px 7px -4px #{$shadow-color}', + 12: '0px 7px 8px -4px #{$shadow-color}', + 13: '0px 7px 8px -4px #{$shadow-color}', + 14: '0px 7px 9px -4px #{$shadow-color}', + 15: '0px 8px 9px -5px #{$shadow-color}', + 16: '0px 8px 10px -5px #{$shadow-color}', + 17: '0px 8px 11px -5px #{$shadow-color}', + 18: '0px 9px 11px -5px #{$shadow-color}', + 19: '0px 9px 12px -6px #{$shadow-color}', + 20: '0px 10px 13px -6px #{$shadow-color}', + 21: '0px 10px 13px -6px #{$shadow-color}', + 22: '0px 10px 14px -6px #{$shadow-color}', + 23: '0px 11px 14px -7px #{$shadow-color}', + 24: '0px 11px 15px -7px #{$shadow-color}' + ); +} + +@function _get-penumbra-map($color, $opacity) { + $shadow-color: if(type-of($color) == color, rgba($color, $opacity * 0.14), $color); + + @return ( + 0: '0px 0px 0px 0px #{$shadow-color}', + 1: '0px 1px 1px 0px #{$shadow-color}', + 2: '0px 2px 2px 0px #{$shadow-color}', + 3: '0px 3px 4px 0px #{$shadow-color}', + 4: '0px 4px 5px 0px #{$shadow-color}', + 5: '0px 5px 8px 0px #{$shadow-color}', + 6: '0px 6px 10px 0px #{$shadow-color}', + 7: '0px 7px 10px 1px #{$shadow-color}', + 8: '0px 8px 10px 1px #{$shadow-color}', + 9: '0px 9px 12px 1px #{$shadow-color}', + 10: '0px 10px 14px 1px #{$shadow-color}', + 11: '0px 11px 15px 1px #{$shadow-color}', + 12: '0px 12px 17px 2px #{$shadow-color}', + 13: '0px 13px 19px 2px #{$shadow-color}', + 14: '0px 14px 21px 2px #{$shadow-color}', + 15: '0px 15px 22px 2px #{$shadow-color}', + 16: '0px 16px 24px 2px #{$shadow-color}', + 17: '0px 17px 26px 2px #{$shadow-color}', + 18: '0px 18px 28px 2px #{$shadow-color}', + 19: '0px 19px 29px 2px #{$shadow-color}', + 20: '0px 20px 31px 3px #{$shadow-color}', + 21: '0px 21px 33px 3px #{$shadow-color}', + 22: '0px 22px 35px 3px #{$shadow-color}', + 23: '0px 23px 36px 3px #{$shadow-color}', + 24: '0px 24px 38px 3px #{$shadow-color}' + ); +} + +@function _get-ambient-map($color, $opacity) { + $shadow-color: if(type-of($color) == color, rgba($color, $opacity * 0.12), $color); + + @return ( + 0: '0px 0px 0px 0px #{$shadow-color}', + 1: '0px 1px 3px 0px #{$shadow-color}', + 2: '0px 1px 5px 0px #{$shadow-color}', + 3: '0px 1px 8px 0px #{$shadow-color}', + 4: '0px 1px 10px 0px #{$shadow-color}', + 5: '0px 1px 14px 0px #{$shadow-color}', + 6: '0px 1px 18px 0px #{$shadow-color}', + 7: '0px 2px 16px 1px #{$shadow-color}', + 8: '0px 3px 14px 2px #{$shadow-color}', + 9: '0px 3px 16px 2px #{$shadow-color}', + 10: '0px 4px 18px 3px #{$shadow-color}', + 11: '0px 4px 20px 3px #{$shadow-color}', + 12: '0px 5px 22px 4px #{$shadow-color}', + 13: '0px 5px 24px 4px #{$shadow-color}', + 14: '0px 5px 26px 4px #{$shadow-color}', + 15: '0px 6px 28px 5px #{$shadow-color}', + 16: '0px 6px 30px 5px #{$shadow-color}', + 17: '0px 6px 32px 5px #{$shadow-color}', + 18: '0px 7px 34px 6px #{$shadow-color}', + 19: '0px 7px 36px 6px #{$shadow-color}', + 20: '0px 8px 38px 7px #{$shadow-color}', + 21: '0px 8px 40px 7px #{$shadow-color}', + 22: '0px 8px 42px 7px #{$shadow-color}', + 23: '0px 9px 44px 8px #{$shadow-color}', + 24: '0px 9px 46px 8px #{$shadow-color}' + ); +} + +// The default duration value for elevation transitions. +$mat-elevation-transition-duration: 280ms !default; + +// The default easing value for elevation transitions. +$mat-elevation-transition-timing-function: $mat-fast-out-slow-in-timing-function; + +// The default color for elevation shadows. +$mat-elevation-color: black !default; + +// The default opacity scaling value for elevation shadows. +$mat-elevation-opacity: 1 !default; + +// Prefix for elevation-related selectors. +$_mat-elevation-prefix: 'mat-elevation-z'; + +// Applies the correct css rules to an element to give it the elevation specified by $zValue. +// The $zValue must be between 0 and 24. +@mixin mat-elevation($zValue, $color: $mat-elevation-color, $opacity: $mat-elevation-opacity) { + @if type-of($zValue) != number or not unitless($zValue) { + @error '$zValue must be a unitless number'; + } + @if $zValue < 0 or $zValue > 24 { + @error '$zValue must be between 0 and 24'; + } + + box-shadow: #{map-get(_get-umbra-map($color, $opacity), $zValue)}, + #{map-get(_get-penumbra-map($color, $opacity), $zValue)}, + #{map-get(_get-ambient-map($color, $opacity), $zValue)}; +} + +@mixin _mat-theme-elevation($zValue, $theme, $opacity: $mat-elevation-opacity) { + $foreground: map-get($theme, foreground); + $elevation-color: map-get($foreground, elevation); + $elevation-color-or-default: if($elevation-color == null, $mat-elevation-color, $elevation-color); + + @include mat-elevation($zValue, $elevation-color-or-default, $opacity); +} + +// Applies the elevation to an element in a manner that allows +// consumers to override it via the Material elevation classes. +@mixin mat-overridable-elevation( + $zValue, + $color: $mat-elevation-color, + $opacity: $mat-elevation-opacity) { + &:not([class*='#{$_mat-elevation-prefix}']) { + @include mat-elevation($zValue, $color, $opacity); + } +} + +@mixin _mat-theme-overridable-elevation($zValue, $theme, $opacity: $mat-elevation-opacity) { + $foreground: map-get($theme, foreground); + $elevation-color: map-get($foreground, elevation); + $elevation-color-or-default: if($elevation-color == null, $mat-elevation-color, $elevation-color); + + @include mat-overridable-elevation($zValue, $elevation-color-or-default, $opacity); +} + +// Returns a string that can be used as the value for a transition property for elevation. +// Calling this function directly is useful in situations where a component needs to transition +// more than one property. +// +// .foo { +// transition: mat-elevation-transition-property-value(), opacity 100ms ease; +// } +@function mat-elevation-transition-property-value( + $duration: $mat-elevation-transition-duration, + $easing: $mat-elevation-transition-timing-function) { + @return box-shadow #{$duration} #{$easing}; +} + +// Applies the correct css rules needed to have an element transition between elevations. +// This mixin should be applied to elements whose elevation values will change depending on their +// context (e.g. when active or disabled). +// +// NOTE(traviskaufman): Both this mixin and the above function use default parameters so they can +// be used in the same way by clients. +@mixin mat-elevation-transition( + $duration: $mat-elevation-transition-duration, + $easing: $mat-elevation-transition-timing-function) { + transition: mat-elevation-transition-property-value($duration, $easing); +} + +// Color palettes from the Material Design spec. +// See https://material.io/design/color/ +// +// Contrast colors are hard-coded because it is too difficult (probably impossible) to +// calculate them. These contrast colors are pulled from the public Material Design spec swatches. +// While the contrast colors in the spec are not prescriptive, we use them for convenience. + + +// @deprecated renamed to $dark-primary-text. +// @breaking-change 8.0.0 +$black-87-opacity: rgba(black, 0.87); +// @deprecated renamed to $light-primary-text. +// @breaking-change 8.0.0 +$white-87-opacity: rgba(white, 0.87); +// @deprecated use $dark-[secondary-text,disabled-text,dividers,focused] instead. +// @breaking-change 8.0.0 +$black-12-opacity: rgba(black, 0.12); +// @deprecated use $light-[secondary-text,disabled-text,dividers,focused] instead. +// @breaking-change 8.0.0 +$white-12-opacity: rgba(white, 0.12); +// @deprecated use $dark-[secondary-text,disabled-text,dividers,focused] instead. +// @breaking-change 8.0.0 +$black-6-opacity: rgba(black, 0.06); +// @deprecated use $light-[secondary-text,disabled-text,dividers,focused] instead. +// @breaking-change 8.0.0 +$white-6-opacity: rgba(white, 0.06); + +$dark-primary-text: rgba(black, 0.87); +$dark-secondary-text: rgba(black, 0.54); +$dark-disabled-text: rgba(black, 0.38); +$dark-dividers: rgba(black, 0.12); +$dark-focused: rgba(black, 0.12); +$light-primary-text: white; +$light-secondary-text: rgba(white, 0.7); +$light-disabled-text: rgba(white, 0.5); +$light-dividers: rgba(white, 0.12); +$light-focused: rgba(white, 0.12); + +$mat-red: ( + 50: #ffebee, + 100: #ffcdd2, + 200: #ef9a9a, + 300: #e57373, + 400: #ef5350, + 500: #f44336, + 600: #e53935, + 700: #d32f2f, + 800: #c62828, + 900: #b71c1c, + A100: #ff8a80, + A200: #ff5252, + A400: #ff1744, + A700: #d50000, + contrast: ( + 50: $dark-primary-text, + 100: $dark-primary-text, + 200: $dark-primary-text, + 300: $dark-primary-text, + 400: $dark-primary-text, + 500: $light-primary-text, + 600: $light-primary-text, + 700: $light-primary-text, + 800: $light-primary-text, + 900: $light-primary-text, + A100: $dark-primary-text, + A200: $light-primary-text, + A400: $light-primary-text, + A700: $light-primary-text, + ) +); + +$mat-pink: ( + 50: #fce4ec, + 100: #f8bbd0, + 200: #f48fb1, + 300: #f06292, + 400: #ec407a, + 500: #e91e63, + 600: #d81b60, + 700: #c2185b, + 800: #ad1457, + 900: #880e4f, + A100: #ff80ab, + A200: #ff4081, + A400: #f50057, + A700: #c51162, + contrast: ( + 50: $dark-primary-text, + 100: $dark-primary-text, + 200: $dark-primary-text, + 300: $dark-primary-text, + 400: $dark-primary-text, + 500: $light-primary-text, + 600: $light-primary-text, + 700: $light-primary-text, + 800: $light-primary-text, + 900: $light-primary-text, + A100: $dark-primary-text, + A200: $light-primary-text, + A400: $light-primary-text, + A700: $light-primary-text, + ) +); + +$mat-purple: ( + 50: #f3e5f5, + 100: #e1bee7, + 200: #ce93d8, + 300: #ba68c8, + 400: #ab47bc, + 500: #9c27b0, + 600: #8e24aa, + 700: #7b1fa2, + 800: #6a1b9a, + 900: #4a148c, + A100: #ea80fc, + A200: #e040fb, + A400: #d500f9, + A700: #aa00ff, + contrast: ( + 50: $dark-primary-text, + 100: $dark-primary-text, + 200: $dark-primary-text, + 300: $light-primary-text, + 400: $light-primary-text, + 500: $light-primary-text, + 600: $light-primary-text, + 700: $light-primary-text, + 800: $light-primary-text, + 900: $light-primary-text, + A100: $dark-primary-text, + A200: $light-primary-text, + A400: $light-primary-text, + A700: $light-primary-text, + ) +); + +$mat-deep-purple: ( + 50: #ede7f6, + 100: #d1c4e9, + 200: #b39ddb, + 300: #9575cd, + 400: #7e57c2, + 500: #673ab7, + 600: #5e35b1, + 700: #512da8, + 800: #4527a0, + 900: #311b92, + A100: #b388ff, + A200: #7c4dff, + A400: #651fff, + A700: #6200ea, + contrast: ( + 50: $dark-primary-text, + 100: $dark-primary-text, + 200: $dark-primary-text, + 300: $light-primary-text, + 400: $light-primary-text, + 500: $light-primary-text, + 600: $light-primary-text, + 700: $light-primary-text, + 800: $light-primary-text, + 900: $light-primary-text, + A100: $dark-primary-text, + A200: $light-primary-text, + A400: $light-primary-text, + A700: $light-primary-text, + ) +); + +$mat-indigo: ( + 50: #e8eaf6, + 100: #c5cae9, + 200: #9fa8da, + 300: #7986cb, + 400: #5c6bc0, + 500: #3f51b5, + 600: #3949ab, + 700: #303f9f, + 800: #283593, + 900: #1a237e, + A100: #8c9eff, + A200: #536dfe, + A400: #3d5afe, + A700: #304ffe, + contrast: ( + 50: $dark-primary-text, + 100: $dark-primary-text, + 200: $dark-primary-text, + 300: $light-primary-text, + 400: $light-primary-text, + 500: $light-primary-text, + 600: $light-primary-text, + 700: $light-primary-text, + 800: $light-primary-text, + 900: $light-primary-text, + A100: $dark-primary-text, + A200: $light-primary-text, + A400: $light-primary-text, + A700: $light-primary-text, + ) +); + +$mat-blue: ( + 50: #e3f2fd, + 100: #bbdefb, + 200: #90caf9, + 300: #64b5f6, + 400: #42a5f5, + 500: #2196f3, + 600: #1e88e5, + 700: #1976d2, + 800: #1565c0, + 900: #0d47a1, + A100: #82b1ff, + A200: #448aff, + A400: #2979ff, + A700: #2962ff, + contrast: ( + 50: $dark-primary-text, + 100: $dark-primary-text, + 200: $dark-primary-text, + 300: $dark-primary-text, + 400: $dark-primary-text, + 500: $light-primary-text, + 600: $light-primary-text, + 700: $light-primary-text, + 800: $light-primary-text, + 900: $light-primary-text, + A100: $dark-primary-text, + A200: $light-primary-text, + A400: $light-primary-text, + A700: $light-primary-text, + ) +); + +$mat-light-blue: ( + 50: #e1f5fe, + 100: #b3e5fc, + 200: #81d4fa, + 300: #4fc3f7, + 400: #29b6f6, + 500: #03a9f4, + 600: #039be5, + 700: #0288d1, + 800: #0277bd, + 900: #01579b, + A100: #80d8ff, + A200: #40c4ff, + A400: #00b0ff, + A700: #0091ea, + contrast: ( + 50: $dark-primary-text, + 100: $dark-primary-text, + 200: $dark-primary-text, + 300: $dark-primary-text, + 400: $dark-primary-text, + 500: $light-primary-text, + 600: $light-primary-text, + 700: $light-primary-text, + 800: $light-primary-text, + 900: $light-primary-text, + A100: $dark-primary-text, + A200: $dark-primary-text, + A400: $dark-primary-text, + A700: $light-primary-text, + ) +); + +$mat-cyan: ( + 50: #e0f7fa, + 100: #b2ebf2, + 200: #80deea, + 300: #4dd0e1, + 400: #26c6da, + 500: #00bcd4, + 600: #00acc1, + 700: #0097a7, + 800: #00838f, + 900: #006064, + A100: #84ffff, + A200: #18ffff, + A400: #00e5ff, + A700: #00b8d4, + contrast: ( + 50: $dark-primary-text, + 100: $dark-primary-text, + 200: $dark-primary-text, + 300: $dark-primary-text, + 400: $dark-primary-text, + 500: $light-primary-text, + 600: $light-primary-text, + 700: $light-primary-text, + 800: $light-primary-text, + 900: $light-primary-text, + A100: $dark-primary-text, + A200: $dark-primary-text, + A400: $dark-primary-text, + A700: $dark-primary-text, + ) +); + +$mat-teal: ( + 50: #e0f2f1, + 100: #b2dfdb, + 200: #80cbc4, + 300: #4db6ac, + 400: #26a69a, + 500: #009688, + 600: #00897b, + 700: #00796b, + 800: #00695c, + 900: #004d40, + A100: #a7ffeb, + A200: #64ffda, + A400: #1de9b6, + A700: #00bfa5, + contrast: ( + 50: $dark-primary-text, + 100: $dark-primary-text, + 200: $dark-primary-text, + 300: $dark-primary-text, + 400: $dark-primary-text, + 500: $light-primary-text, + 600: $light-primary-text, + 700: $light-primary-text, + 800: $light-primary-text, + 900: $light-primary-text, + A100: $dark-primary-text, + A200: $dark-primary-text, + A400: $dark-primary-text, + A700: $dark-primary-text, + ) +); + +$mat-green: ( + 50: #e8f5e9, + 100: #c8e6c9, + 200: #a5d6a7, + 300: #81c784, + 400: #66bb6a, + 500: #4caf50, + 600: #43a047, + 700: #388e3c, + 800: #2e7d32, + 900: #1b5e20, + A100: #b9f6ca, + A200: #69f0ae, + A400: #00e676, + A700: #00c853, + contrast: ( + 50: $dark-primary-text, + 100: $dark-primary-text, + 200: $dark-primary-text, + 300: $dark-primary-text, + 400: $dark-primary-text, + 500: $dark-primary-text, + 600: $light-primary-text, + 700: $light-primary-text, + 800: $light-primary-text, + 900: $light-primary-text, + A100: $dark-primary-text, + A200: $dark-primary-text, + A400: $dark-primary-text, + A700: $dark-primary-text, + ) +); + +$mat-light-green: ( + 50: #f1f8e9, + 100: #dcedc8, + 200: #c5e1a5, + 300: #aed581, + 400: #9ccc65, + 500: #8bc34a, + 600: #7cb342, + 700: #689f38, + 800: #558b2f, + 900: #33691e, + A100: #ccff90, + A200: #b2ff59, + A400: #76ff03, + A700: #64dd17, + contrast: ( + 50: $dark-primary-text, + 100: $dark-primary-text, + 200: $dark-primary-text, + 300: $dark-primary-text, + 400: $dark-primary-text, + 500: $dark-primary-text, + 600: $dark-primary-text, + 700: $light-primary-text, + 800: $light-primary-text, + 900: $light-primary-text, + A100: $dark-primary-text, + A200: $dark-primary-text, + A400: $dark-primary-text, + A700: $dark-primary-text, + ) +); + +$mat-lime: ( + 50: #f9fbe7, + 100: #f0f4c3, + 200: #e6ee9c, + 300: #dce775, + 400: #d4e157, + 500: #cddc39, + 600: #c0ca33, + 700: #afb42b, + 800: #9e9d24, + 900: #827717, + A100: #f4ff81, + A200: #eeff41, + A400: #c6ff00, + A700: #aeea00, + contrast: ( + 50: $dark-primary-text, + 100: $dark-primary-text, + 200: $dark-primary-text, + 300: $dark-primary-text, + 400: $dark-primary-text, + 500: $dark-primary-text, + 600: $dark-primary-text, + 700: $dark-primary-text, + 800: $dark-primary-text, + 900: $light-primary-text, + A100: $dark-primary-text, + A200: $dark-primary-text, + A400: $dark-primary-text, + A700: $dark-primary-text, + ) +); + +$mat-yellow: ( + 50: #fffde7, + 100: #fff9c4, + 200: #fff59d, + 300: #fff176, + 400: #ffee58, + 500: #ffeb3b, + 600: #fdd835, + 700: #fbc02d, + 800: #f9a825, + 900: #f57f17, + A100: #ffff8d, + A200: #ffff00, + A400: #ffea00, + A700: #ffd600, + contrast: ( + 50: $dark-primary-text, + 100: $dark-primary-text, + 200: $dark-primary-text, + 300: $dark-primary-text, + 400: $dark-primary-text, + 500: $dark-primary-text, + 600: $dark-primary-text, + 700: $dark-primary-text, + 800: $dark-primary-text, + 900: $dark-primary-text, + A100: $dark-primary-text, + A200: $dark-primary-text, + A400: $dark-primary-text, + A700: $dark-primary-text, + ) +); + +$mat-amber: ( + 50: #fff8e1, + 100: #ffecb3, + 200: #ffe082, + 300: #ffd54f, + 400: #ffca28, + 500: #ffc107, + 600: #ffb300, + 700: #ffa000, + 800: #ff8f00, + 900: #ff6f00, + A100: #ffe57f, + A200: #ffd740, + A400: #ffc400, + A700: #ffab00, + contrast: ( + 50: $dark-primary-text, + 100: $dark-primary-text, + 200: $dark-primary-text, + 300: $dark-primary-text, + 400: $dark-primary-text, + 500: $dark-primary-text, + 600: $dark-primary-text, + 700: $dark-primary-text, + 800: $dark-primary-text, + 900: $dark-primary-text, + A100: $dark-primary-text, + A200: $dark-primary-text, + A400: $dark-primary-text, + A700: $dark-primary-text, + ) +); + +$mat-orange: ( + 50: #fff3e0, + 100: #ffe0b2, + 200: #ffcc80, + 300: #ffb74d, + 400: #ffa726, + 500: #ff9800, + 600: #fb8c00, + 700: #f57c00, + 800: #ef6c00, + 900: #e65100, + A100: #ffd180, + A200: #ffab40, + A400: #ff9100, + A700: #ff6d00, + contrast: ( + 50: $dark-primary-text, + 100: $dark-primary-text, + 200: $dark-primary-text, + 300: $dark-primary-text, + 400: $dark-primary-text, + 500: $dark-primary-text, + 600: $dark-primary-text, + 700: $dark-primary-text, + 800: $light-primary-text, + 900: $light-primary-text, + A100: $dark-primary-text, + A200: $dark-primary-text, + A400: $dark-primary-text, + A700: black, + ) +); + +$mat-deep-orange: ( + 50: #fbe9e7, + 100: #ffccbc, + 200: #ffab91, + 300: #ff8a65, + 400: #ff7043, + 500: #ff5722, + 600: #f4511e, + 700: #e64a19, + 800: #d84315, + 900: #bf360c, + A100: #ff9e80, + A200: #ff6e40, + A400: #ff3d00, + A700: #dd2c00, + contrast: ( + 50: $dark-primary-text, + 100: $dark-primary-text, + 200: $dark-primary-text, + 300: $dark-primary-text, + 400: $dark-primary-text, + 500: $light-primary-text, + 600: $light-primary-text, + 700: $light-primary-text, + 800: $light-primary-text, + 900: $light-primary-text, + A100: $dark-primary-text, + A200: $dark-primary-text, + A400: $light-primary-text, + A700: $light-primary-text, + ) +); + +$mat-brown: ( + 50: #efebe9, + 100: #d7ccc8, + 200: #bcaaa4, + 300: #a1887f, + 400: #8d6e63, + 500: #795548, + 600: #6d4c41, + 700: #5d4037, + 800: #4e342e, + 900: #3e2723, + A100: #d7ccc8, + A200: #bcaaa4, + A400: #8d6e63, + A700: #5d4037, + contrast: ( + 50: $dark-primary-text, + 100: $dark-primary-text, + 200: $dark-primary-text, + 300: $light-primary-text, + 400: $light-primary-text, + 500: $light-primary-text, + 600: $light-primary-text, + 700: $light-primary-text, + 800: $light-primary-text, + 900: $light-primary-text, + A100: $dark-primary-text, + A200: $dark-primary-text, + A400: $light-primary-text, + A700: $light-primary-text, + ) +); + +$mat-grey: ( + 50: #fafafa, + 100: #f5f5f5, + 200: #eeeeee, + 300: #e0e0e0, + 400: #bdbdbd, + 500: #9e9e9e, + 600: #757575, + 700: #616161, + 800: #424242, + 900: #212121, + A100: #ffffff, + A200: #eeeeee, + A400: #bdbdbd, + A700: #616161, + contrast: ( + 50: $dark-primary-text, + 100: $dark-primary-text, + 200: $dark-primary-text, + 300: $dark-primary-text, + 400: $dark-primary-text, + 500: $dark-primary-text, + 600: $light-primary-text, + 700: $light-primary-text, + 800: $light-primary-text, + 900: $light-primary-text, + A100: $dark-primary-text, + A200: $dark-primary-text, + A400: $dark-primary-text, + A700: $light-primary-text, + ) +); + +// Alias for alternate spelling. +$mat-gray: $mat-grey; + +$mat-blue-grey: ( + 50: #eceff1, + 100: #cfd8dc, + 200: #b0bec5, + 300: #90a4ae, + 400: #78909c, + 500: #607d8b, + 600: #546e7a, + 700: #455a64, + 800: #37474f, + 900: #263238, + A100: #cfd8dc, + A200: #b0bec5, + A400: #78909c, + A700: #455a64, + contrast: ( + 50: $dark-primary-text, + 100: $dark-primary-text, + 200: $dark-primary-text, + 300: $dark-primary-text, + 400: $light-primary-text, + 500: $light-primary-text, + 600: $light-primary-text, + 700: $light-primary-text, + 800: $light-primary-text, + 900: $light-primary-text, + A100: $dark-primary-text, + A200: $dark-primary-text, + A400: $light-primary-text, + A700: $light-primary-text, + ) +); + +// Alias for alternate spelling. +$mat-blue-gray: $mat-blue-grey; + + +// Background palette for light themes. +$mat-light-theme-background: ( + status-bar: map_get($mat-grey, 300), + app-bar: map_get($mat-grey, 100), + background: map_get($mat-grey, 50), + hover: rgba(black, 0.04), // TODO(kara): check style with Material Design UX + card: white, + dialog: white, + disabled-button: rgba(black, 0.12), + raised-button: white, + focused-button: $dark-focused, + selected-button: map_get($mat-grey, 300), + selected-disabled-button: map_get($mat-grey, 400), + disabled-button-toggle: map_get($mat-grey, 200), + unselected-chip: map_get($mat-grey, 300), + disabled-list-option: map_get($mat-grey, 200), +); + +// Background palette for dark themes. +$mat-dark-theme-background: ( + status-bar: black, + app-bar: map_get($mat-grey, 900), + background: #303030, + hover: rgba(white, 0.04), // TODO(kara): check style with Material Design UX + card: map_get($mat-grey, 800), + dialog: map_get($mat-grey, 800), + disabled-button: rgba(white, 0.12), + raised-button: map-get($mat-grey, 800), + focused-button: $light-focused, + selected-button: map_get($mat-grey, 900), + selected-disabled-button: map_get($mat-grey, 800), + disabled-button-toggle: black, + unselected-chip: map_get($mat-grey, 700), + disabled-list-option: black, +); + +// Foreground palette for light themes. +$mat-light-theme-foreground: ( + base: black, + divider: $dark-dividers, + dividers: $dark-dividers, + disabled: $dark-disabled-text, + disabled-button: rgba(black, 0.26), + disabled-text: $dark-disabled-text, + elevation: black, + hint-text: $dark-disabled-text, + secondary-text: $dark-secondary-text, + icon: rgba(black, 0.54), + icons: rgba(black, 0.54), + text: rgba(black, 0.87), + slider-min: rgba(black, 0.87), + slider-off: rgba(black, 0.26), + slider-off-active: rgba(black, 0.38), +); + +// Foreground palette for dark themes. +$mat-dark-theme-foreground: ( + base: white, + divider: $light-dividers, + dividers: $light-dividers, + disabled: $light-disabled-text, + disabled-button: rgba(white, 0.3), + disabled-text: $light-disabled-text, + elevation: black, + hint-text: $light-disabled-text, + secondary-text: $light-secondary-text, + icon: white, + icons: white, + text: white, + slider-min: white, + slider-off: rgba(white, 0.3), + slider-off-active: rgba(white, 0.3), +); + + + +// For a given hue in a palette, return the contrast color from the map of contrast palettes. +// @param $color-map +// @param $hue +@function mat-contrast($palette, $hue) { + @return map-get(map-get($palette, contrast), $hue); +} + + +// Creates a map of hues to colors for a theme. This is used to define a theme palette in terms +// of the Material Design hues. +// @param $color-map +// @param $primary +// @param $lighter +@function mat-palette($base-palette, $default: 500, $lighter: 100, $darker: 700, $text: $default) { + $result: map_merge($base-palette, ( + default: map-get($base-palette, $default), + lighter: map-get($base-palette, $lighter), + darker: map-get($base-palette, $darker), + text: map-get($base-palette, $text), + + default-contrast: mat-contrast($base-palette, $default), + lighter-contrast: mat-contrast($base-palette, $lighter), + darker-contrast: mat-contrast($base-palette, $darker) + )); + + // For each hue in the palette, add a "-contrast" color to the map. + @each $hue, $color in $base-palette { + $result: map_merge($result, ( + '#{$hue}-contrast': mat-contrast($base-palette, $hue) + )); + } + + @return $result; +} + + +// Gets a color from a theme palette (the output of mat-palette). +// The hue can be one of the standard values (500, A400, etc.), one of the three preconfigured +// hues (default, lighter, darker), or any of the aforementioned prefixed with "-contrast". +// +// @param $color-map The theme palette (output of mat-palette). +// @param $hue The hue from the palette to use. If this is a value between 0 and 1, it will +// be treated as opacity. +// @param $opacity The alpha channel value for the color. +@function mat-color($palette, $hue: default, $opacity: null) { + // If hueKey is a number between zero and one, then it actually contains an + // opacity value, so recall this function with the default hue and that given opacity. + @if type-of($hue) == number and $hue >= 0 and $hue <= 1 { + @return mat-color($palette, default, $hue); + } + + $color: map-get($palette, $hue); + + @if (type-of($color) != color) { + // If the $color resolved to something different from a color (e.g. a CSS variable), + // we can't apply the opacity anyway so we return the value as is, otherwise Sass can + // throw an error or output something invalid. + @return $color; + } + + @return rgba($color, if($opacity == null, opacity($color), $opacity)); +} + + +// Creates a container object for a light theme to be given to individual component theme mixins. +@function mat-light-theme($primary, $accent, $warn: mat-palette($mat-red)) { + @return ( + primary: $primary, + accent: $accent, + warn: $warn, + is-dark: false, + foreground: $mat-light-theme-foreground, + background: $mat-light-theme-background, + ); +} + + +// Creates a container object for a dark theme to be given to individual component theme mixins. +@function mat-dark-theme($primary, $accent, $warn: mat-palette($mat-red)) { + @return ( + primary: $primary, + accent: $accent, + warn: $warn, + is-dark: true, + foreground: $mat-dark-theme-foreground, + background: $mat-dark-theme-background, + ); +} + + + +$mat-ripple-color-opacity: 0.1; + +@mixin mat-ripple() { + + // The host element of an mat-ripple directive should always have a position of "absolute" or + // "relative" so that the ripples inside are correctly positioned relatively to the container. + .mat-ripple { + overflow: hidden; + + // By default, every ripple container should have position: relative in favor of creating an + // easy API for developers using the MatRipple directive. + position: relative; + } + + .mat-ripple.mat-ripple-unbounded { + overflow: visible; + } + + .mat-ripple-element { + position: absolute; + border-radius: 50%; + pointer-events: none; + + transition: opacity, transform 0ms cubic-bezier(0, 0, 0.2, 1); + transform: scale(0); + + // In high contrast mode the ripple is opaque, causing it to obstruct the content. + @include cdk-high-contrast { + display: none; + } + } +} + +/* Theme for the ripple elements.*/ +@mixin mat-ripple-theme($theme) { + $foreground: map_get($theme, foreground); + $foreground-base: map_get($foreground, base); + + .mat-ripple-element { + // If the ripple color is resolves to a color *type*, we can use it directly, otherwise + // (e.g. it resolves to a CSS variable) we fall back to using the color and setting an opacity. + @if (type-of($foreground-base) == color) { + background-color: rgba($foreground-base, $mat-ripple-color-opacity); + } + @else { + background-color: $foreground-base; + opacity: $mat-ripple-color-opacity; + } + } +} + + + +// Utility for fetching a nested value from a typography config. +@function _mat-get-type-value($config, $level, $name) { + @return map-get(map-get($config, $level), $name); +} + +// Gets the font size for a level inside a typography config. +@function mat-font-size($config, $level) { + @return _mat-get-type-value($config, $level, font-size); +} + +// Gets the line height for a level inside a typography config. +@function mat-line-height($config, $level) { + @return _mat-get-type-value($config, $level, line-height); +} + +// Gets the font weight for a level inside a typography config. +@function mat-font-weight($config, $level) { + @return _mat-get-type-value($config, $level, font-weight); +} + +// Gets the letter spacing for a level inside a typography config. +@function mat-letter-spacing($config, $level) { + @return _mat-get-type-value($config, $level, letter-spacing); +} + +// Gets the font-family from a typography config and removes the quotes around it. +@function mat-font-family($config, $level: null) { + $font-family: map-get($config, font-family); + + @if $level != null { + $font-family: _mat-get-type-value($config, $level, font-family); + } + + // Guard against unquoting non-string values, because it's deprecated. + @return if(type-of($font-family) == string, unquote($font-family), $font-family); +} + +// Outputs the shorthand `font` CSS property, based on a set of typography values. Falls back to +// the individual properties if a value that isn't allowed in the shorthand is passed in. +@mixin mat-typography-font-shorthand($font-size, $font-weight, $line-height, $font-family) { + // If any of the values are set to `inherit`, we can't use the shorthand + // so we fall back to passing in the individual properties. + @if ($font-size == inherit or + $font-weight == inherit or + $line-height == inherit or + $font-family == inherit or + $font-size == null or + $font-weight == null or + $line-height == null or + $font-family == null) { + + font-size: $font-size; + font-weight: $font-weight; + line-height: $line-height; + font-family: $font-family; + } + @else { + // Otherwise use the shorthand `font`, because it's the least amount of bytes. Note + // that we need to use interpolation for `font-size/line-height` in order to prevent + // Sass from dividing the two values. + font: $font-weight #{$font-size}/#{$line-height} $font-family; + } +} + +// Converts a typography level into CSS styles. +@mixin mat-typography-level-to-styles($config, $level) { + $font-size: mat-font-size($config, $level); + $font-weight: mat-font-weight($config, $level); + $line-height: mat-line-height($config, $level); + $font-family: mat-font-family($config, $level); + + @include mat-typography-font-shorthand($font-size, $font-weight, $line-height, $font-family); + letter-spacing: mat-letter-spacing($config, $level); +} + + +@mixin mat-option-theme($theme) { + $foreground: map-get($theme, foreground); + $background: map-get($theme, background); + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + + .mat-option { + color: mat-color($foreground, text); + + &:hover:not(.mat-option-disabled), + &:focus:not(.mat-option-disabled) { + background: mat-color($background, hover); + } + + // In multiple mode there is a checkbox to show that the option is selected. + &.mat-selected:not(.mat-option-multiple):not(.mat-option-disabled) { + background: mat-color($background, hover); + } + + &.mat-active { + background: mat-color($background, hover); + color: mat-color($foreground, text); + } + + &.mat-option-disabled { + color: mat-color($foreground, hint-text); + } + } + + .mat-primary .mat-option.mat-selected:not(.mat-option-disabled) { + color: mat-color($primary, text); + } + + .mat-accent .mat-option.mat-selected:not(.mat-option-disabled) { + color: mat-color($accent, text); + } + + .mat-warn .mat-option.mat-selected:not(.mat-option-disabled) { + color: mat-color($warn, text); + } +} + +@mixin mat-option-typography($config) { + .mat-option { + font: { + family: mat-font-family($config); + size: mat-font-size($config, subheading-2); + } + } +} + + + + + +@mixin mat-optgroup-theme($theme) { + $foreground: map-get($theme, foreground); + + .mat-optgroup-label { + color: mat-color($foreground, secondary-text); + } + + .mat-optgroup-disabled .mat-optgroup-label { + color: mat-color($foreground, hint-text); + } +} + +@mixin mat-optgroup-typography($config) { + .mat-optgroup-label { + @include mat-typography-level-to-styles($config, body-2); + } +} + + + +@mixin mat-pseudo-checkbox-theme($theme) { + $is-dark-theme: map-get($theme, is-dark); + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + $background: map-get($theme, background); + + // NOTE(traviskaufman): While the spec calls for translucent blacks/whites for disabled colors, + // this does not work well with elements layered on top of one another. To get around this we + // blend the colors together based on the base color and the theme background. + $white-30pct-opacity-on-dark: #686868; + $black-26pct-opacity-on-light: #b0b0b0; + $disabled-color: if($is-dark-theme, $white-30pct-opacity-on-dark, $black-26pct-opacity-on-light); + $colored-box-selector: '.mat-pseudo-checkbox-checked, .mat-pseudo-checkbox-indeterminate'; + + .mat-pseudo-checkbox { + color: mat-color(map-get($theme, foreground), secondary-text); + + &::after { + color: mat-color($background, background); + } + } + + .mat-pseudo-checkbox-disabled { + color: $disabled-color; + } + + .mat-primary .mat-pseudo-checkbox-checked, + .mat-primary .mat-pseudo-checkbox-indeterminate { + background: mat-color(map-get($theme, primary)); + } + + // Default to the accent color. Note that the pseudo checkboxes are meant to inherit the + // theme from their parent, rather than implementing their own theming, which is why we + // don't attach to the `mat-*` classes. Also note that this needs to be below `.mat-primary` + // in order to allow for the color to be overwritten if the checkbox is inside a parent that + // has `mat-accent` and is placed inside another parent that has `mat-primary`. + .mat-pseudo-checkbox-checked, + .mat-pseudo-checkbox-indeterminate, + .mat-accent .mat-pseudo-checkbox-checked, + .mat-accent .mat-pseudo-checkbox-indeterminate { + background: mat-color(map-get($theme, accent)); + } + + .mat-warn .mat-pseudo-checkbox-checked, + .mat-warn .mat-pseudo-checkbox-indeterminate { + background: mat-color(map-get($theme, warn)); + } + + .mat-pseudo-checkbox-checked, + .mat-pseudo-checkbox-indeterminate { + &.mat-pseudo-checkbox-disabled { + background: $disabled-color; + } + } +} + + + +// Represents a typography level from the Material design spec. +@function mat-typography-level( + $font-size, + $line-height: $font-size, + $font-weight: 400, + $font-family: null, + $letter-spacing: null) { + + @return ( + font-size: $font-size, + line-height: $line-height, + font-weight: $font-weight, + font-family: $font-family, + letter-spacing: $letter-spacing + ); +} + +// Represents a collection of typography levels. +// Defaults come from https://material.io/guidelines/style/typography.html +// Note: The spec doesn't mention letter spacing. The values here come from +// eyeballing it until it looked exactly like the spec examples. +@function mat-typography-config( + $font-family: 'Roboto, "Helvetica Neue", sans-serif', + $display-4: mat-typography-level(112px, 112px, 300, $letter-spacing: -0.05em), + $display-3: mat-typography-level(56px, 56px, 400, $letter-spacing: -0.02em), + $display-2: mat-typography-level(45px, 48px, 400, $letter-spacing: -0.005em), + $display-1: mat-typography-level(34px, 40px, 400), + $headline: mat-typography-level(24px, 32px, 400), + $title: mat-typography-level(20px, 32px, 500), + $subheading-2: mat-typography-level(16px, 28px, 400), + $subheading-1: mat-typography-level(15px, 24px, 400), + $body-2: mat-typography-level(14px, 24px, 500), + $body-1: mat-typography-level(14px, 20px, 400), + $caption: mat-typography-level(12px, 20px, 400), + $button: mat-typography-level(14px, 14px, 500), + // Line-height must be unit-less fraction of the font-size. + $input: mat-typography-level(inherit, 1.125, 400) +) { + + // Declare an initial map with all of the levels. + $config: ( + display-4: $display-4, + display-3: $display-3, + display-2: $display-2, + display-1: $display-1, + headline: $headline, + title: $title, + subheading-2: $subheading-2, + subheading-1: $subheading-1, + body-2: $body-2, + body-1: $body-1, + caption: $caption, + button: $button, + input: $input, + ); + + // Loop through the levels and set the `font-family` of the ones that don't have one to the base. + // Note that Sass can't modify maps in place, which means that we need to merge and re-assign. + @each $key, $level in $config { + @if map-get($level, font-family) == null { + $new-level: map-merge($level, (font-family: $font-family)); + $config: map-merge($config, ($key: $new-level)); + } + } + + // Add the base font family to the config. + @return map-merge($config, (font-family: $font-family)); +} + +// Adds the base typography styles, based on a config. +@mixin mat-base-typography($config, $selector: '.mat-typography') { + .mat-h1, .mat-headline, #{$selector} h1 { + @include mat-typography-level-to-styles($config, headline); + margin: 0 0 16px; + } + + .mat-h2, .mat-title, #{$selector} h2 { + @include mat-typography-level-to-styles($config, title); + margin: 0 0 16px; + } + + .mat-h3, .mat-subheading-2, #{$selector} h3 { + @include mat-typography-level-to-styles($config, subheading-2); + margin: 0 0 16px; + } + + .mat-h4, .mat-subheading-1, #{$selector} h4 { + @include mat-typography-level-to-styles($config, subheading-1); + margin: 0 0 16px; + } + + // Note: the spec doesn't have anything that would correspond to h5 and h6, but we add these for + // consistency. The font sizes come from the Chrome user agent styles which have h5 at 0.83em + // and h6 at 0.67em. + .mat-h5, #{$selector} h5 { + @include mat-typography-font-shorthand( + // calc is used here to support css variables + calc(#{mat-font-size($config, body-1)} * 0.83), + mat-font-weight($config, body-1), + mat-line-height($config, body-1), + mat-font-family($config, body-1) + ); + + margin: 0 0 12px; + } + + .mat-h6, #{$selector} h6 { + @include mat-typography-font-shorthand( + // calc is used here to support css variables + calc(#{mat-font-size($config, body-1)} * 0.67), + mat-font-weight($config, body-1), + mat-line-height($config, body-1), + mat-font-family($config, body-1) + ); + + margin: 0 0 12px; + } + + .mat-body-strong, .mat-body-2 { + @include mat-typography-level-to-styles($config, body-2); + } + + .mat-body, .mat-body-1, #{$selector} { + @include mat-typography-level-to-styles($config, body-1); + + p { + margin: 0 0 12px; + } + } + + .mat-small, .mat-caption { + @include mat-typography-level-to-styles($config, caption); + } + + .mat-display-4, #{$selector} .mat-display-4 { + @include mat-typography-level-to-styles($config, display-4); + margin: 0 0 56px; + } + + .mat-display-3, #{$selector} .mat-display-3 { + @include mat-typography-level-to-styles($config, display-3); + margin: 0 0 64px; + } + + .mat-display-2, #{$selector} .mat-display-2 { + @include mat-typography-level-to-styles($config, display-2); + margin: 0 0 64px; + } + + .mat-display-1, #{$selector} .mat-display-1 { + @include mat-typography-level-to-styles($config, display-1); + margin: 0 0 64px; + } +} + + + + +@mixin mat-autocomplete-theme($theme) { + $foreground: map-get($theme, foreground); + $background: map-get($theme, background); + + .mat-autocomplete-panel { + @include _mat-theme-overridable-elevation(4, $theme); + background: mat-color($background, card); + color: mat-color($foreground, text); + + // Selected options in autocompletes should not be gray, but we + // only want to override the background for selected options if + // they are *not* in hover or focus state. This change has to be + // made here because base option styles are shared between the + // autocomplete and the select. + .mat-option.mat-selected:not(.mat-active):not(:hover) { + background: mat-color($background, card); + + &:not(.mat-option-disabled) { + color: mat-color($foreground, text); + } + } + } + +} + +@mixin mat-autocomplete-typography($config) { } + +// This contains all of the styles for the badge +// rather than just the color/theme because of +// no style sheet support for directives. + + + + + +$mat-badge-font-size: 12px; +$mat-badge-font-weight: 600; +$mat-badge-default-size: 22px !default; +$mat-badge-small-size: $mat-badge-default-size - 6; +$mat-badge-large-size: $mat-badge-default-size + 6; + +// Mixin for building offset given different sizes +@mixin _mat-badge-size($size) { + .mat-badge-content { + width: $size; + height: $size; + line-height: $size; + } + + &.mat-badge-above { + .mat-badge-content { + top: -$size / 2; + } + } + + &.mat-badge-below { + .mat-badge-content { + bottom: -$size / 2; + } + } + + &.mat-badge-before { + .mat-badge-content { + left: -$size; + } + } + + [dir='rtl'] &.mat-badge-before { + .mat-badge-content { + left: auto; + right: -$size; + } + } + + &.mat-badge-after { + .mat-badge-content { + right: -$size; + } + } + + [dir='rtl'] &.mat-badge-after { + .mat-badge-content { + right: auto; + left: -$size; + } + } + + &.mat-badge-overlap { + &.mat-badge-before { + .mat-badge-content { + left: -$size / 2; + } + } + + [dir='rtl'] &.mat-badge-before { + .mat-badge-content { + left: auto; + right: -$size / 2; + } + } + + &.mat-badge-after { + .mat-badge-content { + right: -$size / 2; + } + } + + [dir='rtl'] &.mat-badge-after { + .mat-badge-content { + right: auto; + left: -$size / 2; + } + } + } +} + +@mixin mat-badge-theme($theme) { + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + $primary: map-get($theme, primary); + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + + .mat-badge-content { + color: mat-color($primary, default-contrast); + background: mat-color($primary); + + @include cdk-high-contrast { + outline: solid 1px; + border-radius: 0; + } + } + + .mat-badge-accent { + .mat-badge-content { + background: mat-color($accent); + color: mat-color($accent, default-contrast); + } + } + + .mat-badge-warn { + .mat-badge-content { + color: mat-color($warn, default-contrast); + background: mat-color($warn); + } + } + + .mat-badge { + position: relative; + } + + .mat-badge-hidden { + .mat-badge-content { + display: none; + } + } + + .mat-badge-disabled { + .mat-badge-content { + $app-background: mat-color($background, 'background'); + $badge-color: mat-color($foreground, disabled-button); + + // The disabled color usually has some kind of opacity, but because the badge is overlayed + // on top of something else, it won't look good if it's opaque. If it is a color *type*, + // we convert it into a solid color by taking the opacity from the rgba value and using + // the value to determine the percentage of the background to put into foreground when + // mixing the colors together. + @if (type-of($badge-color) == color and type-of($app-background) == color) { + $badge-opacity: opacity($badge-color); + background: mix($app-background, rgba($badge-color, 1), (1 - $badge-opacity) * 100%); + } + @else { + background: $badge-color; + } + + color: mat-color($foreground, disabled-text); + } + } + + .mat-badge-content { + position: absolute; + text-align: center; + display: inline-block; + border-radius: 50%; + transition: transform 200ms ease-in-out; + transform: scale(0.6); + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + pointer-events: none; + } + + .ng-animate-disabled .mat-badge-content, + .mat-badge-content._mat-animation-noopable { + transition: none; + } + + // The active class is added after the element is added + // so it can animate scale to default + .mat-badge-content.mat-badge-active { + // Scale to `none` instead of `1` to avoid blurry text in some browsers. + transform: none; + } + + .mat-badge-small { + @include _mat-badge-size($mat-badge-small-size); + } + .mat-badge-medium { + @include _mat-badge-size($mat-badge-default-size); + } + .mat-badge-large { + @include _mat-badge-size($mat-badge-large-size); + } +} + +@mixin mat-badge-typography($config) { + .mat-badge-content { + font-weight: $mat-badge-font-weight; + font-size: $mat-badge-font-size; + font-family: mat-font-family($config); + } + + .mat-badge-small .mat-badge-content { + // Set the font size to 75% of the original. + font-size: $mat-badge-font-size * 0.75; + } + + .mat-badge-large .mat-badge-content { + font-size: $mat-badge-font-size * 2; + } +} + + + + + +@mixin mat-bottom-sheet-theme($theme) { + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + + .mat-bottom-sheet-container { + @include _mat-theme-elevation(16, $theme); + background: mat-color($background, dialog); + color: mat-color($foreground, text); + } +} + +@mixin mat-bottom-sheet-typography($config) { + .mat-bottom-sheet-container { + @include mat-typography-level-to-styles($config, body-1); + } +} + + + + + +$_mat-button-ripple-opacity: 0.1; + +// Applies a focus style to an mat-button element for each of the supported palettes. +@mixin _mat-button-focus-overlay-color($theme) { + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + + &.mat-primary .mat-button-focus-overlay { + background-color: mat-color($primary); + } + + &.mat-accent .mat-button-focus-overlay { + background-color: mat-color($accent); + } + + &.mat-warn .mat-button-focus-overlay { + background-color: mat-color($warn); + } + + &[disabled] .mat-button-focus-overlay { + background-color: transparent; + } +} + +// Applies the background color for a ripple. If the value provided is not a Sass color, +// we assume that we've been given a CSS variable. Since we can't perform alpha-blending +// on a CSS variable, we instead add the opacity directly to the ripple element. +@mixin _mat-button-ripple-background($palette, $hue, $opacity) { + $background-color: mat-color($palette, $hue, $opacity); + background-color: $background-color; + @if (type-of($background-color) != color) { + opacity: $opacity; + } +} + +@mixin _mat-button-ripple-color($theme, $hue, $opacity: $_mat-button-ripple-opacity) { + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + + &.mat-primary .mat-ripple-element { + @include _mat-button-ripple-background($primary, $hue, $opacity); + } + + &.mat-accent .mat-ripple-element { + @include _mat-button-ripple-background($accent, $hue, $opacity); + } + + &.mat-warn .mat-ripple-element { + @include _mat-button-ripple-background($warn, $hue, $opacity); + } +} + +// Applies a property to an mat-button element for each of the supported palettes. +@mixin _mat-button-theme-property($theme, $property, $hue) { + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + + &.mat-primary { + #{$property}: mat-color($primary, $hue); + } + &.mat-accent { + #{$property}: mat-color($accent, $hue); + } + &.mat-warn { + #{$property}: mat-color($warn, $hue); + } + + &.mat-primary, &.mat-accent, &.mat-warn, &[disabled] { + &[disabled] { + $palette: if($property == 'color', $foreground, $background); + #{$property}: mat-color($palette, disabled-button); + } + } +} + +@mixin mat-button-theme($theme) { + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + + .mat-button, .mat-icon-button, .mat-stroked-button { + // Buttons without a background color should inherit the font color. This is necessary to + // ensure that the button is readable on custom background colors. It's wrong to always assume + // that those buttons are always placed inside of containers with the default background + // color of the theme (e.g. themed toolbars). + color: inherit; + background: transparent; + + @include _mat-button-theme-property($theme, 'color', text); + @include _mat-button-focus-overlay-color($theme); + + // Setup the ripple color to be based on the text color. This ensures that the ripples + // are matching with the current theme palette and are in contrast to the background color + // (e.g in themed toolbars). + .mat-ripple-element { + opacity: $_mat-button-ripple-opacity; + background-color: currentColor; + } + } + + .mat-button-focus-overlay { + background: map_get($foreground, base); + } + + // Note: this needs a bit extra specificity, because we're not guaranteed the inclusion + // order of the theme styles and the button reset may end up resetting this as well. + .mat-stroked-button:not([disabled]) { + border-color: mat-color($foreground, divider); + } + + .mat-flat-button, .mat-raised-button, .mat-fab, .mat-mini-fab { + // Default font and background color when not using any color palette. + color: mat-color($foreground, text); + background-color: mat-color($background, raised-button); + + @include _mat-button-theme-property($theme, 'color', default-contrast); + @include _mat-button-theme-property($theme, 'background-color', default); + @include _mat-button-ripple-color($theme, default-contrast); + } + + .mat-stroked-button, .mat-flat-button { + @include _mat-theme-overridable-elevation(0, $theme); + } + + .mat-raised-button { + @include _mat-theme-overridable-elevation(2, $theme); + + &:not([disabled]):active { + @include _mat-theme-overridable-elevation(8, $theme); + } + + &[disabled] { + @include _mat-theme-overridable-elevation(0, $theme); + } + } + + .mat-fab, .mat-mini-fab { + @include _mat-theme-overridable-elevation(6, $theme); + + &:not([disabled]):active { + @include _mat-theme-overridable-elevation(12, $theme); + } + + &[disabled] { + @include _mat-theme-overridable-elevation(0, $theme); + } + } +} + +@mixin mat-button-typography($config) { + .mat-button, .mat-raised-button, .mat-icon-button, .mat-stroked-button, + .mat-flat-button, .mat-fab, .mat-mini-fab { + font: { + family: mat-font-family($config, button); + size: mat-font-size($config, button); + weight: mat-font-weight($config, button); + } + } +} + + + + + + +@mixin mat-button-toggle-theme($theme) { + $foreground: map-get($theme, foreground); + $background: map-get($theme, background); + $divider-color: mat-color($foreground, divider); + + .mat-button-toggle-standalone, + .mat-button-toggle-group { + @include _mat-theme-elevation(2, $theme); + } + + .mat-button-toggle-standalone.mat-button-toggle-appearance-standard, + .mat-button-toggle-group-appearance-standard { + box-shadow: none; + } + + .mat-button-toggle { + color: mat-color($foreground, hint-text); + + .mat-button-toggle-focus-overlay { + background-color: mat-color($background, focused-button); + } + } + + .mat-button-toggle-appearance-standard { + color: mat-color($foreground, text); + background: mat-color($background, card); + + .mat-button-toggle-focus-overlay { + background-color: mat-color($background, focused-button, 1); + } + } + + .mat-button-toggle-group-appearance-standard .mat-button-toggle + .mat-button-toggle { + border-left: solid 1px $divider-color; + } + + [dir='rtl'] .mat-button-toggle-group-appearance-standard .mat-button-toggle + .mat-button-toggle { + border-left: none; + border-right: solid 1px $divider-color; + } + + .mat-button-toggle-group-appearance-standard.mat-button-toggle-vertical { + .mat-button-toggle + .mat-button-toggle { + border-left: none; + border-right: none; + border-top: solid 1px $divider-color; + } + } + + .mat-button-toggle-checked { + background-color: mat-color($background, selected-button); + color: mat-color($foreground, secondary-text); + + &.mat-button-toggle-appearance-standard { + color: mat-color($foreground, text); + } + } + + .mat-button-toggle-disabled { + color: mat-color($foreground, disabled-button); + background-color: mat-color($background, disabled-button-toggle); + + &.mat-button-toggle-appearance-standard { + background: mat-color($background, card); + } + + &.mat-button-toggle-checked { + background-color: mat-color($background, selected-disabled-button); + } + } + + .mat-button-toggle-standalone.mat-button-toggle-appearance-standard, + .mat-button-toggle-group-appearance-standard { + border: solid 1px $divider-color; + } +} + +@mixin mat-button-toggle-typography($config) { + .mat-button-toggle { + font-family: mat-font-family($config); + } +} + + + + + + + +@mixin mat-card-theme($theme) { + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + + .mat-card { + @include _mat-theme-overridable-elevation(1, $theme); + background: mat-color($background, card); + color: mat-color($foreground, text); + + // Needs extra specificity to be able to override the elevation selectors. + &.mat-card-flat { + @include _mat-theme-overridable-elevation(0, $theme); + } + } + + .mat-card-subtitle { + color: mat-color($foreground, secondary-text); + } +} + +@mixin mat-card-typography($config) { + .mat-card { + font-family: mat-font-family($config); + } + + .mat-card-title { + font: { + size: mat-font-size($config, headline); + weight: mat-font-weight($config, title); + } + } + + .mat-card-header .mat-card-title { + font-size: mat-font-size($config, title); + } + + .mat-card-subtitle, + .mat-card-content { + font-size: mat-font-size($config, body-1); + } +} + + + + + + +@mixin mat-checkbox-theme($theme) { + $is-dark-theme: map-get($theme, is-dark); + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + + + // The color of the checkbox's checkmark / mixedmark. + $checkbox-mark-color: mat-color($background, background); + + // NOTE(traviskaufman): While the spec calls for translucent blacks/whites for disabled colors, + // this does not work well with elements layered on top of one another. To get around this we + // blend the colors together based on the base color and the theme background. + $white-30pct-opacity-on-dark: #686868; + $black-26pct-opacity-on-light: #b0b0b0; + $disabled-color: if($is-dark-theme, $white-30pct-opacity-on-dark, $black-26pct-opacity-on-light); + + .mat-checkbox-frame { + border-color: mat-color($foreground, secondary-text); + } + + .mat-checkbox-checkmark { + fill: $checkbox-mark-color; + } + + .mat-checkbox-checkmark-path { + // !important is needed here because a stroke must be set as an + // attribute on the SVG in order for line animation to work properly. + stroke: $checkbox-mark-color !important; + + @include cdk-high-contrast(black-on-white) { + // Having the one above be !important ends up overriding the browser's automatic + // color inversion so we need to re-invert it ourselves for black-on-white. + stroke: #000 !important; + } + } + + .mat-checkbox-mixedmark { + background-color: $checkbox-mark-color; + } + + .mat-checkbox-indeterminate, .mat-checkbox-checked { + &.mat-primary .mat-checkbox-background { + background-color: mat-color($primary); + } + + &.mat-accent .mat-checkbox-background { + background-color: mat-color($accent); + } + + &.mat-warn .mat-checkbox-background { + background-color: mat-color($warn); + } + } + + .mat-checkbox-disabled { + &.mat-checkbox-checked, + &.mat-checkbox-indeterminate { + .mat-checkbox-background { + background-color: $disabled-color; + } + } + + &:not(.mat-checkbox-checked) { + .mat-checkbox-frame { + border-color: $disabled-color; + } + } + + .mat-checkbox-label { + color: mat-color($foreground, secondary-text); + } + + @include cdk-high-contrast { + opacity: 0.5; + } + } + + // This one is moved down here so it can target both + // the theme colors and the disabled state. + @include cdk-high-contrast { + .mat-checkbox-background { + // Needs to be removed because it hides the checkbox outline. + background: none; + } + } + + // Switch this to a solid color since we're using `opacity` + // to control how opaque the ripple should be. + .mat-checkbox .mat-ripple-element { + background-color: map_get(map-get($theme, foreground), base); + } + + .mat-checkbox-checked:not(.mat-checkbox-disabled), + .mat-checkbox:active:not(.mat-checkbox-disabled) { + &.mat-primary .mat-ripple-element { + background: mat-color($primary); + } + + &.mat-accent .mat-ripple-element { + background: mat-color($accent); + } + + &.mat-warn .mat-ripple-element { + background: mat-color($warn); + } + } +} + +@mixin mat-checkbox-typography($config) { + .mat-checkbox { + font-family: mat-font-family($config); + } + + // TODO(kara): Remove this style when fixing vertical baseline + .mat-checkbox-layout .mat-checkbox-label { + line-height: mat-line-height($config, body-2); + } +} + + + + + + +$mat-chip-remove-font-size: 18px; + +@mixin mat-chips-color($foreground, $background) { + background-color: $background; + color: $foreground; + + .mat-chip-remove { + color: $foreground; + opacity: 0.4; + } +} + +@mixin mat-chips-theme-color($palette) { + @include mat-chips-color(mat-color($palette, default-contrast), mat-color($palette)); + + .mat-ripple-element { + background: mat-color($palette, default-contrast, 0.1); + } +} + +@mixin mat-chips-theme($theme) { + $is-dark-theme: map-get($theme, is-dark); + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + + $unselected-background: mat-color($background, unselected-chip); + $unselected-foreground: mat-color($foreground, text); + + .mat-chip.mat-standard-chip { + @include mat-chips-color($unselected-foreground, $unselected-background); + + &:not(.mat-chip-disabled) { + &:active { + @include _mat-theme-elevation(3, $theme); + } + + .mat-chip-remove:hover { + opacity: 0.54; + } + } + + &.mat-chip-disabled { + opacity: 0.4; + } + + &::after { + background: map_get($foreground, base); + } + } + + .mat-chip.mat-standard-chip.mat-chip-selected { + &.mat-primary { + @include mat-chips-theme-color($primary); + } + + &.mat-warn { + @include mat-chips-theme-color($warn); + } + + &.mat-accent { + @include mat-chips-theme-color($accent); + } + } +} + +@mixin mat-chips-typography($config) { + .mat-chip { + font-size: mat-font-size($config, body-2); + font-weight: mat-font-weight($config, body-2); + + .mat-chip-trailing-icon.mat-icon, + .mat-chip-remove.mat-icon { + font-size: $mat-chip-remove-font-size; + } + } +} + + + + + +@mixin mat-table-theme($theme) { + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + + .mat-table { + background: mat-color($background, 'card'); + } + + .mat-table thead, .mat-table tbody, .mat-table tfoot, + mat-header-row, mat-row, mat-footer-row, + [mat-header-row], [mat-row], [mat-footer-row], + .mat-table-sticky { + background: inherit; + } + + mat-row, mat-header-row, mat-footer-row, + th.mat-header-cell, td.mat-cell, td.mat-footer-cell { + border-bottom-color: mat-color($foreground, divider); + } + + .mat-header-cell { + color: mat-color($foreground, secondary-text); + } + + .mat-cell, .mat-footer-cell { + color: mat-color($foreground, text); + } +} + +@mixin mat-table-typography($config) { + .mat-table { + font-family: mat-font-family($config); + } + + .mat-header-cell { + font-size: mat-font-size($config, caption); + font-weight: mat-font-weight($config, body-2); + } + + .mat-cell, .mat-footer-cell { + font-size: mat-font-size($config, body-1); + } +} + + + + + + + +$mat-datepicker-selected-today-box-shadow-width: 1px; +$mat-datepicker-selected-fade-amount: 0.6; +$mat-datepicker-today-fade-amount: 0.2; +$mat-calendar-body-font-size: 13px !default; +$mat-calendar-weekday-table-font-size: 11px !default; + +@mixin _mat-datepicker-color($palette) { + .mat-calendar-body-selected { + background-color: mat-color($palette); + color: mat-color($palette, default-contrast); + } + + .mat-calendar-body-disabled > .mat-calendar-body-selected { + $background: mat-color($palette); + + @if (type-of($background) == color) { + background-color: fade-out($background, $mat-datepicker-selected-fade-amount); + } + @else { + // If we couldn't resolve to background to a color (e.g. it's a CSS variable), + // fall back to fading the content out via `opacity`. + opacity: $mat-datepicker-today-fade-amount; + } + } + + .mat-calendar-body-today.mat-calendar-body-selected { + box-shadow: inset 0 0 0 $mat-datepicker-selected-today-box-shadow-width + mat-color($palette, default-contrast); + } +} + +@mixin mat-datepicker-theme($theme) { + $foreground: map-get($theme, foreground); + $background: map-get($theme, background); + + .mat-calendar-arrow { + border-top-color: mat-color($foreground, icon); + } + + // The prev/next buttons need a bit more specificity to + // avoid being overwritten by the .mat-icon-button. + .mat-datepicker-toggle, + .mat-datepicker-content .mat-calendar-next-button, + .mat-datepicker-content .mat-calendar-previous-button { + color: mat-color($foreground, icon); + } + + .mat-calendar-table-header { + color: mat-color($foreground, hint-text); + } + + .mat-calendar-table-header-divider::after { + background: mat-color($foreground, divider); + } + + .mat-calendar-body-label { + color: mat-color($foreground, secondary-text); + } + + .mat-calendar-body-cell-content { + color: mat-color($foreground, text); + border-color: transparent; + } + + .mat-calendar-body-disabled > .mat-calendar-body-cell-content:not(.mat-calendar-body-selected) { + color: mat-color($foreground, disabled-text); + } + + .mat-calendar-body-cell:not(.mat-calendar-body-disabled):hover, + .cdk-keyboard-focused .mat-calendar-body-active, + .cdk-program-focused .mat-calendar-body-active { + & > .mat-calendar-body-cell-content:not(.mat-calendar-body-selected) { + background-color: mat-color($background, hover); + } + } + + .mat-calendar-body-today:not(.mat-calendar-body-selected) { + // Note: though it's not text, the border is a hint about the fact that this is today's date, + // so we use the hint color. + border-color: mat-color($foreground, hint-text); + } + + .mat-calendar-body-disabled > .mat-calendar-body-today:not(.mat-calendar-body-selected) { + $color: mat-color($foreground, hint-text); + + @if (type-of($color) == color) { + border-color: fade-out($color, $mat-datepicker-today-fade-amount); + } + @else { + // If the color didn't resolve to a color value, but something like a CSS variable, we can't + // fade it out so we fall back to reducing the element opacity. Note that we don't use the + // $mat-datepicker-today-fade-amount, because hint text usually has some opacity applied + // to it already and we don't want them to stack on top of each other. + opacity: 0.5; + } + } + + @include _mat-datepicker-color(map-get($theme, primary)); + + .mat-datepicker-content { + @include _mat-theme-elevation(4, $theme); + background-color: mat-color($background, card); + color: mat-color($foreground, text); + + &.mat-accent { + @include _mat-datepicker-color(map-get($theme, accent)); + } + + &.mat-warn { + @include _mat-datepicker-color(map-get($theme, warn)); + } + } + + .mat-datepicker-content-touch { + @include _mat-theme-elevation(0, $theme); + } + + .mat-datepicker-toggle-active { + color: mat-color(map-get($theme, primary), text); + + &.mat-accent { + color: mat-color(map-get($theme, accent), text); + } + + &.mat-warn { + color: mat-color(map-get($theme, warn), text); + } + } +} + +@mixin mat-datepicker-typography($config) { + .mat-calendar { + font-family: mat-font-family($config); + } + + .mat-calendar-body { + font-size: $mat-calendar-body-font-size; + } + + .mat-calendar-body-label, + .mat-calendar-period-button { + font: { + size: mat-font-size($config, button); + weight: mat-font-weight($config, button); + } + } + + .mat-calendar-table-header th { + font: { + size: $mat-calendar-weekday-table-font-size; + weight: mat-font-weight($config, body-1); + } + } +} + + + + + + + +@mixin mat-dialog-theme($theme) { + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + + .mat-dialog-container { + @include _mat-theme-elevation(24, $theme); + background: mat-color($background, dialog); + color: mat-color($foreground, text); + } +} + +@mixin mat-dialog-typography($config) { + .mat-dialog-title { + @include mat-typography-level-to-styles($config, title); + } +} + + + + + + +@mixin mat-expansion-panel-theme($theme) { + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + + .mat-expansion-panel { + @include _mat-theme-overridable-elevation(2, $theme); + background: mat-color($background, card); + color: mat-color($foreground, text); + } + + .mat-action-row { + border-top-color: mat-color($foreground, divider); + } + + .mat-expansion-panel { + & .mat-expansion-panel-header.cdk-keyboard-focused, + & .mat-expansion-panel-header.cdk-program-focused, + &:not(.mat-expanded) .mat-expansion-panel-header:hover { + &:not([aria-disabled='true']) { + background: mat-color($background, hover); + } + } + } + + // Disable the hover on touch devices since it can appear like it is stuck. We can't use + // `@media (hover)` above, because the desktop support browser support isn't great. + @media (hover: none) { + .mat-expansion-panel:not(.mat-expanded):not([aria-disabled='true']) + .mat-expansion-panel-header:hover { + background: mat-color($background, card); + } + } + + .mat-expansion-panel-header-title { + color: mat-color($foreground, text); + } + + .mat-expansion-panel-header-description, + .mat-expansion-indicator::after { + color: mat-color($foreground, secondary-text); + } + + .mat-expansion-panel-header[aria-disabled='true'] { + color: mat-color($foreground, disabled-button); + + .mat-expansion-panel-header-title, + .mat-expansion-panel-header-description { + color: inherit; + } + } +} + +@mixin mat-expansion-panel-typography($config) { + .mat-expansion-panel-header { + font: { + family: mat-font-family($config, subheading-1); + size: mat-font-size($config, subheading-1); + weight: mat-font-weight($config, subheading-1); + } + } + + .mat-expansion-panel-content { + @include mat-typography-level-to-styles($config, body-1); + } +} + + + + +// This mixin will ensure that lines that overflow the container will hide the overflow and +// truncate neatly with an ellipsis. +@mixin mat-truncate-line() { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +// Mixin to provide all mat-line styles, changing secondary font size based on whether the list +// is in dense mode. +@mixin mat-line-base($secondary-font-size) { + .mat-line { + @include mat-truncate-line(); + display: block; + box-sizing: border-box; + + // all lines but the top line should have smaller text + &:nth-child(n+2) { + font-size: $secondary-font-size; + } + } +} + +// This mixin normalizes default element styles, e.g. font weight for heading text. +@mixin mat-normalize-text() { + & > * { + margin: 0; + padding: 0; + font-weight: normal; + font-size: inherit; + } +} + +// This mixin provides base styles for the wrapper around mat-line elements in a list. +@mixin mat-line-wrapper-base() { + @include mat-normalize-text(); + + display: flex; + flex-direction: column; + width: 100%; + box-sizing: border-box; + overflow: hidden; + + // Must remove wrapper when lines are empty or it takes up horizontal + // space and pushes other elements to the right. + &:empty { + display: none; + } +} + + + +// Include this empty mixin for consistency with the other components. +@mixin mat-grid-list-theme($theme) { } + +@mixin mat-grid-list-typography($config) { + .mat-grid-tile-header, + .mat-grid-tile-footer { + @include mat-line-base(mat-font-size($config, caption)); + font-size: mat-font-size($config, body-1); + } +} + + + + +// Include this empty mixin for consistency with the other components. +@mixin mat-icon-theme($theme) { + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + + .mat-icon { + &.mat-primary { + color: mat-color($primary, text); + } + + &.mat-accent { + color: mat-color($accent, text); + } + + &.mat-warn { + color: mat-color($warn, text); + } + } +} + +@mixin mat-icon-typography($config) { } + + + + + +// Renders a gradient for showing the dashed line when the input is disabled. +// Unlike using a border, a gradient allows us to adjust the spacing of the dotted line +// to match the Material Design spec. +@mixin mat-control-disabled-underline($color) { + background-image: linear-gradient(to right, $color 0%, $color 33%, transparent 0%); + background-size: 4px 100%; + background-repeat: repeat-x; +} + +// Figures out the color of the placeholder for a form control. +// Used primarily to prevent the various form controls from +// becoming out of sync since these colors aren't in a palette. +@function _mat-control-placeholder-color($theme) { + $foreground: map-get($theme, foreground); + $is-dark-theme: map-get($theme, is-dark); + @return mat-color($foreground, secondary-text, if($is-dark-theme, 0.5, 0.42)); +} + + +/* stylelint-disable material/no-prefixes */ +@mixin user-select($value) { + -webkit-user-select: $value; + -moz-user-select: $value; + -ms-user-select: $value; + user-select: $value; +} + +@mixin input-placeholder { + &::placeholder { + @content; + } + + &::-moz-placeholder { + @content; + } + + &::-webkit-input-placeholder { + @content; + } + + &:-ms-input-placeholder { + @content; + } +} + +@mixin cursor-grab { + cursor: -webkit-grab; + cursor: grab; +} + +@mixin cursor-grabbing { + cursor: -webkit-grabbing; + cursor: grabbing; +} + +@mixin backface-visibility($value) { + -webkit-backface-visibility: $value; + backface-visibility: $value; +} +/* stylelint-enable */ + + + +@mixin mat-input-theme($theme) { + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + $foreground: map-get($theme, foreground); + + .mat-form-field-type-mat-native-select .mat-form-field-infix::after { + color: mat-color($foreground, secondary-text); + } + + .mat-input-element:disabled, + .mat-form-field-type-mat-native-select.mat-form-field-disabled .mat-form-field-infix::after { + color: mat-color($foreground, disabled-text); + } + + .mat-input-element { + caret-color: mat-color($primary, text); + + @include input-placeholder { + color: _mat-control-placeholder-color($theme); + } + + // On dark themes we set the native `select` color to some shade of white, + // however the color propagates to all of the `option` elements, which are + // always on a white background inside the dropdown, causing them to blend in. + // Since we can't change background of the dropdown, we need to explicitly + // reset the color of the options to something dark. + @if (map-get($theme, is-dark)) { + option { + color: $dark-primary-text; + } + + option:disabled { + color: $dark-disabled-text; + } + } + } + + .mat-accent .mat-input-element { + caret-color: mat-color($accent, text); + } + + .mat-warn .mat-input-element, + .mat-form-field-invalid .mat-input-element { + caret-color: mat-color($warn, text); + } + + .mat-form-field-type-mat-native-select.mat-form-field-invalid .mat-form-field-infix::after { + color: mat-color($warn, text); + } +} + +@mixin mat-input-typography($config) { + // The unit-less line-height from the font config. + $line-height: mat-line-height($config, input); + + // The amount of space between the top of the line and the top of the actual text + // (as a fraction of the font-size). + $line-spacing: ($line-height - 1) / 2; + + // elements seem to have their height set slightly too large on Safari causing the text to + // be misaligned w.r.t. the placeholder. Adding this margin corrects it. + input.mat-input-element { + margin-top: -$line-spacing * 1em; + } +} + + + + + + + +@mixin mat-list-theme($theme) { + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + + .mat-list-base { + .mat-list-item { + color: mat-color($foreground, text); + } + + .mat-list-option { + color: mat-color($foreground, text); + } + + .mat-subheader { + color: mat-color($foreground, secondary-text); + } + } + + .mat-list-item-disabled { + background-color: mat-color($background, disabled-list-option); + } + + .mat-list-option, + .mat-nav-list .mat-list-item, + .mat-action-list .mat-list-item { + &:hover, &:focus { + background: mat-color($background, 'hover'); + } + } +} + +@mixin mat-list-typography($config) { + $font-family: mat-font-family($config); + + .mat-list-item { + font-family: $font-family; + } + + .mat-list-option { + font-family: $font-family; + } + + // Default list + .mat-list-base { + .mat-list-item { + font-size: mat-font-size($config, subheading-2); + @include mat-line-base(mat-font-size($config, body-1)); + } + + .mat-list-option { + font-size: mat-font-size($config, subheading-2); + @include mat-line-base(mat-font-size($config, body-1)); + } + + .mat-subheader { + font-family: mat-font-family($config, body-2); + font-size: mat-font-size($config, body-2); + font-weight: mat-font-weight($config, body-2); + } + } + + // Dense list + .mat-list-base[dense] { + .mat-list-item { + font-size: mat-font-size($config, caption); + @include mat-line-base(mat-font-size($config, caption)); + } + + .mat-list-option { + font-size: mat-font-size($config, caption); + @include mat-line-base(mat-font-size($config, caption)); + } + + .mat-subheader { + font-family: $font-family; + font-size: mat-font-size($config, caption); + font-weight: mat-font-weight($config, body-2); + } + } +} + + + + + + + +@mixin mat-menu-theme($theme) { + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + + .mat-menu-panel { + @include _mat-theme-overridable-elevation(4, $theme); + background: mat-color($background, 'card'); + } + + .mat-menu-item { + background: transparent; + color: mat-color($foreground, 'text'); + + &[disabled] { + &, &::after { + color: mat-color($foreground, 'disabled'); + } + } + } + + .mat-menu-item .mat-icon-no-color, + .mat-menu-item-submenu-trigger::after { + color: mat-color($foreground, 'icon'); + } + + .mat-menu-item:hover, + .mat-menu-item.cdk-program-focused, + .mat-menu-item.cdk-keyboard-focused, + .mat-menu-item-highlighted { + &:not([disabled]) { + background: mat-color($background, 'hover'); + } + } +} + +@mixin mat-menu-typography($config) { + .mat-menu-item { + font: { + family: mat-font-family($config, body-1); + size: mat-font-size($config, body-1); + weight: mat-font-weight($config, body-1); + } + } +} + + + + + + +@mixin mat-paginator-theme($theme) { + $foreground: map-get($theme, foreground); + $background: map-get($theme, background); + + .mat-paginator { + background: mat-color($background, 'card'); + } + + .mat-paginator, + .mat-paginator-page-size .mat-select-trigger { + color: mat-color($foreground, secondary-text); + } + + .mat-paginator-decrement, + .mat-paginator-increment { + border-top: 2px solid mat-color($foreground, 'icon'); + border-right: 2px solid mat-color($foreground, 'icon'); + } + + .mat-paginator-first, + .mat-paginator-last { + border-top: 2px solid mat-color($foreground, 'icon'); + } + + .mat-icon-button[disabled] { + .mat-paginator-decrement, + .mat-paginator-increment, + .mat-paginator-first, + .mat-paginator-last { + border-color: mat-color($foreground, 'disabled'); + } + } +} + +@mixin mat-paginator-typography($config) { + .mat-paginator, + .mat-paginator-page-size .mat-select-trigger { + font: { + family: mat-font-family($config, caption); + size: mat-font-size($config, caption); + } + } +} + + + + + +@mixin mat-progress-bar-theme($theme) { + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + + .mat-progress-bar-background { + fill: mat-color($primary, lighter); + } + + .mat-progress-bar-buffer { + background-color: mat-color($primary, lighter); + } + + .mat-progress-bar-fill::after { + background-color: mat-color($primary); + } + + .mat-progress-bar.mat-accent { + .mat-progress-bar-background { + fill: mat-color($accent, lighter); + } + + .mat-progress-bar-buffer { + background-color: mat-color($accent, lighter); + } + + .mat-progress-bar-fill::after { + background-color: mat-color($accent); + } + } + + .mat-progress-bar.mat-warn { + .mat-progress-bar-background { + fill: mat-color($warn, lighter); + } + + .mat-progress-bar-buffer { + background-color: mat-color($warn, lighter); + } + + .mat-progress-bar-fill::after { + background-color: mat-color($warn); + } + } +} + +@mixin mat-progress-bar-typography($config) { } + + + + + + +@mixin mat-progress-spinner-theme($theme) { + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + + .mat-progress-spinner, .mat-spinner { + circle { + stroke: mat-color($primary); + } + + &.mat-accent circle { + stroke: mat-color($accent); + } + + &.mat-warn circle { + stroke: mat-color($warn); + } + } +} + +@mixin mat-progress-spinner-typography($config) { } + + + + + +@mixin _mat-radio-color($palette) { + &.mat-radio-checked .mat-radio-outer-circle { + border-color: mat-color($palette); + } + + .mat-radio-inner-circle, + .mat-radio-ripple .mat-ripple-element:not(.mat-radio-persistent-ripple), + &.mat-radio-checked .mat-radio-persistent-ripple, + &:active .mat-radio-persistent-ripple { + background-color: mat-color($palette); + } +} + +@mixin mat-radio-theme($theme) { + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + + .mat-radio-outer-circle { + border-color: mat-color($foreground, secondary-text); + } + + .mat-radio-button { + &.mat-primary { + @include _mat-radio-color($primary); + } + + &.mat-accent { + @include _mat-radio-color($accent); + } + + &.mat-warn { + @include _mat-radio-color($warn); + } + + // This needs extra specificity, because the classes above are combined + // (e.g. `.mat-radio-button.mat-accent`) which increases their specificity a lot. + // TODO: consider making the selectors into descendants (`.mat-primary .mat-radio-button`). + &.mat-radio-disabled { + &.mat-radio-checked .mat-radio-outer-circle, + .mat-radio-outer-circle { + border-color: mat-color($foreground, disabled); + } + + .mat-radio-ripple .mat-ripple-element, + .mat-radio-inner-circle { + background-color: mat-color($foreground, disabled); + } + + .mat-radio-label-content { + color: mat-color($foreground, disabled); + } + } + + // Switch this to a solid color since we're using `opacity` + // to control how opaque the ripple should be. + .mat-ripple-element { + background-color: map_get($foreground, base); + } + } +} + +@mixin mat-radio-typography($config) { + .mat-radio-button { + font-family: mat-font-family($config); + } +} + + + + + + + + +@mixin mat-select-theme($theme) { + $foreground: map-get($theme, foreground); + $background: map-get($theme, background); + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + + .mat-select-value { + color: mat-color($foreground, text); + } + + .mat-select-placeholder { + color: _mat-control-placeholder-color($theme); + } + + .mat-select-disabled .mat-select-value { + color: mat-color($foreground, disabled-text); + } + + .mat-select-arrow { + color: mat-color($foreground, secondary-text); + } + + .mat-select-panel { + background: mat-color($background, card); + @include _mat-theme-overridable-elevation(4, $theme); + + .mat-option.mat-selected:not(.mat-option-multiple) { + background: mat-color($background, hover, 0.12); + } + } + + .mat-form-field { + &.mat-focused { + &.mat-primary .mat-select-arrow { + color: mat-color($primary, text); + } + + &.mat-accent .mat-select-arrow { + color: mat-color($accent, text); + } + + &.mat-warn .mat-select-arrow { + color: mat-color($warn, text); + } + } + + .mat-select.mat-select-invalid .mat-select-arrow { + color: mat-color($warn, text); + } + + .mat-select.mat-select-disabled .mat-select-arrow { + color: mat-color($foreground, disabled-text); + } + } +} + +@mixin mat-select-typography($config) { + // The unit-less line-height from the font config. + $line-height: mat-line-height($config, input); + + .mat-select { + font-family: mat-font-family($config); + } + + .mat-select-trigger { + height: $line-height * 1em; + } +} + + + + + + +@mixin mat-sidenav-theme($theme) { + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + + $drawer-background-color: mat-color($background, dialog); + $drawer-container-background-color: mat-color($background, background); + $drawer-push-background-color: mat-color($background, dialog); + $drawer-side-border: solid 1px mat-color($foreground, divider); + + .mat-drawer-container { + background-color: $drawer-container-background-color; + color: mat-color($foreground, text); + } + + .mat-drawer { + background-color: $drawer-background-color; + color: mat-color($foreground, text); + + &.mat-drawer-push { + background-color: $drawer-push-background-color; + } + + &:not(.mat-drawer-side) { + // The elevation of z-16 is noted in the design specifications. + // See https://material.io/design/components/navigation-drawer.html + @include _mat-theme-elevation(16, $theme); + } + } + + .mat-drawer-side { + border-right: $drawer-side-border; + + &.mat-drawer-end { + border-left: $drawer-side-border; + border-right: none; + } + } + + [dir='rtl'] .mat-drawer-side { + border-left: $drawer-side-border; + border-right: none; + + &.mat-drawer-end { + border-left: none; + border-right: $drawer-side-border; + } + } + + .mat-drawer-backdrop.mat-drawer-shown { + $opacity: 0.6; + $backdrop-color: mat-color($background, card, $opacity); + + @if (type-of($backdrop-color) == color) { + // We use invert() here to have the darken the background color expected to be used. If the + // background is light, we use a dark backdrop. If the background is dark, + // we use a light backdrop. + background-color: invert($backdrop-color); + } + @else { + // If we couldn't resolve the backdrop color to a color value, fall back to using + // `opacity` to make it opaque since its end value could be a solid color. + background-color: $backdrop-color; + opacity: $opacity; + } + } +} + +@mixin mat-sidenav-typography($config) { } + + + + + + +@mixin _mat-slide-toggle-checked($palette, $thumb-checked-hue) { + &.mat-checked { + .mat-slide-toggle-thumb { + background-color: mat-color($palette, $thumb-checked-hue); + } + + .mat-slide-toggle-bar { + // Opacity is determined from the specs for the selection controls. + // See: https://material.io/design/components/selection-controls.html#specs + background-color: mat-color($palette, $thumb-checked-hue, 0.54); + } + + .mat-ripple-element { + // Set no opacity for the ripples because the ripple opacity will be adjusted dynamically + // based on the type of interaction with the slide-toggle (e.g. for hover, focus) + background-color: mat-color($palette, $thumb-checked-hue); + } + } +} + +@mixin mat-slide-toggle-theme($theme) { + $is-dark: map_get($theme, is-dark); + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + + // Color hues are based on the specs which briefly show the hues that are applied to a switch. + // The 2018 specs no longer describe how dark switches should look like. Due to the lack of + // information for dark themed switches, we partially keep the old behavior that is based on + // the previous specifications. For the checked color we always use the `default` hue because + // that follows MDC and also makes it easier for people to create a custom theme without needing + // to specify each hue individually. + $thumb-unchecked-hue: if($is-dark, 400, 50); + $thumb-checked-hue: default; + + $bar-unchecked-color: mat-color($foreground, disabled); + $ripple-unchecked-color: mat-color($foreground, base); + + .mat-slide-toggle { + @include _mat-slide-toggle-checked($accent, $thumb-checked-hue); + + &.mat-primary { + @include _mat-slide-toggle-checked($primary, $thumb-checked-hue); + } + + &.mat-warn { + @include _mat-slide-toggle-checked($warn, $thumb-checked-hue); + } + + &:not(.mat-checked) .mat-ripple-element { + // Set no opacity for the ripples because the ripple opacity will be adjusted dynamically + // based on the type of interaction with the slide-toggle (e.g. for hover, focus) + background-color: $ripple-unchecked-color; + } + } + + .mat-slide-toggle-thumb { + @include _mat-theme-elevation(1, $theme); + background-color: mat-color($mat-grey, $thumb-unchecked-hue); + } + + .mat-slide-toggle-bar { + background-color: $bar-unchecked-color; + } +} + +@mixin mat-slide-toggle-typography($config) { + .mat-slide-toggle-content { + font-family: mat-font-family($config); + } +} + + + + + +@mixin _mat-slider-inner-content-theme($palette) { + .mat-slider-track-fill, + .mat-slider-thumb, + .mat-slider-thumb-label { + background-color: mat-color($palette); + } + + .mat-slider-thumb-label-text { + color: mat-color($palette, default-contrast); + } +} + +@mixin mat-slider-theme($theme) { + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + + $mat-slider-off-color: mat-color($foreground, slider-off); + $mat-slider-off-focused-color: mat-color($foreground, slider-off-active); + $mat-slider-disabled-color: mat-color($foreground, slider-off); + $mat-slider-labeled-min-value-thumb-color: mat-color($foreground, slider-min); + $mat-slider-labeled-min-value-thumb-label-color: mat-color($foreground, slider-off); + $mat-slider-focus-ring-color: mat-color($accent, default, 0.2); + $mat-slider-focus-ring-min-value-color: mat-color($foreground, base, 0.12); + $mat-slider-tick-color: mat-color($foreground, base, 0.7); + $mat-slider-tick-size: 2px; + + .mat-slider-track-background { + background-color: $mat-slider-off-color; + } + + .mat-primary { + @include _mat-slider-inner-content-theme($primary); + } + + .mat-accent { + @include _mat-slider-inner-content-theme($accent); + } + + .mat-warn { + @include _mat-slider-inner-content-theme($warn); + } + + .mat-slider-focus-ring { + background-color: $mat-slider-focus-ring-color; + } + + .mat-slider:hover, + .cdk-focused { + .mat-slider-track-background { + background-color: $mat-slider-off-focused-color; + } + } + + .mat-slider-disabled { + .mat-slider-track-background, + .mat-slider-track-fill, + .mat-slider-thumb { + background-color: $mat-slider-disabled-color; + } + + &:hover { + .mat-slider-track-background { + background-color: $mat-slider-disabled-color; + } + } + } + + .mat-slider-min-value { + .mat-slider-focus-ring { + background-color: $mat-slider-focus-ring-min-value-color; + } + + &.mat-slider-thumb-label-showing { + .mat-slider-thumb, + .mat-slider-thumb-label { + background-color: $mat-slider-labeled-min-value-thumb-color; + } + + &.cdk-focused { + .mat-slider-thumb, + .mat-slider-thumb-label { + background-color: $mat-slider-labeled-min-value-thumb-label-color; + } + } + } + + &:not(.mat-slider-thumb-label-showing) { + .mat-slider-thumb { + border-color: $mat-slider-off-color; + background-color: transparent; + } + + &:hover, + &.cdk-focused { + .mat-slider-thumb { + border-color: $mat-slider-off-focused-color; + } + + &.mat-slider-disabled .mat-slider-thumb { + border-color: $mat-slider-disabled-color; + } + } + } + } + + .mat-slider-has-ticks .mat-slider-wrapper::after { + border-color: $mat-slider-tick-color; + } + + .mat-slider-horizontal .mat-slider-ticks { + background-image: repeating-linear-gradient(to right, $mat-slider-tick-color, + $mat-slider-tick-color $mat-slider-tick-size, transparent 0, transparent); + // Firefox doesn't draw the gradient correctly with 'to right' + // (see https://bugzilla.mozilla.org/show_bug.cgi?id=1314319). + background-image: -moz-repeating-linear-gradient(0.0001deg, $mat-slider-tick-color, + $mat-slider-tick-color $mat-slider-tick-size, transparent 0, transparent); + } + + .mat-slider-vertical .mat-slider-ticks { + background-image: repeating-linear-gradient(to bottom, $mat-slider-tick-color, + $mat-slider-tick-color $mat-slider-tick-size, transparent 0, transparent); + } +} + +@mixin mat-slider-typography($config) { + .mat-slider-thumb-label-text { + font: { + family: mat-font-family($config); + size: mat-font-size($config, caption); + weight: mat-font-weight($config, body-2); + } + } +} + + + + + +@mixin mat-stepper-theme($theme) { + $foreground: map-get($theme, foreground); + $background: map-get($theme, background); + $primary: map-get($theme, primary); + $warn: map-get($theme, warn); + + .mat-step-header { + &.cdk-keyboard-focused, + &.cdk-program-focused, + &:hover { + background-color: mat-color($background, hover); + } + + // On touch devices the :hover state will linger on the element after a tap. + // Reset it via `@media` after the declaration, because the media query isn't + // supported by all browsers yet. + @media (hover: none) { + &:hover { + background: none; + } + } + + .mat-step-label, + .mat-step-optional { + // TODO(josephperrott): Update to using a corrected disabled-text contrast + // instead of secondary-text. + color: mat-color($foreground, secondary-text); + } + + .mat-step-icon { + // TODO(josephperrott): Update to using a corrected disabled-text contrast + // instead of secondary-text. + background-color: mat-color($foreground, secondary-text); + color: mat-color($primary, default-contrast); + } + + .mat-step-icon-selected, + .mat-step-icon-state-done, + .mat-step-icon-state-edit { + background-color: mat-color($primary); + color: mat-color($primary, default-contrast); + } + + .mat-step-icon-state-error { + background-color: transparent; + color: mat-color($warn, text); + } + + .mat-step-label.mat-step-label-active { + color: mat-color($foreground, text); + } + + .mat-step-label.mat-step-label-error { + color: mat-color($warn, text); + } + } + + .mat-stepper-horizontal, .mat-stepper-vertical { + background-color: mat-color($background, card); + } + + .mat-stepper-vertical-line::before { + border-left-color: mat-color($foreground, divider); + } + + .mat-horizontal-stepper-header::before, + .mat-horizontal-stepper-header::after, + .mat-stepper-horizontal-line { + border-top-color: mat-color($foreground, divider); + } +} + +@mixin mat-stepper-typography($config) { + .mat-stepper-vertical, .mat-stepper-horizontal { + font-family: mat-font-family($config); + } + + .mat-step-label { + font: { + size: mat-font-size($config, body-1); + weight: mat-font-weight($config, body-1); + }; + } + + .mat-step-sub-label-error { + font-weight: normal; + } + + .mat-step-label-error { + font-size: mat-font-size($config, body-2); + } + + .mat-step-label-selected { + font: { + size: mat-font-size($config, body-2); + weight: mat-font-weight($config, body-2); + }; + } +} + +@mixin mat-sort-theme($theme) { + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + + .mat-sort-header-arrow { + $table-background: mat-color($background, 'card'); + $text-color: mat-color($foreground, secondary-text); + + // Because the arrow is made up of multiple elements that are stacked on top of each other, + // we can't use the semi-trasparent color from the theme directly. If the value is a color + // *type*, we convert it into a solid color by taking the opacity from the rgba value and + // using the value to determine the percentage of the background to put into foreground + // when mixing the colors together. Otherwise, if it resolves to something different + // (e.g. it resolves to a CSS variable), we use the color directly. + @if (type-of($table-background) == color and type-of($text-color) == color) { + $text-opacity: opacity($text-color); + color: mix($table-background, rgba($text-color, 1), (1 - $text-opacity) * 100%); + } + @else { + color: $text-color; + } + } +} + +@mixin mat-sort-typography($config) { } + + + + + +@mixin mat-tabs-theme($theme) { + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + $header-border: 1px solid mat-color($foreground, divider); + + .mat-tab-nav-bar, + .mat-tab-header { + border-bottom: $header-border; + } + + .mat-tab-group-inverted-header { + .mat-tab-nav-bar, + .mat-tab-header { + border-top: $header-border; + border-bottom: none; + } + } + + .mat-tab-label, .mat-tab-link { + color: mat-color($foreground, text); + + &.mat-tab-disabled { + color: mat-color($foreground, disabled-text); + } + } + + .mat-tab-header-pagination-chevron { + border-color: mat-color($foreground, text); + } + + .mat-tab-header-pagination-disabled .mat-tab-header-pagination-chevron { + border-color: mat-color($foreground, disabled-text); + } + + // Remove header border when there is a background color + .mat-tab-group[class*='mat-background-'] .mat-tab-header, + .mat-tab-nav-bar[class*='mat-background-'] { + border-bottom: none; + border-top: none; + } + + .mat-tab-group, .mat-tab-nav-bar { + $theme-colors: ( + primary: $primary, + accent: $accent, + warn: $warn + ); + + @each $name, $color in $theme-colors { + // Set the foreground color of the tabs + &.mat-#{$name} { + @include _mat-tab-label-focus($color); + @include _mat-ink-bar($color); + + // Override ink bar when background color is the same + &.mat-background-#{$name} { + @include _mat-ink-bar($color, default-contrast); + } + } + } + + @each $name, $color in $theme-colors { + // Set background color of the tabs and override focus color + &.mat-background-#{$name} { + @include _mat-tab-label-focus($color); + @include _mat-tabs-background($color); + } + } + } +} + +@mixin _mat-ink-bar($color, $hue: default) { + .mat-ink-bar { + background-color: mat-color($color, $hue); + } +} + +@mixin _mat-tab-label-focus($tab-focus-color) { + .mat-tab-label, + .mat-tab-link { + &.cdk-keyboard-focused, + &.cdk-program-focused { + &:not(.mat-tab-disabled) { + background-color: mat-color($tab-focus-color, lighter, 0.3); + } + } + } +} + +@mixin _mat-tabs-background($background-color) { + // Set background color for the tab group + .mat-tab-header, .mat-tab-links, .mat-tab-header-pagination { + background-color: mat-color($background-color); + } + + // Set labels to contrast against background + .mat-tab-label, .mat-tab-link { + color: mat-color($background-color, default-contrast); + + &.mat-tab-disabled { + color: mat-color($background-color, default-contrast, 0.4); + } + } + + // Set pagination chevrons to contrast background + .mat-tab-header-pagination-chevron { + border-color: mat-color($background-color, default-contrast); + } + + .mat-tab-header-pagination-disabled .mat-tab-header-pagination-chevron { + border-color: mat-color($background-color, default-contrast, 0.4); + } + + // Set ripples color to be the contrast color of the new background. Otherwise the ripple + // color will be based on the app background color. + .mat-ripple-element { + background-color: mat-color($background-color, default-contrast, 0.12); + } +} + +@mixin mat-tabs-typography($config) { + .mat-tab-group { + font-family: mat-font-family($config); + } + + .mat-tab-label, .mat-tab-link { + font: { + family: mat-font-family($config, button); + size: mat-font-size($config, button); + weight: mat-font-weight($config, button); + } + } +} + + + + + + +@mixin _mat-toolbar-color($palette) { + background: mat-color($palette); + color: mat-color($palette, default-contrast); +} + +@mixin _mat-toolbar-form-field-overrides { + .mat-form-field-underline, + .mat-form-field-ripple, + .mat-focused .mat-form-field-ripple { + background-color: currentColor; + } + + .mat-form-field-label, + .mat-focused .mat-form-field-label, + .mat-select-value, + .mat-select-arrow, + .mat-form-field.mat-focused .mat-select-arrow { + color: inherit; + } + + .mat-input-element { + caret-color: currentColor; + } +} + +@mixin mat-toolbar-theme($theme) { + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + + .mat-toolbar { + background: mat-color($background, app-bar); + color: mat-color($foreground, text); + + &.mat-primary { + @include _mat-toolbar-color($primary); + } + + &.mat-accent { + @include _mat-toolbar-color($accent); + } + + &.mat-warn { + @include _mat-toolbar-color($warn); + } + + @include _mat-toolbar-form-field-overrides; + } +} + +@mixin mat-toolbar-typography($config) { + .mat-toolbar, + .mat-toolbar h1, + .mat-toolbar h2, + .mat-toolbar h3, + .mat-toolbar h4, + .mat-toolbar h5, + .mat-toolbar h6 { + @include mat-typography-level-to-styles($config, title); + margin: 0; + } +} + + + + + +$mat-tooltip-target-height: 22px; +$mat-tooltip-font-size: 10px; +$mat-tooltip-vertical-padding: ($mat-tooltip-target-height - $mat-tooltip-font-size) / 2; + +$mat-tooltip-handset-target-height: 30px; +$mat-tooltip-handset-font-size: 14px; +$mat-tooltip-handset-vertical-padding: + ($mat-tooltip-handset-target-height - $mat-tooltip-handset-font-size) / 2; + +@mixin mat-tooltip-theme($theme) { + .mat-tooltip { + background: mat-color($mat-grey, 700, 0.9); + } +} + +@mixin mat-tooltip-typography($config) { + .mat-tooltip { + font-family: mat-font-family($config); + font-size: $mat-tooltip-font-size; + padding-top: $mat-tooltip-vertical-padding; + padding-bottom: $mat-tooltip-vertical-padding; + } + + .mat-tooltip-handset { + font-size: $mat-tooltip-handset-font-size; + padding-top: $mat-tooltip-handset-vertical-padding; + padding-bottom: $mat-tooltip-handset-vertical-padding; + } +} + + + + + +@mixin mat-snack-bar-theme($theme) { + $is-dark-theme: map-get($theme, is-dark); + $accent: map-get($theme, accent); + + .mat-snack-bar-container { + // Use the primary text on the dark theme, even though the lighter one uses + // a secondary, because the contrast on the light primary text is poor. + color: if($is-dark-theme, $dark-primary-text, $light-secondary-text); + background: if($is-dark-theme, map-get($mat-grey, 50), #323232); + + @include _mat-theme-elevation(6, $theme); + } + + .mat-simple-snackbar-action { + color: if($is-dark-theme, inherit, mat-color($accent, text)); + } +} + +@mixin mat-snack-bar-typography($config) { + .mat-simple-snackbar { + font: { + family: mat-font-family($config, body-1); + size: mat-font-size($config, body-1); + } + } + + .mat-simple-snackbar-action { + line-height: 1; + font: { + family: inherit; + size: inherit; + weight: mat-font-weight($config, button); + } + } +} + + + + + + + + + + + + + + +// Theme styles that only apply to the fill appearance of the form-field. + +@mixin mat-form-field-fill-theme($theme) { + $foreground: map-get($theme, foreground); + $is-dark-theme: map-get($theme, is-dark); + + $fill-background: mat-color($foreground, base, if($is-dark-theme, 0.1, 0.04)); + $fill-disabled-background: mat-color($foreground, base, if($is-dark-theme, 0.05, 0.02)); + $underline-color: mat-color($foreground, divider, if($is-dark-theme, 0.5, 0.42)); + $label-disabled-color: mat-color($foreground, disabled-text); + + .mat-form-field-appearance-fill { + .mat-form-field-flex { + background-color: $fill-background; + } + + &.mat-form-field-disabled .mat-form-field-flex { + background-color: $fill-disabled-background; + } + + .mat-form-field-underline::before { + background-color: $underline-color; + } + + &.mat-form-field-disabled { + .mat-form-field-label { + color: $label-disabled-color; + } + + .mat-form-field-underline::before { + background-color: transparent; + } + } + } +} + +// Used to make instances of the _mat-form-field-label-floating mixin negligibly different, +// and prevent Google's CSS Optimizer from collapsing the declarations. This is needed because some +// of the selectors contain pseudo-classes not recognized in all browsers. If a browser encounters +// an unknown pseudo-class it will discard the entire rule set. +$mat-form-field-fill-dedupe: 0; + +// Applies a floating label above the form field control itself. +@mixin _mat-form-field-fill-label-floating($font-scale, $infix-padding, $infix-margin-top) { + transform: translateY(-$infix-margin-top - $infix-padding + $mat-form-field-fill-dedupe) + scale($font-scale); + width: 100% / $font-scale + $mat-form-field-fill-dedupe; + + $mat-form-field-fill-dedupe: $mat-form-field-fill-dedupe + 0.00001 !global; +} + +@mixin mat-form-field-fill-typography($config) { + // The unit-less line-height from the font config. + $line-height: mat-line-height($config, input); + // The amount to scale the font for the floating label and subscript. + $subscript-font-scale: 0.75; + // The padding on top of the infix. + $infix-padding-top: 0.25em; + // The padding below the infix. + $infix-padding-bottom: 0.75em; + // The margin applied to the form-field-infix to reserve space for the floating label. + $infix-margin-top: 1em * $line-height * $subscript-font-scale; + // The amount we offset the label from the input text in the fill appearance. + $fill-appearance-label-offset: -0.5em; + + .mat-form-field-appearance-fill { + .mat-form-field-infix { + padding: $infix-padding-top 0 $infix-padding-bottom 0; + } + + .mat-form-field-label { + top: $infix-margin-top + $infix-padding-top; + margin-top: $fill-appearance-label-offset; + } + + &.mat-form-field-can-float { + &.mat-form-field-should-float .mat-form-field-label, + .mat-input-server:focus + .mat-form-field-label-wrapper .mat-form-field-label { + @include _mat-form-field-fill-label-floating( + $subscript-font-scale, $infix-padding-top + $fill-appearance-label-offset, + $infix-margin-top); + } + + // Server-side rendered matInput with a label attribute but label not shown + // (used as a pure CSS stand-in for mat-form-field-should-float). + .mat-input-server[label]:not(:label-shown) + .mat-form-field-label-wrapper + .mat-form-field-label { + @include _mat-form-field-fill-label-floating( + $subscript-font-scale, $infix-padding-top + $fill-appearance-label-offset, + $infix-margin-top); + } + } + } +} + + + + + + + +// Theme styles that only apply to the legacy appearance of the form-field. + +@mixin mat-form-field-legacy-theme($theme) { + $foreground: map-get($theme, foreground); + $is-dark-theme: map-get($theme, is-dark); + + $label-color: mat-color($foreground, secondary-text); + $underline-color: mat-color($foreground, divider, if($is-dark-theme, 0.7, 0.42)); + + .mat-form-field-appearance-legacy { + .mat-form-field-label { + color: $label-color; + } + + .mat-hint { + color: $label-color; + } + + .mat-form-field-underline { + background-color: $underline-color; + } + + &.mat-form-field-disabled .mat-form-field-underline { + @include mat-control-disabled-underline($underline-color); + } + } +} + +// Used to make instances of the _mat-form-field-label-floating mixin negligibly different, +// and prevent Google's CSS Optimizer from collapsing the declarations. This is needed because some +// of the selectors contain pseudo-classes not recognized in all browsers. If a browser encounters +// an unknown pseudo-class it will discard the entire rule set. +$mat-form-field-legacy-dedupe: 0; + +// Applies a floating label above the form field control itself. +@mixin _mat-form-field-legacy-label-floating($font-scale, $infix-padding, $infix-margin-top) { + // We use perspective to fix the text blurriness as described here: + // http://www.useragentman.com/blog/2014/05/04/fixing-typography-inside-of-2-d-css-transforms/ + // This results in a small jitter after the label floats on Firefox, which the + // translateZ fixes. + transform: translateY(-$infix-margin-top - $infix-padding) scale($font-scale) perspective(100px) + translateZ(0.001px + $mat-form-field-legacy-dedupe); + // The tricks above used to smooth out the animation on chrome and firefox actually make things + // worse on IE, so we don't include them in the IE version. + -ms-transform: translateY(-$infix-margin-top - $infix-padding + $mat-form-field-legacy-dedupe) + scale($font-scale); + + width: 100% / $font-scale + $mat-form-field-legacy-dedupe; + + $mat-form-field-legacy-dedupe: $mat-form-field-legacy-dedupe + 0.00001 !global; +} + +// Same as mixin above, but omits the translateZ for printing purposes. +@mixin _mat-form-field-legacy-label-floating-print($font-scale, $infix-padding, $infix-margin-top) { + // This results in a small jitter after the label floats on Firefox, which the + // translateZ fixes. + transform: translateY(-$infix-margin-top - $infix-padding + $mat-form-field-legacy-dedupe) + scale($font-scale); + // The tricks above used to smooth out the animation on chrome and firefox actually make things + // worse on IE, so we don't include them in the IE version. + $mat-form-field-legacy-dedupe: $mat-form-field-legacy-dedupe + 0.00001 !global; +} + +@mixin mat-form-field-legacy-typography($config) { + // The unit-less line-height from the font config. + $line-height: mat-line-height($config, input); + // The amount to scale the font for the floating label and subscript. + $subscript-font-scale: 0.75; + // The amount of space between the top of the line and the top of the actual text + // (as a fraction of the font-size). + $line-spacing: ($line-height - 1) / 2; + // The padding on the infix. Mocks show half of the text size, but seem to measure from the edge + // of the text itself, not the edge of the line; therefore we subtract off the line spacing. + $infix-padding: 0.5em - $line-spacing; + // The margin applied to the form-field-infix to reserve space for the floating label. + $infix-margin-top: 1em * $line-height * $subscript-font-scale; + // The space between the bottom of the .mat-form-field-flex area and the subscript wrapper. + // Mocks show half of the text size, but this margin is applied to an element with the subscript + // text font size, so we need to divide by the scale factor to make it half of the original text + // size. We again need to subtract off the line spacing since the mocks measure to the edge of the + // text, not the edge of the line. + $subscript-margin-top: 0.5em / $subscript-font-scale - ($line-spacing * 2); + // The padding applied to the form-field-wrapper to reserve space for the subscript, since it's + // absolutely positioned. This is a combination of the subscript's margin and line-height, but we + // need to multiply by the subscript font scale factor since the wrapper has a larger font size. + $wrapper-padding-bottom: ($subscript-margin-top + $line-height) * $subscript-font-scale; + + .mat-form-field-appearance-legacy { + .mat-form-field-wrapper { + padding-bottom: $wrapper-padding-bottom; + } + + .mat-form-field-infix { + padding: $infix-padding 0; + } + + &.mat-form-field-can-float { + &.mat-form-field-should-float .mat-form-field-label, + .mat-input-server:focus + .mat-form-field-label-wrapper .mat-form-field-label { + @include _mat-form-field-legacy-label-floating( + $subscript-font-scale, $infix-padding, $infix-margin-top); + } + + // @breaking-change 8.0.0 will rely on AutofillMonitor instead. + .mat-form-field-autofill-control:-webkit-autofill + .mat-form-field-label-wrapper + .mat-form-field-label { + @include _mat-form-field-legacy-label-floating( + $subscript-font-scale, $infix-padding, $infix-margin-top); + } + + // Server-side rendered matInput with a label attribute but label not shown + // (used as a pure CSS stand-in for mat-form-field-should-float). + .mat-input-server[label]:not(:label-shown) + .mat-form-field-label-wrapper + .mat-form-field-label { + @include _mat-form-field-legacy-label-floating( + $subscript-font-scale, $infix-padding, $infix-margin-top); + } + } + + .mat-form-field-label { + top: $infix-margin-top + $infix-padding; + } + + .mat-form-field-underline { + // We want the underline to start at the end of the content box, not the padding box, + // so we move it up by the padding amount. + bottom: $wrapper-padding-bottom; + } + + .mat-form-field-subscript-wrapper { + margin-top: $subscript-margin-top; + + // We want the subscript to start at the end of the content box, not the padding box, + // so we move it up by the padding amount (adjusted for the smaller font size); + top: calc(100% - #{$wrapper-padding-bottom / $subscript-font-scale}); + } + } + + // translateZ causes the label to not appear while printing, so we override it to not + // apply translateZ while printing + @media print { + .mat-form-field-appearance-legacy { + &.mat-form-field-can-float { + &.mat-form-field-should-float .mat-form-field-label, + .mat-input-server:focus + .mat-form-field-label-wrapper .mat-form-field-label { + @include _mat-form-field-legacy-label-floating-print( + $subscript-font-scale, $infix-padding, $infix-margin-top); + } + + // @breaking-change 8.0.0 will rely on AutofillMonitor instead. + .mat-form-field-autofill-control:-webkit-autofill + .mat-form-field-label-wrapper + .mat-form-field-label { + @include _mat-form-field-legacy-label-floating-print( + $subscript-font-scale, $infix-padding, $infix-margin-top); + } + + // Server-side rendered matInput with a label attribute but label not shown + // (used as a pure CSS stand-in for mat-form-field-should-float). + .mat-input-server[label]:not(:label-shown) + .mat-form-field-label-wrapper + .mat-form-field-label { + @include _mat-form-field-legacy-label-floating-print( + $subscript-font-scale, $infix-padding, $infix-margin-top); + } + } + } + } +} + + + + + + + +// Theme styles that only apply to the outline appearance of the form-field. + +@mixin mat-form-field-outline-theme($theme) { + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + $foreground: map-get($theme, foreground); + $is-dark-theme: map-get($theme, is-dark); + + $label-disabled-color: mat-color($foreground, disabled-text); + $outline-color: mat-color($foreground, divider, if($is-dark-theme, 0.3, 0.12)); + $outline-color-hover: mat-color($foreground, divider, if($is-dark-theme, 1, 0.87)); + $outline-color-primary: mat-color($primary); + $outline-color-accent: mat-color($accent); + $outline-color-warn: mat-color($warn); + $outline-color-disabled: mat-color($foreground, divider, if($is-dark-theme, 0.15, 0.06)); + + .mat-form-field-appearance-outline { + .mat-form-field-outline { + color: $outline-color; + } + + .mat-form-field-outline-thick { + color: $outline-color-hover; + } + + &.mat-focused { + .mat-form-field-outline-thick { + color: $outline-color-primary; + } + + &.mat-accent .mat-form-field-outline-thick { + color: $outline-color-accent; + } + + &.mat-warn .mat-form-field-outline-thick { + color: $outline-color-warn; + } + } + + // Class repeated so that rule is specific enough to override focused accent color case. + &.mat-form-field-invalid.mat-form-field-invalid { + .mat-form-field-outline-thick { + color: $outline-color-warn; + } + } + + &.mat-form-field-disabled { + .mat-form-field-label { + color: $label-disabled-color; + } + + .mat-form-field-outline { + color: $outline-color-disabled; + } + } + } +} + +// Used to make instances of the _mat-form-field-label-floating mixin negligibly different, +// and prevent Google's CSS Optimizer from collapsing the declarations. This is needed because some +// of the selectors contain pseudo-classes not recognized in all browsers. If a browser encounters +// an unknown pseudo-class it will discard the entire rule set. +$mat-form-field-outline-dedupe: 0; + +// Applies a floating label above the form field control itself. +@mixin _mat-form-field-outline-label-floating($font-scale, $infix-padding, $infix-margin-top) { + transform: translateY(-$infix-margin-top - $infix-padding + $mat-form-field-outline-dedupe) + scale($font-scale); + width: 100% / $font-scale + $mat-form-field-outline-dedupe; + + $mat-form-field-outline-dedupe: $mat-form-field-outline-dedupe + 0.00001 !global; +} + +@mixin mat-form-field-outline-typography($config) { + // The unit-less line-height from the font config. + $line-height: mat-line-height($config, input); + // The amount to scale the font for the floating label and subscript. + $subscript-font-scale: 0.75; + // The padding above and below the infix. + $infix-padding: 1em; + // The margin applied to the form-field-infix to reserve space for the floating label. + $infix-margin-top: 1em * $line-height * $subscript-font-scale; + // The space between the bottom of the .mat-form-field-flex area and the subscript wrapper. + // Mocks show half of the text size, but this margin is applied to an element with the subscript + // text font size, so we need to divide by the scale factor to make it half of the original text + // size. + $subscript-margin-top: 0.5em / $subscript-font-scale; + // The padding applied to the form-field-wrapper to reserve space for the subscript, since it's + // absolutely positioned. This is a combination of the subscript's margin and line-height, but we + // need to multiply by the subscript font scale factor since the wrapper has a larger font size. + $wrapper-padding-bottom: ($subscript-margin-top + $line-height) * $subscript-font-scale; + // The amount we offset the label from the input text in the outline appearance. + $outline-appearance-label-offset: -0.25em; + + .mat-form-field-appearance-outline { + .mat-form-field-infix { + padding: $infix-padding 0 $infix-padding 0; + } + + .mat-form-field-label { + top: $infix-margin-top + $infix-padding; + margin-top: $outline-appearance-label-offset; + } + + &.mat-form-field-can-float { + &.mat-form-field-should-float .mat-form-field-label, + .mat-input-server:focus + .mat-form-field-label-wrapper .mat-form-field-label { + @include _mat-form-field-outline-label-floating( + $subscript-font-scale, $infix-padding + $outline-appearance-label-offset, + $infix-margin-top); + } + + // Server-side rendered matInput with a label attribute but label not shown + // (used as a pure CSS stand-in for mat-form-field-should-float). + .mat-input-server[label]:not(:label-shown) + .mat-form-field-label-wrapper + .mat-form-field-label { + @include _mat-form-field-outline-label-floating( + $subscript-font-scale, $infix-padding + $outline-appearance-label-offset, + $infix-margin-top); + } + } + } +} + + + + + + + +// Theme styles that only apply to the standard appearance of the form-field. + +@mixin mat-form-field-standard-theme($theme) { + $foreground: map-get($theme, foreground); + $is-dark-theme: map-get($theme, is-dark); + + $underline-color: mat-color($foreground, divider, if($is-dark-theme, 0.7, 0.42)); + + .mat-form-field-appearance-standard { + .mat-form-field-underline { + background-color: $underline-color; + } + + &.mat-form-field-disabled .mat-form-field-underline { + @include mat-control-disabled-underline($underline-color); + } + } +} + +@mixin mat-form-field-standard-typography($config) {} + + +// Theme styles that apply to all appearances of the form-field. +@mixin mat-form-field-theme($theme) { + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + $is-dark-theme: map-get($theme, is-dark); + + // Label colors. Required is used for the `*` star shown in the label. + $label-color: mat-color($foreground, secondary-text, if($is-dark-theme, 0.7, 0.6)); + $focused-label-color: mat-color($primary, text); + $required-label-color: mat-color($accent, text); + + // Underline colors. + $underline-color-base: mat-color($foreground, divider, if($is-dark-theme, 1, 0.87)); + $underline-color-accent: mat-color($accent, text); + $underline-color-warn: mat-color($warn, text); + $underline-focused-color: mat-color($primary, text); + + .mat-form-field-label { + color: $label-color; + } + + .mat-hint { + color: $label-color; + } + + .mat-form-field.mat-focused .mat-form-field-label { + color: $focused-label-color; + + &.mat-accent { + color: $underline-color-accent; + } + + &.mat-warn { + color: $underline-color-warn; + } + } + + .mat-focused .mat-form-field-required-marker { + color: $required-label-color; + } + + .mat-form-field-ripple { + background-color: $underline-color-base; + } + + .mat-form-field.mat-focused { + .mat-form-field-ripple { + background-color: $underline-focused-color; + + &.mat-accent { + background-color: $underline-color-accent; + } + + &.mat-warn { + background-color: $underline-color-warn; + } + } + } + + .mat-form-field-type-mat-native-select.mat-focused:not(.mat-form-field-invalid) { + .mat-form-field-infix::after { + color: $underline-focused-color; + } + + &.mat-accent .mat-form-field-infix::after { + color: $underline-color-accent; + } + + &.mat-warn .mat-form-field-infix::after { + color: $underline-color-warn; + } + } + + // Styling for the error state of the form field. Note that while the same can be + // achieved with the ng-* classes, we use this approach in order to ensure that the same + // logic is used to style the error state and to show the error messages. + .mat-form-field.mat-form-field-invalid { + .mat-form-field-label { + color: $underline-color-warn; + + &.mat-accent, + .mat-form-field-required-marker { + color: $underline-color-warn; + } + } + + .mat-form-field-ripple, + .mat-form-field-ripple.mat-accent { + background-color: $underline-color-warn; + } + } + + .mat-error { + color: $underline-color-warn; + } + + @include mat-form-field-legacy-theme($theme); + @include mat-form-field-standard-theme($theme); + @include mat-form-field-fill-theme($theme); + @include mat-form-field-outline-theme($theme); +} + +// Used to make instances of the _mat-form-field-label-floating mixin negligibly different, +// and prevent Google's CSS Optimizer from collapsing the declarations. This is needed because some +// of the selectors contain pseudo-classes not recognized in all browsers. If a browser encounters +// an unknown pseudo-class it will discard the entire rule set. +$mat-form-field-dedupe: 0; + +// Applies a floating label above the form field control itself. +@mixin _mat-form-field-label-floating($font-scale, $infix-padding, $infix-margin-top) { + transform: translateY(-$infix-margin-top - $infix-padding + $mat-form-field-dedupe) + scale($font-scale); + width: 100% / $font-scale + $mat-form-field-dedupe; + + $mat-form-field-dedupe: $mat-form-field-dedupe + 0.00001 !global; +} + +@mixin mat-form-field-typography($config) { + // The unit-less line-height from the font config. + $line-height: mat-line-height($config, input); + + // The amount to scale the font for the floating label and subscript. + $subscript-font-scale: 0.75; + // The amount to scale the font for the prefix and suffix icons. + $prefix-suffix-icon-font-scale: 1.5; + + // The padding on the infix. Mocks show half of the text size. + $infix-padding: 0.5em; + // The margin applied to the form-field-infix to reserve space for the floating label. + $infix-margin-top: 1em * $line-height * $subscript-font-scale; + // Font size to use for the label and subscript text. + $subscript-font-size: $subscript-font-scale * 100%; + // Font size to use for the for the prefix and suffix icons. + $prefix-suffix-icon-font-size: $prefix-suffix-icon-font-scale * 100%; + // The space between the bottom of the .mat-form-field-flex area and the subscript wrapper. + // Mocks show half of the text size, but this margin is applied to an element with the subscript + // text font size, so we need to divide by the scale factor to make it half of the original text + // size. + $subscript-margin-top: 0.5em / $subscript-font-scale; + // The padding applied to the form-field-wrapper to reserve space for the subscript, since it's + // absolutely positioned. This is a combination of the subscript's margin and line-height, but we + // need to multiply by the subscript font scale factor since the wrapper has a larger font size. + $wrapper-padding-bottom: ($subscript-margin-top + $line-height) * $subscript-font-scale; + + .mat-form-field { + @include mat-typography-level-to-styles($config, input); + } + + .mat-form-field-wrapper { + padding-bottom: $wrapper-padding-bottom; + } + + .mat-form-field-prefix, + .mat-form-field-suffix { + // Allow icons in a prefix or suffix to adapt to the correct size. + .mat-icon { + font-size: $prefix-suffix-icon-font-size; + line-height: $line-height; + } + + // Allow icon buttons in a prefix or suffix to adapt to the correct size. + .mat-icon-button { + height: $prefix-suffix-icon-font-scale * 1em; + width: $prefix-suffix-icon-font-scale * 1em; + + .mat-icon { + height: $line-height * 1em; + line-height: $line-height; + } + } + } + + .mat-form-field-infix { + padding: $infix-padding 0; + // Throws off the baseline if we do it as a real margin, so we do it as a border instead. + border-top: $infix-margin-top solid transparent; + } + + .mat-form-field-can-float { + &.mat-form-field-should-float .mat-form-field-label, + .mat-input-server:focus + .mat-form-field-label-wrapper .mat-form-field-label { + @include _mat-form-field-label-floating( + $subscript-font-scale, $infix-padding, $infix-margin-top); + } + + // Server-side rendered matInput with a label attribute but label not shown + // (used as a pure CSS stand-in for mat-form-field-should-float). + .mat-input-server[label]:not(:label-shown) + .mat-form-field-label-wrapper + .mat-form-field-label { + @include _mat-form-field-label-floating( + $subscript-font-scale, $infix-padding, $infix-margin-top); + } + } + + .mat-form-field-label-wrapper { + top: -$infix-margin-top; + padding-top: $infix-margin-top; + } + + .mat-form-field-label { + top: $infix-margin-top + $infix-padding; + } + + .mat-form-field-underline { + // We want the underline to start at the end of the content box, not the padding box, + // so we move it up by the padding amount. + bottom: $wrapper-padding-bottom; + } + + .mat-form-field-subscript-wrapper { + font-size: $subscript-font-size; + margin-top: $subscript-margin-top; + + // We want the subscript to start at the end of the content box, not the padding box, + // so we move it up by the padding amount (adjusted for the smaller font size); + top: calc(100% - #{$wrapper-padding-bottom / $subscript-font-scale}); + } + + @include mat-form-field-legacy-typography($config); + @include mat-form-field-standard-typography($config); + @include mat-form-field-fill-typography($config); + @include mat-form-field-outline-typography($config); +} + + + + + +@mixin mat-tree-theme($theme) { + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + + .mat-tree { + background: mat-color($background, 'card'); + } + + .mat-tree-node, + .mat-nested-tree-node { + color: mat-color($foreground, text); + } +} + +@mixin mat-tree-typography($config) { + .mat-tree { + font-family: mat-font-family($config); + } + + .mat-tree-node, + .mat-nested-tree-node { + font-weight: mat-font-weight($config, body-1); + font-size: mat-font-size($config, body-1); + } +} + + + +// Includes all of the typographic styles. +@mixin angular-material-typography($config: null) { + @if $config == null { + $config: mat-typography-config(); + } + + @include mat-badge-typography($config); + @include mat-base-typography($config); + @include mat-autocomplete-typography($config); + @include mat-bottom-sheet-typography($config); + @include mat-button-typography($config); + @include mat-button-toggle-typography($config); + @include mat-card-typography($config); + @include mat-checkbox-typography($config); + @include mat-chips-typography($config); + @include mat-table-typography($config); + @include mat-datepicker-typography($config); + @include mat-dialog-typography($config); + @include mat-expansion-panel-typography($config); + @include mat-form-field-typography($config); + @include mat-grid-list-typography($config); + @include mat-icon-typography($config); + @include mat-input-typography($config); + @include mat-menu-typography($config); + @include mat-paginator-typography($config); + @include mat-progress-bar-typography($config); + @include mat-progress-spinner-typography($config); + @include mat-radio-typography($config); + @include mat-select-typography($config); + @include mat-sidenav-typography($config); + @include mat-slide-toggle-typography($config); + @include mat-slider-typography($config); + @include mat-stepper-typography($config); + @include mat-sort-typography($config); + @include mat-tabs-typography($config); + @include mat-toolbar-typography($config); + @include mat-tooltip-typography($config); + @include mat-list-typography($config); + @include mat-option-typography($config); + @include mat-optgroup-typography($config); + @include mat-snack-bar-typography($config); + @include mat-tree-typography($config); +} + + +// Mixin that renders all of the core styles that are not theme-dependent. +@mixin mat-core($typography-config: null) { + @include angular-material-typography($typography-config); + @include mat-ripple(); + @include cdk-a11y(); + @include cdk-overlay(); + @include cdk-text-field(); +} + +// Mixin that renders all of the core styles that depend on the theme. +@mixin mat-core-theme($theme) { + @include mat-ripple-theme($theme); + @include mat-option-theme($theme); + @include mat-optgroup-theme($theme); + @include mat-pseudo-checkbox-theme($theme); + + // Provides external CSS classes for each elevation value. Each CSS class is formatted as + // `mat-elevation-z$zValue` where `$zValue` corresponds to the z-space to which the element is + // elevated. + @for $zValue from 0 through 24 { + .#{$_mat-elevation-prefix}#{$zValue} { + @include _mat-theme-elevation($zValue, $theme); + } + } + + // Wrapper element that provides the theme background when the user's content isn't + // inside of a `mat-sidenav-container`. Note that we need to exclude the ampersand + // selector in case the mixin is included at the top level. + .mat-app-background#{if(&, ', &.mat-app-background', '')} { + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + + background-color: mat-color($background, background); + color: mat-color($foreground, text); + } + + // Marker that is used to determine whether the user has added a theme to their page. + @at-root { + .mat-theme-loaded-marker { + display: none; + } + } +} + + + + + + + + + + + + + + + + +@mixin mat-divider-theme($theme) { + $foreground: map-get($theme, foreground); + + .mat-divider { + border-top-color: mat-color($foreground, divider); + } + + .mat-divider-vertical { + border-right-color: mat-color($foreground, divider); + } +} + + + + + + + + + + + + + + + + + + + + + + + + + +// Create a theme. +@mixin angular-material-theme($theme) { + @include mat-core-theme($theme); + @include mat-autocomplete-theme($theme); + @include mat-badge-theme($theme); + @include mat-bottom-sheet-theme($theme); + @include mat-button-theme($theme); + @include mat-button-toggle-theme($theme); + @include mat-card-theme($theme); + @include mat-checkbox-theme($theme); + @include mat-chips-theme($theme); + @include mat-table-theme($theme); + @include mat-datepicker-theme($theme); + @include mat-dialog-theme($theme); + @include mat-divider-theme($theme); + @include mat-expansion-panel-theme($theme); + @include mat-form-field-theme($theme); + @include mat-grid-list-theme($theme); + @include mat-icon-theme($theme); + @include mat-input-theme($theme); + @include mat-list-theme($theme); + @include mat-menu-theme($theme); + @include mat-paginator-theme($theme); + @include mat-progress-bar-theme($theme); + @include mat-progress-spinner-theme($theme); + @include mat-radio-theme($theme); + @include mat-select-theme($theme); + @include mat-sidenav-theme($theme); + @include mat-slide-toggle-theme($theme); + @include mat-slider-theme($theme); + @include mat-stepper-theme($theme); + @include mat-sort-theme($theme); + @include mat-tabs-theme($theme); + @include mat-toolbar-theme($theme); + @include mat-tooltip-theme($theme); + @include mat-tree-theme($theme); + @include mat-snack-bar-theme($theme); +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\theme\custom-effects.scss + +// +button:hover:not([disabled]), +ion-button:hover:not([disabled]) { + @include animation("flash", 1500ms); +} + +.picker-opt-disabled { + opacity: 0.5; +} + +.action-sheet-cancel, +.action-sheet-setructive, +.action-sheet-group-cancel { + color: var(--x-color-danger) !important; +} + +// +// Dialog Theme ... +::ng-deep.mat-card, +::ng-deep.mat-expansion-panel, +::ng-deep.mat-dialog-container { + color: var(--x-color-light-contrast); + background-color: var(--x-color-light); +} + +::ng-deep.full-with { + .x-wrapper { + width: 100%; + } +} + +.full-with { + width: 100%; +} + +.x-minimal-popover { + .popover-content { + width: 90px !important; + max-width: 90px !important; + } +} + +.dynamic-slot { + flex: 0 auto !important; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\theme\fab-theme.scss + +// +//#region Fab ... +$isRTL: var(--is-rtl); +$fabMargin: 16px; + +x-fab[pageFab] { + // + ion-fab { + // + @if $isRTL { + left: $fabMargin !important; + right: auto !important; + } @else { + right: $fabMargin !important; + left: auto !important; + } + + // + position: fixed !important; + margin-top: calc(var(--content-offset-bottom) * -1); + margin-bottom: calc(var(--content-offset-bottom) * -1); + + // + transition: transform 250ms ease 0s, opacity 250ms ease 0s; + } + + // + &.start-scroll { + ion-fab { + transform: translateY(200px) scale3d(0, 0, 0) !important; + } + } +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\theme\markdown-theme.scss + +/** + * MIT License + * Copyright (c) 2021 Ayush Saini + * Holi Theme for prism.js + * @author Ayush Saini <@AyushCodes on Twitter> + */ + + code[class*='language-'], + pre[class*='language-'] { + color: #d6e7ff; + background: #030314; + text-shadow: none; + font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace; + font-size: 1em; + line-height: 1.5; + letter-spacing: .2px; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + text-align: left; + + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; + } + + pre[class*='language-']::-moz-selection, + pre[class*='language-'] ::-moz-selection, + code[class*='language-']::-moz-selection, + code[class*='language-'] ::-moz-selection, + pre[class*='language-']::selection, + pre[class*='language-'] ::selection, + code[class*='language-']::selection, + code[class*='language-'] ::selection { + color: inherit; + background: #1d3b54; + text-shadow: none; + } + + pre[class*='language-'] { + border: 1px solid #2a4555; + border-radius: 5px; + padding: 1.5em 1em; + margin: 1em 0; + overflow: auto; + } + + :not(pre) > code[class*='language-'] { + color: #f0f6f6; + background: #2a4555; + padding: 0.2em 0.3em; + border-radius: 0.2em; + box-decoration-break: clone; + } + + .token.comment, + .token.prolog, + .token.doctype, + .token.cdata { + color: #446e69; + } + + .token.punctuation { + color: #d6b007; + } + + .token.property, + .token.tag, + .token.boolean, + .token.number, + .token.constant, + .token.symbol, + .token.deleted { + color: #d6e7ff; + } + + .token.selector, + .token.attr-name, + .token.builtin, + .token.inserted { + color: #e60067; + } + + .token.string, + .token.char { + color: #49c6ec; + } + + .token.operator, + .token.entity, + .token.url, + .language-css .token.string, + .style .token.string { + color: #ec8e01; + background: transparent; + } + + .token.atrule, + .token.attr-value, + .token.keyword { + color: #0fe468; + } + + .token.function, + .token.class-name { + color: #78f3e9; + } + + .token.regex, + .token.important, + .token.variable { + color: #d6e7ff; + } + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\theme\material-fonts.scss + +// +//#region Material Fonts ... +/* cyrillic-ext */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 300; + src: local("Roboto Light"), local("Roboto-Light"), + url("../assets/font/KFOlCnqEu92Fr1MmSU5fCRc4AMP6lbBP.woff2") + format("woff2"); + unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, + U+FE2E-FE2F; +} +/* cyrillic */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 300; + src: local("Roboto Light"), local("Roboto-Light"), + url("../assets/font/KFOlCnqEu92Fr1MmSU5fABc4AMP6lbBP.woff2") + format("woff2"); + unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} +/* greek-ext */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 300; + src: local("Roboto Light"), local("Roboto-Light"), + url("../assets/font/KFOlCnqEu92Fr1MmSU5fCBc4AMP6lbBP.woff2") + format("woff2"); + unicode-range: U+1F00-1FFF; +} +/* greek */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 300; + src: local("Roboto Light"), local("Roboto-Light"), + url("../assets/font/KFOlCnqEu92Fr1MmSU5fBxc4AMP6lbBP.woff2") + format("woff2"); + unicode-range: U+0370-03FF; +} +/* vietnamese */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 300; + src: local("Roboto Light"), local("Roboto-Light"), + url("../assets/font/KFOlCnqEu92Fr1MmSU5fCxc4AMP6lbBP.woff2") + format("woff2"); + unicode-range: U+0102-0103, U+0110-0111, U+1EA0-1EF9, U+20AB; +} +/* latin-ext */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 300; + src: local("Roboto Light"), local("Roboto-Light"), + url("../assets/font/KFOlCnqEu92Fr1MmSU5fChc4AMP6lbBP.woff2") + format("woff2"); + unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, + U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 300; + src: local("Roboto Light"), local("Roboto-Light"), + url("../assets/font/KFOlCnqEu92Fr1MmSU5fBBc4AMP6lQ.woff2") + format("woff2"); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, + U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, + U+FEFF, U+FFFD; +} +/* Persian */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 300; + src: url("../assets/font/irsans.ttf"); + unicode-renge: U+0621, U+0623, U+0626, U+0624, U+0627, U+0628, U+067E, U+062A, + U+062B, U+062C, U+0686, U+062D, U+062E, U+062F, U+0630, U+0631, U+0632, + U+0698, U+0633, U+0634, U+0635, U+0636, U+0637, U+0638, U+0639, U+063A, + U+0641, U+0642, U+06A9, U+06AF, U+0644, U+0645, U+0646, U+0648, U+0647, + U+06CC, U+0622, U+06C0, U+0644, U+0627, U+0640, U+067E, U+0686, U+0698, + U+06AF, U+0660, U+0661, U+0662, U+0663, U+0664, U+0665, U+0666, U+0667, + U+0668, U+0669, U+064A, U+0643; +} + +/* cyrillic-ext */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 400; + src: local("Roboto"), local("Roboto-Regular"), + url("../assets/font/KFOmCnqEu92Fr1Mu72xKKTU1Kvnz.woff2") + format("woff2"); + unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, + U+FE2E-FE2F; +} +/* cyrillic */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 400; + src: local("Roboto"), local("Roboto-Regular"), + url("../assets/font/KFOmCnqEu92Fr1Mu5mxKKTU1Kvnz.woff2") + format("woff2"); + unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} +/* greek-ext */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 400; + src: local("Roboto"), local("Roboto-Regular"), + url("../assets/font/KFOmCnqEu92Fr1Mu7mxKKTU1Kvnz.woff2") + format("woff2"); + unicode-range: U+1F00-1FFF; +} +/* greek */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 400; + src: local("Roboto"), local("Roboto-Regular"), + url("../assets/font/KFOmCnqEu92Fr1Mu4WxKKTU1Kvnz.woff2") + format("woff2"); + unicode-range: U+0370-03FF; +} +/* vietnamese */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 400; + src: local("Roboto"), local("Roboto-Regular"), + url("../assets/font/KFOmCnqEu92Fr1Mu7WxKKTU1Kvnz.woff2") + format("woff2"); + unicode-range: U+0102-0103, U+0110-0111, U+1EA0-1EF9, U+20AB; +} +/* latin-ext */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 400; + src: local("Roboto"), local("Roboto-Regular"), + url("../assets/font/KFOmCnqEu92Fr1Mu7GxKKTU1Kvnz.woff2") + format("woff2"); + unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, + U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 400; + src: local("Roboto"), local("Roboto-Regular"), + url("../assets/font/KFOmCnqEu92Fr1Mu4mxKKTU1Kg.woff2") + format("woff2"); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, + U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, + U+FEFF, U+FFFD; +} +/* Persian */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 400; + src: url("../assets/font/irsans.ttf"); + unicode-renge: U+0621, U+0623, U+0626, U+0624, U+0627, U+0628, U+067E, U+062A, + U+062B, U+062C, U+0686, U+062D, U+062E, U+062F, U+0630, U+0631, U+0632, + U+0698, U+0633, U+0634, U+0635, U+0636, U+0637, U+0638, U+0639, U+063A, + U+0641, U+0642, U+06A9, U+06AF, U+0644, U+0645, U+0646, U+0648, U+0647, + U+06CC, U+0622, U+06C0, U+0644, U+0627, U+0640, U+067E, U+0686, U+0698, + U+06AF, U+0660, U+0661, U+0662, U+0663, U+0664, U+0665, U+0666, U+0667, + U+0668, U+0669, U+064A, U+0643; +} + + +/* cyrillic-ext */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 500; + src: local("Roboto Medium"), local("Roboto-Medium"), + url("../assets/font/KFOlCnqEu92Fr1MmEU9fCRc4AMP6lbBP.woff2") + format("woff2"); + unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, + U+FE2E-FE2F; +} +/* cyrillic */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 500; + src: local("Roboto Medium"), local("Roboto-Medium"), + url("../assets/font/KFOlCnqEu92Fr1MmEU9fABc4AMP6lbBP.woff2") + format("woff2"); + unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} +/* greek-ext */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 500; + src: local("Roboto Medium"), local("Roboto-Medium"), + url("../assets/font/KFOlCnqEu92Fr1MmEU9fCBc4AMP6lbBP.woff2") + format("woff2"); + unicode-range: U+1F00-1FFF; +} +/* greek */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 500; + src: local("Roboto Medium"), local("Roboto-Medium"), + url("../assets/font/KFOlCnqEu92Fr1MmEU9fBxc4AMP6lbBP.woff2") + format("woff2"); + unicode-range: U+0370-03FF; +} +/* vietnamese */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 500; + src: local("Roboto Medium"), local("Roboto-Medium"), + url("../assets/font/KFOlCnqEu92Fr1MmEU9fCxc4AMP6lbBP.woff2") + format("woff2"); + unicode-range: U+0102-0103, U+0110-0111, U+1EA0-1EF9, U+20AB; +} +/* latin-ext */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 500; + src: local("Roboto Medium"), local("Roboto-Medium"), + url("../assets/font/KFOlCnqEu92Fr1MmEU9fChc4AMP6lbBP.woff2") + format("woff2"); + unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, + U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 500; + src: local("Roboto Medium"), local("Roboto-Medium"), + url("../assets/font/KFOlCnqEu92Fr1MmEU9fBBc4AMP6lQ.woff2") + format("woff2"); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, + U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, + U+FEFF, U+FFFD; +} +/* Persian */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 500; + src: url("../assets/font/irsans.ttf"); + unicode-renge: U+0621, U+0623, U+0626, U+0624, U+0627, U+0628, U+067E, U+062A, + U+062B, U+062C, U+0686, U+062D, U+062E, U+062F, U+0630, U+0631, U+0632, + U+0698, U+0633, U+0634, U+0635, U+0636, U+0637, U+0638, U+0639, U+063A, + U+0641, U+0642, U+06A9, U+06AF, U+0644, U+0645, U+0646, U+0648, U+0647, + U+06CC, U+0622, U+06C0, U+0644, U+0627, U+0640, U+067E, U+0686, U+0698, + U+06AF, U+0660, U+0661, U+0662, U+0663, U+0664, U+0665, U+0666, U+0667, + U+0668, U+0669, U+064A, U+0643; +} +//#endregion + +// +//#region Material Design Icons ... +@font-face { + font-family: "Material Icons"; + font-style: normal; + font-weight: 400; + src: url("../assets/font/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2") + format("woff2"); +} + +.material-icons { + font-family: "Material Icons"; + font-weight: normal; + font-style: normal; + font-size: 24px; + line-height: 1; + letter-spacing: normal; + text-transform: none; + display: inline-block; + white-space: nowrap; + word-wrap: normal; + direction: ltr; + -webkit-font-feature-settings: "liga"; + -webkit-font-smoothing: antialiased; +} +//#endregion + + +@mixin x-toolbar-typography($config) { + .mat-toolbar { + @include mat-typography-level-to-styles($config, body-2); + margin: 0; + } + + .mat-toolbar h1 { + @include mat-typography-level-to-styles($config, headline); + margin: 0; + } + + .mat-toolbar h2 { + @include mat-typography-level-to-styles($config, title); + margin: 0; + } + + .mat-toolbar h3 { + @include mat-typography-level-to-styles($config, subheading-2); + margin: 0; + } + + .mat-toolbar h4 { + @include mat-typography-level-to-styles($config, subheading-1); + margin: 0; + } + + .mat-toolbar h5 { + @include mat-typography-level-to-styles($config, body-1); + margin: 0; + } + + .mat-toolbar h6 { + @include mat-typography-level-to-styles($config, caption); + margin: 0; + } +} + + +body { + @extend .mat-typography; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\theme\material-icons.scss + +@font-face { + font-family: 'Material Icons'; + font-style: normal; + font-weight: 400; + src: url('../assets/font/MaterialIcons-Regular.eot'); /* For IE6-8 */ + src: local('Material Icons'), local('MaterialIcons-Regular'), + url('../assets/font/MaterialIcons-Regular.woff2') format('woff2'), + url('../assets/font/MaterialIcons-Regular.woff') format('woff'), + url('../assets/font/MaterialIcons-Regular.ttf') format('truetype'); +} + +.material-icons { + font-family: 'Material Icons'; + font-weight: normal; + font-style: normal; + font-size: 24px; /* Preferred icon size */ + display: inline-block; + line-height: 1; + text-transform: none; + letter-spacing: normal; + word-wrap: normal; + white-space: nowrap; + direction: ltr; + + /* Support for all WebKit browsers. */ + -webkit-font-smoothing: antialiased; + /* Support for Safari and Chrome. */ + text-rendering: optimizeLegibility; + + /* Support for Firefox. */ + -moz-osx-font-smoothing: grayscale; + + /* Support for IE. */ + font-feature-settings: 'liga'; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\theme\theme.scss + +@import "./base-theme.scss"; +@import "./material-icons.scss"; +@import "./material-fonts.scss"; +@import "./markdown-theme.scss"; +@import "./fab-theme.scss"; + +@include mat-core(); + +$x-primary: mat-palette($mat-blue); +$x-accent: mat-palette($mat-cyan); +$x-warn: mat-palette($mat-red); + +// Create the theme object (a Sass map containing all of the palettes). +$x-theme: mat-light-theme($x-primary, $x-accent, $x-warn); +@include angular-material-theme($x-theme); + +// +@function generate-x-theme($primary, $accent, $warn) { + // + $x-primary: $primary; + $x-accent: $accent; + $x-warn: $warn; + + // + $x-theme: mat-light-theme($primary, $accent, $warn); + + // + @return $x-theme; +} + +// +@mixin x-theme($theme) { + @include angular-material-theme($theme); +} + +// +@mixin x-ion-color() { + // + // XFramework Color Mechanism ... + :root { + --x-color-primary: var(--ion-color-primary); + --x-color-primary-shade: var(--ion-color-primary-shade); + --x-color-primary-tint: var(--ion-color-primary-tint); + + --x-color-secondary: var(--ion-color-secondary); + --x-color-secondary-shade: var(--ion-color-secondary-shade); + --x-color-secondary-tint: var(--ion-color-secondary-tint); + + --x-color-tertiary: var(--ion-color-tertiary); + --x-color-tertiary-shade: var(--ion-color-tertiary-shade); + --x-color-tertiary-tint: var(--ion-color-tertiary-tint); + + --x-color-success: var(--ion-color-success); + --x-color-success-shade: var(--ion-color-success-shade); + --x-color-success-tint: var(--ion-color-success-tint); + + --x-color-warning: var(--ion-color-warning); + --x-color-warning-shade: var(--ion-color-warning-shade); + --x-color-warning-tint: var(--ion-color-warning-tint); + + --x-color-danger: var(--ion-color-danger); + --x-color-danger-shade: var(--ion-color-danger-shade); + --x-color-danger-tint: var(--ion-color-danger-tint); + + --x-color-dark: var(--ion-color-dark); + --x-color-dark-shade: var(--ion-color-dark-shade); + --x-color-dark-tint: var(--ion-color-dark-tint); + + --x-color-medium: var(--ion-color-medium); + --x-color-medium-shade: var(--ion-color-medium-shade); + --x-color-medium-tint: var(--ion-color-medium-tint); + + --x-color-light: var(--ion-color-light); + --x-color-light-shade: var(--ion-color-light-shade); + --x-color-light-tint: var(--ion-color-light-tint); + + --x-color-primary-contrast: var(--ion-color-primary-contrast); + --x-color-primary-shade-contrast: var(--ion-color-primary-contrast); + --x-color-primary-tint-contrast: var(--ion-color-primary-contrast); + + --x-color-secondary-contrast: var(--ion-color-secondary-contrast); + --x-color-secondary-shade-contrast: var(--ion-color-secondary-contrast); + --x-color-secondary-tint-contrast: var(--ion-color-secondary-contrast); + + --x-color-tertiary-contrast: var(--ion-color-tertiary-contrast); + --x-color-tertiary-shade-contrast: var(--ion-color-tertiary-contrast); + --x-color-tertiary-tint-contrast: var(--ion-color-tertiary-contrast); + + --x-color-success-contrast: var(--ion-color-success-contrast); + --x-color-success-shade-contrast: var(--ion-color-success-contrast); + --x-color-success-tint-contrast: var(--ion-color-success-contrast); + + --x-color-warning-contrast: var(--ion-color-warning-contrast); + --x-color-warning-shade-contrast: var(--ion-color-warning-contrast); + --x-color-warning-tint-contrast: var(--ion-color-warning-contrast); + + --x-color-danger-contrast: var(--ion-color-danger-contrast); + --x-color-danger-shade-contrast: var(--ion-color-danger-contrast); + --x-color-danger-tint-contrast: var(--ion-color-danger-contrast); + + --x-color-dark-contrast: var(--ion-color-dark-contrast); + --x-color-dark-shade-contrast: var(--ion-color-dark-contrast); + --x-color-dark-tint-contrast: var(--ion-color-dark-contrast); + + --x-color-medium-contrast: var(--ion-color-medium-contrast); + --x-color-medium-shade-contrast: var(--ion-color-medium-contrast); + --x-color-medium-tint-contrast: var(--ion-color-medium-contrast); + + --x-color-light-contrast: var(--ion-color-light-contrast); + --x-color-light-shade-contrast: var(--ion-color-light-contrast); + --x-color-light-tint-contras: var(--ion-color-light-contrast); + } + + // + // Define a Palette Based on Collection ... + $m-color-palette: mat-palette($m-color-collection, m-ionic); + + // + // Merge Defined Palette to Theme Object ... + $x-theme: map_merge( + $x-theme, + ( + m-ionic: $m-color-palette, + ) + ); + + // + // Generate a General Object for feauture Usages ... + $x-ionic: map-get($x-theme, m-ionic); +} + +// +@mixin x-to-ion-color() { + // + // XFramework Color Mechanism ... + :root { + --ion-color-primary: var(--x-color-primary); + --ion-color-primary-shade: var(--x-color-primary-shade); + --ion-color-primary-tint: var(--x-color-primary-tint); + + --ion-color-primary-contrast: var(--x-color-primary-contrast); + --ion-color-primary-contrast: var(--x-color-primary-contrast); + --ion-color-primary-contrast: var(--x-color-primary-contrast); + + --ion-color-secondary: var(--x-color-secondary); + --ion-color-secondary-shade: var(--x-color-secondary-shade); + --ion-color-secondary-tint: var(--x-color-secondary-tint); + + --ion-color-secondary-contrast: var(--x-color-secondary-contrast); + --ion-color-secondary-contrast: var(--x-color-secondary-contrast); + --ion-color-secondary-contrast: var(--x-color-secondary-contrast); + + --ion-color-tertiary: var(--x-color-tertiary); + --ion-color-tertiary-shade: var(--x-color-tertiary-shade); + --ion-color-tertiary-tint: var(--x-color-tertiary-tint); + + --ion-color-tertiary-contrast: var(--x-color-tertiary-contrast); + --ion-color-tertiary-contrast: var(--x-color-tertiary-contrast); + --ion-color-tertiary-contrast: var(--x-color-tertiary-contrast); + + --ion-color-success: var(--x-color-success); + --ion-color-success-shade: var(--x-color-success-shade); + --ion-color-success-tint: var(--x-color-success-tint); + + --ion-color-success-contrast: var(--x-color-success-contrast); + --ion-color-success-contrast: var(--x-color-success-contrast); + --ion-color-success-contrast: var(--x-color-success-contrast); + + --ion-color-warning: var(--x-color-warning); + --ion-color-warning-shade: var(--x-color-warning-shade); + --ion-color-warning-tint: var(--x-color-warning-tint); + + --ion-color-warning-contrast: var(--x-color-warning-contrast); + --ion-color-warning-contrast: var(--x-color-warning-contrast); + --ion-color-warning-contrast: var(--x-color-warning-contrast); + + --ion-color-danger: var(--x-color-danger); + --ion-color-danger-shade: var(--x-color-danger-shade); + --ion-color-danger-tint: var(--x-color-danger-tint); + + --ion-color-danger-contrast: var(--x-color-danger-contrast); + --ion-color-danger-contrast: var(--x-color-danger-contrast); + --ion-color-danger-contrast: var(--x-color-danger-contrast); + + --ion-color-dark: var(--x-color-dark); + --ion-color-dark-shade: var(--x-color-dark-shade); + --ion-color-dark-tint: var(--x-color-dark-tint); + + --ion-color-dark-contrast: var(--x-color-dark-contrast); + --ion-color-dark-contrast: var(--x-color-dark-contrast); + --ion-color-dark-contrast: var(--x-color-dark-contrast); + + --ion-color-medium: var(--x-color-medium); + --ion-color-medium-shade: var(--x-color-medium-shade); + --ion-color-medium-tint: var(--x-color-medium-tint); + + --ion-color-medium-contrast: var(--x-color-medium-contrast); + --ion-color-medium-contrast: var(--x-color-medium-contrast); + --ion-color-medium-contrast: var(--x-color-medium-contrast); + + --ion-color-light: var(--x-color-light); + --ion-color-light-shade: var(--x-color-light-shade); + --ion-color-light-tint: var(--x-color-light-tint); + + --ion-color-light-contrast: var(--x-color-light-contrast); + --ion-color-light-contrast: var(--x-color-light-contrast); + --ion-color-light-contrast: var(--x-color-light-contrast); + } + + // + // Define a Palette Based on Collection ... + $m-color-palette: mat-palette($m-color-collection, m-ionic); + + // + // Merge Defined Palette to Theme Object ... + $x-theme: map_merge( + $x-theme, + ( + m-ionic: $m-color-palette, + ) + ); + + // + // Generate a General Object for feauture Usages ... + $x-ionic: map-get($x-theme, m-ionic); +} + +// +//#region Custom Ionic Based Colors ... + +// +//#region Define a Collection ... +$m-color-collection: ( + primary: var(--x-color-primary), + primary-shade: var(--x-color-primary-shade), + primary-tint: var(--x-color-primary-tint), + secondary: var(--x-color-secondary), + secondary-shade: var(--x-color-secondary-shade), + secondary-tint: var(--x-color-secondary-tint), + tertiary: var(--x-color-tertiary), + tertiary-shade: var(--x-color-tertiary-shade), + tertiary-tint: var(--x-color-tertiary-tint), + success: var(--x-color-success), + success-shade: var(--x-color-success-shade), + success-tint: var(--x-color-success-tint), + warning: var(--x-color-warning), + warning-shade: var(--x-color-warning-shade), + warning-tint: var(--x-color-warning-tint), + danger: var(--x-color-danger), + danger-shade: var(--x-color-danger-shade), + danger-tint: var(--x-color-danger-tint), + dark: var(--x-color-dark), + dark-shade: var(--x-color-dark-shade), + dark-tint: var(--x-color-dark-tint), + medium: var(--x-color-medium), + medium-shade: var(--x-color-medium-shade), + medium-tint: var(--x-color-medium-tint), + light: var(--x-color-light), + light-shade: var(--x-color-light-shade), + light-tint: var(--x-color-light-tint), + contrast: ( + primary: var(--x-color-primary-contrast), + primary-shade: var(--x-color-primary-shade-contrast), + primary-tint: var(--x-color-primary-tint-contrast), + secondary: var(--x-color-secondary-contrast), + secondary-shade: var(--x-color-secondary-shade-contrast), + secondary-tint: var(--x-color-secondary-tint-contrast), + tertiary: var(--x-color-tertiary-contrast), + tertiary-shade: var(--x-color-tertiary-shade-contrast), + tertiary-tint: var(--x-color-tertiary-tint-contrast), + success: var(--x-color-success-contrast), + success-shade: var(--x-color-success-shade-contrast), + success-tint: var(--x-color-success-tint-contrast), + warning: var(--x-color-warning-contrast), + warning-shade: var(--x-color-warning-shade-contrast), + warning-tint: var(--x-color-warning-tint-contrast), + danger: var(--x-color-danger-contrast), + danger-shade: var(--x-color-danger-shade-contrast), + danger-tint: var(--x-color-danger-tint-contrast), + dark: var(--x-color-dark-contrast), + dark-shade: var(--x-color-dark-shade-contrast), + dark-tint: var(--x-color-dark-tint-contrast), + medium: var(--x-color-medium-contrast), + medium-shade: var(--x-color-medium-shade-contrast), + medium-tint: var(--x-color-medium-tint-contrast), + light: var(--x-color-light-contrast), + light-shade: var(--x-color-light-shade-contrast), + light-tint: var(--x-color-light-tint-contrast), + ), +); +//#endregion + +// +// Define a Palette Based on Collection ... +$m-color-palette: mat-palette($m-color-collection, m-ionic); + +// +// Merge Defined Palette to Theme Object ... +$x-theme: map_merge( + $x-theme, + ( + m-ionic: $m-color-palette, + ) +); + +// +// Generate a General Object for feauture Usages ... +$x-ionic: map-get($x-theme, m-ionic); + +// +//#region Generate General Classes for Support these Colors ... +// +//#region Primary ... +.mat-primary { + // + color: mat-color($x-ionic, primary-contrast) !important; + background-color: mat-color($x-ionic, primary) !important; + + // + --color: mat-color($x-ionic, primary-contrast) !important; + --background: mat-color($x-ionic, primary) !important; +} + +.mat-primary-shade { + // + color: mat-color($x-ionic, primary-shade-contrast) !important; + background-color: mat-color($x-ionic, primary-shade) !important; + + // + --color: mat-color($x-ionic, primary-shade-contrast) !important; + --background: mat-color($x-ionic, primary-shade) !important; +} + +.mat-primary-tint { + // + color: mat-color($x-ionic, primary-tint-contrast) !important; + background-color: mat-color($x-ionic, primary-tint) !important; + + // + --color: mat-color($x-ionic, primary-tint-contrast) !important; + --background: mat-color($x-ionic, primary-tint) !important; +} + +.c-primary { + // + color: mat-color($x-ionic, primary) !important; + + // + --color: mat-color($x-ionic, primary) !important; +} + +.c-primary-shade { + // + color: mat-color($x-ionic, primary-shade) !important; + + // + --color: mat-color($x-ionic, primary-shade) !important; +} + +.c-primary-tint { + // + color: mat-color($x-ionic, primary-tint) !important; + + // + --color: mat-color($x-ionic, primary-tint) !important; +} + +//#endregion + +// +//#region Secondary ... +.mat-secondary { + // + color: mat-color($x-ionic, secondary-contrast) !important; + background-color: mat-color($x-ionic, secondary) !important; + + // + --color: mat-color($x-ionic, secondary-contrast) !important; + --background: mat-color($x-ionic, secondary) !important; +} + +.mat-secondary-shade { + // + color: mat-color($x-ionic, secondary-shade-contrast) !important; + background-color: mat-color($x-ionic, secondary-shade) !important; + + // + --color: mat-color($x-ionic, secondary-shade-contrast) !important; + --background: mat-color($x-ionic, secondary-shade) !important; +} + +.mat-secondary-tint { + // + color: mat-color($x-ionic, secondary-tint-contrast) !important; + background-color: mat-color($x-ionic, secondary-tint) !important; + + // + --color: mat-color($x-ionic, secondary-tint-contrast) !important; + --background: mat-color($x-ionic, secondary-tint) !important; +} + +.c-secondary { + // + color: mat-color($x-ionic, secondary) !important; + + // + --color: mat-color($x-ionic, secondary) !important; +} + +.c-secondary-shade { + // + color: mat-color($x-ionic, secondary-shade) !important; + + // + --color: mat-color($x-ionic, secondary-shade) !important; +} + +.c-secondary-tint { + // + color: mat-color($x-ionic, secondary-tint) !important; + + // + --color: mat-color($x-ionic, secondary-tint) !important; +} + +//#endregion + +// +//#region Tertiary ... +.mat-tertiary { + // + color: mat-color($x-ionic, tertiary-contrast) !important; + background-color: mat-color($x-ionic, tertiary) !important; + + // + --color: mat-color($x-ionic, tertiary-contrast) !important; + --background: mat-color($x-ionic, tertiary) !important; +} + +.mat-tertiary-shade { + // + color: mat-color($x-ionic, tertiary-shade-contrast) !important; + background-color: mat-color($x-ionic, tertiary-shade) !important; + + // + --color: mat-color($x-ionic, tertiary-shade-contrast) !important; + --background: mat-color($x-ionic, tertiary-shade) !important; +} + +.mat-tertiary-tint { + // + color: mat-color($x-ionic, tertiary-tint-contrast) !important; + background-color: mat-color($x-ionic, tertiary-tint) !important; + + // + --color: mat-color($x-ionic, tertiary-tint-contrast) !important; + --background: mat-color($x-ionic, tertiary-tint) !important; +} + +.c-tertiary { + // + color: mat-color($x-ionic, tertiary) !important; + + // + --color: mat-color($x-ionic, tertiary) !important; +} + +.c-tertiary-shade { + // + color: mat-color($x-ionic, tertiary-shade) !important; + + // + --color: mat-color($x-ionic, tertiary-shade) !important; +} + +.c-tertiary-tint { + // + color: mat-color($x-ionic, tertiary-tint) !important; + + // + --color: mat-color($x-ionic, tertiary-tint) !important; +} + +//#endregion + +// +//#region Success ... +.mat-success { + // + color: mat-color($x-ionic, success-contrast) !important; + background-color: mat-color($x-ionic, success) !important; + + // + --color: mat-color($x-ionic, success-contrast) !important; + --background: mat-color($x-ionic, success) !important; +} + +.mat-success-shade { + // + color: mat-color($x-ionic, success-shade-contrast) !important; + background-color: mat-color($x-ionic, success-shade) !important; + + // + --color: mat-color($x-ionic, success-shade-contrast) !important; + --background: mat-color($x-ionic, success-shade) !important; +} + +.mat-success-tint { + // + color: mat-color($x-ionic, success-tint-contrast) !important; + background-color: mat-color($x-ionic, success-tint) !important; + + // + --color: mat-color($x-ionic, success-tint-contrast) !important; + --background: mat-color($x-ionic, success-tint) !important; +} + +.c-success { + // + color: mat-color($x-ionic, success) !important; + + // + --color: mat-color($x-ionic, success) !important; +} + +.c-success-shade { + // + color: mat-color($x-ionic, success-shade) !important; + + // + --color: mat-color($x-ionic, success-shade) !important; +} + +.c-success-tint { + // + color: mat-color($x-ionic, success-tint) !important; + + // + --color: mat-color($x-ionic, success-tint) !important; +} + +//#endregion + +// +//#region Warning ... +.mat-warning { + // + color: mat-color($x-ionic, warning-contrast) !important; + background-color: mat-color($x-ionic, warning) !important; + + // + --color: mat-color($x-ionic, warning-contrast) !important; + --background: mat-color($x-ionic, warning) !important; +} + +.mat-warning-shade { + // + color: mat-color($x-ionic, warning-shade-contrast) !important; + background-color: mat-color($x-ionic, warning-shade) !important; + + // + --color: mat-color($x-ionic, warning-shade-contrast) !important; + --background: mat-color($x-ionic, warning-shade) !important; +} + +.mat-warning-tint { + // + color: mat-color($x-ionic, warning-tint-contrast) !important; + background-color: mat-color($x-ionic, warning-tint) !important; + + // + --color: mat-color($x-ionic, warning-tint-contrast) !important; + --background: mat-color($x-ionic, warning-tint) !important; +} + +.c-warning { + // + color: mat-color($x-ionic, warning) !important; + + // + --color: mat-color($x-ionic, warning) !important; +} + +.c-warning-shade { + // + color: mat-color($x-ionic, warning-shade) !important; + + // + --color: mat-color($x-ionic, warning-shade) !important; +} + +.c-warning-tint { + // + color: mat-color($x-ionic, warning-tint) !important; + + // + --color: mat-color($x-ionic, warning-tint) !important; +} + +//#endregion + +// +//#region Danger ... +.mat-danger { + // + color: mat-color($x-ionic, danger-contrast) !important; + background-color: mat-color($x-ionic, danger) !important; + + // + --color: mat-color($x-ionic, danger-contrast) !important; + --background: mat-color($x-ionic, danger) !important; +} + +.mat-danger-shade { + // + color: mat-color($x-ionic, danger-shade-contrast) !important; + background-color: mat-color($x-ionic, danger-shade) !important; + + // + --color: mat-color($x-ionic, danger-shade-contrast) !important; + --background: mat-color($x-ionic, danger-shade) !important; +} + +.mat-danger-tint { + // + color: mat-color($x-ionic, danger-tint-contrast) !important; + background-color: mat-color($x-ionic, danger-tint) !important; + + // + --color: mat-color($x-ionic, danger-tint-contrast) !important; + --background: mat-color($x-ionic, danger-tint) !important; +} + +.c-danger { + // + color: mat-color($x-ionic, danger) !important; + + // + --color: mat-color($x-ionic, danger) !important; +} + +.c-danger-shade { + // + color: mat-color($x-ionic, danger-shade) !important; + + // + --color: mat-color($x-ionic, danger-shade) !important; +} + +.c-danger-tint { + // + color: mat-color($x-ionic, danger-tint) !important; + + // + --color: mat-color($x-ionic, danger-tint) !important; +} + +//#endregion + +// +//#region Dark ... +.mat-dark { + // + color: mat-color($x-ionic, dark-contrast) !important; + background-color: mat-color($x-ionic, dark) !important; + + // + --color: mat-color($x-ionic, dark-contrast) !important; + --background: mat-color($x-ionic, dark) !important; +} + +.mat-dark-shade { + // + color: mat-color($x-ionic, dark-shade-contrast) !important; + background-color: mat-color($x-ionic, dark-shade) !important; + + // + --color: mat-color($x-ionic, dark-shade-contrast) !important; + --background: mat-color($x-ionic, dark-shade) !important; +} + +.mat-dark-tint { + // + color: mat-color($x-ionic, dark-tint-contrast) !important; + background-color: mat-color($x-ionic, dark-tint) !important; + + // + --color: mat-color($x-ionic, dark-tint-contrast) !important; + --background: mat-color($x-ionic, dark-tint) !important; +} + +.c-dark { + // + color: mat-color($x-ionic, dark) !important; + + // + --color: mat-color($x-ionic, dark) !important; +} + +.c-dark-shade { + // + color: mat-color($x-ionic, dark-shade) !important; + + // + --color: mat-color($x-ionic, dark-shade) !important; +} + +.c-dark-tint { + // + color: mat-color($x-ionic, dark-tint) !important; + + // + --color: mat-color($x-ionic, dark-tint) !important; +} + +//#endregion + +// +//#region Medium ... +.mat-medium { + // + color: mat-color($x-ionic, medium-contrast) !important; + background-color: mat-color($x-ionic, medium) !important; + + // + --color: mat-color($x-ionic, medium-contrast) !important; + --background: mat-color($x-ionic, medium) !important; +} + +.mat-medium-shade { + // + color: mat-color($x-ionic, medium-shade-contrast) !important; + background-color: mat-color($x-ionic, medium-shade) !important; + + // + --color: mat-color($x-ionic, medium-shade-contrast) !important; + --background: mat-color($x-ionic, medium-shade) !important; +} + +.mat-medium-tint { + // + color: mat-color($x-ionic, medium-tint-contrast) !important; + background-color: mat-color($x-ionic, medium-tint) !important; + + // + --color: mat-color($x-ionic, medium-tint-contrast) !important; + --background: mat-color($x-ionic, medium-tint) !important; +} + +.c-medium { + // + color: mat-color($x-ionic, medium) !important; + + // + --color: mat-color($x-ionic, medium) !important; +} + +.c-medium-shade { + // + color: mat-color($x-ionic, medium-shade) !important; + + // + --color: mat-color($x-ionic, medium-shade) !important; +} + +.c-medium-tint { + // + color: mat-color($x-ionic, medium-tint) !important; + + // + --color: mat-color($x-ionic, medium-tint) !important; +} + +//#endregion + +// +//#region Light ... +.mat-light { + // + color: mat-color($x-ionic, light-contrast) !important; + background-color: mat-color($x-ionic, light) !important; + + // + --color: mat-color($x-ionic, light-contrast) !important; + --background: mat-color($x-ionic, light) !important; +} + +.mat-light-shade { + // + color: mat-color($x-ionic, light-shade-contrast) !important; + background-color: mat-color($x-ionic, light-shade) !important; + + // + --color: mat-color($x-ionic, light-shade-contrast) !important; + --background: mat-color($x-ionic, light-shade) !important; +} + +.mat-light-tint { + // + color: mat-color($x-ionic, light-tint-contrast) !important; + background-color: mat-color($x-ionic, light-tint) !important; + + // + --color: mat-color($x-ionic, light-tint-contrast) !important; + --background: mat-color($x-ionic, light-tint) !important; +} + +.c-light { + // + color: mat-color($x-ionic, light) !important; + + // + --color: mat-color($x-ionic, light) !important; +} + +.c-light-shade { + // + color: mat-color($x-ionic, light-shade) !important; + + // + --color: mat-color($x-ionic, light-shade) !important; +} + +.c-light-tint { + // + color: mat-color($x-ionic, light-tint) !important; + + // + --color: mat-color($x-ionic, light-tint) !important; +} + +//#endregion +//#endregion + +.cb-inherit { + // + color: inherit !important; + --color: inherit !important; + + // + background-color: inherit !important; + --background-color: inherit !important; +} + +.transparent { + --background: transparent !important; + background-color: transparent !important; +} + +//#endregion + +// +//#region Custom Styles ... +html, +body { + height: 100%; +} + +body { + margin: 0; + padding: 0; + overflow-x: hidden; + font-family: Roboto, "Helvetica Neue", sans-serif; +} + +* { + box-sizing: border-box; +} + +*::-webkit-scrollbar { + display: none; +} + +[hidden] { + display: none !important; +} +//#endregion + +// +@import "_tools"; + +// +// Since here we must able to use all Capabilities, +// so we import this at the end ... +@import "./custom-effects.scss"; + +// +// Implement to ionic them ... +@include x-to-ion-color(); + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\tokens\x-injectable-tokens.ts + +import { InjectionToken } from '@angular/core'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export const X_FRAMEWORK_COMPONENTS_CONFIG = new InjectionToken< + XFrameworkComponentsConfig +>('x_config'); + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-action-bar\x-action-bar.component.html + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + {{ getResourceValue(action.title) | async }} +
+
+
+ + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-action-bar\x-action-bar.component.scss + +$action-item-margin: var(--action-items-offset, 2px); +$action-item-gutter: var(--action-items-gutter, 8px); + +.x-actions-bar { + padding: { + left: $action-item-gutter !important; + right: $action-item-gutter !important; + } +} + +.x-action-bar-item { + margin: { + left: $action-item-margin !important; + right: $action-item-margin !important; + } +} + +.x-action-bar-item-inner { + display: flex; + align-items: center; + + > * { + margin: { + left: 2px !important; + right: 2px !important; + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-action-bar\x-action-bar.component.ts + +import { + sort, + nameof, + toArray, + isFunction, + filterList, + isNullOrEmptyString, +} from 'x-framework-core'; +import { + XActionBarItem, + XActionBarAction, + XActionBarItemSlot, + XActionBarActionModel, + XActionBarActionIdentifier, + XActionBarItemSlotIdentifier, + XActionBarContainerComponent, +} from './x-action-bar.typings'; +import { map } from 'rxjs/operators'; +import { Subject, Subscription } from 'rxjs'; +import { XButtonType } from '../x-button/x-button.typings'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-action-bar', + templateUrl: './x-action-bar.component.html', + styleUrls: ['./x-action-bar.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XActionBarComponent extends XActionBarContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + private ACTIONS: XActionBarItem[] = []; + private ACTIONS_HIDDEN: Map = new Map(); + private ACTIONS_DISABLED: Map = + new Map(); + + // + readonly ButtonTypes = Object.assign({}, XButtonType); + + get preparedActions() { + return this.ACTIONS; + } + //#endregion + + // + //#region LifeCycles ... + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region Actions Changed ... + // + const isActionsChanged = changeKeys.includes( + nameof('actions') + ); + if (isActionsChanged) { + await this.prepareActions(); + } + //#endregion + + // + //#region Action Provider Changed ... + // + const isActionProviderChaged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChaged) { + this.registerActionProvider(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region UI Providers ... + // + //#region Slot Based Items Fetcher ... + getSlotItems(slot: XActionBarItemSlotIdentifier) { + return filterList(this.actions, (action) => + this.getValue(action.slot).pipe(map((iSlot) => iSlot === slot)) + ); + } + + getStartSoltItems() { + return this.getSlotItems(XActionBarItemSlot.START); + } + + getEndSoltItems() { + return this.getSlotItems(XActionBarItemSlot.END); + } + + isOnlyIcon(item: XActionBarItem) { + return this.getOptionalValue(item.onlyIcon).pipe( + map((onlyIcon) => !!onlyIcon) + ); + } + + isDisabledAction(item: XActionBarItem) { + // + if (!item) { + return this.getValue(false); + } + + // + return this.getValue(item.id).pipe( + map((id) => + isNullOrEmptyString(id) + ? false + : this.ACTIONS_DISABLED.has(id) + ? this.ACTIONS_DISABLED.get(id) + : false + ) + ); + } + + isHidedAction(item: XActionBarItem) { + // + if (!item) { + return this.getValue(false); + } + + // + return this.getValue(item.id).pipe( + map((id) => + isNullOrEmptyString(id) + ? false + : this.ACTIONS_HIDDEN.has(id) + ? this.ACTIONS_HIDDEN.get(id) + : false + ) + ); + } + //#endregion + + async getActionIds() { + // + if (!this.hasChild(this.preparedActions)) { + return []; + } + + // + const IDS = this.preparedActions.map((a) => this.getValueAsync(a.id)); + + // + return await Promise.all(IDS); + } + //#endregion + + // + //#region UI Handlers ... + async handleActionClicked(item: XActionBarItem, event: Event) { + // + const disabled = await this.getValueAsync(item.disabled); + if (disabled || this.uiDisabled || this.loading) { + return; + } + + // + const id = await this.getValueAsync(item.id); + + // + if (item.handler && isFunction(item.handler)) { + await item.handler({ + action: id, + event, + }); + } + + // + this.actionFired.emit({ + action: id, + event, + }); + } + //#endregion + + // + //#region Actions ... + /** + * check an action with specific id exists in actions or not ... + * + * @param id required action id ... + */ + async isActionExists(id: string) { + return (await this.getActionIds()).findIndex((i) => i === id) > -1; + } + + /** + * retrieve all actions ... + */ + async getActions() { + // + const ACTIONS_TYPE = await this.getValueAsync(this.actions); + if (!ACTIONS_TYPE) { + return []; + } + + // + return toArray(ACTIONS_TYPE); + } + + /** + * retrieve an action ... + * + * @param id action id + */ + async getAction(id: string) { + // + const isExists = await this.isActionExists(id); + if (!isExists) { + return null; + } + + // + const ACTIONS = await this.getActions(); + if (!this.hasChild(ACTIONS)) { + return null; + } + + // + const existsAction = ACTIONS.find( + async (a) => (await this.getValueAsync(a.id)) === id + ); + + // + return this.toPreparedAction(existsAction); + } + + /** + * manually fire a registered action + * + * @param id action id + */ + async fireAction(id: string, event?: Event) { + // + const isExist = await this.isActionExists(id); + if (!isExist) { + return; + } + + // + const action = await this.getAction(id); + if (!action) { + return; + } + + // + const canFire = + !this.uiDisabled && + !this.loading && + !(await this.getOptionalValueAsync(action.disabled)); + if (!canFire) { + return; + } + + // + await this.handleActionClicked(action, event); + } + //#endregion + + // + //#region Private ... + // + //#region ActionProvider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe( + async (action) => await this.handleActionProviderAction(action) + ); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + // + private async prepareActions() { + // + this.ACTIONS = null; + if (!this.actions) { + // + this.detectChanges(); + return; + } + + // + const ACTIONS_TYPE = await this.getValueAsync(this.actions); + if (!ACTIONS_TYPE) { + // + this.detectChanges(); + return; + } + + // + const ACTIONS = toArray(ACTIONS_TYPE); + if (!this.hasChild(ACTIONS)) { + // + this.detectChanges(); + return; + } + + // + this.ACTIONS = [ + ...sort(ACTIONS, (a, b) => (a.index && b.index ? a.index - b.index : -1)), + ]; + + // + for (const a of this.ACTIONS) { + // + const id = await this.getValueAsync(a.id); + + // + if (!this.ACTIONS_DISABLED.has(id)) { + this.ACTIONS_DISABLED.set(id, false); + } + + // + if (!this.ACTIONS_HIDDEN.has(id)) { + this.ACTIONS_HIDDEN.set(id, false); + } + } + + // + this.detectChanges(); + } + + private async actionsCount() { + return (await this.getActions()).length || 0; + } + + private async findActionIdex(id: string) { + // + const isExists = await this.isActionExists(id); + if (!isExists) { + return -1; + } + + // + const ACTIONS_TYPE = await this.getValueAsync(this.actions); + if (!ACTIONS_TYPE) { + return null; + } + + // + const ACTIONS = toArray(ACTIONS_TYPE); + if (!this.hasChild(ACTIONS)) { + return null; + } + + // + return ACTIONS.findIndex( + async (a) => (await this.getValueAsync(a.id)) === id + ); + } + + private async toPreparedAction(item: XActionBarItem) { + // + if (!item) { + return null; + } + + // + return { + index: item.index, + handler: item.handler, + id: await this.getValueAsync(item.id), + icon: await this.getValueAsync(item.icon), + slot: await this.getValueAsync(item.slot), + color: await this.getValueAsync(item.color), + title: await this.getValueAsync(item.title), + hidden: await this.getValueAsync(item.hidden), + onlyIcon: await this.getValueAsync(item.onlyIcon), + disabled: await this.getValueAsync(item.disabled), + cssClass: await this.getValueAsync(item.cssClass), + iconColor: await this.getValueAsync(item.iconColor), + titleColor: await this.getValueAsync(item.titleColor), + } as XActionBarItem; + } + + private async handleActionProviderAction(model: XActionBarActionModel) { + // + if (!model || isNullOrEmptyString(model.action)) { + return; + } + + // + const actionId = await this.getValueAsync(model.id); + + // + switch (model.action) { + // + case XActionBarAction.Refresh: + this.detectChanges(); + break; + + // + case XActionBarAction.FireAction: + await this.fireAction(model.id, model.event); + break; + + // + case XActionBarAction.EnableAction: + // + if (this.ACTIONS_DISABLED.has(actionId)) { + // + this.ACTIONS_DISABLED.set(actionId, false); + this.detectChanges(); + } else { + if (!this.hasChild(this.ACTIONS)) { + setTimeout(() => this.handleActionProviderAction(model), 500); + } + } + break; + + // + case XActionBarAction.DisbaleAction: + // + if (this.ACTIONS_DISABLED.has(actionId)) { + // + this.ACTIONS_DISABLED.set(actionId, true); + this.detectChanges(); + } else { + if (!this.hasChild(this.ACTIONS)) { + setTimeout(() => this.handleActionProviderAction(model), 500); + } + } + break; + + // + case XActionBarAction.ShowAction: + // + if (this.ACTIONS_HIDDEN.has(actionId)) { + // + this.ACTIONS_HIDDEN.set(actionId, false); + this.detectChanges(); + } else { + if (!this.hasChild(this.ACTIONS)) { + setTimeout(() => this.handleActionProviderAction(model), 500); + } + } + break; + + // + case XActionBarAction.HideAction: + // + if (this.ACTIONS_HIDDEN.has(actionId)) { + // + this.ACTIONS_HIDDEN.set(actionId, true); + this.detectChanges(); + } else { + if (!this.hasChild(this.ACTIONS)) { + setTimeout(() => this.handleActionProviderAction(model), 500); + } + } + break; + } + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-action-bar\x-action-bar.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XIconModule } from '../x-icon/x-icon.module'; +import { XButtonModule } from '../x-button/x-button.module'; +import { XActionBarComponent } from './x-action-bar.component'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XActionBarComponent], + imports: [XIconModule, CommonModule, XButtonModule, XBaseComponentModule], + exports: [ + XIconModule, + CommonModule, + XButtonModule, + XActionBarComponent, + XBaseComponentModule, + ], +}) +export class XActionBarModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-action-bar\x-action-bar.typings.ts + +import { + XColor, + XResourceIDs, + XStandardType, + XOneOrManyType, + XColorIdentifier, + XResourceIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { + Input, + Inject, + NgZone, + Output, + Component, + Renderer2, + ElementRef, + Injectable, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XBaseContainer, + IXBaseContainer, + XBaseComponentContainer, + XBaseContainerComponent, + IXBaseComponentContainer, + XBaseComponentContainerComponent, +} from '../classes/i-x-base.container'; +import { map } from 'rxjs/operators'; +import { Observable, forkJoin, Subject } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { XIconNames, XIconIdentifier } from '../x-icon/x-icon.typings'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +// +//#region ActionBar Item ... +export enum XActionBarItemSlot { + START = 'start', + END = 'end', +} + +export type XActionBarItemSlotIdentifier = XActionBarItemSlot | string; + +export interface XActionBarItem { + index?: number; + id: XStandardType; + hidden?: XStandardType; + onlyIcon?: XStandardType; + disabled?: XStandardType; + icon: XStandardType; + color?: XStandardType; + title?: XStandardType; + iconColor?: XStandardType; + titleColor?: XStandardType; + cssClass?: XStandardType>; + slot: XStandardType; + handler?: (model?: XActionBarActionFiredModel) => XStandardType; +} + +export interface XActionBarActionFiredModel { + action: string; + event?: Event; +} +//#endregion + +// +//#region Action Provider ... +export enum XActionBarAction { + Refresh = 'refresh', + FireAction = 'fire_action', + HideAction = 'hide_action', + ShowAction = 'show_action', + EnableAction = 'enable_action', + DisbaleAction = 'disable_action', +} + +export type XActionBarActionIdentifier = XActionBarAction | string; + +export interface XActionBarActionModel + extends XBaseActionModel { + id?: string; + event?: Event; +} +//#endregion + +// +//#region Default Actionbar Item IDs ... +export enum DefaultActionBarItemIds { + Add = 'add', + Set = 'set', + Edit = 'edit', + Remove = 'remove', + Upload = 'upload', + Download = 'download', + SelectAll = 'select_all', + DeSelectAll = 'de_select_all', + SelectAllGroup = 'select_all_group', + InverseSelection = 'inverse_selection', + DeSelectAllGroup = 'de_select_all_group', +} + +export type DefaultActionBarItemIdsIdentifier = + | DefaultActionBarItemIds + | string; +//#endregion + +// +//#region Default Items ... +export const defaultActionBarRemoveItem: XActionBarItem = { + onlyIcon: true, + color: XColor.Danger, + icon: XIconNames.remove, + iconColor: XColor.Danger, + title: XResourceIDs.delete, + slot: XActionBarItemSlot.START, + id: DefaultActionBarItemIds.Remove, +}; + +export const defaultActionBarUploadItem: XActionBarItem = { + onlyIcon: true, + color: XColor.Tertiary, + icon: XIconNames.upload, + iconColor: XColor.Tertiary, + title: XResourceIDs.upload, + slot: XActionBarItemSlot.START, + id: DefaultActionBarItemIds.Upload, +}; + +export const defaultActionBarDownloadItem: XActionBarItem = { + onlyIcon: true, + color: XColor.Tertiary, + icon: XIconNames.download, + iconColor: XColor.Tertiary, + title: XResourceIDs.download, + slot: XActionBarItemSlot.START, + id: DefaultActionBarItemIds.Download, +}; + +export const defaultActionBarSelectAllItem: XActionBarItem = { + onlyIcon: true, + color: XColor.Tertiary, + iconColor: XColor.Tertiary, + icon: XIconNames.select_all, + slot: XActionBarItemSlot.END, + title: XResourceIDs.select_all, + id: DefaultActionBarItemIds.SelectAll, +}; + +export const defaultActionBarDeSelectAllItem: XActionBarItem = { + onlyIcon: true, + color: XColor.Tertiary, + iconColor: XColor.Tertiary, + slot: XActionBarItemSlot.END, + icon: XIconNames.deselect_all, + title: XResourceIDs.deselect_all, + id: DefaultActionBarItemIds.DeSelectAll, +}; + +export const defaultActionBarSelectAllGroupItem: XActionBarItem = { + onlyIcon: true, + color: XColor.Tertiary, + iconColor: XColor.Tertiary, + slot: XActionBarItemSlot.END, + icon: XIconNames.select_all_group, + title: XResourceIDs.select_all_group, + id: DefaultActionBarItemIds.SelectAllGroup, +}; + +export const defaultActionBarDeSelectAllGroupItem: XActionBarItem = { + onlyIcon: true, + color: XColor.Tertiary, + iconColor: XColor.Tertiary, + slot: XActionBarItemSlot.END, + icon: XIconNames.deselect_all_group, + title: XResourceIDs.deselect_all_group, + id: DefaultActionBarItemIds.DeSelectAllGroup, +}; + +export const defaultActionBarInverseSelectionItem: XActionBarItem = { + onlyIcon: true, + color: XColor.Tertiary, + iconColor: XColor.Tertiary, + slot: XActionBarItemSlot.END, + icon: XIconNames.inverse_selection, + title: XResourceIDs.inverse_selection, + id: DefaultActionBarItemIds.InverseSelection, +}; + +export const defaultActionBarAddItem: XActionBarItem = { + onlyIcon: true, + icon: XIconNames.add, + color: XColor.Success, + title: XResourceIDs.add, + iconColor: XColor.Success, + slot: XActionBarItemSlot.START, + id: DefaultActionBarItemIds.Add, +}; + +export const defaultActionBarSetItem: XActionBarItem = { + onlyIcon: true, + color: XColor.Success, + icon: XIconNames.done, + title: XResourceIDs.set, + iconColor: XColor.Success, + slot: XActionBarItemSlot.START, + id: DefaultActionBarItemIds.Set, +}; + +export const defaultActionBarEditItem: XActionBarItem = { + onlyIcon: true, + icon: XIconNames.edit, + color: XColor.Warning, + title: XResourceIDs.edit, + iconColor: XColor.Warning, + slot: XActionBarItemSlot.START, + id: DefaultActionBarItemIds.Edit, +}; +//#endregion + +// +//#region Container ... +export interface IXActionBarContainer extends IXBaseContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + color: XStandardType; + foregroundColor: XStandardType; + + // + actions: XStandardType>; + + // + actionFired: EventEmitter; +} + +@Injectable({ + providedIn: 'root', +}) +export class XActionBarContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider = new Subject(); + readonly cssClass: XStandardType>; + + // + readonly color: XStandardType = XColorWithBrightness.Light; + readonly foregroundColor: XStandardType = false; + + // + readonly actions: XStandardType>; + + // + readonly actionFired = new EventEmitter(); + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XActionBarContainerComponent + extends XBaseContainerComponent + implements IXActionBarContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + color: XStandardType = this.propertyProvider.color; + + @Input() + foregroundColor: XStandardType = + this.propertyProvider.foregroundColor; + + // + @Input() + actions: XStandardType> = + this.propertyProvider.actions; + + // + @Output() + actionFired: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XActionBarContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXActionBarContainer + ): Observable { + return forkJoin({ + // + cssClass: this.getValue(container ? container.cssClass : this.cssClass), + + // + color: this.getValue(container ? container.color : this.color), + foregroundColor: this.getValue( + container ? container.foregroundColor : this.foregroundColor + ), + + // + actions: this.getValue(container ? container.actions : this.actions), + }).pipe( + map((data) => { + return { + // + ...data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + actionFired: this.actionFired, + }; + }) + ); + } + //#endregion +} +//#endregion + +// +//#region ComponentContainer ... +type TXComponentActionBarContainer = { + [TKey in keyof IXActionBarContainer as `actionBar${Capitalize< + string & TKey + >}`]: IXActionBarContainer[TKey]; +}; + +export interface IXComponentActionBarContainer + extends TXComponentActionBarContainer, + IXBaseComponentContainer { + showActionBar: XStandardType; + actionBarColor: XStandardType; + actionBarForegroundColor: XStandardType; + actionBarActionProvider: Subject; + actionBarCssClass: XStandardType>; + actionBarActionFired: EventEmitter; + actionBarActions: XStandardType>; +} + +@Injectable({ + providedIn: 'root', +}) +export class XComponentActionBarContainer + extends XBaseComponentContainer + implements Readonly +{ + // + //#region Props ... + // + readonly showActionBar: XStandardType = true; + + // + readonly actionBarActionProvider: Subject = + this.propertyProvider.actionProvider; + readonly actionBarCssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + readonly actionBarColor: XStandardType = + this.propertyProvider.color; + readonly actionBarForegroundColor: XStandardType = + this.propertyProvider.foregroundColor; + + // + readonly actionBarActions: XStandardType> = + this.propertyProvider.actions; + + // + readonly actionBarActionFired: EventEmitter = + this.propertyProvider.actionFired; + //#endregion + + // + //#region Constructor ... + constructor(protected propertyProvider: XActionBarContainer) { + super(); + } + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XComponentActionBarContainerComponent + extends XBaseComponentContainerComponent + implements IXComponentActionBarContainer +{ + // + //#region Props ... + // + @Input() + showActionBar: XStandardType = this.propertyProvider.showActionBar; + + // + @Input() + actionBarActionProvider: Subject = + this.propertyProvider.actionBarActionProvider; + + @Input() + actionBarCssClass: XStandardType> = + this.propertyProvider.actionBarCssClass; + + // + @Input() + actionBarColor: XStandardType = + this.propertyProvider.actionBarColor; + + @Input() + actionBarForegroundColor: XStandardType = + this.propertyProvider.actionBarForegroundColor; + + // + @Input() + actionBarActions: XStandardType> = + this.propertyProvider.actionBarActions; + + // + @Output() + actionBarActionFired: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XComponentActionBarContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXComponentActionBarContainer + ): Observable { + return forkJoin({ + // + showActionBar: this.getValue( + container ? container.showActionBar : this.showActionBar + ), + + // + actionBarCssClass: this.getValue( + container ? container.actionBarCssClass : this.actionBarCssClass + ), + + // + actionBarColor: this.getValue( + container ? container.actionBarColor : this.actionBarColor + ), + actionBarForegroundColor: this.getValue( + container + ? container.actionBarForegroundColor + : this.actionBarForegroundColor + ), + + // + actionBarActions: this.getValue( + container ? container.actionBarActions : this.actionBarActions + ), + }).pipe( + map((data) => { + return { + // + ...data, + + // + actionBarActionProvider: container + ? container.actionBarActionProvider + : this.actionBarActionProvider, + + // + actionBarActionFired: this.actionBarActionFired, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-avatar-upload\x-avatar-upload.component.html + + + + + + + + +
+ +
+ + +
+ +
+
+
+ + + + +
+ + + + + + + + + +
+ + + + + + + + +
+ + + + + + + + + + + + + + + + + +
+
+
+
+
+
+ + + +
+ +
+
+ + + + + + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-avatar-upload\x-avatar-upload.component.scss + +// +:host { + --avatar-size: 64px; + --top-radius: 5px; + --padding: 6px; +} + +.x-file-drag-drop-wrapper { + padding-bottom: var(--padding); + border-top-left-radius: var(--top-radius); + border-top-right-radius: var(--top-radius); +} + +.x-avatar-container { + padding: 6px; + border-radius: 5px; +} + +.thumbnail-wrapper { + height: var(--avatar-size, 64px) !important; +} + +x-thumbnail { + --thumbnail-size: var(--avatar-size, 64px); +} + +x-file-drop-area { + --padding: 0px; +} + +.avatar-actions { + // + @extend .thumbnail-wrapper; + + // + display: flex; + flex-direction: row; + align-items: stretch; + justify-content: flex-end; + + // + * { + flex: 0 0 auto; + margin: { + top: auto; + bottom: auto; + } + } + + // + * { + margin: { + left: 3px; + right: 3px; + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-avatar-upload\x-avatar-upload.component.ts + +import { + Component, + ViewChild, + ElementRef, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + sort, + nameof, + isArray, + toArray, + hasChild, + toBoolean, + getArrayOf, + XDialogResult, + isExtensionOk, + getErrorException, + getImageDimensions, + XColorWithBrightness, + XExceptionIdentifier, +} from 'x-framework-core'; +import { + XAvatarStatus, + XAvatarUploadAction, + XAvatarUploadActionModel, + XAvatarUploadActionButtonName, + XAvatarUploadContainerComponent, +} from './x-avatar-upload.typings'; +import { map } from 'rxjs/operators'; +import { Subject, Subscription } from 'rxjs'; +import { XIconNames } from '../x-icon/x-icon.typings'; +import { XButtonType } from '../x-button/x-button.typings'; +import { XThumbnailType } from '../x-thumbnail/x-thumbnail.typings'; +import { XSlotName, XSlotLayout } from '../x-slotter/x-slotter.typings'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-avatar-upload', + templateUrl: './x-avatar-upload.component.html', + styleUrls: ['./x-avatar-upload.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XAvatarUploadComponent extends XAvatarUploadContainerComponent { + // + //#region Props ... + // + private SELECTED_AVATAR: File; + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + readonly SlotNames = Object.assign({}, XSlotName); + readonly IconNames = Object.assign({}, XIconNames); + readonly SlotLayout = Object.assign({}, XSlotLayout); + readonly ButtonTypes = Object.assign({}, XButtonType); + readonly ThumbnailTypes = Object.assign({}, XThumbnailType); + readonly ColorNames = Object.assign({}, XColorWithBrightness); + readonly ActionButtonNames = Object.assign({}, XAvatarUploadActionButtonName); + + // + selectedFileWidth = 0; + selectedFileHeight = 0; + + // + @ViewChild('fileInput', { static: false }) + private fileInputRef!: ElementRef; + + set avatar(v: File) { + // + this.SELECTED_AVATAR = v; + + // + if (!v) { + return; + } + + // + getImageDimensions(v).then((dimension) => { + if (dimension) { + // + this.selectedFileWidth = dimension.width; + this.selectedFileHeight = dimension.height; + + // + this.detectChanges(); + } + }); + } + + get avatar() { + return this.SELECTED_AVATAR; + } + //#endregion + + // + //#region LifeCycles ... + onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region Action Provider ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region Task Handlers ... + //#endregion + + // + //#region UI Providers ... + getActions() { + return this.getArrayValue(this.actions).pipe( + map((actions) => sort(actions, (a1, a2) => a1.index - a2.index)) + ); + } + + isCroppableImage() { + return ( + this.avatar && + this.selectedFileWidth > 0 && + this.selectedFileHeight > 0 && + this.selectedFileWidth >= this.config.minAllowedImageSize && + this.selectedFileHeight >= this.config.minAllowedImageSize + ); + } + //#endregion + + // + //#region UI Handlers ... + // + handleFilesSelected() { + // + if (!this.fileInputRef) { + return; + } + + // + const fileInput = this.fileInputRef.nativeElement; + if (!fileInput || !fileInput.files) { + return; + } + + // + const files = getArrayOf(fileInput.files); + + // + if (!files || files.length === 0) { + return; + } + + // + const file = files[0]; + if (!file) { + return; + } + + // + fileInput.value = null; + + // + this.handleFileDropped(files); + } + + // + async handleFileDropped(files: File | File[]) { + // + if (!files) { + return; + } + + // + this.loading = true; + + // + let file: File; + if (!isArray(files) && files instanceof File) { + file = files as File; + } else { + // + const filesArr = files as File[]; + if (hasChild(filesArr)) { + file = filesArr[0]; + } + } + + // + this.loading = false; + + // + if (!file) { + return; + } + + // + const status = await this.getFileStatus(file); + if (status !== XAvatarStatus.OK) { + this.loading = false; + await this.handleFileStatus(status); + return; + } + + // + this.avatar = file; + this.avatarChange.emit(file); + + // + this.detectChanges(); + } + + // + async handleActionButtonClicked(name: XAvatarUploadActionButtonName) { + switch (name) { + // + case XAvatarUploadActionButtonName.Crop: + this.loadCropper(); + break; + + // + case XAvatarUploadActionButtonName.Clear: + await this.clear(); + break; + + // + case XAvatarUploadActionButtonName.Select: + this.select(); + break; + } + } + //#endregion + + // + //#region Actions ... + // + select() { + // + if (!this.fileInputRef || !this.fileInputRef.nativeElement) { + return; + } + + // + this.fileInputRef.nativeElement.click(); + } + + // + async clear(force = false) { + // + if (!this.avatar) { + return; + } + + // + if (force) { + this.clearImage(); + return; + } + + // + await this.managerService.dialogService.presentAreYouSureYesNoDialog(() => { + this.clearImage(); + }); + } + //#endregion + + // + //#region Private ... + // + //#region ActionProvider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XAvatarUploadAction.Clear: + await this.clear(toBoolean(model.payload)); + break; + + // + case XAvatarUploadAction.Select: + this.select(); + break; + + // + case XAvatarUploadAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + private loadCropper() { + this.imageCropperService.showCropper(this.avatar); + + // + const s = this.applyTakeUntilWrapper( + this.imageCropperService.onResult.asObservable() + ).subscribe((res) => { + // + if (res.result === XDialogResult.Ok && res.file) { + // + this.avatar = null; + setTimeout(() => { + // + this.avatar = res.file; + this.avatarChange.emit(res.file); + + // + this.detectChanges(); + }, 100); + } + + // + s.unsubscribe(); + }); + } + + private clearImage() { + // + if (!this.avatar) { + return; + } + + // + this.avatar = null; + this.selectedFileWidth = 0; + this.selectedFileHeight = 0; + this.avatarChange.emit(null); + + // + this.detectChanges(); + } + + private async getFileStatus(file: File) { + // + // Check Not Empty File ... + if (!file || file.size === 0) { + return XAvatarStatus.EMPTY; + } + + // + // Check File Extension Validation ... + const isExtensionIsOk = await this.isExtensionOk(file); + if (!isExtensionIsOk) { + return XAvatarStatus.NOT_ALLOWED_TYPE; + } + + // + // Check File Min Size Ok ... + const isMinAllowedFileSizeOk = await this.isMinAllowedFileSizeOk(file); + if (!isMinAllowedFileSizeOk) { + return XAvatarStatus.MIN_SIZE_NOT_REACHED; + } + + // + // Check File Max Size Ok ... + const isMaxAllowedFileSizeOk = await this.isMaxAllowedFileSizeOk(file); + if (!isMaxAllowedFileSizeOk) { + return XAvatarStatus.SIZE_EXCEEDED; + } + + // + // Everythings Ok ... + return XAvatarStatus.OK; + } + + private async handleFileStatus(status: XAvatarStatus) { + // + let identifier: XExceptionIdentifier = null; + switch (status) { + // + case XAvatarStatus.EMPTY: + identifier = await this.getValueAsync(this.emptyFileException); + break; + + // + case XAvatarStatus.NOT_ALLOWED_TYPE: + identifier = await this.getValueAsync(this.notAllowedFileException); + break; + + // + case XAvatarStatus.SIZE_EXCEEDED: + identifier = await this.getValueAsync(this.maxAllowedFileSizeException); + break; + + // + case XAvatarStatus.MIN_SIZE_NOT_REACHED: + identifier = await this.getValueAsync(this.minAllowedFileSizeException); + break; + } + + // + const exception = getErrorException(identifier); + if (exception) { + this.managerService.notificationService.presentErrorNotification({ + exception, + dissmissable: true, + }); + } + } + + // + //#region File Selection Policies ... + /** + * Check File Extension ... + */ + private async isExtensionOk(file: File) { + // + if (!file) { + return false; + } + + // + const allowedExtensionsType = await this.getValueAsync( + this.allowedExtensions + ); + if (!allowedExtensionsType) { + return true; + } + + // + const allowedExtensions = toArray(allowedExtensionsType); + if (!hasChild(allowedExtensions)) { + return true; + } + + // + const result = isExtensionOk(file, allowedExtensions); + return result; + } + + /** + * Check File Size less than or Equals Max Required Size ... + */ + private async isMaxAllowedFileSizeOk(file: File) { + // + const maxAllowedFileSize = await this.getValueAsync( + this.maxAllowedFileSize + ); + + // + const result = + maxAllowedFileSize && maxAllowedFileSize > 0 + ? file.size <= maxAllowedFileSize + : true; + + // + return result; + } + + /** + * Check File Size satisfied Min Required Size ... + */ + private async isMinAllowedFileSizeOk(file: File) { + // + const minAllowedFileSize = await this.getValueAsync( + this.minAllowedFileSize + ); + + // + const result = + minAllowedFileSize && minAllowedFileSize > 0 + ? file.size >= minAllowedFileSize + : true; + + // + return result; + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-avatar-upload\x-avatar-upload.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XIconModule } from '../x-icon/x-icon.module'; +import { XCardModule } from '../x-card/x-card.module'; +import { XButtonModule } from '../x-button/x-button.module'; +import { XSlotterModule } from '../x-slotter/x-slotter.module'; +import { XAvatarUploadComponent } from './x-avatar-upload.component'; +import { XThumbnailModule } from '../x-thumbnail/x-thumbnail.module'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; +import { XFileDropAreaModule } from '../x-file-drop-area/x-file-drop-area.module'; + +@NgModule({ + declarations: [XAvatarUploadComponent], + imports: [ + XIconModule, + XCardModule, + CommonModule, + XButtonModule, + XSlotterModule, + XThumbnailModule, + XFileDropAreaModule, + XBaseComponentModule, + ], + exports: [ + XIconModule, + XCardModule, + XButtonModule, + XSlotterModule, + XThumbnailModule, + XFileDropAreaModule, + XBaseComponentModule, + XAvatarUploadComponent, + ], +}) +export class XAvatarUploadModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-avatar-upload\x-avatar-upload.typings.ts + +import { + Input, + Output, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XColor, + XResourceIDs, + XStandardType, + XOneOrManyType, + XColorIdentifier, + XResourceIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { + XCardFileFileDropContainer, + IXCardFileFileDropContainer, + XCardFileFileDropContainerComponent, +} from '../classes/i-x-file.container'; +import { concatMap, map } from 'rxjs/operators'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { XIconIdentifier, XIconNames } from '../x-icon/x-icon.typings'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XImageCropperService } from '../x-image-cropper/x-image-cropper.service'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export enum XAvatarUploadAction { + // + Clear = 'clear', + Select = 'select', + Refresh = 'refresh', +} + +export type XAvatarUploadActionIdentifier = XAvatarUploadAction | string; + +export interface XAvatarUploadActionModel + extends XBaseActionModel {} + +export enum XAvatarStatus { + OK = 'ok', + EMPTY = 'empty', + SIZE_EXCEEDED = 'size_exceeded', + NOT_ALLOWED_TYPE = 'not_allowed_type', + MIN_SIZE_NOT_REACHED = 'min_size_not_reached', +} + +export type XAvatarStatusIdentifier = XAvatarStatus | string; + +export enum XAvatarUploadActionButtonName { + Crop, + Clear, + Select, +} + +export interface XAvatarUploadActionButton { + index?: number; + name: XAvatarUploadActionButtonName; + icon?: XStandardType; + color?: XStandardType; + tooltip?: XStandardType; +} + +export const defaultAvatarUploadActionButtons: XAvatarUploadActionButton[] = [ + // + // Select ... + { + index: 0, + color: XColor.Success, + icon: XIconNames.touch, + tooltip: XResourceIDs.select, + name: XAvatarUploadActionButtonName.Select, + }, + // + // Crop ... + { + index: 1, + icon: XIconNames.crop, + color: XColor.Tertiary, + tooltip: XResourceIDs.crop, + name: XAvatarUploadActionButtonName.Crop, + }, + // + // Clear ... + { + index: 2, + color: XColor.Warning, + icon: XIconNames.clear, + tooltip: XResourceIDs.clear, + name: XAvatarUploadActionButtonName.Clear, + }, +]; + +// +//#region Inside Card Container ... +export interface IXAvatarUploadContainer extends IXCardFileFileDropContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + color: XStandardType; + foregroundColor: XStandardType; + + // + placeHolderAvatar: XStandardType; + + // + actions: XStandardType>; + + // + avatarChange: EventEmitter; +} + +@Injectable({ + providedIn: 'root', +}) +export class XAvatarUploadContainer + extends XCardFileFileDropContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider: Subject = + new Subject(); + readonly cssClass: XStandardType>; + + // + readonly color: XStandardType = XColorWithBrightness.Light; + readonly foregroundColor: XStandardType = false; + + // + readonly placeHolderAvatar: XStandardType; + + // + readonly actions: XStandardType> = + defaultAvatarUploadActionButtons; + + // + readonly avatarChange: EventEmitter = new EventEmitter(); + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XAvatarUploadContainerComponent + extends XCardFileFileDropContainerComponent + implements IXAvatarUploadContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + color: XStandardType = this.propertyProvider.color; + + @Input() + foregroundColor: XStandardType = + this.propertyProvider.foregroundColor; + + // + @Input() + placeHolderAvatar: XStandardType = + this.propertyProvider.placeHolderAvatar; + + // + @Input() + actions: XStandardType> = + this.propertyProvider.actions; + + // + @Output() + avatarChange: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + public imageCropperService: XImageCropperService, + protected propertyProvider: XAvatarUploadContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXAvatarUploadContainer + ): Observable { + return super + .extractProps(container ? container : this) + .pipe( + concatMap((superData) => { + return forkJoin({ + // + cssClass: this.getValue( + container ? container.cssClass : this.cssClass + ), + color: this.getValue(container ? container.color : this.color), + foregroundColor: this.getValue( + container ? container.foregroundColor : this.foregroundColor + ), + placeHolderAvatar: this.getValue( + container ? container.placeHolderAvatar : this.placeHolderAvatar + ), + actions: this.getValue( + container ? container.actions : this.actions + ), + }).pipe( + map((data) => { + return { + superData, + data, + }; + }) + ); + }) + ) + .pipe( + map((info) => { + return { + // + ...info.superData, + + // + ...info.data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + avatarChange: this.avatarChange, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-button\x-button.component.html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ getResourceValue(title) | async }} + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-button\x-button.component.scss + +:host ::ng-deep .x-button-icon-wrapper { + vertical-align: middle; + display: inline-grid; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-button\x-button.component.ts + +import { + nameof, + isNullOrUndefined, + XColorWithBrightness, +} from 'x-framework-core'; +import { + XButtonType, + XButtonAction, + XButtonActionModel, + XButtonIconPosition, + XButtonContainerComponent, +} from './x-button.typings'; +import { concatMap, map, switchMap } from 'rxjs/operators'; +import { of, Observable, Subscription, Subject } from 'rxjs'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; +import { XSlotLayout, XSlotName } from '../x-slotter/x-slotter.typings'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-button', + templateUrl: './x-button.component.html', + styleUrls: ['./x-button.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XButtonComponent extends XButtonContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + readonly XSlotNames = Object.assign({}, XSlotName); + readonly XButtonTypes = Object.assign({}, XButtonType); + readonly XSlotLayouts = Object.assign({}, XSlotLayout); + readonly XButtonIconPositions = Object.assign({}, XButtonIconPosition); + //#endregion + + // + //#region LifeCycle ... + onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region Action Provider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + + // + //#region is IconPositionChanged ... + const isIconPositionChanged = changeKeys.includes( + nameof('iconPosition') + ); + if (isIconPositionChanged && isNullOrUndefined(this.iconPosition)) { + // + this.iconPosition = XButtonIconPosition.Start; + this.detectChanges(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region UI Providers ... + hasIcon() { + return this.notNullOrUndefinedValue(this.icon); + } + + hasTitle() { + return this.notNullOrUndefinedValue(this.title); + } + + useSlot() { + return this.hasIcon().pipe( + concatMap((hasIcon) => + this.hasTitle().pipe(map((hasTitle) => hasIcon && hasTitle)) + ) + ); + } + + isVerticalLayout() { + return this.useSlot().pipe( + concatMap((useSlot) => { + // + if (!useSlot) { + return of(false); + } + + // + return this.getValue(this.iconPosition).pipe( + map( + (iPos) => + iPos === this.XButtonIconPositions.Above || + iPos === this.XButtonIconPositions.Below + ) + ); + }) + ); + } + + iconSlotName(): Observable { + return this.getValue(this.iconPosition).pipe( + map((iPos) => { + // + switch (iPos) { + // + case this.XButtonIconPositions.Start: + return this.XSlotNames.START; + + // + case this.XButtonIconPositions.End: + return this.XSlotNames.END; + + // + case this.XButtonIconPositions.Above: + return this.XSlotNames.TOP; + + // + case this.XButtonIconPositions.Below: + return this.XSlotNames.BOTTOM; + + // + default: + return this.XSlotNames.START; + } + }) + ); + } + + titleSlotName(): Observable { + return this.iconSlotName().pipe( + map((iSlot) => { + // + switch (iSlot) { + // + case this.XSlotNames.START: + return this.XSlotNames.END; + + // + case this.XSlotNames.END: + return this.XSlotNames.START; + + // + case this.XSlotNames.TOP: + return this.XSlotNames.BOTTOM; + + // + case this.XSlotNames.BOTTOM: + return this.XSlotNames.TOP; + + // + default: + return this.XSlotNames.CENTER; + } + }) + ); + } + + getButtonColor() { + return this.hasIcon().pipe( + switchMap((hasIcon) => + this.getValue(hasIcon ? XColorWithBrightness.Transparent : this.color) + ) + ); + } + //#endregion + + // + //#region UI Handlers ... + handleButtonClicked(event?: Event) { + // + if (this.uiDisabled || this.loading) { + return; + } + + // + this.clicked.emit(event); + } + //#endregion + + // + //#region Actions ... + //#endregion + + // + //#region Private ... + // + //#region ActionProvider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + // Handle Action Provider ... + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XButtonAction.Click: + this.clicked.emit(); + break; + + // + case XButtonAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-button\x-button.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XIconModule } from '../x-icon/x-icon.module'; +import { XButtonComponent } from './x-button.component'; +import { XSlotterModule } from '../x-slotter/x-slotter.module'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XButtonComponent], + imports: [CommonModule, XIconModule, XSlotterModule, XBaseComponentModule], + exports: [ + CommonModule, + XIconModule, + XSlotterModule, + XButtonComponent, + XBaseComponentModule, + ], +}) +export class XButtonModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-button\x-button.typings.ts + +import { + Input, + Output, + Inject, + NgZone, + Renderer2, + Component, + Injectable, + ElementRef, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XStandardType, + XOneOrManyType, + XColorIdentifier, + XResourceIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { map } from 'rxjs/operators'; +import { + IXBaseContainer, + XBaseContainerComponent, +} from '../classes/i-x-base.container'; +import { forkJoin, Observable, Subject } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XIconIdentifier } from '../x-icon/x-icon.typings'; +import { XBaseContainer } from './../classes/i-x-base.container'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export enum XButtonType { + Raised = 'raised', + Icon = 'icon', +} + +export type XButtonTypeIdentifier = XButtonType | string; + +export enum XButtonIconPosition { + Above = 'above', + Start = 'strat', + Below = 'below', + End = 'end', +} + +export type XButtonIconPositionIdentifier = XButtonIconPosition | string; + +export enum XButtonAction { + Click = 'click', + Refresh = 'refresh', +} + +export type XButtonActionIdentifier = XButtonAction | string; + +export interface XButtonActionModel + extends XBaseActionModel {} + +// +//#region Container ... +export interface IXButtonContainer extends IXBaseContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + type: XStandardType; + title: XStandardType; + icon: XStandardType; + iconPosition: XStandardType; + + // + color: XStandardType; + foregroundColor: XStandardType; + iconColor: XStandardType; + + // + clicked: EventEmitter; +} + +@Injectable({ + providedIn: 'root', +}) +export class XButtonContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + readonly actionProvider: Subject = + new Subject(); + readonly cssClass: XStandardType>; + + // + readonly type: XStandardType = XButtonType.Raised; + readonly title: XStandardType; + readonly icon: XStandardType; + readonly iconPosition: XStandardType = + XButtonIconPosition.Start; + + // + readonly color: XStandardType = XColorWithBrightness.Light; + readonly foregroundColor: XStandardType = false; + readonly iconColor: XStandardType = + XColorWithBrightness.Light; + + // + readonly clicked: EventEmitter = new EventEmitter(); + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XButtonContainerComponent + extends XBaseContainerComponent + implements IXButtonContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + type: XStandardType = this.propertyProvider.type; + + @Input() + title: XStandardType = this.propertyProvider.title; + + @Input() + icon: XStandardType = this.propertyProvider.icon; + + @Input() + iconPosition: XStandardType = + this.propertyProvider.iconPosition; + + // + @Input() + color: XStandardType = this.propertyProvider.color; + + @Input() + foregroundColor: XStandardType = + this.propertyProvider.foregroundColor; + + @Input() + iconColor: XStandardType = this.propertyProvider.iconColor; + + // + @Output() + clicked: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XButtonContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXButtonContainer + ): Observable { + return forkJoin({ + // + cssClass: this.getValue(container ? container.cssClass : this.cssClass), + + // + type: this.getValue(container ? container.type : this.type), + title: this.getValue(container ? container.title : this.title), + icon: this.getValue(container ? container.icon : this.icon), + iconPosition: this.getValue( + container ? container.iconPosition : this.iconPosition + ), + + // + color: this.getValue(container ? container.color : this.color), + foregroundColor: this.getValue( + container ? container.foregroundColor : this.foregroundColor + ), + iconColor: this.getValue( + container ? container.iconColor : this.iconColor + ), + }).pipe( + map((data) => { + return { + // + ...data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + clicked: this.clicked, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-card\x-card.component.html + + + + + +
+ + + + +
+ + + + {{ getResourceValue(title) | async }} + + + + + {{ getResourceValue(subTitle) | async }} + + + +
+ + + + + + + + + + + + + + + + + + +
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-card\x-card.component.scss + +// +@import '../theme/theme.scss'; + +// +:host::ng-deep.x-card.no-text { + .mat-card-header-text { + display: none; + visibility: hidden; + } +} + +// +.x-card { + @extend .p-0; + width: inherit; + height: var(--height, inherit); +} + +.x-card.page-card { + @include push-auto(); + @include applyWidthHeight(); + @include m-tb($d-l); + @include animation('fadeInUp'); + + &.mobile { + box-sizing: border-box; + width: $d-100p; + max-width: $d-100p; + min-width: $d-100p; + @include m(0); + margin-bottom: $d-l; + } +} + +.x-header, +.x-content, +.x-actions, +.x-footer { + @include p($d-md); +} + +.x-footer { + margin: 0px !important; +} + +.x-image { + width: 100% !important; + @extend .m-0; +} + +.x-actions { + margin: 0 !important; +} + +.x-header:empty, +.x-footer:empty, +.x-actions:empty { + display: none; + visibility: hidden; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-card\x-card.component.ts + +import { + XCardAction, + XCardActionModel, + XCardContainerComponent, +} from './x-card.typings'; +import { Subject, Subscription } from 'rxjs'; +import { concatMap, map } from 'rxjs/operators'; +import { nameof, isNullOrEmptyString } from 'x-framework-core'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-card', + templateUrl: './x-card.component.html', + styleUrls: ['./x-card.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XCardComponent extends XCardContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + //#endregion + + // + //#region LifeCycles ... + onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region Action Provider Changed ... + // + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region UI Providers ... + isNoTextCard() { + return this.getResourceValue(this.title).pipe( + concatMap((title) => + this.getResourceValue(this.subTitle).pipe( + map((subTitle) => { + return { + title, + subTitle, + }; + }), + map((object) => { + return ( + isNullOrEmptyString(object.title) && + isNullOrEmptyString(object.subTitle) + ); + }) + ) + ) + ); + } + //#endregion + + // + //#region Private ... + // + //#region ActionProvider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + // Handle Action Provider ... + this.ACTION_PROVIDER_SUBSCRIPTION = this.applyTakeUntilWrapper( + this.actionProvider.asObservable() + ).subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XCardAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-card\x-card.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XCardComponent } from './x-card.component'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [ + XCardComponent + ], + imports: [CommonModule, XBaseComponentModule], + exports: [XBaseComponentModule, XCardComponent], +}) +export class XCardModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-card\x-card.typings.ts + +import { + Input, + Inject, + NgZone, + Output, + Component, + Renderer2, + ElementRef, + Injectable, + EventEmitter, + ChangeDetectorRef, + TemplateRef, +} from '@angular/core'; +import { + XStandardType, + XOneOrManyType, + XColorIdentifier, + XResourceIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { map } from 'rxjs/operators'; +import { + XBaseContainer, + IXBaseContainer, + XBaseComponentContainer, + XBaseContainerComponent, + IXBaseComponentContainer, + XBaseComponentContainerComponent, +} from '../classes/i-x-base.container'; +import { forkJoin, Observable, Subject } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export enum XCardAction { + Refresh = 'refresh', +} + +export type XCardActionIdentifier = XCardAction | string; + +export interface XCardActionModel + extends XBaseActionModel {} + +// +//#region Container ... +export interface IXCardContainer extends IXBaseContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + color: XStandardType; + foregroundColor: XStandardType; + + // + showHeader: XStandardType; + title: XStandardType; + subTitle: XStandardType; + avatarUrl: XStandardType; + avatarTemplate: TemplateRef; + + // + imageUrl: XStandardType; + imageDescription: XStandardType; + + // + isInPage: XStandardType; + + // + showContent: XStandardType; + showActions: XStandardType; + showFooter: XStandardType; +} + +@Injectable({ + providedIn: 'root', +}) +export class XCardContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + readonly actionProvider: Subject = + new Subject(); + + readonly cssClass: XStandardType>; + + // + readonly color: XStandardType = XColorWithBrightness.Light; + readonly foregroundColor: XStandardType = false; + + // + readonly showHeader: XStandardType = false; + readonly title: XStandardType; + readonly subTitle: XStandardType; + readonly avatarUrl: XStandardType; + readonly avatarTemplate: TemplateRef; + + // + readonly imageUrl: XStandardType; + readonly imageDescription: XStandardType; + + // + readonly isInPage: XStandardType = false; + + // + readonly showContent: XStandardType = true; + readonly showActions: XStandardType = false; + readonly showFooter: XStandardType = false; + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XCardContainerComponent + extends XBaseContainerComponent + implements IXCardContainer +{ + // + //#region Props ... + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + color: XStandardType = this.propertyProvider.color; + + @Input() + foregroundColor: XStandardType = + this.propertyProvider.foregroundColor; + + // + @Input() + showHeader: XStandardType = this.propertyProvider.showHeader; + + @Input() + title: XStandardType = this.propertyProvider.title; + + @Input() + subTitle: XStandardType = this.propertyProvider.subTitle; + + @Input() + avatarUrl: XStandardType = this.propertyProvider.avatarUrl; + + @Input() + avatarTemplate: TemplateRef = this.propertyProvider.avatarTemplate; + + // + @Input() + imageUrl: XStandardType = this.propertyProvider.imageUrl; + + @Input() + imageDescription: XStandardType = + this.propertyProvider.imageDescription; + + // + @Input() + isInPage: XStandardType = this.propertyProvider.isInPage; + + // + @Input() + showContent: XStandardType = this.propertyProvider.showContent; + + @Input() + showActions: XStandardType = this.propertyProvider.showActions; + + @Input() + showFooter: XStandardType = this.propertyProvider.showFooter; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XCardContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXCardContainer + ): Observable { + return forkJoin({ + // + cssClass: this.getArrayValue( + container ? container.cssClass : this.cssClass + ), + + // + color: this.getValue(container ? container.color : this.color), + foregroundColor: this.getValue( + container ? container.foregroundColor : this.foregroundColor + ), + + // + showHeader: this.getValue( + container ? container.showHeader : this.showHeader + ), + title: this.getValue(container ? container.title : this.title), + subTitle: this.getValue(container ? container.subTitle : this.subTitle), + avatarUrl: this.getValue( + container ? container.avatarUrl : this.avatarUrl + ), + avatarTemplate: this.getValue( + container ? container.avatarTemplate : this.avatarTemplate + ), + + // + imageUrl: this.getValue(container ? container.imageUrl : this.imageUrl), + imageDescription: this.getValue( + container ? container.imageDescription : this.imageDescription + ), + + // + isInPage: this.getValue(container ? container.isInPage : this.isInPage), + + // + showContent: this.getValue( + container ? container.showContent : this.showContent + ), + showActions: this.getValue( + container ? container.showActions : this.showActions + ), + showFooter: this.getValue( + container ? container.showFooter : this.showFooter + ), + }).pipe( + map((data) => { + return { + // + ...data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + }; + }) + ); + } + //#endregion +} +//#endregion + +// +//#region ComponentContainer ... +type TXComponentCardContainer = { + [TKey in keyof Pick< + IXCardContainer, + | 'actionProvider' + | 'cssClass' + | 'color' + | 'foregroundColor' + | 'title' + | 'subTitle' + | 'showFooter' + | 'showActions' + > as `card${Capitalize}`]: IXCardContainer[TKey]; +}; + +export interface IXComponentCardContainer + extends TXComponentCardContainer, + IXBaseComponentContainer { + isInPage: XStandardType; + wrapWithCard: XStandardType; +} + +@Injectable({ + providedIn: 'root', +}) +export class XComponentCardContainer + extends XBaseComponentContainer + implements Readonly +{ + // + //#region Props ... + // + readonly isInPage: XStandardType = false; + readonly wrapWithCard: XStandardType = false; + + // + readonly cardActionProvider: Subject = + this.propertyProvider.actionProvider; + readonly cardCssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + readonly cardColor: XStandardType = + this.propertyProvider.color; + readonly cardForegroundColor: XStandardType = + this.propertyProvider.foregroundColor; + + // + readonly cardTitle: XStandardType = + this.propertyProvider.title; + readonly cardSubTitle: XStandardType = + this.propertyProvider.subTitle; + + // + readonly cardShowFooter: XStandardType = + this.propertyProvider.showFooter; + readonly cardShowActions: XStandardType = + this.propertyProvider.showActions; + //#endregion + + // + //#region Constructor ... + constructor(protected propertyProvider: XCardContainer) { + super(); + } + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XComponentCardContainerComponent + extends XBaseComponentContainerComponent + implements IXComponentCardContainer +{ + // + //#region Props ... + // + @Input() + isInPage: XStandardType = this.propertyProvider.isInPage; + + @Input() + wrapWithCard: XStandardType = this.propertyProvider.wrapWithCard; + + // + @Input() + cardActionProvider: Subject = + this.propertyProvider.cardActionProvider; + + @Input() + cardCssClass: XStandardType> = + this.propertyProvider.cardCssClass; + + // + @Input() + cardColor: XStandardType = this.propertyProvider.cardColor; + + @Input() + cardForegroundColor: XStandardType = + this.propertyProvider.cardForegroundColor; + + // + @Input() + cardTitle: XStandardType = + this.propertyProvider.cardTitle; + + @Input() + cardSubTitle: XStandardType = + this.propertyProvider.cardSubTitle; + + // + @Input() + cardShowFooter: XStandardType = this.propertyProvider.cardShowFooter; + + @Input() + cardShowActions: XStandardType = + this.propertyProvider.cardShowActions; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XComponentCardContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXComponentCardContainer + ): Observable { + return forkJoin({ + // + isInPage: this.getValue(container ? container.isInPage : this.isInPage), + wrapWithCard: this.getValue( + container ? container.wrapWithCard : this.wrapWithCard + ), + cardCssClass: this.getValue( + container ? container.cardCssClass : this.cardCssClass + ), + cardColor: this.getValue( + container ? container.cardColor : this.cardColor + ), + cardForegroundColor: this.getValue( + container ? container.cardForegroundColor : this.cardForegroundColor + ), + cardTitle: this.getValue( + container ? container.cardTitle : this.cardTitle + ), + cardSubTitle: this.getValue( + container ? container.cardSubTitle : this.cardSubTitle + ), + cardShowFooter: this.getValue( + container ? container.cardShowFooter : this.cardShowFooter + ), + cardShowActions: this.getValue( + container ? container.cardShowActions : this.cardShowActions + ), + }).pipe( + map((data) => { + return { + // + ...data, + + // + cardActionProvider: container + ? container.cardActionProvider + : this.cardActionProvider, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-color\x-color.component.html + +
+ +
+ {{ + item.key + }} +
+ + +
+ + + +
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-color\x-color.component.scss + +:host { + --size: 32px; +} + +.x-color { + direction: ltr !important; + width: 100%; + display: flex; +} + +.x-color-title { + flex: 1 1 auto; + + margin: { + top: auto; + bottom: auto; + } + + max-width: 230px; +} + +.x-spacer { + flex: 1 1 auto; +} + +.x-color-value-button { + background-color: transparent; +} + +.x-color-value { + opacity: 0.7; + width: var(--size); + border-radius: 50%; + height: var(--size); + border-style: var(--border-style); + border-color: var(--border-color); + border-width: var(--border-width); +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-color\x-color.component.ts + +import { + nameof, + parseXColor, + isNullOrEmptyString, + getXColorCssVariableName, +} from 'x-framework-core'; +import { + XColorAction, + XColorCssVars, + XColorActionModel, + XColorContainerComponent, +} from './x-color.typings'; +import { Subject, Subscription } from 'rxjs'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; + +@Component({ + selector: 'x-color', + templateUrl: './x-color.component.html', + styleUrls: ['./x-color.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XColorComponent extends XColorContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + //#endregion + + // + //#region LifeCycles ... + async onInit() { + super.onInit(); + + // + await this.applyDefaults(); + } + + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region Size Changed ... + const isSizeChanged = changeKeys.includes(nameof('size')); + if (isSizeChanged) { + await this.applySize(); + } + //#endregion + + // + //#region borderWidth Changed ... + const isBorderWidthChanged = changeKeys.includes( + nameof('borderWidth') + ); + if (isBorderWidthChanged) { + await this.applyBorderWith(); + } + //#endregion + + // + //#region borderStyle Changed ... + const isBorderStyleChanged = changeKeys.includes( + nameof('borderStyle') + ); + if (isBorderStyleChanged) { + await this.applyBorderStyle(); + } + //#endregion + + // + //#region borderColor Changed ... + const isBorderColorChanged = changeKeys.includes( + nameof('borderColor') + ); + if (isBorderColorChanged) { + await this.applyBorderColor(); + } + //#endregion + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region UI Handlers ... + async handleCopyToClipboard(value: string) { + this.managerService.saveToClipboard(value); + } + //#endregion + + // + //#region Private ... + // + //#region ActionProvider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + switch (model.action) { + // + case XColorAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + // + //#region Appliers ... + private async applyDefaults() { + // + await this.applySize(); + await this.applyBorderWith(); + await this.applyBorderColor(); + await this.applyBorderStyle(); + } + + private async applySize() { + // + let size = await this.getValueAsync(this.size); + size = !size || size === 0 ? 32 : size; + + // + this.setCssVar(XColorCssVars.Size, `${size}px`); + this.detectChanges(); + } + + private async applyBorderWith() { + // + let borderWidth = await this.getValueAsync(this.borderWidth); + + // + this.setCssVar(XColorCssVars.BorderWidth, `${borderWidth}px`); + this.detectChanges(); + } + + private async applyBorderStyle() { + // + let borderStyle = await this.getValueAsync(this.borderStyle); + + // + this.setCssVar(XColorCssVars.BorderStyle, `${borderStyle}`); + this.detectChanges(); + } + + private async applyBorderColor() { + // + const color = await this.getValueAsync(this.borderColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XColorCssVars.BorderColor, `var(${bgColor})`); + this.detectChanges(); + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-color\x-color.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XColorComponent } from './x-color.component'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XColorComponent], + imports: [CommonModule, XBaseComponentModule], + exports: [CommonModule, XColorComponent, XBaseComponentModule], +}) +export class XColorModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-color\x-color.typings.ts + +import { + Input, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + ChangeDetectorRef, +} from '@angular/core'; +import { + XBorderStyle, + XStandardType, + XColorKeyValue, + XOneOrManyType, + XColorIdentifier, + XColorWithBrightness, + XBorderStyleIdentifier, +} from 'x-framework-core'; +import { map } from 'rxjs/operators'; +import { + XBaseContainer, + IXBaseContainer, + XBaseContainerComponent, +} from '../classes/i-x-base.container'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export enum XColorAction { + Refresh = 'refresh', + SetSelectedColor = 'set_selected_color', +} + +export type XColorActionIdentifier = XColorAction | string; + +export interface XColorActionModel + extends XBaseActionModel {} + +export enum XColorCssVars { + Size = 'size', + BorderWidth = 'border-width', + BorderColor = 'border-color', + BorderStyle = 'border-style', +} + +// +//#region Container ... +export interface IXColorContainer extends IXBaseContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + showLabel: XStandardType; + labelColor: XStandardType; + + // + size: XStandardType; + value: XStandardType; + + // + borderWidth: XStandardType; + borderStyle: XStandardType; + borderColor: XStandardType; +} + +@Injectable({ + providedIn: 'root' +}) +export class XColorContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + readonly actionProvider: Subject = + new Subject(); + + readonly cssClass: XStandardType>; + + // + readonly showLabel: XStandardType = true; + readonly labelColor: XStandardType = + XColorWithBrightness.Dark; + + // + readonly size: XStandardType = 32; + + // + readonly value: XStandardType; + + // + readonly borderWidth: XStandardType = 1; + readonly borderStyle: XStandardType = + XBorderStyle.Solid; + readonly borderColor: XStandardType = + XColorWithBrightness.LightShade; + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XColorContainerComponent + extends XBaseContainerComponent + implements IXColorContainer +{ + // + //#region Props ... + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + showLabel: XStandardType = this.propertyProvider.showLabel; + + @Input() + labelColor: XStandardType = + this.propertyProvider.labelColor; + + // + @Input() + size: XStandardType = this.propertyProvider.size; + + @Input() + value: XStandardType = this.propertyProvider.value; + + // + @Input() + borderWidth: XStandardType = this.propertyProvider.borderWidth; + + @Input() + borderStyle: XStandardType = + this.propertyProvider.borderStyle; + + @Input() + borderColor: XStandardType = + this.propertyProvider.borderColor; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XColorContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXColorContainer + ): Observable { + return forkJoin({ + // + cssClass: this.getArrayValue( + container ? container.cssClass : this.cssClass + ), + + // + showLabel: this.getValue( + container ? container.showLabel : this.showLabel + ), + labelColor: this.getValue( + container ? container.labelColor : this.labelColor + ), + + // + size: this.getValue(container ? container.size : this.size), + + // + value: this.getValue(container ? container.value : this.value), + + // + borderWidth: this.getValue( + container ? container.borderWidth : this.borderWidth + ), + borderStyle: this.getValue( + container ? container.borderStyle : this.borderStyle + ), + borderColor: this.getValue( + container ? container.borderColor : this.borderColor + ), + }).pipe( + map((data) => { + return { + // + ...data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-color-picker\x-color-picker.component.html + + + + + + + + +
+ +
+ + +
+ +
+
+
+ + + + +
+ +
+ +
+ +
+
+ + +
+ +
+ +
+ + +
+
+ + +
+ + +
+ +
+ +
+
+ + +
+ +
+
+
+
+ + +
+ + +
+ + {{ hexValue }} + + +
+ +
+
+ + +
+ + {{ rgbaValue }} + + +
+ +
+
+ + +
+ + {{ hslaValue }} + + +
+ +
+
+
+
+
+ + +
+
+ +
+ +
+ + +
+ +
+
+
+ + +
+
+ + + {{ getResourceValue(presetColorsLable) | async }} + + +
+
+
+
+
+
+ + + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-color-picker\x-color-picker.component.scss + +:host { + display: block; + width: inherit; + --cursor-color: val(--x-color-dark); +} + +:host ::ng-deep .x-on-top { + z-index: 10000; +} + +@mixin boder-radius($radius) { + -moz-border-radius: $radius; + -webkit-border-radius: $radius; + border-radius: $radius; + -khtml-border-radius: $radius; +} + +.x-force-ltr { + direction: ltr !important; +} + +.x-color-picker { + // + margin: 0; + font-size: 11px; + + // + box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + + // + height: auto; + width: inherit; + cursor: default; + min-width: 200px; + + // + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.x-color-palette { + // + width: 100%; + border: none; + cursor: pointer; + min-height: 130px; + margin-bottom: 8px; + background-size: 100% 100%; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOYAAACCCAYAAABSD7T3AAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4AIWDwksPWR6lgAAIABJREFUeNrtnVuT47gRrAHN+P//Or/61Y5wONZ7mZ1u3XAeLMjJZGZVgdKsfc5xR3S0RIIUW+CHzCpc2McYo7XGv3ex7UiZd57rjyzzv+v+33X/R/+3r/f7vR386Y+TvKNcf/wdhTLPcv9qU2wZd74uth0t1821jkIZLPcsI/6nWa4XvutquU0Z85mnx80S/ZzgpnLnOtHNt7/ofx1TKXcSNzN/7qbMQ3ju7rNQmMYYd/4s2j9aa+P+gGaMcZrb1M/tdrvf7/d2v99P9/t93O/3cbvdxu12G9frdVwul3E+n8c///nP+2+//Xb66aefxl//+tfx5z//2YK5Al2rgvf4UsbpdGrB52bAvArXpuzjmiqAVSGz5eDmGYXzhbAZmCrnmzddpUU+8Y1dAOYeXCtDUwVwV7YCGH6uAmyMcZ9l5vkUaBPGMUZ7/J5w/792/fvv9Xq93263dr/fTxPECeME8nK5jM/Pz/HTTz/dv337dvrll1/GP/7xj/G3v/1t/OUvfwkVswongjdOp9PzH3U3D3zmWGnZVXn4jCqs7wC2BKP4/8tAzkZsoWx6XrqeHZymvp4ABCBJhTQwKfDT8gzrZCIqi5AhiACjBfEB2rP8/X63MM7f6/V6v9/v7Xa7bYC83W7jcrlsVHIq5ffv30+//fbb+OWXX8ZPP/00/v73v4+ff/75JSvbeu+bL2WMMaFbAlpBNM85QX+ct6qoSqkPAwuQlBVKqGNFSUOAA3Bmu7gC5hNOd15nSwvAOUW7C4giUCV8Sgn5L9hNFIqTsp0GxI0ysioyjAjkY/tGJVEpz+fz+OWXX+7fv38//f777+Pbt2/j119/HT///PP49ddfx8fHRwrmTjV779EXu2px2xhjwtdJZQcAWQIPLPISsMJaSwiD8gzIKrwSyATE5j5nAbR5c1dBUwBlsEWW0h6LqiYsqFPAQxCyRZ3wOSARxmlXMX5k64pQfvv27f75+dk+Pj5OHx8f4/v37+Pbt2/jt99+G9++fRsfHx/jcrmUFLO31gYDWblxRIs/TqfT7ousxJsAxXA2Gc7TA9XdgfdoHbFsj76X2+1WArgI1ageGwA3qupqoHsmcbI6Fu93quggFa9d7LeDtgKfAFHBJ+NEByIkcJ5KervdTmhhGcgJJSZ5vn//fj+fz+18Pp8+Pz/H5+fnmGD+/vvv4/v37+Pj42N8fn6O2+1Ws7JjjP6wraMI5E4RZ8x2vV5TSwkquotV7/d7Tz6HFWsD/qNcdw0CQ3q/321c686TwDVIdbuy73zNldhSHb8I2klZznm+InBS4U6n0302aBFsLhHDAKJVJVglfI9jhvu53W53sLANYNxAiDA6MCeUHx8f9+v12i6XS7tcLqcZW57P5yeY8/fz83Ocz+fnsSmYUyknWEG85WBst9stzSLyMdfr9Qi08iY15UZ0LlDGLhR3o5zK2j7OPUTD0E+nU3tk7Xb/16NFbhloAMuY1zjLUOO3BKeIDe+Z8s3/J4gFo4TM5jPmuRg28foUKKVSwo16TgA5npywcWLHgYl/Pz8/73/605/ab7/91m63W7tcLie0sZj4mao5gTyfz88E0f1+j8EcYzwTPEG2cqjyfHNF0M8fuqEiaOVnRzZZQNh5fwQyHg/HDGfJo89Q1zb/quu5XC6773I2XKfTqd/v9+d3wuqWva/YTdUdEV3fhIv/Viyps6YE3x3r43K5bJQS66zaxVGFsvd+//j4aF+/fm3fv39vt9utff36tf3+++/tdrudvn37ZuNLBaaCMgUzC+rZRiFowxUuJI8YMqcCp9Opq5vagaYU6lGJA1XQqejchw6Cj0Gw5nYBrGw01A2O206n04BGouNNyTfp/FwElhUey6nXrIKw7QQWddxuN2ldL5fL839gSPF8ahu/JvBO48CPSuqMf8Vp9/P53L58+dLu93s7n8/tfr8/39/v9/b5+TkhPJ3P56mQ436/j+/fv+/iSgbzer0+AZx/5+88bv6OMda6S5z6kd21fYC9dxv7cIJJ2d9AOS30fPMzyHiTM8B4DF6XUlYHp4KQW3W+1t77MNB1vGHxWq7Xa7vf78+y5/N5A+H1et29xuP5dbYtyaRu4AksbPq6936fjRzXRxBbPr/b+b18+fKljTHaBBBfn8/n0/1+H1++fBnn8zm0sB8fH5u4cr5GuBhMVk0EEn9RsctgVhM+ixlJtMA23R8B6yysAstBOgFXIKKCMIgToMqNEu2fYMH7ztc732dQKkCj1ytAZtY0Kx8pIr8GGJ+AT3V+2Hirhl++fBmXy2Wz73w+b17P8p+fn8/tUwGVleVkTyUb68DkfayWY4zxNRihU4EpLJPZVrK+u7J4/mgfKqeLW9X2REWlItL1diynbDDb3+jXgYjQqn0rrxWc+NkILP7F7xIbMvx7vV53x40xnlbWJF12ZSag/N0pW6t+ZzmOMzHjajKwDfond78zYTdfq18up97zr2q8v3IioBprRtBl0EZ9og5WBRGOdOHjIjXF7UotFbgOWnXzIJyzYvjG5IYgsmMOxHkz8OsMSrVNWeq5T8DaOcbEv1Od5rbs9aO7YvMet63EkF++fMExq+MRl4/L5bLZN/+ez+fnZ6KazuMqXSQVO5spJXflHAIzes/xJseckRJiDMog9d6VfRrqXMr6KpVV27jRwJacGovOAM1zMdQMnwK1AubK63kdCChvI1C7g0z9nf/D+Xze2Vj8H7Gx4P9duQlsYCrqyN8XqG3Hm/10Oj3jw/n+crlstuM+jPmmxT2dTuPz83Pzt2pn1XsEHX/bnPaVqVmh0xwOt0o6XLLAHePUU203wHfcrspCwmV3TryB5s0Mseeg97x/BwzCjBlbB+pRAPla0BVQuT6V6QHdBlj3d0KG147b+DqxQeUymDO43W4dQar+TIjwmAd0z8/h65vf0/yLv3Pb5XLpru/ydDo9s7ET0I+Pj6dKK9VUEIeKWQWPAOrJ8LKd4vE+t91Y3e7UFlWatg2VwJnb+HPmtvm/sfK59/OaWF3x/eP1UPHvA5DDYDpYXfb0drv1V2DkBkxtw/tEWVVlXWdC9pFYs5/jfh9dS/16vW7s6lTG+TfqsxSJHxkXXq/Xdr1eu4LsfD6P3vsT3N77DkL+zPm5jSdKL4zR3AxQd6rHkLkYlSowsrq7znzu6wSwdsMJOXmA5fBcjxtgMGBYHlr5zokhtsMCTgXLQOW4XC6dEyEMprL8mAQzXRgduix2yZzorxkYsDn3hB1VeMLGsXsVtgl2pW8S3svk0vw7R4hNaHvv4cACl5HFzwIH0Kc6zu4XjDPR/jpAVxWzO1Xk2DDb3vTcxeGU1iWZHkmIDWziWKvirCJ4Dravs6IJ/GG6cTqWdXDy+fArQDVVkLqkVjAoZIITdmmIqXwqa95N3+MGYoZQdRVNO53Y1xRkhO16vY7eu507Ca9lJnbGpxOemQhSw/AQsmmp5zU9BiU8G6wvX76M6/U6Pj4+do0Bz4CpgiknTUeDqwlKBmg3u4OVjrZ1A+rAcgaejWq6eJCvCYFDONSwOgHX4EQRw8lxbzDOdEK6gZ3Hk1b+8g2o1JFtKXyv/fEdTXuWjWXdAZiBp6ADeDrCFiim7B6ZFneeI7Gvm/PMkUDX67W7xI8b0D7/v8dA9qfN5oaCf74WZjH0mf1cmfY1Y0JUFmVrTWu8uzkNcLtEj7u5FXBTkfC6GOA5q8YMxO8KVvF6sAVGdcrUbsKODcQKkLMOMdmlxum642YrPm26AlhZW1YB1R+rrGswE8TaYAWeUMxdf+WjwSvZ2Ef3ytOyfn5+PpVPAaqOn43MtNBqvmjjxbjM4lZjZY4gqNMI5ktaW/sYKNwS+9lFQzGihmMCKPa7+Z0V6Eb0GRmobtpX8JljWu5FMLN5ja6hG9kwQgZqf5+1NH5UxzkFReCdWhJ8XdlGUkxO7HRlYRm4mVO43W7ter12TPJEw/rmEN3L5SKHIWZg9mz+pUoKOYq5bJTJdX2gme1UcxMZQFaEQIlHct32M+Y1BzGkGuzfiyAN9z+ugplZ1symCrDCYYkGxDTpI9RzBy0rHyeDUC1nWaeUaD9n4xkNyYMBDZtzZ3B++fJlY21XFDOcARJlabOyiS3uCpLI9jrZjCDkaVvcCCjwognKShWdzXZWlZMvVTgD8LpqlCLrqgbcB+qYwrgKYpT0ccCqbKyCValkEabn/FynogCrPKfqf51xJ7sGB2ZXcZmxoSOztjx300DZi7a0/2AIR0UlBag9SuDw6KcAzlaB7vHZvWpjK90dyrq6bKyDUZQbR0B05biLQkHIcSUmgIK+SwuqgHCnoio2RQU1yj+BnBy9pphVKLGyC7ZzFK1pxWK+E8IhVCWLN/uLtnUU4ayoYLoaANz8FdtaSvY4pV0BEW2ls61czqllBKpTyKgMAhrZ1cdc1RROtPmvWNkdcKZ7ZKxaWjiPLJMpp7OZKxA+rqG/oJLjxf0pnJlqLoDZo3gyU0mKGys2taKecj/d1C+rJSplBqlTyAqgR+D8KjKlmRL2gtUcAdCtsL+ijCNT1oqqqkH2OHEbG5sDFnUg5Aa+yLou2VU1ptj1S2ZQqv1ORZN9IWzRfgaRBxKoBE8UWyqlJFtrIc0AxNjSjed99CTY/XDfSzCz5M0IZoVEsWnPFNTsl8ooVC1TzbGgqFZNDSgVwKK+1sGDMKqxZCWGVMDysiEr1jVSQJUYwj5iHOlThdHt44SQg9CN+nl8D90NMIgAdgr46JqRiR9I8vRdFvbr17m/yxUMKjNLMiVUADwu2CWGhhi+F55TWM9M9cogzms1dnM4uOF/LAEYWdcqnM7yFmyq3IfwmOROd7Y1iFWtOjoY8To41mTV5IysgFFuRzsbWFGbNIIJCDv1dOo4lZG7jWBwRFtVTKuWyeCByJKOan8oZ3ep9XddNl0tDuaywLz9cXPYeDAA0SpkBO9sbVcTOVWldPv4uyzEkzxHtjvonHoSkFEWNoo1d8DhcQputd2ppNon4BzoAiJ1hBFQg0dVtdbGHHDQWushmNEQukLM2QO1G2Y8bgTXqFhcBJj7EjPgcPts8US8qPpPB/dXznOh5Z438tzH5ec6QgrOKrRRfKmysBmUDB+PhYabMlVPER+GCSITTzr7am2tArH3bgcEzPJm+cr5jJ4NnHNFDVrFXcI5Le9k5Jnw+bedbV+FfRzZIHaOOaOsLY0/7UGs58DjrGwKMIMFIGzOEW1/jGsdAtCN6hEAI4hBe9YXeRROBSVPAVPAqvIM5bx5hVKWAMP6zBRy3iescridVdFBinBxXDnG2GRY2XbCvp1lhvGtO9Bxu5h908XQu42lnSArMFdizMim8uwRCxPGnnOS8lwpnbOiDqTAjsrRN/PcoAScCbaACqVM40ylnjjTBs+bwWlAG23/UKbdkiwKWIQPGzWaczpoSlxPEj822cNWkpS7FyzsDrqpfgpG3jahw2vgbaSQAxuLWZYt7JzyNe8JoZpNAcvDFOdw0wqYT9AK1rZz/DdbSlLPp0ryIxgQJlK9AZlEq7IOXpohg9PIhrCng88JsOxiV4ZWAYfg4sikx/8ky2Z9l862uqwrfscIH8+ugTmVGyiddeVYUgEMn4GZzg14EwIsh9sx2cKKiWXReuOE5gzGOQgdlRKVVdlevqb279Xq0Qnsts2VDaBO0coezsruWtHApu6sKG4IBhN0aGU2kLrMKGRTN3HmbCDwKV14zvkMEDG4QfZVspVlaNU2mhc5TEZ3N1h/zqTheuLpW05ZWTGVjb3dbnNmxKZBnN8JqidaVLKAOyARNLS+MB54Z2+VaqoMLKroVBlngefnTPAcoHNWCSvlfA8CI0HEmBNBnBlXyMrzU7A7WVm94PPqQ2gmqKx+WDGsnvilmcSOBJqOK1nYyAIzuAyesq3UdSK3KfWcYKD95HmfYOU3qser2CtYEUA+FpfqdNvgPBZUBhDrGONRVlQsh8rLcaUCykHG0OOUwTlLBrsh5soEMGezi1E4HRVt1icp5wZEFXdibCkG8Y8vX75sbO4E0iom9z+hjSiOfy3DhpXItpVhE+UGQdvoWjtChmrGHf4YAzKgBNnGtuJxFCeGdhUAfQLLK8kBYAP6gvFJZajMG3Xkycy8KuC0q4Eyymwtwdxdv2M0mIBtK0LKnf640j00Auq4gUkdWGlhs22qJc6dZCsL19oxnlTJG4SYVRIGpD8TPFBuM6OElbS1pldid4mGAyN6ZIupbC5bXJN9fdpbThSxLUaI8IG1XIYBxW3Tjs6KQosKcxfxcQmdnwRGM10GnFcCy2XYunLMyAkdgk4mePiczsLygthcBut6goOqS7YVFXADLjaosB6s6ofcZWAZSIRYqSUkizYwttYab3vUOQ9w2HRxIIg8WwRVeE68xi4UtL3zRphxplzwuZrcqYCq1I3jPI5dnJIygEohMbPqVJSzrwzxBJTs5zN+ReUSgxikPQVF3JVBeNQxbHENrEMNvEdFZVV9lH9+ORGEsNZQpyTNc4C3AG7XF4ngzq+DrO2zbuaaOXgdaFcdkEotoSFBVX2qJ0C8OWZeG4KGlpghA0XfTOPCqV2qqwQ26QWfF2PMLhI2w1lVAa2aPsYd0za25MQRwgcZN6uQDCi+ZxiD4XEM2kZxOT41FnZnaRlcpZouzlRqqdbQVWopQoSB58RV50lBNrHi/AwXS5LrwDVlpY3Fc3ByiYGc52Trist6kOXdwInAQtJpp5QchyaquYOV7Su+fxVMaV3dc0RE2S6mUY0gLt2pMcYqrKIQ9w2l1gpQUMtQYcmmbt5DTNxdhnUCjQqtbK9SUSzvrC0mmhhE1e2FS2+oxypy/ZASutkmtjx3vcBC24PX65nbqkBCRhfjS9kIYPnee8cMagVOhI/3T1fAmdtAWZsCswTJCkQVNa0qWKSKPOpHAUhD9DrbVcyoYkwqhvh17vYAayXLQyKGYdxlUDFp494rBXRjYgO17DDYetNIUj/ezp6S0lnlpEwsWmJMkOwsKXeZKEAjIHn0EQJISaRBcO6UMINz7p/bEjjnw4ft+xmDvksxX4G2rIris7qaeKwAFMP2Oi7n4criuZwtpSUwpfLxSnORSrIqusc5ZFaXysqRWjiZ2DyAWEIL35tVSoQElFACjOeGGSE7AHEQgdo/LSvCOgGBvkxsmDbvlS3Fp5vhaB2TAGqRKrKKMrhLVpaGzEVjZ0OQxDhaCTA+QyRR1d15aQzrJntL3RibsipjG6jlgL4yqbS0sNYg1e84vhbBVrElK64CUcWYXDfKxhpIuxiVJZUxsbMy/uRBKTNRQ4kQ3LdRYLS0rJjRPlTPqY6gdJsEDc+aQXAn+HgsNUCbRuF0Oj0zwnA7bWDkbhO5Ens00qeQhS1laBMl5M/cAaxsLF8rKyql+Tf7ELLEGu/ixiimdCvo0TjfpjKwaggen4eh5v7LokLKbLuyvHhcZG8dhGrEDx7Hg93ZppJF7qBqO3iVveXEDQNInzeoe8Yq6ePaZBZ2JviM3W2UAGotekRCAGq4EkF1X3DOnR11yRsBL1tRa0PVcZiNFXZ2c34FskvomInQQ6lzpJoZbJxk43NwKJFBquJSsrByHydxKOnTxQASBmS3j+JMnsHSla3Ec6K9VWoJVn9zfjwOM7hqYAAqJQwE2a3nA48J2QGegRkpZNivSY+ys3EkKd4oJIwsvIHl3cWgLt5k4NH6OmtLWdpurOkwEMupYc7eMtDRhOcI2ui5JhVIzXzLyto/GAPuZoyo8wkoduVgJglCt7OhGbgID4Mq4si+63zUS1FuFFXFlqyaj2emHlLMcBqYu0FMuR28BbB7lOxRMSiCQXFhCKuwkhZ+pYDiGSgbsKKV8MiSRsuHSIWM9rklRiIlZZuqXjsQK8ooYJMgq3JKWVkhHbhsVxFUzthOWPkYijcbx54IKsSdT+uLr3crGKyoYgFiGR9iBk4kfloUX+JIlQRQqabmpgnhqtpQpb6RVQ1WH5DnrS4hEoGZqaerQ2dhFbz8XePxShmDbo70eISjoorO2vK8SJXI4SUmEU4zWKDzUDtWTYw7xXlbSTEj4FRg7zKnKoGRALv0Gs9Tgc1BpCywGZRQAtqVz2xrBcAMzEpfZwFSa2G5W0QBFjSMapWAEFa3HcGN7CxDzECyIkJ97qwrqWNTWVo876PPsjPkj2wvgroM5lLZKMETKVql/CvnWVFiFa/SzJUQwkoZsr67Y6vlSRV3/2tmNTOY3vnaxYwMuoPKqdzR1w7IqHymlPxaAThfU7Ko2ZXYj4AYJHL+kNdKwRQYESTRa5fsUZ/rVC1TMTyWVyYoqNtuzaHsMyv2tvoarxdfqwYgU1axFo/cnql1FGsqK+uAROV8BX4GU8WcZTATi2q7Qcyi0O0V+GhWBMNRUkn8H1SsWVE5By3Gi0ECqUeJoBfAtDa4amkdXG37AGP5Ggeb84p7UazpoKRzdFzeQ8HkoHGxprKy/Hpm5t12p47J6xTYDEz7uINEXSuxYXvFskYAc+ySxH9sf5ftKzU6IbwVBcUGg5e5FMCEXSErZR0wGayV19woM9guPjTqJdVTqR4uE4nJnLldWVkECCZLd2VLF+xtamex7IpiriSDUpvrpn9lrwGMCHyppMH+ps6LILsuFGUj1XEOXiqbqSHPUKnClpWV68kqtURVNDY4TNaocykoYeTU5ngGEQa/S1DnnE4AeXMcKjHPAmFVjCBENaeyLVNHfr3px8xUstJ94hIpfH4HKE/eDaArK6lSyVVFbdt1gxTIVk3pppVlFXi4pEhVBTObquohU85MLXn1iahvUkHJjSCMc01tLFveVVBx0DodM6jftCu7DOtIzYxrc0qp1JGP2ayYFz2Gb6HvMrO8cnGtV6Gjm3uImSfD2GpWK6uowbZGMxFKQCo1pOMtcMXFpRst+hXGoAomF3sSTBGgTglbBKWwsQ3tZqaYSp0Z1CimRDWFcCJUPYJ00BI5FkKYNoifuQxmN88SWVXWLMaUqqqgC0BmQJR6sk3u9NCf6jYLXxAfqsYEgVLAhRY2AtgtflZNFmFyhxdrLkAdWlk4D88M2ixHyepIdhMHrG/iR1ZGtq0MGpbDbRPYOXeSY1M6Ny4ZstvGSktK+XbFPATj2D371saPEsAMXhXrsZ0km/XStkhhMyBfsa6uXFZe2VCe+YMr1+GKgwrQyNYq1VRrB+EizAow6NsdNKcyVEkYeM73ys6q4kAHp6BiFklTkIrVC5oYV7uzwOGCz4UJ0Stq2lWMJy4wtb+RetL6tZFicnJmBw5UjCvXXMZVJX2MQkbf+XN5EWd78Vz8/JEsMZTBiKNzsm1inLRUQ74H4NidaqI68j5sAFgxcRveC7ieLJXfQYxjZZ2CsiWFewZXJmBIlZ1tdtrX4hSuateKso/RZOtOKW2nmq1oTzeK6dRWAWu2NRVb4hq0SXm1GvtugHrbr5IXqmSktg5CuDE2MSlPwsY5kNE2Wp3AqiZbWVLAxiBF+2iBZbuNj6MB6rsMLC7FyasaYDyo7KkoPyEtw3pEMXfPvxAJi2jAQQgjrz0rLIZSWZlIoNhwd5xK4AR9mYNjWAaLrnuImJeBVN9zBORObVvbr+mTTfFSEJLSRnHo7hEJoIi8MFqjxmvgmF5URZz4zLFgZZ8Ctu2X7ggVccKm9gVxIsOHqxXgNMKnFWZYnf1dBnOhayXq17QwFlWW09eNKyVJFmXqaONGA5aCegMbJ3UUkGY1ic3nKWgjq8qfVYGQG1gRt6rs62a6HiqqUOqdesK5NmX4nGofJoiE1d0dF9lVVkvT1/kEEaaCoYOwFpcVcoLM+7669PxC9rWqktH0sWUYld0VCpuBZ/stVRcGgy9WX2+U1Qthi9SzAqSxzZsy+OiFzBYnySGV6Gku44rD8BCOZBV3BvD5+AKRHNwMEsB6EzHnJpkTAeiUlEGkcECeB6GDZTp5YEJTlvdrknxYjTllMkfNtXwDjM7uVjK5JXUUn43rrqpK2jytaxHW0M5G8DC8rtHMYs7KSgduVQMGTYFqFvVS6rkD3sDJ46afdYFwoq11AOKCBLhvwoUgc8IGANycR6knZrdJPdsuxnyjfd3FovTlRMdEdtOl5CMV5EHsXQBis7TOwvIDZaGj2Vnpbh7cpK63VwYEMLwqbjzyl699sawFFkF1yqjUU31HfC6sW1ZFVFuXVXVgz9keEaw0ys1lWfm+azQAQSWA+hKYVfsZjPncAcUB9oIayy/UZXRNckDGji77GsWbvBo6tPrWPqOyVkBUq+INeqpzNdYs/u0ifh5qmpqIW+33JVSUcwY70KL4U9lYdU6ljtSls7lmfi9g3YzeQfVkaGFaV3ODCnaD2N8wsEDFklE3RzM3ZghdYkWHsszq70FIecnKkVkt8ezMzRq9bkGuKojRLBVSod3Y1yPqKgYW7JRQTPVyy5xIYLjOgxgT52RKJUY1dOrIiRd4futQx/A5AcSmEjz0vFWrkLzvbWAu9HOWbGgxFk1VNTpnBKk6TgwisI/HcxYXP1uAWO72ULFlBTq+aSu2VTUs6hrxM2CF+hEor1VIA9ZmFUaab1lSSgZsVs4sxzHlVLoJHr9H4DhONTkI1XC0/wiY2NoWAG5RlnHFnq6oLccpQddMuJ/O17JVA5OHLi0BqCztq7Y1++ucCd98qLI8MIHBV/cKjxQTme3hFBS3MyCqnDsuym2o80HjvFFTtrURmNaGJsmVahImjTsUXKtQZTAVs7Mvv8/+fzUrZAXcLJ6M4koe6XP0b6SmWWNDzyUpQ8bl+LtWx4tuqZ36cRYV3yuVxPNwvIiqiQCSmu7srgTzR6nkyhpCarXwFy1vGd5iP2cY06lFr5Njhhg1Y6+NB28ftbK83s8rf7kLJbKwDFPbLg25a0AdZJEiqr5phixKMDlRUtcssq1hriLqGoH+zeNgVm9OemjsETV8JdF0NHnkIFxWY1OB4Yrp7rtWJ7NgAAAPXklEQVQ3oNs5nplyVf8u2FoLu1JrHveaZWQjqAkshtFa2gzsSG3Zpkbvg3HafF9slPPlldjFlK80Gysm8Mr4MPhneNWENPGjAIpmilTPATdTRTXlCBYHYAQuPwA36xIpWtGN4q3Y2MhiGsUpuSSnlEJRD8PorC7CFYVw+F51qThgabxsTxWzCGY0ZSsb3lfqAy0OPNjNy8xiQQKsHYFQ2HBZVvVbBuq3m1oWKajqaonsM6uZUr6CjXWNZ0l5E3h3jURma6kP3MJIiy1Lm+kahQq41N2iZja5sjtlLYNZHZrH6qUGm4vMbDp6Rw2CFmvuyFkrBcCyMtFqBaECmsHoK9BZ2LA/lJcRqSaDqnaWbrZdGaz3DLgIvBln4woGztbyJGqslwxkhhHrTjTYFXCtOoKS8uLdofVdAbOylGU6nlYpXWZts4nXBq6WxJitMNokHUJnbnJplQm+aGpY2a5GMV2QD1hRubBPFKdumf5OHkLHz0F9luE5kjBjRa0nFE5CUGqHw32MmjZ6xkgINVnSnZ1VZStK2qKlRaLlQgK7uTq7JFXJwM+3SOEKyhZNI+tJ0I5qMYy9k2qJD7dVWdqKXa0CKNR0Ccjg+B2IYu2fcBZJZkMFgM11r0X92wilghFGgzVnexlqB7xL9mS29SiYUVY2nXOZjNBRsyDsQPRWW5hrZ4XcdC4HVWRbjgJr4sFofK5SzjQ7rhI1UebdPdEbj6sqIvTZQZ5va08rABsAW0UxeWytAk7A2KJ9ZpxzCioB24XFtYAeXYxr6anSqhLgppEqWbGwLunTgrV+IjWlL29ljaAl4EQMGsErp4apeZiquwRXLXAqOCeru32mmydc6oWTSWpFAGdzeTB8RTHVMEtlM90CbbQCYhPjq3egYr1FGdYIQjiuDGZ5zZ/AzobKGOyLxti6c4Rwtv2anyWlLICnlLhxJRXt6A5ebDBWFNONbxWZ2d02mnu4S9YECpeppV1zSWRBWxHYzVIv1CXSouwqqX3jBBBDZdYQbpTQW4ZQlS8r5kH4suSRmg2++3JN10x1PaAmEkmtYlEdeGpJEM6kOuCqCR22oSujj5IV2HdT0zj5prLKTjXFAPjdQlyq7xIBxAQP5yMczG4VxAKw0n6ilZ2QBce2pLulkuxxqnoIzFfgqyqjil9S1VNwBrFmeyeops8yOjZUybZdfS8CuaTIJumzs5tODaNtLpFDQ/PcJGweLhmeL1nB0KqiUDScsiUVD89Di3HtrKtSULw3RLiygZD+7sF8JTObgYsrGvDNUFRGl1iy0Ll1YkUc2aJYMog920I8qW6YDCg1Mqk0JHJFKXkbgbRreI+qpYNOZHrVcDUba7pjsphSJNtK6upgRNAVoOS0mugBeN4bIZgHhuPZ/s1ENaX6KsVr+YNrh1Nb7ipR0PE5zbNRegCbrHRUw6Yf07dLBJl1f8KB9as2V1nNqAsl62LBBhehwalerkHmB1JFIEZKSEusdl5JQj1nJlHXSCF342gJ9CYGrXelknJIXqVP8sD+qtplCR3XH2qfKq0ygMp+KnVkKxNlZ8m2YkIlVMiCnXUwl7qznBKSvQz3m3Pt6oQbXO5b5FixCh/fHxUQW/AEcK6zCNqKQnL9sywqmKuwvqSYzT/aPVNNpVyhvRW21aqciCsjdWvBwILUvh5VyCzbWoC1pJjJ680CWsl+udKB6T5RwG1mlohnlpbg47iz5U9ha0FGtmRLFYBtO99y97Ap0z+ZDTAog6kSLZsMHg/IFkkgp6CpvU2U0cYVSdnmkjwBdOmXbxTWNWzuIbipMioVxEckZEoahSOiy2M3K0jcC1LhVDwaqG0ZvkcWqCnrG4GIxykrqlbWdw6LQyBaZR8HmLRIhQWsHswD42ZXVLNkf9l+FlW0HVQ2lwFsC/Z1FdzlQR0KaPfo+Fdfu+/dwVRICu1CGR7AEIiAhc+AZUF0kOBaPxmUqg4i64vQnU4nFDYJ9Nz+1fVXveH9qmr+kPILx8oKcRV/BFbxbE0JMT0kSD4w6L/lNY8ocsqagVdU3A3MjxhxcGuqzsPH4irpaow1q6OyrVjvp9Npc59E91LldboYVzJWdimWfAW2SNEKcDaX2FmBLLA/uKxlmhh613Is1URQApbKfttwxL02q6Onx5pQxSbPojAg+v5hAnN6LHVRDXIsvKtRjiS0qJUyZTAXVbAK82ElFJWaQdVoqUC1Unt7BVaTQudM6SuqexjQJN4+0icaxv/utbKv83ETbT8H8gjcOKxOJmbUa6OOVXht3dFY6rHv9XoNzFLceEA1o8+pKm0LAHPHZ2rYKjFq0hfZFixsqHJgD3eD5n+U0kb1mFjXkn2lvMSSOsNE/CdIAKF0Sytq6urOHUN5gwg4GZosgbmggM5ucra2qrS2Ig1cbiBBcxYzgzUDNLCvL8GbZXNp6ORy3LmS+Kk83zRIAK6A1ioKa2I9NapIuiUFdfC9766PFZUtqUr6KbWk+zZU1a/ZrIXEztrjTOfz7hwKziCeXIaraHtbZIMz+2pGgazCmw4qWAFvEdhodYp0Xq0pV7G1YWYWbO4qhGq42+Z8BYtrLWvluNPpZAeaFFS1vubPgbgxsqcpnAaszBovKaFoDQ8BGtjfUOl4NAG2nmQV04feJgumvX2fsrQEWZghL0JnVdYkn3DOZIeRN86RqPWCmsvGVqEMRnwxQAxwS8EMYo3IzmY2+BCcLp4MKiuyuhImamlbZFcNoNl7tp+RHd18ZjQIRKyXdFRhN98/hyKqwXWNo7O1wiaXoHN108REZZWEq6grnIfjzeg8jdRf1XEL4kkXa5bBjKxoKaljBjeHlVxQ4GaycpW4lDOAKtnTxHAtOfzOtZwHAM7sqVXkV6yu6kap1nHkXKqWF/4XHqjenNKqBjpR3l1ch3Ejg1+EsgdQhsdG0B4FM9sWAVWpuAyiwTPleZxt9VyZVS2qXfReWqTAilpr9ApoWTjxymit7NwV4JTriZyOA9B0k7HFfULourmKYHVnRQvqGL5HMHdqFcR2qWpmcK6eTwx2dipWrviDilr+fKWq3OWRWdHKwA4eu8wjchbeRzFilqjjZN3ufCpfkJ0/scVpnYk6L0PI77lxdWCZ87WiWm7B/AGquQSnujGKsB8CJmiJq8q1pKIVWyqOiTK66r18BN8r74/AE71fdC3yPS2MxdOpnE1tlVxD9JmVOoggN+r4PjAXVFPa3Eg5jVJGFVUGNolH20GVrUB7BOySWq6WqYQdWR92pcFMYMwckbSgCKCqD67DiiWu1g8MQC9ByfcFqW1L+jL714qNCuznoSxt0da2gtWN1G8F0BK0NN0nuimelUF9dIdAfjO44UT3CjQLoUeLHJFTO3gmpRuIIOvwBQCbqNeo3qtZ9iF6xVK13GRlo4zqimq+CGdTiR1uRY8oqgE02hZBa79kZXPMquxRHKla2saZWN4mRqZUj0vLCKhkjKnqOQHNuSZVJoKvAqS1wpEquvWDC1B2ypwrCPsRMEPVTODMLJMDv6qeKXwi2JYV5Sq4qKyvgGsHCLiuj2jR59V8gMqSJ2FJZRXEHVRHj3sFPrct6OpqlW1GpatQdt0GvwfM6n63InsGVFhJGaBqgqqIV6IsXllZgySPq4R3bnt3wi5cv+cN2yqQLW1T95KYVsWWtKk4cB9W53WQQflQYR6Wl4HaJZjvVE0D5yvq+RKgZCs5qdBEP5sD94cAvQLlSgNaSMAtHx88BuNQ41zdFsX30zKbcs0MLD/ihkpQzl0wiTqKLTfbKmCmyYICnK0IbaieC4CG9iSyLQ7cIMGQwau6TKoq60Apl3WN40LZpca1CKKK9VQyyIEn8w0F8F6CL2h8o3ixGwC7s7EWzCOqmcApYxYD4jsAzVS0sl2t98pA7vrKophCVSonbYpgH6mvSn24pTBV4sdtV3BtMq5k82y+IADvUJ0uAlkCVTxIaPm+UNu/qkV4F1TzHXCGrXIAqItBKypqK99VtAOVs64O4ObX7pHLVCpYHcRmwvLR7TvYAKBBN58LGVzDuFz+hQbWgncQyCZAk+VbsPSouf93261iZgmfCpwRbAvqmSqriU2PwhjaoOyYqtIegVXViTsmyta6bGySpY3gyRrpIyAeaWDDxtpsXwKyalMDKNP7YBXMqEskUsi2uC8FNAPxAKTVfT1o6VzM0E0jF+1rWcUuHvdyg7vgoFplX8HpvHpMCOMRUPHzZkInsqlFKNX/EIO52E0SxSzOwob2VmRLW5D1XIU0rbgM1AzWgyC7fe8G7xUAK/taEBat7luqtyP7EmsaJQOj5F+mrnZfCuYCfBUAWwShyd6pMY/vAHG1UqOYpbI/gy5T0CMKm+UO3gFuC85dgfDVeguPDfITrIBLsLrcgdh3CFgFZjaKJ4Iv3F8ANEqvuxR1tVKOgLoCa1jxboBAkj6v7j/icFbA7f4rfRnQDLRViG13i0vqBQrYVqBbADZT0ZpiHoSzvQpopKIFS3sE1HfBWlHXd0H7LnArqvougMtljHBgZnh3Eoz/BKjLML4Z2Aq0+hEJr9jaVUBbvNzCIUiroC7AWmmFw4o5AK3MtB5VypZMSFgs05JyGVwlwBqsEGAAa2ZU1CjUexXGsE4rKriilBvFzOKKo3AuAroE6QFQU3u8YpNXwS5k+1TZt5UrwouN4KiUEw+k3ZWDp1RXHNRqXb21Ts39945yZSg3VnZFNQ9CF3XeZyr5DgBXKiwCMa2MxeTDYXgP1Fsf9QNKZc0k81RJk3r6EQ3rCmBVyLL75EjZ1pIVDHoFtiOAHoB0BdTVylqBsKKKS+AeBXJVLY+CXASuGvO/Auq7GuEjDfGKg1oKa1z/dmmi9I9SUGNhl0AtfulHAawoYrnSkmNXAVuGEhrEVXvUF+A5Ct2PqNOjDetyna4CmeUolmeXLN4Aq7C5Sj10Q7yjgl+t6CNxSRHmI5X+CpwreYB3Qfdqna4q21KdBuc4GoZsn49ZOOiVinwHqK9WzjvgeweEh2AU5+vtxZ9Cd9Wqkh49V18E5oj6vVyn0RStAyGIO5edXRKd5B0VGVXq2yr3xYp+5Ut+C4QJ4P1N339pQMjRejj4vb/Dcr6rQc3O/0rjmtZpeYCBiCHfCemRbNhbK/pNUPc3wfKy5f2D7OlL3/uPhve/oU4T0F8f+VNM2vyoiv0jK+KHQfdHq+0bncz4oz73/+Y6LbKw1o/5B7eOf1Rl/0du9B9tn/9bvrf/j+v0h6ttn2tp/r/4819y4/zv5391uvzzfwDifz6phT1MPgAAAABJRU5ErkJggg=='); +} + +.x-cursor { + cursor: default; + width: 16px; + height: 16px; + border-width: 2px; + position: relative; + border-style: solid; + @include boder-radius(50%); + border-color: var(--cursor-color); +} + +.x-info-box { + // + display: flex; + margin-bottom: 8px; + + // + .x-start { + margin: { + top: auto; + bottom: auto; + } + + position: relative; + } + + // + .x-margin { + width: 2px; + height: auto; + + margin: { + left: 4px; + right: 4px; + } + } + + // + .x-end { + flex: 1 1 auto; + } +} + +.x-selected-color { + top: 0; + left: 0; + width: 40px; + height: 40px; + position: absolute; + @include boder-radius(50%); +} + +.x-selected-color-background { + width: 40px; + height: 40px; + @include boder-radius(50%); + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAh0lEQVRYR+2W0QlAMQgD60zdfwOdqa8TmI/wQMr5K0I5bZLIzLOa2nt37VVVbd+dDx5obgCC3KBLwJ2ff4PnVidkf+ucIhw80HQaCLo3DMH3CRK3iFsmAWVl6hPNDwt8EvNE5q+YuEXcMgkonVM6SdyCoEvAnZ8v1Hjx817MilmxSUB5rdLJDycZgUAZUch/AAAAAElFTkSuQmCC'); +} + +.x-color-hue { + width: 100%; + height: 16px; + border: none; + cursor: pointer; + margin-bottom: 8px; + background-size: 100% 100%; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJYAAAAQCAYAAAD06IYnAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4AIWDwkUFWbCCAAAAFxJREFUaN7t0kEKg0AQAME2x83/n2qu5qCgD1iDhCoYdpnbQC9bbY1qVO/jvc6k3ad91s7/7F1/csgPrujuQ17BDYSFsBAWwgJhISyEBcJCWAgLhIWwEBYIi2f7Ar/1TCgFH2X9AAAAAElFTkSuQmCC'); +} + +.x-color-alpha { + width: 100%; + height: 16px; + border: none; + cursor: pointer; + background-size: 100% 100%; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJYAAAAQCAYAAAD06IYnAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4AIWDwYQlZMa3gAAAWVJREFUaN7tmEGO6jAQRCsOArHgBpyAJYGjcGocxAm4A2IHpmoWE0eBH+ezmFlNvU06shJ3W6VEelWMUQAIIF9f6qZpimsA1LYtS2uF51/u27YVAFZVRUkEoGHdPV/sIcbIEIIkUdI/9Xa7neyv61+SWFUVAVCSct00TWn2fv6u3+Ecfd3tXzy/0+nEUu+SPjo/kqzrmiQpScN6v98XewfA8/lMkiLJ2WxGSUopcT6fM6U0NX9/frfbjev1WtfrlZfLhYfDQQHG/AIOlnGwjINlHCxjHCzjYJm/TJWdCwquJXseFFzGwDNNeiKMOJTO8xQdDQaeB29+K9efeLaBo9J7vdvtJj1RjFFjfiv7qv95tjx/7leSQgh93e1ffMeIp6O+YQjho/N791t1XVOSSI7N//K+4/GoxWLBx+PB5/Op5XLJ+/3OlJJWqxU3m83ovv5iGf8KjYNlHCxjHCzjYBkHy5gf5gusvQU7U37jTAAAAABJRU5ErkJggg=='); +} + +.x-color-value { + margin-bottom: 8px; +} + +.x-hex, +.x-rgba, +.x-hsla { + display: flex; + margin-bottom: 8px; +} + +.x-color-text { + display: block; + flex: 1 1 auto; + margin: { + top: auto; + bottom: auto; + } +} + +.x-color-input { + // + width: 100%; + background-color: transparent !important; + + // + border: { + top: unset; + left: unset; + right: unset; + } + + &:focus { + outline: unset; + } +} + +.x-preset-colors-wrapper { + * { + text-align: start; + } +} + +.x-preset-label { + display: block; +} + +.x-preset-colors-box { + display: flex; + flex-wrap: wrap; + justify-content: center; +} + +.x-preset-color { + width: 18px; + height: 18px; + cursor: pointer; + display: inline-block; + margin: 6px 6px 8px 6px; + @include boder-radius(25%); + border: #a9a9a9 solid 1px; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-color-picker\x-color-picker.component.ts + +import { + Component, + ViewChild, + ElementRef, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + nameof, + rgbString, + XHSVAColor, + hslaToHsva, + hsvaToHsla, + rgbaString, + rgbaToHsva, + hsvaToRgba, + rgbaToHex1, + hslaString, + parseXColor, + stringToHsva, + toNormalString, + denormalizeRGBA, + isNullOrEmptyString, + XColorWithBrightness, + XSliderValueEventModel, + getXColorCssVariableName, +} from 'x-framework-core'; +import { + XSliderPosition, + XSliderDimension, + XColorPickerAction, + XColorPickerCssVar, + XColorPickerActionModel, + XColorPickerOutputFormat, + DEFAULT_COLOR_PICKER_COLOR, + XColorPickerContainerComponent, +} from './x-color-picker.typings'; +import { Subscription, Subject } from 'rxjs'; + +@Component({ + selector: 'x-color-picker', + templateUrl: './x-color-picker.component.html', + styleUrls: ['./x-color-picker.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XColorPickerComponent extends XColorPickerContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + private LAST_OUTPUT_COLOR: string; + + // + readonly ColorFormats = Object.assign({}, XColorPickerOutputFormat); + + // + hexValue: string; + hslaValue: string; + rgbaValue: string; + + // + hsva: XHSVAColor = { + h: 0, + s: 0, + v: 0, + a: 1, + }; + hueSliderColor: string; + slider: XSliderPosition; + alphaSliderColor: string; + sliderDimMax: XSliderDimension; + + // + inputColor: string; + + // + //#region View Childs ... + // + //#region ColorPicker ViewChild ... + private COLOR_PICKER: ElementRef; + + @ViewChild('colorPicker', { static: false }) + set colorPicker(v: ElementRef) { + // + this.COLOR_PICKER = v; + this.initializeComponent(); + } + + get colorPicker() { + return this.COLOR_PICKER; + } + //#endregion + + // + //#region PaletteSlider ViewChild ... + private PALETTE_SLIDER: ElementRef; + + @ViewChild('paletteSlider', { static: false }) + set paletteSlider(v: ElementRef) { + // + this.PALETTE_SLIDER = v; + this.initializeComponent(); + } + + get paletteSlider() { + return this.PALETTE_SLIDER; + } + //#endregion + + // + //#region HueSlider ViewChild ... + private HUE_SLIDER: ElementRef; + + @ViewChild('hueSlider', { static: false }) + set hueSlider(v: ElementRef) { + // + this.HUE_SLIDER = v; + this.initializeComponent(); + } + + get hueSlider() { + return this.HUE_SLIDER; + } + //#endregion + + // + //#region AlphaSlider ViewChild ... + private ALPHA_SLIDER: ElementRef; + + @ViewChild('alphaSlider', { static: false }) + set alphaSlider(v: ElementRef) { + // + this.ALPHA_SLIDER = v; + this.initializeComponent(); + } + + get alphaSlider() { + return this.ALPHA_SLIDER; + } + //#endregion + //#endregion + //#endregion + + // + //#region LifeCycles ... + async onInit() { + super.onInit(); + + // + await this.applyColors(); + await this.initializeComponent(); + } + + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region Colors Changed ... + const isCursorColorChanged = changeKeys.includes( + nameof('cursorColor') + ); + if (isCursorColorChanged) { + await this.applyColors(); + } + //#endregion + + // + //#region SelectedColor Changed ... + const isSelectedColorChanged = changeKeys.includes( + nameof('selectedColor') + ); + if (isSelectedColorChanged) { + // + const selectedColor = await this.getValueAsync(this.selectedColor); + if (isNullOrEmptyString(selectedColor)) { + this.LAST_OUTPUT_COLOR = DEFAULT_COLOR_PICKER_COLOR; + } else { + this.LAST_OUTPUT_COLOR = selectedColor; + } + + // + await this.initializeComponent(); + } + //#endregion + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + } + + async afterViewInit() { + super.afterViewInit(); + } + + async onResize() { + super.onResize(); + + // + await this.initializeComponent(); + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region UI Providers ... + //#endregion + + // + //#region UI Handlers ... + async handleSaveSelectedColorInClipboard(color: string) { + // + if (isNullOrEmptyString(color)) { + return; + } + + // + const isSaved = this.managerService.saveToClipboard(color); + if (!isSaved) { + return; + } + + // + await this.managerService.notificationService.presentDefaultSuccessNotification(); + } + + async handleClearSelectedColor() { + // + this.selectedColor = DEFAULT_COLOR_PICKER_COLOR; + this.LAST_OUTPUT_COLOR = DEFAULT_COLOR_PICKER_COLOR; + + // + this.detectChanges(); + this.colorSelected.emit(''); + } + + async handleInputColor(event?: any) { + // + if (isNullOrEmptyString(this.inputColor)) { + return; + } + + // + const color = toNormalString(this.inputColor); + this.selectedColor = color; + await this.initializeComponent(); + } + //#endregion + + // + //#region Actions ... + async setR(model: XSliderValueEventModel) { + // + const rgba = hsvaToRgba(this.hsva); + rgba.r = model.v / model.rg; + this.hsva = rgbaToHsva(rgba); + + // + await this.update(); + } + + async setG(model: XSliderValueEventModel) { + // + const rgba = hsvaToRgba(this.hsva); + rgba.g = model.v / model.rg; + this.hsva = rgbaToHsva(rgba); + + // + await this.update(); + } + + async setB(model: XSliderValueEventModel) { + // + const rgba = hsvaToRgba(this.hsva); + rgba.b = model.v / model.rg; + this.hsva = rgbaToHsva(rgba); + + // + await this.update(); + } + + async setHue(model: XSliderValueEventModel) { + // + this.hsva.h = model.v / model.rg; + + // + await this.update(); + } + + async setHexColor(model: string) { + // + let hsva = stringToHsva(model); + if (!hsva) { + hsva = stringToHsva(model); + } + + // + if (hsva) { + // + this.hsva = hsva; + await this.update(); + } + } + + async setAlpha(model: XSliderValueEventModel) { + // + this.hsva.a = model.v / model.rg; + + // + await this.update(); + } + + async setSaturation(model: XSliderValueEventModel) { + // + const hsla = hsvaToHsla(this.hsva); + hsla.s = model.v / model.rg; + this.hsva = hslaToHsva(hsla); + + // + await this.update(); + } + + async setLightness(model: XSliderValueEventModel) { + // + const hsla = hsvaToHsla(this.hsva); + hsla.l = model.v / model.rg; + this.hsva = hslaToHsva(hsla); + + // + await this.update(); + } + + async setSaturationAndBrightness(model: XSliderValueEventModel) { + // + this.hsva.s = model.s / model.rgX; + this.hsva.v = model.v / model.rgY; + + // + await this.update(); + } + //#endregion + + // + //#region Private ... + // + //#region ActionProvider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + switch (model.action) { + // + case XColorPickerAction.Refresh: + this.detectChanges(); + break; + + // + case XColorPickerAction.SetSelectedColor: + // + if (model.payload) { + // + this.selectedColor = model.payload as string; + await this.initializeComponent(); + } + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + private async applyColors() { + // + let cursorColor = await this.getValueAsync(this.cursorColor); + if (!cursorColor) { + cursorColor = XColorWithBrightness.Dark; + } + + // + const colorDescriptor = parseXColor(cursorColor); + if (!colorDescriptor) { + this.detectChanges(); + return; + } + + // + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XColorPickerCssVar.CursorColor, `var(${bgColor})`); + + // + this.detectChanges(); + } + + private async initializeComponent() { + // + if (!this.colorPicker) { + return; + } + + // + const colorPickerElement = this.colorPicker.nativeElement as HTMLElement; + if (!colorPickerElement) { + return; + } + + // + let paletteWidth = 0; + let paletteHeight = 0; + let hueOffsetWith = 0; + let alphaOffsetWith = 0; + + // + //#region Check Palette ... + const hasPaletteChannel = await this.getValueAsync(this.showPaletteChannel); + if (hasPaletteChannel) { + // + if (!this.paletteSlider) { + return; + } + + // + const paletteElement = this.paletteSlider.nativeElement as HTMLElement; + if (!paletteElement) { + return; + } + + // + paletteWidth = paletteElement.offsetWidth; + if (paletteWidth === 0) { + setTimeout(() => this.initializeComponent(), 100); + return; + } + + // + paletteHeight = paletteElement.offsetHeight; + if (paletteHeight === 0) { + setTimeout(() => this.initializeComponent(), 100); + return; + } + } + //#endregion + + // + //#region Check Hue ... + const hasHueChannel = await this.getValueAsync(this.showHueChannel); + if (hasHueChannel) { + // + if (!this.hueSlider) { + return; + } + + // + const hueElement = this.hueSlider.nativeElement as HTMLElement; + if (!hueElement) { + return; + } + + // + hueOffsetWith = hueElement.offsetWidth; + if (hueOffsetWith === 0) { + setTimeout(() => this.initializeComponent(), 100); + return; + } + } + //#endregion + + // + //#region Check Alpha ... + const hasAlphaChannel = await this.getValueAsync(this.showAlphaChannel); + if (hasAlphaChannel) { + // + if (!this.alphaSlider) { + return; + } + + // + const alphaElement = this.alphaSlider.nativeElement as HTMLElement; + if (!alphaElement) { + return; + } + + // + alphaOffsetWith = alphaElement.offsetWidth; + if (alphaOffsetWith === 0) { + setTimeout(() => this.initializeComponent(), 100); + return; + } + } + //#endregion + + // + this.sliderDimMax = { + h: hueOffsetWith, + s: paletteWidth, + v: paletteHeight, + a: alphaOffsetWith, + }; + + // + this.slider = { + h: 0, + s: 0, + v: 0, + a: 0, + }; + + // + let selectedColor = await this.getValueAsync(this.selectedColor); + if (isNullOrEmptyString(selectedColor)) { + selectedColor = DEFAULT_COLOR_PICKER_COLOR; + } + + // + let hsva = stringToHsva(selectedColor); + if (hsva) { + // + this.hsva = hsva; + + // + await this.update(); + } + + // + this.detectChanges(); + } + + private async getSelectedColor() { + // + const rgba = denormalizeRGBA(hsvaToRgba(this.hsva)); + + // + let outputFormat = await this.getValueAsync(this.outputFormat); + if (isNullOrEmptyString(outputFormat)) { + outputFormat = XColorPickerOutputFormat.XHEX; + } + + // + let result = ''; + switch (outputFormat) { + // + case XColorPickerOutputFormat.XHEX: + result = rgbaToHex1(rgba, true); + break; + + // + case XColorPickerOutputFormat.XHSLA: + // + const hsla = hsvaToHsla(this.hsva); + result = hslaString(hsla); + break; + + // + case XColorPickerOutputFormat.XRGBA: + result = rgbaString(rgba); + break; + } + + // + return result; + } + + private async update() { + // + const rgba = denormalizeRGBA(hsvaToRgba(this.hsva)); + const hsla = hsvaToHsla(this.hsva); + + // + const hHsva: XHSVAColor = { + h: this.hsva.h, + s: 1, + v: 1, + a: 1, + }; + const hueRgba = denormalizeRGBA(hsvaToRgba(hHsva)); + + // + this.hexValue = rgbaToHex1(rgba, true); + this.rgbaValue = rgbaString(rgba); + this.hslaValue = hslaString(hsla); + + // + this.alphaSliderColor = rgbString(rgba); + this.hueSliderColor = rgbString(hueRgba); + + // + this.selectedColor = await this.getSelectedColor(); + const selectedColor = await this.getValueAsync(this.selectedColor); + + // + this.slider = { + h: this.hsva.h * this.sliderDimMax.h - 8, + s: this.hsva.s * this.sliderDimMax.s - 8, + v: (1 - this.hsva.v) * this.sliderDimMax.v - 8, + a: this.hsva.a * this.sliderDimMax.a - 8, + }; + + // + if ( + !isNullOrEmptyString(this.LAST_OUTPUT_COLOR) && + this.LAST_OUTPUT_COLOR !== selectedColor + ) { + // + this.LAST_OUTPUT_COLOR = selectedColor; + this.colorSelected.emit(selectedColor); + } + + // + this.detectChanges(); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-color-picker\x-color-picker.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XCardModule } from '../x-card/x-card.module'; +import { XIconModule } from '../x-icon/x-icon.module'; +import { XSlotterModule } from '../x-slotter/x-slotter.module'; +import { XColorPickerComponent } from './x-color-picker.component'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; +import { XColorTextDirective } from './directives/x-color-text.directive'; +import { XColorPickerDialogService } from './x-color-picker-dialog/x-color-picker-dialog.service'; +import { XColorPickerDialogComponent } from './x-color-picker-dialog/x-color-picker-dialog.component'; + +@NgModule({ + providers: [XColorPickerDialogService], + entryComponents: [XColorPickerDialogComponent], + declarations: [ + XColorTextDirective, + XColorPickerComponent, + XColorPickerDialogComponent, + ], + imports: [ + XCardModule, + XIconModule, + CommonModule, + XSlotterModule, + XBaseComponentModule, + ], + exports: [ + XCardModule, + XIconModule, + CommonModule, + XSlotterModule, + XColorTextDirective, + XBaseComponentModule, + XColorPickerComponent, + XColorPickerDialogComponent, + ], +}) +export class XColorPickerModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-color-picker\x-color-picker.typings.ts + +import { + Input, + Output, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XHSVAColor, + XResourceIDs, + XStandardType, + XOneOrManyType, + XColorIdentifier, + XResourceIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { + XComponentCardContainer, + IXComponentCardContainer, + XComponentCardContainerComponent, +} from '../x-card/x-card.typings'; +import { map, concatMap } from 'rxjs/operators'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export enum XColorPickerAction { + Refresh = 'refresh', + SetSelectedColor = 'set_selected_color', +} + +export type XColorPickerActionIdentifier = XColorPickerAction | string; + +export interface XColorPickerActionModel + extends XBaseActionModel {} + +export interface XSliderPosition extends Required {} + +export interface XSliderDimension extends Required {} + +export enum XColorPickerOutputFormat { + XHEX = 'hex', + XRGBA = 'rgba', + XHSLA = 'hsla', +} + +export type XColorPickerOutputFormatIdentifier = + | XColorPickerOutputFormat + | string; + +export enum XColorPickerCssVar { + CursorColor = 'cursor-color', +} + +export const DEFAULT_COLOR_PICKER_COLOR = '#ffffffff'; + +// +//#region Container ... +export interface IXColorPickerContainer extends IXComponentCardContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + colorSelected: EventEmitter; + + // + //#region Colors ... + selectedColor: XStandardType; + color: XStandardType; + foregroundColor: XStandardType; + copyToClipboardIconColor: XStandardType; + colorInputIconColor: XStandardType; + clearColorIconColor: XStandardType; + colorTextColor: XStandardType; + presetColorsLableColor: XStandardType; + cursorColor: XStandardType; + //#endregion + + // + //#region Labels ... + colorInputIconLabel: XStandardType; + copyToClipboardIconLabel: XStandardType; + clearColorIconLabel: XStandardType; + //#endregion + + // + //#region PresetColors ... + presetColorsLable: XStandardType; + presetColors: XStandardType>; + //#endregion + + // + //#region Represent Color Value ... + representColorFormats: XStandardType< + XOneOrManyType + >; + outputFormat: XStandardType; + //#endregion + + // + //#region Show Parts Props ... + showColorInput: XStandardType; + showCopyToClipboard: XStandardType; + showSelectedColorPreview: XStandardType; + showPaletteChannel: XStandardType; + showHueChannel: XStandardType; + showAlphaChannel: XStandardType; + showColorValue: XStandardType; + showPresetColors: XStandardType; + //#endregion +} + +@Injectable({ + providedIn: 'root', +}) +export class XColorPickerContainer + extends XComponentCardContainer + implements Readonly +{ + // + //#region Props ... + readonly actionProvider: Subject = + new Subject(); + readonly cssClass: XStandardType>; + + // + readonly colorSelected: EventEmitter = new EventEmitter(); + + // + //#region Colors ... + readonly selectedColor: XStandardType = DEFAULT_COLOR_PICKER_COLOR; + + // + readonly color: XStandardType = XColorWithBrightness.Light; + readonly foregroundColor: XStandardType = false; + + // + readonly copyToClipboardIconColor: XStandardType = + XColorWithBrightness.TertiaryShade; + + readonly colorInputIconColor: XStandardType = + XColorWithBrightness.SecondaryShade; + + readonly clearColorIconColor: XStandardType = + XColorWithBrightness.Danger; + + readonly colorTextColor: XStandardType = + XColorWithBrightness.Dark; + + readonly presetColorsLableColor: XStandardType = + XColorWithBrightness.Dark; + + readonly cursorColor: XStandardType = + XColorWithBrightness.Dark; + //#endregion + + // + //#region Labels ... + readonly colorInputIconLabel: XStandardType = + XResourceIDs.input; + + readonly copyToClipboardIconLabel: XStandardType = + XResourceIDs.copy_to_clipboard; + + readonly clearColorIconLabel: XStandardType = + XResourceIDs.clear; + //#endregion + + // + //#region PresetColors ... + readonly presetColorsLable: XStandardType; + readonly presetColors: XStandardType>; + //#endregion + + // + //#region Represent Color Value ... + readonly representColorFormats: XStandardType< + XOneOrManyType + > = [XColorPickerOutputFormat.XHEX]; + + readonly outputFormat: XStandardType = + XColorPickerOutputFormat.XHEX; + //#endregion + + // + //#region Show Parts Props ... + readonly showColorInput: XStandardType = true; + readonly showCopyToClipboard: XStandardType = false; + readonly showSelectedColorPreview: XStandardType = true; + readonly showPaletteChannel: XStandardType = true; + readonly showHueChannel: XStandardType = true; + readonly showAlphaChannel: XStandardType = true; + readonly showColorValue: XStandardType = true; + readonly showPresetColors: XStandardType = true; + //#endregion + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XColorPickerContainerComponent + extends XComponentCardContainerComponent + implements IXColorPickerContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Output() + colorSelected: EventEmitter = new EventEmitter(); + + // + //#region Colors ... + @Input() + selectedColor: XStandardType = this.propertyProvider.selectedColor; + + @Input() + color: XStandardType = this.propertyProvider.color; + + @Input() + foregroundColor: XStandardType = + this.propertyProvider.foregroundColor; + + @Input() + copyToClipboardIconColor: XStandardType = + this.propertyProvider.copyToClipboardIconColor; + + @Input() + colorInputIconColor: XStandardType = + this.propertyProvider.colorInputIconColor; + + @Input() + clearColorIconColor: XStandardType = + this.propertyProvider.clearColorIconColor; + + @Input() + colorTextColor: XStandardType = + this.propertyProvider.colorTextColor; + + @Input() + presetColorsLableColor: XStandardType = + this.propertyProvider.presetColorsLableColor; + + @Input() + cursorColor: XStandardType = + this.propertyProvider.cursorColor; + //#endregion + + // + //#region Labels ... + @Input() + colorInputIconLabel: XStandardType = + this.propertyProvider.colorInputIconLabel; + + @Input() + copyToClipboardIconLabel: XStandardType = + this.propertyProvider.copyToClipboardIconLabel; + + @Input() + clearColorIconLabel: XStandardType = + this.propertyProvider.clearColorIconLabel; + //#endregion + + // + //#region PresetColors ... + @Input() + presetColorsLable: XStandardType = + this.propertyProvider.presetColorsLable; + + @Input() + presetColors: XStandardType> = + this.propertyProvider.presetColors; + //#endregion + + // + //#region Represent Color Value ... + @Input() + representColorFormats: XStandardType< + XOneOrManyType + > = this.propertyProvider.representColorFormats; + + @Input() + outputFormat: XStandardType = + this.propertyProvider.outputFormat; + //#endregion + + // + //#region Show Parts Props ... + @Input() + showColorInput: XStandardType = this.propertyProvider.showColorInput; + + @Input() + showCopyToClipboard: XStandardType = + this.propertyProvider.showCopyToClipboard; + + @Input() + showSelectedColorPreview: XStandardType = + this.propertyProvider.showSelectedColorPreview; + + @Input() + showPaletteChannel: XStandardType = + this.propertyProvider.showPaletteChannel; + + @Input() + showHueChannel: XStandardType = this.propertyProvider.showHueChannel; + + @Input() + showAlphaChannel: XStandardType = + this.propertyProvider.showAlphaChannel; + + @Input() + showColorValue: XStandardType = this.propertyProvider.showColorValue; + + @Input() + showPresetColors: XStandardType = + this.propertyProvider.showPresetColors; + //#endregion + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XColorPickerContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXColorPickerContainer + ): Observable { + return super + .extractProps(container ? container : this) + .pipe( + concatMap((superData) => { + return forkJoin({ + // + cssClass: this.getArrayValue( + container ? container.cssClass : this.cssClass + ), + + // + //#region Colors ... + selectedColor: this.getValue( + container ? container.selectedColor : this.selectedColor + ), + color: this.getValue(container ? container.color : this.color), + foregroundColor: this.getValue( + container ? container.foregroundColor : this.foregroundColor + ), + copyToClipboardIconColor: this.getValue( + container + ? container.copyToClipboardIconColor + : this.copyToClipboardIconColor + ), + colorInputIconColor: this.getValue( + container + ? container.colorInputIconColor + : this.colorInputIconColor + ), + clearColorIconColor: this.getValue( + container + ? container.clearColorIconColor + : this.clearColorIconColor + ), + colorTextColor: this.getValue( + container ? container.colorTextColor : this.colorTextColor + ), + presetColorsLableColor: this.getValue( + container + ? container.presetColorsLableColor + : this.presetColorsLableColor + ), + cursorColor: this.getValue( + container ? container.cursorColor : this.cursorColor + ), + //#endregion + + // + //#region Labels ... + colorInputIconLabel: this.getValue( + container + ? container.colorInputIconLabel + : this.colorInputIconLabel + ), + copyToClipboardIconLabel: this.getValue( + container + ? container.copyToClipboardIconLabel + : this.copyToClipboardIconLabel + ), + clearColorIconLabel: this.getValue( + container + ? container.clearColorIconLabel + : this.clearColorIconLabel + ), + //#endregion + + // + //#region PresetColors ... + presetColorsLable: this.getValue( + container ? container.presetColorsLable : this.presetColorsLable + ), + presetColors: this.getValue( + container ? container.presetColors : this.presetColors + ), + //#endregion + + // + //#region Represent Color Value ... + representColorFormats: this.getValue( + container + ? container.representColorFormats + : this.representColorFormats + ), + outputFormat: this.getValue( + container ? container.outputFormat : this.outputFormat + ), + //#endregion + + // + //#region Show Parts Props ... + showColorInput: this.getValue( + container ? container.showColorInput : this.showColorInput + ), + showCopyToClipboard: this.getValue( + container + ? container.showCopyToClipboard + : this.showCopyToClipboard + ), + showSelectedColorPreview: this.getValue( + container + ? container.showSelectedColorPreview + : this.showSelectedColorPreview + ), + showPaletteChannel: this.getValue( + container ? container.showPaletteChannel : this.showPaletteChannel + ), + showHueChannel: this.getValue( + container ? container.showHueChannel : this.showHueChannel + ), + showAlphaChannel: this.getValue( + container ? container.showAlphaChannel : this.showAlphaChannel + ), + showColorValue: this.getValue( + container ? container.showColorValue : this.showColorValue + ), + showPresetColors: this.getValue( + container ? container.showPresetColors : this.showPresetColors + ), + //#endregion + }).pipe( + map((data) => { + return { + superData, + data, + }; + }) + ); + }) + ) + .pipe( + map((info) => { + return { + // + ...info.superData, + + // + ...info.data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + colorSelected: this.colorSelected, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-color-picker\directives\x-color-text.directive.ts + +import { + Input, + Output, + Directive, + EventEmitter, + HostListener, +} from '@angular/core'; + +@Directive({ + selector: '[xColorText]', +}) +export class XColorTextDirective { + // + @Input() + text: any; + + @Input() + rg: number; + + // + @Output() + newValue = new EventEmitter(); + + // + @HostListener('input', ['$event.target.value']) + changeInput(value: string) { + // + if (this.rg === undefined) { + this.newValue.emit(value); + } else { + // + const numeric = parseFloat(value); + + // + if (!isNaN(numeric) && numeric >= 0 && numeric <= this.rg) { + this.newValue.emit({ v: numeric, rg: this.rg }); + } + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-color-picker\x-color-picker-dialog\x-color-picker-dialog.component.html + + + + +
+ + + +
+ +
+
+ + + + +

+ {{ getTitle() | async }} +

+
+
+ + + +
+ +
+
+
+
+
+ + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-color-picker\x-color-picker-dialog\x-color-picker-dialog.component.scss + +::ng-deep.fullscreen-dialog { + // + width: 80vw !important; + max-width: 80vw !important; + + // + height: 80vh !important; + max-height: 80vh !important; + + // + &.mobile { + // + width: 100vw !important; + max-width: 100vw !important; + + // + height: 100vh !important; + max-height: 100vh !important; + } + + // + .mat-dialog-container { + padding: 0 !important; + } +} + +x-icon { + --size: 24px; +} + +.toolbar-area { + margin: 8px; +} + +.toolbar-item { + width: auto; + height: 48px; +} + +.toolbar-button { + width: 32px; + margin: { + left: 4px; + right: 4px; + } +} + +h1, +h2, +h3, +h4, +h5, +h6 { + margin: 0 !important; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-color-picker\x-color-picker-dialog\x-color-picker-dialog.component.ts + +import { + Inject, + NgZone, + Renderer2, + Component, + ElementRef, + ChangeDetectorRef, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + XColor, + hasChild, + isFunction, + XResourceIDs, + XDialogResult, + isNullOrUndefined, + isNullOrEmptyString, +} from 'x-framework-core'; +import { + XColorPickerActionModel, + XColorPickerOutputFormat, +} from '../x-color-picker.typings'; +import { Subject, of } from 'rxjs'; +import { concatMap, map } from 'rxjs/operators'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XBasePage } from '../../classes/x-base-page'; +import { XManagerService } from 'x-framework-services'; +import { XIconNames } from '../../x-icon/x-icon.typings'; +import { XSlotName } from '../../x-slotter/x-slotter.typings'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { XColorPickerDialogConfig } from './x-color-picker-dialog.typings'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from './../../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from './../../config/x-framework-components.config'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-color-picker-dialog', + templateUrl: './x-color-picker-dialog.component.html', + styleUrls: ['./x-color-picker-dialog.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XColorPickerDialogComponent extends XBasePage { + // + //#region Props ... + // + selectIcon = XIconNames.checked; + backIcon = XIconNames.arrow_back; + SlotNames = Object.assign({}, XSlotName); + + // + isMobileUi$ = this.managerService.isMobileUi$; + isNotMobile$ = this.managerService.isNotMobileUi$; + + // + actionProvider = new Subject(); + + // + selectedColor: string; + //#endregion + + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + @Inject(MAT_DIALOG_DATA) public data: any, + public dialogRef: MatDialogRef + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region LifeCycles ... + async onInit() { + super.onInit(); + + // + this.preventBack = true; + } + + async afterViewInit() { + super.afterViewInit(); + } + //#endregion + + // + //#region UI Providers ... + getData() { + return this.getValue(this.data as XColorPickerDialogConfig); + } + + hasData() { + return !isNullOrUndefined(this.getData()); + } + + canSelect() { + return !isNullOrEmptyString(this.selectedColor); + } + + getTitle() { + return this.getData().pipe( + concatMap((config) => this.getResourceValue(config.title)) + ); + } + + /** + * retrieve selectedColor value ... + */ + getSelectedColor() { + return this.getData().pipe( + concatMap((config) => this.getValue(config.selectedColor)) + ); + } + + // + //#region Colors ... + /** + * retrieve color value ... + */ + getColor() { + return this.getData().pipe( + concatMap((config) => this.getValue(config.color)), + map((color) => (!isNullOrEmptyString(color) ? color : XColor.Light)) + ); + } + + /** + * determines is Foureground color or not ... + */ + isForegroundColor() { + return this.getData().pipe( + concatMap((config) => this.getOptionalValue(config.foregroundColor)) + ); + } + + /** + * retrieve cursorColor value ... + */ + getCursorColor() { + return this.getData().pipe( + concatMap((config) => this.getValue(config.cursorColor)), + map((color) => (!isNullOrEmptyString(color) ? color : XColor.Dark)) + ); + } + + /** + * retrieve colorTextColor value ... + */ + getColorTextColor() { + return this.getData().pipe( + concatMap((config) => this.getValue(config.colorTextColor)), + map((color) => (!isNullOrEmptyString(color) ? color : XColor.Dark)) + ); + } + + /** + * retrieve clearColorIconColor value ... + */ + getClearColorIconColor() { + return this.getData().pipe( + concatMap((config) => this.getValue(config.clearColorIconColor)), + map((color) => (!isNullOrEmptyString(color) ? color : XColor.Danger)) + ); + } + + /** + * retrieve colorInputIconColor value ... + */ + getColorInputIconColor() { + return this.getData().pipe( + concatMap((config) => this.getValue(config.colorInputIconColor)), + map((color) => (!isNullOrEmptyString(color) ? color : XColor.Secondary)) + ); + } + + /** + * retrieve presetColorsLableColor value ... + */ + getPresetColorsLableColor() { + return this.getData().pipe( + concatMap((config) => this.getValue(config.presetColorsLableColor)), + map((color) => (!isNullOrEmptyString(color) ? color : XColor.Dark)) + ); + } + + /** + * retrieve copyToClipboardIconColor value ... + */ + getCopyToClipboardIconColor() { + return this.getData().pipe( + concatMap((config) => this.getValue(config.copyToClipboardIconColor)), + map((color) => (!isNullOrEmptyString(color) ? color : XColor.Tertiary)) + ); + } + //#endregion + + // + //#region Labels ... + /** + * retrieve presetColorsLable value ... + */ + getPresetColorsLable() { + return this.getData().pipe( + concatMap((config) => this.getResourceValue(config.presetColorsLable)), + map((value) => + !isNullOrEmptyString(value) ? value : XResourceIDs.preset_colors + ) + ); + } + + /** + * retrieve clearColorIconLabel value ... + */ + getClearColorIconLabel() { + return this.getData().pipe( + concatMap((config) => this.getResourceValue(config.clearColorIconLabel)), + map((value) => (!isNullOrEmptyString(value) ? value : XResourceIDs.clear)) + ); + } + + /** + * retrieve colorInputIconLabel value ... + */ + getColorInputIconLabel() { + return this.getData().pipe( + concatMap((config) => this.getResourceValue(config.colorInputIconLabel)), + map((value) => (!isNullOrEmptyString(value) ? value : XResourceIDs.input)) + ); + } + + /** + * retrieve copyToClipboardIconLabel value ... + */ + getCopyToClipboardIconLabel() { + return this.getData().pipe( + concatMap((config) => + this.getResourceValue(config.copyToClipboardIconLabel) + ), + map((value) => + !isNullOrEmptyString(value) ? value : XResourceIDs.copy_to_clipboard + ) + ); + } + //#endregion + + // + //#region Preset Colors ... + getPresetColors() { + return this.getData().pipe( + concatMap((config) => this.getArrayValue(config.presetColors)) + ); + } + //#endregion + + // + //#region Represent Format ... + /** + * retrieve representColorFormats value ... + */ + getRepresentColorFormats() { + return this.getData().pipe( + concatMap((config) => this.getArrayValue(config.representColorFormats)), + map((formats) => + hasChild(formats) ? formats : [XColorPickerOutputFormat.XHEX] + ) + ); + } + + /** + * retrieve outputFormat value ... + */ + getOutputFormat() { + return this.getData().pipe( + concatMap((config) => this.getValue(config.outputFormat)), + map((format) => + !isNullOrEmptyString(format) ? format : XColorPickerOutputFormat.XHEX + ) + ); + } + //#endregion + + // + //#region Show Parts ... + /** + * determines showCopyToClipboard or not ... + */ + showCopyToClipboard() { + return this.getData().pipe( + concatMap((config) => this.getOptionalValue(config.showCopyToClipboard)) + ); + } + + /** + * determines showColorInput or not ... + */ + showColorInput() { + return this.getData().pipe( + concatMap((config) => + !isNullOrUndefined(config.showColorInput) + ? this.getOptionalValue(config.showColorInput) + : of(true) + ) + ); + } + + /** + * determines showSelectedColorPreview or not ... + */ + showSelectedColorPreview() { + return this.getData().pipe( + concatMap((config) => + !isNullOrUndefined(config.showSelectedColorPreview) + ? this.getOptionalValue(config.showSelectedColorPreview) + : of(true) + ) + ); + } + + /** + * determines showPaletteChannel or not ... + */ + showPaletteChannel() { + return this.getData().pipe( + concatMap((config) => + !isNullOrUndefined(config.showPaletteChannel) + ? this.getOptionalValue(config.showPaletteChannel) + : of(true) + ) + ); + } + + /** + * determines showHueChannel or not ... + */ + showHueChannel() { + return this.getData().pipe( + concatMap((config) => + !isNullOrUndefined(config.showHueChannel) + ? this.getOptionalValue(config.showHueChannel) + : of(true) + ) + ); + } + + /** + * determines showAlphaChannel or not ... + */ + showAlphaChannel() { + return this.getData().pipe( + concatMap((config) => + !isNullOrUndefined(config.showAlphaChannel) + ? this.getOptionalValue(config.showAlphaChannel) + : of(true) + ) + ); + } + + /** + * determines showColorValue or not ... + */ + showColorValue() { + return this.getData().pipe( + concatMap((config) => + !isNullOrUndefined(config.showColorValue) + ? this.getOptionalValue(config.showColorValue) + : of(true) + ) + ); + } + + /** + * determines showPresetColors or not ... + */ + showPresetColors() { + return this.getData().pipe( + concatMap((config) => + config.showPresetColors + ? this.getOptionalValue(config.showPresetColors) + : of(null) + ) + ); + } + //#endregion + + // + /** + * retrieve css classes ... + */ + getCssClass() { + return this.getData().pipe( + concatMap((config) => this.getArrayValue(config.cssClass)) + ); + } + + // + //#region + /** + * retireve cardColor value ... + */ + getCardColor() { + return this.getData().pipe( + concatMap((config) => this.getValue(config.cardColor)), + map((color) => (!isNullOrEmptyString(color) ? color : XColor.Light)) + ); + } + + /** + * determines is cardForegroundColor or not ... + */ + isCardForegroundColor() { + return this.getData().pipe( + concatMap((config) => this.getOptionalValue(config.cardForegroundColor)) + ); + } + + /** + * retrieve cardTitle value ... + */ + getCardTitle() { + return this.getData().pipe( + concatMap((config) => this.getResourceValue(config.cardTitle)) + ); + } + + /** + * retrieve cardSubTitle value ... + */ + getCardSubTitle() { + return this.getData().pipe( + concatMap((config) => this.getResourceValue(config.cardSubTitle)) + ); + } + //#endregion + //#endregion + + // + //#region UI Handlers ... + async handleBack() { + this.dialogRef.close(); + } + + async handleColorSelected(event: string) { + // + this.selectedColor = event; + + // + const config = await this.getValueAsync(this.getData()); + if (!config || !config.colorSelected || !isFunction(config.colorSelected)) { + return; + } + + // + config.colorSelected.emit(event); + + // + this.detectChanges(); + } + + async handleSelect() { + // + if (!this.canSelect()) { + return; + } + + // + this.dialogRef.close({ + result: XDialogResult.Ok, + payload: this.selectedColor, + }); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-color-picker\x-color-picker-dialog\x-color-picker-dialog.service.ts + +import { + XResourceIDs, + XBaseService, + XDialogResult, + isNullOrUndefined, +} from 'x-framework-core'; +import { + MatDialog, + MatDialogRef, + MatDialogConfig, +} from '@angular/material/dialog'; +import { + XColorPickerDialogResult, + XColorPickerDialogConfig, +} from './x-color-picker-dialog.typings'; +import { XManagerService } from 'x-framework-services'; +import { Injectable, Inject, Output, EventEmitter } from '@angular/core'; +import { XColorPickerDialogComponent } from './x-color-picker-dialog.component'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from './../../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from './../../config/x-framework-components.config'; + +@Injectable({ + providedIn: 'root', +}) +export class XColorPickerDialogService extends XBaseService { + // + //#region Props ... + private MOBILE_CLASS_NAME = 'mobile'; + private DIALOG_REF: MatDialogRef; + + // + isMobile$ = this.managerService.isMobileUi$; + + // tslint:disable-next-line:no-output-on-prefix + @Output() + onResult = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + private dialog: MatDialog, + private managerService: XManagerService + ) { + super(config); + } + //#endregion + + // + //#region Actions ... + /** + * Open Select Map Dialog ... + */ + async openDialog(config?: XColorPickerDialogConfig) { + // + // Prevent Double Bubble ... + if (this.DIALOG_REF) { + return; + } + + // + // Prepare Config ... + if (!config) { + config = {}; + } + + // + // Set Default Title ... + if (config && !config.title) { + config.title = XResourceIDs.color_picker_dialog_title; + } + + // + // Prepare Dialog Config ... + const data: MatDialogConfig = { + data: config, + }; + + // + data.panelClass = 'fullscreen-dialog'; + + // + // Show Cropper inside a Dialog ... + this.DIALOG_REF = this.dialog.open(XColorPickerDialogComponent, data); + this.DIALOG_REF.disableClose = true; + + // + // Attach Mobile Class ... + this.isMobile$.subscribe((isMobile) => { + // + if (isMobile) { + if (this.DIALOG_REF) { + this.DIALOG_REF.addPanelClass(this.MOBILE_CLASS_NAME); + } + } else { + if (this.DIALOG_REF) { + this.DIALOG_REF.removePanelClass(this.MOBILE_CLASS_NAME); + } + } + }); + + // + // Handle Backdrop ... + this.DIALOG_REF.backdropClick().subscribe(() => { + // + // Cancel Dialog ... + this.onResult.emit({ result: XDialogResult.Cancel }); + + // + this.closeDialog(); + }); + + // + // Handle Dialog Result ... + this.DIALOG_REF.afterClosed().subscribe( + (model: XColorPickerDialogResult) => { + // + if (isNullOrUndefined(model) || model.result !== XDialogResult.Ok) { + // + // Cancel Dialog ... + this.onResult.emit({ result: XDialogResult.Cancel }); + } else { + // + // Handle Results ... + this.onResult.emit(model); + } + + // + this.closeDialog(); + } + ); + } + + /** + * Close Select Map Dialog ... + */ + closeDialog() { + // + // Prevent Go Forward ... + if (!this.DIALOG_REF) { + return; + } + + // + // Close Dialog ... + this.DIALOG_REF.close(); + this.DIALOG_REF = null; + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-color-picker\x-color-picker-dialog\x-color-picker-dialog.typings.ts + +import { XDialogResult } from 'x-framework-core'; +import { IXColorPickerContainer } from '../x-color-picker.typings'; +import { XBaseDialogConfig } from '../../classes/i-x-base-dialog.container'; + +export interface XColorPickerDialogResult { + payload?: string; + result: XDialogResult; +} + +export interface XColorPickerDialogConfig + extends XBaseDialogConfig, + Partial {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-counter\x-counter.component.html + + + + + + + + +
+ +
+ + +
+ +
+
+
+ + + +

+ {{ displayMessage }} +

+
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-counter\x-counter.component.scss + +.display-message-container { + margin: 2px; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-counter\x-counter.component.ts + +import { + XCounterTick, + XCounterType, + XCounterState, + XCounterAction, + XCounterActionModel, + XCounterTypeIdentifier, + XCounterContainerComponent, +} from './x-counter.typings'; +import { nameof } from 'x-framework-core'; +import { Subscription, interval, Subject } from 'rxjs'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; + +@Component({ + selector: 'x-counter', + templateUrl: './x-counter.component.html', + styleUrls: ['./x-counter.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XCounterComponent extends XCounterContainerComponent { + // + //#region Props ... + private StepCount: number; + private IsCounting: boolean; + private TYPE: XCounterTypeIdentifier; + private TimerSubscription: Subscription; + + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + displayMessage = ''; + //#endregion + + // + //#region LifeCycles ... + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region Handle Values ... + const isStopValueChanged = changeKeys.includes( + nameof('stopValue') + ); + const isStartValueChanged = changeKeys.includes( + nameof('startValue') + ); + const isCurrentValueChanged = changeKeys.includes( + nameof('currentValue') + ); + if (isStopValueChanged || isStartValueChanged || isCurrentValueChanged) { + this.setBoundaries(this.startValue, this.stopValue, this.currentValue); + } + //#endregion + + // + //#region Handle Type Changed ... + const isTypeChanged = changeKeys.includes( + nameof('type') + ); + if (isTypeChanged) { + await this.prepareType(); + } + //#endregion + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + } + + async onInit() { + super.onInit(); + + // + await this.prepareType(); + + // + // Try to Show Start Value ... + this.updateDisplayMessage(); + + // + await this.handleAutoStart(); + } + + onDestroy() { + super.onDestroy(); + + // + if (this.TimerSubscription) { + this.TimerSubscription.unsubscribe(); + this.TimerSubscription = null; + } + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region UI Providers ... + //#endregion + + // + //#region UI Handlers ... + //#endregion + + // + //#region Actions ... + setBoundaries(start: number, stop: number, current: number) { + // + this.startValue = start; + this.stopValue = stop; + this.currentValue = current; + } + + async start() { + // + const isUpdated = await this.updateValues(); + + // + if (isUpdated && !this.TimerSubscription && this.getStepsCount() > 0) { + this.startCounter(); + } + } + + pause() { + // + if (this.TimerSubscription) { + this.IsCounting = false; + this.TimerSubscription.unsubscribe(); + this.TimerSubscription = null; + } + } + + cancel() { + this.currentValue = this.stopValue; + } + + getCounterState(): XCounterState { + // + if ( + this.TYPE === XCounterType.CountDown && + this.currentValue === this.startValue + ) { + return XCounterState.Start; + } + + // + if ( + this.TYPE === XCounterType.CountUp && + this.currentValue === this.stopValue + ) { + return XCounterState.Start; + } + + // + if (this.isLastStep()) { + return XCounterState.Stop; + } + + // + if ( + this.TYPE === XCounterType.CountDown && + this.currentValue > this.startValue + ) { + return XCounterState.Tick; + } + + // + if ( + this.TYPE === XCounterType.CountUp && + this.currentValue < this.startValue + ) { + return XCounterState.Tick; + } + + // + return XCounterState.Stop; + } + //#endregion + + // + //#region Private ... + // + //#region ActionProvider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + switch (model.action) { + // + case XCounterAction.START: + await this.start(); + break; + + // + case XCounterAction.PAUSE: + this.pause(); + break; + + // + case XCounterAction.CANCEL: + this.cancel(); + break; + + // + case XCounterAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + private startCounter() { + // + this.StepCount = this.getStepsCount(); + + // + if (!this.IsCounting && this.StepCount && this.StepCount > 0) { + // + this.IsCounting = true; + this.TimerSubscription = interval(this.delay).subscribe(() => { + // + this.updateDisplayMessage(); + + // + const eventModel = this.getTickModel(); + + // + if (eventModel.state === XCounterState.Start) { + this.counterStart.emit(eventModel); + } + + // + if (eventModel.state === XCounterState.Stop) { + this.counterStop.emit(eventModel); + } + + // + this.counterTtick.emit(eventModel); + + // + if (this.isLastStep()) { + this.pause(); + } else if (this.TYPE === XCounterType.CountDown) { + this.currentValue--; + } else if (this.TYPE === XCounterType.CountUp) { + this.currentValue++; + } + }); + } + } + + private getTickModel(): XCounterTick { + return { + type: this.TYPE, + start: this.startValue, + stop: this.stopValue, + current: this.currentValue, + delay: this.delay, + state: this.getCounterState(), + } as XCounterTick; + } + + private getStepsCount() { + // + let count = -1; + + // + switch (this.TYPE) { + case XCounterType.CountDown: + count = this.startValue - this.stopValue; + break; + + case XCounterType.CountUp: + count = this.stopValue - this.startValue; + break; + } + + // + if (isNaN(count)) { + count = -1; + } + + // + return count; + } + + private isLastStep() { + // + switch (this.TYPE) { + // + case XCounterType.CountDown: + return this.currentValue <= this.stopValue; + + // + default: + case XCounterType.CountUp: + return this.currentValue >= this.startValue; + } + } + + private updateValues() { + return new Promise((res) => { + // + switch (this.TYPE) { + case XCounterType.CountDown: + // + if (this.startValue < this.stopValue) { + res(false); + return; + } + + // + if (!this.currentValue) { + this.currentValue = this.startValue; + } + break; + + case XCounterType.CountUp: + if (this.startValue > this.stopValue) { + res(false); + return; + } + + // + if (!this.currentValue) { + this.currentValue = this.stopValue; + } + break; + } + + // + res(true); + }); + } + + private updateDisplayMessage() { + // + this.displayMessage = this.displayPattern( + this.stopValue, + this.startValue, + this.currentValue + ); + } + + // + private async prepareType() { + this.TYPE = await this.getValueAsync(this.type); + } + + // + private async handleAutoStart() { + // + const isAutoStart = await this.getValueAsync(this.autoStart); + if (!isAutoStart) { + return; + } + + // + await this.start(); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-counter\x-counter.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XCardModule } from '../x-card/x-card.module'; +import { XCounterComponent } from './x-counter.component'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XCounterComponent], + imports: [XCardModule, CommonModule, XBaseComponentModule], + exports: [XCardModule, CommonModule, XCounterComponent, XBaseComponentModule], +}) +export class XCounterModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-counter\x-counter.typings.ts + +import { + Input, + Output, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XComponentCardContainer, + IXComponentCardContainer, + XComponentCardContainerComponent, +} from '../x-card/x-card.typings'; +import { concatMap, map } from 'rxjs/operators'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XOneOrManyType, XStandardType } from 'x-framework-core'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export enum XCounterType { + CountUp = 'count_up', + CountDown = 'count_down', +} + +export type XCounterTypeIdentifier = XCounterType | string; + +export enum XCounterState { + Start, + Tick, + Stop, +} + +export interface XCounterTick { + type: XCounterType; + start: number; + stop: number; + current: number; + delay: number; + state: XCounterState; +} + +export const defaultStringPatternMaker = ( + max: number, + min: number, + current: number +) => { + return `${current}`; +}; + +export enum XCounterAction { + START = 'start', + PAUSE = 'pause', + CANCEL = 'cancel', + + // + Refresh = 'refresh', +} + +export type XCounterActionIdentifier = XCounterAction | string; + +export interface XCounterActionModel + extends XBaseActionModel {} + +// +//#region Container ... +export interface IXCounterContainer extends IXComponentCardContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + type: XStandardType; + autoStart: XStandardType; + stopValue: number; + startValue: number; + currentValue: number; + delay: number; + + // + displayPattern: (min: number, max: number, current: number) => string; + + // + counterStart: EventEmitter; + counterTtick: EventEmitter; + counterStop: EventEmitter; +} + +@Injectable({ + providedIn: 'root', +}) +export class XCounterContainer + extends XComponentCardContainer + implements Readonly +{ + // + //#region Props ... + readonly actionProvider: Subject = + new Subject(); + + readonly cssClass: XStandardType>; + + // + readonly type: XStandardType = XCounterType.CountDown; + readonly autoStart: XStandardType = false; + readonly stopValue: number; + readonly startValue: number; + readonly currentValue: number; + readonly delay: number = 1000; + + // + readonly displayPattern: ( + min: number, + max: number, + current: number + ) => string = defaultStringPatternMaker; + + // + readonly counterStart: EventEmitter = + new EventEmitter(); + readonly counterTtick: EventEmitter = + new EventEmitter(); + readonly counterStop: EventEmitter = + new EventEmitter(); + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XCounterContainerComponent + extends XComponentCardContainerComponent + implements IXCounterContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + @Input() + type: XStandardType = this.propertyProvider.type; + + @Input() + autoStart: XStandardType = false; + + @Input() + stopValue: number = this.propertyProvider.stopValue; + + @Input() + startValue: number = this.propertyProvider.startValue; + + @Input() + currentValue: number = this.propertyProvider.currentValue; + + @Input() + delay = this.propertyProvider.delay; + + @Input() + displayPattern: (min: number, max: number, current: number) => string = + this.propertyProvider.displayPattern; + + // + @Output() + counterStart: EventEmitter = new EventEmitter(); + + @Output() + counterTtick: EventEmitter = new EventEmitter(); + + @Output() + counterStop: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XCounterContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXCounterContainer + ): Observable { + return super + .extractProps(container ? container : this) + .pipe( + concatMap((superData) => { + return forkJoin({ + // + cssClass: this.getArrayValue( + container ? container.cssClass : this.cssClass + ), + + // + type: this.getValue(container ? container.type : this.type), + autoStart: this.getValue( + container ? container.autoStart : this.autoStart + ), + stopValue: this.getValue( + container ? container.stopValue : this.stopValue + ), + startValue: this.getValue( + container ? container.startValue : this.startValue + ), + currentValue: this.getValue( + container ? container.currentValue : this.currentValue + ), + delay: this.getValue(container ? container.delay : this.delay), + displayPattern: this.getValue( + container ? container.displayPattern : this.displayPattern + ), + }).pipe( + map((data) => { + return { + superData, + data, + }; + }) + ); + }) + ) + .pipe( + map((info) => { + return { + // + ...info.superData, + + // + ...info.data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + counterStart: this.counterStart, + counterTtick: this.counterTtick, + counterStop: this.counterStop, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-data-pager\x-data-pager.component.html + + + + + + + + +
+ +
+ + +
+ +
+
+
+ + + +
+ + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + {{ + preparedPaginatorOptions.pageInfoProvider( + managerService.currentLocale, + currentPage, + pageSize, + totalPages, + totalItems + ) + }} + + + + +
+ + +
+ + {{ applyLocale(pageSize) }} + +
+
+
+ + + + + + + + + + + + + +
+
+ + + + + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-data-pager\x-data-pager.component.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-data-pager\x-data-pager.component.ts + +import { + nameof, + XLocale, + toNumber, + hasChild, + applyLocale, + XModalButtonRole, + XPickerColumnOption, + XBaseDataPagerQuery, +} from 'x-framework-core'; +import { + XDataPagerAction, + XDataPagerActionModel, + XDataPagerPresentType, + XDataPagerPaginatorOptions, + XDataPagerPaginatorPosition, + XDataPagerContainerComponent, +} from './x-data-pager.typings'; +import { Subject, Subscription } from 'rxjs'; +import { Component, ViewChild } from '@angular/core'; +import { XIconNames } from '../x-icon/x-icon.typings'; +import { ChangeDetectionStrategy } from '@angular/core'; +import { MatPaginator } from '@angular/material/paginator'; + +@Component({ + selector: 'x-data-pager', + templateUrl: './x-data-pager.component.html', + styleUrls: ['./x-data-pager.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XDataPagerComponent extends XDataPagerContainerComponent { + // + //#region Props ... + // + private MODAL: any; + private PAGE_SIZE: number; + private TOTAL_PAGES: number; + private TOTAL_ITEMS: number; + private CURRENT_PAGE: number; + private PAGINATOR: MatPaginator; + private PAGINATOR_PAGE_SUBSCRIPTION: Subscription; + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + private PAGINATOR_OPTIONS: XDataPagerPaginatorOptions; + + // + readonly DataPagerPresetTypes = Object.assign({}, XDataPagerPresentType); + readonly DataPagerPaginatorPositions = Object.assign( + {}, + XDataPagerPaginatorPosition + ); + + // + showPageNumberInput: boolean; + + // + @ViewChild(MatPaginator, { static: false }) + set paginator(v: MatPaginator) { + if (!v) { + return; + } + + // + this.PAGINATOR = v; + this.setPaginatorData(); + } + + get paginator() { + return this.PAGINATOR; + } + + get pageSize() { + return this.PAGE_SIZE; + } + + get currentPage() { + return this.CURRENT_PAGE; + } + + get totalPages() { + return this.TOTAL_PAGES; + } + + get totalItems() { + return this.TOTAL_ITEMS; + } + + get preparedPaginatorOptions() { + return this.PAGINATOR_OPTIONS; + } + //#endregion + + // + //#region LifeCycles ... + async onInit() { + super.onInit(); + + await this.preparePaginatorOptions(); + } + + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + + // + //#region PaginatorOptionChanged ... + const isPaginatorOptionsChanged = changeKeys.includes( + nameof('paginatorOptions') + ); + if (isPaginatorOptionsChanged) { + await this.preparePaginatorOptions(); + } + //#endregion + + // + //#region Query Changed ... + const isTaskChanged = changeKeys.includes( + nameof('task') + ); + const isQueryChanged = changeKeys.includes( + nameof('query') + ); + if (isQueryChanged || isTaskChanged) { + await this.handleQueryChanged(); + } + //#endregion + + // + //#region Result Changed ... + const isResultChanged = changeKeys.includes( + nameof('result') + ); + if (isResultChanged) { + await this.handleResultChanged(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region UI Handlers ... + async handlePageChange(event: CustomEvent | any) { + // + const selectedPage = (event.detail.value as number) || undefined; + if (!selectedPage) { + return; + } + + // + await this.toPage(selectedPage); + } + + async handlePageSizeChange(event?: any) { + // + if (!this.preparedPaginatorOptions || !this.pageSize) { + return; + } + + // + const pageSizes = await this.getValueAsync( + this.preparedPaginatorOptions.pageSizes + ); + if (!hasChild(pageSizes)) { + return; + } + + // + const options: XPickerColumnOption[] = pageSizes.map((ps) => { + // + const opt: XPickerColumnOption = { + text: this.applyLocale(ps.toString()), + value: ps, + disabled: ps === this.pageSize, + }; + + // + return opt; + }); + + // + if (this.MODAL) { + await this.managerService.dialogService.pickerController.dismiss(); + this.MODAL = null; + } + + // + this.MODAL = await this.managerService.dialogService.presentPicker({ + buttons: [ + { + text: this.resourceProvider(this.ResourceIDs.accept), + role: XModalButtonRole.Selected, + handler: (value) => { + if (!value) { + return; + } + + // + const pageSize = value.pageSize.value; + this.changePageSize(pageSize); + }, + }, + { + text: this.resourceProvider(this.ResourceIDs.cancel), + role: XModalButtonRole.Cancel, + }, + ], + columns: [{ name: 'pageSize', options }], + }); + + // + await this.MODAL.onWillDismiss(); + this.MODAL = null; + } + //#endregion + + // + //#region Actions ... + // + //#region Pager Actions ... + async firstPage() { + // + if (this.uiDisabled || this.loading) { + return; + } + + // + const hasPaginator = await this.getValueAsync(this.showPaginator); + if (!hasPaginator || !this.paginator) { + return; + } + + // + this.paginator.firstPage(); + } + + async lastPage() { + // + if (this.uiDisabled || this.loading) { + return; + } + + // + const hasPaginator = await this.getValueAsync(this.showPaginator); + if (!hasPaginator || !this.paginator) { + return; + } + + // + this.paginator.lastPage(); + } + + async nextPage() { + // + if (this.uiDisabled || this.loading) { + return; + } + + // + const hasPaginator = await this.getValueAsync(this.showPaginator); + if (!hasPaginator || !this.paginator) { + return; + } + + // + this.paginator.nextPage(); + } + + async previousPage() { + // + if (this.uiDisabled || this.loading) { + return; + } + + // + const hasPaginator = await this.getValueAsync(this.showPaginator); + if (!hasPaginator || !this.paginator) { + return; + } + + // + this.paginator.previousPage(); + } + + async toPage(index: number) { + // + if (this.uiDisabled || this.loading) { + return; + } + + // + const hasPaginator = await this.getValueAsync(this.showPaginator); + if (!hasPaginator || !this.paginator) { + return; + } + + // + index = index === 0 ? 1 : index; + + // + this.paginator.pageIndex = index - 1; + this.paginator.page.next({ + pageIndex: index - 1, + pageSize: this.pageSize, + length: this.totalItems, + }); + } + + async changePageSize(size: number) { + // + if (this.uiDisabled || this.loading) { + return; + } + + // + if (!size || !this.paginator) { + return; + } + + // + this.paginator.pageSize = size; + this.paginator.page.next({ + pageIndex: 0, + pageSize: size, + length: this.totalItems, + }); + } + //#endregion + //#endregion + + // + //#region Private ... + // + //#region Action Provider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XDataPagerAction.Refresh: + this.detectChanges(); + break; + + // + case XDataPagerAction.GoFirst: + await this.firstPage(); + break; + + // + case XDataPagerAction.GoLast: + await this.lastPage(); + break; + + // + case XDataPagerAction.GoNext: + await this.nextPage(); + break; + + // + case XDataPagerAction.GoPrev: + await this.previousPage(); + break; + + // + case XDataPagerAction.ToPage: + if (model.payload) { + await this.toPage(model.payload); + } + break; + + // + case XDataPagerAction.ChangePageSize: + if (model.payload) { + await this.changePageSize(model.payload); + } + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + // + private async handleQueryChanged() { + // + this.result = null; + + // + if (!this.query || !this.task) { + return; + } + + // + const mTask = this.task(this.query); + + // + this.taskReady.emit(mTask); + } + + private async handleResultChanged() { + // + if (!this.result) { + return; + } + + // + //#region Extract Required Options ... + this.CURRENT_PAGE = this.query.pageNumber; + this.PAGE_SIZE = this.query.rowsInPage; + this.TOTAL_PAGES = this.calculatePageCount( + this.result.count, + this.query.rowsInPage + ); + this.TOTAL_ITEMS = this.result.count; + //#endregion + + // + if (!this.preparedPaginatorOptions) { + await this.preparePaginatorOptions(); + } + + // + await this.setPaginatorData(); + } + + private async setPaginatorData() { + // + if (!this.PAGINATOR) { + return; + } + + // + this.PAGINATOR.pageSize = this.PAGE_SIZE; + this.PAGINATOR.pageIndex = this.CURRENT_PAGE - 1; + this.PAGINATOR.length = this.TOTAL_ITEMS; + + // + // Prepare Paginator Options ... + await this.preparePaginatorOptions(); + await this.registerPaginatorHandlers(); + } + + private async preparePaginatorOptions() { + // + if (!this.result) { + return; + } + + // + //#region Preparing Options ... + // + const defaultOptions = this.defaultPaginatorOptions(); + const options = await this.getValueAsync(this.paginatorOptions); + const preparedOptions: XDataPagerPaginatorOptions = { + ...defaultOptions, + ...options, + pageSize: this.query.rowsInPage, + }; + + // + if (options) { + // + if (!options.first) { + preparedOptions.first = undefined; + } else { + // + const first = options.first; + + // + if (first.disabled) { + const firstDisabled = await this.getValueAsync(first.disabled); + preparedOptions.first.disabled = + defaultOptions.first.disabled && firstDisabled; + } else { + preparedOptions.first.disabled = defaultOptions.first.disabled; + } + + // + if (!first.icon) { + preparedOptions.first.icon = defaultOptions.first.icon; + } + + // + if (!first.title) { + preparedOptions.first.title = defaultOptions.first.title; + } + } + + // + if (!options.last) { + preparedOptions.last = undefined; + } else { + // + const last = options.last; + + // + if (last.disabled) { + const lastDisabled = await this.getValueAsync(last.disabled); + preparedOptions.last.disabled = + defaultOptions.last.disabled && lastDisabled; + } else { + preparedOptions.last.disabled = defaultOptions.last.disabled; + } + + // + if (!last.icon) { + preparedOptions.last.icon = defaultOptions.last.icon; + } + + // + if (!last.title) { + preparedOptions.last.title = defaultOptions.last.title; + } + } + + // + if (!options.next) { + preparedOptions.next = undefined; + } else { + // + const next = options.next; + + // + if (next.disabled) { + const nextDisabled = await this.getValueAsync(next.disabled); + preparedOptions.next.disabled = + defaultOptions.next.disabled && nextDisabled; + } else { + preparedOptions.next.disabled = defaultOptions.next.disabled; + } + + // + if (!next.icon) { + preparedOptions.next.icon = defaultOptions.next.icon; + } + + // + if (!next.title) { + preparedOptions.next.title = defaultOptions.next.title; + } + } + + // + if (!options.prev) { + preparedOptions.prev = undefined; + } else { + // + const prev = options.prev; + + // + if (prev.disabled) { + const prevDisabled = await this.getValueAsync(prev.disabled); + preparedOptions.prev.disabled = + defaultOptions.prev.disabled && prevDisabled; + } else { + preparedOptions.prev.disabled = defaultOptions.prev.disabled; + } + + // + if (!prev.icon) { + preparedOptions.prev.icon = defaultOptions.prev.icon; + } + + // + if (!prev.title) { + preparedOptions.prev.title = defaultOptions.prev.title; + } + } + } + + // + this.PAGINATOR_OPTIONS = preparedOptions; + + // + this.detectChanges(); + //#endregion + } + + private async registerPaginatorHandlers() { + // + //#region PageHandler Actions ... + // + if (!this.PAGINATOR) { + return; + } + + // + // Unsubscribe last ... + if (this.PAGINATOR_PAGE_SUBSCRIPTION) { + this.PAGINATOR_PAGE_SUBSCRIPTION.unsubscribe(); + this.PAGINATOR_PAGE_SUBSCRIPTION = null; + } + + // + // Subscribe New ... + this.PAGINATOR_PAGE_SUBSCRIPTION = this.applyTakeUntilWrapper( + this.paginator.page.asObservable() + ).subscribe(async (res) => { + // + // Prepare New Query ... + const query: XBaseDataPagerQuery = { + ...this.query, + // + pageNumber: res.pageIndex + 1, + rowsInPage: res.pageSize, + }; + + // + // Notify Query Changed ... + this.queryChange.emit(query); + }); + //#endregion + } + + private defaultPaginatorOptions() { + // + const options: XDataPagerPaginatorOptions = { + pageSizes: this.config.pageSizes, + pageSize: this.config.defaultPageSize, + first: { + title: 'first', + icon: XIconNames.first, + disabled: !this.paginator || !this.paginator.hasPreviousPage(), + }, + last: { + title: 'last', + icon: XIconNames.last, + disabled: !this.paginator || !this.paginator.hasNextPage(), + }, + prev: { + title: 'previous', + icon: XIconNames.previous, + disabled: !this.paginator || !this.paginator.hasPreviousPage(), + }, + next: { + title: 'next', + icon: XIconNames.next, + disabled: !this.paginator || !this.paginator.hasNextPage(), + }, + pageInfoProvider: ( + locale: XLocale, + currentPage: number, + pageSize: number, + totalPages: number, + totalItems: number + ) => { + // + const info = `${currentPage}/${totalPages}`; + return applyLocale(info, locale); + }, + }; + + // + return options; + } + + private calculatePageCount(itemsCount: number, pageSize: number) { + // + // Data Correction ... + pageSize = toNumber(pageSize); + itemsCount = toNumber(itemsCount); + + // + // Result ... + return Math.floor((itemsCount + (pageSize - 1)) / pageSize); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-data-pager\x-data-pager.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XCardModule } from '../x-card/x-card.module'; +import { XIconModule } from '../x-icon/x-icon.module'; +import { XEmptyModule } from '../x-empty/x-empty.module'; +import { XDataPagerComponent } from './x-data-pager.component'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XDataPagerComponent], + imports: [ + XCardModule, + XIconModule, + XEmptyModule, + CommonModule, + XBaseComponentModule, + ], + exports: [ + XIconModule, + XCardModule, + XEmptyModule, + CommonModule, + XDataPagerComponent, + XBaseComponentModule, + ], +}) +export class XDataPagerModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-data-pager\x-data-pager.typings.ts + +import { + Input, + Output, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + TemplateRef, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XStandardType, + XOneOrManyType, + XColorIdentifier, + XBaseDataPagerQuery, + XColorWithBrightness, + XBaseDataPagerQueryResult, +} from 'x-framework-core'; +import { + XCardEmptyContainer, + XCardEmptyContainerComponent, +} from '../classes/i-x-card-empty.container'; +import { map, concatMap } from 'rxjs/operators'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { XTablePaginatorOptions } from '../x-table/x-table.typings'; +import { IXCardEmptyContainer } from '../classes/i-x-card-empty.container'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export enum XDataPagerPaginatorPosition { + Above = 'above', + Below = 'below', +} + +export type XDataPagerPaginatorPositionIdentifier = + | XDataPagerPaginatorPosition + | string; + +// tslint:disable-next-line:no-empty-interface +export interface XDataPagerPaginatorOptions extends XTablePaginatorOptions {} + +export enum XDataPagerPresentType { + Item = 'item', + Page = 'page', +} + +export type XDataPagerPresentTypeIdentifier = XDataPagerPresentType | string; + +export enum XDataPagerAction { + GoFirst = 'go_first', + GoLast = 'go_last', + GoNext = 'go_next', + GoPrev = 'go_prev', + ToPage = 'to_page', + ChangePageSize = 'change_page_size', + Refresh = 'refresh', +} + +export type XDataPagerActionIdentifier = XDataPagerAction | string; + +export interface XDataPagerActionModel + extends XBaseActionModel {} + +// +//#region Inside Card/Empty Container ... +export interface IXDataPagerContainer extends IXCardEmptyContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + pageChanged: EventEmitter; + type: XStandardType; + itemTemplate: TemplateRef; + + // + result: XBaseDataPagerQueryResult; + task: ( + query: XBaseDataPagerQuery + ) => Observable>; + taskReady: EventEmitter>>; + query: XBaseDataPagerQuery; + queryChange: EventEmitter; + + // + showPaginator: XStandardType; + paginatorPosition: XStandardType; + paginatorColor: XStandardType; + paginatorOptions: XStandardType; + + // + emptyListTemplate: TemplateRef; +} + +@Injectable({ + providedIn: 'root', +}) +export class XDataPagerContainer + extends XCardEmptyContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider = new Subject(); + readonly cssClass: XStandardType>; + + // + readonly pageChanged: EventEmitter = new EventEmitter(); + readonly type: XStandardType = + XDataPagerPresentType.Item; + readonly itemTemplate: TemplateRef; + + // + readonly result: XBaseDataPagerQueryResult; + readonly task: ( + query: XBaseDataPagerQuery + ) => Observable>; + readonly taskReady: EventEmitter>> = + new EventEmitter>>(); + readonly query: XBaseDataPagerQuery; + readonly queryChange: EventEmitter = + new EventEmitter(); + + // + readonly showPaginator: XStandardType; + readonly paginatorPosition: XStandardType = + XDataPagerPaginatorPosition.Below; + readonly paginatorColor: XStandardType = + XColorWithBrightness.Light; + readonly paginatorOptions: XStandardType; + + // + readonly emptyListTemplate: TemplateRef; + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XDataPagerContainerComponent + extends XCardEmptyContainerComponent + implements IXDataPagerContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Output() + pageChanged: EventEmitter = new EventEmitter(); + + @Input() + type: XStandardType = + this.propertyProvider.type; + + @Input() + itemTemplate: TemplateRef = this.propertyProvider.itemTemplate; + + // + @Input() + result: XBaseDataPagerQueryResult = this.propertyProvider.result; + + @Input() + task: ( + query: XBaseDataPagerQuery + ) => Observable> = this.propertyProvider.task; + + @Output() + taskReady: EventEmitter>> = + new EventEmitter(); + + @Input() + query: XBaseDataPagerQuery = this.propertyProvider.query; + + @Output() + queryChange: EventEmitter = new EventEmitter(); + + // + @Input() + showPaginator: XStandardType = this.propertyProvider.showPaginator; + + @Input() + paginatorPosition: XStandardType = + this.propertyProvider.paginatorPosition; + + @Input() + paginatorColor: XStandardType = + this.propertyProvider.paginatorColor; + + @Input() + paginatorOptions: XStandardType = + this.propertyProvider.paginatorOptions; + + // + @Input() + emptyListTemplate: TemplateRef = this.propertyProvider.emptyListTemplate; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XDataPagerContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXDataPagerContainer + ): Observable { + return super + .extractProps(container ? container : this) + .pipe( + concatMap((superData) => { + return forkJoin({ + // + cssClass: this.getArrayValue( + container ? container.cssClass : this.cssClass + ), + + // + type: this.getValue(container ? container.type : this.type), + itemTemplate: this.getValue( + container ? container.itemTemplate : this.itemTemplate + ), + result: this.getValue(container ? container.result : this.result), + task: this.getValue(container ? container.task : this.task), + query: this.getValue(container ? container.query : this.query), + showPaginator: this.getValue( + container ? container.showPaginator : this.showPaginator + ), + paginatorPosition: this.getValue( + container ? container.paginatorPosition : this.paginatorPosition + ), + paginatorColor: this.getValue( + container ? container.paginatorColor : this.paginatorColor + ), + paginatorOptions: this.getValue( + container ? container.paginatorOptions : this.paginatorOptions + ), + + // + emptyListTemplate: this.getValue( + container ? container.emptyListTemplate : this.emptyListTemplate + ), + }).pipe( + map((data) => { + return { + superData, + data, + }; + }) + ); + }) + ) + .pipe( + map((info) => { + return { + // + ...info.superData, + + // + ...info.data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + pageChanged: this.pageChanged, + taskReady: this.taskReady, + queryChange: this.queryChange, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-data-scroller\x-data-scroller.component.html + + + + + + + + +
+ +
+ + +
+ +
+
+
+ + + +
+ + + +
+
+ + + + + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-data-scroller\x-data-scroller.component.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-data-scroller\x-data-scroller.component.ts + +import { + XDataScrollerAction, + XDataScrollerActionModel, + XDataScrollerContainerComponent, +} from './x-data-scroller.typings'; +import { Component } from '@angular/core'; +import { of, Subject, Subscription } from 'rxjs'; +import { ChangeDetectionStrategy } from '@angular/core'; +import { nameof, hasChild, XBaseDataPagerQuery } from 'x-framework-core'; + +@Component({ + selector: 'x-data-scroller', + templateUrl: './x-data-scroller.component.html', + styleUrls: ['./x-data-scroller.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XDataScrollerComponent extends XDataScrollerContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + canNext: boolean = true; + items: any[] = []; + //#endregion + + // + //#region LifeCycles ... + async onInit() { + super.onInit(); + } + + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + + // + //#region Query Changed ... + const isTaskChanged = changeKeys.includes( + nameof('task') + ); + const isQueryChanged = changeKeys.includes( + nameof('query') + ); + if (isQueryChanged || isTaskChanged) { + await this.handleQueryChanged(); + } + //#endregion + + // + //#region Result Changed ... + const isResultChanged = changeKeys.includes( + nameof('result') + ); + if (isResultChanged) { + await this.handleResultChanged(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region UI Handlers ... + //#endregion + + // + //#region Actions ... + async refreshItems() { + // + if (this.uiDisabled || this.loading) { + return; + } + + // + this.items = []; + this.result = null; + this.canNext = true; + this.notifyCanNext(); + + // + const query: XBaseDataPagerQuery = { + pageNumber: 1, + rowsInPage: this.query.rowsInPage, + }; + + // + // Notify Query Changed ... + this.queryChange.emit(query); + } + + async loadMore() { + // + if (!this.canNext || this.uiDisabled || this.loading) { + this.notifyCanNext(); + return; + } + + // + // Prepare New Query ... + const query: XBaseDataPagerQuery = { + ...this.query, + // + pageNumber: this.query.pageNumber + 1, + rowsInPage: this.query.rowsInPage, + }; + + // + // Notify Query Changed ... + this.queryChange.emit(query); + } + + async clearItems() { + // + this.items = []; + this.result = null; + this.canNext = true; + this.notifyCanNext(); + + // + this.detectChanges(); + } + //#endregion + + // + //#region Private ... + // + //#region Action Provider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.notifyCanNext(); + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XDataScrollerAction.Refresh: + this.detectChanges(); + break; + + // + case XDataScrollerAction.LoadMore: + await this.loadMore(); + break; + + // + case XDataScrollerAction.RefreshItems: + await this.refreshItems(); + break; + + // + case XDataScrollerAction.ClearItems: + await this.clearItems(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + // + private async handleQueryChanged() { + // + this.result = null; + + // + if (!this.query || !this.task) { + return; + } + + // + const mTask = this.task(this.query); + + // + this.taskReady.emit(mTask); + } + + private async handleResultChanged() { + // + if (!this.result || !hasChild(this.result.items)) { + return; + } + + // + of(...this.result.items).subscribe({ + next: item => { + setTimeout(() => { + // + this.items.push(item); + this.detectChanges(); + }, 100); + }, + error: (err) => {}, + complete: () => { + this.canNext = this.items.length < this.result.count; + this.notifyCanNext(); + } + }) + } + + private notifyCanNext() { + // + if (!this.actionProvider) { + return; + } + + // + this.actionProvider.next({ + action: XDataScrollerAction.CanNext, + payload: this.canNext, + }); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-data-scroller\x-data-scroller.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XCardModule } from '../x-card/x-card.module'; +import { XEmptyModule } from '../x-empty/x-empty.module'; +import { XDataScrollerComponent } from './x-data-scroller.component'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XDataScrollerComponent], + imports: [XCardModule, CommonModule, XEmptyModule, XBaseComponentModule], + exports: [ + XCardModule, + CommonModule, + XEmptyModule, + XBaseComponentModule, + XDataScrollerComponent, + ], +}) +export class XDataScrollerModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-data-scroller\x-data-scroller.typings.ts + +import { + Input, + Output, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + TemplateRef, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XStandardType, + XOneOrManyType, + XBaseDataPagerQuery, + XBaseDataPagerQueryResult, +} from 'x-framework-core'; +import { + IXCardEmptyContainer, + XCardEmptyContainer, + XCardEmptyContainerComponent, +} from '../classes/i-x-card-empty.container'; +import { concatMap, map } from 'rxjs/operators'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export enum XDataScrollerAction { + Refresh = 'refresh', + RefreshItems = 'refresh_items', + LoadMore = 'load_more', + ClearItems = 'clear_items', + CanNext = 'can_next', +} + +export type XDataScrollerActionIdentifier = XDataScrollerAction | string; + +export interface XDataScrollerActionModel + extends XBaseActionModel {} + +// +//#region Inside Card/Empty Container ... +export interface IXDataScrollerContainer extends IXCardEmptyContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + result: XBaseDataPagerQueryResult; + task: (query: XBaseDataPagerQuery) => Observable; + taskReady: EventEmitter>; + query: XBaseDataPagerQuery; + queryChange: EventEmitter; + + // + emptyListTemplate: TemplateRef; +} + +@Injectable({ + providedIn: 'root', +}) +export class XDataScrollerContainer + extends XCardEmptyContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider = new Subject(); + readonly cssClass: XStandardType>; + + // + readonly result: XBaseDataPagerQueryResult; + readonly task: (query: XBaseDataPagerQuery) => Observable; + readonly taskReady = new EventEmitter>(); + readonly query: XBaseDataPagerQuery; + readonly queryChange = new EventEmitter(); + + // + readonly emptyListTemplate: TemplateRef; + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XDataScrollerContainerComponent + extends XCardEmptyContainerComponent + implements IXDataScrollerContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + result: XBaseDataPagerQueryResult = this.propertyProvider.result; + + @Input() + task: (query: XBaseDataPagerQuery) => Observable = + this.propertyProvider.task; + + @Output() + taskReady: EventEmitter> = new EventEmitter(); + + @Input() + query: XBaseDataPagerQuery = this.propertyProvider.query; + + @Output() + queryChange: EventEmitter = new EventEmitter(); + + // + @Input() + emptyListTemplate: TemplateRef; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XDataScrollerContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXDataScrollerContainer + ): Observable { + return super + .extractProps(container ? container : this) + .pipe( + concatMap((superData) => { + return forkJoin({ + // + cssClass: this.getArrayValue( + container ? container.cssClass : this.cssClass + ), + + // + result: this.getValue(container ? container.result : this.result), + task: this.getValue(container ? container.task : this.task), + query: this.getValue(container ? container.query : this.query), + }).pipe( + map((data) => { + return { + superData, + data, + }; + }) + ); + }) + ) + .pipe( + map((info) => { + return { + // + ...info.superData, + + // + ...info.data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + emptyListTemplate: container + ? container.emptyListTemplate + : this.emptyListTemplate, + + // + taskReady: this.taskReady, + queryChange: this.queryChange, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-description-list\x-description-list.component.html + + + + + + + + +
+ +
+ + +
+ +
+
+
+ + + + +
+ + +
+ + + +
+ +
+ + {{ getTitle(key) | async }} + +
+ + + +
+ + + + {{ getContent(key, preparedModel[key]) | async }} + + + + + + + + + + + +
+ + +
+
+ + +
+
+ + +
+ +
+
+
+
+
+ + + +
+ + + + + +
+ {{ getTitle(key) | async }} +
+ + +
+ + +
+
+
+
+
+
+
+ + + + + + + + + + + + + + + {{ value }} + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-description-list\x-description-list.component.scss + +:host ::ng-deep .x-description-list { + &.isLegacy { + .x-card { + .x-description-list-title { + padding: { + left: 0; + right: 0; + } + } + + .x-description-list-description:not(.full-width) { + padding: { + left: 0; + right: 0; + } + } + } + + .x-description-list-title { + padding: { + left: 4px; + right: 4px; + } + } + + .x-description-list-description:not(.full-width) { + padding: { + left: 4px; + right: 4px; + } + } + + dl { + margin: { + top: 0; + bottom: 0; + } + } + + dl.inline dd { + display: inline; + margin: 0; + } + + dl.inline dd::after { + white-space: pre; + content: "\a"; + } + + dl.inline dt { + display: inline-block; + min-width: 100px; + } + + dt { + margin-top: 8px; + margin-bottom: 8px; + } + + dt::after { + content: ": "; + } + + dd { + text-align: center; + + // + &.full-width { + margin-inline-end: 0 !important; + margin-inline-start: 0 !important; + } + } + } + + &.isNotLegacy { + dd { + width: 100%; + display: inline; + margin-inline-start: 8px; + } + + .x-description-list-item-container { + width: 100%; + } + + .x-title-span::after { + content: ": "; + } + + x-slotter { + width: 100%; + display: inline-block; + } + + .x-title-span, + .x-decription-span { + max-width: calc(90%); + } + + .x-slotter { + .x-start-top-slot { + flex: 1 1 auto !important; + } + + .x-end-bottom-slot { + flex: 1 1 0% !important; + } + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-description-list\x-description-list.component.ts + +import { + Component, + QueryList, + ContentChildren, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + tap, + map, + filter, + mergeMap, + concatMap, + switchMap, +} from 'rxjs/operators'; +import { + keys, + nameof, + isDate, + isArray, + toArray, + isObject, + isNumber, + isString, + toXString, + isFunction, + isSameObject, + XModelProperty, + XOneOrManyType, + toNormalString, + getPropertyValue, + setPropertyValue, + isNullOrUndefined, + isNullOrEmptyString, +} from 'x-framework-core'; +import { + XDescriptionListAction, + XDescriptionListActionModel, + XDescriptionListContainerComponent, +} from './x-description-list.typings'; +import { XSlotLayout, XSlotName } from '../x-slotter/x-slotter.typings'; +import { of, from, Subject, forkJoin, Observable, Subscription } from 'rxjs'; +import { XDescriptionListItemComponent } from './x-description-list-item/x-description-list-item.component'; + +@Component({ + selector: 'x-description-list', + templateUrl: './x-description-list.component.html', + styleUrls: ['./x-description-list.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XDescriptionListComponent< + T = any +> extends XDescriptionListContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + private keyPrefix = ''; + private MODEL: T; + + // + readonly SlotNames = Object.assign({}, XSlotName); + readonly SlotLayouts = Object.assign({}, XSlotLayout); + + // + get prefix() { + return this.keyPrefix; + } + + get preparedModel() { + return this.MODEL; + } + + // + @ContentChildren(XDescriptionListItemComponent) + items: QueryList; + //#endregion + + // + //#region LifeCycles ... + async onInit() { + super.onInit(); + + // + await this.prepareModel(); + } + + async afterViewInit() { + super.afterViewInit(); + + // + // prepare KeyPrefix ... + const keyPrefix = this.element.nativeElement + ? (this.element.nativeElement as HTMLElement).getAttribute('keyPrefix') + : ''; + if (!isNullOrEmptyString(keyPrefix)) { + this.keyPrefix += `${keyPrefix}`; + } + } + + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region Model Changed ... + const isModelChanged = changeKeys.includes( + nameof('model') + ); + + // + if (isModelChanged) { + await this.prepareModel(); + } + //#endregion + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region UI Providers ... + keys(model: T) { + // + let modelKeys = keys(model); + + // + if (!this.hasChild(this.sortOrderModelKeys)) { + return modelKeys; + } + + // + const sortOrderArray = toArray(this.sortOrderModelKeys) as string[]; + const result: string[] = [ + ...sortOrderArray.filter((k) => modelKeys.includes(k)), + ...modelKeys.filter((k) => !sortOrderArray.includes(k)), + ]; + + // + return result; + } + + canShowKey(key: string) { + // + return forkJoin({ + key: this.getValue(key), + model: this.getValue(this.model), + keyPrefix: this.getValue(this.keyPrefix), + hiddenModelKeys: this.getArrayValue(this.hiddenModelKeys), + hideOptionalValues: this.getValue(this.hideOptionalValues), + availableModelKeys: this.getArrayValue(this.availableModelKeys), + }).pipe( + map((provider) => { + // + const value = provider.model[provider.key]; + + // + const modelHasValue = + !isNullOrUndefined(provider.model) && + !isNullOrUndefined(value) && + !isNullOrEmptyString(toXString(provider.model[provider.key])); + const hasChildHiddens = this.hasChild(provider.hiddenModelKeys); + const hasChildAvailables = this.hasChild(provider.availableModelKeys); + + // + if (!hasChildAvailables && !hasChildHiddens) { + // + if (!modelHasValue && provider.hideOptionalValues) { + return false; + } + + // + return true; + } + + // + const isInHiddens = provider.hiddenModelKeys.includes( + provider.key as any + ); + const isInAvailables = provider.availableModelKeys.includes( + provider.key as any + ); + + // + if ( + (!hasChildAvailables && !isInHiddens) || + (isInAvailables && !hasChildHiddens) || + (isInAvailables && !isInHiddens) || + (!isInAvailables && + !isInHiddens && + isNumber(provider.key) && + !isNullOrEmptyString(provider.keyPrefix)) + ) { + // + if (!modelHasValue && provider.hideOptionalValues) { + return false; + } + + // + return true; + } + + // + return false; + }) + ); + } + + canShowLabelKey(key: string) { + return this.getArrayValue(this.hiddenLabelModelKeys).pipe( + map((hiddenLabelsModelKeys) => { + // + const hasChildHiddenLabels = this.hasChild(hiddenLabelsModelKeys); + + // + if (!hasChildHiddenLabels) { + return true; + } + + // + const isInHiddens = hiddenLabelsModelKeys.includes(key as any); + return hasChildHiddenLabels && !isInHiddens; + }) + ); + } + + getTitle(key: string): Observable { + return this.getValue(this.modelKeyProvider).pipe( + concatMap((provider) => { + return isNullOrUndefined(provider) || !isFunction(provider) + ? this.getValue(key) + : this.getValue(provider(key as any)); + }) + ); + } + + hasContentItem(key: string) { + return of(this.items).pipe( + concatMap((items) => { + return isNullOrUndefined(items) + ? of(false) + : this.getArrayValue(items.toArray()).pipe( + switchMap((items) => + from(items).pipe( + mergeMap((item) => + this.getValue(item.name).pipe( + map((name) => ({ name, item })) + ) + ), + filter( + (itemData) => + toNormalString(itemData.name) === toNormalString(key) + ), + map((dataObject) => !isNullOrUndefined(dataObject)) + ) + ) + ); + }) + ); + } + + getContentItem( + key: string, + value: any + ): Observable { + return this.hasContentItem(key).pipe( + concatMap((isExists) => { + return !isExists + ? of(null) + : of(this.items).pipe( + concatMap((items) => { + return isNullOrUndefined(items) + ? of(null) + : this.getArrayValue(items.toArray()).pipe( + switchMap((items) => + from(items).pipe( + mergeMap((item) => + this.getValue(item.name).pipe( + map((name) => ({ name, item })) + ) + ), + filter( + (itemData) => + toNormalString(itemData.name) === + toNormalString(key) + ), + tap((dataObject) => (dataObject.item.value = value)), + map((dataObject) => dataObject.item) + ) + ) + ); + }) + ); + }) + ); + } + + getContent(key: string, value: any): Observable { + return this.getValue(this.modelValueProvider).pipe( + concatMap((provider) => { + return isNullOrUndefined(provider) || !isFunction(provider) + ? this.getValue(value) + : this.getValue(provider(key as any, value)); + }) + ); + } + + canDescribe(key: string, value: any) { + return of(value).pipe( + map( + (eVal) => + isDate(eVal) || + isFunction(eVal) || + (!isArray(eVal) && !isObject(eVal)) + ) + ); + } + + getDescriptionTitleClasses(key: string) { + return [`x-description-${key}-title`]; + } + + getDescriptionContentClasses(key: string) { + return [`x-description-${key}-title`]; + } + + // + //#region Neste ... + getKeyPrefix(key: string) { + // + const result = isNullOrEmptyString(this.keyPrefix) + ? `${key}` + : `${this.keyPrefix}.${key}`; + + // + return result; + } + + getHiddenModelKeys(key: string) { + return this.prepareNestedCollection(key, this.hiddenModelKeys); + } + + getSortOrderModelKeys(key: string) { + return this.prepareNestedCollection(key, this.sortOrderModelKeys); + } + + getAvailableModelKeys(key: string) { + return this.prepareNestedCollection(key, this.availableModelKeys); + } + + getHiddenLabelModelKeys(key: string) { + return this.prepareNestedCollection(key, this.hiddenLabelModelKeys); + } + //#endregion + //#endregion + + // + //#region UI Handlers ... + //#endregion + + // + //#region Actions ... + async setModelProperty(property: XModelProperty) { + // + if (!property || isNullOrEmptyString(property.key)) { + return; + } + + // + const model = this.MODEL; + const nodeValue = getPropertyValue(model, property.prefix); + const modelKeys = isNullOrUndefined(nodeValue) ? [] : keys(nodeValue); + if (!this.hasChild(modelKeys)) { + return; + } + + // + const isExists = modelKeys.includes(property.key as string); + if (!isExists) { + return; + } + + // + // normalize value ... + const normalValue = isString(property.value) + ? !isNullOrEmptyString(property.value) && + !isNullOrUndefined(property.value) + ? property.value.toString().trim() + : undefined + : isNullOrUndefined(property.value) + ? undefined + : property.value; + + // + if (nodeValue[property.key] === normalValue) { + return; + } + + // + const setterPropertyPath = isNullOrEmptyString(property.prefix) + ? `${property.key as string}` + : `${property.prefix}.${property.key as string}`; + + // + setPropertyValue(model, setterPropertyPath, normalValue); + + // + this.MODEL = { + ...model, + }; + this.detectChanges(); + this.onModelChanged.emit(model); + } + + async getModelProperty(key: keyof T | string) { + // + if (isNullOrEmptyString(key)) { + return null; + } + + // + const model = await this.getValueAsync(this.model); + const modelKeys = await this.getValueAsync(this.keys(model)); + if (!this.hasChild(modelKeys)) { + return null; + } + + // + const isExists = modelKeys.includes(key); + if (!isExists) { + return null; + } + + // + const result = model[key as any]; + return result; + } + + async getModelViewProperty(key: keyof T | string) { + // + if (isNullOrEmptyString(key)) { + return null; + } + + // + const model = await this.getValueAsync(this.model); + const modelKeys = await this.getValueAsync(this.keys(model)); + if (!this.hasChild(modelKeys)) { + return null; + } + + // + const isExists = modelKeys.includes(key); + if (!isExists) { + return null; + } + + // + const result = await this.getValueAsync( + this.getContent(key, model[key as any]) + ); + return result; + } + + async setModel(model: T) { + // + if (!model || isSameObject(model as any, this.MODEL as any)) { + return; + } + + // + this.model = { + ...model, + }; + this.MODEL = { + ...model, + }; + + // + await this.prepareModel(); + this.onModelChanged.emit(model); + } + + async getModel() { + // + const result = await this.getValueAsync(this.model); + return result; + } + //#endregion + + // + //#region Private ... + // + //#region ActionProvider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + let modelProperty: XModelProperty = null; + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + switch (model.action) { + // + case XDescriptionListAction.Refresh: + this.detectChanges(); + break; + + // + case XDescriptionListAction.ClearProperty: + // + modelProperty = model.payload as XModelProperty; + if ( + !model.payload || + isNullOrUndefined(model) || + isNullOrUndefined(model.payload) || + isNullOrEmptyString(model.action) + ) { + return; + } + + // + modelProperty.value = null; + + // + await this.setModelProperty(modelProperty); + break; + + // + case XDescriptionListAction.SetProperty: + // + modelProperty = model.payload as XModelProperty; + if ( + !model.payload || + isNullOrUndefined(model) || + isNullOrUndefined(model.payload) || + isNullOrEmptyString(model.action) + ) { + return; + } + + // + await this.setModelProperty(modelProperty); + break; + + // + case XDescriptionListAction.SetModel: + // + const resetModel = model.payload as T; + if (!model || !model.payload || !resetModel) { + return; + } + + // + await this.setModel(resetModel); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + private async prepareModel() { + // + this.MODEL = null; + this.detectChanges(); + + // + if (!this.model) { + return; + } + + // + this.MODEL = await this.getValueAsync(this.model); + this.detectChanges(); + } + + private prepareNestedCollection( + key: string, + collection: XOneOrManyType + ) { + // + return forkJoin({ + key: this.getValue(key), + keyPrefix: this.getValue(this.getKeyPrefix(key)), + model: this.getValue(this.preparedModel[key]), + collection: this.getArrayValue(collection), + }).pipe( + map((provider) => { + // + const allAvailableCollectionItems = provider.collection; + + // + const keySelector = isNullOrUndefined(provider.keyPrefix) + ? `${provider.key}.` + : `${provider.keyPrefix}.`; + + // + const canFilter = + !isNumber(provider.key) && + !provider.keyPrefix.includes(`${provider.key}.`); + const result = canFilter + ? allAvailableCollectionItems + .filter((k) => k.includes(`${provider.key}.`)) + .map((k) => k.replace(`${provider.key}.`, '')) + : allAvailableCollectionItems; + + // + return result; + }) + ); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-description-list\x-description-list.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XCardModule } from '../x-card/x-card.module'; +import { XSlotterModule } from '../x-slotter/x-slotter.module'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; +import { XDescriptionListComponent } from './x-description-list.component'; +import { XDescriptionListItemComponent } from './x-description-list-item/x-description-list-item.component'; + +@NgModule({ + declarations: [XDescriptionListComponent, XDescriptionListItemComponent], + imports: [XCardModule, CommonModule, XSlotterModule, XBaseComponentModule], + exports: [ + XCardModule, + CommonModule, + XSlotterModule, + XBaseComponentModule, + XDescriptionListComponent, + XDescriptionListItemComponent, + ], +}) +export class XDescriptionListModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-description-list\x-description-list.typings.ts + +import { + Input, + Output, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + TemplateRef, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XStandardType, + XOneOrManyType, + XColorIdentifier, + XModelFieldsType, + XModelKeyProvider, + XModelValueProvider, + XColorWithBrightness, +} from 'x-framework-core'; +import { + XComponentCardContainer, + IXComponentCardContainer, + XComponentCardContainerComponent, +} from '../x-card/x-card.typings'; +import { concatMap, map } from 'rxjs/operators'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export enum XDescriptionListAction { + // + ExpandAll = 'expand-all', + CollapseAll = 'collapse-all', + + // + ClearProperty = 'clear_property', + SetProperty = 'set_property', + + // + SetModel = 'set_model', + + // + Refresh = 'refresh', +} + +export type XDescriptionListActionIdentifier = XDescriptionListAction | string; + +export interface XDescriptionListActionModel + extends XBaseActionModel {} + +// +//#region Inside Card Container ... +export interface IXDescriptionListContainer + extends IXComponentCardContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + isLegacy: XStandardType; + showLabels: XStandardType; + color: XStandardType; + foregroundColor: XStandardType; + labelColor: XStandardType; + valueColor: XStandardType; + propertyActionTemplate: TemplateRef; + onModelChanged: EventEmitter; + + // + model: XStandardType; + + // + //#region Model Providers ... + hideOptionalValues: XStandardType; + availableModelKeys: XModelFieldsType; + hiddenModelKeys: XModelFieldsType; + hiddenLabelModelKeys: XModelFieldsType; + sortOrderModelKeys: XModelFieldsType; + modelKeyProvider: XModelKeyProvider; + modelValueProvider: XModelValueProvider; + //#endregion +} + +@Injectable({ + providedIn: 'root', +}) +export class XDescriptionListContainer + extends XComponentCardContainer + implements Readonly +{ + // + //#region Props ... + readonly actionProvider: Subject = + new Subject(); + + readonly cssClass: XStandardType>; + + // + readonly isLegacy: XStandardType = true; + readonly showLabels: XStandardType = false; + readonly color: XStandardType = XColorWithBrightness.Light; + readonly foregroundColor: XStandardType = false; + readonly labelColor: XStandardType = + XColorWithBrightness.TertiaryShade; + readonly valueColor: XStandardType = + XColorWithBrightness.Dark; + readonly propertyActionTemplate: TemplateRef; + + // + readonly onModelChanged: EventEmitter; + + // + readonly model: XStandardType; + + // + //#region Model Providers ... + readonly hideOptionalValues: XStandardType; + readonly availableModelKeys: XModelFieldsType; + readonly hiddenModelKeys: XModelFieldsType; + readonly hiddenLabelModelKeys: XModelFieldsType; + readonly sortOrderModelKeys: XModelFieldsType; + readonly modelKeyProvider: XModelKeyProvider; + readonly modelValueProvider: XModelValueProvider; + //#endregion + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XDescriptionListContainerComponent + extends XComponentCardContainerComponent + implements IXDescriptionListContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + isLegacy: XStandardType = this.propertyProvider.isLegacy; + + @Input() + showLabels: XStandardType = this.propertyProvider.showLabels; + + @Input() + color: XStandardType = this.propertyProvider.color; + + @Input() + foregroundColor: XStandardType = + this.propertyProvider.foregroundColor; + + @Input() + labelColor: XStandardType = + this.propertyProvider.labelColor; + + @Input() + valueColor: XStandardType = + this.propertyProvider.valueColor; + + @Input() + propertyActionTemplate: TemplateRef = + this.propertyProvider.propertyActionTemplate; + + @Output() + onModelChanged: EventEmitter = new EventEmitter(); + + // + @Input() + model: XStandardType = this.propertyProvider.model; + + // + //#region Model Providers ... + @Input() + hideOptionalValues: XStandardType = + this.propertyProvider.hideOptionalValues; + + @Input() + availableModelKeys: XModelFieldsType = + this.propertyProvider.availableModelKeys; + + @Input() + hiddenModelKeys: XModelFieldsType = this.propertyProvider.hiddenModelKeys; + + @Input() + hiddenLabelModelKeys: XModelFieldsType = + this.propertyProvider.hiddenLabelModelKeys; + + @Input() + sortOrderModelKeys: XModelFieldsType = + this.propertyProvider.sortOrderModelKeys; + + @Input() + modelKeyProvider: XModelKeyProvider = + this.propertyProvider.modelKeyProvider; + + @Input() + modelValueProvider: XModelValueProvider = + this.propertyProvider.modelValueProvider; + //#endregion + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XDescriptionListContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXDescriptionListContainer + ): Observable { + return super + .extractProps(container ? container : this) + .pipe( + concatMap((superData) => { + return forkJoin({ + // + cssClass: this.getArrayValue( + container ? container.cssClass : this.cssClass + ), + + // + isLegacy: this.getValue( + container ? container.isLegacy : this.isLegacy + ), + showLabels: this.getValue( + container ? container.showLabels : this.showLabels + ), + color: this.getValue(container ? container.color : this.color), + foregroundColor: this.getValue( + container ? container.foregroundColor : this.foregroundColor + ), + labelColor: this.getValue( + container ? container.labelColor : this.labelColor + ), + valueColor: this.getValue( + container ? container.valueColor : this.valueColor + ), + propertyActionTemplate: this.getValue( + container + ? container.propertyActionTemplate + : this.propertyActionTemplate + ), + model: this.getValue(container ? container.model : this.model), + }).pipe( + map((data) => { + return { + superData, + data, + }; + }) + ); + }) + ) + .pipe( + map((info) => { + return { + // + ...info.superData, + + // + ...info.data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + //#region Model Providers ... + hideOptionalValues: container + ? container.hideOptionalValues + : this.hideOptionalValues, + availableModelKeys: container + ? container.availableModelKeys + : this.availableModelKeys, + hiddenModelKeys: container + ? container.hiddenModelKeys + : this.hiddenModelKeys, + hiddenLabelModelKeys: container + ? container.hiddenLabelModelKeys + : this.hiddenLabelModelKeys, + sortOrderModelKeys: container + ? container.sortOrderModelKeys + : this.sortOrderModelKeys, + modelKeyProvider: container + ? container.modelKeyProvider + : this.modelKeyProvider, + modelValueProvider: container + ? container.modelValueProvider + : this.modelValueProvider, + //#endregion + + // + onModelChanged: this.onModelChanged, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-description-list\x-description-list-item\x-description-list-item.component.html + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-description-list\x-description-list-item\x-description-list-item.component.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-description-list\x-description-list-item\x-description-list-item.component.ts + +import { nameof } from 'x-framework-core'; +import { + XDescriptionListItemAction, + XDescriptionListItemActionModel, + XDescriptionListItemContainerComponent, +} from './x-description-list-item.typings'; +import { Subject, Subscription } from 'rxjs'; +import { Component, ViewChild, TemplateRef } from '@angular/core'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-description-list-item', + templateUrl: './x-description-list-item.component.html', + styleUrls: ['./x-description-list-item.component.scss'], +}) +export class XDescriptionListItemComponent extends XDescriptionListItemContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + @ViewChild('content', { static: false }) + content: TemplateRef; + //#endregion + + // + //#region LifeCycles ... + onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + //#endregion + + // + //#region Private ... + // + //#region ActionProvider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + // Handle Action Provider ... + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XDescriptionListItemAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-description-list\x-description-list-item\x-description-list-item.typings.ts + +import { + Input, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + ChangeDetectorRef, +} from '@angular/core'; +import { + XBaseContainer, + IXBaseContainer, + XBaseContainerComponent, +} from '../../classes/i-x-base.container'; +import { map } from 'rxjs/operators'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XOneOrManyType, XStandardType } from 'x-framework-core'; +import { XBaseActionModel } from '../../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../../config/x-framework-components.config'; + +export enum XDescriptionListItemAction { + Refresh = 'refresh', +} +export type XDescriptionListItemActionIdentifier = + | XDescriptionListItemAction + | string; + +export interface XDescriptionListItemActionModel + extends XBaseActionModel {} + +// +//#region Container ... +export interface IXDescriptionListItemContainer extends IXBaseContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + name: XStandardType; + value: XStandardType; +} + +@Injectable({ + providedIn: 'root' +}) +export class XDescriptionListItemContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + readonly actionProvider: Subject = + new Subject(); + + readonly cssClass: XStandardType>; + + // + readonly name: XStandardType; + readonly value: XStandardType; + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XDescriptionListItemContainerComponent + extends XBaseContainerComponent + implements IXDescriptionListItemContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + name: XStandardType = this.propertyProvider.name; + + @Input() + value: XStandardType = this.propertyProvider.value; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XDescriptionListItemContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXDescriptionListItemContainer + ): Observable { + return forkJoin({ + // + cssClass: this.getArrayValue( + container ? container.cssClass : this.cssClass + ), + + // + name: this.getValue(container ? container.name : this.name), + value: this.getValue(container ? container.value : this.value), + }).pipe( + map((data) => { + return { + // + ...data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-empty\x-empty.component.html + +
+

+ {{ getResourceValue(message) | async }} +

+ + + +
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-empty\x-empty.component.scss + +:host { +--padding: 6px; +} + +.x-empty { + padding: var(--padding); +} + +.x-empty-title { + padding-bottom: 6px; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-empty\x-empty.component.ts + +import { + XEmptyAction, + XEmptyActionModel, + XEmptyContainerComponent, +} from './x-empty.typings'; +import { nameof } from 'x-framework-core'; +import { Subject, Subscription } from 'rxjs'; +import { XButtonType } from '../x-button/x-button.typings'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-empty', + templateUrl: './x-empty.component.html', + styleUrls: ['./x-empty.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XEmptyComponent extends XEmptyContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + readonly ButtonTypes = Object.assign({}, XButtonType); + //#endregion + + // + //#region LifeCycles ... + onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region Action Provider Changed ... + // + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region Private ... + // + //#region ActionProvider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + // Handle Action Provider ... + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XEmptyAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-empty\x-empty.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XEmptyComponent } from './x-empty.component'; +import { XIconModule } from '../x-icon/x-icon.module'; +import { XButtonModule } from '../x-button/x-button.module'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XEmptyComponent], + imports: [XIconModule, XButtonModule, CommonModule, XBaseComponentModule], + exports: [XIconModule, XButtonModule, XEmptyComponent, XBaseComponentModule], +}) +export class XEmptyModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-empty\x-empty.typings.ts + +import { + Input, + Output, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XResourceIDs, + XStandardType, + XOneOrManyType, + XColorIdentifier, + XResourceIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { + XBaseContainer, + IXBaseContainer, + XBaseComponentContainer, + XBaseContainerComponent, + IXBaseComponentContainer, + XBaseComponentContainerComponent, +} from '../classes/i-x-base.container'; +import { map } from 'rxjs/operators'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { XIconIdentifier, XIconNames } from '../x-icon/x-icon.typings'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export enum XEmptyAction { + Refresh = 'refresh', +} + +export type XEmptyActionIdentifier = XEmptyAction | string; + +export interface XEmptyActionModel + extends XBaseActionModel {} + +// +//#region Container ... +export interface IXEmptyContainer extends IXBaseContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + color: XStandardType; + foregroundColor: XStandardType; + + // + message: XStandardType; + messageColor: XStandardType; + foregroundMessageColor: XStandardType; + + // + hasRefresh: XStandardType; + + // + refreshButtonColor: XStandardType; + foregroundRefreshButtonColor: XStandardType; + + // + refreshIcon: XStandardType; + + // + refresh: EventEmitter; +} + +@Injectable({ + providedIn: 'root', +}) +export class XEmptyContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + readonly actionProvider: Subject = + new Subject(); + + readonly cssClass: XStandardType>; + + // + readonly color: XStandardType = XColorWithBrightness.Light; + readonly foregroundColor: XStandardType = false; + + // + readonly messageColor: XStandardType = + XColorWithBrightness.Dark; + readonly foregroundMessageColor: XStandardType = true; + + // + readonly hasRefresh: XStandardType = true; + + // + readonly refreshButtonColor: XStandardType = + XColorWithBrightness.Dark; + readonly foregroundRefreshButtonColor: XStandardType = true; + readonly message: XStandardType = XResourceIDs.no_item; + + // + readonly refreshIcon: XStandardType = XIconNames.refresh; + + // + readonly refresh: EventEmitter = new EventEmitter(); + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XEmptyContainerComponent + extends XBaseContainerComponent + implements IXEmptyContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + color: XStandardType = this.propertyProvider.color; + + @Input() + foregroundColor: XStandardType = + this.propertyProvider.foregroundColor; + + // + @Input() + message: XStandardType = this.propertyProvider.message; + + @Input() + messageColor: XStandardType = + this.propertyProvider.messageColor; + + @Input() + foregroundMessageColor: XStandardType = + this.propertyProvider.foregroundMessageColor; + + // + @Input() + hasRefresh: XStandardType = this.propertyProvider.hasRefresh; + + // + @Input() + refreshButtonColor: XStandardType = + this.propertyProvider.refreshButtonColor; + + @Input() + foregroundRefreshButtonColor: XStandardType = + this.propertyProvider.foregroundRefreshButtonColor; + + // + @Input() + refreshIcon: XStandardType = + this.propertyProvider.refreshIcon; + + @Output() + refresh: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XEmptyContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXEmptyContainer + ): Observable { + return forkJoin({ + // + cssClass: this.getValue(container ? container.cssClass : this.cssClass), + + // + color: this.getValue(container ? container.color : this.color), + foregroundColor: this.getValue( + container ? container.foregroundColor : this.foregroundColor + ), + + // + messageColor: this.getValue( + container ? container.messageColor : this.messageColor + ), + foregroundMessageColor: this.getValue( + container + ? container.foregroundMessageColor + : this.foregroundMessageColor + ), + + // + hasRefresh: this.getValue( + container ? container.hasRefresh : this.hasRefresh + ), + refreshButtonColor: this.getValue( + container ? container.refreshButtonColor : this.refreshButtonColor + ), + foregroundRefreshButtonColor: this.getValue( + container + ? container.foregroundRefreshButtonColor + : this.foregroundRefreshButtonColor + ), + refreshIcon: this.getValue( + container ? container.refreshIcon : this.refreshIcon + ), + + // + message: this.getValue(container ? container.message : this.message), + }).pipe( + map((data) => { + return { + // + ...data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + refresh: this.refresh, + }; + }) + ); + } + //#endregion +} +//#endregion + +// +//#region ComponentContainer ... +type TXComponentEmptyContainer = { + [TKey in keyof IXEmptyContainer as `empty${Capitalize< + string & TKey + >}`]: IXEmptyContainer[TKey]; +}; + +export interface IXComponentEmptyContainer + extends TXComponentEmptyContainer, + IXBaseComponentContainer { + showEmpty: XStandardType; +} + +@Injectable({ + providedIn: 'root', +}) +export class XComponentEmptyContainer + extends XBaseComponentContainer + implements Readonly +{ + // + //#region Props ... + // + readonly showEmpty: XStandardType = false; + + // + readonly emptyActionProvider: Subject = + this.propertyProvider.actionProvider; + readonly emptyCssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + readonly emptyColor: XStandardType = + this.propertyProvider.color; + readonly emptyForegroundColor: XStandardType = + this.propertyProvider.foregroundColor; + + // + readonly emptyMessage: XStandardType = + this.propertyProvider.message; + readonly emptyMessageColor: XStandardType = + this.propertyProvider.messageColor; + readonly emptyForegroundMessageColor: XStandardType = + this.propertyProvider.foregroundMessageColor; + + // + readonly emptyHasRefresh: XStandardType = + this.propertyProvider.hasRefresh; + + // + readonly emptyRefreshButtonColor: XStandardType = + this.propertyProvider.refreshButtonColor; + readonly emptyForegroundRefreshButtonColor: XStandardType = + this.propertyProvider.foregroundRefreshButtonColor; + + // + readonly emptyRefreshIcon: XStandardType = + this.propertyProvider.refreshIcon; + + // + readonly emptyRefresh: EventEmitter = this.propertyProvider.refresh; + //#endregion + + // + //#region Constructor ... + constructor(protected propertyProvider: XEmptyContainer) { + super(); + } + //#endregion +} + +@Injectable({ + providedIn: 'root', +}) +@Component({ + template: ``, +}) +export abstract class XComponentEmptyContainerComponent + extends XBaseComponentContainerComponent + implements IXComponentEmptyContainer +{ + // + //#region Props ... + // + @Input() + showEmpty: XStandardType = this.propertyProvider.showEmpty; + + // + @Input() + emptyActionProvider: Subject = + this.propertyProvider.emptyActionProvider; + + @Input() + emptyCssClass: XStandardType> = + this.propertyProvider.emptyCssClass; + + // + @Input() + emptyColor: XStandardType = + this.propertyProvider.emptyColor; + + @Input() + emptyForegroundColor: XStandardType = + this.propertyProvider.emptyForegroundColor; + + // + @Input() + emptyMessage: XStandardType = + this.propertyProvider.emptyMessage; + + @Input() + emptyMessageColor: XStandardType = + this.propertyProvider.emptyMessageColor; + + @Input() + emptyForegroundMessageColor: XStandardType = + this.propertyProvider.emptyForegroundMessageColor; + + // + @Input() + emptyHasRefresh: XStandardType = + this.propertyProvider.emptyHasRefresh; + + // + @Input() + emptyRefreshButtonColor: XStandardType = + this.propertyProvider.emptyRefreshButtonColor; + + @Input() + emptyForegroundRefreshButtonColor: XStandardType = + this.propertyProvider.emptyForegroundRefreshButtonColor; + + // + @Input() + emptyRefreshIcon: XStandardType = + this.propertyProvider.emptyRefreshIcon; + + // + @Output() + emptyRefresh: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XComponentEmptyContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXComponentEmptyContainer + ): Observable { + return forkJoin({ + // + showEmpty: this.getValue( + container ? container.showEmpty : this.showEmpty + ), + emptyCssClass: this.getValue( + container ? container.emptyCssClass : this.emptyCssClass + ), + emptyColor: this.getValue( + container ? container.emptyColor : this.emptyColor + ), + emptyForegroundColor: this.getValue( + container ? container.emptyForegroundColor : this.emptyForegroundColor + ), + emptyMessage: this.getValue( + container ? container.emptyMessage : this.emptyMessage + ), + emptyMessageColor: this.getValue( + container ? container.emptyMessageColor : this.emptyMessageColor + ), + emptyForegroundMessageColor: this.getValue( + container + ? container.emptyForegroundMessageColor + : this.emptyForegroundMessageColor + ), + emptyHasRefresh: this.getValue( + container ? container.emptyHasRefresh : this.emptyHasRefresh + ), + emptyRefreshButtonColor: this.getValue( + container + ? container.emptyRefreshButtonColor + : this.emptyRefreshButtonColor + ), + emptyForegroundRefreshButtonColor: this.getValue( + container + ? container.emptyForegroundRefreshButtonColor + : this.emptyForegroundRefreshButtonColor + ), + emptyRefreshIcon: this.getValue( + container ? container.emptyRefreshIcon : this.emptyRefreshIcon + ), + }).pipe( + map((data) => { + return { + // + ...data, + + // + emptyActionProvider: container + ? container.emptyActionProvider + : this.emptyActionProvider, + + // + emptyRefresh: this.emptyRefresh, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-expandable-list\x-expandable-list.component.html + + + + + + + + +
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

{{ getResourceValue(item.title) | async }}

+
+ + + +
{{ getResourceValue(item.title) | async }}
+
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-expandable-list\x-expandable-list.component.scss + +:host ::ng-deep { + // + --divider-color: var(--x-color-dark); + + // + --child-color: var(--x-color-primary-shade-contrast); + --child-background-color: var(--x-color-primary-shade); + + // + --child-active-color: var(--x-color-dark-tint-contrast); + --child-active-background-color: var(--x-color-dark-tint); + + // + --highlight-color: var(--x-color-warning-contrast); + --highlight-background-color: var(--x-color-warning); + + // + --parent-color: var(--x-color-tertiary-tint-contrast); + --parent-background-color: var(--x-color-tertiary-tint); + + // + --parent-active-color: var(--x-color-dark-contrast); + --parent-active-background-color: var(--x-color-dark); +} + +:host ::ng-deep .invisible { + display: none; + visibility: hidden; +} + +ion-list, +ion-item, +ion-item-group { + width: 100%; + --background: transparent; + --inner-padding-end: 0px; + --inner-padding-start: 0px; + --color: var(--child-color); + --border-color: var(--divider-color); +} + +ion-item-divider { + width: 100%; + position: relative; + --border-color: var(--divider-color); +} + +h1, +h2, +h3, +h4, +h5, +h6 { + margin: 0; +} + +.item-parent { + // + width: 100%; + padding-top: 0; + padding-bottom: 0; + position: relative; + + // + color: var(--parent-color); + background-color: var(--parent-background-color); + + // + *, + ion-icon, + x-icon { + color: var(--parent-color) !important; + } +} + +.item-child { + padding-top: 0; + padding-bottom: 0; + display: block !important; + transition: opacity 0.2s ease-in; + color: var(--child-color) !important; + background-color: var(--child-background-color) !important; + + // + *, + ion-icon, + x-icon { + color: var(--child-color) !important; + } +} + +.item-child:hover, +.item-parent:hover { + color: var(--highlight-color) !important; + --color: var(--highlight-color) !important; + background-color: var(--highlight-background-color) !important; + + // + *, + ion-icon, + x-icon { + color: var(--highlight-color) !important; + } +} + +.item-parent.active-link, +.active-parent { + // + color: var(--parent-active-color) !important; + background-color: var(--parent-active-background-color) !important; + + // + *, + ion-icon, + x-icon { + color: var(--parent-active-color) !important; + } +} + +.item-child.active-link, +.active-child { + // + color: var(--child-active-color) !important; + background-color: var(--child-active-background-color) !important; + + // + *, + ion-icon, + x-icon { + color: var(--child-active-color) !important; + } +} + +ion-icon { + &.invisible { + visibility: hidden; + } +} + +.collapsed { + opacity: 0; + height: 0; +} + +.ion-hide { + display: none !important; +} + +x-card { + width: inherit; + height: inherit; +} + +.item-parent, +.expandable-item-parent { + // + cursor: pointer; + + // + min-height: 48px; + + // + &.active { + color: var(--parent-active-color); + background-color: var(--parent-active-background-color); + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-expandable-list\x-expandable-list.component.ts + +import { + nameof, + toArray, + hasChild, + parseXColor, + XOneOrManyType, + toNormalString, + isNullOrEmptyString, + getXColorCssVariableName, +} from 'x-framework-core'; +import { + findItemById, + findItemByRoute, + parentItemsById, + parentItemsByRoute, + containsItemsByRoute, +} from './x-expandable-list.tools'; +import { + XExpandableListItem, + XExpandableListAction, + XExpandableListCssVarKeys, + XExpandableListActionModel, + XExpandableListStartupState, + XExpandableListContainerComponent, +} from './x-expandable-list.typings'; +import { concatMap, map } from 'rxjs/operators'; +import { XIconNames } from '../x-icon/x-icon.typings'; +import { forkJoin, of, Subject, Subscription } from 'rxjs'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-expandable-list', + templateUrl: './x-expandable-list.component.html', + styleUrls: ['./x-expandable-list.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XExpandableListComponent extends XExpandableListContainerComponent { + // + //#region Props ... + // + private ACTIVATED_CHILD: XExpandableListItem; + private ACTIVATED_PARENTS: XExpandableListItem[]; + + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + //#endregion + + // + //#region LifeCycle ... + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region Item Changed ... + const isItemsChanged = changeKeys.includes( + nameof('items') + ); + if (isItemsChanged) { + await this.handleStartupState(); + } + //#endregion + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + + // + //#region Color Change Detectors ... + const isChildColorChanged = changeKeys.includes( + nameof('childColor') + ); + if (isChildColorChanged) { + await this.applyChildColor(); + } + + const isChildActiveColorChanged = changeKeys.includes( + nameof('childActiveColor') + ); + if (isChildActiveColorChanged) { + await this.applyChildActiveColor(); + } + + const isParentColorChanged = changeKeys.includes( + nameof('parentColor') + ); + if (isParentColorChanged) { + await this.applyParentColor(); + } + + const isParentActiveColorChanged = changeKeys.includes( + nameof('parentActiveColor') + ); + if (isParentActiveColorChanged) { + await this.applyParentActiveColor(); + } + + const isHighlightColorChanged = changeKeys.includes( + nameof('highlightColor') + ); + if (isHighlightColorChanged) { + await this.applyHighlightColor(); + } + + const isDividerColorChanged = changeKeys.includes( + nameof('dividerColor') + ); + if (isDividerColorChanged) { + await this.applyDividerColor(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region UI Provider Actions ... + hasChildItems(item: XExpandableListItem) { + // + if (!item || !item.childs) { + return of(false); + } + + return this.getValue(item.childs).pipe(map((childs) => hasChild(childs))); + } + + getIconName(item: XExpandableListItem) { + return this.isExpanded(item).pipe( + map((isExpanded) => + isExpanded + ? XIconNames.arrow_dropup.toString() + : XIconNames.arrow_dropdown.toString() + ) + ); + } + + isParentActivated(item: XExpandableListItem) { + // + return this.getValue(item).pipe( + concatMap((pItem) => + this.getValue(this.supportRouting).pipe( + map((supportRouting) => { + return { item: pItem, supportRouting }; + }) + ) + ), + concatMap((dObject) => { + // + if (!dObject || !dObject.item) { + return this.getValue(false); + } + + // + return this.getArrayValue(dObject.item.href).pipe( + map((hrefArray) => this.managerService.mergeRoutes(hrefArray)), + concatMap((route) => { + // + if (!this.ACTIVATED_PARENTS || !hasChild(this.ACTIVATED_PARENTS)) { + return this.getValue(false); + } + + // + if (dObject.supportRouting) { + // + const routes$ = forkJoin( + this.ACTIVATED_PARENTS.map((i) => + this.getArrayValue(i.href).pipe( + map((href) => this.managerService.mergeRoutes(href)) + ) + ) + ); + + // + return routes$.pipe( + map( + (routes) => + routes.findIndex( + (r) => toNormalString(r) === toNormalString(route) + ) > -1 + ) + ); + } else { + // + const ids$ = forkJoin(this.ACTIVATED_PARENTS.map((i) => i.id)); + + // + return ids$.pipe( + map( + (ids) => + ids.findIndex( + (i) => + toNormalString(i) === toNormalString(dObject.item.id) + ) > -1 + ) + ); + } + }) + ); + }) + ); + } + + isChildActivated(item: XExpandableListItem) { + // + return this.getValue(item).pipe( + concatMap((pItem) => + this.getValue(this.supportRouting).pipe( + map((supportRouting) => { + return { item: pItem, supportRouting }; + }) + ) + ), + concatMap((dObject) => { + // + if (!dObject || !dObject.item || !this.ACTIVATED_CHILD) { + return this.getValue(false); + } + + // + return this.getArrayValue(this.ACTIVATED_CHILD.href) + .pipe(map((hrefArray) => this.managerService.mergeRoutes(hrefArray))) + .pipe( + concatMap((activatedRoute) => { + return this.getArrayValue(dObject.item.href) + .pipe( + map((hrefArray) => this.managerService.mergeRoutes(hrefArray)) + ) + .pipe( + map((itemRoute) => { + return { itemRoute, activatedRoute }; + }), + map((routeObject) => { + // + if (dObject.supportRouting) { + return ( + toNormalString(routeObject.itemRoute) === + toNormalString(routeObject.activatedRoute) + ); + } else { + return ( + toNormalString(this.ACTIVATED_CHILD.id) === + toNormalString(dObject.item.id) + ); + } + }) + ); + }) + ); + }) + ); + } + + isHidden(item: XExpandableListItem) { + return !item || !item.hidden ? of(false) : this.getValue(item.hidden); + } + + isDisabled(item: XExpandableListItem) { + return !item || !item.disabled ? of(false) : this.getValue(item.disabled); + } + + isExpanded(item: XExpandableListItem) { + return !item || !item.opened ? of(false) : this.getValue(item.opened); + } + //#endregion + + // + //#region UI Handlers ... + async handleItemSelectedAction( + item: XExpandableListItem, + parent: XExpandableListItem + ) { + // + // Check Component State ... + if (this.loading || this.uiDisabled) { + return; + } + + // + // Emit Event Handler ... + this.itemSelected.emit(item); + + // + const isSupportRouting = await this.getValueAsync(this.supportRouting); + if (isSupportRouting) { + // + const currUrl: string = this.managerService.getRoute(); + const itemHref = await this.getArrayValueAsync(item.href); + const itemUrl = this.managerService.mergeRoutes(itemHref); + + // + if (currUrl === itemUrl) { + // + await this.setActivatedParent(parent); + await this.setActivatedChild(item); + } else { + await this.handleItemRoutingActions(item); + } + } else { + // + await this.setActivatedParent(parent); + await this.setActivatedChild(item); + } + } + //#endregion + + // + //#region Actions ... + async toggle( + item: XExpandableListItem, + siblings: XExpandableListItem[] + ) { + // + const isExpanded = await this.toPromise(this.isExpanded(item)); + item.opened = !isExpanded; + + // + const isCloseSibling = await this.toPromise( + this.getValue(this.closeSiblings) + ); + + // + if (isCloseSibling) { + this.handleCloseSiblings(item, siblings); + } + + // + if (this.loading || this.uiDisabled) { + return; + } + + // + this.itemSelected.emit(item); + + // + await this.handleItemRoutingActions(item, true); + } + + async expand(item: XExpandableListItem) { + item.opened = true; + } + + async expandAll() { + // + const items = await this.getArrayValueAsync(this.items); + await this.handleChangeState(items, true); + } + + async collapse(item: XExpandableListItem) { + item.opened = false; + } + + async collapseAll() { + // + const items = await this.getArrayValueAsync(this.items); + await this.handleChangeState(items, false); + } + //#endregion + + // + //#region Privates ... + // + //#region Action Provider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + switch (model.action) { + // + case XExpandableListAction.Refresh: + this.detectChanges(); + break; + + // + case XExpandableListAction.ExpandAll: + await this.expandAll(); + break; + + // + case XExpandableListAction.CollapseAll: + await this.collapseAll(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + // + //#region Parent and Child Avtivated ... + private async handleApplyCurrentItems() { + // + if (this.uiDisabled || this.loading) { + setTimeout(async () => await this.handleApplyCurrentItems(), 100); + } + + // + const supportRouting = await this.getValueAsync(this.supportRouting); + if (supportRouting) { + await this.handleApplyCurrentItemsByRouting(); + } else { + await this.handleApplyCurrentItemsById(); + } + + // + this.detectChanges(); + } + + private async handleApplyCurrentItemsByRouting() { + // + const currentRoute = this.managerService.getRoute(); + if (isNullOrEmptyString(currentRoute)) { + return; + } + + // + const foundedItem = await findItemByRoute(this.items, currentRoute); + const parentItems = await parentItemsByRoute(this.items, currentRoute); + + // + if (!foundedItem || !hasChild(parentItems)) { + // + if (foundedItem) { + // + this.ACTIVATED_CHILD = foundedItem; + this.ACTIVATED_PARENTS = [foundedItem]; + } + + // + return; + } + + // + this.ACTIVATED_PARENTS = parentItems; + this.ACTIVATED_CHILD = foundedItem; + } + + private async handleApplyCurrentItemsById() { + // + const items = await this.getArrayValueAsync(this.items); + const currentId = this.ACTIVATED_CHILD + ? this.ACTIVATED_CHILD.id + : hasChild(items) + ? items[0].id + : ''; + if (isNullOrEmptyString(currentId)) { + return; + } + + // + const foundedItem = await findItemById(this.items, currentId); + const parentItems = await parentItemsById(this.items, currentId); + + // + if (!foundedItem || !hasChild(parentItems)) { + return; + } + + // + this.ACTIVATED_PARENTS = parentItems; + this.ACTIVATED_CHILD = foundedItem; + } + //#endregion + + private async handleItemRoutingActions( + item: XExpandableListItem, + isParent = false + ) { + // + if (!item || this.uiDisabled || this.loading) { + return; + } + + // + const hidden = await this.getValueAsync(item.hidden); + const disabled = await this.getValueAsync(item.disabled); + if (hidden || disabled) { + return; + } + + // + //#region Handle Routing ... + // + const href = await this.getValueAsync(item.href); + const route = this.managerService.mergeRoutes(href); + const supportRouting = await this.getValueAsync(this.supportRouting); + const replaceUrl = await this.getOptionalValueAsync(item.replaceUrl); + const disableParentRouting = await this.getValueAsync( + this.disableParentRouting + ); + + // + // When just a not Child Parent with Href provided ... + const childsArr = !item.childs + ? [] + : await this.getArrayValueAsync(item.childs); + if (isParent && !hasChild(childsArr)) { + isParent = false; + } + + // + if (isParent && disableParentRouting) { + return; + } + + // + if (supportRouting && hasChild(href) && !isNullOrEmptyString(route)) { + await this.managerService.navigateByUrlReplace(route, {}, replaceUrl); + } + //#endregion + } + + private handleCloseSiblings( + item: XExpandableListItem, + siblings: XOneOrManyType> + ) { + // + const pSiblings = toArray(siblings); + const others = pSiblings.filter((i) => item.id !== i.id); + others.forEach((i) => (i.opened = false)); + + // + this.detectChanges(); + } + + private async handleChangeState( + items: XOneOrManyType>, + expand: boolean + ) { + // + const pItems = toArray(items); + pItems.forEach((i) => { + // + i.opened = expand; + + // + this.getValueAsync(i.childs).then((childs) => { + if (hasChild(childs)) { + this.handleChangeState(childs, expand); + } + }); + + // + this.detectChanges(); + }); + } + + private async handleStartupState() { + // + const items = (await this.getValueAsync( + this.items + )) as XExpandableListItem[]; + + // + await this.applyStartupState(items); + await this.handleApplyCurrentItems(); + } + + private async setActivatedParent(v: XExpandableListItem) { + // + const isExists = hasChild(this.ACTIVATED_PARENTS) + ? this.ACTIVATED_PARENTS.findIndex( + (p) => toNormalString(p.id) === toNormalString(v.id) + ) > 1 + : false; + + // + if (isExists) { + return; + } + + // + this.ACTIVATED_PARENTS = await containsItemsByRoute(this.items, v.href); + + // + this.detectChanges(); + } + + private async setActivatedChild(v: XExpandableListItem) { + // + const isSamed = this.ACTIVATED_CHILD + ? v.id === this.ACTIVATED_CHILD.id + : false; + + // + if (isSamed) { + return; + } + + // + this.ACTIVATED_CHILD = v; + + // + this.detectChanges(); + } + + private async applyStartupState( + items: XOneOrManyType> + ) { + // + if (!hasChild(items)) { + return; + } + + // + const startupState = await this.getValueAsync(this.startupState); + const pItems = await this.getArrayValueAsync(items); + + // + pItems.forEach((i) => { + // + // Handle StartUp State for Items ... + i.opened = + startupState === XExpandableListStartupState.CollapseAll + ? false + : startupState === XExpandableListStartupState.ExpandAll + ? true + : false; + + // + if (i.childs) { + this.getValueAsync(i.childs).then((childs) => { + this.applyStartupState(childs); + }); + } + + // + this.detectChanges(); + }); + + // + this.detectChanges(); + } + + // + //#region Color Appliers ... + private async applyChildColor() { + // + const color = await this.getValueAsync(this.childColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const fgColor = getXColorCssVariableName( + colorDescriptor.name, + true, + colorDescriptor.brightnessLevel + ); + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(fgColor) || isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XExpandableListCssVarKeys.ChildColor, `var(${fgColor})`); + this.setCssVar(XExpandableListCssVarKeys.ChildBgColor, `var(${bgColor})`); + + // + this.detectChanges(); + } + + private async applyChildActiveColor() { + // + const color = await this.getValueAsync(this.childActiveColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const fgColor = getXColorCssVariableName( + colorDescriptor.name, + true, + colorDescriptor.brightnessLevel + ); + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(fgColor) || isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar( + XExpandableListCssVarKeys.ChildActiveColor, + `var(${fgColor})` + ); + this.setCssVar( + XExpandableListCssVarKeys.ChildActiveBgColor, + `var(${bgColor})` + ); + + // + this.detectChanges(); + } + + private async applyParentColor() { + // + const color = await this.getValueAsync(this.parentColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const fgColor = getXColorCssVariableName( + colorDescriptor.name, + true, + colorDescriptor.brightnessLevel + ); + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(fgColor) || isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XExpandableListCssVarKeys.ParentColor, `var(${fgColor})`); + this.setCssVar(XExpandableListCssVarKeys.ParentBgColor, `var(${bgColor})`); + + // + this.detectChanges(); + } + + private async applyParentActiveColor() { + // + const color = await this.getValueAsync(this.parentActiveColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const fgColor = getXColorCssVariableName( + colorDescriptor.name, + true, + colorDescriptor.brightnessLevel + ); + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(fgColor) || isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar( + XExpandableListCssVarKeys.ParentActiveColor, + `var(${fgColor})` + ); + this.setCssVar( + XExpandableListCssVarKeys.ParentActiveBgColor, + `var(${bgColor})` + ); + + // + this.detectChanges(); + } + + private async applyHighlightColor() { + // + const color = await this.getValueAsync(this.highlightColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const fgColor = getXColorCssVariableName( + colorDescriptor.name, + true, + colorDescriptor.brightnessLevel + ); + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(fgColor) || isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XExpandableListCssVarKeys.HighlightColor, `var(${fgColor})`); + this.setCssVar( + XExpandableListCssVarKeys.HighlightBgColor, + `var(${bgColor})` + ); + + // + this.detectChanges(); + } + + private async applyDividerColor() { + // + const color = await this.getValueAsync(this.dividerColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const colorVar = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(colorVar)) { + return; + } + + // + this.setCssVar(XExpandableListCssVarKeys.DividerColor, `var(${colorVar})`); + + // + this.detectChanges(); + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-expandable-list\x-expandable-list.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XIconModule } from '../x-icon/x-icon.module'; +import { XCardModule } from '../x-card/x-card.module'; +import { XExpandableListComponent } from './x-expandable-list.component'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XExpandableListComponent], + imports: [CommonModule, XBaseComponentModule, XCardModule, XIconModule], + exports: [ + XCardModule, + XIconModule, + XBaseComponentModule, + XExpandableListComponent, + ], +}) +export class XExpandableListModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-expandable-list\x-expandable-list.tools.ts + +import { + toArray, + hasChild, + toPromise, + mergeRoutes, + XStandardType, + XOneOrManyType, + toNormalString, + isNullOrUndefined, + isNullOrEmptyString, +} from 'x-framework-core'; +import { XExpandableListItem } from './x-expandable-list.typings'; + +export async function toFlatItems( + items: XStandardType>> +) { + // + const result: XExpandableListItem[] = []; + + // + if (!items) { + return result; + } + + // + const pItemsArray = await toPromise(items); + if (!hasChild(pItemsArray)) { + return result; + } + + // + const pItems = toArray(pItemsArray); + for (const item of pItems) { + // + const childsArray = await toPromise(item.childs); + const isParent = hasChild(childsArray); + + // + result.push(item); + + // + if (isParent) { + // + const childsRes = await toFlatItems(item.childs); + result.push(...childsRes); + } + } + + // + return result; +} + +// +//#region By Route ... +export async function findItemByRoute( + items: XStandardType>>, + route: XStandardType> +) { + // + let result: XExpandableListItem = null; + + // + if (!items || !route) { + return result; + } + + // + const routeArray = await toPromise(route); + if (!hasChild(routeArray)) { + return result; + } + + // + const pRoute = mergeRoutes(routeArray); + if (isNullOrEmptyString(pRoute)) { + return result; + } + + // + const pItems = await toFlatItems(items); + if (!hasChild(pItems)) { + return result; + } + + // + for (const item of pItems) { + // + const itemRouteArray = await toPromise(item.href); + if (hasChild(itemRouteArray)) { + // + const itemRoute = mergeRoutes(itemRouteArray); + if ( + !isNullOrEmptyString(itemRoute) && + toNormalString(itemRoute) === toNormalString(pRoute) + ) { + result = item; + } + } + } + + // + return result; +} + +export async function containsItemsByRoute( + items: XStandardType>>, + route: XStandardType> +) { + // + const result: XExpandableListItem[] = []; + + // + if (!items || !route) { + return result; + } + + // + const routeArray = await toPromise(route); + if (!hasChild(routeArray)) { + return result; + } + + // + const pRoute = mergeRoutes(routeArray); + if (isNullOrEmptyString(pRoute)) { + return result; + } + + // + const pItems = await toFlatItems(items); + if (!hasChild(pItems)) { + return result; + } + + // + for (const item of pItems) { + // + const itemRouteArray = await toPromise(item.href); + if (hasChild(itemRouteArray)) { + // + const itemRoute = mergeRoutes(itemRouteArray); + if ( + !isNullOrEmptyString(itemRoute) && + toNormalString(pRoute).includes(toNormalString(itemRoute)) + ) { + result.push(item); + } + } + } + + // + return result; +} + +export async function parentItemsByRoute( + items: XStandardType>>, + route: XStandardType> +) { + // + let result: XExpandableListItem[] = []; + + // + if (!items || !route) { + return result; + } + + // + const parentItems = await containsItemsByRoute(items, route); + const foundedItem = await findItemByRoute(items, route); + if (!hasChild(parentItems) || isNullOrUndefined(foundedItem)) { + return result; + } + + // + result = parentItems.filter( + (i) => toNormalString(i.id) !== toNormalString(foundedItem.id) + ); + + // + return result; +} +//#endregion + +// +//#region By Id ... +export async function findItemById( + items: XStandardType>>, + id: XStandardType +) { + // + let result: XExpandableListItem = null; + + // + if (!items || !id) { + return result; + } + + // + const pId = await toPromise(id); + if (isNullOrEmptyString(pId)) { + return result; + } + + // + const pItems = await toFlatItems(items); + if (!hasChild(pItems)) { + return result; + } + + // + for (const item of pItems) { + // + const itemRouteArray = await toPromise(item.href); + if (hasChild(itemRouteArray)) { + // + if ( + !isNullOrEmptyString(item.id) && + toNormalString(item.id) === toNormalString(pId) + ) { + result = item; + } + } + } + + // + return result; +} + +export async function containsItemsById( + items: XStandardType>>, + id: XStandardType +) { + // + const result: XExpandableListItem[] = []; + + // + if (!items || !id) { + return result; + } + + // + const pId = await toPromise(id); + if (isNullOrEmptyString(pId)) { + return result; + } + + // + const pItems = await toFlatItems(items); + if (!hasChild(pItems)) { + return result; + } + + // + for (const item of pItems) { + // + const isSameId = toNormalString(item.id) === toNormalString(pId); + if (isSameId) { + result.push(item); + } else { + // + const childsArr = await toPromise(item.childs); + const childs = toArray(childsArr); + const isParent = hasChild(childs); + + // + if (isParent) { + const pChildsRes = await containsItemsById(childsArr, id); + result.push(...pChildsRes); + } + } + } + + // + return result; +} + +export async function parentItemsById( + items: XStandardType>>, + id: XStandardType +) { + // + let result: XExpandableListItem[] = []; + + // + if (!items || !id) { + return result; + } + + // + const parentItems = await containsItemsById(items, id); + const foundedItem = await findItemById(items, id); + if (!hasChild(parentItems) || isNullOrUndefined(foundedItem)) { + return result; + } + + // + result = parentItems.filter( + (i) => toNormalString(i.id) !== toNormalString(foundedItem.id) + ); + + // + return result; +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-expandable-list\x-expandable-list.typings.ts + +import { + Input, + Output, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + TemplateRef, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XStandardType, + XOneOrManyType, + XColorIdentifier, + XResourceIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { + XComponentCardContainer, + IXComponentCardContainer, + XComponentCardContainerComponent, +} from '../x-card/x-card.typings'; +import { concatMap, map } from 'rxjs/operators'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export interface XExpandableListItem { + id: string; + data: XStandardType; + opened?: XStandardType; + hidden?: XStandardType; + disabled?: XStandardType; + replaceUrl?: XStandardType; + title: XStandardType; + href?: XStandardType>; + description?: XStandardType; + childs?: XStandardType>>; +} + +export enum XExpandableListStartupState { + ExpandAll = 'expand-all', + CollapseAll = 'collapse-all', +} + +export type XExpandableListStartupStateIdentifier = + | XExpandableListStartupState + | string; + +export enum XExpandableListAction { + // + Refresh = 'refresh', + + // + ExpandAll = 'expand-all', + CollapseAll = 'collapse-all', +} + +export type XExpandableListActionIdentifier = XExpandableListAction | string; + +export interface XExpandableListActionModel + extends XBaseActionModel {} + +export enum XExpandableListCssVarKeys { + // + ChildColor = 'child-color', + ChildBgColor = 'child-background-color', + + // + ChildActiveColor = 'child-active-color', + ChildActiveBgColor = 'child-active-background-color', + + // + ParentColor = 'parent-color', + ParentBgColor = 'parent-background-color', + + // + ParentActiveColor = 'parent-active-color', + ParentActiveBgColor = 'parent-active-background-color', + + // + HighlightColor = 'highlight-color', + HighlightBgColor = 'highlight-background-color', + + // + DividerColor = 'divider-color', +} + +// +//#region Inside Card Container ... +export interface IXExpandableListContainer extends IXComponentCardContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + itemCssClass: XStandardType>; + + // + supportRouting: XStandardType; + disableParentRouting: XStandardType; + showParentDetailIcon: XStandardType; + showChildDetailIcon: XStandardType; + closeSiblings: XStandardType; + startupState: XStandardType; + items: XStandardType>>; + parentTemplate: TemplateRef; + childTemplate: TemplateRef; + + // + childColor: XStandardType; + childActiveColor: XStandardType; + parentColor: XStandardType; + parentActiveColor: XStandardType; + highlightColor: XStandardType; + dividerColor: XStandardType; + + // + itemSelected: EventEmitter>; +} + +@Injectable({ + providedIn: 'root', +}) +export class XExpandableListContainer + extends XComponentCardContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider: Subject; + readonly cssClass: XStandardType>; + + // + readonly itemCssClass: XStandardType>; + + // + readonly supportRouting: XStandardType = false; + readonly disableParentRouting: XStandardType = true; + readonly showParentDetailIcon: XStandardType = true; + readonly showChildDetailIcon: XStandardType = true; + readonly closeSiblings: XStandardType = false; + readonly startupState: XStandardType = + XExpandableListStartupState.CollapseAll; + readonly items: XStandardType>>; + readonly parentTemplate: TemplateRef; + readonly childTemplate: TemplateRef; + + // + readonly childColor: XStandardType = + XColorWithBrightness.PrimaryShade; + readonly childActiveColor: XStandardType = + XColorWithBrightness.Dark; + readonly parentColor: XStandardType = + XColorWithBrightness.TertiaryTint; + readonly parentActiveColor: XStandardType = + XColorWithBrightness.Dark; + readonly highlightColor: XStandardType = + XColorWithBrightness.Warning; + readonly dividerColor: XStandardType = + XColorWithBrightness.Dark; + + // + readonly itemSelected: EventEmitter> = + new EventEmitter>(); + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XExpandableListContainerComponent + extends XComponentCardContainerComponent + implements IXExpandableListContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + itemCssClass: XStandardType> = + this.propertyProvider.itemCssClass; + + // + @Input() + supportRouting: XStandardType = this.propertyProvider.supportRouting; + + @Input() + disableParentRouting: XStandardType = + this.propertyProvider.disableParentRouting; + + @Input() + showParentDetailIcon: XStandardType = + this.propertyProvider.showParentDetailIcon; + + @Input() + showChildDetailIcon: XStandardType = + this.propertyProvider.showChildDetailIcon; + + @Input() + closeSiblings: XStandardType = this.propertyProvider.closeSiblings; + + @Input() + startupState: XStandardType = + this.propertyProvider.startupState; + + @Input() + items: XStandardType>> = + this.propertyProvider.items; + + @Input() + parentTemplate: TemplateRef = this.propertyProvider.parentTemplate; + + @Input() + childTemplate: TemplateRef = this.propertyProvider.childTemplate; + + // + @Input() + childColor: XStandardType = + this.propertyProvider.childColor; + + @Input() + childActiveColor: XStandardType = + this.propertyProvider.childActiveColor; + + @Input() + parentColor: XStandardType = + this.propertyProvider.parentColor; + + @Input() + parentActiveColor: XStandardType = + this.propertyProvider.parentActiveColor; + + @Input() + highlightColor: XStandardType = + this.propertyProvider.highlightColor; + + @Input() + dividerColor: XStandardType = + this.propertyProvider.dividerColor; + + // + @Output() + itemSelected: EventEmitter> = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XExpandableListContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXExpandableListContainer + ): Observable { + return super + .extractProps(container ? container : this) + .pipe( + concatMap((superData) => { + return forkJoin({ + // + cssClass: this.getArrayValue( + container ? container.cssClass : this.cssClass + ), + + // + itemCssClass: this.getValue( + container ? container.itemCssClass : this.itemCssClass + ), + + // + supportRouting: this.getValue( + container ? container.supportRouting : this.supportRouting + ), + disableParentRouting: this.getValue( + container + ? container.disableParentRouting + : this.disableParentRouting + ), + showParentDetailIcon: this.getValue( + container + ? container.showParentDetailIcon + : this.showParentDetailIcon + ), + showChildDetailIcon: this.getValue( + container + ? container.showChildDetailIcon + : this.showChildDetailIcon + ), + closeSiblings: this.getValue( + container ? container.closeSiblings : this.closeSiblings + ), + startupState: this.getValue( + container ? container.startupState : this.startupState + ), + items: this.getValue(container ? container.items : this.items), + parentTemplate: this.getValue( + container ? container.parentTemplate : this.parentTemplate + ), + childTemplate: this.getValue( + container ? container.childTemplate : this.childTemplate + ), + + // + childColor: this.getValue( + container ? container.childColor : this.childColor + ), + childActiveColor: this.getValue( + container ? container.childActiveColor : this.childActiveColor + ), + parentColor: this.getValue( + container ? container.parentColor : this.parentColor + ), + parentActiveColor: this.getValue( + container ? container.parentActiveColor : this.parentActiveColor + ), + highlightColor: this.getValue( + container ? container.highlightColor : this.highlightColor + ), + dividerColor: this.getValue( + container ? container.dividerColor : this.dividerColor + ), + }).pipe( + map((data) => { + return { + superData, + data, + }; + }) + ); + }) + ) + .pipe( + map((info) => { + return { + // + ...info.superData, + + // + ...info.data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + itemSelected: this.itemSelected, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-expandable-panel\x-expandable-panel.component.html + + + + + + + + +
+ +
+ + +
+ +
+
+
+ + + + +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ + + + + +
+
+
+
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+
+ + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-expandable-panel\x-expandable-panel.component.scss + +:host { + --panel-header-size: 48px; +} + +.x-expandable-panel-header { + height: var(--panel-header-size); +} + +.applyColors { + .mat-expansion-panel-header { + // + color: var(--header-foreground-color, var(--x-color-light-contrast)); + background-color: var(--header-background-color, var(--x-color-light)); + + * { + color: var(--header-foreground-color, var(--x-color-light-contrast)); + } + + .mat-expansion-indicator { + &::after { + color: var( + --header-foreground-color, + var(--x-color-light-contrast) + ) !important; + } + } + + &:focus, + &:hover, + &.mat-expanded { + background: unset; + color: var( + --header-highlight-foreground-color, + var(--x-color-light-contrast) + ); + background-color: var( + --header-highlight-background-color, + var(--x-color-light) + ); + + * { + color: var( + --header-highlight-foreground-color, + var(--x-color-light-contrast) + ); + } + + .mat-expansion-indicator { + &::after { + color: var( + --header-highlight-foreground-color, + var(--x-color-light-contrast) + ); + } + } + } + } +} + +:host ::ng-deep .mat-expansion-indicator { + &::after { + color: var( + --header-foreground-color, + var(--x-color-light-contrast) + ) !important; + } +} + +.x-panel-actions, +.x-panel-custom-actions-wrapper { + width: 100%; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-expandable-panel\x-expandable-panel.component.ts + +import { + Component, + QueryList, + ContentChildren, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + nameof, + parseXColor, + XStandardType, + XModalButtonRole, + isNullOrEmptyString, + XColorWithBrightness, + getXColorCssVariableName, +} from 'x-framework-core'; +import { + XExpandablePanelAction, + XExpandablePanelCssVars, + XExpandablePanelActionModel, + XExpandablePanelStartupState, + XExpandablePandelContainerComponent, +} from './x-expandable-panel.typings'; +import { Subject, Subscription } from 'rxjs'; +import { concatMap, map } from 'rxjs/operators'; +import { XButtonType } from '../x-button/x-button.typings'; +import { XPanelComponent } from './x-panel/x-panel.component'; +import { XNPRButtonType } from '../classes/i-x-next-prev-reset.container'; + +@Component({ + selector: 'x-expandable-panel', + templateUrl: './x-expandable-panel.component.html', + styleUrls: ['./x-expandable-panel.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XExpandablePanelComponent extends XExpandablePandelContainerComponent { + // + //#region Props ... + // + private IS_ACCORDION: boolean; + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + readonly NPRButtonTypes = Object.assign({}, XNPRButtonType); + + // + //#region Panels Props ... + // + private PANELS: QueryList; + + // + @ContentChildren(XPanelComponent) + set panels(p: QueryList) { + // + if (!p) { + return; + } + + // + this.PANELS = p; + this.detectChanges(); + } + + // + get panels() { + return this.PANELS; + } + //#endregion + //#endregion + + // + //#region LifeCycles ... + async afterViewInit() { + super.afterViewInit(); + + // + await this.handleStartupState(); + } + + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + const isAccordionChanged = changeKeys.includes( + nameof('isAccordion') + ); + if (isAccordionChanged) { + this.IS_ACCORDION = await this.getValueAsync(this.isAccordion); + } + + // + const isHeaderColorChanged = changeKeys.includes( + nameof('headerColor') + ); + if (isHeaderColorChanged) { + await this.applyHeaderColor(); + } + + // + const isHeaderHighlighColorChanged = changeKeys.includes( + nameof('headerHighlightColor') + ); + if (isHeaderHighlighColorChanged) { + await this.applyHeaderHighlightColor(); + } + + // + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + //#endregion + + // + //#region Register Handlers ... + registerInstanceHandlers() { + super.registerInstanceHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region UI Provider ... + hasStepAction(panel: XPanelComponent, type: XNPRButtonType) { + // + switch (type) { + // + case this.NPRButtonTypes.Next: + return this.getOptionalValue(panel.hasNext); + + // + case this.NPRButtonTypes.Prev: + return this.getOptionalValue(panel.hasPrev); + + // + case this.NPRButtonTypes.Reset: + return this.getOptionalValue(panel.hasReset); + } + } + + hasPanels() { + return this.panels && this.panels.length > 0; + } + + hasActions(panel: XPanelComponent) { + return this.getValue(panel.hasNext).pipe( + concatMap((hasNext) => + this.getValue(panel.hasPrev).pipe( + map((hasPrev) => { + return { + hasNext, + hasPrev, + }; + }) + ) + ), + concatMap((hasNextPrev) => + this.getValue(panel.hasReset).pipe( + map((hasReset) => { + return { + ...hasNextPrev, + hasReset, + }; + }) + ) + ), + map((info) => info.hasNext || info.hasPrev || info.hasReset) + ); + } + + getIsAccordion() { + return this.IS_ACCORDION; + } + + getExpanded(panel: XPanelComponent) { + return this.getValue(panel.id).pipe( + concatMap((panelId) => + this.getValue(panel.expanded).pipe( + map((panelExpanded) => { + return { + panelId, + panelExpanded, + }; + }) + ) + ), + map((panelInfo) => { + return { + ...panelInfo, + isAccordion: this.IS_ACCORDION, + }; + }), + map((info) => + info.isAccordion ? this.selected === info.panelId : info.panelExpanded + ) + ); + } + + // + //#region Button Providers ... + getButtonIcon(panel: XPanelComponent, type: XNPRButtonType) { + return this.getActionButton(panel, type).pipe( + concatMap((actionButton) => this.getValue(actionButton.icon)) + ); + } + + getButtonType(panel: XPanelComponent, type: XNPRButtonType) { + return this.getActionButton(panel, type).pipe( + concatMap((actionButton) => this.getValue(actionButton.type)), + map((btnType) => + !isNullOrEmptyString(btnType) ? btnType : XButtonType.Raised + ) + ); + } + + getButtonTitle(panel: XPanelComponent, type: XNPRButtonType) { + return this.getActionButton(panel, type).pipe( + concatMap((actionButton) => this.getResourceValue(actionButton.title)) + ); + } + + getButtonColor(panel: XPanelComponent, type: XNPRButtonType) { + return this.getActionButton(panel, type).pipe( + concatMap((actionButton) => this.getValue(actionButton.color)), + map((btnColor) => + !isNullOrEmptyString(btnColor) ? btnColor : XColorWithBrightness.Primary + ) + ); + } + + getButtonDisabled(panel: XPanelComponent, type: XNPRButtonType) { + return this.getActionButtonDisabled(panel, type); + } + + getButtonCssClass(panel: XPanelComponent, type: XNPRButtonType) { + return this.getActionButton(panel, type).pipe( + concatMap((actionButton) => this.getArrayValue(actionButton.cssClass)) + ); + } + + getButtonIconPosition(panel: XPanelComponent, type: XNPRButtonType) { + return this.getActionButton(panel, type).pipe( + concatMap((actionButton) => this.getValue(actionButton.iconPosition)) + ); + } + + getButtonForegroundColor(panel: XPanelComponent, type: XNPRButtonType) { + return this.getActionButton(panel, type).pipe( + concatMap((actionButton) => + this.getOptionalValue(actionButton.foregroundColor) + ) + ); + } + //#endregion + //#endregion + + // + //#region UI Handlers ... + async handleNPRStep(panel: XPanelComponent, type: XNPRButtonType) { + // + switch (type) { + // + case this.NPRButtonTypes.Next: + await this.handleNextStep(panel); + break; + + // + case this.NPRButtonTypes.Prev: + await this.handlePrevStep(panel); + break; + + // + case this.NPRButtonTypes.Reset: + await this.handleReset(panel); + break; + } + } + + async handleNextStep(panel: XPanelComponent, force?: boolean) { + // + if ( + this.uiDisabled || + this.loading || + !panel || + panel.loading || + panel.uiDisabled || + !this.hasPanels() + ) { + return; + } + + // + let nextId: string; + const panelId = await this.getValueAsync(panel.id); + const panelIndex = await this.getPanelIndex(panelId); + if (panelIndex === -1) { + return; + } + if (panelIndex < this.panels.length - 1) { + nextId = await this.getValueAsync( + this.panels.toArray()[panelIndex + 1].id + ); + } + if (isNullOrEmptyString(nextId)) { + return; + } + + // + if (!force) { + // + const isUiBusy = this.uiDisabled || this.loading; + const canNextStep = await this.getValueAsync(this.canNext); + const disableNextStep = await this.getValueAsync(panel.disableNext); + + // + if (isUiBusy || disableNextStep || !canNextStep) { + return; + } + + // + const isStepNextConfitmedType = panel.nextConfirmer + ? panel.nextConfirmer() + : this.getValue(true); + const isStepNextConfirmed = await this.getValueAsync( + isStepNextConfitmedType + ); + if (!isStepNextConfirmed) { + return; + } + } + + // + this.selected = nextId; + this.detectChanges(); + this.selectedChange.emit(nextId); + this.onNext.emit(panelId); + this.detectChanges(); + } + + async handlePrevStep(panel: XPanelComponent, force?: boolean) { + // + if ( + this.uiDisabled || + this.loading || + !panel || + panel.loading || + panel.uiDisabled || + !this.hasPanels() + ) { + return; + } + + // + let prevId: string; + const panelId = await this.getValueAsync(panel.id); + const panelIndex = await this.getPanelIndex(panelId); + if (panelIndex === -1) { + return; + } + if (panelIndex > 0) { + prevId = await this.getValueAsync( + this.panels.toArray()[panelIndex - 1].id + ); + } + if (isNullOrEmptyString(prevId)) { + return; + } + + // + if (!force) { + const isUiBusy = this.uiDisabled || this.loading; + const canPrevStep = await this.getValueAsync(this.canPrev); + const disablePrevStep = await this.getValueAsync(panel.disablePrev); + if (isUiBusy || disablePrevStep || !canPrevStep) { + return; + } + + // + const isStepPrevConfitmedType = panel.prevConfirmer + ? panel.prevConfirmer() + : this.getValue(true); + const isStepPrevConfirmed = await this.getValueAsync( + isStepPrevConfitmedType + ); + if (!isStepPrevConfirmed) { + return; + } + } + + // + this.selected = prevId; + this.detectChanges(); + this.selectedChange.emit(prevId); + this.onPrev.emit(panelId); + this.detectChanges(); + } + + async handleReset(panel: XPanelComponent, force?: boolean) { + // + if ( + this.uiDisabled || + this.loading || + !panel || + panel.loading || + panel.uiDisabled || + !this.hasPanels() + ) { + return; + } + + // + const stepId = await this.getValueAsync(panel.id); + const firstId = await this.getValueAsync(this.panels.toArray()[0].id); + + // + if (!force) { + const isUiBusy = this.uiDisabled || this.loading; + const canResetStep = await this.getValueAsync(this.canReset); + const disableResetStep = await this.getValueAsync(panel.disableReset); + if (isUiBusy || disableResetStep || !canResetStep) { + return; + } + + // + const isConfirmedType = await this.resetConfirmer(); + const isConfirmed = await this.getValueAsync(isConfirmedType); + + // + if (!isConfirmed) { + return; + } + + // + const isStepResetConfitmedType = panel.resetConfirmer + ? panel.resetConfirmer() + : this.getValue(true); + const isStepResetConfirmed = await this.getValueAsync( + isStepResetConfitmedType + ); + if (!isStepResetConfirmed) { + return; + } + } + + // + this.selected = firstId; + this.detectChanges(); + this.selectedChange.emit(firstId); + this.onReset.emit(stepId); + this.detectChanges(); + } + + async handleToStep(panel: XPanelComponent) { + // + if ( + this.uiDisabled || + this.loading || + !panel || + panel.loading || + panel.uiDisabled || + !this.hasPanels() || + this.selected === (await this.getValueAsync(panel.id)) + ) { + return; + } + + // + const panelId = await this.getValueAsync(panel.id); + + // + this.selected = panelId; + this.detectChanges(); + this.selectedChange.emit(panelId); + this.detectChanges(); + } + + async handlePanelOpened(panel: XPanelComponent) { + // + const panelId = await this.getValueAsync(panel.id); + this.selected = panelId; + this.detectChanges(); + } + //#endregion + + // + //#region Actions ... + async next(force?: boolean) { + // + const panel = await this.getPanel(this.selected); + if (!panel) { + return; + } + + // + await this.handleNextStep(panel, force); + } + + async prev(force?: boolean) { + // + const panel = await this.getPanel(this.selected); + if (!panel) { + return; + } + + // + await this.handlePrevStep(panel, force); + } + + async reset(force?: boolean) { + // + const panel = await this.getPanel(this.selected); + if (!panel) { + return; + } + + // + await this.handleReset(panel, force); + } + + async to(id: XStandardType, force?: boolean) { + // + if (!id || !this.hasPanels()) { + return; + } + + // + const panelId = await this.getValueAsync(id); + if (!panelId) { + return; + } + + // + const panelIndex = await this.getPanelIndex(panelId); + if (panelIndex === -1) { + return; + } + + // + const panel = await this.getPanel(panelId); + if (!panel) { + return; + } + + // + const selectedIndex = await this.getPanelIndex(this.selected); + if (selectedIndex === -1 || selectedIndex === panelIndex) { + return; + } + + // + if (panelIndex > selectedIndex) { + // + // next confirm ... + // + if (!force) { + // + const isUiBusy = this.uiDisabled || this.loading; + const canNextStep = await this.getValueAsync(this.canNext); + const disableNextStep = await this.getValueAsync(panel.disableNext); + + // + if (isUiBusy || disableNextStep || !canNextStep) { + return; + } + + // + const isStepNextConfitmedType = panel.nextConfirmer + ? panel.nextConfirmer() + : this.getValue(true); + const isStepNextConfirmed = await this.getValueAsync( + isStepNextConfitmedType + ); + if (!isStepNextConfirmed) { + return; + } + } + + // + this.selected = panelId; + this.selectedChange.emit(panelId); + } else if (panelIndex < selectedIndex) { + // + // prev Confirm ... + // + if (!force) { + const isUiBusy = this.uiDisabled || this.loading; + const canPrevStep = await this.getValueAsync(this.canPrev); + const disablePrevStep = await this.getValueAsync(panel.disablePrev); + if (isUiBusy || disablePrevStep || !canPrevStep) { + return; + } + + // + const isStepPrevConfitmedType = panel.prevConfirmer + ? panel.prevConfirmer() + : this.getValue(true); + const isStepPrevConfirmed = await this.getValueAsync( + isStepPrevConfitmedType + ); + if (!isStepPrevConfirmed) { + return; + } + } + + // + this.selected = panelId; + this.selectedChange.emit(panelId); + } + } + + async expandAll() { + // + if (!this.hasChild(this.PANELS)) { + return; + } + + // + this.PANELS.toArray().forEach((panel) => { + // + if (panel) { + // + panel.expanded = true; + panel.detectChanges(); + } + }); + + // + this.detectChanges(); + } + + async collapseAll() { + // + if (!this.hasChild(this.PANELS)) { + return; + } + + // + this.PANELS.toArray().forEach((panel) => { + // + if (panel) { + // + panel.expanded = false; + panel.detectChanges(); + } + }); + + // + this.detectChanges(); + } + //#endregion + + // + //#region Private ... + private async handleStartupState() { + // + const state = await this.getValueAsync(this.startupState); + if (isNullOrEmptyString(state)) { + return; + } + + // + switch (state) { + // + case XExpandablePanelStartupState.CollapseAll: + await this.collapseAll(); + break; + + // + case XExpandablePanelAction.ExpandAll: + await this.expandAll(); + break; + } + } + + private defaultStepperResetConfirmer() { + // + return new Promise((resolver) => { + // + this.managerService.dialogService.presentAreYouSureYesNoDialog( + () => resolver(true), + XModalButtonRole.Destructive, + () => resolver(false) + ); + }); + } + + private getActionButton(panel: XPanelComponent, type: XNPRButtonType) { + // + switch (type) { + // + case this.NPRButtonTypes.Next: + return this.getValue(panel.nextButton); + + // + case this.NPRButtonTypes.Prev: + return this.getValue(panel.prevButton); + + // + case this.NPRButtonTypes.Reset: + return this.getValue(panel.resetButton); + } + } + + private getActionButtonDisabled( + panel: XPanelComponent, + type: XNPRButtonType + ) { + // + switch (type) { + // + case this.NPRButtonTypes.Next: + return this.getOptionalValue(panel.disableNext).pipe( + map((sDisable) => this.uiDisabled || panel.uiDisabled || sDisable) + ); + + // + case this.NPRButtonTypes.Prev: + return this.getOptionalValue(panel.disablePrev).pipe( + map((sDisable) => this.uiDisabled || panel.uiDisabled || sDisable) + ); + + // + case this.NPRButtonTypes.Reset: + return this.getOptionalValue(panel.disableReset).pipe( + map((sDisable) => this.uiDisabled || panel.uiDisabled || sDisable) + ); + } + } + + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + switch (model.action) { + // + case XExpandablePanelAction.NEXT: + await this.next(model.force); + break; + + // + case XExpandablePanelAction.PREV: + await this.prev(model.force); + break; + + // + case XExpandablePanelAction.RESET: + await this.reset(model.force); + break; + + // + case XExpandablePanelAction.TO: + if (model.payload) { + await this.to(model.payload, model.force); + } + break; + + // + case XExpandablePanelAction.ExpandAll: + await this.expandAll(); + break; + + // + case XExpandablePanelAction.CollapseAll: + await this.collapseAll(); + break; + + // + case XExpandablePanelAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + + private async getPanel(id: string) { + // + if (!id || !this.hasPanels()) { + return null; + } + + // + return this.panels.toArray().find(async (p) => { + const pId = await this.getValueAsync(p.id); + return pId === id; + }); + } + + private async getPanelIndex(id: string) { + // + if (!id || !this.hasPanels()) { + return -1; + } + + // + let result = -1; + const panels = this.panels.toArray(); + if (!this.hasChild(panels)) { + return result; + } + + // + for (let i = 0; i < panels.length; i++) { + // + const panel = panels[i]; + const pId = await this.getValueAsync(panel.id); + if (pId === id) { + result = i; + } + } + + // + return result; + } + + // + //#region Color Appliers ... + private async applyHeaderColor() { + // + const color = await this.getValueAsync(this.headerColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const fgColor = getXColorCssVariableName( + colorDescriptor.name, + true, + colorDescriptor.brightnessLevel + ); + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(fgColor) || isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar( + XExpandablePanelCssVars.HeaderForegroundColor, + `var(${fgColor})` + ); + this.setCssVar( + XExpandablePanelCssVars.HeaderBackgroundColor, + `var(${bgColor})` + ); + + // + this.detectChanges(); + } + + private async applyHeaderHighlightColor() { + // + const color = await this.getValueAsync(this.headerHighlightColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const fgColor = getXColorCssVariableName( + colorDescriptor.name, + true, + colorDescriptor.brightnessLevel + ); + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(fgColor) || isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar( + XExpandablePanelCssVars.HeaderHighlightForegroundColor, + `var(${fgColor})` + ); + this.setCssVar( + XExpandablePanelCssVars.HeaderHighlightBackgroundColor, + `var(${bgColor})` + ); + + // + this.detectChanges(); + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-expandable-panel\x-expandable-panel.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XIconModule } from '../x-icon/x-icon.module'; +import { XCardModule } from '../x-card/x-card.module'; +import { XButtonModule } from '../x-button/x-button.module'; +import { XPanelComponent } from './x-panel/x-panel.component'; +import { XSlotterModule } from '../x-slotter/x-slotter.module'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; +import { XExpandablePanelComponent } from './x-expandable-panel.component'; + +@NgModule({ + declarations: [XPanelComponent, XExpandablePanelComponent], + imports: [ + XIconModule, + XCardModule, + CommonModule, + XButtonModule, + XSlotterModule, + XBaseComponentModule, + ], + exports: [ + XIconModule, + XCardModule, + CommonModule, + XButtonModule, + XSlotterModule, + XPanelComponent, + XBaseComponentModule, + XExpandablePanelComponent, + ], +}) +export class XExpandablePanelModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-expandable-panel\x-expandable-panel.typings.ts + +import { + Input, + Output, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XStandardType, + XOneOrManyType, + XModalButtonRole, + XColorIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { concatMap, map } from 'rxjs/operators'; +import { + XNextPrevResetHostContainer, + XCardNextPrevResetHostContainer, + IXCardNextPrevResetHostContainer, + XCardNextPrevResetHostContainerComponent, +} from '../classes/i-x-next-prev-reset.container'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { XComponentCardContainer } from '../x-card/x-card.typings'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export enum XExpandablePanelStartupState { + ExpandAll = 'expand-all', + CollapseAll = 'collapse-all', +} + +export type XExpandablePanelStartupStateIdentifier = + | XExpandablePanelStartupState + | string; + +export enum XExpandablePanelAction { + NEXT = 'next', + PREV = 'prev', + RESET = 'reset', + TO = 'to', + + // + ExpandAll = 'expand-all', + CollapseAll = 'collapse-all', + + // + Refresh = 'refresh', +} + +export type XExpandablePanelActionIdentifier = XExpandablePanelAction | string; + +export interface XExpandablePanelActionModel + extends XBaseActionModel { + force?: boolean; +} + +export enum XExpandablePanelCssVars { + // + HeaderForegroundColor = 'header-foreground-color', + HeaderBackgroundColor = 'header-background-color', + + // + HeaderHighlightForegroundColor = 'header-highlight-foreground-color', + HeaderHighlightBackgroundColor = 'header-highlight-background-color', +} + +export type XExpandablePandelCssVarsIdentifier = + | XExpandablePanelCssVars + | string; + +// +//#region Inside Host Container ... +export interface IXExpandablePandelContainer + extends IXCardNextPrevResetHostContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + startupState: XStandardType; + isAccordion: XStandardType; + multiExpanded: XStandardType; + hideDescriptionOnSmallScreens: XStandardType; + + // + applyColors: XStandardType; + headerColor: XStandardType; + headerHighlightColor: XStandardType; + + // + resetConfirmer: () => XStandardType; + selected: string; + + // + selectedChange: EventEmitter; +} + +@Injectable({ + providedIn: 'root', +}) +export class XExpandablePandelContainer + extends XCardNextPrevResetHostContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider = new Subject(); + readonly cssClass: XStandardType>; + + // + readonly startupState: XStandardType = + XExpandablePanelStartupState.ExpandAll; + readonly isAccordion: XStandardType; + readonly multiExpanded: XStandardType; + readonly hideDescriptionOnSmallScreens: XStandardType; + + // + readonly applyColors: XStandardType = true; + readonly headerColor: XStandardType = + XColorWithBrightness.Primary; + readonly headerHighlightColor: XStandardType = + XColorWithBrightness.Warning; + + // + readonly resetConfirmer: () => XStandardType = () => { + return new Promise((resolver) => { + // + this.managerService.dialogService.presentAreYouSureYesNoDialog( + () => resolver(true), + XModalButtonRole.Destructive, + () => resolver(false) + ); + }); + }; + readonly selected: string; + + // + readonly selectedChange: EventEmitter; + //#endregion + + // + //#region Constructor ... + constructor( + public managerService: XManagerService, + protected cardPropertyProvider: XComponentCardContainer, + protected nextPrevResetHostPropertyProvider: XNextPrevResetHostContainer + ) { + super(cardPropertyProvider, nextPrevResetHostPropertyProvider); + } + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XExpandablePandelContainerComponent + extends XCardNextPrevResetHostContainerComponent + implements IXExpandablePandelContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + startupState: XStandardType = + this.propertyProvider.startupState; + + @Input() + isAccordion: XStandardType = this.propertyProvider.isAccordion; + + @Input() + multiExpanded: XStandardType = this.propertyProvider.multiExpanded; + + @Input() + hideDescriptionOnSmallScreens: XStandardType = + this.propertyProvider.hideDescriptionOnSmallScreens; + + // + @Input() + applyColors: XStandardType = this.propertyProvider.applyColors; + + @Input() + headerColor: XStandardType = + this.propertyProvider.headerColor; + + @Input() + headerHighlightColor: XStandardType = + this.propertyProvider.headerHighlightColor; + + // + @Input() + resetConfirmer: () => XStandardType = + this.propertyProvider.resetConfirmer; + + @Input() + selected: string = this.propertyProvider.selected; + + // + @Output() + selectedChange: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XExpandablePandelContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXExpandablePandelContainer + ): Observable { + return super + .extractProps(container ? container : this) + .pipe( + concatMap((superData) => { + return forkJoin({ + cssClass: this.getValue( + container ? container.cssClass : this.cssClass + ), + startupState: this.getValue( + container ? container.startupState : this.startupState + ), + isAccordion: this.getValue( + container ? container.isAccordion : this.isAccordion + ), + multiExpanded: this.getValue( + container ? container.multiExpanded : this.multiExpanded + ), + hideDescriptionOnSmallScreens: this.getValue( + container + ? container.hideDescriptionOnSmallScreens + : this.hideDescriptionOnSmallScreens + ), + applyColors: this.getValue( + container ? container.applyColors : this.applyColors + ), + headerColor: this.getValue( + container ? container.headerColor : this.headerColor + ), + headerHighlightColor: this.getValue( + container + ? container.headerHighlightColor + : this.headerHighlightColor + ), + resetConfirmer: this.getValue( + container ? container.resetConfirmer : this.resetConfirmer + ), + selected: this.getValue( + container ? container.selected : this.selected + ), + }).pipe( + map((data) => { + return { + superData, + data, + }; + }) + ); + }) + ) + .pipe( + map((info) => { + return { + // + ...info.superData, + + // + ...info.data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + selectedChange: this.selectedChange, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-expandable-panel\x-panel\x-panel.component.html + + + + + + + + + + + + + + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-expandable-panel\x-panel\x-panel.component.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-expandable-panel\x-panel\x-panel.component.ts + +import { + Component, + ViewChild, + TemplateRef, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + XPanelAction, + XPanelActionModel, + XPanelContainerComponent, +} from './x-panel.typings'; +import { nameof } from 'x-framework-core'; +import { Subject, Subscription } from 'rxjs'; + +@Component({ + selector: 'x-panel', + templateUrl: './x-panel.component.html', + styleUrls: ['./x-panel.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XPanelComponent extends XPanelContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + //#region ViewChilds ... + @ViewChild('title', { static: false }) + title!: TemplateRef; + + @ViewChild('description', { static: false }) + description!: TemplateRef; + + @ViewChild('content', { static: false }) + content!: TemplateRef; + + @ViewChild('actions', { static: false }) + actions!: TemplateRef; + //#endregion + //#endregion + + // + //#region LifeCycle ... + onChange(changeKeys: string[]) { + super.onChange(changeKeys); + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region Privates ... + // + //#region Action Provider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + switch (model.action) { + // + case XPanelAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-expandable-panel\x-panel\x-panel.typings.ts + +import { + Input, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + ChangeDetectorRef, +} from '@angular/core'; +import { concatMap, map } from 'rxjs/operators'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { + IXNextPrevResetContainer, + XNextPrevResetContainer, + XNextPrevResetContainerComponent, +} from '../../classes/i-x-next-prev-reset.container'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../../classes/x-base-action.model'; +import { XStandardType, XOneOrManyType, generateUuid } from 'x-framework-core'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../../config/x-framework-components.config'; + +export enum XPanelAction { + Refresh = 'refresh', +} + +export type XPanelActionIdentifier = XPanelAction | string; + +export interface XPanelActionModel + extends XBaseActionModel {} + +// +//#region Inside Card Container ... +export interface IXPanelContainer extends IXNextPrevResetContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + id: XStandardType; + + // + hideToggle: XStandardType; + expanded: XStandardType; + hasCustomActions: XStandardType; +} + +@Injectable({ + providedIn: 'root' +}) +export class XPanelContainer + extends XNextPrevResetContainer + implements Readonly +{ + // + //#region Props ... + readonly actionProvider = new Subject(); + readonly cssClass: XStandardType>; + + // + readonly id: XStandardType = generateUuid('XPanelComponent'); + + // + readonly hideToggle: XStandardType = false; + readonly expanded: XStandardType; + readonly hasCustomActions: XStandardType; + //#endregion +} + +@Injectable({ + providedIn: 'root' +}) +@Component({ + template: ` + + `, +}) +export abstract class XPanelContainerComponent + extends XNextPrevResetContainerComponent + implements IXPanelContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + @Input() + id: XStandardType = this.propertyProvider.id; + + @Input() + hideToggle: XStandardType = this.propertyProvider.hideToggle; + + @Input() + expanded: XStandardType = this.propertyProvider.expanded; + + @Input() + hasCustomActions: XStandardType = + this.propertyProvider.hasCustomActions; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XPanelContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXPanelContainer + ): Observable { + return super + .extractProps(container ? container : this) + .pipe( + concatMap((superData) => { + return forkJoin({ + // + cssClass: this.getValue( + container ? container.cssClass : this.cssClass + ), + id: this.getValue(container ? container.id : this.id), + hideToggle: this.getValue( + container ? container.hideToggle : this.hideToggle + ), + expanded: this.getValue( + container ? container.expanded : this.expanded + ), + hasCustomActions: this.getValue( + container ? container.hasCustomActions : this.hasCustomActions + ), + }).pipe( + map((data) => { + return { + superData, + data, + }; + }) + ); + }) + ) + .pipe( + map((info) => { + return { + // + ...info.superData, + + // + ...info.data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-fab\x-fab.component.html + +
+ + + + + + +
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-fab\x-fab.component.scss + +:host ::ng-deep .x-icon { + background-color: transparent !important; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-fab\x-fab.component.ts + +import { + XFabItem, + XFabButton, + XFabAction, + XFabButtonSide, + XFabActionModel, + XFabContainerComponent, +} from './x-fab.typings'; +import { + nameof, + hasChild, + isFunction, + isNullOrUndefined, +} from 'x-framework-core'; +import { IonFab } from '@ionic/angular'; +import { concatMap, map } from 'rxjs/operators'; +import { of, Observable, Subject, Subscription } from 'rxjs'; +import { Component, ViewChild, ChangeDetectionStrategy } from '@angular/core'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-fab', + templateUrl: './x-fab.component.html', + styleUrls: ['./x-fab.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XFabComponent extends XFabContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + private FAB: IonFab; + private PARSED_ITEM: XFabItem; + private PARSED_CHILDS: XFabButton[] = []; + private HAS_TOP_SIDE_CHILDS: boolean; + private HAS_END_SIDE_CHILDS: boolean; + private HAS_START_SIDE_CHILDS: boolean; + private HAS_BOTTOM_SIDE_CHILDS: boolean; + + // + @ViewChild('fab', { static: false }) + set fab(v: IonFab) { + // + if (!v) { + return; + } + + // + this.FAB = v; + this.detectChanges(); + } + + get fab() { + return this.FAB; + } + + get parsedItem() { + return this.PARSED_ITEM; + } + + get parsedChilds() { + return this.PARSED_CHILDS; + } + + get hasTopSideChilds() { + return this.HAS_TOP_SIDE_CHILDS; + } + + get hasEndSideChilds() { + return this.HAS_END_SIDE_CHILDS; + } + + get hasStartSideChilds() { + return this.HAS_START_SIDE_CHILDS; + } + + get hasBottomSideChilds() { + return this.HAS_BOTTOM_SIDE_CHILDS; + } + //#endregion + + // + //#region LifeCycles ... + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region ActionProvider Changed ... + // + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + + // + //#region Items Changes ... + // + const isItemsChanged = changeKeys.includes(nameof('item')); + if (isItemsChanged) { + // + const item = await this.getValueAsync(this.item); + this.detectChanges(); + if (!isNullOrUndefined(item)) { + // + this.PARSED_ITEM = item; + this.detectChanges(); + + // + if (item.childs) { + // + const childs = await this.getValueAsync(item.childs); + if (!hasChild(childs)) { + return; + } + + // + // Sorting Childs ... + this.PARSED_CHILDS = childs.sort((a, b) => a.index - b.index); + this.detectChanges(); + + // + // Determine Sides ... + this.PARSED_CHILDS.forEach(async (ch) => { + // + const side = await this.getValueAsync(ch.side); + switch (side) { + case XFabButtonSide.TOP: + if (!this.HAS_TOP_SIDE_CHILDS) { + this.HAS_TOP_SIDE_CHILDS = true; + } + break; + + case XFabButtonSide.END: + if (!this.HAS_END_SIDE_CHILDS) { + this.HAS_END_SIDE_CHILDS = true; + } + break; + + case XFabButtonSide.START: + if (!this.HAS_START_SIDE_CHILDS) { + this.HAS_START_SIDE_CHILDS = true; + } + break; + + case XFabButtonSide.BOTTOM: + if (!this.HAS_BOTTOM_SIDE_CHILDS) { + this.HAS_BOTTOM_SIDE_CHILDS = true; + } + break; + } + + // + this.detectChanges(); + }); + } + } + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region UI Provider ... + getFixedValue(items: XFabItem) { + return of(items).pipe( + concatMap((rItems) => { + // + if (!rItems) { + return of(''); + } + + // + const obs = this.getOptionalValue(rItems.fixed) as Observable; + return obs.pipe(map((res) => (res === true ? 'fixed' : ''))); + }) + ); + } + + // + //#region Fab Button Side Providers ... + isTopSideFab(child: XFabButton) { + return this.getValue(child.side).pipe( + map((side) => side === XFabButtonSide.TOP.toString()) + ); + } + + isEndSideFab(child: XFabButton) { + return this.getValue(child.side).pipe( + map((side) => side === XFabButtonSide.END.toString()) + ); + } + + isStartSideFab(child: XFabButton) { + return this.getValue(child.side).pipe( + map((side) => side === XFabButtonSide.START.toString()) + ); + } + + isBottomSideFab(child: XFabButton) { + return this.getValue(child.side).pipe( + map((side) => side === XFabButtonSide.BOTTOM.toString()) + ); + } + //#endregion + //#endregion + + // + //#region UI Handlers ... + async handleFabButtonClicked(item: XFabButton | XFabButton) { + // + if (!item) { + return; + } + + // + if (item.handler && isFunction(item.handler)) { + await this.getValueAsync(item.handler(item.id)); + } + } + //#endregion + + // + //#region Actions ... + async close() { + // + if (!this.fab) { + return; + } + + // + await this.fab.close(); + } + //#endregion + + // + //#region Private ... + // + //#region Action Provider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + // Handle Action Provider ... + this.ACTION_PROVIDER_SUBSCRIPTION = this.applyTakeUntilWrapper( + this.actionProvider.asObservable() + ).subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XFabAction.Refresh: + this.detectChanges(); + break; + + // + case XFabAction.CLOSE: + await this.close(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-fab\x-fab.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XFabComponent } from './x-fab.component'; +import { XIconModule } from '../x-icon/x-icon.module'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XFabComponent], + imports: [CommonModule, XBaseComponentModule, XIconModule], + exports: [XBaseComponentModule, XIconModule, XFabComponent], +}) +export class XFabModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-fab\x-fab.typings.ts + +import { + Input, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + ChangeDetectorRef, +} from '@angular/core'; +import { + XStandardType, + XOneOrManyType, + XColorIdentifier, + XRouterDirection, + XResourceIdentifier, + XRouterTargetIdentifier, +} from 'x-framework-core'; +import { map } from 'rxjs/operators'; +import { + XBaseContainer, + IXBaseContainer, + XBaseContainerComponent, +} from '../classes/i-x-base.container'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XIconIdentifier } from '../x-icon/x-icon.typings'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export enum XFabVerticalSlot { + TOP = 'top', + CENTER = 'center', + BOTTOM = 'bottom', +} + +export type XFabVerticalSlotIdentifier = XFabVerticalSlot | string; + +export enum XFabHorizontalSlot { + START = 'start', + CENTER = 'center', + END = 'end', +} + +export type XFabHorizontalSlotIdentifier = XFabHorizontalSlot | string; + +export enum XFabButtonSide { + TOP = 'top', + END = 'end', + START = 'start', + BOTTOM = 'bottom', +} + +export type XFabButtonSideIdentifier = XFabButtonSide | string; + +export enum XFabButtonSize { + REGULAR = '', + MINI = 'small', +} + +export type XFabButtonSizeIdentifier = XFabButtonSize | string; + +export interface XFabButton { + // + id: string; + index: number; + + // + icon: XStandardType; + side: XStandardType; + size?: XStandardType; + + // + disabled?: XStandardType; + hidden?: XStandardType; + + // + color: XStandardType; + + // + tooltip?: XStandardType; + + // + href?: XStandardType; + download?: XStandardType; + routerDirection?: XStandardType; + routerTarget?: XStandardType; + + // + handler?: (id?: string) => XStandardType; +} + +type FAB_MAIN_BUTTON = Pick< + XFabButton, + | 'id' + | 'icon' + | 'size' + | 'color' + | 'hidden' + | 'handler' + | 'tooltip' + | 'disabled' + | 'href' + | 'download' + | 'routerDirection' + | 'routerTarget' +>; + +export interface XFabItem extends FAB_MAIN_BUTTON { + // + vertical: XStandardType; + horizontal: XStandardType; + + // + fixed?: XStandardType; + edged?: XStandardType; + + // + activated?: XStandardType; + + // + childs?: XStandardType; +} + +export enum XFabAction { + CLOSE = 'close', + + // + Refresh = 'refresh', +} + +export type XFabActionIdentifier = XFabAction | string; + +export interface XFabActionModel + extends XBaseActionModel {} + +// +//#region Container ... +export interface IXFabContainer extends IXBaseContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + childsCssClass: XStandardType>; + item: XStandardType; +} + +@Injectable({ + providedIn: 'root' +}) +export class XFabContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + readonly actionProvider: Subject = + new Subject(); + + readonly cssClass: XStandardType>; + + // + readonly childsCssClass: XStandardType>; + readonly item: XStandardType = null; + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XFabContainerComponent + extends XBaseContainerComponent + implements IXFabContainer +{ + // + //#region Props ... + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + @Input() + childsCssClass: XStandardType> = + this.propertyProvider.childsCssClass; + + @Input() + item: XStandardType = this.propertyProvider.item; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XFabContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps(container?: IXFabContainer): Observable { + return forkJoin({ + // + cssClass: this.getValue(container ? container.cssClass : this.cssClass), + childsCssClass: this.getValue( + container ? container.childsCssClass : this.childsCssClass + ), + item: this.getValue(container ? container.item : this.item), + }).pipe( + map((data) => { + return { + // + ...data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-file-drop-area\x-file-drop-area.component.html + + + + + + + + +
+ +
+
+
+ + + +
+
+ +
+
+
+ + + +
+
+ +
+ + {{ getResourceValue(message) | async }} + +
+
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-file-drop-area\x-file-drop-area.component.scss + +:host { + width: inherit; + height: inherit; + + --label-color: var(--x-color-light-contrast); +} + +.x-wrapper { + cursor: grab; +} + +.x-wrapper:active { + cursor: grabbing; +} + +.no-drag { + cursor: unset; +} + +.x-droppable-container { + border-radius: 10px; +} + +.x-drop-area { + width: 100%; + height: auto; + + box-sizing: border-box; + + border-radius: 10px; + border: 2px dashed var(--label-color); + + text-align: center; + + padding: { + top: 16px; + bottom: 16px; + } +} + +.x-drag-drop-title { + color: var(--label-color); +} + +.x-icon { + margin: { + left: auto; + right: auto; + } + + margin-bottom: 16px; + + font-size: 8px; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-file-drop-area\x-file-drop-area.component.ts + +import { + nameof, + parseXColor, + isNullOrEmptyString, + getXColorCssVariableName, +} from 'x-framework-core'; +import { + XFileDropAreaAction, + XFileDropAreaCssVars, + XFileDropAreaActionModel, + XFileDropAreaContainerComponent, +} from './x-file-drop-area.typings'; +import { Subject, Subscription } from 'rxjs'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-file-drop-area', + templateUrl: './x-file-drop-area.component.html', + styleUrls: ['./x-file-drop-area.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XFileDropAreaComponent extends XFileDropAreaContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + //#endregion + + // + //#region LifeCycles ... + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region Color Changed ... + const isColorChanged = changeKeys.includes( + nameof('color') + ); + if (isColorChanged) { + await this.applyColor(); + } + //#endregion + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region UI Provider ... + //#endregion + + // + //#region Private ... + // + //#region ActionProvider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe((model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XFileDropAreaAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + private async applyColor() { + // + const color = await this.getValueAsync(this.color); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const fgColor = getXColorCssVariableName( + colorDescriptor.name, + true, + colorDescriptor.brightnessLevel + ); + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(fgColor) || isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XFileDropAreaCssVars.LabelColor, `var(${fgColor})`); + + // + this.detectChanges(); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-file-drop-area\x-file-drop-area.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XIconModule } from '../x-icon/x-icon.module'; +import { XCardModule } from '../x-card/x-card.module'; +import { XListModule } from '../x-list/x-list.module'; +import { XFileDropAreaComponent } from './x-file-drop-area.component'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XFileDropAreaComponent], + imports: [ + XListModule, + XCardModule, + XIconModule, + CommonModule, + XBaseComponentModule, + ], + exports: [ + XCardModule, + XIconModule, + XListModule, + XBaseComponentModule, + XFileDropAreaComponent, + ], +}) +export class XFileDropAreaModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-file-drop-area\x-file-drop-area.typings.ts + +import { + Input, + Output, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + TemplateRef, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XResourceIDs, + XStandardType, + XOneOrManyType, + XColorIdentifier, + XResourceIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { + XCardActionModel, + XComponentCardContainer, + IXComponentCardContainer, + XComponentCardContainerComponent, +} from '../x-card/x-card.typings'; +import { + XBaseComponentContainer, + IXBaseComponentContainer, + XBaseComponentContainerComponent, +} from '../classes/i-x-base.container'; +import { concatMap, map } from 'rxjs/operators'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { XIconIdentifier, XIconNames } from '../x-icon/x-icon.typings'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export enum XFileDropAreaAction { + Refresh = 'refresh', +} + +export type XFileDropAreaActionIdentifier = XFileDropAreaAction | string; + +export interface XFileDropAreaActionModel + extends XBaseActionModel {} + +export enum XFileDropAreaCssVars { + LabelColor = 'label-color', +} + +// +//#region Container ... +export interface IXFileDropAreaContainer extends IXComponentCardContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + color: XStandardType; + foregroundColor: XStandardType; + + // + icon: XStandardType; + message: XStandardType; + + // + disableFileCheck: XStandardType; + + // + supportsMultipleFiles: XStandardType; + allowedExtensions: XStandardType>; + + // + dragDisabled: XStandardType; + contentTemplate: TemplateRef; + filesChangeEmiter: EventEmitter>; + + // + successColor: XStandardType; + errorColor: XStandardType; + accentColor: XStandardType; +} + +@Injectable({ + providedIn: 'root', +}) +export class XFileDropAreaContainer + extends XComponentCardContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider = new Subject(); + readonly cssClass: XStandardType>; + + // + readonly color: XStandardType = XColorWithBrightness.Light; + readonly foregroundColor: XStandardType = false; + + readonly icon: XStandardType = XIconNames.arrow_down; + readonly message: XStandardType = + XResourceIDs.drag_drop_here; + + // + readonly disableFileCheck: XStandardType = false; + + // + readonly allowedExtensions: XStandardType> = []; + readonly supportsMultipleFiles: XStandardType = true; + + // + readonly dragDisabled: XStandardType = false; + readonly contentTemplate: TemplateRef; + readonly filesChangeEmiter = new EventEmitter>(); + + // + readonly successColor: XStandardType = + XColorWithBrightness.Success; + readonly errorColor: XStandardType = + XColorWithBrightness.Danger; + readonly accentColor: XStandardType = + XColorWithBrightness.Secondary; + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XFileDropAreaContainerComponent + extends XComponentCardContainerComponent + implements IXFileDropAreaContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + color: XStandardType = this.propertyProvider.color; + + @Input() + foregroundColor: XStandardType = + this.propertyProvider.foregroundColor; + + // + @Input() + icon: XStandardType = this.propertyProvider.icon; + + @Input() + message: XStandardType = this.propertyProvider.message; + + // + @Input() + disableFileCheck: XStandardType = + this.propertyProvider.disableFileCheck; + + // + @Input() + supportsMultipleFiles: XStandardType = + this.propertyProvider.supportsMultipleFiles; + + @Input() + allowedExtensions: XStandardType> = + this.propertyProvider.allowedExtensions; + + // + @Input() + dragDisabled: XStandardType = this.propertyProvider.dragDisabled; + + @Input() + contentTemplate: TemplateRef = this.propertyProvider.contentTemplate; + + @Output() + filesChangeEmiter: EventEmitter> = new EventEmitter(); + + // + @Input() + successColor: XStandardType = + this.propertyProvider.successColor; + + @Input() + errorColor: XStandardType = + this.propertyProvider.errorColor; + + @Input() + accentColor: XStandardType = + this.propertyProvider.accentColor; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XFileDropAreaContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXFileDropAreaContainer + ): Observable { + return super + .extractProps(container ? container : this) + .pipe( + concatMap((superData) => { + return forkJoin({ + // + cssClass: this.getArrayValue( + container ? container.cssClass : this.cssClass + ), + + // + color: this.getValue(container ? container.color : this.color), + foregroundColor: this.getValue( + container ? container.foregroundColor : this.foregroundColor + ), + icon: this.getValue(container ? container.icon : this.icon), + message: this.getValue( + container ? container.message : this.message + ), + disableFileCheck: this.getValue( + container ? container.disableFileCheck : this.disableFileCheck + ), + supportsMultipleFiles: this.getValue( + container + ? container.supportsMultipleFiles + : this.supportsMultipleFiles + ), + allowedExtensions: this.getValue( + container ? container.allowedExtensions : this.allowedExtensions + ), + dragDisabled: this.getValue( + container ? container.dragDisabled : this.dragDisabled + ), + successColor: this.getValue( + container ? container.successColor : this.successColor + ), + errorColor: this.getValue( + container ? container.errorColor : this.errorColor + ), + accentColor: this.getValue( + container ? container.accentColor : this.accentColor + ), + }).pipe( + map((data) => { + return { + superData, + data, + }; + }) + ); + }) + ) + .pipe( + map((info) => { + return { + // + ...info.superData, + + // + ...info.data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + contentTemplate: container + ? container.contentTemplate + : this.contentTemplate, + + // + filesChangeEmiter: this.filesChangeEmiter, + }; + }) + ); + } + //#endregion +} +//#endregion + +// +//#region ComponentContainer ... +type TXComponentFileDropAreaContainer = { + [TKey in keyof IXFileDropAreaContainer as `fileDropArea${Capitalize< + string & TKey + >}`]: IXFileDropAreaContainer[TKey]; +}; + +export interface IXComponentFileDropAreaContainer + extends TXComponentFileDropAreaContainer, + IXBaseComponentContainer { + showFileDropArea: XStandardType; +} + +@Injectable({ + providedIn: 'root', +}) +export class XComponentFileDropAreaContainer + extends XBaseComponentContainer + implements Readonly +{ + // + //#region Props ... + // + readonly showFileDropArea: XStandardType = true; + + // + readonly fileDropAreaActionProvider: Subject = + this.propertyProvider.actionProvider; + + readonly fileDropAreaCssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + readonly fileDropAreaColor: XStandardType = + this.propertyProvider.color; + + readonly fileDropAreaForegroundColor: XStandardType = + this.propertyProvider.foregroundColor; + + // + readonly fileDropAreaIcon: XStandardType = + this.propertyProvider.icon; + + readonly fileDropAreaMessage: XStandardType = + this.propertyProvider.message; + + // + readonly fileDropAreaDisableFileCheck: XStandardType = + this.propertyProvider.disableFileCheck; + + // + readonly fileDropAreaSupportsMultipleFiles: XStandardType = + this.propertyProvider.supportsMultipleFiles; + + readonly fileDropAreaAllowedExtensions: XStandardType< + XOneOrManyType + > = this.propertyProvider.allowedExtensions; + + // + readonly fileDropAreaDragDisabled: XStandardType = + this.propertyProvider.dragDisabled; + + readonly fileDropAreaContentTemplate: TemplateRef = + this.propertyProvider.contentTemplate; + + readonly fileDropAreaFilesChangeEmiter: EventEmitter> = + this.propertyProvider.filesChangeEmiter; + + // + readonly fileDropAreaSuccessColor: XStandardType = + this.propertyProvider.successColor; + + readonly fileDropAreaErrorColor: XStandardType = + this.propertyProvider.errorColor; + + readonly fileDropAreaAccentColor: XStandardType = + this.propertyProvider.accentColor; + + // + //#region ComponentsCard ... + readonly fileDropAreaIsInPage: XStandardType = false; + readonly fileDropAreaWrapWithCard: XStandardType = false; + readonly fileDropAreaCardShowFooter: XStandardType = false; + readonly fileDropAreaCardShowActions: XStandardType = false; + readonly fileDropAreaCardColor: XStandardType = + XColorWithBrightness.Light; + readonly fileDropAreaCardForegroundColor: XStandardType = false; + readonly fileDropAreaCardTitle: XStandardType; + readonly fileDropAreaCardActionProvider: Subject = + new Subject(); + readonly fileDropAreaCardSubTitle: XStandardType; + readonly fileDropAreaCardCssClass: XStandardType>; + //#endregion + //#endregion + + // + //#region Constructor ... + constructor(protected propertyProvider: XFileDropAreaContainer) { + super(); + } + //#endregion +} + +@Injectable({ + providedIn: 'root', +}) +@Component({ + template: ` + + `, +}) +export abstract class XComponentFileDropAreaContainerComponent + extends XBaseComponentContainerComponent + implements IXComponentFileDropAreaContainer +{ + // + //#region Props ... + // + @Input() + fileDropAreaActionProvider: Subject = + this.propertyProvider.fileDropAreaActionProvider; + + @Input() + fileDropAreaCssClass: XStandardType> = + this.propertyProvider.fileDropAreaCssClass; + + // + @Input() + fileDropAreaColor: XStandardType = + this.propertyProvider.fileDropAreaColor; + + @Input() + fileDropAreaForegroundColor: XStandardType = + this.propertyProvider.fileDropAreaForegroundColor; + + // + @Input() + fileDropAreaIcon: XStandardType = + this.propertyProvider.fileDropAreaIcon; + + @Input() + fileDropAreaMessage: XStandardType = + this.propertyProvider.fileDropAreaMessage; + + // + @Input() + fileDropAreaDisableFileCheck: XStandardType = + this.propertyProvider.fileDropAreaDisableFileCheck; + + // + @Input() + fileDropAreaSupportsMultipleFiles: XStandardType = + this.propertyProvider.fileDropAreaSupportsMultipleFiles; + + @Input() + fileDropAreaAllowedExtensions: XStandardType> = + this.propertyProvider.fileDropAreaAllowedExtensions; + + // + @Input() + fileDropAreaDragDisabled: XStandardType = + this.propertyProvider.fileDropAreaDragDisabled; + + @Input() + fileDropAreaContentTemplate: TemplateRef = + this.propertyProvider.fileDropAreaContentTemplate; + + @Output() + fileDropAreaFilesChangeEmiter: EventEmitter> = + new EventEmitter(); + + // + @Input() + fileDropAreaSuccessColor: XStandardType = + this.propertyProvider.fileDropAreaSuccessColor; + + @Input() + fileDropAreaErrorColor: XStandardType = + this.propertyProvider.fileDropAreaErrorColor; + + @Input() + fileDropAreaAccentColor: XStandardType = + this.propertyProvider.fileDropAreaAccentColor; + + // + @Input() + showFileDropArea: XStandardType = + this.propertyProvider.showFileDropArea; + + // + //#region ComponentsCard ... + @Input() + fileDropAreaIsInPage: XStandardType = + this.propertyProvider.fileDropAreaIsInPage; + + @Input() + fileDropAreaWrapWithCard: XStandardType = + this.propertyProvider.fileDropAreaWrapWithCard; + + @Input() + fileDropAreaCardShowFooter: XStandardType = + this.propertyProvider.fileDropAreaCardShowFooter; + + @Input() + fileDropAreaCardShowActions: XStandardType = + this.propertyProvider.fileDropAreaCardShowActions; + + @Input() + fileDropAreaCardColor: XStandardType = + this.propertyProvider.fileDropAreaCardColor; + + @Input() + fileDropAreaCardForegroundColor: XStandardType = + this.propertyProvider.fileDropAreaCardForegroundColor; + + @Input() + fileDropAreaCardTitle: XStandardType = + this.propertyProvider.fileDropAreaCardTitle; + + @Input() + fileDropAreaCardActionProvider: Subject = + this.propertyProvider.fileDropAreaCardActionProvider; + + @Input() + fileDropAreaCardSubTitle: XStandardType = + this.propertyProvider.fileDropAreaCardSubTitle; + + @Input() + fileDropAreaCardCssClass: XStandardType> = + this.propertyProvider.fileDropAreaCardCssClass; + //#endregion + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XComponentFileDropAreaContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXComponentFileDropAreaContainer + ): Observable { + return forkJoin({ + // + fileDropAreaCssClass: this.getValue( + container ? container.fileDropAreaCssClass : this.fileDropAreaCssClass + ), + fileDropAreaColor: this.getValue( + container ? container.fileDropAreaColor : this.fileDropAreaColor + ), + fileDropAreaForegroundColor: this.getValue( + container + ? container.fileDropAreaForegroundColor + : this.fileDropAreaForegroundColor + ), + fileDropAreaIcon: this.getValue( + container ? container.fileDropAreaIcon : this.fileDropAreaIcon + ), + fileDropAreaMessage: this.getValue( + container ? container.fileDropAreaMessage : this.fileDropAreaMessage + ), + fileDropAreaDisableFileCheck: this.getValue( + container + ? container.fileDropAreaDisableFileCheck + : this.fileDropAreaDisableFileCheck + ), + fileDropAreaSupportsMultipleFiles: this.getValue( + container + ? container.fileDropAreaSupportsMultipleFiles + : this.fileDropAreaSupportsMultipleFiles + ), + fileDropAreaAllowedExtensions: this.getValue( + container + ? container.fileDropAreaAllowedExtensions + : this.fileDropAreaAllowedExtensions + ), + fileDropAreaDragDisabled: this.getValue( + container + ? container.fileDropAreaDragDisabled + : this.fileDropAreaDragDisabled + ), + fileDropAreaContentTemplate: this.getValue( + container + ? container.fileDropAreaContentTemplate + : this.fileDropAreaContentTemplate + ), + fileDropAreaSuccessColor: this.getValue( + container + ? container.fileDropAreaSuccessColor + : this.fileDropAreaSuccessColor + ), + fileDropAreaErrorColor: this.getValue( + container + ? container.fileDropAreaErrorColor + : this.fileDropAreaErrorColor + ), + fileDropAreaAccentColor: this.getValue( + container + ? container.fileDropAreaAccentColor + : this.fileDropAreaAccentColor + ), + showFileDropArea: this.getValue( + container ? container.showFileDropArea : this.showFileDropArea + ), + fileDropAreaIsInPage: this.getValue( + container ? container.fileDropAreaIsInPage : this.fileDropAreaIsInPage + ), + fileDropAreaWrapWithCard: this.getValue( + container + ? container.fileDropAreaWrapWithCard + : this.fileDropAreaWrapWithCard + ), + fileDropAreaCardShowFooter: this.getValue( + container + ? container.fileDropAreaCardShowFooter + : this.fileDropAreaCardShowFooter + ), + fileDropAreaCardShowActions: this.getValue( + container + ? container.fileDropAreaCardShowActions + : this.fileDropAreaCardShowActions + ), + fileDropAreaCardColor: this.getValue( + container ? container.fileDropAreaCardColor : this.fileDropAreaCardColor + ), + fileDropAreaCardForegroundColor: this.getValue( + container + ? container.fileDropAreaCardForegroundColor + : this.fileDropAreaCardForegroundColor + ), + fileDropAreaCardTitle: this.getValue( + container ? container.fileDropAreaCardTitle : this.fileDropAreaCardTitle + ), + fileDropAreaCardSubTitle: this.getValue( + container + ? container.fileDropAreaCardSubTitle + : this.fileDropAreaCardSubTitle + ), + fileDropAreaCardCssClass: this.getValue( + container + ? container.fileDropAreaCardCssClass + : this.fileDropAreaCardCssClass + ), + }).pipe( + map((data) => { + return { + // + ...data, + + // + fileDropAreaActionProvider: container + ? container.fileDropAreaActionProvider + : this.fileDropAreaActionProvider, + + // + fileDropAreaCardActionProvider: container + ? container.fileDropAreaCardActionProvider + : this.fileDropAreaCardActionProvider, + + // + fileDropAreaFilesChangeEmiter: this.fileDropAreaFilesChangeEmiter, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-file-upload\x-file-upload.component.html + + + + + + + + +
+ +
+
+
+ + + +
+ + + + + + + + + + + + +
+
+ + + +
+ +
+
+ + + + + + + + + +
+ +
+ {{ item.data.name }} +
+
+
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-file-upload\x-file-upload.component.scss + +:host { + --top-radius: 5px; + --padding: 6px; + --host-bg-color: var(--x-color-light); +} + +:host ::ng-deep .x-card { + box-shadow: unset !important; +} + +:host ::ng-deep .x-thumbnail, +:host ::ng-deep .x-item { + width: 32px !important; + height: 32px !important; + object-fit: contain !important; + object-position: center !important; +} + +:host ::ng-deep ion-searchbar { + padding-left: unset; + padding-right: unset; + padding-inline-end: 0 !important; + padding-inline-start: 0 !important; +} + +.x-file-item { + width: 100%; + text-align: start; +} + +.x-file-item, +.x-file-thumbnail { + height: 32px; +} + +.x-file-thumbnail { + width: 32px; +} + +.x-file-title, +.x-file-thumbnail { + margin: { + left: 4px; + right: 4px; + } +} + +.x-file-title { + text-align: start; + + margin: { + top: auto; + bottom: auto; + } +} + +.x-file-upload-file-drag-drop-wrapper { + padding: var(--padding); + background-color: var(--host-bg-color); + border-top-left-radius: var(--top-radius); + border-top-right-radius: var(--top-radius); +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-file-upload\x-file-upload.component.ts + +import { + ViewChild, + Component, + ElementRef, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + XColor, + nameof, + isFile, + isArray, + toArray, + isString, + hasChild, + XFileType, + XFileBlob, + getArrayOf, + isXFileBlob, + parseXColor, + getFileInfo, + isExtensionOk, + XOneOrManyType, + getErrorException, + isNullOrUndefined, + isNullOrEmptyString, + XExceptionIdentifier, + XBaseRangeRequestDto, + getXColorCssVariableName, +} from 'x-framework-core'; +import { + XFileStatus, + XFileUploadAction, + XFileUploadCssVars, + XFileUploadActionModel, + XFileUploadContainerComponent, +} from './x-file-upload.typings'; +import { map } from 'rxjs/operators'; +import { + XActionBarItem, + defaultActionBarAddItem, + DefaultActionBarItemIds, + defaultActionBarUploadItem, + defaultActionBarRemoveItem, + defaultActionBarSelectAllItem, + defaultActionBarDeSelectAllItem, + defaultActionBarInverseSelectionItem, +} from '../x-action-bar/x-action-bar.typings'; +import { XIconNames } from '../x-icon/x-icon.typings'; +import { BehaviorSubject, Subject, Subscription } from 'rxjs'; +import { XListItem, XListAction } from '../x-list/x-list.typings'; +import { XThumbnailType } from '../x-thumbnail/x-thumbnail.typings'; +import { XFileDropAreaAction } from '../x-file-drop-area/x-file-drop-area.typings'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-file-upload', + templateUrl: './x-file-upload.component.html', + styleUrls: ['./x-file-upload.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XFileUploadComponent extends XFileUploadContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + private addDisablerSubject = new BehaviorSubject(false); + private uploadDisablerSubject = new BehaviorSubject(true); + private removeDisablerSubject = new BehaviorSubject(true); + private selectAllDisablerSubject = new BehaviorSubject(true); + private deSelectAllDisablerSubject = new BehaviorSubject(true); + private inverseSelectionDisablerSubject = new BehaviorSubject(true); + + // + @ViewChild('fileInput', { static: false }) + private fileInputRef!: ElementRef; + + // + private inputFiles: File[] = []; + fileListItems: XListItem[] = []; + fileListItemsSubject: BehaviorSubject[]> = + new BehaviorSubject([]); + + // + get files() { + return this.inputFiles; + } + //#endregion + + // + //#region LifeCycles ... + async onInit() { + super.onInit(); + + // + await this.prepareActionBarActions(); + } + + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + const isColorChanged = changeKeys.includes( + nameof('color') + ); + if (isColorChanged) { + await this.applyColor(); + } + + // + //#region ActionBar Actions ... + const isActionBarActionsChanged = changeKeys.includes( + nameof('actionBarActions') + ); + const isActionBarHideActionsChanged = changeKeys.includes( + nameof('actionBarHideActions') + ); + if (isActionBarActionsChanged || isActionBarHideActionsChanged) { + await this.prepareActionBarActions(); + } + //#endregion + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + //#endregion + + // + //#region Register Handlers ... + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region Ui Providers ... + async canAddFile() { + // + const isMaxFilesOk = await this.isMaxAllowedFilesOk( + this.inputFiles.length + 1 + ); + const isMaxSizeOk = await this.isMaxAllowedSizeOk(); + + // + const result = isMaxFilesOk && isMaxSizeOk; + if (result) { + this.addDisablerSubject.next(false); + } else { + this.addDisablerSubject.next(true); + } + + // + return result; + } + + getThumbnailType(item: any) { + return this.getValue(item).pipe( + map((it) => { + // + let result = XThumbnailType.GENERAL; + + // + let fileInfo; + if (isString(it)) { + fileInfo = getFileInfo(it); + } else if (!isFile(it) && isXFileBlob(fileInfo)) { + fileInfo = getFileInfo((it as XFileBlob).name); + } else { + fileInfo = getFileInfo((it as File).name); + } + + // + switch (fileInfo.type) { + case XFileType.Image: + case XFileType.CoverImage: + case XFileType.ProfileImage: + result = XThumbnailType.CUSTOM; + break; + } + + // + return result; + }) + ); + } + + getThumbnailIdentifier(item: any) { + return this.getValue(item).pipe( + map((it) => { + // + let result = ''; + if (!isFile(it)) { + result = !isNullOrUndefined(it.name) ? it.name : it; + } else { + result = it; + } + + // + return result; + }) + ); + } + //#endregion + + // + //#region Ui Handlers ... + async handleFileDropped(files: File | File[]) { + // + if (!files) { + return; + } + + // + this.loading = true; + + // + if (!isArray(files) && files instanceof File) { + await this.handleAddNewFile(files as File); + } else { + // + const filesArr = files as File[]; + for (const file of filesArr) { + await this.handleAddNewFile(file); + } + } + + // + this.loading = false; + this.fileChange.emit(this.inputFiles); + this.handleChangeDetection(); + } + + async handleAddNewFile(file: File) { + // + if (this.isExists(file)) { + return; + } + + // + const status = await this.getFileStatusForAdd(file); + + // + await this.prepareActionBarActionsSubjects(this.getSelectedFiles()); + if (status !== XFileStatus.OK) { + this.loading = false; + await this.handleFileStatusForAdd(status); + return; + } + + // + this.inputFiles.push(file); + const fileListItem = await this.getListItem(file); + this.fileListItems.push(fileListItem); + this.fileListItemsSubject.next(this.fileListItems); + this.handleChangeDetection(); + } + + async handleRemoveItems(request: XBaseRangeRequestDto) { + // + if (!request || !hasChild(request.items)) { + return; + } + + // + this.loading = true; + + // + const filteredFiles = this.inputFiles.filter( + (f) => !request.items.includes(f) + ); + this.inputFiles = []; + this.fileListItems = []; + + // + if (!hasChild(filteredFiles)) { + // + this.inputFiles = []; + this.fileListItems = []; + this.fileListItemsSubject.next([]); + } else { + await this.handleFileDropped(filteredFiles); + } + + // + this.loading = false; + this.fileChange.emit(this.inputFiles); + this.handleChangeDetection(); + } + + async handleFilesSelectionChanged(items: XListItem[]) { + await this.prepareActionBarActionsSubjects(items); + } + + async handleListOrderChanged() { + // + const listItems = await this.getArrayValueAsync(this.fileListItems); + this.inputFiles = [...listItems.map((li) => li.data)]; + + // + this.loading = false; + this.fileChange.emit(this.inputFiles); + this.handleChangeDetection(); + } + + handleFilesSelected() { + // + if (!this.fileInputRef) { + return; + } + + // + const fileInput = this.fileInputRef.nativeElement; + if (!fileInput || !fileInput.files) { + return; + } + + // + const files = getArrayOf(fileInput.files); + + // + if (!files || files.length === 0) { + return; + } + + // + const file = files[0]; + if (!file) { + return; + } + + // + fileInput.value = null; + + // + this.handleFileDropped(files); + } + //#endregion + + // + //#region Actions ... + selectFiles() { + // + const isBussy = this.uiDisabled || this.loading; + if (isBussy) { + return; + } + + // + if (!this.fileInputRef) { + return; + } + + // + const fileInput = this.fileInputRef.nativeElement; + if (!fileInput) { + return; + } + + // + fileInput.click(); + } + + isExists(file: File) { + // + const result = + file && hasChild(this.inputFiles) + ? this.inputFiles.findIndex( + (f) => + f.name === file.name && + f.size === file.size && + f.type === file.type && + f.lastModified === file.lastModified + ) > -1 + : false; + + // + return result; + } + + getSelectedFiles() { + return this.fileListItems.filter((i) => i.isSelected === true); + } + + /** + * sync files with list ... + * @param files + */ + async handleSyncFiles(files: Array) { + // + // first Remove List and Files ... + await this.handleRemoveItems({ + items: this.files, + }); + + // + // Drop New Files ... + await this.handleFileDropped(files); + } + //#endregion + + // + //#region Private ... + /** + * retrieve all exists files in list current size ... + */ + private currentFilesSize() { + // + let result = 0; + this.inputFiles.forEach((i) => (result += i.size)); + + // + return result; + } + + /** + * Check File Extension ... + */ + private async isExtensionOk(file: File) { + // + if (!file) { + return false; + } + + // + const allowedExtensionsType = await this.getValueAsync( + this.allowedExtensions + ); + if (!allowedExtensionsType) { + return true; + } + + // + const allowedExtensions = toArray(allowedExtensionsType); + if (!hasChild(allowedExtensions)) { + return true; + } + + // + const result = isExtensionOk(file, allowedExtensions); + return result; + } + + /** + * Check Number of Files is Ok after and before Upload ... + */ + private async isMaxAllowedFilesOk(num?: number) { + // + if (!num && !hasChild(this.inputFiles)) { + return true; + } + + // + if (!num) { + num = this.inputFiles.length; + } + + // + const maxAllowedFiles = await this.getValueAsync(this.maxAllowedFiles); + const result = + maxAllowedFiles && maxAllowedFiles > 0 ? num <= maxAllowedFiles : true; + + // + return result; + } + + /** + * Check all File Sizes after and before Upload ... + */ + private async isMaxAllowedSizeOk(file?: File) { + // + let currentSize = this.currentFilesSize(); + + // + if (file) { + currentSize += file.size; + } + + // + const maxAllowedSize = await this.getValueAsync(this.maxAllowedSize); + + // + const result = + maxAllowedSize && maxAllowedSize > 0 + ? currentSize <= maxAllowedSize + : true; + + // + return result; + } + + /** + * Check File Size satisfied Min Required Size ... + */ + private async isMinAllowedFileSizeOk(file: File) { + // + const minAllowedFileSize = await this.getValueAsync( + this.minAllowedFileSize + ); + + // + const result = + minAllowedFileSize && minAllowedFileSize > 0 + ? file.size >= minAllowedFileSize + : true; + + // + return result; + } + + /** + * Check File Size less than or Equals Max Required Size ... + */ + private async isMaxAllowedFileSizeOk(file: File) { + // + const maxAllowedFileSize = await this.getValueAsync( + this.maxAllowedFileSize + ); + + // + const result = + maxAllowedFileSize && maxAllowedFileSize > 0 + ? file.size <= maxAllowedFileSize + : true; + + // + return result; + } + + /** + * Check File Status for Add a File to List ... + */ + private async getFileStatusForAdd(file: File) { + // + // Check Not Empty File ... + if (!file || file.size === 0) { + return XFileStatus.EMPTY; + } + + // + // Check File Extension Validation ... + const isExtensionIsOk = await this.isExtensionOk(file); + if (!isExtensionIsOk) { + return XFileStatus.NOT_ALLOWED_TYPE; + } + + // + // Check File Min Size Ok ... + const isMinAllowedFileSizeOk = await this.isMinAllowedFileSizeOk(file); + if (!isMinAllowedFileSizeOk) { + return XFileStatus.MIN_SIZE_NOT_REACHED; + } + + // + // Check File Max Size Ok ... + const isMaxAllowedFileSizeOk = await this.isMaxAllowedFileSizeOk(file); + if (!isMaxAllowedFileSizeOk) { + return XFileStatus.SIZE_EXCEEDED; + } + + // + // Check Max Files Size Ok ... + const isMaxAllowedSizeOk = await this.isMaxAllowedSizeOk(file); + if (!isMaxAllowedSizeOk) { + return XFileStatus.MAX_ALLOWED_SIZE_REACHED; + } + + // + // Check Max Files Ok ... + const isMaxAllowedFilesOk = await this.isMaxAllowedFilesOk( + this.inputFiles.length + 1 + ); + if (!isMaxAllowedFilesOk) { + return XFileStatus.MAX_ALLOWED_FILES_REACHED; + } + + // + // Everythings Ok ... + return XFileStatus.OK; + } + + /** + * recieve file status and act based of it (notifiy when exception) ... + * + * @param status File Status for Add ... + */ + private async handleFileStatusForAdd(status: XFileStatus) { + // + let identifier: XExceptionIdentifier = null; + switch (status) { + // + case XFileStatus.EMPTY: + identifier = await this.getValueAsync(this.emptyFileException); + break; + + // + case XFileStatus.NOT_ALLOWED_TYPE: + identifier = await this.getValueAsync(this.notAllowedFileException); + break; + + // + case XFileStatus.SIZE_EXCEEDED: + identifier = await this.getValueAsync(this.maxAllowedFileSizeException); + break; + + // + case XFileStatus.MAX_ALLOWED_SIZE_REACHED: + identifier = await this.getValueAsync(this.maxAllowedSizeException); + break; + + // + case XFileStatus.MAX_ALLOWED_FILES_REACHED: + identifier = await this.getValueAsync(this.maxAllowedFilesException); + break; + + // + case XFileStatus.MIN_SIZE_NOT_REACHED: + identifier = await this.getValueAsync(this.minAllowedFileSizeException); + break; + } + + // + const exception = getErrorException(identifier); + if (exception) { + this.managerService.notificationService.presentErrorNotification({ + exception, + dissmissable: true, + }); + } + } + + private handleChangeDetection() { + // + if (this.listActionProvider) { + this.listActionProvider.next({ + action: XListAction.Refresh, + }); + } + + // + if (this.fileDropAreaActionProvider) { + this.fileDropAreaActionProvider.next({ + action: XFileDropAreaAction.Refresh, + }); + } + + // + this.detectChanges(); + } + + // + //#region ActionBar Preparations ... + private async prepareActionBarActions() { + // + //#region Prepare Default Actions ... + const hideActions = await this.getArrayValueAsync( + this.actionBarHideActions + ); + + // + const defaultActions: XOneOrManyType = []; + + // + // Add ... + const addId = DefaultActionBarItemIds.Add.toString(); + if (!hideActions.includes(addId)) { + defaultActions.push({ + // + ...defaultActionBarAddItem, + + // + index: 1, + disabled: this.addDisablerSubject.asObservable(), + + // + handler: () => { + this.selectFiles(); + }, + }); + } + + // + // Upload ... + const uploadId = DefaultActionBarItemIds.Upload.toString(); + if (!hideActions.includes(uploadId)) { + defaultActions.push({ + // + ...defaultActionBarUploadItem, + + // + index: 2, + disabled: this.uploadDisablerSubject.asObservable(), + }); + } + + // + // Remove ... + const removeId = DefaultActionBarItemIds.Remove.toString(); + if (!hideActions.includes(removeId)) { + defaultActions.push({ + // + ...defaultActionBarRemoveItem, + + // + index: 3, + disabled: this.removeDisablerSubject.asObservable(), + }); + } + + // + // Select All ... + const selectAllId = DefaultActionBarItemIds.SelectAll.toString(); + if (!hideActions.includes(selectAllId)) { + defaultActions.push({ + // + ...defaultActionBarSelectAllItem, + + // + index: 4, + disabled: this.selectAllDisablerSubject.asObservable(), + }); + } + + // + // Inverse Selection ... + const inverseSelectionId = + DefaultActionBarItemIds.InverseSelection.toString(); + if (!hideActions.includes(inverseSelectionId)) { + defaultActions.push({ + // + ...defaultActionBarInverseSelectionItem, + + // + index: 5, + disabled: this.inverseSelectionDisablerSubject.asObservable(), + }); + } + + // + // De Select All ... + const deselectAllId = DefaultActionBarItemIds.DeSelectAll.toString(); + if (!hideActions.includes(deselectAllId)) { + defaultActions.push({ + // + ...defaultActionBarDeSelectAllItem, + + // + index: 6, + disabled: this.deSelectAllDisablerSubject.asObservable(), + }); + } + //#endregion + + // + if (!this.actionBarActions) { + // + this.actionBarActions = defaultActions; + this.handleChangeDetection(); + return; + } + + // + const actions = await this.getArrayValueAsync(this.actionBarActions); + if (!hasChild(actions)) { + // + this.actionBarActions = defaultActions; + this.handleChangeDetection(); + return; + } + + // + this.actionBarActions = actions; + this.handleChangeDetection(); + } + + private async prepareActionBarActionsSubjects(items: XListItem[]) { + // + // Check Can Add ... + const canAdd = await this.canAddFile(); + if (canAdd) { + this.addDisablerSubject.next(false); + } else { + this.addDisablerSubject.next(true); + } + + // + // Check Select All ... + if (!hasChild(this.fileListItems)) { + // + this.addDisablerSubject.next(false); + this.uploadDisablerSubject.next(true); + this.removeDisablerSubject.next(true); + this.selectAllDisablerSubject.next(true); + this.deSelectAllDisablerSubject.next(true); + this.inverseSelectionDisablerSubject.next(true); + } else { + // + this.uploadDisablerSubject.next(false); + + // + // DeSelectAll ... + if (!hasChild(items)) { + // + this.removeDisablerSubject.next(true); + this.deSelectAllDisablerSubject.next(true); + this.inverseSelectionDisablerSubject.next(true); + } else { + // + this.removeDisablerSubject.next(false); + this.deSelectAllDisablerSubject.next(false); + this.inverseSelectionDisablerSubject.next(false); + } + + // + if (this.fileListItems.length === items.length) { + // + this.selectAllDisablerSubject.next(true); + this.inverseSelectionDisablerSubject.next(true); + } else { + this.selectAllDisablerSubject.next(false); + } + } + + // + this.handleChangeDetection(); + } + //#endregion + + // + //#region Handler Registerrers ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + switch (model.action) { + // + case XFileUploadAction.SELECT_FILES: + this.selectFiles(); + break; + + // + case XFileUploadAction.CLEAR_LIST: + await this.handleRemoveItems({ + items: this.files, + }); + break; + + // + case XFileUploadAction.SYNC_LIST: + await this.handleSyncFiles(model.payload); + break; + + // + case XFileUploadAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + // + //#region List Handlers ... + private async getListItem(item: any) { + // + if (!item) { + return null; + } + + // + const result: XListItem = { + data: item, + disabled: this.uiDisabled, + slideOptions: [ + { + id: 'delete_file', + icon: XIconNames.remove, + title: this.ResourceIDs.delete, + color: XColor.Danger, + onlyIcon: true, + disabled: this.uiDisabled, + slot: 'end', + handler: async () => { + await this.confirmAndRemove(item); + }, + }, + ], + }; + + // + return result; + } + + private isValidDataSource() { + return hasChild(this.inputFiles); + } + + private addToDataSource(item: XListItem) { + this.fileListItems.push(item); + } + + private changeDataSourceItem(item: XListItem) { + // + const index = this.fileListItems.findIndex((i) => i.data === item.data); + if (index === -1) { + return; + } + + // + this.fileListItems[index] = item; + } + + private async clearListItems() { + this.fileListItems = null; + this.fileListItems = []; + } + + private async refreshListItems(forceClean = false) { + // + if (forceClean) { + await this.clearListItems(); + } + + // + if (!this.isValidDataSource()) { + return; + } + + // + for (const f of this.inputFiles) { + // + const fListItem = await this.getListItem(f); + + // + if (forceClean) { + this.addToDataSource(fListItem); + } else { + this.changeDataSourceItem(fListItem); + } + } + } + + private async confirmAndRemove(items: XOneOrManyType) { + // + if (!items) { + return; + } + + // + const extractedItems: File[] = !items + ? [] + : isArray(items) + ? (items as File[]) + : [items as File]; + const removeRequest: XBaseRangeRequestDto = { + items: [...extractedItems], + }; + + // + const confirmRemove = await this.getValueAsync(this.listConfirmRemove); + if (confirmRemove) { + await this.managerService.dialogService.presentAreYouSureYesNoDialog( + () => { + this.handleRemoveItems(removeRequest); + } + ); + } else { + this.handleRemoveItems(removeRequest); + } + } + //#endregion + + // + //#region Color Appliers ... + private async applyColor() { + // + const color = await this.getValueAsync(this.color); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const fgColor = getXColorCssVariableName( + colorDescriptor.name, + true, + colorDescriptor.brightnessLevel + ); + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(fgColor) || isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XFileUploadCssVars.HostBgColor, `var(${bgColor})`); + + // + this.handleChangeDetection(); + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-file-upload\x-file-upload.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XCardModule } from '../x-card/x-card.module'; +import { XListModule } from '../x-list/x-list.module'; +import { XFileUploadComponent } from './x-file-upload.component'; +import { XThumbnailModule } from '../x-thumbnail/x-thumbnail.module'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; +import { XFileDropAreaModule } from '../x-file-drop-area/x-file-drop-area.module'; + +@NgModule({ + declarations: [XFileUploadComponent], + imports: [ + XCardModule, + XListModule, + CommonModule, + XThumbnailModule, + XFileDropAreaModule, + XBaseComponentModule, + ], + exports: [ + XCardModule, + XListModule, + XThumbnailModule, + XFileDropAreaModule, + XBaseComponentModule, + XFileUploadComponent, + ], +}) +export class XFileUploadModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-file-upload\x-file-upload.typings.ts + +import { + Input, + Output, + Inject, + NgZone, + Component, + Renderer2, + Injectable, + ElementRef, + TemplateRef, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + hasChild, + XFileBlob, + XStandardType, + XOneOrManyType, + toNormalString, + XColorIdentifier, + fileToJsonString, + XResourceIdentifier, + XColorWithBrightness, + XExceptionIdentifier, +} from 'x-framework-core'; +import { + XListItem, + XListFilteringHandler, + XComponentListContainer, + IXComponentListContainer, +} from '../x-list/x-list.typings'; +import { + XCardActionModel, + XComponentCardContainer, + IXComponentCardContainer, +} from '../x-card/x-card.typings'; +import { + XEmptyActionModel, + XComponentEmptyContainer, + IXComponentEmptyContainer, +} from '../x-empty/x-empty.typings'; +import { map } from 'rxjs/operators'; +import { + XBaseContainer, + IXBaseContainer, + XBaseContainerComponent, +} from '../classes/i-x-base.container'; +import { + XActionBarItem, + XActionBarActionModel, + XActionBarActionIdentifier, + XActionBarActionFiredModel, + XComponentActionBarContainer, + IXComponentActionBarContainer, +} from '../x-action-bar/x-action-bar.typings'; +import { + XSearchBarActionModel, + XSearchBarTypeIdentifier, + XComponentSearchBarContainer, + IXComponentSearchBarContainer, +} from '../x-search-bar/x-search-bar.typings'; +import { forkJoin, Observable, Subject } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { + XFileDropAreaActionModel, + XComponentFileDropAreaContainer, + IXComponentFileDropAreaContainer, +} from '../x-file-drop-area/x-file-drop-area.typings'; +import { XManagerService } from 'x-framework-services'; +import { XIconIdentifier } from '../x-icon/x-icon.typings'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { IXFileContainer, XFileContainer } from '../classes/i-x-file.container'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export enum XFileStatus { + OK = 'ok', + EMPTY = 'empty', + SIZE_EXCEEDED = 'size_exceeded', + NOT_ALLOWED_TYPE = 'not_allowed_type', + MIN_SIZE_NOT_REACHED = 'min_size_not_reached', + MAX_ALLOWED_FILES_REACHED = 'max_allowed_files_reached', + MAX_ALLOWED_SIZE_REACHED = 'max_allowed_size_reached', +} + +export type XFileStatusIdentifier = XFileStatus | string; + +export enum XFileUploadAction { + SELECT_FILES = 'select_files', + CLEAR_LIST = 'clear_list', + SYNC_LIST = 'sync_list', + + // + Refresh = 'refresh', +} + +export type XFileUploadActionIdentifier = XFileUploadAction | string; + +export interface XFileUploadActionModel + extends XBaseActionModel {} + +export const defaultFileUploadListFilteringHandler: XListFilteringHandler = ( + query: string, + items: XListItem[] +) => { + // + if (!query || !hasChild(items)) { + return []; + } + + // + return items.filter((i) => + toNormalString(fileToJsonString(i.data)).includes(toNormalString(query)) + ); +}; + +export enum XFileUploadCssVars { + HostBgColor = 'host-bg-color', +} + +// +//#region Container ... +// +type TXFileUploadContainer = IXComponentCardContainer & + Partial & + Partial & + IXComponentActionBarContainer & + IXComponentSearchBarContainer & + IXComponentEmptyContainer & + IXFileContainer; + +export interface IXFileUploadContainer + extends IXBaseContainer, + TXFileUploadContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + color: XStandardType; + foregroundColor: XStandardType; + + // + fileChange: EventEmitter>; + + // + actionBarHideActions: XStandardType< + XOneOrManyType + >; +} + +@Injectable({ + providedIn: 'root', +}) +export class XFileUploadContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + // + //#region Self ... + // + readonly actionProvider: Subject = + new Subject(); + readonly cssClass: XStandardType>; + + // + readonly color: XStandardType = XColorWithBrightness.Light; + readonly foregroundColor: XStandardType = false; + + // + readonly fileChange: EventEmitter> = new EventEmitter< + XOneOrManyType + >(); + + readonly actionBarHideActions: XStandardType< + XOneOrManyType + >; + //#endregion + + // + //#region File Drop Props ... + // + readonly showFileDropArea: XStandardType = + this.fileDropAreaPropertyProvider.showFileDropArea; + + // + readonly fileDropAreaActionProvider: Subject = + this.fileDropAreaPropertyProvider.fileDropAreaActionProvider; + readonly fileDropAreaCssClass: XStandardType> = + this.fileDropAreaPropertyProvider.fileDropAreaCssClass; + + // + readonly fileDropAreaColor: XStandardType = + this.fileDropAreaPropertyProvider.fileDropAreaColor; + readonly fileDropAreaForegroundColor: XStandardType = + this.fileDropAreaPropertyProvider.fileDropAreaForegroundColor; + + // + readonly fileDropAreaIcon: XStandardType = + this.fileDropAreaPropertyProvider.fileDropAreaIcon; + readonly fileDropAreaMessage: XStandardType = + this.fileDropAreaPropertyProvider.fileDropAreaMessage; + + // + readonly fileDropAreaDisableFileCheck: XStandardType = + this.fileDropAreaPropertyProvider.fileDropAreaDisableFileCheck; + + // + readonly fileDropAreaSupportsMultipleFiles: XStandardType = + this.fileDropAreaPropertyProvider.fileDropAreaSupportsMultipleFiles; + readonly fileDropAreaAllowedExtensions: XStandardType< + XOneOrManyType + > = this.fileDropAreaPropertyProvider.fileDropAreaAllowedExtensions; + + // + readonly fileDropAreaDragDisabled: XStandardType = + this.fileDropAreaPropertyProvider.fileDropAreaDragDisabled; + readonly fileDropAreaContentTemplate: TemplateRef = + this.fileDropAreaPropertyProvider.fileDropAreaContentTemplate; + readonly fileDropAreaFilesChangeEmiter: EventEmitter> = + this.fileDropAreaPropertyProvider.fileDropAreaFilesChangeEmiter; + + // + readonly fileDropAreaSuccessColor: XStandardType = + this.fileDropAreaPropertyProvider.fileDropAreaSuccessColor; + readonly fileDropAreaErrorColor: XStandardType = + this.fileDropAreaPropertyProvider.fileDropAreaErrorColor; + readonly fileDropAreaAccentColor: XStandardType = + this.fileDropAreaPropertyProvider.fileDropAreaAccentColor; + //#endregion + + // + //#region Card Props ... + readonly isInPage: XStandardType = + this.cardPropertyProvider.isInPage; + readonly wrapWithCard: XStandardType = + this.cardPropertyProvider.wrapWithCard; + readonly cardActionProvider: Subject = + this.cardPropertyProvider.cardActionProvider; + readonly cardCssClass: XStandardType> = + this.cardPropertyProvider.cardCssClass; + readonly cardColor: XStandardType = + this.cardPropertyProvider.cardColor; + readonly cardForegroundColor: XStandardType = + this.cardPropertyProvider.cardForegroundColor; + readonly cardTitle: XStandardType = + this.cardPropertyProvider.cardTitle; + readonly cardSubTitle: XStandardType = + this.cardPropertyProvider.cardSubTitle; + readonly cardShowFooter: XStandardType = + this.cardPropertyProvider.cardShowFooter; + readonly cardShowActions: XStandardType = + this.cardPropertyProvider.cardShowActions; + //#endregion + + // + //#region List Props ... + readonly showList: XStandardType = + this.listPropertyProvider.showList; + readonly listConfirmRemove: XStandardType = + this.listPropertyProvider.listConfirmRemove; + readonly listActionProvider = this.listPropertyProvider.listActionProvider; + readonly listCssClass: XStandardType> = + this.listPropertyProvider.listCssClass; + readonly listShowToolbar: XStandardType = true; + readonly listStickyToolbar: XStandardType = + this.listPropertyProvider.listStickyToolbar; + readonly listEnableReordering: XStandardType = + this.listPropertyProvider.listEnableReordering; + readonly listShowDivider: XStandardType = + this.listPropertyProvider.listShowDivider; + readonly listFilteringHandler: XListFilteringHandler = + this.listPropertyProvider.listFilteringHandler; + //#endregion + + // + //#region ActionBar ... + // + readonly showActionBar: XStandardType = true; + + // + readonly actionBarActionProvider: Subject = + this.actionBarPropertyProvider.actionBarActionProvider; + readonly actionBarCssClass: XStandardType> = + this.actionBarPropertyProvider.actionBarCssClass; + + // + readonly actionBarColor: XStandardType = + this.actionBarPropertyProvider.actionBarColor; + readonly actionBarForegroundColor: XStandardType = + this.actionBarPropertyProvider.actionBarForegroundColor; + + // + readonly actionBarActions: XStandardType> = + this.actionBarPropertyProvider.actionBarActions; + + // + readonly actionBarActionFired: EventEmitter = + this.actionBarPropertyProvider.actionBarActionFired; + //#endregion + + // + //#region SearchBar ... + // + readonly showSearchBar: XStandardType = + this.searchBarPropertyProvider.showSearchBar; + + // + readonly searchBarActionProvider: Subject = + this.searchBarPropertyProvider.searchBarActionProvider; + readonly searchBarCssClass: XStandardType> = + this.searchBarPropertyProvider.searchBarCssClass; + + // + readonly searchBarColor: XStandardType = + this.searchBarPropertyProvider.searchBarColor; + readonly searchBarForegroundColor: XStandardType = + this.searchBarPropertyProvider.searchBarForegroundColor; + + // + readonly searchBarSearchInputColor: XStandardType = + this.searchBarPropertyProvider.searchBarSearchInputColor; + readonly searchBarForegroundSearchInputColor: XStandardType = + this.searchBarPropertyProvider.searchBarForegroundSearchInputColor; + + // + readonly searchBarType: XStandardType = + this.searchBarPropertyProvider.searchBarType; + readonly searchBarTitle: XStandardType = + this.searchBarPropertyProvider.searchBarTitle; + readonly searchBarValue: XStandardType = + this.searchBarPropertyProvider.searchBarValue; + readonly searchBarEnterKeyHint: XStandardType = + this.searchBarPropertyProvider.searchBarEnterKeyHint; + readonly searchBarKeyboardType: XStandardType = + this.searchBarPropertyProvider.searchBarKeyboardType; + readonly searchBarSpellcheck: XStandardType = + this.searchBarPropertyProvider.searchBarSpellcheck; + readonly searchBarDebounce: XStandardType = + this.searchBarPropertyProvider.searchBarDebounce; + + // + readonly searchBarShowCancelButton: XStandardType = + this.searchBarPropertyProvider.searchBarShowCancelButton; + readonly searchBarCancelButtonIcon: XStandardType = + this.searchBarPropertyProvider.searchBarCancelButtonIcon; + readonly searchBarCancelButtonTitle: XStandardType = + this.searchBarPropertyProvider.searchBarCancelButtonTitle; + readonly searchBarCancelButtonIconColor: XStandardType = + this.searchBarPropertyProvider.searchBarCancelButtonIconColor; + + // + readonly searchBarShowClearButton: XStandardType = + this.searchBarPropertyProvider.searchBarShowClearButton; + readonly searchBarClearButtonIcon: XStandardType = + this.searchBarPropertyProvider.searchBarClearButtonIcon; + readonly searchBarClearButtonIconColor: XStandardType = + this.searchBarPropertyProvider.searchBarClearButtonIconColor; + + // + readonly searchBarSearchButtonIcon: XStandardType = + this.searchBarPropertyProvider.searchBarSearchButtonIcon; + readonly searchBarSearchButtonIconColor: XStandardType = + this.searchBarPropertyProvider.searchBarSearchButtonIconColor; + + // + readonly searchBarQueryChange: EventEmitter = + this.searchBarPropertyProvider.searchBarQueryChange; + readonly searchBarOnEvent: EventEmitter = + this.searchBarPropertyProvider.searchBarOnEvent; + //#endregion + + // + //#region Empty Props ... + readonly showEmpty: XStandardType = + this.emptyPropertyProvider.showEmpty; + readonly emptyActionProvider: Subject = + this.emptyPropertyProvider.emptyActionProvider; + readonly emptyCssClass: XStandardType> = + this.emptyPropertyProvider.emptyCssClass; + readonly emptyColor: XStandardType = + this.emptyPropertyProvider.emptyColor; + readonly emptyForegroundColor: XStandardType = + this.emptyPropertyProvider.emptyForegroundColor; + readonly emptyMessage: XStandardType = + this.emptyPropertyProvider.emptyMessage; + readonly emptyMessageColor: XStandardType = + this.emptyPropertyProvider.emptyMessageColor; + readonly emptyForegroundMessageColor: XStandardType = + this.emptyPropertyProvider.emptyForegroundMessageColor; + readonly emptyHasRefresh: XStandardType = + this.emptyPropertyProvider.emptyHasRefresh; + readonly emptyRefreshButtonColor: XStandardType = + this.emptyPropertyProvider.emptyRefreshButtonColor; + readonly emptyForegroundRefreshButtonColor: XStandardType = + this.emptyPropertyProvider.emptyForegroundRefreshButtonColor; + readonly emptyRefreshIcon: XStandardType = + this.emptyPropertyProvider.emptyRefreshIcon; + readonly emptyRefresh: EventEmitter = + this.emptyPropertyProvider.emptyRefresh; + //#endregion + + // + //#region File Props ... + readonly emptyFileException: XStandardType = + this.filePropertyProvider.emptyFileException; + readonly notAllowedFileException: XStandardType = + this.filePropertyProvider.notAllowedFileException; + readonly allowedExtensions: XStandardType> = + this.filePropertyProvider.allowedExtensions; + readonly maxAllowedFiles: XStandardType = + this.filePropertyProvider.maxAllowedFiles; + readonly maxAllowedFilesException: XStandardType = + this.filePropertyProvider.maxAllowedFilesException; + readonly maxAllowedSize: XStandardType = + this.filePropertyProvider.maxAllowedSize; + readonly maxAllowedSizeException: XStandardType = + this.filePropertyProvider.maxAllowedSizeException; + readonly minAllowedFileSize: XStandardType = + this.filePropertyProvider.minAllowedFileSize; + readonly minAllowedFileSizeException: XStandardType = + this.filePropertyProvider.minAllowedFileSizeException; + readonly maxAllowedFileSize: XStandardType = + this.filePropertyProvider.maxAllowedFileSize; + readonly maxAllowedFileSizeException: XStandardType = + this.filePropertyProvider.maxAllowedFileSizeException; + //#endregion + //#endregion + + // + //#region Constructor ... + constructor( + protected filePropertyProvider: XFileContainer, + protected cardPropertyProvider: XComponentCardContainer, + protected listPropertyProvider: XComponentListContainer, + protected emptyPropertyProvider: XComponentEmptyContainer, + protected actionBarPropertyProvider: XComponentActionBarContainer, + protected searchBarPropertyProvider: XComponentSearchBarContainer, + protected fileDropAreaPropertyProvider: XComponentFileDropAreaContainer + ) { + super(); + } + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XFileUploadContainerComponent + extends XBaseContainerComponent + implements IXFileUploadContainer +{ + // + //#region Props ... + // + //#region Self ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + color: XStandardType = this.propertyProvider.color; + + @Input() + foregroundColor: XStandardType = + this.propertyProvider.foregroundColor; + + // + @Output() + fileChange: EventEmitter> = new EventEmitter(); + + @Input() + actionBarHideActions: XStandardType< + XOneOrManyType + > = this.propertyProvider.actionBarHideActions; + //#endregion + + // + //#region File Drop Props ... + // + @Input() + showFileDropArea: XStandardType = + this.propertyProvider.showFileDropArea; + + // + @Input() + fileDropAreaActionProvider: Subject = + this.propertyProvider.fileDropAreaActionProvider; + + @Input() + fileDropAreaCssClass: XStandardType> = + this.propertyProvider.fileDropAreaCssClass; + + // + @Input() + fileDropAreaColor: XStandardType = + this.propertyProvider.fileDropAreaColor; + + @Input() + fileDropAreaForegroundColor: XStandardType = + this.propertyProvider.fileDropAreaForegroundColor; + + // + @Input() + fileDropAreaIcon: XStandardType = + this.propertyProvider.fileDropAreaIcon; + + @Input() + fileDropAreaMessage: XStandardType = + this.propertyProvider.fileDropAreaMessage; + + // + @Input() + fileDropAreaDisableFileCheck: XStandardType = + this.propertyProvider.fileDropAreaDisableFileCheck; + + // + @Input() + fileDropAreaSupportsMultipleFiles: XStandardType = + this.propertyProvider.fileDropAreaSupportsMultipleFiles; + + @Input() + fileDropAreaAllowedExtensions: XStandardType> = + this.propertyProvider.fileDropAreaAllowedExtensions; + + // + @Input() + fileDropAreaDragDisabled: XStandardType = + this.propertyProvider.fileDropAreaDragDisabled; + + @Input() + fileDropAreaContentTemplate: TemplateRef = + this.propertyProvider.fileDropAreaContentTemplate; + + @Output() + fileDropAreaFilesChangeEmiter: EventEmitter> = + new EventEmitter(); + + // + @Input() + fileDropAreaSuccessColor: XStandardType = + this.propertyProvider.fileDropAreaSuccessColor; + + @Input() + fileDropAreaErrorColor: XStandardType = + this.propertyProvider.fileDropAreaErrorColor; + + @Input() + fileDropAreaAccentColor: XStandardType = + this.propertyProvider.fileDropAreaAccentColor; + //#endregion + + // + //#region Card Props ... + @Input() + isInPage: XStandardType = this.propertyProvider.isInPage; + + @Input() + wrapWithCard: XStandardType = this.propertyProvider.wrapWithCard; + + @Input() + cardActionProvider: Subject = + this.propertyProvider.cardActionProvider; + + @Input() + cardCssClass: XStandardType> = + this.propertyProvider.cardCssClass; + + @Input() + cardColor: XStandardType = this.propertyProvider.cardColor; + + @Input() + cardForegroundColor: XStandardType = + this.propertyProvider.cardForegroundColor; + + @Input() + cardTitle: XStandardType = + this.propertyProvider.cardTitle; + + @Input() + cardSubTitle: XStandardType = + this.propertyProvider.cardSubTitle; + + @Input() + cardShowFooter: XStandardType = this.propertyProvider.cardShowFooter; + + @Input() + cardShowActions: XStandardType = + this.propertyProvider.cardShowActions; + //#endregion + + // + //#region List Props ... + @Input() + showList: XStandardType = this.propertyProvider.showList; + + @Input() + listConfirmRemove: XStandardType = + this.propertyProvider.listConfirmRemove; + + @Input() + listActionProvider = this.propertyProvider.listActionProvider; + + @Input() + listCssClass: XStandardType> = + this.propertyProvider.listCssClass; + + @Input() + listShowToolbar: XStandardType = + this.propertyProvider.listShowToolbar; + + @Input() + listStickyToolbar: XStandardType = + this.propertyProvider.listStickyToolbar; + + @Input() + listEnableReordering: XStandardType = + this.propertyProvider.listEnableReordering; + + @Input() + listShowDivider: XStandardType = + this.propertyProvider.listShowDivider; + + @Input() + listFilteringHandler: XListFilteringHandler = + this.propertyProvider.listFilteringHandler; + //#endregion + + // + //#region ActionBar ... + // + @Input() + showActionBar: XStandardType = this.propertyProvider.showActionBar; + + // + @Input() + actionBarActionProvider: Subject = + this.propertyProvider.actionBarActionProvider; + + @Input() + actionBarCssClass: XStandardType> = + this.propertyProvider.actionBarCssClass; + + // + @Input() + actionBarColor: XStandardType = + this.propertyProvider.actionBarColor; + + @Input() + actionBarForegroundColor: XStandardType = + this.propertyProvider.actionBarForegroundColor; + + // + @Input() + actionBarActions: XStandardType> = + this.propertyProvider.actionBarActions; + + // + @Output() + actionBarActionFired: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region SearchBar ... + // + @Input() + showSearchBar: XStandardType = this.propertyProvider.showSearchBar; + + // + @Input() + searchBarActionProvider: Subject = + this.propertyProvider.searchBarActionProvider; + + @Input() + searchBarCssClass: XStandardType> = + this.propertyProvider.searchBarCssClass; + + // + @Input() + searchBarColor: XStandardType = + this.propertyProvider.searchBarColor; + + @Input() + searchBarForegroundColor: XStandardType = + this.propertyProvider.searchBarForegroundColor; + + // + @Input() + searchBarSearchInputColor: XStandardType = + this.propertyProvider.searchBarSearchInputColor; + + @Input() + searchBarForegroundSearchInputColor: XStandardType = + this.propertyProvider.searchBarForegroundSearchInputColor; + + // + @Input() + searchBarType: XStandardType = + this.propertyProvider.searchBarType; + + @Input() + searchBarTitle: XStandardType = + this.propertyProvider.searchBarTitle; + + @Input() + searchBarValue: XStandardType = this.propertyProvider.searchBarValue; + + @Input() + searchBarEnterKeyHint: XStandardType = + this.propertyProvider.searchBarEnterKeyHint; + + @Input() + searchBarKeyboardType: XStandardType = + this.propertyProvider.searchBarKeyboardType; + + @Input() + searchBarSpellcheck: XStandardType = + this.propertyProvider.searchBarSpellcheck; + + @Input() + searchBarDebounce: XStandardType = + this.propertyProvider.searchBarDebounce; + + // + @Input() + searchBarShowCancelButton: XStandardType = + this.propertyProvider.searchBarShowCancelButton; + + @Input() + searchBarCancelButtonIcon: XStandardType = + this.propertyProvider.searchBarCancelButtonIcon; + + @Input() + searchBarCancelButtonTitle: XStandardType = + this.propertyProvider.searchBarCancelButtonTitle; + + @Input() + searchBarCancelButtonIconColor: XStandardType = + this.propertyProvider.searchBarCancelButtonIconColor; + + // + @Input() + searchBarShowClearButton: XStandardType = + this.propertyProvider.searchBarShowClearButton; + + @Input() + searchBarClearButtonIcon: XStandardType = + this.propertyProvider.searchBarClearButtonIcon; + + @Input() + searchBarClearButtonIconColor: XStandardType = + this.propertyProvider.searchBarClearButtonIconColor; + + // + @Input() + searchBarSearchButtonIcon: XStandardType = + this.propertyProvider.searchBarSearchButtonIcon; + + @Input() + searchBarSearchButtonIconColor: XStandardType = + this.propertyProvider.searchBarSearchButtonIconColor; + + // + @Output() + searchBarQueryChange: EventEmitter = new EventEmitter(); + + @Output() + searchBarOnEvent: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Empty Props ... + @Input() + showEmpty: XStandardType = this.propertyProvider.showEmpty; + + @Input() + emptyActionProvider: Subject = + this.propertyProvider.emptyActionProvider; + + @Input() + emptyCssClass: XStandardType> = + this.propertyProvider.emptyCssClass; + + @Input() + emptyColor: XStandardType = + this.propertyProvider.emptyColor; + + @Input() + emptyForegroundColor: XStandardType = + this.propertyProvider.emptyForegroundColor; + + @Input() + emptyMessage: XStandardType = + this.propertyProvider.emptyMessage; + + @Input() + emptyMessageColor: XStandardType = + this.propertyProvider.emptyMessageColor; + + @Input() + emptyForegroundMessageColor: XStandardType = + this.propertyProvider.emptyForegroundMessageColor; + + @Input() + emptyHasRefresh: XStandardType = + this.propertyProvider.emptyHasRefresh; + + @Input() + emptyRefreshButtonColor: XStandardType = + this.propertyProvider.emptyRefreshButtonColor; + + @Input() + emptyForegroundRefreshButtonColor: XStandardType = + this.propertyProvider.emptyForegroundRefreshButtonColor; + + @Input() + emptyRefreshIcon: XStandardType = + this.propertyProvider.emptyRefreshIcon; + + @Output() + emptyRefresh: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region File Props ... + @Input() + emptyFileException: XStandardType = + this.propertyProvider.emptyFileException; + + @Input() + notAllowedFileException: XStandardType = + this.propertyProvider.notAllowedFileException; + + @Input() + allowedExtensions: XStandardType> = + this.propertyProvider.allowedExtensions; + + @Input() + maxAllowedFiles: XStandardType = + this.propertyProvider.maxAllowedFiles; + + @Input() + maxAllowedFilesException: XStandardType = + this.propertyProvider.maxAllowedFilesException; + + @Input() + maxAllowedSize: XStandardType = this.propertyProvider.maxAllowedSize; + + @Input() + maxAllowedSizeException: XStandardType = + this.propertyProvider.maxAllowedSizeException; + + @Input() + minAllowedFileSize: XStandardType = + this.propertyProvider.minAllowedFileSize; + + @Input() + minAllowedFileSizeException: XStandardType = + this.propertyProvider.minAllowedFileSizeException; + + @Input() + maxAllowedFileSize: XStandardType = + this.propertyProvider.maxAllowedFileSize; + + @Input() + maxAllowedFileSizeException: XStandardType = + this.propertyProvider.maxAllowedFileSizeException; + //#endregion + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XFileUploadContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXFileUploadContainer + ): Observable { + return forkJoin({ + // + cssClass: this.getValue(container ? container.cssClass : this.cssClass), + color: this.getValue(container ? container.color : this.color), + foregroundColor: this.getValue( + container ? container.foregroundColor : this.foregroundColor + ), + actionBarHideActions: this.getValue( + container ? container.actionBarHideActions : this.actionBarHideActions + ), + showFileDropArea: this.getValue( + container ? container.showFileDropArea : this.showFileDropArea + ), + fileDropAreaCssClass: this.getValue( + container ? container.fileDropAreaCssClass : this.fileDropAreaCssClass + ), + fileDropAreaColor: this.getValue( + container ? container.fileDropAreaColor : this.fileDropAreaColor + ), + fileDropAreaForegroundColor: this.getValue( + container + ? container.fileDropAreaForegroundColor + : this.fileDropAreaForegroundColor + ), + fileDropAreaIcon: this.getValue( + container ? container.fileDropAreaIcon : this.fileDropAreaIcon + ), + fileDropAreaMessage: this.getValue( + container ? container.fileDropAreaMessage : this.fileDropAreaMessage + ), + fileDropAreaDisableFileCheck: this.getValue( + container + ? container.fileDropAreaDisableFileCheck + : this.fileDropAreaDisableFileCheck + ), + fileDropAreaSupportsMultipleFiles: this.getValue( + container + ? container.fileDropAreaSupportsMultipleFiles + : this.fileDropAreaSupportsMultipleFiles + ), + fileDropAreaAllowedExtensions: this.getValue( + container + ? container.fileDropAreaAllowedExtensions + : this.fileDropAreaAllowedExtensions + ), + fileDropAreaDragDisabled: this.getValue( + container + ? container.fileDropAreaDragDisabled + : this.fileDropAreaDragDisabled + ), + fileDropAreaContentTemplate: this.getValue( + container + ? container.fileDropAreaContentTemplate + : this.fileDropAreaContentTemplate + ), + fileDropAreaSuccessColor: this.getValue( + container + ? container.fileDropAreaSuccessColor + : this.fileDropAreaSuccessColor + ), + fileDropAreaErrorColor: this.getValue( + container + ? container.fileDropAreaErrorColor + : this.fileDropAreaErrorColor + ), + fileDropAreaAccentColor: this.getValue( + container + ? container.fileDropAreaAccentColor + : this.fileDropAreaAccentColor + ), + isInPage: this.getValue(container ? container.isInPage : this.isInPage), + wrapWithCard: this.getValue( + container ? container.wrapWithCard : this.wrapWithCard + ), + cardCssClass: this.getValue( + container ? container.cardCssClass : this.cardCssClass + ), + cardColor: this.getValue( + container ? container.cardColor : this.cardColor + ), + cardForegroundColor: this.getValue( + container ? container.cardForegroundColor : this.cardForegroundColor + ), + cardTitle: this.getValue( + container ? container.cardTitle : this.cardTitle + ), + cardSubTitle: this.getValue( + container ? container.cardSubTitle : this.cardSubTitle + ), + cardShowFooter: this.getValue( + container ? container.cardShowFooter : this.cardShowFooter + ), + cardShowActions: this.getValue( + container ? container.cardShowActions : this.cardShowActions + ), + showList: this.getValue(container ? container.showList : this.showList), + listConfirmRemove: this.getValue( + container ? container.listConfirmRemove : this.listConfirmRemove + ), + listCssClass: this.getValue( + container ? container.listCssClass : this.listCssClass + ), + listShowToolbar: this.getValue( + container ? container.listShowToolbar : this.listShowToolbar + ), + listStickyToolbar: this.getValue( + container ? container.listStickyToolbar : this.listStickyToolbar + ), + listEnableReordering: this.getValue( + container ? container.listEnableReordering : this.listEnableReordering + ), + listShowDivider: this.getValue( + container ? container.listShowDivider : this.listShowDivider + ), + listFilteringHandler: this.getValue( + container ? container.listFilteringHandler : this.listFilteringHandler + ), + showActionBar: this.getValue( + container ? container.showActionBar : this.showActionBar + ), + actionBarCssClass: this.getValue( + container ? container.actionBarCssClass : this.actionBarCssClass + ), + actionBarColor: this.getValue( + container ? container.actionBarColor : this.actionBarColor + ), + actionBarForegroundColor: this.getValue( + container + ? container.actionBarForegroundColor + : this.actionBarForegroundColor + ), + actionBarActions: this.getValue( + container ? container.actionBarActions : this.actionBarActions + ), + showSearchBar: this.getValue( + container ? container.showSearchBar : this.showSearchBar + ), + searchBarCssClass: this.getValue( + container ? container.searchBarCssClass : this.searchBarCssClass + ), + searchBarColor: this.getValue( + container ? container.searchBarColor : this.searchBarColor + ), + searchBarForegroundColor: this.getValue( + container + ? container.searchBarForegroundColor + : this.searchBarForegroundColor + ), + searchBarSearchInputColor: this.getValue( + container + ? container.searchBarSearchInputColor + : this.searchBarSearchInputColor + ), + searchBarForegroundSearchInputColor: this.getValue( + container + ? container.searchBarForegroundSearchInputColor + : this.searchBarForegroundSearchInputColor + ), + searchBarType: this.getValue( + container ? container.searchBarType : this.searchBarType + ), + searchBarTitle: this.getValue( + container ? container.searchBarTitle : this.searchBarTitle + ), + searchBarValue: this.getValue( + container ? container.searchBarValue : this.searchBarValue + ), + searchBarEnterKeyHint: this.getValue( + container ? container.searchBarEnterKeyHint : this.searchBarEnterKeyHint + ), + searchBarKeyboardType: this.getValue( + container ? container.searchBarKeyboardType : this.searchBarKeyboardType + ), + searchBarSpellcheck: this.getValue( + container ? container.searchBarSpellcheck : this.searchBarSpellcheck + ), + searchBarDebounce: this.getValue( + container ? container.searchBarDebounce : this.searchBarDebounce + ), + searchBarShowCancelButton: this.getValue( + container + ? container.searchBarShowCancelButton + : this.searchBarShowCancelButton + ), + searchBarCancelButtonIcon: this.getValue( + container + ? container.searchBarCancelButtonIcon + : this.searchBarCancelButtonIcon + ), + searchBarCancelButtonTitle: this.getValue( + container + ? container.searchBarCancelButtonTitle + : this.searchBarCancelButtonTitle + ), + searchBarCancelButtonIconColor: this.getValue( + container + ? container.searchBarCancelButtonIconColor + : this.searchBarCancelButtonIconColor + ), + searchBarShowClearButton: this.getValue( + container + ? container.searchBarShowClearButton + : this.searchBarShowClearButton + ), + searchBarClearButtonIcon: this.getValue( + container + ? container.searchBarClearButtonIcon + : this.searchBarClearButtonIcon + ), + searchBarClearButtonIconColor: this.getValue( + container + ? container.searchBarClearButtonIconColor + : this.searchBarClearButtonIconColor + ), + searchBarSearchButtonIcon: this.getValue( + container + ? container.searchBarSearchButtonIcon + : this.searchBarSearchButtonIcon + ), + searchBarSearchButtonIconColor: this.getValue( + container + ? container.searchBarSearchButtonIconColor + : this.searchBarSearchButtonIconColor + ), + showEmpty: this.getValue( + container ? container.showEmpty : this.showEmpty + ), + emptyCssClass: this.getValue( + container ? container.emptyCssClass : this.emptyCssClass + ), + emptyColor: this.getValue( + container ? container.emptyColor : this.emptyColor + ), + emptyForegroundColor: this.getValue( + container ? container.emptyForegroundColor : this.emptyForegroundColor + ), + emptyMessage: this.getValue( + container ? container.emptyMessage : this.emptyMessage + ), + emptyMessageColor: this.getValue( + container ? container.emptyMessageColor : this.emptyMessageColor + ), + emptyForegroundMessageColor: this.getValue( + container + ? container.emptyForegroundMessageColor + : this.emptyForegroundMessageColor + ), + emptyHasRefresh: this.getValue( + container ? container.emptyHasRefresh : this.emptyHasRefresh + ), + emptyRefreshButtonColor: this.getValue( + container + ? container.emptyRefreshButtonColor + : this.emptyRefreshButtonColor + ), + emptyForegroundRefreshButtonColor: this.getValue( + container + ? container.emptyForegroundRefreshButtonColor + : this.emptyForegroundRefreshButtonColor + ), + emptyRefreshIcon: this.getValue( + container ? container.emptyRefreshIcon : this.emptyRefreshIcon + ), + emptyFileException: this.getValue( + container ? container.emptyFileException : this.emptyFileException + ), + notAllowedFileException: this.getValue( + container + ? container.notAllowedFileException + : this.notAllowedFileException + ), + allowedExtensions: this.getValue( + container ? container.allowedExtensions : this.allowedExtensions + ), + maxAllowedFiles: this.getValue( + container ? container.maxAllowedFiles : this.maxAllowedFiles + ), + maxAllowedFilesException: this.getValue( + container + ? container.maxAllowedFilesException + : this.maxAllowedFilesException + ), + maxAllowedSize: this.getValue( + container ? container.maxAllowedSize : this.maxAllowedSize + ), + maxAllowedSizeException: this.getValue( + container + ? container.maxAllowedSizeException + : this.maxAllowedSizeException + ), + minAllowedFileSize: this.getValue( + container ? container.minAllowedFileSize : this.minAllowedFileSize + ), + minAllowedFileSizeException: this.getValue( + container + ? container.minAllowedFileSizeException + : this.minAllowedFileSizeException + ), + maxAllowedFileSize: this.getValue( + container ? container.maxAllowedFileSize : this.maxAllowedFileSize + ), + maxAllowedFileSizeException: this.getValue( + container + ? container.maxAllowedFileSizeException + : this.maxAllowedFileSizeException + ), + }).pipe( + map((data) => { + return { + // + ...data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + fileChange: this.fileChange, + + // + fileDropAreaActionProvider: container + ? container.fileDropAreaActionProvider + : this.fileDropAreaActionProvider, + + // + fileDropAreaFilesChangeEmiter: this.fileDropAreaFilesChangeEmiter, + + // + cardActionProvider: container + ? container.cardActionProvider + : this.cardActionProvider, + + // + listActionProvider: container + ? container.listActionProvider + : this.listActionProvider, + + // + actionBarActionProvider: container + ? container.actionBarActionProvider + : this.actionBarActionProvider, + + // + actionBarActionFired: this.actionBarActionFired, + + // + searchBarActionProvider: container + ? container.searchBarActionProvider + : this.searchBarActionProvider, + + // + searchBarQueryChange: this.searchBarQueryChange, + searchBarOnEvent: this.searchBarOnEvent, + + // + emptyActionProvider: container + ? container.emptyActionProvider + : this.emptyActionProvider, + + // + emptyRefresh: this.emptyRefresh, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-form\x-form.component.html + + + + + + + + +
+ +
+ + +
+ +
+
+
+ + + + +
+ +
+ + + + + + + + + +
+ +
+ + + + + + {{ getControlLabel(control) | async }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+
+ + + + + + + + +
+ + + + + +
+
+
+
+ + + + +
+ + + + + + {{ getControlLabel(control) | async }} + + + + + {{ getResourceValue(option.viewValue) | async }} + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + {{ getResourceValue(option.viewValue) | async }} + + + + + +
+
+ + + + + + +
+
+ + + + +
+ +
+ + + + + + + + + + {{ getResourceValue(option.viewValue) | async }} + + +
+ + + + + + +
+
+ + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + +
+
+ + + + +
+ +
+ + + + + + + + + + + + + + + {{ getControlLabel(control) | async }} + + + + + + + + + + + + + +
+ + + + + + +
+
+ + + + +
+ +
+ + + + + + + + + + +
+ + + + + + +
+
+ + + + +
+ + + + + + {{ getControlLabel(control) | async }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + +
+
+ + + + + + + + + +
+ +
+
+ + + + +
+ +
+
+ + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + +
+
+ + + + +
+ +
+ + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + +
+ +
+
+
+ + + + + + + + + +
+ + + + + + + + + +
+ + + + + + +
+
+ + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + +
+
+
+
+
+
+
+ + + + + +
+ + + + + {{ getErrorHandlerErrorMessage(handler) | async }} + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ getControlHint(control) | async }} + + + + +
+ + + + {{ getErrorHandlerErrorMessage(handler, control) | async }} + + + +
+
+ + + + +
+ + +
+ +
+
+ + + + + + + + + + + + + + + + + + + + +
+ + + + + + +
+ + + + + {{ getControlLabel(control) | async }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-form\x-form.component.scss + +// +:host { + // + --regular-color: var(--x-color-dark); + --valid-color: var(--x-color-success); + --hint-color: var(--x-color-tertiary); + --invalid-color: var(--x-color-danger); + --active-color: var(--x-color-tertiary); + --pending-color: var(--x-color-warning); + --disabled-color: var(--x-color-medium); + --accent-color: var(--x-color-secondary); + --fill-color: var(--x-color-secondary-tint); + --disabled-fill-color: var(--x-color-light-shade); + + // + --hint-text-size: 7pt; + --error-text-size: 7pt; + + // + --form-fields-vertical-margin: 16px; +} + +// +//#region Mixins ... +// +@mixin formFieldRegularProps { + // + x-icon { + --size: 20px; + } + + // + .x-field-label { + } + + // + .auto-size-flex { + flex: 0 0 auto !important; + } + + // + .full-heigh { + * { + height: 100%; + } + } +} + +@mixin formInputElementProps { + // + .mat-input-element { + @include colorHandler(var(--regular-color), false); + } + + // + .x-form-input-element { + width: 100%; + border: unset; + outline: unset; + border-color: transparent; + + // + &:focus { + // outline: var(--active-color) auto 1px; + } + + // + @include colorHandler(var(--regular-color), false); + @include backgroundColorHandler(transparent, false); + } + + // + .x-form-date-input-element, + .x-form-input-element:not(.mat-input-element) { + height: 100% !important; + } +} + +@mixin formFieldHintProps( + $color: var(--hint-color), + $size: var(--hint-text-size) +) { + .x-form-control-hint { + // + color: $color; + font-size: $size; + } +} + +@mixin formFieldErrorProps( + $color: var(--invalid-color), + $size: var(--error-text-size) +) { + // + // + $duration: 250ms; + $transform-show: scaleY(1); + $transform-hide: scaleY(0); + .x-form-error, + .x-form-field-error { + // + color: $color; + font-size: $size; + + // + height: 0; + + // + transform: $transform-hide; + -ms-transform: $transform-hide; + -webkit-transform: $transform-hide; + // + transition: transform; + -webkit-transition: transform; + // + transition-duration: $duration; + -webkit-transition-duration: $duration; + + // + &.show { + // + height: auto !important; + // + transform: $transform-show; + -ms-transform: $transform-show; + -webkit-transform: $transform-show; + // + transition-duration: $duration; + -webkit-transition-duration: $duration; + } + } +} + +@mixin formFieldProps { + // + .x-form-field { + // + @include dateProps; + @include sliderProps; + @include radioButtonProps; + @include selectOptionProps; + @include toogleColorHandler; + @include checkBoxColorHandler; + @include formFieldRegularProps; + @include formInputElementProps; + @include formFieldAppearanceHandler; + @include colorHandler(var(--regular-color), false); + @include backgroundColorHandler(transparent !important); + + // + &.x-mat-form-field { + // + width: 100%; + + // + &.mat-focused { + @include formFieldFocusHandler; + } + } + + // + &.x-form-radio-field { + // + outline: unset; + + // + &.x-focused { + // + @include formFieldFocusHandler; + @include iconStatusColorHandler(var(--active-color)); + } + } + + // + &:not(.x-mat-form-field):not(.x-form-radio-field) { + // + outline: unset; + + // + &.x-focused { + // + @include formFieldFocusHandler; + border-color: var(--active-color); + } + } + } +} + +@mixin autoCompleteProps { + /* Auto Complete Styling */ + // + $duration: 250ms; + $transform-show: scaleY(1); + $transform-hide: scaleY(0); + + .x-auto-complete-wrapper { + position: unset; + display: unset; + } + + .x-auto-complete { + // + position: content; + + // + &.x-auto-complete-margin { + margin-top: 0; + } + + // + height: calc(50%); + max-height: 300px; + + // + left: 8px; + right: 8px; + overflow-y: scroll; + + // + z-index: 113; + + // + transform: $transform-hide; + -ms-transform: $transform-hide; + -webkit-transform: $transform-hide; + + // + transition: transform; + -webkit-transition: transform; + + // + transition-duration: $duration; + -webkit-transition-duration: $duration; + + // + &.open { + // + transform: $transform-show; + -ms-transform: $transform-show; + -webkit-transform: $transform-show; + + // + transition-duration: $duration; + -webkit-transition-duration: $duration; + } + } + /* END Auto Complete Styling */ +} + +@mixin selectOptionProps { + // + .x-select-option { + width: 100%; + margin-bottom: 4px; + + &:last-child { + margin-bottom: 8px; + } + } +} + +@mixin dateProps { + // + .mat-icon-button { + display: table-cell; + vertical-align: bottom; + } +} + +@mixin radioButtonProps($disabled: false) { + // + .x-radio-button { + // + @include radioColorHandler(var(--regular-color), true, false); + + // + &.active { + // + @include radioColorHandler(var(--active-color) !important, true, true); + } + } + + &.x-form-radio-field { + // + outline: unset; + + // + &.x-focused { + // + .x-field-label { + color: var(--active-color) !important; + } + border-color: var(--active-color) !important; + + // + @include iconStatusColorHandler(var(--active-color)); + } + } +} + +@mixin formFieldFocusHandler { + // + .mat-form-field-ripple { + @include backgroundColorHandler(var(--active-color) !important, false); + } + + // + @include iconStatusColorHandler(var(--active-color)); + @include formFieldLabelColorHandler(var(--active-color)); + @include formFieldWrapperColorHandler(var(--active-color)); + @include matFormFieldOutlineColorHandler(var(--active-color) !important); +} + +@mixin formFieldAppearanceHandler($disabled: false) { + // + // Label Colors ... + @if $disabled { + // + @include formFieldLabelColorHandler(var(--disabled-color)); + + // + .x-form-field-appearance-fill:not(.x-mat-form-field) { + border-color: var(--disabled-fill-color); + } + + // + .mat-select-value-text { + color: var(--disabled-color) !important; + } + } + + // + .mat-select-value-text { + color: var(--regular-color); + } + + // + // None ... + .x-form-field-appearance-none { + // + @if $disabled { + @include formFieldLabelColorHandler(var(--disabled-color)); + } @else { + } + } + + // + // Legacy ... + // Standard ... + .x-form-field-appearance-legacy, + .x-form-field-appearance-standard { + // + @if $disabled { + @include matFormFieldUnderlineColorHandler( + var(--disabled-fill-color) !important, + true + ); + } @else { + @include matFormFieldUnderlineColorHandler(var(--fill-color) !important); + } + + // + &:not(.x-mat-form-field) { + @if $disabled { + } @else { + // + padding: 8px; + border-radius: 0; + border-bottom-width: 1px; + border-bottom-style: solid; + border-color: var(--fill-color); + } + } + } + + // + // Outline ... + .x-form-field-appearance-outline { + // + @if $disabled { + @include matFormFieldOutlineColorHandler(var(--disabled-fill-color)); + } @else { + @include formFieldLabelColorHandler(var(--regular-color)); + @include matFormFieldOutlineColorHandler(var(--fill-color)); + } + + // + &:not(.x-mat-form-field) { + @if $disabled { + @include backgroundColorHandler(transaprent!important, false); + } @else { + // + padding: 8px; + border-radius: 3px; + border-width: 1px; + border-style: solid; + } + } + } + + // + // Fill ... + .x-form-field-appearance-fill { + // + &.x-mat-form-field:not(.x-form-field-disabled) { + // + @if $disabled { + // + .mat-form-field-flex { + @include colorHandler(var(--disabled-fill-color) !important, false); + @include backgroundColorHandler( + var(--disabled-fill-color) !important, + false + ); + } + + // + @include matFormFieldUnderlineColorHandler(var(--disabled-color)); + } @else { + // + @include backgroundColorHandler(var(--fill-color) !important, false); + + // + .mat-form-field-flex { + @include colorHandler(var(--regular-color), false); + @include backgroundColorHandler(var(--fill-color) !important, false); + } + + // + @include matFormFieldUnderlineColorHandler(var(--accent-color)); + } + } + + // + @if $disabled { + // + &.x-form-field-disabled { + background-color: var(--disabled-fill-color) !important; + } + + // + .mat-form-field-flex { + @include colorHandler(var(--disabled-color) !important, false); + @include backgroundColorHandler( + var(--disabled-fill-color) !important, + false + ); + } + + // + .x-form-slider-control { + // + .mat-slider-thumb { + background-color: var(--disabled-color) !important; + } + + // + .mat-slider-wrapper, + .mat-slider-track-wrapper { + background-color: var(--disabled-color) !important; + } + } + + // + @include matFormFieldUnderlineColorHandler(var(--disabled-color)); + } @else { + // + .x-form-slider-control { + .mat-slider-wrapper, + .mat-slider-track-wrapper { + background-color: var(--regular-color) !important; + } + } + } + + // + &:not(.x-mat-form-field) { + @if $disabled { + @include backgroundColorHandler( + var(--disabled-fill-color) !important, + false + ); + } @else { + // + padding: 8px; + border-radius: 3px; + border-bottom-width: 1px; + border-bottom-style: solid; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + border-color: var(--regular-color); + + // + @include backgroundColorHandler(var(--fill-color) !important, false); + } + } + } +} + +@mixin formFieldStatusHandler { + // + // Global ... + @include formFieldAppearanceHandler(false); + + // + // Disabled ... + &.x-form-field-wrapper-disabled { + // + * { + cursor: default; + pointer-events: none; + } + + // + x-icon.x-action-icon > * { + color: var(--disabled-color) !important; + } + + // + .mat-form-field-disabled { + color: var(--disabled-color) !important; + } + + // + .x-form-input-element { + // + opacity: 1 !important; + border-color: var(--disabled-fill-color) !important; + + // + @include colorHandler(var(--disabled-color) !important, false); + } + + // + .x-form-control { + opacity: 1 !important; + @include colorHandler(var(--disabled-color) !important, false); + } + + // + .mat-slide-toggle-bar { + background-color: var(--disabled-fill-color) !important; + } + + // + @include sliderProps(true); + @include formFieldAppearanceHandler(true); + @include formFieldHintProps(var(--disabled-color)); + @include formFieldErrorProps(var(--disabled-color)); + @include checkBoxColorHandler(--disabled-color, true); + @include iconStatusColorHandler(var(--disabled-color), true); + @include formFieldWrapperColorHandler(var(--disabled-color) !important); + @include radioColorHandler(var(--disabled-color) !important, true, true); + } + + // + // Valid ... + &.x-form-field-wrapper-valid { + // + @include iconStatusColorHandler(var(--valid-color), true); + + // + &.apply-status { + @include formFieldWrapperColorHandler(var(--valid-color)); + } + } + + // + // Invalid ... + &.x-form-field-wrapper-invalid { + // + @include iconStatusColorHandler(var(--invalid-color), true); + + // + &.apply-status { + @include formFieldWrapperColorHandler(var(--invalid-color)); + } + } + + // + // Pending ... + &.x-form-field-wrapper-pending { + // + @include iconStatusColorHandler(var(--pending-color), true); + + // + &.apply-status { + @include formFieldWrapperColorHandler(var(--pending-color)); + } + } +} + +@mixin colorHandler($color: var(--regular-color), $applyToChilds: true) { + color: $color; + + // + @if $applyToChilds { + * { + :not(x-icon):not(x-icon > *) { + color: $color; + } + } + } + + // + @include matFormFieldOutlineColorHandler($color); +} + +@mixin backgroundColorHandler($color: transaprent, $applyToChilds: true) { + // + background-color: $color; + + // + @if $applyToChilds { + *:not(.x-field-label):not(.x-field-label + *):not(.mat-form-field-ripple):not(.mat-slide-toggle-thumb):not(.mat-slide-toggle-bar):not(.mat-checkbox-frame):not(.mat-radio-inner-circle):not(.mat-form-field-underline):not(.mat-slide):not(.mat-slider + *):not(.x-markdown *):not(.x-color *):not(.x-map + *):not(.x-selected-color):not(.x-color-palette):not(.x-color-alpha) { + background-color: $color; + } + } +} + +@mixin formFieldWrapperColorHandler($color: var(--regular-color)) { + // + .x-form-input-element { + // outline: $color auto 1px; + } + + // + .x-form-field:not(.x-mat-form-field) { + border-color: $color; + } + + // + @include sliderProps; + @include toogleColorHandler($color); + @include checkBoxColorHandler($color); + @include formFieldLabelColorHandler($color); + @include matFormFieldOutlineColorHandler($color); + @include iconStatusColorHandler($color !important); + @include matFormFieldUnderlineColorHandler($color !important); +} + +@mixin radioColorHandler( + $color: var(--regular-color), + $applyOnOuterCircle: false, + $applyOnInnerCircle: false +) { + // + .mat-radio-label { + // + color: $color; + + // + @if $applyOnOuterCircle { + .mat-radio-outer-circle { + border-color: $color; + } + } + + // + @if $applyOnInnerCircle { + .mat-radio-inner-circle { + background-color: $color; + } + } + } +} + +@mixin checkBoxColorHandler($color: var(--regular-color), $disabled: false) { + // + .mat-checkbox-frame { + @if $disabled { + border-color: var(--disabled-color) !important; + } @else { + border-color: var(--regular-color); + } + } + + // + .mat-checkbox-checked { + .mat-checkbox-frame, + .mat-checkbox-background { + background-color: var(--active-color) !important; + } + } + + // + .mat-checkbox-label { + .x-field-label { + width: 100%; + } + } + + // + .x-form-control { + &.has-icon { + .mat-checkbox-layout { + margin: { + left: 4px; + right: 4px; + } + } + } + } +} + +@mixin toogleColorHandler($color: var(--regular-color)) { + // + :not(.x-form-field-disabled) { + .mat-slide-toggle-bar { + background-color: var(--active-color) !important; + } + } + + // + .x-form-field-disabled { + .mat-slide-toggle-bar { + background-color: var(--disabled-color) !important; + } + .mat-slide-toggle-thumb { + background-color: var(--disabled-fill-color) !important; + } + } + + // + .mat-slide-toggle-thumb { + background-color: $color !important; + } + + // + .x-form-control { + &.has-icon { + .mat-slide-toggle-label { + margin: { + left: 4px; + right: 4px; + } + } + } + } +} + +@mixin sliderProps($disabled: false) { + // + .x-form-slider-control { + // + @if $disabled { + // + cursor: default; + pointer-events: none; + } + + // + &:not(.vertical) { + width: 100%; + } + + // + &.vertical { + display: block; + margin: { + left: auto; + right: auto; + } + } + + // + .mat-slider-wrapper, + .mat-slider-track-wrapper { + // + @if $disabled { + background-color: var(--disabled-fill-color) !important; + } @else { + background-color: var(--fill-color); + } + } + + // + .mat-slider-thumb, + .mat-slider-thumb-label, + .mat-slider-thumb-label-text { + // + @if $disabled { + color: var(--disabled-color) !important; + background-color: var(--disabled-fill-color) !important; + } @else { + color: var(--accent-color); + background-color: var(--active-color); + } + } + + // + .mat-slider-track-fill { + // + @if $disabled { + background-color: var(--disabled-color) !important; + } @else { + background-color: var(--active-color); + } + } + + // + .mat-slider-vertical { + } + } +} + +@mixin formFieldLabelColorHandler($color: var(--regular-color)) { + // + .mat-form-field-label { + color: $color; + } + + // + .x-field-label { + color: $color; + } + + // + .mat-select-placeholder { + color: $color; + } +} + +@mixin iconStatusColorHandler($color: var(--regular-color), $important: false) { + // + x-icon.x-status-handler > * { + // + @if $important { + color: $color !important; + } @else { + color: $color; + } + } +} + +@mixin matFormFieldUnderlineColorHandler( + $color: var(--fill-color), + $clearBackground: false +) { + // + .mat-form-field-underline { + // + border-color: $color; + background-color: $color; + + // + &::before { + background-color: $color; + } + + // + @if $clearBackground { + background-image: none; + } + } + + // + .mat-select-arrow { + color: $color; + } +} + +@mixin matFormFieldOutlineColorHandler($color: var(--regular-color)) { + // + color: $color; + + // + .mat-form-field-outline-start, + .mat-form-field-outline-end, + .mat-form-field-outline-gap { + border-width: 1px !important; + } + + // + .mat-form-field-outline, + .mat-form-field-outline-thick { + color: $color; + border-width: 1px !important; + } +} +//#endregion + +// +::ng-deep.cdk-overlay-pane { + // + transform: translateY(0px) !important; + + // + .mat-datepicker-content-container { + height: auto !important; + } +} + +// +::ng-deep.x-form-select-control { + // + min-width: 100% !important; + + // + &:not(.x-form-select-control-panel) { + max-width: 100% !important; + } + + // + &.x-form-select-control-panel { + } + + // // + // .mat-select-value-text { + // color: var(--regular-color) !important; + // } + + // + .x-select-option { + // + color: var(--regular-color) !important; + background-color: var(--fill-color) !important; + + // + &.mat-active { + color: var(--active-color) !important; + } + + // + &.mat-selected { + // + color: var(--active-color) !important; + + // + .mat-pseudo-checkbox-checked { + background: var(--active-color) !important; + } + + // + .mat-pseudo-checkbox::after { + color: var(--accent-color) !important; + } + } + + // + &:hover { + $level: 0.5; + filter: brightness($level); + -webkit-filter: brightness($level); + color: var(--active-color) !important; + } + } +} + +// +::ng-deep.x-form-control-date-picker { + // + &.mat-calendar { + // + color: var(--regular-color) !important; + background-color: var(--fill-color) !important; + + // + .mat-calendar-arrow { + border-top-color: unset; + } + + // + .mat-calendar-body-today { + color: var(--active-color); + border-color: var(--acttive-color); + } + + // + .mat-calendar-table-header { + color: var(--accent-color) !important; + } + + // + .mat-calendar-body-selected { + // + color: var(--accent-color); + background-color: var(--active-color); + border-color: var(--accent-color); + } + + // + .mat-calendar-next-button, + .mat-calendar-previous-button, + .mat-calendar-period-button { + color: var(--active-color) !important; + } + + // + .mat-calendar-body-label { + color: var(--active-color) !important; + } + + // + .mat-calendar-body-cell-content { + color: var(--regular-color) !important; + } + + // + .mat-calendar-body-disabled { + color: var(--disabled-color) !important; + filter: contrast(0.5) !important; + } + } +} + +// +:host ::ng-deep .x-form { + // + display: flex; + flex-wrap: wrap; + flex-direction: row; + + // + .x-form-field-wrapper { + // + width: 100%; + margin-bottom: var(--form-fields-vertical-margin); + + // + .mat-form-field-wrapper { + padding-bottom: 0; + } + + // + .mat-form-field-underline { + position: initial; + } + + // + .ng-invalid, + .mat-form-field-invalid { + // + *:not(.x-field-label):not(ion-icon):not(.mat-form-field-outline):not(.mat-form-field-outline-thick):not(.mat-form-field-outline-end):not(.mat-form-field-outline-gap):not(.mat-form-field-outline-start) { + color: var(--regular-color); + } + + // + &.apply-status { + * { + color: var(--invalid-color) !important; + } + } + + *:not(.x-field-label) { + background-color: transparent; + } + } + + // + &.readonly { + // + .x-field-label { + color: var(--disabled-color) !important; + } + + // + .x-form-control { + color: var(--disabled-color) !important; + } + } + + // + @include formFieldProps; + @include autoCompleteProps; + @include formFieldStatusHandler; + @include formFieldAppearanceHandler(false); + @include formFieldHintProps(var(--hint-color), var(--hint-text-size)); + @include formFieldErrorProps(var(--invalid-color), var(--error-text-size)); + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-form\x-form.component.ts + +import { + FormGroup, + FormControl, + AbstractControl, + ValidationErrors, +} from '@angular/forms'; +import { + defaultFormControlClearIcon, + defaultFormControlSelectIcon, +} from './x-form.tools'; +import { + keys, + sort, + nameof, + XColor, + isDate, + assign, + entries, + XUiEvent, + hasChild, + isFunction, + parseXColor, + momentToDate, + XResourceIDs, + XBorderStyle, + XExceptionIDs, + XDialogResult, + XOneOrManyType, + isNullOrUndefined, + isNullOrEmptyString, + XColorWithBrightness, + getXColorCssVariableName, +} from 'x-framework-core'; +import { + XFormCssVar, + XFormStatus, + XFormUpdateOn, + XFormBaseAction, + XFormControlType, + XFormActionModel, + XControlIconRole, + XFormErrorHandler, + XFormControlConfig, + XFormControlAction, + XFormIconDescriptor, + XControlIconPosition, + XFormStatusIdentifier, + XFormMapControlConfig, + XControlIconDescriptor, + XFormControlAppearance, + XFormInputControlConfig, + XFormRadioControlConfig, + XFormContainerComponent, + XFormSelectControlConfig, + XFormSliderControlConfig, + XFormSelectControlOption, + XFormControlLabelPosition, + XControlIconRoleIdentifier, + XFormMarkdownControlConfig, + XFormControlNameIdentifier, + XFormMapControlPresentType, + XFormCheckBoxControlConfig, + XFormSelectorControlConfig, + XBaseFormControlActionModel, + XFormDatePickerControlConfig, + XFormColorPickerControlConfig, + XFormControlAutoCompleteConfig, + XFormAvatarUploadControlConfig, + XFormControlAppearanceDescriptor, + XFormControlValueChangeEventModel, + XFormControlStatusChangeEventModel, + XFormColorPickerControlPresentType, + XFormDatePickerControlPickerPosition, +} from './x-form.typings'; +import { + XMapAction, + XMapLocation, + XMapActionModel, + XMapItemPosition, + defaultMarkerStyle, + defaultMapSearchPlaceProvider, +} from '../x-map/x-map.typings'; +import { isMoment } from 'moment'; +import { + XMarkdownMode, + XMarkdownActionModel, +} from '../x-markdown/x-markdown.typings'; +import { + XSearchBarType, + XSearchBarActionModel, + XSearchBarEnterKeyHint, +} from '../x-search-bar/x-search-bar.typings'; +import { + XColorPickerActionModel, + XColorPickerOutputFormat, +} from '../x-color-picker/x-color-picker.typings'; +import { + XAvatarUploadActionModel, + defaultAvatarUploadActionButtons, +} from '../x-avatar-upload/x-avatar-upload.typings'; +import { map, concatMap, tap } from 'rxjs/operators'; +import { XIconNames } from '../x-icon/x-icon.typings'; +import { toLongLat, fromLongLat } from '../x-map/x-map.tools'; +import { XStringInputFormatType } from 'x-framework-services'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; +import { MatFormFieldAppearance } from '@angular/material/form-field'; +import { of, Subject, Subscription, Observable, forkJoin } from 'rxjs'; +import { MatDatepickerInputEvent } from '@angular/material/datepicker'; +import { XSlotName, XSlotLayout } from '../x-slotter/x-slotter.typings'; +import { XMapDialogConfig } from '../x-map/x-map-dialog/x-map-dialog.typings'; +import { XFileDropAreaActionModel } from '../x-file-drop-area/x-file-drop-area.typings'; +import { XColorPickerDialogConfig } from '../x-color-picker/x-color-picker-dialog/x-color-picker-dialog.typings'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-form', + templateUrl: './x-form.component.html', + styleUrls: ['./x-form.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XFormComponent extends XFormContainerComponent { + // + //#region Props ... + // + private VALUE: any; + private FORM: FormGroup; + private FORM_CONFIG: any; + private ERRORS: ValidationErrors; + private actionProviders: any = {}; + private STATUS: XFormStatusIdentifier; + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + readonly SlotNames = Object.assign({}, XSlotName); + readonly SlotLayouts = Object.assign({}, XSlotLayout); + readonly FormStatuses = Object.assign({}, XFormStatus); + readonly ControlTypes = Object.assign({}, XFormControlType); + readonly ControlIconRoles = Object.assign({}, XControlIconRole); + readonly ControlIconPositions = Object.assign({}, XControlIconPosition); + readonly FormControlAppearances = Object.assign({}, XFormControlAppearance); + + // + currentInputFocus: string; + + // + get value() { + return this.VALUE; + } + + get status() { + return this.STATUS; + } + + get errors() { + return this.ERRORS; + } + + get isStatusValid() { + return this.status === XFormStatus.Valid; + } + + get form() { + return this.FORM; + } + //#endregion + + // + //#region LifeCycles ... + async onInit() { + super.onInit(); + + // + await this.applyColors(); + } + + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + // Form Config ... + const isFormConfigChanged = changeKeys.includes( + nameof('formConfig') + ); + if (isFormConfigChanged) { + await this.prepareFormGroup(); + } + + // + // Action Provider ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + await this.registerActionProvider(); + } + + // + //#region UI Disabled or Loading ... + const isUiDisabledChanged = changeKeys.includes( + nameof('uiDisabled') + ); + if (isUiDisabledChanged) { + await this.handleUiDisabledChanged(); + } + //#endregion + + // + //#region Color Change Detecotrs ... + // + const isRegularColorChanged = changeKeys.includes( + nameof('regularColor') + ); + if (isRegularColorChanged) { + await this.applyRegularColor(); + } + + // + const isAccentColorChanged = changeKeys.includes( + nameof('accentColor') + ); + if (isAccentColorChanged) { + await this.applyAccentColor(); + } + + // + const isHintColorChanged = changeKeys.includes( + nameof('hintColor') + ); + if (isHintColorChanged) { + await this.applyHintColor(); + } + + // + const isPendingColorChanged = changeKeys.includes( + nameof('pendingColor') + ); + if (isPendingColorChanged) { + await this.applyPendingColor(); + } + + // + const isActiveColorChanged = changeKeys.includes( + nameof('activeColor') + ); + if (isActiveColorChanged) { + await this.applyActiveColor(); + } + + // + const isValidColorChanged = changeKeys.includes( + nameof('validColor') + ); + if (isValidColorChanged) { + await this.applyValidColor(); + } + + // + const isInvalidColorChanged = changeKeys.includes( + nameof('invalidColor') + ); + if (isInvalidColorChanged) { + await this.applyInvalidColor(); + } + + // + const isFillColorChanged = changeKeys.includes( + nameof('fillColor') + ); + if (isFillColorChanged) { + await this.applyFillColor(); + } + + // + const isDisabledColorChanged = changeKeys.includes( + nameof('disabledColor') + ); + if (isDisabledColorChanged) { + await this.applyDisabledColor(); + } + + // + const isDisabledFillColorChanged = changeKeys.includes( + nameof('disabledFillColor') + ); + if (isDisabledFillColorChanged) { + await this.applyDisabledFillColor(); + } + //#endregion + } + + async afterViewInit() { + super.afterViewInit(); + + // + this.managerService.dispatchResizeEvent(); + setTimeout(() => this.managerService.dispatchResizeEvent(), 500); + this.detectChanges(); + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region UI Providers ... + // + //#region Form Providers ... + // + //#region Form Config ... + /** + * prepare form config object + */ + getFormConfig() { + return this.getValue(this.formConfig); + } + //#endregion + + // + //#region Form Model ... + /** + * get form name ... + */ + getFormName() { + return this.getFormConfig().pipe( + concatMap((formConfig) => this.getValue(formConfig.name)) + ); + } + //#endregion + + // + //#region Css Class Lists... + /** + * retrieve form cssClasses ... + */ + getFormCssClass() { + return this.getArrayValue(this.cssClass).pipe( + concatMap((cssClasses) => + this.getFormName().pipe( + map((formName) => { + return { + name: formName, + cssClasses, + }; + }) + ) + ), + map((descriptor) => { + return descriptor.cssClasses.concat( + 'x-form', + `x-status-${this.STATUS}`, + `x-form-${descriptor.name}` + ); + }) + ); + } + + /** + * retrieve form control wrapper classes list ... + * + * @param control form control config ... + */ + getFormControlWrapperClasses(control: XFormControlConfig) { + return this.getControlInfo(control).pipe( + map((cInfo) => { + // + const fControl = this.getControl(control.propName); + const isFocused = + this.getControlInputElementId(control) === this.currentInputFocus; + + // + return [ + 'x-form-field-wrapper', + `x-form-${cInfo.type}-field-wrapper`, + `x-form-field-${control.index.toString()}-wrapper`, + `x-form-field-${control.propName.toString()}-wrapper`, + `x-form-field-appearance-${cInfo.appearance + .toString() + .toLowerCase()}-wrapper`, + // + // Focused ... + ...(isFocused ? ['x-focused'] : []), + // + // Disabled ... + ...(cInfo.disabled || fControl.disabled + ? ['x-form-field-wrapper-disabled'] + : []), + // + // Valid ... + ...(fControl.valid ? ['x-form-field-wrapper-valid'] : []), + // + // Invalid ... + ...(fControl.invalid ? ['x-form-field-wrapper-invalid'] : []), + // + // Pending ... + ...(fControl.pending ? ['x-form-field-wrapper-pending'] : []), + ]; + }) + ); + } + + /** + * retrieve form errors classes ... + */ + getFormErrorsClasses() { + return this.getFormName().pipe( + map((formName) => { + return ['x-form-errors', `x-form-${formName}-errors`]; + }) + ); + } + + /** + * retrieve form specific error's class lists ... + * + * @param handler form error handler ... + */ + getFormErrorClasses(handler: XFormErrorHandler) { + return this.getFormName().pipe( + concatMap((formName) => + this.getErrorHandlerErrorName(handler).pipe( + map((errorName) => { + return { + formName, + errorName, + }; + }) + ) + ), + map((descriptor) => { + return [ + 'x-form-error', + `x-form-${descriptor.formName}-error`, + `x-form-${descriptor.errorName}-error`, + `x-form-${descriptor.formName}-${descriptor.errorName}-error`, + ]; + }) + ); + } + + /** + * retrieve form card footer css classes ... + */ + getFormCardFooterCssClass() { + return this.getFormName().pipe( + map((formName) => { + return ['x-form-footer', `x-form-${formName}-footer`]; + }) + ); + } + + /** + * retrieve form card actions css classes ... + */ + getFormCardActionsCssClass() { + return this.getFormName().pipe( + map((formName) => { + return ['x-form-actions', `x-form-${formName}-actions`]; + }) + ); + } + + /** + * retrieve form field classes list ... + * + * @param control form control config ... + */ + getFormFieldClasses(control: XFormControlConfig) { + return this.getControlInfo(control).pipe( + map((cInfo) => { + // + const fControl = this.getControl(control.propName); + const isFocused = + this.getControlInputElementId(control) === this.currentInputFocus; + + // + return [ + 'x-form-field', + `x-form-${cInfo.type}-field`, + `x-form-field-${control.index.toString()}`, + `x-form-field-${control.propName.toString()}`, + `x-form-field-appearance-${cInfo.appearance + .toString() + .toLowerCase()}`, + // + // Focused ... + ...(isFocused ? ['x-focused'] : []), + // + // Disabled ... + ...(cInfo.disabled || fControl.disabled + ? ['x-form-field-disabled'] + : []), + // + // Valid ... + ...(fControl.valid ? ['x-form-field-valid'] : []), + // + // Invalid ... + ...(fControl.invalid ? ['x-form-field-invalid'] : []), + // + // Pending ... + ...(fControl.pending ? ['x-form-field-pending'] : []), + ]; + }) + ); + } + + /** + * retrieve form control classes list ... + * + * @param control form control config ... + */ + getFormControlClasses(control: XFormControlConfig) { + return this.getControlType(control).pipe( + map((type) => { + return [ + 'x-form-control', + `x-form-${type}-control`, + `x-form-control-${control.index.toString()}`, + `x-form-control-${control.propName.toString()}`, + ]; + }) + ); + } + + /** + * retrieve focusHandler of a form control ... + * + * @param control form control config ... + */ + getControlFocusHandler(control: XFormControlConfig) { + return this.getValue(control).pipe(map((config) => config.focusHandler)); + } + + /** + * retriev a form control label classes ... + */ + getFormControlFieldLabelClasses(control: XFormControlConfig) { + return this.getControlType(control).pipe( + map((type) => { + return [ + 'x-form-field-label', + `x-form-${type}-field-label`, + `x-form-field-label-${control.index.toString()}`, + `x-form-field-label-${control.propName.toString()}`, + ]; + }) + ); + } + + /** + * retrieve form control hint class list + */ + getFormControlHintClasses(control: XFormControlConfig) { + return this.getControlInfo(control).pipe( + map((cInfo) => { + return [ + 'x-form-control-hint', + `x-form-${cInfo.type}-control-hint`, + `x-form-control-${control.index.toString()}-hint`, + `x-form-control-${control.propName.toString()}-hint`, + ]; + }) + ); + } + //#endregion + + // + //#region Form Model ... + /** + * retrieve for model which provide's initial input for each field. + * if the form model is this type is Partial ... + */ + getFormModel() { + return this.getFormConfig().pipe( + concatMap((formConfig) => this.getOptionalValue(formConfig.model)) + ); + } + + /** + * determines the form has intial value provider or not ... + */ + formHasModel() { + return this.getFormModel().pipe( + map((formModel) => !isNullOrUndefined(formModel)) + ); + } + //#endregion + + // + //#region Form Updte On ... + /** + * retrieve form update strategy identifier ... + */ + getFormUpdateOn() { + return this.getFormConfig().pipe( + concatMap((formConfig) => + this.getOptionalValue(formConfig.updateOn).pipe( + map((updateOn) => (updateOn ? updateOn : XFormUpdateOn.CHANGE)) + ) + ) + ); + } + //#endregion + + // + //#region Form Validators ... + /** + * retrieve form Validators object ... + */ + getFormValidators() { + return this.getFormConfig().pipe( + concatMap((formConfig) => this.getOptionalValue(formConfig.validators)) + ); + } + + /** + * determines form has Validators or not ... + */ + formHasValidators() { + return this.getFormValidators().pipe( + map((formValidators) => !isNullOrUndefined(formValidators)) + ); + } + //#endregion + + // + //#region Form Controls ... + /** + * retrieve form Controls Configs ... + */ + getFormControls() { + return this.getFormConfig().pipe( + concatMap((formConfig) => + this.getArrayValue(formConfig.controls).pipe( + map((arrVal) => sort(arrVal, (v1, v2) => v1.index - v2.index)) + ) + ) + ); + } + //#endregion + + // + //#region Form Error Handlers ... + /** + * retrieve form Error Handlers Descriptor object ... + */ + getFormErrorHandlers() { + return this.getFormConfig().pipe( + concatMap((formConfig) => this.getArrayValue(formConfig.errorHandlers)) + ); + } + + /** + * determines form has Error Handlers or not ... + */ + formHasErrorHandlers() { + return this.hasChildValue(this.getFormErrorHandlers()); + } + + /** + * determines for has specific error or not ... + * + * @param handler form error handler ... + */ + formHasError(handler: XFormErrorHandler) { + return this.getValue(handler.errorName).pipe( + map((errorName) => { + return ( + !isNullOrUndefined(this.ERRORS) && + keys(this.ERRORS).includes(errorName) && + !isNullOrUndefined(this.ERRORS[errorName]) + ); + }) + ); + } + + /** + * determine form has specific error by name ... + * + * @param name error name ... + */ + formHasErrorByName(name: string) { + return ( + !isNullOrUndefined(this.ERRORS) && + keys(this.ERRORS).includes(name) && + !isNullOrUndefined(this.ERRORS[name]) + ); + } + + /** + * retrieve specific error from form ... + * + * @param handler form error handler ... + */ + getFormError(handler: XFormErrorHandler) { + return this.getErrorHandlerErrorName(handler).pipe( + concatMap((errorName) => + this.formHasError(handler).pipe( + map((hasError) => { + return { + errorName, + hasError, + }; + }) + ) + ), + map((descriptor) => + descriptor.hasError ? this.ERRORS[descriptor.errorName] : null + ) + ); + } + + /** + * retrieve specific form error by name ... + * + * @param name error name ... + */ + getFormErrorByName(name: string) { + return this.formHasErrorByName(name) ? this.ERRORS[name] : null; + } + //#endregion + //#endregion + + // + //#region Form Control Providers ... + /** + * retrieve specific control config ... + * + * @param name control name + */ + getFormControl(name: XFormControlNameIdentifier) { + return this.getFormControls().pipe( + map((controls) => controls.find((c) => c.propName === name)) + ); + } + + // + getControlInputElementId(control: XFormControlConfig) { + return `x-form-${control.propName.toString()}-input-element`; + } + + // + getControlInputElementIdByName(name: XFormControlNameIdentifier) { + return `x-form-${name.toString()}-input-element`; + } + + // + getControlInputElementClasses(control: XFormControlConfig) { + return this.getControlInputElementCssClasses(control).pipe( + concatMap((inpCss) => + this.getControlType(control).pipe( + map((type) => { + // + return [ + 'x-form-input-element', + `x-form-${type}-input-element`, + `x-form-${control.index.toString()}-input-element`, + `x-form-${control.propName.toString()}-input-element`, + // + ...inpCss.map((i) => i.toString()), + ]; + }) + ) + ) + ); + } + + // + getControlInputElementCssClasses(control: XFormControlConfig) { + return this.getCustomControlConfig(control).pipe( + concatMap((config) => { + return this.getArrayValue( + !isNullOrUndefined(config) && !isNullOrUndefined(config.inputCssClass) + ? config.inputCssClass + : [] + ); + }) + ); + } + + // + getControlDisableInput(control: XFormControlConfig) { + return this.getCustomControlConfig(control).pipe( + concatMap((config) => { + return this.getOptionalValue( + !isNullOrUndefined(config) && !isNullOrUndefined(config.disableInput) + ? config.disableInput + : false + ); + }) + ); + } + + // + isInputControlReadOnly(control: XFormControlConfig) { + return this.getControlType(control).pipe( + concatMap((type) => { + // + if (type === XFormControlType.Text) { + return this.getControlTypeConfig(control).pipe( + concatMap((config) => + config && (config as XFormInputControlConfig) + ? this.getOptionalValue( + (config as XFormInputControlConfig).readonly + ) + : of(false) + ) + ); + } else { + return this.getValue(false); + } + }) + ); + } + + // + getControlDisableIcon(control: XFormControlConfig) { + return this.getCustomControlConfig(control).pipe( + concatMap((config) => { + return this.getOptionalValue( + !isNullOrUndefined(config) && !isNullOrUndefined(config.disableIcon) + ? config.disableIcon + : false + ); + }) + ); + } + + // + //#region Control Type Providers ... + // + //#region Type Descriptor ... + /** + * retrieve a control type descriptor object ... + * + * @param control form control config object ... + */ + getControlTypeDescriptor(control: XFormControlConfig) { + return this.getValue(control.type); + } + //#endregion + + // + //#region Control Type ... + /** + * retrieve a control type ... + * + * @param control form control config object ... + */ + getControlType(control: XFormControlConfig) { + return this.getControlTypeDescriptor(control).pipe( + concatMap((typeDesc) => + this.getValue( + typeDesc && typeDesc.type ? typeDesc.type : XFormControlType.Hidden + ) + ) + ); + } + //#endregion + + // + //#region Control Type Config ... + /** + * retrieve a control type specific config object ... + * + * @param control form control config object ... + */ + getControlTypeConfig(control: XFormControlConfig) { + return this.getControlTypeDescriptor(control).pipe( + concatMap((typeDesc) => this.getOptionalValue(typeDesc.config)) + ); + } + //#endregion + + // + //#region Control Action Icon Configs ... + /** + * determines a custom control has clear icon or not ... + */ + hasControlClearIcon(control: XFormControlConfig): Observable { + return this.getCustomControlConfig(control).pipe( + map((config) => { + return ( + !isNullOrUndefined(config) && !isNullOrUndefined(config.clearIcon) + ); + }) + ); + } + + /** + * retrieve custom control clear icon + * + * @param control form control config object ... + */ + getControlClearIcon( + control: XFormControlConfig + ): Observable { + return this.getCustomControlConfig(control).pipe( + concatMap((config) => { + return !isNullOrUndefined(config) + ? this.getValue(config.clearIcon) + : of(null); + }), + map((iconSelector) => { + return !isNullOrUndefined(iconSelector) + ? iconSelector + : defaultFormControlClearIcon; + }) + ); + } + + /** + * determines a custom control has select icon or not ... + */ + hasControlSelectIcon(control: XFormControlConfig): Observable { + return this.getCustomControlConfig(control).pipe( + map((config) => { + return ( + !isNullOrUndefined(config) && !isNullOrUndefined(config.selectIcon) + ); + }) + ); + } + + /** + * retrieve custom control select icon + * + * @param control form control config object ... + */ + getControlSelectIcon( + control: XFormControlConfig + ): Observable { + return this.getCustomControlConfig(control).pipe( + concatMap((config) => { + return !isNullOrUndefined(config) + ? this.getValue(config.selectIcon) + : of(null); + }), + map((iconSelector) => { + return !isNullOrUndefined(iconSelector) + ? iconSelector + : defaultFormControlSelectIcon; + }) + ); + } + + // + //#region XForm Custom Control IconDescriptor Providers ... + /** + * retrieve Custom Control Action Icon's Name ... + * + * @param role Icon Action Descriptor... + * @param descriptor Selector icon descriptor ... + */ + getControlIconDescriptorName( + role: XControlIconRoleIdentifier, + descriptor: XControlIconDescriptor + ) { + return this.getValue(descriptor.name).pipe( + map((name) => { + return !isNullOrUndefined(name) && !isNullOrEmptyString(name.toString()) + ? name + : role === this.ControlIconRoles.Select + ? XIconNames.touch + : XIconNames.clear; + }) + ); + } + + /** + * retrieve Custom Control Action Icon's color ... + * + * @param role Icon Action Descriptor... + * @param descriptor Selector icon descriptor ... + */ + getControlIconDescriptorColor( + role: XControlIconRoleIdentifier, + descriptor: XControlIconDescriptor + ) { + return this.getValue(descriptor.color).pipe( + map((color) => { + return !isNullOrEmptyString(color) + ? color + : role === this.ControlIconRoles.Select + ? XColorWithBrightness.Success + : XColorWithBrightness.Danger; + }) + ); + } + + /** + * retrieve Custom Control Action Icon's applyStateColor ... + * + * @param descriptor Selector icon descriptor ... + */ + getControlIconDescriptorApplyStateColor(descriptor: XControlIconDescriptor) { + return this.getOptionalValue(descriptor.applyStateColor); + } + + /** + * retrieve Custom Control Action Icon's tooltip ... + * + * @param role Icon Action Descriptor... + * @param descriptor Selector icon descriptor ... + */ + getControlIconDescriptorTooltip( + role: XControlIconRoleIdentifier, + descriptor: XControlIconDescriptor + ) { + return this.getResourceValue(descriptor.tooltip).pipe( + map((tooltip) => { + return !isNullOrEmptyString(tooltip) + ? tooltip + : role === this.ControlIconRoles.Select + ? this.resourceProvider(this.ResourceIDs.select) + : this.resourceProvider(this.ResourceIDs.clear); + }) + ); + } + + /** + * retrieve Custom Control Action Icon's position ... + * + * @param descriptor Selector icon descriptor ... + */ + getControlIconDescriptorPosition(descriptor: XControlIconDescriptor) { + return this.getValue(descriptor.position).pipe( + map((position) => { + return !isNullOrEmptyString(position) + ? position + : XControlIconPosition.Suffix; + }) + ); + } + //#endregion + //#endregion + + // + //#region Control Type Configs + // + //#region Markdown Config ... + /** + * retrieve Markdown Control Config ... + * + * @param control form control config object ... + */ + getMarkdownConfig(control: XFormControlConfig) { + return this.getControlTypeConfig(control).pipe( + map((config) => config as XFormMarkdownControlConfig) + ); + } + + /** + * determines control has Markdown Config or not ... + * + * @param control form control config object ... + */ + hasMarkdownConfig(control: XFormControlConfig) { + return this.getMarkdownConfig(control).pipe( + map((config) => !isNullOrUndefined(config)) + ); + } + + /** + * determines markdown Card is In Page or not ... + * + * @param control form control config object ... + */ + isMarkdownInPage(control: XFormControlConfig) { + return this.getMarkdownConfig(control).pipe( + concatMap((config) => { + return this.getOptionalValue(config.isInPage); + }) + ); + } + + /** + * retrieve markdown has toolbar ... + * + * @param control form control config object ... + */ + getMarkDownHasToolbar(control: XFormControlConfig) { + return this.getMarkdownConfig(control).pipe( + concatMap((config) => { + return this.getOptionalValue(config.hasToolbar); + }) + ); + } + + /** + * retrieve markdown wrap with card ... + * + * @param control form control config object ... + */ + isMarkdownWrapWithCard(control: XFormControlConfig) { + return this.getMarkdownConfig(control).pipe( + concatMap((config) => { + return this.getOptionalValue(config.wrapWithCard); + }) + ); + } + + /** + * retrieve markdown card color ... + * + * @param control form control config object ... + */ + getMarkdownCardColor(control: XFormControlConfig) { + return this.getMarkdownConfig(control).pipe( + concatMap((config) => { + return this.getValue( + config && config.cardColor ? config.cardColor : XColor.Light + ); + }) + ); + } + + /** + * retrieve markdown editorBoxColor value ... + * + * @param control form control config object ... + */ + getMarkdownEditorBoxColor(control: XFormControlConfig) { + return this.getMarkdownConfig(control).pipe( + concatMap((config) => { + return this.getValue( + config && config.editorBoxColor ? config.editorBoxColor : XColor.Light + ); + }) + ); + } + + /** + * retrieve markdown previewBoxColor value ... + * + * @param control form control config object ... + */ + getMarkdownPreviewBoxColor(control: XFormControlConfig) { + return this.getMarkdownConfig(control).pipe( + concatMap((config) => { + return this.getValue( + config && config.previewBoxColor + ? config.previewBoxColor + : XColorWithBrightness.MediumTint + ); + }) + ); + } + + /** + * determines markdown toolbar color is foreground color or not ... + * + * @param control form control config object ... + */ + getMarkdownCardForegroundColor(control: XFormControlConfig) { + return this.getMarkdownConfig(control).pipe( + concatMap((config) => { + return this.getOptionalValue(config.cardForegroundColor); + }) + ); + } + + /** + * retrieve markdown mode ... + * + * @param control form control config object ... + */ + getMarkdownMode(control: XFormControlConfig) { + return this.getMarkdownConfig(control).pipe( + concatMap((config) => this.getValue(config.mode || null)), + map((mode) => (mode ? mode : XMarkdownMode.PREVIEW)) + ); + } + + /** + * retrieve markdown Toolbar Color ... + * + * @param control form control config object ... + */ + getMarkDownToolbarColor(control: XFormControlConfig) { + return this.getMarkdownConfig(control).pipe( + concatMap((config) => { + return this.getValue( + config && config.toolbarColor ? config.toolbarColor : XColor.Light + ); + }) + ); + } + + /** + * retrieve markdown Card Title ... + * + * @param control form control config object ... + */ + getMarkdownCardTitle(control: XFormControlConfig) { + return this.getMarkdownConfig(control).pipe( + concatMap((config) => { + return this.getOptionalValue(config.cardTitle); + }) + ); + } + + /** + * retrieve markdown action provider ... + * + * @param control form control config object ... + */ + getMarkdownActionProvider(control: XFormControlConfig) { + return this.getMarkdownConfig(control).pipe( + map((config) => { + // + if (!config.actionProvider) { + // + if (!this.actionProviders[control.propName]) { + this.actionProviders[control.propName] = + new Subject(); + } + + // + return this.actionProviders[ + control.propName + ] as Subject; + } else { + return config.actionProvider; + } + }) + ); + } + + /** + * determines markdown toolbar color is foreground color or not ... + * + * @param control form control config object ... + */ + getMarkDownToolbarForegroundColor(control: XFormControlConfig) { + return this.getMarkdownConfig(control).pipe( + concatMap((config) => { + return this.getOptionalValue(config.toolbarForegroundColor); + }) + ); + } + + /** + * retrieve markdown cssClass ... + * + * @param control form control config object ... + */ + getMarkdownCssClass(control: XFormControlConfig) { + return this.getMarkdownConfig(control).pipe( + concatMap((config) => { + return this.getArrayValue(config.cssClass); + }) + ); + } + + /** + * retrieve markdown content ... + * + * @param control form control config object ... + */ + getMarkDownContent(control: XFormControlConfig) { + return this.getValue(control).pipe( + map((ctrl) => { + // + if (!this.hasControl(ctrl.propName)) { + return ''; + } + + // + const xFormControl = this.getControl(ctrl.propName); + if (!xFormControl) { + return ''; + } + + // + return (xFormControl.value || '') as string; + }) + ); + } + + /** + * retrieve markdown Card SubTitle ... + * + * @param control form control config object ... + */ + getMarkdownCardSubTitle(control: XFormControlConfig) { + return this.getMarkdownConfig(control).pipe( + concatMap((config) => { + return this.getResourceValue(config.cardSubTitle); + }) + ); + } + + /** + * retrieve markdown toolbar css class ... + * + * @param control form control config object ... + */ + getMarkdownToolbarCssClass(control: XFormControlConfig) { + return this.getMarkdownConfig(control).pipe( + concatMap((config) => { + return this.getArrayValue(config.toolbarCssClass); + }) + ); + } + + /** + * retrieve markdown toolbar config ... + * + * @param control form control config object ... + */ + getMarkdownToolbarConfig(control: XFormControlConfig) { + return this.getMarkdownConfig(control).pipe( + concatMap((config) => { + return this.getOptionalValue(config.toolbarConfig); + }) + ); + } + //#endregion + + // + //#region Select/Radio Config ... + /** + * retrieve select control config ... + * + * @param control form control config object ... + */ + getSelectConfig(control: XFormControlConfig) { + return this.getControlTypeConfig(control).pipe( + map((config) => config as XFormSelectControlConfig) + ); + } + + /** + * determines select control has config or not ... + * + * @param control form control config object ... + */ + hasSelectConfig(control: XFormControlConfig) { + return this.getSelectConfig(control).pipe( + map((config) => !isNullOrUndefined(config)) + ); + } + + /** + * retrieve select control options ... + * + * @param control form control config object ... + */ + getSelectOptions(control: XFormControlConfig) { + return this.getSelectConfig(control).pipe( + concatMap((config) => this.getArrayValue(config.options)) + ); + } + + /** + * determines select control can select multiple or not ... + * + * @param control form control config object ... + */ + getSelectMultiple(control: XFormControlConfig) { + return this.getSelectConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.multiple)) + ); + } + + /** + * retrieve Select Control Panel Calsses ... + */ + getSelectPanelClasses(control: XFormControlConfig) { + return this.getFormControlClasses(control).pipe( + concatMap((classes) => + this.getSelectMultiple(control).pipe( + map((isMultiple) => ({ classes, isMultiple })) + ) + ), + map((selectInfo) => { + return [ + ...selectInfo.classes, + 'x-form-select-control-panel', + ...(selectInfo.isMultiple ? ['multiple'] : []), + ]; + }) + ); + } + + /** + * retrieve select control option css classes ... + * + * @param control form control config object ... + */ + getOptionClassName(option: XFormSelectControlOption) { + return this.getValue(option).pipe( + map((opt) => ['x-select-option', `x-select-${opt.value}-option`]) + ); + } + + // + // Radio ... + /** + * retrieve radio control config ... + * + * @param control form control config object ... + */ + getRadioConfig(control: XFormControlConfig) { + return this.getControlTypeConfig(control).pipe( + map((config) => config as XFormRadioControlConfig) + ); + } + + /** + * determines radio control has config or not ... + * + * @param control form control config object ... + */ + hasRadioConfig(control: XFormControlConfig) { + return this.getRadioConfig(control).pipe( + map((config) => !isNullOrUndefined(config)) + ); + } + + /** + * retrieve radio control options ... + * + * @param control form control config object ... + */ + getRadioOptions(control: XFormControlConfig) { + return this.getRadioConfig(control).pipe( + concatMap((config) => this.getArrayValue(config.options)) + ); + } + + /** + * retrieve a radio control options label position ... + * + * @param control form control config object ... + */ + getRadioLabelPosition(control: XFormControlConfig) { + return this.getControlAppearanceDesc(control).pipe( + concatMap((appearanceConfig) => + this.getOptionalValue( + !isNullOrUndefined(appearanceConfig) + ? appearanceConfig.labelPosition + : XFormControlLabelPosition.After + ).pipe(map((result) => result as 'before' | 'after')) + ) + ); + } + //#endregion + + // + //#region CheckBox Config ... + /** + * retrieve checkbox control config ... + * + * @param control form control config object ... + */ + getCheckboxConfig(control: XFormControlConfig) { + return this.getControlTypeConfig(control).pipe( + map((config) => config as XFormCheckBoxControlConfig) + ); + } + + /** + * determines checkbox control has config or not ... + * + * @param control form control config object ... + */ + hasCheckboxConfig(control: XFormControlConfig) { + return this.getCheckboxConfig(control).pipe( + map((config) => !isNullOrUndefined(config)) + ); + } + + /** + * retrieve a checkbox control label position ... + * + * @param control form control config object ... + */ + getCheckboxLabelPosition(control: XFormControlConfig) { + return this.getControlAppearanceDesc(control).pipe( + concatMap((appearanceConfig) => + this.getOptionalValue( + !isNullOrUndefined(appearanceConfig) + ? appearanceConfig.labelPosition + : XFormControlLabelPosition.After + ).pipe(map((result) => result as 'before' | 'after')) + ) + ); + } + //#endregion + + // + //#region Date Picker Config ... + /** + * retrieve date control config ... + * + * @param control form control config object ... + */ + getDateConfig(control: XFormControlConfig) { + return this.getControlTypeConfig(control).pipe( + map((config) => config as XFormDatePickerControlConfig) + ); + } + + /** + * determines date control has config or not ... + * + * @param control form control config object ... + */ + hasDateConfig(control: XFormControlConfig) { + return this.getDateConfig(control).pipe( + map((config) => !isNullOrUndefined(config)) + ); + } + + /** + * retrieve date control start at ... + * + * @param control form control config object ... + */ + getDateStartAt(control: XFormControlConfig) { + return this.getDateConfig(control).pipe( + concatMap((config) => this.getValue(config.startAt)) + ); + } + + /** + * retrieve date control picker position ... + * + * @param control form control config object ... + */ + getDatePickerPosition(control: XFormControlConfig) { + return this.getDateConfig(control).pipe( + concatMap((config) => this.getValue(config.datePickerPosition)) + ); + } + + /** + * retrieve date control filter ... + * + * @param control form control config object ... + */ + getDateFilter(control: XFormControlConfig) { + return this.getDateConfig(control).pipe( + concatMap((config) => this.getValue(config.dateFilter)) + ); + } + + /** + * retrieve date control input handler ... + * + * @param control form control config object ... + */ + getDateInputHandler(control: XFormControlConfig) { + return this.getDateConfig(control).pipe( + concatMap((config) => this.getValue(config.dateInput)) + ); + } + + /** + * retrieve date control change handler ... + * + * @param control form control config object ... + */ + getDateChangeHandler(control: XFormControlConfig) { + return this.getDateConfig(control).pipe( + concatMap((config) => this.getValue(config.dateChange)) + ); + } + + /** + * determines date control input field disabled or not ... + * + * @param control form control config object ... + */ + isDatePickerInputDisabled(control: XFormControlConfig) { + return this.getDateConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.disableInput)) + ); + } + + /** + * determines date control input field disabled or not ... + * + * @param control form control config object ... + */ + isDatePickerToggleDisabled(control: XFormControlConfig) { + return this.getDateConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.disablePicker)) + ); + } + + /** + * retriece date control classes ... + * + * @param control form control config object ... + */ + getDatePickerClasses(control: XFormControlConfig) { + return this.getControlType(control).pipe( + map((type) => { + return [ + 'x-date-picker', + `x-form-control-${type}-picker`, + `x-form-control-${control.propName.toString()}-date-picker`, + ]; + }) + ); + } + + /** + * retriece date control toggle icon name ... + * + * @param control form control config object ... + */ + getDatePickerIcon(control: XFormControlConfig) { + return this.getDateConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.toggleIcon)), + map((icon) => (icon ? icon : XIconNames.calendar)) + ); + } + + /** + * retriece date control toggle icon color ... + * + * @param control form control config object ... + */ + getDatePickerIconColor(control: XFormControlConfig) { + return this.getDateConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.iconColor)), + map((color) => (color ? color : XColor.Light)) + ); + } + + /** + * retriece date control toggle icon tooltip ... + * + * @param control form control config object ... + */ + getDatePickerIconTooltip(control: XFormControlConfig) { + return this.getDateConfig(control).pipe( + concatMap((config) => this.getResourceValue(config.iconTooltip)) + ); + } + + /** + * retriece date control toggle apply color state ... + * + * @param control form control config object ... + */ + getDatePickerIconApplyStateColor(control: XFormControlConfig) { + return this.getDateConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.applyStateColor)) + ); + } + + /** + * determines date control icon position is suffix or not ... + * + * @param control form control config object ... + */ + isDatePickerHasSuffixIcon(control: XFormControlConfig) { + return this.getDatePickerPosition(control).pipe( + map( + (position) => position === XFormDatePickerControlPickerPosition.Suffix + ) + ); + } + + /** + * determines date control icon position is prefix or not ... + * + * @param control form control config object ... + */ + isDatePickerHasPrefixIcon(control: XFormControlConfig) { + return this.getDatePickerPosition(control).pipe( + map( + (position) => position === XFormDatePickerControlPickerPosition.Prefix + ) + ); + } + //#endregion + + // + //#region Slider Config ... + /** + * retrieve slider control config ... + * + * @param control form control config object ... + */ + getSliderConfig(control: XFormControlConfig) { + return this.getControlTypeConfig(control).pipe( + map((config) => config as XFormSliderControlConfig) + ); + } + + /** + * determines slider control has config or not ... + * + * @param control form control config object ... + */ + hasSliderConfig(control: XFormControlConfig) { + return this.getSliderConfig(control).pipe( + map((config) => !isNullOrUndefined(config)) + ); + } + + /** + * retrieve slider control min value ... + * + * @param control form control config object ... + */ + getSliderMin(control: XFormControlConfig) { + return this.getSliderConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.min)), + map((min) => (min ? min : 0)) + ); + } + + /** + * retrieve slider control max value ... + * + * @param control form control config object ... + */ + getSliderMax(control: XFormControlConfig) { + return this.getSliderConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.max)), + map((max) => (max ? max : 99)) + ); + } + + /** + * retrieve slider control step value ... + * + * @param control form control config object ... + */ + getSliderStep(control: XFormControlConfig) { + return this.getSliderConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.step)), + map((step) => (step ? step : 1)) + ); + } + + /** + * retrieve slider control invert value ... + * + * @param control form control config object ... + */ + getSliderInvert(control: XFormControlConfig) { + return this.getSliderConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.invert)) + ); + } + + /** + * determines slider control is vertical or not ... + * + * @param control form control config object ... + */ + isSliderVertical(control: XFormControlConfig) { + return this.getSliderConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.vertical)) + ); + } + + /** + * determines slider control show thumb label or not ... + * + * @param control form control config object ... + */ + showSliderThumbLabel(control: XFormControlConfig) { + return this.getSliderConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.thumbLabel)) + ); + } + + /** + * retrieve slider formatter function ... + */ + getSliderFormatter(control: XFormControlConfig) { + return this.getSliderConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.formatter)) + ); + } + //#endregion + + // + //#region Toggle Config ... + /** + * retrieve a checkbox control label position ... + * + * @param control form control config object ... + */ + getToggleLabelPosition(control: XFormControlConfig) { + return this.getControlAppearanceDesc(control).pipe( + concatMap((appearanceConfig) => + this.getOptionalValue( + !isNullOrUndefined(appearanceConfig) + ? appearanceConfig.labelPosition + : XFormControlLabelPosition.After + ).pipe(map((result) => result as 'before' | 'after')) + ) + ); + } + //#endregion + + // + //#region Map Config ... + /** + * retrieve map control config ... + * + * @param control form control config object ... + */ + getMapConfig(control: XFormControlConfig) { + return this.getControlTypeConfig(control).pipe( + map((config) => config as XFormMapControlConfig) + ); + } + + /** + * determines map control has config or not ... + * + * @param control form control config object ... + */ + hasMapConfig(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + map((config) => !isNullOrUndefined(config)) + ); + } + + /** + * retrieve map component's selected point value ... + * + * @param control form control config object ... + */ + getMapSelectedPoint(control: XFormControlConfig): Observable { + // + const fControl = this.getControl(control.propName); + if (!fControl || !fControl.value) { + return of(null); + } + + // + const fValue = fControl.value as string; + if (isNullOrEmptyString(fValue)) { + return of(null); + } + + // + const result = fromLongLat(fValue); + this.getMapActionProvider(control).pipe( + tap((actionProvider) => { + // + if (actionProvider) { + // + actionProvider.next({ + action: XMapAction.SetCenter, + payload: result, + }); + } + }) + ); + + // + return of(result); + } + + /** + * retrieve map control zoom value ... + * + * @param control form control config object ... + */ + getMapLatitude(control: XFormControlConfig) { + return this.getMapSelectedPoint(control).pipe( + map((point) => (point && point.latitude ? +point.latitude : 0)) + ); + } + + /** + * retrieve map control zoom value ... + * + * @param control form control config object ... + */ + getMapLongitude(control: XFormControlConfig) { + return this.getMapSelectedPoint(control).pipe( + map((point) => (point && point.longitude ? +point.longitude : 0)) + ); + } + + /** + * retrieve map control zoom value ... + * + * @param control form control config object ... + */ + getMapZoom(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => (config.zoom ? this.getValue(config.zoom) : of(0))) + ); + } + + /** + * retrieve map control markerStyle value ... + * + * @param control form control config object ... + */ + getMapMarkerStyle(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.markerStyle)), + map((markerStyle) => (markerStyle ? markerStyle : defaultMarkerStyle)) + ); + } + + /** + * retrieve map control cssClass value ... + * + * @param control form control config object ... + */ + getMapCssClass(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getArrayValue(config.cssClass)) + ); + } + + /** + * determines map control canSelect or not ... + * + * @param control form control config object ... + */ + mapCanSelect(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.canSelect)) + ); + } + + /** + * determines map control showProgressBar or not ... + * + * @param control form control config object ... + */ + mapShowProgressBar(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.showProgressBar)), + map((canShow) => (isNullOrUndefined(canShow) ? true : canShow)) + ); + } + + /** + * retrieve map control progressBarColor value ... + * + * @param control form control config object ... + */ + getMapProgressBarColor(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.progressBarColor)), + map((color) => (color ? color : XColor.Light)) + ); + } + + /** + * retrieve map control progressBarPosition value ... + * + * @param control form control config object ... + */ + getMapProgressBarPosition(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.progressBarPosition)), + map((position) => + isNullOrEmptyString(position) ? XMapItemPosition.TOP : position + ) + ); + } + + // + //#region Map Search Bar ... + /** + * determines map control showSearchBar or not ... + * + * @param control form control config object ... + */ + mapShowSearchBar(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.showSearchBar)), + map((result) => (isNullOrUndefined(result) ? true : result)) + ); + } + + /** + * retrieve map control searchBarType value ... + * + * @param control form control config object ... + */ + getMapSearchBarType(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.searchBarType)), + map((result) => + isNullOrEmptyString(result) ? XSearchBarType.Search : result + ) + ); + } + + /** + * retrieve map control searchBarValue value ... + * + * @param control form control config object ... + */ + getMapSearchBarValue(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getValue(config.searchBarValue)) + ); + } + + /** + * retrieve map control searchBarDebounce value ... + * + * @param control form control config object ... + */ + getMapSearchBarDebounce(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.searchBarDebounce)), + map((result) => + isNullOrUndefined(result) + ? this.config.defaultSearchDebounceTime + : result + ) + ); + } + + /** + * retrieve map control searchBarSpellcheck value ... + * + * @param control form control config object ... + */ + getMapSearchBarSpellcheck(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getValue(config.searchBarSpellcheck)), + map((result) => (isNullOrUndefined(result) ? false : result)) + ); + } + + /** + * retrieve map control searchPlaceProvider value ... + * + * @param control form control config object ... + */ + getMapSearchPlaceProvider(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getValue(config.searchPlaceProvider)), + map((result) => + isNullOrUndefined(result) ? defaultMapSearchPlaceProvider : result + ) + ); + } + + /** + * retrieve map control searchBarColor value ... + * + * @param control form control config object ... + */ + getMapSearchBarColor(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.searchBarColor)), + map((result) => + isNullOrEmptyString(result) ? XColorWithBrightness.Primary : result + ) + ); + } + + /** + * retrieve map control searchBarForegroundColor value ... + * + * @param control form control config object ... + */ + getMapSearchBarForegroundColor(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.searchBarForegroundColor) + ) + ); + } + + /** + * retrieve map control searchBarClearIcon value ... + * + * @param control form control config object ... + */ + getMapSearchBarClearButtonIcon(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.searchBarClearButtonIcon) + ), + map((result) => (isNullOrUndefined(result) ? XIconNames.clear : result)) + ); + } + + /** + * retrieve map control searchBarTitle value ... + * + * @param control form control config object ... + */ + getMapSearchBarTitle(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getValue(config.searchBarTitle)), + map((result) => + isNullOrUndefined(result) ? XResourceIDs.search : result + ) + ); + } + + /** + * retrieve map control searchBarSearchIcon value ... + * + * @param control form control config object ... + */ + getMapSearchBarSearchButtonIcon(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.searchBarSearchButtonIcon) + ), + map((result) => (isNullOrUndefined(result) ? XIconNames.search : result)) + ); + } + + /** + * retrieve map control searchBarInputColor value ... + * + * @param control form control config object ... + */ + getMapSearchBarSearchInputColor(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.searchBarSearchInputColor) + ), + map((result) => + isNullOrEmptyString(result) ? XColorWithBrightness.Primary : result + ) + ); + } + + /** + * retrieve map control searchBarForegroundInputColor value ... + * + * @param control form control config object ... + */ + getMapSearchBarForegroundSearchInputColor(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.searchBarForegroundSearchInputColor) + ) + ); + } + + /** + * retrieve map control searchResultListColor value ... + * + * @param control form control config object ... + */ + getMapSearchResultListColor(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.searchResultListColor) + ), + map((result) => + isNullOrEmptyString(result) ? XColorWithBrightness.Light : result + ) + ); + } + + /** + * retrieve map control searchBarActionProvider value ... + * + * @param control form control config object ... + */ + getMapSearchBarActionProvider(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + map((config) => { + // + const key = `${control.propName.toString()}_SearchBar`; + if (!config.searchBarActionProvider) { + // + if (!this.actionProviders[key]) { + this.actionProviders[key] = new Subject(); + } + + // + return this.actionProviders[key] as Subject; + } else { + return config.searchBarActionProvider; + } + }) + ); + } + + /** + * retrieve map control searchBarClearIconColor value ... + * + * @param control form control config object ... + */ + getMapSearchBarClearButtonIconColor(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.searchBarClearButtonIconColor) + ), + map((result) => + isNullOrEmptyString(result) ? XColorWithBrightness.Light : result + ) + ); + } + + /** + * retrieve map control searchBarCssClass value ... + * + * @param control form control config object ... + */ + getMapSearchBarCssClass(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getArrayValue(config.searchBarCssClass)) + ); + } + + /** + * retrieve map control searchBarSearchIconColor value ... + * + * @param control form control config object ... + */ + getMapSearchBarSearchButtonIconColor(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.searchBarSearchButtonIconColor) + ), + map((result) => + isNullOrEmptyString(result) ? XColorWithBrightness.Light : result + ) + ); + } + + /** + * retrieve map control searchBarCancelButtonIcon value ... + * + * @param control form control config object ... + */ + getMapSearchBarCancelButtonIcon(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.searchBarCancelButtonIcon) + ), + map((result) => (isNullOrUndefined(result) ? XIconNames.cancel : result)) + ); + } + + /** + * retrieve map control searchBarPosition value ... + * + * @param control form control config object ... + */ + getMapSearchBarPosition(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.searchBarPosition)), + map((result) => + isNullOrEmptyString(result) ? XMapItemPosition.TOP : result + ) + ); + } + + /** + * retrieve map control searchBarKeyboardType value ... + * + * @param control form control config object ... + */ + getMapSearchBarKeyboardType(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.searchBarKeyboardType) + ), + map((result) => + isNullOrEmptyString(result) ? XSearchBarType.Search : result + ) + ); + } + + /** + * retrieve map control searchBarCancelButtonTitle value ... + * + * @param control form control config object ... + */ + getMapSearchBarCancelButtonTitle(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getValue(config.searchBarCancelButtonTitle)) + ); + } + + /** + * retrieve map control searchBarEnterKeyHint value ... + * + * @param control form control config object ... + */ + getMapSearchBarEnterKeyHint(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.searchBarEnterKeyHint) + ), + map((result) => + isNullOrEmptyString(result) ? XSearchBarEnterKeyHint.Search : result + ) + ); + } + + /** + * determines map control searchBarShowCancelButton or not ... + * + * @param control form control config object ... + */ + mapShowSearchBarCancelButton(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.searchBarShowCancelButton) + ) + ); + } + //#endregion + + /** + * determines map control showSelectedLocation or not ... + * + * @param control form control config object ... + */ + mapShowSelectedLocation(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.showSelectedLocation)), + map((result) => (isNullOrUndefined(result) ? false : result)) + ); + } + + /** + * retrieve map control selectedLocationPosition value ... + * + * @param control form control config object ... + */ + getMapSelectedLocationPosition(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.selectedLocationPosition) + ), + map((position) => + isNullOrEmptyString(position) ? XMapItemPosition.TOP : position + ) + ); + } + + /** + * determines map control showGoMarkedPlace or not ... + * + * @param control form control config object ... + */ + mapShowGoMarkedPlace(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.showGoMarkedPlace)), + map((result) => (isNullOrUndefined(result) ? true : result)) + ); + } + + /** + * retrieve map control goMarkedPlacePosition value ... + * + * @param control form control config object ... + */ + getMapGoMarkedPlacePosition(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.goMarkedPlacePosition) + ), + map((position) => + isNullOrEmptyString(position) ? XMapItemPosition.TOP : position + ) + ); + } + + /** + * determines map control showClearMarker or not ... + * + * @param control form control config object ... + */ + mapShowClearMarker(control: XFormControlConfig) { + // + // Clear Marker in Form Components only must shown when user decide to it and the present Type is with dialog + // since the clear value handle clear marker actions in without dialog mode ... + return this.getMapConfig(control).pipe( + concatMap((config) => + forkJoin({ + showClearMarker: this.getOptionalValue(config.showClearMarker), + withDialog: this.isMapPresentWithDialog(control), + }) + ), + map( + (info) => + (isNullOrUndefined(info.showClearMarker) + ? true + : info.showClearMarker) && info.withDialog + ) + ); + } + + /** + * retrieve map control clearMarkerPosition value ... + * + * @param control form control config object ... + */ + getMapClearMarkerPosition(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.clearMarkerPosition)), + map((position) => + isNullOrEmptyString(position) ? XMapItemPosition.TOP : position + ) + ); + } + + /** + * determines map control showLocator or not ... + * + * @param control form control config object ... + */ + mapShowLocator(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.showLocator)), + map((result) => (isNullOrUndefined(result) ? true : result)) + ); + } + + /** + * retrieve map control locatorPosition value ... + * + * @param control form control config object ... + */ + getMapLocatorPosition(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.locatorPosition)), + map((position) => + isNullOrEmptyString(position) ? XMapItemPosition.TOP : position + ) + ); + } + + /** + * determines map control showZoom or not ... + * + * @param control form control config object ... + */ + mapShowZoom(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.showZoom)), + map((result) => (isNullOrUndefined(result) ? true : result)) + ); + } + + /** + * determines map control showRotate or not ... + * + * @param control form control config object ... + */ + mapShowRotate(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.showRotate)), + map((result) => (isNullOrUndefined(result) ? true : result)) + ); + } + + /** + * determines map control showZoomSlider or not ... + * + * @param control form control config object ... + */ + mapShowZoomSlider(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.showZoomSlider)), + map((result) => (isNullOrUndefined(result) ? false : result)) + ); + } + + /** + * determines map control isInPage or not ... + * + * @param control form control config object ... + */ + isMapInPage(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.isInPage)) + ); + } + + /** + * determines map control wrapWithCard or not ... + * + * @param control form control config object ... + */ + isMapWrapWithCard(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.wrapWithCard)) + ); + } + + /** + * retrieve map control cardColor value ... + * + * @param control form control config object ... + */ + getMapCardColor(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.cardColor)), + map((color) => (color ? color : XColor.Light)) + ); + } + + /** + * determines map control cardForegroundColor or not ... + * + * @param control form control config object ... + */ + isMapCardForegroundColor(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.cardForegroundColor)) + ); + } + + /** + * retrieve map control cardTitle value ... + * + * @param control form control config object ... + */ + getMapCardTitle(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getResourceValue(config.cardTitle)) + ); + } + + /** + * retrieve map control cardSubTitle value ... + * + * @param control form control config object ... + */ + getMapCardSubTitle(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getResourceValue(config.cardSubTitle)) + ); + } + + /** + * determines map control showCardFooter or not ... + * + * @param control form control config object ... + */ + mapCardShowFooter(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.cardShowFooter)) + ); + } + + /** + * retrieve map control actionProvider value ... + * + * @param control form control config object ... + */ + getMapActionProvider(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + map((config) => { + // + if (!config.actionProvider) { + // + if (!this.actionProviders[control.propName]) { + this.actionProviders[control.propName] = + new Subject(); + } + + // + return this.actionProviders[ + control.propName + ] as Subject; + } else { + return config.actionProvider; + } + }) + ); + } + + // + //#region Map Present Types ... + /** + * retrieve map control presentType value ... + * + * @param control form control config object ... + */ + getMapPresentType(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.presentType)), + map((presentType) => + !isNullOrEmptyString(presentType) + ? presentType + : XFormMapControlPresentType.WithoutDialog + ) + ); + } + + /** + * determines map control present WithoutDialog or not ... + * + * @param control form control config object ... + */ + isMapPresentWithoutDialog(control: XFormControlConfig) { + return this.getMapPresentType(control).pipe( + map( + (presentType) => + presentType === XFormMapControlPresentType.WithoutDialog + ) + ); + } + + /** + * determines map control present WithDialog or not ... + * + * @param control form control config object ... + */ + isMapPresentWithDialog(control: XFormControlConfig) { + return this.getMapPresentType(control).pipe( + map( + (presentType) => presentType === XFormMapControlPresentType.WithDialog + ) + ); + } + //#endregion + //#endregion + + // + //#region ColorPicker Config ... + /** + * retrieve color picker control config ... + * + * @param control form control config object ... + */ + getColorPickerConfig(control: XFormControlConfig) { + return this.getControlTypeConfig(control).pipe( + map((config) => config as XFormColorPickerControlConfig) + ); + } + + /** + * determines color picker control has config or not ... + * + * @param control form control config object ... + */ + hasColorPickerConfig(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + map((config) => !isNullOrUndefined(config)) + ); + } + + /** + * retrieve color picker control color value ... + * + * @param control form control config object ... + */ + getColorPickerColor(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => this.getValue(config.color)), + map((color) => (isNullOrEmptyString(color) ? XColor.Light : color)) + ); + } + + /** + * retrieve color picker control colorViewBorderColor value ... + * + * @param control form control config object ... + */ + getColorPickerColorViewBorderColor(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => this.getValue(config.colorViewBorderColor)), + map((color) => (isNullOrEmptyString(color) ? XColor.Light : color)) + ); + } + + /** + * retrieve color picker control colorViewBorderStyle value ... + * + * @param control form control config object ... + */ + getColorPickerColorViewBorderStyle(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => this.getValue(config.colorViewBorderStyle)), + map((style) => (isNullOrEmptyString(style) ? XBorderStyle.Solid : style)) + ); + } + + /** + * retrieve color picker control colorViewBorderWidth value ... + * + * @param control form control config object ... + */ + getColorPickerColorViewBorderWidth(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => this.getValue(config.colorViewBorderWidth)) + ); + } + + /** + * retrieve color picker control cssClass value ... + * + * @param control form control config object ... + */ + getColorPickerCssClass(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => this.getArrayValue(config.cssClass)) + ); + } + + /** + * retrieve color picker control cardColor value ... + * + * @param control form control config object ... + */ + getColorPickerCardColor(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.cardColor)), + map((color) => (!isNullOrEmptyString(color) ? color : XColor.Light)) + ); + } + + /** + * retrieve color picker control cardTitle value ... + * + * @param control form control config object ... + */ + getColorPickerCardTitle(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => this.getResourceValue(config.cardTitle)) + ); + } + + /** + * retrieve color picker control cursorColor value ... + * + * @param control form control config object ... + */ + getColorPickerCursorColor(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => this.getValue(config.cursorColor)), + map((color) => (!isNullOrEmptyString(color) ? color : XColor.Dark)) + ); + } + + /** + * retrieve color picker control presetColors value ... + * + * @param control form control config object ... + */ + getColorPickerPresetColors(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => this.getArrayValue(config.presetColors)) + ); + } + + /** + * retrieve color picker control outputFormat value ... + * + * @param control form control config object ... + */ + getColorPickerOutputFormat(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => this.getValue(config.outputFormat)), + map((format) => + !isNullOrEmptyString(format) ? format : XColorPickerOutputFormat.XHEX + ) + ); + } + + /** + * retrieve color picker control cardSubTitle value ... + * + * @param control form control config object ... + */ + getColorPickerCardSubTitle(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => this.getResourceValue(config.cardSubTitle)) + ); + } + + /** + * retrieve color picker control colorTextColor value ... + * + * @param control form control config object ... + */ + getColorPickerColorTextColor(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => this.getValue(config.colorTextColor)), + map((color) => (!isNullOrEmptyString(color) ? color : XColor.Dark)) + ); + } + + /** + * determines color picker control showColorValue or not ... + * + * @param control form control config object ... + */ + colorPickerShowColorValue(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => + config.showColorValue ? this.getValue(config.showColorValue) : of(true) + ) + ); + } + + /** + * determines color picker control showHueChannel or not ... + * + * @param control form control config object ... + */ + colorPickerShowHueChannel(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => + config.showHueChannel ? this.getValue(config.showHueChannel) : of(true) + ) + ); + } + + /** + * determines color picker control foregroundColor or not ... + * + * @param control form control config object ... + */ + isColorPickerForegroundColor(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.foregroundColor)) + ); + } + + /** + * determines color picker control showPresetColors or not ... + * + * @param control form control config object ... + */ + colorPickerShowPresetColors(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => + config.showPresetColors + ? this.getValue(config.showPresetColors) + : of(true) + ) + ); + } + + /** + * determines color picker control showAlphaChannel or not ... + * + * @param control form control config object ... + */ + colorPickerShowAlphaChannel(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => + config.showAlphaChannel + ? this.getValue(config.showAlphaChannel) + : of(true) + ) + ); + } + + /** + * retrieve color picker control presetColorsLable value ... + * + * @param control form control config object ... + */ + getColorPickerPresetColorsLable(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => this.getResourceValue(config.presetColorsLable)), + map((value) => + isNullOrEmptyString(value) ? XResourceIDs.preset_colors : value + ) + ); + } + + /** + * retrieve color picker control copyToClipboardIconLabel value ... + * + * @param control form control config object ... + */ + getColorPickerCopyToClipboardIconLabel(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => + this.getResourceValue(config.copyToClipboardIconLabel) + ), + map((value) => + isNullOrEmptyString(value) ? XResourceIDs.copy_to_clipboard : value + ) + ); + } + + /** + * retrieve color picker control colorInputIconLabel value ... + * + * @param control form control config object ... + */ + getColorPickerColorInputIconLabel(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => this.getResourceValue(config.colorInputIconLabel)), + map((value) => (isNullOrEmptyString(value) ? XResourceIDs.input : value)) + ); + } + + /** + * retrieve color picker control clearColorIconLabel value ... + * + * @param control form control config object ... + */ + getColorPickerClearColorIconLabel(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => this.getResourceValue(config.clearColorIconLabel)), + map((value) => (isNullOrEmptyString(value) ? XResourceIDs.clear : value)) + ); + } + + /** + * determines color picker control showPaletteChannel or not ... + * + * @param control form control config object ... + */ + colorPickerShowPaletteChannel(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => + config.showPaletteChannel + ? this.getValue(config.showPaletteChannel) + : of(true) + ) + ); + } + + /** + * determines color picker control cardForegroundColor or not ... + * + * @param control form control config object ... + */ + isColorPickerCardForegroundColor(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.cardForegroundColor)) + ); + } + + /** + * determines color picker control showCopyToClipboard or not ... + * + * @param control form control config object ... + */ + colorPickerShowCopyToClipboard(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => + config.showCopyToClipboard + ? this.getValue(config.showCopyToClipboard) + : of(false) + ) + ); + } + + /** + * determines color picker control showColorInput or not ... + * + * @param control form control config object ... + */ + colorPickerShowColorInput(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => + config.showCopyToClipboard + ? this.getValue(config.showColorInput) + : of(true) + ) + ); + } + + /** + * retrieve color picker control representColorFormats value ... + * + * @param control form control config object ... + */ + getColorPickerRepresentColorFormats(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => this.getArrayValue(config.representColorFormats)), + map((formats) => + hasChild(formats) ? formats : [XColorPickerOutputFormat.XHEX] + ) + ); + } + + /** + * retrieve color picker control presetColorsLableColor value ... + * + * @param control form control config object ... + */ + getColorPickerPresetColorsLableColor(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => this.getValue(config.presetColorsLableColor)), + map((color) => (!isNullOrEmptyString(color) ? color : XColor.Dark)) + ); + } + + /** + * retrieve color picker control copyToClipboardIconColor value ... + * + * @param control form control config object ... + */ + getColorPickerCopyToClipboardIconColor(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => this.getValue(config.copyToClipboardIconColor)), + map((color) => (!isNullOrEmptyString(color) ? color : XColor.Tertiary)) + ); + } + + /** + * retrieve color picker control copyToClipboardIconColor value ... + * + * @param control form control config object ... + */ + getColorPickerColorInputIconColor(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => this.getValue(config.colorInputIconColor)), + map((color) => (!isNullOrEmptyString(color) ? color : XColor.Secondary)) + ); + } + + /** + * retrieve color picker control clearColorIconColor value ... + * + * @param control form control config object ... + */ + getColorPickerClearColorIconColor(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => this.getValue(config.clearColorIconColor)), + map((color) => (!isNullOrEmptyString(color) ? color : XColor.Danger)) + ); + } + + /** + * determines color picker control showSelectedColorPreview or not ... + * + * @param control form control config object ... + */ + colorPickerShowSelectedColorPreview(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => + config.showSelectedColorPreview + ? this.getValue(config.showSelectedColorPreview) + : of(true) + ) + ); + } + + /** + * determines color picker control isInPage or not ... + * + * @param control form control config object ... + */ + isColorPickerInPage(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.isInPage)) + ); + } + + /** + * determines color picker control wrapWithCard or not ... + * + * @param control form control config object ... + */ + isColorPickerWrapWithCard(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.wrapWithCard)) + ); + } + + /** + * determines color picker control showCardFooter or not ... + * + * @param control form control config object ... + */ + colorPickercardShowFooter(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.showCardFooter)) + ); + } + + /** + * retrieve color picker component's selected value ... + * + * @param control form control config object ... + */ + getColorPickerSelectedColor(control: XFormControlConfig) { + // + const fControl = this.getControl(control.propName); + if (!fControl) { + return of(''); + } + + // + const fValue = fControl.value as string; + if (isNullOrEmptyString(fValue)) { + return of(''); + } + + // + return of(fValue); + } + + /** + * retrieve color picker control actionProvider value ... + * + * @param control form control config object ... + */ + getColorPickerActionProvider(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + map((config) => { + // + if (!config.actionProvider) { + // + if (!this.actionProviders[control.propName]) { + this.actionProviders[control.propName] = + new Subject(); + } + + // + return this.actionProviders[ + control.propName + ] as Subject; + } else { + return config.actionProvider; + } + }) + ); + } + + // + //#region ColorPicker Present Types ... + /** + * retrieve color picker control presentType value ... + * + * @param control form control config object ... + */ + getColorPickerPresentType(control: XFormControlConfig) { + return this.getColorPickerConfig(control).pipe( + concatMap((config) => + !isNullOrUndefined(config) && !isNullOrUndefined(config.presentType) + ? this.getOptionalValue(config.presentType) + : of(null) + ), + map((presentType) => + !isNullOrEmptyString(presentType) + ? presentType + : XFormColorPickerControlPresentType.WithoutDialog + ) + ); + } + + /** + * determines color picker control present WithoutDialog or not ... + * + * @param control form control config object ... + */ + isColorPickerPresentWithoutDialog(control: XFormControlConfig) { + return this.getColorPickerPresentType(control).pipe( + map( + (presentType) => + presentType === XFormColorPickerControlPresentType.WithoutDialog + ) + ); + } + + /** + * determines color picker control present WithDialog or not ... + * + * @param control form control config object ... + */ + isColorPickerPresentWithDialog(control: XFormControlConfig) { + return this.getColorPickerPresentType(control).pipe( + map( + (presentType) => + presentType === XFormColorPickerControlPresentType.WithDialog + ) + ); + } + //#endregion + //#endregion + + // + //#region Avatar Config ... + /** + * retrieve avatar upload control config ... + * + * @param control form control config object ... + */ + getAvatarConfig(control: XFormControlConfig) { + return this.getControlTypeConfig(control).pipe( + map((config) => config as XFormAvatarUploadControlConfig) + ); + } + + /** + * determines avatar upload control has config or not ... + * + * @param control form control config object ... + */ + hasAvatarConfig(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + map((config) => !isNullOrUndefined(config)) + ); + } + + /** + * determines avatar upload control canDrop or not ... + * + * @param control form control config object ... + */ + avatarCanDrop(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.canDrop)) + ); + } + + /** + * determines avatar upload control isInPage or not ... + * + * @param control form control config object ... + */ + isAvatarInPage(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.isInPage)) + ); + } + + /** + * determines avatar upload control wrapWithCard or not ... + * + * @param control form control config object ... + */ + isAvatarWrapWithCard(control: XFormControlConfig) { + return this.getMapConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.wrapWithCard)) + ); + } + + /** + * retrieve avatar upload control color value ... + * + * @param control form control config object ... + */ + getAvatarColor(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.color)), + map((color) => (color ? color : XColor.Light)) + ); + } + + /** + * determines map control showCardFooter or not ... + * + * @param control form control config object ... + */ + avatarCardShowFooter(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.showCardFooter)) + ); + } + + /** + * determines avatar upload control foregroundColor or not ... + * + * @param control form control config object ... + */ + isAvatarForegroundColor(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.foregroundColor)) + ); + } + + /** + * retrieve avatar upload control placeHolderAvatar value ... + * + * @param control form control config object ... + */ + getAvatarPlaceHolderAvatar(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => this.getValue(config.placeHolderAvatar)) + ); + } + + /** + * retrieve avatar upload control minAllowedFileSize value ... + * + * @param control form control config object ... + */ + getAvatarMinAllowedFileSize(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.minAllowedFileSize)), + map((minAllowedFileSize) => + minAllowedFileSize ? minAllowedFileSize : this.config.minAllowedFileSize + ) + ); + } + + /** + * retrieve avatar upload control maxAllowedFileSize value ... + * + * @param control form control config object ... + */ + getAvatarMaxAllowedFileSize(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.maxAllowedFileSize)), + map((maxAllowedFileSize) => + maxAllowedFileSize + ? maxAllowedFileSize + : this.config.maxProfileImageSize + ) + ); + } + + /** + * retrieve avatar upload control cardColor value ... + * + * @param control form control config object ... + */ + getAvatarCardColor(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.cardColor)), + map((color) => (color ? color : XColor.Light)) + ); + } + + /** + * determines avatar upload control cardForegroundColor or not ... + * + * @param control form control config object ... + */ + isAvatarCardForegroundColor(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.cardForegroundColor)) + ); + } + + /** + * determines avatar upload control fileDropAreaIsInPage or not ... + * + * @param control form control config object ... + */ + isAvatarDropAreaInPage(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.fileDropAreaIsInPage)) + ); + } + + /** + * retrieve avatar upload control cardTitle value ... + * + * @param control form control config object ... + */ + getAvatarCardTitle(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => this.getResourceValue(config.cardTitle)) + ); + } + + /** + * retrieve avatar upload control cssClass value ... + * + * @param control form control config object ... + */ + getAvatarCssClass(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => this.getArrayValue(config.cssClass)) + ); + } + + /** + * determines avatar upload control fileDropAreaWrapWithCard or not ... + * + * @param control form control config object ... + */ + isAvatarDropAreaWrapWithCard(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.fileDropAreaWrapWithCard) + ) + ); + } + + /** + * retrieve avatar upload control fileDropAreaIcon value ... + * + * @param control form control config object ... + */ + getAvatarDropAreaIcon(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => this.getValue(config.fileDropAreaIcon)), + map((icon) => (icon ? icon : XIconNames.arrow_down)) + ); + } + + /** + * determines avatar upload control fileDropAreaDragDisabled or not ... + * + * @param control form control config object ... + */ + isAvatarDropAreaDragDisabled(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.fileDropAreaDragDisabled) + ) + ); + } + + /** + * retrieve avatar upload control cardSubTitle value ... + * + * @param control form control config object ... + */ + getAvatarCardSubTitle(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => this.getResourceValue(config.cardSubTitle)) + ); + } + + /** + * retrieve avatar control actionProvider value ... + * + * @param control form control config object ... + */ + getAvatarActionProvider(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + map((config) => { + // + if (!config.actionProvider) { + // + if (!this.actionProviders[control.propName]) { + this.actionProviders[control.propName] = + new Subject(); + } + + // + return this.actionProviders[ + control.propName + ] as Subject; + } else { + return config.actionProvider; + } + }) + ); + } + + /** + * determines map control fileDropAreaShowCardFooter or not ... + * + * @param control form control config object ... + */ + avatarDropAreaCardShowFooter(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.fileDropAreaShowCardFooter) + ) + ); + } + + /** + * retrieve avatar upload control fileDropAreaColor value ... + * + * @param control form control config object ... + */ + getAvatarDropAreaColor(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.fileDropAreaColor)), + map((color) => (color ? color : XColor.Light)) + ); + } + + /** + * determines avatar upload control fileDropAreaForegroundColor or not ... + * + * @param control form control config object ... + */ + isAvatarDropAreaForegroundColor(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.fileDropAreaForegroundColor) + ) + ); + } + + /** + * determines avatar upload control fileDropAreaDisableFileCheck or not ... + * + * @param control form control config object ... + */ + isAvatarDropAreaDisableFileCheck(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.fileDropAreaDisableFileCheck) + ) + ); + } + + /** + * retrieve avatar upload control fileDropAreaCardColor value ... + * + * @param control form control config object ... + */ + getAvatarDropAreaCardColor(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.fileDropAreaCardColor) + ), + map((color) => (color ? color : XColor.Light)) + ); + } + + /** + * determines avatar upload control fileDropAreaCardForegroundColor or not ... + * + * @param control form control config object ... + */ + isAvatarDropAreaCardForegroundColor(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.fileDropAreaCardForegroundColor) + ) + ); + } + + /** + * retrieve avatar upload control emptyFileException value ... + * + * @param control form control config object ... + */ + getAvatarEmptyFileException(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.emptyFileException)), + map((exception) => (exception ? exception : XExceptionIDs.EmptyFile)) + ); + } + + /** + * retrieve avatar upload control fileDropAreaErrorColor value ... + * + * @param control form control config object ... + */ + getAvatarDropAreaErrorColor(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.fileDropAreaErrorColor) + ), + map((color) => (color ? color : XColor.Danger)) + ); + } + + /** + * retrieve avatar upload control fileDropAreaMessage value ... + * + * @param control form control config object ... + */ + getAvatarDropAreaMessage(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => this.getOptionalValue(config.fileDropAreaMessage)), + map((message) => (message ? message : XResourceIDs.drag_drop_here)), + concatMap((resource) => this.getResourceValue(resource)) + ); + } + + /** + * retrieve avatar upload control fileDropAreaAccentColor value ... + * + * @param control form control config object ... + */ + getAvatarDropAreaAccesntColor(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.fileDropAreaAccentColor) + ), + map((color) => (color ? color : XColor.Secondary)) + ); + } + + /** + * retrieve avatar upload control allowedExtensions value ... + * + * @param control form control config object ... + */ + getAvatarAllowedExtensions(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => this.getArrayValue(config.allowedExtensions)), + map((allowedExtensions) => + hasChild(allowedExtensions) + ? allowedExtensions + : this.config.allowedImageExtensions + ) + ); + } + + /** + * retrieve avatar upload control fileDropAreaCardTitle value ... + * + * @param control form control config object ... + */ + getAvatarDropAreaCardTitle(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => this.getResourceValue(config.fileDropAreaCardTitle)) + ); + } + + /** + * retrieve avatar upload control fileDropAreaSuccessColor value ... + * + * @param control form control config object ... + */ + getAvatarDropAreaSuccessColor(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.fileDropAreaSuccessColor) + ), + map((color) => (color ? color : XColor.Success)) + ); + } + + /** + * determines avatar upload control fileDropAreaSupportsMultipleFiles or not ... + * + * @param control form control config object ... + */ + isAvatarDropAreaSupportsMultipleFiles(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.fileDropAreaSupportsMultipleFiles) + ) + ); + } + + /** + * retrieve avatar upload control fileDropAreaCssClass value ... + * + * @param control form control config object ... + */ + getAvatarDropAreaCssClass(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => this.getArrayValue(config.fileDropAreaCssClass)) + ); + } + + /** + * retrieve avatar upload control fileDropAreaCardSubTitle value ... + * + * @param control form control config object ... + */ + getAvatarDropAreaCardSubTitle(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => + this.getResourceValue(config.fileDropAreaCardSubTitle) + ) + ); + } + + /** + * retrieve avatar upload control emptyFileException value ... + * + * @param control form control config object ... + */ + getAvatarNotAllowedFileException(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.notAllowedFileException) + ), + map((exception) => (exception ? exception : XExceptionIDs.InavlidFile)) + ); + } + + /** + * retrieve avatar upload control fileDropAreaContentTemplate value ... + * + * @param control form control config object ... + */ + getAvatarDropAreaContentTemplate(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.fileDropAreaContentTemplate) + ) + ); + } + + /** + * retrieve avatar upload control minAllowedFileSizeException value ... + * + * @param control form control config object ... + */ + getAvatarMinAllowedFileSizeException(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.minAllowedFileSizeException) + ), + map((exception) => + exception ? exception : XExceptionIDs.MinFilesSizeExceeded + ) + ); + } + + /** + * retrieve avatar upload control maxAllowedFileSizeException value ... + * + * @param control form control config object ... + */ + getAvatarMaxAllowedFileSizeException(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => + this.getOptionalValue(config.maxAllowedFileSizeException) + ), + map((exception) => + exception ? exception : XExceptionIDs.MaxFilesSizeExceeded + ) + ); + } + + /** + * retrieve avatar control actions value ... + * + * @param control form control config object ... + */ + getAvatarActions(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => this.getArrayValue(config.actions)), + concatMap((actions) => + hasChild(actions) + ? of(actions) + : this.getArrayValue(defaultAvatarUploadActionButtons) + ) + ); + } + + /** + * retrieve avatar control fileDropAreaActionProvider value ... + * + * @param control form control config object ... + */ + getAvatarDropAreaActionProvider(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + map((config) => { + // + const key = `${control.propName.toString()}_drop_area`; + + // + if (!config.fileDropAreaActionProvider) { + // + if (!this.actionProviders[key]) { + this.actionProviders[key] = new Subject(); + } + + // + return this.actionProviders[key] as Subject; + } else { + return config.fileDropAreaActionProvider; + } + }) + ); + } + + /** + * retrieve avatar upload control allowedExtensions value ... + * + * @param control form control config object ... + */ + getAvatarDropAreaAllowedExtensions(control: XFormControlConfig) { + return this.getAvatarConfig(control).pipe( + concatMap((config) => + this.getArrayValue(config.fileDropAreaAllowedExtensions) + ), + map((allowedExtensions) => + hasChild(allowedExtensions) + ? allowedExtensions + : this.config.allowedImageExtensions + ) + ); + } + //#endregion + + // + //#region Selector Config ... + /** + * retrieve selector control config ... + * + * @param control form control config object ... + */ + getSelectorConfig(control: XFormControlConfig) { + return this.getControlTypeConfig(control).pipe( + map((config) => config as XFormSelectorControlConfig) + ); + } + + /** + * determines selector control has config or not ... + * + * @param control form control config object ... + */ + hasSelectorConfig(control: XFormControlConfig) { + return this.getSelectorConfig(control).pipe( + map((config) => !isNullOrUndefined(config)) + ); + } + //#endregion + //#endregion + //#endregion + + // + //#region Control Update On ... + /** + * retrieve a control UpdateOn strategy ... + * + * @param control form control config object ... + */ + getControlUpdateOn(control: XFormControlConfig) { + return this.getOptionalValue(control.updateOn).pipe( + map((updateOn) => (updateOn ? updateOn : XFormUpdateOn.CHANGE)) + ); + } + //#endregion + + // + //#region Control Validators ... + /** + * retrieve a control validators descriptor object ... + * + * @param control form control config object ... + */ + getControlValidators(control: XFormControlConfig) { + return this.getOptionalValue(control.validators); + } + + /** + * determines a control has Validators or not ... + * + * @param control form control config object ... + */ + controlHasValidators(control: XFormControlConfig) { + return this.getControlValidators(control).pipe( + map((controlValidators) => !isNullOrUndefined(controlValidators)) + ); + } + //#endregion + + // + //#region Control Event Handlers ... + /** + * retrieve a control event handlers descriptor object ... + * + * @param control form control config object ... + */ + getControlEventHandlers(control: XFormControlConfig) { + return this.getOptionalValue(control.eventHandlers); + } + + /** + * determines a control has validators or not ... + * + * @param control form control config object ... + */ + controlHasEventHandlers(control: XFormControlConfig) { + return this.getControlEventHandlers(control).pipe( + map((eventHandlers) => !isNullOrUndefined(eventHandlers)) + ); + } + //#endregion + + // + //#region Control Appearance Descriptor ... + /** + * retrieve a control appearance descriptor object ... + * + * @param control form control config object ... + */ + getControlAppearanceDesc( + control: XFormControlConfig + ): Observable { + return !isNullOrUndefined(control) + ? this.getValue(control.appearance) + : of(null); + } + + // + //#region Appearance Parts ... + // + //#region Control Hidden ... + /** + * determines a control hidden or not ... + * + * @param control form control config object ... + */ + getControlHidden(control: XFormControlConfig) { + return this.getControlAppearanceDesc(control).pipe( + concatMap((appearanceConfig) => + this.getOptionalValue( + !isNullOrUndefined(appearanceConfig) ? appearanceConfig.hidden : true + ) + ) + ); + } + //#endregion + + // + //#region Control Disabled ... + /** + * determines a control is disabled or not ... + * + * @param control form control config object ... + */ + getControlDisabled(control: XFormControlConfig) { + return this.getControlAppearanceDesc(control).pipe( + concatMap((appearanceConfig) => + this.getOptionalValue( + !isNullOrUndefined(appearanceConfig) + ? appearanceConfig.disabled + : true + ) + ) + ); + } + //#endregion + + // + //#region Control Hint ... + /** + * retrieve a control hint ... + * + * @param control form control config object ... + */ + getControlHint(control: XFormControlConfig) { + return this.getControlAppearanceDesc(control).pipe( + concatMap((appearanceConfig) => + this.getResourceValue( + !isNullOrUndefined(appearanceConfig) ? appearanceConfig.hint : '' + ) + ) + ); + } + //#endregion + + // + //#region Control Label ... + /** + * retrieve a control label ... + * + * @param control form control config object ... + */ + getControlLabel(control: XFormControlConfig) { + return this.getControlAppearanceDesc(control).pipe( + concatMap((appearanceConfig) => + this.getResourceValue( + !isNullOrUndefined(appearanceConfig) ? appearanceConfig.label : '' + ) + ) + ); + } + //#endregion + + // + //#region Control Tooltip ... + /** + * retrieve a control tooltip ... + * + * @param control form control config object ... + */ + getControlTooltip(control: XFormControlConfig) { + return this.getControlAppearanceDesc(control).pipe( + concatMap((appearanceConfig) => + this.getResourceValue( + !isNullOrUndefined(appearanceConfig) ? appearanceConfig.tooltip : '' + ) + ) + ); + } + //#endregion + + // + //#region Control PlaceHolder ... + /** + * retrieve a control placeholder ... + * + * @param control form control config object ... + */ + getControlPlaceHolder(control: XFormControlConfig) { + return this.getControlAppearanceDesc(control).pipe( + concatMap((appearanceConfig) => + this.getResourceValue( + !isNullOrUndefined(appearanceConfig) + ? appearanceConfig.placeholder + : '' + ) + ) + ); + } + //#endregion + + // + //#region Control CssClass ... + /** + * retrieve a control cssClaases ... + * + * @param control form control config object ... + */ + getControlCssClass(control: XFormControlConfig) { + return this.getControlAppearanceDesc(control).pipe( + concatMap((appearanceConfig) => + this.getArrayValue( + !isNullOrUndefined(appearanceConfig) ? appearanceConfig.cssClass : [] + ) + ), + concatMap((cssClasses) => + this.getControlType(control).pipe( + map((type) => { + return { + type, + cssClasses, + }; + }) + ) + ), + map((descriptor) => { + return descriptor.cssClasses.concat( + 'x-form-control', + `x-form-control-${control.index}`, + `x-form-control-${descriptor.type}` + ); + }) + ); + } + //#endregion + + // + //#region Control Icons Descriptor ... + /** + * retrieve a control icons descriptor object ... + * + * @param control form control config object ... + */ + getControlIconDesc(control: XFormControlConfig) { + return this.getControlAppearanceDesc(control).pipe( + concatMap((appearanceConfig) => + this.getOptionalValue( + !isNullOrUndefined(appearanceConfig) ? appearanceConfig.icons : null + ) + ) + ); + } + + /** + * determines a control has icons descriptor object or not ... + * + * @param control form control config object ... + */ + controlHasIcons(control: XFormControlConfig) { + return this.getControlIconDesc(control).pipe( + map((iconDesc) => !isNullOrUndefined(iconDesc)) + ); + } + + /** + * determines a control has suffix icon or not ... + * + * @param control form control config object ... + */ + controlHasSuffixIcon(control: XFormControlConfig) { + return this.getControlIconDesc(control).pipe( + concatMap((iconDesc) => + this.getOptionalValue(iconDesc ? iconDesc.suffix : null).pipe( + map((iconSuffix) => !isNullOrUndefined(iconSuffix)) + ) + ) + ); + } + + /** + * retrieve a control suffix icon object ... + * + * @param control form control config object ... + */ + getControlSuffixIcon(control: XFormControlConfig) { + return this.getControlIconDesc(control).pipe( + concatMap((iconDesc) => + this.getOptionalValue(iconDesc ? iconDesc.suffix : null) + ) + ); + } + + /** + * determines a control has prefix icon or not ... + * + * @param control form control config object ... + */ + controlHasPrefixIcon(control: XFormControlConfig) { + return this.getControlIconDesc(control).pipe( + concatMap((iconDesc) => + this.getOptionalValue(iconDesc ? iconDesc.prefix : null).pipe( + map((iconPrefix) => !isNullOrUndefined(iconPrefix)) + ) + ) + ); + } + + /** + * retrieve a control prefix icon ... + * + * @param control form control config object ... + */ + getControlPrefixIcon(control: XFormControlConfig) { + return this.getControlIconDesc(control).pipe( + concatMap((iconDesc) => + this.getOptionalValue(iconDesc ? iconDesc.prefix : null) + ) + ); + } + + /** + * retrieve an icon descriptor's icon name ... + * + * @param icon form control icon descriptor ... + */ + getControlIconName(icon: XFormIconDescriptor) { + return this.getValue(icon.name); + } + + /** + * retrieve an icon descriptor's icon color ... + * + * @param icon form control icon descriptor ... + */ + getControlIconColor(icon: XFormIconDescriptor) { + return this.getOptionalValue(icon.color).pipe( + map((color) => (color ? color : XColor.Light)) + ); + } + + /** + * retrieve an icon descriptor's icon tooltip ... + * + * @param icon form control icon descriptor ... + */ + getControlIconTooltip(icon: XFormIconDescriptor) { + return this.getResourceValue(icon.tooltip); + } + + /** + * retrieve an icon descriptor's apply state color ... + * + * @param icon form control icon descriptor ... + */ + getControlIconApplyStateColor(icon: XFormIconDescriptor) { + return this.getOptionalValue(icon.applyStateColor); + } + //#endregion + + // + //#region Control Formatter Config ... + /** + * retrieve a control formatter config descriptor object ... + * + * @param control form control config object ... + */ + getControlFormatterConfig(control: XFormControlConfig) { + return this.getControlAppearanceDesc(control).pipe( + concatMap((appearanceConfig) => + this.getOptionalValue( + !isNullOrUndefined(appearanceConfig) + ? appearanceConfig.formatter + : null + ) + ) + ); + } + + /** + * determines a control has formatter config descriptor or not ... + * + * @param control form control config object ... + */ + controlHasFormatter(control: XFormControlConfig) { + return this.getControlFormatterConfig(control).pipe( + map((formatterConfig) => !isNullOrUndefined(formatterConfig)) + ); + } + + /** + * retrieve a control formatter locale ... + * + * @param control form control config object ... + */ + getFormatterLocale(control: XFormControlConfig) { + return this.getControlFormatterConfig(control).pipe( + concatMap((formatterConfig) => this.getValue(formatterConfig.locale)) + ); + } + + /** + * retrieve a control formatter seperator ... + * + * @param control form control config object ... + */ + getFormatterSeperator(control: XFormControlConfig) { + return this.getControlFormatterConfig(control).pipe( + concatMap((formatterConfig) => + this.getOptionalValue(formatterConfig.seperator) + ) + ); + } + + /** + * retrieve a control formatter type ... + * + * @param control form control config object ... + */ + getFormatterType(control: XFormControlConfig) { + return this.getControlFormatterConfig(control).pipe( + concatMap((formatterConfig) => this.getValue(formatterConfig.type)) + ); + } + + /** + * retrieve a control formatter currency label ... + * + * @param control form control config object ... + */ + getFormatterCurrencyLabel(control: XFormControlConfig) { + return this.getControlFormatterConfig(control).pipe( + concatMap((formatterConfig) => + isNullOrUndefined(formatterConfig.currencyLable) + ? of('') + : this.getResourceValue(formatterConfig.currencyLable) + ) + ); + } + + /** + * retrieve a control formatter string formatter ... + * + * @param control form control config object ... + */ + getFormatterStringFormatter(control: XFormControlConfig) { + return this.getControlFormatterConfig(control).pipe( + concatMap((formatterConfig) => + this.getArrayValue(formatterConfig.stringFormatter) + ) + ); + } + + /** + * retrieve a control formatter string formatter ... + * + * @param control form control config object ... + */ + getFormatterInputType(control: XFormControlConfig) { + return this.getControlFormatterConfig(control).pipe( + concatMap((formatterConfig) => + isNullOrUndefined(formatterConfig.inputType) + ? of(XStringInputFormatType.None) + : this.getValue(formatterConfig.inputType) + ) + ); + } + //#endregion + + // + //#region Control Progressbar ... + /** + * retrieve a control progress bar config descriptor object ... + * + * @param control form control config object ... + */ + getControlProgressBarConfig(control: XFormControlConfig) { + return this.getControlAppearanceDesc(control).pipe( + concatMap((appearanceConfig) => + this.getOptionalValue( + !isNullOrUndefined(appearanceConfig) + ? appearanceConfig.progressBar + : null + ) + ) + ); + } + + /** + * retrieve a control has progress bar object descriptor or not ... + * + * @param control form control config object ... + */ + controlHasProgressBar(control: XFormControlConfig) { + return this.getControlProgressBarConfig(control).pipe( + map((progressBarConfig) => !isNullOrUndefined(progressBarConfig)) + ); + } + + /** + * retrieve a control progressbar color ... + * + * @param control form control config object ... + */ + getProgressBarColor(control: XFormControlConfig) { + return this.getControlProgressBarConfig(control).pipe( + concatMap((progressBarConfig) => this.getValue(progressBarConfig.color)) + ); + } + //#endregion + + // + //#region Control AutoComplete ... + /** + * retrieve an id number for form control auto complete view ... + * + * @param control form control config object ... + */ + getAutoCompleteId(control: XFormControlConfig) { + return `x-${control.propName.toString()}-auto-complete`; + } + + /** + * retrieve a control autocomplete config object ... + * + * @param control form control config object ... + */ + getControlAutoCompleteConfig(control: XFormControlConfig) { + return this.getControlAppearanceDesc(control).pipe( + concatMap((appearanceConfig) => + this.getOptionalValue( + !isNullOrUndefined(appearanceConfig) + ? appearanceConfig.autoComplete + : null + ) + ) + ); + } + + /** + * retrieve a control has autocomplete or not ... + * + * @param control form control config object ... + */ + controlHasAutoComplete(control: XFormControlConfig) { + return this.getControlAutoCompleteConfig(control).pipe( + map((autoConpleteConfig) => !isNullOrUndefined(autoConpleteConfig)) + ); + } + + /** + * retrieve a control autocomplete opened ... + * + * @param control form control config object ... + */ + getAutoCompleteOpened(control: XFormControlConfig) { + return this.getControlAutoCompleteConfig(control).pipe( + concatMap((autoCompleteConfig) => + this.getOptionalValue(autoCompleteConfig.opened) + ) + ); + } + + /** + * retrieve a control autocomplete item template ... + * + * @param control form control config object ... + */ + getAutoCompleteItemTemplate(control: XFormControlConfig) { + return this.getControlAutoCompleteConfig(control).pipe( + concatMap((autoCompleteConfig) => + this.getValue(autoCompleteConfig.itemTemplate) + ) + ); + } + + /** + * retrieve a control autocomplete color ... + * + * @param control form control config object ... + */ + getAutoCompleteColor(control: XFormControlConfig) { + return this.getControlAutoCompleteConfig(control).pipe( + concatMap((autoCompleteConfig) => + this.getOptionalValue(autoCompleteConfig.color).pipe( + map((color) => (color ? color : XColor.Light)) + ) + ) + ); + } + + /** + * retrieve a control autocomplete onSelect ... + * + * @param control form control config object ... + */ + getAutoCompleteOnSelect(control: XFormControlConfig) { + return this.getControlAutoCompleteConfig(control).pipe( + concatMap((autoCompleteConfig) => + this.getValue(autoCompleteConfig.onSelect) + ) + ); + } + + /** + * retrieve a control autocomplete Items ... + * + * @param control form control config object ... + */ + getAutoCompleteItems(control: XFormControlConfig) { + return this.getControlAutoCompleteConfig(control).pipe( + concatMap((autoCompleteConfig) => { + return !isNullOrUndefined(autoCompleteConfig.items) && + isFunction(autoCompleteConfig.items) + ? this.getArrayValue(autoCompleteConfig.items()) + : this.getValue([]); + }) + ); + } + + /** + * retrieve autocomplete CssClass ... + * + * @param control form control config object ... + */ + getAutoCompleteCssClass(control: XFormControlConfig) { + return this.getControlAutoCompleteConfig(control).pipe( + concatMap((autoCompleteConfig) => + this.getArrayValue(autoCompleteConfig.cssClass).pipe( + map((cssClasses) => + cssClasses.concat( + 'x-auto-complete', + `x-${control.propName.toString()}-auto-complete` + ) + ) + ) + ) + ); + } + + /** + * retrieve control errors classes ... + * + * @param control form control config ... + */ + getControlErrorsClasses(control: XFormControlConfig) { + return this.getControlType(control).pipe( + map((type) => { + return [ + 'x-form-control-errors', + `x-form-${type}-control-errors`, + `x-form-control-${control.index.toString()}-errors`, + `x-form-control-${control.propName.toString()}-errors`, + ]; + }) + ); + } + + /** + * retrieve special control's special error's class list ... + * + * @param control form control config ... + * @param handler form error handler instance ... + */ + getControlErrorClasses( + control: XFormControlConfig, + handler: XFormErrorHandler + ) { + return this.getControlType(control).pipe( + concatMap((type) => + this.getErrorHandlerErrorName(handler).pipe( + map((errorName) => { + return { + type, + errorName, + }; + }) + ) + ), + map((descriptor) => { + return [ + 'x-form-field-error', + `x-form-field-${descriptor.type}-error`, + `x-form-field-${descriptor.errorName}-error`, + `x-form-field-${control.propName.toString()}-error`, + `x-form-field-${descriptor.type}-${descriptor.errorName}-error`, + ]; + }) + ); + } + //#endregion + + // + //#region Control Appearance ... + /** + * retrieve a control appearance ... + * + * @param control form control config object ... + */ + getControlAppearance(control: XFormControlConfig) { + return this.getControlAppearanceDesc(control).pipe( + concatMap((appearanceConfig) => + this.getOptionalValue( + !isNullOrUndefined(appearanceConfig) && + !isNullOrUndefined(appearanceConfig.appearance) + ? appearanceConfig.appearance + : XFormControlAppearance.Fill + ).pipe(map((result) => result as MatFormFieldAppearance)) + ) + ); + } + getControlAppearanceObject(control: XFormControlConfig) { + return this.getControlAppearanceDesc(control).pipe( + concatMap((appearanceConfig) => + this.getOptionalValue( + !isNullOrUndefined(appearanceConfig) && + !isNullOrUndefined(appearanceConfig.appearance) + ? appearanceConfig.appearance + : XFormControlAppearance.Fill + ) + ) + ); + } + //#endregion + + // + //#region Control Label Position ... + /** + * retrieve a control label position ... + * + * @param control form control config object ... + */ + getControlLabelPosition(control: XFormControlConfig) { + return this.getControlAppearanceDesc(control).pipe( + concatMap((appearanceConfig) => + this.getOptionalValue( + !isNullOrUndefined(appearanceConfig) + ? appearanceConfig.labelPosition + : XFormControlLabelPosition.Before + ) + ) + ); + } + //#endregion + //#endregion + //#endregion + + // + //#region Control Error Handlers ... + /** + * retrieve a control error handlers descriptor object ... + * + * @param control form control config object ... + */ + getControlErrorHandlers(control: XFormControlConfig) { + return this.getArrayValue(control.errorHandlers); + } + + /** + * determines a control has error handlers descriptors object or not ... + * + * @param control form control config object ... + */ + controlHasErrorHandlers(control: XFormControlConfig) { + return this.hasChildValue(this.getControlErrorHandlers(control)); + } + + controlHasErrors(control: XFormControlConfig) { + return this.getValue(control).pipe( + concatMap((ctrl) => + this.getValue(this.getControl(ctrl.propName)).pipe( + map((fControl) => ({ ctrl, fControl })) + ) + ), + map((ctrlInfo) => { + return isNullOrUndefined(ctrlInfo.ctrl) || + isNullOrUndefined(ctrlInfo.fControl) + ? false + : !isNullOrUndefined(ctrlInfo.fControl.errors); + }) + ); + } + + /** + * determines a control has specific error or not ... + * + * @param controlName control name ... + * @param errorName error name ... + */ + controlHasErrorByName( + controlName: XFormControlNameIdentifier, + errorName: string + ) { + // + const control = this.getControl(controlName); + if (!control) { + return false; + } + + // + const result = + !isNullOrUndefined(control.errors) && + keys(control.errors).includes(errorName) && + !isNullOrUndefined(control.errors[errorName]); + + // + return result; + } + + /** + * determine a control has specific error or not ... + * + * @param control form control config ... + * @param handler form error handler instance ... + */ + controlHasError(control: XFormControlConfig, handler: XFormErrorHandler) { + return this.getErrorHandlerErrorName(handler).pipe( + map((errorName) => { + return this.controlHasErrorByName(control.propName, errorName); + }) + ); + } + + controlGetError(control: XFormControlConfig, handler: XFormErrorHandler) { + return this.getErrorHandlerErrorName(handler).pipe( + map((errorName) => { + return this.controlHasErrorByName(control.propName, errorName); + }) + ); + } + + /** + * retrieve specific error of a control ... + * + * @param controlName control name ... + * @param errorName error name ... + */ + getControlErrorByName( + controlName: XFormControlNameIdentifier, + errorName: string + ) { + // + const control = this.getControl(controlName); + if (!this.controlHasErrorByName(controlName, errorName)) { + return false; + } + + // + return control.errors[errorName] || null; + } + + /** + * retrieve specific error of a control ... + * + * @param control form control config ... + * @param handler form error handler instance ... + */ + getControlError(control: XFormControlConfig, handler: XFormErrorHandler) { + return this.getErrorHandlerErrorName(handler).pipe( + map((errorName) => { + if (!isNullOrUndefined(control)) { + return this.getControlErrorByName(control.propName, errorName); + } else { + return this.getFormErrorByName(errorName); + } + }) + ); + } + //#endregion + //#endregion + + // + //#region Error Handler Providers ... + /** + * retrieve an error handler error name ... + * + * @param errorHandler an error handler descriptor object ... + */ + getErrorHandlerErrorName(errorHandler: XFormErrorHandler) { + return this.getValue(errorHandler.errorName); + } + + /** + * retrieve an error handler error message ... + * + * @param errorHandler an error handler descriptor object ... + */ + getErrorHandlerErrorMessage( + errorHandler: XFormErrorHandler, + control?: XFormControlConfig + ) { + // + if ( + errorHandler.errorDataProvider && + isFunction(errorHandler.errorDataProvider) + ) { + // + return this.getControlError(control, errorHandler).pipe( + concatMap((controlError) => + this.getResourceValue(errorHandler.errorDataProvider(controlError)) + ) + ); + } else { + return this.getResourceValue(errorHandler.errorMessage); + } + } + //#endregion + //#endregion + + // + //#region UI Handlers ... + // + //#region Common Controls UI Handlers ... + async handleControlBlured(name: XFormControlNameIdentifier) { + // + if (!this.FORM_CONFIG || !this.hasControl(name)) { + return; + } + + // + const control = await this.getValueAsync(this.getFormControl(name)); + if (!control) { + return; + } + + // + const eventModel: XBaseFormControlActionModel = { + name: control.propName, + }; + + // + this.controlBlured.emit(eventModel); + + // + const eventHandlers = await this.getValueAsync( + this.getControlEventHandlers(control) + ); + if (!eventHandlers || !eventHandlers.onBlured) { + return; + } + + // + await this.getValueAsync(eventHandlers.onBlured(eventModel)); + } + + async handleControlFocused(name: XFormControlNameIdentifier) { + // + if (!this.FORM_CONFIG || !this.hasControl(name)) { + return; + } + + // + const control = await this.getValueAsync(this.getFormControl(name)); + if (!control) { + return; + } + + // + const inputId = await this.getValueAsync( + this.getControlInputElementId(control) + ); + this.currentInputFocus = inputId; + + // + const eventModel: XBaseFormControlActionModel = { + name: control.propName, + }; + + // + this.controlFocused.emit(eventModel); + + // + const eventHandlers = await this.getValueAsync( + this.getControlEventHandlers(control) + ); + if (!eventHandlers || !eventHandlers.onFocused) { + return; + } + + // + await this.getValueAsync(eventHandlers.onFocused(eventModel)); + } + + async handleControlValueChanged(event: XFormControlValueChangeEventModel) { + // + if ( + !this.FORM || + this.uiDisabled || + !this.FORM_CONFIG || + !this.hasControl(event.name) + ) { + return; + } + + // + const control = await this.getValueAsync(this.getFormControl(event.name)); + if (control) { + // + const isDisabled = await this.getValueAsync( + this.getControlDisabled(control) + ); + if (isDisabled) { + return; + } + } + + // + this.controlValueChanged.emit(event); + } + + async handleControlStatusChanged(event: XFormControlStatusChangeEventModel) { + // + if ( + !this.FORM || + this.uiDisabled || + !this.FORM_CONFIG || + !this.hasControl(event.name) + ) { + return; + } + + // + const control = await this.getValueAsync(this.getFormControl(event.name)); + if (control) { + // + const isDisabled = await this.getValueAsync( + this.getControlDisabled(control) + ); + if (isDisabled) { + return; + } + } + + // + this.controlStatusChanged.emit(event); + } + + async handleControlIconClicked(icon: XFormIconDescriptor) { + // + if (!this.FORM || !this.FORM_CONFIG || !icon.handler) { + return; + } + + // + await this.getValueAsync(icon.handler()); + } + + async handleControlActionIconClicked( + role: XControlIconRoleIdentifier, + descriptor: XControlIconDescriptor, + control: XFormControlConfig + ) { + // + const type = await this.getValueAsync(this.getControlType(control)); + + // + switch (type) { + // + case XFormControlType.Map: + // + await this.handleMapIconClicked(role, descriptor, control); + break; + + // + case XFormControlType.Selector: + await this.handleItemSelectorIconClicked(role, descriptor, control); + break; + + // + case XFormControlType.ColorPicker: + // + await this.handleColorPickerIconClicked(role, descriptor, control); + break; + + // + default: + break; + } + } + //#endregion + + // + //#region DatePicker UI Handlers ... + async handleDatePickerDateInput( + name: XFormControlNameIdentifier, + event: MatDatepickerInputEvent + ) { + // + let result: Date = null; + const isDateInput = isDate(event.value); + const isMomentInput = isMoment(event.value); + if (isMomentInput) { + result = momentToDate(event.value); + } else if (isDateInput) { + result = event.value as Date; + } + + // + await this.setValue(name, result); + + // + if (!this.FORM_CONFIG || !this.hasControl(name)) { + return; + } + + // + const control = await this.getValueAsync(this.getFormControl(name)); + if (!control) { + return; + } + + // + const handler = await this.getValueAsync(this.getDateInputHandler(control)); + if (!handler) { + return; + } + + // + // Fire Hanlder ... + await this.getValueAsync(handler(result)); + } + + async handleDatePickerDateChange( + name: XFormControlNameIdentifier, + event: MatDatepickerInputEvent + ) { + // + let result: Date = null; + const isDateInput = isDate(event.value); + const isMomentInput = isMoment(event.value); + if (isMomentInput) { + result = momentToDate(event.value); + } else if (isDateInput) { + result = event.value as Date; + } + + // + await this.setValue(name, result); + + // + if (!this.FORM_CONFIG || !this.hasControl(name)) { + return; + } + + // + const control = await this.getValueAsync(this.getFormControl(name)); + if (!control) { + return; + } + + // + const handler = await this.getValueAsync( + this.getDateChangeHandler(control) + ); + if (!handler) { + return; + } + + // + // Fire Hanlder ... + await this.getValueAsync(handler(result)); + } + //#endregion + + // + //#region Auto Complete Controls UI Handlers ... + async handleAutoCompleteResultSelected( + control: XFormControlConfig, + autoComplete: XFormControlAutoCompleteConfig, + event: any + ) { + // + if (!this.FORM || !this.FORM_CONFIG || !this.hasControl(control.propName)) { + return; + } + + // + const formCtrl = this.getControl(control.propName); + if (!formCtrl) { + return; + } + + // + formCtrl.setValue(event.data); + const formInputControl = this.getElementById( + this.getControlInputElementId(control) + ); + if (formInputControl) { + (formInputControl as HTMLInputElement).select(); + } + + // + if (autoComplete.onSelect && isFunction(autoComplete.onSelect)) { + await this.getValueAsync(autoComplete.onSelect(event)); + } + + // + this.focusOnControl(control.propName); + + // + this.detectChanges(); + } + + async handleAutoCompleteEscaped(control: XFormControlConfig) { + // + if (!this.FORM || !this.FORM_CONFIG || !this.hasControl(control.propName)) { + return; + } + + // + const autoComplete = await this.getValueAsync( + this.getControlAutoCompleteConfig(control) + ); + if ( + autoComplete && + autoComplete.onEscape && + isFunction(autoComplete.onEscape) + ) { + await this.getValueAsync(autoComplete.onEscape()); + } + + // + this.detectChanges(); + } + + async handleAutoCompleteOutsideClicked(control: XFormControlConfig) { + // + if (!this.FORM || !this.FORM_CONFIG || !this.hasControl(control.propName)) { + return; + } + + // + const autoComplete = await this.getValueAsync( + this.getControlAutoCompleteConfig(control) + ); + if ( + autoComplete && + autoComplete.onOutsideClicked && + isFunction(autoComplete.onOutsideClicked) + ) { + await this.getValueAsync(autoComplete.onOutsideClicked()); + } + + // + this.detectChanges(); + } + //#endregion + + // + //#region Markdown Control UI Handlers ... + async handleMarkDownContentChanged( + name: XFormControlNameIdentifier, + event: string + ) { + // + if (!this.hasControl(name)) { + return; + } + + // + await this.setValue(name, event); + + // + const controlConfig = await this.getValueAsync(this.getFormControl(name)); + if (!controlConfig) { + return; + } + + // + const markDownConfig = await this.getValueAsync( + this.getMarkdownConfig(controlConfig) + ); + if (!markDownConfig || !markDownConfig.contentChanged) { + return; + } + + // + await this.getValueAsync(markDownConfig.contentChanged(event)); + } + //#endregion + + // + //#region Checkbox Control UI Handlers ... + async handleCheckboxCheckedChange( + control: XFormControlConfig, + checked: boolean + ) { + // + if (!this.hasControl(control.propName)) { + return; + } + + // + const config = await this.getValueAsync(this.getCheckboxConfig(control)); + if (!config || !config.checkedChanged) { + return; + } + + // + await this.getValueAsync(config.checkedChanged(checked)); + } + //#endregion + + // + //#region Map UI Handlers ... + async handleMapIsReady(control: XFormControlConfig) { + // + if (!this.hasControl(control.propName)) { + return; + } + + // + const config = await this.getValueAsync(this.getMapConfig(control)); + if (!config || !config.isReady) { + return; + } + + // + config.isReady.emit(); + } + + async handleMapZoomChanged(control: XFormControlConfig, zoom: number) { + } + + async handleMapLocationSelected( + control: XFormControlConfig, + event: XMapLocation + ) { + // + if (!this.hasControl(control.propName)) { + return; + } + + // + const longLat = event ? toLongLat(event) : null; + await this.setValue(control.propName, longLat); + + // + const config = await this.getValueAsync(this.getMapConfig(control)); + if (!config || !config.locationSelected) { + return; + } + + // + config.locationSelected.emit(event); + } + + async handleMapIconClicked( + role: XControlIconRoleIdentifier, + descriptor: XControlIconDescriptor, + control: XFormControlConfig + ) { + // + const presentType = await this.getValueAsync( + this.getMapPresentType(control) + ); + + // + switch (role) { + // + case this.ControlIconRoles.Clear: + await this.managerService.dialogService.presentAreYouSureYesNoDialog( + async () => { + // + await this.setValue(control.propName, undefined); + + // + const mapControlActionPtovider = await this.getValueAsync( + this.getMapActionProvider(control) + ); + if ( + mapControlActionPtovider && + presentType === XFormMapControlPresentType.WithoutDialog + ) { + // + // Clear Marker ... + mapControlActionPtovider.next({ + action: XMapAction.ClearMarker, + }); + } + + // + // Notify Handler ... + if ( + !isNullOrUndefined(descriptor) && + !isNullOrUndefined(descriptor.handler) && + isFunction(descriptor.handler) + ) { + await this.getValueAsync(descriptor.handler()); + } + } + ); + break; + + // + case this.ControlIconRoles.Select: + // + // Notify Handler ... + if ( + !isNullOrUndefined(descriptor) && + !isNullOrUndefined(descriptor.handler) && + isFunction(descriptor.handler) + ) { + await this.getValueAsync(descriptor.handler()); + } + + // + let center: XMapLocation = null; + const longLatControl = this.getControl(control.propName); + if (longLatControl) { + // + const longLat = longLatControl.value as string; + if (!isNullOrEmptyString(longLat)) { + center = fromLongLat(longLat); + } + } + + // + // Prevent go forward if map control present type does't support map dialog ... + if (presentType === XFormMapControlPresentType.WithoutDialog) { + return; + } + + // + // Prepare Selector Dialog Config ... + const controlConfig = await this.getValueAsync( + this.getMapConfig(control) + ); + const dialogConfig: XMapDialogConfig = { + ...controlConfig, + // + latitude: center && center.latitude ? +center.latitude : 0, + longitude: center && center.longitude ? +center.longitude : 0, + + // + showSelectedLocation: true, + }; + + // + // Open Dialog ... + await this.mapDialogService.openDialog(dialogConfig); + + // + // Wait for Handle Dialog Result ... + const s = this.mapDialogService.onResult + .asObservable() + .subscribe(async (result) => { + // + let value: any; + + // + if ( + !isNullOrUndefined(result) && + !isNullOrUndefined(result.payload) && + result.result === XDialogResult.Ok + ) { + value = toLongLat(result.payload); + } + + // + if (!isNullOrUndefined(value)) { + await this.setValue(control.propName, value); + } + + // + s.unsubscribe(); + }); + break; + } + } + + async handleMapControlInputValueChanged( + control: XFormControlConfig, + event: string + ) { + // + if (!this.hasControl(control.propName)) { + return; + } + + // + const config = await this.getValueAsync(this.getMapConfig(control)); + if ( + !config || + !config.inputValueChanged || + !isFunction(config.inputValueChanged) + ) { + return; + } + + // + await this.getValueAsync(config.inputValueChanged(event)); + } + + async handleMapSearchBarQueryChange( + control: XFormControlConfig, + event: string + ) { + // + if (!this.hasControl(control.propName)) { + return; + } + + // + const config = await this.getValueAsync(this.getMapConfig(control)); + if (!config || !config.searchBarQueryChange) { + return; + } + + // + config.searchBarQueryChange.emit(event); + } + //#endregion + + // + //#region ColorPicker UI Handlers ... + async handleColorPickerIconClicked( + role: XControlIconRoleIdentifier, + descriptor: XControlIconDescriptor, + control: XFormControlConfig + ) { + // + switch (role) { + // + case this.ControlIconRoles.Clear: + await this.managerService.dialogService.presentAreYouSureYesNoDialog( + async () => { + // + await this.setValue(control.propName, undefined); + + // + // Notify Handler ... + if ( + !isNullOrUndefined(descriptor) && + !isNullOrUndefined(descriptor.handler) && + isFunction(descriptor.handler) + ) { + await this.getValueAsync(descriptor.handler()); + } + } + ); + break; + + // + case this.ControlIconRoles.Select: + // + // Notify Handler ... + if ( + !isNullOrUndefined(descriptor) && + !isNullOrUndefined(descriptor.handler) && + isFunction(descriptor.handler) + ) { + await this.getValueAsync(descriptor.handler()); + } + + // + const presentType = await this.getValueAsync( + this.getColorPickerPresentType(control) + ); + + // + // Prevent go forward if color picker control present type does't support map dialog ... + if (presentType === XFormColorPickerControlPresentType.WithoutDialog) { + return; + } + + // + let selectedColor: string = null; + const colorPickerControl = this.getControl(control.propName); + if (colorPickerControl) { + // + const colorVal = colorPickerControl.value as string; + if (!isNullOrEmptyString(colorVal)) { + selectedColor = colorVal; + } + } + + // + // Prepare Selector Dialog Config ... + const controlConfig = await this.getValueAsync( + this.getColorPickerConfig(control) + ); + const dialogConfig: XColorPickerDialogConfig = { + ...controlConfig, + + // + selectedColor, + }; + + // + // Open Dialog ... + await this.colorPickerDialogService.openDialog(dialogConfig); + + // + // Wait for Handle Dialog Result ... + const s = this.colorPickerDialogService.onResult + .asObservable() + .subscribe(async (result) => { + // + let value: any; + + // + if ( + !isNullOrUndefined(result) && + !isNullOrUndefined(result.payload) && + result.result === XDialogResult.Ok + ) { + value = result.payload; + } + + // + if (!isNullOrUndefined(value)) { + await this.setValue(control.propName, value); + } + + // + s.unsubscribe(); + }); + break; + } + } + + async handleColorPickerControlInputValueChanged( + control: XFormControlConfig, + event: string + ) { + // + if (!this.hasControl(control.propName)) { + return; + } + + // + const config = await this.getValueAsync(this.getColorPickerConfig(control)); + if ( + !config || + !config.inputValueChanged || + !isFunction(config.inputValueChanged) + ) { + return; + } + + // + await this.getValueAsync(config.inputValueChanged(event)); + } + + async handleColorPickerColorSelected( + control: XFormControlConfig, + event: string + ) { + // + if (!this.hasControl(control.propName)) { + return; + } + + // + await this.setValue(control.propName, event); + + // + const config = await this.getValueAsync(this.getColorPickerConfig(control)); + if (!config || !config.colorSelected) { + return; + } + + // + config.colorSelected.emit(event); + } + //#endregion + + // + //#region Avatar UI Handlers ... + async handleAvatarFileDropAreaFilesChangeEmitter( + control: XFormControlConfig, + event?: XOneOrManyType + ) { + // + if (!event || !this.hasControl(control.propName)) { + return; + } + + // + const config = await this.getValueAsync(this.getAvatarConfig(control)); + if ( + !config || + !config.fileDropAreaFilesChangeEmiter || + !isFunction(config.fileDropAreaFilesChangeEmiter) + ) { + return; + } + + // + await this.getValueAsync(config.fileDropAreaFilesChangeEmiter(event)); + } + + async handleAvatarAvatarChange(control: XFormControlConfig, event?: File) { + // + if (!this.hasControl(control.propName)) { + return; + } + + // + await this.setValue(control.propName, event); + + // + const config = await this.getValueAsync(this.getAvatarConfig(control)); + if (!config || !config.avatarChange || !isFunction(config.avatarChange)) { + return; + } + + // + await this.getValueAsync(config.avatarChange(event)); + } + //#endregion + + // + //#region Selector UI Handlers ... + async handleItemSelectorIconClicked( + role: XControlIconRoleIdentifier, + descriptor: XControlIconDescriptor, + control: XFormControlConfig + ) { + // + switch (role) { + // + case this.ControlIconRoles.Clear: + await this.managerService.dialogService.presentAreYouSureYesNoDialog( + async () => { + // + await this.setValue(control.propName, undefined); + + // + // Notify Handler ... + if ( + !isNullOrUndefined(descriptor) && + !isNullOrUndefined(descriptor.handler) && + isFunction(descriptor.handler) + ) { + await this.getValueAsync(descriptor.handler()); + } + } + ); + break; + + // + case this.ControlIconRoles.Select: + // + // Notify Handler ... + if ( + !isNullOrUndefined(descriptor) && + !isNullOrUndefined(descriptor.handler) && + isFunction(descriptor.handler) + ) { + await this.getValueAsync(descriptor.handler()); + } + + // + // Prepare Selector Dialog Config ... + const controlConfig = await this.getValueAsync( + this.getSelectorConfig(control) + ); + const dialogConfig: XFormSelectorControlConfig = { + ...controlConfig, + }; + + // + // Open Dialog ... + await this.selectorDialogService.openDialog(dialogConfig); + + // + // Wait for Handle Dialog Result ... + const s = this.selectorDialogService.onResult + .asObservable() + .subscribe(async (result) => { + // + let content: any; + + // + if ( + !isNullOrUndefined(result) && + !isNullOrUndefined(result.payload) && + result.result === XDialogResult.Ok + ) { + content = result.payload; + } + + // + if (!isNullOrUndefined(content)) { + // + const value = + !controlConfig || + !controlConfig.formatSelectedItem || + !isFunction(controlConfig.formatSelectedItem) + ? content + : await this.getValueAsync( + controlConfig.formatSelectedItem(content) + ); + + // + await this.setValue(control.propName, value); + } + + // + s.unsubscribe(); + }); + break; + } + } + + async handleSelectorControlInputValueChanged( + control: XFormControlConfig, + event: string + ) { + // + if (!this.hasControl(control.propName)) { + return; + } + + // + const config = await this.getValueAsync(this.getSelectorConfig(control)); + if ( + !config || + !config.inputValueChanged || + !isFunction(config.inputValueChanged) + ) { + return; + } + + // + await this.getValueAsync(config.inputValueChanged(event)); + } + //#endregion + //#endregion + + // + //#region Actions ... + /** + * determine a control exists or not ... + * + * @param name a control name ... + */ + hasControl(name: XFormControlNameIdentifier) { + // + if (!this.FORM || !this.FORM_CONFIG) { + return false; + } + + // + const controlName = this.getControlName(name); + if (!controlName) { + return false; + } + + // + return ( + this.FORM.contains(controlName) || + !isNullOrUndefined(this.FORM.controls[controlName]) + ); + } + + /** + * retrieve a control based on it's name ... + * + * @param name a control name ... + */ + getControl(name: XFormControlNameIdentifier) { + // + if (!this.hasControl(name)) { + return null; + } + + // + const controlName = this.getControlName(name); + if (!controlName) { + return null; + } + + // + return this.FORM.get(controlName) || null; + } + + /** + * retrieve a form control status if exists in form ... + * + * @param control a form control config ... + */ + getControlStatus(control: XFormControlConfig) { + // + const mControl = this.getControl(control.propName); + if (!mControl) { + return XFormStatus.UnKnown; + } + + // + return mControl.status as XFormStatus; + } + + /** + * focus on a control ... + * + * @param name a control name ... + */ + focusOnControl(name: XFormControlNameIdentifier) { + // + if (!this.hasControl(name)) { + return; + } + + // + const inputElement = this.getElementById( + this.getControlInputElementIdByName(name) + ); + if (!inputElement) { + return; + } + + // + setTimeout(() => inputElement.focus(), 100); + } + + /** + * blur from a control ... + * + * @param name a control name ... + */ + blurOfControl(name: XFormControlNameIdentifier) { + // + if (!this.hasControl(name)) { + return; + } + + // + const inputElement = this.getElementById( + this.getControlInputElementIdByName(name) + ); + if (!inputElement) { + return; + } + + // + setTimeout(() => inputElement.blur(), 100); + } + + /** + * determines specific control has value or not ... + * + * @param name a control name ... + */ + controlHasValue(name: XFormControlNameIdentifier) { + return !this.hasControl(name) + ? false + : this.getControl(name).value && + !isNullOrEmptyString(this.getControl(name).value.toString()); + } + + /** + * retrieve an specific control value ... + * + * @param name a control name ... + */ + getControlValue(name: XFormControlNameIdentifier) { + return !this.hasControl(name) ? undefined : this.getControl(name).value; + } + + /** + * determines a control has error by it's name ... + * + * @param name a control name ... + */ + controlHasErrorByControlName(name: XFormControlNameIdentifier) { + return !this.hasControl(name) + ? false + : !isNullOrUndefined(this.getControl(name).errors); + } + + /** + * set a value to an specific control or form ... + * + * @param name a control name ... + * @param value a value of any type ... + */ + async setValue(name?: XFormControlNameIdentifier, value?: any) { + // + if (!name) { + return; + } + + // + if (name) { + // + const control = this.getControl(name); + if (!control) { + return; + } + + // + control.setValue(value); + } else { + this.FORM.setValue(value, { emitEvent: true }); + } + + // + this.detectChanges(); + } + + /** + * trigger markAsDirty on form or specific control ... + * if provide a control name and it's exists ... + * + * @param name a control name ... + */ + async markAsDirty(name?: XFormControlNameIdentifier) { + // + if (!this.FORM || !this.FORM_CONFIG || this.uiDisabled) { + return; + } + + // + const control = this.getControl(name); + if (!control) { + // + this.FORM.markAsDirty(); + this.detectChanges(); + return; + } + + // + control.markAsDirty(); + this.detectChanges(); + } + + /** + * trigger markAsTouched on form or specific control ... + * if provide a control name and it's exists ... + * + * @param name a control name ... + */ + async markAsTouched(name?: XFormControlNameIdentifier) { + // + if (!this.FORM || !this.FORM_CONFIG) { + return; + } + + // + const control = this.getControl(name); + if (!control) { + // + this.FORM.markAsTouched(); + this.detectChanges(); + return; + } + + // + control.markAsTouched(); + this.detectChanges(); + } + + /** + * trigger markAsPending on form or specific control ... + * if provide a control name and it's exists ... + * + * @param name a control name ... + */ + async markAsPending(name?: XFormControlNameIdentifier) { + // + if (!this.FORM || !this.FORM_CONFIG) { + return; + } + + // + const control = this.getControl(name); + if (!control) { + // + this.FORM.markAsPending(); + this.detectChanges(); + return; + } + + // + control.markAsPending(); + this.detectChanges(); + } + + /** + * trigger markAsPristine on form or specific control ... + * if provide a control name and it's exists ... + * + * @param name a control name ... + */ + async markAsPristine(name?: XFormControlNameIdentifier) { + // + if (!this.FORM || !this.FORM_CONFIG) { + return; + } + + // + const control = this.getControl(name); + if (!control) { + // + this.FORM.markAsPristine(); + this.detectChanges(); + return; + } + + // + control.markAsPristine(); + this.detectChanges(); + } + + /** + * trigger markAsUntouched on form or specific control ... + * if provide a control name and it's exists ... + * + * @param name a control name ... + */ + async markAsUnTouched(name?: XFormControlNameIdentifier) { + // + if (!this.FORM || !this.FORM_CONFIG) { + return; + } + + // + const control = this.getControl(name); + if (!control) { + // + this.FORM.markAsUntouched(); + this.detectChanges(); + return; + } + + // + control.markAsUntouched(); + this.detectChanges(); + } + + /** + * trigger updateValueAndValidity on form or specific control ... + * if provide a control name and it's exists ... + * + * @param name a control name ... + */ + async updateValueAndValidity(name?: XFormControlNameIdentifier) { + // + if (!this.FORM || !this.FORM_CONFIG) { + return; + } + + // + const control = this.getControl(name); + if (!control) { + // + this.FORM.updateValueAndValidity({ emitEvent: true }); + this.detectChanges(); + return; + } + + // + control.updateValueAndValidity({ emitEvent: true }); + this.detectChanges(); + } + + /** + * trigger setError on form or specific control ... + * if provide a control name and it's exists ... + * + * @param errors: an instance of ValidationErrors object ... + * @param name a control name ... + */ + async setError(errors: ValidationErrors, name?: XFormControlNameIdentifier) { + // + if (!this.FORM || !this.FORM_CONFIG) { + return; + } + + // + const control = this.getControl(name); + if (!control) { + // + this.FORM.setErrors(errors, { emitEvent: true }); + this.detectChanges(); + return; + } + + // + control.setErrors(errors, { emitEvent: true }); + this.detectChanges(); + } + + /** + * clearErrors on form or specific control ... + * if provide a control name and it's exists ... + * + * @param name a control name ... + */ + async clearError(name?: XFormControlNameIdentifier) { + // + if (!this.FORM || !this.FORM_CONFIG) { + return; + } + + // + const control = this.getControl(name); + if (!control) { + // + this.FORM.setErrors(null, { emitEvent: true }); + this.detectChanges(); + return; + } + + // + control.setErrors(null, { emitEvent: true }); + this.detectChanges(); + } + + /** + * enable form or form control ... + * + * @param name a control name ... + */ + async enable(name?: XFormControlNameIdentifier) { + // + if (!this.FORM || !this.FORM_CONFIG) { + return; + } + + // + const control = this.getControl(name); + if (!control) { + // + this.FORM.enable({ emitEvent: true }); + this.detectChanges(); + return; + } + + // + control.enable({ emitEvent: true }); + this.detectChanges(); + } + + /** + * disable form or form control ... + * + * @param name a control name ... + */ + async disable(name?: XFormControlNameIdentifier) { + // + if (!this.FORM || !this.FORM_CONFIG) { + return; + } + + // + const control = this.getControl(name); + if (!control) { + // + this.FORM.disable({ emitEvent: true }); + this.detectChanges(); + return; + } + + // + control.disable({ emitEvent: true }); + this.detectChanges(); + } + + /** + * remove all data and form elements ... + */ + async clearData() { + // + // this.formConfig = null; + this.FORM_CONFIG = null; + this.FORM = null; + + // + this.detectChanges(); + + // + await this.prepareFormGroup(); + } + + /** + * refresh component ... + */ + async refresh() { + this.detectChanges(); + } + //#endregion + + // + //#region Private ... + // + //#region Prepare Form Group ... + private async prepareFormGroup() { + // + //#region Form Config ... + // + this.currentInputFocus = undefined; + const modelFormConfig = await this.getValueAsync(this.formConfig); + if (!modelFormConfig) { + return; + } + + // + const formConfig = { + name: await this.getValueAsync(this.getFormName()), + model: (await this.getValueAsync(this.formHasModel())) + ? await this.getValueAsync(this.getFormModel()) + : undefined, + updateOn: await this.getValueAsync(this.getFormUpdateOn()), + controls: await Promise.all( + ( + await this.getValueAsync(this.getFormControls()) + ).map(async (c) => { + // + //#region Action Provider ... + let actionProvider = c.actionProvider ? c.actionProvider : undefined; + + // + if (!actionProvider) { + // + if (!this.actionProviders[c.propName]) { + this.actionProviders[c.propName] = new Subject(); + } + + // + actionProvider = this.actionProviders[c.propName]; + } + //#endregion + + // + //#region Control ... + return { + index: c.index, + propName: c.propName, + type: { + type: await this.getValueAsync(this.getControlType(c)), + config: await this.getValueAsync(this.getControlTypeConfig(c)), + }, + updateOn: await this.getValueAsync(this.getControlUpdateOn(c)), + validators: (await this.getValueAsync(this.controlHasValidators(c))) + ? await this.getValueAsync(this.getControlValidators(c)) + : undefined, + actionProvider, + eventHandlers: (await this.getValueAsync( + this.controlHasEventHandlers(c) + )) + ? await this.getValueAsync(this.getControlEventHandlers(c)) + : undefined, + appearance: { + hidden: await this.getValueAsync(this.getControlHidden(c)), + disabled: await this.getValueAsync(this.getControlDisabled(c)), + hint: await this.getValueAsync(this.getControlHint(c)), + label: await this.getValueAsync(this.getControlLabel(c)), + tooltip: await this.getValueAsync(this.getControlTooltip(c)), + placeholder: await this.getValueAsync( + this.getControlPlaceHolder(c) + ), + cssClass: await this.getValueAsync(this.getControlCssClass(c)), + icons: (await this.getValueAsync(this.controlHasIcons(c))) + ? { + suffix: (await this.getValueAsync( + this.controlHasSuffixIcon(c) + )) + ? { + name: await this.getValueAsync( + this.getControlIconName( + await this.getValueAsync( + this.getControlSuffixIcon(c) + ) + ) + ), + color: await this.getValueAsync( + this.getControlIconColor( + await this.getValueAsync( + this.getControlSuffixIcon(c) + ) + ) + ), + applyStateColor: await this.getValueAsync( + this.getControlIconApplyStateColor( + await this.getValueAsync( + this.getControlSuffixIcon(c) + ) + ) + ), + handler: ( + await this.getValueAsync( + this.getControlSuffixIcon(c) + ) + ).handler, + } + : undefined, + prefix: (await this.getValueAsync( + this.controlHasPrefixIcon(c) + )) + ? { + name: await this.getValueAsync( + this.getControlIconName( + await this.getValueAsync( + this.getControlPrefixIcon(c) + ) + ) + ), + color: await this.getValueAsync( + this.getControlIconColor( + await this.getValueAsync( + this.getControlPrefixIcon(c) + ) + ) + ), + applyStateColor: await this.getValueAsync( + this.getControlIconApplyStateColor( + await this.getValueAsync( + this.getControlPrefixIcon(c) + ) + ) + ), + handler: ( + await this.getValueAsync( + this.getControlPrefixIcon(c) + ) + ).handler, + } + : undefined, + } + : undefined, + formatter: (await this.getValueAsync(this.controlHasFormatter(c))) + ? { + locale: await this.getValueAsync( + this.getFormatterLocale(c) + ), + seperator: await this.getValueAsync( + this.getFormatterSeperator(c) + ), + type: await this.getValueAsync(this.getFormatterType(c)), + currencyLable: await this.getValueAsync( + this.getFormatterCurrencyLabel(c) + ), + inputType: await this.getValueAsync( + this.getFormatterInputType(c) + ), + stringFormatter: await this.getValueAsync( + this.getFormatterStringFormatter(c) + ), + } + : undefined, + progressBar: (await this.getValueAsync( + this.controlHasProgressBar(c) + )) + ? { + color: await this.getValueAsync( + this.getProgressBarColor(c) + ), + } + : undefined, + autoComplete: (await this.getValueAsync( + this.controlHasAutoComplete(c) + )) + ? { + itemTemplate: await this.getValueAsync( + this.getAutoCompleteItemTemplate(c) + ), + opened: await this.getValueAsync( + this.getAutoCompleteOpened(c) + ), + color: await this.getValueAsync( + this.getAutoCompleteColor(c) + ), + onSelect: await this.getValueAsync( + this.getAutoCompleteOnSelect(c) + ), + items: await this.getValueAsync( + this.getAutoCompleteItems(c) + ), + cssClass: await this.getValueAsync( + this.getAutoCompleteCssClass(c) + ), + } + : undefined, + appearance: await this.getValueAsync( + this.getControlAppearance(c) + ), + labelPosition: await this.getValueAsync( + this.getControlLabelPosition(c) + ), + }, + errorHandlers: (await this.getValueAsync( + this.controlHasErrorHandlers(c) + )) + ? await Promise.all( + ( + await this.getValueAsync(this.getControlErrorHandlers(c)) + ).map(async (e) => { + return { + errorName: await this.getValueAsync(e.errorName), + errorMessage: await this.getResourceValueAsync( + e.errorMessage + ), + }; + }) + ) + : undefined, + }; + //#endregion + }) + ), + validators: (await this.getValueAsync(this.formHasValidators())) + ? await this.getValueAsync(this.getFormValidators()) + : undefined, + errorHandlers: (await this.getValueAsync(this.formHasErrorHandlers())) + ? await Promise.all( + ( + await this.getValueAsync(this.getFormErrorHandlers()) + ).map(async (e) => { + return { + errorName: await this.getValueAsync(e.errorName), + errorMessage: await this.getResourceValueAsync(e.errorMessage), + }; + }) + ) + : undefined, + }; + + // + if (!this.FORM_CONFIG) { + this.FORM_CONFIG = formConfig; + } + //#endregion + + // + //#region Preparing Form Controls ... + const controls = assign( + {}, + ...formConfig.controls + .map((c) => { + // + const initialValue = { + value: + !isNullOrUndefined(formConfig.model) && + !isNullOrUndefined(formConfig.model[c.propName.toString()]) + ? formConfig.model[c.propName.toString()] + : null, + disabled: + this.uiDisabled || + (c.appearance && c.appearance.disabled + ? c.appearance.disabled + : false), + }; + + // + const formControl = new FormControl(initialValue, { + updateOn: c.updateOn.toString() as 'change' | 'blur' | 'submit', + validators: c.validators ? c.validators.validators : null, + asyncValidators: c.validators ? c.validators.asyncValidators : null, + }); + + // + //#region Apply Event Handlers ... + // + //#region Value Change ... + formControl.valueChanges.subscribe(async (res) => { + // + const eventModel: XFormControlValueChangeEventModel = { + name: c.propName, + value: res, + }; + + // + if ( + c.eventHandlers && + c.eventHandlers.valueChanged && + isFunction(c.eventHandlers.valueChanged) + ) { + c.eventHandlers.valueChanged(eventModel); + } + + // + await this.handleControlValueChanged(eventModel); + + // + //#region Handle Map Control Input Value Change Action ... + const controlConfig = await this.getValueAsync( + this.getFormControl(c.propName) + ); + if (controlConfig) { + // + const type = await this.getValueAsync( + this.getControlType(controlConfig) + ); + + // + if (type === XFormControlType.Map) { + await this.handleMapControlInputValueChanged( + controlConfig, + res + ); + } else if (type === XFormControlType.ColorPicker) { + await this.handleColorPickerControlInputValueChanged( + controlConfig, + res + ); + } else if (type === XFormControlType.Selector) { + await this.handleSelectorControlInputValueChanged( + controlConfig, + res + ); + } + } + //#endregion + + // + this.detectChanges(); + }); + + // + // Notify Initial Value ... + if ( + !isNullOrUndefined(initialValue) && + !isNullOrUndefined(initialValue.value) + ) { + // + const eventModel: XFormControlValueChangeEventModel = { + name: c.propName, + value: initialValue.value, + }; + + // + if ( + c.eventHandlers && + c.eventHandlers.valueChanged && + isFunction(c.eventHandlers.valueChanged) + ) { + c.eventHandlers.valueChanged(eventModel); + } + + // + this.controlValueChanged.emit(eventModel); + } + //#endregion + + // + //#region Status Change ... + formControl.statusChanges.subscribe(async (res) => { + // + const eventModel: XFormControlStatusChangeEventModel = { + name: c.propName, + status: res, + }; + + // + if ( + c.eventHandlers && + c.eventHandlers.statusChanged && + isFunction(c.eventHandlers.statusChanged) + ) { + c.eventHandlers.statusChanged(eventModel); + } + + // + await this.handleControlStatusChanged(eventModel); + + // + this.detectChanges(); + }); + //#endregion + //#endregion + + // + //#region Apply Action Provider ... + if (c.actionProvider) { + // + c.actionProvider.asObservable().subscribe(async (model) => { + // + if ( + !model || + !model.name || + isNullOrEmptyString(model.name.toString()) || + !this.hasControl(model.name) + ) { + return; + } + + // + switch (model.action) { + // + case XFormBaseAction.Enable: + await this.enable(model.name); + break; + + // + case XFormBaseAction.Disable: + await this.disable(model.name); + break; + + // + case XFormBaseAction.SetError: + // + const errors = model.payload as ValidationErrors; + if (model.payload && errors) { + await this.setError(errors, model.name); + } + break; + + // + case XFormBaseAction.ClearError: + await this.clearError(model.name); + break; + + // + case XFormBaseAction.MarkAsDirty: + await this.markAsDirty(model.name); + break; + + // + case XFormBaseAction.MarkAsTouched: + await this.markAsTouched(model.name); + break; + + // + case XFormBaseAction.MarkAsPending: + await this.markAsPending(model.name); + break; + + // + case XFormBaseAction.MarkAsPristine: + await this.markAsPending(model.name); + break; + + // + case XFormBaseAction.MarkAsUnTouched: + await this.markAsTouched(model.name); + break; + + // + case XFormBaseAction.UpdateValueAndValidity: + await this.updateValueAndValidity(model.name); + break; + } + }); + } + //#endregion + + // + return { name: c.propName, control: formControl }; + }) + .map((fc) => { + return { key: fc.name.toString(), value: fc.control }; + }) + ) as { [key: string]: AbstractControl }; + //#endregion + + // + //#region Prepare Form Group ... + if (!this.FORM) { + // + this.FORM = new FormGroup( + controls, + formConfig.validators + ? { + updateOn: formConfig.updateOn.toString() as + | 'change' + | 'blur' + | 'submit', + validators: formConfig.validators.validators || null, + asyncValidators: formConfig.validators.asyncValidators || null, + } + : null + ); + + // + // Form Value Changed ... + this.FORM.valueChanges.subscribe((res) => { + // + if (this.uiDisabled) { + return; + } + + // + this.valueChanged.emit({ + name: formConfig.name, + value: res, + }); + + // + this.VALUE = res; + + // + this.detectChanges(); + }); + + // + // Form Status Changed ... + this.FORM.statusChanges.subscribe((res) => { + // + if (this.uiDisabled) { + return; + } + + // + this.STATUS = res; + + // + if (this.FORM.errors) { + this.ERRORS = this.FORM.errors; + } else { + this.ERRORS = null; + } + + // + this.statusChanged.emit({ + name: formConfig.name, + status: res, + }); + + // + this.detectChanges(); + }); + } else { + // + entries(controls).forEach((ctrlEtry) => { + // + const name = ctrlEtry[0]; + const control = ctrlEtry[1] as AbstractControl; + + // + if (this.FORM.contains(name)) { + this.FORM.setControl(name, control); + } + }); + } + //#endregion + + // + this.FORM.markAllAsTouched(); + this.FORM.updateValueAndValidity(); + + // + this.detectChanges(); + } + //#endregion + + // + //#region Control Config Object ... + // + private getControlName(name?: XFormControlNameIdentifier) { + return name ? name.toString() : null; + } + + // + private getCustomControlConfig(control: XFormControlConfig) { + return this.getControlType(control).pipe( + concatMap((type) => { + switch (type) { + // + case XFormControlType.Map: + return this.getMapConfig(control); + + // + case XFormControlType.Selector: + return this.getSelectorConfig(control); + + // + case XFormControlType.ColorPicker: + return this.getColorPickerConfig(control); + + // + default: + return of(null); + } + }) + ); + } + + // + private getControlInfo(control: XFormControlConfig) { + return this.getControlType(control).pipe( + concatMap((type) => + this.getControlDisabled(control).pipe( + map((disabled) => ({ type, disabled })) + ) + ), + concatMap((typeInfo) => + this.getControlAppearance(control).pipe( + map((appearance) => ({ ...typeInfo, appearance })) + ) + ) + ); + } + //#endregion + + // + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XFormBaseAction.Enable: + await this.enable(model.name); + break; + + // + case XFormBaseAction.Disable: + await this.disable(model.name); + break; + + // + case XFormBaseAction.Refresh: + await this.refresh(); + break; + + // + case XFormBaseAction.SetError: + // + if (model.payload) { + await this.setError(model.payload, model.name); + } + break; + + // + case XFormBaseAction.ClearError: + await this.clearError(model.name); + break; + + // + case XFormBaseAction.MarkAsDirty: + await this.markAsDirty(model.name); + break; + + // + case XFormBaseAction.MarkAsTouched: + await this.markAsTouched(model.name); + break; + + // + case XFormBaseAction.MarkAsPending: + await this.markAsPending(model.name); + break; + + // + case XFormBaseAction.MarkAsPristine: + await this.markAsPending(model.name); + break; + + // + case XFormBaseAction.MarkAsUnTouched: + await this.markAsUnTouched(model.name); + break; + + // + case XFormBaseAction.UpdateValueAndValidity: + await this.updateValueAndValidity(model.name); + break; + + // + case XFormBaseAction.SetValue: + await this.setValue(model.name, model.payload); + break; + + // + case XFormBaseAction.ClearData: + await this.clearData(); + break; + + // + case XFormControlAction.Focuse: + if (model.name) { + this.focusOnControl(model.name); + } + break; + + // + case XFormControlAction.Blur: + if (model.name) { + this.blurOfControl(model.name); + } + break; + } + }); + } + + // + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + + // + private async handleUiDisabledChanged() { + // + if (this.uiDisabled) { + await this.disable(); + } else { + await this.enable(); + } + } + + // + //#region Color Appliers ... + private async applyColors() { + // + await this.applyRegularColor(); + await this.applyAccentColor(); + await this.applyHintColor(); + await this.applyPendingColor(); + await this.applyActiveColor(); + await this.applyValidColor(); + await this.applyInvalidColor(); + await this.applyFillColor(); + await this.applyDisabledColor(); + await this.applyDisabledFillColor(); + } + + private async applyRegularColor() { + // + const color = await this.getValueAsync(this.regularColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XFormCssVar.RegularColor, `var(${bgColor})`); + this.setCssVar( + XFormCssVar.RegularColor, + `var(${bgColor})`, + document.documentElement + ); + + // + this.detectChanges(); + } + + private async applyAccentColor() { + // + let color = await this.getValueAsync(this.accentColor); + if (!color) { + color = XColor.Secondary; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XFormCssVar.AccentColor, `var(${bgColor})`); + this.setCssVar( + XFormCssVar.AccentColor, + `var(${bgColor})`, + document.documentElement + ); + + // + this.detectChanges(); + } + + private async applyHintColor() { + // + let color = await this.getValueAsync(this.hintColor); + if (!color) { + color = XColor.Tertiary; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XFormCssVar.HintColor, `var(${bgColor})`); + this.setCssVar( + XFormCssVar.HintColor, + `var(${bgColor})`, + document.documentElement + ); + + // + this.detectChanges(); + } + + private async applyPendingColor() { + // + let color = await this.getValueAsync(this.pendingColor); + if (!color) { + color = XColor.Warning; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XFormCssVar.PendingColor, `var(${bgColor})`); + this.setCssVar( + XFormCssVar.PendingColor, + `var(${bgColor})`, + document.documentElement + ); + + // + this.detectChanges(); + } + + private async applyActiveColor() { + // + let color = await this.getValueAsync(this.activeColor); + if (!color) { + color = XColor.Tertiary; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XFormCssVar.ActiveColor, `var(${bgColor})`); + this.setCssVar( + XFormCssVar.ActiveColor, + `var(${bgColor})`, + document.documentElement + ); + + // + this.detectChanges(); + } + + private async applyValidColor() { + // + let color = await this.getValueAsync(this.validColor); + if (!color) { + color = XColor.Success; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XFormCssVar.ValidColor, `var(${bgColor})`); + this.setCssVar( + XFormCssVar.ValidColor, + `var(${bgColor})`, + document.documentElement + ); + + // + this.detectChanges(); + } + + private async applyInvalidColor() { + // + let color = await this.getValueAsync(this.invalidColor); + if (!color) { + color = XColor.Danger; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XFormCssVar.InvalidColor, `var(${bgColor})`); + this.setCssVar( + XFormCssVar.InvalidColor, + `var(${bgColor})`, + document.documentElement + ); + + // + this.detectChanges(); + } + + private async applyFillColor() { + // + let color = await this.getValueAsync(this.fillColor); + if (!color) { + color = XColorWithBrightness.SecondaryTint; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XFormCssVar.FillColor, `var(${bgColor})`); + this.setCssVar( + XFormCssVar.FillColor, + `var(${bgColor})`, + document.documentElement + ); + + // + this.detectChanges(); + } + + private async applyDisabledColor() { + // + let color = await this.getValueAsync(this.disabledColor); + if (!color) { + color = XColorWithBrightness.LightTint; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XFormCssVar.DisableColor, `var(${bgColor})`); + this.setCssVar( + XFormCssVar.DisableColor, + `var(${bgColor})`, + document.documentElement + ); + + // + this.detectChanges(); + } + + private async applyDisabledFillColor() { + // + let color = await this.getValueAsync(this.disabledFillColor); + if (!color) { + color = XColorWithBrightness.LightShade; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XFormCssVar.DisabledFillColor, `var(${bgColor})`); + this.setCssVar( + XFormCssVar.DisabledFillColor, + `var(${bgColor})`, + document.documentElement + ); + + // + this.detectChanges(); + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-form\x-form.module.ts + +import { + LocaleFactory, + XPersianDateAdapter, + MatDateFormatFactory, +} from 'x-framework-core'; +import { + DateAdapter, + MAT_DATE_LOCALE, + MAT_DATE_FORMATS, +} from '@angular/material/core'; +import { CommonModule } from '@angular/common'; +import { XMapModule } from '../x-map/x-map.module'; +import { LOCALE_ID, NgModule } from '@angular/core'; +import { XFormComponent } from './x-form.component'; +import { XIconModule } from '../x-icon/x-icon.module'; +import { XCardModule } from '../x-card/x-card.module'; +import { XListModule } from '../x-list/x-list.module'; +import { XColorModule } from '../x-color/x-color.module'; +import { XDialogsModule } from '../dialogs/x-dialog.module'; +import { XSlotterModule } from '../x-slotter/x-slotter.module'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { XMarkdownModule } from '../x-markdown/x-markdown.module'; +import { XDataPagerModule } from '../x-data-pager/x-data-pager.module'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; +import { XFileUploadModule } from '../x-file-upload/x-file-upload.module'; +import { XColorPickerModule } from '../x-color-picker/x-color-picker.module'; +import { XAvatarUploadModule } from '../x-avatar-upload/x-avatar-upload.module'; +import { MAT_MOMENT_DATE_ADAPTER_OPTIONS } from '@angular/material-moment-adapter'; + +@NgModule({ + entryComponents: [], + declarations: [XFormComponent], + imports: [ + XMapModule, + XCardModule, + XListModule, + XIconModule, + FormsModule, + CommonModule, + XColorModule, + XDialogsModule, + XSlotterModule, + XMarkdownModule, + XDataPagerModule, + XFileUploadModule, + XColorPickerModule, + ReactiveFormsModule, + XAvatarUploadModule, + XBaseComponentModule, + ], + exports: [ + XMapModule, + XCardModule, + XListModule, + XIconModule, + FormsModule, + CommonModule, + XColorModule, + XDialogsModule, + XFormComponent, + XSlotterModule, + XMarkdownModule, + XDataPagerModule, + XFileUploadModule, + XColorPickerModule, + ReactiveFormsModule, + XAvatarUploadModule, + XBaseComponentModule, + ], + providers: [ + { + provide: LOCALE_ID, + useFactory: LocaleFactory, + }, + { + provide: MAT_DATE_LOCALE, + useFactory: LocaleFactory, + }, + { + deps: [MAT_DATE_LOCALE], + provide: MAT_DATE_FORMATS, + useFactory: MatDateFormatFactory, + }, + { + provide: DateAdapter, + useClass: XPersianDateAdapter, + deps: [MAT_DATE_LOCALE, MAT_MOMENT_DATE_ADAPTER_OPTIONS], + }, + ], +}) +export class XFormModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-form\x-form.tools.ts + +import { XIconNames } from '../x-icon/x-icon.typings'; +import { XColorWithBrightness } from 'x-framework-core'; +import { XControlIconPosition, XControlIconDescriptor } from './x-form.typings'; + +export const defaultFormControlClearIcon: XControlIconDescriptor = { + name: XIconNames.clear, + applyStateColor: false, + color: XColorWithBrightness.Danger, + position: XControlIconPosition.Suffix, +} + +export const defaultFormControlSelectIcon: XControlIconDescriptor = { + name: XIconNames.touch, + applyStateColor: false, + color: XColorWithBrightness.Success, + position: XControlIconPosition.Suffix, +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-form\x-form.typings.ts + +import { + Input, + Inject, + NgZone, + Output, + Renderer2, + Component, + ElementRef, + Injectable, + TemplateRef, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XLocale, + XBaseDto, + XStandardType, + XOneOrManyType, + XColorIdentifier, + XResourceIdentifier, + XExceptionIdentifier, + XColorWithBrightness, + XBorderStyleIdentifier, +} from 'x-framework-core'; +import { + XManagerService, + XFormInputFormatterIdentifier, + XStringInputFormatTypeIdentifier, +} from 'x-framework-services'; +import { + XMarkdownActionModel, + XMarkdownModeIdentifier, + XMarkdownToolbarConfigProps, +} from '../x-markdown/x-markdown.typings'; +import { + XComponentCardContainer, + IXComponentCardContainer, + XComponentCardContainerComponent, +} from '../x-card/x-card.typings'; +import { concatMap, map } from 'rxjs/operators'; +import { + XAvatarUploadActionModel, + XAvatarUploadActionButton, +} from '../x-avatar-upload/x-avatar-upload.typings'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { forkJoin, Observable, Subject } from 'rxjs'; +import { AbstractControlOptions } from '@angular/forms'; +import { XIconIdentifier } from '../x-icon/x-icon.typings'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { IXMapContainer, XMapActionModel } from '../x-map/x-map.typings'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XMapDialogService } from '../x-map/x-map-dialog/x-map-dialog.service'; +import { XColorPickerActionModel } from '../x-color-picker/x-color-picker.typings'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; +import { XFileDropAreaActionModel } from '../x-file-drop-area/x-file-drop-area.typings'; +import { XSelectorDialogConfig } from '../dialogs/x-selector-dialog/x-selector-dialog.typings'; +import { XSelectorDialogService } from '../dialogs/x-selector-dialog/x-selector-dialog.service'; +import { XColorPickerDialogConfig } from '../x-color-picker/x-color-picker-dialog/x-color-picker-dialog.typings'; +import { XColorPickerDialogService } from '../x-color-picker/x-color-picker-dialog/x-color-picker-dialog.service'; + +// +//#region Css Vars ... +export enum XFormCssVar { + FillColor = 'fill-color', + HintColor = 'hint-color', + ValidColor = 'valid-color', + ActiveColor = 'active-color', + AccentColor = 'accent-color', + RegularColor = 'regular-color', + PendingColor = 'pending-color', + InvalidColor = 'invalid-color', + DisableColor = 'disabled-color', + DisabledFillColor = 'disabled-fill-color', +} +//#endregion + +// +//#region Base Form Control Action Model ... +export type XFormControlNameIdentifier = string | number | symbol; + +export interface XBaseFormControlActionModel { + name?: XFormControlNameIdentifier; +} +//#endregion + +// +//#region Control Auto Complete Config ... +export interface XFormControlAutoCompleteConfig { + opened?: XStandardType; + color?: XStandardType; + onSelect?: (item: T) => XStandardType; + items: () => XStandardType>; + itemTemplate: XStandardType>; + cssClass?: XStandardType>; + onEscape?: () => XStandardType; + onOutsideClicked?: () => XStandardType; +} +//#endregion + +// +//#region XForm Validator Model ... +export type XFormValidatorDescriptor = Partial< + Pick +>; +//#endregion + +// +//#region Error Handler Configs ... +export interface XFormErrorHandler { + errorName: XStandardType; + errorMessage: XStandardType; + errorDataProvider?: (errorObject: any) => XStandardType; +} +//#endregion + +// +//#region Formatter Config ... +export interface XFormControlFormatterConfig { + locale?: XStandardType; + seperator?: XStandardType; + type: XStandardType; + inputType?: XStandardType; + currencyLable?: XStandardType; + stringFormatter?: XStandardType>; +} +//#endregion + +// +//#region Control Progressbar Config ... +export interface XFormControlProgressbarConfig { + color: XStandardType; +} +//#endregion + +// +//#region XForm Control Appearance ... +export enum XFormControlAppearance { + None = 'none', + Legacy = 'legacy', + Standard = 'standard', + Fill = 'fill', + Outline = 'outline', +} + +export type XFormControlAppearanceIdentifier = XFormControlAppearance | string; + +export interface XFormControlAppearanceDescriptor { + hidden?: XStandardType; + disabled?: XStandardType; + hint?: XStandardType; + label: XStandardType; + tooltip?: XStandardType; + placeholder?: XStandardType; + cssClass?: XStandardType>; + icons?: XStandardType; + formatter?: XStandardType; + progressBar?: XStandardType; + autoComplete?: XStandardType; + appearance?: XStandardType; + labelPosition?: XStandardType; +} +//#endregion + +// +//#region Icon Configs ... +export enum XControlIconPosition { + Prefix = 'prefix', + Suffix = 'suffix', +} + +export type XControlIconPositionIdentifier = XControlIconPosition | string; + +export enum XControlIconRole { + Clear = 'clear', + Select = 'select', +} + +export type XControlIconRoleIdentifier = XControlIconRole | string; + +export interface XControlIconDescriptor { + handler?: () => XStandardType; + name?: XStandardType; + color?: XStandardType; + applyStateColor?: XStandardType; + tooltip?: XStandardType; + position?: XStandardType; +} + +export interface XFormIconDescriptor + extends Pick< + Partial, + 'handler' | 'name' | 'color' | 'applyStateColor' | 'tooltip' + > {} + +export interface XFormControlIconDescriptor { + suffix?: XStandardType; + prefix?: XStandardType; +} +//#endregion + +// +//#region Form Control Status and Values Changed ... +export interface XFormControlValueChangeEventModel + extends XBaseFormControlActionModel { + value: any; +} + +export interface XFormControlStatusChangeEventModel + extends XBaseFormControlActionModel { + status: XFormStatusIdentifier; +} + +// tslint:disable-next-line:no-empty-interface +export interface XFormValueChangeEventModel + extends XFormControlValueChangeEventModel {} + +// tslint:disable-next-line:no-empty-interface +export interface XFormStatusChangeEventModel + extends XFormControlStatusChangeEventModel {} + +// +// Form and Form Control Status ... +export enum XFormStatus { + UnKnown = 'UNKNOWN', + Valid = 'VALID', + Invalid = 'INVALID', + Pending = 'PENDING', + Disabled = 'DISABLED', +} + +export type XFormStatusIdentifier = XFormStatus | string; + +export interface XFormControlEventsDescriptor { + statusChanged?: ( + model: XFormControlStatusChangeEventModel + ) => XStandardType; + valueChanged?: ( + model: XFormControlValueChangeEventModel + ) => XStandardType; + onBlured?: (model: XBaseFormControlActionModel) => XStandardType; + onFocused?: (model: XBaseFormControlActionModel) => XStandardType; +} +//#endregion + +// +//#region Form Control Label Position ... +export enum XFormControlLabelPosition { + After = 'after', + Before = 'before', +} + +export type XFormControlLabelPositionIdentifier = + | XFormControlLabelPosition + | string; +//#endregion + +// +//#region Form and Form Control Action Provider ... +export enum XFormBaseAction { + // + MarkAsDirty = 'mark_as_dirty', + MarkAsTouched = 'mark_as_touched', + MarkAsPending = 'mark_as_pending', + MarkAsPristine = 'mark_as_pristine', + MarkAsUnTouched = 'mark_as_untouched', + UpdateValueAndValidity = 'update_value_and_validity', + // + SetError = 'set_error', + ClearError = 'clear_error', + // + SetValue = 'set_value', + // + Enable = 'enable', + Disable = 'disable', + // + Refresh = 'refresh', + // + ClearData = 'clear_data', +} + +export enum XFormControlAction { + Focuse = 'focuse', + Blur = 'blur', +} + +export type XFormControlActionIdentifier = + | XFormBaseAction + | XFormControlAction + | string; + +export interface XFormControlActionProviderModel + extends XBaseActionModel, + XBaseFormControlActionModel {} + +export interface XFormActionModel extends XFormControlActionProviderModel {} +//#endregion + +// +//#region XForm and XForm Control Update On ... +export enum XFormUpdateOn { + BLUR = 'blur', + CHANGE = 'change', + SUBMIT = 'submit', +} + +export type XFormUpdateOnIdentifier = XFormUpdateOn | string; +//#endregion + +// +//#region Form Control Types ... +export enum XFormControlType { + Text = 'text', + Number = 'number', + Password = 'password', + Color = 'color', + Email = 'email', + Tel = 'tel', + Url = 'url', + Select = 'select', + Radio = 'radio', + CheckBox = 'checkbox', + Hidden = 'hidden', + Date = 'date', + Slider = 'slider', + Toggle = 'toggle', + MarkDown = 'markdown', + Map = 'map', + ColorPicker = 'color_picker', + AvatarUpload = 'avatar_upload', + Selector = 'selector', +} + +export type XFormControlTypeIdentifier = XFormControlType | string; + +export type XFormControlTypeConfig = + | XFormMapControlConfig + | XFormRadioControlConfig + | XFormInputControlConfig + | XFormSelectControlConfig + | XFormSliderControlConfig + | XFormSelectorControlConfig + | XFormMarkdownControlConfig + | XFormCheckBoxControlConfig + | XFormDatePickerControlConfig + | XFormColorPickerControlConfig + | XFormAvatarUploadControlConfig + | undefined; +export interface XFormControlTypeDescriptor { + type: XStandardType; + config?: XStandardType; +} + +// +//#region Type Configs ... +// +//#region Input Type ... +export interface XFormInputControlConfig { + readonly?: XStandardType; +} +//#endregion + +// +//#region Select Type ... +export interface XFormSelectControlOption { + value: XStandardType; + viewValue: XStandardType; +} + +export interface XFormSelectControlConfig { + options?: XStandardType>>; + multiple?: XStandardType; +} +//#endregion + +// +//#region Radio Type ... +export interface XFormRadioControlOption { + value: XStandardType; + viewValue: XStandardType; +} + +export interface XFormRadioControlConfig { + options?: XStandardType>>; +} +//#endregion + +// +//#region Date Control Config ... +export enum XFormDatePickerControlPickerPosition { + Suffix = 'suffix', + Prefix = 'prefix', +} + +export type XFormDatePickerControlPickerPositionIdentifier = + | XFormDatePickerControlPickerPosition + | string; + +export interface XFormDatePickerControlConfig { + startAt?: XStandardType; + dateFilter?: (d: any) => boolean; + applyStateColor?: XStandardType; + toggleIcon?: XStandardType; + iconColor?: XStandardType; + datePickerPosition: XStandardType; + disableInput?: XStandardType; + disablePicker?: XStandardType; + iconTooltip?: XStandardType; + dateInput?: (event: Date) => XStandardType; + dateChange?: (event: Date) => XStandardType; +} +//#endregion + +// +//#region Slider Control ... +export interface XFormSliderControlConfig { + min?: XStandardType; + max?: XStandardType; + step?: XStandardType; + invert?: XStandardType; + vertical?: XStandardType; + thumbLabel?: XStandardType; + formatter?: (value: number) => string; +} +//#endregion + +// +//#region Markdown Control Config ... +export interface XFormMarkdownControlConfig { + // + isInPage?: XStandardType; + hasToolbar?: XStandardType; + wrapWithCard?: XStandardType; + cardColor?: XStandardType; + cardForegroundColor?: XStandardType; + mode?: XStandardType; + toolbarColor?: XStandardType; + cardTitle?: XStandardType; + actionProvider?: Subject; + toolbarForegroundColor?: XStandardType; + cssClass?: XStandardType>; + editorBoxColor?: XStandardType; + previewBoxColor?: XStandardType; + cardSubTitle?: XStandardType; + toolbarCssClass?: XStandardType>; + contentChanged?: (content: string) => XStandardType; + toolbarConfig?: XStandardType; +} +//#endregion + +// +//#region Check Box Config ... +export interface XFormCheckBoxControlConfig { + checkedChanged?: (checked: boolean) => XStandardType; +} +//#endregion + +// +//#region Map Config ... +export enum XFormMapControlPresentType { + WithDialog = 'width_dialog', + WithoutDialog = 'without_dialog', +} + +export type XFormMapControlPresentTypeIdentifier = + | XFormMapControlPresentType + | string; + +// +export interface XFormMapControlConfig + extends Pick< + Partial, + | 'zoom' + | 'markerStyle' + | 'cssClass' + | 'canSelect' + // + | 'showProgressBar' + | 'progressBarColor' + | 'progressBarPosition' + // + | 'showSearchBar' + | 'searchBarType' + | 'searchBarValue' + | 'searchBarDebounce' + | 'searchBarQueryChange' + | 'searchBarSpellcheck' + | 'searchPlaceProvider' + | 'searchBarColor' + | 'searchBarForegroundColor' + | 'searchBarClearButtonIcon' + | 'searchBarTitle' + | 'searchBarSearchButtonIcon' + | 'searchBarSearchInputColor' + | 'searchBarForegroundSearchInputColor' + | 'searchResultListColor' + | 'searchBarActionProvider' + | 'searchBarClearButtonIconColor' + | 'searchBarCssClass' + | 'searchBarSearchButtonIconColor' + | 'searchBarCancelButtonIcon' + | 'searchBarPosition' + | 'searchBarKeyboardType' + | 'searchBarCancelButtonTitle' + | 'searchBarEnterKeyHint' + | 'searchBarShowCancelButton' + // + | 'showSelectedLocation' + | 'selectedLocationPosition' + // + | 'showGoMarkedPlace' + | 'goMarkedPlacePosition' + // + | 'showClearMarker' + | 'clearMarkerPosition' + // + | 'showLocator' + | 'locatorPosition' + // + | 'showZoom' + | 'showRotate' + | 'showZoomSlider' + // + | 'isReady' + | 'locationSelected' + // + | 'isInPage' + | 'wrapWithCard' + | 'cardShowFooter' + | 'cardColor' + | 'cardForegroundColor' + | 'cardTitle' + | 'cardSubTitle' + > { + // + disableIcon?: XStandardType; + disableInput?: XStandardType; + actionProvider?: Subject; + clearIcon?: XStandardType; + selectIcon?: XStandardType; + inputCssClass?: XStandardType>; + inputValueChanged?: (value?: string) => XStandardType; + presentType?: XStandardType; +} +//#endregion + +// +//#region ColorPicker Config ... +export enum XFormColorPickerControlPresentType { + WithDialog = 'width_dialog', + WithoutDialog = 'without_dialog', + // Both = 'both', +} + +export type XFormColorPickerControlPresentTypeIdentifier = + | XFormColorPickerControlPresentType + | string; + +// +export interface XFormColorPickerControlConfig + extends Pick< + Partial, + | 'color' + | 'cssClass' + | 'cardColor' + | 'cardTitle' + | 'cursorColor' + | 'presetColors' + | 'outputFormat' + | 'cardSubTitle' + | 'colorSelected' + | 'colorTextColor' + | 'showColorValue' + | 'showHueChannel' + | 'showColorInput' + | 'foregroundColor' + | 'showPresetColors' + | 'showAlphaChannel' + | 'presetColorsLable' + | 'showPaletteChannel' + | 'cardForegroundColor' + | 'showCopyToClipboard' + | 'colorInputIconLabel' + | 'colorInputIconColor' + | 'clearColorIconColor' + | 'clearColorIconLabel' + | 'representColorFormats' + | 'presetColorsLableColor' + | 'copyToClipboardIconLabel' + | 'copyToClipboardIconColor' + | 'showSelectedColorPreview' + > { + // + isInPage?: XStandardType; + disableIcon?: XStandardType; + disableInput?: XStandardType; + wrapWithCard?: XStandardType; + showCardFooter?: XStandardType; + colorViewBorderWidth?: XStandardType; + actionProvider?: Subject; + clearIcon?: XStandardType; + selectIcon?: XStandardType; + inputCssClass?: XStandardType>; + colorViewBorderColor?: XStandardType; + colorViewBorderStyle?: XStandardType; + inputValueChanged?: (value?: string) => XStandardType; + presentType?: XStandardType; +} +//#endregion + +// +//#region Avatar Upload Config ... +export interface XFormAvatarUploadControlConfig { + fileDropAreaFilesChangeEmiter?: ( + files?: XOneOrManyType + ) => XStandardType; + canDrop?: XStandardType; + isInPage?: XStandardType; + wrapWithCard?: XStandardType; + color?: XStandardType; + showCardFooter?: XStandardType; + foregroundColor?: XStandardType; + placeHolderAvatar?: XStandardType; + minAllowedFileSize?: XStandardType; + maxAllowedFileSize?: XStandardType; + cardColor?: XStandardType; + cardForegroundColor?: XStandardType; + fileDropAreaIsInPage?: XStandardType; + cardTitle?: XStandardType; + cssClass?: XStandardType>; + fileDropAreaWrapWithCard?: XStandardType; + fileDropAreaIcon?: XStandardType; + fileDropAreaDragDisabled?: XStandardType; + cardSubTitle?: XStandardType; + actionProvider?: Subject; + fileDropAreaShowCardFooter?: XStandardType; + fileDropAreaColor?: XStandardType; + fileDropAreaForegroundColor?: XStandardType; + avatarChange?: (avatar?: File) => XStandardType; + fileDropAreaDisableFileCheck?: XStandardType; + fileDropAreaCardColor?: XStandardType; + fileDropAreaCardForegroundColor?: XStandardType; + emptyFileException?: XStandardType; + fileDropAreaErrorColor?: XStandardType; + fileDropAreaMessage?: XStandardType; + fileDropAreaAccentColor?: XStandardType; + allowedExtensions?: XStandardType>; + fileDropAreaCardTitle?: XStandardType; + fileDropAreaSuccessColor?: XStandardType; + fileDropAreaSupportsMultipleFiles?: XStandardType; + fileDropAreaCssClass?: XStandardType>; + fileDropAreaCardSubTitle?: XStandardType; + notAllowedFileException?: XStandardType; + fileDropAreaContentTemplate?: XStandardType>; + fileDropAreaActionProvider?: Subject; + minAllowedFileSizeException?: XStandardType; + maxAllowedFileSizeException?: XStandardType; + actions?: XStandardType>; + fileDropAreaAllowedExtensions?: XStandardType>; +} +//#endregion + +// +//#region Selector Config ... +export interface XFormSelectorControlConfig + extends Partial { + // + clearIcon?: XStandardType; + selectIcon?: XStandardType; + inputCssClass?: XStandardType>; + inputValueChanged?: (value?: string) => XStandardType; + formatSelectedItem?: (item: XOneOrManyType) => XStandardType; +} +//#endregion +//#endregion +//#endregion + +// +//#region Form Control Config ... +export interface XFormControlConfig { + index: number; + propName: keyof Partial; + focusHandler?: Subject; + type: XStandardType; + updateOn?: XStandardType; + validators?: XStandardType; + actionProvider?: Subject; + eventHandlers?: XStandardType; + appearance: XStandardType; + errorHandlers?: XStandardType>; +} +//#endregion + +// +// Form Config ... +export interface XFormConfig { + name: XStandardType; + model?: XStandardType>; + updateOn?: XStandardType; + validators?: XStandardType; + controls: XStandardType>>; + errorHandlers?: XStandardType>; +} + +// +//#region Inside Card Container ... +export interface IXFormContainer extends IXComponentCardContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + formConfig: XStandardType; + applyStatusOnControls: XStandardType; + + // + regularColor: XStandardType; + accentColor: XStandardType; + hintColor: XStandardType; + activeColor: XStandardType; + validColor: XStandardType; + invalidColor: XStandardType; + pendingColor: XStandardType; + fillColor: XStandardType; + disabledColor: XStandardType; + disabledFillColor: XStandardType; + + // + valueChanged: EventEmitter; + statusChanged: EventEmitter; + controlFocused: EventEmitter; + controlBlured: EventEmitter; + controlValueChanged: EventEmitter; + controlStatusChanged: EventEmitter; +} + +@Injectable({ + providedIn: 'root', +}) +export class XFormContainer + extends XComponentCardContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider: Subject = + new Subject(); + readonly cssClass: XStandardType>; + readonly formConfig: XStandardType = null; + readonly applyStatusOnControls: XStandardType = false; + + // + readonly regularColor: XStandardType = + XColorWithBrightness.Dark; + readonly accentColor: XStandardType = + XColorWithBrightness.Secondary; + readonly hintColor: XStandardType = + XColorWithBrightness.Tertiary; + readonly activeColor: XStandardType = + XColorWithBrightness.Tertiary; + readonly validColor: XStandardType = + XColorWithBrightness.Success; + readonly invalidColor: XStandardType = + XColorWithBrightness.Danger; + readonly pendingColor: XStandardType = + XColorWithBrightness.Warning; + readonly fillColor: XStandardType = + XColorWithBrightness.SecondaryTint; + readonly disabledColor: XStandardType = + XColorWithBrightness.LightTint; + readonly disabledFillColor: XStandardType = + XColorWithBrightness.LightShade; + + // + readonly valueChanged: EventEmitter = + new EventEmitter(); + readonly statusChanged: EventEmitter = + new EventEmitter(); + readonly controlFocused: EventEmitter = + new EventEmitter(); + readonly controlBlured: EventEmitter = + new EventEmitter(); + readonly controlValueChanged: EventEmitter = + new EventEmitter(); + readonly controlStatusChanged: EventEmitter = + new EventEmitter(); + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XFormContainerComponent + extends XComponentCardContainerComponent + implements IXFormContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + @Input() + formConfig: XStandardType = this.propertyProvider.formConfig; + + @Input() + applyStatusOnControls: XStandardType = + this.propertyProvider.applyStatusOnControls; + + // + @Input() + regularColor: XStandardType = + this.propertyProvider.regularColor; + + @Input() + accentColor: XStandardType = + this.propertyProvider.accentColor; + + @Input() + hintColor: XStandardType = this.propertyProvider.hintColor; + + @Input() + activeColor: XStandardType = + this.propertyProvider.activeColor; + + @Input() + validColor: XStandardType = + this.propertyProvider.validColor; + + @Input() + invalidColor: XStandardType = + this.propertyProvider.invalidColor; + + @Input() + pendingColor: XStandardType = + this.propertyProvider.pendingColor; + + @Input() + fillColor: XStandardType = this.propertyProvider.fillColor; + + @Input() + disabledColor: XStandardType = + this.propertyProvider.disabledColor; + + @Input() + disabledFillColor: XStandardType = + this.propertyProvider.disabledFillColor; + + // + @Output() + valueChanged: EventEmitter = new EventEmitter(); + + @Output() + statusChanged: EventEmitter = new EventEmitter(); + + @Output() + controlFocused: EventEmitter = new EventEmitter(); + + @Output() + controlBlured: EventEmitter = new EventEmitter(); + + @Output() + controlValueChanged: EventEmitter = new EventEmitter(); + + @Output() + controlStatusChanged: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XFormContainer, + protected mapDialogService: XMapDialogService, + protected selectorDialogService: XSelectorDialogService, + protected colorPickerDialogService: XColorPickerDialogService + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXFormContainer + ): Observable { + return super + .extractProps(container ? container : this) + .pipe( + concatMap((superData) => { + return forkJoin({ + // + cssClass: this.getValue( + container ? container.cssClass : this.cssClass + ), + formConfig: this.getValue( + container ? container.formConfig : this.formConfig + ), + applyStatusOnControls: this.getValue( + container + ? container.applyStatusOnControls + : this.applyStatusOnControls + ), + regularColor: this.getValue( + container ? container.regularColor : this.regularColor + ), + accentColor: this.getValue( + container ? container.accentColor : this.accentColor + ), + hintColor: this.getValue( + container ? container.hintColor : this.hintColor + ), + activeColor: this.getValue( + container ? container.activeColor : this.activeColor + ), + validColor: this.getValue( + container ? container.validColor : this.validColor + ), + invalidColor: this.getValue( + container ? container.invalidColor : this.invalidColor + ), + pendingColor: this.getValue( + container ? container.pendingColor : this.pendingColor + ), + fillColor: this.getValue( + container ? container.fillColor : this.fillColor + ), + disabledColor: this.getValue( + container ? container.disabledColor : this.disabledColor + ), + disabledFillColor: this.getValue( + container ? container.disabledFillColor : this.disabledFillColor + ), + }).pipe( + map((data) => { + return { + superData, + data, + }; + }) + ); + }) + ) + .pipe( + map((info) => { + return { + // + ...info.superData, + + // + ...info.data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + valueChanged: this.valueChanged, + statusChanged: this.statusChanged, + controlFocused: this.controlFocused, + controlBlured: this.controlBlured, + controlValueChanged: this.controlValueChanged, + controlStatusChanged: this.controlStatusChanged, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-form\validators\x-form-validators.ts + +import { + isValidURL, + XCountryCode, + isNullOrUndefined, + isValidPhoneNumber, + isNullOrEmptyString, + toNumber, + toXString, +} from 'x-framework-core'; +import { ValidatorFn, AbstractControl, ValidationErrors } from '@angular/forms'; + +export class XFormValidators { + /** + * check given date in between start and end date ... + * + * @param startDate start date for validating + * @param endDate end date for validating + */ + static validateDate( + startDate: Date | string, + endDate: Date | string + ): ValidatorFn { + // + const errorObject = { + startDate, + endDate, + currentDate: new Date(), + xValidateDate: true, + }; + + // + const validatorFn = (control: AbstractControl): ValidationErrors | null => { + // + if ( + isNullOrEmptyString(control.value) || + isNullOrUndefined(startDate) || + isNullOrUndefined(endDate) + ) { + return null; + } + + // + const mStartDate = new Date(startDate); + const mEndDate = new Date(endDate); + const mDate = new Date(control.value); + + // + const isValid = mDate >= mStartDate && mDate <= mEndDate; + if (!isValid) { + // + errorObject.currentDate = mDate; + return errorObject; + } + + // + return null; + }; + + // + return validatorFn; + } + + /** + * check given value be a valid phone number ... + * + * @param defaultCountryCode an optional value which used for country code if not exists in value + */ + static validatePhoneNumber(defaultCountry?: XCountryCode): ValidatorFn { + // + const errorObject = { + xPhoneNumberValidator: true, + }; + + // + const validatorFn = (control: AbstractControl): ValidationErrors | null => { + // + if (isNullOrEmptyString(control.value)) { + return null; + } + + // + const value = control.value as string; + const isValid = isValidPhoneNumber(value, defaultCountry); + + // + if (!isValid) { + return errorObject; + } + + // + return null; + }; + + // + return validatorFn; + } + + /** + * check given value is Valid URL or not ... + */ + static validateUrl(): ValidatorFn { + // + const errorObject = { + xUrlValidator: true, + }; + + // + const validatorFn = (control: AbstractControl): ValidationErrors | null => { + // + if (isNullOrEmptyString(control.value)) { + return null; + } + + // + const value = control.value as string; + const isValid = isValidURL(value); + + // + if (!isValid) { + return errorObject; + } + + // + return null; + }; + + // + return validatorFn; + } + + /** + * check give value notEquals to Zero ... + * usually works for id's in Selector Component ... + */ + static validateNotZero(): ValidatorFn { + // + const errorObject = { + xNotZeroValidator: true, + }; + + // + const validatorFn = (control: AbstractControl): ValidationErrors | null => { + // + if (isNullOrEmptyString(control.value)) { + return null; + } + + // + const value = control.value as string; + const isValid = + !isNullOrEmptyString(value) && + (toXString(value) !== '0' || toNumber(value) !== 0); + + // + if (!isValid) { + return errorObject; + } + + // + return null; + }; + + // + return validatorFn; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-frame\x-frame.component.html + + + + + + + + +
+ +
+ + + +
+
+ + + +
+ + + + + + + + + +
+
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-frame\x-frame.component.scss + +:host { + --x-frame-height: auto; + } + + .v-frame-frame, + .v-frame-container { + width: 100%; + height: var(--x-frame-height); + } + + .v-frame-frame { + border: unset; + } + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-frame\x-frame.component.ts + +import { + Component, + ViewChild, + ElementRef, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + nameof, + generateUuid, + isNullOrUndefined, + isNullOrEmptyString, +} from 'x-framework-core'; +import { + XFrameAction, + XFrameActionModel, + XFrameContainerComponent, +} from './x-frame.typings'; +import { Subject, Subscription } from 'rxjs'; +import { concatMap, map } from 'rxjs/operators'; +import { SafeHtml, SafeResourceUrl } from '@angular/platform-browser'; + +@Component({ + selector: 'x-frame', + templateUrl: './x-frame.component.html', + styleUrls: ['./x-frame.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XFrameComponent extends XFrameContainerComponent { + // + //#region Props ... + // + readonly id = generateUuid('VFrame'); + + // + private ORIGIN: string = ''; + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + private FRAME_VIEW: ElementRef = null; + + // + @ViewChild('frameView', { static: false }) + set frameView(v: ElementRef) { + // + if (isNullOrUndefined(v)) { + return; + } + + // + this.FRAME_VIEW = v; + this.applyFrameProps(); + } + + // + //#region Getters ... + get iFrame() { + return this.FRAME_VIEW!.nativeElement; + } + + get contentWindow() { + return this.iFrame!.contentWindow; + } + + get contentDocuent() { + return this.iFrame!.contentDocument; + } + + get innerHTML() { + return this.iFrame!.innerHTML; + } + + get innerText() { + return this.iFrame!.innerText; + } + + get frameView() { + return this.FRAME_VIEW; + } + + get origin() { + return this.ORIGIN; + } + //#endregion + + // + contentHTML: SafeHtml; + contentUrl: SafeResourceUrl; + //#endregion + + // + //#region Constructor ... + //#endregion + + // + //#region LifeCycles ... + onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region Action Providers ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + + // + //#region Url Changed ... + const isUrlChanged = changeKeys.includes(nameof('url')); + if (isUrlChanged) { + this.prepareContentUrl(); + } + //#endregion + + // + //#region Content ... + const isContentChanged = changeKeys.includes( + nameof('content') + ); + if (isContentChanged) { + this.prepareContent(); + } + //#endregion + + // + //#region VFrame Props ... + // + //#region Allow ... + const isAllowChanged = changeKeys.includes( + nameof('allow') + ); + if (isAllowChanged) { + this.handleAllowChanged(); + } + //#endregion + + // + //#region FrameLoading ... + const isFrameLoadingChanged = changeKeys.includes( + nameof('frameLoading') + ); + if (isFrameLoadingChanged) { + this.handleFrameLoadingChanged(); + } + //#endregion + + // + //#region FrameReferrerPolicy ... + const isReferrerPolicyChanged = changeKeys.includes( + nameof('referrerPolicy') + ); + if (isReferrerPolicyChanged) { + this.handleReferrerPolicyChanged(); + } + //#endregion + + // + //#region FrameSandbox ... + const isSandboxChanged = changeKeys.includes( + nameof('sandbox') + ); + if (isSandboxChanged) { + this.handleSandboxChanged(); + } + //#endregion + + // + //#region AllowFullScreen ... + const isAllowFullScreenChanged = changeKeys.includes( + nameof('allowFullscreen') + ); + if (isAllowFullScreenChanged) { + this.handleAllowFullScreenChanged(); + } + //#endregion + + // + //#region AllowPaymentRequest ... + const isAllowPaymentRequestChanged = changeKeys.includes( + nameof('allowPaymentRequest') + ); + if (isAllowPaymentRequestChanged) { + this.handleAllowPaymentRequestChanged(); + } + //#endregion + + // + //#region AddAllowTransparency ... + if ( + isAllowChanged || + isSandboxChanged || + isFrameLoadingChanged || + isReferrerPolicyChanged || + isAllowFullScreenChanged || + isAllowPaymentRequestChanged + ) { + this.addTransparency(); + } + //#endregion + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + //#endregion + + // + //#region Register Handlers ... + registerInstanceHandlers() { + super.registerInstanceHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region Task Handlers ... + //#endregion + + // + //#region UI Providers ... + getCssClasses(infix: string) { + return this.getValue(false).pipe( + concatMap(() => this.getArrayValue(this.cssClass)), + map((classes) => { + // + const result: string[] = []; + + // + result.push(`v-frame-${infix}`); + result.push(`v-frame-${this.id}-${infix}`); + + // + if (this.hasChild(classes)) { + result.push(...classes); + } + + // + return result; + }) + ); + } + //#endregion + + // + //#region UI Handlers ... + //#endregion + + // + //#region Actions ... + appendElement(element: HTMLElement) { + // + if ( + isNullOrUndefined(element) || + isNullOrUndefined(this.frameView) || + isNullOrUndefined(this.frameView.nativeElement) + ) { + return; + } + + // + this.appendChild(this.frameView.nativeElement, element); + } + + loadHtml(html: string) { + // + if ( + isNullOrEmptyString(html) || + isNullOrUndefined(this.frameView) || + isNullOrUndefined(this.frameView.nativeElement) + ) { + return; + } + + // + this.frameView.nativeElement.contentWindow.frames.document.body.insertAdjacentHTML( + 'beforebegin', + html + ); + } + + loadText(text: string) { + // + if ( + isNullOrEmptyString(text) || + isNullOrUndefined(this.frameView) || + isNullOrUndefined(this.frameView.nativeElement) + ) { + return; + } + + // + this.frameView.nativeElement.contentWindow.frames.document.body.insertAdjacentText( + 'beforebegin', + text + ); + } + + loadElement(element: HTMLElement) { + // + if ( + isNullOrUndefined(element) || + isNullOrUndefined(this.frameView) || + isNullOrUndefined(this.frameView.nativeElement) + ) { + return; + } + + // + this.frameView.nativeElement.contentWindow.frames.document.body.insertAdjacentElement( + 'beforebegin', + element + ); + } + + applyUrl(url: string) { + // + this.contentUrl = url; + this.detectChanges(); + } + + applyContent(content: string) { + // + this.content = content; + this.detectChanges(); + } + + fullScreen(options?: FullscreenOptions) { + this.frameView.nativeElement.requestFullscreen(options); + } + //#endregion + + // + //#region Abstracts ... + //#endregion + + // + //#region Private ... + // + //#region Action Provider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + // tslint:disable-next-line: deprecation + .subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XFrameAction.Refresh: + this.detectChanges(); + break; + + // + case XFrameAction.AppendElement: + this.appendElement(model.payload); + break; + + // + case XFrameAction.ApplyContent: + this.applyContent(model.payload); + break; + + // + case XFrameAction.ApplyUrl: + this.applyUrl(model.payload); + break; + + // + case XFrameAction.LoadElement: + this.loadElement(model.payload); + break; + + // + case XFrameAction.LoadText: + this.loadText(model.payload); + break; + + // + case XFrameAction.LoadHtml: + this.loadHtml(model.payload); + break; + + // + case XFrameAction.FullScreen: + this.fullScreen(model.payload); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + // + //#region Preparation ... + private prepareContent(content?: string) { + this.getValue(this.content).subscribe((tContent) => { + // + this.ORIGIN = ''; + this.contentUrl = ''; + this.contentHTML = ''; + + // + content = !isNullOrEmptyString(content) ? content : tContent; + if (isNullOrEmptyString(content)) { + this.detectChanges(); + return; + } + + // + this.contentHTML = + this.managerService.securityService.toSafeHTML(content); + }); + } + + private prepareContentUrl(url?: string) { + this.getValue(this.url).subscribe((tUrl) => { + // + this.ORIGIN = ''; + this.contentUrl = ''; + this.contentHTML = ''; + + // + url = !isNullOrEmptyString(url) ? url : tUrl; + if (isNullOrEmptyString(tUrl)) { + this.detectChanges(); + return; + } + + // + // Retrieve Origin ... + const mUrl = new URL(url); + this.ORIGIN = mUrl.origin; + + // + this.contentUrl = + this.managerService.securityService.toSafeResourceURL(url); + }); + } + + private applyFrameAttribute( + attr: string, + value: string, + element: ElementRef + ) { + // + if (isNullOrUndefined(element) || isNullOrEmptyString(attr)) { + return; + } + + // + if (!isNullOrEmptyString(value)) { + // + // Set Attribute ... + this.setAttribute(attr, value, element); + } else { + // + // Remove Attribute ... + this.removeAttribute(attr, element); + } + } + + // + //#region Frame Props ... + private applyFrameProps() { + this.addTransparency(); + this.handleAllowChanged(); + this.handleSandboxChanged(); + this.handleFrameLoadingChanged(); + this.handleReferrerPolicyChanged(); + this.handleAllowFullScreenChanged(); + this.handleAllowPaymentRequestChanged(); + } + + private addTransparency() { + // + const value = true.toString(); + if ( + isNullOrEmptyString(value) || + isNullOrUndefined(this.frameView) || + isNullOrUndefined(this.frameView.nativeElement) + ) { + return; + } + + // + this.applyFrameAttribute('allowtransparency', value, this.frameView); + this.detectChanges(); + } + + private handleAllowChanged() { + this.getArrayValue(this.allow).subscribe((allows) => { + // + if ( + isNullOrUndefined(this.frameView) || + isNullOrUndefined(this.frameView.nativeElement) + ) { + return; + } + + // + const value = this.hasChild(allows) ? allows.join('; ') : null; + + // + this.applyFrameAttribute('allow', value, this.frameView); + this.detectChanges(); + }); + } + + private handleSandboxChanged() { + this.getValue(this.sandbox).subscribe((value) => { + // + if ( + isNullOrEmptyString(value) || + isNullOrUndefined(this.frameView) || + isNullOrUndefined(this.frameView.nativeElement) + ) { + return; + } + + // + this.applyFrameAttribute('sandbox', value, this.frameView); + this.detectChanges(); + }); + } + + private handleFrameLoadingChanged() { + this.getValue(this.frameLoading).subscribe((value) => { + // + if ( + isNullOrEmptyString(value) || + isNullOrUndefined(this.frameView) || + isNullOrUndefined(this.frameView.nativeElement) + ) { + return; + } + + // + this.applyFrameAttribute('loading', value, this.frameView); + this.detectChanges(); + }); + } + + private handleReferrerPolicyChanged() { + this.getArrayValue(this.referrerPolicy).subscribe((referrerPolicies) => { + // + if ( + isNullOrUndefined(this.frameView) || + isNullOrUndefined(this.frameView.nativeElement) + ) { + return; + } + + // + const value = this.hasChild(referrerPolicies) + ? referrerPolicies.join('|') + : null; + + // + this.applyFrameAttribute('referrerpolicy', value, this.frameView); + this.detectChanges(); + }); + } + + private handleAllowFullScreenChanged() { + this.getValue(this.allowFullscreen).subscribe((allowFullscreen) => { + // + const value = allowFullscreen.toString(); + if ( + isNullOrEmptyString(value) || + isNullOrUndefined(this.frameView) || + isNullOrUndefined(this.frameView.nativeElement) + ) { + return; + } + + // + this.applyFrameAttribute('allowfullscreen', value, this.frameView); + this.detectChanges(); + }); + } + + private handleAllowPaymentRequestChanged() { + this.getValue(this.allowPaymentRequest).subscribe((allowPaymentRequest) => { + // + const value = allowPaymentRequest.toString(); + if ( + isNullOrEmptyString(value) || + isNullOrUndefined(this.frameView) || + isNullOrUndefined(this.frameView.nativeElement) + ) { + return; + } + + // + this.applyFrameAttribute('allowpaymentrequest', value, this.frameView); + this.detectChanges(); + }); + } + //#endregion + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-frame\x-frame.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XFrameComponent } from './x-frame.component'; +import { XCardModule } from '../x-card/x-card.module'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XFrameComponent], + imports: [CommonModule, XBaseComponentModule, XCardModule], + exports: [CommonModule, XBaseComponentModule, XCardModule, XFrameComponent], +}) +export class XFrameModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-frame\x-frame.typings.ts + +import { + Input, + Inject, + NgZone, + Output, + Component, + Renderer2, + ElementRef, + Injectable, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XComponentCardContainer, + IXComponentCardContainer, + XComponentCardContainerComponent, +} from '../x-card/x-card.typings'; +import { concatMap, map } from 'rxjs/operators'; +import { forkJoin, Observable, Subject } from 'rxjs'; +import { XManagerService } from 'x-framework-services'; +import { ViewportRuler } from '@angular/cdk/scrolling'; +import { XOneOrManyType, XStandardType } from 'x-framework-core'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export enum XFrameAction { + Refresh = 'refresh', + AppendElement = 'appendElement', + LoadHtml = 'loadHtml', + LoadText = 'loadText', + LoadElement = 'loadElement', + ApplyUrl = 'applyUrl', + ApplyContent = 'applyContent', + FullScreen = 'fullScreen', +} + +export type XFrameActionIdentifier = XFrameAction | string; + +export interface XFrameActionModel + extends XBaseActionModel {} + +// +//#region XFrame Loading ... +export enum XFrameLoading { + Eager = 'eager', + Lazy = 'lazy', +} + +export type XFrameLoadingIdentifier = XFrameLoading | string; +//#endregion + +// +//#region XFrame ReferrerPolicy ... +export enum XFrameReferrerPolicy { + NoReferrer = 'no-referrer', + NoReferrerWhenDowngrade = 'no-referrer-when-downgrade', + Origin = 'origin', + OriginWhenCrossOrigin = 'origin-when-cross-origin', + SameOrigin = 'same-origin', + StrictOriginWhenCrossOrigin = 'strict-origin-when-cross-origin', + UnsafeUrl = 'unsafe-url', +} + +export type XFrameReferrerPolicyIdentifier = XFrameReferrerPolicy | string; +//#endregion + +// +//#region Sandbox ... +export enum XFrameSandbox { + AllowForms = 'allow-forms', + AllowModals = 'allow-modals', + AllowOrientationLock = 'allow-orientation-lock', + AllowPointerLock = 'allow-pointer-lock', + AllowPopups = 'allow-popups', + AllowPopupsToEscapeSandbox = 'allow-popups-to-escape-sandbox', + AllowPresentation = 'allow-presentation', + AllowSameOrigin = 'allow-same-origin', + AllowScripts = 'allow-scripts', + AllowTopNavigation = 'allow-top-navigation', + AllowTopNavigationByUserActivation = 'allow-top-navigation-by-user-activation', +} + +export type XFrameSandboxIdentifier = XFrameSandbox | string; +//#endregion + +// +//#region Containers ... +export interface IXFrameContainer extends IXComponentCardContainer { + url: XStandardType; + content: XStandardType; + onContentLoaded: EventEmitter; + allowFullscreen: XStandardType; + actionProvider: Subject; + allowPaymentRequest: XStandardType; + allow: XStandardType>; + cssClass: XStandardType>; + sandbox: XStandardType; + frameLoading: XStandardType; + referrerPolicy: XStandardType>; +} + +@Injectable({ + providedIn: 'root', +}) +export class XFrameContainer + extends XComponentCardContainer + implements Readonly +{ + // + //#region Props ... + readonly url: XStandardType; + readonly content: XStandardType; + readonly referrerPolicy: XStandardType< + XOneOrManyType + > = null; + readonly actionProvider: Subject = + new Subject(); + readonly allow: XStandardType>; + readonly allowFullscreen: XStandardType = false; + readonly cssClass: XStandardType>; + readonly allowPaymentRequest: XStandardType = false; + readonly sandbox: XStandardType = null; + readonly frameLoading: XStandardType = null; + readonly onContentLoaded: EventEmitter = new EventEmitter(); + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XFrameContainerComponent + extends XComponentCardContainerComponent + implements IXFrameContainer +{ + // + //#region Props ... + @Input() + allowFullscreen: XStandardType = + this.propertyProvider.allowFullscreen; + + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + allowPaymentRequest: XStandardType = + this.propertyProvider.allowPaymentRequest; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + @Input() + sandbox: XStandardType = + this.propertyProvider.sandbox; + + @Input() + frameLoading: XStandardType = + this.propertyProvider.frameLoading; + + @Input() + url: XStandardType = this.propertyProvider.url; + + @Input() + content: XStandardType = this.propertyProvider.content; + + @Input() + referrerPolicy: XStandardType< + XOneOrManyType + > = this.propertyProvider.referrerPolicy; + + @Output() + onContentLoaded: EventEmitter = this.propertyProvider.onContentLoaded; + + @Input() + allow: XStandardType> = this.propertyProvider.allow; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XFrameContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXFrameContainer + ): Observable { + return super + .extractProps(container ? container : this) + .pipe( + concatMap((superData) => { + return forkJoin({ + url: this.getValue(container ? container.url : this.url), + content: this.getValue( + container ? container.content : this.content + ), + allowFullscreen: this.getValue( + container ? container.allowFullscreen : this.allowFullscreen + ), + allowPaymentRequest: this.getValue( + container + ? container.allowPaymentRequest + : this.allowPaymentRequest + ), + allow: this.getArrayValue(container ? container.allow : this.allow), + cssClass: this.getArrayValue( + container ? container.cssClass : this.cssClass + ), + sandbox: this.getValue( + container ? container.sandbox : this.sandbox + ), + frameLoading: this.getValue( + container ? container.frameLoading : this.frameLoading + ), + referrerPolicy: this.getValue( + container ? container.referrerPolicy : this.referrerPolicy + ), + }).pipe( + map((data) => { + return { + superData, + data, + }; + }) + ); + }) + ) + .pipe( + map((info) => { + return { + // + ...info.superData, + + // + ...info.data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + onContentLoaded: container + ? container.onContentLoaded + : this.onContentLoaded, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-grid\x-grid.component.html + + + + + + + + +
+ +
+
+
+ + + +
+ + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{ item | json }}
+
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-grid\x-grid.component.scss + +ion-item-option > ion-item { + --background: transparent !important; +} + +ion-button.transparent-button { + --box-shadow: transparent !important; + --background: transparent !important; + --background-hover: transparent !important; + --background-focused: transparent !important; + --background-activated: transparent !important; +} + +ion-list, +ion-item.item, +ion-item-divider { + --background: transparent !important; +} + +ion-item-divider { + --color: var(--ion-color-dark-shade); +} + +ion-item.item.mobile { + --ion-safe-area-end: 0px; + + --padding-start: 0px; + --inner-padding-start: 0px; + + --padding-end: 0px; + --inner-padding-end: 0px; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + margin: 0; +} + +.x-menu { + z-index: 10; +} + +.x-check { + z-index: 1; +} + +.grid-box { + margin-top: 8px; +} + +.grid-box.mobile { + margin-top: 0px; +} + +gridster { + width: 100%; + display: flex; + direction: ltr; + position: inherit; + flex-direction: column; + background-color: transparent; + height: calc(var(--grid-height, inherit)); + + &.inCard { + position: relative; + } +} + +gridster-item { + padding: 0; + background-color: transparent; +} + +.x-grid-item-wrapper { + position: relative; + background: inherit; +} + +a[href=''], +[disabled], +.disable { + color: inherit; + cursor: default; + pointer-events: none; + text-decoration: none; +} + +.transparent { + --background: transparent !important; + background-color: transparent !important; +} + +x-card { + width: inherit; + height: inherit; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-grid\x-grid.component.ts + +import { + XGridItem, + XGridAction, + XGridActionModel, + XGridPresentType, + defaultGridOptions, + XStaggedGridOptions, + XGridContainerComponent, +} from './x-grid.typings'; +import { Subject, Subscription } from 'rxjs'; +import { nameof, cloneJson, toObservable } from 'x-framework-core'; +import { GridsterItem, GridsterComponent } from 'angular-gridster2'; +import { Component, ViewChild, ChangeDetectionStrategy } from '@angular/core'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-grid', + templateUrl: './x-grid.component.html', + styleUrls: ['./x-grid.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XGridComponent extends XGridContainerComponent { + // + //#region Props ... + // + private COLS: number; + private TYPE: XGridPresentType; + private GRIDSTER: GridsterComponent; + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + colItems: number[] = []; + + get preparedCols() { + return this.COLS; + } + + get preparedType() { + return this.TYPE; + } + + // + @ViewChild('gridster', { static: false }) + set gridster(v: GridsterComponent) { + // + if (!v) { + return; + } + + // + this.GRIDSTER = v; + this.prepareData(); + } + + get gridster() { + return this.GRIDSTER; + } + //#endregion + + // + //#region LifeCycles ... + afterViewInit() { + super.afterContentInit(); + + // + this.prepareData(); + } + + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region ActionProvider Changed ... + // + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + + // + const isColsChanged = changeKeys.includes(nameof('cols')); + const isTypeChanged = changeKeys.includes(nameof('type')); + const isOptionsChanged = changeKeys.includes( + nameof('options') + ); + + // + if (isColsChanged) { + this.COLS = await this.getValueAsync(this.cols); + this.detectChanges(); + } + + // + if (isTypeChanged) { + this.TYPE = await this.getValueAsync(this.type); + this.detectChanges(); + } + + // + if (isColsChanged || isTypeChanged || isOptionsChanged) { + this.prepareData(); + } + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region Ui Provider Actions ... + // + //#region Type Detectors ... + isTypeLinear() { + return this.preparedType === XGridPresentType.AsLinear; + } + + isTypeGrid() { + return this.preparedType === XGridPresentType.AsGrid; + } + + isTypeFlex() { + return this.preparedType === XGridPresentType.AsFlex; + } + + isTypeStaggedGrid() { + return this.preparedType === XGridPresentType.AsStaggedGrid; + } + //#endregion + + // + isNewRow(i: number, cols: number) { + return (i + 1) % cols === 1; + } + + // + toStaggedGrid(index: number) { + return { + id: index, + data: this.items[index], + cols: 1, + rows: 1, + x: 0, + y: 0, + } as GridsterItem; + } + + // + getItemHref(item?: XGridItem) { + // + if (!item || !item.href) { + return toObservable(''); + } + + // + return this.getValue(item.href); + } + + // + getItemHide(item?: XGridItem) { + // + if (!item || !item.hide) { + return toObservable(false); + } + + // + return this.getValue(item.hide); + } + + // + getItemDisabled(item?: XGridItem) { + // + if (!item || !item.disabled) { + return toObservable(false); + } + + // + return this.getValue(item.disabled); + } + //#endregion + + // + //#region Type Detectors ... + //#endregion + + // + //#region Actions ... + async handleItemSelected(item: XGridItem) { + // + if (!this.selectableItems) { + return; + } + + // + const hided = await this.toPromise(this.getItemHide(item)); + const disabled = await this.toPromise(this.getItemDisabled(item)); + + // + if (!hided && !disabled) { + this.itemSelected.emit(item); + } + } + //#endregion + + // + //#region Private ... + // + //#region Action Provider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + // Handle Action Provider ... + this.ACTION_PROVIDER_SUBSCRIPTION = this.applyTakeUntilWrapper( + this.actionProvider.asObservable() + ).subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XGridAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + // + private prepareData() { + // + if (!this.preparedCols) { + return; + } + + // + const mCols = this.preparedCols < 0 ? 0 : this.preparedCols; + this.colItems = Array.from(Array(mCols).keys()); + + // + setTimeout(() => { + // + this.options = { + ...cloneJson(defaultGridOptions), + minCols: mCols, + maxCols: mCols, + } as XStaggedGridOptions; + + // + this.detectChanges(); + }, 300); + + // + this.detectChanges(); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-grid\x-grid.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { GridsterModule } from 'angular-gridster2'; +import { XGridComponent } from './x-grid.component'; +import { XCardModule } from '../x-card/x-card.module'; +import { XEmptyModule } from '../x-empty/x-empty.module'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XGridComponent], + imports: [ + CommonModule, + XCardModule, + XEmptyModule, + GridsterModule, + XBaseComponentModule, + ], + exports: [XCardModule, XGridComponent, XEmptyModule, XBaseComponentModule], +}) +export class XGridModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-grid\x-grid.typings.ts + +import { + Input, + Output, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + TemplateRef, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + IXCardEmptyContainer, + XCardEmptyContainer, + XCardEmptyContainerComponent, +} from '../classes/i-x-card-empty.container'; +import { concatMap, map } from 'rxjs/operators'; +import { GridsterConfig } from 'angular-gridster2'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XStandardType, XOneOrManyType } from 'x-framework-core'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export interface XGridItem { + data: T; + hide?: XStandardType; + disabled?: XStandardType; + href?: XStandardType>; +} + +export interface XStaggedGridOptions extends Partial {} +export const defaultGridOptions: XStaggedGridOptions = { + gridType: 'scrollVertical', + compactType: 'compactLeft&Up', + disableWarnings: true, + draggable: { + enabled: true, + }, + pushItems: true, + resizable: { + enabled: true, + }, + minCols: 2, + maxCols: 2, + minRows: 1, +}; + +export enum XGridPresentType { + AsLinear, + AsGrid, + AsFlex, + AsStaggedGrid, +} + +export enum XGridAction { + Refresh = 'refresh', +} + +export type XGridActionIdentifier = XGridAction | string; + +export interface XGridActionModel + extends XBaseActionModel {} + +// +//#region Inside Card/Empty Container ... +export interface IXGridContainer extends IXCardEmptyContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + options: XStandardType; + cols: XStandardType; + type: XStandardType; + gap: string; + layout: string; + align: string; + selectableItems: XStandardType; + items: XGridItem[]; + itemTemplate: TemplateRef; + + // + itemSelected: EventEmitter>; +} + +@Injectable({ + providedIn: 'root', +}) +export class XGridContainer + extends XCardEmptyContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider: Subject = + new Subject(); + readonly cssClass: XStandardType>; + + // + readonly options: XStandardType; + readonly cols: XStandardType; + readonly type: XStandardType; + readonly gap: string = '1px'; + readonly layout: string = 'row wrap'; + readonly align: string = 'center start'; + readonly selectableItems: XStandardType = false; + readonly items: XGridItem[]; + readonly itemTemplate: TemplateRef; + + // + readonly itemSelected: EventEmitter>; + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XGridContainerComponent + extends XCardEmptyContainerComponent + implements IXGridContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + options: XStandardType = this.propertyProvider.options; + + @Input() + cols: XStandardType = this.propertyProvider.cols; + + @Input() + type: XStandardType = this.propertyProvider.type; + + @Input() + gap: string = this.propertyProvider.gap; + + @Input() + layout: string = this.propertyProvider.layout; + + @Input() + align: string = this.propertyProvider.align; + + @Input() + selectableItems: XStandardType = + this.propertyProvider.selectableItems; + + @Input() + items: XGridItem[] = this.propertyProvider.items; + + @Input() + itemTemplate: TemplateRef = this.propertyProvider.itemTemplate; + + // + @Output() + itemSelected: EventEmitter> = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XGridContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXGridContainer + ): Observable { + return super + .extractProps(container ? container : this) + .pipe( + concatMap((superData) => { + return forkJoin({ + // + cssClass: this.getValue( + container ? container.cssClass : this.cssClass + ), + options: this.getValue( + container ? container.options : this.options + ), + cols: this.getValue(container ? container.cols : this.cols), + type: this.getValue(container ? container.type : this.type), + gap: this.getValue(container ? container.gap : this.gap), + layout: this.getValue(container ? container.layout : this.layout), + align: this.getValue(container ? container.align : this.align), + selectableItems: this.getValue( + container ? container.selectableItems : this.selectableItems + ), + items: this.getValue(container ? container.items : this.items), + itemTemplate: this.getValue( + container ? container.itemTemplate : this.itemTemplate + ), + }).pipe( + map((data) => { + return { + superData, + data, + }; + }) + ); + }) + ) + .pipe( + map((info) => { + return { + // + ...info.superData, + + // + ...info.data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + itemSelected: this.itemSelected, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-icon\x-icon.component.html + + +
+ + + + + + +
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-icon\x-icon.component.scss + +:host { + // + --size: 24px; + + // + display: inline-block; + position: relative; + + // + font-size: var(--size); + line-height: var(--size); + width: var(--size); + height: var(--size); + + // + min-width: var(--size); + min-height: var(--size); + + // + margin: 0 !important; + padding: 0 !important; +} + +// +$delay: 0; +$duration: 1; +$transform: scaleX(-1); + +.x-icon { + // + display: inline-block; + position: relative; + + // + margin: 0 !important; + padding: 0 !important; + + // + box-sizing: border-box !important; + + // + min-width: var(--size); + min-height: var(--size); + + &.rtl { + // + transform: $transform; + -ms-transform: $transform; + -webkit-transform: $transform; + + // + transition: transform; + -webkit-transition: transform; + + // + transition-delay: $delay; + -webkit-transition-delay: $delay; + + // + transition-duration: $duration; + -webkit-transition-duration: $duration; + } +} + +ion-icon, +ion-ripple-effect { + font-size: var(--size); + + min-width: var(--size); + min-height: var(--size); +} + +.ion-activatable { + cursor: pointer; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-icon\x-icon.component.ts + +import { + XIconAction, + XCustomIcons, + XProvidedIcons, + XIconActionModel, + XIconContainerComponent, +} from './x-icon.typings'; +import { map } from 'rxjs/operators'; +import { nameof } from 'x-framework-core'; +import { Subject, Subscription } from 'rxjs'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-icon', + templateUrl: './x-icon.component.html', + styleUrls: ['./x-icon.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XIconComponent extends XIconContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + //#endregion + + // + //#region LifeCycles ... + onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region Icon Provider Properties ... + get providedIcons() { + return XProvidedIcons; + } + + get icon() { + return this.getValue(this.name).pipe( + map((name) => { + return (this.providedIcons as any)[name.toString()] as string; + }) + ); + } + //#endregion + + // + //#region UI Providers ... + isCustomIcon() { + return this.getValue(this.name).pipe( + map((name) => { + return name && XCustomIcons.includes(name.toString()); + }) + ); + } + + customIconUrl() { + return this.getValue(this.name).pipe( + map((name) => { + // + const svgName = (this.providedIcons as any)[name.toString()] + return this.name + ? `./assets/icon/svg/${svgName}.svg` + : null; + }) + ); + } + //#endregion + + // + //#region Action Handlers ... + async handleClicked(event?: Event) { + // + const clickable = await this.toPromise(this.getValue(this.clickable)); + const disabled = this.uiDisabled; + if (!clickable || disabled) { + return; + } + + // + if (event && event.stopPropagation) { + event.stopPropagation(); + } + + // + this.clicked.emit(event); + } + //#endregion + + // + //#region Private ... + // + //#region Action Provider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + switch (model.action) { + // + case XIconAction.Click: + this.clicked.emit(); + break; + + // + case XIconAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-icon\x-icon.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XIconComponent } from './x-icon.component'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XIconComponent], + imports: [CommonModule, XBaseComponentModule], + exports: [XBaseComponentModule, XIconComponent], +}) +export class XIconModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-icon\x-icon.typings.ts + +import { + Input, + Output, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XStandardType, + XOneOrManyType, + XColorIdentifier, +} from 'x-framework-core'; +import { map } from 'rxjs/operators'; +import { + XBaseContainer, + IXBaseContainer, + XBaseContainerComponent, +} from '../classes/i-x-base.container'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export enum XIconAction { + Click = 'click', + Refresh = 'refresh', +} + +export type XIconActionIdentifier = XIconAction | string; + +export interface XIconActionModel + extends XBaseActionModel {} + +export enum XIconNames { + tv = 'tv', + ol = 'ol', + ul = 'ul', + job = 'job', + add = 'add', + sad = 'sad', + web = 'web', + map = 'map', + dns = 'dns', + dvr = 'dvr', + fax = 'fax', + mic = 'mic', + mop = 'mop', + info = 'info', + lock = 'lock', + mail = 'mail', + shop = 'shop', + edit = 'edit', + done = 'done', + list = 'list', + user = 'user', + star = 'star', + sync = 'sync', + undo = 'undo', + redo = 'redo', + home = 'home', + flag = 'flag', + save = 'save', + last = 'last', + next = 'next', + copy = 'copy', + link = 'link', + bold = 'bold', + code = 'code', + crop = 'crop', + call = 'call', + tabs = 'tabs', + cast = 'cast', + wifi = 'wifi', + stop = 'stop', + play = 'play', + phone = 'phone', + reply = 'reply', + label = 'label', + liked = 'liked', + terms = 'terms', + event = 'event', + clear = 'clear', + input = 'input', + close = 'close', + users = 'users', + files = 'files', + login = 'login', + posts = 'posts', + enter = 'enter', + power = 'power', + cloud = 'cloud', + first = 'first', + touch = 'touch', + error = 'error', + print = 'print', + share = 'share', + image = 'image', + audio = 'audio', + video = 'video', + guide = 'guide', + quote = 'quote', + toast = 'toast', + cards = 'cards', + mouse = 'mouse', + album = 'album', + badge = 'badge', + radio = 'radio', + watch = 'watch', + movie = 'movie', + pause = 'pause', + unlock = 'unlock', + cancel = 'cancel', + expand = 'expand', + mobile = 'mobile', + search = 'search', + survey = 'survey', + remove = 'remove', + invite = 'invite', + logout = 'logout', + school = 'school', + server = 'server', + upload = 'upload', + locate = 'locate', + italic = 'italic', + repeat = 'repeat', + report = 'report', + policy = 'policy', + router = 'router', + shield = 'shield', + stylus = 'stylus', + tv_off = 'tv_off', + swords = 'swords', + nearby = 'nearby', + flight = 'flight', + memory = 'memory', + replay = 'replay', + visible = 'visible', + checked = 'checked', + unliked = 'unliked', + profile = 'profile', + account = 'account', + avatars = 'avatars', + archive = 'archive', + refresh = 'refresh', + unknown = 'unknown', + preview = 'preview', + heading = 'heading', + toolbar = 'toolbar', + dialogs = 'dialogs', + dialpad = 'dialpad', + vpn_key = 'vpn_key', + widgets = 'widgets', + passkey = 'passkey', + mic_off = 'mic_off', + monitor = 'monitor', + gpp_bad = 'gpp_bad', + charger = 'charger', + devices = 'devices', + capture = 'capture', + live_tv = 'live_tv', + sd_card = 'sd_card', + verified = 'verified', + collapse = 'collapse', + arrow_up = 'arrow_up', + contacts = 'contacts', + contrast = 'contrast', + calendar = 'calendar', + register = 'register', + explorer = 'explorer', + settings = 'settings', + business = 'business', + bookmark = 'bookmark', + chat_box = 'chat_box', + location = 'location', + previous = 'previous', + download = 'download', + language = 'language', + document = 'document', + logo_npm = 'logo_npm', + logo_pwa = 'logo_pwa', + logo_rss = 'logo_rss', + logo_usd = 'logo_usd', + logo_vue = 'logo_vue', + dropdown = 'dropdown', + add_call = 'add_call', + sim_card = 'sim_card', + security = 'security', + password = 'password', + podcasts = 'podcasts', + wifi_off = 'wifi_off', + no_sound = 'no_sound', + keyboard = 'keyboard', + exercise = 'exercise', + computer = 'computer', + autoplay = 'autoplay', + autostop = 'autostop', + videocam = 'videocam', + label_off = 'label_off', + swap_vert = 'swap_vert', + dashboard = 'dashboard', + checkmark = 'checkmark', + star_half = 'star_half', + thumbs_up = 'thumbs_up', + bookmarks = 'bookmarks', + bluetooth = 'bluetooth', + analytics = 'analytics', + clipboard = 'clipboard', + logo_css3 = 'logo_css3', + logo_edge = 'logo_edge', + logo_sass = 'logo_sass', + touch_app = 'touch_app', + voicemail = 'voicemail', + call_made = 'call_made', + subtitles = 'subtitles', + music_off = 'music_off', + encrypted = 'encrypted', + apartment = 'apartment', + dark_mode = 'dark_mode', + deskphone = 'deskphone', + autopause = 'autopause', + video_cam = 'video_cam', + swap_horiz = 'swap_horiz', + first_name = 'first_name', + un_checked = 'un_checked', + arrow_down = 'arrow_down', + arrow_back = 'arrow_back', + select_all = 'select_all', + management = 'management', + logo_apple = 'logo_apple', + logo_ionic = 'logo_ionic', + logo_html5 = 'logo_html5', + blockchain = 'blockchain', + logo_react = 'logo_react', + logo_skype = 'logo_skype', + logo_slack = 'logo_slack', + logo_yahoo = 'logo_yahoo', + data_table = 'data_table', + device_hub = 'device_hub', + call_split = 'call_split', + router_off = 'router_off', + light_mode = 'light_mode', + mouse_lock = 'mouse_lock', + nightlight = 'nightlight', + volume_off = 'volume_off', + volume_low = 'volume_low', + play_arrow = 'play_arrow', + video_call = 'video_call', + video_chat = 'video_chat', + video_file = 'video_file', + voice_chat = 'voice_chat', + fit_screen = 'fit_screen', + fullscreen = 'fullscreen', + rotate_left = 'rotate_left', + visible_off = 'visible_off', + friendships = 'friendships', + thumbs_down = 'thumbs_down', + chat_bubble = 'chat_bubble', + stats_chart = 'stats_chart', + logo_amazon = 'logo_amazon', + logo_chrome = 'logo_chrome', + logo_docker = 'logo_docker', + logo_github = 'logo_github', + logo_gitlab = 'logo_gitlab', + logo_google = 'logo_google', + blockchain1 = 'blockchain1', + logo_nodejs = 'logo_nodejs', + logo_paypal = 'logo_paypal', + voicemail_2 = 'voicemail_2', + devices_off = 'devices_off', + exit_to_app = 'exit_to_app', + ring_volume = 'ring_volume', + call_missed = 'call_missed', + privacy_tip = 'privacy_tip', + dual_screen = 'dual_screen', + engineering = 'engineering', + volume_high = 'volume_high', + volume_mute = 'volume_mute', + play_circle = 'play_circle', + fast_rewind = 'fast_rewind', + rotate_right = 'rotate_right', + chat_bubbles = 'chat_bubbles', + deselect_all = 'deselect_all', + arrow_dropup = 'arrow_dropup', + logo_android = 'logo_android', + logo_angular = 'logo_angular', + logo_bitcoin = 'logo_bitcoin', + logo_dropbox = 'logo_dropbox', + logo_firefox = 'logo_firefox', + logo_windows = 'logo_windows', + logo_twitter = 'logo_twitter', + logo_youtube = 'logo_youtube', + wifi_calling = 'wifi_calling', + phone_locked = 'phone_locked', + phone_missed = 'phone_missed', + call_quality = 'call_quality', + screen_share = 'screen_share', + room_service = 'room_service', + other_houses = 'other_houses', + dock_to_left = 'dock_to_left', + keyboard_off = 'keyboard_off', + cameraswitch = 'cameraswitch', + architecture = 'architecture', + fast_forward = 'fast_forward', + arrow_forward = 'arrow_forward', + accessibility = 'accessibility', + cloud_offline = 'cloud_offline', + menu_vertical = 'menu_vertical', + logo_electron = 'logo_electron', + logo_facebook = 'logo_facebook', + logo_firebase = 'logo_firebase', + logo_linkedin = 'logo_linkedin', + logo_markdown = 'logo_markdown', + logo_whatsapp = 'logo_whatsapp', + color_palette = 'color_palette', + phone_enabled = 'phone_enabled', + phone_in_talk = 'phone_in_talk', + call_received = 'call_received', + print_connect = 'print_connect', + screen_record = 'screen_record', + shield_person = 'shield_person', + verified_user = 'verified_user', + network_check = 'network_check', + encrypted_off = 'encrypted_off', + dock_to_right = 'dock_to_right', + electric_bolt = 'electric_bolt', + reset_password = 'reset_password', + customers_club = 'customers_club', + checkmark_done = 'checkmark_done', + arrow_dropdown = 'arrow_dropdown', + logo_instagram = 'logo_instagram', + logo_wordpress = 'logo_wordpress', + devices_fold_2 = 'devices_fold_2', + perm_phone_msg = 'perm_phone_msg', + phone_callback = 'phone_callback', + phone_disabled = 'phone_disabled', + settings_phone = 'settings_phone', + print_disabled = 'print_disabled', + wifi_tethering = 'wifi_tethering', + speech_to_text = 'speech_to_text', + mouse_lock_off = 'mouse_lock_off', + directions_car = 'directions_car', + local_shipping = 'local_shipping', + cast_connected = 'cast_connected', + voice_chat_off = 'voice_chat_off', + password_change = 'password_change', + menu_horizontal = 'menu_horizontal', + logo_javascript = 'logo_javascript', + phone_forwarded = 'phone_forwarded', + string_resources = 'string_resources', + chatbox_ellipses = 'chatbox_ellipses', + checkmark_circle = 'checkmark_circle', + select_all_group = 'select_all_group', + blockchain_block = 'blockchain_block', + friendship_block = 'friendship_block', + blockchain_token = 'blockchain_token', + document_scanner = 'document_scanner', + splitscreen_left = 'splitscreen_left', + close_fullscreen = 'close_fullscreen', + inverse_selection = 'inverse_selection', + friendship_reuest = 'friendship_reuest', + friendship_friend = 'friendship_friend', + friendship_reject = 'friendship_reject', + friendship_cancel = 'friendship_cancel', + friendship_accept = 'friendship_accept', + stop_screen_share = 'stop_screen_share', + missed_video_call = 'missed_video_call', + keyboard_lock_off = 'keyboard_lock_off', + video_camera_back = 'video_camera_back', + deselect_all_group = 'deselect_all_group', + logo_stackoverflow = 'logo_stackoverflow', + logo_web_component = 'logo_web_component', + blockchain_address = 'blockchain_address', + friendship_unblock = 'friendship_unblock', + friendship_unknown = 'friendship_unknown', + wifi_calling_bar_1 = 'wifi_calling_bar_1', + wifi_calling_bar_2 = 'wifi_calling_bar_2', + video_camera_front = 'video_camera_front', + picture_in_picture = 'picture_in_picture', + logo_apple_appstore = 'logo_apple_appstore', + friendship_follower = 'friendship_follower', + friendship_unfollow = 'friendship_unfollow', + signal_cellular_alt = 'signal_cellular_alt', + signal_disconnected = 'signal_disconnected', + chat_bubble_ellipses = 'chat_bubble_ellipses', + friendship_following = 'friendship_following', + call_missed_outgoing = 'call_missed_outgoing', + audio_video_receiver = 'audio_video_receiver', + admin_panel_settings = 'admin_panel_settings', + checkmark_done_circle = 'checkmark_done_circle', + logo_google_playstore = 'logo_google_playstore', + video_camera_back_add = 'video_camera_back_add', + blockchain_transaction = 'blockchain_transaction', + display_external_input = 'display_external_input', + picture_in_picture_off = 'picture_in_picture_off', + video_camera_front_off = 'video_camera_front_off', + phone_bluetooth_speaker = 'phone_bluetooth_speaker', + desktop_access_disabled = 'desktop_access_disabled', + blockchain_smart_contract = 'blockchain_smart_contract', + blockchain_contract_address = 'blockchain_contract_address', +} + +export const defaultIcon = ''; + +export type XIconName = [keyof typeof XIconNames]; + +export type XIconIdentifier = XIconName | XIconNames | string; + +export type XIconsIndex = { + [key in keyof typeof XIconNames]?: string; +}; + +// +// Contains Path of Custom icons File Names ... +export const XProvidedIcons = { + tv: 'tv', + ol: 'ol', + ul: 'ul', + add: 'add', + dns: 'dns', + dvr: 'dvr', + fax: 'fax', + mic: 'mic', + mop: 'mop', + sad: 'sad', + map: 'map', + mail: 'mail', + undo: 'undo', + redo: 'redo', + home: 'home', + flag: 'flag', + save: 'save', + sync: 'sync', + play: 'play', + star: 'star', + cast: 'cast', + wifi: 'wifi', + call: 'call', + tabs: 'tabs', + stop: 'stop', + code: 'code', + bold: 'bold', + crop: 'crop', + web: 'globe', + link: 'link', + last: 'last', + next: 'next', + copy: 'copy', + phone: 'call', + guide: 'help', + reply: 'reply', + label: 'label', + visible: 'eye', + liked: 'heart', + mobile: 'call', + shop: 'basket', + event: 'pulse', + clear: 'close', + edit: 'create', + close: 'close', + enter: 'enter', + power: 'power', + pause: 'pause', + cloud: 'cloud', + toast: 'toast', + cards: 'cards', + mouse: 'mouse', + album: 'album', + badge: 'badge', + radio: 'radio', + watch: 'watch', + movie: 'movie', + image: 'image', + quote: 'quote', + touch: 'touch', + error: 'error', + first: 'first', + print: 'print', + cancel: 'close', + remove: 'trash', + terms: 'hammer', + input: 'pencil', + login: 'log-in', + posts: 'albums', + files: 'folder', + italic: 'italic', + locate: 'locate', + search: 'search', + job: 'megaphone', + list: 'list-box', + rewind: 'rewind', + school: 'school', + server: 'server', + repeat: 'repeat', + report: 'report', + policy: 'policy', + router: 'router', + shield: 'shield', + stylus: 'stylus', + tv_off: 'tv_off', + swords: 'swords', + nearby: 'nearby', + flight: 'flight', + memory: 'memory', + replay: 'replay', + unknown: 'attach', + video: 'videocam', + done: 'checkmark', + logout: 'log-out', + avatars: 'images', + account: 'person', + invite: 'contract', + archive: 'archive', + toolbar: 'toolbar', + dialogs: 'dialogs', + dialpad: 'dialpad', + vpn_key: 'vpn_key', + widgets: 'widgets', + passkey: 'passkey', + mic_off: 'mic_off', + monitor: 'monitor', + gpp_bad: 'gpp_bad', + charger: 'charger', + devices: 'devices', + sd_card: 'sd_card', + capture: 'capture', + live_tv: 'live_tv', + preview: 'preview', + heading: 'heading', + refresh: 'refresh', + lock: 'lock-closed', + unlock: 'lock-open', + un_checked: 'close', + survey: 'newspaper', + chat_box: 'chatbox', + verified: 'verified', + arrow_up: 'arrow-up', + business: 'business', + contrast: 'contrast', + first_name: 'person', + calendar: 'calendar', + checked: 'checkmark', + dashboard: 'journal', + explorer: 'browsers', + settings: 'settings', + bookmark: 'bookmark', + dropdown: 'dropdown', + add_call: 'add_call', + sim_card: 'sim_card', + security: 'security', + password: 'password', + podcasts: 'podcasts', + wifi_off: 'wifi_off', + no_sound: 'no_sound', + keyboard: 'keyboard', + exercise: 'exercise', + computer: 'computer', + videocam: 'videocam', + autoplay: 'autoplay', + autostop: 'autostop', + logo_npm: 'logo-npm', + logo_pwa: 'logo-pwa', + logo_rss: 'logo-rss', + logo_usd: 'logo-usd', + logo_vue: 'logo-vue', + previous: 'previous', + location: 'location', + language: 'language', + document: 'document', + expand: 'expand_more', + friendships: 'people', + user: 'person-circle', + video_cam: 'videocam', + share: 'share-social', + deselect_all: 'close', + label_off: 'label_off', + visible_off: 'eye-off', + users: 'people-circle', + checkmark: 'checkmark', + star_half: 'star-half', + bookmarks: 'bookmarks', + thumbs_up: 'thumbs-up', + bluetooth: 'bluetooth', + logo_css3: 'logo-css3', + logo_edge: 'logo-edge', + logo_sass: 'logo-sass', + voicemail: 'voicemail', + call_made: 'call_made', + touch_app: 'touch_app', + subtitles: 'subtitles', + music_off: 'music_off', + encrypted: 'encrypted', + apartment: 'apartment', + dark_mode: 'dark_mode', + deskphone: 'deskphone', + autopause: 'autopause', + upload: 'cloud-upload', + audio: 'musical-notes', + clipboard: 'clipboard', + analytics: 'analytics', + select_all: 'checkbox', + management: 'construct', + unliked: 'heart-outline', + arrow_down: 'arrow-down', + arrow_back: 'arrow-back', + profile: 'person-circle', + register: 'finger-print', + customers_club: 'trophy', + volume_off: 'volume-off', + volume_low: 'volume-low', + logo_html5: 'logo-html5', + logo_ionic: 'logo-ionic', + logo_apple: 'logo-apple', + logo_react: 'logo-react', + logo_skype: 'logo-skype', + logo_slack: 'logo-slack', + logo_yahoo: 'logo-yahoo', + blockchain: 'blockchain', + data_table: 'data_table', + device_hub: 'device_hub', + call_split: 'call_split', + router_off: 'router_off', + light_mode: 'light_mode', + mouse_lock: 'mouse_lock', + nightlight: 'nightlight', + play_arrow: 'play_arrow', + video_call: 'video_call', + video_chat: 'video_chat', + video_file: 'video_file', + voice_chat: 'voice_chat', + fit_screen: 'fit_screen', + fullscreen: 'fullscreen', + friendship_reuest: 'add', + arrow_dropup: 'caret-up', + collapse: 'chevron_right', + contacts: 'people-circle', + friendship_block: 'block', + chat_bubble: 'chatbubble', + info: 'information-circle', + rotate_left: 'rotate_left', + swap_vert: 'swap-vertical', + friendship_friend: 'forum', + download: 'cloud-download', + thumbs_down: 'thumbs-down', + stats_chart: 'stats-chart', + volume_high: 'volume-high', + volume_mute: 'volume-mute', + play_circle: 'play-circle', + logo_amazon: 'logo-amazon', + logo_chrome: 'logo-chrome', + logo_docker: 'logo-docker', + logo_github: 'logo-github', + logo_gitlab: 'logo-gitlab', + logo_google: 'logo-google', + logo_paypal: 'logo-paypal', + blockchain1: 'blockchain1', + logo_nodejs: 'logo-nodejs', + voicemail_2: 'voicemail_2', + devices_off: 'devices_off', + exit_to_app: 'exit_to_app', + ring_volume: 'ring_volume', + call_missed: 'call_missed', + privacy_tip: 'privacy_tip', + dual_screen: 'dual_screen', + engineering: 'engineering', + fast_rewind: 'fast_rewind', + reset_password: 'construct', + chat_bubbles: 'chatbubbles', + friendship_reject: 'remove', + friendship_follower: 'chat', + rotate_right: 'rotate_right', + logo_android: 'logo-android', + logo_angular: 'logo-angular', + logo_bitcoin: 'logo-bitcoin', + logo_dropbox: 'logo-dropbox', + logo_firefox: 'logo-firefox', + logo_twitter: 'logo-twitter', + logo_youtube: 'logo-youtube', + logo_windows: 'logo-windows', + wifi_calling: 'wifi_calling', + phone_locked: 'phone_locked', + phone_missed: 'phone_missed', + call_quality: 'call_quality', + screen_share: 'screen_share', + room_service: 'room_service', + other_houses: 'other_houses', + dock_to_left: 'dock_to_left', + keyboard_off: 'keyboard_off', + cameraswitch: 'cameraswitch', + architecture: 'architecture', + fast_forward: 'fast_forward', + arrow_dropdown: 'caret-down', + string_resources: 'pricetags', + swap_horiz: 'swap-horizontal', + arrow_forward: 'arrow-forward', + password_change: 'lock-closed', + cloud_offline: 'cloud-offline', + accessibility: 'accessibility', + logo_electron: 'logo-electron', + logo_facebook: 'logo-facebook', + logo_firebase: 'logo-firebase', + logo_linkedin: 'logo-linkedin', + logo_markdown: 'logo-markdown', + logo_whatsapp: 'logo-whatsapp', + phone_enabled: 'phone_enabled', + phone_in_talk: 'phone_in_talk', + call_received: 'call_received', + print_connect: 'print_connect', + screen_record: 'screen_record', + shield_person: 'shield_person', + verified_user: 'verified_user', + network_check: 'network_check', + encrypted_off: 'encrypted_off', + dock_to_right: 'dock_to_right', + electric_bolt: 'electric_bolt', + color_palette: 'color-palette', + friendship_accept: 'checkmark', + friendship_following: 'comment', + checkmark_done: 'checkmark-done', + logo_instagram: 'logo-instagram', + logo_wordpress: 'logo-wordpress', + devices_fold_2: 'devices_fold_2', + perm_phone_msg: 'perm_phone_msg', + phone_callback: 'phone_callback', + phone_disabled: 'phone_disabled', + settings_phone: 'settings_phone', + print_disabled: 'print_disabled', + wifi_tethering: 'wifi_tethering', + speech_to_text: 'speech_to_text', + mouse_lock_off: 'mouse_lock_off', + directions_car: 'directions_car', + local_shipping: 'local_shipping', + cast_connected: 'cast_connected', + voice_chat_off: 'voice_chat_off', + logo_javascript: 'logo-javascript', + phone_forwarded: 'phone_forwarded', + menu_vertical: 'ellipsis-vertical', + chatbox_ellipses: 'chatbox-ellipses', + checkmark_circle: 'checkmark-circle', + blockchain_block: 'blockchain-block', + blockchain_token: 'blockchain-token', + document_scanner: 'document_scanner', + splitscreen_left: 'splitscreen_left', + close_fullscreen: 'close_fullscreen', + friendship_unblock: 'thumbs_up_down', + friendship_unfollow: 'highlight_off', + select_all_group: 'select_all_group', + stop_screen_share: 'stop_screen_share', + missed_video_call: 'missed_video_call', + keyboard_lock_off: 'keyboard_lock_off', + video_camera_back: 'video_camera_back', + menu_horizontal: 'ellipsis-horizontal', + logo_stackoverflow: 'logo-stackoverflow', + blockchain_address: 'blockchain-address', + logo_web_component: 'logo-web-component', + wifi_calling_bar_1: 'wifi_calling_bar_1', + wifi_calling_bar_2: 'wifi_calling_bar_2', + video_camera_front: 'video_camera_front', + picture_in_picture: 'picture_in_picture', + deselect_all_group: 'deselect_all_group', + logo_apple_appstore: 'logo-apple-appstore', + signal_cellular_alt: 'signal_cellular_alt', + signal_disconnected: 'signal_disconnected', + friendship_cancel: 'remove_circle_outline', + inverse_selection: 'remove-circle-outline', + chat_bubble_ellipses: 'chatbubble-ellipses', + call_missed_outgoing: 'call_missed_outgoing', + audio_video_receiver: 'audio_video_receiver', + admin_panel_settings: 'admin_panel_settings', + friendship_unknown: 'radio_button_unchecked', + checkmark_done_circle: 'checkmark-done-circle', + video_camera_back_add: 'video_camera_back_add', + logo_google_playstore: 'logo-google-playstore', + blockchain_transaction: 'blockchain-transaction', + video_camera_front_off: 'video_camera_front_off', + display_external_input: 'display_external_input', + picture_in_picture_off: 'picture_in_picture_off', + phone_bluetooth_speaker: 'phone_bluetooth_speaker', + desktop_access_disabled: 'desktop_access_disabled', + blockchain_smart_contract: 'blockchain-smart-contract', + blockchain_contract_address: 'blockchain-contract-address', +}; + +export const XCustomIcons: string[] = [ + XIconNames.ol, + XIconNames.ul, + XIconNames.tv, + XIconNames.dns, + XIconNames.dvr, + XIconNames.fax, + XIconNames.mic, + XIconNames.mop, + XIconNames.call, + XIconNames.tabs, + XIconNames.cast, + XIconNames.wifi, + XIconNames.stop, + XIconNames.cast, + XIconNames.link, + XIconNames.bold, + XIconNames.code, + XIconNames.undo, + XIconNames.redo, + XIconNames.next, + XIconNames.last, + XIconNames.list, + XIconNames.toast, + XIconNames.cards, + XIconNames.mouse, + XIconNames.album, + XIconNames.badge, + XIconNames.radio, + XIconNames.watch, + XIconNames.guide, + XIconNames.image, + XIconNames.quote, + XIconNames.touch, + XIconNames.error, + XIconNames.first, + XIconNames.reply, + XIconNames.label, + XIconNames.movie, + XIconNames.repeat, + XIconNames.report, + XIconNames.policy, + XIconNames.router, + XIconNames.shield, + XIconNames.stylus, + XIconNames.tv_off, + XIconNames.swords, + XIconNames.nearby, + XIconNames.flight, + XIconNames.memory, + XIconNames.replay, + XIconNames.expand, + XIconNames.italic, + XIconNames.toolbar, + XIconNames.devices, + XIconNames.dialogs, + XIconNames.dialpad, + XIconNames.vpn_key, + XIconNames.widgets, + XIconNames.passkey, + XIconNames.mic_off, + XIconNames.monitor, + XIconNames.gpp_bad, + XIconNames.charger, + XIconNames.devices, + XIconNames.sd_card, + XIconNames.capture, + XIconNames.live_tv, + XIconNames.monitor, + XIconNames.preview, + XIconNames.heading, + XIconNames.dropdown, + XIconNames.add_call, + XIconNames.sim_card, + XIconNames.security, + XIconNames.password, + XIconNames.podcasts, + XIconNames.wifi_off, + XIconNames.no_sound, + XIconNames.keyboard, + XIconNames.exercise, + XIconNames.computer, + XIconNames.videocam, + XIconNames.autoplay, + XIconNames.autostop, + XIconNames.previous, + XIconNames.collapse, + XIconNames.verified, + XIconNames.touch_app, + XIconNames.voicemail, + XIconNames.call_made, + XIconNames.subtitles, + XIconNames.music_off, + XIconNames.encrypted, + XIconNames.apartment, + XIconNames.dark_mode, + XIconNames.deskphone, + XIconNames.autopause, + XIconNames.label_off, + XIconNames.data_table, + XIconNames.device_hub, + XIconNames.call_split, + XIconNames.router_off, + XIconNames.light_mode, + XIconNames.mouse_lock, + XIconNames.nightlight, + XIconNames.play_arrow, + XIconNames.video_call, + XIconNames.video_chat, + XIconNames.video_file, + XIconNames.voice_chat, + XIconNames.fit_screen, + XIconNames.fullscreen, + XIconNames.blockchain, + XIconNames.voicemail_2, + XIconNames.devices_off, + XIconNames.exit_to_app, + XIconNames.ring_volume, + XIconNames.call_missed, + XIconNames.privacy_tip, + XIconNames.dual_screen, + XIconNames.engineering, + XIconNames.fast_rewind, + XIconNames.blockchain1, + XIconNames.logo_docker, + XIconNames.logo_gitlab, + XIconNames.logo_paypal, + XIconNames.rotate_left, + XIconNames.wifi_calling, + XIconNames.phone_locked, + XIconNames.phone_missed, + XIconNames.call_quality, + XIconNames.screen_share, + XIconNames.room_service, + XIconNames.other_houses, + XIconNames.dock_to_left, + XIconNames.keyboard_off, + XIconNames.cameraswitch, + XIconNames.architecture, + XIconNames.rotate_right, + XIconNames.fast_forward, + XIconNames.phone_enabled, + XIconNames.phone_in_talk, + XIconNames.call_received, + XIconNames.print_connect, + XIconNames.screen_record, + XIconNames.shield_person, + XIconNames.verified_user, + XIconNames.network_check, + XIconNames.encrypted_off, + XIconNames.dock_to_right, + XIconNames.electric_bolt, + XIconNames.devices_fold_2, + XIconNames.perm_phone_msg, + XIconNames.phone_callback, + XIconNames.phone_disabled, + XIconNames.settings_phone, + XIconNames.print_disabled, + XIconNames.wifi_tethering, + XIconNames.speech_to_text, + XIconNames.mouse_lock_off, + XIconNames.directions_car, + XIconNames.local_shipping, + XIconNames.cast_connected, + XIconNames.voice_chat_off, + XIconNames.phone_forwarded, + XIconNames.document_scanner, + XIconNames.splitscreen_left, + XIconNames.close_fullscreen, + XIconNames.blockchain_block, + XIconNames.blockchain_token, + XIconNames.select_all_group, + XIconNames.friendship_block, + XIconNames.stop_screen_share, + XIconNames.missed_video_call, + XIconNames.keyboard_lock_off, + XIconNames.video_camera_back, + XIconNames.friendship_friend, + XIconNames.friendship_reject, + XIconNames.friendship_cancel, + XIconNames.wifi_calling_bar_1, + XIconNames.wifi_calling_bar_2, + XIconNames.video_camera_front, + XIconNames.picture_in_picture, + XIconNames.blockchain_address, + XIconNames.deselect_all_group, + XIconNames.friendship_unblock, + XIconNames.friendship_unknown, + XIconNames.signal_cellular_alt, + XIconNames.signal_disconnected, + XIconNames.friendship_follower, + XIconNames.friendship_unfollow, + XIconNames.call_missed_outgoing, + XIconNames.audio_video_receiver, + XIconNames.admin_panel_settings, + XIconNames.friendship_following, + XIconNames.video_camera_back_add, + XIconNames.video_camera_front_off, + XIconNames.display_external_input, + XIconNames.picture_in_picture_off, + XIconNames.blockchain_transaction, + XIconNames.phone_bluetooth_speaker, + XIconNames.desktop_access_disabled, + XIconNames.blockchain_smart_contract, + XIconNames.blockchain_contract_address, +]; + +export type XIconSlot = 'start' | 'end'; + +// +//#region Container ... +export interface IXIconContainer extends IXBaseContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + slot: XStandardType; + name: XStandardType; + + // + color: XStandardType; + foregroundColor: XStandardType; + + // + applyDirection: XStandardType; + + // + selectable: XStandardType; + clickable: XStandardType; + clicked: EventEmitter; +} + +@Injectable({ + providedIn: 'root', +}) +export class XIconContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + readonly actionProvider: Subject = + new Subject(); + + readonly cssClass: XStandardType>; + + // + readonly slot: XStandardType = 'start'; + readonly name: XStandardType; + + // + readonly color: XStandardType; + readonly foregroundColor: XStandardType = true; + + readonly selectable: XStandardType = false; + readonly clickable: XStandardType = false; + readonly applyDirection: XStandardType = false; + + // + readonly clicked = new EventEmitter(); + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XIconContainerComponent + extends XBaseContainerComponent + implements IXIconContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + slot: XStandardType = this.propertyProvider.slot; + + @Input() + name: XStandardType = this.propertyProvider.name; + + @Input() + color: XStandardType = this.propertyProvider.color; + + @Input() + foregroundColor: XStandardType = + this.propertyProvider.foregroundColor; + + @Input() + selectable: XStandardType = this.propertyProvider.selectable; + + @Input() + clickable: XStandardType = this.propertyProvider.clickable; + + @Input() + applyDirection: XStandardType = this.propertyProvider.applyDirection; + + @Output() + clicked: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XIconContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXIconContainer + ): Observable { + return forkJoin({ + // + cssClass: this.getArrayValue( + container ? container.cssClass : this.cssClass + ), + + // + slot: this.getValue(container ? container.slot : this.slot), + name: this.getValue(container ? container.name : this.name), + color: this.getValue(container ? container.color : this.color), + foregroundColor: this.getValue( + container ? container.foregroundColor : this.foregroundColor + ), + selectable: this.getValue( + container ? container.selectable : this.selectable + ), + clickable: this.getValue( + container ? container.clickable : this.clickable + ), + applyDirection: this.getValue( + container ? container.applyDirection : this.applyDirection + ), + }).pipe( + map((data) => { + return { + // + ...data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + clicked: this.clicked, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-ide\x-ide.component.html + + + + + + + + +
+ +
+ + + +
+
+ + + +
+ + + + + + + +
+ {{ tab.name }} +
+
+
+
+ + +
+
+
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-ide\x-ide.component.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-ide\x-ide.component.ts + +import { + Component, + ViewChild, + ElementRef, + HostListener, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + XIDEMode, + XIDETheme, + XIDEAction, + XIDEActionModel, + XIDEMinFontSize, + XIDEMaxFontSize, + XIDEActionBarItem, + XIDETabDescriptor, + XIDEModeIdentifier, + XIDEDefaultTabName, + XIDEThemeIdentifier, + XIDEContainerComponent, +} from './x-ide.typings'; +import { + nameof, + toArray, + isNumber, + toBoolean, + isBoolean, + countChild, + XStandardType, + toNormalString, + XOneOrManyType, + isNullOrUndefined, + isNullOrEmptyString, + XColorWithBrightness, +} from 'x-framework-core'; +import { + XTabAlign, + XTabsAction, + XTabChangeEvent, + XTabsActionModel, + XTabsHeaderPosition, +} from '../x-tabs/x-tabs.typings'; +import * as ace from 'ace-builds'; +import { + XActionBarItem, + XActionBarItemSlot, + XActionBarActionFiredModel, +} from '../x-action-bar/x-action-bar.typings'; +import { concatMap, map } from 'rxjs/operators'; +import 'ace-builds/src-noconflict/ext-beautify'; +import { XIconNames } from '../x-icon/x-icon.typings'; +import 'ace-builds/src-noconflict/ext-language_tools'; +import { forkJoin, Subject, Subscription } from 'rxjs'; +import { XPopoverOption } from '../dialogs/x-popover/x-popover.typings'; + +// +//#region Themes ... +import 'ace-builds/src-noconflict/theme-ambiance'; +import 'ace-builds/src-noconflict/theme-chaos'; +import 'ace-builds/src-noconflict/theme-chrome'; +import 'ace-builds/src-noconflict/theme-cloud9_day'; +import 'ace-builds/src-noconflict/theme-cloud9_night'; +import 'ace-builds/src-noconflict/theme-cloud9_night_low_color'; +import 'ace-builds/src-noconflict/theme-clouds'; +import 'ace-builds/src-noconflict/theme-clouds_midnight'; +import 'ace-builds/src-noconflict/theme-cobalt'; +import 'ace-builds/src-noconflict/theme-crimson_editor'; +import 'ace-builds/src-noconflict/theme-dawn'; +import 'ace-builds/src-noconflict/theme-dracula'; +import 'ace-builds/src-noconflict/theme-dreamweaver'; +import 'ace-builds/src-noconflict/theme-eclipse'; +import 'ace-builds/src-noconflict/theme-github'; +import 'ace-builds/src-noconflict/theme-gob'; +import 'ace-builds/src-noconflict/theme-gruvbox'; +import 'ace-builds/src-noconflict/theme-gruvbox_dark_hard'; +import 'ace-builds/src-noconflict/theme-gruvbox_light_hard'; +import 'ace-builds/src-noconflict/theme-idle_fingers'; +import 'ace-builds/src-noconflict/theme-iplastic'; +import 'ace-builds/src-noconflict/theme-katzenmilch'; +import 'ace-builds/src-noconflict/theme-kr_theme'; +import 'ace-builds/src-noconflict/theme-kuroir'; +import 'ace-builds/src-noconflict/theme-merbivore'; +import 'ace-builds/src-noconflict/theme-merbivore_soft'; +import 'ace-builds/src-noconflict/theme-mono_industrial'; +import 'ace-builds/src-noconflict/theme-monokai'; +import 'ace-builds/src-noconflict/theme-nord_dark'; +import 'ace-builds/src-noconflict/theme-one_dark'; +import 'ace-builds/src-noconflict/theme-pastel_on_dark'; +import 'ace-builds/src-noconflict/theme-solarized_dark'; +import 'ace-builds/src-noconflict/theme-solarized_light'; +import 'ace-builds/src-noconflict/theme-sqlserver'; +import 'ace-builds/src-noconflict/theme-terminal'; +import 'ace-builds/src-noconflict/theme-textmate'; +import 'ace-builds/src-noconflict/theme-tomorrow'; +import 'ace-builds/src-noconflict/theme-tomorrow_night'; +import 'ace-builds/src-noconflict/theme-tomorrow_night_blue'; +import 'ace-builds/src-noconflict/theme-tomorrow_night_bright'; +import 'ace-builds/src-noconflict/theme-tomorrow_night_eighties'; +import 'ace-builds/src-noconflict/theme-twilight'; +import 'ace-builds/src-noconflict/theme-vibrant_ink'; +import 'ace-builds/src-noconflict/theme-xcode'; +//#endregion + +// +//#region Modes ... +import 'ace-builds/src-noconflict/mode-abap'; +import 'ace-builds/src-noconflict/mode-abc'; +import 'ace-builds/src-noconflict/mode-actionscript'; +import 'ace-builds/src-noconflict/mode-ada'; +import 'ace-builds/src-noconflict/mode-alda'; +import 'ace-builds/src-noconflict/mode-apache_conf'; +import 'ace-builds/src-noconflict/mode-apex'; +import 'ace-builds/src-noconflict/mode-applescript'; +import 'ace-builds/src-noconflict/mode-aql'; +import 'ace-builds/src-noconflict/mode-asciidoc'; +import 'ace-builds/src-noconflict/mode-asl'; +import 'ace-builds/src-noconflict/mode-assembly_x86'; +import 'ace-builds/src-noconflict/mode-autohotkey'; +import 'ace-builds/src-noconflict/mode-batchfile'; +import 'ace-builds/src-noconflict/mode-c9search'; +import 'ace-builds/src-noconflict/mode-c_cpp'; +import 'ace-builds/src-noconflict/mode-cirru'; +import 'ace-builds/src-noconflict/mode-clojure'; +import 'ace-builds/src-noconflict/mode-cobol'; +import 'ace-builds/src-noconflict/mode-coffee'; +import 'ace-builds/src-noconflict/mode-coldfusion'; +import 'ace-builds/src-noconflict/mode-crystal'; +import 'ace-builds/src-noconflict/mode-csharp'; +import 'ace-builds/src-noconflict/mode-csound_document'; +import 'ace-builds/src-noconflict/mode-csound_orchestra'; +import 'ace-builds/src-noconflict/mode-csound_score'; +import 'ace-builds/src-noconflict/mode-csp'; +import 'ace-builds/src-noconflict/mode-css'; +import 'ace-builds/src-noconflict/mode-curly'; +import 'ace-builds/src-noconflict/mode-d'; +import 'ace-builds/src-noconflict/mode-dart'; +import 'ace-builds/src-noconflict/mode-diff'; +import 'ace-builds/src-noconflict/mode-django'; +import 'ace-builds/src-noconflict/mode-dockerfile'; +import 'ace-builds/src-noconflict/mode-dot'; +import 'ace-builds/src-noconflict/mode-drools'; +import 'ace-builds/src-noconflict/mode-edifact'; +import 'ace-builds/src-noconflict/mode-eiffel'; +import 'ace-builds/src-noconflict/mode-ejs'; +import 'ace-builds/src-noconflict/mode-elixir'; +import 'ace-builds/src-noconflict/mode-elm'; +import 'ace-builds/src-noconflict/mode-erlang'; +import 'ace-builds/src-noconflict/mode-forth'; +import 'ace-builds/src-noconflict/mode-fortran'; +import 'ace-builds/src-noconflict/mode-fsharp'; +import 'ace-builds/src-noconflict/mode-fsl'; +import 'ace-builds/src-noconflict/mode-ftl'; +import 'ace-builds/src-noconflict/mode-gcode'; +import 'ace-builds/src-noconflict/mode-gherkin'; +import 'ace-builds/src-noconflict/mode-gitignore'; +import 'ace-builds/src-noconflict/mode-glsl'; +import 'ace-builds/src-noconflict/mode-gobstones'; +import 'ace-builds/src-noconflict/mode-golang'; +import 'ace-builds/src-noconflict/mode-graphqlschema'; +import 'ace-builds/src-noconflict/mode-groovy'; +import 'ace-builds/src-noconflict/mode-haml'; +import 'ace-builds/src-noconflict/mode-handlebars'; +import 'ace-builds/src-noconflict/mode-haskell'; +import 'ace-builds/src-noconflict/mode-haskell_cabal'; +import 'ace-builds/src-noconflict/mode-haxe'; +import 'ace-builds/src-noconflict/mode-hjson'; +import 'ace-builds/src-noconflict/mode-html'; +import 'ace-builds/src-noconflict/mode-html_elixir'; +import 'ace-builds/src-noconflict/mode-html_ruby'; +import 'ace-builds/src-noconflict/mode-ini'; +import 'ace-builds/src-noconflict/mode-io'; +import 'ace-builds/src-noconflict/mode-ion'; +import 'ace-builds/src-noconflict/mode-jack'; +import 'ace-builds/src-noconflict/mode-jade'; +import 'ace-builds/src-noconflict/mode-java'; +import 'ace-builds/src-noconflict/mode-javascript'; +import 'ace-builds/src-noconflict/mode-json'; +import 'ace-builds/src-noconflict/mode-json5'; +import 'ace-builds/src-noconflict/mode-jsoniq'; +import 'ace-builds/src-noconflict/mode-jsp'; +import 'ace-builds/src-noconflict/mode-jssm'; +import 'ace-builds/src-noconflict/mode-jsx'; +import 'ace-builds/src-noconflict/mode-julia'; +import 'ace-builds/src-noconflict/mode-kotlin'; +import 'ace-builds/src-noconflict/mode-latex'; +import 'ace-builds/src-noconflict/mode-latte'; +import 'ace-builds/src-noconflict/mode-less'; +import 'ace-builds/src-noconflict/mode-liquid'; +import 'ace-builds/src-noconflict/mode-lisp'; +import 'ace-builds/src-noconflict/mode-livescript'; +import 'ace-builds/src-noconflict/mode-logiql'; +import 'ace-builds/src-noconflict/mode-logtalk'; +import 'ace-builds/src-noconflict/mode-lsl'; +import 'ace-builds/src-noconflict/mode-lua'; +import 'ace-builds/src-noconflict/mode-luapage'; +import 'ace-builds/src-noconflict/mode-lucene'; +import 'ace-builds/src-noconflict/mode-makefile'; +import 'ace-builds/src-noconflict/mode-markdown'; +import 'ace-builds/src-noconflict/mode-mask'; +import 'ace-builds/src-noconflict/mode-matlab'; +import 'ace-builds/src-noconflict/mode-maze'; +import 'ace-builds/src-noconflict/mode-mediawiki'; +import 'ace-builds/src-noconflict/mode-mel'; +import 'ace-builds/src-noconflict/mode-mips'; +import 'ace-builds/src-noconflict/mode-mixal'; +import 'ace-builds/src-noconflict/mode-mushcode'; +import 'ace-builds/src-noconflict/mode-mysql'; +import 'ace-builds/src-noconflict/mode-nginx'; +import 'ace-builds/src-noconflict/mode-nim'; +import 'ace-builds/src-noconflict/mode-nix'; +import 'ace-builds/src-noconflict/mode-nsis'; +import 'ace-builds/src-noconflict/mode-nunjucks'; +import 'ace-builds/src-noconflict/mode-objectivec'; +import 'ace-builds/src-noconflict/mode-ocaml'; +import 'ace-builds/src-noconflict/mode-partiql'; +import 'ace-builds/src-noconflict/mode-pascal'; +import 'ace-builds/src-noconflict/mode-perl'; +import 'ace-builds/src-noconflict/mode-pgsql'; +import 'ace-builds/src-noconflict/mode-php'; +import 'ace-builds/src-noconflict/mode-php_laravel_blade'; +import 'ace-builds/src-noconflict/mode-pig'; +import 'ace-builds/src-noconflict/mode-plain_text'; +import 'ace-builds/src-noconflict/mode-powershell'; +import 'ace-builds/src-noconflict/mode-praat'; +import 'ace-builds/src-noconflict/mode-prisma'; +import 'ace-builds/src-noconflict/mode-prolog'; +import 'ace-builds/src-noconflict/mode-properties'; +import 'ace-builds/src-noconflict/mode-protobuf'; +import 'ace-builds/src-noconflict/mode-puppet'; +import 'ace-builds/src-noconflict/mode-python'; +import 'ace-builds/src-noconflict/mode-qml'; +import 'ace-builds/src-noconflict/mode-r'; +import 'ace-builds/src-noconflict/mode-raku'; +import 'ace-builds/src-noconflict/mode-razor'; +import 'ace-builds/src-noconflict/mode-rdoc'; +import 'ace-builds/src-noconflict/mode-red'; +import 'ace-builds/src-noconflict/mode-redshift'; +import 'ace-builds/src-noconflict/mode-rhtml'; +import 'ace-builds/src-noconflict/mode-robot'; +import 'ace-builds/src-noconflict/mode-rst'; +import 'ace-builds/src-noconflict/mode-ruby'; +import 'ace-builds/src-noconflict/mode-rust'; +import 'ace-builds/src-noconflict/mode-sac'; +import 'ace-builds/src-noconflict/mode-sass'; +import 'ace-builds/src-noconflict/mode-scad'; +import 'ace-builds/src-noconflict/mode-scala'; +import 'ace-builds/src-noconflict/mode-scheme'; +import 'ace-builds/src-noconflict/mode-scrypt'; +import 'ace-builds/src-noconflict/mode-scss'; +import 'ace-builds/src-noconflict/mode-sh'; +import 'ace-builds/src-noconflict/mode-sjs'; +import 'ace-builds/src-noconflict/mode-slim'; +import 'ace-builds/src-noconflict/mode-smarty'; +import 'ace-builds/src-noconflict/mode-smithy'; +import 'ace-builds/src-noconflict/mode-snippets'; +import 'ace-builds/src-noconflict/mode-soy_template'; +import 'ace-builds/src-noconflict/mode-space'; +import 'ace-builds/src-noconflict/mode-sparql'; +import 'ace-builds/src-noconflict/mode-sql'; +import 'ace-builds/src-noconflict/mode-sqlserver'; +import 'ace-builds/src-noconflict/mode-stylus'; +import 'ace-builds/src-noconflict/mode-svg'; +import 'ace-builds/src-noconflict/mode-swift'; +import 'ace-builds/src-noconflict/mode-tcl'; +import 'ace-builds/src-noconflict/mode-terraform'; +import 'ace-builds/src-noconflict/mode-tex'; +import 'ace-builds/src-noconflict/mode-text'; +import 'ace-builds/src-noconflict/mode-textile'; +import 'ace-builds/src-noconflict/mode-toml'; +import 'ace-builds/src-noconflict/mode-tsx'; +import 'ace-builds/src-noconflict/mode-turtle'; +import 'ace-builds/src-noconflict/mode-twig'; +import 'ace-builds/src-noconflict/mode-typescript'; +import 'ace-builds/src-noconflict/mode-vala'; +import 'ace-builds/src-noconflict/mode-vbscript'; +import 'ace-builds/src-noconflict/mode-velocity'; +import 'ace-builds/src-noconflict/mode-verilog'; +import 'ace-builds/src-noconflict/mode-vhdl'; +import 'ace-builds/src-noconflict/mode-visualforce'; +import 'ace-builds/src-noconflict/mode-wollok'; +import 'ace-builds/src-noconflict/mode-xml'; +import 'ace-builds/src-noconflict/mode-xquery'; +import 'ace-builds/src-noconflict/mode-yaml'; +import 'ace-builds/src-noconflict/mode-zeek'; +//#endregion + +@Component({ + selector: 'x-ide', + templateUrl: './x-ide.component.html', + styleUrls: ['./x-ide.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XIDEComponent extends XIDEContainerComponent { + // + //#region Props ... + // + //#region Private Props ... + private CODE_EDITOR_BEAUTIFY: any = null; + private CODE_EDITOR: ace.Ace.Editor = null; + private CODE_EDITOR_UNDO_MANAGER: any; + + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + private CONTENT_CHANGE_SUBJECT = new Subject(); + + // + readonly Modes = Object.assign({}, XIDEMode); + readonly Themes = Object.assign({}, XIDETheme); + readonly TabAligns = Object.assign({}, XTabAlign); + readonly IconNames = Object.assign({}, XIconNames); + readonly ColorNames = Object.assign({}, XColorWithBrightness); + readonly ActionBarItems = Object.assign({}, XIDEActionBarItem); + readonly ActionBarItemSlots = Object.assign({}, XActionBarItemSlot); + readonly TabHeaderPositions = Object.assign({}, XTabsHeaderPosition); + + // + readonly tabsActionProvider = new Subject(); + //#endregion + + // + @ViewChild('codeEditor') + codeEditorElmRef: ElementRef; + + @HostListener('resize', ['$event']) + onResize (event: any) { + // + if (isNullOrUndefined(this.CODE_EDITOR)) { + return; + } + + // + this.CODE_EDITOR.resize(); + } + + // + lastContent: string; + currentTab = XIDEDefaultTabName; + + // + readonly Sessions: Pick< + XIDETabDescriptor, + 'name' | 'session' | 'canSave' | 'canBeautify' + >[] = []; + //#endregion + + // + //#region Constructor ... + //#endregion + + // + //#region LifeCycles ... + afterViewInit () { + super.afterViewInit(); + + // + this.prepareCodeEditor(); + } + + async onChange (changeKeys: string[]): Promise { + // + //#region Action Provider Changed ... + // + const isActionProviderChaged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChaged) { + this.registerActionProvider(); + } + //#endregion + + // + //#region Theme Changed ... + const isThemeChanged = changeKeys.includes(nameof('theme')); + if (isThemeChanged) { + this.setTheme(this.theme); + } + //#endregion + + // + //#region Mode Changed ... + const isModeChanged = changeKeys.includes(nameof('mode')); + if (isModeChanged) { + this.setMode(this.mode); + } + //#endregion + + // + //#region MinLines Changed ... + const isMinLinesChanged = changeKeys.includes( + nameof('minLines') + ); + if (isMinLinesChanged) { + this.setMinLines(this.minLines); + } + //#endregion + + // + //#region MaxLines Changed ... + const isMaxLinesChanged = changeKeys.includes( + nameof('maxLines') + ); + if (isMaxLinesChanged) { + this.setMaxLines(this.maxLines); + } + //#endregion + + // + //#region FontSize Changed ... + const isFontSizeChanged = changeKeys.includes( + nameof('fontSize') + ); + if (isFontSizeChanged) { + this.setFontSize(this.fontSize); + } + //#endregion + + // + //#region Readonly Changed ... + const isReadonlyChanged = changeKeys.includes( + nameof('readonly') + ); + if (isReadonlyChanged) { + this.setReadOnly(this.readonly); + } + //#endregion + + // + //#region Content Changed ... + const isContentChanged = changeKeys.includes( + nameof('content') + ); + if (isContentChanged) { + this.setContent(this.content); + } + //#endregion + + // + //#region WordWrapping Changed ... + const isWordWrappingChanged = changeKeys.includes( + nameof('wordWrapping') + ); + if (isWordWrappingChanged) { + this.setWordWrapping(this.wordWrapping); + } + //#endregion + + // + //#region ShowPrintMargin Changed ... + const isShowPrintMarginChanged = changeKeys.includes( + nameof('showPrintMargin') + ); + if (isShowPrintMarginChanged) { + this.setShowPrintMargin(this.showPrintMargin); + } + //#endregion + + // + //#region HighlightActiveLine Changed ... + const isHighlightActiveLineChanged = changeKeys.includes( + nameof('highlightActiveLine') + ); + if (isHighlightActiveLineChanged) { + this.setHighlightActiveLine(this.highlightActiveLine); + } + //#endregion + + // + //#region BasicAutocompletion Changed ... + const isBasicAutocompletionChanged = changeKeys.includes( + nameof('basicAutocompletion') + ); + if (isBasicAutocompletionChanged) { + this.setBasicAutocompletion(this.basicAutocompletion); + } + //#endregion + + // + //#region DefaultTabName Changed ... + const isDefaultTabNameChanged = changeKeys.includes( + nameof('defaultTabName') + ); + if (isDefaultTabNameChanged) { + await this.handleDefaultTabNameChanged(this.defaultTabName); + } + //#endregion + + // + //#region ActionBar ... + const isDefaultActionBarItemColorChanged = changeKeys.includes( + nameof('defaultActionBarItemColor') + ); + const isAvailableActionBarItemsChanged = changeKeys.includes( + nameof('availableActionBarItems') + ); + if ( + isAvailableActionBarItemsChanged || + isDefaultActionBarItemColorChanged + ) { + // + if (isNullOrUndefined(this.availableActionBarItems)) { + this.availableActionBarItems = + this.propertyProvider.availableActionBarItems; + } + await this.prepareActionBar(); + } + //#endregion + } + + onDestroy () { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + //#endregion + + // + //#region Register Handlers ... + registerViewHandlers (): void { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + + // + this.applyTakeUntilWrapper( + this.CONTENT_CHANGE_SUBJECT.asObservable() + ).subscribe(async (content) => { + // + if (content === this.lastContent) { + return; + } + + // + this.lastContent = content; + this.updateCurrentTabDescriptor({ + canSave: true, + canBeautify: true, + }); + + // + this.contentChange.emit(content); + + // + await this.prepareActionBar(); + }); + } + //#endregion + + // + //#region Task Handlers ... + //#endregion + + // + //#region UI Providers ... + //#endregion + + // + //#region UI Handlers ... + async handleActionBarActionFired (model: XActionBarActionFiredModel) { + // + if (isNullOrUndefined(model) || isNullOrEmptyString(model.action)) { + return; + } + + // + switch (model.action) { + // + case this.ActionBarItems.ChangeMode: + this.handleActionModeFired(model.event); + break; + + // + case this.ActionBarItems.ChangeTheme: + this.handleActionThemFired(model.event); + break; + + // + case this.ActionBarItems.Close: + this.closeTab(this.currentTab); + break; + + // + case this.ActionBarItems.Undo: + this.undo(); + break; + + // + case this.ActionBarItems.Redo: + this.redo(); + break; + + // + case this.ActionBarItems.Beautify: + await this.beautifyCode(); + break; + + // + case this.ActionBarItems.Save: + // + this.updateCurrentTabDescriptor({ + canSave: false, + }); + this.prepareActionBar(); + break; + } + + // + this.actionBarActionFired.emit(model); + } + + async handleTabsSelectedTabChanged (event: XTabChangeEvent) { + // + if ( + event.index < 0 || + isNullOrUndefined(event) || + event.index > this.Sessions.length || + isNullOrUndefined(this.CODE_EDITOR) + ) { + return; + } + + // + const descriptor = this.Sessions[event.index]; + this.showTab(descriptor.name); + } + //#endregion + + // + //#region Actions ... + setTheme (theme: XStandardType) { + // + if (isNullOrUndefined(this.CODE_EDITOR)) { + return; + } + + // + this.getValue(theme).subscribe((value) => { + // + if (isNullOrEmptyString(value)) { + return; + } + + // + this.CODE_EDITOR.setTheme(value); + }); + } + + setMode (mode: XStandardType) { + // + if (isNullOrUndefined(this.CODE_EDITOR)) { + return; + } + + // + this.getValue(mode).subscribe((value) => { + // + if (isNullOrEmptyString(value)) { + return; + } + + // + this.CODE_EDITOR.getSession().setMode(value); + this.detectChanges(); + }); + } + + setMinLines (minLines: XStandardType) { + // + if (isNullOrUndefined(this.CODE_EDITOR)) { + return; + } + + // + this.getValue(minLines).subscribe((value) => { + // + if (value <= 0) { + return; + } + + // + this.CODE_EDITOR.setOption('minLines', value); + this.detectChanges(); + }); + } + + setMaxLines (maxLines: XStandardType) { + // + if (isNullOrUndefined(this.CODE_EDITOR)) { + return; + } + + // + this.getValue(maxLines).subscribe((value) => { + // + if (value <= 0) { + return; + } + + // + this.CODE_EDITOR.setOption('maxLines', value); + this.detectChanges(); + }); + } + + setFontSize (fontSize: XStandardType) { + // + if (isNullOrUndefined(this.CODE_EDITOR)) { + return; + } + + // + this.getValue(fontSize).subscribe((value) => { + // + if (value <= XIDEMinFontSize || value >= XIDEMaxFontSize) { + return; + } + + // + this.CODE_EDITOR.setFontSize(value); + this.detectChanges(); + }); + } + + setReadOnly (readonly: XStandardType) { + // + if (isNullOrUndefined(this.CODE_EDITOR)) { + return; + } + + // + this.getValue(readonly).subscribe((value) => { + // + this.CODE_EDITOR.setReadOnly(value); + this.detectChanges(); + }); + } + + getFontSize () { + // + if (isNullOrUndefined(this.CODE_EDITOR)) { + return ''; + } + + // + return this.CODE_EDITOR.getFontSize(); + } + + setHighlightActiveLine (highlightActiveLine: XStandardType) { + // + if (isNullOrUndefined(this.CODE_EDITOR)) { + return; + } + + // + this.getValue(highlightActiveLine).subscribe((value) => { + // + this.CODE_EDITOR.setOption('highlightActiveLine', value); + this.detectChanges(); + }); + } + + setBasicAutocompletion (basicAutocompletion: XStandardType) { + // + if (isNullOrUndefined(this.CODE_EDITOR)) { + return; + } + + // + this.getValue(basicAutocompletion).subscribe((value) => { + // + this.CODE_EDITOR.setOption('enableBasicAutocompletion' as any, value); + this.detectChanges(); + }); + } + + setWordWrapping (wordWrapping: XStandardType) { + // + if (isNullOrUndefined(this.CODE_EDITOR)) { + return; + } + + // + this.getValue(wordWrapping).subscribe((value) => { + // + this.CODE_EDITOR.getSession().setUseWrapMode(value); + this.detectChanges(); + }); + } + + setShowLineNumbers (showLineNumbers: XStandardType) { + // + if (isNullOrUndefined(this.CODE_EDITOR)) { + return; + } + + // + this.getValue(showLineNumbers).subscribe((value) => { + // + this.CODE_EDITOR.setOption('showLineNumbers', value); + this.detectChanges(); + }); + } + + setShowPrintMargin (showPrintMargin: XStandardType) { + // + if (isNullOrUndefined(this.CODE_EDITOR)) { + return; + } + + // + this.getValue(showPrintMargin).subscribe((value) => { + // + this.CODE_EDITOR.setShowPrintMargin(value); + this.detectChanges(); + }); + } + + setShowFoldWidgets (showFoldWidgets: XStandardType) { + // + if (isNullOrUndefined(this.CODE_EDITOR)) { + return; + } + + // + this.getValue(showFoldWidgets).subscribe((value) => { + // + this.CODE_EDITOR.setShowFoldWidgets(value); + this.detectChanges(); + }); + } + + // + //#region IDEEdit ... + async beautifyCode () { + // + if ( + isNullOrUndefined(this.CODE_EDITOR) || + isNullOrUndefined(this.CODE_EDITOR_BEAUTIFY) + ) { + return; + } + + // + const session = this.CODE_EDITOR.getSession(); + this.CODE_EDITOR_BEAUTIFY.beautify(session); + + // + this.updateCurrentTabDescriptor({ + canBeautify: false, + }); + await this.prepareActionBar(); + } + + getContent () { + // + if (isNullOrUndefined(this.CODE_EDITOR)) { + return ''; + } + + // + const result = this.CODE_EDITOR.getValue(); + return result; + } + + setContent (value: XStandardType) { + // + if (isNullOrUndefined(this.CODE_EDITOR)) { + return; + } + + // + this.getValue(value).subscribe((content) => { + // + if (isNullOrEmptyString(content)) { + return; + } + + // + if (value === this.getContent()) { + return; + } + + // + this.CODE_EDITOR.setValue(content); + }); + } + + clearcontent (force = false) { + // + if (isNullOrUndefined(this.CODE_EDITOR)) { + return; + } + + // + if (!!force) { + // + this.CODE_EDITOR.setValue(''); + this.detectChanges(); + } else { + // + this.managerService.dialogService.presentAreYouSureYesNoDialog(() => { + this.CODE_EDITOR.setValue(''); + this.detectChanges(); + }); + } + } + + goToLine (lineNumber: number) { + // + const totalLines = this.getTotalLines(); + if ( + lineNumber <= 0 || + totalLines <= 0 || + lineNumber > totalLines || + isNullOrUndefined(this.CODE_EDITOR) + ) { + return; + } + + // + this.CODE_EDITOR.gotoLine(lineNumber, 0, true); + } + + getTotalLines () { + // + if (isNullOrUndefined(this.CODE_EDITOR)) { + return -1; + } + + // + return this.CODE_EDITOR.getSession().getLength(); + } + + canUndo () { + // + if (isNullOrUndefined(this.CODE_EDITOR)) { + return false; + } + + // + const session = this.CODE_EDITOR.getSession(); + if (isNullOrUndefined(session)) { + return false; + } + + // + const undoManager = session.getUndoManager(); + if (isNullOrUndefined(undoManager)) { + return false; + } + + // + return undoManager.canUndo(); + } + + undo (dontSelect = true) { + // + if (isNullOrUndefined(this.CODE_EDITOR)) { + return; + } + + // + const session = this.CODE_EDITOR.getSession(); + if (isNullOrUndefined(session)) { + return; + } + + // + const undoManager = session.getUndoManager(); + if (isNullOrUndefined(undoManager) || !this.canUndo()) { + return; + } + + // + undoManager.undo(session, dontSelect); + this.prepareActionBar(); + } + + canRedo () { + // + if (isNullOrUndefined(this.CODE_EDITOR)) { + return false; + } + + // + const session = this.CODE_EDITOR.getSession(); + if (isNullOrUndefined(session)) { + return false; + } + + // + const undoManager = session.getUndoManager(); + if (isNullOrUndefined(undoManager)) { + return false; + } + + // + return undoManager.canRedo(); + } + + redo (dontSelect = true) { + // + if (isNullOrUndefined(this.CODE_EDITOR)) { + return; + } + + // + const session = this.CODE_EDITOR.getSession(); + if (isNullOrUndefined(session)) { + return; + } + + // + const undoManager = session.getUndoManager(); + if (isNullOrUndefined(undoManager) || !this.canRedo()) { + return; + } + + // + undoManager.redo(session, dontSelect); + this.prepareActionBar(); + } + + cleaEditorHistory () { + // + if (isNullOrUndefined(this.CODE_EDITOR)) { + return; + } + + // + const session = this.CODE_EDITOR.getSession(); + if (isNullOrUndefined(session)) { + return; + } + + // + const undoManager = session.getUndoManager(); + if (isNullOrUndefined(undoManager)) { + return; + } + + // + undoManager.reset(); + this.prepareActionBar(); + } + //#endregion + + // + //#region IDETabs ... + getTabDescritor (name: string) { + // + if ( + isNullOrEmptyString(name) || + !this.hasChild(this.Sessions) || + isNullOrUndefined(this.CODE_EDITOR) + ) { + return null; + } + + // + return this.Sessions.find( + (sd) => toNormalString(sd.name) === toNormalString(name) + ); + } + + getTabDescritorIndex (name: string) { + // + if ( + isNullOrEmptyString(name) || + !this.hasChild(this.Sessions) || + isNullOrUndefined(this.CODE_EDITOR) + ) { + return null; + } + + // + return this.Sessions.findIndex( + (sd) => toNormalString(sd.name) === toNormalString(name) + ); + } + + getCurrentTabDescritor () { + return this.getTabDescritor(this.currentTab); + } + + updateCurrentTabDescriptor ( + options: Pick + ) { + // + const currentTabDescriptor = this.getCurrentTabDescritor(); + const currentTabdescriptorIndex = this.Sessions.findIndex( + (sd) => toNormalString(sd.name) === toNormalString(this.currentTab) + ); + if ( + currentTabdescriptorIndex < 0 || + isNullOrUndefined(currentTabDescriptor) + ) { + return; + } + + // + const optionsKeys = Object.keys(options); + const hasCanSave = optionsKeys.includes( + nameof('canSave') + ); + const hasCanBeautify = optionsKeys.includes( + nameof('canBeautify') + ); + + // + const canSave = hasCanSave ? options.canSave : currentTabDescriptor.canSave; + const canBeautify = hasCanBeautify + ? options.canBeautify + : currentTabDescriptor.canBeautify; + + // + this.Sessions[currentTabdescriptorIndex] = { + ...currentTabDescriptor, + canSave, + canBeautify, + }; + + // + this.detectChanges(); + } + + addTab ( + tab: Pick + ) { + // + if (isNullOrUndefined(tab) || isNullOrUndefined(this.CODE_EDITOR)) { + return; + } + + // + // Set Default Content if not Provided ... + if (isNullOrEmptyString(tab.content)) { + tab.content = ''; + } + + // + // Set Default Mode if Not Provided ... + if ( + isNullOrEmptyString(tab.mode) || + isNullOrUndefined(tab.mode as XIDEMode) || + !isNullOrUndefined(this.getTabDescritor(tab.name)) + ) { + tab.mode = XIDEMode.Text; + } + + // + // Create Editor Session ... + const session = ace.createEditSession(tab.content, tab.mode as any); + const undoManager = new this.CODE_EDITOR_UNDO_MANAGER(); + + // + session.setUndoManager(undoManager); + undoManager.reset(); + + // + this.Sessions.push({ + session, + name: tab.name, + }); + + // + if (!!tab.forceShow) { + // + this.runTimedOut(() => { + // + this.tabsActionProvider.next({ + action: XTabsAction.TO, + payload: this.Sessions.length - 1, + }); + }, 500); + } + + // + this.detectChanges(); + } + + addTabs ( + tabs: XOneOrManyType> + ) { + // + if (!this.hasChild(tabs) || isNullOrUndefined(this.CODE_EDITOR)) { + return; + } + + // + toArray(tabs).forEach((tab) => + this.addTab({ + ...tab, + forceShow: false, + }) + ); + } + + showTab (name: string) { + // + if ( + isNullOrEmptyString(name) || + !this.hasChild(this.Sessions) || + isNullOrUndefined(this.CODE_EDITOR) + ) { + return; + } + + // + const tabDescriptor = this.getTabDescritor(name); + if (isNullOrUndefined(tabDescriptor)) { + return; + } + + // + this.currentTab = tabDescriptor.name; + this.lastContent = tabDescriptor.session.getValue(); + this.CODE_EDITOR.setSession(tabDescriptor.session); + + // + this.prepareActionBar(); + } + + closeTab (name: string, force = false) { + // + this.getValue(this.defaultTabName).subscribe((defaultTabName) => { + // + if ( + isNullOrEmptyString(name) || + !this.hasChild(this.Sessions) || + isNullOrUndefined(this.CODE_EDITOR) || + toNormalString(name) === toNormalString(defaultTabName) + ) { + return; + } + + // + const destinationTab = this.getTabDescritor(name); + const destinationTabIndex = this.getTabDescritorIndex(name); + if (isNullOrUndefined(destinationTab) || destinationTabIndex < 0) { + return; + } + + // + if (!!force || !destinationTab.canSave) { + // + this.handleRemoveTab(destinationTab.name, destinationTabIndex); + this.detectChanges(); + } else { + // + this.managerService.dialogService.presentAreYouSureYesNoDialog(() => { + // + this.handleRemoveTab(destinationTab.name, destinationTabIndex); + this.detectChanges(); + }); + } + }); + } + //#endregion + //#endregion + + // + //#region Abstracts ... + //#endregion + + // + //#region Private ... + // + //#region ActionProvider ... + private registerActionProvider () { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe((action) => { + // + if (isNullOrUndefined(action) || isNullOrEmptyString(action.action)) { + return; + } + + // + switch (action.action) { + // + // Undo ... + case XIDEAction.Undo: + // + const undoDontSelect = toBoolean(action.payload) || false; + this.undo(undoDontSelect); + break; + + // + // Redo ... + case XIDEAction.Redo: + // + const redoDontSelect = toBoolean(action.payload) || false; + this.redo(redoDontSelect); + break; + + // + // CleaEditorHistory ... + case XIDEAction.CleaEditorHistory: + this.cleaEditorHistory(); + break; + + // + // AddTab ... + case XIDEAction.AddTab: + // + const tabDescriptor = action.payload as XIDETabDescriptor; + if ( + !isNullOrUndefined(tabDescriptor) && + !isNullOrUndefined(action.payload) + ) { + this.addTab({ + ...tabDescriptor, + }); + } + break; + + // + // AddTabs ... + case XIDEAction.AddTabs: + // + const tabDescriptors = action.payload as XIDETabDescriptor[]; + if ( + this.hasChild(tabDescriptors) && + !isNullOrUndefined(action.payload) + ) { + this.addTabs([...tabDescriptors]); + } + break; + + // + // ShowTab ... + case XIDEAction.ShowTab: + // + const showTabDescriptor = action.payload as string; + if ( + !isNullOrEmptyString(showTabDescriptor) && + !isNullOrEmptyString(action.payload) + ) { + // + const index = this.getTabDescritorIndex(showTabDescriptor); + + // + this.runTimedOut(() => { + // + this.tabsActionProvider.next({ + action: XTabsAction.TO, + payload: index, + }); + }, 500).then(() => { + this.showTab(showTabDescriptor); + }); + } + break; + + // + // CloseTab ... + case XIDEAction.CloseTab: + // + const forceCloseTab = action.payload.force || false; + const closeTabDescriptor = action.payload.name as string; + if ( + !isNullOrEmptyString(closeTabDescriptor) && + !isNullOrEmptyString(action.payload) + ) { + this.closeTab(closeTabDescriptor, forceCloseTab); + } + break; + + // + // Refresh ... + case XIDEAction.Refresh: + this.detectChanges(); + break; + + // + // SetMode ... + case XIDEAction.SetMode: + // + const mode = action.payload as XIDEMode; + if ( + !isNullOrUndefined(mode) && + !isNullOrUndefined(action.payload) + ) { + this.setMode(mode); + } + break; + + // + // Beautify ... + case XIDEAction.Beautify: + this.beautifyCode(); + break; + + // + // SetTheme ... + case XIDEAction.SetTheme: + // + const theme = action.payload as XIDETheme; + if ( + !isNullOrUndefined(theme) && + !isNullOrUndefined(action.payload) + ) { + this.setTheme(theme); + } + break; + + // + // GoToLine ... + case XIDEAction.GoToLine: + // + const lineNumber = action.payload as number; + if ( + isNumber(action.payload) && + !isNullOrUndefined(action.payload) + ) { + this.goToLine(lineNumber); + } + break; + + // + // SetContent ... + case XIDEAction.SetContent: + // + const content = action.payload as string; + if ( + !isNullOrEmptyString(content) && + !isNullOrUndefined(action.payload) + ) { + this.setContent(content); + } + break; + + // + // SetMinLines ... + case XIDEAction.SetMinLines: + // + const minLines = action.payload as number; + if ( + isNumber(action.payload) && + !isNullOrUndefined(action.payload) + ) { + this.setMinLines(minLines); + } + break; + + // + // SetMaxLines ... + case XIDEAction.SetMaxLines: + // + const maxLines = action.payload as number; + if ( + isNumber(action.payload) && + !isNullOrUndefined(action.payload) + ) { + this.setMaxLines(maxLines); + } + break; + + // + // SetReadOnly ... + case XIDEAction.SetReadOnly: + // + const isReadonly = toBoolean(action.payload); + if ( + isBoolean(action.payload) && + !isNullOrUndefined(action.payload) + ) { + this.setReadOnly(isReadonly); + } + break; + + // + // SetFontSize ... + case XIDEAction.SetFontSize: + // + const fontSize = action.payload as number; + if ( + isNumber(action.payload) && + !isNullOrUndefined(action.payload) + ) { + this.setFontSize(fontSize); + } + break; + + // + // ClearContent ... + case XIDEAction.ClearContent: + // + const isForceClearContent = toBoolean(action.payload); + if ( + isBoolean(action.payload) && + !isNullOrUndefined(action.payload) + ) { + this.clearcontent(isForceClearContent); + } else { + this.clearcontent(); + } + break; + + // + // SetWordWrapping ... + case XIDEAction.SetWordWrapping: + // + const isWrapping = toBoolean(action.payload); + if ( + isBoolean(action.payload) && + !isNullOrUndefined(action.payload) + ) { + this.setWordWrapping(isWrapping); + } + break; + + // + // SetShowFoldWidgets ... + case XIDEAction.SetShowFoldWidgets: + // + const showFoldingWidgets = toBoolean(action.payload); + if ( + isBoolean(action.payload) && + !isNullOrUndefined(action.payload) + ) { + this.setReadOnly(showFoldingWidgets); + } + break; + + // + // SetShowLineNumbers ... + case XIDEAction.SetShowLineNumbers: + // + const showLineNumbers = toBoolean(action.payload); + if ( + isBoolean(action.payload) && + !isNullOrUndefined(action.payload) + ) { + this.setShowLineNumbers(showLineNumbers); + } + break; + + // + // SetShowPrintMargin ... + case XIDEAction.SetShowPrintMargin: + // + const showPrintMargin = toBoolean(action.payload); + if ( + isBoolean(action.payload) && + !isNullOrUndefined(action.payload) + ) { + this.setShowPrintMargin(showPrintMargin); + } + break; + + // + // SetHighlightActiveLine ... + case XIDEAction.SetHighlightActiveLine: + // + const highlightActiveLine = toBoolean(action.payload); + if ( + isBoolean(action.payload) && + !isNullOrUndefined(action.payload) + ) { + this.setHighlightActiveLine(highlightActiveLine); + } + break; + + // + // SetBasicAutocompletion ... + case XIDEAction.SetBasicAutocompletion: + // + const basicAutoCompletion = toBoolean(action.payload); + if ( + isBoolean(action.payload) && + !isNullOrUndefined(action.payload) + ) { + this.setBasicAutocompletion(basicAutoCompletion); + } + break; + } + }); + } + + private unregisterActionProvider () { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + private prepareCodeEditor () { + // + const element = this.codeEditorElmRef.nativeElement; + if (isNullOrUndefined(element)) { + return; + } + + // + this.CODE_EDITOR_BEAUTIFY = ace.require('ace/ext/beautify'); + this.CODE_EDITOR_UNDO_MANAGER = ace.require('ace/undomanager').UndoManager; + + // + this.CODE_EDITOR = ace.edit(element, { + minLines: 1, + maxLines: 1, + }); + + // + //#region Attach Undo Manager ... + this.CODE_EDITOR.getSession().setUndoManager( + new this.CODE_EDITOR_UNDO_MANAGER() + ); + //#endregion + + // + //#region Apply Code Editor Options ... + this.setMode(this.mode); + this.setTheme(this.theme); + this.setMinLines(this.minLines); + this.setMaxLines(this.maxLines); + this.setFontSize(this.fontSize); + this.setReadOnly(this.readonly); + this.setWordWrapping(this.wordWrapping); + this.setShowLineNumbers(this.showLineNumbers); + this.setShowPrintMargin(this.showPrintMargin); + this.setShowFoldWidgets(this.showFoldWidgets); + this.setHighlightActiveLine(this.highlightActiveLine); + this.setBasicAutocompletion(this.basicAutocompletion); + //#endregion + + // + //#region Setting Default Content ... + this.setContent(this.content); + + // + // Reset Undo Manager after first code attached ... + this.CODE_EDITOR.getSession().getUndoManager().reset(); + //#endregion + + // + //#region Default Session Handler ... + forkJoin({ + mode: this.getValue(this.mode), + content: this.getValue(this.content), + name: this.getValue(this.defaultTabName).pipe( + map((defTabName) => defTabName || XIDEDefaultTabName) + ), + }).subscribe((info) => { + // + const name = info.name; + const session = this.CODE_EDITOR.getSession(); + this.Sessions.push({ + session, + name, + }); + }); + //#endregion + + // + //#region Code Editor Event Handlers ... + // + // Change Event handler ... + this.CODE_EDITOR.on('change', (e) => { + // + const currentContent = this.getContent(); + this.CONTENT_CHANGE_SUBJECT.next(currentContent); + }); + //#endregion + + // + this.prepareActionBar(); + + // + // Detectin Changes ... + this.detectChanges(); + } + + private async prepareActionBar () { + // + if (isNullOrUndefined(this.CODE_EDITOR)) { + return; + } + + // + const showActionBar = await this.getValueAsync(this.showActionBar); + if (!showActionBar) { + return; + } + + // + //#region DefaultActionBarActions ... + // + const defaultThemeItem: XActionBarItem = { + onlyIcon: true, + disabled: this.uiDisabled, + title: this.ResourceIDs.theme, + icon: this.IconNames.color_palette, + slot: this.ActionBarItemSlots.START, + id: this.ActionBarItems.ChangeTheme, + iconColor: this.defaultActionBarItemColor, + }; + + // + const defaultModeItem: XActionBarItem = { + onlyIcon: true, + disabled: this.uiDisabled, + icon: this.IconNames.list, + title: this.ResourceIDs.ide_mode, + id: this.ActionBarItems.ChangeMode, + slot: this.ActionBarItemSlots.START, + iconColor: this.defaultActionBarItemColor, + }; + + // + const defaultSaveItem: XActionBarItem = { + onlyIcon: true, + disabled: this.uiDisabled, + icon: this.IconNames.save, + title: this.ResourceIDs.save, + id: this.ActionBarItems.Save, + slot: this.ActionBarItemSlots.END, + iconColor: this.defaultActionBarItemColor, + }; + + // + const defaultCloseItem: XActionBarItem = { + onlyIcon: true, + disabled: this.uiDisabled, + icon: this.IconNames.close, + title: this.ResourceIDs.close, + id: this.ActionBarItems.Close, + slot: this.ActionBarItemSlots.END, + iconColor: this.ColorNames.Danger, + }; + + // + const defaultUndoItem: XActionBarItem = { + onlyIcon: true, + disabled: this.uiDisabled, + icon: this.IconNames.undo, + title: this.ResourceIDs.undo, + id: this.ActionBarItems.Undo, + slot: this.ActionBarItemSlots.END, + iconColor: this.defaultActionBarItemColor, + }; + + // + const defaultRedoItem: XActionBarItem = { + onlyIcon: true, + disabled: this.uiDisabled, + icon: this.IconNames.redo, + title: this.ResourceIDs.redo, + id: this.ActionBarItems.Redo, + slot: this.ActionBarItemSlots.END, + iconColor: this.defaultActionBarItemColor, + }; + + // + const defaultBeautifyItem: XActionBarItem = { + onlyIcon: true, + disabled: this.uiDisabled, + icon: this.IconNames.ul, + id: this.ActionBarItems.Beautify, + slot: this.ActionBarItemSlots.END, + title: this.ResourceIDs.ide_format, + iconColor: this.defaultActionBarItemColor, + }; + //#endregion + + // + this.actionBarActions = []; + this.detectChanges(); + + // + const availableItems = await this.getArrayValueAsync( + this.availableActionBarItems + ); + if (!this.hasChild(availableItems)) { + return; + } + + // + const actionBarItems: XActionBarItem[] = []; + const currentTabDescriptor = this.getCurrentTabDescritor(); + + // + //#region Save ... + const hasSaveItem = availableItems + .map((ai) => ai.actionId) + .includes(this.ActionBarItems.Save); + + // + if (hasSaveItem) { + // + const selectedItem = availableItems.find( + (ai) => ai.actionId === this.ActionBarItems.Save + ); + const providedSaveItem = selectedItem.descriptor; + let saveItem = { + ...defaultSaveItem, + }; + if (!isNullOrUndefined(providedSaveItem)) { + saveItem = { + ...saveItem, + ...providedSaveItem, + }; + } + + // + saveItem = { + ...saveItem, + disabled: + this.uiDisabled || !isNullOrUndefined(currentTabDescriptor) + ? !currentTabDescriptor.canSave + : true, + }; + + // + actionBarItems.push(saveItem); + } + //#endregion + + // + //#region Close ... + const hasCloseItem = availableItems + .map((ai) => ai.actionId) + .includes(this.ActionBarItems.Close); + const hasCloseOnDefaultTab = await this.getValueAsync( + this.hasCloseOnDefaultTab + ); + + // + const currentTabIndex = this.getTabDescritorIndex(this.currentTab); + if ( + hasCloseItem + && (!!hasCloseOnDefaultTab + ? true + : currentTabIndex !== 0 + ) + && currentTabIndex > -1) { + // + const selectedItem = availableItems.find( + (ai) => ai.actionId === this.ActionBarItems.Close + ); + const providedCloseItem = selectedItem.descriptor; + let closeItem = { + ...defaultCloseItem, + }; + if (!isNullOrUndefined(providedCloseItem)) { + closeItem = { + ...closeItem, + ...providedCloseItem, + }; + } + + // + closeItem = { + ...closeItem, + disabled: this.uiDisabled, + }; + + // + actionBarItems.push(closeItem); + } + //#endregion + + // + //#region Unde ... + const hasUndoItem = availableItems + .map((ai) => ai.actionId) + .includes(this.ActionBarItems.Undo); + + // + if (hasUndoItem) { + // + const selectedItem = availableItems.find( + (ai) => ai.actionId === this.ActionBarItems.Undo + ); + const providedUndoItem = selectedItem.descriptor; + let undoItem = { + ...defaultUndoItem, + }; + if (!isNullOrUndefined(providedUndoItem)) { + undoItem = { + ...undoItem, + ...providedUndoItem, + }; + } + + // + // Prepare Disable/Enable Toggling of Item ... + undoItem = { + ...undoItem, + disabled: + this.uiDisabled || + !this.CODE_EDITOR.getSession().getUndoManager().canUndo() || + isNullOrUndefined(this.CODE_EDITOR.getSession().getUndoManager()), + }; + + // + actionBarItems.push(undoItem); + } + //#endregion + + // + //#region Redo ... + const hasRedoItem = availableItems + .map((ai) => ai.actionId) + .includes(this.ActionBarItems.Redo); + + // + if (hasRedoItem) { + // + const selectedItem = availableItems.find( + (ai) => ai.actionId === this.ActionBarItems.Redo + ); + const providedRedoItem = selectedItem.descriptor; + let redoItem = { + ...defaultRedoItem, + }; + if (!isNullOrUndefined(providedRedoItem)) { + redoItem = { + ...redoItem, + ...providedRedoItem, + }; + } + + // + // Prepare Disable/Enable Toggling of Item ... + redoItem = { + ...redoItem, + disabled: + this.uiDisabled || + !this.CODE_EDITOR.getSession().getUndoManager().canRedo() || + isNullOrUndefined(this.CODE_EDITOR.getSession().getUndoManager()), + }; + + // + actionBarItems.push(redoItem); + } + //#endregion + + // + //#region Beautify ... + const hasBeautifyItem = availableItems + .map((ai) => ai.actionId) + .includes(this.ActionBarItems.Beautify); + + // + if (hasBeautifyItem) { + // + const selectedItem = availableItems.find( + (ai) => ai.actionId === this.ActionBarItems.Beautify + ); + const providedBeautifyItem = selectedItem.descriptor; + let beautifyItem = { + ...defaultBeautifyItem, + }; + if (!isNullOrUndefined(providedBeautifyItem)) { + beautifyItem = { + ...beautifyItem, + ...providedBeautifyItem, + }; + } + + // + // Prepare Disable/Enable Toggling of Item ... + beautifyItem = { + ...beautifyItem, + disabled: + this.uiDisabled || + isNullOrUndefined(this.CODE_EDITOR) || + !isNullOrUndefined(currentTabDescriptor) + ? !currentTabDescriptor.canBeautify + : true || isNullOrUndefined(this.CODE_EDITOR_BEAUTIFY), + }; + + // + actionBarItems.push(beautifyItem); + } + //#endregion + + // + //#region Mode ... + const hasModeChangeItem = availableItems + .map((ai) => ai.actionId) + .includes(this.ActionBarItems.ChangeMode); + + // + if (hasModeChangeItem) { + // + const selectedItem = availableItems.find( + (ai) => ai.actionId === this.ActionBarItems.ChangeMode + ); + const providedModeItem = selectedItem.descriptor; + let modeItem = { + ...defaultModeItem, + }; + if (!isNullOrUndefined(providedModeItem)) { + modeItem = { + ...modeItem, + ...providedModeItem, + }; + } + + // + modeItem = { + ...modeItem, + disabled: this.uiDisabled, + }; + + // + actionBarItems.push(modeItem); + } + //#endregion + + // + //#region Theme ... + const hasThemeChangeItem = availableItems + .map((ai) => ai.actionId) + .includes(this.ActionBarItems.ChangeTheme); + + // + if (hasThemeChangeItem) { + // + const selectedItem = availableItems.find( + (ai) => ai.actionId === this.ActionBarItems.ChangeTheme + ); + const providedThemeItem = selectedItem.descriptor; + let themeItem = { + ...defaultThemeItem, + }; + if (!isNullOrUndefined(providedThemeItem)) { + themeItem = { + ...themeItem, + ...providedThemeItem, + }; + } + + // + themeItem = { + ...themeItem, + disabled: this.uiDisabled, + }; + + // + actionBarItems.push(themeItem); + } + //#endregion + + // + this.actionBarActions = [...actionBarItems]; + this.detectChanges(); + } + + // + //#region Tabs Handlers ... + private async handleDefaultTabNameChanged ( + defaultTabName: XStandardType + ) { + // + let value = await this.getValueAsync(defaultTabName); + if (isNullOrEmptyString(value)) { + value = XIDEDefaultTabName; + } + + // + if (!this.hasChild(this.Sessions)) { + return; + } + + // + if ( + this.currentTab === XIDEDefaultTabName || + this.currentTab === this.Sessions[0].name + ) { + this.currentTab = value; + } + + // + this.Sessions[0].name = value; + + // + this.prepareActionBar(); + this.detectChanges(); + } + + private handleRemoveTab (name: string, destinationTabIndex: number) { + // + if ( + destinationTabIndex <= 0 || + isNullOrEmptyString(name) || + destinationTabIndex > this.Sessions.length - 1 + ) { + return; + } + + // + this.Sessions.splice(destinationTabIndex, 1); + this.detectChanges(); + this.tabsActionProvider.next({ + action: XTabsAction.Refresh, + }); + + // + const canShowNewTab = name === this.currentTab; + if (canShowNewTab) { + // + const newTabIndex = this.Sessions.length - 1; + + // + if (countChild(this.Sessions) > 1) { + // + this.tabsActionProvider.next({ + action: XTabsAction.TO, + payload: newTabIndex, + }); + + // + this.tabsActionProvider.next({ + action: XTabsAction.Refresh, + }); + } + + // + const newTab = this.Sessions[newTabIndex]; + this.showTab(newTab.name); + } + + // + this.detectChanges(); + } + //#endregion + + // + //#region ActionBar Handlers ... + private handleActionModeFired (event: Event) { + this.getValue(this.mode) + .pipe( + concatMap((mode) => + this.getArrayValue(this.availableActionBarItems).pipe( + map((availableItems) => { + return { + mode, + availableItems, + }; + }) + ) + ) + ) + .subscribe((info) => { + // + // Selected Item Model ... + const selectedItemModel = info.availableItems.find( + (ai) => ai.actionId === this.ActionBarItems.ChangeMode + ); + + // + // get Item Colors ... + const itemsColor = selectedItemModel.itemsColor; + + // + // Retrieve Popover Present Options ... + const popoverPresentOptions = selectedItemModel.popoverPresentOptions; + + // + // Generate Available Modes ... + const availableModes = Object.keys(this.Modes) + // + // By Filtering Curent Mode ... + .filter((key) => this.Modes[key] !== info.mode); + + // + // Convert Available Modes to XPopoverOptions ... + const options = availableModes.map((key) => { + return { + id: key, + color: !isNullOrEmptyString(itemsColor) + ? itemsColor + : this.ColorNames.Dark, + title: key, + handler: () => { + // + this.mode = this.Modes[key]; + this.setMode(this.Modes[key]); + }, + } as XPopoverOption; + }); + + // + // Present Popover Options ... + this.popoverService.presentContextMenu( + options, + event, + popoverPresentOptions + ); + }); + } + + private handleActionThemFired (event: Event) { + this.getValue(this.theme) + .pipe( + concatMap((theme) => + this.getArrayValue(this.availableActionBarItems).pipe( + map((availableItems) => { + return { + theme, + availableItems, + }; + }) + ) + ) + ) + .subscribe((info) => { + // + // Selected Item Model ... + const selectedItemModel = info.availableItems.find( + (ai) => ai.actionId === this.ActionBarItems.ChangeTheme + ); + + // + // get Item Colors ... + const itemsColor = selectedItemModel.itemsColor; + + // + // Retrieve Popover Present Options ... + const popoverPresentOptions = selectedItemModel.popoverPresentOptions; + + // + // Generate Available Themes ... + const availableThemes = Object.keys(this.Themes) + // + // By Filtering Curent Theme ... + .filter((key) => this.Themes[key] !== info.theme); + + // + // Convert Available Themes to XPopoverOptions ... + const options = availableThemes.map((key) => { + return { + id: key, + color: !isNullOrEmptyString(itemsColor) + ? itemsColor + : this.ColorNames.Dark, + title: key, + handler: () => { + // + this.theme = this.Themes[key]; + this.setTheme(this.Themes[key]); + }, + } as XPopoverOption; + }); + + // + // Present Popover Options ... + this.popoverService.presentContextMenu( + options, + event, + popoverPresentOptions + ); + }); + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-ide\x-ide.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XIDEComponent } from './x-ide.component'; +import { XTabsModule } from '../x-tabs/x-tabs.module'; +import { XIconModule } from '../x-icon/x-icon.module'; +import { XCardModule } from '../x-card/x-card.module'; +import { XActionBarModule } from '../x-action-bar/x-action-bar.module'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XIDEComponent], + imports: [ + XTabsModule, + XIconModule, + XCardModule, + CommonModule, + XActionBarModule, + XBaseComponentModule, + ], + exports: [ + XTabsModule, + XIconModule, + XCardModule, + CommonModule, + XIDEComponent, + XActionBarModule, + XBaseComponentModule, + ], +}) +export class XIDEModule { } + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-ide\x-ide.typings.ts + +import { + Input, + Output, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XStandardType, + XOneOrManyType, + XColorIdentifier, + XResourceIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { + XCardActionModel, + XComponentCardContainer, + IXComponentCardContainer, +} from '../x-card/x-card.typings'; +import * as ace from 'ace-builds'; +import { map } from 'rxjs/operators'; +import { + XBaseContainer, + XBaseContainerComponent, +} from '../classes/i-x-base.container'; +import { + XActionBarItem, + XActionBarActionModel, + XActionBarActionFiredModel, + XComponentActionBarContainer, + IXComponentActionBarContainer, +} from '../x-action-bar/x-action-bar.typings'; +import { forkJoin, Observable, Subject } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/scrolling'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { XPopoverService } from '../dialogs/x-popover/x-popover.service'; +import { XTabAlign, XTabAlignIdentifier } from '../x-tabs/x-tabs.typings'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XPopoverPresentOptions } from '../dialogs/x-popover/x-popover.typings'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +// +//#region Action Provider ... +export enum XIDEAction { + // + Refresh = 'refresh', + SetReadOnly = 'set_read_only', + + // + AddTab = 'add_tab', + ShowTab = 'show_tab', + AddTabs = 'add_tabs', + CloseTab = 'close_tab', + + // + Undo = 'undo', + Redo = 'redo', + CleaEditorHistory = 'clear_editor_history', + Beautify = 'beautify', + GoToLine = 'go_to_line', + SetContent = 'set_content', + ClearContent = 'clear_content', + + // + SetMode = 'set_mode', + SetTheme = 'set_theme', + SetMinLines = 'set_min_lines', + SetMaxLines = 'set_max_lines', + SetFontSize = 'set_font_size', + SetWordWrapping = 'set_word_wrapping', + SetShowFoldWidgets = 'set_show_fold_widgets', + SetShowLineNumbers = 'set_show_line_numbers', + SetShowPrintMargin = 'set_show_print_margin', + SetHighlightActiveLine = 'set_highlight_active_line', + SetBasicAutocompletion = 'set_basic_autoc_ompletion', +} + +export type XIDEActionIdentifier = XIDEAction | string; + +export interface XIDEActionModel + extends XBaseActionModel {} +//#endregion + +// +// Font Size Constants ... +export const XIDEMinFontSize = 12; +export const XIDEMaxFontSize = 25; + +export const XIDEDefaultTabName = 'DEFAULT'; + +// +type AnyTypeIndex = { + [key: string]: any; +}; + +// +export type XIDEOptionsType = Partial & AnyTypeIndex; + +// +//#region Theme ... +export enum XIDETheme { + Ambiance = 'ace/theme/ambiance', + Chaos = 'ace/theme/chaos', + Chrome = 'ace/theme/chrome', + Cloud9Day = 'ace/theme/cloud9_day', + Cloud9Night = 'ace/theme/cloud9_night', + Cloud9NightLowColor = 'ace/theme/cloud9_night_low_color', + Clouds = 'ace/theme/clouds', + CloudsMidnight = 'ace/theme/clouds_midnight', + Cobalt = 'ace/theme/cobalt', + CrimsonEditor = 'ace/theme/crimson_editor', + Dawn = 'ace/theme/dawn', + Dracula = 'ace/theme/dracula', + Dreamweaver = 'ace/theme/dreamweaver', + Eclipse = 'ace/theme/eclipse', + Github = 'ace/theme/github', + Gob = 'ace/theme/gob', + Gruvbox = 'ace/theme/gruvbox', + GruvboxDarkHard = 'ace/theme/gruvbox_dark_hard', + GruvboxLightHard = 'ace/theme/gruvbox_light_hard', + IdleFingers = 'ace/theme/idle_fingers', + Iplastic = 'ace/theme/iplastic', + Katzenmilch = 'ace/theme/katzenmilch', + KrTheme = 'ace/theme/kr_theme', + Kuroir = 'ace/theme/kuroir', + Merbivore = 'ace/theme/merbivore', + MerbivoreSoft = 'ace/theme/merbivore_soft', + MonoIndustrial = 'ace/theme/mono_industrial', + Monokai = 'ace/theme/monokai', + NordDark = 'ace/theme/nord_dark', + OneDark = 'ace/theme/one_dark', + PastelOnDark = 'ace/theme/pastel_on_dark', + SolarizedDark = 'ace/theme/solarized_dark', + SolarizedLight = 'ace/theme/solarized_light', + Sqlserver = 'ace/theme/sqlserver', + Terminal = 'ace/theme/terminal', + Textmate = 'ace/theme/textmate', + Tomorrow = 'ace/theme/tomorrow', + TomorrowNight = 'ace/theme/tomorrow_night', + TomorrowNightBlue = 'ace/theme/tomorrow_night_blue', + TomorrowNightBright = 'ace/theme/tomorrow_night_bright', + TomorrowNightEighties = 'ace/theme/tomorrow_night_eighties', + Twilight = 'ace/theme/twilight', + VibrantInk = 'ace/theme/vibrant_ink', + Xcode = 'ace/theme/xcode', +} + +export type XIDEThemeIdentifier = XIDETheme | string; +//#endregion + +// +//#region Mode ... +export enum XIDEMode { + Abap = 'ace/mode/abap', + Abc = 'ace/mode/abc', + Actionscript = 'ace/mode/actionscript', + Ada = 'ace/mode/ada', + Alda = 'ace/mode/alda', + ApacheConf = 'ace/mode/apache_conf', + Apex = 'ace/mode/apex', + Applescript = 'ace/mode/applescript', + Aql = 'ace/mode/aql', + Asciidoc = 'ace/mode/asciidoc', + Asl = 'ace/mode/asl', + AssemblyX86 = 'ace/mode/assembly_x86', + Autohotkey = 'ace/mode/autohotkey', + Batchfile = 'ace/mode/batchfile', + C9search = 'ace/mode/c9search', + CCpp = 'ace/mode/c_cpp', + Cirru = 'ace/mode/cirru', + Clojure = 'ace/mode/clojure', + Cobol = 'ace/mode/cobol', + Coffee = 'ace/mode/coffee', + Coldfusion = 'ace/mode/coldfusion', + Crystal = 'ace/mode/crystal', + Csharp = 'ace/mode/csharp', + CsoundDocument = 'ace/mode/csound_document', + CsoundOrchestra = 'ace/mode/csound_orchestra', + CsoundScore = 'ace/mode/csound_score', + Csp = 'ace/mode/csp', + Css = 'ace/mode/css', + Curly = 'ace/mode/curly', + D = 'ace/mode/d', + Dart = 'ace/mode/dart', + Diff = 'ace/mode/diff', + Django = 'ace/mode/django', + Dockerfile = 'ace/mode/dockerfile', + Dot = 'ace/mode/dot', + Drools = 'ace/mode/drools', + Edifact = 'ace/mode/edifact', + Eiffel = 'ace/mode/eiffel', + Ejs = 'ace/mode/ejs', + Elixir = 'ace/mode/elixir', + Elm = 'ace/mode/elm', + Erlang = 'ace/mode/erlang', + Forth = 'ace/mode/forth', + Fortran = 'ace/mode/fortran', + Fsharp = 'ace/mode/fsharp', + Fsl = 'ace/mode/fsl', + Ftl = 'ace/mode/ftl', + Gcode = 'ace/mode/gcode', + Gherkin = 'ace/mode/gherkin', + Gitignore = 'ace/mode/gitignore', + Glsl = 'ace/mode/glsl', + Gobstones = 'ace/mode/gobstones', + Golang = 'ace/mode/golang', + Graphqlschema = 'ace/mode/graphqlschema', + Groovy = 'ace/mode/groovy', + Haml = 'ace/mode/haml', + Handlebars = 'ace/mode/handlebars', + Haskell = 'ace/mode/haskell', + HaskellCabal = 'ace/mode/haskell_cabal', + Haxe = 'ace/mode/haxe', + Hjson = 'ace/mode/hjson', + Html = 'ace/mode/html', + HtmlElixir = 'ace/mode/html_elixir', + HtmlRuby = 'ace/mode/html_ruby', + Ini = 'ace/mode/ini', + Io = 'ace/mode/io', + Ion = 'ace/mode/ion', + Jack = 'ace/mode/jack', + Jade = 'ace/mode/jade', + Java = 'ace/mode/java', + Javascript = 'ace/mode/javascript', + Json = 'ace/mode/json', + Json5 = 'ace/mode/json5', + Jsoniq = 'ace/mode/jsoniq', + Jsp = 'ace/mode/jsp', + Jssm = 'ace/mode/jssm', + Jsx = 'ace/mode/jsx', + Julia = 'ace/mode/julia', + Kotlin = 'ace/mode/kotlin', + Latex = 'ace/mode/latex', + Latte = 'ace/mode/latte', + Less = 'ace/mode/less', + Liquid = 'ace/mode/liquid', + Lisp = 'ace/mode/lisp', + Livescript = 'ace/mode/livescript', + Logiql = 'ace/mode/logiql', + Logtalk = 'ace/mode/logtalk', + Lsl = 'ace/mode/lsl', + Lua = 'ace/mode/lua', + Luapage = 'ace/mode/luapage', + Lucene = 'ace/mode/lucene', + Makefile = 'ace/mode/makefile', + Markdown = 'ace/mode/markdown', + Mask = 'ace/mode/mask', + Matlab = 'ace/mode/matlab', + Maze = 'ace/mode/maze', + Mediawiki = 'ace/mode/mediawiki', + Mel = 'ace/mode/mel', + Mips = 'ace/mode/mips', + Mixal = 'ace/mode/mixal', + Mushcode = 'ace/mode/mushcode', + Mysql = 'ace/mode/mysql', + Nginx = 'ace/mode/nginx', + Nim = 'ace/mode/nim', + Nix = 'ace/mode/nix', + Nsis = 'ace/mode/nsis', + Nunjucks = 'ace/mode/nunjucks', + ObjectiveC = 'ace/mode/objectivec', + Ocaml = 'ace/mode/ocaml', + Partiql = 'ace/mode/partiql', + Pascal = 'ace/mode/pascal', + Perl = 'ace/mode/perl', + Pgsql = 'ace/mode/pgsql', + Php = 'ace/mode/php', + PhpLaravelBlade = 'ace/mode/php_laravel_blade', + Pig = 'ace/mode/pig', + PlainText = 'ace/mode/plain_text', + Powershell = 'ace/mode/powershell', + Praat = 'ace/mode/praat', + Prisma = 'ace/mode/prisma', + Prolog = 'ace/mode/prolog', + Properties = 'ace/mode/properties', + Protobuf = 'ace/mode/protobuf', + Puppet = 'ace/mode/puppet', + Python = 'ace/mode/python', + Qml = 'ace/mode/qml', + R = 'ace/mode/r', + Raku = 'ace/mode/raku', + Razor = 'ace/mode/razor', + Rdoc = 'ace/mode/rdoc', + Red = 'ace/mode/red', + Redshift = 'ace/mode/redshift', + Rhtml = 'ace/mode/rhtml', + Robot = 'ace/mode/robot', + Rst = 'ace/mode/rst', + Ruby = 'ace/mode/ruby', + Rust = 'ace/mode/rust', + Sac = 'ace/mode/sac', + Sass = 'ace/mode/sass', + Scad = 'ace/mode/scad', + Scala = 'ace/mode/scala', + Scheme = 'ace/mode/scheme', + Scrypt = 'ace/mode/scrypt', + Scss = 'ace/mode/scss', + Sh = 'ace/mode/sh', + Sjs = 'ace/mode/sjs', + Slim = 'ace/mode/slim', + Smarty = 'ace/mode/smarty', + Smithy = 'ace/mode/smithy', + Snippets = 'ace/mode/snippets', + SoyTemplate = 'ace/mode/soy_template', + Space = 'ace/mode/space', + Sparql = 'ace/mode/sparql', + Sql = 'ace/mode/sql', + Sqlserver = 'ace/mode/sqlserver', + Stylus = 'ace/mode/stylus', + Svg = 'ace/mode/svg', + Swift = 'ace/mode/swift', + Tcl = 'ace/mode/tcl', + Terraform = 'ace/mode/terraform', + Tex = 'ace/mode/tex', + Text = 'ace/mode/text', + Textile = 'ace/mode/textile', + Toml = 'ace/mode/toml', + Tsx = 'ace/mode/tsx', + Turtle = 'ace/mode/turtle', + Twig = 'ace/mode/twig', + Typescript = 'ace/mode/typescript', + Vala = 'ace/mode/vala', + Vbscript = 'ace/mode/vbscript', + Velocity = 'ace/mode/velocity', + Verilog = 'ace/mode/verilog', + Vhdl = 'ace/mode/vhdl', + Visualforce = 'ace/mode/visualforce', + Wollok = 'ace/mode/wollok', + Xml = 'ace/mode/xml', + Xquery = 'ace/mode/xquery', + Yaml = 'ace/mode/yaml', + Zeek = 'ace/mode/zeek', +} + +export type XIDEModeIdentifier = XIDEMode | string; +//#endregion + +// +//#region ActionBar ... +export enum XIDEActionBarItem { + ChangeMode = 'change_mode', + ChangeTheme = 'change_theme', + Save = 'save', + Close = 'close', + Undo = 'undo', + Redo = 'redo', + Beautify = 'beautify', +} + +export type XIDEActionBarItemIdentifier = XIDEActionBarItem | string; + +export type XIDEActionBarItemDescriptor = Partial< + Pick< + XActionBarItem, + | 'slot' + | 'icon' + | 'title' + | 'color' + | 'onlyIcon' + | 'iconColor' + | 'titleColor' + > +>; + +export interface XIDEAvailableActionBarItem { + itemsColor?: XColorIdentifier; + actionId: XIDEActionBarItemIdentifier; + descriptor?: XIDEActionBarItemDescriptor; + popoverPresentOptions?: XPopoverPresentOptions; +} +//#endregion + +// +//#region IDETabs ... +export interface XIDETabDescriptor { + name: string; + content?: string; + canSave?: boolean; + forceShow?: boolean; + canBeautify?: boolean; + mode?: XIDEModeIdentifier; + session?: ace.Ace.EditSession; +} +//#endregion + +// +//#region Container ... +type TXIDEContainer = IXComponentCardContainer & IXComponentActionBarContainer; +export interface IXIDEContainer extends TXIDEContainer { + // + //#region Props ... + content: XStandardType; + minLines: XStandardType; + maxLines: XStandardType; + fontSize: XStandardType; + readonly: XStandardType; + contentChange: EventEmitter; + wordWrapping: XStandardType; + defaultTabName: XStandardType; + showLineNumbers: XStandardType; + showPrintMargin: XStandardType; + showFoldWidgets: XStandardType; + mode: XStandardType; + actionProvider: Subject; + theme: XStandardType; + highlightActiveLine: XStandardType; + basicAutocompletion: XStandardType; + hasCloseOnDefaultTab: XStandardType; + cssClass: XStandardType>; + + // + //#region Tabs ... + alignTabs: XStandardType; + tabHeaderColor: XStandardType; + activeTabColor: XStandardType; + //#endregion + + // + //#region ActionBar ... + defaultActionBarItemColor: XStandardType; + availableActionBarItems: XStandardType< + XOneOrManyType + >; + //#endregion + //#endregion +} + +@Injectable({ + providedIn: 'root', +}) +export class XIDEContainer extends XBaseContainer implements IXIDEContainer { + // + //#region Props ... + // + //#region Self Props ... + readonly content: XStandardType = ''; + readonly minLines: XStandardType = 10; + readonly fontSize: XStandardType = XIDEMinFontSize; + readonly readonly: XStandardType = false; + readonly actionProvider: Subject = + new Subject(); + readonly maxLines: XStandardType = Infinity; + readonly wordWrapping: XStandardType = true; + readonly cssClass: XStandardType>; + readonly showLineNumbers: XStandardType = true; + readonly showFoldWidgets: XStandardType = true; + readonly showPrintMargin: XStandardType = false; + readonly highlightActiveLine: XStandardType = true; + readonly basicAutocompletion: XStandardType = true; + readonly hasCloseOnDefaultTab: XStandardType = false; + readonly defaultTabName: XStandardType = XIDEDefaultTabName; + readonly mode: XStandardType = XIDEMode.Typescript; + readonly theme: XStandardType = XIDETheme.Dracula; + readonly contentChange: EventEmitter = new EventEmitter(); + + // + //#region Tabs ... + readonly alignTabs: XStandardType = XTabAlign.START; + readonly tabHeaderColor: XStandardType = + XColorWithBrightness.Dark; + readonly activeTabColor: XStandardType = + XColorWithBrightness.Warning; + //#endregion + + // + readonly availableActionBarItems: XStandardType< + XOneOrManyType + > = [ + { + actionId: XIDEActionBarItem.Save, + }, + { + actionId: XIDEActionBarItem.Close, + }, + { + actionId: XIDEActionBarItem.Undo, + }, + { + actionId: XIDEActionBarItem.Redo, + }, + { + actionId: XIDEActionBarItem.Beautify, + }, + { + actionId: XIDEActionBarItem.ChangeTheme, + }, + { + actionId: XIDEActionBarItem.ChangeMode, + }, + ]; + readonly defaultActionBarItemColor: XStandardType = + XColorWithBrightness.Dark; + //#endregion + + // + //#region ComponentCard Props ... + readonly isInPage: XStandardType = + this.cardPropertyProvider.isInPage; + readonly wrapWithCard: XStandardType = + this.cardPropertyProvider.wrapWithCard; + readonly cardActionProvider: Subject = + this.cardPropertyProvider.cardActionProvider; + readonly cardCssClass: XStandardType> = + this.cardPropertyProvider.cardCssClass; + readonly cardColor: XStandardType = + this.cardPropertyProvider.cardColor; + readonly cardForegroundColor: XStandardType = + this.cardPropertyProvider.cardForegroundColor; + readonly cardTitle: XStandardType = + this.cardPropertyProvider.cardTitle; + readonly cardSubTitle: XStandardType = + this.cardPropertyProvider.cardSubTitle; + readonly cardShowFooter: XStandardType = + this.cardPropertyProvider.cardShowFooter; + readonly cardShowActions: XStandardType = + this.cardPropertyProvider.cardShowActions; + //#endregion + + // + //#region ComponentActionBar Props ... + readonly showActionBar: XStandardType = + this.actionBarPropertyProvider.showActionBar; + readonly actionBarActionProvider: Subject = + this.actionBarPropertyProvider.actionBarActionProvider; + readonly actionBarCssClass: XStandardType> = + this.actionBarPropertyProvider.actionBarCssClass; + readonly actionBarColor: XStandardType = + this.actionBarPropertyProvider.actionBarColor; + readonly actionBarForegroundColor: XStandardType = + this.actionBarPropertyProvider.actionBarForegroundColor; + readonly actionBarActions: XStandardType> = + this.actionBarPropertyProvider.actionBarActions; + readonly actionBarActionFired: EventEmitter = + this.actionBarPropertyProvider.actionBarActionFired; + //#endregion + //#endregion + + // + //#region Constructor ... + constructor( + protected cardPropertyProvider: XComponentCardContainer, + protected actionBarPropertyProvider: XComponentActionBarContainer + ) { + super(); + } + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export class XIDEContainerComponent + extends XBaseContainerComponent + implements IXIDEContainer +{ + // + //#region Props ... + // + //#region Self Props ... + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + @Input() + defaultTabName: XStandardType = this.propertyProvider.defaultTabName; + + @Input() + hasCloseOnDefaultTab: XStandardType = this.propertyProvider.hasCloseOnDefaultTab; + + @Input() + theme: XStandardType = this.propertyProvider.theme; + + @Input() + mode: XStandardType = this.propertyProvider.mode; + + @Input() + minLines: XStandardType = this.propertyProvider.minLines; + + @Input() + maxLines: XStandardType = this.propertyProvider.maxLines; + + @Input() + fontSize: XStandardType = this.propertyProvider.fontSize; + + @Input() + readonly: XStandardType = this.propertyProvider.readonly; + + @Input() + highlightActiveLine: XStandardType = + this.propertyProvider.highlightActiveLine; + + @Input() + wordWrapping: XStandardType = this.propertyProvider.wordWrapping; + + @Input() + showLineNumbers: XStandardType = + this.propertyProvider.showLineNumbers; + + @Input() + showPrintMargin: XStandardType = + this.propertyProvider.showPrintMargin; + + @Input() + showFoldWidgets: XStandardType = + this.propertyProvider.showFoldWidgets; + + @Input() + basicAutocompletion: XStandardType = + this.propertyProvider.basicAutocompletion; + + @Input() + content: XStandardType = this.propertyProvider.content; + + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Output() + contentChange: EventEmitter = this.propertyProvider.contentChange; + + // + //#region Tabs ... + @Input() + alignTabs: XStandardType = + this.propertyProvider.alignTabs; + + @Input() + tabHeaderColor: XStandardType = + this.propertyProvider.tabHeaderColor; + + @Input() + activeTabColor: XStandardType = + this.propertyProvider.activeTabColor; + //#endregion + + // + //#region ActionBar ... + @Input() + defaultActionBarItemColor: XStandardType = + this.propertyProvider.defaultActionBarItemColor; + + @Input() + availableActionBarItems: XStandardType< + XOneOrManyType + > = this.propertyProvider.availableActionBarItems; + //#endregion + //#endregion + + // + //#region ComponentCard Props ... + @Input() + isInPage: XStandardType = this.propertyProvider.isInPage; + + @Input() + wrapWithCard: XStandardType = this.propertyProvider.wrapWithCard; + + @Input() + cardCssClass: XStandardType> = + this.propertyProvider.cardCssClass; + + @Input() + cardColor: XStandardType = this.propertyProvider.cardColor; + + @Input() + cardForegroundColor: XStandardType = + this.propertyProvider.cardForegroundColor; + + @Input() + cardTitle: XStandardType = + this.propertyProvider.cardTitle; + + @Input() + cardSubTitle: XStandardType = + this.propertyProvider.cardSubTitle; + + @Input() + cardShowFooter: XStandardType = this.propertyProvider.cardShowFooter; + + @Input() + cardShowActions: XStandardType = + this.propertyProvider.cardShowActions; + + @Input() + cardActionProvider: Subject = + this.propertyProvider.cardActionProvider; + //#endregion + + // + //#region ComponentActionBar Props ... + @Input() + showActionBar: XStandardType = this.propertyProvider.showActionBar; + + @Input() + actionBarCssClass: XStandardType> = + this.propertyProvider.actionBarCssClass; + + @Input() + actionBarColor: XStandardType = + this.propertyProvider.actionBarColor; + + @Input() + actionBarForegroundColor: XStandardType = + this.propertyProvider.actionBarForegroundColor; + + @Input() + actionBarActionProvider: Subject = + this.propertyProvider.actionBarActionProvider; + + @Output() + actionBarActionFired: EventEmitter = + this.propertyProvider.actionBarActionFired; + + actionBarActions: XStandardType> = + this.propertyProvider.actionBarActions; + //#endregion + //#endregion + + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XIDEContainer, + protected popoverService: XPopoverService + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstracts ... + public extractProps(container?: IXIDEContainer): Observable { + return forkJoin({ + // + //#region Self Props ... + minLines: this.getValue(container ? container.minLines : this.minLines), + maxLines: this.getValue(container ? container.maxLines : this.maxLines), + fontSize: this.getValue(container ? container.fontSize : this.fontSize), + readonly: this.getValue(container ? container.readonly : this.readonly), + content: this.getValue(container ? container.content : this.content), + mode: this.getValue(container ? container.mode : this.mode), + theme: this.getValue(container ? container.theme : this.theme), + highlightActiveLine: this.getValue( + container ? container.highlightActiveLine : this.highlightActiveLine + ), + cssClass: this.getArrayValue( + container ? container.cssClass : this.cssClass + ), + basicAutocompletion: this.getValue( + container ? container.basicAutocompletion : this.basicAutocompletion + ), + wordWrapping: this.getValue( + container ? container.wordWrapping : this.wordWrapping + ), + showPrintMargin: this.getValue( + container ? container.showPrintMargin : this.showPrintMargin + ), + showLineNumbers: this.getValue( + container ? container.showLineNumbers : this.showLineNumbers + ), + showFoldWidgets: this.getValue( + container ? container.showFoldWidgets : this.showFoldWidgets + ), + defaultTabName: this.getValue( + container ? container.defaultTabName : this.defaultTabName + ), + hasCloseOnDefaultTab: this.getValue( + container ? container.hasCloseOnDefaultTab : this.hasCloseOnDefaultTab + ), + + // + //#region Tabs ... + alignTabs: this.getValue( + container ? container.alignTabs : this.alignTabs + ), + tabHeaderColor: this.getValue( + container ? container.tabHeaderColor : this.tabHeaderColor + ), + activeTabColor: this.getValue( + container ? container.activeTabColor : this.activeTabColor + ), + //#endregion + + // + // ActionBar ... + defaultActionBarItemColor: this.getValue( + container + ? container.defaultActionBarItemColor + : this.defaultActionBarItemColor + ), + availableActionBarItems: this.getArrayValue( + container + ? container.availableActionBarItems + : this.availableActionBarItems + ), + //#endregion + + // + //#region XComponentCard ... + cardCssClass: this.getArrayValue( + container ? container.cardCssClass : this.cardCssClass + ), + isInPage: this.getValue(container ? container.isInPage : this.isInPage), + wrapWithCard: this.getValue( + container ? container.wrapWithCard : this.wrapWithCard + ), + cardShowFooter: this.getValue( + container ? container.cardShowFooter : this.cardShowFooter + ), + cardShowActions: this.getValue( + container ? container.cardShowActions : this.cardShowActions + ), + cardColor: this.getValue( + container ? container.cardColor : this.cardColor + ), + cardForegroundColor: this.getValue( + container ? container.cardForegroundColor : this.cardForegroundColor + ), + cardTitle: this.getValue( + container ? container.cardTitle : this.cardTitle + ), + cardSubTitle: this.getValue( + container ? container.cardSubTitle : this.cardSubTitle + ), + //#endregion + + // + //#region XComponentActionBar ... + showActionBar: this.getValue( + container ? container.showActionBar : this.showActionBar + ), + actionBarCssClass: this.getValue( + container ? container.actionBarCssClass : this.actionBarCssClass + ), + actionBarColor: this.getValue( + container ? container.actionBarColor : this.actionBarColor + ), + actionBarForegroundColor: this.getValue( + container + ? container.actionBarForegroundColor + : this.actionBarForegroundColor + ), + actionBarActions: this.getValue( + container ? container.actionBarActions : this.actionBarActions + ), + //#endregion + }).pipe( + map((data) => { + return { + // + ...data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + cardActionProvider: container + ? container.cardActionProvider + : this.cardActionProvider, + + // + actionBarActionProvider: container + ? container.actionBarActionProvider + : this.actionBarActionProvider, + + // + contentChange: this.contentChange, + actionBarActionFired: this.actionBarActionFired, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-image-cropper\x-image-cropper.component.html + +
+ +
+
+   +
+ + + + + + + + + + + + +
+ +
+ + + + + + + + + + + +
+
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-image-cropper\x-image-cropper.component.scss + +:host::ng-deep .mat-dialog-container { + padding: 0 !important; + margin: 0 !important; +} + +x-icon, +mat-icon { + color: white; +} + + +:host { + display: flex; + position: relative; + + width: 100%; + max-width: 100%; + max-height: 100%; + + padding-top: 6px; + + overflow: hidden; + + text-align: center; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +:host > div { + position: relative; + width: 100%; +} + +:host div > img.source-image { + max-width: 98%; + max-height: 98%; +} + +.actions { + margin: 2px; + margin-top: 6px; +} + +.actions > button { + width: 36px; + min-width: 36px; + + height: 36px36px; + min-height: 36px; + + padding: 0; +} + +.cropper { + position: absolute; + display: flex; + color: #53535c; + background: transparent; + outline-color: rgba(255, 255, 255, 0.3); + outline-width: 100vw; + outline-style: solid; + touch-action: none; +} +@media (orientation: portrait) { + .cropper { + outline-width: 100vh; + } +} +.cropper:after { + position: absolute; + content: ''; + top: 0; + bottom: 0; + left: 0; + right: 0; + pointer-events: none; + border: dashed 1px; + opacity: 0.75; + color: inherit; + z-index: 1; +} +.cropper .move { + width: 100%; + cursor: move; + border: 1px solid rgba(255, 255, 255, 0.5); +} +.cropper .resize { + position: absolute; + display: inline-block; + line-height: 6px; + padding: 8px; + opacity: 0.85; + z-index: 1; +} +.cropper .resize .square { + display: inline-block; + background: #53535c; + width: 6px; + height: 6px; + border: 1px solid rgba(255, 255, 255, 0.5); + box-sizing: content-box; +} +.cropper .resize.topleft { + top: -12px; + left: -12px; + cursor: nwse-resize; +} +.cropper .resize.top { + top: -12px; + left: calc(50% - 12px); + cursor: ns-resize; +} +.cropper .resize.topright { + top: -12px; + right: -12px; + cursor: nesw-resize; +} +.cropper .resize.right { + top: calc(50% - 12px); + right: -12px; + cursor: ew-resize; +} +.cropper .resize.bottomright { + bottom: -12px; + right: -12px; + cursor: nwse-resize; +} +.cropper .resize.bottom { + bottom: -12px; + left: calc(50% - 12px); + cursor: ns-resize; +} +.cropper .resize.bottomleft { + bottom: -12px; + left: -12px; + cursor: nesw-resize; +} +.cropper .resize.left { + top: calc(50% - 12px); + left: -12px; + cursor: ew-resize; +} +.cropper .resize-bar { + position: absolute; + z-index: 1; +} +.cropper .resize-bar.top { + top: -11px; + left: 11px; + width: calc(100% - 22px); + height: 22px; + cursor: ns-resize; +} +.cropper .resize-bar.right { + top: 11px; + right: -11px; + height: calc(100% - 22px); + width: 22px; + cursor: ew-resize; +} +.cropper .resize-bar.bottom { + bottom: -11px; + left: 11px; + width: calc(100% - 22px); + height: 22px; + cursor: ns-resize; +} +.cropper .resize-bar.left { + top: 11px; + left: -11px; + height: calc(100% - 22px); + width: 22px; + cursor: ew-resize; +} +.cropper.rounded { + outline-color: transparent; +} +.cropper.rounded:after { + border-radius: 100%; + box-shadow: 0 0 0 100vw rgba(255, 255, 255, 0.3); +} +@media (orientation: portrait) { + .cropper.rounded:after { + box-shadow: 0 0 0 100vh rgba(255, 255, 255, 0.3); + } +} +.cropper.rounded .move { + border-radius: 100%; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-image-cropper\x-image-cropper.component.ts + +import { + NgZone, + Inject, + Component, + ViewChild, + Renderer2, + ElementRef, + HostListener, + ChangeDetectorRef, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + nameof, + isPromise, + resizeCanvas, + XDialogResult, + isNullOrUndefined, + isNullOrEmptyString, + resetExifOrientation, + transformBase64BasedOnExifRotation, +} from 'x-framework-core'; +import { + XMoveStart, + XDimensions, + XOutputType, + XCropperPosition, + XImageCroppedEvent, + XImageCropperAction, + XImageCropperContainer, + XImageCropperProperties, + XImageCropperActionModel, + XImageCropperContainerComponent, +} from './x-image-cropper.typings'; +import { Subject, Subscription } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { SafeUrl, SafeStyle, DomSanitizer } from '@angular/platform-browser'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from './../config/x-framework-components.config'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-image-cropper', + templateUrl: './x-image-cropper.component.html', + styleUrls: ['./x-image-cropper.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XImageCropperComponent extends XImageCropperContainerComponent { + // + //#region Props ... + private originalImage: any; + private originalBase64: string; + private moveStart: XMoveStart; + private maxSize: XDimensions; + private originalSize: XDimensions; + private setImageMaxSizeRetries = 0; + private cropperScaledMinWidth = 20; + private cropperScaledMinHeight = 20; + + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + safeImgDataUrl: SafeUrl | string; + marginLeft: SafeStyle | string = '0px'; + marginRight: SafeStyle | string = '0px'; + imageVisible = false; + + // + @ViewChild('sourceImage', { static: false }) + sourceImage: ElementRef; + + // + //#region Host Listeners ... + @HostListener('window:resize') + onResize(): void { + this.resizeCropperPosition(); + this.setMaxSize(); + this.setCropperScaledMinSize(); + } + + @HostListener('document:mousemove', ['$event']) + @HostListener('document:touchmove', ['$event']) + moveImg(event: any): void { + if (this.moveStart.active) { + event.stopPropagation(); + event.preventDefault(); + if (this.moveStart.type === 'move') { + this.move(event); + this.checkCropperPosition(true); + } else if (this.moveStart.type === 'resize') { + this.resize(event); + this.checkCropperPosition(false); + } + this.changeDetector.detectChanges(); + } + } + + @HostListener('document:mouseup') + @HostListener('document:touchend') + moveStop(): void { + if (this.moveStart.active) { + this.moveStart.active = false; + this.doAutoCrop(); + } + } + //#endregion + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XImageCropperContainer, + private sanitizer: DomSanitizer, + @Inject(MAT_DIALOG_DATA) private data: any, + private dialogRef: MatDialogRef + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region LifeCycles ... + onInit() { + this.startUp(); + } + + onChanges(changeKeys: string[]): void { + super.onChange(changeKeys); + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + + // + //#region ImageFileChanged ... + const isImageFileChangedChanged = changeKeys.includes( + nameof('imageFileChanged') + ); + if (isImageFileChangedChanged) { + // + this.initCropper(); + if (this.imageFileChanged) { + this.loadImageFile(this.imageFileChanged); + } + } + //#endregion + + // + //#region Image Changed Event Changed ... + const isImageChangedEventChanged = changeKeys.includes( + nameof('imageChangedEvent') + ); + if (isImageChangedEventChanged) { + // + this.initCropper(); + if ( + this.imageChangedEvent && + this.imageChangedEvent.target && + this.imageChangedEvent.target.files && + this.imageChangedEvent.target.files.length > 0 + ) { + this.loadImageFile(this.imageChangedEvent.target.files[0]); + } + } + //#endregion + + // + //#region CropperChanged ... + const isCropperChanged = changeKeys.includes( + nameof('cropper') + ); + if (isCropperChanged) { + // + this.setMaxSize(); + this.setCropperScaledMinSize(); + this.checkCropperPosition(false); + this.doAutoCrop(); + this.changeDetector.markForCheck(); + } + //#endregion + + // + //#region Aspect Ratio Changed ... + const isAspectRatioChanged = changeKeys.includes( + nameof('aspectRatio') + ); + if (isAspectRatioChanged && this.imageVisible) { + this.resetCropperPosition(); + } + //#endregion + + // + //#region ImageBase64 Changed ... + const isImageBase64Changed = changeKeys.includes( + nameof('imageBase64') + ); + if (isImageBase64Changed) { + // + this.initCropper(); + if (!isNullOrEmptyString(this.imageBase64)) { + this.loadBase64Image(this.imageBase64); + } + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region UI Handlers ... + handleImageLoadedInView(): void { + if (this.originalImage != null) { + this.imageLoaded.emit(); + this.setImageMaxSizeRetries = 0; + setTimeout(() => this.checkImageMaxSizeRecursively()); + } + } + + handleCancel(): void { + this.dialogRef.close({ + result: XDialogResult.Cancel, + }); + } + + handleOk(): void { + // + // Get Crop Result and make Dialog close + // Strategy based on it ... + const res = this.crop(); + + // + // if result is null ... + if (isNullOrUndefined(res)) { + // + this.dialogRef.close({ + event: null, + result: XDialogResult.Ok, + }); + // + // if result is Promise Object ... + } else if (isPromise(res)) { + // + (res as Promise).then((event) => { + this.dialogRef.close({ + event, + result: XDialogResult.Ok, + }); + }); + // + // if result ir ImagedCroppedEvent instance ... + } else { + this.dialogRef.close({ + event: res, + result: XDialogResult.Ok, + }); + } + } + + handleRotateLeft() { + this.rotateLeft(); + } + + handleFlipHorizontal() { + this.flipHorizontal(); + } + + handleFlipVertical() { + this.flipVertical(); + } + + handleRotateRight() { + this.rotateRight(); + } + + handleStartMove( + event: any, + moveType: string, + position: string | null = null + ): void { + event.preventDefault(); + this.moveStart = { + active: true, + type: moveType, + position, + clientX: this.getClientX(event), + clientY: this.getClientY(event), + ...this.cropper, + }; + } + //#endregion + + // + //#region Actions ... + rotateLeft() { + this.transformBase64(8); + } + + rotateRight() { + this.transformBase64(6); + } + + flipHorizontal() { + this.transformBase64(2); + } + + flipVertical() { + this.transformBase64(4); + } + + crop( + outputType: XOutputType = this.outputType + ): XImageCroppedEvent | Promise | null { + if (this.sourceImage.nativeElement && this.originalImage != null) { + this.startCropImage.emit(); + const imagePosition = this.getImagePosition(); + const width = imagePosition.x2 - imagePosition.x1; + const height = imagePosition.y2 - imagePosition.y1; + + const cropCanvas = document.createElement('canvas') as HTMLCanvasElement; + cropCanvas.width = width; + cropCanvas.height = height; + + const ctx = cropCanvas.getContext('2d'); + if (ctx) { + ctx.drawImage( + this.originalImage, + imagePosition.x1, + imagePosition.y1, + width, + height, + 0, + 0, + width, + height + ); + const output = { + width, + height, + imagePosition, + cropperPosition: { ...this.cropper }, + }; + const resizeRatio = this.getResizeRatio(width); + if (resizeRatio !== 1) { + output.width = Math.floor(width * resizeRatio); + output.height = Math.floor(height * resizeRatio); + resizeCanvas(cropCanvas, output.width, output.height); + } + + // + const result = this.cropToOutputType(outputType, cropCanvas, output); + return result; + } + } + return null; + } + //#endregion + + // + //#region Private ... + // + //#region ActionProvider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + switch (model.action) { + // + case XImageCropperAction.Refresh: + this.detectChanges(); + break; + + // + case XImageCropperAction.RotateLeft: + this.rotateLeft(); + break; + + // + case XImageCropperAction.RotateRight: + this.rotateRight(); + break; + + // + case XImageCropperAction.FlipVertical: + this.flipVertical(); + break; + + // + case XImageCropperAction.FlipHorizontal: + this.flipHorizontal(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + private startUp() { + // + // Handle Dialog Data ... + if (this.data) { + // + //#region Handle Properties ... + if (this.data.props) { + // + const props = this.data.props as XImageCropperProperties; + + // + if (props.imagefile) { + this.imageFileChanged = props.imagefile; + } + + // + if (props.imageChangedEvent) { + this.imageChangedEvent = props.imageChangedEvent; + } + + // + if (props.imageBase64) { + this.imageBase64 = props.imageBase64; + } + + // + if (props.format) { + this.format = props.format; + } + + // + if (props.outputType) { + this.outputType = props.outputType; + } + + // + if (props.alignImage) { + this.alignImage = props.alignImage; + } + + // + if (props.cropper) { + this.cropper = props.cropper; + } + + // + if (props.autoCrop) { + this.autoCrop = props.autoCrop; + } + + // + if (props.aspectRatio) { + this.aspectRatio = props.aspectRatio; + } + + // + if (props.imageQuality) { + this.imageQuality = props.imageQuality; + } + + // + if (props.resizeToWidth) { + this.resizeToWidth = props.resizeToWidth; + } + + // + if (props.roundCropper) { + this.roundCropper = props.roundCropper; + } + + // + if (props.onlyScaleDown) { + this.onlyScaleDown = props.onlyScaleDown; + } + + // + if (props.cropperMinWidth) { + this.cropperMinWidth = props.cropperMinWidth; + } + + // + if (props.cropperMinHeight) { + this.cropperMinHeight = props.cropperMinHeight; + } + + // + if (props.maintainAspectRatio) { + this.maintainAspectRatio = props.maintainAspectRatio; + } + } + //#endregion + } + + // + this.initCropper(); + if (this.imageFileChanged) { + this.loadImageFile(this.imageFileChanged); + } + } + + private initCropper(): void { + this.imageVisible = false; + this.originalImage = null; + this.safeImgDataUrl = + 'data:image/png;base64,iVBORw0KGg' + + 'oAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQYV2NgAAIAAAU' + + 'AAarVyFEAAAAASUVORK5CYII='; + this.moveStart = { + active: false, + type: null, + position: null, + x1: 0, + y1: 0, + x2: 0, + y2: 0, + clientX: 0, + clientY: 0, + }; + this.maxSize = { + width: 0, + height: 0, + }; + this.originalSize = { + width: 0, + height: 0, + }; + this.cropper.x1 = -100; + this.cropper.y1 = -100; + this.cropper.x2 = 10000; + this.cropper.y2 = 10000; + } + + private loadImageFile(file: File): void { + const fileReader = new FileReader(); + fileReader.onload = (event: any) => { + const imageType = file.type; + if (this.isValidImageType(imageType)) { + resetExifOrientation(event.target.result) + .then((resultBase64: string) => this.loadBase64Image(resultBase64)) + .catch(() => this.loadImageFailed.emit()); + } else { + this.loadImageFailed.emit(); + } + }; + fileReader.readAsDataURL(file); + } + + private isValidImageType(type: string): boolean { + return /image\/(png|jpg|jpeg|bmp|gif|tiff)/.test(type); + } + + private loadBase64Image(imageBase64: string): void { + this.originalBase64 = imageBase64; + this.safeImgDataUrl = + this.sanitizer.bypassSecurityTrustResourceUrl(imageBase64); + this.originalImage = new Image(); + this.originalImage.onload = () => { + this.originalSize.width = this.originalImage.width; + this.originalSize.height = this.originalImage.height; + this.changeDetector.markForCheck(); + }; + this.originalImage.src = imageBase64; + } + + private checkImageMaxSizeRecursively(): void { + if (this.setImageMaxSizeRetries > 40) { + this.loadImageFailed.emit(); + } else if ( + this.sourceImage && + this.sourceImage.nativeElement && + this.sourceImage.nativeElement.offsetWidth > 0 + ) { + this.setMaxSize(); + this.setCropperScaledMinSize(); + this.resetCropperPosition(); + this.cropperReady.emit(); + this.changeDetector.markForCheck(); + } else { + this.setImageMaxSizeRetries++; + setTimeout(() => { + this.checkImageMaxSizeRecursively(); + }, 50); + } + } + + private transformBase64(exifOrientation: number): void { + if (this.originalBase64) { + transformBase64BasedOnExifRotation( + this.originalBase64, + exifOrientation + ).then((rotatedBase64: string) => this.loadBase64Image(rotatedBase64)); + } + } + + private resizeCropperPosition(): void { + const sourceImageElement = this.sourceImage.nativeElement; + if ( + this.maxSize.width !== sourceImageElement.offsetWidth || + this.maxSize.height !== sourceImageElement.offsetHeight + ) { + this.cropper.x1 = + (this.cropper.x1 * sourceImageElement.offsetWidth) / this.maxSize.width; + this.cropper.x2 = + (this.cropper.x2 * sourceImageElement.offsetWidth) / this.maxSize.width; + this.cropper.y1 = + (this.cropper.y1 * sourceImageElement.offsetHeight) / + this.maxSize.height; + this.cropper.y2 = + (this.cropper.y2 * sourceImageElement.offsetHeight) / + this.maxSize.height; + } + } + + private resetCropperPosition(): void { + const sourceImageElement = this.sourceImage.nativeElement; + if (!this.maintainAspectRatio) { + this.cropper.x1 = 0; + this.cropper.x2 = sourceImageElement.offsetWidth; + this.cropper.y1 = 0; + this.cropper.y2 = sourceImageElement.offsetHeight; + } else if ( + sourceImageElement.offsetWidth / this.aspectRatio < + sourceImageElement.offsetHeight + ) { + this.cropper.x1 = 0; + this.cropper.x2 = sourceImageElement.offsetWidth; + const cropperHeight = sourceImageElement.offsetWidth / this.aspectRatio; + this.cropper.y1 = (sourceImageElement.offsetHeight - cropperHeight) / 2; + this.cropper.y2 = this.cropper.y1 + cropperHeight; + } else { + this.cropper.y1 = 0; + this.cropper.y2 = sourceImageElement.offsetHeight; + const cropperWidth = sourceImageElement.offsetHeight * this.aspectRatio; + this.cropper.x1 = (sourceImageElement.offsetWidth - cropperWidth) / 2; + this.cropper.x2 = this.cropper.x1 + cropperWidth; + } + this.doAutoCrop(); + this.imageVisible = true; + } + + private setMaxSize(): void { + const sourceImageElement = this.sourceImage.nativeElement; + this.maxSize.width = sourceImageElement.offsetWidth; + this.maxSize.height = sourceImageElement.offsetHeight; + this.marginLeft = this.sanitizer.bypassSecurityTrustStyle( + 'calc(50% - ' + this.maxSize.width / 2 + 'px)' + ); + this.marginRight = this.sanitizer.bypassSecurityTrustStyle( + 'calc(50% - ' + this.maxSize.width / 2 + 'px)' + ); + } + + private setCropperScaledMinSize(): void { + if (this.originalImage) { + this.setCropperScaledMinWidth(); + this.setCropperScaledMinHeight(); + } else { + this.cropperScaledMinWidth = 20; + this.cropperScaledMinHeight = 20; + } + } + + private setCropperScaledMinWidth(): void { + this.cropperScaledMinWidth = + this.cropperMinWidth > 0 + ? Math.max( + 20, + (this.cropperMinWidth / this.originalImage.width) * + this.maxSize.width + ) + : 20; + } + + private setCropperScaledMinHeight(): void { + if (this.maintainAspectRatio) { + this.cropperScaledMinHeight = Math.max( + 20, + this.cropperScaledMinWidth / this.aspectRatio + ); + } else if (this.cropperMinHeight > 0) { + this.cropperScaledMinHeight = Math.max( + 20, + (this.cropperMinHeight / this.originalImage.height) * + this.maxSize.height + ); + } else { + this.cropperScaledMinHeight = 20; + } + } + + private checkCropperPosition(maintainSize = false): void { + if (this.cropper.x1 < 0) { + this.cropper.x2 -= maintainSize ? this.cropper.x1 : 0; + this.cropper.x1 = 0; + } + if (this.cropper.y1 < 0) { + this.cropper.y2 -= maintainSize ? this.cropper.y1 : 0; + this.cropper.y1 = 0; + } + if (this.cropper.x2 > this.maxSize.width) { + this.cropper.x1 -= maintainSize + ? this.cropper.x2 - this.maxSize.width + : 0; + this.cropper.x2 = this.maxSize.width; + } + if (this.cropper.y2 > this.maxSize.height) { + this.cropper.y1 -= maintainSize + ? this.cropper.y2 - this.maxSize.height + : 0; + this.cropper.y2 = this.maxSize.height; + } + } + + private move(event: any) { + const diffX = this.getClientX(event) - this.moveStart.clientX; + const diffY = this.getClientY(event) - this.moveStart.clientY; + + this.cropper.x1 = this.moveStart.x1 + diffX; + this.cropper.y1 = this.moveStart.y1 + diffY; + this.cropper.x2 = this.moveStart.x2 + diffX; + this.cropper.y2 = this.moveStart.y2 + diffY; + } + + private resize(event: any): void { + const diffX = this.getClientX(event) - this.moveStart.clientX; + const diffY = this.getClientY(event) - this.moveStart.clientY; + switch (this.moveStart.position) { + case 'left': + this.cropper.x1 = Math.min( + this.moveStart.x1 + diffX, + this.cropper.x2 - this.cropperScaledMinWidth + ); + break; + case 'topleft': + this.cropper.x1 = Math.min( + this.moveStart.x1 + diffX, + this.cropper.x2 - this.cropperScaledMinWidth + ); + this.cropper.y1 = Math.min( + this.moveStart.y1 + diffY, + this.cropper.y2 - this.cropperScaledMinHeight + ); + break; + case 'top': + this.cropper.y1 = Math.min( + this.moveStart.y1 + diffY, + this.cropper.y2 - this.cropperScaledMinHeight + ); + break; + case 'topright': + this.cropper.x2 = Math.max( + this.moveStart.x2 + diffX, + this.cropper.x1 + this.cropperScaledMinWidth + ); + this.cropper.y1 = Math.min( + this.moveStart.y1 + diffY, + this.cropper.y2 - this.cropperScaledMinHeight + ); + break; + case 'right': + this.cropper.x2 = Math.max( + this.moveStart.x2 + diffX, + this.cropper.x1 + this.cropperScaledMinWidth + ); + break; + case 'bottomright': + this.cropper.x2 = Math.max( + this.moveStart.x2 + diffX, + this.cropper.x1 + this.cropperScaledMinWidth + ); + this.cropper.y2 = Math.max( + this.moveStart.y2 + diffY, + this.cropper.y1 + this.cropperScaledMinHeight + ); + break; + case 'bottom': + this.cropper.y2 = Math.max( + this.moveStart.y2 + diffY, + this.cropper.y1 + this.cropperScaledMinHeight + ); + break; + case 'bottomleft': + this.cropper.x1 = Math.min( + this.moveStart.x1 + diffX, + this.cropper.x2 - this.cropperScaledMinWidth + ); + this.cropper.y2 = Math.max( + this.moveStart.y2 + diffY, + this.cropper.y1 + this.cropperScaledMinHeight + ); + break; + } + + if (this.maintainAspectRatio) { + this.checkAspectRatio(); + } + } + + private checkAspectRatio(): void { + let overflowX = 0; + let overflowY = 0; + + switch (this.moveStart.position) { + case 'top': + this.cropper.x2 = + this.cropper.x1 + + (this.cropper.y2 - this.cropper.y1) * this.aspectRatio; + overflowX = Math.max(this.cropper.x2 - this.maxSize.width, 0); + overflowY = Math.max(0 - this.cropper.y1, 0); + if (overflowX > 0 || overflowY > 0) { + this.cropper.x2 -= + overflowY * this.aspectRatio > overflowX + ? overflowY * this.aspectRatio + : overflowX; + this.cropper.y1 += + overflowY * this.aspectRatio > overflowX + ? overflowY + : overflowX / this.aspectRatio; + } + break; + case 'bottom': + this.cropper.x2 = + this.cropper.x1 + + (this.cropper.y2 - this.cropper.y1) * this.aspectRatio; + overflowX = Math.max(this.cropper.x2 - this.maxSize.width, 0); + overflowY = Math.max(this.cropper.y2 - this.maxSize.height, 0); + if (overflowX > 0 || overflowY > 0) { + this.cropper.x2 -= + overflowY * this.aspectRatio > overflowX + ? overflowY * this.aspectRatio + : overflowX; + this.cropper.y2 -= + overflowY * this.aspectRatio > overflowX + ? overflowY + : overflowX / this.aspectRatio; + } + break; + case 'topleft': + this.cropper.y1 = + this.cropper.y2 - + (this.cropper.x2 - this.cropper.x1) / this.aspectRatio; + overflowX = Math.max(0 - this.cropper.x1, 0); + overflowY = Math.max(0 - this.cropper.y1, 0); + if (overflowX > 0 || overflowY > 0) { + this.cropper.x1 += + overflowY * this.aspectRatio > overflowX + ? overflowY * this.aspectRatio + : overflowX; + this.cropper.y1 += + overflowY * this.aspectRatio > overflowX + ? overflowY + : overflowX / this.aspectRatio; + } + break; + case 'topright': + this.cropper.y1 = + this.cropper.y2 - + (this.cropper.x2 - this.cropper.x1) / this.aspectRatio; + overflowX = Math.max(this.cropper.x2 - this.maxSize.width, 0); + overflowY = Math.max(0 - this.cropper.y1, 0); + if (overflowX > 0 || overflowY > 0) { + this.cropper.x2 -= + overflowY * this.aspectRatio > overflowX + ? overflowY * this.aspectRatio + : overflowX; + this.cropper.y1 += + overflowY * this.aspectRatio > overflowX + ? overflowY + : overflowX / this.aspectRatio; + } + break; + case 'right': + case 'bottomright': + this.cropper.y2 = + this.cropper.y1 + + (this.cropper.x2 - this.cropper.x1) / this.aspectRatio; + overflowX = Math.max(this.cropper.x2 - this.maxSize.width, 0); + overflowY = Math.max(this.cropper.y2 - this.maxSize.height, 0); + if (overflowX > 0 || overflowY > 0) { + this.cropper.x2 -= + overflowY * this.aspectRatio > overflowX + ? overflowY * this.aspectRatio + : overflowX; + this.cropper.y2 -= + overflowY * this.aspectRatio > overflowX + ? overflowY + : overflowX / this.aspectRatio; + } + break; + case 'left': + case 'bottomleft': + this.cropper.y2 = + this.cropper.y1 + + (this.cropper.x2 - this.cropper.x1) / this.aspectRatio; + overflowX = Math.max(0 - this.cropper.x1, 0); + overflowY = Math.max(this.cropper.y2 - this.maxSize.height, 0); + if (overflowX > 0 || overflowY > 0) { + this.cropper.x1 += + overflowY * this.aspectRatio > overflowX + ? overflowY * this.aspectRatio + : overflowX; + this.cropper.y2 -= + overflowY * this.aspectRatio > overflowX + ? overflowY + : overflowX / this.aspectRatio; + } + break; + } + } + + private doAutoCrop(): void { + if (this.autoCrop) { + this.crop(); + } + } + + private getImagePosition(): XCropperPosition { + const sourceImageElement = this.sourceImage.nativeElement; + const ratio = this.originalSize.width / sourceImageElement.offsetWidth; + return { + x1: Math.round(this.cropper.x1 * ratio), + y1: Math.round(this.cropper.y1 * ratio), + x2: Math.min( + Math.round(this.cropper.x2 * ratio), + this.originalSize.width + ), + y2: Math.min( + Math.round(this.cropper.y2 * ratio), + this.originalSize.height + ), + }; + } + + private cropToOutputType( + outputType: XOutputType, + cropCanvas: HTMLCanvasElement, + output: XImageCroppedEvent + ): XImageCroppedEvent | Promise { + switch (outputType) { + case 'file': + return this.cropToFile(cropCanvas).then((result: Blob | null) => { + output.file = result; + this.imageCropped.emit(output); + return output; + }); + case 'both': + return this.cropToFile(cropCanvas).then((result: Blob | null) => { + output.base64 = this.cropToBase64(cropCanvas); + output.file = result; + this.imageCropped.emit(output); + return output; + }); + default: + output.base64 = this.cropToBase64(cropCanvas); + this.imageCropped.emit(output); + return output; + } + } + + private cropToBase64(cropCanvas: HTMLCanvasElement): string { + const imageBase64 = cropCanvas.toDataURL( + 'image/' + this.format, + this.getQuality() + ); + this.imageCroppedBase64.emit(imageBase64); + return imageBase64; + } + + private cropToFile(cropCanvas: HTMLCanvasElement): Promise { + return this.getCanvasBlob(cropCanvas).then((result: Blob | null) => { + if (result) { + this.imageCroppedFile.emit(result); + } + return result; + }); + } + + private getCanvasBlob(cropCanvas: HTMLCanvasElement): Promise { + return new Promise((resolve) => { + cropCanvas.toBlob( + (result: Blob | null) => this.zone.run(() => resolve(result)), + 'image/' + this.format, + this.getQuality() + ); + }); + } + + private getQuality(): number { + return Math.min(1, Math.max(0, this.imageQuality / 100)); + } + + private getResizeRatio(width: number): number { + return this.resizeToWidth > 0 && + (!this.onlyScaleDown || width > this.resizeToWidth) + ? this.resizeToWidth / width + : 1; + } + + private getClientX(event: any): number { + return ( + event.clientX || + (event.touches && event.touches[0] && event.touches[0].clientX) + ); + } + + private getClientY(event: any): number { + return ( + event.clientY || + (event.touches && event.touches[0] && event.touches[0].clientY) + ); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-image-cropper\x-image-cropper.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XIconModule } from '../x-icon/x-icon.module'; +import { XImageCropperService } from './x-image-cropper.service'; +import { XImageCropperComponent } from './x-image-cropper.component'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + providers: [XImageCropperService], + declarations: [XImageCropperComponent], + entryComponents: [XImageCropperComponent], + imports: [CommonModule, XBaseComponentModule, XIconModule], + exports: [XBaseComponentModule, XIconModule, XImageCropperComponent] +}) +export class XImageCropperModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-image-cropper\x-image-cropper.service.ts + +import { + toFileBlob, + blobToFile, + XDialogResult, + isNullOrUndefined, +} from 'x-framework-core'; +import { + MatDialog, + MatDialogRef, + MatDialogConfig, +} from '@angular/material/dialog'; +import { + XImageCroppedEvent, + XImageCropperResult, + XImageCropperProperties, +} from './x-image-cropper.typings'; +import { XImageCropperComponent } from './x-image-cropper.component'; +import { Injectable, Output, EventEmitter, Inject } from '@angular/core'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +@Injectable({ + providedIn: 'root', +}) +export class XImageCropperService { + // + //#region Props ... + // + private dialogMaxWidth: number; + private dialogMinWidth: number; + + // + private sourceFile: File; + private DIALOG_REF: MatDialogRef; + + // + // tslint:disable-next-line:no-output-on-prefix + @Output() + onResult = new EventEmitter(); + + // + minAllowedImageSize: number; + maxAllowedImageSize: number; + //#endregion + + // + //#region Constructor ... + constructor( + private dialog: MatDialog, + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig + ) { + // + this.minAllowedImageSize = config.minAllowedImageSize; + this.maxAllowedImageSize = config.maxAllowedImageSize; + + // + this.dialogMinWidth = Math.min(window.innerWidth * 0.8, 300); + this.dialogMaxWidth = Math.min(window.innerWidth * 0.8, 400); + } + //#endregion + + // + //#region Public Actions ... + /** + * show Image Cropper ... + * @param file the source image file ... + * @param props Image Cropper Properties ... + */ + showCropper(file: File, props?: XImageCropperProperties) { + // + this.sourceFile = file; + + // + // Prepare Cropper Props ... + let cropperProps: XImageCropperProperties; + if (props) { + cropperProps = props; + if (!props.imagefile) { + cropperProps.imagefile = file; + } + cropperProps.maxSingleFileThumbSize = this.maxAllowedImageSize; + } else { + cropperProps = this.getDefaultCropperProperties(file); + } + + // + const config: MatDialogConfig = { + width: this.dialogMinWidth + 'px', + minWidth: this.dialogMinWidth + 'px', + maxWidth: this.dialogMaxWidth + 'px', + data: { + props: cropperProps, + }, + }; + + // + // Show Cropper inside a Dialog ... + this.DIALOG_REF = this.dialog.open(XImageCropperComponent, config); + this.DIALOG_REF.disableClose = true; + + // + //#region Register Event Handlers ... + // + // Subscribe to Event Listener for catching Cropper Change ... + const cropperComponent = this.DIALOG_REF + ? this.DIALOG_REF.componentInstance + : null; + if (cropperComponent) { + // + if (cropperProps.startCropImage) { + cropperComponent.startCropImage.asObservable().subscribe(() => { + cropperProps.startCropImage(); + }); + } + + // + if (cropperProps.imageCropped) { + cropperComponent.imageCropped + .asObservable() + .subscribe((event: XImageCroppedEvent) => { + cropperProps.imageCropped(event); + }); + } + + // + if (cropperProps.imageCroppedBase64) { + cropperComponent.imageCroppedBase64 + .asObservable() + .subscribe((data: string) => { + cropperProps.imageCroppedBase64(data); + }); + } + + // + if (cropperProps.imageCroppedFile) { + cropperComponent.imageCroppedFile + .asObservable() + .subscribe((mFile: Blob) => { + cropperProps.imageCroppedFile(mFile); + }); + } + + // + if (cropperProps.imageLoaded) { + cropperComponent.imageLoaded.asObservable().subscribe(() => { + cropperProps.imageLoaded(); + }); + } + + // + if (cropperProps.cropperReady) { + cropperComponent.cropperReady.asObservable().subscribe(() => { + cropperProps.cropperReady(); + }); + } + + // + if (cropperProps.loadImageFailed) { + cropperComponent.loadImageFailed.asObservable().subscribe(() => { + cropperProps.loadImageFailed(); + }); + } + } + //#endregion + + // + // Handle Dialog Result ... + this.DIALOG_REF.afterClosed().subscribe((result) => { + // + if (isNullOrUndefined(result) || result.result !== XDialogResult.Ok) { + // + // Cancel Cropper ... + this.onResult.emit({ result: XDialogResult.Cancel }); + } else { + // + // Use Cropper Result ... + const res: XImageCroppedEvent = result.event as XImageCroppedEvent; + + // + let mFile: File; + if (res.file) { + // + mFile = this.toFile(res.file); + this.sourceFile = mFile; + } + + // + this.onResult.emit({ + result: result.result, + data: res.base64, + file: mFile, + }); + } + }); + } + + /** + * close opened Dialog manually ... + */ + closeCropper() { + // + if (this.DIALOG_REF != null) { + this.DIALOG_REF.close(); + } + + // + this.DIALOG_REF = null; + } + //#endregion + + // + //#region Private Actions ... + /** + * Prepare Default Properties for Image Cropper ... + */ + private getDefaultCropperProperties(file: File) { + return { + // + imagefile: file, + + // + aspectRatio: 1, + maintainAspectRatio: true, + + // + resizeToWidth: this.dialogMinWidth, + cropperMinWidth: this.minAllowedImageSize, + cropperMinHeight: this.minAllowedImageSize, + + // + onlyScaleDown: true, + roundCropper: true, + + // + format: 'png', + outputType: 'both', + + // + maxSingleFileThumbSize: this.maxAllowedImageSize, + } as XImageCropperProperties; + } + + /** + * Generate a File Blob Object based on Source Image and Cropped file ... + */ + private toFile(file: Blob): File { + // + if (!this.sourceFile) { + return null; + } + + // + const fileBlob = toFileBlob(this.sourceFile); + + // + return blobToFile(file, fileBlob.name); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-image-cropper\x-image-cropper.typings.ts + +import { + Input, + Output, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { map } from 'rxjs/operators'; +import { + XBaseContainer, + IXBaseContainer, + XBaseContainerComponent, +} from '../classes/i-x-base.container'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XDialogResult, XOneOrManyType, XStandardType } from 'x-framework-core'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +// +//#region Definitions ... +export interface XCropperPosition { + x1: number; + y1: number; + x2: number; + y2: number; +} + +export interface XDimensions { + width: number; + height: number; +} + +export interface XImageCroppedEvent { + base64?: string | null; + file?: Blob | null; + width: number; + height: number; + cropperPosition: XCropperPosition; + imagePosition: XCropperPosition; +} + +export interface XMoveStart { + active: boolean; + type: string | null; + position: string | null; + x1: number; + y1: number; + x2: number; + y2: number; + clientX: number; + clientY: number; +} + +export interface XImageCropperProperties { + // + imageChangedEvent?: (event: any) => void; + + // + imagefile?: File; + imageBase64?: string; + + // + format?: XImageFormat; + outputType?: XOutputType; + alignImage?: XImageAlign; + cropper?: XCropperPosition; + + // + autoCrop?: boolean; + aspectRatio?: number; + imageQuality?: number; + resizeToWidth?: number; + roundCropper?: boolean; + onlyScaleDown?: boolean; + cropperMinWidth?: number; + cropperMinHeight?: number; + maintainAspectRatio?: boolean; + + // + maxSingleFileThumbSize: number; + + // + startCropImage?: () => void; + imageCropped?: (event: XImageCroppedEvent) => void; + imageCroppedBase64?: (data: string) => void; + imageCroppedFile?: (file: Blob) => void; + imageLoaded?: () => void; + cropperReady?: () => void; + loadImageFailed?: () => void; +} + +export interface XImageCropperResult { + result: XDialogResult; + data?: string; + file?: File; +} + +export type XOutputType = 'base64' | 'file' | 'both'; + +export type XImageFormat = 'png' | 'jpeg' | 'bmp' | 'webp' | 'ico'; + +export type XImageAlign = 'left' | 'center'; +//#endregion + +export enum XImageCropperAction { + // + RotateLeft = 'rotate-left', + RotateRight = 'rotate-right', + FlipHorizontal = 'flip-horizontal', + FlipVertical = 'flip-vertical', + + // + Refresh = 'refresh', +} + +export type XImageCropperActionIdentifier = XImageCropperAction | string; + +export interface XImageCropperActionModel + extends XBaseActionModel {} + +// +//#region Container ... +export interface IXImageCropperContainer extends IXBaseContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + imageFileChanged: File; + imageChangedEvent: any; + imageBase64: string; + format: XImageFormat; + outputType: XOutputType; + maintainAspectRatio: boolean; + aspectRatio: number; + resizeToWidth: number; + cropperMinWidth: number; + cropperMinHeight: number; + roundCropper: boolean; + onlyScaleDown: boolean; + imageQuality: number; + autoCrop: boolean; + alignImage: XImageAlign; + cropper: XCropperPosition; + + // + startCropImage: EventEmitter; + imageCropped: EventEmitter; + imageCroppedBase64: EventEmitter; + imageCroppedFile: EventEmitter; + imageLoaded: EventEmitter; + cropperReady: EventEmitter; + loadImageFailed: EventEmitter; +} + +@Injectable({ + providedIn: 'root', +}) +export class XImageCropperContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider: Subject = + new Subject(); + readonly cssClass: XStandardType>; + + // + readonly imageFileChanged: File; + readonly imageChangedEvent: any; + readonly imageBase64: string; + readonly format: XImageFormat = 'png'; + readonly outputType: XOutputType = 'both'; + readonly maintainAspectRatio: boolean = true; + readonly aspectRatio: number = 1; + readonly resizeToWidth: number = 0; + readonly cropperMinWidth: number = 0; + readonly cropperMinHeight: number = 0; + readonly roundCropper: boolean = false; + readonly onlyScaleDown: boolean = false; + readonly imageQuality: number = 92; + readonly autoCrop: boolean = true; + readonly alignImage: XImageAlign = 'center'; + readonly cropper: XCropperPosition = { + x1: -100, + y1: -100, + x2: 10000, + y2: 10000, + }; + + // + readonly startCropImage: EventEmitter = new EventEmitter(); + readonly imageCropped: EventEmitter = + new EventEmitter(); + readonly imageCroppedBase64: EventEmitter = + new EventEmitter(); + readonly imageCroppedFile: EventEmitter = new EventEmitter(); + readonly imageLoaded: EventEmitter = new EventEmitter(); + readonly cropperReady: EventEmitter = new EventEmitter(); + readonly loadImageFailed: EventEmitter = new EventEmitter(); + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XImageCropperContainerComponent + extends XBaseContainerComponent + implements IXImageCropperContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + imageFileChanged: File = this.propertyProvider.imageFileChanged; + + @Input() + imageChangedEvent: any = this.propertyProvider.imageChangedEvent; + + @Input() + imageBase64: string = this.propertyProvider.imageBase64; + + @Input() + format: XImageFormat = this.propertyProvider.format; + + @Input() + outputType: XOutputType = this.propertyProvider.outputType; + + @Input() + maintainAspectRatio: boolean = this.propertyProvider.maintainAspectRatio; + + @Input() + aspectRatio: number = this.propertyProvider.aspectRatio; + + @Input() + resizeToWidth: number = this.propertyProvider.resizeToWidth; + + @Input() + cropperMinWidth: number = this.propertyProvider.cropperMinWidth; + + @Input() + cropperMinHeight: number = this.propertyProvider.cropperMinHeight; + + @Input() + roundCropper: boolean = this.propertyProvider.roundCropper; + + @Input() + onlyScaleDown: boolean = this.propertyProvider.onlyScaleDown; + + @Input() + imageQuality: number = this.propertyProvider.imageQuality; + + @Input() + autoCrop: boolean = this.propertyProvider.autoCrop; + + @Input() + alignImage: XImageAlign = this.propertyProvider.alignImage; + + @Input() + cropper: XCropperPosition = this.propertyProvider.cropper; + + // + @Output() + startCropImage: EventEmitter = new EventEmitter(); + + @Output() + imageCropped: EventEmitter = new EventEmitter(); + + @Output() + imageCroppedBase64: EventEmitter = new EventEmitter(); + + @Output() + imageCroppedFile: EventEmitter = new EventEmitter(); + + @Output() + imageLoaded: EventEmitter = new EventEmitter(); + + @Output() + cropperReady: EventEmitter = new EventEmitter(); + + @Output() + loadImageFailed: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XImageCropperContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXImageCropperContainer + ): Observable { + return forkJoin({ + // + cssClass: this.getValue(container ? container.cssClass : this.cssClass), + imageFileChanged: this.getValue( + container ? container.imageFileChanged : this.imageFileChanged + ), + imageChangedEvent: this.getValue( + container ? container.imageChangedEvent : this.imageChangedEvent + ), + imageBase64: this.getValue( + container ? container.imageBase64 : this.imageBase64 + ), + format: this.getValue(container ? container.format : this.format), + outputType: this.getValue( + container ? container.outputType : this.outputType + ), + maintainAspectRatio: this.getValue( + container ? container.maintainAspectRatio : this.maintainAspectRatio + ), + aspectRatio: this.getValue( + container ? container.aspectRatio : this.aspectRatio + ), + resizeToWidth: this.getValue( + container ? container.resizeToWidth : this.resizeToWidth + ), + cropperMinWidth: this.getValue( + container ? container.cropperMinWidth : this.cropperMinWidth + ), + cropperMinHeight: this.getValue( + container ? container.cropperMinHeight : this.cropperMinHeight + ), + roundCropper: this.getValue( + container ? container.roundCropper : this.roundCropper + ), + onlyScaleDown: this.getValue( + container ? container.onlyScaleDown : this.onlyScaleDown + ), + imageQuality: this.getValue( + container ? container.imageQuality : this.imageQuality + ), + autoCrop: this.getValue(container ? container.autoCrop : this.autoCrop), + alignImage: this.getValue( + container ? container.alignImage : this.alignImage + ), + cropper: this.getValue(container ? container.cropper : this.cropper), + }).pipe( + map((data) => { + return { + // + ...data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + startCropImage: this.startCropImage, + imageCropped: this.imageCropped, + imageCroppedBase64: this.imageCroppedBase64, + imageCroppedFile: this.imageCroppedFile, + imageLoaded: this.imageLoaded, + cropperReady: this.cropperReady, + loadImageFailed: this.loadImageFailed, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-list\x-list.component.html + + + + + + + + +
+ +
+
+
+ + + +
+ +
+ + + + + +
+ + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ getResourceValue(option.title) | async }} + + + + + + + + {{ getResourceValue(option.title) | async }} + + + + + + +
{{ item | json }}
+
+ + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-list\x-list.component.scss + +ion-item-option > ion-item { + --background: transparent !important; +} + +ion-list, +ion-item.item, +ion-item-divider { + --background: transparent !important; +} + +ion-item-divider { + --color: var(--ion-color-dark-shade); +} + +ion-item.item.mobile { + --ion-safe-area-end: 0px; + + --padding-start: 8px; + --inner-padding-start: 0px; + + --padding-end: 8px; + --inner-padding-end: 0px; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + margin: 0; +} + +.x-menu { + z-index: 10; +} + +.x-check { + z-index: 1; +} + +x-card { + width: inherit; + height: inherit; +} + +:host ::ng-deep .button-solid { + --color: unset !important; + --background: unset !important; + --box-shadow: unset !important; +} + +.x-list-toolbar { + &.sticky { + top: 0; + position: sticky; + position: -webkit-sticky; + z-index: 200; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-list\x-list.component.ts + +import { + nameof, + toArray, + hasChild, + isFunction, + removeItem, + XOneOrManyType, + isNullOrUndefined, + isNullOrEmptyString, +} from 'x-framework-core'; +import { + XListItem, + XListAction, + XListActionModel, + XListItemSlideOption, + XListContainerComponent, +} from './x-list.typings'; +import { + XActionBarItem, + XActionBarAction, + XActionBarActionModel, + DefaultActionBarItemIds, + defaultActionBarAddItem, + defaultActionBarRemoveItem, + XActionBarActionFiredModel, + defaultActionBarSelectAllItem, + defaultActionBarDeSelectAllItem, + defaultActionBarInverseSelectionItem, +} from '../x-action-bar/x-action-bar.typings'; +import { map, concatMap } from 'rxjs/operators'; +import { IonItemSliding } from '@ionic/angular'; +import { + XPopoverResult, + XPopoverOption, + XPopoverComponentProps, +} from '../dialogs/x-popover/x-popover.typings'; +import { moveItemInArray } from '@angular/cdk/drag-drop'; +import { XButtonType } from '../x-button/x-button.typings'; +import { Subject, Subscription, BehaviorSubject, of } from 'rxjs'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; +import { toPopoverOptions } from '../dialogs/x-popover/x-popover.tools'; +import { XSearchBarAction } from '../x-search-bar/x-search-bar.typings'; +import { XPopoverComponent } from '../dialogs/x-popover/x-popover.component'; + +@Component({ + // tslint:disable-next-line: component-selector + selector: 'x-list', + templateUrl: './x-list.component.html', + styleUrls: ['./x-list.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XListComponent extends XListContainerComponent { + // + //#region Props ... + // + private SELECTED_ITEMS: XListItem[] = []; + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + private ACTION_BAR_ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + private HIDE_ADD = false; + private DISABLE_ADD = false; + private addDisablerSubject = new BehaviorSubject(false); + + // + private HIDE_REMOVE = false; + private DISABLE_REMOVE = false; + private removeDisablerSubject = new BehaviorSubject(true); + + // + private HIDE_SELECT_ALL = false; + private DISABLE_SELECT_ALL = false; + private selectAllDisablerSubject = new BehaviorSubject(true); + + // + private HIDE_DESELECT_ALL = false; + private DISABLE_DESELECT_ALL = false; + private deSelectAllDisablerSubject = new BehaviorSubject(true); + + // + private HIDE_INVERSE_SELECTION = false; + private DISABLE_INVERSE_SELECTION = false; + private inverseSelectionDisablerSubject = new BehaviorSubject(true); + + // + readonly ButtonTypes = Object.assign({}, XButtonType); + + // + filteredItems: XListItem[] = []; + + // + get count() { + return this.items ? this.getValue(this.items) : of(0); + } + + get filteredItemsCount() { + return this.filteredItems ? this.filteredItems.length : 0; + } + + get selectedItems() { + return this.SELECTED_ITEMS; + } + //#endregion + + // + //#region LifeCycle ... + async onInit() { + super.onInit(); + + // + // await this.handleStartup(); + } + + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + const isActionBarActionProviderChanged = changeKeys.includes( + nameof('actionBarActionProvider') + ); + if (isActionProviderChanged || isActionBarActionProviderChanged) { + this.registerActionProvider(); + } + + // + const isItemsChanged = changeKeys.includes(nameof('items')); + if (isItemsChanged) { + // + await this.handleStartup(); + this.itemsChanged.emit(); + } + + // + const isActionBarActionsChanged = changeKeys.includes( + nameof('actionBarActions') + ); + const isActionBarHideActionsChanged = changeKeys.includes( + nameof('actionBarHideActions') + ); + if (isActionBarActionsChanged || isActionBarHideActionsChanged) { + await this.prepareActionBarActions(); + } + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + //#endregion + + // + //#region UI Providers ... + getStartSlides(item: XListItem) { + return item && item.slideOptions + ? item.slideOptions.filter((opt) => opt.slot === 'start') + : null; + } + + getEndSlides(item: XListItem) { + return item && item.slideOptions + ? item.slideOptions.filter((opt) => opt.slot === 'end') + : null; + } + + getOptionSlot(option: XListItemSlideOption) { + return this.getOptionOnlyIcon(option).pipe( + map((onlyIcon) => { + return onlyIcon ? 'icon-only' : ''; + }) + ); + } + + getOptionIconSlot(option: XListItemSlideOption) { + return this.getOptionOnlyIcon(option).pipe( + concatMap((onlyIcon) => { + return this.getOptionSlot(option).pipe( + map((slot) => { + return onlyIcon ? '' : slot; + }) + ); + }) + ); + } + + getNonSlidingOptionIconSlot(option: XListItemSlideOption) { + return this.getOptionOnlyIcon(option).pipe( + map((onlyIcon) => { + return onlyIcon ? 'icon-only' : 'end'; + }) + ); + } + + getOptionOnlyIcon(option: XListItemSlideOption) { + return this.getValue(!isNullOrUndefined(option) ? option.onlyIcon : false); + } + + getIonItemLine() { + return this.getValue(this.showDivider).pipe( + map((showDivider) => (showDivider ? 'full' : 'none')) + ); + } + //#endregion + + // + //#region UI Handlers ... + handleActionItemSelected(item: XListItem) { + // + if (this.selectableItems) { + item.isSelected = !item.isSelected; + this.issueSelectionChanged(); + } + + // + this.itemSelected.emit(item); + } + + handleDoReorderItems(ev: CustomEvent | any) { + // + const to = ev.detail.to; + // tslint:disable-next-line:no-shadowed-variable + const from = ev.detail.from; + moveItemInArray(this.filteredItems, from, to); + + // + this.orderChanged.emit(); + ev.detail.complete(); + } + + async handleMoreButtonClicked( + event: Event, + slide: IonItemSliding, + item: XListItem + ) { + // + if (!isNullOrUndefined(slide)) { + await slide.closeOpened(); + } + + // + // Disable Only Icons for Menu Options ... + const options: XPopoverOption[] = toArray(toPopoverOptions(item)); + options.forEach((o) => { + // + o.onlyIcon = false; + + // + o.slot = 'end'; + + // + o.handler = undefined; + }); + + // + // Prepare PopOver Component Props ... + const componentProps: XPopoverComponentProps = { + options, + }; + + // + // Present Context Menu Options ... + const popOver = await this.managerService.dialogService.presentPopover({ + event, + componentProps, + translucent: true, + showBackdrop: true, + component: XPopoverComponent, + }); + + // + // Extract Data from PopOver ... + const { data } = await popOver.onWillDismiss(); + if (!data) { + return; + } + + // + const result = data as XPopoverResult; + if ( + isNullOrUndefined(result.option) || + isNullOrEmptyString(result.option.id) + ) { + return; + } + + // + this.handleActionOptionSelected(slide, result.option.id, item); + } + + async handleActionOptionSelected( + slide: IonItemSliding, + id: string, + item: XListItem + ) { + // + if (isNullOrEmptyString(id) || isNullOrUndefined(item)) { + return; + } + + // + // if (!isNullOrUndefined) + const action = item.slideOptions.find((i) => i.id === id); + if ( + !isNullOrUndefined(action) && + !isNullOrUndefined(action.handler) && + isFunction(action.handler) + ) { + await this.getValueAsync(action.handler()); + } + + // + if (this.closeOptionsAfterSelect && !isNullOrUndefined(slide)) { + // + slide.closeOpened().then(() => { + // + this.optionSelected.emit({ + id, + data: item, + }); + }); + } else { + // + this.optionSelected.emit({ + id, + data: item, + }); + } + } + + async handleActionRemoveItems() { + // + if (!this.hasChild(this.SELECTED_ITEMS)) { + return; + } + + // + const items = this.SELECTED_ITEMS.map((i) => i.data); + + // + const confirmRemove = await this.getValueAsync(this.confirmRemove); + if (confirmRemove) { + await this.managerService.dialogService.presentAreYouSureYesNoDialog( + () => { + this.removeItems.emit({ + items, + }); + } + ); + } else { + this.removeItems.emit({ + items, + }); + } + } + + async handleActionBarActionFired(model: XActionBarActionFiredModel) { + // + if (isNullOrUndefined(model) || isNullOrEmptyString(model.action)) { + return; + } + + // + switch (model.action) { + // + case DefaultActionBarItemIds.SelectAll: + await this.selectAll(); + break; + + // + case DefaultActionBarItemIds.DeSelectAll: + await this.deselectAll(); + break; + + // + case DefaultActionBarItemIds.InverseSelection: + await this.inverseSelection(); + break; + + // + case DefaultActionBarItemIds.Remove: + // + await this.handleActionRemoveItems(); + break; + } + + // + this.actionBarActionFired.emit(model); + } + //#endregion + + // + //#region Actions ... + async selectAll() { + // + this.filteredItems.forEach((item) => { + if (!item.disabled) { + // + item.isSelected = true; + this.changeDetector.markForCheck(); + } + }); + + // + this.issueSelectionChanged(); + this.handleChangeDetection(); + } + + async deselectAll() { + // + this.filteredItems.forEach((item) => { + if (!item.disabled) { + // + item.isSelected = false; + this.changeDetector.markForCheck(); + } + }); + + // + this.issueSelectionChanged(); + this.handleChangeDetection(); + } + + async inverseSelection() { + // + this.filteredItems.forEach((item) => { + if (!item.disabled) { + // + item.isSelected = !item.isSelected; + this.changeDetector.markForCheck(); + } + }); + + // + this.issueSelectionChanged(); + this.handleChangeDetection(); + } + + async clearFilter(fill = false) { + this.filteredItems = []; + + // + if (fill) { + // + const items = await this.getValueAsync(this.items); + this.filteredItems = items; + } + + // + this.handleChangeDetection(); + } + + async filterItems(query: string) { + // + this.searchBarQueryChange.emit(query); + + // + await this.clearFilter(); + const items = await this.getValueAsync(this.items); + + // + if (isNullOrEmptyString(query)) { + // + this.filteredItems = items; + this.handleChangeDetection(); + return; + } + + // + const result = await this.getValueAsync( + this.filteringHandler(query, items) + ); + this.filteredItems = result; + + // + this.handleChangeDetection(); + } + //#endregion + + // + //#region Private ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + if (!this.actionBarActionProvider) { + this.actionBarActionProvider = new Subject(); + } + + // + // Handle Action Provider ... + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XListAction.Add: + // + const newItem = model.payload as XListItem; + if (newItem) { + // + const items = await this.getArrayValueAsync(this.items); + this.items = [...items, newItem]; + this.itemsChanged.emit(); + this.handleChangeDetection(); + } + break; + + // + case XListAction.Set: + // + const newItems = model.payload as XListItem[]; + if (newItems) { + // + this.items = [...newItems]; + this.itemsChanged.emit(); + this.handleChangeDetection(); + } + break; + + // + case XListAction.Remove: + // + const mustRemoveItem = model.payload as XListItem; + if (mustRemoveItem) { + // + const items = await this.getArrayValueAsync(this.items); + this.items = [...removeItem(items, mustRemoveItem)]; + this.itemsChanged.emit(); + this.handleChangeDetection(); + } + break; + + // + case XListAction.SelectAll: + await this.selectAll(); + break; + + // + case XListAction.DeselectAll: + await this.deselectAll(); + break; + + // + case XListAction.InverseSelection: + await this.inverseSelection(); + break; + + // + case XListAction.Refresh: + this.handleChangeDetection(); + break; + } + }); + + // + // Handle ActionBar Provider ... + this.ACTION_BAR_ACTION_PROVIDER_SUBSCRIPTION = this.actionBarActionProvider + .asObservable() + .subscribe((model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XActionBarAction.EnableAction: + // + switch (model.id) { + // + case DefaultActionBarItemIds.Add: + // + this.DISABLE_ADD = false; + this.addDisablerSubject.next(false); + break; + + // + case DefaultActionBarItemIds.Remove: + // + this.DISABLE_REMOVE = false; + this.removeDisablerSubject.next(false); + break; + + // + case DefaultActionBarItemIds.SelectAll: + // + this.DISABLE_SELECT_ALL = false; + this.selectAllDisablerSubject.next(false); + break; + + // + case DefaultActionBarItemIds.DeSelectAll: + // + this.DISABLE_DESELECT_ALL = false; + this.deSelectAllDisablerSubject.next(false); + break; + + // + case DefaultActionBarItemIds.InverseSelection: + // + this.DISABLE_INVERSE_SELECTION = false; + this.inverseSelectionDisablerSubject.next(false); + break; + } + break; + + // + case XActionBarAction.DisbaleAction: + // + switch (model.id) { + // + case DefaultActionBarItemIds.Add: + // + this.DISABLE_ADD = true; + this.addDisablerSubject.next(true); + break; + + // + case DefaultActionBarItemIds.Remove: + // + this.DISABLE_REMOVE = true; + this.removeDisablerSubject.next(true); + break; + + // + case DefaultActionBarItemIds.SelectAll: + // + this.DISABLE_SELECT_ALL = true; + this.selectAllDisablerSubject.next(true); + break; + + // + case DefaultActionBarItemIds.DeSelectAll: + // + this.DISABLE_DESELECT_ALL = true; + this.deSelectAllDisablerSubject.next(true); + break; + + // + case DefaultActionBarItemIds.InverseSelection: + // + this.DISABLE_INVERSE_SELECTION = true; + this.inverseSelectionDisablerSubject.next(true); + break; + } + break; + } + }); + } + + private unregisterActionProvider() { + // + if (this.ACTION_PROVIDER_SUBSCRIPTION) { + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + + // + if (this.ACTION_BAR_ACTION_PROVIDER_SUBSCRIPTION) { + // + this.ACTION_BAR_ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_BAR_ACTION_PROVIDER_SUBSCRIPTION = null; + } + } + + private async handleStartup() { + // + await this.prepareActionBarActions(); + + // + this.SELECTED_ITEMS = []; + + // + const items = await this.getValueAsync( + isNullOrUndefined(this.items) ? [] : this.items + ); + if (!hasChild(items)) { + this.filteredItems = []; + } else { + this.filteredItems = items; + } + + // + this.issueSelectionChanged(); + // this.handleChangeDetection(); + } + + private handleChangeDetection() { + // + this.actionBarActionProvider.next({ + action: XActionBarAction.Refresh, + }); + + // + this.searchBarActionProvider.next({ + action: XSearchBarAction.Refresh, + }); + + // + this.detectChanges(); + } + + private issueSelectionChanged() { + // + this.SELECTED_ITEMS = []; + if (!this.selectableItems) { + return; + } + + // + const selectedItems = this.filteredItems.filter((item) => item.isSelected); + this.SELECTED_ITEMS = [...selectedItems]; + + // + this.selectionChanged.emit(selectedItems); + this.prepareActionBarActionsSubjects(selectedItems); + } + + // + //#region ActionBar Preparations ... + private async prepareActionBarActions() { + // + //#region Prepare Default Actions ... + const hideActions = await this.getArrayValueAsync( + this.actionBarHideActions + ); + + // + let idx = 0; + const defaultActions: XOneOrManyType = []; + + // + // Remove ... + const removeId = DefaultActionBarItemIds.Remove.toString(); + if (!hideActions.includes(removeId)) { + // + defaultActions.push({ + // + ...defaultActionBarRemoveItem, + + // + index: idx, + disabled: this.removeDisablerSubject.asObservable(), + }); + + // + idx++; + } + + // + // Add ... + const addId = DefaultActionBarItemIds.Add.toString(); + if (!hideActions.includes(addId)) { + // + defaultActions.push({ + // + ...defaultActionBarAddItem, + + // + index: idx, + disabled: this.addDisablerSubject.asObservable(), + }); + + // + idx++; + } + + // + // Select All ... + const selectAllId = DefaultActionBarItemIds.SelectAll.toString(); + if (!hideActions.includes(selectAllId)) { + // + defaultActions.push({ + // + ...defaultActionBarSelectAllItem, + + // + index: idx, + disabled: this.selectAllDisablerSubject.asObservable(), + }); + + // + idx++; + } + + // + // Inverse Selection ... + const inverseSelectionId = + DefaultActionBarItemIds.InverseSelection.toString(); + if (!hideActions.includes(inverseSelectionId)) { + // + defaultActions.push({ + // + ...defaultActionBarInverseSelectionItem, + + // + index: idx, + disabled: this.inverseSelectionDisablerSubject.asObservable(), + }); + + // + idx++; + } + + // + // De Select All ... + const deselectAllId = DefaultActionBarItemIds.DeSelectAll.toString(); + if (!hideActions.includes(deselectAllId)) { + // + defaultActions.push({ + // + ...defaultActionBarDeSelectAllItem, + + // + index: idx, + disabled: this.deSelectAllDisablerSubject.asObservable(), + }); + + // + idx++; + } + //#endregion + + // + const useDefaultActions = await this.getValueAsync(this.useDefaultActions); + if (!this.actionBarActions && !!useDefaultActions) { + // + this.actionBarActions = defaultActions; + return; + } + + // + const actions = await this.getArrayValueAsync(this.actionBarActions); + if (!hasChild(actions) && !!useDefaultActions) { + // + this.actionBarActions = defaultActions; + return; + } + + // + this.actionBarActions = actions; + } + + private async prepareActionBarActionsSubjects(items: XListItem[]) { + // + const listItems = await this.getValueAsync(this.items); + + // + // Check Select All ... + if (!hasChild(listItems)) { + // + // Remove ... + if (!this.DISABLE_REMOVE) { + this.removeDisablerSubject.next(true); + } + + // + // Select All ... + if (!this.DISABLE_SELECT_ALL) { + this.selectAllDisablerSubject.next(true); + } + + // + // Deselect All ... + if (!this.DISABLE_DESELECT_ALL) { + this.deSelectAllDisablerSubject.next(true); + } + + // + // Inverse Selection ... + if (!this.DISABLE_INVERSE_SELECTION) { + this.inverseSelectionDisablerSubject.next(true); + } + } else { + // + // DeSelectAll ... + if (!hasChild(items)) { + // + // Remove ... + if (!this.DISABLE_REMOVE) { + this.removeDisablerSubject.next(true); + } + + // + // Deselect All ... + if (!this.DISABLE_DESELECT_ALL) { + this.deSelectAllDisablerSubject.next(true); + } + + // + // Inverse Selection ... + if (!this.DISABLE_INVERSE_SELECTION) { + this.inverseSelectionDisablerSubject.next(true); + } + } else { + // + // Remove ... + if (!this.DISABLE_REMOVE) { + this.removeDisablerSubject.next(false); + } + + // + // Deselect All ... + if (!this.DISABLE_DESELECT_ALL) { + this.deSelectAllDisablerSubject.next(false); + } + + // + // Inverse Selection ... + if (!this.DISABLE_INVERSE_SELECTION) { + this.inverseSelectionDisablerSubject.next(false); + } + } + + // + if (listItems.length === items.length) { + // + // Select All ... + if (!this.DISABLE_SELECT_ALL) { + this.selectAllDisablerSubject.next(true); + } + + // + // Inverse Selection ... + if (!this.DISABLE_INVERSE_SELECTION) { + this.inverseSelectionDisablerSubject.next(true); + } + } else { + // + // Select All ... + if (!this.DISABLE_SELECT_ALL) { + this.selectAllDisablerSubject.next(false); + } + } + } + + // + this.handleChangeDetection(); + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-list\x-list.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XListComponent } from './x-list.component'; +import { XIconModule } from '../x-icon/x-icon.module'; +import { XCardModule } from '../x-card/x-card.module'; +import { XEmptyModule } from '../x-empty/x-empty.module'; +import { XActionBarModule } from '../x-action-bar/x-action-bar.module'; +import { XSearchBarModule } from '../x-search-bar/x-search-bar.module'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XListComponent], + imports: [ + XIconModule, + XCardModule, + XEmptyModule, + CommonModule, + XActionBarModule, + XSearchBarModule, + XBaseComponentModule, + ], + exports: [ + XIconModule, + XCardModule, + XEmptyModule, + CommonModule, + XListComponent, + XActionBarModule, + XSearchBarModule, + XBaseComponentModule, + ], +}) +export class XListModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-list\x-list.typings.ts + +import { + Input, + Output, + Inject, + NgZone, + Component, + Renderer2, + Injectable, + ElementRef, + TemplateRef, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + isFile, + hasChild, + toJsonString, + XStandardType, + XOneOrManyType, + toNormalString, + XColorIdentifier, + fileToJsonString, + XResourceIdentifier, + XBaseRangeRequestDto, + XColorWithBrightness, +} from 'x-framework-core'; +import { + XBaseComponentContainer, + IXBaseComponentContainer, + XBaseComponentContainerComponent, +} from '../classes/i-x-base.container'; +import { + XCardEmptyActionSearchBarContainer, + IXCardEmptyActionSearchBarContainer, + XCardEmptyActionSearchBarContainerComponent, +} from '../classes/i-x-card-empty.container'; +import { + XActionBarItem, + XActionBarActionModel, + XActionBarActionIdentifier, + XActionBarActionFiredModel, +} from '../x-action-bar/x-action-bar.typings'; +import { + XSearchBarActionModel, + XSearchBarTypeIdentifier, + XSearchBarEventIdentifier, +} from '../x-search-bar/x-search-bar.typings'; +import { concatMap, map } from 'rxjs/operators'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { XManagerService } from 'x-framework-services'; +import { XIconIdentifier } from '../x-icon/x-icon.typings'; +import { XCardActionModel } from '../x-card/x-card.typings'; +import { XEmptyActionModel } from '../x-empty/x-empty.typings'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +// +//#region Definitions ... +export type XListItemOptionSlot = 'end' | 'start'; + +export interface XListItemSlideOption { + id: string; + icon?: XStandardType; + title?: XStandardType; + color?: XStandardType; + onlyIcon?: XStandardType; + slot: XStandardType; + handler?: (info?: any) => XStandardType; + disabled?: XStandardType; +} + +export interface XListItem { + data: T; + hide?: XStandardType; + disabled?: XStandardType; + isSelected?: boolean; + href?: XOneOrManyType; + showDetailedIcon?: XStandardType; + slideOptions?: XListItemSlideOption[]; +} + +export interface XListItemOptionSelectedEvent { + id: string; + data: any; +} + +export enum XListAction { + // + Add = 'add', + Set = 'set', + Remove = 'remove', + + // + SelectAll = 'select_all', + DeselectAll = 'deselect_all', + InverseSelection = 'inverse_selection', + + // + Refresh = 'refresh', +} + +export type XListActionIdentifier = XListAction | string; + +export interface XListActionModel + extends XBaseActionModel {} + +export type XListFilteringHandler = ( + query: string, + source: XListItem[] +) => XStandardType[]>; + +export function itemToJson(item: any) { + return isFile(item) ? fileToJsonString(item) : toJsonString(item); +} + +export const defaultListFilteringHandler: XListFilteringHandler = ( + query: string, + items: XListItem[] +) => { + // + if (!query || !hasChild(items)) { + return []; + } + + // + const result = items.filter((i) => + toNormalString(itemToJson(i.data)).includes(toNormalString(query)) + ); + + // + return result; +}; +//#endregion + +// +//#region Container ... +export interface IXListContainer extends IXCardEmptyActionSearchBarContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + actionBarHideActions: XStandardType< + XOneOrManyType + >; + + // + useDefaultActions: XStandardType; + + // + showToolbar: XStandardType; + stickyToolbar: XStandardType; + + // + color: XStandardType; + foregroundColor: XStandardType; + + // + enableReordering: XStandardType; + showDivider: XStandardType; + showOptionsAsSlide: XStandardType; + closeOptionsAfterSelect: XStandardType; + showOptionsAsMenu: XStandardType; + + // + selectableItems: XStandardType; + clickableItems: XStandardType; + itemTemplate: TemplateRef; + slidingOptionTemplate: TemplateRef; + nonSlidingOptionTemplate: TemplateRef; + + // + itemSelected: EventEmitter>; + optionSelected: EventEmitter; + confirmRemove: XStandardType; + removeItems: EventEmitter>; + + // + selectionChanged: EventEmitter[]>; + orderChanged: EventEmitter; + items: XStandardType[]>; + showItemOptions: XStandardType; + itemsChanged: EventEmitter; + filteringHandler: XListFilteringHandler; +} + +@Injectable({ + providedIn: 'root', +}) +export class XListContainer + extends XCardEmptyActionSearchBarContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider: Subject = + new Subject(); + readonly cssClass: XStandardType>; + + // + readonly actionBarHideActions: XStandardType< + XOneOrManyType + >; + + readonly useDefaultActions: XStandardType = true; + + readonly showToolbar: XStandardType = false; + readonly stickyToolbar: XStandardType = true; + + // + readonly color: XStandardType = XColorWithBrightness.Light; + readonly foregroundColor: XStandardType = false; + + // + readonly enableReordering: XStandardType = false; + readonly showDivider: XStandardType = true; + readonly showOptionsAsSlide: XStandardType = false; + readonly closeOptionsAfterSelect: XStandardType = true; + readonly showOptionsAsMenu: XStandardType = false; + + // + readonly selectableItems: XStandardType = false; + readonly clickableItems: XStandardType = false; + readonly itemTemplate: TemplateRef; + readonly slidingOptionTemplate: TemplateRef; + readonly nonSlidingOptionTemplate: TemplateRef; + + // + readonly itemSelected: EventEmitter> = new EventEmitter< + XListItem + >(); + readonly optionSelected: EventEmitter = + new EventEmitter(); + readonly confirmRemove: XStandardType = true; + readonly removeItems: EventEmitter> = + new EventEmitter>(); + + // + readonly selectionChanged: EventEmitter[]> = new EventEmitter< + XListItem[] + >(); + readonly orderChanged: EventEmitter = new EventEmitter(); + readonly items: XStandardType[]> = []; + readonly showItemOptions: XStandardType = true; + readonly itemsChanged: EventEmitter = new EventEmitter(); + readonly filteringHandler: XListFilteringHandler = + defaultListFilteringHandler; + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XListContainerComponent + extends XCardEmptyActionSearchBarContainerComponent + implements IXListContainer +{ + // + //#region Props ... + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + actionBarHideActions: XStandardType< + XOneOrManyType + > = this.propertyProvider.actionBarHideActions; + + @Input() + useDefaultActions: XStandardType = + this.propertyProvider.useDefaultActions; + + @Input() + showToolbar: XStandardType = this.propertyProvider.showToolbar; + + @Input() + stickyToolbar: XStandardType = this.propertyProvider.stickyToolbar; + + // + @Input() + color: XStandardType = this.propertyProvider.color; + + @Input() + foregroundColor: XStandardType = + this.propertyProvider.foregroundColor; + + // + @Input() + enableReordering: XStandardType = + this.propertyProvider.enableReordering; + + @Input() + showDivider: XStandardType = this.propertyProvider.showDivider; + + @Input() + showOptionsAsSlide: XStandardType = + this.propertyProvider.showOptionsAsSlide; + + @Input() + closeOptionsAfterSelect: XStandardType = + this.propertyProvider.closeOptionsAfterSelect; + + @Input() + showOptionsAsMenu: XStandardType = + this.propertyProvider.showOptionsAsMenu; + + @Input() + selectableItems: XStandardType = + this.propertyProvider.selectableItems; + + @Input() + clickableItems: XStandardType = this.propertyProvider.clickableItems; + + @Input() + itemTemplate: TemplateRef = this.propertyProvider.itemTemplate; + + @Input() + slidingOptionTemplate: TemplateRef = + this.propertyProvider.slidingOptionTemplate; + + @Input() + nonSlidingOptionTemplate: TemplateRef = + this.propertyProvider.nonSlidingOptionTemplate; + + // + @Output() + itemSelected: EventEmitter> = new EventEmitter(); + + @Output() + optionSelected: EventEmitter = new EventEmitter(); + + @Input() + confirmRemove: XStandardType = this.propertyProvider.confirmRemove; + + @Output() + removeItems: EventEmitter> = new EventEmitter(); + + // + @Output() + selectionChanged: EventEmitter[]> = new EventEmitter(); + + @Output() + orderChanged: EventEmitter = new EventEmitter(); + + // + @Input() + items: XStandardType[]> = this.propertyProvider.items; + + @Input() + showItemOptions: XStandardType = + this.propertyProvider.showItemOptions; + + @Output() + itemsChanged: EventEmitter = new EventEmitter(); + + // + @Input() + filteringHandler: XListFilteringHandler = + this.propertyProvider.filteringHandler; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XListContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXListContainer + ): Observable { + return super + .extractProps(container ? container : this) + .pipe( + concatMap((superData) => { + return forkJoin({ + cssClass: this.getValue( + container ? container.cssClass : this.cssClass + ), + actionBarHideActions: this.getValue( + container + ? container.actionBarHideActions + : this.actionBarHideActions + ), + useDefaultActions: this.getValue( + container ? container.useDefaultActions : this.useDefaultActions + ), + showToolbar: this.getValue( + container ? container.showToolbar : this.showToolbar + ), + stickyToolbar: this.getValue( + container ? container.stickyToolbar : this.stickyToolbar + ), + color: this.getValue(container ? container.color : this.color), + foregroundColor: this.getValue( + container ? container.foregroundColor : this.foregroundColor + ), + enableReordering: this.getValue( + container ? container.enableReordering : this.enableReordering + ), + showDivider: this.getValue( + container ? container.showDivider : this.showDivider + ), + showOptionsAsSlide: this.getValue( + container ? container.showOptionsAsSlide : this.showOptionsAsSlide + ), + closeOptionsAfterSelect: this.getValue( + container + ? container.closeOptionsAfterSelect + : this.closeOptionsAfterSelect + ), + showOptionsAsMenu: this.getValue( + container ? container.showOptionsAsMenu : this.showOptionsAsMenu + ), + selectableItems: this.getValue( + container ? container.selectableItems : this.selectableItems + ), + clickableItems: this.getValue( + container ? container.clickableItems : this.clickableItems + ), + itemTemplate: this.getValue( + container ? container.itemTemplate : this.itemTemplate + ), + slidingOptionTemplate: this.getValue( + container + ? container.slidingOptionTemplate + : this.slidingOptionTemplate + ), + nonSlidingOptionTemplate: this.getValue( + container + ? container.nonSlidingOptionTemplate + : this.nonSlidingOptionTemplate + ), + confirmRemove: this.getValue( + container ? container.confirmRemove : this.confirmRemove + ), + items: this.getValue(container ? container.items : this.items), + showItemOptions: this.getValue( + container ? container.showItemOptions : this.showItemOptions + ), + filteringHandler: this.getValue( + container ? container.filteringHandler : this.filteringHandler + ), + }).pipe( + map((data) => { + return { + superData, + data, + }; + }) + ); + }) + ) + .pipe( + map((info) => { + return { + // + ...info.superData, + + // + ...info.data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + itemsChanged: this.itemsChanged, + itemSelected: this.itemSelected, + optionSelected: this.optionSelected, + removeItems: this.removeItems, + selectionChanged: this.selectionChanged, + orderChanged: this.orderChanged, + }; + }) + ); + } + //#endregion +} +//#endregion + +// +//#region ComponentContainer ... +type TXComponentListContainer = { + [TKey in keyof IXListContainer as `list${Capitalize< + string & TKey + >}`]: IXListContainer[TKey]; +}; + +export interface IXComponentListContainer + extends TXComponentListContainer, + IXBaseComponentContainer { + showList: XStandardType; +} + +@Injectable({ + providedIn: 'root', +}) +export class XComponentListContainer + extends XBaseComponentContainer + implements Readonly +{ + // + //#region Props ... + // + //#region Self ... + // + readonly showList: XStandardType = true; + + // + readonly listActionProvider: Subject = + this.propertyProvider.actionProvider; + readonly listCssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + readonly listActionBarHideActions: XStandardType< + XOneOrManyType + > = this.propertyProvider.actionBarHideActions; + readonly listUseDefaultActions: XStandardType = + this.propertyProvider.useDefaultActions; + readonly listShowToolbar: XStandardType = + this.propertyProvider.showToolbar; + readonly listStickyToolbar: XStandardType = + this.propertyProvider.stickyToolbar; + + // + readonly listColor: XStandardType = + this.propertyProvider.color; + readonly listForegroundColor: XStandardType = + this.propertyProvider.foregroundColor; + + // + readonly listEnableReordering: XStandardType = + this.propertyProvider.enableReordering; + readonly listShowDivider: XStandardType = + this.propertyProvider.showDivider; + readonly listShowOptionsAsSlide: XStandardType = + this.propertyProvider.showOptionsAsSlide; + readonly listCloseOptionsAfterSelect: XStandardType = + this.propertyProvider.closeOptionsAfterSelect; + readonly listShowOptionsAsMenu: XStandardType = + this.propertyProvider.showOptionsAsMenu; + + // + readonly listSelectableItems: XStandardType = + this.propertyProvider.selectableItems; + readonly listClickableItems: XStandardType = + this.propertyProvider.clickableItems; + readonly listItemTemplate: TemplateRef = + this.propertyProvider.itemTemplate; + readonly listSlidingOptionTemplate: TemplateRef = + this.propertyProvider.slidingOptionTemplate; + readonly listNonSlidingOptionTemplate: TemplateRef = + this.propertyProvider.nonSlidingOptionTemplate; + + // + readonly listItemSelected: EventEmitter> = + this.propertyProvider.itemSelected; + readonly listOptionSelected: EventEmitter = + this.propertyProvider.optionSelected; + readonly listConfirmRemove: XStandardType = + this.propertyProvider.confirmRemove; + readonly listRemoveItems: EventEmitter> = + this.propertyProvider.removeItems; + + // + readonly listSelectionChanged: EventEmitter[]> = + this.propertyProvider.selectionChanged; + readonly listOrderChanged: EventEmitter = + this.propertyProvider.orderChanged; + readonly listItems: XStandardType[]> = + this.propertyProvider.items; + readonly listShowItemOptions: XStandardType = + this.propertyProvider.showItemOptions; + readonly listItemsChanged: EventEmitter = + this.propertyProvider.itemsChanged; + readonly listFilteringHandler: XListFilteringHandler = + this.propertyProvider.filteringHandler; + //#endregion + + // + //#region List Card ... + readonly listIsInPage: XStandardType = + this.propertyProvider.isInPage; + readonly listWrapWithCard: XStandardType = + this.propertyProvider.wrapWithCard; + readonly listCardShowFooter: XStandardType = + this.propertyProvider.cardShowFooter; + readonly listCardShowActions: XStandardType = + this.propertyProvider.cardShowActions; + readonly listCardColor: XStandardType = + this.propertyProvider.cardColor; + readonly listCardForegroundColor: XStandardType = + this.propertyProvider.cardForegroundColor; + readonly listCardTitle: XStandardType = + this.propertyProvider.cardTitle; + readonly listCardActionProvider: Subject = + this.propertyProvider.cardActionProvider; + readonly listCardSubTitle: XStandardType = + this.propertyProvider.cardSubTitle; + readonly listCardCssClass: XStandardType> = + this.propertyProvider.cardCssClass; + //#endregion + + // + //#region List Empty ... + // + readonly listShowEmpty: XStandardType = + this.propertyProvider.showEmpty; + readonly listEmptyHasRefresh: XStandardType = + this.propertyProvider.emptyHasRefresh; + readonly listEmptyActionProvider: Subject = + this.propertyProvider.emptyActionProvider; + readonly listEmptyCssClass: XStandardType> = + this.propertyProvider.emptyCssClass; + readonly listEmptyColor: XStandardType = + this.propertyProvider.emptyColor; + readonly listEmptyForegroundColor: XStandardType = + this.propertyProvider.emptyForegroundColor; + readonly listEmptyMessageColor: XStandardType = + this.propertyProvider.emptyMessageColor; + readonly listEmptyForegroundMessageColor: XStandardType = + this.propertyProvider.emptyForegroundMessageColor; + readonly listEmptyRefreshButtonColor: XStandardType = + this.propertyProvider.emptyRefreshButtonColor; + readonly listEmptyForegroundRefreshButtonColor: XStandardType = + this.propertyProvider.emptyForegroundRefreshButtonColor; + readonly listEmptyMessage: XStandardType = + this.propertyProvider.emptyMessage; + readonly listEmptyRefreshIcon: XStandardType = + this.propertyProvider.emptyRefreshIcon; + readonly listEmptyRefresh: EventEmitter = + this.propertyProvider.emptyRefresh; + //#endregion + + // + //#region ActionBar ... + // + readonly listShowActionBar: XStandardType = + this.propertyProvider.showActionBar; + + // + readonly listActionBarActionProvider: Subject = + this.propertyProvider.actionBarActionProvider; + readonly listActionBarCssClass: XStandardType> = + this.propertyProvider.actionBarCssClass; + + // + readonly listActionBarColor: XStandardType = + this.propertyProvider.actionBarColor; + readonly listActionBarForegroundColor: XStandardType = + this.propertyProvider.actionBarForegroundColor; + + // + readonly listActionBarActions: XStandardType> = + this.propertyProvider.actionBarActions; + + // + readonly listActionBarActionFired: EventEmitter = + this.propertyProvider.actionBarActionFired; + //#endregion + + // + //#region SearchBar ... + // + readonly listShowSearchBar: XStandardType = + this.propertyProvider.showSearchBar; + + // + readonly listSearchBarActionProvider: Subject = + this.propertyProvider.searchBarActionProvider; + readonly listSearchBarCssClass: XStandardType> = + this.propertyProvider.searchBarCssClass; + + // + readonly listSearchBarColor: XStandardType = + this.propertyProvider.searchBarColor; + readonly listSearchBarForegroundColor: XStandardType = + this.propertyProvider.searchBarForegroundColor; + + // + readonly listSearchBarSearchInputColor: XStandardType = + this.propertyProvider.searchBarSearchInputColor; + readonly listSearchBarForegroundSearchInputColor: XStandardType = + this.propertyProvider.searchBarForegroundSearchInputColor; + + // + readonly listSearchBarType: XStandardType = + this.propertyProvider.searchBarType; + readonly listSearchBarTitle: XStandardType = + this.propertyProvider.searchBarTitle; + readonly listSearchBarValue: XStandardType = + this.propertyProvider.searchBarValue; + readonly listSearchBarEnterKeyHint: XStandardType = + this.propertyProvider.searchBarEnterKeyHint; + readonly listSearchBarKeyboardType: XStandardType = + this.propertyProvider.searchBarKeyboardType; + readonly listSearchBarSpellcheck: XStandardType = + this.propertyProvider.searchBarSpellcheck; + readonly listSearchBarDebounce: XStandardType = + this.propertyProvider.searchBarDebounce; + + // + readonly listSearchBarShowCancelButton: XStandardType = + this.propertyProvider.searchBarShowCancelButton; + readonly listSearchBarCancelButtonIcon: XStandardType = + this.propertyProvider.searchBarCancelButtonIcon; + readonly listSearchBarCancelButtonTitle: XStandardType = + this.propertyProvider.searchBarCancelButtonTitle; + readonly listSearchBarCancelButtonIconColor: XStandardType = + this.propertyProvider.searchBarCancelButtonIconColor; + + // + readonly listSearchBarShowClearButton: XStandardType = + this.propertyProvider.searchBarShowClearButton; + readonly listSearchBarClearButtonIcon: XStandardType = + this.propertyProvider.searchBarClearButtonIcon; + readonly listSearchBarClearButtonIconColor: XStandardType = + this.propertyProvider.searchBarClearButtonIconColor; + + // + readonly listSearchBarSearchButtonIcon: XStandardType = + this.propertyProvider.searchBarSearchButtonIcon; + readonly listSearchBarSearchButtonIconColor: XStandardType = + this.propertyProvider.searchBarSearchButtonIconColor; + + // + readonly listSearchBarQueryChange: EventEmitter = + this.propertyProvider.searchBarQueryChange; + readonly listSearchBarOnEvent: EventEmitter = + this.propertyProvider.searchBarOnEvent; + //#endregion + //#endregion + + // + //#region Constructor ... + constructor(protected propertyProvider: XListContainer) { + super(); + } + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XComponentListContainerComponent + extends XBaseComponentContainerComponent + implements IXComponentListContainer +{ + // + //#region Props ... + // + //#region Self ... + // + @Input() + showList: XStandardType = this.propertyProvider.showList; + + // + @Input() + listActionProvider: Subject = + this.propertyProvider.listActionProvider; + + @Input() + listCssClass: XStandardType> = + this.propertyProvider.listCssClass; + + // + @Input() + listActionBarHideActions: XStandardType< + XOneOrManyType + > = this.propertyProvider.listActionBarHideActions; + + @Input() + listUseDefaultActions: XStandardType = + this.propertyProvider.listUseDefaultActions; + + @Input() + listShowToolbar: XStandardType = + this.propertyProvider.listShowToolbar; + + @Input() + listStickyToolbar: XStandardType = + this.propertyProvider.listStickyToolbar; + + // + @Input() + listColor: XStandardType = this.propertyProvider.listColor; + + @Input() + listForegroundColor: XStandardType = + this.propertyProvider.listForegroundColor; + + // + @Input() + listEnableReordering: XStandardType = + this.propertyProvider.listEnableReordering; + + @Input() + listShowDivider: XStandardType = + this.propertyProvider.listShowDivider; + + @Input() + listShowOptionsAsSlide: XStandardType = + this.propertyProvider.listShowOptionsAsSlide; + + @Input() + listCloseOptionsAfterSelect: XStandardType = + this.propertyProvider.listCloseOptionsAfterSelect; + + @Input() + listShowOptionsAsMenu: XStandardType = + this.propertyProvider.listShowOptionsAsMenu; + + // + @Input() + listSelectableItems: XStandardType = + this.propertyProvider.listSelectableItems; + + @Input() + listClickableItems: XStandardType = + this.propertyProvider.listClickableItems; + + @Input() + listItemTemplate: TemplateRef = this.propertyProvider.listItemTemplate; + + @Input() + listSlidingOptionTemplate: TemplateRef = + this.propertyProvider.listSlidingOptionTemplate; + + @Input() + listNonSlidingOptionTemplate: TemplateRef = + this.propertyProvider.listNonSlidingOptionTemplate; + + // + @Output() + listItemSelected: EventEmitter> = new EventEmitter(); + + @Output() + listOptionSelected: EventEmitter = new EventEmitter(); + + @Input() + listConfirmRemove: XStandardType = + this.propertyProvider.listConfirmRemove; + + @Output() + listRemoveItems: EventEmitter> = new EventEmitter(); + + // + @Output() + listSelectionChanged: EventEmitter[]> = new EventEmitter(); + + @Output() + listOrderChanged: EventEmitter = new EventEmitter(); + + @Input() + listItems: XStandardType[]> = this.propertyProvider.listItems; + + @Input() + listShowItemOptions: XStandardType = + this.propertyProvider.listShowItemOptions; + + @Output() + listItemsChanged: EventEmitter = new EventEmitter(); + + @Input() + listFilteringHandler: XListFilteringHandler = + this.propertyProvider.listFilteringHandler; + //#endregion + + // + //#region List Card ... + @Input() + listIsInPage: XStandardType = this.propertyProvider.listIsInPage; + + @Input() + listWrapWithCard: XStandardType = + this.propertyProvider.listWrapWithCard; + + @Input() + listCardShowFooter: XStandardType = + this.propertyProvider.listCardShowFooter; + + @Input() + listCardShowActions: XStandardType = + this.propertyProvider.listCardShowActions; + + @Input() + listCardColor: XStandardType = + this.propertyProvider.listCardColor; + + @Input() + listCardForegroundColor: XStandardType = + this.propertyProvider.listCardForegroundColor; + + @Input() + listCardTitle: XStandardType = + this.propertyProvider.listCardTitle; + + @Input() + listCardActionProvider: Subject = + this.propertyProvider.listCardActionProvider; + + @Input() + listCardSubTitle: XStandardType = + this.propertyProvider.listCardSubTitle; + + @Input() + listCardCssClass: XStandardType> = + this.propertyProvider.listCardCssClass; + //#endregion + + // + //#region List Empty ... + // + @Input() + listShowEmpty: XStandardType = this.propertyProvider.listShowEmpty; + + @Input() + listEmptyHasRefresh: XStandardType = + this.propertyProvider.listEmptyHasRefresh; + + @Input() + listEmptyActionProvider: Subject = + this.propertyProvider.listEmptyActionProvider; + + @Input() + listEmptyCssClass: XStandardType> = + this.propertyProvider.listEmptyCssClass; + + @Input() + listEmptyColor: XStandardType = + this.propertyProvider.listEmptyColor; + + @Input() + listEmptyForegroundColor: XStandardType = + this.propertyProvider.listEmptyForegroundColor; + + @Input() + listEmptyMessageColor: XStandardType = + this.propertyProvider.listEmptyMessageColor; + + @Input() + listEmptyForegroundMessageColor: XStandardType = + this.propertyProvider.listEmptyForegroundMessageColor; + + @Input() + listEmptyRefreshButtonColor: XStandardType = + this.propertyProvider.listEmptyRefreshButtonColor; + + @Input() + listEmptyForegroundRefreshButtonColor: XStandardType = + this.propertyProvider.listEmptyForegroundRefreshButtonColor; + + @Input() + listEmptyMessage: XStandardType = + this.propertyProvider.listEmptyMessage; + + @Input() + listEmptyRefreshIcon: XStandardType = + this.propertyProvider.listEmptyRefreshIcon; + + @Output() + listEmptyRefresh: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region ActionBar ... + // + @Input() + listShowActionBar: XStandardType = + this.propertyProvider.listShowActionBar; + + // + @Input() + listActionBarActionProvider: Subject = + this.propertyProvider.listActionBarActionProvider; + + @Input() + listActionBarCssClass: XStandardType> = + this.propertyProvider.listActionBarCssClass; + + // + @Input() + listActionBarColor: XStandardType = + this.propertyProvider.listActionBarColor; + + @Input() + listActionBarForegroundColor: XStandardType = + this.propertyProvider.listActionBarForegroundColor; + + // + @Input() + listActionBarActions: XStandardType> = + this.propertyProvider.listActionBarActions; + + // + @Output() + listActionBarActionFired: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region SearchBar ... + // + @Input() + listShowSearchBar: XStandardType = + this.propertyProvider.listShowSearchBar; + + // + @Input() + listSearchBarActionProvider: Subject = + this.propertyProvider.listSearchBarActionProvider; + + @Input() + listSearchBarCssClass: XStandardType> = + this.propertyProvider.listSearchBarCssClass; + + // + @Input() + listSearchBarColor: XStandardType = + this.propertyProvider.listSearchBarColor; + + @Input() + listSearchBarForegroundColor: XStandardType = + this.propertyProvider.listSearchBarForegroundColor; + + // + @Input() + listSearchBarSearchInputColor: XStandardType = + this.propertyProvider.listSearchBarSearchInputColor; + + @Input() + listSearchBarForegroundSearchInputColor: XStandardType = + this.propertyProvider.listSearchBarForegroundSearchInputColor; + + // + @Input() + listSearchBarType: XStandardType = + this.propertyProvider.listSearchBarType; + + @Input() + listSearchBarTitle: XStandardType = + this.propertyProvider.listSearchBarTitle; + + @Input() + listSearchBarValue: XStandardType = + this.propertyProvider.listSearchBarValue; + + @Input() + listSearchBarEnterKeyHint: XStandardType = + this.propertyProvider.listSearchBarEnterKeyHint; + + @Input() + listSearchBarKeyboardType: XStandardType = + this.propertyProvider.listSearchBarKeyboardType; + + @Input() + listSearchBarSpellcheck: XStandardType = + this.propertyProvider.listSearchBarSpellcheck; + + @Input() + listSearchBarDebounce: XStandardType = + this.propertyProvider.listSearchBarDebounce; + + // + @Input() + listSearchBarShowCancelButton: XStandardType = + this.propertyProvider.listSearchBarShowCancelButton; + + @Input() + listSearchBarCancelButtonIcon: XStandardType = + this.propertyProvider.listSearchBarCancelButtonIcon; + + @Input() + listSearchBarCancelButtonTitle: XStandardType = + this.propertyProvider.listSearchBarCancelButtonTitle; + + @Input() + listSearchBarCancelButtonIconColor: XStandardType = + this.propertyProvider.listSearchBarCancelButtonIconColor; + + // + @Input() + listSearchBarShowClearButton: XStandardType = + this.propertyProvider.listSearchBarShowClearButton; + + @Input() + listSearchBarClearButtonIcon: XStandardType = + this.propertyProvider.listSearchBarClearButtonIcon; + + @Input() + listSearchBarClearButtonIconColor: XStandardType = + this.propertyProvider.listSearchBarClearButtonIconColor; + + // + @Input() + listSearchBarSearchButtonIcon: XStandardType = + this.propertyProvider.listSearchBarSearchButtonIcon; + + @Input() + listSearchBarSearchButtonIconColor: XStandardType = + this.propertyProvider.listSearchBarSearchButtonIconColor; + + // + @Output() + listSearchBarQueryChange: EventEmitter = new EventEmitter(); + + @Output() + listSearchBarOnEvent: EventEmitter = new EventEmitter(); + //#endregion + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XComponentListContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXComponentListContainer + ): Observable { + return forkJoin({ + // + showList: this.getValue(container ? container.showList : this.showList), + listCssClass: this.getValue( + container ? container.listCssClass : this.listCssClass + ), + listActionBarHideActions: this.getValue( + container + ? container.listActionBarHideActions + : this.listActionBarHideActions + ), + listUseDefaultActions: this.getValue( + container ? container.listUseDefaultActions : this.listUseDefaultActions + ), + listShowToolbar: this.getValue( + container ? container.listShowToolbar : this.listShowToolbar + ), + listStickyToolbar: this.getValue( + container ? container.listStickyToolbar : this.listStickyToolbar + ), + listColor: this.getValue( + container ? container.listColor : this.listColor + ), + listForegroundColor: this.getValue( + container ? container.listForegroundColor : this.listForegroundColor + ), + listEnableReordering: this.getValue( + container ? container.listEnableReordering : this.listEnableReordering + ), + listShowDivider: this.getValue( + container ? container.listShowDivider : this.listShowDivider + ), + listShowOptionsAsSlide: this.getValue( + container + ? container.listShowOptionsAsSlide + : this.listShowOptionsAsSlide + ), + listCloseOptionsAfterSelect: this.getValue( + container + ? container.listCloseOptionsAfterSelect + : this.listCloseOptionsAfterSelect + ), + listShowOptionsAsMenu: this.getValue( + container ? container.listShowOptionsAsMenu : this.listShowOptionsAsMenu + ), + listSelectableItems: this.getValue( + container ? container.listSelectableItems : this.listSelectableItems + ), + listClickableItems: this.getValue( + container ? container.listClickableItems : this.listClickableItems + ), + listItemTemplate: this.getValue( + container ? container.listItemTemplate : this.listItemTemplate + ), + listSlidingOptionTemplate: this.getValue( + container + ? container.listSlidingOptionTemplate + : this.listSlidingOptionTemplate + ), + listNonSlidingOptionTemplate: this.getValue( + container + ? container.listNonSlidingOptionTemplate + : this.listNonSlidingOptionTemplate + ), + listConfirmRemove: this.getValue( + container ? container.listConfirmRemove : this.listConfirmRemove + ), + listItems: this.getValue( + container ? container.listItems : this.listItems + ), + listShowItemOptions: this.getValue( + container ? container.listShowItemOptions : this.listShowItemOptions + ), + listFilteringHandler: this.getValue( + container ? container.listFilteringHandler : this.listFilteringHandler + ), + listIsInPage: this.getValue( + container ? container.listIsInPage : this.listIsInPage + ), + listWrapWithCard: this.getValue( + container ? container.listWrapWithCard : this.listWrapWithCard + ), + listCardShowFooter: this.getValue( + container ? container.listCardShowFooter : this.listCardShowFooter + ), + listCardShowActions: this.getValue( + container ? container.listCardShowActions : this.listCardShowActions + ), + listCardColor: this.getValue( + container ? container.listCardColor : this.listCardColor + ), + listCardForegroundColor: this.getValue( + container + ? container.listCardForegroundColor + : this.listCardForegroundColor + ), + listCardTitle: this.getValue( + container ? container.listCardTitle : this.listCardTitle + ), + listCardSubTitle: this.getValue( + container ? container.listCardSubTitle : this.listCardSubTitle + ), + listCardCssClass: this.getValue( + container ? container.listCardCssClass : this.listCardCssClass + ), + listShowEmpty: this.getValue( + container ? container.listShowEmpty : this.listShowEmpty + ), + listEmptyHasRefresh: this.getValue( + container ? container.listEmptyHasRefresh : this.listEmptyHasRefresh + ), + listEmptyCssClass: this.getValue( + container ? container.listEmptyCssClass : this.listEmptyCssClass + ), + listEmptyColor: this.getValue( + container ? container.listEmptyColor : this.listEmptyColor + ), + listEmptyForegroundColor: this.getValue( + container + ? container.listEmptyForegroundColor + : this.listEmptyForegroundColor + ), + listEmptyMessageColor: this.getValue( + container ? container.listEmptyMessageColor : this.listEmptyMessageColor + ), + listEmptyForegroundMessageColor: this.getValue( + container + ? container.listEmptyForegroundMessageColor + : this.listEmptyForegroundMessageColor + ), + listEmptyRefreshButtonColor: this.getValue( + container + ? container.listEmptyRefreshButtonColor + : this.listEmptyRefreshButtonColor + ), + listEmptyForegroundRefreshButtonColor: this.getValue( + container + ? container.listEmptyForegroundRefreshButtonColor + : this.listEmptyForegroundRefreshButtonColor + ), + listEmptyMessage: this.getValue( + container ? container.listEmptyMessage : this.listEmptyMessage + ), + listEmptyRefreshIcon: this.getValue( + container ? container.listEmptyRefreshIcon : this.listEmptyRefreshIcon + ), + listShowActionBar: this.getValue( + container ? container.listShowActionBar : this.listShowActionBar + ), + listActionBarCssClass: this.getValue( + container ? container.listActionBarCssClass : this.listActionBarCssClass + ), + listActionBarColor: this.getValue( + container ? container.listActionBarColor : this.listActionBarColor + ), + listActionBarForegroundColor: this.getValue( + container + ? container.listActionBarForegroundColor + : this.listActionBarForegroundColor + ), + listActionBarActions: this.getValue( + container ? container.listActionBarActions : this.listActionBarActions + ), + listShowSearchBar: this.getValue( + container ? container.listShowSearchBar : this.listShowSearchBar + ), + listSearchBarCssClass: this.getValue( + container ? container.listSearchBarCssClass : this.listSearchBarCssClass + ), + listSearchBarColor: this.getValue( + container ? container.listSearchBarColor : this.listSearchBarColor + ), + listSearchBarForegroundColor: this.getValue( + container + ? container.listSearchBarForegroundColor + : this.listSearchBarForegroundColor + ), + listSearchBarSearchInputColor: this.getValue( + container + ? container.listSearchBarSearchInputColor + : this.listSearchBarSearchInputColor + ), + listSearchBarForegroundSearchInputColor: this.getValue( + container + ? container.listSearchBarForegroundSearchInputColor + : this.listSearchBarForegroundSearchInputColor + ), + listSearchBarType: this.getValue( + container ? container.listSearchBarType : this.listSearchBarType + ), + listSearchBarTitle: this.getValue( + container ? container.listSearchBarTitle : this.listSearchBarTitle + ), + listSearchBarValue: this.getValue( + container ? container.listSearchBarValue : this.listSearchBarValue + ), + listSearchBarEnterKeyHint: this.getValue( + container + ? container.listSearchBarEnterKeyHint + : this.listSearchBarEnterKeyHint + ), + listSearchBarKeyboardType: this.getValue( + container + ? container.listSearchBarKeyboardType + : this.listSearchBarKeyboardType + ), + listSearchBarSpellcheck: this.getValue( + container + ? container.listSearchBarSpellcheck + : this.listSearchBarSpellcheck + ), + listSearchBarDebounce: this.getValue( + container ? container.listSearchBarDebounce : this.listSearchBarDebounce + ), + listSearchBarShowCancelButton: this.getValue( + container + ? container.listSearchBarShowCancelButton + : this.listSearchBarShowCancelButton + ), + listSearchBarCancelButtonIcon: this.getValue( + container + ? container.listSearchBarCancelButtonIcon + : this.listSearchBarCancelButtonIcon + ), + listSearchBarCancelButtonTitle: this.getValue( + container + ? container.listSearchBarCancelButtonTitle + : this.listSearchBarCancelButtonTitle + ), + listSearchBarCancelButtonIconColor: this.getValue( + container + ? container.listSearchBarCancelButtonIconColor + : this.listSearchBarCancelButtonIconColor + ), + listSearchBarShowClearButton: this.getValue( + container + ? container.listSearchBarShowClearButton + : this.listSearchBarShowClearButton + ), + listSearchBarClearButtonIcon: this.getValue( + container + ? container.listSearchBarClearButtonIcon + : this.listSearchBarClearButtonIcon + ), + listSearchBarClearButtonIconColor: this.getValue( + container + ? container.listSearchBarClearButtonIconColor + : this.listSearchBarClearButtonIconColor + ), + listSearchBarSearchButtonIcon: this.getValue( + container + ? container.listSearchBarSearchButtonIcon + : this.listSearchBarSearchButtonIcon + ), + listSearchBarSearchButtonIconColor: this.getValue( + container + ? container.listSearchBarSearchButtonIconColor + : this.listSearchBarSearchButtonIconColor + ), + }).pipe( + map((data) => { + return { + // + ...data, + + // + listActionProvider: container + ? container.listActionProvider + : this.listActionProvider, + + // + listItemSelected: this.listItemSelected, + listOptionSelected: this.listOptionSelected, + listRemoveItems: this.listRemoveItems, + listSelectionChanged: this.listSelectionChanged, + listOrderChanged: this.listOrderChanged, + listItemsChanged: this.listItemsChanged, + + // + listCardActionProvider: container + ? container.listCardActionProvider + : this.listCardActionProvider, + listEmptyActionProvider: container + ? container.listEmptyActionProvider + : this.listEmptyActionProvider, + + // + listEmptyRefresh: this.listEmptyRefresh, + + // + listActionBarActionProvider: container + ? container.listActionBarActionProvider + : this.listActionBarActionProvider, + + // + listActionBarActionFired: this.listActionBarActionFired, + + // + listSearchBarActionProvider: container + ? container.listSearchBarActionProvider + : this.listSearchBarActionProvider, + + // + listSearchBarQueryChange: this.listSearchBarQueryChange, + listSearchBarOnEvent: this.listSearchBarOnEvent, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-map\x-map.component.html + + + + + + + + +
+ +
+
+
+ + + +
+ +
+ + +
+ + + + + + + + + +
+ + +
+
+ + +
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ item.data.display_name }} + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-map\x-map.component.scss + +:host { + --map-height: 200px; + --map-controls-offest: 5px; +} + +:host ::ng-deep .x-auto-size-flex { + flex-grow: 0 !important; + flex-shrink: 1 !important; + flex-basis: auto !important; +} + +.x-map { + position: relative; +} + +.x-map-inner { + height: var(--map-height); + width: 100%; +} + +.x-map-top, +.x-map-bottom { + width: 100%; + position: absolute; + background-color: transparent; +} + +.x-map-top { + top: 0; +} + +.x-map-bottom { + bottom: 0; +} + +.x-map-plugins-wrapper { + margin: .5em; +} + +.x-map-plugin { + &.ol-control { + padding: 0; + position: relative; + margin-top: 0 !important; + } + + button { + padding: 3px; + width: fit-content; + display: inline-block; + } +} + +// +//#region Search Result ... +// +$duration: 250ms; +$transform-show: scaleY(1); +$transform-hide: scaleY(0); + +// +.x-map-search-list { + // + position: absolute; + + // + z-index: 110; + + // + top: 0; + left: 0; + right: 0; + height: 0; + + // + transform: $transform-hide; + -ms-transform: $transform-hide; + -webkit-transform: $transform-hide; + + // + transition: transform; + -webkit-transition: transform; + + // + transition-duration: $duration; + -webkit-transition-duration: $duration; + + // + &.open { + // + height: auto; + + // + transform: $transform-show; + -ms-transform: $transform-show; + -webkit-transform: $transform-show; + + // + transition-duration: $duration; + -webkit-transition-duration: $duration; + } + + // + &.has-search { + top: 56px; + } +} +//#endregion + +// +//#region Ol Theme ... + +:host ::ng-deep { + .x-content.mat-card-content { + padding: 0 !important; + } + + .ol-control { + margin-top: var(--map-controls-offest); + } + + .ol-box { + box-sizing: border-box; + border-radius: 2px; + border: 2px solid blue; + } + + .ol-mouse-position { + top: 8px; + right: 8px; + position: absolute; + } + + .ol-scale-line { + background: rgba(0, 60, 136, 0.3); + border-radius: 4px; + bottom: 8px; + left: 8px; + padding: 2px; + position: absolute; + } + + .ol-scale-line-inner { + border: 1px solid #eee; + border-top: none; + color: #eee; + font-size: 10px; + text-align: center; + margin: 1px; + will-change: contents, width; + transition: all 0.25s; + } + + .ol-scale-bar { + position: absolute; + bottom: 8px; + left: 8px; + } + + .ol-scale-step-marker { + width: 1px; + height: 15px; + background-color: #000000; + float: right; + z-index: 10; + } + + .ol-scale-step-text { + position: absolute; + bottom: -5px; + font-size: 12px; + z-index: 11; + color: #000000; + text-shadow: -2px 0 #ffffff, 0 2px #ffffff, 2px 0 #ffffff, 0 -2px #ffffff; + } + + .ol-scale-text { + position: absolute; + font-size: 14px; + text-align: center; + bottom: 25px; + color: #000000; + text-shadow: -2px 0 #ffffff, 0 2px #ffffff, 2px 0 #ffffff, 0 -2px #ffffff; + } + + .ol-scale-singlebar { + position: relative; + height: 10px; + z-index: 9; + border: 1px solid black; + } + + .ol-unsupported { + display: none; + } + + .ol-viewport, + .ol-unselectable { + -webkit-touch-callout: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + } + + .ol-selectable { + -webkit-touch-callout: default; + -webkit-user-select: text; + -moz-user-select: text; + -ms-user-select: text; + user-select: text; + } + + .ol-grabbing { + cursor: -webkit-grabbing; + cursor: -moz-grabbing; + cursor: grabbing; + } + + .ol-grab { + cursor: move; + cursor: -webkit-grab; + cursor: -moz-grab; + cursor: grab; + } + + .ol-control { + position: absolute; + background-color: rgba(255, 255, 255, 0.4); + border-radius: 4px; + padding: 2px; + } + + .ol-control:hover { + background-color: rgba(255, 255, 255, 0.6); + } + + .ol-zoom { + top: 0.5em; + left: 0.5em; + } + + .ol-rotate { + top: 0.5em; + right: 0.5em; + transition: opacity 0.25s linear, visibility 0s linear; + } + + .ol-rotate.ol-hidden { + opacity: 0; + visibility: hidden; + transition: opacity 0.25s linear, visibility 0s linear 0.25s; + } + + .ol-zoom-extent { + top: 4.643em; + left: 0.5em; + } + + .ol-full-screen { + right: 0.5em; + top: 0.5em; + } + + .ol-control button { + display: block; + margin: 1px; + padding: 0; + color: white; + font-size: 1.14em; + font-weight: bold; + text-decoration: none; + text-align: center; + height: 1.375em; + width: 1.375em; + line-height: 0.4em; + background-color: rgba(0, 60, 136, 0.5); + border: none; + border-radius: 2px; + } + + .ol-control button::-moz-focus-inner { + border: none; + padding: 0; + } + + .ol-zoom-extent button { + line-height: 1.4em; + } + + .ol-compass { + display: block; + font-weight: normal; + font-size: 1.2em; + will-change: transform; + } + + .ol-touch .ol-control button { + font-size: 1.5em; + } + + .ol-touch .ol-zoom-extent { + top: 5.5em; + } + + .ol-control button:hover, + .ol-control button:focus { + text-decoration: none; + background-color: rgba(0, 60, 136, 0.7); + } + + .ol-zoom .ol-zoom-in { + border-radius: 2px 2px 0 0; + } + + .ol-zoom .ol-zoom-out { + border-radius: 0 0 2px 2px; + } + + .ol-attribution { + text-align: right; + bottom: 0.5em; + right: 0.5em; + max-width: calc(100% - 1.3em); + } + + .ol-attribution ul { + margin: 0; + padding: 0 0.5em; + color: #000; + text-shadow: 0 0 2px #fff; + } + + .ol-attribution li { + display: inline; + list-style: none; + } + + .ol-attribution li:not(:last-child):after { + content: ' '; + } + + .ol-attribution img { + max-height: 2em; + max-width: inherit; + vertical-align: middle; + } + + .ol-attribution ul, + .ol-attribution button { + display: inline-block; + } + + .ol-attribution.ol-collapsed ul { + display: none; + } + + .ol-attribution:not(.ol-collapsed) { + background: rgba(255, 255, 255, 0.8); + } + + .ol-attribution.ol-uncollapsible { + bottom: 0; + right: 0; + border-radius: 4px 0 0; + } + + .ol-attribution.ol-uncollapsible img { + margin-top: -0.2em; + max-height: 1.6em; + } + + .ol-attribution.ol-uncollapsible button { + display: none; + } + + .ol-zoomslider { + top: 4.5em; + left: 0.5em; + height: 200px; + } + + .ol-zoomslider button { + position: relative; + height: 10px; + } + + .ol-touch .ol-zoomslider { + top: 5.5em; + } + + .ol-overviewmap { + left: 0.5em; + bottom: 0.5em; + } + + .ol-overviewmap.ol-uncollapsible { + bottom: 0; + left: 0; + border-radius: 0 4px 0 0; + } + + .ol-overviewmap .ol-overviewmap-map, + .ol-overviewmap button { + display: inline-block; + } + + .ol-overviewmap .ol-overviewmap-map { + border: 1px solid #7b98bc; + height: 150px; + margin: 2px; + width: 150px; + } + + .ol-overviewmap:not(.ol-collapsed) button { + bottom: 1px; + left: 2px; + position: absolute; + } + + .ol-overviewmap.ol-collapsed .ol-overviewmap-map, + .ol-overviewmap.ol-uncollapsible button { + display: none; + } + + .ol-overviewmap:not(.ol-collapsed) { + background: rgba(255, 255, 255, 0.8); + } + + .ol-overviewmap-box { + border: 2px dotted rgba(0, 60, 136, 0.7); + } + + .ol-overviewmap .ol-overviewmap-box:hover { + cursor: move; + } + + // + // Disable Attribution ... + .ol-attribution { + display: none !important; + } +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-map\x-map.component.ts + +import { + of, + Subject, + forkJoin, + Observable, + throwError, + Subscription, +} from 'rxjs'; +import { + Component, + ViewChild, + ElementRef, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + XMapAction, + XMapCSSVar, + XPlaceResult, + XMapLocation, + XMapActionModel, + XMapItemPosition, + XMapContainerComponent, + XMapItemPositionIdentifier, +} from './x-map.typings'; +import { + DragPan, + DragZoom, + PinchZoom, + DragRotate, + Interaction, + PinchRotate, + KeyboardPan, + KeyboardZoom, + MouseWheelZoom, + DoubleClickZoom, +} from 'ol/interaction'; +import { + tap, + map, + concatMap, + catchError, + debounceTime, + distinctUntilChanged, +} from 'rxjs/operators'; +import { + nameof, + XHeader, + applyLocale, + applyFinally, + XContentType, + generateUuid, + XExceptionIDs, + XStandardType, + toNormalString, + isNullOrUndefined, + isNullOrEmptyString, +} from 'x-framework-core'; +import { Point } from 'ol/geom'; +import BaseLayer from 'ol/layer/Base'; +import { MapOptions } from 'ol/PluggableMap'; +import { + XSearchBarEvent, + XSearchBarEventIdentifier, +} from '../x-search-bar/x-search-bar.typings'; +import { toMapLocation } from './x-map.tools'; +import { fromLonLat, transform } from 'ol/proj'; +import { HttpHeaders } from '@angular/common/http'; +import { XListItem } from '../x-list/x-list.typings'; +import { OSM, Vector as VectorSource } from 'ol/source'; +import { Map, View, Feature, Overlay, Kinetic } from 'ol'; +import { Zoom, Rotate, ZoomSlider, Control } from 'ol/control'; +import { Tile as TileLayer, Vector as VectorLayer } from 'ol/layer'; +import { XSlotLayout, XSlotName } from '../x-slotter/x-slotter.typings'; +import { XSearchBarComponent } from '../x-search-bar/x-search-bar.component'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-map', + templateUrl: './x-map.component.html', + styleUrls: ['./x-map.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XMapComponent extends XMapContainerComponent { + // + //#region Props ... + // + private ZOOM_DELTA = 2; + private IMPORTANT_ZOOM = 18; + private MAP: Map = undefined; + private ANIMATION_DURATION = 500; + private MARKER: VectorLayer; + private ZOOM_CONTROL = new Zoom(); + private IS_MAP_INITIALIZED = false; + private MARKER_POSITION: XMapLocation; + private ROTATE_CONTROL = new Rotate(); + private ACTION_WAITIONG_DURATION = 500; + private MAP_OPTIONS: MapOptions = undefined; + private ZOOM_SLIDER_CONTROL = new ZoomSlider(); + private positionProvider = new Subject(); + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + private searchFilterSubject$ = new Subject(); + + // + readonly SlotNames = Object.assign({}, XSlotName); + readonly componentId = generateUuid('XMapComponent'); + readonly SlotLayouts = Object.assign({}, XSlotLayout); + readonly ItemPositions = Object.assign({}, XMapItemPosition); + + // + isSearching = false; + searchPaneOpen = false; + currentPosition: string = ''; + searchResult: XPlaceResult[] = []; + showMapView: XStandardType = false; + searchResultListItems: XListItem[] = []; + + // + get isInitialized() { + return this.IS_MAP_INITIALIZED; + } + + get map() { + return this.MAP; + } + + get options() { + return this.MAP_OPTIONS as MapOptions; + } + + get hasMarker() { + return !isNullOrUndefined(this.MARKER); + } + + get markerPosition() { + return this.MARKER_POSITION; + } + + get currentZoom() { + return !this.isInitialized ? -1 : this.MAP.getView().getZoom(); + } + + get currentLatitude() { + // + if (!this.isInitialized) { + return -1; + } + + // + const lonLat = this.map.getView().getCenter(); + const location = toMapLocation(lonLat); + + // + return +location.latitude; + } + + get currentLongitude() { + // + if (!this.isInitialized) { + return -1; + } + + // + const lonLat = this.map.getView().getCenter(); + const location = toMapLocation(lonLat); + + // + return +location.longitude; + } + + @ViewChild('mapView') + mapView: ElementRef; + + @ViewChild('pluginsBar') + pluginsBar: ElementRef; + + @ViewChild('searchBar') + searchBar: XSearchBarComponent; + //#endregion + + // + //#region LifeCycles ... + async afterViewInit() { + super.afterViewInit(); + + // + await this.initializeMap(); + } + + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region CanSelect Change ... + const isCanSelectChanged = changeKeys.includes( + nameof('canSelect') + ); + if (isCanSelectChanged) { + await this.applyCanSelect(); + } + //#endregion + + // + //#region ActionProvider changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + + // + //#region Location Info changed ... + // const isZoomChanged = changeKeys.includes(nameof('zoom')); + // const isLatitudeChanged = changeKeys.includes( + // nameof('latitude') + // ); + // const isLongitudeChanged = changeKeys.includes( + // nameof('longitude') + // ); + // const isAddMarkerOnSelectedLocationChanged = changeKeys.includes( + // nameof('addMarkerOnSelectedLocation') + // ); + // if ( + // isZoomChanged || + // isLatitudeChanged || + // isLongitudeChanged || + // isAddMarkerOnSelectedLocationChanged + // ) { + // await this.applyLocationInfo(); + // } + //#endregion + + // + //#region Show Zoom changed ... + const isShowZoomChanged = changeKeys.includes( + nameof('showZoom') + ); + if (isShowZoomChanged) { + await this.applyZoom(); + } + //#endregion + + // + //#region Show Rotate changed ... + const isShowRotateChanged = changeKeys.includes( + nameof('showRotate') + ); + if (isShowRotateChanged) { + await this.applyRotate(); + } + //#endregion + + // + //#region Show Zoom Slider changed ... + const isShowZoomSliderChanged = changeKeys.includes( + nameof('showZoomSlider') + ); + if (isShowZoomSliderChanged) { + await this.applyZoomSlider(); + } + //#endregion + + // + //#region Offset Height Handling ... + const isShowSearchBarChanged = changeKeys.includes( + nameof('showSearchBar') + ); + const isSearchBarPositionChanged = changeKeys.includes( + nameof('searchBarPosition') + ); + const isShowSelectedLoactionChanged = changeKeys.includes( + nameof('showSelectedLocation') + ); + const isSelectedLocationPositionChanged = changeKeys.includes( + nameof('selectedLocationPosition') + ); + if ( + isShowRotateChanged || + isShowZoomChanged || + isShowZoomSliderChanged || + isShowSearchBarChanged || + isSearchBarPositionChanged || + isShowSelectedLoactionChanged || + isSelectedLocationPositionChanged + ) { + await this.applyContentOffset(); + } + //#endregion + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + // Register Action Provider ... + this.registerActionProvider(); + + // + // Register Search ... + this.registerPluginHandler(); + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + //#endregion + + // + //#region Providers ... + async getCenter( + latitude?: number | XStandardType, + longitude?: number | XStandardType + ) { + // + const desiredLatitude = await this.getNormallizedLatitued(latitude); + const desiredLongitude = await this.getNormallizedLongitude(longitude); + const result = fromLonLat([desiredLongitude, desiredLatitude]); + + // + return result; + } + //#endregion + + // + //#region UI Providers ... + canShowPlugins(position: XMapItemPositionIdentifier) { + return this.getValue(position).pipe( + concatMap((pos) => + isNullOrEmptyString(pos) + ? of(false) + : forkJoin({ + // + // progressBar: forkJoin({ + // canShow: this.getValue(this.showProgressBar), + // position: this.getValue(this.progressBarPosition), + // }), + + // + locator: forkJoin({ + canShow: this.getValue(this.showLocator), + position: this.getValue(this.locatorPosition), + }), + + // + clearMarker: forkJoin({ + canShow: this.getValue(this.showClearMarker), + position: this.getValue(this.clearMarkerPosition), + }), + + // + goMarkedPlace: forkJoin({ + canShow: this.getValue(this.showGoMarkedPlace), + position: this.getValue(this.goMarkedPlacePosition), + }), + + // + selectedLocation: forkJoin({ + canShow: this.getValue(this.showSelectedLocation), + position: this.getValue(this.selectedLocationPosition), + }), + }).pipe( + map((info) => { + return { + pos, + info, + }; + }), + map((data) => { + return ( + // + // ProgressBar ... + // (data.info.progressBar.canShow && + // data.info.progressBar.position === data.pos) || + // + // Locator ... + (data.info.locator.canShow && + data.info.locator.position === data.pos) || + // + // ClearMarker ... + (data.info.clearMarker.canShow && + data.info.clearMarker.position === data.pos) || + // + // Go Marked Place ... + (data.info.goMarkedPlace.canShow && + data.info.goMarkedPlace.position === data.pos) || + // + // Selected Location ... + (data.info.selectedLocation.canShow && + data.info.selectedLocation.position === data.pos) + ); + }) + ) + ) + ); + } + //#endregion + + // + //#region UI Handlers ... + async handleSeach(query: string) { + // + this.searchBarQueryChange.emit(query); + + // + if (this.isSearching) { + // + setTimeout( + async () => await this.handleSeach(query), + this.ACTION_WAITIONG_DURATION + ); + + // + return; + } + + // + if (isNullOrEmptyString(query)) { + // + this.searchResult = []; + this.isSearching = false; + this.searchPaneOpen = false; + this.searchResultListItems = []; + await this.setLoadingState(false); + return; + } + + // + query = toNormalString(query); + this.searchFilterSubject$.next(query); + } + + async handleSearchBarEvents(event: XSearchBarEventIdentifier) { + // + switch (event) { + // + case XSearchBarEvent.FOCUS: + // + setTimeout(() => { + // + this.searchPaneOpen = + this.hasChild(this.searchResult) && + this.hasChild(this.searchResultListItems); + + // + this.detectChanges(); + }, this.ACTION_WAITIONG_DURATION); + break; + + // + case XSearchBarEvent.BLUR: + // + setTimeout(() => { + // + this.searchPaneOpen = false; + this.detectChanges(); + }, this.ACTION_WAITIONG_DURATION); + break; + } + } + + async handleChangeLocation(item: XListItem) { + // + const zoom = this.IMPORTANT_ZOOM - 4; + const addMarker = await this.getValueAsync( + this.addMarkerOnSelectedLocation + ); + if (addMarker) { + await this.addMarker(item.data.lat, item.data.lon, zoom); + } else { + await this.setCenter(item.data.lat, item.data.lon, zoom); + } + } + + async handleLocationResultClickOutside(event: any) { + // + this.searchPaneOpen = false; + this.detectChanges(); + } + + async handleGoMarkedPlace() { + await this.goMarkedPlace(); + } + + async handleClearMarker() { + await this.clearMarker(); + } + + async handleLocate() { + await this.locate(); + } + + async handleSlectedLocationClicked(message: string) { + // + message = applyLocale(message, 'en-US'); + this.managerService.saveToClipboard(message); + } + //#endregion + + // + //#region Actions ... + async updateMap() { + // + // Recalling ... + if (!this.isInitialized) { + setTimeout(async () => { + await this.updateMap(); + }, this.ACTION_WAITIONG_DURATION); + + // + return; + } + + // + //#region Update ... + this.MAP.render(); + this.detectChanges(); + this.MAP.updateSize(); + this.MAP.renderSync(); + this.managerService.dispatchResizeEvent(); + if (this.mapView && this.mapView.nativeElement) { + // + const mapView = this.mapView.nativeElement as HTMLElement; + mapView.blur(); + mapView.focus(); + } + //#endregion + + // + setTimeout(async () => { + await this.updateMap(); + }, this.ACTION_WAITIONG_DURATION); + } + + async setZoom(zoom?: number | XStandardType) { + // + const desiredZoom = await this.getNormallizedZoom(zoom); + + // + if (!this.isInitialized) { + // + setTimeout(async () => { + await this.setZoom(zoom); + }, this.ACTION_WAITIONG_DURATION); + + // + return; + } + + // + this.map.getView().animate({ + zoom: desiredZoom, + duration: this.ANIMATION_DURATION, + }); + } + + async setCenter( + latitude?: number | XStandardType, + longitude?: number | XStandardType, + zoom?: number | XStandardType + ) { + // + const desiredZoom = await this.getNormallizedZoom(zoom); + const center = await this.getCenter(latitude, longitude); + + // + if (!this.isInitialized) { + // + setTimeout(async () => { + await this.setCenter(latitude, longitude, zoom); + }, this.ACTION_WAITIONG_DURATION); + + // + return; + } + + // + this.map.getView().animate({ + center, + zoom: desiredZoom, + duration: this.ANIMATION_DURATION, + }); + } + + async addMarker( + latitude?: number | XStandardType, + longitude?: number | XStandardType, + zoom?: number | XStandardType + ) { + // + if (!this.isInitialized) { + // + setTimeout(async () => { + await this.addMarker(latitude, longitude, zoom); + }, this.ACTION_WAITIONG_DURATION); + + // + return; + } + + // + const desiredZoom = await this.getNormallizedZoom(zoom); + const desiredLat = await this.getNormallizedLatitued(latitude); + const desiredLong = await this.getNormallizedLongitude(longitude); + + // + if (this.MARKER) { + // + this.MAP.removeLayer(this.MARKER); + this.MARKER = null; + this.actionProvider.next({ action: XMapAction.HasNotMarker }); + } + + // + this.MARKER_POSITION = { + zoom: desiredZoom.toString(), + latitude: desiredLat.toString(), + longitude: desiredLong.toString(), + }; + + // + const markerStyle = await this.getValueAsync(this.markerStyle); + + // + this.MARKER = new VectorLayer({ + source: new VectorSource({ + features: [ + new Feature({ + geometry: new Point( + transform([desiredLong, desiredLat], 'EPSG:4326', 'EPSG:3857') + ), + }), + ], + }), + style: markerStyle, + }); + + // + // Netify Selected Location ... + await this.updateSelectedLocation(latitude, longitude); + this.actionProvider.next({ action: XMapAction.HasMarker }); + + // + // Emit Location Selected Event ... + this.locationSelected.emit(this.MARKER_POSITION); + + // + this.MAP.addLayer(this.MARKER); + + // + setTimeout(async () => { + await this.setCenter(desiredLat, desiredLong, desiredZoom); + }, this.ACTION_WAITIONG_DURATION); + } + + async clearMarker() { + // + if (!this.MARKER) { + return; + } + + // + if (!this.isInitialized) { + // + setTimeout(async () => { + await this.clearMarker(); + }, this.ACTION_WAITIONG_DURATION); + + // + return; + } + + // + this.MAP.removeLayer(this.MARKER); + this.MARKER = null; + this.MARKER_POSITION = null; + + // + this.positionProvider.next(''); + this.actionProvider.next({ + action: XMapAction.HasNotMarker, + }); + } + + async goMarkedPlace() { + // + if (!this.hasMarker) { + return; + } + + // + if (!this.isInitialized) { + // + setTimeout(async () => { + await this.goMarkedPlace(); + }, this.ACTION_WAITIONG_DURATION); + + // + return; + } + + // + await this.setCenter( + +this.markerPosition.latitude, + +this.markerPosition.longitude + ); + } + + async locate() { + // + await this.setLoadingState(true); + const location = await this.getValueAsync( + applyFinally( + this.managerService.geoLocationService.getCurrentLocation().pipe( + catchError((err) => { + // + this.showError(err); + + // + return throwError(err); + }) + ), + async () => await this.setLoadingState(false) + ) + ); + + // + if (!isNullOrUndefined(location)) { + // + this.zoom = this.IMPORTANT_ZOOM; + this.latitude = location.coords.latitude; + this.longitude = location.coords.longitude; + + // + await this.addMarker( + location.coords.latitude, + location.coords.longitude, + this.zoom + ); + } + } + //#endregion + + // + //#region Private ... + private async prepareMapOptions() { + // + this.MAP = null; + this.MAP_OPTIONS = null; + this.showMapView = false; + + // + const zoom = 0; // await this.getNormallizedZoom(); + + // + const desiredLatitude = 0; // await this.getNormallizedLatitued(); + const desiredLongitude = 0; // await this.getNormallizedLongitude(); + const center = fromLonLat([desiredLatitude, desiredLongitude]); + + // + const view = new View({ + zoom, + center, + }); + + // + const layers: BaseLayer[] = []; + layers.push( + new TileLayer({ + source: new OSM(), + }) + ); + + // + const controls: Control[] = await this.getControls(); + + // + const interactions: Interaction[] = await this.getInteractions(); + + // + const overlays: Overlay[] = []; + + // + this.MAP_OPTIONS = { + view, + layers, + controls, + overlays, + target: this.componentId, + interactions, + keyboardEventTarget: this.componentId, + }; + } + + private async initializeMap() { + // + if (!isNullOrUndefined(this.MAP)) { + this.MAP = null; + } + + // + await this.prepareMapOptions(); + + // + this.MAP = new Map(this.options); + + // + //#region Attached Events ... + // + // Map Event Handler ... + this.MAP.on('rendercomplete', async (e: any) => { + // + this.isReady.emit(); + + // + this.IS_MAP_INITIALIZED = true; + + // + this.managerService.dispatchResizeEvent(); + Promise.resolve().then(() => this.managerService.dispatchResizeEvent()); + setTimeout( + () => this.managerService.dispatchResizeEvent(), + this.ACTION_WAITIONG_DURATION + ); + }); + + // + // Zoom End ... + this.MAP.on('moveend', async (e: any) => { + // + const zoom = this.map.getView().getZoom(); + const lonLat = this.map.getView().getCenter(); + const location = toMapLocation(lonLat); + location.zoom = zoom.toString(); + + // + this.zoom = zoom; + + // + this.viewChanged.emit(location); + }); + //#endregion + + // + this.showMapView = true; + await this.applyContentOffset(); + await this.updateMap(); + await this.applyCanSelect(); + + // + await this.applyLocationInfo(); + } + + private async getInteractions() { + // + const result: Interaction[] = []; + + // + const delta = this.ZOOM_DELTA; + const duration = this.ANIMATION_DURATION; + + // + const dragPan = true; + const pinchZoom = true; + const onFocusOnly = true; + const pinchRotate = true; + const keyboardPan = true; + const keyboardZoom = true; + const shiftDragZoom = true; + const mouseWheelZoom = true; + const doubleClickZoom = true; + const altShiftDragRotate = true; + + // + //#region Preperation ... + // + // DragRotate ... + const kinetic = new Kinetic(-0.005, 0.05, 100); + if (altShiftDragRotate) { + result.push(new DragRotate()); + } + + // + // DoubleClickZoom ... + if (doubleClickZoom) { + result.push( + new DoubleClickZoom({ + delta, + duration, + }) + ); + } + + // + // DragPan ... + if (dragPan) { + result.push( + new DragPan({ + onFocusOnly, + kinetic, + }) + ); + } + + // + // PinchRotate ... + if (pinchRotate) { + result.push(new PinchRotate()); + } + + // + // PinchZoom ... + if (pinchZoom) { + result.push( + new PinchZoom({ + duration, + }) + ); + } + + // + // KeyboardPan ... + if (keyboardPan) { + result.push(new KeyboardPan()); + } + + // + // KeyboardZoom ... + if (keyboardZoom) { + result.push( + new KeyboardZoom({ + delta, + duration, + }) + ); + } + + // + // MouseWheelZoom ... + if (mouseWheelZoom) { + result.push( + new MouseWheelZoom({ + duration, + onFocusOnly, + }) + ); + } + + // + // DragZoom ... + if (shiftDragZoom) { + result.push( + new DragZoom({ + duration, + }) + ); + } + //#endregion + + // + return result; + } + + private async getControls() { + // + const result: Control[] = []; + + // + //#region Adding Controls ... + // + // Zoom ... + const showZoom = await this.getValueAsync(this.showZoom); + if (showZoom) { + result.push(this.ZOOM_CONTROL); + } + + // + // Rotate ... + const showRotate = await this.getValueAsync(this.showRotate); + if (showRotate) { + result.push(this.ROTATE_CONTROL); + } + + // + // ZoomSlider ... + const showZoomSlider = await this.getValueAsync(this.showZoomSlider); + if (showZoomSlider) { + result.push(this.ZOOM_SLIDER_CONTROL); + } + //#endregion + + // + return result; + } + + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + // Handle Action Provider ... + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XMapAction.AddMarker: + // + if (model.payload) { + // + const result: XMapLocation = model.payload; + if (result) { + // + const lat = +result.latitude; + const long = +result.longitude; + const zoom = +result.zoom; + + // + await this.addMarker(lat, long, zoom); + } + } + break; + + // + case XMapAction.ClearMarker: + await this.clearMarker(); + break; + + // + case XMapAction.GoMarkedPlace: + await this.goMarkedPlace(); + break; + + // + case XMapAction.SetCenter: + // + if (model.payload) { + // + const result: XMapLocation = model.payload; + if (result) { + // + const zoom = +result.zoom; + const lat = +result.latitude; + const long = +result.longitude; + + // + await this.setCenter(lat, long, zoom); + } + } + break; + + // + case XMapAction.SetZoom: + // + if (model.payload) { + // + const zoom = +model.payload; + await this.setZoom(zoom); + } + break; + + // + case XMapAction.Locate: + await this.locate(); + break; + + // + case XMapAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + + private registerPluginHandler() { + // + // Register Query Change ... + this.applyTakeUntilWrapper( + this.searchFilterSubject$ + .asObservable() + .pipe(debounceTime(800), distinctUntilChanged()) + ).subscribe(async (query) => { + await this.handleSearchAction(query); + }); + + // + // Register LocationProvider ... + this.applyTakeUntilWrapper(this.positionProvider.asObservable()).subscribe( + (position) => { + // + this.currentPosition = position; + this.detectChanges(); + } + ); + } + + private async getNormallizedLatitued( + latitude?: number | XStandardType + ) { + const result = await this.getValueAsync(latitude || this.latitude || 0); + return result || 0; + } + + private async getNormallizedLongitude( + longitude?: number | XStandardType + ) { + const result = await this.getValueAsync(longitude || this.longitude || 0); + return result || 0; + } + + private async getNormallizedZoom(zoom?: number | XStandardType) { + // + let result = 0; + if (!zoom) { + result = await this.getValueAsync(this.zoom); + } else { + result = await this.getValueAsync(zoom); + } + + // + result = + result > this.IMPORTANT_ZOOM + ? this.IMPORTANT_ZOOM + : result < 0 + ? 0 + : result; + + // + return result || 0; + } + + private async getSummarySelectedLocationMessage( + latitude?: number | XStandardType, + longitude?: number | XStandardType + ) { + // + const desiredLat = await this.getNormallizedLatitued(latitude); + const desiredLong = await this.getNormallizedLongitude(longitude); + + // + const mLat = this.applyLocale(desiredLat.toString()); + const mLong = this.applyLocale(desiredLong.toString()); + + // + const result = `${mLat}, ${mLong}`; + return result; + } + + // + private async applyLocationInfo() { + // + if (!this.isInitialized) { + // + setTimeout( + async () => await this.applyLocationInfo(), + this.ACTION_WAITIONG_DURATION + ); + // + return; + } + + // + const normallizedZoom = await this.getNormallizedZoom(); + const latitude = await this.getNormallizedLatitued(); + const longitude = await this.getNormallizedLongitude(); + + // + const addMarker = await this.getValueAsync( + this.addMarkerOnSelectedLocation + ); + const zoom = addMarker ? this.IMPORTANT_ZOOM : normallizedZoom; + + // + if (this.longitude && this.latitude) { + // + setTimeout(async () => { + // + if (addMarker) { + await this.addMarker(latitude, longitude, zoom); + } else { + await this.setCenter(latitude, longitude, zoom); + } + }, this.ACTION_WAITIONG_DURATION); + + // + return; + } + + // + if (this.zoom) { + // + setTimeout(async () => { + await this.setZoom(normallizedZoom); + }, 1000); + + // + return; + } + } + + private async applyContentOffset() { + // + let result = 5; + + // + const showSerach = await this.getValueAsync(this.showSearchBar); + if (showSerach) { + // + const searchBarPosition = await this.getValueAsync( + this.searchBarPosition + ); + + // + if (searchBarPosition === XMapItemPosition.TOP && this.searchBar) { + // + const searchBarHeight = this.searchBar.getElementBoundaryHeight(); + if (searchBarHeight === 0) { + // + setTimeout( + async () => await this.applyContentOffset(), + this.ACTION_WAITIONG_DURATION + ); + + // + return; + } + + // + result += searchBarHeight; + } + } + + // + const canShowPluginBarAtTop = await this.getValueAsync( + this.canShowPlugins(this.ItemPositions.TOP) + ); + if (canShowPluginBarAtTop && this.pluginsBar) { + // + const pluginsBarHeight = + this.pluginsBar.nativeElement.getBoundingClientRect().height; + if (pluginsBarHeight === 0) { + // + setTimeout( + async () => await this.applyContentOffset(), + this.ACTION_WAITIONG_DURATION + ); + + // + return; + } + + // + result += pluginsBarHeight; + } + + // + this.setCssVar(XMapCSSVar.MapControlOffset, `${result}px`); + } + + private async applyCanSelect() { + // + if (!this.isInitialized) { + // + setTimeout( + async () => await this.applyCanSelect(), + this.ACTION_WAITIONG_DURATION + ); + + // + return; + } + + // + // Add Marker on Map Clicked ... + const canSelect = await this.getValueAsync(this.canSelect); + if (canSelect) { + this.MAP.on('click', async (args: any) => { + // + this.searchPaneOpen = false; + + // + const lonlat = transform(args.coordinate, 'EPSG:3857', 'EPSG:4326'); + + // + // tslint:disable-next-line:no-shadowed-variable + const lat = lonlat[1] as number; + + // + // tslint:disable-next-line:no-shadowed-variable + const long = lonlat[0] as number; + + // + // , this.IMPORTANT_ZOOM - 6 + await this.addMarker(lat, long); + }); + } + } + + // + //#region Plugin Handlers ... + private async applyZoom(forceUpdate = true) { + // + if (!this.isInitialized) { + // + setTimeout( + async () => await this.applyZoom(), + this.ACTION_WAITIONG_DURATION + ); + + // + return; + } + + // + const showZoom = await this.getValueAsync(this.showZoom); + if (showZoom) { + this.map.addControl(this.ZOOM_CONTROL); + } else { + this.map.removeControl(this.ZOOM_CONTROL); + } + + // + if (forceUpdate) { + await this.updateMap(); + } + } + + private async applyRotate(forceUpdate = true) { + // + if (!this.isInitialized) { + // + setTimeout( + async () => await this.applyRotate(), + this.ACTION_WAITIONG_DURATION + ); + + // + return; + } + + // + const showRotate = await this.getValueAsync(this.showRotate); + if (showRotate) { + this.map.addControl(this.ROTATE_CONTROL); + } else { + this.map.removeControl(this.ROTATE_CONTROL); + } + + // + if (forceUpdate) { + await this.updateMap(); + } + } + + private async applyZoomSlider(forceUpdate = true) { + // + if (!this.isInitialized) { + // + setTimeout( + async () => await this.applyZoomSlider(), + this.ACTION_WAITIONG_DURATION + ); + + // + return; + } + + // + const showZoomSlider = await this.getValueAsync(this.showZoomSlider); + if (showZoomSlider) { + this.map.addControl(this.ZOOM_SLIDER_CONTROL); + } else { + this.map.removeControl(this.ZOOM_SLIDER_CONTROL); + } + + // + if (forceUpdate) { + await this.updateMap(); + } + } + + // + //#region SelectedLocation Plugin ... + private async updateSelectedLocation( + latitude?: number | XStandardType, + longitude?: number | XStandardType + ) { + // + if (!this.isInitialized) { + // + setTimeout(async () => { + await this.updateSelectedLocation(latitude, longitude); + }, this.ACTION_WAITIONG_DURATION); + + // + return; + } + + // + const message = await this.getSummarySelectedLocationMessage( + latitude, + longitude + ); + this.positionProvider.next(message); + } + //#endregion + + // + //#region SearchBar Plugin ... + private async handleSearchAction(query: string) { + // + const url = this.searchPlaceProvider(query); + + // + this.searchResult = []; + this.searchPaneOpen = false; + this.searchResultListItems = []; + + // + await this.setLoadingState(true); + applyFinally(this.getPlaceResults(url), async () => { + this.isSearching = false; + await this.setLoadingState(false); + }).subscribe( + async (res) => { + // + this.searchResult = res; + await this.prepareSearchListItems(); + }, + () => { + this.showError(XExceptionIDs.ActionFailed); + } + ); + } + + private getPlaceResults(url: string): Observable { + // + const headers: HttpHeaders = new HttpHeaders(); + headers.set(XHeader.Accept, XContentType.AppJson); + + // + return applyFinally( + this.applyLoadingWrapper( + this.managerService.httpClient + .get(url, { headers }) + .pipe( + map((res) => { + return res.map((r) => { + // + const mPlace: XPlaceResult = { + display_name: r.display_name, + lat: +r.lat, + lon: +r.lon, + }; + + // + return mPlace; + }); + }), + tap((res) => { + if (!this.hasChild(res)) { + this.searchPaneOpen = false; + } + }) + ) + ), + () => this.setLoadingState(false) + ); + } + + private async prepareSearchListItems() { + // + if (!this.hasChild(this.searchResult)) { + // + this.searchPaneOpen = false; + await this.setLoadingState(false); + return; + } + + // + this.searchResultListItems = this.searchResult.map((r) => { + // + const li: XListItem = { + data: r, + }; + + // + return li; + }); + + // + this.searchPaneOpen = true; + await this.setLoadingState(false); + + // + this.detectChanges(); + } + //#endregion + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-map\x-map.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XMapComponent } from './x-map.component'; +import { XCardModule } from '../x-card/x-card.module'; +import { XIconModule } from '../x-icon/x-icon.module'; +import { XListModule } from '../x-list/x-list.module'; +import { XMapContainerComponent } from './x-map.typings'; +import { XSlotterModule } from '../x-slotter/x-slotter.module'; +import { XMapDialogService } from './x-map-dialog/x-map-dialog.service'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; +import { XMapDialogComponent } from './x-map-dialog/x-map-dialog.component'; + +@NgModule({ + imports: [ + XListModule, + XCardModule, + XIconModule, + CommonModule, + XSlotterModule, + XBaseComponentModule, + ], + exports: [ + XListModule, + XCardModule, + XIconModule, + CommonModule, + XMapComponent, + XSlotterModule, + XMapDialogComponent, + XBaseComponentModule, + ], + providers: [XMapDialogService], + entryComponents: [XMapDialogComponent], + declarations: [XMapComponent, XMapDialogComponent, XMapContainerComponent], +}) +export class XMapModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-map\x-map.tools.ts + +import { XMapLocation } from './x-map.typings'; +import { isNullOrEmptyString, hasChild, isString, isArray, countChild } from 'x-framework-core'; + +/** + * get given position link for open on Google Maps ... + * + * @param model map result model ... + */ +export function getGoogleMapLink(model: XMapLocation) { + return `https://maps.google.com/?q=${model.latitude},${model.longitude}`; +} + +/** + * convert a point string to Map Result ... + * + * @param longLat the string which represent point ... + */ +export function fromLongLat(longLat: string) { + // + if (isNullOrEmptyString(longLat)) { + return null; + } + + // + const longLatArr = longLat.toString().split(','); + if (!hasChild(longLatArr) || longLatArr.length !== 2) { + return null; + } + + // + const result: XMapLocation = { + latitude: longLatArr[1], + longitude: longLatArr[0], + }; + + // + return result; +} + +export function toMapLocation(source: string | Array) { + // + if (isString(source)) { + return fromLongLat(source as string); + } + + // + if (!isArray(source) || countChild(source as Array) < 2) { + return null; + } + + // + const longLatArr = source as Array; + + // + const result: XMapLocation = { + latitude: longLatArr[1].toString(), + longitude: longLatArr[0].toString(), + }; + + // + return result; +} + +/** + * convert a point object to point string ... + * + * @param point the map selected object to convert it ... + */ +export function toLongLat(point: XMapLocation) { + return `${point.longitude},${point.latitude}`; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-map\x-map.typings.ts + +import { + Input, + Inject, + NgZone, + Output, + Component, + Renderer2, + ElementRef, + Injectable, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XStandardType, + XOneOrManyType, + XColorIdentifier, + XResourceIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { + XCardContainer, + XComponentCardContainer, + IXComponentCardContainer, + XComponentCardContainerComponent, +} from '../x-card/x-card.typings'; +import { + XSearchBarActionModel, + XSearchBarTypeIdentifier, + XComponentSearchBarContainer, + IXComponentSearchBarContainer, +} from '../x-search-bar/x-search-bar.typings'; +import { concatMap, map } from 'rxjs/operators'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { forkJoin, Observable, Subject } from 'rxjs'; +import { Style, Circle, Stroke, Fill } from 'ol/style'; +import { XManagerService } from 'x-framework-services'; +import { XIconIdentifier } from '../x-icon/x-icon.typings'; +import { IXBaseContainer } from '../classes/i-x-base.container'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export interface XPlaceResult { + display_name: string; + lat: number; + lon: number; +} + +export enum XMapItemPosition { + TOP = 'top', + BOTTOM = 'bottom', +} + +export type XMapItemPositionIdentifier = XMapItemPosition | string; + +export enum XMapAction { + // + Locate = 'locate', + + // + AddMarker = 'add_marker', + HasMarker = 'has_marker', + ClearMarker = 'clear-marker', + HasNotMarker = 'has_not_marker', + GoMarkedPlace = 'go-marked-place', + + // + SetCenter = 'set_center', + SetZoom = 'set_zoom', + + // + Refresh = 'refresh', +} + +export type XMapActionIdentifier = XMapAction | string; + +export interface XMapActionModel + extends XBaseActionModel {} + +export enum XMapCSSVar { + MapControlOffset = 'map-controls-offest', +} + +export type XMapCSSVarIdentification = XMapCSSVar | string; + +export interface XMapLocation { + zoom?: string; + latitude?: string; + longitude?: string; +} + +export type XMapStyle = Style; + +export const defaultMarkerStyle: XMapStyle = new Style({ + image: new Circle({ + radius: 10, + stroke: new Stroke({ + color: 'rgb(255,0,0)', + width: 5, + }), + fill: new Fill({ + color: 'rgba(255,0,0,.3)', + }), + }), + stroke: new Stroke({ + color: 'rgb(255,0,0)', + width: 5, + }), + fill: new Fill({ + color: 'rgba(255,165,0,.3)', + }), +}); + +export const defaultMapSearchPlaceProvider = (query: string) => { + return ( + 'https://nominatim.openstreetmap.org/search?q=' + + query + + '&format=json&limit=5' + ); +}; + +// +//#region Inside Card Container ... +type TXMapContainer = IXComponentCardContainer & IXComponentSearchBarContainer; +export interface IXMapContainer extends IXBaseContainer, TXMapContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + zoom: XStandardType; + latitude: XStandardType; + longitude: XStandardType; + + canSelect: XStandardType; + addMarkerOnSelectedLocation: XStandardType; + + // + isReady: EventEmitter; + viewChanged: EventEmitter; + locationSelected: EventEmitter; + // zoomChanged: EventEmitter; + + markerStyle: XStandardType; + + showProgressBar: XStandardType; + progressBarColor: XStandardType; + progressBarPosition: XStandardType; + + showSelectedLocation: XStandardType; + selectedLocationPosition: XStandardType; + + showGoMarkedPlace: XStandardType; + goMarkedPlacePosition: XStandardType; + + showClearMarker: XStandardType; + clearMarkerPosition: XStandardType; + + showLocator: XStandardType; + locatorPosition: XStandardType; + + showZoom: XStandardType; + showRotate: XStandardType; + showZoomSlider: XStandardType; + + // + searchBarPosition: XStandardType; + searchPlaceProvider: (query: string) => string; + searchResultListColor: XStandardType; +} + +@Injectable({ + providedIn: 'root', +}) +export class XMapContainer + extends XComponentCardContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider: Subject = + new Subject(); + readonly cssClass: XStandardType>; + + // + readonly zoom: XStandardType = 0; + readonly latitude: XStandardType = 0; + readonly longitude: XStandardType = 0; + + readonly canSelect: XStandardType = true; + readonly addMarkerOnSelectedLocation: XStandardType = true; + + // + readonly isReady: EventEmitter = new EventEmitter(); + readonly viewChanged: EventEmitter = + new EventEmitter(); + readonly locationSelected: EventEmitter = + new EventEmitter(); + // zoomChanged: EventEmitter; + + readonly markerStyle: XStandardType = defaultMarkerStyle; + + readonly showProgressBar: XStandardType = true; + readonly progressBarColor: XStandardType = + XColorWithBrightness.Warning; + readonly progressBarPosition: XStandardType = + XMapItemPosition.TOP; + + readonly showSelectedLocation: XStandardType = true; + readonly selectedLocationPosition: XStandardType = + XMapItemPosition.TOP; + + readonly showGoMarkedPlace: XStandardType = true; + readonly goMarkedPlacePosition: XStandardType = + XMapItemPosition.TOP; + + readonly showClearMarker: XStandardType = true; + readonly clearMarkerPosition: XStandardType = + XMapItemPosition.TOP; + + readonly showLocator: XStandardType = true; + readonly locatorPosition: XStandardType = + XMapItemPosition.TOP; + + readonly showZoom: XStandardType = true; + readonly showRotate: XStandardType = true; + readonly showZoomSlider: XStandardType = false; + + // + readonly searchBarPosition: XStandardType = + XMapItemPosition.TOP; + readonly searchPlaceProvider: (query: string) => string = + defaultMapSearchPlaceProvider; + readonly searchResultListColor: XStandardType = + XColorWithBrightness.Light; + + // + //#region ComponentSearchBar ... + // + readonly showSearchBar: XStandardType = + this.searchBarPropertyProvider.showSearchBar; + + // + readonly searchBarActionProvider: Subject = + this.searchBarPropertyProvider.searchBarActionProvider; + readonly searchBarCssClass: XStandardType> = + this.searchBarPropertyProvider.searchBarCssClass; + + // + readonly searchBarColor: XStandardType = + this.searchBarPropertyProvider.searchBarColor; + readonly searchBarForegroundColor: XStandardType = + this.searchBarPropertyProvider.searchBarForegroundColor; + + // + readonly searchBarSearchInputColor: XStandardType = + this.searchBarPropertyProvider.searchBarSearchInputColor; + readonly searchBarForegroundSearchInputColor: XStandardType = + this.searchBarPropertyProvider.searchBarForegroundSearchInputColor; + + // + readonly searchBarType: XStandardType = + this.searchBarPropertyProvider.searchBarType; + readonly searchBarTitle: XStandardType = + this.searchBarPropertyProvider.searchBarTitle; + readonly searchBarValue: XStandardType = + this.searchBarPropertyProvider.searchBarValue; + readonly searchBarEnterKeyHint: XStandardType = + this.searchBarPropertyProvider.searchBarEnterKeyHint; + readonly searchBarKeyboardType: XStandardType = + this.searchBarPropertyProvider.searchBarKeyboardType; + readonly searchBarSpellcheck: XStandardType = + this.searchBarPropertyProvider.searchBarSpellcheck; + readonly searchBarDebounce: XStandardType = + this.searchBarPropertyProvider.searchBarDebounce; + + // + readonly searchBarShowCancelButton: XStandardType = + this.searchBarPropertyProvider.searchBarShowCancelButton; + readonly searchBarCancelButtonIcon: XStandardType = + this.searchBarPropertyProvider.searchBarCancelButtonIcon; + readonly searchBarCancelButtonTitle: XStandardType = + this.searchBarPropertyProvider.searchBarCancelButtonTitle; + readonly searchBarCancelButtonIconColor: XStandardType = + this.searchBarPropertyProvider.searchBarCancelButtonIconColor; + + // + readonly searchBarShowClearButton: XStandardType = + this.searchBarPropertyProvider.searchBarShowClearButton; + readonly searchBarClearButtonIcon: XStandardType = + this.searchBarPropertyProvider.searchBarClearButtonIcon; + readonly searchBarClearButtonIconColor: XStandardType = + this.searchBarPropertyProvider.searchBarClearButtonIconColor; + + // + readonly searchBarSearchButtonIcon: XStandardType = + this.searchBarPropertyProvider.searchBarSearchButtonIcon; + readonly searchBarSearchButtonIconColor: XStandardType = + this.searchBarPropertyProvider.searchBarSearchButtonIconColor; + + // + readonly searchBarQueryChange: EventEmitter = + this.searchBarPropertyProvider.searchBarQueryChange; + readonly searchBarOnEvent: EventEmitter = + this.searchBarPropertyProvider.searchBarOnEvent; + //#endregion + //#endregion + + // + //#region Constructor ... + constructor( + protected propertyProvider: XCardContainer, + protected searchBarPropertyProvider: XComponentSearchBarContainer + ) { + super(propertyProvider); + } + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export class XMapContainerComponent + extends XComponentCardContainerComponent + implements IXMapContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + zoom: XStandardType = this.propertyProvider.zoom; + + @Input() + latitude: XStandardType = this.propertyProvider.latitude; + + @Input() + longitude: XStandardType = this.propertyProvider.longitude; + + @Input() + canSelect: XStandardType = this.propertyProvider.canSelect; + + @Input() + addMarkerOnSelectedLocation: XStandardType = + this.propertyProvider.addMarkerOnSelectedLocation; + + // + @Output() + isReady: EventEmitter = new EventEmitter(); + + @Output() + viewChanged: EventEmitter = new EventEmitter(); + + @Output() + locationSelected: EventEmitter = new EventEmitter(); + // zoomChanged: EventEmitter; + + @Input() + markerStyle: XStandardType = this.propertyProvider.markerStyle; + + @Input() + showProgressBar: XStandardType = + this.propertyProvider.showProgressBar; + + @Input() + progressBarColor: XStandardType = + this.propertyProvider.progressBarColor; + + @Input() + progressBarPosition: XStandardType = + this.propertyProvider.progressBarPosition; + + @Input() + showSelectedLocation: XStandardType = + this.propertyProvider.showSelectedLocation; + + @Input() + selectedLocationPosition: XStandardType = + this.propertyProvider.selectedLocationPosition; + + @Input() + showGoMarkedPlace: XStandardType = + this.propertyProvider.showGoMarkedPlace; + + @Input() + goMarkedPlacePosition: XStandardType = + this.propertyProvider.goMarkedPlacePosition; + + @Input() + showClearMarker: XStandardType = + this.propertyProvider.showClearMarker; + + @Input() + clearMarkerPosition: XStandardType = + this.propertyProvider.clearMarkerPosition; + + @Input() + showLocator: XStandardType = this.propertyProvider.showLocator; + + @Input() + locatorPosition: XStandardType = + this.propertyProvider.locatorPosition; + + @Input() + showZoom: XStandardType = this.propertyProvider.showZoom; + + @Input() + showRotate: XStandardType = this.propertyProvider.showRotate; + + @Input() + showZoomSlider: XStandardType = this.propertyProvider.showZoomSlider; + + // + @Input() + searchBarPosition: XStandardType = + this.propertyProvider.searchBarPosition; + + @Input() + searchPlaceProvider: (query: string) => string = + this.propertyProvider.searchPlaceProvider; + + @Input() + searchResultListColor: XStandardType = + this.propertyProvider.searchResultListColor; + + // + //#region ComponentSearchBar ... + // + @Input() + showSearchBar: XStandardType = this.propertyProvider.showSearchBar; + + // + @Input() + searchBarActionProvider: Subject = + this.propertyProvider.searchBarActionProvider; + + @Input() + searchBarCssClass: XStandardType> = + this.propertyProvider.searchBarCssClass; + + // + @Input() + searchBarColor: XStandardType = + this.propertyProvider.searchBarColor; + + @Input() + searchBarForegroundColor: XStandardType = + this.propertyProvider.searchBarForegroundColor; + + // + @Input() + searchBarSearchInputColor: XStandardType = + this.propertyProvider.searchBarSearchInputColor; + + @Input() + searchBarForegroundSearchInputColor: XStandardType = + this.propertyProvider.searchBarForegroundSearchInputColor; + + // + @Input() + searchBarType: XStandardType = + this.propertyProvider.searchBarType; + + @Input() + searchBarTitle: XStandardType = + this.propertyProvider.searchBarTitle; + + @Input() + searchBarValue: XStandardType = this.propertyProvider.searchBarValue; + + @Input() + searchBarEnterKeyHint: XStandardType = + this.propertyProvider.searchBarEnterKeyHint; + + @Input() + searchBarKeyboardType: XStandardType = + this.propertyProvider.searchBarKeyboardType; + + @Input() + searchBarSpellcheck: XStandardType = + this.propertyProvider.searchBarSpellcheck; + + @Input() + searchBarDebounce: XStandardType = + this.propertyProvider.searchBarDebounce; + + // + @Input() + searchBarShowCancelButton: XStandardType = + this.propertyProvider.searchBarShowCancelButton; + + @Input() + searchBarCancelButtonIcon: XStandardType = + this.propertyProvider.searchBarCancelButtonIcon; + + @Input() + searchBarCancelButtonTitle: XStandardType = + this.propertyProvider.searchBarCancelButtonTitle; + + @Input() + searchBarCancelButtonIconColor: XStandardType = + this.propertyProvider.searchBarCancelButtonIconColor; + + // + @Input() + searchBarShowClearButton: XStandardType = + this.propertyProvider.searchBarShowClearButton; + + @Input() + searchBarClearButtonIcon: XStandardType = + this.propertyProvider.searchBarClearButtonIcon; + + @Input() + searchBarClearButtonIconColor: XStandardType = + this.propertyProvider.searchBarClearButtonIconColor; + + // + @Input() + searchBarSearchButtonIcon: XStandardType = + this.propertyProvider.searchBarSearchButtonIcon; + + @Input() + searchBarSearchButtonIconColor: XStandardType = + this.propertyProvider.searchBarSearchButtonIconColor; + + // + @Output() + searchBarQueryChange: EventEmitter = new EventEmitter(); + + @Output() + searchBarOnEvent: EventEmitter = new EventEmitter(); + //#endregion + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XMapContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps(container?: IXMapContainer): Observable { + return super + .extractProps(container ? container : this) + .pipe( + concatMap((superData) => { + return forkJoin({ + // + cssClass: this.getValue( + container ? container.cssClass : this.cssClass + ), + zoom: this.getValue(container ? container.zoom : this.zoom), + latitude: this.getValue( + container ? container.latitude : this.latitude + ), + longitude: this.getValue( + container ? container.longitude : this.longitude + ), + canSelect: this.getValue( + container ? container.canSelect : this.canSelect + ), + addMarkerOnSelectedLocation: this.getValue( + container + ? container.addMarkerOnSelectedLocation + : this.addMarkerOnSelectedLocation + ), + markerStyle: this.getValue( + container ? container.markerStyle : this.markerStyle + ), + showProgressBar: this.getValue( + container ? container.showProgressBar : this.showProgressBar + ), + progressBarColor: this.getValue( + container ? container.progressBarColor : this.progressBarColor + ), + progressBarPosition: this.getValue( + container + ? container.progressBarPosition + : this.progressBarPosition + ), + showSelectedLocation: this.getValue( + container + ? container.showSelectedLocation + : this.showSelectedLocation + ), + selectedLocationPosition: this.getValue( + container + ? container.selectedLocationPosition + : this.selectedLocationPosition + ), + showGoMarkedPlace: this.getValue( + container ? container.showGoMarkedPlace : this.showGoMarkedPlace + ), + goMarkedPlacePosition: this.getValue( + container + ? container.goMarkedPlacePosition + : this.goMarkedPlacePosition + ), + showClearMarker: this.getValue( + container ? container.showClearMarker : this.showClearMarker + ), + clearMarkerPosition: this.getValue( + container + ? container.clearMarkerPosition + : this.clearMarkerPosition + ), + showLocator: this.getValue( + container ? container.showLocator : this.showLocator + ), + locatorPosition: this.getValue( + container ? container.locatorPosition : this.locatorPosition + ), + showZoom: this.getValue( + container ? container.showZoom : this.showZoom + ), + showRotate: this.getValue( + container ? container.showRotate : this.showRotate + ), + showZoomSlider: this.getValue( + container ? container.showZoomSlider : this.showZoomSlider + ), + searchBarPosition: this.getValue( + container ? container.searchBarPosition : this.searchBarPosition + ), + searchPlaceProvider: this.getValue( + container + ? container.searchPlaceProvider + : this.searchPlaceProvider + ), + searchResultListColor: this.getValue( + container + ? container.searchResultListColor + : this.searchResultListColor + ), + showSearchBar: this.getValue( + container ? container.showSearchBar : this.showSearchBar + ), + searchBarCssClass: this.getValue( + container ? container.searchBarCssClass : this.searchBarCssClass + ), + searchBarColor: this.getValue( + container ? container.searchBarColor : this.searchBarColor + ), + searchBarForegroundColor: this.getValue( + container + ? container.searchBarForegroundColor + : this.searchBarForegroundColor + ), + searchBarSearchInputColor: this.getValue( + container + ? container.searchBarSearchInputColor + : this.searchBarSearchInputColor + ), + searchBarForegroundSearchInputColor: this.getValue( + container + ? container.searchBarForegroundSearchInputColor + : this.searchBarForegroundSearchInputColor + ), + searchBarType: this.getValue( + container ? container.searchBarType : this.searchBarType + ), + searchBarTitle: this.getValue( + container ? container.searchBarTitle : this.searchBarTitle + ), + searchBarValue: this.getValue( + container ? container.searchBarValue : this.searchBarValue + ), + searchBarEnterKeyHint: this.getValue( + container + ? container.searchBarEnterKeyHint + : this.searchBarEnterKeyHint + ), + searchBarKeyboardType: this.getValue( + container + ? container.searchBarKeyboardType + : this.searchBarKeyboardType + ), + searchBarSpellcheck: this.getValue( + container + ? container.searchBarSpellcheck + : this.searchBarSpellcheck + ), + searchBarDebounce: this.getValue( + container ? container.searchBarDebounce : this.searchBarDebounce + ), + searchBarShowCancelButton: this.getValue( + container + ? container.searchBarShowCancelButton + : this.searchBarShowCancelButton + ), + searchBarCancelButtonIcon: this.getValue( + container + ? container.searchBarCancelButtonIcon + : this.searchBarCancelButtonIcon + ), + searchBarCancelButtonTitle: this.getValue( + container + ? container.searchBarCancelButtonTitle + : this.searchBarCancelButtonTitle + ), + searchBarCancelButtonIconColor: this.getValue( + container + ? container.searchBarCancelButtonIconColor + : this.searchBarCancelButtonIconColor + ), + searchBarShowClearButton: this.getValue( + container + ? container.searchBarShowClearButton + : this.searchBarShowClearButton + ), + searchBarClearButtonIcon: this.getValue( + container + ? container.searchBarClearButtonIcon + : this.searchBarClearButtonIcon + ), + searchBarClearButtonIconColor: this.getValue( + container + ? container.searchBarClearButtonIconColor + : this.searchBarClearButtonIconColor + ), + searchBarSearchButtonIcon: this.getValue( + container + ? container.searchBarSearchButtonIcon + : this.searchBarSearchButtonIcon + ), + searchBarSearchButtonIconColor: this.getValue( + container + ? container.searchBarSearchButtonIconColor + : this.searchBarSearchButtonIconColor + ), + }).pipe( + map((data) => { + return { + superData, + data, + }; + }) + ); + }) + ) + .pipe( + map((info) => { + return { + // + ...info.superData, + + // + ...info.data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + isReady: this.isReady, + viewChanged: this.viewChanged, + locationSelected: this.locationSelected, + + // + searchBarActionProvider: container + ? container.searchBarActionProvider + : this.searchBarActionProvider, + + // + searchBarQueryChange: this.searchBarQueryChange, + searchBarOnEvent: this.searchBarOnEvent, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-map\x-map-dialog\x-map-dialog.component.html + +
+ +
+ + +
+ + + +
+ +
+
+ + + + +

+ {{ getTitle() | async }} +

+
+
+ + + +
+ +
+
+
+
+
+
+ + + + +
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-map\x-map-dialog\x-map-dialog.component.scss + +:host { + --x-map-height: 200px; +} + +::ng-deep.fullscreen-dialog { + // + width: 80vw !important; + max-width: 80vw !important; + + // + height: 80vh !important; + max-height: 80vh !important; + + // + &.mobile { + // + width: 100vw !important; + max-width: 100vw !important; + + // + height: 100vh !important; + max-height: 100vh !important; + } + + // + .mat-dialog-container { + padding: 0 !important; + } +} + +:host ::ng-deep x-map { + // + --map-height: var(--x-map-height); +} + +.x-map-dialog-container { + height: 100%; +} + +x-icon { + --size: 24px; +} + +.toolbar-area { + margin: 8px; +} + +.toolbar-item { + width: auto; + height: 48px; +} + +.toolbar-button { + width: 32px; + margin: { + left: 4px; + right: 4px; + } +} + +h1, +h2, +h3, +h4, +h5, +h6 { + margin: 0 !important; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-map\x-map-dialog\x-map-dialog.component.ts + +import { + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + ChangeDetectorRef, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + XColor, + isFunction, + XResourceIDs, + XDialogResult, + isNullOrUndefined, + isNullOrEmptyString, + XColorWithBrightness, +} from 'x-framework-core'; +import { + XMapAction, + XMapLocation, + XMapActionModel, + XMapItemPosition, + defaultMarkerStyle, + defaultMapSearchPlaceProvider, +} from '../x-map.typings'; +import { ViewChild } from '@angular/core'; +import { Subject, Subscription } from 'rxjs'; +import { concatMap, map } from 'rxjs/operators'; +import { + XSearchBarType, + XSearchBarShowButton, + XSearchBarActionModel, + XSearchBarEnterKeyHint, +} from '../../x-search-bar/x-search-bar.typings'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XBasePage } from '../../classes/x-base-page'; +import { XManagerService } from 'x-framework-services'; +import { XIconNames } from '../../x-icon/x-icon.typings'; +import { XSlotName } from '../../x-slotter/x-slotter.typings'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { XMapDialogConfig, XMapdialogCssVar } from './x-map-dialog.typings'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../../config/x-framework-components.config'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-map-dialog', + templateUrl: './x-map-dialog.component.html', + styleUrls: ['./x-map-dialog.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XMapDialogComponent extends XBasePage { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + readonly selectIcon = XIconNames.checked; + readonly backIcon = XIconNames.arrow_back; + + // + readonly SlotNames = Object.assign({}, XSlotName); + readonly MapItemPositions = Object.assign({}, XMapItemPosition); + + // + hasMarker: boolean; + selectedLocation: XMapLocation; + actionProvider = new Subject(); + + // + @ViewChild('mapDialogContainer') + mapDialogContainer: ElementRef; + + @ViewChild('mapDialogToolbar') + mapDialogToolbar: ElementRef; + //#endregion + + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + @Inject(MAT_DIALOG_DATA) public data: any, + public dialogRef: MatDialogRef + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region LifeCycles ... + async onInit() { + super.onInit(); + + // + this.preventBack = true; + } + + async afterViewInit() { + super.afterViewInit(); + + // + await this.applyMapSize(); + } + + public registerViewHandlers(): void { + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider.subscribe( + (action) => { + switch (action.action) { + // + case XMapAction.HasMarker: + // + this.hasMarker = true; + this.detectChanges(); + break; + + // + case XMapAction.HasNotMarker: + this.hasMarker = false; + this.detectChanges(); + break; + } + } + ); + } + + public onDestroy(): void { + // + if (!isNullOrUndefined(this.ACTION_PROVIDER_SUBSCRIPTION)) { + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + } + //#endregion + + // + //#region UI Providers ... + /** + * retrieve map dialog config ... + */ + getData() { + return this.getValue(this.data as XMapDialogConfig); + } + + /** + * determines map dialog config exists or not ... + */ + hasData() { + return !isNullOrUndefined(this.getData()); + } + + /** + * determines dialog can select result or not ... + */ + dialogCanSelect() { + return ( + this.hasMarker && + !isNullOrUndefined(this.selectedLocation) && + !isNullOrEmptyString(this.selectedLocation.latitude) && + !isNullOrEmptyString(this.selectedLocation.longitude) + ); + } + + /** + * retrieve dialog title ... + */ + getTitle() { + return this.getData().pipe( + concatMap((config) => this.getResourceValue(config.title)) + ); + } + + // + get markerStyle() { + return this.getData().pipe( + concatMap((config) => this.getValue(config.markerStyle)), + map((style) => (style ? style : defaultMarkerStyle)) + ); + } + + get cssClass() { + return this.getData().pipe( + concatMap((config) => this.getArrayValue(config.cssClass)) + ); + } + + get progressBarColor() { + return this.getData().pipe( + concatMap((config) => this.getValue(config.progressBarColor)), + map((color) => (!isNullOrEmptyString(color) ? color : XColor.Warning)) + ); + } + + get canSelect() { + return this.getData().pipe( + concatMap((config) => + this.getValue(config.canSelect ? config.canSelect : true) + ) + ); + } + + // + get latitude() { + return this.getData().pipe( + concatMap((config) => this.getValue(config.latitude)) + ); + } + + get longitude() { + return this.getData().pipe( + concatMap((config) => this.getValue(config.longitude)) + ); + } + + // + get zoom() { + return this.getData().pipe( + concatMap((config) => this.getValue(config.zoom)) + ); + } + + // + get showRotate() { + return this.getData().pipe( + concatMap((config) => this.getOptionalValue(config.showRotate)), + map((canShow) => (isNullOrUndefined(canShow) ? true : canShow)) + ); + } + + get showZoom() { + return this.getData().pipe( + concatMap((config) => this.getOptionalValue(config.showZoom)), + map((canShow) => (isNullOrUndefined(canShow) ? true : canShow)) + ); + } + + get showZoomSlider() { + return this.getData().pipe( + concatMap((config) => this.getOptionalValue(config.showZoomSlider)), + map((canShow) => (isNullOrUndefined(canShow) ? false : canShow)) + ); + } + + get showLocator() { + return this.getData().pipe( + concatMap((config) => this.getOptionalValue(config.showLocator)), + map((canShow) => (isNullOrUndefined(canShow) ? true : canShow)) + ); + } + + get showClearMarker() { + return this.getData().pipe( + concatMap((config) => this.getOptionalValue(config.showClearMarker)), + map((canShow) => (isNullOrUndefined(canShow) ? true : canShow)) + ); + } + + get showGoMarkedPlace() { + return this.getData().pipe( + concatMap((config) => this.getOptionalValue(config.showGoMarkedPlace)), + map((canShow) => (isNullOrUndefined(canShow) ? true : canShow)) + ); + } + + get showSelectedLocation() { + return this.getData().pipe( + concatMap((config) => this.getOptionalValue(config.showSelectedLocation)), + map((canShow) => (isNullOrUndefined(canShow) ? true : canShow)) + ); + } + + get showSearchBar() { + return this.getData().pipe( + concatMap((config) => this.getOptionalValue(config.showSearchBar)), + map((canShow) => (isNullOrUndefined(canShow) ? true : canShow)) + ); + } + + get searchResultListColor() { + return this.getData().pipe( + concatMap((config) => this.getValue(config.searchResultListColor)), + map((color) => (!isNullOrEmptyString(color) ? color : XColor.Light)) + ); + } + + // // + // get isInPage() { + // return this.getData().pipe( + // concatMap((config) => this.getOptionalValue(config.isInPage)) + // ); + // } + + // get wrapWithCard() { + // return this.getData().pipe( + // concatMap((config) => this.getOptionalValue(config.wrapWithCard)) + // ); + // } + + // get cardColor() { + // return this.getData().pipe( + // concatMap((config) => this.getValue(config.progressBarColor)), + // map((color) => (!isNullOrEmptyString(color) ? color : XColor.Light)) + // ); + // } + + // get cardForegroundColor() { + // return this.getData().pipe( + // concatMap((config) => this.getOptionalValue(config.cardForegroundColor)) + // ); + // } + + // get cardTitle() { + // return this.getData().pipe( + // concatMap((config) => this.getResourceValue(config.cardTitle)) + // ); + // } + + // get cardSubTitle() { + // return this.getData().pipe( + // concatMap((config) => this.getResourceValue(config.cardSubTitle)) + // ); + // } + + // get cardShowFooter() { + // return this.getData().pipe( + // concatMap((config) => this.getOptionalValue(config.cardShowFooter)) + // ); + // } + + // + get showProgressBar() { + return this.getData().pipe( + concatMap((config) => this.getOptionalValue(config.showProgressBar)), + map((result) => (isNullOrUndefined(result) ? true : result)) + ); + } + + // get progressBarPosition() { + // return this.getData().pipe( + // concatMap((config) => this.getOptionalValue(config.progressBarPosition)), + // map((result) => + // isNullOrEmptyString(result) ? XMapItemPosition.TOP : result + // ) + // ); + // } + + get locatorPosition() { + return this.getData().pipe( + concatMap((config) => this.getOptionalValue(config.locatorPosition)), + map((result) => + isNullOrEmptyString(result) ? XMapItemPosition.TOP : result + ) + ); + } + + get clearMarkerPosition() { + return this.getData().pipe( + concatMap((config) => this.getOptionalValue(config.clearMarkerPosition)), + map((result) => + isNullOrEmptyString(result) ? XMapItemPosition.TOP : result + ) + ); + } + + get goMarkedPlacePosition() { + return this.getData().pipe( + concatMap((config) => + this.getOptionalValue(config.goMarkedPlacePosition) + ), + map((result) => + isNullOrEmptyString(result) ? XMapItemPosition.TOP : result + ) + ); + } + + get selectedLocationPosition() { + return this.getData().pipe( + concatMap((config) => + this.getOptionalValue(config.selectedLocationPosition) + ), + map((result) => + isNullOrEmptyString(result) ? XMapItemPosition.TOP : result + ) + ); + } + + // + //#region Search ... + get searchPlaceProvider() { + return this.getData().pipe( + concatMap((config) => this.getOptionalValue(config.searchPlaceProvider)), + map((provider) => + !provider || !isFunction(provider) + ? defaultMapSearchPlaceProvider + : provider + ) + ); + } + + get searchBarType() { + return this.getData().pipe( + concatMap((config) => this.getOptionalValue(config.searchBarType)), + map((data) => (isNullOrEmptyString(data) ? XSearchBarType.Search : data)) + ); + } + + get searchBarValue() { + return this.getData().pipe( + concatMap((config) => this.getOptionalValue(config.searchBarValue)) + ); + } + + get searchBarColor() { + return this.getData().pipe( + concatMap((config) => this.getOptionalValue(config.searchBarColor)), + map((data) => + isNullOrEmptyString(data) ? XColorWithBrightness.Primary : data + ) + ); + } + + get searchBarTitle() { + return this.getData().pipe( + concatMap((config) => this.getOptionalValue(config.searchBarTitle)), + map((data) => + !data || isNullOrEmptyString(data.toString()) + ? XResourceIDs.search + : data + ) + ); + } + + // get searchBarPosition() { + // return this.getData().pipe( + // concatMap((config) => this.getOptionalValue(config.searchBarPosition)), + // map((data) => (isNullOrEmptyString(data) ? XMapItemPosition.TOP : data)) + // ); + // } + + get searchBarCssClass() { + return this.getData().pipe( + concatMap((config) => this.getArrayValue(config.searchBarCssClass)) + ); + } + + get searchBarDebounce() { + return this.getData().pipe( + concatMap((config) => this.getOptionalValue(config.searchBarDebounce)), + map((data) => + isNullOrUndefined(data) ? this.config.defaultSearchDebounceTime : data + ) + ); + } + + get searchBarClearButtonIcon() { + return this.getData().pipe( + concatMap((config) => + this.getOptionalValue(config.searchBarClearButtonIcon) + ), + map((data) => + !data || isNullOrEmptyString(data.toString()) ? XIconNames.clear : data + ) + ); + } + + get searchBarSearchInputColor() { + return this.getData().pipe( + concatMap((config) => + this.getOptionalValue(config.searchBarSearchInputColor) + ), + map((data) => + isNullOrEmptyString(data) ? XColorWithBrightness.Primary : data + ) + ); + } + + get searchBarSpellcheck() { + return this.getData().pipe( + concatMap((config) => this.getOptionalValue(config.searchBarSpellcheck)) + ); + } + + get searchBarSearchButtonIcon() { + return this.getData().pipe( + concatMap((config) => + this.getOptionalValue(config.searchBarSearchButtonIcon) + ), + map((data) => + !data || isNullOrEmptyString(data.toString()) ? XIconNames.search : data + ) + ); + } + + get searchBarKeyboardType() { + return this.getData().pipe( + concatMap((config) => + this.getOptionalValue(config.searchBarKeyboardType) + ), + map((data) => (isNullOrEmptyString(data) ? XSearchBarType.Search : data)) + ); + } + + get searchBarEnterKeyHint() { + return this.getData().pipe( + concatMap((config) => + this.getOptionalValue(config.searchBarEnterKeyHint) + ), + map((data) => + isNullOrEmptyString(data) ? XSearchBarEnterKeyHint.Search : data + ) + ); + } + + get searchBarClearIconColor() { + return this.getData().pipe( + concatMap((config) => + this.getOptionalValue(config.searchBarClearButtonIconColor) + ), + map((data) => + isNullOrEmptyString(data) ? XColorWithBrightness.Light : data + ) + ); + } + + get searchBarActionProvider() { + return this.getData().pipe( + map((data) => + !isNullOrUndefined(data.searchBarActionProvider) + ? data.searchBarActionProvider + : new Subject() + ) + ); + } + + get searchBarSearchButtonIconColor() { + return this.getData().pipe( + concatMap((config) => + this.getOptionalValue(config.searchBarSearchButtonIconColor) + ), + map((data) => + isNullOrEmptyString(data) ? XColorWithBrightness.Light : data + ) + ); + } + + get searchBarForegroundColor() { + return this.getData().pipe( + concatMap((config) => + this.getOptionalValue(config.searchBarForegroundColor) + ) + ); + } + + get searchBarShowCancelButton() { + return this.getData().pipe( + concatMap((config) => + this.getOptionalValue(config.searchBarShowCancelButton) + ), + map((data) => + isNullOrEmptyString(data) ? XSearchBarShowButton.Never : data + ) + ); + } + + get searchBarCancelButtonIcon() { + return this.getData().pipe( + concatMap((config) => + this.getOptionalValue(config.searchBarCancelButtonIcon) + ), + map((data) => + !data || isNullOrEmptyString(data.toString()) ? XIconNames.cancel : data + ) + ); + } + + get searchBarCancelButtonTitle() { + return this.getData().pipe( + concatMap((config) => + this.getResourceValue(config.searchBarCancelButtonTitle) + ) + ); + } + + get searchBarForegroundSearchInputColor() { + return this.getData().pipe( + concatMap((config) => + this.getOptionalValue(config.searchBarForegroundSearchInputColor) + ) + ); + } + //#endregion + //#endregion + + // + //#region UI Handlers ... + async handleBack() { + this.dialogRef.close(); + } + + async handleSelect() { + // + if (!this.dialogCanSelect()) { + return; + } + + // + this.dialogRef.close({ + result: XDialogResult.Ok, + payload: this.selectedLocation, + }); + } + + async handleMapIsReady() { + // + const config = await this.getValueAsync(this.getData()); + if (!config || !config.isReady) { + return; + } + + // + config.isReady.emit(); + } + + async handleSearchBarQueryChange(event: string) { + // + const config = await this.getValueAsync(this.getData()); + if (!config || !config.searchBarQueryChange) { + return; + } + + // + config.searchBarQueryChange.emit(event); + } + + async handleMapLocationSelected(event: XMapLocation) { + // + this.selectedLocation = event; + + // + const config = await this.getValueAsync(this.getData()); + if (!config || !config.locationSelected) { + return; + } + + // + config.locationSelected.emit(event); + + // + this.detectChanges(); + } + //#endregion + + // + //#region Private ... + async applyMapSize() { + // + let result = 0; + + // + //#region DialogHeight ... + // + if (!this.mapDialogContainer) { + // + setTimeout(async () => await this.applyMapSize(), 500); + return; + } + const height = + this.mapDialogContainer.nativeElement.getBoundingClientRect().height; + if (height === 0) { + // + setTimeout(async () => await this.applyMapSize(), 500); + return; + } + + // + result += height; + //#endregion + + // + //#region ToolbarHeight ... + if (!this.mapDialogToolbar) { + // + setTimeout(async () => await this.applyMapSize(), 500); + return; + } + const toolbarHeight = + this.mapDialogToolbar.nativeElement.getBoundingClientRect().height; + if (toolbarHeight === 0) { + // + setTimeout(async () => await this.applyMapSize(), 500); + return; + } + + // + result -= toolbarHeight; + //#endregion + + // + this.setCssVar(XMapdialogCssVar.XMapHeight, `${result}px`); + this.detectChanges(); + + // + this.runTimedOut(() => { + this.managerService.dispatchResizeEvent(); + }); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-map\x-map-dialog\x-map-dialog.service.ts + +import { + XColor, + toPromise, + XBaseService, + XResourceIDs, + XDialogResult, + isNullOrUndefined, +} from 'x-framework-core'; +import { + MatDialog, + MatDialogRef, + MatDialogConfig, +} from '@angular/material/dialog'; +import { XManagerService } from 'x-framework-services'; +import { XMapLocation } from '../x-map.typings'; +import { XMapDialogComponent } from './x-map-dialog.component'; +import { Injectable, Output, EventEmitter, Inject } from '@angular/core'; +import { XMapDialogResult, XMapDialogConfig } from './x-map-dialog.typings'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../../config/x-framework-components.config'; + +@Injectable({ + providedIn: 'root', +}) +export class XMapDialogService extends XBaseService { + // + //#region Props ... + private MOBILE_CLASS_NAME = 'mobile'; + private DIALOG_REF: MatDialogRef; + + // + isMobile$ = this.managerService.isMobileUi$; + + // tslint:disable-next-line:no-output-on-prefix + @Output() + onResult = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + private dialog: MatDialog, + private managerService: XManagerService + ) { + super(config); + } + //#endregion + + // + //#region Actions ... + /** + * Open Select Map Dialog ... + */ + async openDialog(config?: XMapDialogConfig) { + // + // Prevent Double Bubble ... + if (this.DIALOG_REF) { + return; + } + + // + // Prepare Config ... + if (!config) { + config = {}; + } + + // + //#region Default Config for Map Component ... + // + // Zoom ... + if (config && !config.zoom) { + config.zoom = 6; + } + + // + // canSelect ... + let selectable = false; + if (config && !isNullOrUndefined(config.canSelect)) { + // + selectable = await toPromise(config.canSelect); + config.canSelect = selectable; + } + + // + // Set Default Title ... + if (config && !config.title) { + // + if (selectable) { + config.title = XResourceIDs.map_dialog_select_title; + } else { + config.title = XResourceIDs.map_dialog_title; + } + } + + // + // Search Bar Color ... + if (config && !config.searchBarColor) { + config.searchBarColor = XColor.Primary; + } + + // + // Progress Bar Color ... + if (config && !config.progressBarColor) { + config.progressBarColor = XColor.Warning; + } + + // + // Search Result List Color ... + if (config && !config.searchResultListColor) { + config.searchResultListColor = XColor.Medium; + } + //#endregion + + // + // Prepare Dialog Config ... + const data: MatDialogConfig = { + data: config, + }; + + // + data.panelClass = 'fullscreen-dialog'; + + // + // Show Cropper inside a Dialog ... + this.DIALOG_REF = this.dialog.open(XMapDialogComponent, data); + this.DIALOG_REF.disableClose = true; + + // + // Attach Mobile Class ... + this.isMobile$.subscribe((isMobile) => { + // + if (isMobile) { + if (this.DIALOG_REF) { + this.DIALOG_REF.addPanelClass(this.MOBILE_CLASS_NAME); + } + } else { + if (this.DIALOG_REF) { + this.DIALOG_REF.removePanelClass(this.MOBILE_CLASS_NAME); + } + } + }); + + // + // Handle Backdrop ... + this.DIALOG_REF.backdropClick().subscribe(() => { + // + // Cancel Dialog ... + this.onResult.emit({ result: XDialogResult.Cancel }); + + // + this.closeDialog(); + }); + + // + // Handle Dialog Result ... + this.DIALOG_REF.afterClosed().subscribe((model: XMapDialogResult) => { + // + if (isNullOrUndefined(model) || model.result !== XDialogResult.Ok) { + // + // Cancel Dialog ... + this.onResult.emit({ result: XDialogResult.Cancel }); + } else { + // + // Handle Results ... + this.onResult.emit(model); + } + + // + this.closeDialog(); + }); + } + + /** + * Close Select Map Dialog ... + */ + closeDialog() { + // + // Prevent Go Forward ... + if (!this.DIALOG_REF) { + return; + } + + // + // Close Dialog ... + this.DIALOG_REF.close(); + this.DIALOG_REF = null; + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-map\x-map-dialog\x-map-dialog.typings.ts + +import { XDialogResult } from 'x-framework-core'; +import { XMapLocation, IXMapContainer } from '../x-map.typings'; +import { XBaseDialogConfig } from '../../classes/i-x-base-dialog.container'; + +export interface XMapDialogResult { + payload?: XMapLocation; + result: XDialogResult; +} + +export interface XMapDialogConfig + extends XBaseDialogConfig, + Pick< + Partial, + | 'zoom' + | 'isReady' + | 'latitude' + | 'cssClass' + | 'showZoom' + | 'longitude' + | 'canSelect' + | 'showRotate' + | 'showLocator' + | 'markerStyle' + | 'showSearchBar' + | 'searchBarType' + | 'actionProvider' + | 'searchBarValue' + | 'searchBarColor' + | 'searchBarTitle' + | 'searchBarColor' + | 'showZoomSlider' + | 'showProgressBar' + | 'showClearMarker' + | 'locatorPosition' + | 'progressBarColor' + | 'locationSelected' + | 'searchBarCssClass' + | 'showGoMarkedPlace' + | 'searchBarDebounce' + | 'searchPlaceProvider' + | 'clearMarkerPosition' + | 'searchBarSpellcheck' + | 'searchBarQueryChange' + | 'showSelectedLocation' + | 'goMarkedPlacePosition' + | 'searchBarKeyboardType' + | 'searchBarEnterKeyHint' + | 'searchResultListColor' + | 'searchBarActionProvider' + | 'searchBarClearButtonIcon' + | 'searchBarForegroundColor' + | 'selectedLocationPosition' + | 'searchBarForegroundColor' + | 'searchBarSearchButtonIcon' + | 'searchBarSearchInputColor' + | 'searchBarSearchInputColor' + | 'searchBarShowCancelButton' + | 'searchBarCancelButtonIcon' + | 'searchBarCancelButtonTitle' + | 'searchBarClearButtonIconColor' + | 'searchBarSearchButtonIconColor' + | 'searchBarForegroundSearchInputColor' + > {} + +export enum XMapdialogCssVar { + XMapHeight = 'x-map-height', +} + +export type XMapdialogCssVarIdentifier = XMapdialogCssVar | string; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-markdown\x-markdown.component.html + + + + + + + + +
+ +
+
+
+ + + +
+ +
+ + + + + + + + + + + +
+ + + + + +
+ + +
+
+ + + +
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-markdown\x-markdown.component.scss + +:host ::ng-deep a { + pointer-events: none; +} + +:host { + width: inherit; + height: inherit; +} + +textarea { + resize: none; +} + +.x-editor { + width: 100%; + min-height: 300px; + height: fit-content; +} + +.x-toolbar-wrapper { + margin-bottom: 0px; +} + +.x-preview { + text-align: justify; +} + +.x-preview-wrapper { + height: fit-content; + min-height: 300px; + border: 1px solid gray; + padding: { + left: 4px; + right: 4px; + } +} + +.x-toolbar-icon { + margin: { + left: 4px; + right: 4px; + } +} + +.no-border { + border: 0; + min-height: unset; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-markdown\x-markdown.component.ts + +import { + ViewChild, + Component, + ElementRef, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + keys, + nameof, + insertText, + XStandardType, + isNullOrEmptyString, +} from 'x-framework-core'; +import { + XMarkdownMode, + XMarkdownAction, + XMarkdownActionModel, + XMarkdownToolbarItem, + XMarkdownModeIdentifier, + XMarkdownToolbarItemName, + XMarkdownToolbarConfigProps, + XMarkdownContainerComponent, + defaultXMarkdownToolbarConfig, +} from './x-markdown.typings'; +import { map } from 'rxjs/operators'; +import 'node_modules/prismjs/prism.js'; +import 'node_modules/marked/marked.min.js'; +import { Subject, Subscription } from 'rxjs'; +import { XSlotLayout } from '../x-slotter/x-slotter.typings'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-markdown', + templateUrl: './x-markdown.component.html', + styleUrls: ['./x-markdown.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XMarkdownComponent extends XMarkdownContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + private TOOLBAR_CONFIG: XMarkdownToolbarConfigProps; + + // + readonly SlotterLayouts = Object.assign({}, XSlotLayout); + + // + innerContent: string = this.content; + preparedMode: XMarkdownModeIdentifier; + toolbarItems = keys(defaultXMarkdownToolbarConfig); + + // + get preparedToolbarConfig() { + return this.TOOLBAR_CONFIG; + } + + // + @ViewChild('markdownEditor', { static: false }) + editor: ElementRef; + //#endregion + + // + //#region LifeCycles ... + async onInit() { + super.onInit(); + + // + await this.prepareMode(); + await this.prepareToolbarConfig(null); + } + + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + + // + //#region Content Chaned ... + const isContentChanged = changeKeys.includes( + nameof('content') + ); + if (isContentChanged && this.content !== this.innerContent) { + this.innerContent = this.content; + this.detectChanges(); + } + //#endregion + + // + //#region Model Changed ... + const isModeChanged = changeKeys.includes( + nameof('mode') + ); + if (isModeChanged) { + await this.prepareMode(); + await this.prepareToolbarConfig(null); + } + //#endregion + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + // Register Action Provider ... + this.registerActionProvider(); + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + //#endregion + + // + //#region UI Providers ... + // + getSlotterLayout() { + return this.isMobileUi$.pipe( + map((isMobileUi) => + isMobileUi ? XSlotLayout.VERTICAL : XSlotLayout.HORIZONTAL + ) + ); + } + + // + //#region Mode Detectors ... + // + isEditMode() { + return this.getValueStream(this.mode).pipe( + map((preparedMode) => preparedMode === XMarkdownMode.EDIT) + ); + } + + // + isPreviewMode() { + return this.getValueStream(this.mode).pipe( + map((preparedMode) => preparedMode === XMarkdownMode.PREVIEW) + ); + } + + // + isBothMode() { + return this.getValueStream(this.mode).pipe( + map((preparedMode) => preparedMode === XMarkdownMode.BOTH) + ); + } + //#endregion + + // + //#region Toolbar Providers ... + hasToolbarItem(name: XMarkdownToolbarItemName | any) { + // + if (!this.TOOLBAR_CONFIG) { + return false; + } + + // + const itesNamess = keys(this.TOOLBAR_CONFIG); + const result = itesNamess.includes(name); + + // + return result; + } + + getToolbarItem(name: XMarkdownToolbarItemName | any) { + // + if (!this.hasToolbarItem(name)) { + return null; + } + + // + const item = this.TOOLBAR_CONFIG[name]; + const defaultItem = this.getDefaultItemConfig(name); + + // + const result: XMarkdownToolbarItem = { + ...defaultItem, + ...item, + }; + + // + return result; + } + //#endregion + //#endregion + + // + //#region Ui Handlers ... + handleContentChanged(content: string) { + // + if (!content) { + content = ''; + } + + // + this.innerContent = content; + this.contentChange.emit(content); + } + + async handleToolbarItemClicked(name: XMarkdownToolbarItemName) { + // + switch (name) { + // + case 'preview': + await this.handleToggleMode(); + break; + + // + case 'bold': + await this.handleAddContent('**', true); + break; + + // + case 'code': + await this.handleAddContent('`', true); + break; + + // + case 'heading': + await this.handleAddContent('# ', false); + break; + + // + case 'image': + await this.handleAddContent('![alt text](image url)', false); + break; + + // + case 'italic': + await this.handleAddContent('*', true); + break; + + // + case 'link': + await this.handleAddContent('[title](url)', false); + break; + + // + case 'ol': + await this.handleAddContent('1. ', false); + break; + + // + case 'ul': + await this.handleAddContent('- ', false); + break; + + // + case 'quote': + await this.handleAddContent('> ', false); + break; + } + + // + if (this.editor) { + this.editor.nativeElement.focus(); + } + } + //#endregion + + // + //#region Private ... + // + //#region ActionProvider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + // Handle Action Provider ... + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XMarkdownAction.Refresh: + this.detectChanges(); + break; + + // + case XMarkdownAction.FireToolbarButtons: + // + if (!isNullOrEmptyString(model.payload)) { + await this.handleToolbarItemClicked(model.payload); + } + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + private async prepareMode() { + this.preparedMode = await this.getValueAsync(this.mode); + await this.changeMode(this.preparedMode); + } + + private async prepareToolbarConfig( + config: XStandardType + ) { + // + if (!config) { + this.TOOLBAR_CONFIG = defaultXMarkdownToolbarConfig; + return; + } + + // + const cfg = await this.getValueAsync(config); + + // + this.TOOLBAR_CONFIG = { + ...cfg, + }; + + // + this.detectChanges(); + } + + private getDefaultItemConfig(name: XMarkdownToolbarItemName) { + return defaultXMarkdownToolbarConfig[name]; + } + + private async changeMode(mode: XMarkdownModeIdentifier) { + // + this.mode = mode; + this.preparedMode = mode; + + // + this.detectChanges(); + } + + private async handleToggleMode() { + // + const mode = await this.getValueAsync(this.mode); + + // + switch (mode) { + case XMarkdownMode.BOTH: + await this.changeMode(XMarkdownMode.EDIT); + break; + + case XMarkdownMode.EDIT: + await this.changeMode(XMarkdownMode.BOTH); + break; + } + } + + private getCurrentPosition() { + // + if (!this.editor) { + return { + start: -1, + end: -1, + }; + } + + // + const start = this.editor.nativeElement.selectionStart; + const end = this.editor.nativeElement.selectionEnd; + + // + const result = { + start: start ? +start : -1, + end: end ? +end : -1, + }; + + // + return result; + } + + private async handleAddContent(content: string, double: boolean = false) { + // + if (!content) { + return; + } + + // + const pos = this.getCurrentPosition(); + + // + if (!pos) { + return; + } + + // + let selectedContent: string; + if (pos.start === pos.end) { + selectedContent = ''; + } else { + // + selectedContent = this.innerContent.substring(pos.start, pos.end); + this.innerContent = this.innerContent.replace(selectedContent, ''); + } + + // + let insertedContent: string; + if (isNullOrEmptyString(selectedContent)) { + insertedContent = double ? `${content}${content}` : `${content}`; + } else { + insertedContent = double + ? `${content}${selectedContent}${content}` + : `${content}${selectedContent}`; + } + + // + const startPos = pos.start > -1 ? pos.start : this.innerContent.length; + this.innerContent = insertText( + this.innerContent || '', + insertedContent, + startPos + ); + this.contentChange.emit(this.innerContent); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-markdown\x-markdown.module.ts + +import { NgModule } from '@angular/core'; +import { MarkdownModule } from 'ngx-markdown'; +import { CommonModule } from '@angular/common'; +import { XCardModule } from '../x-card/x-card.module'; +import { XIconModule } from './../x-icon/x-icon.module'; +import { XMarkdownComponent } from './x-markdown.component'; +import { XSlotterModule } from '../x-slotter/x-slotter.module'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XMarkdownComponent], + imports: [ + CommonModule, + XIconModule, + XCardModule, + XSlotterModule, + XBaseComponentModule, + MarkdownModule.forRoot(), + ], + exports: [ + XIconModule, + XCardModule, + MarkdownModule, + XSlotterModule, + XMarkdownComponent, + XBaseComponentModule, + ], +}) +export class XMarkdownModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-markdown\x-markdown.typings.ts + +import { + Input, + Output, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XResourceIDs, + XStandardType, + XOneOrManyType, + XColorIdentifier, + XResourceIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { + XComponentCardContainer, + IXComponentCardContainer, + XComponentCardContainerComponent, +} from '../x-card/x-card.typings'; +import { concatMap, map } from 'rxjs/operators'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { XIconIdentifier, XIconNames } from '../x-icon/x-icon.typings'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export enum XMarkdownMode { + EDIT = 'edit', + PREVIEW = 'preview', + BOTH = 'both', +} + +export type XMarkdownModeIdentifier = XMarkdownMode | string; + +export enum XMarkdownToolbarItemSlot { + START = 'start', + END = 'end', +} + +export type XMarkdownToolbarItemSlotIdentifier = + | XMarkdownToolbarItemSlot + | string; + +export type XMarkdownToolbarItemName = + | 'image' + | 'link' + | 'ul' + | 'ol' + | 'quote' + | 'code' + | 'bold' + | 'italic' + | 'heading' + | 'preview' + | 'guide'; + +export interface XMarkdownToolbarItem { + icon?: XIconIdentifier; + title?: XResourceIdentifier; + slot?: XMarkdownToolbarItemSlotIdentifier; +} + +export type XMarkdownToolbarConfigProps = { + [key in XMarkdownToolbarItemName]?: XMarkdownToolbarItem; +}; + +export const defaultXMarkdownToolbarConfig: XMarkdownToolbarConfigProps = { + heading: { + icon: XIconNames.heading, + title: XResourceIDs.x_markdown_heading, + slot: XMarkdownToolbarItemSlot.START, + }, + bold: { + icon: XIconNames.bold, + title: XResourceIDs.x_markdown_bold, + slot: XMarkdownToolbarItemSlot.START, + }, + italic: { + icon: XIconNames.italic, + title: XResourceIDs.x_markdown_italic, + slot: XMarkdownToolbarItemSlot.START, + }, + quote: { + icon: XIconNames.quote, + title: XResourceIDs.x_markdown_quote, + slot: XMarkdownToolbarItemSlot.START, + }, + code: { + icon: XIconNames.code, + title: XResourceIDs.x_markdown_code, + slot: XMarkdownToolbarItemSlot.START, + }, + ol: { + icon: XIconNames.ol, + title: XResourceIDs.x_markdown_ol, + slot: XMarkdownToolbarItemSlot.START, + }, + ul: { + icon: XIconNames.ul, + title: XResourceIDs.x_markdown_ul, + slot: XMarkdownToolbarItemSlot.START, + }, + link: { + icon: XIconNames.link, + title: XResourceIDs.x_markdown_link, + slot: XMarkdownToolbarItemSlot.START, + }, + image: { + icon: XIconNames.image, + title: XResourceIDs.x_markdown_image, + slot: XMarkdownToolbarItemSlot.START, + }, + preview: { + icon: XIconNames.preview, + title: XResourceIDs.x_markdown_preview, + slot: XMarkdownToolbarItemSlot.END, + }, + guide: { + icon: XIconNames.guide, + title: XResourceIDs.x_markdown_guide, + slot: XMarkdownToolbarItemSlot.END, + }, +}; + +export enum XMarkdownAction { + // + FireToolbarButtons = 'fire_toolbar_buttons', + + // + Refresh = 'refresh', +} + +export type XMarkdownActionIdentifier = XMarkdownAction | string; + +export interface XMarkdownActionModel + extends XBaseActionModel {} + +// +//#region Inside Card Container ... +export interface IXMarkdownContainer extends IXComponentCardContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + content: string; + contentChange: EventEmitter; + + // + mode: XStandardType; + + // + editorBoxColor: XStandardType; + previewBoxColor: XStandardType; + + // + //#region Toolbar ... + hasToolbar: XStandardType; + toolbarCssClass: XStandardType>; + toolbarColor: XStandardType; + toolbarForegroundColor: XStandardType; + toolbarConfig: XStandardType; + //#endregion +} + +@Injectable({ + providedIn: 'root', +}) +export class XMarkdownContainer + extends XComponentCardContainer + implements Readonly +{ + // + //#region Props ... + readonly actionProvider: Subject = + new Subject(); + + readonly cssClass: XStandardType>; + + // + readonly content: string = ''; + readonly contentChange = new EventEmitter(); + + // + readonly mode: XStandardType = XMarkdownMode.BOTH; + + // + readonly editorBoxColor: XStandardType = + XColorWithBrightness.Transparent; + readonly previewBoxColor: XStandardType = + XColorWithBrightness.Transparent; + + // + //#region Toolbar ... + readonly hasToolbar: XStandardType = true; + readonly toolbarCssClass: XStandardType>; + readonly toolbarColor: XStandardType = + XColorWithBrightness.Light; + readonly toolbarForegroundColor: XStandardType = false; + readonly toolbarConfig: XStandardType; + //#endregion + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XMarkdownContainerComponent + extends XComponentCardContainerComponent + implements IXMarkdownContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + content: string = this.propertyProvider.content; + + @Output() + contentChange: EventEmitter = new EventEmitter(); + + // + @Input() + mode: XStandardType = this.propertyProvider.mode; + + // + @Input() + editorBoxColor: XStandardType = + this.propertyProvider.editorBoxColor; + + @Input() + previewBoxColor: XStandardType = + this.propertyProvider.previewBoxColor; + + // + //#region Toolbar ... + @Input() + hasToolbar: XStandardType = this.propertyProvider.hasToolbar; + + @Input() + toolbarCssClass: XStandardType> = + this.propertyProvider.toolbarCssClass; + + @Input() + toolbarColor: XStandardType = + this.propertyProvider.toolbarColor; + + @Input() + toolbarForegroundColor: XStandardType = + this.propertyProvider.toolbarForegroundColor; + + @Input() + toolbarConfig: XStandardType = + this.propertyProvider.toolbarConfig; + //#endregion + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XMarkdownContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXMarkdownContainer + ): Observable { + return super + .extractProps(container ? container : this) + .pipe( + concatMap((superData) => { + return forkJoin({ + // + cssClass: this.getArrayValue( + container ? container.cssClass : this.cssClass + ), + + // + content: this.getValue( + container ? container.content : this.content + ), + + // + mode: this.getValue(container ? container.mode : this.mode), + + // + editorBoxColor: this.getValue( + container ? container.editorBoxColor : this.editorBoxColor + ), + previewBoxColor: this.getValue( + container ? container.previewBoxColor : this.previewBoxColor + ), + + // + hasToolbar: this.getValue( + container ? container.hasToolbar : this.hasToolbar + ), + toolbarCssClass: this.getArrayValue( + container ? container.toolbarCssClass : this.toolbarCssClass + ), + toolbarColor: this.getValue( + container ? container.toolbarColor : this.toolbarColor + ), + toolbarForegroundColor: this.getValue( + container + ? container.toolbarForegroundColor + : this.toolbarForegroundColor + ), + toolbarConfig: this.getValue( + container ? container.toolbarConfig : this.toolbarConfig + ), + }).pipe( + map((data) => { + return { + superData, + data, + }; + }) + ); + }) + ) + .pipe( + map((info) => { + return { + // + ...info.superData, + + // + ...info.data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + contentChange: this.contentChange, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-model-presenter\x-model-presenter.component.html + + + + + + + + +
+ +
+ + +
+ +
+
+
+ + + + +
+ + + + + +
+ + {{ getTitle(key) | async }} + +
+ + + +
+ + + + {{ getContent(key, preparedModel[key]) | async }} + + + + + + + + + + + +
+ + +
+ + +
+ + +
+ +
+
+
+
+
+
+
+ + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-model-presenter\x-model-presenter.component.scss + +:host { + // + --header-highlight-color: var(--x-color-light-shade); + + // + --header-background-color: var(--x-color-light); + --header-foreground-color: var(--x-color-light-contrast); + + // + --content-background-color: var(--x-color-light); + --content-foreground-color: var(--x-color-light-contrast); + + // + --panel-background-color: var(--x-color-light); + --panel-foreground-color: var(--x-color-light-contrast); +} + +:host ::ng-deep .x-card { + .x-actions { + display: none; + } + + .x-content { + padding: 0px !important; + margin: 0px; + } +} + +:host ::ng-deep .x-expandable-panel { + // + &.mobile { + .mat-expansion-panel-header { + margin: { + top: 2px; + bottom: 6px; + } + } + } + + .mat-expansion-panel { + // + box-shadow: unset; + color: var(--panel-foreground-color); + background-color: var(--panel-background-color); + + // + .mat-expansion-panel-body { + color: var(--panel-foreground-color); + background-color: var(--panel-background-color); + } + + // + .mat-expansion-panel-header { + // + color: var(--header-foreground-color); + background-color: var(--header-background-color); + + // + // Header HighLight ... + &.cdk-keyboard-focused, + &.cdk-program-focused, + &:not(.mat-expanded):hover { + background-color: var(--header-highlight-color); + } + + &.mobile { + .mat-content { + flex-direction: column; + } + + .x-model-presenter-title { + .x-title-span { + max-width: calc(80%); + } + } + } + + .x-expandable-panel-header-title { + // + flex-grow: 0; + margin-left: 4px !important; + + // + .x-model-presenter-title { + width: 100%; + word-wrap: unset; + white-space: pre; + } + + // + .x-model-presenter-title.hasLabel { + .x-title-span::after { + content: " : "; + } + } + } + + .x-expandable-panel-header-description { + // + overflow: hidden; + margin-left: 4px !important; + + // + .x-model-presenter-content { + width: 100%; + } + + .x-end-bottom-slot { + flex: 0 0 auto !important; + } + + .x-start-top-slot { + overflow: hidden; + } + + .x-start-top-slot-inner-container { + max-width: calc(90%); + } + } + } + + // + .mat-expansion-panel-content { + color: var(--content-foreground-color); + background-color: var(--content-background-color); + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-model-presenter\x-model-presenter.component.ts + +import { + Component, + QueryList, + ContentChildren, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + tap, + map, + filter, + mergeMap, + switchMap, + concatMap, +} from 'rxjs/operators'; +import { + keys, + nameof, + isDate, + isArray, + toArray, + isNumber, + isString, + isObject, + toXString, + isFunction, + parseXColor, + isSameObject, + XOneOrManyType, + XModelProperty, + toNormalString, + getPropertyValue, + setPropertyValue, + isNullOrUndefined, + isNullOrEmptyString, + getXColorCssVariableName, +} from 'x-framework-core'; +import { + XModelPresenterAction, + XModelPresenterCssVars, + XModelPresenterActionModel, + XModelPresenterStartupState, + XModelPresenterContainerComponent, +} from './x-model-presenter.typings'; +import { + XExpandablePanelAction, + XExpandablePanelActionModel, +} from '../x-expandable-panel/x-expandable-panel.typings'; +import { XSlotLayout, XSlotName } from '../x-slotter/x-slotter.typings'; +import { of, from, Subject, forkJoin, Observable, Subscription } from 'rxjs'; +import { XModelPresenterItemComponent } from './x-model-presenter-item/x-model-presenter-item.component'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-model-presenter', + templateUrl: './x-model-presenter.component.html', + styleUrls: ['./x-model-presenter.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XModelPresenterComponent< + T = any +> extends XModelPresenterContainerComponent { + // + //#region Props ... + private MODEL: T; + private keyPrefix = ''; + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + readonly SlotNames = Object.assign({}, XSlotName); + readonly SlotLayouts = Object.assign({}, XSlotLayout); + + // + get prefix() { + return this.keyPrefix; + } + + get preparedModel() { + return this.MODEL; + } + + // + @ContentChildren(XModelPresenterItemComponent) + items: QueryList; + //#endregion + + // + //#region LifeCycles ... + async onInit() { + super.onInit(); + + // + await this.prepareModel(); + await this.handleStartupState(); + } + + async afterViewInit() { + super.afterViewInit(); + + // + // prepare KeyPrefix ... + const keyPrefix = this.element.nativeElement + ? (this.element.nativeElement as HTMLElement).getAttribute('keyPrefix') + : ''; + if (!isNullOrEmptyString(keyPrefix)) { + this.keyPrefix += `${keyPrefix}`; + } + } + + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + // + //#region Model Changed ... + const isModelChanged = changeKeys.includes( + nameof('model') + ); + + // + if (isModelChanged) { + await this.prepareModel(); + } + //#endregion + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + const isPanelActionProviderChanged = changeKeys.includes( + nameof('panelActionProvider') + ); + if (isActionProviderChanged || isPanelActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + + // + //#region Color Changes ... + // + //#region Color Changed ... + const isColorChanged = changeKeys.includes( + nameof('color') + ); + if (isColorChanged) { + await this.applyPanelColor(); + } + //#endregion + + // + //#region Header Highlight Color Changed ... + const isHeaderHighlightColorChanged = changeKeys.includes( + nameof('headerHighlightColor') + ); + if (isHeaderHighlightColorChanged) { + await this.applyHeaderHighlightColor(); + } + //#endregion + + // + //#region Header Color Changed ... + const isHeaderColorChanged = changeKeys.includes( + nameof('headerColor') + ); + if (isHeaderColorChanged) { + await this.applyHeaderColor(); + } + //#endregion + + // + //#region Content Color Changed ... + const isContentColorChanged = changeKeys.includes( + nameof('contentColor') + ); + if (isContentColorChanged) { + await this.applyContentColor(); + } + //#endregion + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region UI Providers ... + keys(model: T) { + // + let modelKeys = keys(model); + + // + if (!this.hasChild(this.sortOrderModelKeys)) { + return modelKeys; + } + + // + const sortOrderArray = toArray(this.sortOrderModelKeys) as string[]; + const result: string[] = [ + ...sortOrderArray.filter((k) => modelKeys.includes(k)), + ...modelKeys.filter((k) => !sortOrderArray.includes(k)), + ]; + + // + return result; + } + + canShowKey(key: string) { + // + return forkJoin({ + key: this.getValue(key), + model: this.getValue(this.model), + prefix: this.getValue(this.keyPrefix), + hiddenModelKeys: this.getArrayValue(this.hiddenModelKeys), + hideOptionalValues: this.getValue(this.hideOptionalValues), + availableModelKeys: this.getArrayValue(this.availableModelKeys), + }).pipe( + map((provider) => { + // + const value = provider.model[provider.key]; + + // + const modelHasValue = + !isNullOrUndefined(provider.model) && + !isNullOrUndefined(value) && + !isNullOrEmptyString(toXString(provider.model[provider.key])); + const hasChildHiddens = this.hasChild(provider.hiddenModelKeys); + const hasChildAvailables = this.hasChild(provider.availableModelKeys); + + // + if (!hasChildAvailables && !hasChildHiddens) { + // + if (!modelHasValue && provider.hideOptionalValues) { + return false; + } + + // + return true; + } + + // + const isInHiddens = provider.hiddenModelKeys.includes( + provider.key as any + ); + const isInAvailables = provider.availableModelKeys.includes( + provider.key as any + ); + + // + if ( + (!hasChildAvailables && !isInHiddens) || + (isInAvailables && !hasChildHiddens) || + (isInAvailables && !isInHiddens) || + (!isInAvailables && + !isInHiddens && + isNumber(provider.key) && + !isNullOrEmptyString(provider.prefix)) + ) { + // + if (!modelHasValue && provider.hideOptionalValues) { + return false; + } + + // + return true; + } + + // + return false; + }) + ); + } + + canShowLabelKey(key: string) { + return this.getArrayValue(this.hiddenLabelModelKeys).pipe( + map((hiddenLabelsModelKeys) => { + // + const hasChildHiddenLabels = this.hasChild(hiddenLabelsModelKeys); + + // + if (!hasChildHiddenLabels) { + return true; + } + + // + const isInHiddens = hiddenLabelsModelKeys.includes(key as any); + return hasChildHiddenLabels && !isInHiddens; + }) + ); + } + + getTitle(key: string): Observable { + return this.getValue(this.modelKeyProvider).pipe( + concatMap((provider) => { + return isNullOrUndefined(provider) || !isFunction(provider) + ? this.getValue(key) + : this.getValue(provider(key as any)); + }) + ); + } + + hasContentItem(key: string) { + return of(this.items).pipe( + concatMap((items) => { + return isNullOrUndefined(items) + ? of(false) + : this.getArrayValue(items.toArray()).pipe( + switchMap((items) => + from(items).pipe( + mergeMap((item) => + this.getValue(item.name).pipe( + map((name) => ({ name, item })) + ) + ), + filter( + (itemData) => + toNormalString(itemData.name) === toNormalString(key) + ), + map((dataObject) => !isNullOrUndefined(dataObject)) + ) + ) + ); + }) + ); + } + + getContentItem( + key: string, + value: any + ): Observable { + return this.hasContentItem(key).pipe( + concatMap((isExists) => { + return !isExists + ? of(null) + : of(this.items).pipe( + concatMap((items) => { + return isNullOrUndefined(items) + ? of(null) + : this.getArrayValue(items.toArray()).pipe( + switchMap((items) => + from(items).pipe( + mergeMap((item) => + this.getValue(item.name).pipe( + map((name) => ({ name, item })) + ) + ), + filter( + (itemData) => + toNormalString(itemData.name) === + toNormalString(key) + ), + tap((dataObject) => (dataObject.item.value = value)), + map((dataObject) => dataObject.item) + ) + ) + ); + }) + ); + }) + ); + } + + getContent(key: string, value: any): Observable { + return this.getValue(this.modelValueProvider).pipe( + concatMap((provider) => { + return isNullOrUndefined(provider) || !isFunction(provider) + ? this.getValue(value) + : this.getValue(provider(key as any, value)); + }) + ); + } + + canDescribe(key: string, value: any) { + return of(value).pipe( + map( + (eVal) => + isDate(eVal) || + isFunction(eVal) || + (!isArray(eVal) && !isObject(eVal)) + ) + ); + } + + getModelPresenterTitleClasses(key: string) { + return [`x-model-presenter-${key}-title`]; + } + + getModelPresenterContentClasses(key: string) { + return [`x-model-presenter-${key}-content`]; + } + + // + //#region Neste ... + getKeyPrefix(key: string) { + // + const result = isNullOrEmptyString(this.keyPrefix) + ? `${key}` + : `${this.keyPrefix}.${key}`; + + // + return result; + } + + getHiddenModelKeys(key: string) { + return this.prepareNestedCollection(key, this.hiddenModelKeys); + } + + getSortOrderModelKeys(key: string) { + return this.prepareNestedCollection(key, this.sortOrderModelKeys); + } + + getAvailableModelKeys(key: string) { + return this.prepareNestedCollection(key, this.availableModelKeys); + } + + getHiddenLabelModelKeys(key: string) { + return this.prepareNestedCollection(key, this.hiddenLabelModelKeys); + } + //#endregion + //#endregion + + // + //#region UI Handlers ... + //#endregion + + // + //#region Actions ... + async expandAll() { + this.panelActionProvider.next({ + action: XExpandablePanelAction.ExpandAll, + }); + } + + async collapseAll() { + this.panelActionProvider.next({ + action: XExpandablePanelAction.CollapseAll, + }); + } + + async setModelProperty(property: XModelProperty) { + // + if (!property || isNullOrEmptyString(property.key)) { + return; + } + + // + const model = this.MODEL; + const nodeValue = getPropertyValue(model, property.prefix); + const modelKeys = isNullOrUndefined(nodeValue) ? [] : keys(nodeValue); + if (!this.hasChild(modelKeys)) { + return; + } + + // + const isExists = modelKeys.includes(property.key as string); + if (!isExists) { + return; + } + + // + // normalize value ... + const normalValue = isString(property.value) + ? !isNullOrEmptyString(property.value) && + !isNullOrUndefined(property.value) + ? property.value.toString().trim() + : undefined + : isNullOrUndefined(property.value) + ? undefined + : property.value; + + // + if (nodeValue[property.key] === normalValue) { + return; + } + + // + const setterPropertyPath = isNullOrEmptyString(property.prefix) + ? `${property.key as string}` + : `${property.prefix}.${property.key as string}`; + + // + setPropertyValue(model, setterPropertyPath, normalValue); + + // + this.MODEL = { + ...model, + }; + this.detectChanges(); + this.onModelChanged.emit(model); + } + + async getModelProperty(key: keyof T | string) { + // + if (isNullOrEmptyString(key)) { + return null; + } + + // + const model = await this.getValueAsync(this.model); + const modelKeys = await this.getValueAsync(this.keys(model)); + if (!this.hasChild(modelKeys)) { + return null; + } + + // + const isExists = modelKeys.includes(key); + if (!isExists) { + return null; + } + + // + const result = model[key as any]; + return result; + } + + async getModelViewProperty(key: keyof T | string) { + // + if (isNullOrEmptyString(key)) { + return null; + } + + // + const model = await this.getValueAsync(this.model); + const modelKeys = await this.getValueAsync(this.keys(model)); + if (!this.hasChild(modelKeys)) { + return null; + } + + // + const isExists = modelKeys.includes(key); + if (!isExists) { + return null; + } + + // + const result = await this.getValueAsync( + this.getContent(key, model[key as any]) + ); + return result; + } + + async setModel(model: T) { + // + if (!model || isSameObject(model as any, this.MODEL as any)) { + return; + } + + // + this.model = { + ...model, + }; + this.MODEL = { + ...model, + }; + + // + await this.prepareModel(); + this.onModelChanged.emit(model); + } + + async getModel() { + // + const result = await this.getValueAsync(this.model); + return result; + } + //#endregion + + // + //#region Private ... + // + //#region ActionProvider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + if (!this.panelActionProvider) { + this.panelActionProvider = new Subject(); + } + + // + let modelProperty: XModelProperty = null; + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + switch (model.action) { + // + case XModelPresenterAction.Refresh: + this.detectChanges(); + break; + + // + case XModelPresenterAction.ExpandAll: + await this.expandAll(); + break; + + // + case XModelPresenterAction.CollapseAll: + await this.collapseAll(); + break; + + // + case XModelPresenterAction.ClearProperty: + // + modelProperty = model.payload as XModelProperty; + if ( + !model.payload || + isNullOrUndefined(model) || + isNullOrUndefined(model.payload) || + isNullOrEmptyString(model.action) + ) { + return; + } + + // + modelProperty.value = null; + + // + await this.setModelProperty(modelProperty); + break; + + // + case XModelPresenterAction.SetProperty: + // + modelProperty = model.payload as XModelProperty; + if ( + !model.payload || + isNullOrUndefined(model) || + isNullOrUndefined(model.payload) || + isNullOrEmptyString(model.action) + ) { + return; + } + + // + await this.setModelProperty(modelProperty); + break; + + // + case XModelPresenterAction.SetModel: + // + const resetModel = model.payload as T; + if (!model || !model.payload || !resetModel) { + return; + } + + // + await this.setModel(resetModel); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + private async prepareModel() { + // + this.MODEL = null; + this.detectChanges(); + + // + if (!this.model) { + return; + } + + // + this.MODEL = await this.getValueAsync(this.model); + this.detectChanges(); + } + + private prepareNestedCollection( + key: string, + collection: XOneOrManyType + ) { + // + return forkJoin({ + key: this.getValue(key), + prefix: this.getValue(this.getKeyPrefix(key)), + model: this.getValue(this.preparedModel[key]), + collection: this.getArrayValue(collection), + }).pipe( + map((provider) => { + // + const allAvailableCollectionItems = provider.collection; + + // + const keySelector = isNullOrUndefined(provider.prefix) + ? `${provider.key}.` + : `${provider.prefix}.`; + + // + const canFilter = + !isNumber(provider.key) && + !provider.prefix.includes(`${provider.key}.`); + const result = canFilter + ? allAvailableCollectionItems + .filter((k) => k.includes(`${provider.key}.`)) + .map((k) => k.replace(`${provider.key}.`, '')) + : allAvailableCollectionItems; + + // + return result; + }) + ); + } + + private async handleStartupState() { + // + const state = await this.getValueAsync(this.startupState); + if (isNullOrEmptyString(state)) { + return; + } + + // + switch (state) { + // + case XModelPresenterStartupState.CollapseAll: + await this.collapseAll(); + break; + + // + case XModelPresenterStartupState.ExpandAll: + await this.expandAll(); + break; + } + } + + // + //#region Color Appliers ... + private async applyPanelColor() { + // + const color = await this.getValueAsync(this.color); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const fgColor = getXColorCssVariableName( + colorDescriptor.name, + true, + colorDescriptor.brightnessLevel + ); + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(fgColor) || isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar( + XModelPresenterCssVars.PanelForegroundColor, + `var(${fgColor})` + ); + this.setCssVar( + XModelPresenterCssVars.PanelBackgroundColor, + `var(${bgColor})` + ); + + // + this.detectChanges(); + } + + private async applyHeaderColor() { + // + const color = await this.getValueAsync(this.headerColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const fgColor = getXColorCssVariableName( + colorDescriptor.name, + true, + colorDescriptor.brightnessLevel + ); + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(fgColor) || isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar( + XModelPresenterCssVars.HeaderForegroundColor, + `var(${fgColor})` + ); + this.setCssVar( + XModelPresenterCssVars.HeaderBackgroundColor, + `var(${bgColor})` + ); + + // + this.detectChanges(); + } + + private async applyHeaderHighlightColor() { + // + const color = await this.getValueAsync(this.headerHighlightColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar( + XModelPresenterCssVars.HeaderHighlighColor, + `var(${bgColor})` + ); + + // + this.detectChanges(); + } + + private async applyContentColor() { + // + const color = await this.getValueAsync(this.contentColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const fgColor = getXColorCssVariableName( + colorDescriptor.name, + true, + colorDescriptor.brightnessLevel + ); + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(fgColor) || isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar( + XModelPresenterCssVars.ContentForegroundColor, + `var(${fgColor})` + ); + this.setCssVar( + XModelPresenterCssVars.ContentBackgroundColor, + `var(${bgColor})` + ); + + // + this.detectChanges(); + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-model-presenter\x-model-presenter.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XCardModule } from '../x-card/x-card.module'; +import { XModelPresenterComponent } from './x-model-presenter.component'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; +import { XExpandablePanelModule } from '../x-expandable-panel/x-expandable-panel.module'; +import { XModelPresenterItemComponent } from './x-model-presenter-item/x-model-presenter-item.component'; + +@NgModule({ + declarations: [XModelPresenterComponent, XModelPresenterItemComponent], + imports: [ + XCardModule, + CommonModule, + XBaseComponentModule, + XExpandablePanelModule, + ], + exports: [ + XCardModule, + CommonModule, + XBaseComponentModule, + XExpandablePanelModule, + XModelPresenterComponent, + XModelPresenterItemComponent, + ], +}) +export class XModelPresenterModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-model-presenter\x-model-presenter.typings.ts + +import { + Input, + Output, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + TemplateRef, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XStandardType, + XOneOrManyType, + XModelFieldsType, + XColorIdentifier, + XModelKeyProvider, + XModelValueProvider, + XColorWithBrightness, +} from 'x-framework-core'; +import { + XComponentCardContainer, + IXComponentCardContainer, + XComponentCardContainerComponent, +} from '../x-card/x-card.typings'; +import { concatMap, map } from 'rxjs/operators'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; +import { XExpandablePanelActionModel } from '../x-expandable-panel/x-expandable-panel.typings'; + +export enum XModelPresenterStartupState { + ExpandAll = 'expand-all', + CollapseAll = 'collapse-all', +} + +export type XModelPresenterStartupStateIdentifier = + | XModelPresenterStartupState + | string; + +export enum XModelPresenterAction { + // + ExpandAll = 'expand-all', + CollapseAll = 'collapse-all', + + // + ClearProperty = 'clear_property', + SetProperty = 'set_property', + + // + SetModel = 'set_model', + + // + Refresh = 'refresh', +} + +export type XModelPresenterActionIdentifier = XModelPresenterAction | string; + +export interface XModelPresenterActionModel + extends XBaseActionModel {} + +export enum XModelPresenterCssVars { + // + HeaderHighlighColor = 'header-highlight-color', + + // + HeaderForegroundColor = 'header-foreground-color', + HeaderBackgroundColor = 'header-background-color', + + // + ContentForegroundColor = 'content-foreground-color', + ContentBackgroundColor = 'content-background-color', + + // + PanelForegroundColor = 'panel-foreground-color', + PanelBackgroundColor = 'panel-background-color', +} + +export type XModelPresenterCssVarsIdentifier = XModelPresenterCssVars | string; + +// +//#region Inside Card Container ... +export interface IXModelPresenterContainer + extends IXComponentCardContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + color: XStandardType; + foregroundColor: XStandardType; + + // + headerColor: XStandardType; + headerHighlightColor: XStandardType; + contentColor: XStandardType; + labelColor: XStandardType; + valueColor: XStandardType; + + // + showLabels: XStandardType; + propertyActionTemplate: TemplateRef; + startupState: XStandardType; + + // + panelCssClass: XStandardType>; + panelActionProvider: Subject; + + // + model: XStandardType; + + // + hideOptionalValues: XStandardType; + availableModelKeys: XModelFieldsType; + hiddenModelKeys: XModelFieldsType; + hiddenLabelModelKeys: XModelFieldsType; + sortOrderModelKeys: XModelFieldsType; + modelKeyProvider: XModelKeyProvider; + modelValueProvider: XModelValueProvider; + + // + onModelChanged: EventEmitter; +} + +@Injectable({ + providedIn: 'root', +}) +export class XModelPresenterContainer + extends XComponentCardContainer + implements Readonly> +{ + // + //#region Props ... + // + readonly actionProvider: Subject = + new Subject(); + readonly cssClass: XStandardType>; + + // + readonly color: XStandardType = XColorWithBrightness.Light; + readonly foregroundColor: XStandardType = false; + + // + readonly headerColor: XStandardType = + XColorWithBrightness.Light; + readonly headerHighlightColor: XStandardType = + XColorWithBrightness.LightShade; + readonly contentColor: XStandardType = + XColorWithBrightness.Dark; + readonly labelColor: XStandardType = + XColorWithBrightness.TertiaryShade; + readonly valueColor: XStandardType = + XColorWithBrightness.Dark; + + // + readonly showLabels: XStandardType = true; + readonly propertyActionTemplate: TemplateRef; + readonly startupState: XStandardType = + XModelPresenterStartupState.CollapseAll; + + // + readonly panelCssClass: XStandardType>; + readonly panelActionProvider: Subject = + new Subject(); + + // + readonly model: XStandardType; + + // + readonly hideOptionalValues: XStandardType = false; + readonly availableModelKeys: XModelFieldsType; + readonly hiddenModelKeys: XModelFieldsType; + readonly hiddenLabelModelKeys: XModelFieldsType; + readonly sortOrderModelKeys: XModelFieldsType; + readonly modelKeyProvider: XModelKeyProvider; + readonly modelValueProvider: XModelValueProvider; + + // + readonly onModelChanged: EventEmitter = new EventEmitter(); + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XModelPresenterContainerComponent + extends XComponentCardContainerComponent + implements IXModelPresenterContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + color: XStandardType = this.propertyProvider.color; + + @Input() + foregroundColor: XStandardType = + this.propertyProvider.foregroundColor; + + // + @Input() + headerColor: XStandardType = + this.propertyProvider.headerColor; + + @Input() + headerHighlightColor: XStandardType = + this.propertyProvider.headerHighlightColor; + + @Input() + contentColor: XStandardType = + this.propertyProvider.contentColor; + + @Input() + labelColor: XStandardType = + this.propertyProvider.labelColor; + + @Input() + valueColor: XStandardType = + this.propertyProvider.valueColor; + + // + @Input() + showLabels: XStandardType = this.propertyProvider.showLabels; + + @Input() + propertyActionTemplate: TemplateRef = + this.propertyProvider.propertyActionTemplate; + + @Input() + startupState: XStandardType = + this.propertyProvider.startupState; + + // + @Input() + panelCssClass: XStandardType> = + this.propertyProvider.panelCssClass; + + @Input() + panelActionProvider: Subject = + this.propertyProvider.panelActionProvider; + + // + @Input() + model: XStandardType = this.propertyProvider.model; + + // + @Input() + hideOptionalValues: XStandardType = + this.propertyProvider.hideOptionalValues; + + @Input() + availableModelKeys: XModelFieldsType = + this.propertyProvider.availableModelKeys; + + @Input() + hiddenModelKeys: XModelFieldsType = this.propertyProvider.hiddenModelKeys; + + @Input() + hiddenLabelModelKeys: XModelFieldsType = + this.propertyProvider.hiddenLabelModelKeys; + + @Input() + sortOrderModelKeys: XModelFieldsType = + this.propertyProvider.sortOrderModelKeys; + + @Input() + modelKeyProvider: XModelKeyProvider = + this.propertyProvider.modelKeyProvider; + + @Input() + modelValueProvider: XModelValueProvider = + this.propertyProvider.modelValueProvider; + + // + @Output() + onModelChanged: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XModelPresenterContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXModelPresenterContainer + ): Observable { + return super + .extractProps(container ? container : this) + .pipe( + concatMap((superData) => { + return forkJoin({ + // + cssClass: this.getValue( + container ? container.cssClass : this.cssClass + ), + color: this.getValue(container ? container.color : this.color), + foregroundColor: this.getValue( + container ? container.foregroundColor : this.foregroundColor + ), + headerColor: this.getValue( + container ? container.headerColor : this.headerColor + ), + headerHighlightColor: this.getValue( + container + ? container.headerHighlightColor + : this.headerHighlightColor + ), + contentColor: this.getValue( + container ? container.contentColor : this.contentColor + ), + labelColor: this.getValue( + container ? container.labelColor : this.labelColor + ), + valueColor: this.getValue( + container ? container.valueColor : this.valueColor + ), + showLabels: this.getValue( + container ? container.showLabels : this.showLabels + ), + propertyActionTemplate: this.getValue( + container + ? container.propertyActionTemplate + : this.propertyActionTemplate + ), + startupState: this.getValue( + container ? container.startupState : this.startupState + ), + panelCssClass: this.getValue( + container ? container.panelCssClass : this.panelCssClass + ), + model: this.getValue(container ? container.model : this.model), + hideOptionalValues: this.getValue( + container ? container.hideOptionalValues : this.hideOptionalValues + ), + availableModelKeys: this.getValue( + container ? container.availableModelKeys : this.availableModelKeys + ), + hiddenModelKeys: this.getValue( + container ? container.hiddenModelKeys : this.hiddenModelKeys + ), + hiddenLabelModelKeys: this.getValue( + container + ? container.hiddenLabelModelKeys + : this.hiddenLabelModelKeys + ), + sortOrderModelKeys: this.getValue( + container ? container.sortOrderModelKeys : this.sortOrderModelKeys + ), + modelKeyProvider: this.getValue( + container ? container.modelKeyProvider : this.modelKeyProvider + ), + modelValueProvider: this.getValue( + container ? container.modelValueProvider : this.modelValueProvider + ), + }).pipe( + map((data) => { + return { + superData, + data, + }; + }) + ); + }) + ) + .pipe( + map((info) => { + return { + // + ...info.superData, + + // + ...info.data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + panelActionProvider: container + ? container.panelActionProvider + : this.panelActionProvider, + + // + onModelChanged: this.onModelChanged, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-model-presenter\x-model-presenter-item\x-model-presenter-item.component.html + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-model-presenter\x-model-presenter-item\x-model-presenter-item.component.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-model-presenter\x-model-presenter-item\x-model-presenter-item.component.ts + +import { + Input, + Inject, + NgZone, + Renderer2, + Component, + ViewChild, + ElementRef, + TemplateRef, + ChangeDetectorRef, +} from '@angular/core'; +import { + XModelPresenterItemAction, + XModelPresenterItemModel, +} from './x-model-presenter-item.typings'; +import { Subject, Subscription } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XBaseComponent } from '../../classes/x-base-component'; +import { XStandardType, XOneOrManyType, nameof } from 'x-framework-core'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from './../../config/x-framework-components.config'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-model-presenter-item', + templateUrl: './x-model-presenter-item.component.html', + styleUrls: ['./x-model-presenter-item.component.scss'], + // changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XModelPresenterItemComponent extends XBaseComponent { + // + //#region Props ... + // + @ViewChild('content', { static: false }) + content: TemplateRef; + + // + @Input() + name: XStandardType; + + @Input() + value: XStandardType; + + // + @Input() + cssClass: XStandardType>; + + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + @Input() + actionProvider = new Subject(); + //#endregion + + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region LifeCycles ... + onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + //#endregion + + // + //#region Private ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + // Handle Action Provider ... + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XModelPresenterItemAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-model-presenter\x-model-presenter-item\x-model-presenter-item.typings.ts + +import { XBaseActionModel } from '../../classes/x-base-action.model'; + +export enum XModelPresenterItemAction { + Refresh = 'refresh', +} +export type XModelPresenterItemActionIdentifier = XModelPresenterItemAction | string; + +export interface XModelPresenterItemModel + extends XBaseActionModel {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-page\x-page.component.html + + +
+ + + + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-page\x-page.component.scss + +:host { + height: 100%; + + --side-menu-width: 200px; + --side-menu-min-width: 100px; + --side-menu-max-width: 200px; + + --side-menu-content-height-gutter: 0px; + --side-menu-content-offset-top: 0px; + --side-menu-content-offset-bottom: 0px; + + --content-height-gutter: 0px; + --content-offset-top: 0px; + --content-offset-bottom: 0px; + + ion-refresher { + padding-top: 90px; + } + + ion-content { + // + height: calc(100% - var(--content-height-gutter)); + overflow: auto; + + // + --offset-top: var(--content-offset-top) !important; + --offset-bottom: var(--content-offset-bottom) !important; + + // + --padding-top: 0; + --padding-end: 0; + --padding-start: 0; + --padding-bottom: 0; + } +} + +:host::ng-deep { + ion-header, + ion-footer { + z-index: 10000 !important; + } +} + +.x-menu { + // + --width: var(--side-menu-width) !important; + --min-width: var(--side-menu-min-width) !important; + --max-width: var(--side-menu-max-width) !important; + + // + ion-content { + // + height: calc(100% - var(--side-menu-content-height-gutter)) !important; + overflow: auto !important; + + // + --offset-top: var(--side-menu-content-offset-top) !important; + --offset-bottom: var(--side-menu-content-offset-bottom) !important; + + // + --padding-top: 0; + --padding-end: 0; + --padding-start: 0; + --padding-bottom: 0; + } +} + +.split-pane-visible > .split-pane-side { + border: 0 !important; + width: var(--side-menu-width) !important; + min-width: var(--side-menu-min-width) !important; + max-width: var(--side-menu-max-width) !important; +} + +.x-footer-offset, +.x-toolbar-offset { + visibility: hidden; +} + +.x-page-side-wrapper { + touch-action: pan-y; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-page\x-page.component.ts + +import { + XMenuState, + XMenuAction, + XPageCSSVars, + XPageLoadingEvent, +} from './x-page.typings'; +import { map } from 'rxjs/operators'; +import { IonContent } from '@ionic/angular'; +import { XFabItem } from '../x-fab/x-fab.typings'; +import { isNullOrUndefined, nameof, XBehaviourEvent } from 'x-framework-core'; +import { XPageContainerComponent } from './x-page.container'; +import { XFooterComponent } from './x-footer/x-footer.component'; +import { XToolbarComponent } from './x-toolbar/x-toolbar.component'; +import { Component, ViewChild, ChangeDetectionStrategy } from '@angular/core'; +import { XToolbarAction } from './x-toolbar/x-toolbar.typings'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-page', + templateUrl: './x-page.component.html', + styleUrls: ['./x-page.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XPageComponent extends XPageContainerComponent { + // + //#region Props ... + // + readonly MenuStates = Object.assign({}, XMenuState); + + // + scrollYOffset = 0; + scrollXOffset = 0; + + // + //#region View Childs ... + // + //#region Toolbar ... + private TOOLBAR: XToolbarComponent; + + @ViewChild('toolbar', { static: false }) + set toolbar(v: XToolbarComponent) { + // + if (!isNullOrUndefined(v)) { + // + this.TOOLBAR = v; + this.applyContentSize(); + } + } + + get toolbar() { + return this.TOOLBAR; + } + //#endregion + + // + //#region Footer ... + private FOOTER: XFooterComponent; + + @ViewChild('footer', { static: false }) + set footer(v: XFooterComponent) { + // + if (!isNullOrUndefined(v)) { + // + this.FOOTER = v; + this.applyContentSize(); + } + } + + get footer() { + return this.FOOTER; + } + //#endregion + //#endregion + + // + //#region Side Props ... + // + private IS_SIDE_OPENED = false; + + get isSideOpened() { + return this.IS_SIDE_OPENED; + } + + private CAN_CLOSE_MENU = true; + + get canCloseMenu() { + return this.CAN_CLOSE_MENU; + } + //#endregion + //#endregion + + // + //#region LifeCycle ... + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region CssVars ... + // + //#region SideMenuWidth ... + const isSideMenuWidthChanged = changeKeys.includes( + nameof('sideMenuWidth') + ); + const isSideMenuMinWidthChanged = changeKeys.includes( + nameof('sideMenuMinWidth') + ); + const isSideMenuMaxWidthChanged = changeKeys.includes( + nameof('sideMenuMaxWidth') + ); + if ( + isSideMenuWidthChanged || + isSideMenuMinWidthChanged || + isSideMenuMaxWidthChanged + ) { + await this.applySideMenuWidth(); + } + //#endregion + //#endregion + } + + async afterViewInit() { + super.afterViewInit(); + + // + this.handleMenuStartState(); + await this.applyContentSize(); + } + //#endregion + + // + //#region Register Handlers ... + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerResizeHandler(); + this.registerMenuActionProvider(); + this.registerToolbarActionProvider(); + } + //#endregion + + // + //#region UI Providers ... + //#endregion + + // + //#region UI Handlers ... + async handleChangeSplitPane(event: CustomEvent | any) { + // + const canUseMenu = await this.canUseMenu(); + if (!canUseMenu) { + return; + } + + // + const isSideOpened = event.detail.visible; + this.IS_SIDE_OPENED = isSideOpened; + this.splitterStateChanged.emit(isSideOpened); + + // + if (isSideOpened) { + // + this.CAN_CLOSE_MENU = false; + this.menuState = XMenuState.Opened; + await this.openMenu(); + } else { + // + this.CAN_CLOSE_MENU = true; + this.menuState = XMenuState.Closed; + await this.closeMenu(); + } + } + + async handleMenuStateChanged(state: XMenuState) { + // + const canUseMenu = await this.canUseMenu(); + if (!canUseMenu) { + return; + } + + // + this.menuState = state; + + // + this.menuStateChange.emit(state); + } + + async handleRefresh(event: any) { + // + const loaderEvent: XPageLoadingEvent = { + type: 'RefreshLoader', + event, + }; + + // + this.refreshPage.emit(loaderEvent); + } + + async handleLoadMore(event: any) { + // + const loaderEvent: XPageLoadingEvent = { + type: 'InfinityScrollerLoader', + event, + }; + + // + this.loadMore.emit(loaderEvent); + } + + async cancelLoading(event: any) { + // + if (!event) { + return; + } + + // + event.target.complete(); + } + + async handleScrollStarted(event: XBehaviourEvent) { + // + this.scrolledEvent.emit(event); + + // + this.closeActions(); + } + + async handleScrollingEvent(event: XBehaviourEvent) { + // + this.scrolledEvent.emit(event); + + // + this.scrollYOffset = event.event.detail.scrollTop as number; + this.scrollXOffset = event.event.detail.scrollLeft as number; + + // + await this.applyCaptureOffsets(); + } + + isMenuOpen() { + // + return this.getValue(this.menuState).pipe( + map((menuState) => { + return ( + menuState === XMenuState.WillOpen || menuState === XMenuState.Opened + ); + }) + ); + } + //#endregion + + // + //#region Actions ... + async openMenu() { + // + const canUseMenu = await this.canUseMenu(); + if (!canUseMenu || this.IS_SIDE_OPENED) { + return; + } + + // + this.menuController.open(); + } + + async closeMenu() { + // + const canUseMenu = await this.canUseMenu(); + if (!canUseMenu || !this.CAN_CLOSE_MENU) { + return; + } + + // + this.menuController.close(); + } + + async toggleMenu() { + // + const canUseMenu = await this.canUseMenu(); + if (!canUseMenu) { + return; + } + + // + if (this.isMenuOpen()) { + await this.closeMenu(); + } else { + await this.openMenu(); + } + } + + async scrollToTop(duration?: number) { + // + await this.waitForElement('ion-content[id="x-menu-wrapper"]').then( + (el: any) => { + el.scrollToTop(duration); + } + ); + } + + async scrollToBottom(duration?: number) { + // + await this.waitForElement('ion-content[id="x-menu-wrapper"]').then( + (el: any) => { + el.scrollToBottom(duration); + } + ); + } + + async scrollByPoint(x?: number, y?: number, duration?: number) { + // + await this.waitForElement('ion-content[id="x-menu-wrapper"]').then( + (el: any) => { + el.scrollByPoint(x, y, duration); + } + ); + } + + async scrollToPoint(x?: number, y?: number, duration?: number) { + // + await this.waitForElement('ion-content[id="x-menu-wrapper"]').then( + (el: any) => { + el.scrollToPoint(x, y, duration); + } + ); + } + + closeActions(delay: number = 1000) { + // + if (!this.actions) { + return; + } + + // + setTimeout(() => { + // + if (!this.actions) { + return; + } + + // + this.actions = { + ...this.actions, + activated: false, + } as XFabItem; + + // + this.detectChanges(); + }, delay); + } + + openActions(delay: number = 1000) { + // + if (!this.actions) { + return; + } + + // + setTimeout(() => { + // + if (!this.actions) { + return; + } + + // + this.actions = { + ...this.actions, + activated: true, + } as XFabItem; + + // + this.detectChanges(); + }, delay); + } + //#endregion + + // + //#region Private ... + private async canUseMenu() { + // + if (!this.toolbarHasMenu || !this.toolbarShowMenu) { + return false; + } + + // + const hasMenu = await this.toPromise(this.getValue(this.toolbarHasMenu)); + const showMenu = await this.toPromise(this.getValue(this.toolbarShowMenu)); + + // + if (!hasMenu || !showMenu) { + return false; + } + + // + return true; + } + + private async handleMenuStartState() { + // + const canUseMenu = await this.canUseMenu(); + if (!canUseMenu || this.isSideOpened) { + return; + } + + // + switch (this.menuState) { + // + case XMenuState.WillOpen: + case XMenuState.Opened: + await this.openMenu(); + break; + + // + case XMenuState.WillClose: + case XMenuState.Closed: + await this.closeMenu(); + break; + } + } + + private registerMenuActionProvider() { + // + this.applyTakeUntilWrapper( + this.menuActionProvider.asObservable() + ).subscribe((action) => { + // + switch (action) { + case XMenuAction.Open: + this.openMenu(); + break; + + case XMenuAction.Close: + this.closeMenu(); + break; + + case XMenuAction.Toggle: + this.toggleMenu(); + break; + } + }); + } + + private registerToolbarActionProvider() { + // + if (isNullOrUndefined(this.toolbarActionProvider)) { + return; + } + + // + this.applyTakeUntilWrapper( + this.toolbarActionProvider.asObservable() + ).subscribe(async (event) => { + // + if (isNullOrUndefined(event)) { + return; + } + + // + if (event.action === XToolbarAction.ToggleMenu) { + await this.toggleMenu(); + } + }); + } + + private async registerResizeHandler() { + // + this.applyTakeUntilWrapper(this.ruler.change()).subscribe(async () => { + await this.applyContentSize(); + }); + } + + private async getToolbarHeight() { + // + if (!this.toolbar) { + return 0; + } + + // + const toolbarElm = this.toolbar.getElement(); + + // + if (!toolbarElm) { + return 0; + } + + // + return toolbarElm.getBoundingClientRect().height; + } + + private async getFooterHeight() { + // + if (!this.footer) { + return 0; + } + + // + const footerElm = this.footer.getElement(); + + // + if (!footerElm) { + return 0; + } + + // + return footerElm.getBoundingClientRect().height; + } + + private async applyContentSize() { + // + const showToolbar = await this.getValueAsync(this.showToolbar); + const toolbarHeight = await this.getToolbarHeight(); + + // + const showFooter = await this.getValueAsync(this.showFooter); + const footerHeight = await this.getFooterHeight(); + + // + if ( + (showToolbar && toolbarHeight === 0) || + (showFooter && footerHeight === 0) + ) { + // + setTimeout(() => this.applyContentSize(), 100); + return; + } + + // + const topValue = showToolbar ? `-${toolbarHeight}px` : '0px'; + const bottomValue = showFooter ? `-${footerHeight}px` : '0px'; + + // + this.setCssVar(XPageCSSVars.ContentOffsetTop, topValue); + this.setCssVar(XPageCSSVars.SideMenuContentOffsetTop, topValue); + + // + this.setCssVar(XPageCSSVars.ContentOffsetBottom, bottomValue); + this.setCssVar(XPageCSSVars.SideMenuContentOffsetBottom, bottomValue); + + // + await this.applyCaptureOffsets(); + + // + this.detectChanges(); + } + + private async applySideMenuWidth() { + // + const sideMenuWidth = (await this.getValueAsync(this.sideMenuWidth)) || 200; + const sideMenuMinWidth = + (await this.getValueAsync(this.sideMenuMinWidth)) || 100; + const sideMenuMaxWidth = + (await this.getValueAsync(this.sideMenuMaxWidth)) || 200; + + // + this.setCssVar(XPageCSSVars.SideMenuWidth, `${sideMenuWidth}px`); + this.setCssVar(XPageCSSVars.SideMenuMinWidth, `${sideMenuMinWidth}px`); + this.setCssVar(XPageCSSVars.SideMenuMaxWidth, `${sideMenuMaxWidth}px`); + + // + this.detectChanges(); + } + + private async applyCaptureOffsets() { + // + const showToolbar = await this.getValueAsync(this.showToolbar); + const toolbarHeight = await this.getToolbarHeight(); + + // + const showFooter = await this.getValueAsync(this.showFooter); + const footerHeight = await this.getFooterHeight(); + + // + if ( + (showToolbar && toolbarHeight === 0) || + (showFooter && footerHeight === 0) + ) { + // + setTimeout(() => this.applyCaptureOffsets(), 100); + return; + } + + // + this.captureTopOffset = -1 * this.scrollYOffset + -1 * toolbarHeight; + + // + this.detectChanges(); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-page\x-page.container.ts + +import { + Input, + NgZone, + Output, + Inject, + Component, + Renderer2, + ElementRef, + Injectable, + TemplateRef, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XSpinner, + XStandardType, + XOneOrManyType, + XBehaviourEvent, + XColorIdentifier, + XSpinnerIdentifier, + XBehaviourEventType, + XColorWithBrightness, +} from 'x-framework-core'; +import { + XPageSize, + XSideSlot, + XSideType, + XMenuState, + XMenuAction, + XPageLoadingEvent, + XMenuSlotIdentifier, + XLogoSlotIdentifier, + XPageSizeIdentifier, + XSideSlotIdentifier, + XSideTypeIdentifier, + XBackSlotIdentifier, + XTitleSlotIdentifier, + XContentSlotIdentifier, +} from './x-page.typings'; +import { forkJoin } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { + XToolbarFooterContainer, + IXToolbarFooterContainer, +} from '../classes/i-x-toolbar-footer'; +import { Observable, Subject } from 'rxjs'; +import { MenuController } from '@ionic/angular'; +import { XBasePage } from '../classes/x-base-page'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XIconIdentifier } from '../x-icon/x-icon.typings'; +import { IXBaseContainer } from '../classes/i-x-base.container'; +import { XFooterActionModel } from './x-footer/x-footer.typings'; +import { XFabActionModel, XFabItem } from '../x-fab/x-fab.typings'; +import { XToolbarActionModel } from './x-toolbar/x-toolbar.typings'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; + +// +//#region Container ... +export interface IXPageContainer + extends IXBaseContainer, + IXToolbarFooterContainer { + // + cssClass: XStandardType>; + + // + color: XStandardType; + + // + enableCaptureMode: XStandardType; + downloadCapturedImage: XStandardType; + captureTopOffset: XStandardType; + captureBottomOffset: XStandardType; + imageCaptured: EventEmitter; + + // + provideScrollEvents: XStandardType; + scrollStartClasses: XOneOrManyType; + scrollEndClasses: XOneOrManyType; + scrollingClasses: XOneOrManyType; + scrollBehaviour: XOneOrManyType; + scrollEventActionProvider: Subject; + scrolledEvent: EventEmitter; + + // + toggleMenuWhen: XStandardType; + hasSide: XStandardType; + sideSlot: XStandardType; + sideType: XStandardType; + sideColor: XStandardType; + sideMenuWidth: XStandardType; + sideMenuMinWidth: XStandardType; + sideMenuMaxWidth: XStandardType; + menuState: XStandardType; + menuActionProvider: Subject; + splitterStateChanged: EventEmitter; + menuStateChange: EventEmitter; + + // + showPageRefresher: XStandardType; + refreshPage: EventEmitter; + refreshingText: XStandardType; + refreshPullingText: XStandardType; + refreshPullingIcon: XStandardType; + refreshingSpinner: XStandardType; + + // + showPageInfinityScroller: XStandardType; + infinityScrollDisabled: XStandardType; + infinityScrollThreshold: XStandardType; + infinityScrollerLoadingText: XStandardType; + infinityScrollerLoadingSpinner: XStandardType; + loadMore: EventEmitter; + + // + actions: XStandardType; + actionsActionProvider: Subject; +} + +@Injectable({ + providedIn: 'root', +}) +export class XPageContainer + extends XToolbarFooterContainer + implements Readonly +{ + // + readonly cssClass: XStandardType>; + + // + readonly color: XStandardType = + XColorWithBrightness.LightShade; + + // + readonly enableCaptureMode: XStandardType = false; + readonly downloadCapturedImage: XStandardType = true; + readonly captureTopOffset: XStandardType = 0; + readonly captureBottomOffset: XStandardType = 0; + readonly imageCaptured: EventEmitter = new EventEmitter< + string | Blob + >(); + + // + readonly provideScrollEvents: XStandardType; + readonly scrollStartClasses: XOneOrManyType; + readonly scrollEndClasses: XOneOrManyType; + readonly scrollingClasses: XOneOrManyType; + readonly scrollBehaviour: XOneOrManyType; + readonly scrollEventActionProvider: Subject = + new Subject(); + readonly scrolledEvent: EventEmitter = + new EventEmitter(); + + // + readonly toggleMenuWhen: XStandardType = + XPageSize.Medium; + readonly hasSide: XStandardType = true; + readonly sideSlot: XStandardType = XSideSlot.Start; + readonly sideType: XStandardType = XSideType.Overlay; + readonly sideColor: XStandardType = + XColorWithBrightness.PrimaryShade; + readonly sideMenuWidth: XStandardType = 200; + readonly sideMenuMinWidth: XStandardType = 100; + readonly sideMenuMaxWidth: XStandardType = 200; + readonly menuState: XStandardType = XMenuState.Opened; + readonly menuActionProvider = new Subject(); + readonly splitterStateChanged = new EventEmitter(); + readonly menuStateChange = new EventEmitter(); + + // + readonly showPageRefresher: XStandardType = false; + readonly refreshPage: EventEmitter = + new EventEmitter(); + readonly refreshingText: XStandardType; + readonly refreshPullingText: XStandardType; + readonly refreshPullingIcon: XStandardType; + readonly refreshingSpinner: XStandardType = + XSpinner.Circular; + + // + readonly showPageInfinityScroller: XStandardType = false; + readonly infinityScrollDisabled: XStandardType = false; + readonly infinityScrollThreshold: XStandardType = '200px'; + readonly infinityScrollerLoadingText: XStandardType; + readonly infinityScrollerLoadingSpinner: XStandardType = + XSpinner.Circular; + readonly loadMore: EventEmitter = + new EventEmitter(); + + // + readonly actions: XStandardType; + readonly actionsActionProvider: Subject = + new Subject(); +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XPageContainerComponent + extends XBasePage + implements IXPageContainer +{ + // + //#region Props ... + // + //#region Page ... + // + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + color: XStandardType = this.propertyProvider.color; + + // + @Input() + enableCaptureMode: XStandardType = + this.propertyProvider.enableCaptureMode; + + @Input() + downloadCapturedImage: XStandardType = + this.propertyProvider.downloadCapturedImage; + + @Input() + captureTopOffset: XStandardType = + this.propertyProvider.captureTopOffset; + + @Input() + captureBottomOffset: XStandardType = + this.propertyProvider.captureBottomOffset; + + @Output() + imageCaptured: EventEmitter = new EventEmitter(); + + // + @Input() + provideScrollEvents: XStandardType = + this.propertyProvider.provideScrollEvents; + + @Input() + scrollStartClasses: XOneOrManyType = + this.propertyProvider.scrollStartClasses; + + @Input() + scrollEndClasses: XOneOrManyType = + this.propertyProvider.scrollEndClasses; + + @Input() + scrollingClasses: XOneOrManyType = + this.propertyProvider.scrollingClasses; + + @Input() + scrollBehaviour: XOneOrManyType = + this.propertyProvider.scrollBehaviour; + + @Input() + scrollEventActionProvider: Subject = + this.propertyProvider.scrollEventActionProvider; + + @Output() + scrolledEvent: EventEmitter = new EventEmitter(); + + // + @Input() + toggleMenuWhen: XStandardType = + this.propertyProvider.toggleMenuWhen; + + @Input() + hasSide: XStandardType = this.propertyProvider.hasSide; + + @Input() + sideSlot: XStandardType = this.propertyProvider.sideSlot; + + @Input() + sideType: XStandardType = this.propertyProvider.sideType; + + @Input() + sideColor: XStandardType = this.propertyProvider.sideColor; + + @Input() + sideMenuWidth: XStandardType = this.propertyProvider.sideMenuWidth; + + @Input() + sideMenuMinWidth: XStandardType = + this.propertyProvider.sideMenuMinWidth; + + @Input() + sideMenuMaxWidth: XStandardType = + this.propertyProvider.sideMenuMaxWidth; + + @Input() + menuState: XStandardType = this.propertyProvider.menuState; + + @Input() + menuActionProvider: Subject = + this.propertyProvider.menuActionProvider; + + @Output() + splitterStateChanged: EventEmitter = new EventEmitter(); + + @Output() + menuStateChange: EventEmitter = new EventEmitter(); + + // + @Input() + showPageRefresher: XStandardType = + this.propertyProvider.showPageRefresher; + + @Output() + refreshPage: EventEmitter = new EventEmitter(); + + @Input() + refreshingText: XStandardType = this.propertyProvider.refreshingText; + + @Input() + refreshPullingText: XStandardType = + this.propertyProvider.refreshPullingText; + + @Input() + refreshPullingIcon: XStandardType = + this.propertyProvider.refreshPullingIcon; + + @Input() + refreshingSpinner: XStandardType = + this.propertyProvider.refreshingSpinner; + + // + @Input() + showPageInfinityScroller: XStandardType = + this.propertyProvider.showPageInfinityScroller; + + @Input() + infinityScrollDisabled: XStandardType = + this.propertyProvider.infinityScrollDisabled; + + @Input() + infinityScrollThreshold: XStandardType = + this.propertyProvider.infinityScrollThreshold; + + @Input() + infinityScrollerLoadingText: XStandardType = + this.propertyProvider.infinityScrollerLoadingText; + + @Input() + infinityScrollerLoadingSpinner: XStandardType = + this.propertyProvider.infinityScrollerLoadingSpinner; + + @Output() + loadMore: EventEmitter = new EventEmitter(); + + // + @Input() + actions: XStandardType = this.propertyProvider.actions; + + @Input() + actionsActionProvider: Subject = + this.propertyProvider.actionsActionProvider; + //#endregion + + // + //#region Toolbar ... + // + @Input() + showToolbar: XStandardType = this.propertyProvider.showToolbar; + + // + @Input() + toolbarActionProvider: Subject = + this.propertyProvider.toolbarActionProvider; + + @Input() + toolbarCssClass: XStandardType> = + this.propertyProvider.toolbarCssClass; + + // + @Input() + toolbarColor: XStandardType = + this.propertyProvider.toolbarColor; + + // + @Input() + toolbarShowProgressBar: XStandardType = + this.propertyProvider.toolbarShowProgressBar; + + @Input() + toolbarProgressBarColor: XStandardType = + this.propertyProvider.toolbarProgressBarColor; + + // + @Input() + toolbarShowTitle: XStandardType = + this.propertyProvider.toolbarShowTitle; + + @Input() + toolbarTitleSlot: XStandardType = + this.propertyProvider.toolbarTitleSlot; + + @Input() + toolbarTitle: XStandardType = this.propertyProvider.toolbarTitle; + + // + @Input() + toolbarShowSubTitle: XStandardType = + this.propertyProvider.toolbarShowSubTitle; + + @Input() + toolbarSubTitle: XStandardType = + this.propertyProvider.toolbarSubTitle; + + // + @Input() + toolbarShowLogo: XStandardType = + this.propertyProvider.toolbarShowLogo; + + @Input() + toolbarLogoSlot: XStandardType = + this.propertyProvider.toolbarLogoSlot; + + @Input() + toolbarLogoUrl: XStandardType = this.propertyProvider.toolbarLogoUrl; + + // + @Input() + toolbarHasBack: XStandardType = this.propertyProvider.toolbarHasBack; + + @Input() + toolbarShowBack: XStandardType = + this.propertyProvider.toolbarShowBack; + + @Input() + toolbarBackSlot: XStandardType = + this.propertyProvider.toolbarBackSlot; + + @Input() + toolbarBackHandler?: () => XStandardType = () => { + this.managerService.historyService.back(); + }; + + @Input() + toolbarDefaultHref: string = this.propertyProvider.toolbarDefaultHref; + + // + @Input() + toolbarHasMenu: XStandardType = this.propertyProvider.toolbarHasMenu; + + @Input() + toolbarShowMenu: XStandardType = + this.propertyProvider.toolbarShowMenu; + + @Input() + toolbarAutoHideMenu: XStandardType = + this.propertyProvider.toolbarAutoHideMenu; + + @Input() + toolbarMenuSlot: XStandardType = + this.propertyProvider.toolbarMenuSlot; + + // + @Input() + toolbarStartSlotButtonsTemplate: TemplateRef = + this.propertyProvider.toolbarStartSlotButtonsTemplate; + + @Input() + toolbarEndSlotButtonsTemplate: TemplateRef = + this.propertyProvider.toolbarEndSlotButtonsTemplate; + + // + @Input() + toolbarContentSlot: XStandardType = + this.propertyProvider.toolbarContentSlot; + + @Input() + toolbarContentTemplate: TemplateRef = + this.propertyProvider.toolbarContentTemplate; + //#endregion + + // + //#region Footer ... + // + @Input() + showFooter: XStandardType = this.propertyProvider.showFooter; + + // + @Input() + footerActionProvider: Subject = + this.propertyProvider.footerActionProvider; + + @Input() + footerCssClass: XStandardType> = + this.propertyProvider.footerCssClass; + + // + @Input() + footerColor: XStandardType = + this.propertyProvider.footerColor; + + @Input() + footerContentTemplate: TemplateRef = + this.propertyProvider.footerContentTemplate; + //#endregion + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + public menuController: MenuController, + protected propertyProvider: XPageContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXPageContainer + ): Observable { + return forkJoin({ + // + //#region Page ... + // + cssClass: this.getValue(container ? container.cssClass : this.cssClass), + + // + color: this.getValue(container ? container.color : this.color), + + // + enableCaptureMode: this.getValue( + container ? container.enableCaptureMode : this.enableCaptureMode + ), + downloadCapturedImage: this.getValue( + container ? container.downloadCapturedImage : this.downloadCapturedImage + ), + captureTopOffset: this.getValue( + container ? container.captureTopOffset : this.captureTopOffset + ), + captureBottomOffset: this.getValue( + container ? container.captureBottomOffset : this.captureBottomOffset + ), + + // + provideScrollEvents: this.getValue( + container ? container.provideScrollEvents : this.provideScrollEvents + ), + scrollStartClasses: this.getValue( + container ? container.scrollStartClasses : this.scrollStartClasses + ), + scrollEndClasses: this.getValue( + container ? container.scrollEndClasses : this.scrollEndClasses + ), + scrollingClasses: this.getValue( + container ? container.scrollingClasses : this.scrollingClasses + ), + scrollBehaviour: this.getValue( + container ? container.scrollBehaviour : this.scrollBehaviour + ), + + // + toggleMenuWhen: this.getValue( + container ? container.toggleMenuWhen : this.toggleMenuWhen + ), + hasSide: this.getValue(container ? container.hasSide : this.hasSide), + sideSlot: this.getValue(container ? container.sideSlot : this.sideSlot), + sideType: this.getValue(container ? container.sideType : this.sideType), + sideColor: this.getValue( + container ? container.sideColor : this.sideColor + ), + sideMenuWidth: this.getValue( + container ? container.sideMenuWidth : this.sideMenuWidth + ), + sideMenuMinWidth: this.getValue( + container ? container.sideMenuMinWidth : this.sideMenuMinWidth + ), + sideMenuMaxWidth: this.getValue( + container ? container.sideMenuMaxWidth : this.sideMenuMaxWidth + ), + menuState: this.getValue( + container ? container.menuState : this.menuState + ), + + // + showPageRefresher: this.getValue( + container ? container.showPageRefresher : this.showPageRefresher + ), + refreshingText: this.getValue( + container ? container.refreshingText : this.refreshingText + ), + refreshPullingText: this.getValue( + container ? container.refreshPullingText : this.refreshPullingText + ), + refreshPullingIcon: this.getValue( + container ? container.refreshPullingIcon : this.refreshPullingIcon + ), + refreshingSpinner: this.getValue( + container ? container.refreshingSpinner : this.refreshingSpinner + ), + + // + showPageInfinityScroller: this.getValue( + container + ? container.showPageInfinityScroller + : this.showPageInfinityScroller + ), + infinityScrollDisabled: this.getValue( + container + ? container.infinityScrollDisabled + : this.infinityScrollDisabled + ), + infinityScrollThreshold: this.getValue( + container + ? container.infinityScrollThreshold + : this.infinityScrollThreshold + ), + infinityScrollerLoadingText: this.getValue( + container + ? container.infinityScrollerLoadingText + : this.infinityScrollerLoadingText + ), + infinityScrollerLoadingSpinner: this.getValue( + container + ? container.infinityScrollerLoadingSpinner + : this.infinityScrollerLoadingSpinner + ), + + // + actions: this.getValue(container ? container.actions : this.actions), + //#endregion + + // + //#region Toolbar ... + showToolbar: this.getValue( + container ? container.showToolbar : this.showToolbar + ), + toolbarCssClass: this.getValue( + container ? container.toolbarCssClass : this.toolbarCssClass + ), + toolbarColor: this.getValue( + container ? container.toolbarColor : this.toolbarColor + ), + toolbarShowProgressBar: this.getValue( + container + ? container.toolbarShowProgressBar + : this.toolbarShowProgressBar + ), + toolbarProgressBarColor: this.getValue( + container + ? container.toolbarProgressBarColor + : this.toolbarProgressBarColor + ), + toolbarShowTitle: this.getValue( + container ? container.toolbarShowTitle : this.toolbarShowTitle + ), + toolbarTitleSlot: this.getValue( + container ? container.toolbarTitleSlot : this.toolbarTitleSlot + ), + toolbarTitle: this.getValue( + container ? container.toolbarTitle : this.toolbarTitle + ), + toolbarShowSubTitle: this.getValue( + container ? container.toolbarShowSubTitle : this.toolbarShowSubTitle + ), + toolbarSubTitle: this.getValue( + container ? container.toolbarSubTitle : this.toolbarSubTitle + ), + toolbarShowLogo: this.getValue( + container ? container.toolbarShowLogo : this.toolbarShowLogo + ), + toolbarLogoSlot: this.getValue( + container ? container.toolbarLogoSlot : this.toolbarLogoSlot + ), + toolbarLogoUrl: this.getValue( + container ? container.toolbarLogoUrl : this.toolbarLogoUrl + ), + toolbarHasBack: this.getValue( + container ? container.toolbarHasBack : this.toolbarHasBack + ), + toolbarShowBack: this.getValue( + container ? container.toolbarShowBack : this.toolbarShowBack + ), + toolbarBackSlot: this.getValue( + container ? container.toolbarBackSlot : this.toolbarBackSlot + ), + toolbarBackHandler: this.getValue( + container ? container.toolbarBackHandler : this.toolbarBackHandler + ), + toolbarDefaultHref: this.getValue( + container ? container.toolbarDefaultHref : this.toolbarDefaultHref + ), + toolbarHasMenu: this.getValue( + container ? container.toolbarHasMenu : this.toolbarHasMenu + ), + toolbarShowMenu: this.getValue( + container ? container.toolbarShowMenu : this.toolbarShowMenu + ), + toolbarAutoHideMenu: this.getValue( + container ? container.toolbarAutoHideMenu : this.toolbarAutoHideMenu + ), + toolbarMenuSlot: this.getValue( + container ? container.toolbarMenuSlot : this.toolbarMenuSlot + ), + toolbarStartSlotButtonsTemplate: this.getValue( + container + ? container.toolbarStartSlotButtonsTemplate + : this.toolbarStartSlotButtonsTemplate + ), + toolbarEndSlotButtonsTemplate: this.getValue( + container + ? container.toolbarEndSlotButtonsTemplate + : this.toolbarEndSlotButtonsTemplate + ), + toolbarContentSlot: this.getValue( + container ? container.toolbarContentSlot : this.toolbarContentSlot + ), + toolbarContentTemplate: this.getValue( + container + ? container.toolbarContentTemplate + : this.toolbarContentTemplate + ), + //#endregion + + // + //#region Footer ... + showFooter: this.getValue( + container ? container.showFooter : this.showFooter + ), + footerCssClass: this.getValue( + container ? container.footerCssClass : this.footerCssClass + ), + footerColor: this.getValue( + container ? container.footerColor : this.footerColor + ), + footerContentTemplate: this.getValue( + container ? container.footerContentTemplate : this.footerContentTemplate + ), + //#endregion + }).pipe( + map((data) => { + return { + // + ...data, + + // + toolbarActionProvider: container + ? container.toolbarActionProvider + : this.toolbarActionProvider, + + // + footerActionProvider: container + ? container.footerActionProvider + : this.footerActionProvider, + + // + menuActionProvider: container + ? container.menuActionProvider + : this.menuActionProvider, + + // + scrollEventActionProvider: container + ? container.scrollEventActionProvider + : this.scrollEventActionProvider, + + // + actionsActionProvider: container + ? container.actionsActionProvider + : this.actionsActionProvider, + + // + imageCaptured: this.imageCaptured, + splitterStateChanged: this.splitterStateChanged, + menuStateChange: this.menuStateChange, + scrolledEvent: this.scrolledEvent, + refreshPage: this.refreshPage, + loadMore: this.loadMore, + }; + }) + ); + } + //#endregion +} + +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-page\x-page.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XPageComponent } from './x-page.component'; +import { XFabModule } from './../x-fab/x-fab.module'; +import { XIconModule } from '../x-icon/x-icon.module'; +import { XButtonModule } from '../x-button/x-button.module'; +import { XFooterComponent } from './x-footer/x-footer.component'; +import { XToolbarComponent } from './x-toolbar/x-toolbar.component'; +import { XNavigatorComponent } from './x-navigator/x-navigator.component'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; +import { XScreenCaptureModule } from '../x-screen-capture/x-screen-capture.module'; +import { XExpandableListModule } from '../x-expandable-list/x-expandable-list.module'; +import { XExpandablePanelModule } from '../x-expandable-panel/x-expandable-panel.module'; + +@NgModule({ + imports: [ + XFabModule, + XIconModule, + CommonModule, + XButtonModule, + XBaseComponentModule, + XScreenCaptureModule, + XExpandableListModule, + XExpandablePanelModule, + ], + declarations: [ + XPageComponent, + XFooterComponent, + XToolbarComponent, + XNavigatorComponent, + ], + exports: [ + XFabModule, + XIconModule, + CommonModule, + XButtonModule, + XPageComponent, + XFooterComponent, + XToolbarComponent, + XNavigatorComponent, + XBaseComponentModule, + XScreenCaptureModule, + XExpandableListModule, + XExpandablePanelModule, + ], +}) +export class XPageModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-page\x-page.typings.ts + + +export enum XMenuState { + WillOpen, + Opened, + WillClose, + Closed, +} + +export enum XMenuAction { + Open, + Close, + Toggle, +} + +export enum XPageSize { + ExtraSmall = 'xs', + Small = 'sm', + Medium = 'md', + Large = 'lg', + ExtraLarge = 'xl', +} + +export type XPageSizeIdentifier = XPageSize | string; + +export enum XSideType { + Overlay = 'overlay', + Reveal = 'reveal', + Push = 'push', +} + +export type XSideTypeIdentifier = XSideType | string; + +export enum XBackSlot { + Start = 'start', + End = 'end', +} + +export type XBackSlotIdentifier = XBackSlot | string; + +export enum XMenuSlot { + Start = 'start', + End = 'end', +} + +export type XMenuSlotIdentifier = XMenuSlot | string; + +export enum XLogoSlot { + Start = 'start', + End = 'end', +} + +export type XLogoSlotIdentifier = XLogoSlot | string; + +export enum XTitleSlot { + Start = 'start', + End = 'end', +} + +export type XTitleSlotIdentifier = XTitleSlot | string; + +export enum XContentSlot { + Start = 'start', + Middle = 'middle', + End = 'end', +} + +export type XContentSlotIdentifier = XContentSlot | string; + +export enum XSideSlot { + Start = 'start', + End = 'end', +} + +export type XSideSlotIdentifier = XSideSlot | string; + +export interface XPageLoadingEvent { + type: 'InfinityScrollerLoader' | 'RefreshLoader'; + event: any; +} + +export enum XPageCSSVars { + SideMenuWidth = 'side-menu-width', + SideMenuMinWidth = 'side-menu-min-width', + SideMenuMaxWidth = 'side-menu-max-width', + SideMenuContentHeightGutter = 'side-menu-content-height-gutter', + SideMenuContentOffsetTop = 'side-menu-content-offset-top', + SideMenuContentOffsetBottom = 'side-menu-content-offset-bottom', + ContentHeightGutter = 'content-height-gutter', + ContentOffsetTop = 'content-offset-top', + ContentOffsetBottom = 'content-offset-bottom', +} + +export type XPageCSSVarsIdentifier = XPageCSSVars | string; + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-page\x-footer\x-footer.component.html + + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-page\x-footer\x-footer.component.scss + +:host { + position: absolute; + bottom: 0; + left: 0; + right: 0; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-page\x-footer\x-footer.component.ts + +import { + XFooterAction, + XFooterActionModel, + XFooterContainerComponent, +} from './x-footer.typings'; +import { nameof } from 'x-framework-core'; +import { Subject, Subscription } from 'rxjs'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-footer', + templateUrl: './x-footer.component.html', + styleUrls: ['./x-footer.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XFooterComponent extends XFooterContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + //#endregion + + // + //#region LifeCycles ... + onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region ActionProvider Changed ... + // + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region Private ... + // + //#region Action Provider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + // Handle Action Provider ... + this.ACTION_PROVIDER_SUBSCRIPTION = this.applyTakeUntilWrapper( + this.actionProvider.asObservable() + ).subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XFooterAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-page\x-footer\x-footer.typings.ts + +import { + Input, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + TemplateRef, + ChangeDetectorRef, +} from '@angular/core'; +import { + XStandardType, + XOneOrManyType, + XColorIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { map } from 'rxjs/operators'; +import { + XBaseContainer, + IXBaseContainer, + XBaseContainerComponent, + XBaseComponentContainer, + IXBaseComponentContainer, + XBaseComponentContainerComponent, +} from '../../classes/i-x-base.container'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../../config/x-framework-components.config'; + +export enum XFooterAction { + Refresh = 'refresh', +} + +export type XFooterActionIdentifier = XFooterAction | string; + +export interface XFooterActionModel + extends XBaseActionModel {} + +// +//#region Container ... +export interface IXFooterContainer extends IXBaseContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + color: XStandardType; + contentTemplate: TemplateRef; +} + +@Injectable({ + providedIn: 'root' +}) +export class XFooterContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider: Subject = + new Subject(); + readonly cssClass: XStandardType>; + + // + readonly color: XStandardType = XColorWithBrightness.Light; + readonly contentTemplate: TemplateRef; + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XFooterContainerComponent + extends XBaseContainerComponent + implements IXFooterContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + color: XStandardType = this.propertyProvider.color; + + @Input() + contentTemplate: TemplateRef = this.propertyProvider.contentTemplate; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XFooterContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXFooterContainer + ): Observable { + return forkJoin({ + // + cssClass: this.getValue(container ? container.cssClass : this.cssClass), + color: this.getValue(container ? container.color : this.color), + contentTemplate: this.getValue( + container ? container.contentTemplate : this.contentTemplate + ), + }).pipe( + map((data) => { + return { + // + ...data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + }; + }) + ); + } + //#endregion +} +//#endregion + +// +//#region ComponentContainer ... +type TXComponentFooterContainer = { + [TKey in keyof IXFooterContainer as `footer${Capitalize< + string & TKey + >}`]: IXFooterContainer[TKey]; +}; + +export interface IXComponentFooterContainer + extends TXComponentFooterContainer, + IXBaseComponentContainer { + showFooter: XStandardType; +} + +@Injectable({ + providedIn: 'root' +}) +export class XComponentFooterContainer + extends XBaseComponentContainer + implements Readonly +{ + // + //#region Props ... + // + readonly showFooter: XStandardType = true; + + // + readonly footerActionProvider: Subject = + this.propertyProvider.actionProvider; + readonly footerCssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + readonly footerColor: XStandardType = + this.propertyProvider.color; + readonly footerContentTemplate: TemplateRef = + this.propertyProvider.contentTemplate; + //#endregion + + // + //#region Constructor ... + constructor(protected propertyProvider: XFooterContainer) { + super(); + } + //#endregion +} + +@Injectable({ + providedIn: 'root' +}) +@Component({ + template: ` + + `, +}) +export abstract class XComponentFooterContainerComponent + extends XBaseComponentContainerComponent + implements IXComponentFooterContainer +{ + // + //#region Props ... + // + @Input() + showFooter: XStandardType = this.propertyProvider.showFooter; + + // + @Input() + footerActionProvider: Subject = + this.propertyProvider.footerActionProvider; + + @Input() + footerCssClass: XStandardType> = + this.propertyProvider.footerCssClass; + + // + @Input() + footerColor: XStandardType = + this.propertyProvider.footerColor; + + @Input() + footerContentTemplate: TemplateRef = + this.propertyProvider.footerContentTemplate; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XComponentFooterContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXComponentFooterContainer + ): Observable { + return forkJoin({ + // + showFooter: this.getValue( + container ? container.showFooter : this.showFooter + ), + footerCssClass: this.getValue( + container ? container.footerCssClass : this.footerCssClass + ), + footerColor: this.getValue( + container ? container.footerColor : this.footerColor + ), + footerContentTemplate: this.getValue( + container ? container.footerContentTemplate : this.footerContentTemplate + ), + }).pipe( + map((data) => { + return { + // + ...data, + + // + footerActionProvider: container + ? container.footerActionProvider + : this.footerActionProvider, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-page\x-navigator\x-navigator.component.html + + + + + + + + + + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-page\x-navigator\x-navigator.component.scss + +.nav-item > * { + display: inline-block; + position: relative; + margin: 0; + padding: 0; +} + +.x-nav-item-title { + max-width: 150px; + margin: 4px; +} + +.nav-item-parent { + .x-nav-item-title { + font-size: 10pt; + font-weight: bold; + } +} + +.nav-item-child { + .x-nav-item-title { + font-size: 9pt; + font-weight: normal; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-page\x-navigator\x-navigator.component.ts + +import { + XNavigatorAction, + XNavigatorActionModel, + XNavigatorItemIconSlot, + XNavigatorContainerComponent, + XNavigatorListItem, +} from './x-navigator.typings'; +import { map } from 'rxjs/operators'; +import { Observable, Subject, Subscription } from 'rxjs'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; +import { XStandardType, XOneOrManyType, nameof } from 'x-framework-core'; +import { XExpandableListItem } from '../../x-expandable-list/x-expandable-list.typings'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-navigator', + templateUrl: './x-navigator.component.html', + styleUrls: ['./x-navigator.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XNavigatorComponent extends XNavigatorContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + private EXPANDABLE_LIST_ITEMS: XOneOrManyType>; + + get preparedItems() { + return this.EXPANDABLE_LIST_ITEMS; + } + //#endregion + + // + //#region LifeCycles ... + async onInit() { + super.onInit(); + + // + await this.prepareItems(); + } + + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region ActionProvider Changed ... + // + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + + // + //#region Items Changed ... + const isItemsChanged = changeKeys.includes( + nameof('items') + ); + if (isItemsChanged) { + await this.prepareItems(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region UI Providers ... + isEndSlotted() { + return this.getValue(this.iconSlot).pipe( + map((iconSlot) => iconSlot === XNavigatorItemIconSlot.End) + ); + } + + isStartSlotted() { + return this.getValue(this.iconSlot).pipe( + map((iconSlot) => iconSlot === XNavigatorItemIconSlot.Start) + ); + } + //#endregion + + // + //#region UI Actions ... + //#endregion + + // + //#region Private ... + // + //#region Action Provider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + // Handle Action Provider ... + this.ACTION_PROVIDER_SUBSCRIPTION = this.applyTakeUntilWrapper( + this.actionProvider.asObservable() + ).subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XNavigatorAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + private async prepareItems() { + // + this.EXPANDABLE_LIST_ITEMS = null; + this.detectChanges(); + + // + const items = await this.getArrayValueAsync(this.items); + const preparedItems = await Promise.all( + items.map(async (i) => await this.toExpandableListItem(i)) + ); + + // + this.EXPANDABLE_LIST_ITEMS = [ + ...preparedItems + ]; + + // + this.detectChanges(); + } + + private async toExpandableListItem(item: XNavigatorListItem) { + // + const result: XExpandableListItem = { + // + ...item, + data: '', + childs: [ + ...(await Promise.all( + (item.childs ? await this.getArrayValueAsync(item.childs) : []).map( + async (c) => await this.toExpandableListItem(c) + ) + )), + ], + }; + + // + return result; + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-page\x-navigator\x-navigator.tools.ts + +import { + XPage, + isArray, + toArray, + hasChild, + mergeRoutes, + XOneOrManyType, +} from 'x-framework-core'; +import { XNavigatorListItem } from './x-navigator.typings'; + +export function extractParent(source: XOneOrManyType): any { + // + if (!source) { + return null; + } + + // + const isArrayOfPages = isArray(source); + const page = source as XPage; + const pages = source as XPage[]; + + // + if (isArrayOfPages) { + // + // Handle Array of Pages ... + return pages.filter((p) => hasChild(p.childs)); + } else { + // + // Handle On Page ... + return extractParent(page.childs); + } +} + +export function extractChilds(source: XOneOrManyType) { + // + if (!source) { + return null; + } + + // + const isArrayOfPages = isArray(source); + const page = source as XPage; + const pages = source as XPage[]; + + // + if (isArrayOfPages) { + // + // Handle Array of Pages ... + return pages.filter((p) => !hasChild(p.childs)); + } else { + // + // Handle On Page ... + return extractParent(page.childs); + } +} + +export function toFlat(pages: XOneOrManyType) { + // + if (!pages || !hasChild(pages)) { + return []; + } + + // + let result: XPage[] = []; + + // + const pPages = toArray(pages); + pPages.forEach((p) => { + // + result.push(p); + + // + if (p.childs && hasChild(p.childs)) { + // + const childsFlat = toFlat(p.childs); + result = result.concat(childsFlat); + } + }); + + // + return result; +} + +export function toNavigatiorListItem(page: XPage) { + // + if (!page) { + return null; + } + + // + const pageRoute = mergeRoutes(page.route); + + // + const result: XNavigatorListItem = { + id: page.id ? `${page.id}_(${pageRoute})` : `${page.name}_(${pageRoute})`, + opened: false, + hidden: false, + disabled: false, + icon: page.icon, + href: page.route, + replaceUrl: true, + title: page.title, + description: page.description, + childs: hasChild(page.childs) + ? toArray(page.childs).map((c) => toNavigatiorListItem(c)) + : null, + }; + + // + return result; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-page\x-navigator\x-navigator.typings.ts + +import { + Input, + Output, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XStandardType, + XOneOrManyType, + XColorIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { map } from 'rxjs/operators'; +import { + XBaseContainer, + IXBaseContainer, + XBaseContainerComponent, +} from '../../classes/i-x-base.container'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XIconIdentifier } from '../../x-icon/x-icon.typings'; +import { XBaseActionModel } from '../../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../../config/x-framework-components.config'; +import { XExpandableListItem } from '../../x-expandable-list/x-expandable-list.typings'; + +export type XNavigatorListItemBase = Pick< + XExpandableListItem, + | 'id' + | 'href' + | 'title' + | 'opened' + | 'hidden' + | 'disabled' + | 'replaceUrl' + | 'description' +>; +export interface XNavigatorListItem extends XNavigatorListItemBase { + icon?: XStandardType; + childs?: XStandardType>; +} + +export enum XNavigatorItemIconSlot { + Start = 'start', + End = 'end', +} + +export type XNavigatorItemIconSlotIdentifier = XNavigatorItemIconSlot | string; + +export enum XNavigatorStartupState { + ExpandAll = 'expand-all', + CollapseAll = 'collapse-all', +} + +export type XNavigatorStartupStateIdentifier = XNavigatorStartupState | string; + +export enum XNavigatorAction { + Refresh = 'refresh', +} + +export type XNavigatorActionIdentifier = XNavigatorAction | string; + +export interface XNavigatorActionModel + extends XBaseActionModel {} + +// +//#region Container ... +export interface IXNavigatorContainer extends IXBaseContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + itemCssClass: XStandardType>; + items: XStandardType>; + + // + closeSiblings: XStandardType; + iconSlot: XStandardType; + startupState: XStandardType; + + // + showChildIcon: XStandardType; + showChildDetailIcon: XStandardType; + showParentIcon: XStandardType; + showParentDetailIcon: XStandardType; + disableParentRouting: XStandardType; + + // + childColor: XStandardType; + childActiveColor: XStandardType; + parentColor: XStandardType; + parentActiveColor: XStandardType; + highlightColor: XStandardType; + dividerColor: XStandardType; + + // + itemSelected: EventEmitter; +} + +@Injectable({ + providedIn: 'root', +}) +export class XNavigatorContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider: Subject = + new Subject(); + readonly cssClass: XStandardType>; + + // + readonly itemCssClass: XStandardType>; + readonly items: XStandardType>; + + // + readonly closeSiblings: XStandardType = true; + readonly iconSlot: XStandardType = + XNavigatorItemIconSlot.Start; + readonly startupState: XStandardType = + XNavigatorStartupState.ExpandAll; + + // + readonly showChildIcon: XStandardType = true; + readonly showChildDetailIcon: XStandardType = true; + readonly showParentIcon: XStandardType = true; + readonly showParentDetailIcon: XStandardType = true; + readonly disableParentRouting: XStandardType = true; + + // + readonly childColor: XStandardType = + XColorWithBrightness.PrimaryShade; + readonly childActiveColor: XStandardType = + XColorWithBrightness.Dark; + readonly parentColor: XStandardType = + XColorWithBrightness.TertiaryTint; + readonly parentActiveColor: XStandardType = + XColorWithBrightness.Dark; + readonly highlightColor: XStandardType = + XColorWithBrightness.Warning; + readonly dividerColor: XStandardType = + XColorWithBrightness.Dark; + + // + readonly itemSelected: EventEmitter = + new EventEmitter(); + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XNavigatorContainerComponent + extends XBaseContainerComponent + implements IXNavigatorContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + itemCssClass: XStandardType> = + this.propertyProvider.itemCssClass; + + @Input() + items: XStandardType> = + this.propertyProvider.items; + + // + @Input() + closeSiblings: XStandardType = this.propertyProvider.closeSiblings; + + @Input() + iconSlot: XStandardType = + this.propertyProvider.iconSlot; + + @Input() + startupState: XStandardType = + this.propertyProvider.startupState; + + // + @Input() + showChildIcon: XStandardType = this.propertyProvider.showChildIcon; + + @Input() + showChildDetailIcon: XStandardType = + this.propertyProvider.showChildDetailIcon; + + @Input() + showParentIcon: XStandardType = this.propertyProvider.showParentIcon; + + @Input() + showParentDetailIcon: XStandardType = + this.propertyProvider.showParentDetailIcon; + + @Input() + disableParentRouting: XStandardType = + this.propertyProvider.disableParentRouting; + + // + @Input() + childColor: XStandardType = + this.propertyProvider.childColor; + + @Input() + childActiveColor: XStandardType = + this.propertyProvider.childActiveColor; + + @Input() + parentColor: XStandardType = + this.propertyProvider.parentColor; + + @Input() + parentActiveColor: XStandardType = + this.propertyProvider.parentActiveColor; + + @Input() + highlightColor: XStandardType = + this.propertyProvider.highlightColor; + + @Input() + dividerColor: XStandardType = + this.propertyProvider.dividerColor; + + // + @Output() + itemSelected: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XNavigatorContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXNavigatorContainer + ): Observable { + return forkJoin({ + // + cssClass: this.getValue(container ? container.cssClass : this.cssClass), + itemCssClass: this.getValue( + container ? container.itemCssClass : this.itemCssClass + ), + items: this.getValue(container ? container.items : this.items), + closeSiblings: this.getValue( + container ? container.closeSiblings : this.closeSiblings + ), + iconSlot: this.getValue(container ? container.iconSlot : this.iconSlot), + startupState: this.getValue( + container ? container.startupState : this.startupState + ), + showChildIcon: this.getValue( + container ? container.showChildIcon : this.showChildIcon + ), + showChildDetailIcon: this.getValue( + container ? container.showChildDetailIcon : this.showChildDetailIcon + ), + showParentIcon: this.getValue( + container ? container.showParentIcon : this.showParentIcon + ), + showParentDetailIcon: this.getValue( + container ? container.showParentDetailIcon : this.showParentDetailIcon + ), + disableParentRouting: this.getValue( + container ? container.disableParentRouting : this.disableParentRouting + ), + childColor: this.getValue( + container ? container.childColor : this.childColor + ), + childActiveColor: this.getValue( + container ? container.childActiveColor : this.childActiveColor + ), + parentColor: this.getValue( + container ? container.parentColor : this.parentColor + ), + parentActiveColor: this.getValue( + container ? container.parentActiveColor : this.parentActiveColor + ), + highlightColor: this.getValue( + container ? container.highlightColor : this.highlightColor + ), + dividerColor: this.getValue( + container ? container.dividerColor : this.dividerColor + ), + }).pipe( + map((data) => { + return { + // + ...data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + itemSelected: this.itemSelected, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-page\x-toolbar\x-toolbar.component.html + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + + + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + +
+ + + + + + + + + + + +

+ {{ getValue(title) | async }} +

+ + +
+ {{ getValue(subTitle) | async }} +
+
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-page\x-toolbar\x-toolbar.component.scss + +.x-toolbar-slot, +.x-toolbar-start-slot, +.x-toolbar-middle-slot, +.x-toolbar-end-slot { + visibility: inherit; +} + +.x-toolbar-start-slot, +.x-toolbar-middle-slot, +.x-toolbar-end-slot { + margin: { + left: 4px; + right: 4px; + } +} + +.x-toolbar-slot-container { + display: flex; +} + +.x-toolbar-slot { + display: flex; + flex: 1; +} + +.x-toolbar-slot > *, +.x-toolbar-slot-container > * { + margin: { + left: 2px; + right: 2px; + } + + width: max-content; +} + +ion-thumbnail { + width: 48px !important; + height: 48px !important; +} + +ion-img { + object-fit: contain !important; +} + +:host { + --menu-button-transform: rotate(90deg); + --menu-button-transform-duration: 500ms; +} + +:host.menu-opened { + ion-menu-button { + // + transform: var(--menu-button-transform); + -ms-transform: var(--menu-button-transform); + -webkit-transform: var(--menu-button-transform); + + // + transition: transform; + -webkit-transition: transform; + + // + transition-duration: var(--menu-button-transform-duration); + -webkit-transition-duration: var(--menu-button-transform-duration); + } +} + +ion-menu-button { + // + transition-duration: var(--menu-button-transform-duration); + -webkit-transition-duration: var(--menu-button-transform-duration); +} + +.x-toolbar-title-wrapper { + padding-inline-end: 16px; + padding-inline-start: 0; + + display: block; + margin: { + top: auto; + bottom: auto; + } +} + +.x-toolbar-title, +.x-toolbar-sub-title { + display: block !important; +} + +.x-toolbar-back { +} + +.mobile { + max-width: 150px; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-page\x-toolbar\x-toolbar.component.ts + +import { + XParam, + nameof, + isNullOrEmptyString, + XColorWithBrightness, +} from 'x-framework-core'; +import { + XToolbarAction, + XToolbarActionModel, + XToolbarContainerComponent, +} from './x-toolbar.typings'; +import { map } from 'rxjs/operators'; +import { Subject, Subscription } from 'rxjs'; +import { XIconNames } from '../../x-icon/x-icon.typings'; +import { XMenuSlot, XBackSlot } from './../x-page.typings'; +import { XButtonType } from '../../x-button/x-button.typings'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; +import { XLogoSlot, XTitleSlot, XContentSlot } from '../x-page.typings'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-toolbar', + templateUrl: './x-toolbar.component.html', + styleUrls: ['./x-toolbar.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XToolbarComponent extends XToolbarContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + readonly IconNames = Object.assign({}, XIconNames); + readonly ButtonTypes = Object.assign({}, XButtonType); + readonly ColorNames = Object.assign({}, XColorWithBrightness); + //#endregion + + // + //#region LifeCycle ... + onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region ActionProvider Changed ... + // + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + + //#endregion + + // + //#region Reading Query Params ... + //#endregion + + // + //#region Providers ... + getBackHref() { + // + const isContainReturnUrl = this.managerService.isContainQueryParam( + XParam.ReturnUrl + ); + + // + return !isNullOrEmptyString(this.defaultHref) + ? this.defaultHref + : !isContainReturnUrl + ? null + : this.managerService.getQueryParamValOfType(XParam.ReturnUrl); + } + + canBack() { + // + return this.getValue(this.hasBack).pipe( + map((hasBack) => { + // + if ( + !hasBack && + this.managerService.isContainQueryParam(XParam.HasBack) + ) { + hasBack = + hasBack || + this.managerService.getQueryParamValOfType(XParam.HasBack); + } + + // + return hasBack; + }) + ); + } + + // + //#region Slot Providers ... + isEndSlotBack() { + return this.getValue(this.backSlot).pipe( + map((slot) => slot === XBackSlot.End) + ); + } + + isStartSlotBack() { + return this.getValue(this.backSlot).pipe( + map((slot) => slot === XBackSlot.Start) + ); + } + + isEndSlotMenu() { + return this.getValue(this.menuSlot).pipe( + map((slot) => slot === XMenuSlot.End) + ); + } + + isStartSlotMenu() { + return this.getValue(this.menuSlot).pipe( + map((slot) => slot === XMenuSlot.Start) + ); + } + + isEndSlotLogo() { + return this.getValue(this.logoSlot).pipe( + map((slot) => slot === XLogoSlot.End) + ); + } + + isStartSlotLogo() { + return this.getValue(this.logoSlot).pipe( + map((slot) => slot === XLogoSlot.Start) + ); + } + + isEndSlotTitle() { + return this.getValue(this.titleSlot).pipe( + map((slot) => slot === XTitleSlot.End) + ); + } + + isStartSlotTitle() { + return this.getValue(this.titleSlot).pipe( + map((slot) => slot === XTitleSlot.Start) + ); + } + + isEndSlotContent() { + return this.getValue(this.contentSlot).pipe( + map((slot) => slot === XContentSlot.End) + ); + } + + isMiddleSlotContent() { + return this.getValue(this.contentSlot).pipe( + map((slot) => slot === XContentSlot.Middle) + ); + } + + isStartSlotContent() { + return this.getValue(this.contentSlot).pipe( + map((slot) => slot === XContentSlot.Start) + ); + } + //#endregion + //#endregion + + // + //#region Handlers ... + handleMenuButtonClicked() { + this.actionProvider.next({ action: XToolbarAction.ToggleMenu }); + } + + async handleBackButtonClicked(event: any) { + // + if (this.backHandler) { + // + if (event && event.preventDefault) { + event.preventDefault(); + } + + // + await this.getValueAsync(this.backHandler()); + return; + } + + // + const hasReturnUrl = this.managerService.isContainQueryParam( + XParam.ReturnUrl + ); + const hasDefaultHref = !isNullOrEmptyString(this.defaultHref); + const returnUrl = hasReturnUrl + ? this.managerService.getQueryParamValOfType(XParam.ReturnUrl) + : hasDefaultHref + ? this.defaultHref + : ''; + + // + if (!isNullOrEmptyString(returnUrl)) { + // + await this.managerService.navigateByUrl(returnUrl); + return; + } + + // + this.managerService.historyService.back(); + } + //#endregion + + // + //#region Private ... + // + //#region Action Provider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + // Handle Action Provider ... + this.ACTION_PROVIDER_SUBSCRIPTION = this.applyTakeUntilWrapper( + this.actionProvider.asObservable() + ).subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XToolbarAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-page\x-toolbar\x-toolbar.typings.ts + +import { + Input, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + TemplateRef, + ChangeDetectorRef, +} from '@angular/core'; +import { + XStandardType, + XOneOrManyType, + XColorIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { + XBackSlot, + XLogoSlot, + XMenuSlot, + XTitleSlot, + XContentSlot, + XBackSlotIdentifier, + XLogoSlotIdentifier, + XMenuSlotIdentifier, + XTitleSlotIdentifier, + XContentSlotIdentifier, +} from '../x-page.typings'; +import { map } from 'rxjs/operators'; +import { + XBaseContainer, + IXBaseContainer, + XBaseContainerComponent, + XBaseComponentContainer, + IXBaseComponentContainer, + XBaseComponentContainerComponent, +} from '../../classes/i-x-base.container'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../../config/x-framework-components.config'; + +export enum XToolbarAction { + Refresh = 'refresh', + ToggleMenu = 'ToggleMenu', +} + +export type XToolbarActionIdentifier = XToolbarAction | string; + +export interface XToolbarActionModel + extends XBaseActionModel {} + +// +//#region Container ... +export interface IXToolbarContainer extends IXBaseContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + color: XStandardType; + + // + showProgressBar: XStandardType; + progressBarColor: XStandardType; + + // + showTitle: XStandardType; + titleSlot: XStandardType; + title: XStandardType; + + // + showSubTitle: XStandardType; + subTitle: XStandardType; + + // + showLogo: XStandardType; + logoSlot: XStandardType; + logoUrl: XStandardType; + + // + hasBack: XStandardType; + showBack: XStandardType; + backSlot: XStandardType; + backHandler?: () => XStandardType; + defaultHref: string; + + // + hasMenu: XStandardType; + showMenu: XStandardType; + autoHideMenu: XStandardType; + menuSlot: XStandardType; + + // + startSlotButtonsTemplate: TemplateRef; + endSlotButtonsTemplate: TemplateRef; + + // + contentSlot: XStandardType; + contentTemplate: TemplateRef; +} + +@Injectable({ + providedIn: 'root', +}) +export class XToolbarContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider: Subject = + new Subject(); + readonly cssClass: XStandardType>; + + // + readonly color: XStandardType = + XColorWithBrightness.Primary; + + // + readonly showProgressBar: XStandardType = false; + readonly progressBarColor: XStandardType = + XColorWithBrightness.Warning; + + // + readonly showTitle: XStandardType = true; + readonly titleSlot: XStandardType = XTitleSlot.End; + readonly title: XStandardType; + + // + readonly showSubTitle: XStandardType = false; + readonly subTitle: XStandardType; + + // + readonly showLogo: XStandardType = false; + readonly logoSlot: XStandardType = XLogoSlot.Start; + readonly logoUrl: XStandardType; + + // + readonly hasBack: XStandardType = true; + readonly showBack: XStandardType = false; + readonly backSlot: XStandardType = XBackSlot.Start; + readonly backHandler?: () => XStandardType; + readonly defaultHref: string; + + // + readonly hasMenu: XStandardType = true; + readonly showMenu: XStandardType = false; + readonly autoHideMenu: XStandardType = true; + readonly menuSlot: XStandardType = XMenuSlot.Start; + + // + readonly startSlotButtonsTemplate: TemplateRef; + readonly endSlotButtonsTemplate: TemplateRef; + + // + readonly contentSlot: XStandardType = + XContentSlot.End; + readonly contentTemplate: TemplateRef; + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XToolbarContainerComponent + extends XBaseContainerComponent + implements IXToolbarContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + color: XStandardType = this.propertyProvider.color; + + // + @Input() + showProgressBar: XStandardType = + this.propertyProvider.showProgressBar; + + @Input() + progressBarColor: XStandardType = + this.propertyProvider.progressBarColor; + + // + @Input() + showTitle: XStandardType = this.propertyProvider.showTitle; + + @Input() + titleSlot: XStandardType = + this.propertyProvider.titleSlot; + + @Input() + title: XStandardType = this.propertyProvider.title; + + // + @Input() + showSubTitle: XStandardType = this.propertyProvider.showSubTitle; + + @Input() + subTitle: XStandardType = this.propertyProvider.subTitle; + + // + @Input() + showLogo: XStandardType = this.propertyProvider.showLogo; + + @Input() + logoSlot: XStandardType = this.propertyProvider.logoSlot; + + @Input() + logoUrl: XStandardType = this.propertyProvider.logoUrl; + + // + @Input() + hasBack: XStandardType = this.propertyProvider.hasBack; + + @Input() + showBack: XStandardType = this.propertyProvider.showBack; + + @Input() + backSlot: XStandardType = this.propertyProvider.backSlot; + + @Input() + backHandler?: () => XStandardType = this.propertyProvider.backHandler; + + @Input() + defaultHref: string = this.propertyProvider.defaultHref; + + // + @Input() + hasMenu: XStandardType = this.propertyProvider.hasMenu; + + @Input() + showMenu: XStandardType = this.propertyProvider.showMenu; + + @Input() + autoHideMenu: XStandardType = this.propertyProvider.autoHideMenu; + + @Input() + menuSlot: XStandardType = this.propertyProvider.menuSlot; + + // + @Input() + startSlotButtonsTemplate: TemplateRef = + this.propertyProvider.startSlotButtonsTemplate; + + @Input() + endSlotButtonsTemplate: TemplateRef = + this.propertyProvider.endSlotButtonsTemplate; + + // + @Input() + contentSlot: XStandardType = + this.propertyProvider.contentSlot; + + @Input() + contentTemplate: TemplateRef = this.propertyProvider.contentTemplate; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XToolbarContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXToolbarContainer + ): Observable { + return forkJoin({ + // + cssClass: this.getValue(container ? container.cssClass : this.cssClass), + color: this.getValue(container ? container.color : this.color), + showProgressBar: this.getValue( + container ? container.showProgressBar : this.showProgressBar + ), + progressBarColor: this.getValue( + container ? container.progressBarColor : this.progressBarColor + ), + showTitle: this.getValue( + container ? container.showTitle : this.showTitle + ), + titleSlot: this.getValue( + container ? container.titleSlot : this.titleSlot + ), + title: this.getValue(container ? container.title : this.title), + showSubTitle: this.getValue( + container ? container.showSubTitle : this.showSubTitle + ), + subTitle: this.getValue(container ? container.subTitle : this.subTitle), + showLogo: this.getValue(container ? container.showLogo : this.showLogo), + logoSlot: this.getValue(container ? container.logoSlot : this.logoSlot), + logoUrl: this.getValue(container ? container.logoUrl : this.logoUrl), + hasBack: this.getValue(container ? container.hasBack : this.hasBack), + showBack: this.getValue(container ? container.showBack : this.showBack), + backSlot: this.getValue(container ? container.backSlot : this.backSlot), + backHandler: this.getValue( + container ? container.backHandler : this.backHandler + ), + defaultHref: this.getValue( + container ? container.defaultHref : this.defaultHref + ), + hasMenu: this.getValue(container ? container.hasMenu : this.hasMenu), + showMenu: this.getValue(container ? container.showMenu : this.showMenu), + autoHideMenu: this.getValue( + container ? container.autoHideMenu : this.autoHideMenu + ), + menuSlot: this.getValue(container ? container.menuSlot : this.menuSlot), + startSlotButtonsTemplate: this.getValue( + container + ? container.startSlotButtonsTemplate + : this.startSlotButtonsTemplate + ), + endSlotButtonsTemplate: this.getValue( + container + ? container.endSlotButtonsTemplate + : this.endSlotButtonsTemplate + ), + contentSlot: this.getValue( + container ? container.contentSlot : this.contentSlot + ), + contentTemplate: this.getValue( + container ? container.contentTemplate : this.contentTemplate + ), + }).pipe( + map((data) => { + return { + // + ...data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + }; + }) + ); + } + //#endregion +} +//#endregion + +// +//#region ComponentContainer ... +type TXComponentToolbarContainer = { + [TKey in keyof IXToolbarContainer as `toolbar${Capitalize< + string & TKey + >}`]: IXToolbarContainer[TKey]; +}; + +export interface IXComponentToolbarContainer + extends TXComponentToolbarContainer, + IXBaseComponentContainer { + showToolbar: XStandardType; +} + +@Injectable({ + providedIn: 'root', +}) +export class XComponentToolbarContainer + extends XBaseComponentContainer + implements Readonly +{ + // + //#region Props ... + // + readonly showToolbar: XStandardType = true; + + // + readonly toolbarActionProvider: Subject = + this.propertyProvider.actionProvider; + readonly toolbarCssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + readonly toolbarColor: XStandardType = + this.propertyProvider.color; + + // + readonly toolbarShowProgressBar: XStandardType = + this.propertyProvider.showProgressBar; + readonly toolbarProgressBarColor: XStandardType = + this.propertyProvider.progressBarColor; + + // + readonly toolbarShowTitle: XStandardType = + this.propertyProvider.showTitle; + readonly toolbarTitleSlot: XStandardType = + this.propertyProvider.titleSlot; + readonly toolbarTitle: XStandardType = this.propertyProvider.title; + + // + readonly toolbarShowSubTitle: XStandardType = + this.propertyProvider.showSubTitle; + readonly toolbarSubTitle: XStandardType = + this.propertyProvider.subTitle; + + // + readonly toolbarShowLogo: XStandardType = + this.propertyProvider.showLogo; + readonly toolbarLogoSlot: XStandardType = + this.propertyProvider.logoSlot; + readonly toolbarLogoUrl: XStandardType = + this.propertyProvider.logoUrl; + + // + readonly toolbarHasBack: XStandardType = + this.propertyProvider.hasBack; + readonly toolbarShowBack: XStandardType = + this.propertyProvider.showBack; + readonly toolbarBackSlot: XStandardType = + this.propertyProvider.backSlot; + readonly toolbarBackHandler?: () => XStandardType = + this.propertyProvider.backHandler; + readonly toolbarDefaultHref: string = this.propertyProvider.defaultHref; + + // + readonly toolbarHasMenu: XStandardType = + this.propertyProvider.hasMenu; + readonly toolbarShowMenu: XStandardType = + this.propertyProvider.showMenu; + readonly toolbarAutoHideMenu: XStandardType = + this.propertyProvider.autoHideMenu; + readonly toolbarMenuSlot: XStandardType = + this.propertyProvider.menuSlot; + + // + readonly toolbarStartSlotButtonsTemplate: TemplateRef = + this.propertyProvider.startSlotButtonsTemplate; + readonly toolbarEndSlotButtonsTemplate: TemplateRef = + this.propertyProvider.endSlotButtonsTemplate; + + // + readonly toolbarContentSlot: XStandardType = + this.propertyProvider.contentSlot; + readonly toolbarContentTemplate: TemplateRef = + this.propertyProvider.contentTemplate; + //#endregion + + // + //#region Constructor ... + constructor(protected propertyProvider: XToolbarContainer) { + super(); + } + //#endregion +} + +@Injectable({ + providedIn: 'root', +}) +@Component({ + template: ` + + `, +}) +export abstract class XComponentToolbarContainerComponent + extends XBaseComponentContainerComponent + implements IXComponentToolbarContainer +{ + // + //#region Props ... + // + @Input() + showToolbar: XStandardType = this.propertyProvider.showToolbar; + + // + @Input() + toolbarActionProvider: Subject = + this.propertyProvider.toolbarActionProvider; + + @Input() + toolbarCssClass: XStandardType> = + this.propertyProvider.toolbarCssClass; + + // + @Input() + toolbarColor: XStandardType = + this.propertyProvider.toolbarColor; + + // + @Input() + toolbarShowProgressBar: XStandardType = + this.propertyProvider.toolbarShowProgressBar; + + @Input() + toolbarProgressBarColor: XStandardType = + this.propertyProvider.toolbarProgressBarColor; + + // + @Input() + toolbarShowTitle: XStandardType = + this.propertyProvider.toolbarShowTitle; + + @Input() + toolbarTitleSlot: XStandardType = + this.propertyProvider.toolbarTitleSlot; + + @Input() + toolbarTitle: XStandardType = this.propertyProvider.toolbarTitle; + + // + @Input() + toolbarShowSubTitle: XStandardType = + this.propertyProvider.toolbarShowSubTitle; + + @Input() + toolbarSubTitle: XStandardType = + this.propertyProvider.toolbarSubTitle; + + // + @Input() + toolbarShowLogo: XStandardType = + this.propertyProvider.toolbarShowLogo; + + @Input() + toolbarLogoSlot: XStandardType = + this.propertyProvider.toolbarLogoSlot; + + @Input() + toolbarLogoUrl: XStandardType = this.propertyProvider.toolbarLogoUrl; + + // + @Input() + toolbarHasBack: XStandardType = this.propertyProvider.toolbarHasBack; + + @Input() + toolbarShowBack: XStandardType = + this.propertyProvider.toolbarShowBack; + + @Input() + toolbarBackSlot: XStandardType = + this.propertyProvider.toolbarBackSlot; + + @Input() + toolbarBackHandler?: () => XStandardType = + this.propertyProvider.toolbarBackHandler; + + @Input() + toolbarDefaultHref: string = this.propertyProvider.toolbarDefaultHref; + + // + @Input() + toolbarHasMenu: XStandardType = this.propertyProvider.toolbarHasMenu; + + @Input() + toolbarShowMenu: XStandardType = + this.propertyProvider.toolbarShowMenu; + + @Input() + toolbarAutoHideMenu: XStandardType = + this.propertyProvider.toolbarAutoHideMenu; + + @Input() + toolbarMenuSlot: XStandardType = + this.propertyProvider.toolbarMenuSlot; + + // + @Input() + toolbarStartSlotButtonsTemplate: TemplateRef = + this.propertyProvider.toolbarStartSlotButtonsTemplate; + + @Input() + toolbarEndSlotButtonsTemplate: TemplateRef = + this.propertyProvider.toolbarEndSlotButtonsTemplate; + + // + @Input() + toolbarContentSlot: XStandardType = + this.propertyProvider.toolbarContentSlot; + + @Input() + toolbarContentTemplate: TemplateRef = + this.propertyProvider.toolbarContentTemplate; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XComponentToolbarContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXComponentToolbarContainer + ): Observable { + return forkJoin({ + // + showToolbar: this.getValue( + container ? container.showToolbar : this.showToolbar + ), + // + toolbarCssClass: this.getValue( + container ? container.toolbarCssClass : this.toolbarCssClass + ), + toolbarColor: this.getValue( + container ? container.toolbarColor : this.toolbarColor + ), + toolbarShowProgressBar: this.getValue( + container + ? container.toolbarShowProgressBar + : this.toolbarShowProgressBar + ), + toolbarProgressBarColor: this.getValue( + container + ? container.toolbarProgressBarColor + : this.toolbarProgressBarColor + ), + toolbarShowTitle: this.getValue( + container ? container.toolbarShowTitle : this.toolbarShowTitle + ), + toolbarTitleSlot: this.getValue( + container ? container.toolbarTitleSlot : this.toolbarTitleSlot + ), + toolbarTitle: this.getValue( + container ? container.toolbarTitle : this.toolbarTitle + ), + toolbarShowSubTitle: this.getValue( + container ? container.toolbarShowSubTitle : this.toolbarShowSubTitle + ), + toolbarSubTitle: this.getValue( + container ? container.toolbarSubTitle : this.toolbarSubTitle + ), + toolbarShowLogo: this.getValue( + container ? container.toolbarShowLogo : this.toolbarShowLogo + ), + toolbarLogoSlot: this.getValue( + container ? container.toolbarLogoSlot : this.toolbarLogoSlot + ), + toolbarLogoUrl: this.getValue( + container ? container.toolbarLogoUrl : this.toolbarLogoUrl + ), + toolbarHasBack: this.getValue( + container ? container.toolbarHasBack : this.toolbarHasBack + ), + toolbarShowBack: this.getValue( + container ? container.toolbarShowBack : this.toolbarShowBack + ), + toolbarBackSlot: this.getValue( + container ? container.toolbarBackSlot : this.toolbarBackSlot + ), + toolbarBackHandler: this.getValue( + container ? container.toolbarBackHandler : this.toolbarBackHandler + ), + toolbarDefaultHref: this.getValue( + container ? container.toolbarDefaultHref : this.toolbarDefaultHref + ), + toolbarHasMenu: this.getValue( + container ? container.toolbarHasMenu : this.toolbarHasMenu + ), + toolbarShowMenu: this.getValue( + container ? container.toolbarShowMenu : this.toolbarShowMenu + ), + toolbarAutoHideMenu: this.getValue( + container ? container.toolbarAutoHideMenu : this.toolbarAutoHideMenu + ), + toolbarMenuSlot: this.getValue( + container ? container.toolbarMenuSlot : this.toolbarMenuSlot + ), + toolbarStartSlotButtonsTemplate: this.getValue( + container + ? container.toolbarStartSlotButtonsTemplate + : this.toolbarStartSlotButtonsTemplate + ), + toolbarEndSlotButtonsTemplate: this.getValue( + container + ? container.toolbarEndSlotButtonsTemplate + : this.toolbarEndSlotButtonsTemplate + ), + toolbarContentSlot: this.getValue( + container ? container.toolbarContentSlot : this.toolbarContentSlot + ), + toolbarContentTemplate: this.getValue( + container + ? container.toolbarContentTemplate + : this.toolbarContentTemplate + ), + }).pipe( + map((data) => { + return { + // + ...data, + + // + toolbarActionProvider: container + ? container.toolbarActionProvider + : this.toolbarActionProvider, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-progress-bar\x-progress-bar.component.html + +
+ +
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-progress-bar\x-progress-bar.component.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-progress-bar\x-progress-bar.component.ts + +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { XProgressBarAction, XProgressBarActionModel, XProgressBarContainerComponent, XProgressBarType } from './x-progress-bar.typings'; +import { Subject, Subscription } from 'rxjs'; +import { nameof } from 'x-framework-core'; + +@Component({ + selector: 'x-progress-bar', + templateUrl: './x-progress-bar.component.html', + styleUrls: ['./x-progress-bar.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XProgressBarComponent extends XProgressBarContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + readonly PresentTypes = Object.assign({}, XProgressBarType); + //#endregion + + // + //#region LifeCycle ... + onChange (changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region ActionProvider Changed ... + // + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + } + + onDestroy () { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + //#endregion + + // + //#region Handlers ... + registerViewHandlers () { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region Reading Query Params ... + //#endregion + + // + //#region Providers ... + //#endregion + + // + //#region Private ... + // + //#region Action Provider ... + private registerActionProvider () { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + // Handle Action Provider ... + this.ACTION_PROVIDER_SUBSCRIPTION = this.applyTakeUntilWrapper( + this.actionProvider.asObservable() + ).subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XProgressBarAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider () { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-progress-bar\x-progress-bar.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XProgressBarComponent } from './x-progress-bar.component'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XProgressBarComponent], + imports: [CommonModule, XBaseComponentModule], + exports: [XBaseComponentModule, XProgressBarComponent], +}) +export class XProgressBarModule { } + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-progress-bar\x-progress-bar.typings.ts + +import { + Subject, + forkJoin, + Observable, +} from 'rxjs'; +import { + Input, + NgZone, + Inject, + Renderer2, + Component, + Injectable, + ElementRef, + ChangeDetectorRef, +} from '@angular/core'; +import { + XStandardType, + XOneOrManyType, + XColorIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { map } from 'rxjs/operators'; +import { + XBaseContainer, + IXBaseContainer, + XBaseComponentContainer, + XBaseContainerComponent, + IXBaseComponentContainer, +} from '../classes/i-x-base.container'; +import { XManagerService } from 'x-framework-services'; +import { ViewportRuler } from '@angular/cdk/scrolling'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +// +//#region Prop Definitions ... +export enum XProgressBarType { + Buffer = 'buffer', + Determinate = 'determinate', + Indeterminate = 'indeterminate', +} + +export type XProgressBarTypeIdentifier = XProgressBarType | string; +//#endregion + +// +//#region Action Provider +export enum XProgressBarAction { + Refresh = 'refresh', +} + +export type XProgressBarActionIdentifier = XProgressBarAction | string; + + +export interface XProgressBarActionModel + extends XBaseActionModel { } +//#endregion + +// +//#region Container ... +export interface IXProgressBarContainer extends IXBaseContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + color: XStandardType; + + // + type: XStandardType; + + // + value: XStandardType; + buffer: XStandardType; +} + +@Injectable({ + providedIn: 'root', +}) +export class XProgressBarContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider = new Subject(); + readonly cssClass: XStandardType>; + + // + readonly color: XStandardType = XColorWithBrightness.Warning; + + // + readonly type: XStandardType = XProgressBarType.Indeterminate; + + // + readonly value: XStandardType; + readonly buffer: XStandardType; + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XProgressBarContainerComponent + extends XBaseContainerComponent + implements IXProgressBarContainer { + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + color: XStandardType = + this.propertyProvider.color; + + // + @Input() + type: XStandardType = + this.propertyProvider.type; + + // + @Input() + value: XStandardType = + this.propertyProvider.value; + + @Input() + buffer: XStandardType = + this.propertyProvider.buffer; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XProgressBarContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps ( + container?: IXProgressBarContainer + ): Observable { + return forkJoin({ + // + cssClass: this.getValue(container ? container.cssClass : this.cssClass), + + // + color: this.getValue(container ? container.color : this.color), + + // + type: this.getValue(container ? container.type : this.type), + + // + value: this.getValue(container ? container.value : this.value), + buffer: this.getValue(container ? container.buffer : this.buffer), + }).pipe( + map((data) => { + return { + // + ...data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + }; + }) + ); + } + //#endregion +} +//#endregion + +// +//#region ComponentContainer ... +type TXComponentProgressBarContainer = { + [TKey in keyof IXProgressBarContainer as `progressBar${Capitalize< + string & TKey + >}`]: IXProgressBarContainer[TKey]; +}; + +export interface IXComponentProgressBarContainer + extends TXComponentProgressBarContainer, + IXBaseComponentContainer { + showProgressBar: XStandardType; +} + +@Injectable({ + providedIn: 'root', +}) +export class XComponentProgressBarContainer + extends XBaseComponentContainer + implements Readonly { + // + //#region Props ... + // + readonly showProgressBar: XStandardType = false; + + // + readonly progressBarActionProvider = + this.propertyProvider.actionProvider; + readonly progressBarCssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + readonly progressBarColor: XStandardType = + this.propertyProvider.color; + + // + readonly progressBarType: XStandardType = + this.propertyProvider.type; + + // + readonly progressBarValue: XStandardType = + this.propertyProvider.value; + readonly progressBarBuffer: XStandardType = + this.propertyProvider.buffer; + //#endregion + + // + //#region Constructor ... + constructor(protected propertyProvider: XProgressBarContainer) { + super(); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-screen-capture\x-screen-capture.component.html + +
+ +
+ +
+ + +
+
+
+
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-screen-capture\x-screen-capture.component.scss + +.x-overlay { + top: 0px; + left: 0px; + width: 100vw; + height: 100vh; + position: absolute; + + &.enabled { + background-color: rgba(204, 204, 204, 0.6); + } +} + +.x-rectangle { + position: absolute; + + &.drawing { + border: 1px solid #ff0000; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-screen-capture\x-screen-capture.component.ts + +import { + ViewChild, + Component, + ElementRef, + ChangeDetectionStrategy, +} from '@angular/core'; +import { nameof } from 'x-framework-core'; +import { XScreenCaptureDimensions } from './x-screen-capture.typings'; +import { XScreenCaptureContainerComponent } from './x-screen-capture.container'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-screen-capture', + templateUrl: './x-screen-capture.component.html', + styleUrls: ['./x-screen-capture.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XScreenCaptureComponent extends XScreenCaptureContainerComponent { + // + //#region Props ... + // + rect: HTMLElement; + captureZone: HTMLElement; + + // + isDrawing = false; + + // + clippingDimensions: XScreenCaptureDimensions = { + x: 0, + y: 0, + width: 0, + height: 0, + }; + + // + mouse = { + x: 0, + y: 0, + startX: 0, + startY: 0, + }; + + // + //#region View Childs ... + // + private RECT: ElementRef; + + // + @ViewChild('rect', { static: false }) + set rectangle(v: ElementRef) { + // + this.RECT = v; + this.rect = v.nativeElement; + } + + // + get rectangle() { + return this.RECT; + } + + // + private OVERLAY: ElementRef; + + // + @ViewChild('over', { static: false }) + set overlay(v: ElementRef) { + // + this.OVERLAY = v; + this.captureZone = v.nativeElement; + } + + // + get overlay() { + return this.OVERLAY; + } + + // + private CONTENT: ElementRef; + + // + @ViewChild('content', { static: false }) + set content(v: ElementRef) { + this.CONTENT = v; + } + + // + get content() { + return this.CONTENT; + } + //#endregion + //#endregion + + // + //#region LifeCycles ... + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + const isEnableChanged = changeKeys.includes( + nameof('enable') + ); + if (isEnableChanged) { + // + const enable = await this.getValueAsync(this.enable); + this.reAssignElements(enable); + this.detectChanges(); + } + } + + async afterViewInit() { + super.afterViewInit(); + + // + this.reAssignElements(); + } + //#endregion + + // + //#region UI Handlers ... + async handleMouseMoved(event: MouseEvent) { + this.drawRect(event); + } + + async handleMouseStarted(event: MouseEvent) { + this.startCapture(event); + } + + async handleMouseFinished(event: MouseEvent) { + this.endCapture(event); + } + //#endregion + + // + //#region Private ... + private reAssignElements(enabled?: boolean) { + // + if (!this.OVERLAY || !this.RECT || !this.CONTENT) { + return; + } + + // + this.rect = this.RECT.nativeElement; + this.captureZone = this.OVERLAY.nativeElement; + + // + if (enabled === true) { + this.captureZone.style.cursor = 'crosshair'; + } else { + this.captureZone.style.cursor = 'default'; + } + } + + private setRectangle() { + // + this.rect.style.left = this.clippingDimensions.x + 'px'; + this.rect.style.top = this.clippingDimensions.y + 'px'; + this.rect.style.width = this.clippingDimensions.width + 'px'; + this.rect.style.height = this.clippingDimensions.height + 'px'; + } + + private startCapture(e: any) { + // + if (!this.captureZone) { + // + this.reAssignElements(); + } + + // + this.setMousePosition(e); + + // + this.isDrawing = true; + this.detectChanges(); + + // + this.mouse.startX = this.mouse.x; + this.mouse.startY = this.mouse.y; + this.clippingDimensions = { + x: this.mouse.x, + y: this.mouse.y, + width: 0, + height: 0, + }; + + // + this.setRectangle(); + this.captureZone.style.cursor = 'crosshair'; + this.detectChanges(); + } + + private drawRect(e: any) { + // + if (!this.captureZone) { + return; + } + + // + if (this.isDrawing) { + // + this.setMousePosition(e); + + // + this.clippingDimensions = { + x: + this.mouse.x - this.mouse.startX < 0 + ? this.mouse.x + : this.mouse.startX, + y: + this.mouse.y - this.mouse.startY < 0 + ? this.mouse.y + : this.mouse.startY, + width: Math.abs(this.mouse.x - this.mouse.startX), + height: Math.abs(this.mouse.y - this.mouse.startY), + }; + + // + this.setRectangle(); + } + } + + private setMousePosition(e: any) { + // + if (!this.captureZone) { + return; + } + + // + // tslint:disable-next-line: deprecation + const ev = e || window.event; // Moz || IE + if (ev.pageX) { + // Moz + // + this.mouse.x = ev.pageX + window.pageXOffset; + this.mouse.y = ev.pageY + window.pageYOffset; + } else if (ev.clientX) { + // IE + // + this.mouse.x = ev.clientX + document.body.scrollLeft; + this.mouse.y = ev.clientY + document.body.scrollTop; + } + } + + private async endCapture(ev: MouseEvent) { + // + if (!ev || !this.captureZone) { + return; + } + + // + this.captureZone.style.cursor = 'default'; + this.isDrawing = false; + this.detectChanges(); + + // + const captureDocument = document.body; + + // + const topOffset = await this.getValueAsync(this.topOffset); + if (topOffset !== 0) { + this.clippingDimensions = { + ...this.clippingDimensions, + y: this.clippingDimensions.y + topOffset, + }; + } + + // + const bottomOffset = await this.getValueAsync(this.bottomOffset); + if (bottomOffset !== 0) { + this.clippingDimensions = { + ...this.clippingDimensions, + y: this.clippingDimensions.y + bottomOffset, + }; + } + + // + const canDownload = await this.getOptionalValueAsync( + this.downloadCapturedImage + ); + if (canDownload === true) { + // + const blob = await this.screenCaptureService.captureAndDownload( + captureDocument, + null, + this.clippingDimensions + ); + + // + this.imageCaptured.emit(blob); + } else { + // + const blob = await this.screenCaptureService.capture( + captureDocument, + null, + this.clippingDimensions + ); + + // + this.imageCaptured.emit(blob); + } + + // + this.clippingDimensions = { + x: 0, + y: 0, + width: 0, + height: 0, + }; + + // + this.setRectangle(); + + // + this.reAssignElements(); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-screen-capture\x-screen-capture.container.ts + +import { + Input, + Inject, + NgZone, + Output, + Renderer2, + Component, + ElementRef, + Injectable, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { map } from 'rxjs/operators'; +import { + XBaseContainer, + IXBaseContainer, + XBaseContainerComponent, +} from '../classes/i-x-base.container'; +import { forkJoin, Observable } from 'rxjs'; +import { XStandardType } from 'x-framework-core'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XScreenCaptureService } from './x-screen-capture.service'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +// +//#region Container ... +export interface IXScreenCaptureContainer extends IXBaseContainer { + enable: XStandardType; + topOffset: XStandardType; + bottomOffset: XStandardType; + downloadCapturedImage: XStandardType; + imageCaptured: EventEmitter; +} + +@Injectable({ + providedIn: 'root', +}) +export class XScreenCaptureContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + readonly enable: XStandardType = false; + readonly topOffset: XStandardType = 0; + readonly bottomOffset: XStandardType = 0; + readonly downloadCapturedImage: XStandardType = true; + readonly imageCaptured: EventEmitter = new EventEmitter< + string | Blob + >(); + //#endregion +} + +@Injectable({ + providedIn: 'root', +}) +@Component({ + template: ` + + `, +}) +export abstract class XScreenCaptureContainerComponent + extends XBaseContainerComponent + implements IXScreenCaptureContainer +{ + // + //#region Props ... + @Input() + enable: XStandardType = this.propertyProvider.enable; + + @Input() + topOffset: XStandardType = this.propertyProvider.topOffset; + + @Input() + bottomOffset: XStandardType = this.propertyProvider.bottomOffset; + + @Input() + downloadCapturedImage: XStandardType = + this.propertyProvider.downloadCapturedImage; + + @Output() + imageCaptured: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + public screenCaptureService: XScreenCaptureService, + protected propertyProvider: XScreenCaptureContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXScreenCaptureContainer + ): Observable { + return forkJoin({ + // + enable: this.getValue(container ? container.enable : this.enable), + topOffset: this.getValue( + container ? container.topOffset : this.topOffset + ), + bottomOffset: this.getValue( + container ? container.bottomOffset : this.bottomOffset + ), + downloadCapturedImage: this.getValue( + container ? container.downloadCapturedImage : this.downloadCapturedImage + ), + }).pipe( + map((data) => { + return { + // + ...data, + + // + imageCaptured: this.imageCaptured, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-screen-capture\x-screen-capture.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; +import { XScreenCaptureComponent } from './x-screen-capture.component'; + +@NgModule({ + declarations: [XScreenCaptureComponent], + imports: [CommonModule, XBaseComponentModule], + exports: [XBaseComponentModule, XScreenCaptureComponent], +}) +export class XScreenCaptureModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-screen-capture\x-screen-capture.service.ts + +import { + XMimeType, + blobToImage, + XBaseService, + generateUuid, + canvasToBlob, + dataUrlToImage, + canvasToDataUrl, + clipImageToCanvas, + downloadArrayBufferedFile, +} from 'x-framework-core'; +import { + XScreenCaptureType, + XScreenCaptureOptions, + XScreenCaptureDimensions, + XScreenCaptureTypeIdentifier, +} from './x-screen-capture.typings'; +import * as domToImage from 'dom-to-image'; +import { Injectable, Inject } from '@angular/core'; +import { XNotificationService } from 'x-framework-services'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +@Injectable({ + providedIn: 'root', +}) +export class XScreenCaptureService extends XBaseService { + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + private notificationService: XNotificationService + ) { + super(config); + } + //#endregion + + /** + * Capture Element with Options as specified type ... + * + * @param screen an instance of HTMLElement ... + */ + async capture( + screen: HTMLElement, + type?: XScreenCaptureTypeIdentifier, + options?: XScreenCaptureOptions, + clippingDimensions?: XScreenCaptureDimensions + ) { + // + if (!screen) { + return null; + } + + // + if (!type) { + type = XScreenCaptureType.PNG; + } + + // + if (!options) { + options = {}; + } + + // + this.playCaptureNotification(); + + // + try { + // + switch (type) { + // + // PNG ... + case XScreenCaptureType.PNG: + // + const pngDataUrl = await domToImage.default.toPng(screen, options); + if (!clippingDimensions) { + return pngDataUrl; + } else { + // + const pngImage = await dataUrlToImage(pngDataUrl); + if (!pngImage) { + return null; + } + + // + const pngClippedCanvas = await clipImageToCanvas( + pngImage, + clippingDimensions.x, + clippingDimensions.y, + clippingDimensions.width, + clippingDimensions.height + ); + if (!pngClippedCanvas) { + return null; + } + + // + return canvasToDataUrl(pngClippedCanvas, XMimeType.ImagePNG); + } + + // + // JPeg ... + case XScreenCaptureType.JPeg: + // + const jPegDataUrl = await domToImage.default.toJpeg(screen, options); + if (!clippingDimensions) { + return jPegDataUrl; + } else { + // + const jPegImage = await dataUrlToImage(jPegDataUrl); + if (!jPegImage) { + return null; + } + + // + const jPegClippedCanvas = await clipImageToCanvas( + jPegImage, + clippingDimensions.x, + clippingDimensions.y, + clippingDimensions.width, + clippingDimensions.height + ); + if (!jPegClippedCanvas) { + return null; + } + + // + return canvasToDataUrl(jPegClippedCanvas, XMimeType.ImageJPEG); + } + + // + // SVG ... + case XScreenCaptureType.SVG: + // + const svgDataUrl = await domToImage.default.toSvg(screen, options); + if (!clippingDimensions) { + return svgDataUrl; + } else { + // + const svgImage = await dataUrlToImage(svgDataUrl); + if (!svgImage) { + return null; + } + + // + const svgClippedCanvas = await clipImageToCanvas( + svgImage, + clippingDimensions.x, + clippingDimensions.y, + clippingDimensions.width, + clippingDimensions.height + ); + if (!svgClippedCanvas) { + return null; + } + + // + return canvasToDataUrl(svgClippedCanvas, XMimeType.ImageSVG); + } + + // + // To Blob ... + case XScreenCaptureType.Blob: + // + const blob = await domToImage.default.toBlob(screen, options); + if (!clippingDimensions) { + return blob; + } else { + // + const blobImage = await blobToImage(blob); + if (!blobImage) { + return null; + } + + // + const blobClippedCanvas = await clipImageToCanvas( + blobImage, + clippingDimensions.x, + clippingDimensions.y, + clippingDimensions.width, + clippingDimensions.height + ); + if (!blobClippedCanvas) { + return null; + } + + // + return await canvasToBlob(blobClippedCanvas); + } + } + } catch {} + + // + return null; + } + + /** + * Capture Element with Options as Blob object and Download it ... + * + * @param screen an instance of HTMLElement ... + * @param fileName downloaded file name ... + */ + async captureAndDownload( + screen: HTMLElement, + options?: XScreenCaptureOptions, + clippingDimensions?: XScreenCaptureDimensions, + fileName?: string + ) { + // + this.playCaptureNotification(); + + // + const fName = fileName ? fileName : `${generateUuid()}.png`; + + // + try { + // + const blob = await domToImage.default.toBlob(screen, options); + + // + if (!blob) { + return null; + } + + // + if (!clippingDimensions) { + // + downloadArrayBufferedFile(fName, blob); + return blob; + } else { + // + const blobImage = await blobToImage(blob); + if (!blobImage) { + return null; + } + + // + const blobClippedCanvas = await clipImageToCanvas( + blobImage, + clippingDimensions.x, + clippingDimensions.y, + clippingDimensions.width, + clippingDimensions.height + ); + if (!blobClippedCanvas) { + return null; + } + + // + const clippedBlob = await canvasToBlob(blobClippedCanvas); + if (!clippedBlob) { + return null; + } + + // + downloadArrayBufferedFile(fName, clippedBlob); + return clippedBlob; + } + } catch {} + + // + return null; + } + + /** + * play Capture Notification Sound ... + */ + playCaptureNotification() { + this.notificationService.playCaptureNotification(); + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-screen-capture\x-screen-capture.typings.ts + +import { Options } from 'dom-to-image'; + +// tslint:disable-next-line:no-empty-interface +export interface XScreenCaptureOptions extends Options {} + +export enum XScreenCaptureType { + SVG = 'svg', + PNG = 'png', + Blob = 'blob', + JPeg = 'jpeg', +} + +export type XScreenCaptureTypeIdentifier = XScreenCaptureType | string; + +export interface XScreenCaptureDimensions { + x: number; + y: number; + width: number; + height: number; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-search-bar\x-search-bar.component.html + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-search-bar\x-search-bar.component.scss + +:host { + --clear-icon-color: var(--x-color-danger); + --cancel-icon-color: var(--x-color-primary); + --search-icon-color: var(--x-color-primary); +} + +:host::ng-deep { + .searchbar-clear-icon { + color: var(--clear-icon-color) !important; + } + + .searchbar-search-icon { + color: var(--search-icon-color) !important; + } + + .searchbar-cancel-button { + color: var(--cancel-icon-color) !important; + } +} + +.x-search-bar { + padding: { + left: 3px; + right: 3px; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-search-bar\x-search-bar.component.ts + +import { + nameof, + parseXColor, + isNullOrEmptyString, + getXColorCssVariableName, +} from 'x-framework-core'; +import { + XSearchBarEvent, + XSearchBarAction, + XSearchBarCssVars, + XSearchBarActionModel, + XSearchBarContainerComponent, +} from './x-search-bar.typings'; +import { filter } from 'rxjs/operators'; +import { Subject, Subscription } from 'rxjs'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-search-bar', + templateUrl: './x-search-bar.component.html', + styleUrls: ['./x-search-bar.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XSearchBarComponent extends XSearchBarContainerComponent { + // + //#region Props ... + // + protected applyThemeColors: boolean = true; + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + readonly SearchBarEvents = Object.assign({}, XSearchBarEvent); + //#endregion + + // + //#region LifeCycles ... + afterViewInit() { + super.afterViewInit(); + + // + // Handle Start Value for start searching ... + this.getValue(this.value) + .pipe(filter((query) => !isNullOrEmptyString(query))) + .subscribe((query) => { + this.queryChange.emit(query); + }); + } + + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region Action Provider ... + const isActionProviderChaged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChaged) { + this.registerActionProvider(); + } + //#endregion + + // + //#region Colors ... + // + const isCancelButtonIconColorChanged = changeKeys.includes( + nameof('cancelButtonIconColor') + ); + if (isCancelButtonIconColorChanged) { + await this.applyCancelButtonIconColor(); + } + + // + const isClearButtonIconColorChanged = changeKeys.includes( + nameof('clearButtonIconColor') + ); + if (isClearButtonIconColorChanged) { + await this.applyClearButtonIconColor(); + } + + // + const isSearchButtonIconColorChanged = changeKeys.includes( + nameof('searchButtonIconColor') + ); + if (isSearchButtonIconColorChanged) { + await this.applySearchButtonIconColor(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + //#endregion + + // + //#region Register Handlers ... + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region UI Providers ... + //#endregion + + // + //#region UI Handlers ... + // + handleSearchQueryValueChange(event: CustomEvent | any) { + // + const query = + event && event.detail && event.detail.value + ? event.detail.value.toString().trim().toLowerCase() + : ''; + + // + this.queryChange.emit(query); + } + //#endregion + + // + //#region Actions ... + //#endregion + + // + //#region Private ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XSearchBarAction.ClearValue: + // + this.value = ''; + this.detectChanges(); + break; + + // + case XSearchBarAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + + // + //#region Color Appliers ... + private async applyCancelButtonIconColor() { + // + const color = await this.getValueAsync(this.cancelButtonIconColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XSearchBarCssVars.CancelIconColor, `var(${bgColor})`); + this.setCssVar( + XSearchBarCssVars.CancelIconColor, + `var(${bgColor})`, + document.body + ); + + // + this.detectChanges(); + } + + private async applyClearButtonIconColor() { + // + const color = await this.getValueAsync(this.clearButtonIconColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XSearchBarCssVars.ClearIconColor, `var(${bgColor})`); + this.setCssVar( + XSearchBarCssVars.ClearIconColor, + `var(${bgColor})`, + document.body + ); + + // + this.detectChanges(); + } + + private async applySearchButtonIconColor() { + // + const color = await this.getValueAsync(this.searchButtonIconColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XSearchBarCssVars.SearchIconColor, `var(${bgColor})`); + this.setCssVar( + XSearchBarCssVars.SearchIconColor, + `var(${bgColor})`, + document.body + ); + + // + this.detectChanges(); + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-search-bar\x-search-bar.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XSearchBarComponent } from './x-search-bar.component'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XSearchBarComponent], + imports: [ + CommonModule, + XBaseComponentModule, + ], + exports: [ + CommonModule, + XSearchBarComponent, + XBaseComponentModule, + ], +}) +export class XSearchBarModule { } + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-search-bar\x-search-bar.typings.ts + +import { + XResourceIDs, + XStandardType, + XOneOrManyType, + XColorIdentifier, + XResourceIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { + Input, + Output, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { map } from 'rxjs/operators'; +import { + XBaseContainer, + IXBaseContainer, + XBaseComponentContainer, + XBaseContainerComponent, + IXBaseComponentContainer, + XBaseComponentContainerComponent, +} from '../classes/i-x-base.container'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XIconIdentifier } from '../x-icon/x-icon.typings'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export enum XSearchBarAction { + ClearValue = 'clear_value', + Refresh = 'refresh', +} + +export type XSearchBarActionIdentifier = XSearchBarAction | string; + +export interface XSearchBarActionModel + extends XBaseActionModel {} + +export enum XSearchBarType { + None = 'none', + Text = 'text', + Tel = 'tel', + Url = 'url', + Email = 'email', + Numeric = 'numeric', + Decimal = 'decimal', + Search = 'search', +} + +export type XSearchBarTypeIdentifier = XSearchBarType | string; + +export enum XSearchBarEnterKeyHint { + Done = 'done', + Go = 'go', + Next = 'next', + Previous = 'previous', + Search = 'search', + Send = 'send', + Enter = 'enter', +} + +export type XSearchBarEnterKeyHintIdentifier = XSearchBarEnterKeyHint | string; + +export enum XSearchBarShowButton { + Always = 'always', + Focus = 'focus', + Never = 'never', +} + +export type XSearchBarShowButtonIdentifier = XSearchBarShowButton | string; + +export enum XSearchBarCssVars { + // + ClearIconColor = 'clear-icon-color', + CancelIconColor = 'cancel-icon-color', + SearchIconColor = 'search-icon-color', +} + +export type XSearchBarCssVarsIdentifier = XSearchBarCssVars | string; + +export enum XSearchBarEvent { + FOCUS = 'focus', + BLUR = 'blur', + CANCEL = 'cancel', + CLEAR = 'clear', + INPUT = 'input', +} + +export type XSearchBarEventIdentifier = XSearchBarEvent | string; + +// +//#region Container ... +export interface IXSearchBarContainer extends IXBaseContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + color: XStandardType; + foregroundColor: XStandardType; + + // + searchInputColor: XStandardType; + foregroundSearchInputColor: XStandardType; + + // + type: XStandardType; + title: XStandardType; + value: XStandardType; + enterKeyHint: XStandardType; + keyboardType: XStandardType; + spellcheck: XStandardType; + debounce: XStandardType; + + // + showCancelButton: XStandardType; + cancelButtonIcon: XStandardType; + cancelButtonTitle: XStandardType; + cancelButtonIconColor: XStandardType; + + // + showClearButton: XStandardType; + clearButtonIcon: XStandardType; + clearButtonIconColor: XStandardType; + + // + searchButtonIcon: XStandardType; + searchButtonIconColor: XStandardType; + + // + queryChange: EventEmitter; + onEvent: EventEmitter; +} + +@Injectable({ + providedIn: 'root', +}) +export class XSearchBarContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider: Subject = + new Subject(); + readonly cssClass: XStandardType>; + + // + readonly color: XStandardType = XColorWithBrightness.Light; + readonly foregroundColor: XStandardType = false; + + // + readonly searchInputColor: XStandardType = + XColorWithBrightness.Light; + readonly foregroundSearchInputColor: XStandardType = false; + + // + readonly type: XStandardType = + XSearchBarType.Search; + readonly title: XStandardType = XResourceIDs.search; + readonly value: XStandardType; + readonly enterKeyHint: XStandardType = + XSearchBarEnterKeyHint.Search; + readonly keyboardType: XStandardType = + XSearchBarType.Search; + readonly spellcheck: XStandardType = false; + readonly debounce: XStandardType = + this.config.defaultSearchDebounceTime || 500; + + // + readonly showCancelButton: XStandardType = + XSearchBarShowButton.Never; + readonly cancelButtonIcon: XStandardType; + readonly cancelButtonTitle: XStandardType; + readonly cancelButtonIconColor: XStandardType = + XColorWithBrightness.Primary; + + // + readonly showClearButton: XStandardType = + XSearchBarShowButton.Always; + readonly clearButtonIcon: XStandardType; + readonly clearButtonIconColor: XStandardType = + XColorWithBrightness.Danger; + + // + readonly searchButtonIcon: XStandardType; + readonly searchButtonIconColor: XStandardType = + XColorWithBrightness.Primary; + + // + readonly queryChange: EventEmitter = new EventEmitter(); + readonly onEvent: EventEmitter = + new EventEmitter(); + //#endregion + + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig + ) { + super(); + } + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XSearchBarContainerComponent + extends XBaseContainerComponent + implements IXSearchBarContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + color: XStandardType = this.propertyProvider.color; + + @Input() + foregroundColor: XStandardType = + this.propertyProvider.foregroundColor; + + // + @Input() + searchInputColor: XStandardType = + this.propertyProvider.searchInputColor; + + @Input() + foregroundSearchInputColor: XStandardType = + this.propertyProvider.foregroundSearchInputColor; + + // + @Input() + type: XStandardType = this.propertyProvider.type; + + @Input() + title: XStandardType = this.propertyProvider.title; + + @Input() + value: XStandardType = this.propertyProvider.value; + + @Input() + enterKeyHint: XStandardType = + this.propertyProvider.enterKeyHint; + + @Input() + keyboardType: XStandardType = + this.propertyProvider.keyboardType; + + @Input() + spellcheck: XStandardType = this.propertyProvider.spellcheck; + + @Input() + debounce: XStandardType = this.propertyProvider.debounce; + + // + @Input() + showCancelButton: XStandardType = + this.propertyProvider.showCancelButton; + + @Input() + cancelButtonIcon: XStandardType = + this.propertyProvider.cancelButtonIcon; + + @Input() + cancelButtonTitle: XStandardType = + this.propertyProvider.cancelButtonTitle; + + @Input() + cancelButtonIconColor: XStandardType = + this.propertyProvider.cancelButtonIconColor; + + // + @Input() + showClearButton: XStandardType = + this.propertyProvider.showClearButton; + + @Input() + clearButtonIcon: XStandardType = + this.propertyProvider.clearButtonIcon; + + @Input() + clearButtonIconColor: XStandardType = + this.propertyProvider.clearButtonIconColor; + + // + @Input() + searchButtonIcon: XStandardType = + this.propertyProvider.searchButtonIcon; + + @Input() + searchButtonIconColor: XStandardType = + this.propertyProvider.searchButtonIconColor; + + // + @Output() + queryChange: EventEmitter = new EventEmitter(); + + @Output() + onEvent: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XSearchBarContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXSearchBarContainer + ): Observable { + return forkJoin({ + // + cssClass: this.getArrayValue( + container ? container.cssClass : this.cssClass + ), + + // + color: this.getValue(container ? container.color : this.color), + foregroundColor: this.getValue( + container ? container.foregroundColor : this.foregroundColor + ), + searchInputColor: this.getValue( + container ? container.searchInputColor : this.searchInputColor + ), + foregroundSearchInputColor: this.getValue( + container + ? container.foregroundSearchInputColor + : this.foregroundSearchInputColor + ), + type: this.getValue(container ? container.type : this.type), + title: this.getValue(container ? container.title : this.title), + value: this.getValue(container ? container.value : this.value), + enterKeyHint: this.getValue( + container ? container.enterKeyHint : this.enterKeyHint + ), + keyboardType: this.getValue( + container ? container.keyboardType : this.keyboardType + ), + spellcheck: this.getValue( + container ? container.spellcheck : this.spellcheck + ), + debounce: this.getValue(container ? container.debounce : this.debounce), + showCancelButton: this.getValue( + container ? container.showCancelButton : this.showCancelButton + ), + cancelButtonIcon: this.getValue( + container ? container.cancelButtonIcon : this.cancelButtonIcon + ), + cancelButtonTitle: this.getValue( + container ? container.cancelButtonTitle : this.cancelButtonTitle + ), + cancelButtonIconColor: this.getValue( + container ? container.cancelButtonIconColor : this.cancelButtonIconColor + ), + showClearButton: this.getValue( + container ? container.showCancelButton : this.showCancelButton + ), + clearButtonIcon: this.getValue( + container ? container.clearButtonIcon : this.clearButtonIcon + ), + clearButtonIconColor: this.getValue( + container ? container.clearButtonIconColor : this.clearButtonIconColor + ), + searchButtonIcon: this.getValue( + container ? container.searchButtonIcon : this.searchButtonIcon + ), + searchButtonIconColor: this.getValue( + container ? container.searchButtonIconColor : this.searchButtonIconColor + ), + }).pipe( + map((data) => { + return { + // + ...data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + onEvent: this.onEvent, + queryChange: this.queryChange, + }; + }) + ); + } + //#endregion +} +//#endregion + +// +//#region ComponentContainer ... +type TXComponentSearchBarContainer = { + [TKey in keyof IXSearchBarContainer as `searchBar${Capitalize< + string & TKey + >}`]: IXSearchBarContainer[TKey]; +}; + +export interface IXComponentSearchBarContainer + extends TXComponentSearchBarContainer, + IXBaseComponentContainer { + // + showSearchBar: XStandardType; + + // + searchBarActionProvider: Subject; + searchBarCssClass: XStandardType>; + + // + searchBarColor: XStandardType; + searchBarForegroundColor: XStandardType; + + // + searchBarSearchInputColor: XStandardType; + searchBarForegroundSearchInputColor: XStandardType; + + // + searchBarType: XStandardType; + searchBarTitle: XStandardType; + searchBarValue: XStandardType; + searchBarEnterKeyHint: XStandardType; + searchBarKeyboardType: XStandardType; + searchBarSpellcheck: XStandardType; + searchBarDebounce: XStandardType; + + // + searchBarShowCancelButton: XStandardType; + searchBarCancelButtonIcon: XStandardType; + searchBarCancelButtonTitle: XStandardType; + searchBarCancelButtonIconColor: XStandardType; + + // + searchBarShowClearButton: XStandardType; + searchBarClearButtonIcon: XStandardType; + searchBarClearButtonIconColor: XStandardType; + + // + searchBarSearchButtonIcon: XStandardType; + searchBarSearchButtonIconColor: XStandardType; + + // + searchBarQueryChange: EventEmitter; + searchBarOnEvent: EventEmitter; +} + +@Injectable({ + providedIn: 'root', +}) +export class XComponentSearchBarContainer + extends XBaseComponentContainer + implements Readonly +{ + // + //#region Props ... + // + readonly showSearchBar: XStandardType = true; + + // + readonly searchBarActionProvider: Subject = + this.propertyProvider.actionProvider; + readonly searchBarCssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + readonly searchBarColor: XStandardType = + this.propertyProvider.color; + readonly searchBarForegroundColor: XStandardType = + this.propertyProvider.foregroundColor; + + // + readonly searchBarSearchInputColor: XStandardType = + this.propertyProvider.searchInputColor; + readonly searchBarForegroundSearchInputColor: XStandardType = + this.propertyProvider.foregroundSearchInputColor; + + // + readonly searchBarType: XStandardType = + this.propertyProvider.type; + readonly searchBarTitle: XStandardType = + this.propertyProvider.title; + readonly searchBarValue: XStandardType = this.propertyProvider.value; + readonly searchBarEnterKeyHint: XStandardType = + this.propertyProvider.enterKeyHint; + readonly searchBarKeyboardType: XStandardType = + this.propertyProvider.keyboardType; + readonly searchBarSpellcheck: XStandardType = + this.propertyProvider.spellcheck; + readonly searchBarDebounce: XStandardType = + this.propertyProvider.debounce; + + // + readonly searchBarShowCancelButton: XStandardType = + this.propertyProvider.showCancelButton; + readonly searchBarCancelButtonIcon: XStandardType = + this.propertyProvider.cancelButtonIcon; + readonly searchBarCancelButtonTitle: XStandardType = + this.propertyProvider.cancelButtonTitle; + readonly searchBarCancelButtonIconColor: XStandardType = + this.propertyProvider.cancelButtonIconColor; + + // + readonly searchBarShowClearButton: XStandardType = + this.propertyProvider.showClearButton; + readonly searchBarClearButtonIcon: XStandardType = + this.propertyProvider.clearButtonIcon; + readonly searchBarClearButtonIconColor: XStandardType = + this.propertyProvider.clearButtonIconColor; + + // + readonly searchBarSearchButtonIcon: XStandardType = + this.propertyProvider.searchButtonIcon; + readonly searchBarSearchButtonIconColor: XStandardType = + this.propertyProvider.searchButtonIconColor; + + // + readonly searchBarQueryChange: EventEmitter = + this.propertyProvider.queryChange; + readonly searchBarOnEvent: EventEmitter = + this.propertyProvider.onEvent; + //#endregion + + // + //#region Constructor ... + constructor(protected propertyProvider: XSearchBarContainer) { + super(); + } + //#endregion +} + +@Component({ + template: ``, +}) +export abstract class XComponentSearchBarContainerComponent + extends XBaseComponentContainerComponent + implements IXComponentSearchBarContainer +{ + // + //#region Props ... + // + @Input() + showSearchBar: XStandardType = this.propertyProvider.showSearchBar; + + // + @Input() + searchBarActionProvider: Subject = + this.propertyProvider.searchBarActionProvider; + + @Input() + searchBarCssClass: XStandardType> = + this.propertyProvider.searchBarCssClass; + + // + @Input() + searchBarColor: XStandardType = + this.propertyProvider.searchBarColor; + + @Input() + searchBarForegroundColor: XStandardType = + this.propertyProvider.searchBarForegroundColor; + + // + @Input() + searchBarSearchInputColor: XStandardType = + this.propertyProvider.searchBarSearchInputColor; + + @Input() + searchBarForegroundSearchInputColor: XStandardType = + this.propertyProvider.searchBarForegroundSearchInputColor; + + // + @Input() + searchBarType: XStandardType = + this.propertyProvider.searchBarType; + + @Input() + searchBarTitle: XStandardType = + this.propertyProvider.searchBarTitle; + + @Input() + searchBarValue: XStandardType = this.propertyProvider.searchBarValue; + + @Input() + searchBarEnterKeyHint: XStandardType = + this.propertyProvider.searchBarEnterKeyHint; + + @Input() + searchBarKeyboardType: XStandardType = + this.propertyProvider.searchBarKeyboardType; + + @Input() + searchBarSpellcheck: XStandardType = + this.propertyProvider.searchBarSpellcheck; + + @Input() + searchBarDebounce: XStandardType = + this.propertyProvider.searchBarDebounce; + + // + @Input() + searchBarShowCancelButton: XStandardType = + this.propertyProvider.searchBarShowCancelButton; + + @Input() + searchBarCancelButtonIcon: XStandardType = + this.propertyProvider.searchBarCancelButtonIcon; + + @Input() + searchBarCancelButtonTitle: XStandardType = + this.propertyProvider.searchBarCancelButtonTitle; + + @Input() + searchBarCancelButtonIconColor: XStandardType = + this.propertyProvider.searchBarCancelButtonIconColor; + + // + @Input() + searchBarShowClearButton: XStandardType = + this.propertyProvider.searchBarShowClearButton; + + @Input() + searchBarClearButtonIcon: XStandardType = + this.propertyProvider.searchBarClearButtonIcon; + + @Input() + searchBarClearButtonIconColor: XStandardType = + this.propertyProvider.searchBarClearButtonIconColor; + + // + @Input() + searchBarSearchButtonIcon: XStandardType = + this.propertyProvider.searchBarSearchButtonIcon; + + @Input() + searchBarSearchButtonIconColor: XStandardType = + this.propertyProvider.searchBarSearchButtonIconColor; + + // + @Output() + searchBarQueryChange: EventEmitter = new EventEmitter(); + + @Output() + searchBarOnEvent: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XComponentSearchBarContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXComponentSearchBarContainer + ): Observable { + return forkJoin({ + // + showSearchBar: this.getValue( + container ? container.showSearchBar : this.showSearchBar + ), + + // + searchBarCssClass: this.getArrayValue( + container ? container.searchBarCssClass : this.searchBarCssClass + ), + + // + searchBarColor: this.getValue( + container ? container.searchBarColor : this.searchBarColor + ), + searchBarForegroundColor: this.getValue( + container + ? container.searchBarForegroundColor + : this.searchBarForegroundColor + ), + searchBarSearchInputColor: this.getValue( + container + ? container.searchBarSearchInputColor + : this.searchBarSearchInputColor + ), + searchBarForegroundSearchInputColor: this.getValue( + container + ? container.searchBarForegroundSearchInputColor + : this.searchBarForegroundSearchInputColor + ), + searchBarType: this.getValue( + container ? container.searchBarType : this.searchBarType + ), + searchBarTitle: this.getValue( + container ? container.searchBarTitle : this.searchBarTitle + ), + searchBarValue: this.getValue( + container ? container.searchBarValue : this.searchBarValue + ), + searchBarEnterKeyHint: this.getValue( + container ? container.searchBarEnterKeyHint : this.searchBarEnterKeyHint + ), + searchBarKeyboardType: this.getValue( + container ? container.searchBarKeyboardType : this.searchBarKeyboardType + ), + searchBarSpellcheck: this.getValue( + container ? container.searchBarSpellcheck : this.searchBarSpellcheck + ), + searchBarDebounce: this.getValue( + container ? container.searchBarDebounce : this.searchBarDebounce + ), + searchBarShowCancelButton: this.getValue( + container + ? container.searchBarShowCancelButton + : this.searchBarShowCancelButton + ), + searchBarCancelButtonIcon: this.getValue( + container + ? container.searchBarCancelButtonIcon + : this.searchBarCancelButtonIcon + ), + searchBarCancelButtonTitle: this.getValue( + container + ? container.searchBarCancelButtonTitle + : this.searchBarCancelButtonTitle + ), + searchBarCancelButtonIconColor: this.getValue( + container + ? container.searchBarCancelButtonIconColor + : this.searchBarCancelButtonIconColor + ), + searchBarShowClearButton: this.getValue( + container + ? container.searchBarShowClearButton + : this.searchBarShowClearButton + ), + searchBarClearButtonIcon: this.getValue( + container + ? container.searchBarClearButtonIcon + : this.searchBarClearButtonIcon + ), + searchBarClearButtonIconColor: this.getValue( + container + ? container.searchBarClearButtonIconColor + : this.searchBarClearButtonIconColor + ), + searchBarSearchButtonIcon: this.getValue( + container + ? container.searchBarSearchButtonIcon + : this.searchBarSearchButtonIcon + ), + searchBarSearchButtonIconColor: this.getValue( + container + ? container.searchBarSearchButtonIconColor + : this.searchBarSearchButtonIconColor + ), + }).pipe( + map((data) => { + return { + // + ...data, + + // + searchBarActionProvider: container + ? container.searchBarActionProvider + : this.searchBarActionProvider, + + // + searchBarOnEvent: this.searchBarOnEvent, + searchBarQueryChange: this.searchBarQueryChange, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-slider\x-slide.animation.providers.ts + +import { XSlideAnimationProvider } from './x-slider.typings'; + +// +//#region Animations ... +export const Coverflow: XSlideAnimationProvider = { + coverflowEffect: { + rotate: 50, + stretch: 0, + depth: 100, + modifier: 1, + slideShadows: true + }, + on: { + beforeInit() { + const swiper = this; + swiper.classNames.push( + `${swiper.params.containerModifierClass}coverflow` + ); + swiper.classNames.push(`${swiper.params.containerModifierClass}3d`); + + swiper.params.watchSlidesProgress = true; + swiper.originalParams.watchSlidesProgress = true; + }, + setTranslate() { + const swiper = this; + const { + width: swiperWidth, + height: swiperHeight, + slides, + $wrapperEl, + slidesSizesGrid + } = swiper; + const params = swiper.params.coverflowEffect; + const isHorizontal = swiper.isHorizontal(); + const transform$$1 = swiper.translate; + const center = isHorizontal + ? -transform$$1 + swiperWidth / 2 + : -transform$$1 + swiperHeight / 2; + const rotate = isHorizontal ? params.rotate : -params.rotate; + const translate = params.depth; + // Each slide offset from center + for (let i = 0, length = slides.length; i < length; i += 1) { + const $slideEl = slides.eq(i); + const slideSize = slidesSizesGrid[i]; + const slideOffset = $slideEl[0].swiperSlideOffset; + const offsetMultiplier = + ((center - slideOffset - slideSize / 2) / slideSize) * + params.modifier; + + let rotateY = isHorizontal ? rotate * offsetMultiplier : 0; + let rotateX = isHorizontal ? 0 : rotate * offsetMultiplier; + // var rotateZ = 0 + let translateZ = -translate * Math.abs(offsetMultiplier); + + let translateY = isHorizontal ? 0 : params.stretch * offsetMultiplier; + let translateX = isHorizontal ? params.stretch * offsetMultiplier : 0; + + // Fix for ultra small values + if (Math.abs(translateX) < 0.001) { + translateX = 0; + } + if (Math.abs(translateY) < 0.001) { + translateY = 0; + } + if (Math.abs(translateZ) < 0.001) { + translateZ = 0; + } + if (Math.abs(rotateY) < 0.001) { + rotateY = 0; + } + if (Math.abs(rotateX) < 0.001) { + rotateX = 0; + } + + const slideTransform = + `translate3d(${translateX}px,${translateY}px,${translateZ}px)` + + ` rotateX(${rotateX}deg) rotateY(${rotateY}deg)`; + + $slideEl.transform(slideTransform); + $slideEl[0].style.zIndex = -Math.abs(Math.round(offsetMultiplier)) + 1; + if (params.slideShadows) { + // Set shadows + let $shadowBeforeEl = isHorizontal + ? $slideEl.find('.swiper-slide-shadow-left') + : $slideEl.find('.swiper-slide-shadow-top'); + let $shadowAfterEl = isHorizontal + ? $slideEl.find('.swiper-slide-shadow-right') + : $slideEl.find('.swiper-slide-shadow-bottom'); + if ($shadowBeforeEl.length === 0) { + $shadowBeforeEl = swiper.$( + `
` + ); + // $slideEl.append($shadowBeforeEl); + } + if ($shadowAfterEl.length === 0) { + $shadowAfterEl = swiper.$( + `
` + ); + // $slideEl.append($shadowAfterEl); + } + if ($shadowBeforeEl.length) { + $shadowBeforeEl[0].style.opacity = + offsetMultiplier > 0 ? offsetMultiplier : 0; + } + if ($shadowAfterEl.length) { + $shadowAfterEl[0].style.opacity = + -offsetMultiplier > 0 ? -offsetMultiplier : 0; + } + } + } + + // Set correct perspective for IE10 + if ( + swiper.support.pointerEvents || + swiper.support.prefixedPointerEvents + ) { + const ws = $wrapperEl[0].style; + ws.perspectiveOrigin = `${center}px 50%`; + } + }, + setTransition(duration) { + const swiper = this; + swiper.slides + .transition(duration) + .find( + '.swiper-slide-shadow-top, .swiper-slide-shadow-right, .swiper-slide-shadow-bottom, .swiper-slide-shadow-left' + ) + .transition(duration); + } + } +}; + +// +export const Cube: XSlideAnimationProvider = { + cubeEffect: { + shadow: true, + slideShadows: true, + shadowOffset: 20, + shadowScale: 0.94 + }, + on: { + beforeInit() { + const swiper = this; + swiper.classNames.push(`${swiper.params.containerModifierClass}cube`); + swiper.classNames.push(`${swiper.params.containerModifierClass}3d`); + + const overwriteParams = { + slidesPerView: 1, + slidesPerColumn: 1, + slidesPerGroup: 1, + watchSlidesProgress: true, + resistanceRatio: 0, + spaceBetween: 0, + centeredSlides: false, + virtualTranslate: true + }; + + this.params = Object.assign(this.params, overwriteParams); + this.originalParams = Object.assign(this.originalParams, overwriteParams); + }, + setTranslate() { + const swiper = this; + const { + $el, + $wrapperEl, + slides, + width: swiperWidth, + height: swiperHeight, + rtlTranslate: rtl, + size: swiperSize + } = swiper; + const params = swiper.params.cubeEffect; + const isHorizontal = swiper.isHorizontal(); + const isVirtual = swiper.virtual && swiper.params.virtual.enabled; + let wrapperRotate = 0; + let $cubeShadowEl; + if (params.shadow) { + if (isHorizontal) { + $cubeShadowEl = $wrapperEl.find('.swiper-cube-shadow'); + if ($cubeShadowEl.length === 0) { + $cubeShadowEl = swiper.$('
'); + $wrapperEl.append($cubeShadowEl); + } + $cubeShadowEl.css({ height: `${swiperWidth}px` }); + } else { + $cubeShadowEl = $el.find('.swiper-cube-shadow'); + if ($cubeShadowEl.length === 0) { + $cubeShadowEl = swiper.$('
'); + $el.append($cubeShadowEl); + } + } + } + + for (let i = 0; i < slides.length; i += 1) { + const $slideEl = slides.eq(i); + let slideIndex = i; + if (isVirtual) { + slideIndex = parseInt($slideEl.attr('data-swiper-slide-index'), 10); + } + let slideAngle = slideIndex * 90; + let round = Math.floor(slideAngle / 360); + if (rtl) { + slideAngle = -slideAngle; + round = Math.floor(-slideAngle / 360); + } + const progress = Math.max(Math.min($slideEl[0].progress, 1), -1); + let tx = 0; + let ty = 0; + let tz = 0; + if (slideIndex % 4 === 0) { + tx = -round * 4 * swiperSize; + tz = 0; + } else if ((slideIndex - 1) % 4 === 0) { + tx = 0; + tz = -round * 4 * swiperSize; + } else if ((slideIndex - 2) % 4 === 0) { + tx = swiperSize + round * 4 * swiperSize; + tz = swiperSize; + } else if ((slideIndex - 3) % 4 === 0) { + tx = -swiperSize; + tz = 3 * swiperSize + swiperSize * 4 * round; + } + if (rtl) { + tx = -tx; + } + + if (!isHorizontal) { + ty = tx; + tx = 0; + } + + const transform$$1 = `rotateX(${ + isHorizontal ? 0 : -slideAngle + }deg) rotateY(${ + isHorizontal ? slideAngle : 0 + }deg) translate3d(${tx}px, ${ty}px, ${tz}px)`; + if (progress <= 1 && progress > -1) { + wrapperRotate = slideIndex * 90 + progress * 90; + if (rtl) { + wrapperRotate = -slideIndex * 90 - progress * 90; + } + } + $slideEl.transform(transform$$1); + if (params.slideShadows) { + // Set shadows + let shadowBefore = isHorizontal + ? $slideEl.find('.swiper-slide-shadow-left') + : $slideEl.find('.swiper-slide-shadow-top'); + let shadowAfter = isHorizontal + ? $slideEl.find('.swiper-slide-shadow-right') + : $slideEl.find('.swiper-slide-shadow-bottom'); + if (shadowBefore.length === 0) { + shadowBefore = swiper.$( + `
` + ); + $slideEl.append(shadowBefore); + } + if (shadowAfter.length === 0) { + shadowAfter = swiper.$( + `
` + ); + $slideEl.append(shadowAfter); + } + if (shadowBefore.length) { + shadowBefore[0].style.opacity = Math.max(-progress, 0); + } + if (shadowAfter.length) { + shadowAfter[0].style.opacity = Math.max(progress, 0); + } + } + } + $wrapperEl.css({ + '-webkit-transform-origin': `50% 50% -${swiperSize / 2}px`, + '-moz-transform-origin': `50% 50% -${swiperSize / 2}px`, + '-ms-transform-origin': `50% 50% -${swiperSize / 2}px`, + 'transform-origin': `50% 50% -${swiperSize / 2}px` + }); + + if (params.shadow) { + if (isHorizontal) { + $cubeShadowEl.transform( + `translate3d(0px, ${swiperWidth / 2 + + params.shadowOffset}px, ${-swiperWidth / + 2}px) rotateX(90deg) rotateZ(0deg) scale(${params.shadowScale})` + ); + } else { + const shadowAngle = + Math.abs(wrapperRotate) - + Math.floor(Math.abs(wrapperRotate) / 90) * 90; + const multiplier = + 1.5 - + (Math.sin((shadowAngle * 2 * Math.PI) / 360) / 2 + + Math.cos((shadowAngle * 2 * Math.PI) / 360) / 2); + const scale1 = params.shadowScale; + const scale2 = params.shadowScale / multiplier; + const offset$$1 = params.shadowOffset; + $cubeShadowEl.transform( + `scale3d(${scale1}, 1, ${scale2}) translate3d(0px, ${swiperHeight / + 2 + + offset$$1}px, ${-swiperHeight / 2 / scale2}px) rotateX(-90deg)` + ); + } + } + + const zFactor = + swiper.browser.isSafari || swiper.browser.isUiWebView + ? -swiperSize / 2 + : 0; + $wrapperEl.transform( + `translate3d(0px,0,${zFactor}px) rotateX(${ + swiper.isHorizontal() ? 0 : wrapperRotate + }deg) rotateY(${swiper.isHorizontal() ? -wrapperRotate : 0}deg)` + ); + }, + setTransition(duration) { + const swiper = this; + const { $el, slides } = swiper; + slides + .transition(duration) + .find( + '.swiper-slide-shadow-top, .swiper-slide-shadow-right, .swiper-slide-shadow-bottom, .swiper-slide-shadow-left' + ) + .transition(duration); + if (swiper.params.cubeEffect.shadow && !swiper.isHorizontal()) { + $el.find('.swiper-cube-shadow').transition(duration); + } + } + } +}; + +// +export const Fade: XSlideAnimationProvider = { + on: { + beforeInit() { + const swiper = this; + swiper.classNames.push(`${swiper.params.containerModifierClass}fade`); + const overwriteParams = { + slidesPerView: 1, + slidesPerColumn: 1, + slidesPerGroup: 1, + watchSlidesProgress: true, + spaceBetween: 0, + virtualTranslate: true + }; + swiper.params = Object.assign(swiper.params, overwriteParams); + swiper.params = Object.assign(swiper.originalParams, overwriteParams); + }, + setTranslate() { + const swiper = this; + const { slides } = swiper; + for (let i = 0; i < slides.length; i += 1) { + const $slideEl = swiper.slides.eq(i); + const offset$$1 = $slideEl[0].swiperSlideOffset; + let tx = -offset$$1; + if (!swiper.params.virtualTranslate) { + tx -= swiper.translate; + } + let ty = 0; + if (!swiper.isHorizontal()) { + ty = tx; + tx = 0; + } + const slideOpacity = swiper.params.fadeEffect.crossFade + ? Math.max(1 - Math.abs($slideEl[0].progress), 0) + : 1 + Math.min(Math.max($slideEl[0].progress, -1), 0); + $slideEl + .css({ + opacity: slideOpacity + }) + .transform(`translate3d(${tx}px, ${ty}px, 0px)`); + } + }, + setTransition(duration) { + const swiper = this; + const { slides, $wrapperEl } = swiper; + slides.transition(duration); + if (swiper.params.virtualTranslate && duration !== 0) { + let eventTriggered = false; + slides.transitionEnd(() => { + if (eventTriggered) { + return; + } + if (!swiper || swiper.destroyed) { + return; + } + eventTriggered = true; + swiper.animating = false; + const triggerEvents = ['webkitTransitionEnd', 'transitionend']; + // tslint:disable-next-line:prefer-for-of + for (let i = 0; i < triggerEvents.length; i += 1) { + $wrapperEl.trigger(triggerEvents[i]); + } + }); + } + } + } +}; + +// +export const Flip: XSlideAnimationProvider = { + on: { + beforeInit() { + const swiper = this; + swiper.classNames.push(`${swiper.params.containerModifierClass}flip`); + swiper.classNames.push(`${swiper.params.containerModifierClass}3d`); + const overwriteParams = { + slidesPerView: 1, + slidesPerColumn: 1, + slidesPerGroup: 1, + watchSlidesProgress: true, + spaceBetween: 0, + virtualTranslate: true + }; + swiper.params = Object.assign(swiper.params, overwriteParams); + swiper.originalParams = Object.assign( + swiper.originalParams, + overwriteParams + ); + }, + setTranslate() { + const swiper = this; + const { $, slides, rtlTranslate: rtl } = swiper; + for (let i = 0; i < slides.length; i += 1) { + const $slideEl = slides.eq(i); + let progress = $slideEl[0].progress; + if (swiper.params.flipEffect.limitRotation) { + progress = Math.max(Math.min($slideEl[0].progress, 1), -1); + } + const offset$$1 = $slideEl[0].swiperSlideOffset; + const rotate = -180 * progress; + let rotateY = rotate; + let rotateX = 0; + let tx = -offset$$1; + let ty = 0; + if (!swiper.isHorizontal()) { + ty = tx; + tx = 0; + rotateX = -rotateY; + rotateY = 0; + } else if (rtl) { + rotateY = -rotateY; + } + + $slideEl[0].style.zIndex = + -Math.abs(Math.round(progress)) + slides.length; + + if (swiper.params.flipEffect.slideShadows) { + // Set shadows + let shadowBefore = swiper.isHorizontal() + ? $slideEl.find('.swiper-slide-shadow-left') + : $slideEl.find('.swiper-slide-shadow-top'); + let shadowAfter = swiper.isHorizontal() + ? $slideEl.find('.swiper-slide-shadow-right') + : $slideEl.find('.swiper-slide-shadow-bottom'); + if (shadowBefore.length === 0) { + shadowBefore = swiper.$( + `
` + ); + $slideEl.append(shadowBefore); + } + if (shadowAfter.length === 0) { + shadowAfter = swiper.$( + `
` + ); + $slideEl.append(shadowAfter); + } + if (shadowBefore.length) { + shadowBefore[0].style.opacity = Math.max(-progress, 0); + } + if (shadowAfter.length) { + shadowAfter[0].style.opacity = Math.max(progress, 0); + } + } + $slideEl.transform( + `translate3d(${tx}px, ${ty}px, 0px) rotateX(${rotateX}deg) rotateY(${rotateY}deg)` + ); + } + }, + setTransition(duration) { + const swiper = this; + const { slides, activeIndex, $wrapperEl } = swiper; + slides + .transition(duration) + .find( + '.swiper-slide-shadow-top, .swiper-slide-shadow-right, .swiper-slide-shadow-bottom, .swiper-slide-shadow-left' + ) + .transition(duration); + if (swiper.params.virtualTranslate && duration !== 0) { + let eventTriggered = false; + // eslint-disable-next-line + slides.eq(activeIndex).transitionEnd(function onTransitionEnd() { + if (eventTriggered) { + return; + } + if (!swiper || swiper.destroyed) { + return; + } + + eventTriggered = true; + swiper.animating = false; + const triggerEvents = ['webkitTransitionEnd', 'transitionend']; + // tslint:disable-next-line:prefer-for-of + for (let i = 0; i < triggerEvents.length; i += 1) { + $wrapperEl.trigger(triggerEvents[i]); + } + }); + } + } + } +}; +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-slider\x-slider.component.html + + + + + + + + +
+ +
+
+
+ + + +
+ + + + + +
+
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-slider\x-slider.component.scss + +:host { + --paginator: var(--x-color-dark-tint); + --paginator-active: var(--x-color-secondary); +} + +ion-slides { + --bullet-background: var(--paginator); + --bullet-background-active: var(--paginator-active); +} + +.no-pager { + padding-bottom: 0 !important; +} + +.slider { + padding-bottom: 30px !important; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-slider\x-slider.component.ts + +import { + Component, + QueryList, + ViewChild, + ContentChildren, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + nameof, + isNullOrUndefined, + isNullOrEmptyString, +} from 'x-framework-core'; +import { + XSliderAction, + XSliderConfig, + XSliderAnimation, + XSliderActionModel, + defaultXSliderConfig, + XSliderContainerComponent, + XSliderAnimationIdentifier, + defaultXSliderPaginationConfig, +} from './x-slider.typings'; +import { map } from 'rxjs/operators'; +import { IonSlides } from '@ionic/angular'; +import { Subject, Subscription } from 'rxjs'; +import { XSlideComponent } from './x-slide/x-slide.component'; +import { Coverflow, Cube, Fade, Flip } from './x-slide.animation.providers'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-slider', + templateUrl: './x-slider.component.html', + styleUrls: ['./x-slider.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XSliderComponent extends XSliderContainerComponent { + // + //#region Props ... + // + private LAST_ACTIVE_INDEX = 0; + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + //#region Slider Config ... + // + private SLIDER_CONFIG: any; + + // + get sliderConfig() { + // + if (!this.SLIDER_CONFIG) { + this.SLIDER_CONFIG = this.getConfiguration(); + } + + // + return this.SLIDER_CONFIG; + } + //#endregion + + // + //#region Slider ... + // + private SLIDER: IonSlides; + + // + @ViewChild('slider', { static: false }) + set slider(s: IonSlides) { + this.SLIDER = s; + this.handleConfigurationChanged(); + } + + // + get slider() { + return this.SLIDER; + } + //#endregion + + // + //#region Slides ... + // + private SLIDES: QueryList; + + // + @ContentChildren(XSlideComponent) + set slides(s: QueryList) { + this.SLIDES = s; + this.handleConfigurationChanged(); + } + + // + get slides() { + return this.SLIDES; + } + //#endregion + //#endregion + + // + //#region LifeCycles ... + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + + // + const isConfigChanged = changeKeys.includes( + nameof('options') + ); + const isAnimationChanged = changeKeys.includes( + nameof('animation') + ); + + // + if (isConfigChanged || isAnimationChanged) { + await this.handleConfigurationChanged(); + } + } + + afterContentInit() { + super.afterContentInit(); + + // + if (this.slider) { + this.update(); + } + + // + this.managerService.dispatchResizeEvent(); + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region Ui Action Providers ... + hasPager() { + return this.getValue(this.options).pipe( + map((options) => options.pagination) + ); + } + //#endregion + + // + //#region UI Handlers ... + async handleSlideChanged() { + // + if (isNullOrUndefined(this.slider)) { + return; + } + + // + const index = await this.slider.getActiveIndex(); + if (index === this.LAST_ACTIVE_INDEX) { + return; + } + + // + this.LAST_ACTIVE_INDEX = index; + this.onSlideChange.emit(index); + } + //#endregion + + // + //#region Provided Actions ... + length() { + return this.slider.length(); + } + + getActiveIndex() { + return this.slider.getActiveIndex(); + } + + getPreviousIndex() { + return this.slider.getPreviousIndex(); + } + + getSwiper() { + return this.slider.getSwiper(); + } + + isBeginning() { + return this.slider.isBeginning(); + } + + isEnd() { + return this.slider.isEnd(); + } + + lockSwipeToNext(lock: boolean) { + return this.slider.lockSwipeToNext(lock); + } + + lockSwipeToPrev(lock: boolean) { + return this.slider.lockSwipeToPrev(lock); + } + + lockSwipes(lock: boolean) { + return this.slider.lockSwipes(lock); + } + + slideNext(speed?: number, runCallbacks?: boolean) { + return this.slider.slideNext(speed, runCallbacks); + } + + slidePrev(speed?: number, runCallbacks?: boolean) { + return this.slider.slidePrev(speed, runCallbacks); + } + + slideTo(index: number, speed?: number, runCallbacks?: boolean) { + return this.slider.slideTo(index, speed, runCallbacks); + } + + startAutoplay() { + return this.slider.startAutoplay(); + } + + stopAutoplay() { + return this.slider.stopAutoplay(); + } + + update() { + return this.slider.update(); + } + + updateAutoHeight() { + return this.slider.updateAutoHeight(); + } + //#endregion + + // + //#region Private ... + // + //#region ActionProvider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + switch (model.action) { + // + case XSliderAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + private async handleConfigurationChanged() { + // + const anim = await this.getValueAsync(this.animation); + const options = await this.getValueAsync(this.options); + + // + this.SLIDER_CONFIG = this.getConfiguration(anim, options); + } + + private getConfiguration( + animation?: XSliderAnimationIdentifier, + config?: XSliderConfig + ) { + // + let result: XSliderConfig; + + // + if (isNullOrEmptyString(animation)) { + animation = XSliderAnimation.Coverflow; + } + + // + if (!config) { + config = defaultXSliderConfig; + } + + // + result = { + ...defaultXSliderConfig, + ...config, + speed: + config.speed && config.speed <= 15000 && config.speed >= 500 + ? config.speed + : defaultXSliderConfig.speed, + initialSlide: + this.slides && + config.initialSlide <= this.slides.length && + config.initialSlide >= 0 + ? config.initialSlide + : defaultXSliderConfig.initialSlide, + }; + + // + switch (animation) { + case XSliderAnimation.Coverflow: + result = { + ...result, + ...Coverflow, + }; + break; + + case XSliderAnimation.Cube: + result = { + ...result, + ...Cube, + }; + break; + + case XSliderAnimation.Fade: + result = { + ...result, + ...Fade, + }; + break; + + case XSliderAnimation.Flip: + result = { + ...result, + ...Flip, + }; + break; + } + + // + if (result.pagination) { + result.pagination = defaultXSliderPaginationConfig; + } + + // + return result; + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-slider\x-slider.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XIconModule } from '../x-icon/x-icon.module'; +import { XCardModule } from '../x-card/x-card.module'; +import { XSliderComponent } from './x-slider.component'; +import { XSlideComponent } from './x-slide/x-slide.component'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XSliderComponent, XSlideComponent], + imports: [XCardModule, CommonModule, XBaseComponentModule, XIconModule], + exports: [ + XIconModule, + XCardModule, + XSlideComponent, + XSliderComponent, + XBaseComponentModule, + ], +}) +export class XSliderModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-slider\x-slider.typings.ts + +import { + Input, + Inject, + NgZone, + Output, + Renderer2, + Component, + ElementRef, + Injectable, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XComponentCardContainer, + IXComponentCardContainer, + XComponentCardContainerComponent, +} from '../x-card/x-card.typings'; +import { concatMap, map } from 'rxjs/operators'; +import { forkJoin, Observable, Subject } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XOneOrManyType, XStandardType } from 'x-framework-core'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export enum XSliderAnimation { + Coverflow = 'coverflow', + Cube = 'cube', + Fade = 'fade', + Flip = 'flip', +} + +export type XSliderAnimationIdentifier = XSliderAnimation | string; + +export interface XSlideAnimationProvider { + [key: string]: any; + on: { + [key: string]: any; + beforeInit?: () => void; + setTranslate?: () => void; + setTransition?: (duration?: number) => void; + }; +} + +export interface XSliderPaginationConfig { + el: string; + type: string; + clickable: boolean; +} + +export type XSliderPagination = XSliderPaginationConfig | boolean; + +export const defaultXSliderPaginationConfig: XSliderPaginationConfig = { + el: '.swiper-pagination', + type: 'bullets', + clickable: true, +}; + +export interface XSliderConfig { + lazy: boolean; + zoom: boolean; + autoplay: boolean; + initialSlide: number; + grabCursor: boolean; + autoHeight: boolean; + slidesPerView: number; + loadPrevNext: boolean; + preventClicks: boolean; + pagination: XSliderPagination; + speed: number; + on?: any; +} + +export const defaultXSliderConfig: XSliderConfig = { + lazy: true, + zoom: true, + autoplay: true, + initialSlide: 0, + grabCursor: true, + autoHeight: true, + slidesPerView: 1, + loadPrevNext: true, + preventClicks: false, + speed: 400, + pagination: true, +}; + +export enum XSliderAction { + Refresh = 'refresh', +} + +export type XSliderActionIdentifier = XSliderAction | string; + +export interface XSliderActionModel + extends XBaseActionModel {} + +// +//#region Inside Card Container ... +export interface IXSliderContainer extends IXComponentCardContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + options: XStandardType; + animation: XStandardType; + + // + onSlideChange: EventEmitter; +} + +@Injectable({ + providedIn: 'root', +}) +export class XSliderContainer + extends XComponentCardContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider: Subject = + new Subject(); + readonly cssClass: XStandardType>; + + // + readonly options: XStandardType = defaultXSliderConfig; + readonly animation: XStandardType = + XSliderAnimation.Coverflow; + + // + readonly onSlideChange = new EventEmitter(); + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XSliderContainerComponent + extends XComponentCardContainerComponent + implements IXSliderContainer +{ + // + //#region Props ... + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + @Input() + options: XStandardType = this.propertyProvider.options; + + @Input() + animation: XStandardType = + this.propertyProvider.animation; + + // + @Output() + onSlideChange: EventEmitter = this.propertyProvider.onSlideChange; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XSliderContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXSliderContainer + ): Observable { + return super + .extractProps(container ? container : this) + .pipe( + concatMap((superData) => { + return forkJoin({ + // + cssClass: this.getValue( + container ? container.cssClass : this.cssClass + ), + options: this.getValue( + container ? container.options : this.options + ), + animation: this.getValue( + container ? container.animation : this.animation + ), + }).pipe( + map((data) => { + return { + superData, + data, + }; + }) + ); + }) + ) + .pipe( + map((info) => { + return { + // + ...info.superData, + + // + ...info.data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + onSlideChange: container + ? container.onSlideChange + : this.onSlideChange, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-slider\x-slide\x-slide.component.html + + + +
+ +
+
+
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-slider\x-slide\x-slide.component.scss + +.full-width { + width: 100%; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-slider\x-slide\x-slide.component.ts + +import { + Component, + ViewChild, + TemplateRef, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + XSlideAction, + XSlideActionModel, + XSlideContainerComponent, +} from './x-slide.typings'; +import { nameof } from 'x-framework-core'; +import { Subject, Subscription } from 'rxjs'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-slide', + templateUrl: './x-slide.component.html', + styleUrls: ['./x-slide.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XSlideComponent extends XSlideContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + @ViewChild('content', { static: true }) + template!: TemplateRef; + //#endregion + + // + //#region LifeCycles ... + onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region Private ... + // + //#region ActionProvider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + switch (model.action) { + // + case XSlideAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-slider\x-slide\x-slide.typings.ts + +import { + Input, + NgZone, + Inject, + Component, + Renderer2, + ElementRef, + Injectable, + ChangeDetectorRef, +} from '@angular/core'; +import { map } from 'rxjs/operators'; +import { + XBaseContainer, + IXBaseContainer, + XBaseContainerComponent, +} from '../../classes/i-x-base.container'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { forkJoin, Observable, Subject } from 'rxjs'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../../config/x-framework-components.config'; + +export enum XSlideAction { + Refresh = 'refresh', +} + +export type XSlideActionIdentifier = XSlideAction | string; + +export interface XSlideActionModel + extends XBaseActionModel {} + +// +//#region Container ... +export interface IXSlideContainer extends IXBaseContainer { + actionProvider: Subject; +} + +@Injectable({ + providedIn: 'root' +}) +export class XSlideContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + actionProvider: Subject = new Subject(); + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XSlideContainerComponent + extends XBaseContainerComponent + implements IXSlideContainer +{ + // + //#region Props ... + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XSlideContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXSlideContainer + ): Observable { + return forkJoin({ + // + }).pipe( + map((data) => { + return { + // // + // ...data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-slotter\x-slotter.component.html + +
+ + +
+ + +
+ +
+
+
+ + +
+ +
+ +
+
+
+ + +
+ +
+ +
+
+
+
+
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-slotter\x-slotter.component.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-slotter\x-slotter.component.ts + +import { + Component, + QueryList, + ContentChildren, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + XSlotName, + XSlotLayout, + XSlotterAction, + XSlotterActionModel, + XSlotterContainerComponent, +} from './x-slotter.typings'; +import { map } from 'rxjs/operators'; +import { nameof } from 'x-framework-core'; +import { Subject, Subscription } from 'rxjs'; +import { XSlotComponent } from './x-slot/x-slot.component'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-slotter', + templateUrl: './x-slotter.component.html', + styleUrls: ['./x-slotter.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XSlotterComponent extends XSlotterContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + //#region Slots Props ... + // + private SLOTS: QueryList; + + // + @ContentChildren(XSlotComponent) + set slots(s: QueryList) { + // + this.SLOTS = s; + this.prepareSlots(); + } + + // + get slots() { + return this.SLOTS; + } + + // + startOrTopSlots: XSlotComponent[] = []; + cneterSlots: XSlotComponent[] = []; + endOrBottomSlots: XSlotComponent[] = []; + //#endregion + //#endregion + + // + //#region LifeCycles ... + onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region Ui Providers ... + hasSlots() { + return this.slots && this.slots.length > 0; + } + + getHasCenterSlot() { + return this.applyTakeUntilWrapper(this.getValue(this.hasCenterSlot)); + } + + getHasStartOrTopSlot() { + return this.applyTakeUntilWrapper(this.getValue(this.hasStartOrTopSlot)); + } + + getHasEndOrBottomSlot() { + return this.applyTakeUntilWrapper(this.getValue(this.hasEndOrBottomSlot)); + } + + isLayoutHorizontal() { + return this.getValue(this.layout).pipe( + map((layout) => layout === XSlotLayout.HORIZONTAL) + ); + } + + isLayoutVertical() { + return this.getValue(this.layout).pipe( + map((layout) => layout === XSlotLayout.VERTICAL) + ); + } + + getLayout() { + return this.getValue(this.layout).pipe( + map((layout) => { + return layout === XSlotLayout.VERTICAL ? 'column' : 'row'; + }) + ); + } + //#endregion + + // + //#region Private ... + // + //#region Action Provider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + switch (model.action) { + // + case XSlotterAction.Refresh: + await this.prepareSlots(); + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + private async prepareSlots() { + // + this.startOrTopSlots = []; + this.cneterSlots = []; + this.endOrBottomSlots = []; + + // + this.detectChanges(); + + // + if (!this.hasSlots()) { + return; + } + + // + this.slots.forEach(async (slot) => { + // + const name = await this.getValueAsync(slot.name); + switch (name) { + case XSlotName.START: + this.startOrTopSlots.push(slot); + break; + + case XSlotName.TOP: + this.startOrTopSlots.push(slot); + break; + + case XSlotName.CENTER: + this.cneterSlots.push(slot); + break; + + case XSlotName.END: + this.endOrBottomSlots.push(slot); + break; + + case XSlotName.BOTTOM: + this.endOrBottomSlots.push(slot); + break; + } + + // + this.detectChanges(); + }); + + // + this.detectChanges(); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-slotter\x-slotter.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XSlotterComponent } from './x-slotter.component'; +import { XSlotComponent } from './x-slot/x-slot.component'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XSlotterComponent, XSlotComponent], + imports: [CommonModule, XBaseComponentModule], + exports: [XBaseComponentModule, XSlotterComponent, XSlotComponent] +}) +export class XSlotterModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-slotter\x-slotter.typings.ts + +import { + Input, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + ChangeDetectorRef, +} from '@angular/core'; +import { map } from 'rxjs/operators'; +import { + XBaseContainer, + IXBaseContainer, + XBaseContainerComponent, +} from '../classes/i-x-base.container'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XOneOrManyType, XStandardType } from 'x-framework-core'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export enum XSlotName { + START = 'start', + END = 'end', + CENTER = 'center', + TOP = 'top', + BOTTOM = 'bottom', +} + +export type XSlotNameIdentifier = XSlotName | string; + +export enum XSlotLayout { + HORIZONTAL = 'horizontal', + VERTICAL = 'vertical', +} + +export type XSlotLayoutIdentifier = XSlotLayout | string; + +export enum XSlotterAction { + Refresh = 'refresh', +} + +export type XSlotterActionIdentifier = XSlotterAction | string; + +export interface XSlotterActionModel + extends XBaseActionModel {} + +// +//#region Container ... +export interface IXSlotterContainer extends IXBaseContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + layout: XStandardType; + + // + hasStartOrTopSlot: XStandardType; + startTopSlotCssClass: XStandardType>; + + // + hasCenterSlot: XStandardType; + centerSlotCssClass: XStandardType>; + + // + hasEndOrBottomSlot: XStandardType; + endBottomSlotCssClass: XStandardType>; +} + +@Injectable({ + providedIn: 'root' +}) +export class XSlotterContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider: Subject; + readonly cssClass: XStandardType>; + + // + readonly layout: XStandardType = + XSlotLayout.HORIZONTAL; + + // + readonly hasStartOrTopSlot: XStandardType = true; + readonly startTopSlotCssClass: XStandardType>; + + // + readonly hasCenterSlot: XStandardType = true; + readonly centerSlotCssClass: XStandardType>; + + // + readonly hasEndOrBottomSlot: XStandardType = true; + readonly endBottomSlotCssClass: XStandardType>; + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XSlotterContainerComponent + extends XBaseContainerComponent + implements IXSlotterContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + layout: XStandardType = this.propertyProvider.layout; + + // + @Input() + hasStartOrTopSlot: XStandardType = + this.propertyProvider.hasStartOrTopSlot; + + @Input() + startTopSlotCssClass: XStandardType> = + this.propertyProvider.startTopSlotCssClass; + + // + @Input() + hasCenterSlot: XStandardType = this.propertyProvider.hasCenterSlot; + + @Input() + centerSlotCssClass: XStandardType> = + this.propertyProvider.centerSlotCssClass; + + // + @Input() + hasEndOrBottomSlot: XStandardType = + this.propertyProvider.hasEndOrBottomSlot; + + @Input() + endBottomSlotCssClass: XStandardType> = + this.propertyProvider.endBottomSlotCssClass; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XSlotterContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXSlotterContainer + ): Observable { + return forkJoin({ + // + cssClass: this.getValue(container ? container.cssClass : this.cssClass), + layout: this.getValue(container ? container.layout : this.layout), + hasStartOrTopSlot: this.getValue( + container ? container.hasStartOrTopSlot : this.hasStartOrTopSlot + ), + startTopSlotCssClass: this.getValue( + container ? container.startTopSlotCssClass : this.startTopSlotCssClass + ), + hasCenterSlot: this.getValue( + container ? container.hasCenterSlot : this.hasCenterSlot + ), + centerSlotCssClass: this.getValue( + container ? container.centerSlotCssClass : this.centerSlotCssClass + ), + hasEndOrBottomSlot: this.getValue( + container ? container.hasEndOrBottomSlot : this.hasEndOrBottomSlot + ), + endBottomSlotCssClass: this.getValue( + container ? container.endBottomSlotCssClass : this.endBottomSlotCssClass + ), + }).pipe( + map((data) => { + return { + // + ...data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-slotter\x-slot\x-slot.component.html + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-slotter\x-slot\x-slot.component.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-slotter\x-slot\x-slot.component.ts + +import { + XSlotAction, + XSlotActionModel, + XSlotContainerComponent, +} from './x-slot.typings'; +import { nameof } from 'x-framework-core'; +import { Subject, Subscription } from 'rxjs'; +import { Component, ViewChild, TemplateRef } from '@angular/core'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-slot', + templateUrl: './x-slot.component.html', + styleUrls: ['./x-slot.component.scss'], + // changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XSlotComponent extends XSlotContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + @ViewChild('content', { static: false }) + content: TemplateRef; + //#endregion + + // + //#region LifeCycles ... + onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + //#endregion + + // + //#region Private ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + // Handle Action Provider ... + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XSlotAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-slotter\x-slot\x-slot.typings.ts + +import { + Input, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + ChangeDetectorRef, +} from '@angular/core'; +import { map } from 'rxjs/operators'; +import { + XBaseContainer, + IXBaseContainer, + XBaseContainerComponent, +} from '../../classes/i-x-base.container'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XSlotNameIdentifier } from '../x-slotter.typings'; +import { XOneOrManyType, XStandardType } from 'x-framework-core'; +import { XBaseActionModel } from '../../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../../config/x-framework-components.config'; + +export enum XSlotAction { + Refresh = 'refresh', +} + +export type XSlotActionIdentifier = XSlotAction | string; + +export interface XSlotActionModel + extends XBaseActionModel {} + +// +//#region Container ... +export interface IXSlotContainer extends IXBaseContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + name: XStandardType; +} + +@Injectable({ + providedIn: 'root' +}) +export class XSlotContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider = new Subject(); + readonly cssClass: XStandardType>; + + // + readonly name: XStandardType; + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XSlotContainerComponent + extends XBaseContainerComponent + implements IXSlotContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + name: XStandardType = this.propertyProvider.name; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XSlotContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXSlotContainer + ): Observable { + return forkJoin({ + // + cssClass: this.getValue(container ? container.cssClass : this.cssClass), + name: this.getValue(container ? container.name : this.name), + }).pipe( + map((data) => { + return { + // + ...data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-spinner\x-spinner.component.html + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-spinner\x-spinner.component.scss + +:host { + display: inline-block; + position: relative; + width: 28px; + height: 28px; +} + +ion-spinner { + display: inline-block; + position: relative; + width: 28px; + height: 28px; +} + +.x-spinner { + direction: ltr !important; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-spinner\x-spinner.component.ts + +import { + XSpinnerAction, + XSpinnerActionModel, + XSpinnerContainerComponent, +} from './x-spinner.typings'; +import { nameof } from 'x-framework-core'; +import { Subject, Subscription } from 'rxjs'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-spinner', + templateUrl: './x-spinner.component.html', + styleUrls: ['./x-spinner.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XSpinnerComponent extends XSpinnerContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + //#endregion + + // + //#region LifeCycles ... + onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region Private ... + // + //#region Action Provider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + switch (model.action) { + // + case XSpinnerAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-spinner\x-spinner.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XSpinnerComponent } from './x-spinner.component'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XSpinnerComponent], + imports: [CommonModule, XBaseComponentModule], + exports: [XBaseComponentModule, XSpinnerComponent] +}) +export class XSpinnerModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-spinner\x-spinner.typings.ts + +import { + Input, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + ChangeDetectorRef, +} from '@angular/core'; +import { + XSpinner, + XStandardType, + XOneOrManyType, + XColorIdentifier, + XSpinnerIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { map } from 'rxjs/operators'; +import { + XBaseContainer, + IXBaseContainer, + XBaseContainerComponent, +} from '../classes/i-x-base.container'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export enum XSpinnerAction { + Refresh = 'refresh', +} + +export type XSpinnerActionIdentifier = XSpinnerAction | string; + +export interface XSpinnerActionModel + extends XBaseActionModel {} + +// +//#region Container ... +export interface IXSpinnerContainer extends IXBaseContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + name: XStandardType; + duration: XStandardType; + color: XStandardType; + paused: XStandardType; +} + +@Injectable({ + providedIn: 'root' +}) +export class XSpinnerContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider: Subject; + readonly cssClass: XStandardType>; + + // + readonly name: XStandardType = XSpinner.Circular; + readonly duration: XStandardType = undefined; + readonly color: XStandardType = + XColorWithBrightness.SecondaryShade; + readonly paused: XStandardType = false; + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XSpinnerContainerComponent + extends XBaseContainerComponent + implements IXSpinnerContainer +{ + // + //#region Props ... + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + @Input() + name: XStandardType = this.propertyProvider.name; + + @Input() + duration: XStandardType = this.propertyProvider.duration; + + @Input() + color: XStandardType = this.propertyProvider.color; + + @Input() + paused: XStandardType = this.propertyProvider.paused; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XSpinnerContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXSpinnerContainer + ): Observable { + return forkJoin({ + // + cssClass: this.getValue(container ? container.cssClass : this.cssClass), + + // + name: this.getValue(container ? container.name : this.name), + duration: this.getValue(container ? container.duration : this.duration), + color: this.getValue(container ? container.color : this.color), + paused: this.getValue(container ? container.paused : this.paused), + }).pipe( + map((data) => { + return { + // + ...data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-stepper\x-stepper.component.html + + + + + + + + +
+ +
+ + +
+ +
+
+
+ + + + +
+ + + + + {{ applyLocale(index + 1) }} + + + {{ applyLocale(index + 1) }} + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+
+ + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-stepper\x-stepper.component.scss + +:host { + // + // TODO: add and handle cssVariable and color applier ... + --icon-background-color: var(--x-color-tertiary); + --icon-foreground-color: var(--x-color-tertiary-contrast); + --title-foreground-color: var(--x-color-secondary); + --header-background-color: var(--x-color-light); + --header-foreground-color: var(--x-color-light-contrast); +} + +:host(::ng-deep.disableHeader) { + .mat-step-header { + pointer-events: none; + } +} + +:host ::ng-deep .mat-step-header, +:host ::ng-deep .mat-stepper-vertical, +:host ::ng-deep .mat-stepper-horizontal, +:host ::ng-deep .mat-horizontal-stepper-header-container { + background-color: transparent !important; +} + +:host ::ng-deep .mat-step-icon { + color: var(--icon-foreground-color) !important; + background-color: var(--icon-background-color) !important; +} + +:host ::ng-deep .mat-step-label, +:host ::ng-deep .mat-step-header { + color: var(--header-foreground-color) !important; + background-color: var(--header-background-color) !important; +} + +:host ::ng-deep .mat-step-label { + color: var(--title-foreground-color) !important; +} + +:host ::ng-deep .mat-vertical-stepper-header { + padding: { + top: 4px; + bottom: 4px; + } +} + +// +$transition: opacity; +$duration: 500ms; +$delay: 0; +button { + // + transition: $transition; + -webkit-transition: $transition; + + // + transition-delay: $delay; + -webkit-transition-delay: $delay; + + // + transition-duration: $duration; + -webkit-transition-duration: $duration; +} + +.x-step-actions { + button { + margin: { + left: 4px; + right: 4px; + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-stepper\x-stepper.component.ts + +import { + ViewChild, + Component, + QueryList, + ContentChildren, +} from '@angular/core'; +import { + nameof, + mergeList, + parseXColor, + XStandardType, + isNullOrEmptyString, + XColorWithBrightness, + getXColorCssVariableName, +} from 'x-framework-core'; +import { + XStepperType, + XStepperAction, + XStepperCssVars, + XStepperActionModel, + XStepperLabelPosition, + XStepperContainerComponent, +} from './x-stepper.typings'; +import { + StepperOrientation, + StepperSelectionEvent, + STEPPER_GLOBAL_OPTIONS, +} from '@angular/cdk/stepper'; +import { concatMap, map } from 'rxjs/operators'; +import { MatStepper } from '@angular/material/stepper'; +import { Observable, Subject, Subscription } from 'rxjs'; +import { XStepComponent } from './x-step/x-step.component'; +import { XButtonType } from '../x-button/x-button.typings'; +import { XSlotName } from '../x-slotter/x-slotter.typings'; +import { XNPRButtonType } from '../classes/i-x-next-prev-reset.container'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-stepper', + templateUrl: './x-stepper.component.html', + styleUrls: ['./x-stepper.component.scss'], + providers: [ + { + provide: STEPPER_GLOBAL_OPTIONS, + useValue: { showError: true }, + }, + ], +}) +export class XStepperComponent extends XStepperContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + readonly SlotNames = Object.assign({}, XSlotName); + readonly NPRButtonTypes = Object.assign({}, XNPRButtonType); + + // + @ViewChild('stepper', { static: false }) + private stepper: MatStepper; + + // + selected: string; + // applyThemeColors = true; + + // + //#region Steps Props ... + // + private STEPS: QueryList; + + // + @ContentChildren(XStepComponent) + set steps(s: QueryList) { + // + if (!s) { + return; + } + + // + this.STEPS = s; + this.detectChanges(); + } + + // + get steps() { + return this.STEPS; + } + //#endregion + //#endregion + + // + //#region LifeCycles ... + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + + // + //#region Colors Changed ... + // + const isIconColorChanged = changeKeys.includes( + nameof('iconColor') + ); + if (isIconColorChanged) { + await this.applyIconColor(); + } + + // + const isTitleColorChanged = changeKeys.includes( + nameof('titleColor') + ); + if (isTitleColorChanged) { + await this.applyTitleColor(); + } + + // + const isHeaderColorChanged = changeKeys.includes( + nameof('headerColor') + ); + if (isHeaderColorChanged) { + await this.applyeaderColor(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerInstanceHandlers() { + super.registerInstanceHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region Task Handlers ... + //#endregion + + // + //#region UI Providers ... + // + //#region TypeDetectors ... + getOrientation() { + return this.getValue(this.type).pipe( + map((result) => result as StepperOrientation) + ); + } + + isTypeHorizontal() { + return this.getValue(this.type).pipe( + map((type) => type === XStepperType.HORIZONTAL) + ); + } + + isTypeVertical() { + return this.getValue(this.type).pipe( + map((type) => type === XStepperType.VERTICAL) + ); + } + //#endregion + + // + getLabelPositionValue(): Observable<'bottom' | 'end'> { + return this.getValue(this.labelPosition).pipe( + map((position) => + position === XStepperLabelPosition.END ? 'end' : 'bottom' + ) + ); + } + + // + hasSteps() { + return this.steps && this.steps.length > 0 ? true : false; + } + + hasActions(step: XStepComponent) { + return this.getValue(step.hasNext).pipe( + concatMap((hasNext) => + this.getValue(step.hasPrev).pipe( + map((hasPrev) => { + return { + hasNext, + hasPrev, + }; + }) + ) + ), + concatMap((hasNextPrev) => + this.getValue(step.hasReset).pipe( + map((hasReset) => { + return { + ...hasNextPrev, + hasReset, + }; + }) + ) + ), + map((info) => info.hasNext || info.hasPrev || info.hasReset) + ); + } + + // + //#region Button Providers ... + hasStepAction(step: XStepComponent, type: XNPRButtonType) { + // + switch (type) { + // + case this.NPRButtonTypes.Next: + return this.getOptionalValue(this.hasNext).pipe( + concatMap((cHas) => + this.getOptionalValue(step.hasNext).pipe( + map((sHas) => cHas || sHas) + ) + ) + ); + + // + case this.NPRButtonTypes.Prev: + return this.getOptionalValue(this.hasPrev).pipe( + concatMap((cHas) => + this.getOptionalValue(step.hasPrev).pipe( + map((sHas) => cHas || sHas) + ) + ) + ); + + // + case this.NPRButtonTypes.Reset: + return this.getOptionalValue(this.hasReset).pipe( + concatMap((cHas) => + this.getOptionalValue(step.hasReset).pipe( + map((sHas) => cHas || sHas) + ) + ) + ); + } + } + + getButtonIcon(step: XStepComponent, type: XNPRButtonType) { + return this.getActionButtons(step, type).pipe( + concatMap((btns) => + btns.s && btns.s.icon + ? this.getValue(btns.s.icon) + : this.getValue(btns.c.icon) + ) + ); + } + + getButtonType(step: XStepComponent, type: XNPRButtonType) { + return this.getActionButtons(step, type).pipe( + concatMap((btns) => + btns.s && btns.s.type + ? this.getValue(btns.s.type) + : this.getValue(btns.c.type) + ), + map((btnType) => + !isNullOrEmptyString(btnType) ? btnType : XButtonType.Raised + ) + ); + } + + getButtonTitle(step: XStepComponent, type: XNPRButtonType) { + return this.getActionButtons(step, type).pipe( + concatMap((btns) => + btns.s && btns.s.title + ? this.getResourceValue(btns.s.title) + : this.getResourceValue(btns.c.title) + ) + ); + } + + getButtonColor(step: XStepComponent, type: XNPRButtonType) { + return this.getActionButtons(step, type).pipe( + concatMap((btns) => + btns.s && btns.s.color + ? this.getValue(btns.s.color) + : this.getValue(btns.c.color) + ), + map((btnColor) => + !isNullOrEmptyString(btnColor) ? btnColor : XColorWithBrightness.Primary + ) + ); + } + + getButtonIconColor(step: XStepComponent, type: XNPRButtonType) { + return this.getActionButtons(step, type).pipe( + concatMap((btns) => + btns.s && btns.s.iconColor + ? this.getValue(btns.s.iconColor) + : this.getValue(btns.c.iconColor) + ), + map((btnColor) => + !isNullOrEmptyString(btnColor) ? btnColor : XColorWithBrightness.Primary + ) + ); + } + + getButtonDisabled(step: XStepComponent, type: XNPRButtonType) { + return this.getActionButtonDisabled(step, type); + } + + getButtonCssClass(step: XStepComponent, type: XNPRButtonType) { + return this.getActionButtons(step, type).pipe( + concatMap((btns) => + this.getArrayValue(btns.s.cssClass).pipe( + concatMap((sCss) => + this.getArrayValue(btns.c.cssClass).pipe( + map((cCss) => mergeList(sCss, cCss)) + ) + ) + ) + ) + ); + } + + getButtonIconPosition(step: XStepComponent, type: XNPRButtonType) { + return this.getActionButtons(step, type).pipe( + concatMap((btns) => + btns.s && btns.s.iconPosition + ? this.getValue(btns.s.iconPosition) + : this.getValue(btns.c.iconPosition) + ) + ); + } + + getButtonForegroundColor(step: XStepComponent, type: XNPRButtonType) { + return this.getActionButtons(step, type).pipe( + concatMap((btns) => + btns.s && btns.s.foregroundColor + ? this.getOptionalValue(btns.s.foregroundColor) + : this.getOptionalValue(btns.c.foregroundColor) + ) + ); + } + //#endregion + //#endregion + + // + //#region UI Handlers ... + async handleSelectionChanged(event: StepperSelectionEvent) { + // + if (this.uiDisabled || this.loading || !this.hasSteps()) { + return; + } + + const index = event.selectedIndex; + const stepId = await this.getValueAsync(this.steps.toArray()[index].id); + this.selectedChange.emit(stepId); + } + + async handleNPRStep( + stepper: MatStepper, + step: XStepComponent, + type: XNPRButtonType + ) { + // + switch (type) { + // + case this.NPRButtonTypes.Next: + await this.handleNextStep(stepper, step); + break; + + // + case this.NPRButtonTypes.Prev: + await this.handlePrevStep(stepper, step); + break; + + // + case this.NPRButtonTypes.Reset: + await this.handleReset(stepper, step); + break; + } + } + + async handleNextStep( + stepper: MatStepper, + step: XStepComponent, + force?: boolean + ) { + // + if ( + this.uiDisabled || + this.loading || + !stepper || + !step || + step.loading || + step.uiDisabled || + !this.hasSteps() + ) { + return; + } + + // + let nextId: string; + const stepId = await this.getValueAsync(step.id); + const stepIndex = await this.getStepIndex(stepId); + if (stepIndex === -1) { + return; + } + if (stepIndex < this.steps.length - 1) { + nextId = await this.getValueAsync(this.steps.toArray()[stepIndex + 1].id); + } + + // + const button = await this.getValueAsync(step.nextButton); + if (button.clicked) { + button.clicked.emit(); + } + + if (isNullOrEmptyString(nextId)) { + return; + } + + // + if (!force) { + // + const isUiBusy = this.uiDisabled || this.loading; + const canNextStep = await this.getValueAsync(this.canNext); + const disableNextStep = await this.getValueAsync(step.disableNext); + + // + if (isUiBusy || disableNextStep || !canNextStep) { + return; + } + + // + const isConfirmedType = this.nextConfirmer + ? this.nextConfirmer() + : this.getValue(true); + const isConfirmed = await this.getValueAsync(isConfirmedType); + + // + if (!isConfirmed) { + return; + } + + // + const isStepNextConfitmedType = step.nextConfirmer + ? step.nextConfirmer() + : this.getValue(true); + const isStepNextConfirmed = await this.getValueAsync( + isStepNextConfitmedType + ); + if (!isStepNextConfirmed) { + return; + } + } + + // + this.selected = nextId; + this.selectedChange.emit(nextId); + stepper.next(); + this.onNext.emit(stepId); + this.detectChanges(); + } + + async handlePrevStep( + stepper: MatStepper, + step: XStepComponent, + force?: boolean + ) { + // + if ( + this.uiDisabled || + this.loading || + !stepper || + !step || + step.loading || + step.uiDisabled || + !this.hasSteps() + ) { + return; + } + + // + let prevId: string; + const stepId = await this.getValueAsync(step.id); + const stepIndex = await this.getStepIndex(stepId); + if (stepIndex === -1) { + return; + } + prevId = await this.getValueAsync(this.steps.toArray()[stepIndex].id); + + // + const button = await this.getValueAsync(step.prevButton); + if (button.clicked) { + button.clicked.emit(); + } + + if (isNullOrEmptyString(prevId)) { + return; + } + + // + if (!force) { + const isUiBusy = this.uiDisabled || this.loading; + const canPrevStep = await this.getValueAsync(this.canPrev); + const disablePrevStep = await this.getValueAsync(step.disablePrev); + if (isUiBusy || disablePrevStep || !canPrevStep) { + return; + } + + // + const isConfirmedType = this.prevConfirmer + ? this.prevConfirmer() + : this.getValue(true); + const isConfirmed = await this.getValueAsync(isConfirmedType); + + // + if (!isConfirmed) { + return; + } + + // + const isStepPrevConfitmedType = step.prevConfirmer + ? step.prevConfirmer() + : this.getValue(true); + const isStepPrevConfirmed = await this.getValueAsync( + isStepPrevConfitmedType + ); + if (!isStepPrevConfirmed) { + return; + } + } + + // + this.selected = prevId; + this.selectedChange.emit(prevId); + stepper.previous(); + this.onPrev.emit(stepId); + this.detectChanges(); + } + + async handleReset( + stepper: MatStepper, + step: XStepComponent, + force?: boolean + ) { + // + if ( + this.uiDisabled || + this.loading || + !stepper || + !step || + step.loading || + step.uiDisabled || + !this.hasSteps() + ) { + return; + } + + // + const stepId = await this.getValueAsync(step.id); + const firstId = await this.getValueAsync(this.steps.toArray()[0].id); + + // + const button = await this.getValueAsync(step.resetButton); + if (button.clicked) { + button.clicked.emit(); + } + + // + if (!force) { + const isUiBusy = this.uiDisabled || this.loading; + const canResetStep = await this.getValueAsync(this.canReset); + const disableResetStep = await this.getValueAsync(step.disableReset); + if (isUiBusy || disableResetStep || !canResetStep) { + return; + } + + // + const isConfirmedType = this.resetConfirmer + ? this.resetConfirmer() + : this.getValue(true); + const isConfirmed = await this.getValueAsync(isConfirmedType); + + // + if (!isConfirmed) { + return; + } + + // + const isStepResetConfitmedType = step.resetConfirmer + ? step.resetConfirmer() + : this.getValue(true); + const isStepResetConfirmed = await this.getValueAsync( + isStepResetConfitmedType + ); + if (!isStepResetConfirmed) { + return; + } + } + + // + this.selected = firstId; + this.selectedChange.emit(firstId); + stepper.reset(); + this.onReset.emit(stepId); + this.detectChanges(); + } + + async handleToStep(stepper: MatStepper, step: XStepComponent) { + // + if ( + this.uiDisabled || + this.loading || + !step || + step.loading || + step.uiDisabled || + !this.hasSteps() || + this.selected === (await this.getValueAsync(step.id)) + ) { + return; + } + + // + const stepId = await this.getValueAsync(step.id); + const stepIndex = await this.getStepIndex(stepId); + + // + this.selected = stepId; + this.selectedChange.emit(stepId); + stepper.selectedIndex = stepIndex; + this.detectChanges(); + } + //#endregion + + // + //#region Actions ... + async next(force?: boolean) { + // + if (!this.selected || !this.stepper || !this.hasSteps()) { + return; + } + + // + const stepId = await this.getValueAsync(this.selected); + if (!stepId) { + return; + } + + // + const step = await this.getStep(stepId); + if (!step) { + return; + } + + // + await this.handleNextStep(this.stepper, step, force); + } + + async prev(force?: boolean) { + // + if (!this.selected || !this.stepper || !this.hasSteps()) { + return; + } + + // + const stepId = await this.getValueAsync(this.selected); + if (!stepId) { + return; + } + + // + const step = await this.getStep(stepId); + if (!step) { + return; + } + + // + await this.handlePrevStep(this.stepper, step, force); + } + + async reset(force?: boolean) { + // + if (!this.selected || !this.stepper || !this.hasSteps()) { + return; + } + + // + const stepId = await this.getValueAsync(this.selected); + if (!stepId) { + return; + } + + // + const step = await this.getStep(stepId); + if (!step) { + return; + } + + // + await this.handleReset(this.stepper, step, force); + } + + async to(id: XStandardType, force?: boolean) { + // + if (!id || !this.hasSteps() || !this.stepper) { + return; + } + + // + const stepId = await this.getValueAsync(id); + if (!stepId) { + return; + } + + // + const stepIndex = await this.getStepIndex(stepId); + if (stepIndex === -1) { + return; + } + + // + const step = await this.getStep(stepId); + if (!step) { + return; + } + + // + const selectedIndex = await this.getStepIndex(this.selected); + if (selectedIndex === -1 || selectedIndex === stepIndex) { + return; + } + + // + if (stepIndex > selectedIndex) { + // + // next confirm ... + // + if (!force) { + // + const isUiBusy = this.uiDisabled || this.loading; + const canNextStep = await this.getValueAsync(this.canNext); + const disableNextStep = await this.getValueAsync(step.disableNext); + + // + if (isUiBusy || disableNextStep || !canNextStep) { + return; + } + + // + const isStepNextConfitmedType = step.nextConfirmer + ? step.nextConfirmer() + : this.getValue(true); + const isStepNextConfirmed = await this.getValueAsync( + isStepNextConfitmedType + ); + if (!isStepNextConfirmed) { + return; + } + } + + // + this.selected = stepId; + this.selectedChange.emit(stepId); + } else if (stepIndex < selectedIndex) { + // + // prev Confirm ... + // + if (!force) { + const isUiBusy = this.uiDisabled || this.loading; + const canPrevStep = await this.getValueAsync(this.canPrev); + const disablePrevStep = await this.getValueAsync(step.disablePrev); + if (isUiBusy || disablePrevStep || !canPrevStep) { + return; + } + + // + const isStepPrevConfitmedType = step.prevConfirmer + ? step.prevConfirmer() + : this.getValue(true); + const isStepPrevConfirmed = await this.getValueAsync( + isStepPrevConfitmedType + ); + if (!isStepPrevConfirmed) { + return; + } + } + + // + this.selected = stepId; + this.selectedChange.emit(stepId); + } + } + //#endregion + + // + //#region Private ... + // + //#region ActionProvider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XStepperAction.NEXT: + await this.next(model.force); + break; + + // + case XStepperAction.PREV: + await this.prev(model.force); + break; + + // + case XStepperAction.RESET: + await this.reset(model.force); + break; + + // + case XStepperAction.TO: + if (model.payload) { + await this.to(model.payload, model.force); + } + break; + + // + case XStepperAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + private getActionButtons(step: XStepComponent, type: XNPRButtonType) { + // + switch (type) { + // + case this.NPRButtonTypes.Next: + return this.getValue(this.nextButton).pipe( + concatMap((cButton) => + this.getValue(step.nextButton).pipe( + map((sButton) => ({ c: cButton, s: sButton })) + ) + ) + ); + + // + case this.NPRButtonTypes.Prev: + return this.getValue(this.prevButton).pipe( + concatMap((cButton) => + this.getValue(step.prevButton).pipe( + map((sButton) => ({ c: cButton, s: sButton })) + ) + ) + ); + + // + case this.NPRButtonTypes.Reset: + return this.getValue(this.resetButton).pipe( + concatMap((cButton) => + this.getValue(step.resetButton).pipe( + map((sButton) => ({ c: cButton, s: sButton })) + ) + ) + ); + } + } + + private getActionButtonDisabled(step: XStepComponent, type: XNPRButtonType) { + // + switch (type) { + // + case this.NPRButtonTypes.Next: + return this.getOptionalValue(this.disableNext).pipe( + concatMap((cDisable) => + this.getOptionalValue(step.disableNext).pipe( + map( + (sDisable) => + this.uiDisabled || cDisable || step.uiDisabled || sDisable + ) + ) + ) + ); + + // + case this.NPRButtonTypes.Prev: + return this.getOptionalValue(this.disablePrev).pipe( + concatMap((cDisable) => + this.getOptionalValue(step.disablePrev).pipe( + map( + (sDisable) => + this.uiDisabled || cDisable || step.uiDisabled || sDisable + ) + ) + ) + ); + + // + case this.NPRButtonTypes.Reset: + return this.getOptionalValue(this.disableReset).pipe( + concatMap((cDisable) => + this.getOptionalValue(step.disableReset).pipe( + map( + (sDisable) => + this.uiDisabled || cDisable || step.uiDisabled || sDisable + ) + ) + ) + ); + } + } + + private async getStep(id: string) { + // + if (!id || !this.hasSteps()) { + return null; + } + + // + return this.steps.toArray().find(async (p) => { + const pId = await this.getValueAsync(p.id); + return pId === id; + }); + } + + private async getStepIndex(id: string) { + // + if (!id || !this.hasSteps()) { + return -1; + } + + // + let result = -1; + const steps = this.steps.toArray(); + if (!this.hasChild(steps)) { + return result; + } + + // + for (let i = 0; i < steps.length; i++) { + // + const step = steps[i]; + const stepId = await this.getValueAsync(step.id); + if (stepId === id) { + result = i; + } + } + + // + return result; + } + + // + //#region Color Appliers ... + private async applyIconColor() { + // + const color = await this.getValueAsync(this.iconColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const fgColor = getXColorCssVariableName( + colorDescriptor.name, + true, + colorDescriptor.brightnessLevel + ); + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(fgColor) || isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XStepperCssVars.IconForegroundColor, `var(${fgColor})`); + this.setCssVar(XStepperCssVars.IconBackgroundColor, `var(${bgColor})`); + + // + this.detectChanges(); + } + + private async applyTitleColor() { + // + const color = await this.getValueAsync(this.titleColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XStepperCssVars.TitleForegroundColor, `var(${bgColor})`); + + // + this.detectChanges(); + } + + private async applyeaderColor() { + // + const color = await this.getValueAsync(this.headerColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const fgColor = getXColorCssVariableName( + colorDescriptor.name, + true, + colorDescriptor.brightnessLevel + ); + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(fgColor) || isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XStepperCssVars.HeaderForegroundColor, `var(${fgColor})`); + this.setCssVar(XStepperCssVars.HeaderBackgroundColor, `var(${bgColor})`); + + // + this.detectChanges(); + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-stepper\x-stepper.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ReactiveFormsModule } from '@angular/forms'; +import { XIconModule } from '../x-icon/x-icon.module'; +import { XCardModule } from '../x-card/x-card.module'; +import { XFormModule } from './../x-form/x-form.module'; +import { XStepperComponent } from './x-stepper.component'; +import { XStepComponent } from './x-step/x-step.component'; +import { XButtonModule } from '../x-button/x-button.module'; +import { XSlotterModule } from '../x-slotter/x-slotter.module'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XStepperComponent, XStepComponent], + imports: [ + XIconModule, + XCardModule, + XFormModule, + CommonModule, + XButtonModule, + XSlotterModule, + ReactiveFormsModule, + XBaseComponentModule, + ], + exports: [ + XIconModule, + XCardModule, + XFormModule, + CommonModule, + XButtonModule, + XStepComponent, + XSlotterModule, + XStepperComponent, + ReactiveFormsModule, + XBaseComponentModule, + ], +}) +export class XStepperModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-stepper\x-stepper.typings.ts + +import { + Input, + NgZone, + Inject, + Output, + Component, + Renderer2, + ElementRef, + Injectable, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XStandardType, + XOneOrManyType, + XColorIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { concatMap, map } from 'rxjs/operators'; +import { + XNextPrevResetAndHostContainer, + IXNextPrevResetAndHostContainer, + XNextPrevResetAndHostContainerComponent, +} from '../classes/i-x-next-prev-reset.container'; +import { forkJoin, Observable, Subject } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export enum XStepperType { + HORIZONTAL = 'horizontal', + VERTICAL = 'vertical', +} + +export type XStepperTypeIdentifier = XStepperType | string; + +export enum XStepperLabelPosition { + END = 'end', + BOTTOM = 'bottom', +} + +export type XStepperLabelPositionIdentifier = XStepperLabelPosition | string; + +export enum XStepperAction { + NEXT = 'next', + PREV = 'prev', + RESET = 'reset', + TO = 'to', + + // + Refresh = 'refresh', +} + +export type XStepperActionIdentifier = XStepperAction | string; + +export interface XStepperActionModel + extends XBaseActionModel { + force?: boolean; +} + +export enum XStepperCssVars { + // + IconForegroundColor = 'icon-foreground-color', + IconBackgroundColor = 'icon-background-color', + + // + TitleForegroundColor = 'title-foreground-color', + + // + HeaderForegroundColor = 'header-foreground-color', + HeaderBackgroundColor = 'header-background-color', +} + +export type XStepperCssVarsIdentifier = XStepperCssVars | string; + +export function defaultActionConfirmer() { + return true; +} + +// +//#region Inside Card Container ... +export interface IXStepperContainer extends IXNextPrevResetAndHostContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + type: XStandardType; + showLabel: XStandardType; + labelPosition: XStandardType; + + // + selectableHeaders: XStandardType; + + // + selectedIndex: XStandardType; + selectedChange: EventEmitter; + + // + iconColor: XStandardType; + titleColor: XStandardType; + headerColor: XStandardType; +} + +@Injectable({ + providedIn: 'root', +}) +export class XStepperContainer + extends XNextPrevResetAndHostContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider: Subject = + new Subject(); + readonly cssClass: XStandardType>; + + // + readonly type: XStandardType = + XStepperType.HORIZONTAL; + readonly showLabel: XStandardType = true; + readonly labelPosition: XStandardType = + XStepperLabelPosition.END; + + // + readonly selectableHeaders: XStandardType = false; + + // + readonly selectedIndex: XStandardType; + readonly selectedChange: EventEmitter = new EventEmitter(); + + // + readonly iconColor: XStandardType = + XColorWithBrightness.Tertiary; + readonly titleColor: XStandardType = + XColorWithBrightness.Secondary; + readonly headerColor: XStandardType = + XColorWithBrightness.Light; + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XStepperContainerComponent + extends XNextPrevResetAndHostContainerComponent + implements IXStepperContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + type: XStandardType = this.propertyProvider.type; + + @Input() + showLabel: XStandardType = this.propertyProvider.showLabel; + + @Input() + labelPosition: XStandardType = + this.propertyProvider.labelPosition; + + // + @Input() + selectableHeaders: XStandardType = + this.propertyProvider.selectableHeaders; + + // + @Input() + selectedIndex: XStandardType = this.propertyProvider.selectedIndex; + + @Output() + selectedChange: EventEmitter = new EventEmitter(); + + // + @Input() + iconColor: XStandardType = this.propertyProvider.iconColor; + + @Input() + titleColor: XStandardType = + this.propertyProvider.titleColor; + + @Input() + headerColor: XStandardType = + this.propertyProvider.headerColor; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XStepperContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXStepperContainer + ): Observable { + return super + .extractProps(container ? container : this) + .pipe( + concatMap((superData) => { + return forkJoin({ + // + cssClass: this.getValue( + container ? container.cssClass : this.cssClass + ), + type: this.getValue(container ? container.type : this.type), + showLabel: this.getValue( + container ? container.showLabel : this.showLabel + ), + labelPosition: this.getValue( + container ? container.labelPosition : this.labelPosition + ), + selectableHeaders: this.getValue( + container ? container.selectableHeaders : this.selectableHeaders + ), + selectedIndex: this.getValue( + container ? container.selectedIndex : this.selectedIndex + ), + iconColor: this.getValue( + container ? container.iconColor : this.iconColor + ), + titleColor: this.getValue( + container ? container.titleColor : this.titleColor + ), + headerColor: this.getValue( + container ? container.headerColor : this.headerColor + ), + }).pipe( + map((data) => { + return { + superData, + data, + }; + }) + ); + }) + ) + .pipe( + map((info) => { + return { + // + ...info.superData, + + // + ...info.data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + selectedChange: this.selectedChange, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-stepper\x-step\x-step.component.html + + + + + + + + + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-stepper\x-step\x-step.component.scss + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-stepper\x-step\x-step.component.ts + +import { + XStepAction, + XStepActionModel, + XStepContainerComponent, +} from './x-step.typings'; +import { Subject, Subscription } from 'rxjs'; +import { XModalButtonRole, nameof } from 'x-framework-core'; +import { Component, ViewChild, TemplateRef } from '@angular/core'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-step', + templateUrl: './x-step.component.html', + styleUrls: ['./x-step.component.scss'], +}) +export class XStepComponent extends XStepContainerComponent { + // + //#region Props ... + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + //#region ViewChilds ... + // + @ViewChild('label', { static: false }) + label!: TemplateRef; + + // + @ViewChild('content', { static: false }) + content!: TemplateRef; + + // + @ViewChild('actions', { static: false }) + actions!: TemplateRef; + //#endregion + //#endregion + + // + //#region LifeCycles ... + onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerInstanceHandlers() { + super.registerInstanceHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region Private ... + // + //#region ActionProvider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XStepAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + private defaultStepperResetConfirmer() { + // + return new Promise((resolver) => { + // + this.managerService.dialogService.presentAreYouSureYesNoDialog( + () => resolver(true), + XModalButtonRole.Destructive, + () => resolver(false) + ); + }); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-stepper\x-step\x-step.typings.ts + +import { + Input, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + ChangeDetectorRef, +} from '@angular/core'; +import { concatMap, map } from 'rxjs/operators'; +import { + XNextPrevResetContainer, + IXNextPrevResetContainer, + XNextPrevResetContainerComponent, +} from '../../classes/i-x-next-prev-reset.container'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { forkJoin, Subject, Observable } from 'rxjs'; +import { XManagerService } from 'x-framework-services'; +import { generateUuid, XStandardType } from 'x-framework-core'; +import { XBaseActionModel } from '../../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../../config/x-framework-components.config'; + +export enum XStepAction { + Refresh = 'refresh', +} + +export type XStepActionIdentifier = XStepAction | string; + +export interface XStepActionModel + extends XBaseActionModel { + force?: boolean; +} + +// +//#region Container ... +export interface IXStepContainer extends IXNextPrevResetContainer { + actionProvider: Subject; + id: XStandardType; + hasCustomActions: XStandardType; +} + +@Injectable({ + providedIn: 'root', +}) +export class XStepContainer + extends XNextPrevResetContainer + implements Readonly +{ + // + //#region Props ... + readonly actionProvider: Subject = + new Subject(); + readonly id: XStandardType; // = generateUuid('XStepComponent'); + readonly hasCustomActions: XStandardType = false; + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XStepContainerComponent + extends XNextPrevResetContainerComponent + implements IXStepContainer +{ + // + //#region Props ... + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + id: XStandardType = generateUuid('XStepComponent'); + + @Input() + hasCustomActions: XStandardType = + this.propertyProvider.hasCustomActions; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XStepContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXStepContainer + ): Observable { + return super + .extractProps(container ? container : this) + .pipe( + concatMap((superData) => { + return forkJoin({ + // + id: this.getValue(container ? container.id : this.id), + hasCustomActions: this.getValue( + container ? container.hasCustomActions : this.hasCustomActions + ), + }).pipe( + map((data) => { + return { + superData, + data, + }; + }) + ); + }) + ) + .pipe( + map((info) => { + return { + // + ...info.superData, + + // + ...info.data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-tab-navigator\x-tab-navigator.component.html + +
+ + + + + + + + + + + {{ getResourceValue(item.title) | async }} + + + + + {{ getItemBadgeNumber(item) | async }} + + + + + +
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-tab-navigator\x-tab-navigator.component.scss + +:host { + // + --highlight-foreground-color: var(--x-color-success-contrast); + --highlight-background-color: var(--x-color-success); + + // + --selected-foreground-color: var(--x-color-secondary-contrast); + --selected-background-color: var(--x-color-secondary); + + // + --badge-foreground-color: var(--x-color-dark-contrast); + --badge-background-color: var(--x-color-dark); + + // + --icon-color: var(--x-color-dark); + + // + --foreground-color: var(--x-color-light-contrast); + --background-color: var(--x-color-light); +} + +x-icon { + font-size: 12px; + color: var(--icon-color); +} + +ion-tab-button { + --color: var(--foreground-color); + --color-focused: var(--highlight-foreground-color); + --color-selected: var(--selected-foreground-color); +} + +ion-tab-button::part(native) { + color: var(--foreground-color); + + &:hover { + color: var(--highlight-foreground-color); + } +} + +ion-badge { + color: var(--badge-foreground-color); + background-color: var(--badge-background-color); +} + +.tab-selected { + background-color: var(--selected-background-color) !important; + + x-icon { + color: var(--selected-foreground-color) !important; + } +} + +.x-tab-label { + font-size: 9px !important; + + &:hover { + color: var(--highlight-foreground-color); + } +} + +.x-nav-button:hover { + color: var(--highlight-foreground-color); + background-color: var(--highlight-background-color); + + x-icon { + color: var(--highlight-foreground-color) !important; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-tab-navigator\x-tab-navigator.component.ts + +import { + nameof, + parseXColor, + isNullOrEmptyString, + getXColorCssVariableName, +} from 'x-framework-core'; +import { + XTabNavigatorItem, + XTabNavigatorAction, + XTabNavigatorCssVars, + XTabNavigatorItemLayout, + XTabNavigatorActionModel, + XTabNavigatorContainerComponent, +} from './x-tab-navigator.typings'; +import { map, concatMap } from 'rxjs/operators'; +import { of, Observable, Subject, Subscription } from 'rxjs'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-tab-navigator', + templateUrl: './x-tab-navigator.component.html', + styleUrls: ['./x-tab-navigator.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XTabNavigatorComponent extends XTabNavigatorContainerComponent { + // + //#region Props ... + // + private PREPARED_ITEMS: XTabNavigatorItem[]; + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + get preparedItems() { + return this.PREPARED_ITEMS; + } + //#endregion + + // + //#region LifeCycles ... + async onInit() { + super.onInit(); + + // + await this.prepareItems(); + await this.applyDefaultColors(); + } + + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + + // + //#region Items Changed ... + const isItemsChanged = changeKeys.includes( + nameof('items') + ); + if (isItemsChanged) { + await this.prepareItems(); + } + //#endregion + + // + //#region Colors Changed ... + // + // Color ... + const isColorChanged = changeKeys.includes( + nameof('color') + ); + if (isColorChanged) { + await this.applyColor(); + } + + // + // Icon Color ... + const isIconColorChanged = changeKeys.includes( + nameof('iconColor') + ); + if (isIconColorChanged) { + await this.applyIconColor(); + } + + // + // HighLight Color ... + const isHighLightColorChanged = changeKeys.includes( + nameof('highlightColor') + ); + if (isHighLightColorChanged) { + await this.applyHighLightedColor(); + } + + // + // Selected Color ... + const isSelectedColorChanged = changeKeys.includes( + nameof('selectedColor') + ); + if (isSelectedColorChanged) { + await this.applySelectedColor(); + } + + // + // Badge Color ... + const isBadgeColorChanged = changeKeys.includes( + nameof('badgeColor') + ); + if (isBadgeColorChanged) { + await this.applyBadgeColor(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region Ui Providers ... + isDisabled(item: XTabNavigatorItem) { + return (this.getOptionalValue(item.disabled) as Observable).pipe( + map((disabled) => disabled || this.uiDisabled) + ); + } + + getItemRoute(item: XTabNavigatorItem) { + return this.getArrayValue(item.href).pipe( + map((href) => { + // + if (!this.hasChild(href)) { + return undefined; + } + + // + if (href.length === 1) { + return href[0]; + } + + // + const result = href.join('/').replace('//', '/'); + return result; + }) + ); + } + + getItemTooltip(item: XTabNavigatorItem) { + return this.getResourceValue(item.description).pipe( + concatMap((val) => { + if (isNullOrEmptyString(val.toString())) { + return this.getResourceValue(item.title); + } else { + return of(val); + } + }) + ); + } + + hideTabTitle(item: XTabNavigatorItem) { + return this.getValue(this.hideTabLabelsOnSmallScreens).pipe( + concatMap((hideOnMobile) => { + return hideOnMobile + ? this.isMobileUi$ + : this.getValue(item.layout).pipe( + map((layout) => layout === XTabNavigatorItemLayout.LABEL_HIDE) + ); + }) + ); + } + + hasBadgeNumber(item: XTabNavigatorItem) { + return this.getValue(item.badge).pipe(map((badge) => badge > 0)); + } + + getItemBadgeNumber(item: XTabNavigatorItem) { + return (this.getOptionalValue(item.badge) as Observable).pipe( + map((badge) => { + if (!badge) { + return this.applyLocale(0); + } else { + return this.applyLocale(badge); + } + }) + ); + } + //#endregion + + // + //#region Private ... + private async prepareItems() { + // + const items = await this.getValueAsync(this.items); + this.PREPARED_ITEMS = items; + + // + this.detectChanges(); + } + + // + //#region Action Provider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + switch (model.action) { + // + case XTabNavigatorAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + // + //#region Color Appliers ... + private async applyDefaultColors() { + // + await this.applyColor(); + await this.applyIconColor(); + await this.applyBadgeColor(); + await this.applySelectedColor(); + await this.applyHighLightedColor(); + } + + // + // Color ... + private async applyColor() { + // + const color = await this.getValueAsync(this.color); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const fgColor = getXColorCssVariableName( + colorDescriptor.name, + true, + colorDescriptor.brightnessLevel + ); + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(fgColor) || isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XTabNavigatorCssVars.ForegroundColor, `var(${fgColor})`); + this.setCssVar(XTabNavigatorCssVars.BackgroundColor, `var(${bgColor})`); + + // + this.detectChanges(); + } + + // + // Icon Color ... + private async applyIconColor() { + // + const color = await this.getValueAsync(this.iconColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XTabNavigatorCssVars.IconColor, `var(${bgColor})`); + + // + this.detectChanges(); + } + + // + // HighLight Color ... + private async applyHighLightedColor() { + // + const color = await this.getValueAsync(this.highlightColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const fgColor = getXColorCssVariableName( + colorDescriptor.name, + true, + colorDescriptor.brightnessLevel + ); + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(fgColor) || isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar( + XTabNavigatorCssVars.HighlightedForegroundColor, + `var(${fgColor})` + ); + this.setCssVar( + XTabNavigatorCssVars.HighlightedBackgroundColor, + `var(${bgColor})` + ); + + // + this.detectChanges(); + } + + // + // Selected Color ... + private async applySelectedColor() { + // + const color = await this.getValueAsync(this.selectedColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const fgColor = getXColorCssVariableName( + colorDescriptor.name, + true, + colorDescriptor.brightnessLevel + ); + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(fgColor) || isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar( + XTabNavigatorCssVars.SelectedForegroundColor, + `var(${fgColor})` + ); + this.setCssVar( + XTabNavigatorCssVars.SelectedBackgroundColor, + `var(${bgColor})` + ); + + // + this.detectChanges(); + } + + // + // Badge Color ... + private async applyBadgeColor() { + // + const color = await this.getValueAsync(this.badgeColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const fgColor = getXColorCssVariableName( + colorDescriptor.name, + true, + colorDescriptor.brightnessLevel + ); + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(fgColor) || isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar( + XTabNavigatorCssVars.BadgeForegroundColor, + `var(${fgColor})` + ); + this.setCssVar( + XTabNavigatorCssVars.BadgeBackgroundColor, + `var(${bgColor})` + ); + + // + this.detectChanges(); + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-tab-navigator\x-tab-navigator.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XIconModule } from '../x-icon/x-icon.module'; +import { XTabNavigatorComponent } from './x-tab-navigator.component'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XTabNavigatorComponent], + imports: [CommonModule, XBaseComponentModule, XIconModule], + exports: [XIconModule, XBaseComponentModule, XTabNavigatorComponent] +}) +export class XTabNavigatorModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-tab-navigator\x-tab-navigator.tools.ts + +import { + XTabNavigatorItem, + XTabNavigatorItemLayout, +} from './x-tab-navigator.typings'; +import { XPage } from 'x-framework-core'; + +export function toTabNavigatiorItem(page: XPage) { + // + if (!page) { + return null; + } + + // + const result: XTabNavigatorItem = { + id: page.id ? page.id : page.name, + title: page.title, + description: page.description, + href: page.baseRoute, + disabled: false, + icon: page.icon, + badge: 0, + layout: XTabNavigatorItemLayout.ICON_TOP, + }; + + // + return result; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-tab-navigator\x-tab-navigator.typings.ts + +import { + Input, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + ChangeDetectorRef, +} from '@angular/core'; +import { + XStandardType, + XOneOrManyType, + XColorIdentifier, + XColorWithBrightness, + XRouterTargetIdentifier, +} from 'x-framework-core'; +import { map } from 'rxjs/operators'; +import { + XBaseContainer, + IXBaseContainer, + XBaseContainerComponent, +} from '../classes/i-x-base.container'; +import { forkJoin, Observable, Subject } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XIconIdentifier } from '../x-icon/x-icon.typings'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XNavigatorListItem } from '../x-page/x-navigator/x-navigator.typings'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export interface XTabNavigatorItem + extends Pick< + XNavigatorListItem, + 'id' | 'title' | 'description' | 'href' | 'disabled' + > { + // + icon: XStandardType; + badge?: XStandardType; + // + download?: XStandardType; + routerTarget?: XStandardType; + // + layout: XStandardType; +} + +export enum XTabNavigatorItemLayout { + ICON_TOP = 'icon-top', + ICON_END = 'icon-end', + ICON_HIDE = 'icon-hide', + ICON_START = 'icon-start', + LABEL_HIDE = 'label-hide', + ICON_BOTTOM = 'icon-bottom', +} + +export type XTabNavigatorItemLayoutIdentifier = + | XTabNavigatorItemLayout + | string; + +export enum XTabNavigatorNavigatorSlot { + TOP = 'top', + BOTTOM = 'bottom', +} + +export type XTabNavigatorNavigatorSlotIdentifier = + | XTabNavigatorNavigatorSlot + | string; + +export enum XTabNavigatorAction { + Refresh = 'refresh', +} + +export type XTabNavigatorActionIdentifier = XTabNavigatorAction | string; + +export interface XTabNavigatorActionModel + extends XBaseActionModel {} + +export enum XTabNavigatorCssVars { + // + HighlightedForegroundColor = 'highlight-foreground-color', + HighlightedBackgroundColor = 'highlight-background-color', + + // + SelectedForegroundColor = 'selected-foreground-color', + SelectedBackgroundColor = 'selected-background-color', + + // + BadgeForegroundColor = 'badge-foreground-color', + BadgeBackgroundColor = 'badge-background-color', + + // + IconColor = 'icon-color', + + // + ForegroundColor = 'foreground-color', + BackgroundColor = 'background-color', +} + +// +//#region Container ... +export interface IXTabNavigatorContainer extends IXBaseContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + navigatorSlot: XStandardType; + hideTabLabelsOnSmallScreens: XStandardType; + items: XStandardType; + + // + color: XStandardType; + foregroundColor: XStandardType; + iconColor: XStandardType; + badgeColor: XStandardType; + highlightColor: XStandardType; + selectedColor: XStandardType; +} + +@Injectable({ + providedIn: 'root' +}) +export class XTabNavigatorContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider: Subject = + new Subject(); + readonly cssClass: XStandardType>; + + // + readonly navigatorSlot: XStandardType = + XTabNavigatorNavigatorSlot.BOTTOM; + readonly hideTabLabelsOnSmallScreens: XStandardType = true; + readonly items: XStandardType = []; + + // + readonly color: XStandardType = XColorWithBrightness.Light; + readonly foregroundColor: XStandardType = false; + readonly iconColor: XStandardType = + XColorWithBrightness.Dark; + readonly badgeColor: XStandardType = + XColorWithBrightness.WarningTint; + readonly highlightColor: XStandardType = + XColorWithBrightness.WarningShade; + readonly selectedColor: XStandardType = + XColorWithBrightness.TertiaryShade; + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XTabNavigatorContainerComponent + extends XBaseContainerComponent + implements IXTabNavigatorContainer +{ + // + //#region Props ... + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + @Input() + navigatorSlot: XStandardType = + this.propertyProvider.navigatorSlot; + + @Input() + hideTabLabelsOnSmallScreens: XStandardType = + this.propertyProvider.hideTabLabelsOnSmallScreens; + + @Input() + items: XStandardType = this.propertyProvider.items; + + @Input() + color: XStandardType = this.propertyProvider.color; + + @Input() + foregroundColor: XStandardType = + this.propertyProvider.foregroundColor; + + @Input() + iconColor: XStandardType = this.propertyProvider.iconColor; + + @Input() + badgeColor: XStandardType = + this.propertyProvider.badgeColor; + + @Input() + highlightColor: XStandardType = + this.propertyProvider.highlightColor; + + @Input() + selectedColor: XStandardType = + this.propertyProvider.selectedColor; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XTabNavigatorContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXTabNavigatorContainer + ): Observable { + return forkJoin({ + // + cssClass: this.getValue(container ? container.cssClass : this.cssClass), + navigatorSlot: this.getValue( + container ? container.navigatorSlot : this.navigatorSlot + ), + hideTabLabelsOnSmallScreens: this.getValue( + container + ? container.hideTabLabelsOnSmallScreens + : this.hideTabLabelsOnSmallScreens + ), + items: this.getValue(container ? container.items : this.items), + color: this.getValue(container ? container.color : this.color), + foregroundColor: this.getValue( + container ? container.foregroundColor : this.foregroundColor + ), + iconColor: this.getValue( + container ? container.iconColor : this.iconColor + ), + badgeColor: this.getValue( + container ? container.badgeColor : this.badgeColor + ), + highlightColor: this.getValue( + container ? container.highlightColor : this.highlightColor + ), + selectedColor: this.getValue( + container ? container.selectedColor : this.selectedColor + ), + }).pipe( + map((data) => { + return { + // + ...data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-table\x-table.component.html + + + + + + + + +
+ +
+
+
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ {{ title }} +
+
+ + + + + + + + +
+ {{ value }} +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + {{ + preparedPaginatorOptions.pageInfoProvider( + managerService.currentLocale, + currentPage, + pageSize, + totalPages, + totalItems + ) + }} + + + + +
+ + +
+ + {{ applyLocale(pageSize) }} + +
+
+
+ + + + + + + + + + + + + +
+
+ + + + + + + + + + + + +
+ +
+ + + + + + + +
+
+ + + + + + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-table\x-table.component.scss + +::ng-deep :root, +:host { + // + --divider-color: var(--x-color-dark); + + // + --highlight-color: var(--x-color-warning-shade-contrast); + --highlight-background-color: var(--x-color-warning-shade); + + // + --odd-rows-background-color: inherit; + --even-rows-background-color: inherit; + + // + --check-box-border-color: var(--x-color-dark); + --check-box-check-mark-color: var(--x-color-dark); +} + +x-card { + width: inherit; + height: inherit; +} + +.x-table { + width: inherit; + height: inherit; +} + +mat-row, +td.mat-cell, +mat-header-row, +mat-footer-row, +th.mat-header-cell, +td.mat-footer-cell { + border-bottom-color: var(--divider-color) !important; +} + +.mat-paginator, +.mat-paginator-page-size .mat-select-trigger { + color: unset !important; +} + +.mat-paginator { + background-color: unset !important; +} + +.x-table-paginator-wrapper { + width: 100%; +} + +.x-page-select-icon { + margin: { + left: 4px; + right: 4px; + } +} + +.x-table-cell, +.x-table-header { + flex: 1 !important; +} + +.x-header-title { + width: fit-content; +} + +.x-table-selection { + flex: none !important; +} + +.x-table-cell { + color: inherit !important; + background-color: inherit !important; +} + +.x-table-row { + &:hover { + color: var(--highlight-color) !important; + background-color: var(--highlight-background-color) !important; + } + + &:not(:hover):nth-child(odd) { + background-color: var(--odd-rows-background-color) !important; + } + + &:not(:hover):nth-child(even) { + background-color: var(--even-rows-background-color) !important; + } +} + +.x-check-box { + --border-color: var(--check-box-border-color, inherit) !important; + --checkmark-color: var(--check-box-check-mark-color, inherit) !important; + --background-checked: auto !important; + --border-color-checked: auto !important; +} + +.x-selection-master { + visibility: hidden !important; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-table\x-table.component.ts + +import { + nameof, + XColor, + toArray, + XLocale, + isNumber, + hasChild, + isFunction, + applyLocale, + XResourceIDs, + XModalButtonRole, + XPickerColumnOption, +} from 'x-framework-core'; +import { + XTableAction, + XTableActionModel, + XTableColumnOption, + XTableItemPosition, + XTableActionButton, + XTableActionOptions, + XTablePaginatorOptions, + XTableContainerComponent, + X_TABLE_SELECTION_HEADER_IDENTIFIER, +} from './x-table.typings'; +import { Subject, Subscription } from 'rxjs'; +import { map, concatMap } from 'rxjs/operators'; +import { MatSort } from '@angular/material/sort'; +import { XIconNames } from '../x-icon/x-icon.typings'; +import { SelectionModel } from '@angular/cdk/collections'; +import { MatPaginator } from '@angular/material/paginator'; +import { MatTableDataSource } from '@angular/material/table'; +import { Component, ViewChild, ChangeDetectionStrategy } from '@angular/core'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-table', + templateUrl: './x-table.component.html', + styleUrls: ['./x-table.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XTableComponent extends XTableContainerComponent { + // + //#region Props ... + private MODAL: any; + private SORT: MatSort; + private DATA: any[] = []; + private PAGE_SIZE: number; + private TOTAL_PAGES: number; + private TOTAL_ITEMS: number; + private CURRENT_PAGE: number; + private PAGINATOR: MatPaginator; + private IS_ROWS_SELECTABLE: boolean; + private DISPLAYED_COLUMNS: string[] = []; + private ACTION_OPTIONS: XTableActionOptions; + private SELECTION_SUBSCRIPTION: Subscription; + private SELECTION = new SelectionModel(); + private COLUMNS_DATA: XTableColumnOption[]; + private PAGINATOR_OPTIONS: XTablePaginatorOptions; + private PAGINATOR_INIT_SUBSCRIPTION: Subscription; + private PAGINATOR_PAGE_SUBSCRIPTION: Subscription; + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + private DATA_SOURCE = new MatTableDataSource(this.DATA); + + // + pageNumberInput = false; + selectionRowTitle = X_TABLE_SELECTION_HEADER_IDENTIFIER; + + // + //#region Getters ... + get data() { + return this.DATA; + } + + get isRowsSelectable() { + return this.IS_ROWS_SELECTABLE; + } + + get columnsData() { + return this.COLUMNS_DATA; + } + + get dataSource() { + return this.DATA_SOURCE; + } + + get displayedColumns() { + return this.DISPLAYED_COLUMNS; + } + + get selection() { + return this.SELECTION; + } + + get preparedPaginatorOptions() { + return this.PAGINATOR_OPTIONS; + } + + get pageSize() { + return this.PAGE_SIZE; + } + + get currentPage() { + return this.CURRENT_PAGE; + } + + get totalPages() { + return this.TOTAL_PAGES; + } + + get totalItems() { + return this.TOTAL_ITEMS; + } + + get selectedItems() { + return this.isRowsSelectable && this.selection + ? this.selection.selected + : []; + } + + get preparedActionOptions() { + return this.ACTION_OPTIONS; + } + //#endregion + + // + //#region Sort Props ... + @ViewChild(MatSort, { static: false }) + set sort(v: MatSort) { + if (!v) { + return; + } + + // + this.SORT = v; + this.DATA_SOURCE.sort = v; + } + + get sort() { + return this.SORT; + } + //#endregion + + // + //#region Paginator Props ... + @ViewChild(MatPaginator, { static: false }) + set paginator(v: MatPaginator) { + if (!v) { + return; + } + + // + this.PAGINATOR = v; + this.DATA_SOURCE.paginator = v; + this.registerPaginatorHandlers(); + } + + get paginator() { + return this.PAGINATOR; + } + //#endregion + //#endregion + + // + //#region LifeCycles ... + async onInit() { + super.onInit(); + + // + await this.preparePaginatorOptions(); + } + + async afterViewInit() { + super.afterViewInit(); + + // + await this.prepareUIData(); + await this.prepareSelection(); + await this.prepareActionOptions(); + } + + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + let isLoadingChanged = changeKeys.includes( + nameof('loading') + ); + let isUiDisabledChanged = changeKeys.includes( + nameof('uiDisabled') + ); + if (isLoadingChanged || isUiDisabledChanged) { + await this.prepareActionOptions(); + } + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + + // + // Handle Prepare Data Source ... + if ( + changeKeys.includes(nameof('properties')) || + changeKeys.includes(nameof('selectableRows')) + ) { + await this.prepareDataSource(); + } + + // + // Handle Prepare Selection ... + if ( + changeKeys.includes(nameof('allowMultiSelect')) || + changeKeys.includes(nameof('initialSelectedValues')) + ) { + await this.prepareSelection(); + } + + // + // Handle Paginator Options ... + if (changeKeys.includes(nameof('paginatorOptions'))) { + await this.preparePaginatorOptions(); + } + + // + // Handle Action Options ... + if (changeKeys.includes(nameof('actionOptions'))) { + await this.prepareActionOptions(); + } + + // + // Handle UI Data ... + if (changeKeys.includes(nameof('selectableRows'))) { + await this.prepareUIData(); + } + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region UI Providers ... + isSelectionRow(col: XTableColumnOption) { + return this.getValue(col.name).pipe( + map((colName) => colName === X_TABLE_SELECTION_HEADER_IDENTIFIER) + ); + } + + isSearchbarTopPositioned() { + return this.getValue(this.searchBarPosition).pipe( + map((pos) => pos === XTableItemPosition.TOP) + ); + } + + isPaginatorTopPositioned() { + return this.getValue(this.paginatorPosition).pipe( + map((pos) => pos === XTableItemPosition.TOP) + ); + } + + isActionBarTopPositioned() { + return this.getValue(this.actionBarPosition).pipe( + map((pos) => pos === XTableItemPosition.TOP) + ); + } + + getHeaderClass(col: XTableColumnOption) { + return this.getValue(col.name).pipe( + concatMap((colName) => + this.getValue(this.headerCellCssClass).pipe( + map((cssClass) => { + return { colName, cssClass }; + }) + ) + ), + map((info) => { + // + let result: string[] = []; + + // + const className = `x-table-header-${info.colName.toString()}`; + const cssClasses = toArray(info.cssClass); + + // + result = [className].concat(cssClasses); + + // + return result; + }) + ); + } + + getCellClass(col: XTableColumnOption) { + return this.getValue(col.name).pipe( + concatMap((colName) => + this.getValue(this.cellCssClass).pipe( + map((cssClass) => { + return { colName, cssClass }; + }) + ) + ), + map((info) => { + // + let result: string[] = []; + + // + const className = `x-table-cell-${info.colName.toString()}`; + const cssClasses = toArray(info.cssClass); + + // + result = [className].concat(cssClasses); + + // + return result; + }) + ); + } + + getColumnName(col: XTableColumnOption) { + return this.getValue(col.name).pipe(map(name => name.toString())); + } + //#endregion + + // + //#region UI Handlers ... + async handleRowClicked(row: any) { + // + const isClickable = await this.getValueAsync(this.clickableRows); + const isSelectable = await this.getValueAsync(this.selectableRows); + + // + if (!isClickable && !this.selectableRows) { + return; + } + + // + if (isSelectable && this.selection) { + // + if (this.uiDisabled || this.loading) { + return; + } + + // + this.selection.toggle(row); + } + + // + if (isClickable) { + // + if (this.uiDisabled || this.loading) { + return; + } + + // + this.rowClicked.emit(row); + } + } + + async handleRowSelected(event: CustomEvent | any, row: any) { + // + if (this.uiDisabled || this.loading) { + return; + } + + // + const isSelectable = await this.getValueAsync(this.selectableRows); + if (!isSelectable) { + return; + } + + // + const selected = event.detail.checked as boolean; + + // + if (!selected) { + return; + } + + // + this.rowSelected.emit(row); + } + + async handleFilterData(event: CustomEvent | any) { + // + if (this.uiDisabled || this.loading) { + return; + } + + // + const query = + event && event.detail && event.detail.value + ? event.detail.value.toString().trim().toLowerCase() + : ''; + + // + await this.filterData(query); + } + + async handlePageChange(event: CustomEvent | any) { + // + const selectedPage = (event.detail.value as number) || undefined; + if (!selectedPage) { + return; + } + + // + await this.toPage(selectedPage); + } + + async handlePageSizeChange() { + // + if (!this.preparedPaginatorOptions || !this.pageSize) { + return; + } + + // + const pageSizes = await this.getValueAsync( + this.preparedPaginatorOptions.pageSizes + ); + if (!hasChild(pageSizes)) { + return; + } + + const options: XPickerColumnOption[] = pageSizes.map((ps) => { + // + const opt: XPickerColumnOption = { + text: this.applyLocale(ps.toString()), + value: ps, + disabled: ps === this.pageSize, + }; + + // + return opt; + }); + + // + if (this.MODAL) { + await this.managerService.dialogService.pickerController.dismiss(); + this.MODAL = null; + } + + // + this.MODAL = await this.managerService.dialogService.presentPicker({ + buttons: [ + { + text: this.resourceProvider('accept'), + role: XModalButtonRole.Selected, + handler: (value) => { + if (!value) { + return; + } + + // + const pageSize = value.pageSize.value; + this.changePageSize(pageSize); + this.detectChanges(); + }, + }, + { + text: this.resourceProvider('cancel'), + role: XModalButtonRole.Cancel, + }, + ], + columns: [{ name: 'pageSize', options }], + }); + + // + await this.MODAL.onWillDismiss(); + this.MODAL = null; + } + + async handleActionBarItemClick( + action: XTableActionButton, + selectedItems: any[] + ) { + // + if (action && action.handler && isFunction(action.handler)) { + await this.getValueAsync(action.handler(selectedItems)); + } + } + //#endregion + + // + //#region Actions ... + isAllSelected() { + const numSelected = this.selection.selected.length; + const numRows = this.dataSource.data.length; + return numSelected === numRows; + } + + isAllFilteredSelected() { + // + if ( + !this.dataSource || + !hasChild(this.dataSource.filteredData) || + this.selection.isEmpty() || + !hasChild(this.selection.selected) + ) { + return false; + } + + // + const filteredData = this.dataSource.filteredData; + const result = this.selection.selected.every((s) => + filteredData.includes(s) + ); + + // + return result; + } + + isSelected(item: any) { + // + if ( + !item || + !this.data || + !this.selection || + !this.isRowsSelectable || + !this.data.includes(item) + ) { + return false; + } + + // + return this.selection.isSelected(item); + } + + toggleSelectAll() { + this.isAllSelected() + ? this.selection.clear() + : this.dataSource.data.forEach((row) => this.selection.select(row)); + } + + selectAll() { + // + if ( + !this.isRowsSelectable || + !this.selection || + !this.dataSource || + !this.dataSource.data + ) { + return; + } + + // + this.dataSource.data.forEach((i) => this.selection.select(i)); + } + + selectAllFilteredData() { + // + if ( + !this.isRowsSelectable || + !this.selection || + !this.dataSource || + !this.dataSource.data || + !this.dataSource.filteredData + ) { + return; + } + + // + this.dataSource.filteredData.forEach((i) => this.selection.select(i)); + } + + deselectAll() { + // + if ( + !this.isRowsSelectable || + !this.selection || + !this.dataSource || + !this.dataSource.data || + !this.dataSource.filteredData + ) { + return; + } + + // + this.dataSource.filteredData.forEach((i) => this.selection.deselect(i)); + } + + deselectAllFilteredData() { + // + if (!this.isRowsSelectable || !this.selection) { + return; + } + + // + this.selection.clear(); + } + + inverseSelection() { + // + if ( + !this.isRowsSelectable || + !this.selection || + !this.dataSource || + !this.dataSource.data + ) { + return; + } + + // + this.dataSource.data.forEach((i) => this.selection.toggle(i)); + } + + async filterData(query: string) { + // + this.dataSource.filter = query.trim().toLowerCase(); + this.prepareActionOptions(); + + // + await this.firstPage(); + } + + async firstPage() { + // + const hasPaginator = await this.getValueAsync(this.showPaginator); + if (!hasPaginator || !this.paginator) { + return; + } + + // + this.paginator.firstPage(); + + // + this.detectChanges(); + } + + async lastPage() { + // + const hasPaginator = await this.getValueAsync(this.showPaginator); + if (!hasPaginator || !this.paginator) { + return; + } + + // + this.paginator.lastPage(); + + // + this.detectChanges(); + } + + async nextPage() { + // + const hasPaginator = await this.getValueAsync(this.showPaginator); + if (!hasPaginator || !this.paginator) { + return; + } + + // + this.paginator.nextPage(); + + // + this.detectChanges(); + } + + async previousPage() { + // + const hasPaginator = await this.getValueAsync(this.showPaginator); + if (!hasPaginator || !this.paginator) { + return; + } + + // + this.paginator.previousPage(); + + // + this.detectChanges(); + } + + async toPage(index: number) { + // + const hasPaginator = await this.getValueAsync(this.showPaginator); + if (!hasPaginator || !this.paginator) { + return; + } + + // + index = index === 0 ? 1 : index; + + // + this.paginator.pageIndex = index - 1; + this.paginator.page.next({ + pageIndex: index - 1, + pageSize: this.pageSize, + length: this.totalItems, + }); + + // + this.detectChanges(); + } + + async changePageSize(size: number) { + // + if (!size || !this.paginator) { + return; + } + + // + this.paginator.pageSize = size; + this.paginator.page.emit({ + pageIndex: 0, + pageSize: size, + length: this.totalItems, + }); + + // + this.detectChanges(); + } + //#endregion + + // + //#region Private ... + // + //#region Action Provider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + switch (model.action) { + // + case XTableAction.Refresh: + this.detectChanges(); + break; + + // + case XTableAction.DE_SELECT_ALL: + this.deselectAll(); + break; + + // + case XTableAction.INVERSE_SELECTION: + this.inverseSelection(); + break; + + // + case XTableAction.SELECT_ALL: + this.selectAll(); + break; + + // + case XTableAction.TOGGLE_SELECT_ALL: + this.toggleSelectAll(); + break; + + // + case XTableAction.FIRST_PAGE: + await this.firstPage(); + break; + + // + case XTableAction.LAST_PAGE: + await this.lastPage(); + break; + + // + case XTableAction.NEXT_PAGE: + await this.nextPage(); + break; + + // + case XTableAction.PREVIOUS_PAGE: + await this.previousPage(); + break; + + // + case XTableAction.TO_PAGE: + if (model.payload && isNumber(model.payload)) { + await this.toPage(model.payload as number); + } + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + private async prepareUIData() { + // + this.IS_ROWS_SELECTABLE = await this.getValueAsync(this.selectableRows); + + // + this.detectChanges(); + } + + private async prepareDataSource() { + // + this.DATA = []; + this.COLUMNS_DATA = []; + this.DISPLAYED_COLUMNS = []; + if (!this.properties) { + return; + } + + // + const dataSource = await this.getValueAsync(this.properties.items); + this.DATA = dataSource; + + // + const isSelectableRows = await this.getValueAsync(this.selectableRows); + const baseColumnsData = !isSelectableRows + ? [] + : [ + { + name: X_TABLE_SELECTION_HEADER_IDENTIFIER, + } as XTableColumnOption, + ]; + this.COLUMNS_DATA = baseColumnsData.concat(this.properties.columns); + + // + const displayedColumns = await Promise.all( + this.COLUMNS_DATA.map( + async (cd) => (await this.getValueAsync(cd.name)) as string + ) + ); + this.DISPLAYED_COLUMNS = displayedColumns; + + // + this.DATA_SOURCE.data = this.DATA; + + // + this.detectChanges(); + } + + private async prepareSelection() { + // + const isMultipleSelection = await this.getValueAsync(this.allowMultiSelect); + const initialSelectedValues = await this.getValueAsync( + this.initialSelectedValues + ); + + // + this.SELECTION = new SelectionModel( + isMultipleSelection, + initialSelectedValues + ); + + // + // notify event emitter when has initial selected values ... + if (hasChild(initialSelectedValues)) { + this.rowSelectionChanged.emit(initialSelectedValues); + } + + // + this.registerSelectionChangeHandler(); + + // + this.detectChanges(); + } + + private registerSelectionChangeHandler() { + // + // Unsubscribe Last ... + if (this.SELECTION_SUBSCRIPTION) { + this.SELECTION_SUBSCRIPTION.unsubscribe(); + this.SELECTION_SUBSCRIPTION = null; + } + + // + // Subscribe New ... + this.SELECTION_SUBSCRIPTION = this.applyTakeUntilWrapper( + this.SELECTION.changed.asObservable() + ).subscribe(async () => { + // + await this.prepareActionOptions(); + this.rowSelectionChanged.emit(this.SELECTION.selected); + }); + } + + private async preparePaginatorOptions() { + // + const options = await this.getValueAsync(this.paginatorOptions); + const defaultOptions = this.defaultPaginatorOptions(); + const preparedOptions: XTablePaginatorOptions = { + ...defaultOptions, + ...options, + }; + + // + if (options) { + // + if (!options.first) { + preparedOptions.first = undefined; + } else { + // + const first = options.first; + + // + if (first.disabled) { + const firstDisabled = await this.getValueAsync(first.disabled); + preparedOptions.first.disabled = + defaultOptions.first.disabled && firstDisabled; + } else { + preparedOptions.first.disabled = defaultOptions.first.disabled; + } + + // + if (!first.icon) { + preparedOptions.first.icon = defaultOptions.first.icon; + } + + // + if (!first.title) { + preparedOptions.first.title = defaultOptions.first.title; + } + } + + // + if (!options.last) { + preparedOptions.last = undefined; + } else { + // + const last = options.last; + + // + if (last.disabled) { + const lastDisabled = await this.getValueAsync(last.disabled); + preparedOptions.last.disabled = + defaultOptions.last.disabled && lastDisabled; + } else { + preparedOptions.last.disabled = defaultOptions.last.disabled; + } + + // + if (!last.icon) { + preparedOptions.last.icon = defaultOptions.last.icon; + } + + // + if (!last.title) { + preparedOptions.last.title = defaultOptions.last.title; + } + } + + // + if (!options.next) { + preparedOptions.next = undefined; + } else { + // + const next = options.next; + + // + if (next.disabled) { + const nextDisabled = await this.getValueAsync(next.disabled); + preparedOptions.next.disabled = + defaultOptions.next.disabled && nextDisabled; + } else { + preparedOptions.next.disabled = defaultOptions.next.disabled; + } + + // + if (!next.icon) { + preparedOptions.next.icon = defaultOptions.next.icon; + } + + // + if (!next.title) { + preparedOptions.next.title = defaultOptions.next.title; + } + } + + // + if (!options.prev) { + preparedOptions.prev = undefined; + } else { + // + const prev = options.prev; + + // + if (prev.disabled) { + const prevDisabled = await this.getValueAsync(prev.disabled); + preparedOptions.prev.disabled = + defaultOptions.prev.disabled && prevDisabled; + } else { + preparedOptions.prev.disabled = defaultOptions.prev.disabled; + } + + // + if (!prev.icon) { + preparedOptions.prev.icon = defaultOptions.prev.icon; + } + + // + if (!prev.title) { + preparedOptions.prev.title = defaultOptions.prev.title; + } + } + } + + // + this.PAGINATOR_OPTIONS = preparedOptions; + + // + this.detectChanges(); + } + + private async registerPaginatorHandlers() { + // + if (!this.paginator) { + return; + } + + // + //#region Handle Paginator Ready ... + // + // Unsubscribe Last ... + if (this.PAGINATOR_INIT_SUBSCRIPTION) { + this.PAGINATOR_INIT_SUBSCRIPTION.unsubscribe(); + this.PAGINATOR_INIT_SUBSCRIPTION = null; + } + + // + // Subscribe New ... + this.PAGINATOR_INIT_SUBSCRIPTION = this.applyTakeUntilWrapper( + this.paginator.initialized + ).subscribe(async () => { + // + await this.preparePaginatorOptions(); + + // + //#region Initial Page Info Reciever ... + // + this.PAGE_SIZE = this.paginator.pageSize; + this.TOTAL_ITEMS = this.paginator.length; + this.PAGE_SIZE = this.paginator.pageSize; + this.CURRENT_PAGE = this.paginator.pageIndex + 1; + + // + const totalPagesFloat = this.paginator.length / this.paginator.pageSize; + this.TOTAL_PAGES = Math.floor(totalPagesFloat); + + // + if ( + totalPagesFloat > this.TOTAL_PAGES && + totalPagesFloat < this.TOTAL_PAGES + 1 + ) { + this.TOTAL_PAGES++; + } + //#endregion + + // + this.detectChanges(); + }); + //#endregion + + // + //#region Handle Page Changes ... + // + // Unsubscribe last ... + if (this.PAGINATOR_PAGE_SUBSCRIPTION) { + this.PAGINATOR_PAGE_SUBSCRIPTION.unsubscribe(); + this.PAGINATOR_PAGE_SUBSCRIPTION = null; + } + + // + // Subscribe New ... + this.PAGINATOR_PAGE_SUBSCRIPTION = this.applyTakeUntilWrapper( + this.paginator.page.asObservable() + ).subscribe(async (res) => { + // + // ReNew Pagination Options ... + await this.preparePaginatorOptions(); + + // + // Handle Current Page ... + this.PAGE_SIZE = res.pageSize; + this.TOTAL_ITEMS = res.length; + this.PAGE_SIZE = res.pageSize; + this.CURRENT_PAGE = res.pageIndex + 1; + this.pageChanged.emit(this.CURRENT_PAGE); + + // + //#region Handle Calculation of Total pages ... + const totalPagesFloat = res.length / res.pageSize; + this.TOTAL_PAGES = Math.floor(totalPagesFloat); + + // + if ( + totalPagesFloat > this.TOTAL_PAGES && + totalPagesFloat < this.TOTAL_PAGES + 1 + ) { + this.TOTAL_PAGES++; + } + //#endregion + + // + this.detectChanges(); + }); + //#endregion + } + + private defaultPaginatorOptions() { + // + const options: XTablePaginatorOptions = { + pageSizes: this.config.pageSizes, + pageSize: this.config.defaultPageSize, + first: { + title: 'first', + icon: XIconNames.first, + disabled: !this.paginator || !this.paginator.hasPreviousPage(), + }, + last: { + title: 'last', + icon: XIconNames.last, + disabled: !this.paginator || !this.paginator.hasNextPage(), + }, + prev: { + title: 'previous', + icon: XIconNames.previous, + disabled: !this.paginator || !this.paginator.hasPreviousPage(), + }, + next: { + title: 'next', + icon: XIconNames.next, + disabled: !this.paginator || !this.paginator.hasNextPage(), + }, + pageInfoProvider: ( + locale: XLocale, + currentPage: number, + pageSize: number, + totalPages: number + ) => { + // + const info = `${currentPage}/${totalPages}`; + return applyLocale(info, locale); + }, + }; + + // + return options; + } + + private async prepareActionOptions() { + // + let preparedOptions: XTableActionOptions = {}; + const options = await this.getValueAsync(this.actionOptions); + const defaultOptions = this.defaultActionOptions(); + + // + if (!options) { + preparedOptions = defaultOptions; + } else { + // + if (options.start) { + preparedOptions.start = [...options.start, ...defaultOptions.start]; + } else { + preparedOptions.start = [...defaultOptions.start]; + } + + // + if (options.end) { + preparedOptions.end = [...defaultOptions.end, ...options.end]; + } else { + preparedOptions.end = [...defaultOptions.end]; + } + } + + // + this.ACTION_OPTIONS = preparedOptions; + + // + this.detectChanges(); + } + + private defaultActionOptions() { + // + const options: XTableActionOptions = { + start: [ + { + title: XResourceIDs.delete, + icon: XIconNames.remove, + color: XColor.Danger, + disabled: + this.uiDisabled || + this.loading || + !this.selection || + !hasChild(this.data) || + !hasChild(this.selection.selected), + hidden: !this.isRowsSelectable, + handler: (items: any) => { + // + this.managerService.dialogService.presentActionModal({ + header: XResourceIDs.are_you_sure, + buttons: [], + keyboardClose: true, + backdropDismiss: true, + hasOk: true, + okHandler: () => { + this.removeItems.emit(items); + }, + hasCancel: true, + }); + }, + }, + ], + end: [ + { + title: XResourceIDs.select_all, + icon: XIconNames.select_all, + color: XColor.Tertiary, + disabled: + this.uiDisabled || + this.loading || + !this.selection || + !hasChild(this.data) || + this.isAllSelected(), + hidden: !this.isRowsSelectable, + handler: () => { + this.selectAll(); + }, + }, + { + title: XResourceIDs.select_all_group, + icon: XIconNames.select_all_group, + color: XColor.Tertiary, + disabled: + this.uiDisabled || + this.loading || + !hasChild(this.dataSource.filteredData) || + this.isAllFilteredSelected(), + hidden: + !this.dataSource || + !this.isRowsSelectable || + !this.dataSource.filter, + handler: () => { + this.selectAllFilteredData(); + }, + }, + { + title: XResourceIDs.deselect_all, + icon: XIconNames.deselect_all, + color: XColor.Tertiary, + disabled: + this.uiDisabled || + this.loading || + !this.selection || + !hasChild(this.data) || + !hasChild(this.selection.selected), + hidden: !this.isRowsSelectable, + handler: () => { + this.deselectAll(); + }, + }, + { + title: XResourceIDs.deselect_all_group, + icon: XIconNames.deselect_all_group, + color: XColor.Tertiary, + disabled: + this.uiDisabled || + this.loading || + !this.selection || + !hasChild(this.data) || + !this.isAllFilteredSelected() || + !hasChild(this.selection.selected), + hidden: + !this.isRowsSelectable || + !this.dataSource.filter || + !hasChild(this.dataSource.filteredData), + handler: () => { + this.deselectAllFilteredData(); + }, + }, + { + title: XResourceIDs.inverse_selection, + icon: XIconNames.inverse_selection, + color: XColor.Tertiary, + disabled: + this.uiDisabled || + this.loading || + !this.selection || + !hasChild(this.data) || + !hasChild(this.selection.selected), + hidden: !this.isRowsSelectable, + handler: () => { + this.inverseSelection(); + }, + }, + ], + }; + + // + return options; + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-table\x-table.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XTableComponent } from './x-table.component'; +import { XCardModule } from '../x-card/x-card.module'; +import { XIconModule } from '../x-icon/x-icon.module'; +import { XEmptyModule } from '../x-empty/x-empty.module'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XTableComponent], + imports: [ + XCardModule, + XIconModule, + XEmptyModule, + CommonModule, + XBaseComponentModule, + ], + exports: [ + XCardModule, + XIconModule, + XEmptyModule, + XTableComponent, + XBaseComponentModule, + ], +}) +export class XTableModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-table\x-table.typings.ts + +import { + Inject, + Input, + NgZone, + Output, + Component, + Renderer2, + Injectable, + ElementRef, + TemplateRef, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XLocale, + XResourceIDs, + XStandardType, + XOneOrManyType, + XColorIdentifier, + XResourceIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { + XCardEmptyContainer, + IXCardEmptyContainer, + XCardEmptyContainerComponent, +} from '../classes/i-x-card-empty.container'; +import { concatMap, map } from 'rxjs/operators'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { forkJoin, Observable, Subject } from 'rxjs'; +import { XManagerService } from 'x-framework-services'; +import { XIconIdentifier } from '../x-icon/x-icon.typings'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export type XTableColumn = keyof T; + +export type XTableColumnIdentifier = XTableColumn | string; + +export interface XTableColumnOption { + // + hidden?: XStandardType; + name: XTableColumnIdentifier; + sortable?: XStandardType; + icon?: XStandardType; + customColumn?: XStandardType; + title: XStandardType; + description?: XStandardType; +} + +export interface XTableConfig { + // + items: XStandardType; + columns: XTableColumnOption[]; +} + +export type XSortDirection = 'asc' | 'desc'; + +export enum XTableItemPosition { + TOP = 'top', + BOTTOM = 'bottom', +} + +export type XTableItemPositionIdentifier = XTableItemPosition | string; + +export const X_TABLE_SELECTION_HEADER_IDENTIFIER = 'selection'; + +export interface XTableActionButton { + hidden?: XStandardType; + disabled?: XStandardType; + icon?: XStandardType; + color?: XStandardType; + title?: XStandardType; + handler?: (item?: any) => XStandardType; +} + +export interface XTablePaginatorOptions { + pageSizes?: XStandardType; + pageSize?: XStandardType; + first?: XTableActionButton; + last?: XTableActionButton; + next?: XTableActionButton; + prev?: XTableActionButton; + pageInfoProvider?: ( + locale: XLocale, + currentPage: number, + pageSize: number, + totalPages: number, + totalItems: number + ) => string; +} + +export interface XTableActionOptions { + end?: XTableActionButton[]; + start?: XTableActionButton[]; +} + +export enum XTableAction { + // + Refresh = 'refresh', + + // + TOGGLE_SELECT_ALL = 'toggle-select-all', + SELECT_ALL = 'select-all', + DE_SELECT_ALL = 'deselect-all', + INVERSE_SELECTION = 'inverse-selection', + FIRST_PAGE = 'first-page', + LAST_PAGE = 'last-page', + NEXT_PAGE = 'next-page', + PREVIOUS_PAGE = 'previous-page', + TO_PAGE = 'to-page', +} + +export type XTableActionIdentifier = XTableAction | string; + +export interface XTableActionModel + extends XBaseActionModel {} + +// +//#region Inside Card/Empty Container ... +export interface IXTableContainer extends IXCardEmptyContainer { + // + //#region Other Props ... + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + color: XStandardType; + foregroundColor: XStandardType; + + canClearSort: XStandardType; + cellCssClass: XStandardType>; + properties: XTableConfig; + cellTemplate: TemplateRef; + customCellTemplate: TemplateRef; + //#endregion + + // + //#region Header Props ... + // + showHeader: XStandardType; + stickyHeader: XStandardType; + headerCellCssClass: XStandardType>; + + // + headerColor: XStandardType; + headerForegroundColor: XStandardType; + //#endregion + + // + //#region Click Props ... + clickableRows: XStandardType; + rowClicked: EventEmitter; + //#endregion + + // + //#region Selection props ... + selectableRows: XStandardType; + rowSelected: EventEmitter; + rowSelectionChanged: EventEmitter; + allowMultiSelect: XStandardType; + initialSelectedValues: XStandardType; + //#endregion + + // + //#region Filter/Search Props ... + showSearchBar: XStandardType; + searchBarPosition: XStandardType; + searchBarColor: XStandardType; + searchTitle: XStandardType; + //#endregion + + // + //#region Paginator Props ... + showPaginator: XStandardType; + paginatorPosition: XStandardType; + paginatorColor: XStandardType; + paginatorOptions: XStandardType; + pageChanged: EventEmitter; + //#endregion + + // + //#region Action Props ... + showActionBar: XStandardType; + actionBarPosition: XStandardType; + actionBarColor: XStandardType; + actionOptions: XStandardType; + removeItems: EventEmitter; + //#endregion +} + +@Injectable({ + providedIn: 'root', +}) +export class XTableContainer + extends XCardEmptyContainer + implements Readonly +{ + // + //#region Props ... + // + //#region Other Props ... + // + readonly actionProvider: Subject = + new Subject(); + readonly cssClass: XStandardType>; + + // + readonly color: XStandardType = XColorWithBrightness.Light; + readonly foregroundColor: XStandardType = false; + + readonly canClearSort: XStandardType = true; + readonly cellCssClass: XStandardType>; + readonly properties: XTableConfig; + readonly cellTemplate: TemplateRef; + readonly customCellTemplate: TemplateRef; + //#endregion + + // + //#region Header Props ... + // + readonly showHeader: XStandardType = true; + readonly stickyHeader: XStandardType = true; + readonly headerCellCssClass: XStandardType>; + + // + readonly headerColor: XStandardType = + XColorWithBrightness.Dark; + readonly headerForegroundColor: XStandardType = false; + //#endregion + + // + //#region Click Props ... + readonly clickableRows: XStandardType = false; + readonly rowClicked: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Selection props ... + readonly selectableRows: XStandardType = false; + readonly rowSelected: EventEmitter = new EventEmitter(); + readonly rowSelectionChanged: EventEmitter = new EventEmitter(); + readonly allowMultiSelect: XStandardType = true; + readonly initialSelectedValues: XStandardType = []; + //#endregion + + // + //#region Filter/Search Props ... + readonly showSearchBar: XStandardType = false; + readonly searchBarPosition: XStandardType = + XTableItemPosition.TOP; + readonly searchBarColor: XStandardType = + XColorWithBrightness.Light; + readonly searchTitle: XStandardType = + XResourceIDs.search; + //#endregion + + // + //#region Paginator Props ... + readonly showPaginator: XStandardType; + readonly paginatorPosition: XStandardType = + XTableItemPosition.BOTTOM; + readonly paginatorColor: XStandardType = + XColorWithBrightness.Light; + readonly paginatorOptions: XStandardType; + readonly pageChanged: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Action Props ... + readonly showActionBar: XStandardType; + readonly actionBarPosition: XStandardType = + XTableItemPosition.TOP; + readonly actionBarColor: XStandardType = + XColorWithBrightness.Light; + readonly actionOptions: XStandardType; + readonly removeItems: EventEmitter = new EventEmitter(); + //#endregion + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XTableContainerComponent + extends XCardEmptyContainerComponent + implements IXTableContainer +{ + // + //#region Props ... + // + //#region Other Props ... + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + canClearSort: XStandardType = this.propertyProvider.canClearSort; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + @Input() + color: XStandardType = this.propertyProvider.color; + + @Input() + foregroundColor: XStandardType = + this.propertyProvider.foregroundColor; + + @Input() + cellCssClass: XStandardType> = + this.propertyProvider.cellCssClass; + + @Input() + properties: XTableConfig = this.propertyProvider.properties; + + @Input() + cellTemplate: TemplateRef = this.propertyProvider.cellTemplate; + + @Input() + customCellTemplate: TemplateRef = + this.propertyProvider.customCellTemplate; + //#endregion + + // + //#region Header Props ... + @Input() + showHeader: XStandardType = this.propertyProvider.showHeader; + + @Input() + stickyHeader: XStandardType = this.propertyProvider.stickyHeader; + + @Input() + headerColor: XStandardType = + this.propertyProvider.headerColor; + + @Input() + headerForegroundColor: XStandardType = + this.propertyProvider.headerForegroundColor; + + @Input() + headerCellCssClass: XStandardType> = + this.propertyProvider.headerCellCssClass; + //#endregion + + // + //#region Click Props ... + @Input() + clickableRows: XStandardType = this.propertyProvider.clickableRows; + + @Output() + rowClicked: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Selection props ... + @Input() + selectableRows: XStandardType = this.propertyProvider.selectableRows; + + @Output() + rowSelected: EventEmitter = new EventEmitter(); + + @Output() + rowSelectionChanged: EventEmitter = new EventEmitter(); + + @Input() + allowMultiSelect: XStandardType = + this.propertyProvider.allowMultiSelect; + + @Input() + initialSelectedValues: XStandardType = + this.propertyProvider.initialSelectedValues; + //#endregion + + // + //#region Filter/Search Props ... + @Input() + showSearchBar: XStandardType = this.propertyProvider.showSearchBar; + + @Input() + searchBarPosition: XStandardType = + this.propertyProvider.searchBarPosition; + + @Input() + searchBarColor: XStandardType = + this.propertyProvider.searchBarColor; + + @Input() + searchTitle: XStandardType = + this.propertyProvider.searchTitle; + //#endregion + + // + //#region Paginator Props ... + @Input() + showPaginator: XStandardType = this.propertyProvider.showPaginator; + + @Input() + paginatorPosition: XStandardType = + this.propertyProvider.paginatorPosition; + + @Input() + paginatorColor: XStandardType = + this.propertyProvider.paginatorColor; + + @Input() + paginatorOptions: XStandardType = + this.propertyProvider.paginatorOptions; + + @Output() + pageChanged: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Action Props ... + @Input() + showActionBar: XStandardType = this.propertyProvider.showActionBar; + + @Input() + actionBarPosition: XStandardType = + this.propertyProvider.actionBarPosition; + + @Input() + actionBarColor: XStandardType = + this.propertyProvider.actionBarColor; + + @Input() + actionOptions: XStandardType = + this.propertyProvider.actionOptions; + + @Output() + removeItems: EventEmitter = new EventEmitter(); + //#endregion + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XTableContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXTableContainer + ): Observable { + return super + .extractProps(container ? container : this) + .pipe( + concatMap((superData) => { + return forkJoin({ + // + canClearSort: this.getValue( + container ? container.canClearSort : this.canClearSort + ), + cssClass: this.getValue( + container ? container.cssClass : this.cssClass + ), + color: this.getValue(container ? container.color : this.color), + foregroundColor: this.getValue( + container ? container.foregroundColor : this.foregroundColor + ), + cellCssClass: this.getValue( + container ? container.cellCssClass : this.cellCssClass + ), + properties: this.getValue( + container ? container.properties : this.properties + ), + cellTemplate: this.getValue( + container ? container.cellTemplate : this.cellTemplate + ), + customCellTemplate: this.getValue( + container ? container.customCellTemplate : this.customCellTemplate + ), + showHeader: this.getValue( + container ? container.showHeader : this.showHeader + ), + stickyHeader: this.getValue( + container ? container.stickyHeader : this.stickyHeader + ), + headerColor: this.getValue( + container ? container.headerColor : this.headerColor + ), + headerForegroundColor: this.getValue( + container + ? container.headerForegroundColor + : this.headerForegroundColor + ), + headerCellCssClass: this.getValue( + container ? container.headerCellCssClass : this.headerCellCssClass + ), + clickableRows: this.getValue( + container ? container.clickableRows : this.clickableRows + ), + selectableRows: this.getValue( + container ? container.selectableRows : this.selectableRows + ), + allowMultiSelect: this.getValue( + container ? container.allowMultiSelect : this.allowMultiSelect + ), + initialSelectedValues: this.getValue( + container + ? container.initialSelectedValues + : this.initialSelectedValues + ), + showSearchBar: this.getValue( + container ? container.showSearchBar : this.showSearchBar + ), + searchBarPosition: this.getValue( + container ? container.searchBarPosition : this.searchBarPosition + ), + searchBarColor: this.getValue( + container ? container.searchBarColor : this.searchBarColor + ), + searchTitle: this.getValue( + container ? container.searchTitle : this.searchTitle + ), + showPaginator: this.getValue( + container ? container.showPaginator : this.showPaginator + ), + paginatorPosition: this.getValue( + container ? container.paginatorPosition : this.paginatorPosition + ), + paginatorColor: this.getValue( + container ? container.paginatorColor : this.paginatorColor + ), + paginatorOptions: this.getValue( + container ? container.paginatorOptions : this.paginatorOptions + ), + showActionBar: this.getValue( + container ? container.showActionBar : this.showActionBar + ), + actionBarPosition: this.getValue( + container ? container.actionBarPosition : this.actionBarPosition + ), + actionBarColor: this.getValue( + container ? container.actionBarColor : this.actionBarColor + ), + actionOptions: this.getValue( + container ? container.actionOptions : this.actionOptions + ), + }).pipe( + map((data) => { + return { + superData, + data, + }; + }) + ); + }) + ) + .pipe( + map((info) => { + return { + // + ...info.superData, + + // + ...info.data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + rowSelectionChanged: this.rowSelectionChanged, + pageChanged: this.pageChanged, + rowClicked: this.rowClicked, + rowSelected: this.rowSelected, + removeItems: this.removeItems, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-tabs\x-tabs.component.html + + + + + + + + +
+ +
+
+
+ + + +
+ + + +
+ +
+
+ + + + +
+
+
+
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-tabs\x-tabs.component.scss + +:host { + // + --tab-header-color: var(--x-color-primary); + + // + --tab-indicator-color: var(--x-color-secondary); + + // + --tab-paginator-foreground-color: var(--x-color-secondary-contrast); + --tab-paginator-background-color: var(--x-color-secondary); + + // + --active-tab-foreground-color: var(--x-color-secondary-contrast); + --active-tab-background-color: var(--x-color-secondary); +} + +:host ::ng-deep.mat-ink-bar { + background-color: var(--tab-indicator-color) !important; +} + +.x-wrapper { + // + width: inherit; + height: inherit; +} + +x-card { + // + width: inherit; + height: inherit; +} + +:host ::ng-deep .x-wrapper { + .mat-tab-header { + display: flex; + } + + &.hide-header { + .mat-tab-header { + display: none; + } + } +} + +:host ::ng-deep.swipable { + // + cursor: grab; + + // + -webkit-user-select: none; /* Chrome all / Safari all */ + -moz-user-select: none; /* Firefox all */ + -ms-user-select: none; /* IE 10+ */ + user-select: none; /* Likely future */ +} + +:host ::ng-deep.mat-tab-label-active { + opacity: 1 !important; + + // + color: var(--active-tab-foreground-color) !important; + background-color: var(--active-tab-background-color) !important; + + .mat-tab-label-content { + // + color: var(--active-tab-foreground-color) !important; + background-color: var(--active-tab-background-color) !important; + + * { + // + color: var(--active-tab-foreground-color) !important; + background-color: var(--active-tab-background-color) !important; + } + } +} + +:host ::ng-deep .mat-tab-header-pagination { + // + background-color: var(--tab-paginator-background-color) !important; +} + +:host ::ng-deep.mat-tab-header-pagination-chevron { + border-color: var(--tab-paginator-foreground-color) !important; +} + +:host ::ng-deep .mat-tab-labels { + // + background-color: var(--tab-header-color) !important; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-tabs\x-tabs.component.ts + +import { + QueryList, + Component, + ContentChildren, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + nameof, + parseXColor, + isNullOrEmptyString, + getXColorCssVariableName, +} from 'x-framework-core'; +import { + XTabAlign, + XTabsAction, + XTabsCssVars, + XTabChangeEvent, + XTabsActionModel, + XTabsHeaderPosition, + XTabsContainerComponent, +} from './x-tabs.typings'; +import { map, concatMap } from 'rxjs/operators'; +import { XTabComponent } from './x-tab/x-tab.component'; +import { MatTabChangeEvent } from '@angular/material/tabs'; +import { of, Subject, Subscription, Observable } from 'rxjs'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-tabs', + templateUrl: './x-tabs.component.html', + styleUrls: ['./x-tabs.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XTabsComponent extends XTabsContainerComponent { + // + //#region Props ... + // + private TABS: QueryList; + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + canPrev$ = new Subject(); + canNext$ = new Subject(); + + // + get tabs() { + return this.TABS; + } + + // + @ContentChildren(XTabComponent) + set tabs(s: QueryList) { + this.TABS = s; + } + //#endregion + + // + //#region LifeCycles ... + async onInit() { + super.onInit(); + + // + await this.handleSelectedIndex(); + await this.applyDefaultColors(); + } + + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + const hasSelectedIndex = changeKeys.includes( + nameof('selectedIndex') + ); + if (hasSelectedIndex) { + await this.handleSelectedIndex(); + } + + // + //#region Color Changed ... + // + // Tab Header Color ... + const isTabHeaderColorChanged = changeKeys.includes( + nameof('headerColor') + ); + if (isTabHeaderColorChanged) { + await this.applyTabHeaderColor(); + } + + // + // Active Tab Color ... + const isActiveTabColorChanged = changeKeys.includes( + nameof('activeTabColor') + ); + if (isActiveTabColorChanged) { + await this.applyActiveTabColor(); + } + + // + // Tab Indicator Color ... + const isTabIndicatorColorChanged = changeKeys.includes( + nameof('tabIndicatorColor') + ); + if (isTabIndicatorColorChanged) { + await this.applyTabIndicatorColor(); + } + + // + // Tab Paginator Color ... + const isTabPaginatorColorChanged = changeKeys.includes( + nameof('tabPaginatorColor') + ); + if (isTabPaginatorColorChanged) { + await this.applyTabPaginatorColor(); + } + //#endregion + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region Task Handlers ... + //#endregion + + // + //#region UI Providers ... + isDynamicHeight() { + return this.getValue(this.dynamicHeight).pipe( + map((isDynamic) => (isDynamic ? '' : undefined)) + ); + } + + isStretched() { + return this.getValue(this.alignTabs).pipe( + map((align) => (align === XTabAlign.STRETCH ? '' : undefined)) + ); + } + + getTabAlignValue() { + return this.isStretched().pipe( + concatMap((isStretched) => { + if (isStretched) { + return of(undefined); + } else { + return this.getValue(this.alignTabs); + } + }) + ); + } + + swipeDisabled() { + return this.getValue(this.swipable).pipe( + map((swipable) => !swipable || this.uiDisabled) + ); + } + + getHeaderPosition(): Observable<'above' | 'below'> { + return this.getValue(this.headerPosition).pipe( + map((position) => + position === XTabsHeaderPosition.ABOVE ? 'above' : 'below' + ) + ); + } + //#endregion + + // + //#region UI Handlers ... + async handleSelectedTabChanged(event: MatTabChangeEvent) { + // + const mEvent: XTabChangeEvent = { + index: event.index, + tab: this.tabs.toArray()[event.index], + }; + + // + const canLoop = await this.canLoop(); + + // + if (event.index === 0) { + // + this.canPrev$.next(false || canLoop); + this.canNext$.next(true); + } else if (event.index === this.tabs.length - 1) { + // + this.canPrev$.next(true); + this.canNext$.next(false || canLoop); + } else if (event.index > 0 && event.index < this.tabs.length) { + // + this.canPrev$.next(true); + this.canNext$.next(true); + } + + // + this.selectedTabChange.emit(mEvent); + this.selectedIndexChange.emit(mEvent.index); + } + + async handleSwipeEnd() { + await this.prev(); + } + + async handleSwipeStart() { + await this.next(); + } + //#endregion + + // + //#region Actions ... + length() { + return this.tabs ? this.tabs.length : 0; + } + + getTab(index: number) { + return this.checkIndex(index) ? this.tabs.toArray()[index] : null; + } + + async next() { + // + const isLocked = await this.isLocked(); + if (isLocked) { + return; + } + + // + const current = this.selectedIndex; + const canLoop = await this.canLoop(); + const next = this.checkIndex(current + 1) + ? current + 1 + : canLoop + ? 0 + : current; + + // + this.selectedIndex = next; + this.detectChanges(); + } + + async prev() { + // + const isLocked = await this.isLocked(); + if (isLocked) { + return; + } + + // + const current = this.selectedIndex; + const canLoop = await this.canLoop(); + const prev = this.checkIndex(current - 1) + ? current - 1 + : canLoop + ? this.tabs.length - 1 + : current; + + // + this.selectedIndex = prev; + this.detectChanges(); + } + + async first() { + // + const isLocked = await this.isLocked(); + if (isLocked) { + return; + } + + // + this.selectedIndex = 0; + this.detectChanges(); + } + + async last() { + // + const isLocked = await this.isLocked(); + if (isLocked) { + return; + } + + // + this.selectedIndex = this.tabs.length - 1; + this.detectChanges(); + } + + async to(index: number) { + // + const isLocked = await this.isLocked(); + if (isLocked) { + return; + } + + // + if (!this.checkIndex(index)) { + return; + } + + // + this.selectedIndex = index; + this.detectChanges(); + } + //#endregion + + // + //#region Private ... + // + //#region ActionProvider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XTabsAction.FIRST: + await this.first(); + break; + + // + case XTabsAction.LAST: + await this.last(); + break; + + // + case XTabsAction.NEXT: + await this.next(); + break; + + // + case XTabsAction.PREV: + await this.prev(); + break; + + // + case XTabsAction.TO: + if (model.payload) { + await this.to(model.payload); + } + break; + + // + case XTabsAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + private checkIndex(index: number) { + return this.tabs && index < this.tabs.length && index >= 0; + } + + private async canLoop() { + // + const result = await this.getValueAsync(this.loopTabs); + return result || false; + } + + private async isLocked() { + // + const result = await this.getValueAsync(this.lock); + return result || false; + } + + private async handleSelectedIndex() { + // + const startIndex = await this.getValueAsync(this.selectedIndex); + + // + if (!this.checkIndex(startIndex)) { + // + this.selectedIndex = 0; + return; + } + + // + this.selectedIndex = startIndex; + } + + // + //#region Color Appliers ... + private async applyDefaultColors() { + // + await this.applyActiveTabColor(); + await this.applyTabHeaderColor(); + await this.applyTabPaginatorColor(); + await this.applyTabIndicatorColor(); + } + + private async applyTabHeaderColor() { + // + const color = await this.getValueAsync(this.headerColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XTabsCssVars.TabHeaderColor, `var(${bgColor})`); + + // + this.detectChanges(); + } + + private async applyActiveTabColor() { + // + const color = await this.getValueAsync(this.activeTabColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const fgColor = getXColorCssVariableName( + colorDescriptor.name, + true, + colorDescriptor.brightnessLevel + ); + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(fgColor) || isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XTabsCssVars.ActiveTabForegroundColor, `var(${fgColor})`); + this.setCssVar(XTabsCssVars.ActiveTabBackgroundColor, `var(${bgColor})`); + + // + this.detectChanges(); + } + + private async applyTabIndicatorColor() { + // + const color = await this.getValueAsync(this.tabIndicatorColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XTabsCssVars.TabIndicatorColor, `var(${bgColor})`); + + // + this.detectChanges(); + } + + private async applyTabPaginatorColor() { + // + const color = await this.getValueAsync(this.tabPaginatorColor); + if (!color) { + return; + } + + // + const colorDescriptor = parseXColor(color); + if (!colorDescriptor) { + return; + } + + // + const fgColor = getXColorCssVariableName( + colorDescriptor.name, + true, + colorDescriptor.brightnessLevel + ); + const bgColor = getXColorCssVariableName( + colorDescriptor.name, + false, + colorDescriptor.brightnessLevel + ); + if (isNullOrEmptyString(fgColor) || isNullOrEmptyString(bgColor)) { + return; + } + + // + this.setCssVar(XTabsCssVars.TabPaginatorForegroundColor, `var(${fgColor})`); + this.setCssVar(XTabsCssVars.TabPaginatorBackgroundColor, `var(${bgColor})`); + + // + this.detectChanges(); + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-tabs\x-tabs.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XTabsComponent } from './x-tabs.component'; +import { XCardModule } from '../x-card/x-card.module'; +import { XIconModule } from '../x-icon/x-icon.module'; +import { XTabComponent } from './x-tab/x-tab.component'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; +import { XTabsContainerComponent } from './x-tabs.typings'; + +@NgModule({ + declarations: [XTabsComponent, XTabComponent, XTabsContainerComponent], + imports: [CommonModule, XBaseComponentModule, XIconModule, XCardModule], + exports: [ + XIconModule, + XCardModule, + XTabComponent, + XTabsComponent, + XBaseComponentModule + ] +}) +export class XTabsModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-tabs\x-tabs.typings.ts + +import { + Input, + Inject, + NgZone, + Output, + Renderer2, + Component, + ElementRef, + Injectable, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XStandardType, + XOneOrManyType, + XColorIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { + XComponentCardContainer, + IXComponentCardContainer, + XComponentCardContainerComponent, +} from '../x-card/x-card.typings'; +import { concatMap, map } from 'rxjs/operators'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { forkJoin, Observable, Subject } from 'rxjs'; +import { XManagerService } from 'x-framework-services'; +import { XTabComponent } from './x-tab/x-tab.component'; +import { MatTabChangeEvent } from '@angular/material/tabs'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export enum XTabsHeaderPosition { + ABOVE = 'above', + BELOW = 'below', +} + +export type XTabsHeaderPositionIdentifier = XTabsHeaderPosition | string; + +export interface XTabChangeEvent extends Pick { + tab: XTabComponent; +} + +export enum XTabAlign { + START = 'start', + CENTER = 'center', + END = 'end', + STRETCH = 'stretch', +} + +export type XTabAlignIdentifier = XTabAlign | string; + +export enum XTabsAction { + FIRST = 'first', + LAST = 'last', + PREV = 'prev', + NEXT = 'next', + TO = 'to', + + // + Refresh = 'refresh', +} + +export type XTabsActionIdentifier = XTabsAction | string; + +export interface XTabsActionModel + extends XBaseActionModel {} + +export enum XTabsCssVars { + // + TabHeaderColor = 'tab-header-color', + + // + TabIndicatorColor = 'tab-indicator-color', + + // + ActiveTabForegroundColor = 'active-tab-foreground-color', + ActiveTabBackgroundColor = 'active-tab-background-color', + + // + TabPaginatorForegroundColor = 'tab-paginator-foreground-color', + TabPaginatorBackgroundColor = 'tab-paginator-background-color', +} + +export type XTabsCssVarsIdentifier = XTabsCssVars | string; + +// +//#region Inside Card Container ... +export interface IXTabsContainer extends IXComponentCardContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + selectedIndex: number; + selectedIndexChange: EventEmitter; + + // + showHeader: XStandardType; + disablePagination: XStandardType; + alignTabs: XStandardType; + dynamicHeight: XStandardType; + animationDuration: XStandardType; + disableRipple: XStandardType; + headerPosition: XStandardType; + + // + loopTabs: XStandardType; + swipable: XStandardType; + animationDone: EventEmitter; + + // + lock: XStandardType; + + // + color: XStandardType; + headerColor: XStandardType; + activeTabColor: XStandardType; + tabIndicatorColor: XStandardType; + tabPaginatorColor: XStandardType; + + // + selectedTabChange: EventEmitter; +} + +@Injectable({ + providedIn: 'root', +}) +export class XTabsContainer + extends XComponentCardContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider: Subject = + new Subject(); + readonly cssClass: XStandardType>; + + // + readonly selectedIndex: number = 0; + readonly selectedIndexChange: EventEmitter = + new EventEmitter(); + + // + readonly showHeader: XStandardType = true; + readonly disablePagination: XStandardType = false; + readonly alignTabs: XStandardType = XTabAlign.START; + readonly dynamicHeight: XStandardType = false; + readonly disableRipple: XStandardType = false; + readonly headerPosition: XStandardType = + XTabsHeaderPosition.ABOVE; + + // + readonly loopTabs: XStandardType = true; + readonly swipable: XStandardType = false; + readonly animationDuration: XStandardType = '500ms'; + + // + readonly lock: XStandardType = false; + + // + readonly color: XStandardType = + XColorWithBrightness.Transparent; + readonly headerColor: XStandardType = + XColorWithBrightness.Primary; + readonly activeTabColor: XStandardType = + XColorWithBrightness.Secondary; + readonly tabIndicatorColor: XStandardType = + XColorWithBrightness.SecondaryTint; + readonly tabPaginatorColor: XStandardType = + XColorWithBrightness.SecondaryShade; + + // + readonly animationDone: EventEmitter = new EventEmitter(); + readonly selectedTabChange: EventEmitter = + new EventEmitter(); + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export class XTabsContainerComponent + extends XComponentCardContainerComponent + implements IXTabsContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + selectedIndex: number = this.propertyProvider.selectedIndex; + + @Output() + selectedIndexChange: EventEmitter = new EventEmitter(); + + // + @Input() + showHeader: XStandardType = this.propertyProvider.showHeader; + + @Input() + disablePagination: XStandardType = + this.propertyProvider.disablePagination; + + @Input() + alignTabs: XStandardType = + this.propertyProvider.alignTabs; + + @Input() + dynamicHeight: XStandardType = this.propertyProvider.dynamicHeight; + + @Input() + disableRipple: XStandardType = this.propertyProvider.disableRipple; + + @Input() + headerPosition: XStandardType = + this.propertyProvider.headerPosition; + + // + @Input() + loopTabs: XStandardType = this.propertyProvider.loopTabs; + + @Input() + swipable: XStandardType = this.propertyProvider.swipable; + + @Input() + animationDuration: XStandardType = + this.propertyProvider.animationDuration; + + // + @Input() + color: XStandardType = this.propertyProvider.color; + + @Input() + headerColor: XStandardType = + this.propertyProvider.headerColor; + + @Input() + activeTabColor: XStandardType = + this.propertyProvider.activeTabColor; + + @Input() + tabIndicatorColor: XStandardType = + this.propertyProvider.tabIndicatorColor; + + @Input() + tabPaginatorColor: XStandardType = + this.propertyProvider.tabPaginatorColor; + + // + @Output() + animationDone: EventEmitter = new EventEmitter(); + + @Output() + selectedTabChange: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XTabsContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXTabsContainer + ): Observable { + return super + .extractProps(container ? container : this) + .pipe( + concatMap((superData) => { + return forkJoin({ + // + cssClass: this.getValue( + container ? container.cssClass : this.cssClass + ), + selectedIndex: this.getValue( + container ? container.selectedIndex : this.selectedIndex + ), + showHeader: this.getValue( + container ? container.showHeader : this.showHeader + ), + disablePagination: this.getValue( + container ? container.disablePagination : this.disablePagination + ), + alignTabs: this.getValue( + container ? container.alignTabs : this.alignTabs + ), + dynamicHeight: this.getValue( + container ? container.dynamicHeight : this.dynamicHeight + ), + animationDuration: this.getValue( + container ? container.animationDuration : this.animationDuration + ), + disableRipple: this.getValue( + container ? container.disableRipple : this.disableRipple + ), + headerPosition: this.getValue( + container ? container.headerPosition : this.headerPosition + ), + loopTabs: this.getValue( + container ? container.loopTabs : this.loopTabs + ), + swipable: this.getValue( + container ? container.swipable : this.swipable + ), + lock: this.getValue(container ? container.lock : this.lock), + color: this.getValue(container ? container.color : this.color), + headerColor: this.getValue( + container ? container.headerColor : this.headerColor + ), + activeTabColor: this.getValue( + container ? container.activeTabColor : this.activeTabColor + ), + tabIndicatorColor: this.getValue( + container ? container.tabIndicatorColor : this.tabIndicatorColor + ), + tabPaginatorColor: this.getValue( + container ? container.tabPaginatorColor : this.tabPaginatorColor + ), + }).pipe( + map((data) => { + return { + superData, + data, + }; + }) + ); + }) + ) + .pipe( + map((info) => { + return { + // + ...info.superData, + + // + ...info.data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + animationDone: this.animationDone, + selectedTabChange: this.selectedTabChange, + selectedIndexChange: this.selectedIndexChange, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-tabs\x-tab\x-tab.component.html + + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-tabs\x-tab\x-tab.component.scss + +.full-width { + width: 100%; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-tabs\x-tab\x-tab.component.ts + +import { + Component, + ViewChild, + TemplateRef, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + XTabAction, + XTabActionModel, + XTabContainerComponent, +} from './x-tab.typings'; +import { nameof } from 'x-framework-core'; +import { Subject, Subscription } from 'rxjs'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-tab', + templateUrl: './x-tab.component.html', + styleUrls: ['./x-tab.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XTabComponent extends XTabContainerComponent { + // + //#region Props ... + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + @ViewChild('label', { static: false }) + label!: TemplateRef; + + // + @ViewChild('content', { static: false }) + content!: TemplateRef; + //#endregion + + // + //#region LifeCycles ... + onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region Private ... + // + //#region ActionProvider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XTabAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-tabs\x-tab\x-tab.typings.ts + +import { + Input, + Inject, + NgZone, + Renderer2, + ElementRef, + ChangeDetectorRef, +} from '@angular/core'; +import { map } from 'rxjs/operators'; +import { + XBaseContainer, + IXBaseContainer, + XBaseContainerComponent, +} from '../../classes/i-x-base.container'; +import { forkJoin, Observable, Subject } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { Component, Injectable } from '@angular/core'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../../config/x-framework-components.config'; + +export enum XTabAction { + Refresh = 'refresh', +} + +export type XTabActionIdentifier = XTabAction | string; + +export interface XTabActionModel + extends XBaseActionModel {} + +// +//#region Container ... +export interface IXTabContainer extends IXBaseContainer { + actionProvider: Subject; +} + +@Injectable({ + providedIn: 'root' +}) +export class XTabContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + readonly actionProvider: Subject = + new Subject(); + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XTabContainerComponent + extends XBaseContainerComponent + implements IXTabContainer +{ + // + //#region Props ... + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XTabContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps(container?: IXTabContainer): Observable { + return forkJoin({ + // + }).pipe( + map((data) => { + return { + // + // ...data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-theme\x-theme-typings.ts + +// +//#region Form Interfaces ... +export interface XThemeBaseFormModel { + name: string; + } + + export interface XThemeDarkFormModel { + // + //#region Dark ... + darkXColorPrimary: string; + darkXColorPrimaryContrast: string; + darkXColorPrimaryShade: string; + darkXColorPrimaryShadeContrast: string; + darkXColorPrimaryTint: string; + darkXColorPrimaryTintContrast: string; + darkXColorSecondary: string; + darkXColorSecondaryContrast: string; + darkXColorSecondaryShade: string; + darkXColorSecondaryShadeContrast: string; + darkXColorSecondaryTint: string; + darkXColorSecondaryTintContrast: string; + darkXColorTertiary: string; + darkXColorTertiaryContrast: string; + darkXColorTertiaryShade: string; + darkXColorTertiaryShadeContrast: string; + darkXColorTertiaryTint: string; + darkXColorTertiaryTintContrast: string; + darkXColorSuccess: string; + darkXColorSuccessContrast: string; + darkXColorSuccessShade: string; + darkXColorSuccessShadeContrast: string; + darkXColorSuccessTint: string; + darkXColorSuccessTintContrast: string; + darkXColorWarning: string; + darkXColorWarningContrast: string; + darkXColorWarningShade: string; + darkXColorWarningShadeContrast: string; + darkXColorWarningTint: string; + darkXColorWarningTintContrast: string; + darkXColorDanger: string; + darkXColorDangerContrast: string; + darkXColorDangerShade: string; + darkXColorDangerShadeContrast: string; + darkXColorDangerTint: string; + darkXColorDangerTintContrast: string; + darkXColorDark: string; + darkXColorDarkContrast: string; + darkXColorDarkShade: string; + darkXColorDarkShadeContrast: string; + darkXColorDarkTint: string; + darkXColorDarkTintContrast: string; + darkXColorMedium: string; + darkXColorMediumContrast: string; + darkXColorMediumShade: string; + darkXColorMediumShadeContrast: string; + darkXColorMediumTint: string; + darkXColorMediumTintContrast: string; + darkXColorLight: string; + darkXColorLightContrast: string; + darkXColorLightShade: string; + darkXColorLightShadeContrast: string; + darkXColorLightTint: string; + darkXColorLightTintContrast: string; + //#endregion + } + + export interface XThemeLightFormModel { + // + //#region Light ... + lightXColorPrimary: string; + lightXColorPrimaryContrast: string; + lightXColorPrimaryShade: string; + lightXColorPrimaryShadeContrast: string; + lightXColorPrimaryTint: string; + lightXColorPrimaryTintContrast: string; + lightXColorSecondary: string; + lightXColorSecondaryContrast: string; + lightXColorSecondaryShade: string; + lightXColorSecondaryShadeContrast: string; + lightXColorSecondaryTint: string; + lightXColorSecondaryTintContrast: string; + lightXColorTertiary: string; + lightXColorTertiaryContrast: string; + lightXColorTertiaryShade: string; + lightXColorTertiaryShadeContrast: string; + lightXColorTertiaryTint: string; + lightXColorTertiaryTintContrast: string; + lightXColorSuccess: string; + lightXColorSuccessContrast: string; + lightXColorSuccessShade: string; + lightXColorSuccessShadeContrast: string; + lightXColorSuccessTint: string; + lightXColorSuccessTintContrast: string; + lightXColorWarning: string; + lightXColorWarningContrast: string; + lightXColorWarningShade: string; + lightXColorWarningShadeContrast: string; + lightXColorWarningTint: string; + lightXColorWarningTintContrast: string; + lightXColorDanger: string; + lightXColorDangerContrast: string; + lightXColorDangerShade: string; + lightXColorDangerShadeContrast: string; + lightXColorDangerTint: string; + lightXColorDangerTintContrast: string; + lightXColorDark: string; + lightXColorDarkContrast: string; + lightXColorDarkShade: string; + lightXColorDarkShadeContrast: string; + lightXColorDarkTint: string; + lightXColorDarkTintContrast: string; + lightXColorMedium: string; + lightXColorMediumContrast: string; + lightXColorMediumShade: string; + lightXColorMediumShadeContrast: string; + lightXColorMediumTint: string; + lightXColorMediumTintContrast: string; + lightXColorLight: string; + lightXColorLightContrast: string; + lightXColorLightShade: string; + lightXColorLightShadeContrast: string; + lightXColorLightTint: string; + lightXColorLightTintContrast: string; + //#endregion + } + + export interface XThemeFormModel + extends XThemeBaseFormModel, + XThemeDarkFormModel, + XThemeLightFormModel {} + //#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-theme\x-theme.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XCardModule } from '../x-card/x-card.module'; +import { XIconModule } from '../x-icon/x-icon.module'; +import { XTabsModule } from '../x-tabs/x-tabs.module'; +import { XListModule } from '../x-list/x-list.module'; +import { XFormModule } from '../x-form/x-form.module'; +import { XColorModule } from '../x-color/x-color.module'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; +import { XColorPickerModule } from '../x-color-picker/x-color-picker.module'; +import { XThemeManagerComponent } from './x-theme-manager/x-theme-manager.component'; +import { XModelPresenterModule } from '../x-model-presenter/x-model-presenter.module'; + +@NgModule({ + declarations: [XThemeManagerComponent], + imports: [ + XFormModule, + XIconModule, + XCardModule, + XTabsModule, + XListModule, + XColorModule, + CommonModule, + XColorPickerModule, + XBaseComponentModule, + XModelPresenterModule, + ], + exports: [ + XFormModule, + XIconModule, + XCardModule, + XTabsModule, + XListModule, + XColorModule, + CommonModule, + XColorPickerModule, + XBaseComponentModule, + XModelPresenterModule, + XThemeManagerComponent, + ], + providers: [], +}) +export class XThemeModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-theme\x-theme.tools.ts + +import { XThemePack, XThemeType } from 'x-framework-core'; +import { XThemeBaseFormModel, XThemeDarkFormModel, XThemeFormModel, XThemeLightFormModel } from './x-theme-typings'; + +/** + * convert to XThemeFormModel + * + * @param theme an instance of XThemePack ... + */ +export function themeToForm(theme: XThemePack) { + // + if (!theme) { + return getEmptyThemeFormModel(); + } + + // + return { + name: theme.name, + + // + //#region Dark ... + darkXColorPrimary: theme.dark.colors.XColorPrimary || '', + darkXColorPrimaryContrast: theme.dark.colors.XColorPrimaryContrast || '', + darkXColorPrimaryShade: theme.dark.colors.XColorPrimaryShade || '', + darkXColorPrimaryShadeContrast: + theme.dark.colors.XColorPrimaryShadeContrast || '', + darkXColorPrimaryTint: theme.dark.colors.XColorPrimaryTint || '', + darkXColorPrimaryTintContrast: + theme.dark.colors.XColorPrimaryTintContrast || '', + darkXColorSecondary: theme.dark.colors.XColorSecondary || '', + darkXColorSecondaryContrast: + theme.dark.colors.XColorSecondaryContrast || '', + darkXColorSecondaryShade: theme.dark.colors.XColorSecondaryShade || '', + darkXColorSecondaryShadeContrast: + theme.dark.colors.XColorSecondaryShadeContrast || '', + darkXColorSecondaryTint: theme.dark.colors.XColorSecondaryTint || '', + darkXColorSecondaryTintContrast: + theme.dark.colors.XColorSecondaryTintContrast || '', + darkXColorTertiary: theme.dark.colors.XColorTertiary || '', + darkXColorTertiaryContrast: theme.dark.colors.XColorTertiaryContrast || '', + darkXColorTertiaryShade: theme.dark.colors.XColorTertiaryShade || '', + darkXColorTertiaryShadeContrast: + theme.dark.colors.XColorTertiaryShadeContrast || '', + darkXColorTertiaryTint: theme.dark.colors.XColorTertiaryTint || '', + darkXColorTertiaryTintContrast: + theme.dark.colors.XColorTertiaryTintContrast || '', + darkXColorSuccess: theme.dark.colors.XColorSuccess || '', + darkXColorSuccessContrast: theme.dark.colors.XColorSuccessContrast || '', + darkXColorSuccessShade: theme.dark.colors.XColorSuccessShade || '', + darkXColorSuccessShadeContrast: + theme.dark.colors.XColorSuccessShadeContrast || '', + darkXColorSuccessTint: theme.dark.colors.XColorSuccessTint || '', + darkXColorSuccessTintContrast: + theme.dark.colors.XColorSuccessTintContrast || '', + darkXColorWarning: theme.dark.colors.XColorWarning || '', + darkXColorWarningContrast: theme.dark.colors.XColorWarningContrast || '', + darkXColorWarningShade: theme.dark.colors.XColorWarningShade || '', + darkXColorWarningShadeContrast: + theme.dark.colors.XColorWarningShadeContrast || '', + darkXColorWarningTint: theme.dark.colors.XColorWarningTint || '', + darkXColorWarningTintContrast: + theme.dark.colors.XColorWarningTintContrast || '', + darkXColorDanger: theme.dark.colors.XColorDanger || '', + darkXColorDangerContrast: theme.dark.colors.XColorDangerContrast || '', + darkXColorDangerShade: theme.dark.colors.XColorDangerShade || '', + darkXColorDangerShadeContrast: + theme.dark.colors.XColorDangerShadeContrast || '', + darkXColorDangerTint: theme.dark.colors.XColorDangerTint || '', + darkXColorDangerTintContrast: + theme.dark.colors.XColorDangerTintContrast || '', + darkXColorDark: theme.dark.colors.XColorDark || '', + darkXColorDarkContrast: theme.dark.colors.XColorDarkContrast || '', + darkXColorDarkShade: theme.dark.colors.XColorDarkShade || '', + darkXColorDarkShadeContrast: + theme.dark.colors.XColorDarkShadeContrast || '', + darkXColorDarkTint: theme.dark.colors.XColorDarkTint || '', + darkXColorDarkTintContrast: theme.dark.colors.XColorDarkTintContrast || '', + darkXColorMedium: theme.dark.colors.XColorMedium || '', + darkXColorMediumContrast: theme.dark.colors.XColorMediumContrast || '', + darkXColorMediumShade: theme.dark.colors.XColorMediumShade || '', + darkXColorMediumShadeContrast: + theme.dark.colors.XColorMediumShadeContrast || '', + darkXColorMediumTint: theme.dark.colors.XColorMediumTint || '', + darkXColorMediumTintContrast: + theme.dark.colors.XColorMediumTintContrast || '', + darkXColorLight: theme.dark.colors.XColorLight || '', + darkXColorLightContrast: theme.dark.colors.XColorLightContrast || '', + darkXColorLightShade: theme.dark.colors.XColorLightShade || '', + darkXColorLightShadeContrast: + theme.dark.colors.XColorLightShadeContrast || '', + darkXColorLightTint: theme.dark.colors.XColorLightTint || '', + darkXColorLightTintContrast: + theme.dark.colors.XColorLightTintContrast || '', + //#endregion + + // + //#region Light ... + lightXColorPrimary: theme.light.colors.XColorPrimary || '', + lightXColorPrimaryContrast: theme.light.colors.XColorPrimaryContrast || '', + lightXColorPrimaryShade: theme.light.colors.XColorPrimaryShade || '', + lightXColorPrimaryShadeContrast: + theme.light.colors.XColorPrimaryShadeContrast || '', + lightXColorPrimaryTint: theme.light.colors.XColorPrimaryTint || '', + lightXColorPrimaryTintContrast: + theme.light.colors.XColorPrimaryTintContrast || '', + lightXColorSecondary: theme.light.colors.XColorSecondary || '', + lightXColorSecondaryContrast: + theme.light.colors.XColorSecondaryContrast || '', + lightXColorSecondaryShade: theme.light.colors.XColorSecondaryShade || '', + lightXColorSecondaryShadeContrast: + theme.light.colors.XColorSecondaryShadeContrast || '', + lightXColorSecondaryTint: theme.light.colors.XColorSecondaryTint || '', + lightXColorSecondaryTintContrast: + theme.light.colors.XColorSecondaryTintContrast || '', + lightXColorTertiary: theme.light.colors.XColorTertiary || '', + lightXColorTertiaryContrast: + theme.light.colors.XColorTertiaryContrast || '', + lightXColorTertiaryShade: theme.light.colors.XColorTertiaryShade || '', + lightXColorTertiaryShadeContrast: + theme.light.colors.XColorTertiaryShadeContrast || '', + lightXColorTertiaryTint: theme.light.colors.XColorTertiaryTint || '', + lightXColorTertiaryTintContrast: + theme.light.colors.XColorTertiaryTintContrast || '', + lightXColorSuccess: theme.light.colors.XColorSuccess || '', + lightXColorSuccessContrast: theme.light.colors.XColorSuccessContrast || '', + lightXColorSuccessShade: theme.light.colors.XColorSuccessShade || '', + lightXColorSuccessShadeContrast: + theme.light.colors.XColorSuccessShadeContrast || '', + lightXColorSuccessTint: theme.light.colors.XColorSuccessTint || '', + lightXColorSuccessTintContrast: + theme.light.colors.XColorSuccessTintContrast || '', + lightXColorWarning: theme.light.colors.XColorWarning || '', + lightXColorWarningContrast: theme.light.colors.XColorWarningContrast || '', + lightXColorWarningShade: theme.light.colors.XColorWarningShade || '', + lightXColorWarningShadeContrast: + theme.light.colors.XColorWarningShadeContrast || '', + lightXColorWarningTint: theme.light.colors.XColorWarningTint || '', + lightXColorWarningTintContrast: + theme.light.colors.XColorWarningTintContrast || '', + lightXColorDanger: theme.light.colors.XColorDanger || '', + lightXColorDangerContrast: theme.light.colors.XColorDangerContrast || '', + lightXColorDangerShade: theme.light.colors.XColorDangerShade || '', + lightXColorDangerShadeContrast: + theme.light.colors.XColorDangerShadeContrast || '', + lightXColorDangerTint: theme.light.colors.XColorDangerTint || '', + lightXColorDangerTintContrast: + theme.light.colors.XColorDangerTintContrast || '', + lightXColorDark: theme.light.colors.XColorDark || '', + lightXColorDarkContrast: theme.light.colors.XColorDarkContrast || '', + lightXColorDarkShade: theme.light.colors.XColorDarkShade || '', + lightXColorDarkShadeContrast: + theme.light.colors.XColorDarkShadeContrast || '', + lightXColorDarkTint: theme.light.colors.XColorDarkTint || '', + lightXColorDarkTintContrast: + theme.light.colors.XColorDarkTintContrast || '', + lightXColorMedium: theme.light.colors.XColorMedium || '', + lightXColorMediumContrast: theme.light.colors.XColorMediumContrast || '', + lightXColorMediumShade: theme.light.colors.XColorMediumShade || '', + lightXColorMediumShadeContrast: + theme.light.colors.XColorMediumShadeContrast || '', + lightXColorMediumTint: theme.light.colors.XColorMediumTint || '', + lightXColorMediumTintContrast: + theme.light.colors.XColorMediumTintContrast || '', + lightXColorLight: theme.light.colors.XColorLight || '', + lightXColorLightContrast: theme.light.colors.XColorLightContrast || '', + lightXColorLightShade: theme.light.colors.XColorLightShade || '', + lightXColorLightShadeContrast: + theme.light.colors.XColorLightShadeContrast || '', + lightXColorLightTint: theme.light.colors.XColorLightTint || '', + lightXColorLightTintContrast: + theme.light.colors.XColorLightTintContrast || '', + //#endregion + } as XThemeFormModel; +} + +/** + * convert to XThemePack + * + * @param form an instance of XThemeFormModel ... + */ +export function formToTheme(form: XThemeFormModel) { + // + if (!form) { + return getEmptyThemePack(); + } + + // + return { + // + name: form.name || '', + + // + // Dark ... + dark: { + type: XThemeType.Dark, + colors: { + XColorPrimary: form.darkXColorPrimary || '', + XColorPrimaryContrast: form.darkXColorPrimaryContrast || '', + XColorPrimaryShade: form.darkXColorPrimaryShade || '', + XColorPrimaryShadeContrast: form.darkXColorPrimaryShadeContrast || '', + XColorPrimaryTint: form.darkXColorPrimaryTint || '', + XColorPrimaryTintContrast: form.darkXColorPrimaryTintContrast || '', + XColorSecondary: form.darkXColorSecondary || '', + XColorSecondaryContrast: form.darkXColorSecondaryContrast || '', + XColorSecondaryShade: form.darkXColorSecondaryShade || '', + XColorSecondaryShadeContrast: + form.darkXColorSecondaryShadeContrast || '', + XColorSecondaryTint: form.darkXColorSecondaryTint || '', + XColorSecondaryTintContrast: form.darkXColorSecondaryTintContrast || '', + XColorTertiary: form.darkXColorTertiary || '', + XColorTertiaryContrast: form.darkXColorTertiaryContrast || '', + XColorTertiaryShade: form.darkXColorTertiaryShade || '', + XColorTertiaryShadeContrast: form.darkXColorTertiaryShadeContrast || '', + XColorTertiaryTint: form.darkXColorTertiaryTint || '', + XColorTertiaryTintContrast: form.darkXColorTertiaryTintContrast || '', + XColorSuccess: form.darkXColorSuccess || '', + XColorSuccessContrast: form.darkXColorSuccessContrast || '', + XColorSuccessShade: form.darkXColorSuccessShade || '', + XColorSuccessShadeContrast: form.darkXColorSuccessShadeContrast || '', + XColorSuccessTint: form.darkXColorSuccessTint || '', + XColorSuccessTintContrast: form.darkXColorSuccessTintContrast || '', + XColorWarning: form.darkXColorWarning || '', + XColorWarningContrast: form.darkXColorWarningContrast || '', + XColorWarningShade: form.darkXColorWarningShade || '', + XColorWarningShadeContrast: form.darkXColorWarningShadeContrast || '', + XColorWarningTint: form.darkXColorWarningTint || '', + XColorWarningTintContrast: form.darkXColorWarningTintContrast || '', + XColorDanger: form.darkXColorDanger || '', + XColorDangerContrast: form.darkXColorDangerContrast || '', + XColorDangerShade: form.darkXColorDangerShade || '', + XColorDangerShadeContrast: form.darkXColorDangerShadeContrast || '', + XColorDangerTint: form.darkXColorDangerTint || '', + XColorDangerTintContrast: form.darkXColorDangerTintContrast || '', + XColorDark: form.darkXColorDark || '', + XColorDarkContrast: form.darkXColorDarkContrast || '', + XColorDarkShade: form.darkXColorDarkShade || '', + XColorDarkShadeContrast: form.darkXColorDarkShadeContrast || '', + XColorDarkTint: form.darkXColorDarkTint || '', + XColorDarkTintContrast: form.darkXColorDarkTintContrast || '', + XColorMedium: form.darkXColorMedium || '', + XColorMediumContrast: form.darkXColorMediumContrast || '', + XColorMediumShade: form.darkXColorMediumShade || '', + XColorMediumShadeContrast: form.darkXColorMediumShadeContrast || '', + XColorMediumTint: form.darkXColorMediumTint || '', + XColorMediumTintContrast: form.darkXColorMediumTintContrast || '', + XColorLight: form.darkXColorLight || '', + XColorLightContrast: form.darkXColorLightContrast || '', + XColorLightShade: form.darkXColorLightShade || '', + XColorLightShadeContrast: form.darkXColorLightShadeContrast || '', + XColorLightTint: form.darkXColorLightTint || '', + XColorLightTintContrast: form.darkXColorLightTintContrast || '', + }, + }, + + // + // Light ... + light: { + type: XThemeType.Light, + colors: { + XColorPrimary: form.lightXColorPrimary || '', + XColorPrimaryContrast: form.lightXColorPrimaryContrast || '', + XColorPrimaryShade: form.lightXColorPrimaryShade || '', + XColorPrimaryShadeContrast: form.lightXColorPrimaryShadeContrast || '', + XColorPrimaryTint: form.lightXColorPrimaryTint || '', + XColorPrimaryTintContrast: form.lightXColorPrimaryTintContrast || '', + XColorSecondary: form.lightXColorSecondary || '', + XColorSecondaryContrast: form.lightXColorSecondaryContrast || '', + XColorSecondaryShade: form.lightXColorSecondaryShade || '', + XColorSecondaryShadeContrast: + form.lightXColorSecondaryShadeContrast || '', + XColorSecondaryTint: form.lightXColorSecondaryTint || '', + XColorSecondaryTintContrast: + form.lightXColorSecondaryTintContrast || '', + XColorTertiary: form.lightXColorTertiary || '', + XColorTertiaryContrast: form.lightXColorTertiaryContrast || '', + XColorTertiaryShade: form.lightXColorTertiaryShade || '', + XColorTertiaryShadeContrast: + form.lightXColorTertiaryShadeContrast || '', + XColorTertiaryTint: form.lightXColorTertiaryTint || '', + XColorTertiaryTintContrast: form.lightXColorTertiaryTintContrast || '', + XColorSuccess: form.lightXColorSuccess || '', + XColorSuccessContrast: form.lightXColorSuccessContrast || '', + XColorSuccessShade: form.lightXColorSuccessShade || '', + XColorSuccessShadeContrast: form.lightXColorSuccessShadeContrast || '', + XColorSuccessTint: form.lightXColorSuccessTint || '', + XColorSuccessTintContrast: form.lightXColorSuccessTintContrast || '', + XColorWarning: form.lightXColorWarning || '', + XColorWarningContrast: form.lightXColorWarningContrast || '', + XColorWarningShade: form.lightXColorWarningShade || '', + XColorWarningShadeContrast: form.lightXColorWarningShadeContrast || '', + XColorWarningTint: form.lightXColorWarningTint || '', + XColorWarningTintContrast: form.lightXColorWarningTintContrast || '', + XColorDanger: form.lightXColorDanger || '', + XColorDangerContrast: form.lightXColorDangerContrast || '', + XColorDangerShade: form.lightXColorDangerShade || '', + XColorDangerShadeContrast: form.lightXColorDangerShadeContrast || '', + XColorDangerTint: form.lightXColorDangerTint || '', + XColorDangerTintContrast: form.lightXColorDangerTintContrast || '', + XColorDark: form.lightXColorDark || '', + XColorDarkContrast: form.lightXColorDarkContrast || '', + XColorDarkShade: form.lightXColorDarkShade || '', + XColorDarkShadeContrast: form.lightXColorDarkShadeContrast || '', + XColorDarkTint: form.lightXColorDarkTint || '', + XColorDarkTintContrast: form.lightXColorDarkTintContrast || '', + XColorMedium: form.lightXColorMedium || '', + XColorMediumContrast: form.lightXColorMediumContrast || '', + XColorMediumShade: form.lightXColorMediumShade || '', + XColorMediumShadeContrast: form.lightXColorMediumShadeContrast || '', + XColorMediumTint: form.lightXColorMediumTint || '', + XColorMediumTintContrast: form.lightXColorMediumTintContrast || '', + XColorLight: form.lightXColorLight || '', + XColorLightContrast: form.lightXColorLightContrast || '', + XColorLightShade: form.lightXColorLightShade || '', + XColorLightShadeContrast: form.lightXColorLightShadeContrast || '', + XColorLightTint: form.lightXColorLightTint || '', + XColorLightTintContrast: form.lightXColorLightTintContrast || '', + }, + }, + } as XThemePack; +} + +/** + * prepare an empty XThemePack instance ... + */ +export function getEmptyThemePack() { + return { + name: '', + dark: { + type: XThemeType.Dark, + colors: { + XColorPrimary: '', + XColorPrimaryContrast: '', + XColorPrimaryShade: '', + XColorPrimaryShadeContrast: '', + XColorPrimaryTint: '', + XColorPrimaryTintContrast: '', + XColorSecondary: '', + XColorSecondaryContrast: '', + XColorSecondaryShade: '', + XColorSecondaryShadeContrast: '', + XColorSecondaryTint: '', + XColorSecondaryTintContrast: '', + XColorTertiary: '', + XColorTertiaryContrast: '', + XColorTertiaryShade: '', + XColorTertiaryShadeContrast: '', + XColorTertiaryTint: '', + XColorTertiaryTintContrast: '', + XColorSuccess: '', + XColorSuccessContrast: '', + XColorSuccessShade: '', + XColorSuccessShadeContrast: '', + XColorSuccessTint: '', + XColorSuccessTintContrast: '', + XColorWarning: '', + XColorWarningContrast: '', + XColorWarningShade: '', + XColorWarningShadeContrast: '', + XColorWarningTint: '', + XColorWarningTintContrast: '', + XColorDanger: '', + XColorDangerContrast: '', + XColorDangerShade: '', + XColorDangerShadeContrast: '', + XColorDangerTint: '', + XColorDangerTintContrast: '', + XColorDark: '', + XColorDarkContrast: '', + XColorDarkShade: '', + XColorDarkShadeContrast: '', + XColorDarkTint: '', + XColorDarkTintContrast: '', + XColorMedium: '', + XColorMediumContrast: '', + XColorMediumShade: '', + XColorMediumShadeContrast: '', + XColorMediumTint: '', + XColorMediumTintContrast: '', + XColorLight: '', + XColorLightContrast: '', + XColorLightShade: '', + XColorLightShadeContrast: '', + XColorLightTint: '', + XColorLightTintContrast: '', + }, + }, + light: { + type: XThemeType.Light, + colors: { + XColorPrimary: '', + XColorPrimaryContrast: '', + XColorPrimaryShade: '', + XColorPrimaryShadeContrast: '', + XColorPrimaryTint: '', + XColorPrimaryTintContrast: '', + XColorSecondary: '', + XColorSecondaryContrast: '', + XColorSecondaryShade: '', + XColorSecondaryShadeContrast: '', + XColorSecondaryTint: '', + XColorSecondaryTintContrast: '', + XColorTertiary: '', + XColorTertiaryContrast: '', + XColorTertiaryShade: '', + XColorTertiaryShadeContrast: '', + XColorTertiaryTint: '', + XColorTertiaryTintContrast: '', + XColorSuccess: '', + XColorSuccessContrast: '', + XColorSuccessShade: '', + XColorSuccessShadeContrast: '', + XColorSuccessTint: '', + XColorSuccessTintContrast: '', + XColorWarning: '', + XColorWarningContrast: '', + XColorWarningShade: '', + XColorWarningShadeContrast: '', + XColorWarningTint: '', + XColorWarningTintContrast: '', + XColorDanger: '', + XColorDangerContrast: '', + XColorDangerShade: '', + XColorDangerShadeContrast: '', + XColorDangerTint: '', + XColorDangerTintContrast: '', + XColorDark: '', + XColorDarkContrast: '', + XColorDarkShade: '', + XColorDarkShadeContrast: '', + XColorDarkTint: '', + XColorDarkTintContrast: '', + XColorMedium: '', + XColorMediumContrast: '', + XColorMediumShade: '', + XColorMediumShadeContrast: '', + XColorMediumTint: '', + XColorMediumTintContrast: '', + XColorLight: '', + XColorLightContrast: '', + XColorLightShade: '', + XColorLightShadeContrast: '', + XColorLightTint: '', + XColorLightTintContrast: '', + }, + }, + } as XThemePack; +} + +/** + * prepare an empty XThemeBaseFormModel instance ... + */ +export function getEmptyThemeBaseFormModel() { + return { + name: '', + } as XThemeBaseFormModel; +} + +/** + * prepare an empty XThemeLightFormModel instance ... + */ +export function getEmptyThemeLightFormModel() { + return { + lightXColorPrimary: '', + lightXColorPrimaryContrast: '', + lightXColorPrimaryShade: '', + lightXColorPrimaryShadeContrast: '', + lightXColorPrimaryTint: '', + lightXColorPrimaryTintContrast: '', + lightXColorSecondary: '', + lightXColorSecondaryContrast: '', + lightXColorSecondaryShade: '', + lightXColorSecondaryShadeContrast: '', + lightXColorSecondaryTint: '', + lightXColorSecondaryTintContrast: '', + lightXColorTertiary: '', + lightXColorTertiaryContrast: '', + lightXColorTertiaryShade: '', + lightXColorTertiaryShadeContrast: '', + lightXColorTertiaryTint: '', + lightXColorTertiaryTintContrast: '', + lightXColorSuccess: '', + lightXColorSuccessContrast: '', + lightXColorSuccessShade: '', + lightXColorSuccessShadeContrast: '', + lightXColorSuccessTint: '', + lightXColorSuccessTintContrast: '', + lightXColorWarning: '', + lightXColorWarningContrast: '', + lightXColorWarningShade: '', + lightXColorWarningShadeContrast: '', + lightXColorWarningTint: '', + lightXColorWarningTintContrast: '', + lightXColorDanger: '', + lightXColorDangerContrast: '', + lightXColorDangerShade: '', + lightXColorDangerShadeContrast: '', + lightXColorDangerTint: '', + lightXColorDangerTintContrast: '', + lightXColorDark: '', + lightXColorDarkContrast: '', + lightXColorDarkShade: '', + lightXColorDarkShadeContrast: '', + lightXColorDarkTint: '', + lightXColorDarkTintContrast: '', + lightXColorMedium: '', + lightXColorMediumContrast: '', + lightXColorMediumShade: '', + lightXColorMediumShadeContrast: '', + lightXColorMediumTint: '', + lightXColorMediumTintContrast: '', + lightXColorLight: '', + lightXColorLightContrast: '', + lightXColorLightShade: '', + lightXColorLightShadeContrast: '', + lightXColorLightTint: '', + lightXColorLightTintContrast: '', + } as XThemeLightFormModel; +} + +/** + * prepare an empty XThemeDarkFormModel instance ... + */ +export function getEmptyThemeDarkFormModel() { + return { + darkXColorPrimary: '', + darkXColorPrimaryContrast: '', + darkXColorPrimaryShade: '', + darkXColorPrimaryShadeContrast: '', + darkXColorPrimaryTint: '', + darkXColorPrimaryTintContrast: '', + darkXColorSecondary: '', + darkXColorSecondaryContrast: '', + darkXColorSecondaryShade: '', + darkXColorSecondaryShadeContrast: '', + darkXColorSecondaryTint: '', + darkXColorSecondaryTintContrast: '', + darkXColorTertiary: '', + darkXColorTertiaryContrast: '', + darkXColorTertiaryShade: '', + darkXColorTertiaryShadeContrast: '', + darkXColorTertiaryTint: '', + darkXColorTertiaryTintContrast: '', + darkXColorSuccess: '', + darkXColorSuccessContrast: '', + darkXColorSuccessShade: '', + darkXColorSuccessShadeContrast: '', + darkXColorSuccessTint: '', + darkXColorSuccessTintContrast: '', + darkXColorWarning: '', + darkXColorWarningContrast: '', + darkXColorWarningShade: '', + darkXColorWarningShadeContrast: '', + darkXColorWarningTint: '', + darkXColorWarningTintContrast: '', + darkXColorDanger: '', + darkXColorDangerContrast: '', + darkXColorDangerShade: '', + darkXColorDangerShadeContrast: '', + darkXColorDangerTint: '', + darkXColorDangerTintContrast: '', + darkXColorDark: '', + darkXColorDarkContrast: '', + darkXColorDarkShade: '', + darkXColorDarkShadeContrast: '', + darkXColorDarkTint: '', + darkXColorDarkTintContrast: '', + darkXColorMedium: '', + darkXColorMediumContrast: '', + darkXColorMediumShade: '', + darkXColorMediumShadeContrast: '', + darkXColorMediumTint: '', + darkXColorMediumTintContrast: '', + darkXColorLight: '', + darkXColorLightContrast: '', + darkXColorLightShade: '', + darkXColorLightShadeContrast: '', + darkXColorLightTint: '', + darkXColorLightTintContrast: '', + } as XThemeDarkFormModel; +} + +/** + * prepare an empty XThemeFormModel instance ... + */ +export function getEmptyThemeFormModel() { + return { + ...getEmptyThemeBaseFormModel(), + ...getEmptyThemeDarkFormModel(), + ...getEmptyThemeLightFormModel(), + } as XThemeFormModel; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-theme\x-theme-manager\x-theme-manager.component.html + + + + + + + + +
+ +
+
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + +
+
+ + + + + +
+
+
+ + + + {{ item.data.name }} + + + + + + + + + + + + + + + + + + + + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-theme\x-theme-manager\x-theme-manager.component.scss + +:host ::ng-deep x-color { + --size: 24px !important; + display: inline-block; +} + +.actions { + margin: { + top: 8px; + bottom: 8px; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-theme\x-theme-manager\x-theme-manager.component.ts + +import { + nameof, + assign, + XParam, + toArray, + hasChild, + XUiEvent, + cloneJson, + parseJson, + isFunction, + isSameType, + XThemeType, + XThemePack, + isSameObject, + XResourceIDs, + XDialogResult, + XExceptionIDs, + XBaseKeyValue, + XStandardType, + XPickerOptions, + toNormalString, + XOneOrManyType, + XModelProperty, + XThemeDescriptor, + XModalButtonRole, + XModelKeyProvider, + isNullOrUndefined, + isNullOrEmptyString, + XThemeTypeIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { + XThemeManagerTabs, + XThemeManagerAction, + XThemeManagerActionModel, + XThemeManagerContainerComponent, +} from './x-theme-manager.typings'; +import { + XTabsAction, + XTabChangeEvent, + XTabsActionModel, +} from '../../x-tabs/x-tabs.typings'; +import { + XListItem, + XListAction, + XListActionModel, + XListItemSlideOption, + XListFilteringHandler, + defaultListFilteringHandler, +} from '../../x-list/x-list.typings'; +import { Subject, Subscription } from 'rxjs'; +import { + XActionBarItem, + XActionBarAction, + XActionBarActionModel, + DefaultActionBarItemIds, + defaultActionBarSetItem, + defaultActionBarUploadItem, + XActionBarActionFiredModel, +} from '../../x-action-bar/x-action-bar.typings'; +import { + XSearchBarType, + XSearchBarAction, + XSearchBarActionModel, + XSearchBarEventIdentifier, +} from '../../x-search-bar/x-search-bar.typings'; +import { + XFileUploadAction, + XFileUploadActionModel, +} from '../../x-file-upload/x-file-upload.typings'; +import { getEmptyThemePack } from '../x-theme.tools'; +import { XIconNames } from '../../x-icon/x-icon.typings'; +import { + XModelPresenterAction, + XModelPresenterActionModel, +} from '../../x-model-presenter/x-model-presenter.typings'; +import { XButtonType } from '../../x-button/x-button.typings'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; +import { XSlotLayout, XSlotName } from '../../x-slotter/x-slotter.typings'; +import { XPopoverComponent } from '../../dialogs/x-popover/x-popover.component'; +import { XColorPickerOutputFormat } from '../../x-color-picker/x-color-picker.typings'; +import { XFileDropAreaActionModel } from '../../x-file-drop-area/x-file-drop-area.typings'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-theme-manager', + templateUrl: './x-theme-manager.component.html', + styleUrls: ['./x-theme-manager.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XThemeManagerComponent extends XThemeManagerContainerComponent { + // + //#region Props ... + // + private loadingObject: HTMLIonLoadingElement; + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + readonly SlotNames = Object.assign({}, XSlotName); + readonly Tabs = Object.assign({}, XThemeManagerTabs); + readonly SlotLayouts = Object.assign({}, XSlotLayout); + readonly ButtonTypes = Object.assign({}, XButtonType); + readonly ColorNames = Object.assign({}, XColorWithBrightness); + readonly themeUploadListActionProvider = new Subject(); + readonly allowedThemeExtensions: XStandardType> = [ + '.json', + ]; + readonly themeUploadActionProvider = new Subject(); + readonly themeUploadListActionBarHideActions: XStandardType< + XOneOrManyType + >; + readonly themeModelPresenterActionProvider = + new Subject(); + readonly themeUploadListActionbarActions: XStandardType< + XOneOrManyType + >; + readonly themeUploadFileDropAreaActionProvider = + new Subject(); + + // + canSaveTheme = false; + listItems: XListItem[]; + selectedTheme: XThemePack = null; + uploadThemes: XOneOrManyType; + currentTab: number = this.Tabs.List; + baseTheme: Readonly = null; + showList: XStandardType = true; + hiddenThemModelKeys: XOneOrManyType = [ + `${nameof('dark')}.${nameof('type')}`, + `${nameof('light')}.${nameof('type')}`, + ]; + themeKeyProvider: XModelKeyProvider = (key) => { + // + let result = key as string; + + // + switch (key) { + // + case nameof('name'): + result = this.resourceProvider(this.ResourceIDs.theme); + break; + + // + case nameof('dark'): + result = this.resourceProvider(this.ResourceIDs.theme_dark); + break; + + // + case nameof('light'): + result = this.resourceProvider(this.ResourceIDs.theme_light); + break; + + // + case nameof('colors'): + result = this.resourceProvider(this.ResourceIDs.colors); + break; + + // + default: + result = key as string; + break; + } + + // + return result; + }; + listActionProvider = new Subject(); + tabsActionProvider = new Subject(); + searchBarType: XStandardType = XSearchBarType.Search; + searchBarActionProvider = new Subject(); + actionBarActionProvider = new Subject(); + actionBarActions: XStandardType>; + themeModelPresenterActionActions: XOneOrManyType = [ + { + // + ...defaultActionBarSetItem, + + // + title: this.ResourceIDs.save, + disabled: true, + + // + handler: async (id) => { + await this.handleUpdateTheme(); + }, + }, + ]; + listFilteringHandler: XListFilteringHandler = defaultListFilteringHandler; + //#endregion + + // + //#region LifeCycles ... + async afterViewInit() { + super.afterViewInit(); + + // + await this.startUp(); + } + + onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region UI Provider ... + getColorValue(key: string, value: string) { + return { + key, + value, + } as XBaseKeyValue; + } + //#endregion + + // + //#region UI Handler ... + // + async handleSelectedTabChanged(event: XTabChangeEvent) { + this.currentTab = event.index; + } + + async handleListRefresh() { + // + await this.startUp(); + + // + if (!isNullOrUndefined(this.listEmptyRefresh)) { + this.listEmptyRefresh.emit(); + } + } + + async handleListActionBarActionFired(model: XActionBarActionFiredModel) { + // + if (isNullOrUndefined(model) || isNullOrEmptyString(model.action)) { + return; + } + + // + switch (model.action) { + // + case DefaultActionBarItemIds.Upload: + await this.handleUploadTheme(); + break; + } + + // + if (!isNullOrUndefined(this.listActionBarActionFired)) { + this.listActionBarActionFired.emit(model); + } + } + + async handleListSearchBarOnEvent(event: XSearchBarEventIdentifier) { + // + if (!isNullOrUndefined(this.listSearchBarOnEvent)) { + this.listSearchBarOnEvent.emit(event); + } + } + + async handleListSearchBarQueryChange(event: string) { + // + if (!isNullOrUndefined(this.listSearchBarQueryChange)) { + this.listSearchBarQueryChange.emit(event); + } + } + + async handleThemeUploadActionFired(model: XActionBarActionFiredModel) { + // + // Validate Args ... + if (isNullOrUndefined(model) || isNullOrEmptyString(model.action)) { + return; + } + + // + switch (model.action) { + // + case DefaultActionBarItemIds.Upload: + await this.handleThemesImport(); + break; + } + } + + async handleThemeUploadFileChanged(files: XOneOrManyType) { + this.uploadThemes = files; + } + + // + //#region ModelPresenter Handlers ... + async handleThemePropertyActionMenuClicked( + key: string, + prefix: string, + value: any + ) { + // + if (isNullOrEmptyString(key)) { + return; + } + + // + const onlyIcon = false; + const slideOptions: XListItemSlideOption[] = []; + + // + // Edit ... + slideOptions.push({ + id: 'property_edit', + icon: await this.getValueAsync(this.propertyEditIcon), + title: this.ResourceIDs.edit, + color: isNullOrUndefined(value) + ? await this.getValueAsync(this.emptyPropertyEditIconColor) + : await this.getValueAsync(this.propertyEditIconColor), + slot: 'start', + onlyIcon, + handler: async () => { + await this.handleThemePropertyEdit(key, prefix, value); + }, + }); + + // + // Menu Items ... + const item: XListItem = { + data: '', + slideOptions, + }; + + // + const popOver = await this.managerService.dialogService.presentPopover({ + component: XPopoverComponent, + componentProps: assign({}, { key: XParam.Item, value: item }), + translucent: true, + showBackdrop: true, + event, + }); + + // + const { data } = await popOver.onWillDismiss(); + if (!data || !data.id || !data.data) { + return; + } + + // + const optionId = data.id.toString() as string; + const option = + item && item.slideOptions + ? item.slideOptions.find((so) => so.id === optionId) + : null; + if (!option) { + return; + } + + // + const handler = option.handler; + if (!handler || !isFunction(handler)) { + return; + } + + // + // Run Handler ... + await handler(); + } + + async handleThemePropertyEdit(key: string, prefix: string, value: any) { + // + if (isNullOrEmptyString(key)) { + return; + } + + // + await this.colorPickerDialogService.openDialog({ + // + title: key, + + // + selectedColor: value, + + // + showColorInput: true, + showPresetColors: false, + showCopyToClipboard: true, + + // + cardColor: await this.getValueAsync(this.cardColor), + cardForegroundColor: await this.getValueAsync(this.cardForegroundColor), + + // + representColorFormats: [XColorPickerOutputFormat.XHEX], + }); + + // + const subscription = this.colorPickerDialogService.onResult + .asObservable() + .subscribe((result) => { + // + if ( + result.result !== XDialogResult.Ok || + !result.payload || + isNullOrEmptyString(result.payload) + ) { + return; + } + + // + const value = result.payload as string; + this.themeModelPresenterActionProvider.next({ + action: XModelPresenterAction.SetProperty, + payload: { + key, + value, + prefix, + } as XModelProperty, + }); + + // + subscription.unsubscribe(); + }); + } + + async handleThemeContentChanged(event: any) { + // + const isTheme = isSameType(event, this.selectedTheme); + if (!isTheme) { + return; + } + + // + const isThemeChanged = !isSameObject(this.baseTheme, this.selectedTheme); + this.canSaveTheme = isThemeChanged; + + // + toArray(this.themeModelPresenterActionActions)[0].disabled = + !this.canSaveTheme; + + // + this.themeChange.emit(isThemeChanged); + this.handleChangeDetection(); + } + + async handleUpdateTheme() { + // + if (!this.selectedTheme || !this.baseTheme || !this.canSaveTheme) { + return; + } + + // + await this.managerService.dialogService.presentAreYouSureYesNoDialog( + async () => { + await this.updateTheme(this.selectedTheme); + } + ); + } + //#endregion + //#endregion + + // + //#region Actions ... + async back() { + // + switch (this.currentTab) { + // + case this.Tabs.List: + this.baseTheme = null; + this.selectedTheme = null; + break; + + // + case this.Tabs.Details: + await this.changeTab(this.Tabs.List); + break; + + // + case this.Tabs.Import: + // + this.uploadThemes = []; + this.themeUploadActionProvider.next({ + action: XFileUploadAction.CLEAR_LIST, + }); + await this.changeTab(this.Tabs.List); + break; + + // + case this.Tabs.Edit: + this.baseTheme = null; + this.selectedTheme = null; + this.canSaveTheme = false; + this.themeChange.emit(false); + await this.changeTab(this.Tabs.List); + break; + } + } + + async clearList() { + // + this.listItems = []; + this.handleChangeDetection(); + } + + async setTheme(theme: XThemePack, whichType: XThemeTypeIdentifier) { + // + if (!theme && isNullOrEmptyString(theme.name)) { + return; + } + + // + const restart = await this.getOptionalValueAsync(this.restartAfterSetTheme); + + // + const isSetted = await this.managerService.themeManagerService.setTheme( + theme, + whichType, + true, + restart + ); + if (isSetted) { + // + await this.prepareListItems(); + + // + this.managerService.settingsService.restart(); + } + } + + async editTheme(theme: XThemePack) { + // + if (!theme || !isNullOrUndefined(this.loadingObject)) { + return; + } + + // + await this.presentLoading(); + + // + this.baseTheme = cloneJson(theme); + this.selectedTheme = { + ...theme, + }; + await this.changeTab(this.Tabs.Edit); + this.handleChangeDetection(); + } + + async cloneTheme(theme: XThemePack) { + // + if (!theme) { + return; + } + + // + await this.managerService.dialogService.presentAlert({ + subHeader: XResourceIDs.theme, + message: XResourceIDs.theme_clone_title, + inputs: [ + { + type: 'text', + name: 'name', + placeholder: XResourceIDs.theme_name_description, + }, + ], + buttons: [ + { + text: XResourceIDs.clone, + role: XModalButtonRole.Selected, + cssClass: ['btn-selected'], + handler: async (value) => { + // + if (value && value.name && !isNullOrEmptyString(value.name)) { + // + const themeName = toNormalString(theme.name); + const clonedThemeName = toNormalString(value.name as string); + const isCloned = + await this.managerService.themeManagerService.cloneTheme( + themeName, + clonedThemeName, + false + ); + + // + if (isCloned) { + // + this.showSuccess(); + await this.prepareListItems(); + } else { + this.showError(XExceptionIDs.ActionFailed); + } + } else { + this.showError(XExceptionIDs.InvalidData); + } + }, + }, + { + text: XResourceIDs.cancel, + role: XModalButtonRole.Cancel, + cssClass: ['btn-cancel'], + handler: () => {}, + }, + ], + }); + } + + async downloadTheme(theme: XThemePack) { + // + if (!theme) { + return; + } + + // + await this.managerService.themeManagerService.exportTheme(theme.name); + } + + async removeTheme(theme: XThemePack) { + // + if (!theme) { + return; + } + + // + await this.managerService.dialogService.presentAreYouSureYesNoDialog(() => { + this.managerService.themeManagerService + .removeTheme(toNormalString(theme.name), false) + .then((isRemoved) => { + if (isRemoved) { + // + this.showSuccess(); + this.prepareListItems(); + } else { + this.showError(); + } + }); + }); + } + + async previewTheme(theme: XThemePack) { + // + if (!theme || !isNullOrUndefined(this.loadingObject)) { + return; + } + + // + await this.presentLoading(); + + // + this.selectedTheme = { + ...theme, + }; + await this.changeTab(this.Tabs.Details); + this.handleChangeDetection(); + } + + async saveTheme(theme: XThemePack) { + // + if (!theme) { + return false; + } + + // + if ( + isNullOrEmptyString(theme.name) || + (await this.managerService.themeManagerService.isThemeExists(theme.name)) + ) { + this.showError(XExceptionIDs.Duplicate); + return false; + } + + // + const isSaved = await this.managerService.themeManagerService.saveTheme( + theme, + false + ); + + // + if (isSaved) { + // + this.themeChange.emit(false); + this.managerService.settingsService.restart(); + } + + return isSaved; + } + + async updateTheme(theme: XThemePack) { + // + if (!theme || isNullOrEmptyString(theme.name)) { + return false; + } + + // + const isExists = + await this.managerService.themeManagerService.isThemeExists(theme.name); + if (!isExists) { + this.showError(XExceptionIDs.NotFound); + return false; + } + + // + const isUpdated = await this.managerService.themeManagerService.updateTheme( + theme.name, + theme, + false, + true + ); + + // + if (isUpdated) { + // + this.themeChange.emit(false); + this.managerService.settingsService.restart(); + } + + // + return isUpdated; + } + //#endregion + + // + //#region Private ... + // + //#region Handler Registerrers ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + switch (model.action) { + // + case XThemeManagerAction.Refresh: + this.detectChanges(); + break; + + // + case XThemeManagerAction.Back: + await this.back(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + // + //#region Private Actions ... + private async startUp() { + // + this.tabChange.emit(this.currentTab); + await this.prepareActionBarActions(); + await this.prepareListItems(); + } + + private async prepareListItems() { + // + this.listItems = null; + + // + const loadingDialog = + await this.managerService.dialogService.presentLoading({ + showBackdrop: true, + backdropDismiss: false, + message: this.resourceProvider(XResourceIDs.loading), + }); + + // + const currentTheme = + await this.managerService.themeManagerService.getCurrentTheme(); + const themes = await this.managerService.themeManagerService.getThemes(); + if (!currentTheme || !hasChild(themes)) { + // + await this.clearList(); + await loadingDialog.dismiss(); + return; + } + + // + // Prepare List Items ... + this.listItems = themes.map((theme) => { + // + const isCurrent = + toNormalString(theme.name) === toNormalString(currentTheme.name); + + // + return { + data: theme, + slideOptions: [ + // + // Remove ... + { + id: 'delete_theme', + icon: XIconNames.remove, + title: this.ResourceIDs.delete, + color: XColorWithBrightness.Danger, + onlyIcon: true, + disabled: this.uiDisabled || this.loading || isCurrent, + slot: 'end', + handler: () => { + this.removeTheme(theme); + }, + }, + // + // Set As Current ... + { + id: 'set_theme', + icon: XIconNames.checked, + title: this.ResourceIDs.set, + color: XColorWithBrightness.Success, + onlyIcon: true, + disabled: + this.uiDisabled || + this.loading || + toNormalString(theme.name) === toNormalString(currentTheme.name), + slot: 'start', + handler: () => { + // + // Prepare Picker Options for Theme Setting ... + const options: XPickerOptions = { + buttons: [ + { + text: XResourceIDs.set, + role: XModalButtonRole.Selected, + cssClass: ['btn-selected'], + handler: async (value) => { + // + if (!value || !value.type || !value.type.value) { + return; + } + + // + const type = value.type.value as XThemeTypeIdentifier; + await this.setTheme(theme, type); + }, + }, + { + text: XResourceIDs.cancel, + role: XModalButtonRole.Cancel, + cssClass: ['btn-cancel'], + }, + ], + columns: [ + { + name: 'type', + options: [ + { + text: XResourceIDs.theme_light, + value: XThemeType.Light, + }, + { + text: XResourceIDs.theme_dark, + value: XThemeType.Dark, + }, + ], + }, + ], + }; + + // + // Present Theme Picker ... + this.managerService.dialogService.presentPicker(options); + }, + }, + // + // Check For Change Mode ... + ...(isCurrent + ? [ + // + // Change Mode ... + { + id: 'change_theme_mode', + icon: XIconNames.contrast, + title: this.ResourceIDs.theme_mode, + color: XColorWithBrightness.Success, + onlyIcon: true, + disabled: this.uiDisabled || this.loading, + slot: 'start', + handler: async () => { + // + const currentType = + await this.managerService.themeManagerService.getCurrentThemeType(); + const type = + currentType === XThemeType.Light + ? XThemeType.Dark + : XThemeType.Light; + await this.setTheme(theme, type); + }, + }, + ] + : []), + // + // Preview ... + { + id: 'preview_theme', + icon: XIconNames.preview, + title: this.ResourceIDs.details, + color: XColorWithBrightness.Secondary, + onlyIcon: true, + disabled: this.uiDisabled || this.loading, + slot: 'start', + handler: async () => { + await this.previewTheme(theme); + }, + }, + // + // Edit ... + { + id: 'edit_theme', + icon: XIconNames.edit, + title: this.ResourceIDs.edit, + color: XColorWithBrightness.Tertiary, + onlyIcon: true, + disabled: this.uiDisabled || this.loading, // || isCurrent, + slot: 'start', + handler: () => { + this.editTheme(theme); + }, + }, + // + // Clone ... + { + id: 'clone_theme', + icon: XIconNames.copy, + title: this.ResourceIDs.clone, + color: XColorWithBrightness.Success, + onlyIcon: true, + disabled: this.uiDisabled || this.loading, + slot: 'start', + handler: async () => { + await this.cloneTheme(theme); + }, + }, + // + // Download ... + { + id: 'download_theme', + icon: XIconNames.download, + title: this.ResourceIDs.download, + color: XColorWithBrightness.SecondaryShade, + onlyIcon: true, + disabled: this.uiDisabled || this.loading, + slot: 'start', + handler: async () => { + await this.downloadTheme(theme); + }, + }, + ], + } as XListItem; + }); + + // + await loadingDialog.dismiss(); + + // + this.handleChangeDetection(); + } + + private handleChangeDetection() { + // + this.tabsActionProvider.next({ + action: XTabsAction.Refresh, + }); + + // + this.listActionProvider.next({ + action: XListAction.Refresh, + }); + + // + this.actionBarActionProvider.next({ + action: XActionBarAction.Refresh, + }); + + // + this.searchBarActionProvider.next({ + action: XSearchBarAction.Refresh, + }); + + // + this.themeUploadActionProvider.next({ + action: XFileUploadAction.Refresh, + }); + + // + this.themeUploadListActionProvider.next({ + action: XListAction.Refresh, + }); + + // + this.themeModelPresenterActionProvider.next({ + action: XModelPresenterAction.Refresh, + }); + + // + this.themeUploadFileDropAreaActionProvider.next({ + action: XFileUploadAction.Refresh, + }); + + // + this.managerService.dispatchResizeEvent(); + + // + this.detectChanges(); + } + + private async presentLoading() { + // + this.loadingObject = await this.managerService.dialogService.presentLoading( + { + message: this.resourceProvider(this.ResourceIDs.loading), + } + ); + } + + private async changeTab(tab?: XThemeManagerTabs) { + // + if (!tab) { + tab = this.Tabs.List; + } + + // + // + // Prevent go forward if args is not ready ... + if (this.loading || this.uiDisabled || this.currentTab === tab) { + return; + } + + // + //#region Pre Change Actions ... + // + // Based On Current Tab ... + switch (this.currentTab) { + // + case this.Tabs.List: + break; + + // + case this.Tabs.Details: + break; + + // + case this.Tabs.Import: + break; + + // + case this.Tabs.Edit: + break; + } + + // + // Based On Dest Tab ... + switch (tab) { + // + case this.Tabs.List: + this.baseTheme = null; + this.selectedTheme = null; + break; + + // + case this.Tabs.Details: + // + if (isNullOrUndefined(this.selectedTheme)) { + return; + } + break; + + // + case this.Tabs.Import: + break; + + // + case this.Tabs.Edit: + // + if (isNullOrUndefined(this.selectedTheme)) { + return; + } + break; + } + //#endregion + + // + // Change Current Tab ... + this.currentTab = tab; + this.detectChanges(); + + // + //#region Post Change Actions ... + switch (tab) { + // + case this.Tabs.List: + break; + + // + case this.Tabs.Details: + break; + + // + case this.Tabs.Import: + break; + + // + case this.Tabs.Edit: + break; + } + //#endregion + + // + this.tabChange.emit(tab); + this.handleChangeDetection(); + } + + private async handleUploadTheme() { + await this.changeTab(this.Tabs.Import); + } + + private async handleThemesImport() { + // + if (!this.hasChild(this.uploadThemes)) { + return; + } + + // + const emptyTheme = getEmptyThemePack(); + const importedThemes = await this.getArrayValueAsync(this.uploadThemes); + + // + importedThemes.forEach(async (themeFile) => { + // + const fileReader = new FileReader(); + fileReader.readAsText(themeFile); + fileReader.onloadend = async (e) => { + // + if (!e || !e.target || !e.target.result) { + return; + } + + // + try { + // + // Check Theme Valid ... + const themePack = parseJson(e.target.result.toString()); + if (!themePack) { + // + await this.issueThemeError( + themeFile.name, + XExceptionIDs.InavlidFile + ); + + // + return; + } + + // + // Check Theme Type ... + const isCorrectThemeType = isSameType(themePack, emptyTheme); + if (!isCorrectThemeType) { + // + await this.issueThemeError( + themeFile.name, + XExceptionIDs.UnsupportedFileType + ); + + // + return; + } + + // + // Check Theme Name and Existance ... + if ( + isNullOrEmptyString(themePack.name) || + (await this.managerService.themeManagerService.isThemeExists( + themePack.name + )) + ) { + // + this.issueThemeError(themeFile.name, XExceptionIDs.Duplicate); + + // + return; + } + + // + const isSaved = await this.saveTheme(themePack); + if (isSaved) { + await this.prepareListItems(); + } else { + // + await this.issueThemeError( + themeFile.name, + XExceptionIDs.ActionFailed + ); + + // + return; + } + } catch { + await this.issueThemeError(themeFile.name, XExceptionIDs.InavlidFile); + } + }; + }); + + // + await this.back(); + } + + private async issueThemeError(fileName: string, exception: XExceptionIDs) { + // + if (isNullOrEmptyString(fileName)) { + return; + } + + // + const message = `${this.managerService.localizationService.getErrorMessage( + exception + )}_${fileName}`; + + // + await this.managerService.notificationService.presentDangerNotification({ + dissmissable: true, + message, + }); + } + //#endregion + + // + //#region ActionBar Actions ... + private async prepareActionBarActions() { + // + //#region Prepare Default Actions ... + const hideActions: string | string[] = []; + + // + const defaultActions: XOneOrManyType = []; + + // + // Import ... + const importId = DefaultActionBarItemIds.Upload.toString(); + if (!hideActions.includes(importId)) { + defaultActions.push({ + // + ...defaultActionBarUploadItem, + + // + title: XResourceIDs.import, + color: XColorWithBrightness.Success, + + // + index: 1, + disabled: this.uiDisabled, + }); + } + //#endregion + + // + if (!this.actionBarActions) { + // + this.actionBarActions = defaultActions; + this.handleChangeDetection(); + return; + } + + // + const actions = await this.getArrayValueAsync(this.actionBarActions); + if (!hasChild(actions)) { + // + this.actionBarActions = defaultActions; + this.handleChangeDetection(); + return; + } + + // + this.actionBarActions = actions; + this.handleChangeDetection(); + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-theme\x-theme-manager\x-theme-manager.typings.ts + +import { + Input, + Inject, + NgZone, + Output, + Renderer2, + Component, + Injectable, + ElementRef, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XResourceIDs, + XBorderStyle, + XStandardType, + XOneOrManyType, + XColorIdentifier, + XResourceIdentifier, + XColorWithBrightness, + XBorderStyleIdentifier, +} from 'x-framework-core'; +import { + XCardContainer, + XComponentCardContainer, + IXComponentCardContainer, + XComponentCardContainerComponent, +} from '../../x-card/x-card.typings'; +import { + XComponentListContainer, + IXComponentListContainer, +} from '../../x-list/x-list.typings'; +import { + XFileContainer, + IXFileContainer, +} from '../../classes/i-x-file.container'; +import { concatMap, map } from 'rxjs/operators'; +import { + XActionBarActionFiredModel, + XComponentActionBarContainer, + IXComponentActionBarContainer, +} from '../../x-action-bar/x-action-bar.typings'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { forkJoin, Observable, Subject } from 'rxjs'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../../classes/x-base-action.model'; +import { XIconIdentifier, XIconNames } from '../../x-icon/x-icon.typings'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../../tokens/x-injectable-tokens'; +import { IXFileUploadContainer } from '../../x-file-upload/x-file-upload.typings'; +import { XFileUploadContainer } from './../../x-file-upload/x-file-upload.typings'; +import { XSearchBarEventIdentifier } from '../../x-search-bar/x-search-bar.typings'; +import { XFrameworkComponentsConfig } from '../../config/x-framework-components.config'; +import { XColorPickerDialogService } from '../../x-color-picker/x-color-picker-dialog/x-color-picker-dialog.service'; + +export enum XThemeManagerAction { + // + Back = 'back', + Refresh = 'refresh', +} + +export type XThemeManagerActionIdentifier = XThemeManagerAction | string; + +export interface XThemeManagerActionModel + extends XBaseActionModel {} + +export enum XThemeManagerTabs { + List, + Details, + Edit, + Import, +} + +// +//#region Inside Card Container ... +// +type TXSelectedComponentListContainer = Pick< + IXComponentListContainer, + | 'listCssClass' + | 'listShowDivider' + // + // List Empty ... + | 'listShowEmpty' + | 'listEmptyActionProvider' + | 'listEmptyColor' + | 'listEmptyCssClass' + | 'listEmptyForegroundColor' + | 'listEmptyForegroundMessageColor' + | 'listEmptyForegroundRefreshButtonColor' + | 'listEmptyHasRefresh' + | 'listEmptyMessage' + | 'listEmptyMessageColor' + | 'listEmptyRefresh' + | 'listEmptyRefreshButtonColor' + | 'listEmptyRefreshIcon' + // + // List Search ... + | 'listSearchBarValue' + | 'listSearchBarColor' + | 'listSearchBarDebounce' + | 'listSearchBarSearchInputColor' + | 'listSearchBarForegroundSearchInputColor' + | 'listSearchBarCancelButtonIcon' + | 'listSearchBarCancelButtonTitle' + | 'listSearchBarClearButtonIcon' + | 'listSearchBarClearButtonIconColor' + | 'listSearchBarCssClass' + | 'listSearchBarEnterKeyHint' + | 'listSearchBarForegroundColor' + | 'listSearchBarKeyboardType' + | 'listActionBarActionFired' + | 'listSearchBarOnEvent' + | 'listSearchBarQueryChange' + | 'listSearchBarSearchButtonIcon' + | 'listSearchBarSearchButtonIconColor' + | 'listSearchBarShowCancelButton' + | 'listSearchBarSpellcheck' + | 'listSearchBarTitle' + | 'listShowSearchBar' +>; + +// +type TXSelectedComponentActionBarContainer = Pick< + IXComponentActionBarContainer, + | 'showActionBar' + | 'actionBarColor' + | 'actionBarCssClass' + | 'actionBarForegroundColor' +>; + +// +type TXSelectedComponentFileUploadContainer = Pick< + { + [TKey in keyof IXFileUploadContainer as `themeUpload${Capitalize< + string & TKey + >}`]: IXFileUploadContainer[TKey]; + }, + // + | 'themeUploadColor' + | 'themeUploadCssClass' + | 'themeUploadEmptyMessage' + | 'themeUploadForegroundColor' + | 'themeUploadFileDropAreaIcon' + | 'themeUploadShowFileDropArea' + | 'themeUploadFileDropAreaColor' + | 'themeUploadFileDropAreaMessage' + | 'themeUploadFileDropAreaCssClass' + | 'themeUploadFileDropAreaErrorColor' + | 'themeUploadFileDropAreaAccentColor' + | 'themeUploadFileDropAreaSuccessColor' + | 'themeUploadFileDropAreaForegroundColor' + | 'themeUploadEmptyForegroundMessageColor' + | 'themeUploadFileDropAreaSupportsMultipleFiles' +>; + +// +type TXSelectedFileContainer = Pick< + IXFileContainer, + | 'maxAllowedSize' + | 'minAllowedFileSize' + | 'maxAllowedFileSize' + | 'maxAllowedFiles' +>; + +// +type TXThemeManagerContainer = IXComponentCardContainer & + TXSelectedComponentActionBarContainer & + TXSelectedComponentListContainer & + TXSelectedComponentFileUploadContainer & + TXSelectedFileContainer & {}; + +// +export interface IXThemeManagerContainer extends TXThemeManagerContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + color: XStandardType; + + // + themeChange: EventEmitter; + tabChange: EventEmitter>; + + // + restartAfterSetTheme: XStandardType; + colorViewBorderWidth: XStandardType; + colorViewBorderStyle: XStandardType; + + // + propertyLabelColor: XStandardType; + propertyValueColor: XStandardType; + colorViewBorderColor: XStandardType; + + // + propertyActionMenuIconColor: XStandardType; + propertyActionMenuIcon: XStandardType; + propertyEditIconColor: XStandardType; + propertyEditIcon: XStandardType; + propertyClearIconColor: XStandardType; + propertyClearIcon: XStandardType; + emptyPropertyEditIconColor: XStandardType; + + // + presenterHeaderColor: XStandardType; + presenterContentColor: XStandardType; + presenterHeaderHighlightColor: XStandardType; + + // + saveColor: XStandardType; + saveLabel: XStandardType; + resetColor: XStandardType; + resetLabel: XStandardType; +} + +@Injectable({ + providedIn: 'root', +}) +export class XThemeManagerContainer + extends XComponentCardContainer + implements Readonly +{ + // + //#region Props ... + // + //#region Self ... + readonly actionProvider: Subject = + new Subject(); + readonly cssClass: XStandardType>; + + // + readonly color: XStandardType = XColorWithBrightness.Light; + + // + readonly themeChange: EventEmitter = new EventEmitter(); + readonly tabChange: EventEmitter> = new EventEmitter< + XStandardType + >(); + + // + readonly restartAfterSetTheme: XStandardType = true; + readonly colorViewBorderWidth: XStandardType = 1; + readonly colorViewBorderStyle: XStandardType = + XBorderStyle.Solid; + + // + readonly propertyLabelColor: XStandardType = + XColorWithBrightness.Tertiary; + readonly propertyValueColor: XStandardType = + XColorWithBrightness.Dark; + readonly colorViewBorderColor: XStandardType = + XColorWithBrightness.LightShade; + + // + readonly propertyActionMenuIconColor: XStandardType = + XColorWithBrightness.Dark; + readonly propertyActionMenuIcon: XStandardType = + XIconNames.menu_vertical; + readonly propertyEditIconColor: XStandardType = + XColorWithBrightness.Tertiary; + readonly propertyEditIcon: XStandardType = XIconNames.edit; + readonly propertyClearIconColor: XStandardType = + XColorWithBrightness.Danger; + readonly propertyClearIcon: XStandardType = XIconNames.clear; + readonly emptyPropertyEditIconColor: XStandardType = + XColorWithBrightness.Danger; + + // + readonly presenterHeaderColor: XStandardType = + XColorWithBrightness.Light; + readonly presenterContentColor: XStandardType = + XColorWithBrightness.Dark; + readonly presenterHeaderHighlightColor: XStandardType = + XColorWithBrightness.LightShade; + + // + readonly saveColor: XStandardType = + XColorWithBrightness.Tertiary; + readonly saveLabel: XStandardType = XResourceIDs.save; + + // + readonly resetColor: XStandardType = + XColorWithBrightness.WarningShade; + readonly resetLabel: XStandardType = XResourceIDs.reset; + //#endregion + + // + //#region ActionBar ... + readonly showActionBar = this.actionBarPropertyProvider.showActionBar; + readonly actionBarColor = this.actionBarPropertyProvider.actionBarColor; + readonly actionBarCssClass = this.actionBarPropertyProvider.actionBarCssClass; + readonly actionBarForegroundColor = + this.actionBarPropertyProvider.actionBarForegroundColor; + //#endregion + + // + //#region List ... + readonly listCssClass = this.listPropertyProvider.listCssClass; + readonly listShowDivider = this.listPropertyProvider.listShowDivider; + + // + //#region List Empty ... + readonly listShowEmpty = this.listPropertyProvider.listShowEmpty; + readonly listEmptyActionProvider = + this.listPropertyProvider.listEmptyActionProvider; + readonly listEmptyColor = this.listPropertyProvider.listEmptyColor; + readonly listEmptyCssClass = this.listPropertyProvider.listEmptyCssClass; + readonly listEmptyForegroundColor = + this.listPropertyProvider.listEmptyForegroundColor; + readonly listEmptyForegroundMessageColor = + this.listPropertyProvider.listEmptyForegroundMessageColor; + readonly listEmptyForegroundRefreshButtonColor = + this.listPropertyProvider.listEmptyForegroundRefreshButtonColor; + readonly listEmptyHasRefresh = this.listPropertyProvider.listEmptyHasRefresh; + readonly listEmptyMessage = this.listPropertyProvider.listEmptyMessage; + readonly listEmptyMessageColor = + this.listPropertyProvider.listEmptyMessageColor; + readonly listEmptyRefresh = this.listPropertyProvider.listEmptyRefresh; + readonly listEmptyRefreshButtonColor = + this.listPropertyProvider.listEmptyRefreshButtonColor; + readonly listEmptyRefreshIcon = + this.listPropertyProvider.listEmptyRefreshIcon; + //#endregion + + // + //#region List Search ... + readonly listSearchBarValue = this.listPropertyProvider.listSearchBarValue; + readonly listSearchBarColor = this.listPropertyProvider.listSearchBarColor; + readonly listSearchBarDebounce = + this.listPropertyProvider.listSearchBarDebounce; + readonly listSearchBarSearchInputColor = + this.listPropertyProvider.listSearchBarSearchInputColor; + readonly listSearchBarForegroundSearchInputColor = + this.listPropertyProvider.listSearchBarForegroundSearchInputColor; + readonly listSearchBarCancelButtonIcon = + this.listPropertyProvider.listSearchBarCancelButtonIcon; + readonly listSearchBarCancelButtonTitle = + this.listPropertyProvider.listSearchBarCancelButtonTitle; + readonly listSearchBarClearButtonIcon = + this.listPropertyProvider.listSearchBarClearButtonIcon; + readonly listSearchBarClearButtonIconColor = + this.listPropertyProvider.listSearchBarClearButtonIconColor; + readonly listSearchBarCssClass = + this.listPropertyProvider.listSearchBarCssClass; + readonly listSearchBarEnterKeyHint = + this.listPropertyProvider.listSearchBarEnterKeyHint; + readonly listSearchBarForegroundColor = + this.listPropertyProvider.listSearchBarForegroundColor; + readonly listSearchBarKeyboardType = + this.listPropertyProvider.listSearchBarKeyboardType; + readonly listActionBarActionFired = + this.listPropertyProvider.listActionBarActionFired; + readonly listSearchBarOnEvent = + this.listPropertyProvider.listSearchBarOnEvent; + readonly listSearchBarQueryChange = + this.listPropertyProvider.listSearchBarQueryChange; + readonly listSearchBarSearchButtonIcon = + this.listPropertyProvider.listSearchBarSearchButtonIcon; + readonly listSearchBarSearchButtonIconColor = + this.listPropertyProvider.listSearchBarSearchButtonIconColor; + readonly listSearchBarShowCancelButton = + this.listPropertyProvider.listSearchBarShowCancelButton; + readonly listSearchBarSpellcheck = + this.listPropertyProvider.listSearchBarSpellcheck; + readonly listSearchBarTitle = this.listPropertyProvider.listSearchBarTitle; + readonly listShowSearchBar = this.listPropertyProvider.listShowSearchBar; + //#endregion + //#endregion + + // + //#region File Upload ... + readonly themeUploadColor = this.fileUploadPropertyProvider.color; + readonly themeUploadCssClass = this.fileUploadPropertyProvider.cssClass; + readonly themeUploadEmptyMessage = + this.fileUploadPropertyProvider.emptyMessage; + readonly themeUploadForegroundColor = + this.fileUploadPropertyProvider.foregroundColor; + readonly themeUploadFileDropAreaIcon = + this.fileUploadPropertyProvider.fileDropAreaIcon; + readonly themeUploadShowFileDropArea = + this.fileUploadPropertyProvider.showFileDropArea; + readonly themeUploadFileDropAreaColor = + this.fileUploadPropertyProvider.fileDropAreaColor; + readonly themeUploadFileDropAreaMessage = + this.fileUploadPropertyProvider.fileDropAreaMessage; + readonly themeUploadFileDropAreaCssClass = + this.fileUploadPropertyProvider.fileDropAreaCssClass; + readonly themeUploadFileDropAreaErrorColor = + this.fileUploadPropertyProvider.fileDropAreaErrorColor; + readonly themeUploadFileDropAreaAccentColor = + this.fileUploadPropertyProvider.fileDropAreaAccentColor; + readonly themeUploadFileDropAreaSuccessColor = + this.fileUploadPropertyProvider.fileDropAreaSuccessColor; + readonly themeUploadFileDropAreaForegroundColor = + this.fileUploadPropertyProvider.fileDropAreaForegroundColor; + readonly themeUploadEmptyForegroundMessageColor = + this.fileUploadPropertyProvider.emptyForegroundMessageColor; + readonly themeUploadFileDropAreaSupportsMultipleFiles = + this.fileUploadPropertyProvider.fileDropAreaSupportsMultipleFiles; + //#endregion + + // + //#region File ... + readonly maxAllowedSize = this.filePropertyProvider.maxAllowedSize; + readonly minAllowedFileSize = this.filePropertyProvider.minAllowedFileSize; + readonly maxAllowedFileSize = this.filePropertyProvider.maxAllowedFileSize; + readonly maxAllowedFiles = this.filePropertyProvider.maxAllowedFiles; + //#endregion + //#endregion + + // + //#region Constructor ... + constructor( + protected filePropertyProvider: XFileContainer, + protected cardPropertyProvider: XCardContainer, + protected listPropertyProvider: XComponentListContainer, + protected fileUploadPropertyProvider: XFileUploadContainer, + protected actionBarPropertyProvider: XComponentActionBarContainer + ) { + super(cardPropertyProvider); + } + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XThemeManagerContainerComponent + extends XComponentCardContainerComponent + implements IXThemeManagerContainer +{ + // + //#region Props ... + // + //#region Self ... + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + @Input() + color: XStandardType = this.propertyProvider.color; + + // + @Output() + themeChange: EventEmitter = new EventEmitter(); + + @Output() + tabChange: EventEmitter> = new EventEmitter(); + + // + @Input() + restartAfterSetTheme: XStandardType = + this.propertyProvider.restartAfterSetTheme; + + @Input() + colorViewBorderWidth: XStandardType = + this.propertyProvider.colorViewBorderWidth; + + @Input() + colorViewBorderStyle: XStandardType = + this.propertyProvider.colorViewBorderStyle; + + // + @Input() + propertyLabelColor: XStandardType = + this.propertyProvider.propertyLabelColor; + + @Input() + propertyValueColor: XStandardType = + this.propertyProvider.propertyValueColor; + + @Input() + colorViewBorderColor: XStandardType = + this.propertyProvider.colorViewBorderColor; + + // + @Input() + propertyActionMenuIconColor: XStandardType = + this.propertyProvider.propertyActionMenuIconColor; + + @Input() + propertyActionMenuIcon: XStandardType = + this.propertyProvider.propertyActionMenuIcon; + + @Input() + propertyEditIconColor: XStandardType = + this.propertyProvider.propertyEditIconColor; + + @Input() + propertyEditIcon: XStandardType = + this.propertyProvider.propertyEditIcon; + + @Input() + propertyClearIconColor: XStandardType = + this.propertyProvider.propertyClearIconColor; + + @Input() + propertyClearIcon: XStandardType = + this.propertyProvider.propertyClearIcon; + + @Input() + emptyPropertyEditIconColor: XStandardType = + this.propertyProvider.emptyPropertyEditIconColor; + + // + @Input() + presenterHeaderColor: XStandardType = + this.propertyProvider.presenterHeaderColor; + + @Input() + presenterContentColor: XStandardType = + this.propertyProvider.presenterContentColor; + + @Input() + presenterHeaderHighlightColor: XStandardType = + this.propertyProvider.presenterHeaderHighlightColor; + + // + @Input() + saveColor: XStandardType = this.propertyProvider.saveColor; + + @Input() + saveLabel: XStandardType = + this.propertyProvider.saveLabel; + + // + @Input() + resetColor: XStandardType = + this.propertyProvider.resetColor; + + @Input() + resetLabel: XStandardType = + this.propertyProvider.resetLabel; + //#endregion + + // + //#region ActionBar ... + @Input() + showActionBar: XStandardType = this.propertyProvider.showActionBar; + + @Input() + actionBarColor: XStandardType = + this.propertyProvider.actionBarColor; + + @Input() + actionBarCssClass: XStandardType> = + this.propertyProvider.actionBarCssClass; + + @Input() + actionBarForegroundColor: XStandardType = + this.propertyProvider.actionBarForegroundColor; + //#endregion + + // + //#region List ... + @Input() + listCssClass = this.propertyProvider.listCssClass; + + @Input() + listShowDivider = this.propertyProvider.listShowDivider; + + // + //#region List Empty ... + @Input() + listShowEmpty = this.propertyProvider.listShowEmpty; + + @Input() + listEmptyActionProvider = this.propertyProvider.listEmptyActionProvider; + + @Input() + listEmptyColor = this.propertyProvider.listEmptyColor; + + @Input() + listEmptyCssClass = this.propertyProvider.listEmptyCssClass; + + @Input() + listEmptyForegroundColor = this.propertyProvider.listEmptyForegroundColor; + + @Input() + listEmptyForegroundMessageColor = + this.propertyProvider.listEmptyForegroundMessageColor; + + @Input() + listEmptyForegroundRefreshButtonColor = + this.propertyProvider.listEmptyForegroundRefreshButtonColor; + + @Input() + listEmptyHasRefresh = this.propertyProvider.listEmptyHasRefresh; + + @Input() + listEmptyMessage = this.propertyProvider.listEmptyMessage; + + @Input() + listEmptyMessageColor = this.propertyProvider.listEmptyMessageColor; + + @Output() + listEmptyRefresh: EventEmitter = new EventEmitter(); + + @Input() + listEmptyRefreshButtonColor = + this.propertyProvider.listEmptyRefreshButtonColor; + + @Input() + listEmptyRefreshIcon = this.propertyProvider.listEmptyRefreshIcon; + //#endregion + + // + //#region List Search ... + @Input() + listSearchBarValue = this.propertyProvider.listSearchBarValue; + + @Input() + listSearchBarColor = this.propertyProvider.listSearchBarColor; + + @Input() + listSearchBarDebounce = this.propertyProvider.listSearchBarDebounce; + + @Input() + listSearchBarSearchInputColor = + this.propertyProvider.listSearchBarSearchInputColor; + + @Input() + listSearchBarForegroundSearchInputColor = + this.propertyProvider.listSearchBarForegroundSearchInputColor; + + @Input() + listSearchBarCancelButtonIcon = + this.propertyProvider.listSearchBarCancelButtonIcon; + + @Input() + listSearchBarCancelButtonTitle = + this.propertyProvider.listSearchBarCancelButtonTitle; + + @Input() + listSearchBarClearButtonIcon = + this.propertyProvider.listSearchBarClearButtonIcon; + + @Input() + listSearchBarClearButtonIconColor = + this.propertyProvider.listSearchBarClearButtonIconColor; + + @Input() + listSearchBarCssClass = this.propertyProvider.listSearchBarCssClass; + + @Input() + listSearchBarEnterKeyHint = this.propertyProvider.listSearchBarEnterKeyHint; + + @Input() + listSearchBarForegroundColor = + this.propertyProvider.listSearchBarForegroundColor; + + @Input() + listSearchBarKeyboardType = this.propertyProvider.listSearchBarKeyboardType; + + @Output() + listActionBarActionFired: EventEmitter = new EventEmitter(); + + @Output() + listSearchBarOnEvent: EventEmitter = new EventEmitter(); + + @Output() + listSearchBarQueryChange: EventEmitter = new EventEmitter(); + + @Input() + listSearchBarSearchButtonIcon = + this.propertyProvider.listSearchBarSearchButtonIcon; + + @Input() + listSearchBarSearchButtonIconColor = + this.propertyProvider.listSearchBarSearchButtonIconColor; + + @Input() + listSearchBarShowCancelButton = + this.propertyProvider.listSearchBarShowCancelButton; + + @Input() + listSearchBarSpellcheck = this.propertyProvider.listSearchBarSpellcheck; + + @Input() + listSearchBarTitle = this.propertyProvider.listSearchBarTitle; + + @Input() + listShowSearchBar = this.propertyProvider.listShowSearchBar; + //#endregion + //#endregion + + // + //#region File Upload ... + @Input() + themeUploadColor = this.propertyProvider.themeUploadColor; + + @Input() + themeUploadCssClass = this.propertyProvider.themeUploadCssClass; + + @Input() + themeUploadEmptyMessage = this.propertyProvider.themeUploadEmptyMessage; + + @Input() + themeUploadForegroundColor = this.propertyProvider.themeUploadForegroundColor; + + @Input() + themeUploadFileDropAreaIcon = + this.propertyProvider.themeUploadFileDropAreaIcon; + + @Input() + themeUploadShowFileDropArea = + this.propertyProvider.themeUploadShowFileDropArea; + + @Input() + themeUploadFileDropAreaColor = + this.propertyProvider.themeUploadFileDropAreaColor; + + @Input() + themeUploadFileDropAreaMessage = + this.propertyProvider.themeUploadFileDropAreaMessage; + + @Input() + themeUploadFileDropAreaCssClass = + this.propertyProvider.themeUploadFileDropAreaCssClass; + + @Input() + themeUploadFileDropAreaErrorColor = + this.propertyProvider.themeUploadFileDropAreaErrorColor; + + @Input() + themeUploadFileDropAreaAccentColor = + this.propertyProvider.themeUploadFileDropAreaAccentColor; + + @Input() + themeUploadFileDropAreaSuccessColor = + this.propertyProvider.themeUploadFileDropAreaSuccessColor; + + @Input() + themeUploadFileDropAreaForegroundColor = + this.propertyProvider.themeUploadFileDropAreaForegroundColor; + + @Input() + themeUploadEmptyForegroundMessageColor = + this.propertyProvider.themeUploadEmptyForegroundMessageColor; + + @Input() + themeUploadFileDropAreaSupportsMultipleFiles = + this.propertyProvider.themeUploadFileDropAreaSupportsMultipleFiles; + //#endregion + + // + //#region File ... + @Input() + maxAllowedSize = this.propertyProvider.maxAllowedSize; + + @Input() + minAllowedFileSize = this.propertyProvider.minAllowedFileSize; + + @Input() + maxAllowedFileSize = this.propertyProvider.maxAllowedFileSize; + + @Input() + maxAllowedFiles = this.propertyProvider.maxAllowedFiles; + //#endregion + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XThemeManagerContainer, + public colorPickerDialogService: XColorPickerDialogService + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXThemeManagerContainer + ): Observable { + return super + .extractProps(container ? container : this) + .pipe( + concatMap((superData) => { + return forkJoin({ + // + cssClass: this.getValue( + container ? container.cssClass : this.cssClass + ), + + // + restartAfterSetTheme: this.getValue( + container + ? container.restartAfterSetTheme + : this.restartAfterSetTheme + ), + colorViewBorderWidth: this.getValue( + container + ? container.colorViewBorderWidth + : this.colorViewBorderWidth + ), + colorViewBorderStyle: this.getValue( + container + ? container.colorViewBorderStyle + : this.colorViewBorderStyle + ), + + // + propertyLabelColor: this.getValue( + container ? container.propertyLabelColor : this.propertyLabelColor + ), + propertyValueColor: this.getValue( + container ? container.propertyValueColor : this.propertyValueColor + ), + colorViewBorderColor: this.getValue( + container + ? container.colorViewBorderColor + : this.colorViewBorderColor + ), + color: this.getValue(container ? container.color : this.color), + + // + propertyActionMenuIconColor: this.getValue( + container + ? container.propertyActionMenuIconColor + : this.propertyActionMenuIconColor + ), + propertyActionMenuIcon: this.getValue( + container + ? container.propertyActionMenuIcon + : this.propertyActionMenuIcon + ), + propertyEditIconColor: this.getValue( + container + ? container.propertyEditIconColor + : this.propertyEditIconColor + ), + propertyEditIcon: this.getValue( + container ? container.propertyEditIcon : this.propertyEditIcon + ), + propertyClearIconColor: this.getValue( + container + ? container.propertyClearIconColor + : this.propertyClearIconColor + ), + propertyClearIcon: this.getValue( + container ? container.propertyClearIcon : this.propertyClearIcon + ), + emptyPropertyEditIconColor: this.getValue( + container + ? container.emptyPropertyEditIconColor + : this.emptyPropertyEditIconColor + ), + + // + presenterHeaderColor: this.getValue( + container + ? container.presenterHeaderColor + : this.presenterHeaderColor + ), + presenterContentColor: this.getValue( + container + ? container.presenterContentColor + : this.presenterContentColor + ), + presenterHeaderHighlightColor: this.getValue( + container + ? container.presenterHeaderHighlightColor + : this.presenterHeaderHighlightColor + ), + + // + saveColor: this.getValue( + container ? container.saveColor : this.saveColor + ), + saveLabel: this.getValue( + container ? container.saveLabel : this.saveLabel + ), + + // + resetColor: this.getValue( + container ? container.resetColor : this.resetColor + ), + resetLabel: this.getValue( + container ? container.resetLabel : this.resetLabel + ), + + // + showActionBar: this.getValue( + container ? container.showActionBar : this.showActionBar + ), + actionBarColor: this.getValue( + container ? container.actionBarColor : this.actionBarColor + ), + actionBarCssClass: this.getValue( + container ? container.actionBarCssClass : this.actionBarCssClass + ), + actionBarForegroundColor: this.getValue( + container + ? container.actionBarForegroundColor + : this.actionBarForegroundColor + ), + + // + listCssClass: this.getValue( + container ? container.listCssClass : this.listCssClass + ), + listShowDivider: this.getValue( + container ? container.listShowDivider : this.listShowDivider + ), + + // + listShowEmpty: this.getValue( + container ? container.listShowEmpty : this.listShowEmpty + ), + listEmptyColor: this.getValue( + container ? container.listEmptyColor : this.listEmptyColor + ), + listEmptyCssClass: this.getValue( + container ? container.listEmptyCssClass : this.listEmptyCssClass + ), + listEmptyForegroundColor: this.getValue( + container + ? container.listEmptyForegroundColor + : this.listEmptyForegroundColor + ), + listEmptyForegroundMessageColor: this.getValue( + container + ? container.listEmptyForegroundMessageColor + : this.listEmptyForegroundMessageColor + ), + listEmptyForegroundRefreshButtonColor: this.getValue( + container + ? container.listEmptyForegroundRefreshButtonColor + : this.listEmptyForegroundRefreshButtonColor + ), + listEmptyHasRefresh: this.getValue( + container + ? container.listEmptyHasRefresh + : this.listEmptyHasRefresh + ), + listEmptyMessage: this.getValue( + container ? container.listEmptyMessage : this.listEmptyMessage + ), + listEmptyMessageColor: this.getValue( + container + ? container.listEmptyMessageColor + : this.listEmptyMessageColor + ), + listEmptyRefreshButtonColor: this.getValue( + container + ? container.listEmptyRefreshButtonColor + : this.listEmptyRefreshButtonColor + ), + listEmptyRefreshIcon: this.getValue( + container + ? container.listEmptyRefreshIcon + : this.listEmptyRefreshIcon + ), + + // + listSearchBarValue: this.getValue( + container ? container.listSearchBarValue : this.listSearchBarValue + ), + listSearchBarColor: this.getValue( + container ? container.listSearchBarColor : this.listSearchBarColor + ), + listSearchBarDebounce: this.getValue( + container + ? container.listSearchBarDebounce + : this.listSearchBarDebounce + ), + listSearchBarSearchInputColor: this.getValue( + container + ? container.listSearchBarSearchInputColor + : this.listSearchBarSearchInputColor + ), + listSearchBarForegroundSearchInputColor: this.getValue( + container + ? container.listSearchBarForegroundSearchInputColor + : this.listSearchBarForegroundSearchInputColor + ), + listSearchBarCancelButtonIcon: this.getValue( + container + ? container.listSearchBarCancelButtonIcon + : this.listSearchBarCancelButtonIcon + ), + listSearchBarCancelButtonTitle: this.getValue( + container + ? container.listSearchBarCancelButtonTitle + : this.listSearchBarCancelButtonTitle + ), + listSearchBarClearButtonIcon: this.getValue( + container + ? container.listSearchBarClearButtonIcon + : this.listSearchBarClearButtonIcon + ), + listSearchBarClearButtonIconColor: this.getValue( + container + ? container.listSearchBarClearButtonIconColor + : this.listSearchBarClearButtonIconColor + ), + listSearchBarCssClass: this.getValue( + container + ? container.listSearchBarCssClass + : this.listSearchBarCssClass + ), + listSearchBarEnterKeyHint: this.getValue( + container + ? container.listSearchBarEnterKeyHint + : this.listSearchBarEnterKeyHint + ), + listSearchBarForegroundColor: this.getValue( + container + ? container.listSearchBarForegroundColor + : this.listSearchBarForegroundColor + ), + listSearchBarKeyboardType: this.getValue( + container + ? container.listSearchBarKeyboardType + : this.listSearchBarKeyboardType + ), + listSearchBarSearchButtonIcon: this.getValue( + container + ? container.listSearchBarSearchButtonIcon + : this.listSearchBarSearchButtonIcon + ), + listSearchBarSearchButtonIconColor: this.getValue( + container + ? container.listSearchBarSearchButtonIconColor + : this.listSearchBarSearchButtonIconColor + ), + listSearchBarShowCancelButton: this.getValue( + container + ? container.listSearchBarShowCancelButton + : this.listSearchBarShowCancelButton + ), + listSearchBarSpellcheck: this.getValue( + container + ? container.listSearchBarSpellcheck + : this.listSearchBarSpellcheck + ), + listSearchBarTitle: this.getValue( + container ? container.listSearchBarTitle : this.listSearchBarTitle + ), + listShowSearchBar: this.getValue( + container ? container.listShowSearchBar : this.listShowSearchBar + ), + + // + themeUploadColor: this.getValue( + container ? container.themeUploadColor : this.themeUploadColor + ), + themeUploadCssClass: this.getValue( + container + ? container.themeUploadCssClass + : this.themeUploadCssClass + ), + themeUploadEmptyMessage: this.getValue( + container + ? container.themeUploadEmptyMessage + : this.themeUploadEmptyMessage + ), + themeUploadForegroundColor: this.getValue( + container + ? container.themeUploadForegroundColor + : this.themeUploadForegroundColor + ), + themeUploadFileDropAreaIcon: this.getValue( + container + ? container.themeUploadFileDropAreaIcon + : this.themeUploadFileDropAreaIcon + ), + themeUploadShowFileDropArea: this.getValue( + container + ? container.themeUploadShowFileDropArea + : this.themeUploadShowFileDropArea + ), + themeUploadFileDropAreaColor: this.getValue( + container + ? container.themeUploadFileDropAreaColor + : this.themeUploadFileDropAreaColor + ), + themeUploadFileDropAreaMessage: this.getValue( + container + ? container.themeUploadFileDropAreaMessage + : this.themeUploadFileDropAreaMessage + ), + themeUploadFileDropAreaCssClass: this.getValue( + container + ? container.themeUploadFileDropAreaCssClass + : this.themeUploadFileDropAreaCssClass + ), + themeUploadFileDropAreaErrorColor: this.getValue( + container + ? container.themeUploadFileDropAreaErrorColor + : this.themeUploadFileDropAreaErrorColor + ), + themeUploadFileDropAreaAccentColor: this.getValue( + container + ? container.themeUploadFileDropAreaAccentColor + : this.themeUploadFileDropAreaAccentColor + ), + themeUploadFileDropAreaSuccessColor: this.getValue( + container + ? container.themeUploadFileDropAreaSuccessColor + : this.themeUploadFileDropAreaSuccessColor + ), + themeUploadFileDropAreaForegroundColor: this.getValue( + container + ? container.themeUploadFileDropAreaForegroundColor + : this.themeUploadFileDropAreaForegroundColor + ), + themeUploadEmptyForegroundMessageColor: this.getValue( + container + ? container.themeUploadEmptyForegroundMessageColor + : this.themeUploadEmptyForegroundMessageColor + ), + themeUploadFileDropAreaSupportsMultipleFiles: this.getValue( + container + ? container.themeUploadFileDropAreaSupportsMultipleFiles + : this.themeUploadFileDropAreaSupportsMultipleFiles + ), + + // + maxAllowedSize: this.getValue( + container ? container.maxAllowedSize : this.maxAllowedSize + ), + minAllowedFileSize: this.getValue( + container ? container.minAllowedFileSize : this.minAllowedFileSize + ), + maxAllowedFileSize: this.getValue( + container ? container.maxAllowedFileSize : this.maxAllowedFileSize + ), + maxAllowedFiles: this.getValue( + container ? container.maxAllowedFiles : this.maxAllowedFiles + ), + }).pipe( + map((data) => { + return { + superData, + data, + }; + }) + ); + }) + ) + .pipe( + map((info) => { + return { + // + ...info.superData, + + // + ...info.data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + themeChange: this.themeChange, + tabChange: this.tabChange, + + // + listEmptyActionProvider: container + ? container.listEmptyActionProvider + : this.listEmptyActionProvider, + + // + listEmptyRefresh: this.listEmptyRefresh, + + // + listActionBarActionFired: this.listActionBarActionFired, + listSearchBarOnEvent: this.listSearchBarOnEvent, + listSearchBarQueryChange: this.listSearchBarQueryChange, + }; + }) + ); + } + //#endregion +} + +function XStandardType() { + throw new Error('Function not implemented.'); +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-thumbnail\x-thumbnail.component.html + +
+ + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-thumbnail\x-thumbnail.component.scss + +:host { + --thumbnail-size: 32px; +} + +x-icon { + --size: var(--thumbnail-size, 32px); + color: inherit; +} + +.x-item, +.x-wrapper, +ion-thumbnail { + // + width: var(--thumbnail-size, 32px); + min-width: var(--thumbnail-size, 32px); + + // + height: var(--thumbnail-size, 32px); + min-height: var(--thumbnail-size, 32px); + + // + color: inherit; +} + +ion-thumbnail.circular { + --border-radius: 50%; +} + +.x-thumbnail { + // + display: inline-block; + position: relative; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-thumbnail\x-thumbnail.component.ts + +import { + nameof, + isString, + XFileType, + getFileType, + XGeneralThumbnails, + getGeneralThumbnail, +} from 'x-framework-core'; +import { + XThumbnailType, + XThumbnailAction, + XThumbnailInputType, + XThumbnailActionModel, + XThumbnailContainerComponent, +} from './x-thumbnail.typings'; +import { Subject, Subscription } from 'rxjs'; +import { XIconNames } from '../x-icon/x-icon.typings'; +import { map, filter, concatMap } from 'rxjs/operators'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-thumbnail', + templateUrl: './x-thumbnail.component.html', + styleUrls: ['./x-thumbnail.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XThumbnailComponent extends XThumbnailContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + private PREPARED_IDENTIFIER: XThumbnailInputType; + + get preparedIdentifier() { + return this.PREPARED_IDENTIFIER; + } + //#endregion + + // + //#region LifeCycles ... + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + + // + //#region Identifier Changed ... + const isIdentifierChanged = changeKeys.includes( + nameof('identifier') + ); + if (isIdentifierChanged) { + await this.prepareIdentifier(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region Register Handlers ... + //#endregion + + // + //#region UI Providers ... + // + //#region Type Providers ... + isTypeGeneral() { + return this.getValue(this.type).pipe( + map( + (type) => + type === XThumbnailType.GENERAL || type !== XThumbnailType.CUSTOM + ) + ); + } + + isTypeCustom() { + return this.getValue(this.type).pipe( + map((type) => type === XThumbnailType.CUSTOM) + ); + } + //#endregion + + isUrlIdentifier() { + // + // && isValidURL(identifier as string) + return this.getIdentifier().pipe(map((identifier) => isString(identifier))); + } + + isImageTypeIdentifier() { + return this.isUrlIdentifier().pipe( + filter((isUrl) => !isUrl), + concatMap(() => this.getIdentifier()), + map((identifier) => { + // + const type = getFileType(identifier); + return [ + XFileType.CoverImage, + XFileType.Image, + XFileType.ProfileImage, + ].includes(type); + }) + ); + } + + getGThumbnail(identifier: XThumbnailInputType) { + return this.getValue(getGeneralThumbnail(identifier)).pipe( + map((type) => { + switch (type) { + // + case XGeneralThumbnails.IMAGE: + return XIconNames.image; + + // + case XGeneralThumbnails.AUDIO: + return XIconNames.audio; + + // + case XGeneralThumbnails.VIDEO: + return XIconNames.video; + + // + case XGeneralThumbnails.DOCUMENT: + default: + return XIconNames.document; + } + }) + ); + } + //#endregion + + // + //#region UI Handlers ... + async handleClicked() { + // + if (this.uiDisabled || this.loading) { + return; + } + + // + const isClickable = await this.getValueAsync(this.clickable); + if (isClickable) { + this.clicked.emit(); + } + } + //#endregion + + // + //#region Actions ... + //#endregion + + // + //#region Private ... + // + //#region ActionProvider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + // Handle Action Provider ... + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XThumbnailAction.Refresh: + this.detectChanges(); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + private getIdentifier() { + return this.getValue(this.identifier); + } + + private async prepareIdentifier() { + // + const identifier = await this.getValueAsync(this.identifier); + this.PREPARED_IDENTIFIER = identifier; + + // + this.detectChanges(); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-thumbnail\x-thumbnail.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XIconModule } from '../x-icon/x-icon.module'; +import { XThumbnailComponent } from './x-thumbnail.component'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XThumbnailComponent], + imports: [CommonModule, XIconModule, XBaseComponentModule], + exports: [XIconModule, XBaseComponentModule, XThumbnailComponent], +}) +export class XThumbnailModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-thumbnail\x-thumbnail.typings.ts + +import { + Input, + Output, + Inject, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { map } from 'rxjs/operators'; +import { + XBaseContainer, + IXBaseContainer, + XBaseContainerComponent, +} from '../classes/i-x-base.container'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { XFileBlob, XOneOrManyType, XStandardType } from 'x-framework-core'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export enum XThumbnailType { + GENERAL = 'general', + CUSTOM = 'custom', +} + +export type XThumbnailTypeIdentifier = XThumbnailType | string; + +export type XThumbnailInputType = File | Blob | XFileBlob | string; + +export enum XThumbnailAction { + Refresh = 'refresh', +} + +export type XThumbnailActionIdentifier = XThumbnailAction | string; + +export interface XThumbnailActionModel + extends XBaseActionModel {} + +// +//#region Container ... +export interface IXThumbnailContainer extends IXBaseContainer { + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + circular: XStandardType; + clickable: XStandardType; + selectable: XStandardType; + type: XStandardType; + identifier: XStandardType; + + // + clicked: EventEmitter; +} + +@Injectable({ + providedIn: 'root', +}) +export class XThumbnailContainer + extends XBaseContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider: Subject = + new Subject(); + + readonly cssClass: XStandardType>; + + // + readonly circular: XStandardType = false; + + readonly clickable: XStandardType = false; + + readonly selectable: XStandardType = false; + + readonly type: XStandardType = XThumbnailType.GENERAL; + + readonly identifier: XStandardType; + + // + readonly clicked: EventEmitter = new EventEmitter(); + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XThumbnailContainerComponent + extends XBaseContainerComponent + implements IXThumbnailContainer +{ + // + //#region Props ... + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + @Input() + circular: XStandardType = this.propertyProvider.circular; + + @Input() + clickable: XStandardType = this.propertyProvider.clickable; + + @Input() + selectable: XStandardType = this.propertyProvider.selectable; + + @Input() + type: XStandardType = this.propertyProvider.type; + + @Input() + identifier: XStandardType = + this.propertyProvider.identifier; + + @Output() + clicked: EventEmitter = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XThumbnailContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXThumbnailContainer + ): Observable { + return forkJoin({ + cssClass: this.getArrayValue( + container ? container.cssClass : this.cssClass + ), + circular: this.getValue(container ? container.circular : this.circular), + clickable: this.getValue( + container ? container.clickable : this.clickable + ), + selectable: this.getValue( + container ? container.selectable : this.selectable + ), + type: this.getValue(container ? container.type : this.type), + identifier: this.getValue( + container ? container.identifier : this.identifier + ), + }).pipe( + map((data) => { + return { + // + ...data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + clicked: this.clicked, + }; + }) + ); + } + //#endregion +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-tree\x-tree.component.html + + + + + + + + +
+ +
+
+
+ + + + + + + + + + + + +
+ +
+
+ + + + + + + +
+ +
+
+
+
+
+ +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-tree\x-tree.component.scss + +:host(::ng-deep.mat-tree) { + background-color: unset !important; +} + +:host(::ng-deep.mat-tree-node) { + align-items: baseline !important; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-tree\x-tree.component.ts + +import { + XTreeNode, + XTreeAction, + XTreeFlatNode, + XTreeActionModel, + XTreeContainerComponent, +} from './x-tree.typings'; +import { + nameof, + toArray, + isNullOrUndefined, + isNullOrEmptyString, +} from 'x-framework-core'; +import { + MatTreeFlattener, + MatTreeFlatDataSource, +} from '@angular/material/tree'; +import { Subject, Subscription } from 'rxjs'; +import { FlatTreeControl } from '@angular/cdk/tree'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'x-tree', + templateUrl: './x-tree.component.html', + styleUrls: ['./x-tree.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class XTreeComponent extends XTreeContainerComponent { + // + //#region Props ... + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + // + treeControl: FlatTreeControl = null; + + // + treeFlattener: MatTreeFlattener< + XTreeNode, + XTreeFlatNode, + XTreeFlatNode + > = null; + + // + dataSource: MatTreeFlatDataSource< + XTreeNode, + XTreeFlatNode, + XTreeFlatNode + > = null; + //#endregion + + // + //#region LifeCycles ... + async onChange(changeKeys: string[]) { + super.onChange(changeKeys); + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('actionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + + // + //#region Nodes Changes ... + const isNodesChanged = changeKeys.includes(nameof('nodes')); + if (isNodesChanged) { + await this.prepareDataSource(); + } + //#endregion + } + + onDestroy() { + super.onDestroy(); + + // + this.unregisterActionProvider(); + } + + registerViewHandlers() { + super.registerViewHandlers(); + + // + this.registerActionProvider(); + } + //#endregion + + // + //#region Register Handlers ... + //#endregion + + // + //#region UI Providers ... + getNodeClassNames(node: XTreeFlatNode) { + // + const result = ['x-tree-node', `x-tree-node-level-${node.level}`]; + + // + if (node.expandable) { + result.push('x-tree-expandable-node'); + } + + // + if (!isNullOrEmptyString(node.id)) { + result.push(`x-tree-node-id-${node.id}`); + result.push(`x-tree-node-id-${node.id}-level-${node.level}`); + } + + // + return this.getArrayValue(result); + } + + nodeHasChild = (_: number, node: XTreeFlatNode) => node.expandable; + //#endregion + + // + //#region UI Handlers ... + handleTreeNodeToggle(id: string, isExpanded: boolean) { + // + if (isNullOrEmptyString(id)) { + return; + } + + // + this.onNodeToggle.emit({ + id, + isExpanded, + }); + } + //#endregion + + // + //#region Actions ... + //#endregion + + // + //#region Private ... + // + //#region ActionProvider ... + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.actionProvider) { + this.actionProvider = new Subject(); + } + + // + // Handle Action Provider ... + this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider + .asObservable() + .subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model.action) { + // + case XTreeAction.Refresh: + this.detectChanges(); + break; + + // + case XTreeAction.Expand: + // + if (this.treeControl) { + // + // Get Tree Node ... + if (model.payload) { + // + const id = model.payload.toString(); + if (!isNullOrEmptyString(id)) { + // + const node = this.treeControl.dataNodes.find( + (d) => d.id === id + ); + if (!isNullOrUndefined(node)) { + this.treeControl.expand(node); + } + } + } + } + break; + + // + case XTreeAction.Collapse: + // + if (this.treeControl) { + // + // Get Tree Node ... + if (model.payload) { + // + const id = model.payload.toString(); + if (!isNullOrEmptyString(id)) { + // + const node = this.treeControl.dataNodes.find( + (d) => d.id === id + ); + if (!isNullOrUndefined(node)) { + this.treeControl.collapse(node); + } + } + } + } + break; + + // + case XTreeAction.ExpandAll: + // + if (this.treeControl) { + this.treeControl.expandAll(); + } + break; + + // + case XTreeAction.CollapseAll: + // + if (this.treeControl) { + this.treeControl.collapseAll(); + } + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + //#endregion + + // + //#region Tree ... + async prepareDataSource() { + // + this.treeControl = null; + this.treeFlattener = null; + this.dataSource = null; + + // + const nodes = await this.getArrayValueAsync(this.nodes); + if (!this.hasChild(nodes)) { + this.detectChanges(); + return; + } + + // + this.treeControl = new FlatTreeControl( + (node) => node.level, + (node) => node.expandable + ); + + // + this.treeFlattener = new MatTreeFlattener( + (node: XTreeNode, level: number) => { + return { + id: node.id, + level: level, + data: node.data, + tooltip: node.tooltip, + expandable: this.hasChild(node.childs), + } as XTreeFlatNode; + }, + (node) => node.level, + (node) => node.expandable, + (node) => toArray(node.childs) + ); + + // + this.dataSource = new MatTreeFlatDataSource( + this.treeControl, + this.treeFlattener + ); + this.dataSource.data = nodes; + + // + this.detectChanges(); + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-tree\x-tree.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { XTreeComponent } from './x-tree.component'; +import { XCardModule } from '../x-card/x-card.module'; +import { XIconModule } from '../x-icon/x-icon.module'; +import { XBaseComponentModule } from '../modules/x-base-component.module'; + +@NgModule({ + declarations: [XTreeComponent], + imports: [CommonModule, XCardModule, XIconModule, XBaseComponentModule], + exports: [XIconModule, XCardModule, XBaseComponentModule, XTreeComponent], +}) +export class XTreeModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-tree\x-tree.typings.ts + +import { + Input, + Inject, + Output, + NgZone, + Component, + Renderer2, + ElementRef, + Injectable, + TemplateRef, + EventEmitter, + ChangeDetectorRef, +} from '@angular/core'; +import { + XStandardType, + XOneOrManyType, + XColorIdentifier, + XResourceIdentifier, + XColorWithBrightness, +} from 'x-framework-core'; +import { + XComponentCardContainer, + IXComponentCardContainer, + XComponentCardContainerComponent, +} from '../x-card/x-card.typings'; +import { concatMap, map } from 'rxjs/operators'; +import { Observable, Subject, forkJoin } from 'rxjs'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XManagerService } from 'x-framework-services'; +import { XBaseActionModel } from '../classes/x-base-action.model'; +import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkComponentsConfig } from '../config/x-framework-components.config'; + +export interface XTreeNodeToggle { + id: string; + isExpanded: boolean; +} + +export interface XTreeNode { + id: string; + data: T; + childs?: XOneOrManyType>; + tooltip?: XStandardType; +} + +export interface XTreeFlatNode { + id: string; + level: number; + data: XTreeNode; + expandable: boolean; + tooltip?: XStandardType; +} + +export enum XTreeAction { + // + Refresh = 'refresh', + + // + Expand = 'expand', + Collapse = 'collapse', + + // + ExpandAll = 'expand-all', + CollapseAll = 'collapse-all', +} + +export type XTreeActionIdentifier = XTreeAction | string; + +export interface XTreeActionModel + extends XBaseActionModel {} + +// +//#region Component Container Typings ... +export interface IXTreeContainer extends IXComponentCardContainer { + // + //#region Props ... + // + actionProvider: Subject; + cssClass: XStandardType>; + + // + isForegroundToggleColor: XStandardType; + toggleIconColor: XStandardType; + + // + nodes: XStandardType>>; + + // + nodeTemplate: TemplateRef; + + // + onNodeToggle: EventEmitter; + //#endregion +} + +@Injectable({ + providedIn: 'root', +}) +export class XTreeContainer + extends XComponentCardContainer + implements Readonly +{ + // + //#region Props ... + // + readonly actionProvider: Subject; + readonly cssClass: XStandardType>; + + // + readonly isForegroundToggleColor: XStandardType = true; + readonly toggleIconColor: XStandardType = + XColorWithBrightness.Dark; + + // + readonly nodes: XStandardType>>; + + // + readonly nodeTemplate: TemplateRef; + + // + readonly onNodeToggle = new EventEmitter(); + //#endregion +} + +@Injectable() +@Component({ + template: ``, +}) +export abstract class XTreeContainerComponent + extends XComponentCardContainerComponent + implements IXTreeContainer +{ + // + //#region Props ... + // + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + // + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + // + @Input() + toggleIconColor: XStandardType = + this.propertyProvider.toggleIconColor; + + // + @Input() + isForegroundToggleColor: XStandardType = + this.propertyProvider.isForegroundToggleColor; + + // + @Input() + nodes: XStandardType>> = + this.propertyProvider.nodes; + + // + @Input() + nodeTemplate: TemplateRef = this.propertyProvider.nodeTemplate; + + // + @Output() + onNodeToggle = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + public constructor( + @Inject(X_FRAMEWORK_COMPONENTS_CONFIG) + public config: XFrameworkComponentsConfig, + public managerService: XManagerService, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public zone: NgZone, + public changeDetector: ChangeDetectorRef, + protected propertyProvider: XTreeContainer + ) { + super( + config, + managerService, + element, + renderer, + ruler, + zone, + changeDetector, + propertyProvider + ); + } + //#endregion + + // + //#region Abstract ... + public extractProps( + container?: IXTreeContainer + ): Observable { + return super + .extractProps(container ? container : this) + .pipe( + concatMap((superData) => { + return forkJoin({ + // + cssClass: this.getArrayValue( + container ? container.cssClass : this.cssClass + ), + + // + toggleIconColor: this.getValue( + container ? container.toggleIconColor : this.toggleIconColor + ), + + // + isForegroundToggleColor: this.getValue( + container + ? container.isForegroundToggleColor + : this.isForegroundToggleColor + ), + + // + nodes: this.getArrayValue(container ? container.nodes : this.nodes), + }).pipe( + map((data) => { + return { + superData, + data, + }; + }) + ); + }) + ) + .pipe( + map((info) => { + return { + // + ...info.superData, + + // + ...info.data, + + // + actionProvider: container + ? container.actionProvider + : this.actionProvider, + + // + nodeTemplate: container + ? container.nodeTemplate + : this.nodeTemplate, + + // + onNodeToggle: container.onNodeToggle + ? container.onNodeToggle + : this.onNodeToggle, + }; + }) + ); + } + //#endregion +} +//#endregion diff --git a/Documents/GeneratedContext/xDashboard-xFrameworkCoreHolder-20260809_235514.txt b/Documents/GeneratedContext/xDashboard-xFrameworkCoreHolder-20260809_235514.txt new file mode 100644 index 0000000..02745b8 --- /dev/null +++ b/Documents/GeneratedContext/xDashboard-xFrameworkCoreHolder-20260809_235514.txt @@ -0,0 +1,12920 @@ + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\jalali-moment.d.ts + +declare function moment(inp?: moment.MomentInput, format?: moment.MomentFormatSpecification, strict?: boolean): moment.Moment; +declare function moment(inp?: moment.MomentInput, format?: moment.MomentFormatSpecification, language?: string, strict?: boolean): moment.Moment; + +declare namespace moment { + type RelativeTimeKey = 's' | 'm' | 'mm' | 'h' | 'hh' | 'd' | 'dd' | 'M' | 'MM' | 'y' | 'yy'; + type CalendarKey = 'sameDay' | 'nextDay' | 'lastDay' | 'nextWeek' | 'lastWeek' | 'sameElse' | string; + type LongDateFormatKey = 'LTS' | 'LT' | 'L' | 'LL' | 'LLL' | 'LLLL' | 'lts' | 'lt' | 'l' | 'll' | 'lll' | 'llll'; + + interface Locale { + [key: string]: any; + calendar(key?: CalendarKey, m?: Moment, now?: Moment): string; + + longDateFormat(key: LongDateFormatKey): string; + invalidDate(): string; + ordinal(n: number): string; + + preparse(inp: string): string; + postformat(inp: string): string; + relativeTime(n: number, withoutSuffix: boolean, + key: RelativeTimeKey, isFuture: boolean): string; + pastFuture(diff: number, absRelTime: string): string; + set(config: Object): void; + + months(): string[]; + jMonths(): string[]; + + months(m: Moment, format?: string): string; + jMonths(m: Moment): string; + monthsShort(): string[]; + jMonthsShort():string[]; + monthsShort(m: Moment, format?: string): string; + jMonthsParse(monthName: string): number; + monthsParse(monthName: string, format: string, strict: boolean): number; + monthsRegex(strict: boolean): RegExp; + monthsShortRegex(strict: boolean): RegExp; + + week(m: Moment): number; + firstDayOfYear(): number; + firstDayOfWeek(): number; + + weekdays(): string[]; + weekdays(m: Moment, format?: string): string; + weekdaysMin(): string[]; + weekdaysMin(m: Moment): string; + weekdaysShort(): string[]; + weekdaysShort(m: Moment): string; + weekdaysParse(weekdayName: string, format: string, strict: boolean): number; + weekdaysRegex(strict: boolean): RegExp; + weekdaysShortRegex(strict: boolean): RegExp; + weekdaysMinRegex(strict: boolean): RegExp; + + isPM(input: string): boolean; + meridiem(hour: number, minute: number, isLower: boolean): string; + } + + interface StandaloneFormatSpec { + format: string[]; + standalone: string[]; + isFormat?: RegExp; + } + + interface WeekSpec { + dow: number; + doy: number; + } + + type CalendarSpecVal = string | ((m?: MomentInput, now?: Moment) => string); + interface CalendarSpec { + sameDay?: CalendarSpecVal; + nextDay?: CalendarSpecVal; + lastDay?: CalendarSpecVal; + nextWeek?: CalendarSpecVal; + lastWeek?: CalendarSpecVal; + sameElse?: CalendarSpecVal; + + // any additional properties might be used with moment.calendarFormat + [x: string]: CalendarSpecVal | void; // undefined + } + + type RelativeTimeSpecVal = ( + string | + ((n: number, withoutSuffix: boolean, + key: RelativeTimeKey, isFuture: boolean) => string) + ); + type RelativeTimeFuturePastVal = string | ((relTime: string) => string); + + interface RelativeTimeSpec { + future: RelativeTimeFuturePastVal; + past: RelativeTimeFuturePastVal; + s: RelativeTimeSpecVal; + m: RelativeTimeSpecVal; + mm: RelativeTimeSpecVal; + h: RelativeTimeSpecVal; + hh: RelativeTimeSpecVal; + d: RelativeTimeSpecVal; + dd: RelativeTimeSpecVal; + M: RelativeTimeSpecVal; + MM: RelativeTimeSpecVal; + y: RelativeTimeSpecVal; + yy: RelativeTimeSpecVal; + } + + interface LongDateFormatSpec { + LTS: string; + LT: string; + L: string; + LL: string; + LLL: string; + LLLL: string; + + // lets forget for a sec that any upper/lower permutation will also work + lts?: string; + lt?: string; + l?: string; + ll?: string; + lll?: string; + llll?: string; + } + + type MonthWeekdayFn = (momentToFormat: Moment, format?: string) => string; + type WeekdaySimpleFn = (momentToFormat: Moment) => string; + + interface LocaleSpecification { + useGregorianParser?: boolean; + months?: string[] | StandaloneFormatSpec | MonthWeekdayFn; + monthsShort?: string[] | StandaloneFormatSpec | MonthWeekdayFn; + + weekdays?: string[] | StandaloneFormatSpec | MonthWeekdayFn; + weekdaysShort?: string[] | StandaloneFormatSpec | WeekdaySimpleFn; + weekdaysMin?: string[] | StandaloneFormatSpec | WeekdaySimpleFn; + + meridiemParse?: RegExp; + meridiem?: (hour: number, minute: number, isLower: boolean) => string; + + isPM?: (input: string) => boolean; + + longDateFormat?: LongDateFormatSpec; + calendar?: CalendarSpec; + relativeTime?: RelativeTimeSpec; + invalidDate?: string; + ordinal?: (n: number) => string; + ordinalParse?: RegExp; + + week?: WeekSpec; + + // Allow anything: in general any property that is passed as locale spec is + // put in the locale object so it can be used by locale functions + [x: string]: any; + } + + interface MomentObjectOutput { + years: number; + /* One digit */ + months: number; + /* Day of the month */ + date: number; + hours: number; + minutes: number; + seconds: number; + milliseconds: number; + } + + interface Duration { + humanize(withSuffix?: boolean): string; + + abs(): Duration; + + as(units: unitOfTime.Base): number; + get(units: unitOfTime.Base): number; + + milliseconds(): number; + asMilliseconds(): number; + + seconds(): number; + asSeconds(): number; + + minutes(): number; + asMinutes(): number; + + hours(): number; + asHours(): number; + + days(): number; + asDays(): number; + + weeks(): number; + asWeeks(): number; + + months(): number; + asMonths(): number; + + years(): number; + asYears(): number; + + add(inp?: DurationInputArg1, unit?: DurationInputArg2): Duration; + subtract(inp?: DurationInputArg1, unit?: DurationInputArg2): Duration; + + locale(): string; + locale(locale: LocaleSpecifier): Duration; + localeData(): Locale; + + toISOString(): string; + toJSON(): string; + + /** + * @deprecated since version 2.8.0 + */ + lang(locale: LocaleSpecifier): Moment; + /** + * @deprecated since version 2.8.0 + */ + lang(): Locale; + /** + * @deprecated + */ + toIsoString(): string; + } + + interface MomentRelativeTime { + future: any; + past: any; + s: any; + m: any; + mm: any; + h: any; + hh: any; + d: any; + dd: any; + M: any; + MM: any; + y: any; + yy: any; + } + + interface MomentLongDateFormat { + L: string; + LL: string; + LLL: string; + LLLL: string; + LT: string; + LTS: string; + + l?: string; + ll?: string; + lll?: string; + llll?: string; + lt?: string; + lts?: string; + } + + interface MomentParsingFlags { + empty: boolean; + unusedTokens: string[]; + unusedInput: string[]; + overflow: number; + charsLeftOver: number; + nullInput: boolean; + invalidMonth: string | void; // null + invalidFormat: boolean; + userInvalidated: boolean; + iso: boolean; + parsedDateParts: any[]; + meridiem: string | void; // null + } + + interface MomentParsingFlagsOpt { + empty?: boolean; + unusedTokens?: string[]; + unusedInput?: string[]; + overflow?: number; + charsLeftOver?: number; + nullInput?: boolean; + invalidMonth?: string; + invalidFormat?: boolean; + userInvalidated?: boolean; + iso?: boolean; + parsedDateParts?: any[]; + meridiem?: string; + } + + interface MomentBuiltinFormat { + __momentBuiltinFormatBrand: any; + } + + type MomentFormatSpecification = string | MomentBuiltinFormat | (string | MomentBuiltinFormat)[]; + + namespace unitOfTime { + type Base = ( + "year" | "years" | "y" | "jYear" | + "month" | "months" | "M" | "jMonth" | "jmonth" | + "week" | "weeks" | "w" | "jw" | + "day" | "days" | "d" | "jDay" | "jD" | + "hour" | "hours" | "h" | + "minute" | "minutes" | "m" | + "second" | "seconds" | "s" | + "millisecond" | "milliseconds" | "ms" + ); + + type _quarter = "quarter" | "quarters" | "Q"; + type _isoWeek = "isoWeek" | "isoWeeks" | "W"; + type _date = "date" | "dates" | "D"; + type DurationConstructor = Base | _quarter; + + type DurationAs = Base; + + type StartOf = Base | _quarter | _isoWeek | _date; + + type Diff = Base | _quarter; + + type MomentConstructor = Base | _date; + + type All = Base | _quarter | _isoWeek | _date | + "weekYear" | "weekYears" | "gg" | + "isoWeekYear" | "isoWeekYears" | "GG" | + "dayOfYear" | "dayOfYears" | "DDD" | + "weekday" | "weekdays" | "e" | + "isoWeekday" | "isoWeekdays" | "E"; + } + + interface MomentInputObject { + years?: number; + year?: number; + y?: number; + + months?: number; + month?: number; + M?: number; + + days?: number; + day?: number; + d?: number; + + dates?: number; + date?: number; + D?: number; + + hours?: number; + hour?: number; + h?: number; + + minutes?: number; + minute?: number; + m?: number; + + seconds?: number; + second?: number; + s?: number; + + milliseconds?: number; + millisecond?: number; + ms?: number; + } + + interface DurationInputObject extends MomentInputObject { + quarters?: number; + quarter?: number; + Q?: number; + + weeks?: number; + week?: number; + w?: number; + } + + interface MomentSetObject extends MomentInputObject { + weekYears?: number; + weekYear?: number; + gg?: number; + + isoWeekYears?: number; + isoWeekYear?: number; + GG?: number; + + quarters?: number; + quarter?: number; + Q?: number; + + weeks?: number; + week?: number; + w?: number; + + isoWeeks?: number; + isoWeek?: number; + W?: number; + + dayOfYears?: number; + dayOfYear?: number; + DDD?: number; + + weekdays?: number; + weekday?: number; + e?: number; + + isoWeekdays?: number; + isoWeekday?: number; + E?: number; + } + + interface FromTo { + from: MomentInput; + to: MomentInput; + } + + type MomentInput = Moment | Date | string | number | (number | string)[] | MomentInputObject | void; // null | undefined + type DurationInputArg1 = Duration | number | string | FromTo | DurationInputObject | void; // null | undefined + type DurationInputArg2 = unitOfTime.DurationConstructor; + type LocaleSpecifier = string | Moment | Duration | string[] | boolean; + + interface MomentCreationData { + input: MomentInput; + format?: MomentFormatSpecification; + locale: Locale; + isUTC: boolean; + strict?: boolean; + } + + interface Moment extends Object{ + format(format?: string): string; + + startOf(unitOfTime: unitOfTime.StartOf): Moment; + endOf(unitOfTime: unitOfTime.StartOf): Moment; + + add(amount?: DurationInputArg1, unit?: DurationInputArg2): Moment; + /** + * @deprecated reverse syntax + */ + add(unit: unitOfTime.DurationConstructor, amount: number|string): Moment; + + subtract(amount?: DurationInputArg1, unit?: DurationInputArg2): Moment; + /** + * @deprecated reverse syntax + */ + subtract(unit: unitOfTime.DurationConstructor, amount: number|string): Moment; + + calendar(time?: MomentInput, formats?: CalendarSpec): string; + + clone(): Moment; + + /** + * @return Unix timestamp in milliseconds + */ + valueOf(): number; + + // current date/time in local mode + local(keepLocalTime?: boolean): Moment; + isLocal(): boolean; + + // current date/time in UTC mode + utc(keepLocalTime?: boolean): Moment; + isUTC(): boolean; + /** + * @deprecated use isUTC + */ + isUtc(): boolean; + + parseZone(): Moment; + isValid(): boolean; + invalidAt(): number; + + hasAlignedHourOffset(other?: MomentInput): boolean; + + creationData(): MomentCreationData; + parsingFlags(): MomentParsingFlags; + + year(y: number): Moment; + year(): number; + jYear(y: number): Moment; + jYear():number; + /** + * @deprecated use year(y) + */ + years(y: number): Moment; + /** + * @deprecated use year() + */ + years(): number; + quarter(): number; + quarter(q: number): Moment; + quarters(): number; + quarters(q: number): Moment; + month(M: number|string): Moment; + jMonth(M: number|string): Moment; + month(): number; + jMonth(): number; + /** + * @deprecated use month(M) + */ + months(M: number|string): Moment; + /** + * @deprecated use month() + */ + months(): number; + day(d: number|string): Moment; + day(): number; + jDay(d: number|string): Moment; + jDay(): number; + days(d: number|string): Moment; + days(): number; + date(d: number): Moment; + jDate(d: number): Moment; + date(): number; + jDate(): number; + /** + * @deprecated use date(d) + */ + dates(d: number): Moment; + /** + * @deprecated use jDate(d) + */ + jDates(d: number): Moment; + /** + * @deprecated use date() + */ + dates(): number; + /** + * @deprecated use jDate() + */ + jDates():number; + + hour(h: number): Moment; + hour(): number; + hours(h: number): Moment; + hours(): number; + minute(m: number): Moment; + minute(): number; + minutes(m: number): Moment; + minutes(): number; + second(s: number): Moment; + second(): number; + seconds(s: number): Moment; + seconds(): number; + millisecond(ms: number): Moment; + millisecond(): number; + milliseconds(ms: number): Moment; + milliseconds(): number; + weekday(): number; + //TODO:jweekday + weekday(d: number): Moment; + isoWeekday(): number; + isoWeekday(d: number|string): Moment; + weekYear(): number; + jWeekYear(): number; + weekYear(d: number): Moment; + jWeekYear(d: number): Moment; + isoWeekYear(): number; + isoWeekYear(d: number): Moment; + week(): number; + jWeek(): number; + week(d: number): Moment; + jWeek(d: number): Moment; + weeks(): number; + jWeeks(): number;//TODO:check it + weeks(d: number): Moment; + isoWeek(): number; + isoWeek(d: number): Moment; + isoWeeks(): number; + isoWeeks(d: number): Moment; + weeksInYear(): number; + //TODO : jWeeksInYear + isoWeeksInYear(): number; + dayOfYear(): number; + jDayOfYear(): number; + dayOfYear(d: number): Moment; + jDayOfYear(d: number): Moment; + + from(inp: MomentInput, suffix?: boolean): string; + to(inp: MomentInput, suffix?: boolean): string; + fromNow(withoutSuffix?: boolean): string; + toNow(withoutPrefix?: boolean): string; + + diff(b: MomentInput, unitOfTime?: unitOfTime.Diff, precise?: boolean): number; + + toArray(): number[]; + toDate(): Date; + toISOString(): string; + inspect(): string; + toJSON(): string; + unix(): number; + + isLeapYear(): boolean; + jIsLeapYear(): boolean; + /** + * @deprecated in favor of utcOffset + */ + zone(): number; + zone(b: number|string): Moment; + utcOffset(): number; + utcOffset(b: number|string, keepLocalTime?: boolean): Moment; + isUtcOffset(): boolean; + daysInMonth(): number; + jDaysInMonth(): number; + isDST(): boolean; + + zoneAbbr(): string; + zoneName(): string; + + isBefore(inp?: MomentInput, granularity?: unitOfTime.StartOf): boolean; + isAfter(inp?: MomentInput, granularity?: unitOfTime.StartOf): boolean; + isSame(inp?: MomentInput, granularity?: unitOfTime.StartOf): boolean; + isSameOrAfter(inp?: MomentInput, granularity?: unitOfTime.StartOf): boolean; + isSameOrBefore(inp?: MomentInput, granularity?: unitOfTime.StartOf): boolean; + isBetween(a: MomentInput, b: MomentInput, granularity?: unitOfTime.StartOf, inclusivity?: "()" | "[)" | "(]" | "[]"): boolean; + + doAsGregorian(): Moment; + + /** + * @deprecated as of 2.8.0, use locale + */ + lang(language: LocaleSpecifier): Moment; + /** + * @deprecated as of 2.8.0, use locale + */ + lang(): Locale; + + locale(): string; + locale(locale: LocaleSpecifier): Moment; + + localeData(): Locale; + + /** + * @deprecated no reliable implementation + */ + isDSTShifted(): boolean; + + // NOTE(constructor): Same as moment constructor + /** + * @deprecated as of 2.7.0, use moment.min/max + */ + max(inp?: MomentInput, format?: MomentFormatSpecification, strict?: boolean): Moment; + /** + * @deprecated as of 2.7.0, use moment.min/max + */ + max(inp?: MomentInput, format?: MomentFormatSpecification, language?: string, strict?: boolean): Moment; + + // NOTE(constructor): Same as moment constructor + /** + * @deprecated as of 2.7.0, use moment.min/max + */ + min(inp?: MomentInput, format?: MomentFormatSpecification, strict?: boolean): Moment; + /** + * @deprecated as of 2.7.0, use moment.min/max + */ + min(inp?: MomentInput, format?: MomentFormatSpecification, language?: string, strict?: boolean): Moment; + + get(unit: unitOfTime.All): number; + set(unit: unitOfTime.All, value: number): Moment; + set(objectLiteral: MomentSetObject): Moment; + + toObject(): MomentObjectOutput; + } + + export var version: string; + export var fn: Moment; + + // NOTE(constructor): Same as moment constructor + export function utc(inp?: MomentInput, format?: MomentFormatSpecification, strict?: boolean): Moment; + export function utc(inp?: MomentInput, format?: MomentFormatSpecification, language?: string, strict?: boolean): Moment; + + export function unix(timestamp: number): Moment; + + export function invalid(flags?: MomentParsingFlagsOpt): Moment; + export function isMoment(m: any): m is Moment; + export function isDate(m: any): m is Date; + export function isDuration(d: any): d is Duration; + + /** + * @deprecated in 2.8.0 + */ + export function lang(language?: string): string; + /** + * @deprecated in 2.8.0 + */ + export function lang(language?: string, definition?: Locale): string; + + export function locale(language?: string): string; + export function locale(language?: string[]): string; + export function locale(language?: string, definition?: LocaleSpecification | void): string; // null | undefined + + export function localeData(key?: string | string[]): Locale; + + export function duration(inp?: DurationInputArg1, unit?: DurationInputArg2): Duration; + + // NOTE(constructor): Same as moment constructor + export function parseZone(inp?: MomentInput, format?: MomentFormatSpecification, strict?: boolean): Moment; + export function parseZone(inp?: MomentInput, format?: MomentFormatSpecification, language?: string, strict?: boolean): Moment; + + export function months(): string[]; + export function months(index: number): string; + export function months(format: string): string[]; + export function months(format: string, index: number): string; + export function monthsShort(): string[]; + export function monthsShort(index: number): string; + export function monthsShort(format: string): string[]; + export function monthsShort(format: string, index: number): string; + + export function jWeekOfYear(mom: Moment, firstDayOfWeek: number, firstDayOfWeekOfYear: number): MomentInput; + export function jDaysInMonth(jMear: number, jMonth: number): number; + export function jIsLeapYear(jYear: number): boolean; + export function useJalaliSystemPrimarily(): void; + export function useJalaliSystemSecondary(): void; + export var jConvert: any; + + export function from (date: string, locale: string, format?: string): Moment; + + export function weekdays(): string[]; + export function weekdays(index: number): string; + export function weekdays(format: string): string[]; + export function weekdays(format: string, index: number): string; + export function weekdays(localeSorted: boolean): string[]; + export function weekdays(localeSorted: boolean, index: number): string; + export function weekdays(localeSorted: boolean, format: string): string[]; + export function weekdays(localeSorted: boolean, format: string, index: number): string; + export function weekdaysShort(): string[]; + export function weekdaysShort(index: number): string; + export function weekdaysShort(format: string): string[]; + export function weekdaysShort(format: string, index: number): string; + export function weekdaysShort(localeSorted: boolean): string[]; + export function weekdaysShort(localeSorted: boolean, index: number): string; + export function weekdaysShort(localeSorted: boolean, format: string): string[]; + export function weekdaysShort(localeSorted: boolean, format: string, index: number): string; + export function weekdaysMin(): string[]; + export function weekdaysMin(index: number): string; + export function weekdaysMin(format: string): string[]; + export function weekdaysMin(format: string, index: number): string; + export function weekdaysMin(localeSorted: boolean): string[]; + export function weekdaysMin(localeSorted: boolean, index: number): string; + export function weekdaysMin(localeSorted: boolean, format: string): string[]; + export function weekdaysMin(localeSorted: boolean, format: string, index: number): string; + + export function min(...moments: MomentInput[]): Moment; + export function max(...moments: MomentInput[]): Moment; + + /** + * Returns unix time in milliseconds. Overwrite for profit. + */ + export function now(): number; + + export function defineLocale(language: string, localeSpec: LocaleSpecification | void): Locale; // null + export function updateLocale(language: string, localeSpec: LocaleSpecification | void): Locale; // null + + export function locales(): string[]; + + export function normalizeUnits(unit: unitOfTime.All): string; + export function relativeTimeThreshold(threshold: string): number | boolean; + export function relativeTimeThreshold(threshold: string, limit: number): boolean; + export function relativeTimeRounding(fn: (num: number) => number): boolean; + export function relativeTimeRounding(): (num: number) => number; + export function calendarFormat(m: Moment, now: Moment): string; + + /** + * Constant used to enable explicit ISO_8601 format parsing. + */ + export var ISO_8601: MomentBuiltinFormat; + + export var defaultFormat: string; + export var defaultFormatUtc: string; +} + +export = moment; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\public-api.ts + +/* + * Public API Surface of x-framework-core + */ + +//#region Classes ... +export * from './lib/classes/x-loggable'; +export * from './lib/classes/x-base.service'; +export * from './lib/classes/x-renderer.class'; +export * from './lib/classes/x-persian-date-adapter'; +//#endregion + +// +//#region Constants ... +export * from './lib/constants/x-file.enum'; +export * from './lib/constants/x.constants'; +export * from './lib/constants/x-crud.enum'; +export * from './lib/constants/x-param.enum'; +export * from './lib/constants/x-header.enum'; +export * from './lib/constants/x-border.enum'; +export * from './lib/constants/x-setting.resources'; +export * from './lib/constants/x-exception.resources'; +export * from './lib/constants/x-translation.resources'; +//#endregion + +// +//#region Guards ... +export * from './lib/guards/x-can-deactivate.guard'; +//#endregion + +// +//#region Models ... +export * from './lib/models/x-base-dto'; +export * from './lib/models/x-grouped-data.dto'; +export * from './lib/models/x-base.indexed.dto'; +export { + XBehaviourEvent, + XBehaviourEventType, +} from './lib/directives/x-scroll-behavior.directive'; +//#endregion + +// +//#region Tools ... +export * from './lib/tools/x-task.tools'; +export * from './lib/tools/x-page.tools'; +export * from './lib/tools/x-uuid-tools'; +export * from './lib/tools/x-file.tools'; +export * from './lib/tools/x-array.tools'; +export * from './lib/tools/x-color.tools'; +export * from './lib/tools/x-number.tools'; +export * from './lib/tools/x-object.tools'; +export * from './lib/tools/x-string.tools'; +export * from './lib/tools/x-to-form-data'; +export * from './lib/tools/x-formatter.tools'; +export * from './lib/tools/x-date-time.tools'; +export * from './lib/tools/x-exception.tools'; +export * from './lib/tools/x-observable.tools'; +export * from './lib/tools/x-type-detect.tools'; +export * from './lib/tools/x-type-convert.tools'; +export * from './lib/tools/x-type-defaults.tools'; +//#endregion + +// +//#region Common Typings ... +export * from './lib/typings/x.typings'; +export * from './lib/typings/x-page.typings'; +export * from './lib/typings/x-query.typings'; +export * from './lib/typings/x-color.typings'; +export * from './lib/typings/x-storage.typings'; +export * from './lib/typings/x-setting.typings'; +export * from './lib/typings/x-manager.typings'; +export * from './lib/typings/x-date-time.typings'; +export * from './lib/typings/x-exception.typings'; +export * from './lib/typings/x-translation.typings'; +export * from './lib/typings/x-task-manager.typings'; +export * from './lib/typings/x-notification.typings'; +export * from './lib/typings/x-theme-manager.typings'; +export * from './lib/typings/x-base-component.typings'; +//#endregion + +// +//#region Export Directives ... +export * from './lib/directives/x-var.directive'; +export * from './lib/directives/x-color.directive'; +export * from './lib/directives/x-focus.directive'; +export * from './lib/directives/x-render.directive'; +export * from './lib/directives/x-slider.directive'; +export * from './lib/directives/x-on-enter.directive'; +export * from './lib/directives/x-formatter.directive'; +export * from './lib/directives/x-droppable.directive'; +export * from './lib/directives/x-clipboard.directive'; +export * from './lib/directives/x-on-escape.directive'; +export * from './lib/directives/x-file-reader.directive'; +export * from './lib/directives/x-mouse-handler.directive'; +export * from './lib/directives/x-focus-handler.directive'; +export * from './lib/directives/x-scroll-behavior.directive'; +export * from './lib/directives/x-on-outside-click.directive'; +export * from './lib/directives/x-stop-click-propagation.directive'; +//#endregion + +// +//#region Export Validators ... +export * from './lib/validators/x-general-validators'; +export * from './lib/validators/x-valid-url.directive'; +export * from './lib/validators/x-valid-email.directive'; +export * from './lib/validators/x-value-match.directive'; +export * from './lib/validators/x-valid-phone-number.directive'; +//#endregion + +// +//#region Export Pipse ... +export * from './lib/pipes/x-as.pipe'; +export * from './lib/pipes/x-log.pipe'; +export * from './lib/pipes/x-formatter.pipe'; +export * from './lib/pipes/x-safe-html.pipe'; +//#endregion + +// +//#region Tokens ... +export * from './lib/tokens/x-injectable-tokens'; +//#endregion + +// +//#region Export Config ... +export * from './lib/config/x-framework-core.config'; +//#endregion + +// +//#region Module ... +export * from './lib/modules/x-directives.module'; +export * from './lib/x-framework-core.module'; +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\test.ts + +// This file is required by karma.conf.js and loads recursively all the .spec and framework files + +import 'zone.js'; +import 'zone.js/testing'; +import { getTestBed } from '@angular/core/testing'; +import { + BrowserDynamicTestingModule, + platformBrowserDynamicTesting +} from '@angular/platform-browser-dynamic/testing'; + +declare const require: { + context(path: string, deep?: boolean, filter?: RegExp): { + keys(): string[]; + (id: string): T; + }; +}; + +// First, initialize the Angular testing environment. +getTestBed().initTestEnvironment( + BrowserDynamicTestingModule, + platformBrowserDynamicTesting() +); +// Then we find all the tests. +const context = require.context('./', true, /\.spec\.ts$/); +// And load the modules. +context.keys().map(context); + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\x-framework-core.module.ts + +import { NgModule } from '@angular/core'; +import { IonicModule } from '@ionic/angular' +import { CommonModule } from '@angular/common'; +import { XDirectivesModule } from './modules/x-directives.module'; +import { HAMMER_GESTURE_CONFIG } from '@angular/platform-browser'; +import { XHammerGestureConfig } from './classes/x-hammer-gesture-config'; + +@NgModule({ + declarations: [], + providers: [ + // + // Gesture Config ... + { + provide: HAMMER_GESTURE_CONFIG, + useClass: XHammerGestureConfig, + }, + ], + imports: [CommonModule, IonicModule, XDirectivesModule], + exports: [CommonModule, IonicModule, XDirectivesModule], +}) +export class XFrameworkCoreModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\classes\x-base.service.ts + +import { XLoggable } from './x-loggable'; + +export abstract class XBaseService extends XLoggable { +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\classes\x-hammer-gesture-config.ts + +import { Injectable } from '@angular/core'; +import { HammerGestureConfig } from '@angular/platform-browser'; + +@Injectable() +export class XHammerGestureConfig extends HammerGestureConfig { + public overrides = { + pinch: { + enable: false + }, + rotate: { + enable: false + }, + swipe: { + direction: 6 + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\classes\x-loggable.ts + +import { + isNullOrUndefined, + isNullOrEmptyString +} from '../tools/x-type-detect.tools'; +import { Inject } from '@angular/core'; +import { X_FRAMEWORK_CORE_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkCoreConfig } from '../config/x-framework-core.config'; + +export enum XLogType { + Warn, + Error, + Debug, + Info +} + +export interface GetLogTag { + getLogTag(): string; +} + +export abstract class XLoggable implements GetLogTag { + // + // Constructor ... + constructor( + @Inject(X_FRAMEWORK_CORE_CONFIG) public config: XFrameworkCoreConfig + ) {} + + // + // GetLogTag Implementation ... + /** + * retrieve logging tag ... + */ + getLogTag(): string { + // + if (this.constructor) { + return `${this.config.baseLogTag}_[${this.constructor.name}]`; + } + + // + return `${this.config.baseLogTag}_[NON-Constructor]`; + } + + protected log(type: XLogType, message?: any, ...args: any[]) { + // + if (!this.config.enableLogging) { + return; + } + + // + let logMsg = !isNullOrEmptyString(message) + ? this.getLogTag() + ' => ' + message + : this.getLogTag(); + + // + const logArgs = args; + args.push(new Date()); + + // + switch (type) { + case XLogType.Debug: + // + logMsg = 'D: ' + logMsg; + + // + isNullOrUndefined(logArgs) + ? // tslint:disable-next-line:no-console + console.debug(logMsg) + : // tslint:disable-next-line:no-console + console.debug(logMsg, logArgs); + break; + + case XLogType.Error: + // + logMsg = 'E: ' + logMsg; + + // + isNullOrUndefined(logArgs) + ? console.error(logMsg) + : console.error(logMsg, logArgs); + break; + + case XLogType.Warn: + // + logMsg = 'W: ' + logMsg; + + // + isNullOrUndefined(logArgs) + ? console.warn(logMsg) + : console.warn(logMsg, logArgs); + break; + + case XLogType.Info: + // + logMsg = 'I: ' + logMsg; + + // + isNullOrUndefined(logArgs) + ? // tslint:disable-next-line:no-console + console.info(logMsg) + : // tslint:disable-next-line:no-console + console.info(logMsg, logArgs); + break; + } + } + + protected logDebug(message?: any, ...args: any) { + this.log(XLogType.Debug, message, args); + } + + protected logError(message?: any, ...args: any) { + this.log(XLogType.Error, message, args); + } + + protected logWarn(message?: any, ...args: any) { + this.log(XLogType.Warn, message, args); + } + + protected logInfo(message?: any, ...args: any) { + this.log(XLogType.Info, message, args); + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\classes\x-persian-date-adapter.ts + +import * as moment_ from 'jalali-moment'; +const moment = moment_; +import { + MomentDateAdapter, + MAT_MOMENT_DATE_FORMATS, +} from '@angular/material-moment-adapter'; +import { XParam } from '../constants/x-param.enum'; +import { DateAdapter } from '@angular/material/core'; +import { applyLocale, XLocale } from '../typings/x-translation.typings'; +import { toPersianNumbers } from '../tools/x-string.tools'; +import { localeData, Moment, isMoment, invalid } from 'jalali-moment'; +import { isString } from '../tools/x-type-detect.tools'; + +export const PERSIAN_DATE_FORMATS = { + parse: { + dateInput: 'jYYYY/jMM/jDD', + }, + display: { + dateInput: 'jYYYY/jMM/jDD', + monthYearLabel: 'jYYYY jMMMM', + dateA11yLabel: 'jYYYY/jMM/jDD', + monthYearA11yLabel: 'jYYYY jMMMM', + }, +}; + +export class XPersianDateAdapter extends DateAdapter { + // + private mLocale: XLocale = 'fa-IR'; + + constructor() { + super(); + super.setLocale(this.mLocale); + } + + getYear(date: Moment): number { + return this.clone(date).jYear(); + } + + getMonth(date: Moment): number { + return this.clone(date).jMonth(); + } + + getDate(date: Moment): number { + return this.clone(date).jDate(); + } + + getDayOfWeek(date: Moment): number { + return this.clone(date).day(); + } + + getMonthNames(style: 'long' | 'short' | 'narrow'): string[] { + // + const jLocalData = localeData(this.mLocale); + + // + switch (style) { + case 'long': + case 'short': + return jLocalData._jMonths.slice(0); + case 'narrow': + return jLocalData._jMonthsShort.slice(0); + } + } + + getDateNames(): string[] { + const valuesArray = Array(31); + for (let i = 0; i < 31; i++) { + valuesArray[i] = toPersianNumbers(String(i + 1)); + } + return valuesArray; + } + + getDayOfWeekNames(style: 'long' | 'short' | 'narrow'): string[] { + switch (style) { + case 'long': + return localeData(this.mLocale).weekdays().slice(0); + case 'short': + return localeData(this.mLocale).weekdaysShort().slice(0); + case 'narrow': + return ['ی', 'د', 'س', 'چ', 'پ', 'ج', 'ش']; + } + } + + getYearName(date: Moment): string { + return toPersianNumbers(this.clone(date).jYear().toString()); + } + + getFirstDayOfWeek(): number { + return localeData(this.mLocale).firstDayOfWeek(); + } + + getNumDaysInMonth(date: Moment): number { + return this.clone(date).jDaysInMonth(); + } + + clone(date: Moment | Date | string | any): Moment { + // + const isDateMoment = moment.isMoment(date); + if (!isDateMoment) { + date = moment(new Date(date)); + } + + // + let result: Moment; + + // + try { + result = date.clone().locale(this.mLocale); + } catch { + // + result = this.today().clone().locale(this.mLocale); + } + + // + return result; + } + + createDate(year: number, month: number, date: number): Moment { + if (month < 0 || month > 11) { + throw Error( + `Invalid month index "${month}". Month index has to be between 0 and 11.` + ); + } + if (date < 1) { + throw Error(`Invalid date "${date}". Date has to be greater than 0.`); + } + const result = moment() + .jYear(year) + .jMonth(month) + .jDate(date) + .hours(0) + .minutes(0) + .seconds(0) + .milliseconds(0) + .locale(this.mLocale); + + if (this.getMonth(result) !== month) { + throw Error(`Invalid date ${date} for month with index ${month}.`); + } + if (!result.isValid()) { + throw Error(`Invalid date "${date}" for month with index "${month}".`); + } + return result; + } + + today(): Moment { + return moment().locale(this.mLocale); + } + + parse(value: any, parseFormat: string | string[]): Moment | null { + if (value && typeof value === 'string') { + return moment(value, parseFormat, this.mLocale); + } + return value ? moment(value).locale(this.mLocale) : null; + } + + format(date: Moment | Date | string | any, displayFormat: string): string { + // + if (isString(date)) { + date = applyLocale(date.toString(), 'en-US'); + } + + // + date = this.clone(date); + + // + if (!this.isValid(date)) { + throw Error('JalaliMomentDateAdapter: Cannot format invalid date.'); + } + + // + try { + return applyLocale(date.format(displayFormat), this.mLocale); + } catch { + return applyLocale(this.today().format(displayFormat), this.mLocale); + } + } + + addCalendarYears(date: Moment, years: number): Moment { + return this.clone(date).add(years, 'jYear'); + } + + addCalendarMonths(date: Moment, months: number): Moment { + return this.clone(date).add(months, 'jmonth'); + } + + addCalendarDays(date: Moment, days: number): Moment { + return this.clone(date).add(days, 'jDay'); + } + + toIso8601(date: Moment): string { + return this.clone(date).format(); + } + + isDateInstance(obj: any): boolean { + return isMoment(obj); + } + + isValid(date: Moment): boolean { + return this.clone(date).isValid(); + } + + invalid(): Moment { + return invalid(); + } + + deserialize(value: any): Moment | null { + let date; + if (value instanceof Date) { + date = moment(value); + } + if (typeof value === 'string') { + if (!value) { + return null; + } + date = moment(value).locale(this.mLocale); + } + if (date && this.isValid(date)) { + return date; + } + return super.deserialize(value); + } +} + +export const DateAdapterFactory = (matDateLocale: XLocale) => { + // + if (matDateLocale === 'fa-IR') { + return new XPersianDateAdapter(); + } + + // + return new MomentDateAdapter(matDateLocale); +}; + +export const MatDateFormatFactory = (matDateLocale: XLocale) => { + // + if (matDateLocale === 'fa-IR') { + return PERSIAN_DATE_FORMATS; + } + + // + return MAT_MOMENT_DATE_FORMATS; +}; + +export const LocaleFactory = () => { + return window[XParam.Language]; +}; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\classes\x-renderer.class.ts + +import { + NgZone, + Inject, + Renderer2, + ElementRef, + RendererStyleFlags2, +} from '@angular/core'; +import { Observable } from 'rxjs'; +import { + isFunction, + isNullOrUndefined, + isNullOrEmptyString, +} from '../tools/x-type-detect.tools'; +import { XLoggable } from './x-loggable'; +import { hasChild } from '../tools/x-array.tools'; +import { ViewportRuler } from '@angular/cdk/overlay'; +import { XRef, XStandardType } from '../typings/x.typings'; +import { XCssProperties } from '../typings/x-setting.typings'; +import { toObservable, toPromise } from '../tools/x-observable.tools'; +import { X_FRAMEWORK_CORE_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkCoreConfig } from '../config/x-framework-core.config'; +import { waitForXElement } from '../tools/x-object.tools'; + +export class XRenderer extends XLoggable { + // + //#endregion Props ... + private PREFIX = '--'; + //#endregion + + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_CORE_CONFIG) public config: XFrameworkCoreConfig, + public element: ElementRef, + public renderer: Renderer2, + public ruler: ViewportRuler, + public ngZone: NgZone + ) { + super(config); + } + //#endregion + + // + //#region Document ... + getElementById(id: string) { + return document.getElementById(id); + } + + getElementsByClassName(className: string) { + return document.getElementsByClassName(className); + } + + getElementsByName(elementName: string) { + return document.getElementsByName(elementName); + } + + getElementsByTagName( + tagName: K | string + ) { + return document.getElementsByTagName(tagName); + } + + getRootNode() { + return document.getRootNode(); + } + + waitForElement(selector: string) { + return waitForXElement(selector); + } + + querySelector(selector: K | string) { + return document.querySelector(selector); + } + + querySelectorAll( + selector: K | string + ) { + return document.querySelectorAll(selector); + } + //#endregion + + // + //#region Renderer ... + /** + * convert an element to HTML Element + * for accessing props ... + * + * @param el elementRef of the selected element + */ + getElement(el?: ElementRef | HTMLElement) { + // + if (!el) { + el = this.element; + } + + // + return el instanceof ElementRef + ? ((el as ElementRef).nativeElement as HTMLElement) + : (el as HTMLElement); + } + + /** + * adding classes to an element ... + * + * @param clz the classes names + * @param el the element + */ + addClass(clz: string, el?: ElementRef | HTMLElement) { + this.renderer.addClass(this.getElement(el), clz); + } + + /** + * remove classes from an element ... + * + * @param clz the classes names + * @param el the element + */ + removeClass(clz: string, el?: ElementRef | HTMLElement) { + this.renderer.removeClass(this.getElement(el), clz); + } + + /** + * create an element ... + * + * @param name create an element + * @param nameSpace adding a namespace + */ + createElement(name: string, nameSpace?: string) { + return this.renderer.createElement(name, nameSpace); + } + + /** + * create a Text Element ... + * + * @param value a text value + */ + createText(value: string) { + return this.renderer.createText(value); + } + + /** + * adding a child element to another element ... + * + * @param parent the parent element + * @param newChild the child element + */ + appendChild(parent: any, newChild: any) { + this.renderer.appendChild(parent, newChild); + } + + /** + * adding a child element to another element ... + * + * @param child child element + * @param el parent element + */ + appendChildToElement(child: any, el?: ElementRef | HTMLElement) { + this.renderer.appendChild(this.getElement(el), child); + } + + /** + * set an attribute of an element ... + * + * @param name attribute name + * @param value attribute value + * @param el destination element + * @param namespace name space name + */ + setAttribute( + name: string, + value: string, + el?: ElementRef | HTMLElement, + namespace?: string | null + ) { + this.renderer.setAttribute(this.getElement(el), name, value, namespace); + } + + /** + * remove an attribute from an element ... + * + * @param name attribute name + * @param el destination element + * @param namespace name space name + */ + removeAttribute( + name: string, + el?: ElementRef | HTMLElement, + namespace?: string | null + ) { + this.renderer.removeAttribute(this.getElement(el), name, namespace); + } + + /** + * set style of an element ... + * + * @param style the style name + * @param value style value + * @param el destination element + * @param flags flags + */ + setStyle( + style: string, + value: any, + el?: ElementRef | HTMLElement, + flags?: RendererStyleFlags2 + ) { + this.renderer.setStyle(this.getElement(el), style, value, flags); + } + + /** + * remove an style from an element + * + * @param style style name + * @param el destination element + * @param flags flags + */ + removeStyle( + style: string, + el?: ElementRef | HTMLElement, + flags?: RendererStyleFlags2 + ) { + this.renderer.removeStyle(this.getElement(el), style, flags); + } + + /** + * set property to an element ... + * + * @param name property name + * @param value property value + * @param el destination element + */ + setProperty(name: string, value: any, el?: ElementRef | HTMLElement) { + this.renderer.setProperty(this.getElement(el), name, value); + } + + /** + * set value of specific node + * + * @param node specific element + * @param value value + */ + setValue(node: any, value: string) { + this.renderer.setValue(node, value); + } + + /** + * recieve computed style of an element ... + * + * @param el destination element + */ + getComputedStyle(el?: ElementRef | HTMLElement) { + return getComputedStyle(this.getElement(el)); + } + + /** + * prepare a css var name + * + * @param key css variable name + */ + prepareCssKey(key: XCssProperties) { + return `${this.PREFIX}${key}`; + } + + /** + * remove css key prefix ... + * + * @param key a prepared css var ... + */ + removeCssKeyPrefix(key: XCssProperties) { + return key.replace(this.PREFIX, ''); + } + + /** + * read specific css var value of an element + * @param name css variable name + * @param element destination element + */ + getCssVar(name: XCssProperties, el?: ElementRef | HTMLElement) { + // + return this.getComputedStyle(el).getPropertyValue(this.prepareCssKey(name)); + } + + /** + * set css var into element ... + * + * @param name css var name + * @param value css var value + * @param elements destination element of array of elements + */ + setCssVar( + name: XCssProperties, + value: string, + el?: ElementRef | HTMLElement + ) { + // + if (isNullOrEmptyString(name)) { + return; + } + + // + this.getElement(el).style.setProperty(this.prepareCssKey(name), value); + } + + /** + * load script and add to component dynamically ... + * + * @param path path to js file + */ + addScript(path: string) { + // + const s = this.renderer.createElement('script'); + s.type = 'text/javascript'; + s.src = path; + s.text = ``; + this.renderer.appendChild(document.body, s); + } + + /** + * add script content ... + * + * @param script the script block + */ + addScriptContent(script: string) { + // + const s = this.renderer.createElement('script'); + s.type = 'text/javascript'; + s.text = `${script}`; + this.renderer.appendChild(document.body, s); + } + //#endregion + + // + //#region Providers ... + public getElementBoundaryRect(el?: ElementRef | HTMLElement) { + return this.getElement(el).getBoundingClientRect(); + } + + public getElementBoundaryWidth(el?: ElementRef | HTMLElement) { + return this.getElementBoundaryRect().width; + } + + public getElementBoundaryHeight(el?: ElementRef | HTMLElement) { + return this.getElementBoundaryRect().height; + } + + public getElementClientWidth(el?: ElementRef | HTMLElement) { + return this.getElement(el).clientWidth; + } + + public getElementClientHeight(el?: ElementRef | HTMLElement) { + return this.getElement(el).clientHeight; + } + + public getElementOffsetWidth(el?: ElementRef | HTMLElement) { + return this.getElement(el).offsetWidth; + } + + public getElementOffsetHeight(el?: ElementRef | HTMLElement) { + return this.getElement(el).offsetHeight; + } + //#endregion + + // + //#region Zone Actions ... + run(fn: (...args: any[]) => T, applyThis?: any, applyArgs?: any[]): T { + return this.ngZone.run(fn, applyThis, applyArgs); + } + + runTask( + fn: (...args: any[]) => T, + applyThis?: any, + applyArgs?: any[], + name?: string + ): T { + return this.ngZone.runTask(fn, applyThis, applyArgs, name); + } + + runGuarded( + fn: (...args: any[]) => T, + applyThis?: any, + applyArgs?: any[] + ): T { + return this.ngZone.runGuarded(fn, applyThis, applyArgs); + } + + runOutsideAngular(fn: (...args: any[]) => T): T { + return this.ngZone.runOutsideAngular(fn); + } + //#endregion + + // + //#region Action Runnings ... + async runTimedOut( + fn: (...args: any[]) => T, + delay?: XStandardType + ) { + // + let desiredDelay = 0; + if (!isNullOrUndefined(delay)) { + desiredDelay = await toPromise(toObservable(delay)); + } + + // + if (!fn) { + return null; + } + + // + return await new Promise((res, rej) => { + // + setTimeout(() => { + // + try { + // + const result = fn(); + res(result); + } catch { + // + rej(null); + } + }, desiredDelay); + }); + } + //#endregion + + // + //#region Private ... + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\config\x-framework-core.config.ts + +import { XCountryCode } from '../typings/x.typings'; +import { XNotificationSound } from '../typings/x-notification.typings'; +import { XLanguageDescriptor, XLocale } from '../typings/x-translation.typings'; + +/** + * Application Configuration Structure ... + */ +export interface XFrameworkCoreConfig { + // + // Environment Identifier ... + isProd: boolean; + + // + // App Info ... + appInstanceName: string; + appNameResource: string; + appCompanyResource: string; + appVersion: string; + + // + poweredValue: string; + + // + // Logging Configuration ... + enableLogging: boolean; + baseLogTag: string; + + // + // Language Configuration ... + availableLanguages: XLanguageDescriptor[]; + defaultLanguage: XLocale; + + // + // Audio Service Configurations ... + defaultNotificationDelay: number; + notificationAudioSources: XNotificationSound[]; + + // + // Security Service Configurations ... + secretKey: string; + + // + // Image Related Configs ... + minAllowedImageSize: number; + maxAllowedImageSize: number; + allowedImageExtensions: string[]; + + // + // Profile Image Configs ... + maxProfileImageSize: number; + maxProfileImagesUploadFiles: number; + minAllowedImageCroppableSize: number; + + // + // DCountry Code ... + defaultCountryCode: XCountryCode; + + // + // Page Sizes ... + defaultPageSize: number; + pageSizes: number[]; + + // + // Action Delays ... + defaultActionDelay: number; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\constants\x-border.enum.ts + +export enum XBorderStyle { + Dotted = 'dotted', + Dashed = 'dashed', + Solid = 'solid', + Double = 'double', + Grove = 'groove', + Ridge = 'ridge', + Inset = 'inset', + Outset = 'outset', + None = 'none', + Hidden = 'hidden', +} + +export type XBorderStyleIdentifier = XBorderStyle | string; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\constants\x-crud.enum.ts + +export enum XCrudAction { + Add = 'add', + Update = 'update', + Delete = 'delete' +} + +export type XCrudActionIdentifier = XCrudAction | string; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\constants\x-exception.resources.ts + +import { XExceptionIndex } from '../typings/x-exception.typings'; + +export enum XExceptionIDs { + // + //#region Invalid Exceptions 1xx ... + InvalidData = 100, + InvalidDate = 101, + BadRequest = 102, + InvalidArgs = 103, + InvalidMessage = 104, + InvalidToken = 105, + InvalidVerificationCode = 106, + InvalidEmailAddress = 107, + InvalidMobileNumber = 108, + InvalidPassword = 109, + InavlidUrl = 110, + InvalidDevice = 111, + InvalidProfileImage = 112, + InavlidFile = 113, + InvalidUserName = 114, + //#endregion + + // + //#region Not (...) Exceptions 2xx ... + NotFound = 200, + NotAllowed = 201, + NotAuthorized = 202, + NotVisible = 203, + //#endregion + + // + //#region User/Registration/Account/Bann Exceptions 3xx ... + AccountLockedOut = 300, + LoginFailed = 301, + UserRegisteredBefore = 302, + UserDisabled = 303, + DeviceBanned = 304, + VerificationCodeResendNotAllowed = 305, + PasswordsSame = 306, + MobilesSame = 307, + EmailsSame = 308, + MobileInUsed = 309, + EmailInUsed = 310, + MobileNotConfirmed = 311, + EmailNotConfirmed = 312, + PostBanned = 313, + UserBanned = 314, + //#endregion + + // + //#region General Exceptions 4xx ... + Unknown = 400, + Duplicate = 401, + ActionFailed = 402, + ExpiredDate = 403, + Unavailable = 404, + MaxAllowedTagExceeded = 405, + Timeout = 406, + //#endregion + + // + //#region File Exceptions 5xx ... + EmptyFile = 500, + MaxFileSizeExceeded = 501, + UnsupportedFileType = 502, + MaxAllowedMediaExceeded = 503, + //#endregion + + // + //#region Configuration Exceptions 6xx ... + InvalidConfiguration = 600, + StorageServiceInitialFailed = 601, + MessageServiceInitialFailed = 602, + //#endregion + + // + //#region xFramework Exceptions 9xx ... + MaxAllowedFilesExceeded = 900, + MinFilesSizeExceeded = 901, + MaxFilesSizeExceeded = 902, + MustAcceptTerms = 903, + UnsupportedBrowser = 904, + PermissionDenied = 905, + PositionUnavailable = 906, + StorageNotSupported = 907, + MustLogout = 908, + InvalidLocale = 909, + InvalidUserSelector = 910, + //#endregion +} + +/** + * a collection of Available Exceptions on System ... + */ +export const Exceptions: XExceptionIndex = { + // + //#region Invalid Exceptions 1xx ... + InvalidData: { + id: 100, + message: 'Invalid Data ...', + }, + InvalidDate: { + id: 101, + message: 'Invalid Date ...', + }, + BadRequest: { + id: 102, + message: 'Bad Request ...', + }, + InvalidArgs: { + id: 103, + message: 'Invalid Args ...', + }, + InvalidMessage: { + id: 104, + message: 'Invalid Message ...', + }, + InvalidToken: { + id: 105, + message: 'Invalid Token ...', + }, + InvalidVerificationCode: { + id: 106, + message: 'Invalid Verification Code ...', + }, + InvalidEmailAddress: { + id: 107, + message: 'Invalid Email Address ...', + }, + InvalidMobileNumber: { + id: 108, + message: 'Invalid Mobile Number ...', + }, + InvalidPassword: { + id: 109, + message: 'Invalid Password ...', + }, + InavlidUrl: { + id: 110, + message: 'Inavlid Url ...', + }, + InvalidDevice: { + id: 111, + message: 'Invalid Device ...', + }, + InvalidProfileImage: { + id: 112, + message: 'Invalid Profile Image ...', + }, + InavlidFile: { + id: 113, + message: 'Invalid File ...', + }, + InvalidUserName: { + id: 114, + message: 'Invalid Username ...', + }, + //#endregion + + // + //#region Not (...) Exceptions 2xx ... + NotFound: { + id: 200, + message: 'Not Found ...', + }, + NotAllowed: { + id: 201, + message: 'Not Allowed ...', + }, + NotAuthorized: { + id: 202, + message: 'Not Authorized ...', + }, + NotVisible: { + id: 203, + message: 'Not Visible ...', + }, + //#endregion + + // + //#region User/Registration/Account/Bann Exceptions 3xx ... + AccountLockedOut: { + id: 300, + message: 'Account Locked out Until: (%$s)', + }, + LoginFailed: { + id: 301, + message: 'Login Failed ...', + }, + UserRegisteredBefore: { + id: 302, + message: 'User Registered Before ...', + }, + UserDisabled: { + id: 303, + message: 'User Disabled ...', + }, + DeviceBanned: { + id: 304, + message: 'Device Banned Until:(%$s)', + }, + VerificationCodeResendNotAllowed: { + id: 305, + message: `You can't Request Verification code Until:(%$s)`, + }, + PasswordsSame: { + id: 306, + message: 'Passwords Same ...', + }, + MobilesSame: { + id: 307, + message: 'Mobiles Same ...', + }, + EmailsSame: { + id: 308, + message: 'Emails Same ...', + }, + MobileInUsed: { + id: 309, + message: 'Mobile In Used ...', + }, + EmailInUsed: { + id: 310, + message: 'Email In Used ...', + }, + MobileNotConfirmed: { + id: 311, + message: 'Mobile Not Confirmed ...', + }, + EmailNotConfirmed: { + id: 312, + message: 'Email Not Confirmed ...', + }, + PostBanned: { + id: 313, + message: 'Post Banned ...', + }, + UserBanned: { + id: 314, + message: 'User Banned ...', + }, + //#endregion + + // + //#region General Exceptions 4xx ... + Unknown: { + id: 400, + message: 'Unknown Error ...', + }, + Duplicate: { + id: 401, + message: 'Duplicate ...', + }, + ActionFailed: { + id: 402, + message: 'Action Failed ...', + }, + ExpiredDate: { + id: 403, + message: 'Expired Date ...', + }, + Unavailable: { + id: 404, + message: 'Unavailable ...', + }, + MaxAllowedTagExceeded: { + id: 405, + message: 'Max Allowed Tag Exceeded ...', + }, + Timeout: { + id: 406, + message: 'Timed out ...', + }, + //#endregion + + // + //#region File Exceptions 5xx ... + EmptyFile: { + id: 500, + message: 'Empty File ...', + }, + MaxFileSizeExceeded: { + id: 501, + message: 'max File size exceeded ...', + }, + UnsupportedFileType: { + id: 502, + message: 'Unsupported File Type ...', + }, + MaxAllowedMediaExceeded: { + id: 503, + message: 'Max Allowed Media Exceeded ...', + }, + //#endregion + + // + //#region Configuration Exceptions 6xx ... + InvalidConfiguration: { + id: 600, + message: 'Invalid Configuration ...', + }, + StorageServiceInitialFailed: { + id: 601, + message: 'Storage Service Initiallization Failed ...', + }, + MessageServiceInitialFailed: { + id: 602, + message: 'Message Service Initialization Failed ...', + }, + //#endregion + + // + //#region xFramework Exceptions 9xx ... + MaxAllowedFilesExceeded: { + id: 900, + message: 'Max Allowed Files Exceeded ...', + }, + MinFilesSizeExceeded: { + id: 901, + message: 'Min Files Size not Exceeded ...', + }, + MaxFilesSizeExceeded: { + id: 902, + message: 'Max Files Size Exceeded ...', + }, + MustAcceptTerms: { + id: 903, + message: 'Must Accept Terms And Conditions ...', + }, + UnsupportedBrowser: { + id: 904, + message: 'Browser does not support location services ...', + }, + PermissionDenied: { + id: 905, + message: 'You have rejected access to your location ...', + }, + PositionUnavailable: { + id: 906, + message: 'Unable to determine your location ...', + }, + StorageNotSupported: { + id: 907, + message: 'Service timeout has been reached ...', + }, + MustLogout: { + id: 908, + message: `logged in user's can't access to this content ...`, + }, + InvalidLocale: { + id: 909, + message: 'Invalid Locale ...', + }, + InvalidUserSelector: { + id: 910, + message: 'Invalid User Selector ...', + }, + //#endregion +}; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\constants\x-file.enum.ts + +// +//#region XFile Types ... +export enum XFileType { + Image, + ProfileImage, + CoverImage, + Audio, + Video, + Document, +} + +export enum XGeneralThumbnails { + UNKNOWN = 'unknown', + IMAGE = 'image', + AUDIO = 'audio', + VIDEO = 'video', + DOCUMENT = 'document', +} + +export const TypesMatcher = { + IMAGE: 'image', + VIDEO: 'video', + AUDIO: 'audio', +}; +//#endregion + +// +//#region File Extensions ... +export enum XAudioFileExtensions { + Aif = '.aif', + Cda = '.cda', + Iff = '.iff', + Mid = '.mid', + Midi = '.midi', + Mp3 = '.mp3', + Mpa = '.mpa', + Wav = '.wav', + Wma = '.wma', + Wpl = '.wpl', + Aa = '.aa', + Aac = '.aac', + Aax = '.aax', + Act = '.act', + Aiff = '.aiff', + Alac = '.alac', + Amr = '.amr', + Ape = '.ape', + Au = '.au', + Awb = '.awb', + Dss = '.dss', + Dvf = '.dvf', + Flac = '.flac', + Gsm = '.gsm', + Iklax = '.iklax', + Ivs = '.ivs', + M4a = '.m4a', + M4b = '.m4b', + M4p = '.m4p', + Mmf = '.mmf', + Mpc = '.mpc', + Msv = '.msv', + Nmf = '.nmf', + Ogg = '.ogg', + Oga = '.oga', + Mogg = '.mogg', + Opus = '.opus', + Ra = '.ra', + Rm = '.rm', + Raw = '.raw', + Rf64 = '.rf64', + Sln = '.sln', + Tta = '.tta', + Voc = '.voc', + Vox = '.vox', + Wv = '.wv', + Eightsvx = '.8svx', +} + +export enum XVideoFileExtensions { + Avi = '.avi', + Flv = '.flv', + H264 = '.h264', + M4v = '.m4v', + Mkv = '.mkv', + Mov = '.mov', + Mp4 = '.mp4', + Mpg = '.mpg', + Mpeg = '.mpeg', + Rm = '.rm', + Swf = '.swf', + Vob = '.vob', + Wmv = '.wmv', + ThreeG2 = '.3g2', + ThreeGp = '.3gp', + Webm = '.webm', + Viv = '.viv', + Gifv = '.gifv', + Svi = '.svi', + Roq = '.roq', + Rmvb = '.rmvb', + Yuv = '.yuv', + Qt = '.qt', + Ogv = '.ogv', + Ogg = '.ogg', + Nsv = '.nsv', + Mng = '.mng', + M4p = '.m4p', + M2v = '.m2v', + Mp2 = '.mp2', + Mpe = '.mpe', + Mpv = '.mpv', + Mts = '.mts', + M2ts = '.m2ts', + Mxf = '.mxf', + Gif = '.gif', + F4v = '.f4v', + F4p = '.f4p', + F4a = '.f4a', + F4b = '.f4b', + Drc = '.drc', + Amv = '.amv', + Asf = '.asf', +} + +export enum XImageFileExtensions { + Ai = '.ai', + Bmp = '.bmp', + Gif = '.gif', + Jpeg = '.jpeg', + Jpg = '.jpg', + Max = '.max', + Obj = '.obj', + Png = '.png', + Ps = '.ps', + Psd = '.psd', + Svg = '.svg', + Tif = '.tif', + Tiff = '.tiff', + Threeds = '.3ds', + Threedm = '.3dm', + Ico = '.ico', + Icns = '.icns', +} + +export enum XDocumentFileExtensions { + Asp = '.asp', + Aspx = '.aspx', + Cer = '.cer', + Cfm = '.cfm', + Cgi = '.cgi', + Pl = '.pl', + Cs = '.cs', + Css = '.css', + SCss = '.scss', + Htm = '.htm', + Html = '.html', + Js = '.js', + Jsp = '.jsp', + JSON = '.json', + MD = '.md', + VSCodeWorkspace = '.code-workspace', + Part = '.part', + Php = '.php', + Rss = '.rss', + Xhtml = '.xhtml', + Bak = '.bak', + Cfg = '.cfg', + Cpl = '.cpl', + Cur = '.cur', + Dll = '.dll', + Dmp = '.dmp', + Drv = '.drv', + Ini = '.ini', + Lnk = '.lnk', + Msi = '.msi', + Sys = '.sys', + Tmp = '.tmp', + Accdb = '.accdb', + Csv = '.csv', + Dat = '.dat', + Db = '.db', + Dbf = '.dbf', + Log = '.log', + Mdb = '.mdb', + Pdb = '.pdb', + Sav = '.sav', + Sql = '.sql', + Tar = '.tar', + TarGz = '.tar.gz', + Key = '.key', + Odp = '.odp', + Pps = '.pps', + Ppt = '.ppt', + Pptx = '.pptx', + Ods = '.ods', + Xlr = '.xlr', + Xls = '.xls', + Xlsx = '.xlsx', + Doc = '.doc', + Docx = '.docx', + Odt = '.odt', + Msg = '.msg', + Pdf = '.pdf', + Rtf = '.rtf', + Tex = '.tex', + Txt = '.txt', + Wks = '.wks', + Wps = '.wps', + Wpd = '.wpd', + Apk = '.apk', + Bat = '.bat', + Com = '.com', + Exe = '.exe', + Py = '.py', + Pyw = '.pyw', + Wsf = '.wsf', + Appx = '.appx', + SevenZ = '.7z', + A = '.a', + Aapkg = '.aapkg', + Ace = '.ace', + Alz = '.alz', + Bke = '.bke', + Arj = '.arj', + B = '.b', + Ba = '.ba', + Bin = '.bin', + Bz2 = '.bz2', + Xaml = '.xaml', + Deb = '.deb', + Dmg = '.dmg', + Dn = '.dn', + Egg = '.egg', + Egt = '.egt', + Ezip = '.ezip', + Ess = '.ess', + Gz = '.gz', + Ipg = '.ipg', + Jar = '.jar', + Lzh = '.lzh', + Lz = '.lz', + Lzo = '.lzo', + Lzma = '.lzma', + Mhtml = '.mhtml', + Nth = '.nth', + Oar = '.oar', + Pak = '.pak', + Par = '.par', + Par2 = '.par2', + Paf = '.paf', + Pea = '.pea', + Pyk = '.pyk', + Pk3 = '.pk3', + Pk4 = '.pk4', + Pxz = '.pxz', + Rar = '.rar', + Sis = '.sis', + Sisx = '.sisx', + Skb = '.skb', + Sq = '.sq', + Tib = '.tib', + Xap = '.xap', + Xz = '.xz', + Z = '.z', + Ts = '.ts', + Zoo = '.zoo', + Zip = '.zip', +} +//#endregion + +// +//#region File Extension Descriptions ... +export enum XAudioFileExtensionDescriptions { + Aif = 'AIF/Audio Interchange audio', + Cda = 'CD audio track', + Iff = 'Interchange File Format', + Mid = 'MIDI audio', + Midi = 'MIDI audio', + Mp3 = 'MPEG Layer III Audio', + Mpa = 'MPEG-2 audio', + Wav = 'Standard audio file container format', + Wma = 'Windows Media Audio format', + Wpl = 'Windows Media Player playlist', + Aa = 'A low-bitrate audiobook container format', + Aac = 'Advanced Audio Coding format', + Aax = 'An Audiobook format', + Act = 'lossy ADPCM 8 kbit/s compressed audio format', + Aiff = 'Apple, A standard uncompressed CD-quality, audio file format', + Alac = 'Apple, An audio coding format developed by Apple Inc', + Amr = 'AMR-NB audio, used primarily for speech', + Ape = "Monkey's Audio lossless audio compression format", + Au = 'Sun Microsystems standard audio file format', + Awb = 'AMR-WB audio, used primarily for speech', + Dss = 'Olympus proprietary format', + Dvf = 'A Sony proprietary format for compressed voice files', + Flac = 'Free Lossless Audio Codec', + Gsm = 'Designed for telephony use in Europe', + Iklax = 'iKlax Media proprietary format', + Ivs = 'Digital Rights Management developed by 3D Solar UK Ltd', + M4a = 'audio-only MPEG-4 file, used by Apple for unprotected music', + M4b = 'Audiobook / podcast extension with AAC or ALAC encoded audio in an MPEG-4 container', + M4p = 'A version of AAC with proprietary Digital Rights Management developed by Apple for use in music downloaded from their iTunes Music Store and their music streaming service known as Apple Music', + Mmf = 'Yamaha, Samsung A Samsung audio format', + Mpc = 'Musepack, an open source lossy audio codec', + Msv = 'Sony proprietary format for Memory Stick compressed voice', + Nmf = 'NICE Media Player audio', + Ogg = 'Xiph.Org Foundation A free, open source container format', + Oga = 'Xiph.Org Foundation A free, open source container format', + Mogg = 'Xiph.Org Foundation A free, open source container format', + Opus = 'lossy audio compression format developed by the Internet Engineering Task Force (IETF) and made especially suitable for interactive real-time applications over the Internet', + Ra = 'RealAudio format designed for streaming audio over the Internet', + Rm = 'RealAudio format designed for streaming audio over the Internet', + Raw = 'raw file can contain audio in any format', + Rf64 = 'One successor to the Wav format', + Sln = 'Signed Linear PCM format used by Asterisk', + Tta = 'The True Audio, real-time lossless audio codec', + Voc = 'Creative Technology The file format', + Vox = 'Adaptive Differential Pulse Code Modulation', + Wv = 'Format for wavpack', + Eightsvx = 'Electronic Arts The IFF-8SVX format for 8-bit sound samples', +} + +export enum XVideoFileExtensionDescriptions { + Avi = 'Audio Video Interleave', + Flv = 'Adobe Flash Video', + H264 = 'H.264 Video', + M4v = 'Apple MP4 Video, MPEG-4 Part 12', + Mkv = 'Matroska Multimedia Container', + Mov = 'Apple QuickTime Movie', + Mp4 = 'MPEG-4 Video', + Mpg = 'MPEG Video', + Mpeg = 'MPEG Video', + Rm = 'Real Media', + Swf = 'Shockwave flash', + Vob = 'DVD Video Object', + Wmv = 'Windows Media Video', + ThreeG2 = '3GPP2 Multimedia', + ThreeGp = '3GPP multimedia', + Webm = 'Royalty-free format created for HTML5 video', + Viv = 'VivoActive based upon H.263 video', + Gifv = 'Video alternative to GIF', + Svi = 'Samsung video format for portable players', + Roq = 'used by Quake', + Rmvb = 'RealMedia Variable Bitrate', + Yuv = 'Raw video format', + Qt = 'QuickTime File Format', + Ogv = 'Ogg Video', + Ogg = 'Ogg Video', + Nsv = 'Nullsoft Streaming Video', + Mng = 'Multiple-image Network Graphics', + M4p = 'MPEG-4 Part 14', + M2v = 'MPEG-2 Part 3', + Mp2 = 'MPEG-1 Part 2', + Mpe = 'MPEG-1', + Mpv = 'MPEG-1', + Mts = 'MPEG Transport Stream', + M2ts = 'MPEG Transport Stream', + Mxf = 'Material Exchange Format', + Gif = 'Simple animation, inefficient compression', + F4v = 'Adobe Flash Video', + F4p = 'Adobe Flash Video', + F4a = 'Adobe Flash Video', + F4b = 'Adobe Flash Video', + Drc = 'Dirac', + Amv = 'Modified version of AVI', + Asf = 'Advanced Systems Format', +} + +export enum XImageFileExtensionDescriptions { + Ai = 'Adobe Illustrator', + Bmp = 'Bitmap image', + Gif = 'Graphical Interchange Format', + Jpeg = 'Joints Photographic Experts Group', + Jpg = 'Joints Photographic Experts Group', + Max = '3ds Max Scene', + Obj = 'Wavefront 3D Object', + Png = 'Portable Network Graphic image', + Ps = 'PostScript', + Psd = 'Adobe Photoshop Document image', + Svg = 'Scalable Vector Graphics', + Tif = 'TIFF image', + Tiff = 'TIFF image', + Threeds = '3D Studio Scene', + Threedm = 'Rhino 3D Model', + Ico = 'Icon', + Icns = 'macOS X icon resource', +} + +export enum XDocumentFileExtensionDescriptions { + Asp = 'Active Server Page', + Aspx = 'Active Server Page', + Cer = 'Internet security certificate', + Cfm = 'ColdFusion Markup', + Cgi = 'Common Gateway Interface Script', + Pl = 'Perl script', + Cs = 'C# document', + Css = 'Cascading Style Sheet', + SCss = 'Sass and Less Style Sheet', + Htm = 'Hypertext Markup Language', + Html = 'Hypertext Markup Language', + Js = 'JavaScript', + Jsp = 'Java Server Page', + JSON = 'Java Script Object Notation', + MD = 'Markdown', + VSCodeWorkspace = 'VSCode Workspace', + Part = 'Partially downloaded', + Php = 'PHP Source Code', + Rss = 'Rich Site Summary', + Xhtml = 'Extensible Hypertext Markup Language', + Bak = 'Backup', + Cfg = 'Configuration', + Cpl = 'Windows Control panel', + Cur = 'Windows cursor', + Dll = 'Dynamic Link Library', + Dmp = 'Dump', + Drv = 'Device driver', + Ini = 'Initialization', + Lnk = 'Windows shortcut', + Msi = 'Windows installer package', + Sys = 'Windows system', + Tmp = 'Temporary', + Accdb = 'Access 2007 Database', + Csv = 'Comma separated value', + Dat = 'Data', + Db = 'Database', + Dbf = 'Database', + Log = 'Log', + Mdb = 'Microsoft Access database', + Pdb = 'Program Database', + Sav = 'Save', + Sql = 'Structured Query Language database', + Tar = 'Linux / Unix tarball file archive', + TarGz = 'gzipped tar', + Key = 'Keynote presentation', + Odp = 'OpenOffice Impress presentation', + Pps = 'PowerPoint slide show', + Ppt = 'PowerPoint presentation', + Pptx = 'PowerPoint Open XML presentation', + Ods = 'OpenOffice Calc spreadsheet', + Xlr = 'Microsoft Works spreadsheet', + Xls = 'Microsoft Excel', + Xlsx = 'Microsoft Excel Open XML spreadsheet', + Doc = 'Microsoft Word', + Docx = 'Microsoft Word Open XML', + Odt = 'OpenOffice Writer document', + Msg = 'Outlook Mail Message', + Pdf = 'Portable Document Format', + Rtf = 'Rich Text Format', + Tex = 'LaTeX document', + Txt = 'Plain text', + Wks = 'Lotus 1-2-3 Spreadsheet', + Wps = 'Microsoft Works Word Processor Document', + Wpd = 'WordPerfect document', + Apk = 'Android package', + Bat = 'Batch', + Bin = 'Binary', + Com = 'MS-DOS command', + Exe = 'Executable', + Jar = 'Java Archive', + Py = 'Python', + Pyw = 'Python', + Wsf = 'Windows Script', + Appx = 'Microsoft Application Package', + SevenZ = '7-Zip compressed', + A = 'An external file extension for C/C++', + Aapkg = 'ArchestrA IDE', + Ace = 'ACE compressed', + Alz = 'ALZip compressed', + Bke = 'BackupEarth.com data compression', + Arj = 'ARJ compressed', + B = 'Similar to .a, but less compressed', + Ba = 'Scifer External Archive Type', + Bz2 = 'bzip2', + Xaml = 'Used in programs like Visual Studio to create exe files', + Deb = 'Debian install package', + Dmg = 'Apple compressed/encrypted format', + Dn = 'Adobe Dimension CC file format', + Egg = 'Alzip Egg Edition compressed', + Egt = 'EGT Universal Document', + Ezip = 'EGT Compressed Folder', + Ess = 'SmartSense', + Gz = 'Compressed', + Ipg = 'Format in which Apple Inc. packages their iPod games', + Lzh = 'Lempel, Ziv, Huffman', + Lz = 'Lempel, Ziv', + Lzo = 'Lempel, Ziv', + Lzma = 'Lempel Ziv Markov chain algorithm compressed', + Mhtml = 'Mime HTML (Hyper-Text Markup Language) code', + Nth = 'Nokia Theme Used by Nokia Series 40 Cellphones', + Oar = 'OAR archive', + Pak = 'Enhanced type of .ARC archive', + Par = 'Parchive', + Par2 = 'Parchive', + Paf = 'Portable Application File', + Pea = 'PeaZip archive', + Pyk = 'Compressed', + Pk3 = 'Quake 3 archive', + Pk4 = 'Opens similarly to a zip archive.', + Pxz = 'A compressed layered image', + Rar = 'Rar Archive', + Sis = 'Symbian Application Package', + Sisx = 'Symbian Application Package', + Skb = 'Google SketchUp backup', + Sq = 'Squish Compressed Archive', + Tib = 'Acronis True Image backup', + Xap = 'Windows Phone Application Package', + Xz = 'xz compressed files, based on LZMA/LZMA2 algorithm', + Z = 'Unix compress', + Ts = 'Typescript', + Zoo = 'based on LZW', + Zip = 'popular compression format', +} +//#endregion + +export type XFileExtensions = + | XAudioFileExtensions + | XVideoFileExtensions + | XImageFileExtensions + | XDocumentFileExtensions; + +export type XFileExtensionDescriptions = + | XAudioFileExtensionDescriptions + | XVideoFileExtensionDescriptions + | XImageFileExtensionDescriptions + | XDocumentFileExtensionDescriptions; + +// +//#region Files Informations Constants ... +// +const XImageFileKeys = Object.keys(Object.assign({}, XImageFileExtensions)); +const XImageFilesInfo = XImageFileKeys.map((ik) => { + return { + identifier: ik, + type: XFileType.Image, + typeIdentifier: XGeneralThumbnails.IMAGE, + extension: XImageFileExtensions[ik], + description: XImageFileExtensionDescriptions[ik], + }; +}); + +// +const XAudioFileKeys = Object.keys(Object.assign({}, XAudioFileExtensions)); +const XAudioFilesInfo = XAudioFileKeys.map((ik) => { + return { + identifier: ik, + type: XFileType.Audio, + typeIdentifier: XGeneralThumbnails.AUDIO, + extension: XAudioFileExtensions[ik], + description: XAudioFileExtensionDescriptions[ik], + }; +}); + +// +const XVideoFileKeys = Object.keys(Object.assign({}, XVideoFileExtensions)); +const XVideoFilesInfo = XVideoFileKeys.map((ik) => { + return { + identifier: ik, + type: XFileType.Video, + typeIdentifier: XGeneralThumbnails.VIDEO, + extension: XVideoFileExtensions[ik], + description: XVideoFileExtensionDescriptions[ik], + }; +}); + +// +const XDocumentFileKeys = Object.keys( + Object.assign({}, XDocumentFileExtensions) +); +const XDocumentFilesInfo = XDocumentFileKeys.map((ik) => { + return { + identifier: ik, + type: XFileType.Document, + typeIdentifier: XGeneralThumbnails.DOCUMENT, + extension: XDocumentFileExtensions[ik], + description: XDocumentFileExtensionDescriptions[ik], + }; +}); + +/** + * provides all informations about supported files ... + */ +export const XFileInfos = [ + ...XImageFilesInfo, + ...XVideoFilesInfo, + ...XAudioFilesInfo, + ...XDocumentFilesInfo, +]; +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\constants\x-header.enum.ts + +export enum XHeader { + File = 'file', + Files = 'files', + Scope = 'scope', + Accept = 'Accept', + ExpiresAt = 'expires_at', + XPoweredBy = 'X-PoweredBy', + ActionToken = 'actionToken', + ContentType = 'Content-Type', + RefreshToken = 'refresh_token', + Authorization = 'Authorization', + AuthorizationPrefix = 'Bearer ', + RevisionChecksum = 'revision_checksum', + RejectUnauthorized = 'rejectUnauthorized', +} + +export enum XContentType { + AppJson = 'application/json', + AppOctetStream = 'application/octet-stream', +} + +export function getAuthorizationHeaderValue(token: string) { + return `${XHeader.AuthorizationPrefix}${token}`; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\constants\x-param.enum.ts + +export enum XParam { + // + // QueryParams and Headers ... + Id = 'id', + ReturnUrl = 'ru', + Type = 'type', + // + Errors = 'errors', + // + Item = 'item', + Items = 'items', + // + Option = 'option', + Options = 'options', + // + HasBack = 'b', + // + // Header + Language = 'language', + // + // XQuery interface Params ... + Filter = 'Filter', + ContainsDetail = 'ContainsDetail', + EnableTracking = 'EnableTracking', + SortBy = 'SortBy', + IsAscending = 'IsAscending', + Page = 'Page', + PageSize = 'PageSize', + // + // XPageRequest interface Params ... + First = 'First', + Last = 'Last', + After = 'After', + Before = 'Before', + DescendingSort = 'DescendingSort', + // + // Exception ... + Exception = 'ex', +} +//#endregion + +export type XParamIdentifier = XParam | string; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\constants\x-setting.resources.ts + +export enum XSettingsStorageKey { + Locale = '[SETTINGS] Locale' +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\constants\x-translation.resources.ts + +export enum XResourceIDs { + // + //#region App ... + app_name, + company, + //#endregion + + // + //#region Commons ... + id, + file_name, + // + error, + required, + + // + loading, + + // + username, + password, + + // + first_name, + last_name, + + // + verified, + unverified, + + // + thumbnail, + language, + + // + take_screenshot, + enable_capture_mode, + disable_capture_mode, + + // + be, + not_to_be, + enabled, + disabled, + banned, + un_banned, + //#endregion + + // + //#region File Types ... + audio, + video, + document, + cover_image, + image, + avatar, + unknown, + location, + //#endregion + + // + //#region Messages ... + are_you_sure, + success_msg, + locked_msg, + empty_content, + no_item, + drag_drop_here, + //#endregion + + // + //#region Components ... + map_dialog_title, + map_dialog_select_title, + color_picker_dialog_title, + //#endregion + + // + //#region Verbs ... + dismiss, + accept, + ok, + save, + cancel, + retry, + edit, + reset, + change, + start, + yes, + no, + verify, + confirm, + crop, + clear, + set, + select, + send, + show, + hide, + search, + delete, + clone, + select_all, + select_file, + select_all_group, + deselect_all, + deselect_all_group, + inverse_selection, + selected_location, + color, + colors, + preset_colors, + copy_to_clipboard, + // + upload, + import, + continue, + finish, + enable, + disable, + // + all, + new, + add, + update, + details, + translation, + + // + download, + list, + options, + descriptions, + + // + type, + + // + media, + medias, + + // + go, + input, + preview, + + // + close, + undo, + redo, + + // + refresh, + + // + submit, + + // + //#region Pagination ... + first, + last, + previous, + next, + back, + //#endregion + //#endregion + + // + //#region Themes Related ... + // + theme, + theme_mode, + // + theme_dark, + theme_light, + // + theme_name, + theme_name_description, + // + theme_clone_title, + //#endregion + + // + //#region Color Picker Related ... + input_color_title, + input_color_description, + //#endregion + + // + //#region Markdown Component ... + x_markdown_preview, + x_markdown_image, + x_markdown_link, + x_markdown_ol, + x_markdown_ul, + x_markdown_quote, + x_markdown_code, + x_markdown_heading, + x_markdown_italic, + x_markdown_bold, + x_markdown_guide, + //#endregion + + // + //#region XIDE Component ... + ide_mode, + ide_format, + //#endregion + + // + //#region Password Validation Errors ... + required_length, + required_unique_chars, + require_digit, + require_lowercase, + require_uppercase, + require_non_alphanumeric, + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\constants\x.constants.ts + +export const DEFAULT_JAVASCRIPT_VOID_HREF = 'javascript:void(0)'; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\directives\x-clipboard.directive.ts + +import { + Input, + Output, + Directive, + HostListener, + EventEmitter +} from '@angular/core'; +import { isNullOrEmptyString } from '../tools/x-type-detect.tools'; + +@Directive({ + // tslint:disable-next-line:directive-selector + selector: '[xClipboard]' +}) +export class XClipboardDirective { + // + @Input() + clipData: string; + + @Output() + copied = new EventEmitter(); + + @HostListener('click', ['$event']) + whenClicked(event: MouseEvent) { + event.preventDefault(); + + // + if (isNullOrEmptyString(this.clipData)) { + return; + } + + // + const listener = (e: ClipboardEvent) => { + // + // tslint:disable-next-line:no-string-literal + const clipboard = e.clipboardData || window['clipboardData']; + clipboard.setData('text', this.clipData); + e.preventDefault(); + + this.copied.emit(this.clipData); + }; + + // + document.addEventListener('copy', listener, false); + document.execCommand('copy'); + document.removeEventListener('copy', listener, false); + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\directives\x-color.directive.ts + +import { + Input, + Renderer2, + OnChanges, + Directive, + ElementRef, + SimpleChanges, +} from '@angular/core'; +import { + getXColorClassName, + getXColorForegroundClassName, +} from '../tools/x-color.tools'; +import { keys } from '../tools/x-object.tools'; +import { XStandardType } from '../typings/x.typings'; +import { concatMap, map, first } from 'rxjs/operators'; +import { toObservable } from '../tools/x-observable.tools'; +import { isNullOrEmptyString } from '../tools/x-type-detect.tools'; +import { XColorIdentifier, XColor } from '../typings/x-color.typings'; + +/** + * this directive used to apply an specific color on an element ... + */ +@Directive({ + // tslint:disable-next-line:directive-selector + selector: '[xColor]', +}) +export class XColorDirective implements OnChanges { + // + private COLOR_CLASS: string; + + // + /** + * a color identifier to apply on element ... + */ + // tslint:disable-next-line:no-input-rename + @Input('xColor') + xColor: XStandardType; + + /** + * determines the selected color must apply on foreground or background of element + */ + @Input() + isForeground: XStandardType = false; + + // + constructor(private element: ElementRef, private renderer: Renderer2) {} + + // + ngOnChanges(changes: SimpleChanges) { + // + const changeKeys = keys(changes); + if (changeKeys.includes('xColor') || changeKeys.includes('isForeground')) { + this.handleColorChanges(); + } + } + + // + private handleColorChanges() { + // + toObservable(this.xColor) + .pipe( + concatMap((color) => { + return toObservable(this.isForeground).pipe( + map((isForeground) => { + return { + color, + isForeground, + }; + }) + ); + }), + first() + ) + .subscribe((info) => { + // + if (!info.color) { + info.color = XColor.Dark; + } + + // + const colorClassName = info.isForeground + ? getXColorForegroundClassName(info.color) + : getXColorClassName(info.color); + + // + if (!isNullOrEmptyString(this.COLOR_CLASS)) { + this.renderer.removeClass( + this.element.nativeElement, + this.COLOR_CLASS + ); + } + + // + this.COLOR_CLASS = colorClassName; + this.renderer.addClass(this.element.nativeElement, colorClassName); + }); + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\directives\x-droppable.directive.ts + +import { + Input, + Output, + Renderer2, + Directive, + ElementRef, + EventEmitter, + HostListener, +} from '@angular/core'; +import { + getXColorClassName, + getXColorForegroundClassName, +} from '../tools/x-color.tools'; +import { hasChild } from '../tools/x-array.tools'; +import { XOneOrManyType } from '../typings/x.typings'; +import { isArray, isString } from '../tools/x-type-detect.tools'; +import { XColorIdentifier, XColor } from '../typings/x-color.typings'; +import { getExtension, isExistsExtension } from '../tools/x-file.tools'; + +@Directive({ + // tslint:disable-next-line:directive-selector + selector: '[xDroppable]', +}) +export class XDroppableDirective { + // + private DISABLED: boolean; + private COLOR_CLASSES: string[] = []; + + // + @Input() + disableFileCheck = false; + + @Input() + public allowedExtensions: XOneOrManyType = []; + + @Input() + public successColor: XColorIdentifier = XColor.Success; + + @Input() + public errorColor: XColorIdentifier = XColor.Danger; + + @Input() + public accentColor: XColorIdentifier = XColor.Secondary; + + @Input() + public supportsMultipleFiles = true; + + @Input() + public isForegroundColor = false; + + @Input() + set dragDisabled(value: boolean) { + // + if (value !== this.DISABLED) { + // + this.DISABLED = value; + if (!value) { + this.setBackgroundColor('transparent'); + } else { + this.setBackgroundColor(this.successColor); + } + } + } + + @Input() + set error(val: boolean) { + if (val) { + this.setBackgroundColor(this.errorColor); + return; + } + + if (this.DISABLED) { + return; + } + + this.setBackgroundColor('transparent'); + } + + @Output() + public filesChangeEmiter: EventEmitter = new EventEmitter(); + + constructor(private elementRef: ElementRef, private renderer: Renderer2) {} + + @HostListener('dragover', ['$event']) + public async onDragOver(event: any) { + // + event.preventDefault(); + event.stopPropagation(); + + // + if (this.DISABLED) { + return; + } + + // + await this.setBackgroundColor(this.accentColor); + } + + @HostListener('dragleave', ['$event']) + public async onDragLeave(event: any) { + // + event.preventDefault(); + event.stopPropagation(); + + // + if (this.DISABLED) { + return; + } + + // + await this.setBackgroundColor('transparent'); + } + + @HostListener('drop', ['$event']) + public async onDrop(event: DragEvent) { + event.preventDefault(); + event.stopPropagation(); + + // + if (this.DISABLED) { + return; + } + + // + const files = Array.from(event.dataTransfer.files); + if (files.length === 0) { + await this.setBackgroundColor(this.errorColor); + return; + } + + // + // Retrieve Allowen Extensions ... + let allowedExtensions: string[] = []; + if (isArray(this.allowedExtensions)) { + allowedExtensions = this.allowedExtensions as string[]; + } else if (isString(this.allowedExtensions)) { + allowedExtensions.push(this.allowedExtensions as string); + } + + // + if (!this.disableFileCheck) { + if (files.length === 1) { + // + const file = files[0]; + const ext = getExtension(file.name); + + // + let isAllowed = true; + + if (hasChild(allowedExtensions)) { + isAllowed = isExistsExtension(allowedExtensions, ext); + } + + // + if (isAllowed) { + // + await this.setBackgroundColor(this.successColor); + } else { + await this.setBackgroundColor(this.errorColor); + return; + } + + // + this.filesChangeEmiter.emit(file); + await this.setBackgroundColor('transparent'); + } else { + // + if (!this.supportsMultipleFiles) { + await this.setBackgroundColor(this.errorColor); + return; + } + + // + const isAllowedAll = !hasChild(allowedExtensions) + ? true + : files.every((file) => { + // + const ext = getExtension(file.name); + // + let isAllowed = true; + if (hasChild(allowedExtensions)) { + isAllowed = isExistsExtension(allowedExtensions, ext); + } + + // + return isAllowed; + }); + + // + if (isAllowedAll) { + // + await this.setBackgroundColor(this.successColor); + } else { + await this.setBackgroundColor(this.errorColor); + return; + } + + // + this.filesChangeEmiter.emit(files); + await this.setBackgroundColor('transparent'); + } + } else { + // + this.filesChangeEmiter.emit(files); + await this.setBackgroundColor('transparent'); + } + } + + private async setBackgroundColor(color: XColorIdentifier) { + // + if (!color) { + color = 'transparent'; + } + + // + const colorClassName = this.isForegroundColor + ? getXColorForegroundClassName(color) + : getXColorClassName(color); + + // + if (!this.COLOR_CLASSES.includes(colorClassName)) { + this.COLOR_CLASSES.push(colorClassName); + } + + // + if (hasChild(this.COLOR_CLASSES)) { + this.COLOR_CLASSES.forEach((clz) => { + this.renderer.removeClass(this.elementRef.nativeElement, clz); + }); + } + + // + this.renderer.addClass(this.elementRef.nativeElement, colorClassName); + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\directives\x-file-reader.directive.ts + +import { + Input, + Output, + OnInit, + OnChanges, + Directive, + OnDestroy, + EventEmitter, + SimpleChanges +} from '@angular/core'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { XFileType } from '../constants/x-file.enum'; +import { getFileType } from '../tools/x-file.tools'; +import { keys, nameof } from '../tools/x-object.tools'; +import { XFileReaderInputType } from '../typings/x.typings'; + +@Directive({ + // tslint:disable-next-line:directive-selector + selector: '[xFileReader]' +}) +export class XFileReaderDirective implements OnInit, OnChanges, OnDestroy { + // + private UNSUBSCRIBER = new Subject(); + private FILE_READER: FileReader; + + // + @Input('xFileReader') + file: XFileReaderInputType; + + @Input() + refreshFileReader = new Subject(); + + // + // tslint:disable-next-line:no-output-on-prefix + @Output() + onFileReaderError = new EventEmitter(); + + // + // tslint:disable-next-line:no-output-on-prefix + @Output() + onFileReaderLoad = new EventEmitter(); + + // + // tslint:disable-next-line:no-output-on-prefix + @Output() + onFileReaderProgress = new EventEmitter(); + + // + // tslint:disable-next-line:no-output-on-prefix + @Output() + onFileReaderFinish = new EventEmitter(); + + // + ngOnInit() { + this.refreshFileReader + .asObservable() + .pipe(takeUntil(this.UNSUBSCRIBER)) + .subscribe(() => { + this.handleStartReading(); + }); + } + + // + ngOnDestroy() { + this.UNSUBSCRIBER.next(); + this.UNSUBSCRIBER.complete(); + this.UNSUBSCRIBER = null; + } + + // + ngOnChanges(changes: SimpleChanges) { + // + const changeKeys = keys(changes); + const isFileChanged = changeKeys.includes( + nameof('file') + ); + if (isFileChanged) { + this.handleStartReading(); + } + } + + // + private async handleStartReading() { + // + await this.destroyReader(); + + // + const type = getFileType(this.file); + if (type !== XFileType.Image) { + return; + } + + // + this.FILE_READER = new FileReader(); + + // + this.FILE_READER.onerror = err => { + this.onFileReaderError.emit(err); + this.destroyReader(); + }; + + // + this.FILE_READER.onabort = err => { + this.onFileReaderError.emit(err); + this.destroyReader(); + }; + + // + this.FILE_READER.onloadstart = _ => this.onFileReaderLoad.emit(true); + this.FILE_READER.onprogress = ev => this.onFileReaderProgress.emit(ev); + this.FILE_READER.onloadend = _ => { + this.onFileReaderLoad.emit(false); + this.destroyReader(); + }; + + // + this.FILE_READER.onload = _ => + this.onFileReaderFinish.emit(this.FILE_READER.result); + + // + this.FILE_READER.readAsDataURL(this.file as Blob); + } + + private async destroyReader() { + // + if (!this.FILE_READER) { + return; + } + + // + this.FILE_READER.abort(); + this.FILE_READER = null; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\directives\x-focus-handler.directive.ts + +import { + Input, + Directive, + ElementRef, + HostListener, + ChangeDetectorRef, +} from '@angular/core'; +import { toArray } from '../tools/x-array.tools'; +import { toPromise } from '../tools/x-observable.tools'; +import { XOneOrManyType, XStandardType } from '../typings/x.typings'; + +@Directive({ + // tslint:disable-next-line:directive-selector + selector: '[xFocusHandler]', +}) +export class XFocusedHandlerDirective { + // + //#region Props ... + @Input() + enableHandler: XStandardType = false; + + @Input() + appliedClassNames: XStandardType> = 'x-focused'; + //#endregion + + // + //#region Constructor ... + constructor( + public el: ElementRef, + public changeDetector: ChangeDetectorRef + ) {} + //#endregion + + // + //#region HostListener Handlers ... + @HostListener('focus', ['$event']) + async whenFocused(event: any) { + // + const isEnable = await this.isEnable(); + if (!isEnable) { + return; + } + + // + const el = this.getElement(); + if (!el) { + return; + } + + // + const appliedClasses = await this.getClasses(); + + // + appliedClasses.forEach((clz) => { + el.classList.add(clz); + }); + + // + this.changeDetector.detectChanges(); + } + + @HostListener('blur', ['$event']) + async whenBlured(event: any) { + // + const isEnable = await this.isEnable(); + if (!isEnable) { + return; + } + + // + const el = this.getElement(); + if (!el) { + return; + } + + // + const appliedClasses = await this.getClasses(); + + // + appliedClasses.forEach((clz) => { + el.classList.remove(clz); + }); + + // + this.changeDetector.detectChanges(); + } + //#endregion + + // + //#region Private ... + private async isEnable() { + return await toPromise(this.enableHandler); + } + + private async getClasses() { + // + const classType = await toPromise(this.appliedClassNames); + return toArray(classType); + } + + private getElement() { + return this.el && this.el.nativeElement + ? (this.el.nativeElement as HTMLElement) + : null; + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\directives\x-focus.directive.ts + +import { + Input, + Directive, + OnDestroy, + OnChanges, + ElementRef, + AfterViewInit, + SimpleChanges, + AfterContentInit, +} from '@angular/core'; +import { takeUntil } from 'rxjs/operators'; +import { Subject, Subscription } from 'rxjs'; +import { nameof } from '../tools/x-object.tools'; +import { isNullOrUndefined } from '../tools/x-type-detect.tools'; + +@Directive({ + // tslint:disable-next-line:directive-selector + selector: '[xFocus]', +}) +export class XFocusDirective + implements AfterViewInit, AfterContentInit, OnChanges, OnDestroy +{ + // + private subscription: Subscription; + private ngUnsubscribe = new Subject(); + + // + @Input('xFocus') + reFocus = new Subject(); + + constructor(private element: ElementRef) {} + + // + //#region LifeCycles ... + ngAfterViewInit() { + this.initFocusHandler(); + } + + ngAfterContentInit() { + this.doFocus(); + } + + ngOnChanges(changes: SimpleChanges): void { + // + if (isNullOrUndefined(changes)) { + return; + } + + // + const changeKeys = Object.keys(changes); + const isReFocusChanged = changeKeys.includes( + nameof('reFocus') + ); + if (isReFocusChanged) { + this.initFocusHandler(); + } + } + + ngOnDestroy() { + // + this.ngUnsubscribe.next(); + this.ngUnsubscribe.complete(); + } + //#endregion + + private doFocus() { + setTimeout(() => this.element.nativeElement.focus(), 500); + } + + private initFocusHandler() { + // + if (!isNullOrUndefined(this.subscription)) { + // + this.subscription.unsubscribe(); + this.subscription = undefined; + } + + // + if (isNullOrUndefined(this.reFocus)) { + this.reFocus = new Subject(); + } + + // + this.subscription = this.reFocus + .asObservable() + .pipe(takeUntil(this.ngUnsubscribe)) + .subscribe(() => { + this.doFocus(); + }); + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\directives\x-formatter.directive.ts + +import { + Input, + OnInit, + Directive, + ElementRef, + HostListener, +} from '@angular/core'; +import { XFormatterOption } from '../typings/x.typings'; +import { applyFormat, clearFormat } from '../tools/x-formatter.tools'; + +@Directive({ + // tslint:disable-next-line:directive-selector + selector: '[xFormatter]', +}) +export class XFormatterDirective implements OnInit { + // + private el: HTMLInputElement; + + // + @Input('xFormatter') + options: XFormatterOption; + + @Input() + clearLocale = true; + + // + constructor(private elementRef: ElementRef) { + this.el = this.elementRef.nativeElement; + } + + // + ngOnInit() { + this.el.value = this.format(this.el.value); + } + + // + @HostListener('focus', ['$event.target.value']) + onFocus(value: any) { + this.el.value = this.clear(value); + } + + // + @HostListener('blur', ['$event.target.value']) + onBlur(value: any) { + this.el.value = this.format(value); + } + + // + private format(value: any) { + return applyFormat(this.options, value ? value.toString() : ''); + } + + // + private clear(value: any) { + return clearFormat( + this.options, + this.clearLocale, + value ? value.toString() : '' + ); + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\directives\x-mouse-handler.directive.ts + +import { + Input, + OnInit, + Output, + Directive, + OnDestroy, + OnChanges, + ElementRef, + HostListener, + EventEmitter, + AfterViewInit, + SimpleChanges, + AfterContentInit, +} from '@angular/core'; +import { XStandardType } from '../typings/x.typings'; +import { toPromise } from '../tools/x-observable.tools'; +import { isNullOrUndefined } from '../tools/x-type-detect.tools'; + +/** + * this is a directive which bind to mouse/touch events and react based on them ... + */ +@Directive({ + // tslint:disable-next-line:directive-selector + selector: '[xMouseHandler]', +}) +export class XMouseHandlerDirective + implements OnInit, OnChanges, AfterViewInit, AfterContentInit, OnDestroy { + // + //#region Props ... + @Input() + enableHandler: XStandardType = false; + + @Output() + started = new EventEmitter(); + + @Output() + moved = new EventEmitter(); + + @Output() + finished = new EventEmitter(); + + // tslint:disable-next-line:no-output-on-prefix + @Output() + onWindowStarted = new EventEmitter(); + + // tslint:disable-next-line:no-output-on-prefix + @Output() + onWindowMoved = new EventEmitter(); + + // tslint:disable-next-line:no-output-on-prefix + @Output() + onWindowFinished = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + constructor(private el: ElementRef) {} + //#endregion + + // + //#region LifeCycles ... + ngOnInit() {} + + ngOnChanges(changes: SimpleChanges) {} + + ngAfterViewInit() {} + + ngAfterContentInit() {} + + ngOnDestroy() {} + //#endregion + + // + //#region Host Listeners ... + // + //#region Element Listeners ... + @HostListener('document:mousedown', ['$event']) + @HostListener('document:touchstart', ['$event']) + async whenMoveStart(event: MouseEvent) { + // + const isEnable = await this.isEnable(); + if (!isEnable) { + return; + } + + // + if (!this.isTargetMatched(event)) { + return; + } + + // + this.started.emit(event); + } + + @HostListener('document:mousemove', ['$event']) + @HostListener('document:touchmove', ['$event']) + async whenMove(event: MouseEvent) { + // + const isEnable = await this.isEnable(); + if (!isEnable) { + return; + } + + // + if (!this.isTargetMatched(event)) { + return; + } + + // + this.moved.emit(event); + } + + @HostListener('document:mouseup', ['$event']) + @HostListener('document:touchend', ['$event']) + async whenMoveFinished(event: MouseEvent) { + // + const isEnable = await this.isEnable(); + if (!isEnable) { + return; + } + + // + if (!this.isTargetMatched(event)) { + return; + } + + // + this.finished.emit(event); + } + //#endregion + + // + //#region Window Listeners ... + @HostListener('window:mousedown', ['$event']) + @HostListener('window:touchstart', ['$event']) + async whenWindowMoveStart(event: MouseEvent) { + // + const isEnable = await this.isEnable(); + if (!isEnable) { + return; + } + + // + this.onWindowStarted.emit(event); + } + + @HostListener('window:mousemove', ['$event']) + @HostListener('window:touchmove', ['$event']) + async whenWindowMove(event: MouseEvent) { + // + const isEnable = await this.isEnable(); + if (!isEnable) { + return; + } + + // + this.onWindowMoved.emit(event); + } + + @HostListener('window:mouseup', ['$event']) + @HostListener('window:touchend', ['$event']) + async whenWindowMoveFinished(event: MouseEvent) { + // + const isEnable = await this.isEnable(); + if (!isEnable) { + return; + } + + // + this.onWindowFinished.emit(event); + } + //#endregion + //#endregion + + // + //#region Private ... + private async isEnable() { + return await toPromise(this.enableHandler); + } + + private isTargetMatched(event: MouseEvent) { + return !isNullOrUndefined(event) + ? (this.el.nativeElement.contains(event.target) as boolean) + : false; + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\directives\x-on-enter.directive.ts + +import { Directive, Output, EventEmitter, HostListener } from '@angular/core'; + +@Directive({ + // tslint:disable-next-line:directive-selector + selector: '[xOnEnter]' +}) +export class XOnEnterDirective { + // + @Output('xOnEnter') + listener: EventEmitter = new EventEmitter(); + + // + @HostListener('keyup.enter', ['$event']) + onEnterPressed(event: any) { + // tslint:disable-next-line: deprecation + this.listener.emit(event); + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\directives\x-on-escape.directive.ts + +import { Directive, Output, EventEmitter, HostListener } from '@angular/core'; + +@Directive({ + // tslint:disable-next-line:directive-selector + selector: '[xOnEscape]' +}) +export class XOnEscapeDirective { + // + @Output('xOnEscape') + listener: EventEmitter = new EventEmitter(); + + // + @HostListener('keyup.esc', ['$event']) + onEscapePressed(event: any) { + // tslint:disable-next-line: deprecation + this.listener.emit(event); + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\directives\x-on-outside-click.directive.ts + +import { + Output, + Directive, + ElementRef, + EventEmitter, + HostListener, +} from '@angular/core'; + +@Directive({ + // tslint:disable-next-line:directive-selector + selector: '[xOnOutsideClicked]', +}) +export class XOutsideClickDirective { + // + @Output('xOnOutsideClicked') + listener: EventEmitter = new EventEmitter(); + + // + constructor(private elementRef: ElementRef) {} + + @HostListener('document:click', ['$event.target']) + public onClick(targetElement: any) { + // + const clickedInside = this.elementRef.nativeElement.contains(targetElement); + if (!clickedInside) { + this.listener.emit(); + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\directives\x-render.directive.ts + +import { + Directive, + Renderer2, + ElementRef, + RendererStyleFlags2 +} from '@angular/core'; + +@Directive({ + // tslint:disable-next-line:directive-selector + selector: '[xRender]' +}) +export class XRenderDirective { + // + constructor(private renderer: Renderer2, private el: ElementRef) {} + + addClass(clz: string) { + this.renderer.addClass(this.el.nativeElement, clz); + } + + removeClass(clz: string) { + this.renderer.removeClass(this.el.nativeElement, clz); + } + + createElement(name: string, nameSpace?: string) { + return this.renderer.createElement(name, nameSpace); + } + + createText(value: string) { + return this.renderer.createText(value); + } + + appendChild(parent: any, newChild: any) { + this.renderer.appendChild(parent, newChild); + } + + appendChildToElement(child: any) { + this.renderer.appendChild(this.el.nativeElement, child); + } + + setAttribute(name: string, value: string, namespace?: string | null) { + this.renderer.setAttribute(this.el.nativeElement, name, value, namespace); + } + + removeAttribute(name: string, namespace?: string | null) { + this.renderer.removeAttribute(this.el.nativeElement, name, namespace); + } + + setStyle(style: string, value: any, flags?: RendererStyleFlags2) { + this.renderer.setStyle(this.el.nativeElement, style, value, flags); + } + + removeStyle(style: string, flags?: RendererStyleFlags2) { + this.renderer.removeStyle(this.el.nativeElement, style, flags); + } + + setProperty(name: string, value: any) { + this.renderer.setProperty(this.el.nativeElement, name, value); + } + + setValue(node: any, value: string) { + this.renderer.setValue(node, value); + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\directives\x-scroll-behavior.directive.ts + +import { + Input, + OnInit, + Output, + Directive, + Renderer2, + OnDestroy, + OnChanges, + EventEmitter, + HostListener, + AfterViewInit, + SimpleChanges, +} from '@angular/core'; +import { Subject, Subscription } from 'rxjs'; +import { hasChild } from '../tools/x-array.tools'; +import { keys, nameof } from '../tools/x-object.tools'; +import { isArray, isString } from '../tools/x-type-detect.tools'; + +// +//#region Definitions ... +export interface XBehaviourEvent { + type: XBehaviourEventType; + event: any; + elements: Element[]; +} + +export type XBehaviourEventType = 'scrollStart' | 'scrollEnd' | 'scrolling'; +//#endregion + +@Directive({ + // tslint:disable-next-line:directive-selector + selector: '[xScrollBehaviour]', +}) +export class XScrollBehaviourDirective + implements OnInit, OnChanges, OnDestroy, AfterViewInit +{ + // + private elementsRef: Element[] = []; + private xUnSubscriber = new Subject(); + + // + @Output() + scrollStarted = new EventEmitter(); + + @Output() + scrollEnded = new EventEmitter(); + + @Output() + scrolling = new EventEmitter(); + + // + @Input() + scrollStartClasses: string | string[]; + + @Input() + scrollEndClasses: string | string[]; + + @Input() + scrollingClasses: string | string[]; + + // + private ACTION_PROVIDER_SUBSCRIPTION: Subscription; + + @Input() + scrollingActionProvider = new Subject(); + + // + @Input('xScrollBehaviour') + elements: string | string[]; + + // + @HostListener('ionScrollStart', ['$event']) + async onScrollStart(event: any) { + // + await this.prepareScroller(); + + // + await this.removeClass(this.scrollEndClasses); + await this.addClass(this.scrollStartClasses); + + // + const eventModel: XBehaviourEvent = { + type: 'scrollStart', + event, + elements: this.elementsRef, + }; + + // + this.scrollStarted.emit(eventModel); + } + + @HostListener('ionScrollEnd', ['$event']) + async onScrollEnd(event: any) { + // + await this.prepareScroller(); + + // + await this.removeClass(this.scrollStartClasses); + await this.removeClass(this.scrollingClasses); + await this.addClass(this.scrollEndClasses); + + // + const eventModel: XBehaviourEvent = { + type: 'scrollEnd', + event, + elements: this.elementsRef, + }; + + // + this.scrollEnded.emit(eventModel); + } + + @HostListener('ionScroll', ['$event']) + async onScroll(event: any) { + // + await this.prepareScroller(); + + // + await this.addClass(this.scrollingClasses); + + // + const eventModel: XBehaviourEvent = { + type: 'scrolling', + event, + elements: this.elementsRef, + }; + + // + this.scrolling.emit(eventModel); + } + + // + constructor(public renderer: Renderer2) {} + + // + async ngOnInit() { + // + await this.prepareScroller(); + } + + async ngAfterViewInit() { + // + await this.prepareScroller(); + } + + ngOnChanges(changes: SimpleChanges) { + // + const changeKeys = keys(changes); + + // + //#region ActionProvider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('scrollingActionProvider') + ); + if (isActionProviderChanged) { + this.registerActionProvider(); + } + //#endregion + } + + ngOnDestroy() { + // + this.unregisterActionProvider(); + + // + this.xUnSubscriber.next(); + this.xUnSubscriber.complete(); + } + + // + private getType() { + // + if (!this.elements) { + return 'unknown'; + } + + // + if (isArray(this.elements)) { + return 'array'; + } else { + return 'single'; + } + } + + private registerActionProvider() { + // + this.unregisterActionProvider(); + + // + if (!this.scrollingActionProvider) { + this.scrollingActionProvider = new Subject(); + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION = this.scrollingActionProvider + .asObservable() + .subscribe(async (model) => { + // + if (!model) { + return; + } + + // + switch (model) { + // + case 'scrollStart': + await this.onScrollStart(null); + break; + + // + case 'scrollEnd': + await this.onScrollEnd(null); + break; + + // + case 'scrolling': + await this.onScroll(null); + break; + } + }); + } + + private unregisterActionProvider() { + // + if (!this.ACTION_PROVIDER_SUBSCRIPTION) { + return; + } + + // + this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe(); + this.ACTION_PROVIDER_SUBSCRIPTION = null; + } + + private async prepareScroller() { + // + await this.generateElementsRef(); + await this.addTransition(); + + // + this.registerActionProvider(); + } + + private async generateElementsRef() { + // + this.elementsRef = []; + + // + const inputType = this.getType(); + if (inputType === 'unknown') { + return []; + } else if (inputType === 'single') { + // + const elements = document.querySelectorAll(this.elements as string); + if (elements.length > 0) { + elements.forEach((e) => { + this.elementsRef.push(e); + }); + } + } else if (inputType === 'array') { + // + (this.elements as string[]).forEach((eSelector) => { + // + const elements = document.querySelectorAll(eSelector); + if (elements.length > 0) { + elements.forEach((e) => { + this.elementsRef.push(e); + }); + } + }); + } + + // + return this.elementsRef; + } + + private async addClass(cls: string | string[]) { + // + if (!hasChild(this.elementsRef)) { + return; + } + + // + const clsList = isString(cls) ? [cls as string] : (cls as string[]); + if (!hasChild(clsList)) { + return; + } + + // + this.elementsRef.forEach((e) => { + e.classList.add(...clsList); + }); + } + + private async removeClass(cls: string | string[]) { + // + if (!hasChild(this.elementsRef)) { + return; + } + + // + const clsList = isString(cls) ? [cls as string] : (cls as string[]); + if (!hasChild(clsList)) { + return; + } + + // + this.elementsRef.forEach((e) => { + e.classList.remove(...clsList); + }); + } + + private async addTransition(descriptor?: string) { + // + if (!descriptor) { + descriptor = 'transform 500ms, opacity 250ms'; + } + + // + if (!hasChild(this.elementsRef)) { + return; + } + + // + this.elementsRef.forEach((e) => { + this.renderer.setStyle(e, '-webkit-transition', descriptor); + this.renderer.setStyle(e, 'transition', descriptor); + }); + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\directives\x-slider.directive.ts + +import { + Input, + Output, + Directive, + ElementRef, + HostListener, + EventEmitter, +} from '@angular/core'; + +export interface XSliderValueEventModel { + s?: number; + v?: number; + rg?: number; + rgX?: number; + rgY?: number; +} + +@Directive({ + // tslint:disable-next-line:directive-selector + selector: '[xSlider]', +}) +export class XSliderDirective { + // + @Input() + rgX: number; + + @Input() + rgY: number; + + // + @Output() + newValue = new EventEmitter(); + + // + private listenerMove: any = (event: any) => { + this.move(event); + // tslint:disable-next-line:semicolon + }; + + // + private listenerStop: any = () => { + this.stop(); + // tslint:disable-next-line:semicolon + }; + + // + constructor(private el: ElementRef) {} + + setCursor(event: any) { + // + const height = this.el.nativeElement.offsetHeight; + const width = this.el.nativeElement.offsetWidth; + const x = Math.max(0, Math.min(this.getX(event), width)); + const y = Math.max(0, Math.min(this.getY(event), height)); + + // + if (this.rgX !== undefined && this.rgY !== undefined) { + // + this.newValue.emit({ + s: x / width, + v: 1 - y / height, + rgX: this.rgX, + rgY: this.rgY, + }); + } else if (this.rgX === undefined && this.rgY !== undefined) { + // + // ready to use vertical sliders + this.newValue.emit({ v: y / height, rg: this.rgY }); + } else { + // + this.newValue.emit({ v: x / width, rg: this.rgX }); + } + } + + move(event: any) { + event.preventDefault(); + this.setCursor(event); + } + + @HostListener('mousedown', ['$event']) + @HostListener('touchstart', ['$event']) + start(event: any) { + // + this.setCursor(event); + + // + document.addEventListener('mousemove', this.listenerMove); + document.addEventListener('touchmove', this.listenerMove); + document.addEventListener('mouseup', this.listenerStop); + document.addEventListener('touchend', this.listenerStop); + } + + stop() { + // + document.removeEventListener('mousemove', this.listenerMove); + document.removeEventListener('touchmove', this.listenerMove); + document.removeEventListener('mouseup', this.listenerStop); + document.removeEventListener('touchend', this.listenerStop); + } + + getX(event: any): number { + // + return ( + (event.pageX !== undefined ? event.pageX : event.touches[0].pageX) - + this.el.nativeElement.getBoundingClientRect().left - + window.pageXOffset + ); + } + + getY(event: any): number { + // + return ( + (event.pageY !== undefined ? event.pageY : event.touches[0].pageY) - + this.el.nativeElement.getBoundingClientRect().top - + window.pageYOffset + ); + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\directives\x-stop-click-propagation.directive.ts + +import { Directive, HostListener } from '@angular/core'; + +@Directive({ + // tslint:disable-next-line:directive-selector + selector: '[xStopClickPropagation]' +}) +export class XStopClickPropagationDirective { + @HostListener('click', ['$event']) + public onClick(event: any): void { + event.stopPropagation(); + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\directives\x-var.directive.ts + +import { + isNullOrUndefined, + isNullOrEmptyString, +} from '../tools/x-type-detect.tools'; +import { keys } from '../tools/x-object.tools'; +import { hasChild } from '../tools/x-array.tools'; +import { Input, Directive, ChangeDetectorRef } from '@angular/core'; + +export interface XVarModel { + name: string; + value: any; +} + +@Directive({ + selector: '[xVar]', + exportAs: 'xVar', +}) +export class XVarDirective { + // + private context: any = {}; + + // + @Input('xVar') + set xVar(value: XVarModel) { + // + this.context[this.prepareVarName(value.name)] = value; + this.changeDetector.detectChanges(); + } + + constructor(private changeDetector: ChangeDetectorRef) {} + + hasVar(name: string) { + return ( + !isNullOrEmptyString(name) && + !isNullOrUndefined(this.context) && + hasChild(keys(this.context)) && + !isNullOrUndefined(this.context[name]) + ); + } + + getVar(name: string) { + return this.hasVar(this.prepareVarName(name)) + ? this.context[this.prepareVarName(name)] + : undefined; + } + + getVars() { + return this.context; + } + + private prepareVarName(name: string) { + return `xVar_${name}`; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\guards\x-can-deactivate.guard.ts + +import { + CanDeactivate, + RouterStateSnapshot, + ActivatedRouteSnapshot +} from '@angular/router'; +import { Observable } from 'rxjs'; +import { Injectable } from '@angular/core'; + +export interface XCanDeactivate { + canDeactivate: ( + component: XCanDeactivate, + route: ActivatedRouteSnapshot, + state: RouterStateSnapshot + ) => Observable | Promise | boolean; +} + +@Injectable() +export class XCanDeactivateGuard implements CanDeactivate { + canDeactivate( + component: XCanDeactivate, + route: ActivatedRouteSnapshot, + state: RouterStateSnapshot + ) { + const url: string = state.url; + + return component.canDeactivate + ? component.canDeactivate(component, route, state) + : true; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\models\x-base-dto.ts + +import { isSameObject } from '../tools/x-object.tools'; + +// tslint:disable-next-line:no-empty-interface +export interface XBaseDto {} + +export abstract class XBaseDtoHelper { + // + //#region Abstracts ... + abstract defaultValues: T; + abstract get values(): T; + abstract prepareModelFields(dto?: T): T; + abstract fill(dto?: T): void; + //#endregion + + // + //#region Constructor ... + constructor(dto?: T) { + // + this.fill(this.prepareModelFields(dto)); + } + //#endregion + + // + //#region Actions ... + public isDefaultModel(dto?: T): boolean { + // + if (!dto) { + dto = { + ...this.values, + }; + } + + // + const result = isSameObject( + this.prepareModelFields(dto), + this.defaultValues + ); + + // + return result; + } + //#endregion +} + +export interface XBaseNumberIDDto { + id: number; +} + +// tslint:disable-next-line: no-empty-interface +export interface XBaseRequestDto {} + +export interface XBaseRangeRequestDto extends XBaseRequestDto { + items: Array; +} + +// tslint:disable-next-line:no-empty-interface +export interface XBaseResponseDto {} + +export interface XBaseStorableDto extends XBaseDto { + id?: T +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\models\x-base.indexed.dto.ts + +import { XBaseDto } from './x-base-dto'; + +export interface XBaseIndexedDto extends XBaseDto { + item: T; + key: TKey; + index: Tindex; +} + +export interface XBaseIntIndexedDto + extends XBaseIndexedDto {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\models\x-grouped-data.dto.ts + +import { XBaseDto } from "./x-base-dto"; + +export interface XGroupedDataDto extends XBaseDto { + groupedBy: TGroupedy; + members: Array; + timeStamp: Date; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\modules\x-directives.module.ts + +import { NgModule } from '@angular/core'; +import { IonicModule } from '@ionic/angular'; +import { FormsModule } from '@angular/forms'; +import { XAsPipe } from '../pipes/x-as.pipe'; +import { XLogPipe } from '../pipes/x-log.pipe'; +import { CommonModule } from '@angular/common'; +import { XSafeHtmlPipe } from '../pipes/x-safe-html.pipe'; +import { XFormatterPipe } from '../pipes/x-formatter.pipe'; +import { XVarDirective } from '../directives/x-var.directive'; +import { XFocusDirective } from '../directives/x-focus.directive'; +import { XColorDirective } from '../directives/x-color.directive'; +import { XRenderDirective } from '../directives/x-render.directive'; +import { XSliderDirective } from '../directives/x-slider.directive'; +import { XOnEnterDirective } from '../directives/x-on-enter.directive'; +import { XValidUrlDirective } from '../validators/x-valid-url.directive'; +import { XOnEscapeDirective } from '../directives/x-on-escape.directive'; +import { XDroppableDirective } from '../directives/x-droppable.directive'; +import { XClipboardDirective } from '../directives/x-clipboard.directive'; +import { XFormatterDirective } from '../directives/x-formatter.directive'; +import { XValueMatchDirective } from '../validators/x-value-match.directive'; +import { XValidEmailDirective } from '../validators/x-valid-email.directive'; +import { XFileReaderDirective } from '../directives/x-file-reader.directive'; +import { XMouseHandlerDirective } from '../directives/x-mouse-handler.directive'; +import { XFocusedHandlerDirective } from '../directives/x-focus-handler.directive'; +import { XOutsideClickDirective } from '../directives/x-on-outside-click.directive'; +import { XScrollBehaviourDirective } from '../directives/x-scroll-behavior.directive'; +import { XValidPhoneNumberDirective } from '../validators/x-valid-phone-number.directive'; +import { XStopClickPropagationDirective } from '../directives/x-stop-click-propagation.directive'; + +@NgModule({ + declarations: [ + // + // Directives + XVarDirective, + XFocusDirective, + XColorDirective, + XSliderDirective, + XRenderDirective, + XOnEnterDirective, + XOnEscapeDirective, + XFormatterDirective, + XClipboardDirective, + XDroppableDirective, + XFileReaderDirective, + XOutsideClickDirective, + XMouseHandlerDirective, + XFocusedHandlerDirective, + XScrollBehaviourDirective, + XStopClickPropagationDirective, + + // + // Pipes ... + XAsPipe, + XLogPipe, + XSafeHtmlPipe, + XFormatterPipe, + + // + // Validators ... + XValidUrlDirective, + XValueMatchDirective, + XValidEmailDirective, + XValidPhoneNumberDirective, + ], + imports: [CommonModule, IonicModule, FormsModule], + exports: [ + // + IonicModule, + FormsModule, + CommonModule, + + // + // Directives + XVarDirective, + XFocusDirective, + XColorDirective, + XSliderDirective, + XRenderDirective, + XOnEnterDirective, + XOnEscapeDirective, + XFormatterDirective, + XDroppableDirective, + XClipboardDirective, + XFileReaderDirective, + XOutsideClickDirective, + XMouseHandlerDirective, + XFocusedHandlerDirective, + XScrollBehaviourDirective, + XStopClickPropagationDirective, + + // + // Pipes ... + XAsPipe, + XLogPipe, + XSafeHtmlPipe, + XFormatterPipe, + + // + // Validators ... + XValidUrlDirective, + XValueMatchDirective, + XValidEmailDirective, + XValidPhoneNumberDirective, + ], +}) +export class XDirectivesModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\pipes\x-as.pipe.ts + +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'xAs', + pure: true, +}) +export class XAsPipe implements PipeTransform { + /** + * for casting a dynamic template var to specific type ... + * + * @param value the pure template var ... + * @param _type the type which required to cast value to it ... + * @returns a type casted var ... + */ + transform(value: any, _type: (new (...args: any[]) => T) | T): T { + return value as T; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\pipes\x-formatter.pipe.ts + +import { Pipe, PipeTransform } from '@angular/core'; +import { applyFormat, clearFormat } from '../tools/x-formatter.tools'; +import { XFormatterOption, XFormatterType } from '../typings/x.typings'; + +@Pipe({ + name: 'xFormatter', + pure: true, +}) +export class XFormatterPipe implements PipeTransform { + // + private defaultFormatterOption: XFormatterOption = { + type: XFormatterType.ApplyLocale, + }; + + // + transform(value: any, options?: XFormatterOption): string { + return this.format(options, value); + } + + // + clearFormat(value: any, options?: XFormatterOption, clearLocale = false) { + // + options = { + ...this.defaultFormatterOption, + ...options, + }; + + // + return clearFormat(options, clearLocale, value) || ''; + } + + // + private format(options: XFormatterOption, value: any) { + return applyFormat( + { ...this.defaultFormatterOption, ...options }, + value ? value.toString() : '' + ); + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\pipes\x-log.pipe.ts + +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'xLog', + pure: true, +}) +export class XLogPipe implements PipeTransform { + /** + * do a console log from a pipe + * + * @param value the specified value + * @param args it is optional and unused here + */ + transform(value: any, ...args: any[]): any { + // + console.log('XLogPipe: ', value); + return null; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\pipes\x-safe-html.pipe.ts + +import { Pipe, PipeTransform } from '@angular/core'; +import { DomSanitizer } from '@angular/platform-browser'; + +@Pipe({ + name: 'xSafeHTML', + pure: true, +}) +export class XSafeHtmlPipe implements PipeTransform { + // + constructor(private sanitizer: DomSanitizer) {} + + // + transform(value: any, args?: any): any { + return this.sanitizer.bypassSecurityTrustHtml(value); + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\tokens\x-injectable-tokens.ts + +import { InjectionToken } from '@angular/core'; +import { XFrameworkCoreConfig } from '../config/x-framework-core.config'; + +export const X_FRAMEWORK_CORE_CONFIG = new InjectionToken( + 'x_config' +); + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\tools\x-array.tools.ts + +// +//#region Array Tools ... + +import { XOneOrManyType } from '../typings/x.typings'; +import { isArray } from './x-type-detect.tools'; + +/** + * retrieve last item of an Array ... + * + * @param list an instance of XOneOrManyType object ... + */ +export function lastItem(list: XOneOrManyType) { + // + if (!list || !hasChild(list)) { + return null; + } + + // + const listArray = toArray(list); + + // + return listArray[listArray.length - 1]; +} + +/** + * extract and retrieve specific item of an array ... + * + * @param index index of Array Item ... + * @param list an instance of XOneOrManyType object ... + */ +export function nthItem(index: number, list: XOneOrManyType) { + // + if (!list || !hasChild(list)) { + return null; + } + + // + const listArray = toArray(list); + if (index < 0) { + try { + return listArray[listArray.length - 1 + index]; + } catch { + return null; + } + } + + // + try { + return listArray[index]; + } catch { + return null; + } +} + +/** + * extract specific range of items from an array ... + * + * @param index extracting start index ... + * @param count number of extracting items ... + * @param list an instance of XOneOrManyType object ... + * @param reverse optional param to determines extracting direction ... + */ +export function nthItems( + index: number, + count: number, + list: XOneOrManyType, + reverse?: boolean +) { + // + if (!list || !hasChild(list) || count === 0) { + return null; + } + + // + let mIndex = index; + const listArray = toArray(list); + if (index < 0) { + // + mIndex = listArray.length - 1 + index; + } + + // + let mCount = count; + if (mCount < 0) { + // + mIndex += count; + mCount = count * -1; + } + + // + const result = listArray.splice(mIndex, mCount); + if (reverse) { + result.reverse(); + } + + // + return result; +} + +/** + * determines an array has childs or not ... + * + * @param list an instance of XOneOrManyType object ... + */ +export function hasChild(list: XOneOrManyType) { + return list && toArray(list).length > 0; +} + +/** + * count an array childs ... + * + * @param list an instance of XOneOrManyType object ... + */ +export function countChild(list: XOneOrManyType) { + return toArray(list).length; +} + +/** + * convert an indexable object to array ... + * + * @param index indexed object ... + */ +export function getArrayOf(index: { [key: string]: T }): T[] { + // + const result: T[] = []; + + // + Object.keys(index).forEach((key) => { + // + result.push(index[key] as T); + }); + + // + return result; +} + +/** + * merge all items inside collection of items ... + * @param args a collection of XOneOrManyOfType items ... + */ +export function mergeList(...args: XOneOrManyType[]) { + // + const result: T[] = []; + + // + args.forEach((arg) => { + result.push(...toArray(arg)); + }); + + // + return result; +} + +/** + * convert value to array ... + * + * @param list an instance of XOneOrManyType object ... + */ +export function toArray(list: XOneOrManyType) { + // + if (!list) { + return []; + } + + // + if (isArray(list)) { + return list as T[]; + } + + // + return [list as T]; +} + +/** + * convert a value of specific type to array of another type ... + * + * @param list an instance of XOneOrManyType object ... + * @returns an instance of TDest[] object ... + */ +export function convertArray( + list: XOneOrManyType +): XOneOrManyType { + // + let array = toArray(list); + + // + const result: TDest[] = !hasChild(array) + ? [] + : array.map((i) => i as any as TDest); + + // + return result; +} + +/** + * Calls a defined callback function on each element of an array, and returns an array that contains the results. + * + * @param list an instance of XOneOrManyType object ... + * @param callback A function that accepts up to three arguments. + * The map method calls the callbackfn function one time for each element in the array. + */ +export function mapArray( + list: XOneOrManyType, + callback: (value: T, index: number, array: T[]) => T +) { + // + if (!list) { + return null; + } + + // + const pList = toArray(list); + + // + return pList.map(callback); +} + +/** + * remove specific index of an array objects and return cleared array ... + * + * @param list an instance of XOneOrManyType object ... + * @param index specific item index ... + */ +export function removeItemByIndex(list: XOneOrManyType, index: number) { + // + if (!hasChild(list) || index < 0 || index > countChild(list) - 1) { + return []; + } + + // + return toArray(list).splice(index, 1); +} + +/** + * remove specific item of an array and return cleared array ... + * + * @param list an instance of XOneOrManyType object ... + * @param item specific item ... + */ +export function removeItem(list: XOneOrManyType, item: T) { + // + if (!hasChild(list) || !item) { + return []; + } + + // + const listArray = toArray(list); + const iIndex = listArray.indexOf(item); + if (iIndex === -1) { + return []; + } + + // + return listArray.splice(iIndex, 1); +} + +/** + * check an array contains an specific item or not ... + * + * @param list an instance of XOneOrManyType object ... + * @param item specific item ... + */ +export function isExists(list: XOneOrManyType, item: T) { + return toArray(list).findIndex((i) => i === item) !== -1; +} + +/** + * sort array ... + * + * @param list an instance of XOneOrManyType object ... + * @param compareFn campare function ... + */ +export function sort( + list: XOneOrManyType, + compareFn?: (a: T, b: T) => number +) { + // + const array = toArray(list); + var result = array.sort(compareFn); + + // + return result; +} + +/** + * retrieve only unique members of an Array ... + * + * @param list an instance of XOneOrManyType object ... + */ +export function onlyUniques(list: XOneOrManyType) { + return toArray(list).filter((v, i, s) => s.indexOf(v) === i); +} + +/** + * Check two Array is Equals or not ... + * + * @param a + * @param b + * @returns + */ +export function isXEqual(a: T[], b: T[]): boolean { + if (a.length !== b.length) return false; + return a.every( + (value, index) => + JSON.stringify(value) === JSON.stringify(b[index]) && + typeof value === typeof b[index] + ); +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\tools\x-color.tools.ts + +import { + isNumber, + isString, + isNullOrUndefined, + isNullOrEmptyString, +} from './x-type-detect.tools'; +import { + XColor, + XRGBAColor, + XHSVAColor, + XHSLAColor, + XColorIdentifier, + XColorDescribtor, + XColorBrightnessLevel, + XColorFormatIdentifier, +} from '../typings/x-color.typings'; +import { toNumber, toXString } from './x-type-convert.tools'; + +// +//#region XColor tools .. +/** + * retrieve a color name ... + * + * @param color the colorname + * @param level the color brightness which default is Regular + */ +export function getXColorName( + color: XColorIdentifier, + level: XColorBrightnessLevel = XColorBrightnessLevel.Regular +) { + // + const mColor = color.toString(); + return level === XColorBrightnessLevel.Regular + ? mColor + : mColor + `-${level.toString()}`; +} + +/** + * retrieve specific color CSS Variable Name ... + * + * @param color Color name + * @param isContrast determines to get Text Color on Color var name + * @param level determines color brightness level + */ +export function getXColorCssVariableName( + color: XColorIdentifier, + isContrast: boolean = false, + level: XColorBrightnessLevel = XColorBrightnessLevel.Regular +) { + // + return `--x-color-${color.toLowerCase()}${ + level === XColorBrightnessLevel.Regular ? '' : '-' + level.toLowerCase() + }${!!isContrast ? '-contrast' : ''}`; +} + +/** + * retrieve a color class \ name ... + * + * @param color the colorname + * @param level the color brightness which default is Regular + */ +export function getXColorClassName( + color: XColorIdentifier, + level: XColorBrightnessLevel = XColorBrightnessLevel.Regular +) { + // + if (color.toString() === 'inherit') { + return 'cb-inherit'; + } + + // + const mColor = 'mat-' + color.toString(); + return level === XColorBrightnessLevel.Regular + ? mColor + : mColor + `-${level.toString()}`; +} + +/** + * retrieve a color foreground class name ... + * + * @param color the colorname + * @param level the color brightness which default is Regular + */ +export function getXColorForegroundClassName(color: XColorIdentifier) { + // + if (color.toString() === 'inherit') { + return 'cb-inherit'; + } + + // + const mColor = 'c-' + color.toString(); + return mColor; +} + +/** + * parse a color identifier and return it's descriptor ... + * + * @param color the color identifier to parse ... + */ +export function parseXColor(color: XColorIdentifier): XColorDescribtor { + // + const strColor = color.toString().trim().toLowerCase(); + const isContrast = strColor.includes('contrast'); + const brightnessLevel: XColorBrightnessLevel = strColor.includes('tint') + ? XColorBrightnessLevel.Tint + : strColor.includes('shade') + ? XColorBrightnessLevel.Shade + : XColorBrightnessLevel.Regular; + const name = strColor + .replace('-', '') + .replace('contrast', '') + .replace(brightnessLevel.toString(), '') as XColor; + + // + const result: XColorDescribtor = { + name, + brightnessLevel, + isContrast, + }; + + // + return result; +} +//#endregion + +// +//#region Color Parsers / Converters ... +// +//#region RGBA Source ... +/** + * determine a color is XRGBAColor or not ... + * + * @param v an input color representation ... + */ +export function isRGB(v: any) { + // + if (isNullOrUndefined(v)) { + return false; + } + + // + const color = v as XRGBAColor; + if (isNullOrUndefined(color)) { + // + const result = + !isNullOrUndefined(v.r) && + isNumber(v.r) && + !isNullOrUndefined(v.g) && + isNumber(v.g) && + !isNullOrUndefined(v.b) && + isNumber(v.b); + // + return result; + } + + // + if ( + isString(v) && + toXString(v).includes('rgb') && + !toXString(v).includes('rgba') + ) { + return true; + } + + // + return false; +} + +/** + * determine a color is XRGBAColor or not ... + * + * @param v an input color representation ... + */ +export function isRGBA(v: any) { + // + if (isNullOrUndefined(v)) { + return false; + } + + // + const color = v as XRGBAColor; + if (isNullOrUndefined(color)) { + // + const result = + !isNullOrUndefined(v.r) && + isNumber(v.r) && + !isNullOrUndefined(v.g) && + isNumber(v.g) && + !isNullOrUndefined(v.b) && + isNumber(v.b) && + !isNullOrUndefined(v.a) && + isNumber(v.a); + + // + return result; + } + + // + if (isString(v) && toXString(v).includes('rgba')) { + return true; + } + + // + return false; +} + +/** + * convert an rgb color to hex representation ... + * + * @param color an instance of XRGBAColor ... + */ +export function rgbaToHex(color: XRGBAColor): string { + // + const r = color.r < 0 ? 0 : color.r > 255 ? 255 : color.r; + const g = color.g < 0 ? 0 : color.g > 255 ? 255 : color.g; + const b = color.b < 0 ? 0 : color.b > 255 ? 255 : color.b; + const a = color.a < 0 ? 0 : color.a > 1 ? 1 : color.a; + + // + let sr = r.toString(16); + let sg = g.toString(16); + let sb = b.toString(16); + let sa = Math.round(a * 255).toString(16); + + // + sr = sr.length === 1 ? '0' + sr : sr; + sg = sg.length === 1 ? '0' + sg : sg; + sb = sb.length === 1 ? '0' + sb : sb; + sa = sa.length === 1 ? '0' + sa : sa; + + // + const result = '#' + sr + sg + sb + sa; + + // + return result; +} + +/** + * convert an rgb color to hex representation ... + * + * @param color an instance of XRGBAColor ... + * @param allowHex8 determines hext string length be 8 char or not ... + */ +export function rgbaToHex1( + color: XRGBAColor, + allowHex8: boolean = true +): string { + // + let result = + '#' + + // tslint:disable-next-line:no-bitwise + ((1 << 24) | (color.r << 16) | (color.g << 8) | color.b) + .toString(16) + .substr(1); + + // + if ( + result[1] === result[2] && + result[3] === result[4] && + result[5] === result[6] && + color.a === 1 && + !allowHex8 + ) { + result = '#' + result[1] + result[3] + result[5]; + } + + // + if (allowHex8) { + // tslint:disable-next-line:no-bitwise + result += ((1 << 8) | Math.round(color.a * 255)).toString(16).substr(1); + } + + // + return result; +} + +/** + * get an rgb string form color representation ... + * + * @param color an instance of XRGBAColor ... + */ +export function rgbString(color: XRGBAColor): string { + // + if (isNullOrUndefined(color)) { + return 'rgba(0, 0, 0, 1)'; + } + + // + const r = color.r < 0 ? 0 : color.r > 255 ? 255 : color.r; + const g = color.g < 0 ? 0 : color.g > 255 ? 255 : color.g; + const b = color.b < 0 ? 0 : color.b > 255 ? 255 : color.b; + + // + const result = 'rgb(' + r + ',' + g + ',' + b + ')'; + + // + return result; +} + +/** + * get an rgba string form color representation ... + * + * @param color an instance of XRGBAColor ... + */ +export function rgbaString(color: XRGBAColor): string { + // + if (isNullOrUndefined(color)) { + return 'rgba(0, 0, 0, 1)'; + } + + // + const r = color.r < 0 ? 0 : color.r > 255 ? 255 : color.r; + const g = color.g < 0 ? 0 : color.g > 255 ? 255 : color.g; + const b = color.b < 0 ? 0 : color.b > 255 ? 255 : color.b; + const a = color.a < 0 ? 0 : color.a > 1 ? 1 : color.a; + + // + const result = 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')'; + + // + return result; +} + +/** + * convert hsva to XHSVAColor ... + * + * @param rgba an XRGBAColor representation of color ... + */ +export function rgbaToHsva(rgba: XRGBAColor): XHSVAColor { + // + let result: XHSVAColor = null; + + // + // Validate Args ... + if (isNullOrUndefined(rgba)) { + return result; + } + + // + rgba.r /= 255; + rgba.g /= 255; + rgba.b /= 255; + + // + let a = Math.min(rgba.a, 1); + + // + let max = Math.max(rgba.r, rgba.g, rgba.b); + let min = Math.min(rgba.r, rgba.g, rgba.b); + + // + let h, + s, + v = max; + var d = max - min; + s = max == 0 ? 0 : d / max; + if (max == min) { + h = 0; // achromatic + } else { + switch (max) { + // + case rgba.r: + h = (rgba.g - rgba.b) / d + (rgba.g < rgba.b ? 6 : 0); + break; + + // + case rgba.g: + h = (rgba.b - rgba.r) / d + 2; + break; + + // + case rgba.b: + h = (rgba.r - rgba.g) / d + 4; + break; + } + + // + h /= 6; + } + + // + return { + h, + s, + v, + a + } +} + +/** + * converts an string to RGBA instance ... + * + * @param value input string value ... + * @returns an instance of XRGBAColor ... + */ +export function stringToRGBA(value: string): XRGBAColor { + // + // validate args ... + if (!isRGBA(value) || isNullOrEmptyString(value)) { + return null; + } + + // + // define default result ... + let result: XRGBAColor = null; + + // + // replace color token ... + value = value.includes('rgba') + ? value.replace('rgba', '') + : value.includes('rgb') + ? value.replace('rgb', '') + : value; + + // + // Replace paranteses ... + value = value.replace('(', '').replace(')', '').replace(';', ''); + + // + // Convert to array ... + const valuesArr = value.split(','); + if (valuesArr.length === 3) { + valuesArr.push('1'); + } + + // + // Prepare Materials ... + const r = toNumber(valuesArr[0]); + const g = toNumber(valuesArr[1]); + const b = toNumber(valuesArr[2]); + const a = toNumber(valuesArr[3]); + + // + // Create result ... + result = { + r, + g, + b, + a, + }; + + // + // return result ... + return result; +} + +/** + * denormalize XRGBAColor instance ... + * + * @param rgba an XRGBAColor representation of color ... + */ +export function denormalizeRGBA(color: XRGBAColor): XRGBAColor { + // + const result: XRGBAColor = { + r: Math.round(color.r * 255), + g: Math.round(color.g * 255), + b: Math.round(color.b * 255), + a: color.a, + }; + + // + return result; +} +//#endregion + +// +//#region HEX Source ... +/** + * convert hex to HSV ... + * + * @param color string representor ... + */ +export function hexToHsva(hex: string): XHSVAColor { + // + let result: XHSVAColor = null; + + // + // validate args ... + if (isNullOrEmptyString(hex) || !isHEX(hex)) { + return result; + } + + // + // normalize hex ... + hex = normalizeHexColor(hex, 8); + + // + // Convert hex to rgba ... + const rgba = hexToRgba(hex); + if (isNullOrUndefined(rgba)) { + return result; + } + + // + // Generate result ... + result = rgbaToHsva(rgba); + + // + // return result ... + return result; +} + +/** + * convert hex to rgba ... + * + * @param hex an string which represent color hex ... + */ +export function hexToRgba(hex: string): XRGBAColor { + // + let r = '0'; + let g = '0'; + let b = '0'; + let a: any = 1; + + // + if (hex.length === 7) { + // + r = '0x' + hex[1] + hex[1]; + g = '0x' + hex[2] + hex[2]; + b = '0x' + hex[3] + hex[3]; + a = '0x' + hex[4] + hex[4]; + } else if (hex.length === 9) { + // + r = '0x' + hex[1] + hex[2]; + g = '0x' + hex[3] + hex[4]; + b = '0x' + hex[5] + hex[6]; + a = '0x' + hex[7] + hex[8]; + } + + // + a = +(a / 255).toFixed(3); + + // + const result: XRGBAColor = { + r: +r, + g: +g, + b: +b, + a, + }; + + return result; +} + +/** + * + * @param hex an string ... + */ +export function normalizeHexColor(hex: string, forceLength: 6 | 8 = 8) { + // + const isValid = isHEX(hex); + if (!isValid) { + return ''; + } + + // + hex = hex.startsWith('#') ? hex.replace('#', '') : hex; + hex = hex.toLowerCase().trim(); + + // + let result = ''; + const hexLength = hex.length; + const hexArr = Array.from(hex); + + // + // 6 Length ... + if (forceLength === 6) { + // + if (hexLength === 6) { + result = hex; + } else if (hexLength === 3 || hexLength === 4) { + result = + hexArr[0] + hexArr[0] + hexArr[1] + hexArr[1] + hexArr[2] + hexArr[2]; + } + + // + return `#${result}`; + } + + // + // 8 Length ... + if (hexLength === 8) { + result = hex; + } else if (hexLength === 7) { + result = + hexArr[0] + + hexArr[1] + + hexArr[2] + + hexArr[3] + + hexArr[4] + + hexArr[5] + + 'f' + + hexArr[6]; + } else if (hexLength === 6) { + result = hex + 'ff'; + } else if (hexLength === 4) { + result = + hexArr[0] + + hexArr[0] + + hexArr[1] + + hexArr[1] + + hexArr[2] + + hexArr[2] + + hexArr[3] + + hexArr[3]; + } else if (hexLength === 3) { + result = + hexArr[0] + hexArr[0] + hexArr[1] + hexArr[1] + hexArr[2] + hexArr[2]; + } + + // + return `#${result}`; +} + +/** + * determines an string is hex color or not ... + * + * @param hex an string ... + * @returns boolean + */ +export function isHEX(hex: string) { + // + if (isNullOrEmptyString(hex) || isNullOrEmptyString(hex.trim())) { + return false; + } + + // + hex = hex.toLowerCase().trim().replace('#', ''); + + // + const availableLength = [3, 4, 6, 8]; + const availableDigits = [ + '0', + '1', + '2', + '3', + '4', + '5', + '6', + '7', + '8', + '9', + 'a', + 'b', + 'c', + 'd', + 'e', + 'f', + ]; + + // + let result = availableLength.findIndex((l) => hex.length === l) > -1; + if (!!result) { + result = Array.from(hex).every((char) => availableDigits.includes(char)); + } + + // + return result; +} +//#endregion + +// +//#region HSVA Source ... +/** + * determine a color is XRGBAColor or not ... + * + * @param v an input color representation ... + */ +export function isHSVA(v: any) { + // + if (isNullOrUndefined(v)) { + return false; + } + + // + const color = v as XHSVAColor; + if (isNullOrUndefined(color)) { + // + const result = + !isNullOrUndefined(v.h) && + isNumber(v.h) && + !isNullOrUndefined(v.s) && + isNumber(v.s) && + !isNullOrUndefined(v.v) && + isNumber(v.v) && + !isNullOrUndefined(v.a) && + isNumber(v.a); + + // + return result; + } + + // + return true; +} + +/** + * convert hsv to hex ... + * + * @param color an instance of XHSVColor ... + */ +export function hsvaToHex(color: XHSVAColor): string { + // + const i = Math.floor(color.h * 6); + const f = color.h * 6 - i; + const p = color.v * (1 - color.s); + const q = color.v * (1 - f * color.s); + const t = color.v * (1 - (1 - f) * color.s); + + // + let r = 0; + let g = 0; + let b = 0; + + // + switch (i % 6) { + case 0: + r = color.v; + g = t; + b = p; + break; + case 1: + r = q; + g = color.v; + b = p; + break; + case 2: + r = p; + g = color.v; + b = t; + break; + case 3: + r = p; + g = q; + b = color.v; + break; + case 4: + r = t; + g = p; + b = color.v; + break; + case 5: + r = color.v; + g = p; + b = q; + break; + } + + // + r = Math.floor(r * 255) + 256; + g = Math.floor(g * 255) + 256; + b = Math.floor(b * 255) + 256; + + // + const result = + '#' + + r.toString(16).slice(1) + + g.toString(16).slice(1) + + b.toString(16).slice(1); + + // + return result; +} + +/** + * convert hsva to XHSLAColor ... + * + * @param hsva an XHSVAColor representation of color ... + */ +export function hsvaToHsla(hsva: XHSVAColor): XHSLAColor { + // + let s = hsva.s; + const h = hsva.h; + const v = hsva.v; + const a = hsva.a; + + // + let result: XHSLAColor = {}; + + // + if (v === 0) { + // + result = { + h, + s: 0, + l: 0, + a, + }; + } else if (s === 0 && v === 1) { + // + result = { + h, + s: 1, + l: 1, + a, + }; + } else { + // + const l = (v * (2 - s)) / 2; + s = (v * s) / (1 - Math.abs(2 * l - 1)); + + // + result = { + h, + s, + l, + a, + }; + } + + // + return result; +} + +/** + * convert hsv to rgba ... + * + * @param color an instance of XHSVColor ... + */ +export function hsvaToRgba(hsva: XHSVAColor): XRGBAColor { + // + const h = hsva.h; + const s = hsva.s; + const v = hsva.v; + const a = hsva.a; + + // + let r: number; + let g: number; + let b: number; + + // + const i = Math.floor(h * 6); + const f = h * 6 - i; + const p = v * (1 - s); + const q = v * (1 - f * s); + const t = v * (1 - (1 - f) * s); + + // + switch (i % 6) { + case 0: + (r = v), (g = t), (b = p); + break; + case 1: + (r = q), (g = v), (b = p); + break; + case 2: + (r = p), (g = v), (b = t); + break; + case 3: + (r = p), (g = q), (b = v); + break; + case 4: + (r = t), (g = p), (b = v); + break; + case 5: + (r = v), (g = p), (b = q); + break; + } + + // + const result: XRGBAColor = { + r, + g, + b, + a, + }; + + // + return result; +} + +/** + * convert string representation of a color to XHSVAColor ... + * + * @param colorString an string representation of a color ... + */ +export function stringToHsva(colorString: string = ''): XHSVAColor { + // + // Validate Args ... + if (isNullOrEmptyString(colorString)) { + return null; + } + + // + // Result ... + let result: XHSVAColor = null; + + // + // Get Type ... + const type = isHEX(colorString) + ? 'hex' + : isRGBA(colorString) + ? 'rgba' + : isRGB(colorString) + ? 'rgb' + : isHSVA(colorString) + ? 'hsva' + : isHSLA(colorString) + ? 'hsla' + : 'unknown'; + + // + // get result value based on input type ... + switch (type) { + // + // 1 Hex ... + case 'hex': + result = hexToHsva(colorString); + break; + + // + // 2 RGB ... + // 3 RGBA ... + case 'rgb': + case 'rgba': + // + const rgba = stringToRGBA(colorString); + result = rgbaToHsva(rgba); + break; + + // + result = null; + break; + + // + // 5 HSLA ... + case 'hsla': + const hsla = stringToHsla(colorString); + result = hslaToHsva(hsla); + break; + + // + // 4 HSVA ... + // 6 Unknown ... + case 'hsva': + case 'unknown': + result = null; + break; + } + + // + // return result ... + return result; +} +//#endregion + +// +//#region HSLA Source ... +/** + * determine a color is XRGBAColor or not ... + * + * @param v an input color representation ... + */ +export function isHSLA(v: any) { + // + if (isNullOrUndefined(v)) { + return false; + } + + // + const color = v as XHSLAColor; + if (isNullOrUndefined(color)) { + // + const result = + !isNullOrUndefined(v.h) && + isNumber(v.h) && + !isNullOrUndefined(v.s) && + isNumber(v.s) && + !isNullOrUndefined(v.l) && + isNumber(v.l) && + !isNullOrUndefined(v.a) && + isNumber(v.a); + + // + return result; + } + + // + return true; +} + +/** + * convert hsla to XHSVAColor ... + * + * @param hsla an XHSLAColor representation of color ... + */ +export function hslaToHsva(hsla: XHSLAColor): XHSVAColor { + // + let s = Math.min(hsla.s, 1); + const h = Math.min(hsla.h, 1); + const l = Math.min(hsla.l, 1); + const a = Math.min(hsla.a, 1); + + // + let result: XHSVAColor = {}; + + // + if (l === 0) { + result = { + h, + s: 0, + v: 0, + a: 0, + }; + } else { + // + const v = l + (s * (1 - Math.abs(2 * l - 1))) / 2; + s = (2 * (v - l)) / v; + + // + result = { + h, + s, + v, + a, + }; + } + + // + return result; +} + +/** + * get an hsla string form color representation ... + * + * @param color an instance of XHSLAColor ... + */ +export function hslaString(color: XHSLAColor): string { + // + if (isNullOrUndefined(color)) { + return 'hsla(0, 100%, 100%, 1)'; + } + + // + const result = + 'hsla(' + color.h + ',' + color.s + '%,' + color.l + '%,' + color.a + ')'; + + // + return result; +} + +/** + * converts string to XHSLAColor ... + * + * @param value string value ... + * @returns instance of XHSLAColor instance ... + */ +export function stringToHsla(value: string): XHSLAColor { + // + let result: XHSLAColor = null; + + // + // Validate Args ... + if (isNullOrEmptyString(value) || !value.includes('hsla')) { + return result; + } + + // + // clear unused chars ... + const hslaArray = value + .replace('%', '') + .replace('(', '') + .replace(')', '') + .replace(';', '') + .split(','); + if (hslaArray.length < 4) { + return result; + } + + // + // generate required parts ... + const h = toNumber(hslaArray[0]); + const s = toNumber(hslaArray[1]); + const l = toNumber(hslaArray[2]); + const a = toNumber(hslaArray[3]); + + // + // Create result ... + result = { + h, + s, + l, + a, + }; + + // + // return result ... + return result; +} +//#endregion +//#endregion + +/** + * format input color ... + * + * @param hsva an XHSVAColor representation of a color ... + * @param format the type of output ... + * @param allowHex8 convert to 8 length string or not ... + */ +export function formatColor( + hsva: XHSVAColor, + format: XColorFormatIdentifier, + allowHex8: boolean +): string { + // + if (isNullOrUndefined(hsva)) { + hsva = { + h: 0, + s: 0, + v: 0, + a: 1, + }; + } + + // + if (hsva.a < 1) { + // + switch (format) { + // + case 'hsla': + // + const hsla = hsvaToHsla(hsva); + + // + const hslaColor: XHSLAColor = { + h: Math.round(hsla.h * 360), + s: Math.round(hsla.s * 100), + l: Math.round(hsla.l * 100), + a: Math.round(hsla.a * 100) / 100, + }; + + // + return hslaString(hslaColor); + + // + default: + // + if (allowHex8 && format === 'hex') { + return rgbaToHex1(denormalizeRGBA(hsvaToRgba(hsva)), allowHex8); + } + + // + const rgba = denormalizeRGBA(hsvaToRgba(hsva)); + rgba.a = Math.round(rgba.a * 100) / 100; + + // + return rgbaString(rgba); + } + } else { + // + switch (format) { + // + case 'hsla': + // + const hsla = hsvaToHsla(hsva); + + // + const hslaColor: XHSLAColor = { + h: Math.round(hsla.h * 360), + s: Math.round(hsla.s * 100), + l: Math.round(hsla.l * 100), + a: Math.round(hsla.a * 100) / 100, + }; + + // + return hslaString(hslaColor); + + // + case 'rgba': + // + const rgbaColor = denormalizeRGBA(hsvaToRgba(hsva)); + + // + return rgbaString(rgbaColor); + + // + default: + return rgbaToHex1(denormalizeRGBA(hsvaToRgba(hsva)), allowHex8); + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\tools\x-date-time.tools.ts + +import { + isDate, + isString, + isNumber, + isNullOrUndefined, + isNullOrEmptyString, +} from './x-type-detect.tools'; +import * as moment_ from 'jalali-moment'; +const moment = moment_; +import { Moment, unitOfTime } from 'jalali-moment'; +import { toXString } from './x-type-convert.tools'; +import { toPersianNumbers } from './x-string.tools'; +import { XLocale } from '../typings/x-translation.typings'; +import { XUnitOfTime } from '../typings/x-date-time.typings'; + +// +//#region Date Time Utils ... +export function isDefaultUTCDate(utcDate: Date | string | number): boolean { + // + var dateString = isNullOrUndefined(utcDate) + ? '0001-01-01T00:00:00' + : toXString(utcDate); + if ( + dateString === '0001-01-01T00:00:00' || + dateString === '1901-01-01T00:00:00.000Z' + ) { + return true; + } + + // + const mDateString = fromUtcDate(utcDate).toISOString(); + if ( + mDateString === '0001-01-01T00:00:00' || + mDateString === '1901-01-01T00:00:00.000Z' + ) { + return true; + } + + // + return false; +} + +/** + * convert a UTC date to locale ... + */ +export function fromUtcDate(utcDate: Date | string | number): Date { + // + const mUtcDate = new Date(utcDate); + + // + const utcDateObject = Date.UTC( + mUtcDate.getFullYear(), + mUtcDate.getMonth(), + mUtcDate.getDate(), + mUtcDate.getHours(), + mUtcDate.getMinutes(), + mUtcDate.getSeconds(), + mUtcDate.getMilliseconds() + ); + + // + return new Date(utcDateObject); +} + +export function fromUtcString(utcDateString: string): Date { + // + const utcDate = new Date(utcDateString); + + // + return fromUtcDate(utcDate); +} + +export function fromUtc(date: Date | string) { + return isDate(date) + ? fromUtcDate(date) + : isString(date) + ? fromUtcString(date.toString()) + : new Date(); +} + +/** + * format a Date to specific format + * and return the formatted string, based on forLocal ... + */ +export function formatDate( + date: Date | string, + forLocale: XLocale, + format: string, + isUtc: boolean = false, + toUtc: boolean = false +) { + // + let momentDate = isUtc ? moment(fromUtc(date)) : moment(date); + + // + if (toUtc) { + momentDate = momentDate.utc(); + } + + // + let result = momentDate.format(format); + + // + if (forLocale === 'fa-IR') { + result = toPersianNumbers(result); + } + + // + return result; +} + +export function getMomentInstance( + input?: string | number | Date, + isUtc?: boolean +): Moment { + // + let startDate = moment(input); + + // + if (isUtc) { + startDate = moment(input).utc(); + } + + // + return startDate; +} + +export function getDateFormatString(locale?: XLocale) { + // + if (locale === 'fa-IR') { + return 'HH:mm:ss jYYYY/jMM/jDD'; + } + + // + return 'YYYY-MM-DD HH:mm:ss'; +} + +export function getDateOfBirthDateFormatString(locale?: XLocale) { + // + if (locale === 'fa-IR') { + return 'jYYYY/jMM/jDD'; + } + + // + return 'YYYY/MM/DD'; +} + +export function getErrorDateFormatString(locale?: XLocale) { + // + if (locale === 'fa-IR') { + return 'HH:mm:ss-jYYYY/jMM/jDD'; + } + + // + return 'YYYY-MM-DD HH:mm:ss'; +} + +export function getRegularDateFormatString(locale?: XLocale) { + // + if (locale === 'fa-IR') { + return 'HH:mm:ss jYYYY-jMM-jDD'; + } + + // + return 'YYYY-MM-DD HH:mm:ss'; +} + +export function getJustYearFormatString(locale?: XLocale) { + // + if (locale === 'fa-IR') { + return 'jYYYY'; + } + + // + return 'YYYY'; +} + +export function getYear(momentDate: Moment) { + return momentDate.format('YYYY'); +} + +export function getMonth(momentDate: Moment) { + return momentDate.format('MM'); +} + +export function getDay(momentDate: Moment) { + return momentDate.format('DD'); +} + +export function getHours(momentDate: Moment) { + return momentDate.format('HH'); +} + +export function getMinutes(momentDate: Moment) { + return momentDate.format('mm'); +} + +export function getSeconds(momentDate: Moment) { + return momentDate.format('ss'); +} + +export function getMilliseconds(momentDate: Moment) { + return momentDate.format('SSS'); +} + +export function getUTCDate(date?: any) { + // + if (!date) { + date = new Date(); + } + + // + const mDate = new Date(date).toUTCString(); + + // + return new Date(mDate); +} + +export function getUTCDateString(date?: any) { + // + if (!date) { + date = new Date(); + } + + // + const mDate = new Date(date).toUTCString(); + + // + return mDate; +} + +export function createDate( + year?: string, + month?: string, + day?: string, + hours?: string, + minutes?: string, + seconds?: string, + milliseconds?: string +) { + // + const result = new Date(); + + // + if (year) { + result.setFullYear(+year); + } + + // + if (month) { + result.setMonth(+month - 1); + } + + // + if (day) { + result.setDate(+day); + } + + // + if (hours) { + result.setHours(+hours); + } + + // + if (minutes) { + result.setMinutes(+minutes); + } + + // + if (seconds) { + result.setSeconds(+seconds); + } + + // + if (milliseconds) { + result.setMilliseconds(+milliseconds); + } + + // + return result; +} + +export function getUnitOfTimeForLocale( + unit: XUnitOfTime, + locale: XLocale = 'en-US' +) { + // + let result: unitOfTime.DurationConstructor = null; + + // + switch (unit) { + // + case XUnitOfTime.Year: + // + switch (locale) { + // + case 'en-US': + result = 'year'; + break; + + // + case 'fa-IR': + result = 'jYear'; + break; + } + break; + + // + case XUnitOfTime.Month: + // + switch (locale) { + // + case 'en-US': + result = 'month'; + break; + + // + case 'fa-IR': + result = 'jMonth'; + break; + } + break; + + // + case XUnitOfTime.Day: + // + switch (locale) { + // + case 'en-US': + result = 'day'; + break; + + // + case 'fa-IR': + result = 'jDay'; + break; + } + break; + } + + // + return result; +} + +/** + * add specific amount of year to start date ... + * + * @param date start date ... + * @param value amount of specific unit of time ... + * @param locale the locale which required to date handling ... + */ +export function addYear( + date: string | number | Date, + value: number, + locale: XLocale = 'en-US' +) { + // + if (isNullOrUndefined(date) || isNullOrEmptyString(locale)) { + return getMomentInstance(date); + } + + // + let yearSpecifier = getUnitOfTimeForLocale(XUnitOfTime.Year, locale); + if (isNullOrUndefined(yearSpecifier)) { + yearSpecifier = 'year'; + } + + // + return getMomentInstance(date).add(value, yearSpecifier).toDate(); +} + +/** + * add specific amount of month to start date ... + * + * @param date start date ... + * @param value amount of specific unit of time ... + * @param locale the locale which required to date handling ... + */ +export function addMonth( + date: string | number | Date, + value: number, + locale: XLocale = 'en-US' +) { + // + if (isNullOrUndefined(date) || isNullOrEmptyString(locale)) { + return getMomentInstance(date); + } + + // + let yearSpecifier = getUnitOfTimeForLocale(XUnitOfTime.Month, locale); + if (isNullOrUndefined(yearSpecifier)) { + yearSpecifier = 'month'; + } + + // + return getMomentInstance(date).add(value, yearSpecifier).toDate(); +} + +/** + * add specific amount of days to start date ... + * + * @param date start date ... + * @param value amount of specific unit of time ... + * @param locale the locale which required to date handling ... + */ +export function addDay( + date: string | number | Date, + value: number, + locale: XLocale = 'en-US' +) { + // + if (isNullOrUndefined(date) || isNullOrEmptyString(locale)) { + return getMomentInstance(date); + } + + // + let yearSpecifier = getUnitOfTimeForLocale(XUnitOfTime.Day, locale); + if (isNullOrUndefined(yearSpecifier)) { + yearSpecifier = 'day'; + } + + // + return getMomentInstance(date).add(value, yearSpecifier).toDate(); +} + +export function addUnitOfTime( + unit?: XUnitOfTime, + value?: number, + date?: string | number | Date, + locale?: XLocale +) { + // + if (isNullOrUndefined(unit)) { + unit = XUnitOfTime.Year; + } + + // + if (isNullOrUndefined(date)) { + date = new Date(); + } + + // + if (isNullOrUndefined(locale)) { + locale = 'en-US'; + } + + // + if (isNullOrUndefined(value)) { + value = 0; + } + + // + return getMomentInstance(date) + .add(value, getUnitOfTimeForLocale(unit, locale)) + .toDate(); +} + +/** + * get date time format string of given value ... + */ +export function toTimeFormat(value: number) { + // + if (value < 10) { + return `0${value}`; + } else { + return `${value}`; + } +} + +export function addSecconds(value?: Date, secconds?: number) { + // + let result = new Date(); + + // + if (value) { + result = value; + } + + // + if (secconds) { + result = new Date(result.getTime() + secconds * 1000); + } + + // + return result; +} + +export function addMinutes(value?: Date, minutes?: number) { + return addSecconds(value, minutes ? minutes * 60 : minutes); +} + +export function addHours(value?: Date, hours?: number) { + return addMinutes(value, hours ? hours * 60 : hours); +} + +export function isExpiredDate(date: Date | string | number) { + // + const nowMills = new Date().getTime(); + const mDateTimeMills = new Date(date).getTime(); + + // + return nowMills > mDateTimeMills; +} + +export function isExpiredUTCDate(utcDate: Date | string | number) { + // + const nowUTCString = new Date().toUTCString(); + const nowMills = fromUtcDate(nowUTCString).getTime(); + const mUtcDateTimeMills = fromUtcDate(utcDate).getTime(); + + // + return nowMills > mUtcDateTimeMills; +} + +/** + * convert a date to it's milliseconds representation format ... + * + * @param date date representer + */ +export function getMills(date?: Date | any) { + // + let mDate: Date; + if (!date) { + mDate = new Date(); + } else { + mDate = new Date(date); + } + + // + return mDate.getTime(); +} + +/** + * convert any object to date ... + */ +export function momentToDate(date: any) { + return new Date(moment(date.toString()).format()); +} + +/** + * check and compare two date must be same ... + */ +export function isSameDate(sourceDate: any, destDate: any) { + // + const sourceTime = new Date(sourceDate).getTime(); + const destTime = new Date(destDate).getTime(); + + // + return sourceDate === destDate; +} + +/** + * check a given value has a date or not ... + * + * @param source given value to check ... + */ +export function isValueDate(source: any) { + // + let result = isDate(source); + if (!result && (isString(source) || isNumber(source))) { + // + let d: Date = null; + try { + d = new Date(source); + } catch {} + + // + result = !isNullOrUndefined(d) && isDate(d); + } + + // + return result; +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\tools\x-exception.tools.ts + +import { + keys, + getVal, + fromJson, + parseJson, + toJsonString, +} from './x-object.tools'; +import { toNumber } from './x-type-convert.tools'; +import { toNormalString } from './x-string.tools'; +import { HttpErrorResponse } from '@angular/common/http'; +import { XExceptions, XException } from '../typings/x-exception.typings'; +import { isNumber, isString, isNullOrUndefined } from './x-type-detect.tools'; +import { XExceptionIDs, Exceptions } from '../constants/x-exception.resources'; + +export function getXException(exception: XExceptionIDs | XExceptions) { + // + const isExId = isNumber(exception); + const exceptionKey = isExId ? getVal(XExceptionIDs, exception) : exception; + + // + return Exceptions[exceptionKey]; +} + +export function getXExceptionsArray() { + // + const result: XException[] = + keys(Exceptions).map((key: any) => Exceptions[key]) || []; + + // + return result; +} + +export function getXExceptionByErrorId(errorId: number) { + // + const result = getXExceptionsArray().find((e) => e.id === toNumber(errorId)); + return result; +} + +/** + * Prepare and Return Specific Error object + */ +export function getError(error: XExceptionIDs | XExceptions | XException) { + // + const isExId = isNumber(error); + const isExTitle = isString(error); + + // + return new Error( + toJsonString( + isExId + ? getXException(error as XExceptionIDs) + : isExTitle + ? getXException(error as XExceptions) + : (error as XException) + ) + ); +} + +/** + * extract Exception information of given error param + */ +export function getErrorException(err: any = undefined) { + // + if (isNullOrUndefined(err)) { + err = Exceptions.Unknown; + } + + // + let exception: XException; + + // + if (err instanceof HttpErrorResponse) { + err = err.error; + } else if (err instanceof Error) { + err = parseJson(err.message); + } else { + try { + err = parseJson(toNormalString(err)); + } catch {} + } + + // + if (isValidException(err)) { + exception = getXExceptionByErrorId(err.id); + } else if (isNumber(err) || isString(err)) { + exception = getXException(err); + } else if (err.id && isNumber(err.id)) { + exception = getXExceptionByErrorId(err.id); + } + + // + if (!exception) { + exception = Exceptions.Unknown; + } + + // + return exception; +} + +/** + * check an object is Valid Exception or not ... + */ +export function isValidException(err: any) { + // + if (isNullOrUndefined(err)) { + return false; + } + + // + if (err.id && isNumber(err.id) && err.message && isString(err.message)) { + return true; + } + + // + if (err.hasOwnProperty('id') && err.hasOwnProperty('message')) { + try { + const jExp = fromJson(err); + if (jExp.id && isNumber(jExp) && jExp.message && isString(jExp.message)) { + return true; + } + } catch {} + } + + // + const jsonStr = toNormalString(toJsonString(err)); + if (jsonStr.indexOf('id') !== -1 && jsonStr.indexOf('message') !== -1) { + return true; + } + + // + return false; +} + +export function throwException( + exception: XExceptionIDs | XExceptions | XException +): never { + // + throw getError(exception); +} + +export type XExceptionIdentifier = + | XExceptionIDs + | XExceptions + | XException + | string; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\tools\x-file.tools.ts + +import { + XMimeType, + XRouterTarget, + XMimeTypeIdentifier, + XRouterTargetIdentifier, +} from '../typings/x.typings'; +import { + isString, + isNullOrUndefined, + isNullOrEmptyString, +} from './x-type-detect.tools'; +import { + XFileType, + XFileInfos, + TypesMatcher, + XGeneralThumbnails, +} from '../constants/x-file.enum'; +import { hasChild } from './x-array.tools'; +import { generateUuid } from './x-uuid-tools'; +import { isSameObject } from './x-object.tools'; +import { toNormalString } from './x-string.tools'; +import { toXString } from './x-type-convert.tools'; +import { DomSanitizer } from '@angular/platform-browser'; +import { XResourceIDs } from '../constants/x-translation.resources'; + +// +//#region Definitions ... +export interface XFileBlob { + lastModified: number; + name: string; + size: number; + type: string; +} + +const defaultXFileBlob: XFileBlob = { + lastModified: 0, + name: '', + size: 0, + type: '', +}; +//#endregion + +// +//#region Helper Methdos ... +/** + * extract a file's extension ... + * + * @param fileName the file which going to get it's extension + */ +export function getExtension(fileName: string) { + // + const index = fileName.lastIndexOf('.'); + if (index === -1) { + return ''; + } + + // + return fileName.substr(index, fileName.length - index); +} + +/** + * find a file representer object ... + * + * @param path file path ... + * @returns an info object which represent informations about file ... + */ +export function getFileInfo(path: string) { + // + if (isNullOrEmptyString(path)) { + return null; + } + + // + const ext = getExtension(path); + if (isNullOrEmptyString(ext)) { + return null; + } + + // + const infoObj = XFileInfos.find( + (inf) => toNormalString(inf.extension) === toNormalString(ext) + ); + if (isNullOrUndefined(infoObj)) { + return null; + } + + // + return infoObj; +} + +/** + * determines an object instance is XFileBlob instance or not ... + * + * @param value any object instance ... + * @returns a boolean value ... + */ +export function isXFileBlob(value: any) { + return isSameObject(value, defaultXFileBlob); +} + +/** + * determines an extension's which exists or not in list + * + * @param allowedExtensions the extensions list + * @param fileExtension the file extensons which searched in list + */ +export function isExistsExtension( + allowedExtensions: string[], + fileExtension: string +) { + // + if (!fileExtension || !hasChild(allowedExtensions)) { + return false; + } + + // + let result = false; + allowedExtensions.forEach((ext) => { + // + if (ext.trim().toLowerCase() === fileExtension.trim().toLowerCase()) { + // + result = true; + } + }); + + // + return result; +} + +/** + * check a file extension is valid and exists in given collection ... + * @param file input source + * @param allowedExtensions allowed file extensions ... + */ +export function isExtensionOk(file: File, allowedExtensions: string[]) { + // + if (!file || !hasChild(allowedExtensions)) { + return false; + } + + // + const fileExt = getExtension(file.name); + const result = isExistsExtension(allowedExtensions, fileExt); + + // + return result; +} + +/** + * Determine the File Size Limitation Passed ... + * @param file File instance ... + */ +export function isSizeOk(file: File, maxSingleFileSize: number) { + return file.size <= maxSingleFileSize; +} + +/** + * Determine the File Size Limitation Passed ... + * @param file File instance ... + */ +export function isSizeOkForThumb(file: File, maxSingleFileThumbSize: number) { + return file.size <= maxSingleFileThumbSize; +} + +/** + * Determine the File is Image or not ... + * @param file File instance ... + */ +export function isImage(file: File | Blob | XFileBlob | string) { + // + if (file instanceof File || (file instanceof Blob && file.type)) { + return file.type.toLowerCase().indexOf(TypesMatcher.IMAGE) !== -1; + } else if (isXFileBlob(file)) { + // + const blob = file as XFileBlob; + const path = !isNullOrUndefined(blob) ? blob.name : ''; + const obj = getFileInfo(path); + if (isNullOrUndefined(obj)) { + return false; + } + + // + const result = + obj.type === XFileType.Image || + obj.type === XFileType.CoverImage || + obj.type === XFileType.ProfileImage; + return result; + } else if (isString(file)) { + // + const path = toXString(file); + const obj = getFileInfo(path); + if (isNullOrUndefined(obj)) { + return false; + } + + // + const result = + obj.type === XFileType.Image || + obj.type === XFileType.CoverImage || + obj.type === XFileType.ProfileImage; + return result; + } + + // + return false; +} + +/** + * Determine the File is Video or not ... + * @param file File instance ... + */ +export function isVideo(file: File | Blob | XFileBlob | string) { + // + if (file instanceof File || (file instanceof Blob && file.type)) { + return file.type.toLowerCase().indexOf(TypesMatcher.VIDEO) !== -1; + } else if (isXFileBlob(file)) { + // + const blob = file as XFileBlob; + const path = !isNullOrUndefined(blob) ? blob.name : ''; + const obj = getFileInfo(path); + if (isNullOrUndefined(obj)) { + return false; + } + + // + return obj.type === XFileType.Video; + } else if (isString(file)) { + // + const path = toXString(file); + const obj = getFileInfo(path); + if (isNullOrUndefined(obj)) { + return false; + } + + // + return obj.type === XFileType.Video; + } + + // + return false; +} + +/** + * Determine the File is Audio or not ... + * @param file File instance ... + */ +export function isAudio(file: File | Blob | XFileBlob | string) { + // + if (file instanceof File || (file instanceof Blob && file.type)) { + return file.type.toLowerCase().indexOf(TypesMatcher.AUDIO) !== -1; + } else if (isXFileBlob(file)) { + // + const blob = file as XFileBlob; + const path = !isNullOrUndefined(blob) ? blob.name : ''; + const obj = getFileInfo(path); + if (isNullOrUndefined(obj)) { + return false; + } + + // + return obj.type === XFileType.Video; + } else if (isString(file)) { + // + const path = toXString(file); + const obj = getFileInfo(path); + if (isNullOrUndefined(obj)) { + return false; + } + + // + return obj.type === XFileType.Video; + } + + // + return false; +} + +/** + * Determine the File is Document or not ... + * @param file File instance ... + */ +export function isDocument(file: File | Blob | XFileBlob | string) { + // + if (file instanceof File || (file instanceof Blob && file.type)) { + return !isImage(file) && !isAudio(file) && !isVideo(file); + } else if (isXFileBlob(file)) { + // + const blob = file as XFileBlob; + const path = !isNullOrUndefined(blob) ? blob.name : ''; + const obj = getFileInfo(path); + if (isNullOrUndefined(obj)) { + return false; + } + + // + return obj.type === XFileType.Document; + } else if (isString(file)) { + // + const path = toXString(file); + const obj = getFileInfo(path); + if (isNullOrUndefined(obj)) { + return false; + } + + // + return obj.type === XFileType.Document; + } + + // + return false; +} + +/** + * Check file size is ok and type is Image ... + * @param file File instance ... + */ +export function isImageAndSizeOk(file: File, maxSingleFileSize: number) { + return isSizeOk(file, maxSingleFileSize) && isImage(file); +} + +/** + * Check file size is ok and type is Audio ... + * @param file File instance ... + */ +export function isAudioAndSizeOk(file: File, maxSingleFileSize: number) { + return isSizeOk(file, maxSingleFileSize) && isAudio(file); +} + +/** + * Check file size is ok and type is Video ... + * @param file File instance ... + */ +export function isVideoAndSizeOk(file: File, maxSingleFileSize: number) { + return isSizeOk(file, maxSingleFileSize) && isVideo(file); +} + +/** + * Check file size is ok and type is Document ... + * @param file File instance ... + */ +export function isDocumentAndSizeOk(file: File, maxSingleFileSize: number) { + return isSizeOk(file, maxSingleFileSize) && isDocument(file); +} + +/** + * Check file size is ok and type is Image ... + * @param file File instance ... + */ +export function isImageAndSizeOkForThumb( + file: File, + maxSingleFileThumbSize: number +) { + return isSizeOkForThumb(file, maxSingleFileThumbSize) && isImage(file); +} + +/** + * Check file size is ok and type is Audio ... + * @param file File instance ... + */ +export function isAudioAndSizeOkForThumb( + file: File, + maxSingleFileThumbSize: number +) { + return isSizeOkForThumb(file, maxSingleFileThumbSize) && isAudio(file); +} + +/** + * Check file size is ok and type is Video ... + * @param file File instance ... + */ +export function isVideoAndSizeOkForThumb( + file: File, + maxSingleFileThumbSize: number +) { + return isSizeOkForThumb(file, maxSingleFileThumbSize) && isVideo(file); +} + +/** + * Check file size is ok and type is Document ... + * @param file File instance ... + */ +export function isDocumentAndSizeOkForThumb( + file: File, + maxSingleFileThumbSize: number +) { + return isSizeOkForThumb(file, maxSingleFileThumbSize) && isDocument(file); +} + +/** + * retrieve type of a file identifier ... + * + * @param file an identifier to a file ... + * @returns XFileType ... + */ +export function getFileType(file: File | Blob | XFileBlob | string | any) { + // + const isFile = file instanceof File || file instanceof Blob; + if (isFile) { + // + const mFile = file as Blob; + if (isImage(mFile)) { + return XFileType.Image; + } else if (isAudio(mFile)) { + return XFileType.Audio; + } else if (isVideo(mFile)) { + return XFileType.Video; + } else { + return XFileType.Document; + } + } else if ((file as any).type) { + return (file as any).type as XFileType; + } else if (isXFileBlob(file)) { + // + const obj = file as XFileBlob; + const path = obj.name; + const info = getFileInfo(path); + if (isNullOrUndefined(info)) { + return null; + } + + // + return info.type; + } else if (isString(file)) { + // + const path = toXString(file); + const info = getFileInfo(path); + if (isNullOrUndefined(info)) { + return null; + } + + // + return info.type; + } else { + return null; + } +} + +export function generateFileName(blob: Blob, prefix?: string) { + // + const type = getFileType(blob); + const result = `${ + isNullOrEmptyString(prefix) ? '' : prefix + '_' + }${generateUuid()}.${ + type === XFileType.Image + ? 'jpg' + : type === XFileType.Audio + ? 'mp3' + : type === XFileType.Video + ? 'mp4' + : 'doc' + }`; + + // + return result; +} + +export function getGeneralThumbnail( + file: File | Blob | XFileBlob | string | any +) { + // + let result = XGeneralThumbnails.UNKNOWN; + const type = getFileType(file); + + // + switch (type) { + // + case XFileType.Audio: + result = XGeneralThumbnails.AUDIO; + break; + + // + case XFileType.Video: + result = XGeneralThumbnails.VIDEO; + break; + case XFileType.Image: + result = XGeneralThumbnails.IMAGE; + break; + + // + case XFileType.Document: + default: + result = XGeneralThumbnails.DOCUMENT; + break; + } + + // + return result; +} + +/** + * represent File instance as FileBlob ... + * @param file the File instance + */ +export function toFileBlob(file: File): XFileBlob { + return { + lastModified: file.lastModified, + type: file.type, + name: file.name, + size: file.size, + } as XFileBlob; +} + +/** + * represent File instance as JSON String ... + * @param file the File instance + */ +export function fileToJsonString(file: File): string { + return JSON.stringify(toFileBlob(file)); +} + +/** + * parse FileBlob from a JSON String ... + * @param file the File instance + */ +export function fileFromJsonString(value: string): XFileBlob | null { + try { + return JSON.parse(value) as XFileBlob; + } catch { + return null; + } +} + +export function toFileBlobs(files: Set): XFileBlob[] { + // + const result: XFileBlob[] = []; + + // + files.forEach((f) => { + result.push(toFileBlob(f)); + }); + + // + return result; +} + +export function dataURLtoFile(dataurl: string | any, filename: string) { + // + const arr = dataurl.split(','); + const mime = arr[0].match(/:(.*?);/)[1]; + const bstr = atob(arr[1]); + let n = bstr.length; + const u8arr = new Uint8Array(n); + + while (n--) { + u8arr[n] = bstr.charCodeAt(n); + } + + // + return new File([u8arr], filename, { type: mime }); +} + +export function blobToFile(theBlob: Blob, fileName: string): File { + // + const b: any = theBlob; + + // + b.lastModifiedDate = new Date(); + b.name = fileName; + + // + return theBlob as File; +} + +export function getFileTypeResourceId(type: XFileType) { + switch (type) { + // + case XFileType.Audio: + return XResourceIDs.audio; + + // + case XFileType.Video: + return XResourceIDs.video; + + // + case XFileType.Document: + return XResourceIDs.document; + + // + case XFileType.ProfileImage: + return XResourceIDs.avatar; + + // + case XFileType.CoverImage: + return XResourceIDs.cover_image; + + // + case XFileType.Image: + return XResourceIDs.image; + + // + default: + return XResourceIDs.unknown; + } +} + +/** + * return file width and height ... + * + * @param file source image file + */ +export async function getImageDimensions(file: File) { + // + if (!file || !isImage(file)) { + throw new Error('Invalid Args ...'); + } + + // + return new Promise<{ width: number; height: number }>((res, err) => { + // + const reader: FileReader = new FileReader(); + + // + reader.onloadend = (e: any) => { + // + const dataUrl = e.target.result as string; + + // + const img = new Image(); + img.onload = () => { + // + res({ + width: img.width, + height: img.height, + }); + }; + + // + img.src = dataUrl; + }; + + // + reader.onerror = () => { + err('Error on Reading Image'); + }; + + // + reader.readAsDataURL(file); + }); +} + +export function isImageType(type: XFileType) { + switch (type) { + // + case XFileType.Image: + case XFileType.CoverImage: + case XFileType.ProfileImage: + return true; + + // + default: + return false; + } +} + +// +//#region Canvas & Image & ArrayBuffer & Blob & File Manipulation Tools ... +export function resetExifOrientation(srcBase64: string): Promise { + try { + const exifRotation = getExifRotation(srcBase64); + if (exifRotation > 1) { + return transformBase64BasedOnExifRotation(srcBase64, exifRotation); + } else { + return Promise.resolve(srcBase64); + } + } catch (ex) { + return Promise.reject(ex); + } +} + +export function transformBase64BasedOnExifRotation( + srcBase64: string, + exifRotation: number +): Promise { + return new Promise((resolve, reject) => { + const img = new Image(); + img.onload = () => { + const width = img.width; + const height = img.height; + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d'); + + if (ctx) { + if (4 < exifRotation && exifRotation < 9) { + canvas.width = height; + canvas.height = width; + } else { + canvas.width = width; + canvas.height = height; + } + transformCanvas(ctx, exifRotation, width, height); + ctx.drawImage(img, 0, 0); + resolve(canvas.toDataURL()); + } else { + reject(new Error('No context')); + } + }; + img.src = srcBase64; + }); +} + +export function resizeCanvas( + canvas: HTMLCanvasElement, + width: number, + height: number, + // tslint:disable-next-line:no-shadowed-variable + resizeCanvas: boolean = true +) { + const widthSource = canvas.width; + const heightSource = canvas.height; + width = Math.round(width); + height = Math.round(height); + + const ratioW = widthSource / width; + const ratioH = heightSource / height; + const ratioWHalf = Math.ceil(ratioW / 2); + const ratioHHalf = Math.ceil(ratioH / 2); + + const ctx = canvas.getContext('2d'); + if (ctx) { + const img = ctx.getImageData(0, 0, widthSource, heightSource); + const img2 = ctx.createImageData(width, height); + const data = img.data; + const data2 = img2.data; + + for (let j = 0; j < height; j++) { + for (let i = 0; i < width; i++) { + // + const x2 = (i + j * width) * 4; + const centerY = j * ratioH; + let weight = 0; + let weights = 0; + let weightsAlpha = 0; + let gxR = 0; + let gxG = 0; + let gxB = 0; + let gxA = 0; + + // + const xxStart = Math.floor(i * ratioW); + const yyStart = Math.floor(j * ratioH); + + // + let xxStop = Math.ceil((i + 1) * ratioW); + let yyStop = Math.ceil((j + 1) * ratioH); + xxStop = Math.min(xxStop, widthSource); + yyStop = Math.min(yyStop, heightSource); + + // + for (let yy = yyStart; yy < yyStop; yy++) { + // + const dy = Math.abs(centerY - yy) / ratioHHalf; + const centerX = i * ratioW; + const w0 = dy * dy; // pre-calc part of w + + // + for (let xx = xxStart; xx < xxStop; xx++) { + // + const dx = Math.abs(centerX - xx) / ratioWHalf; + const w = Math.sqrt(w0 + dx * dx); + + // + if (w >= 1) { + // pixel too far + continue; + } + + // + // hermite filter + weight = 2 * w * w * w - 3 * w * w + 1; + const posX = 4 * (xx + yy * widthSource); + + // + // alpha + gxA += weight * data[posX + 3]; + weightsAlpha += weight; + + // + // colors + if (data[posX + 3] < 255) { + weight = (weight * data[posX + 3]) / 250; + } + gxR += weight * data[posX]; + gxG += weight * data[posX + 1]; + gxB += weight * data[posX + 2]; + weights += weight; + } + } + + // + data2[x2] = gxR / weights; + data2[x2 + 1] = gxG / weights; + data2[x2 + 2] = gxB / weights; + data2[x2 + 3] = gxA / weightsAlpha; + } + } + + // + // clear and resize canvas + if (resizeCanvas) { + // + canvas.width = width; + canvas.height = height; + } else { + ctx.clearRect(0, 0, widthSource, heightSource); + } + + // + // draw + ctx.putImageData(img2, 0, 0); + } +} + +// +export function getExifRotation(imageBase64: string): number { + // + const view = new DataView(base64ToArrayBuffer(imageBase64)); + if (view.getUint16(0, false) !== 0xffd8) { + return -2; + } + + // + const length = view.byteLength; + let offset = 2; + while (offset < length) { + // + if (view.getUint16(offset + 2, false) <= 8) { + return -1; + } + + // + const marker = view.getUint16(offset, false); + offset += 2; + if (marker === 0xffe1) { + // + if (view.getUint32((offset += 2), false) !== 0x45786966) { + return -1; + } + + // + const little = view.getUint16((offset += 6), false) === 0x4949; + offset += view.getUint32(offset + 4, little); + const tags = view.getUint16(offset, little); + offset += 2; + + // + for (let i = 0; i < tags; i++) { + if (view.getUint16(offset + i * 12, little) === 0x0112) { + return view.getUint16(offset + i * 12 + 8, little); + } + } + // tslint:disable-next-line:no-bitwise + } else if ((marker & 0xff00) !== 0xff00) { + break; + } else { + offset += view.getUint16(offset, false); + } + } + return -1; +} + +export function base64ToArrayBuffer(imageBase64: string) { + // + imageBase64 = imageBase64.replace(/^data\:([^\;]+)\;base64,/gim, ''); + const binaryString = atob(imageBase64); + const len = binaryString.length; + const bytes = new Uint8Array(len); + + // + for (let i = 0; i < len; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + + // + return bytes.buffer; +} + +export function transformCanvas( + ctx: any, + orientation: number, + width: number, + height: number +) { + switch (orientation) { + case 2: + ctx.transform(-1, 0, 0, 1, width, 0); + break; + case 3: + ctx.transform(-1, 0, 0, -1, width, height); + break; + case 4: + ctx.transform(1, 0, 0, -1, 0, height); + break; + case 5: + ctx.transform(0, 1, 1, 0, 0, 0); + break; + case 6: + ctx.transform(0, 1, -1, 0, height, 0); + break; + case 7: + ctx.transform(0, -1, -1, 0, height, width); + break; + case 8: + ctx.transform(0, -1, 1, 0, 0, width); + break; + } +} + +export function arrayBufferToBase64( + arrBuffer: ArrayBuffer | SharedArrayBuffer +) { + // + try { + const base64 = btoa( + new Uint8Array(arrBuffer).reduce( + (data, byte) => data + String.fromCharCode(byte), + '' + ) + ); + + // + return base64; + } catch { + return null; + } +} + +export function arrayBufferToBase64Image( + sanitizer: DomSanitizer, + arrBuffer: ArrayBuffer | SharedArrayBuffer +) { + // + try { + // + const base64 = arrayBufferToBase64(arrBuffer); + + // + const imageData = 'data:image/png;base64,' + base64; + return sanitizer.bypassSecurityTrustUrl(imageData); + } catch { + return null; + } +} + +export function getBlobURL(fileName: string, fileBlob: Blob) { + const result = window.URL.createObjectURL(blobToFile(fileBlob, fileName)); + return result; +} + +export function toSafeUrl(sanitizer: DomSanitizer, url: any) { + return sanitizer.bypassSecurityTrustUrl(url); +} + +export function removeElement(element: any) { + // + if (element) { + element.remove(); + } + + // + element = null; +} + +export async function createCanvas(width: number, height: number) { + // + const result = document.createElement('canvas'); + + // + result.width = width; + result.height = height; + + // + return Promise.resolve(result); +} + +export async function clipImageToCanvas( + image: CanvasImageSource, + clipStartX: number, + clipStartY: number, + clipWidth: number, + clipHeight: number +) { + // + const canvas = await createCanvas(Math.abs(clipWidth), Math.abs(clipHeight)); + // + const context = canvas.getContext('2d'); + // + context.drawImage( + image, + clipStartX, + clipStartY, + clipWidth, + clipHeight, + 0, + 0, + canvas.width, + canvas.height + ); + // + removeElement(image); + return canvas; +} + +export async function dataUrlToImage( + dataUrl: string +): Promise { + // + return new Promise((resolve) => { + // + if (!dataUrl) { + resolve(null); + } + + // + const image = new Image(); + + // + image.onload = () => { + resolve(image); + }; + + // + image.onerror = () => { + resolve(null); + }; + + // + image.src = dataUrl; + }); +} + +export async function blobToImage(blob: Blob): Promise { + // + return new Promise((resolve) => { + // + if (!blob) { + resolve(null); + } + + // + const url = URL.createObjectURL(blob); + const image = new Image(); + + // + image.onload = () => { + // + URL.revokeObjectURL(url); + + // + resolve(image); + }; + + // + image.onerror = () => { + resolve(null); + }; + + // + image.src = url; + }); +} + +export async function canvasToDataUrl( + canvas: HTMLCanvasElement, + mimeType?: XMimeTypeIdentifier +) { + // + if (!canvas) { + return null; + } + + // + if (!mimeType) { + mimeType = XMimeType.ImagePNG; + } + + // + const result = canvas.toDataURL(mimeType); + + // + return Promise.resolve(result); +} + +export async function canvasToBlob( + canvas: HTMLCanvasElement, + mimeType?: XMimeTypeIdentifier, + quality?: number +): Promise { + // + return new Promise((resolve) => { + // + if (!canvas) { + resolve(null); + } + + // + if (!mimeType) { + mimeType = XMimeType.ImagePNG; + } + + // + if (!quality || quality > 1 || quality < 0) { + quality = 1; + } + + // + canvas.toBlob( + (blob) => { + resolve(blob); + }, + mimeType, + quality + ); + }); +} + +export async function downloadCanvas( + canvas: HTMLCanvasElement, + mimeType?: XMimeTypeIdentifier, + fileName?: string +) { + // + if (!canvas) { + return; + } + + // + if (!fileName) { + fileName = `${generateUuid()}.png`; + } + + // + const dataUrl = await canvasToDataUrl(canvas, mimeType); + if (!dataUrl) { + return; + } + + // + downloadFileByUrl(dataUrl, fileName); +} +//#endregion + +// +//#region Download File Actions ... +export function downloadArrayBufferedFile(fileName: string, fileBlob: Blob) { + // + try { + // + const url = getBlobURL(fileName, fileBlob); + + // + const downloadAnchor = document.createElement('a'); + downloadAnchor.href = url; + downloadAnchor.download = fileName; + + // + downloadAnchor.click(); + } catch {} +} + +export function downloadFileByUrl(url: string, fileName: string) { + // + const downloadAnchor = document.createElement('a'); + + // + downloadAnchor.href = url; + downloadAnchor.target = XRouterTarget.BLANK; + downloadAnchor.download = fileName; + + // + downloadAnchor.click(); + downloadAnchor.remove(); +} + +/** + * Method is use to download file. + * + * @param data - Array Buffer data + * @param type - type of the document. + * @param fileName - name of file. + * @param target - link target. + */ +export function downloadFile( + data: any, + mimeType?: string, + fileName?: string, + target: XRouterTargetIdentifier = XRouterTarget.BLANK +): void { + // + if (isNullOrUndefined(data)) { + return; + } + + // + // Wrap by try/catch ... + try { + // + // Prepare Blob based on mimeType ... + let blob: Blob = null; + if (isNullOrEmptyString(mimeType)) { + blob = new Blob([data]); + } else { + blob = new Blob([data], { type: mimeType }); + } + + // + // Prepare Url ... + const url = window.URL.createObjectURL(blob); + + // + // Create Anchor ... + const downloadAnchor = document.createElement('a'); + downloadAnchor.href = url; + + // + // Prepare Target ... + if (!isNullOrEmptyString(target)) { + downloadAnchor.target = target; + } + + // + // Attach FileName ... + if (!isNullOrEmptyString(fileName)) { + downloadAnchor.download = fileName; + } + + // + // Click to Download ... + downloadAnchor.click(); + } catch {} +} +//#endregion +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\tools\x-formatter.tools.ts + +import { hasChild } from './x-array.tools'; +import { removeAll } from './x-string.tools'; +import { throwException } from './x-exception.tools'; +import { isNullOrEmptyString } from './x-type-detect.tools'; +import { applyLocale } from '../typings/x-translation.typings'; +import { XExceptionIDs } from '../constants/x-exception.resources'; +import { XFormatterOption, XFormatterType } from '../typings/x.typings'; + +/** + * apply a format to a string ... + * + * @param options format options ... + * @param value a value which required to format ... + */ +export function applyFormat(options: XFormatterOption, value: string) { + // + value = value ? value.toString() : ''; + options = { + ...(!options ? { type: XFormatterType.ApplyLocale } : options), + }; + + // + const type = options.type; + + // + // Validate Options Based on Type ... + switch (type) { + // + case XFormatterType.ApplyLocale: + validateLocale(options); + break; + + // + case XFormatterType.FormatCurrencyAndApplyLocale: + validateLocale(options); + validateCurrency(options); + validateFormatter(options); + break; + + // + case XFormatterType.StringFormatterAndApplyLocale: + validateLocale(options); + validatePattern(options); + validateFormatter(options); + break; + + // + case XFormatterType.FormatCurrency: + validateCurrency(options); + validateFormatter(options); + break; + + // + case XFormatterType.StringFormatter: + validatePattern(options); + validateFormatter(options); + break; + } + + // + // Prepared Formatted Value ... + let result = isCurrencyType(options) + ? getCurrencyFormattedValue(options, value) + : isStringFormatterType(options) + ? getStringFormattedValue(options, value) + : value || ''; + + // + // Apply Locale if Localeable Input Type ... + if (isLocaleType(options)) { + result = getLocaleFormattedValue(options, result); + } + + // + return result; +} + +/** + * clear applied format ... + * + * @param options format options ... + * @param clearLocale clear applies locale ... + * @param value a value wich using to clear formatt ... + */ +export function clearFormat( + options: XFormatterOption, + clearLocale: boolean, + value: string +) { + // + let result = value.toString() || ''; + + // + const seperator = isNullOrEmptyString(options.seperator) + ? '' + : options.seperator; + + // + // Remove All occurence of seperator ... + result = removeAll(value, seperator, true); + + // + // Currency Lable ... + if (isCurrencyType(options) && !isNullOrEmptyString(options.currencyLable)) { + result = result.replace(options.currencyLable, '').trim(); + } + + // + // Change Locale to Default ... + if (isLocaleType(options)) { + result = !isNullOrEmptyString(result) + ? applyLocale( + result, + clearLocale ? 'en-US' : options.locale ? options.locale : 'en-US' + ) + : ''; + } + + // + return result; +} + +// +//#region applyFormat Function Helpers ... +// +function validateLocale(options: XFormatterOption) { + // + // || isNullOrEmptyString(options.locale) + if (!options) { + throwInvalidArgException(); + } +} + +// +function validateCurrency(options: XFormatterOption) { + // + // || isNullOrEmptyString(options.currencyLable) + if (!options) { + throwInvalidArgException(); + } +} + +// +function validateFormatter(options: XFormatterOption) { + // + // || isNullOrEmptyString(options.seperator) + if (!options) { + throwInvalidArgException(); + } +} + +// +function validatePattern(options: XFormatterOption) { + if (!options || !options.pattern) { + throwInvalidArgException(); + } +} + +// +function isCurrencyType(options: XFormatterOption) { + // + if (!options || !options.type) { + return false; + } + + // + switch (options.type) { + // + case XFormatterType.FormatCurrency: + case XFormatterType.FormatCurrencyAndApplyLocale: + return true; + + // + default: + return false; + } +} + +// +function isStringFormatterType(options: XFormatterOption) { + // + if (!options || !options.type) { + return false; + } + + // + switch (options.type) { + // + case XFormatterType.StringFormatter: + case XFormatterType.StringFormatterAndApplyLocale: + return true; + + // + default: + return false; + } +} + +// +function isLocaleType(options: XFormatterOption) { + // + if (!options || !options.type) { + return false; + } + + // + switch (options.type) { + // + case XFormatterType.ApplyLocale: + case XFormatterType.FormatCurrencyAndApplyLocale: + case XFormatterType.StringFormatterAndApplyLocale: + return true; + + // + default: + return false; + } +} + +// +function throwInvalidArgException() { + throwException(XExceptionIDs.InvalidArgs); +} + +// +function getLocaleFormattedValue(options: XFormatterOption, value: any) { + // + const mValue = value ? (value.toString() as string) : ''; + return applyLocale(mValue, options.locale); +} + +// +function getStringFormattedValue(options: XFormatterOption, value: any) { + // + const mValue = value ? (value.toString() as string) : ''; + + // + if (!hasChild(options.pattern)) { + return mValue; + } + + // + let mSource = mValue; + let mResultValue = ''; + options.pattern.forEach((l, i) => { + if (mSource.length >= l) { + // + const selected = mSource.substring(0, l); + + // + mResultValue += + i === options.pattern.length + ? selected + : `${selected}${options.seperator}`; + + // + mSource = mSource.substr(l, mValue.length); + } + }); + + // + if (mSource.length > 0) { + mResultValue += `${mSource}`; + } + + // + return mResultValue; +} + +// +function getCurrencyFormattedValue(options: XFormatterOption, value: any) { + // + const mValue = value ? ((+value).toString() as string) : ''; + const formattedValue = mValue.replace( + /\B(?=(\d{3})+(?!\d))/g, + options.seperator + ); + + // + const result = !isNullOrEmptyString(options.currencyLable) + ? `${formattedValue} ${options.currencyLable}` + : formattedValue; + + // + return result; +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\tools\x-number.tools.ts + +// +//#region Number Tools ... +/** + * generate a random number in specific range ... + */ +export function random(min?: number, max?: number) { + // + if (!min) { + min = 0; + } + + // + if (!max) { + max = 1000; + } + + // + min = Math.ceil(min); + max = Math.floor(max); + + // + return Math.floor(Math.random() * (max - min + 1)) + min; +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\tools\x-object.tools.ts + +import { + isDate, + isArray, + getType, + isObject, + DataTypes, + isNullOrUndefined, + isNullOrEmptyString, +} from './x-type-detect.tools'; +import { + XDeep, + XDeepPartial, + XBaseKeyValue, + XModelFieldTypeIndex, +} from '../typings/x.typings'; +import { Type } from '@angular/core'; +import { isSameDate } from './x-date-time.tools'; +import { countChild, hasChild, lastItem, onlyUniques } from './x-array.tools'; + +// +//#region Object Tools ... +/** + * this is an extension on objects which returns a not refrence copy of an object ... + */ +export function clone(obj: any) { + return Object.assign({}, obj); +} + +/** + * clone an object using JSON ... + * + * @param obj source object + */ +export function cloneJson(obj: T) { + return JSON.parse(JSON.stringify(obj)) as T; +} + +/** + * an extension on objects which convert an object to string ... + * + * @param obj source object + */ +export function toJsonString(obj: any) { + return JSON.stringify(obj); +} + +/** + * parse a json string to specific type ... + * + * @param jsonString a json string to parse ... + */ +export function parseJson(jsonString: string) { + return JSON.parse(jsonString) as T; +} + +/** + * convert object to json string and parse from it ... + * + * @param val any object ... + */ +export function fromJson(val: any): T { + return JSON.parse(JSON.stringify(val)) as T; +} + +/** + * assign a Value to an Object ... + * + * @param source source object which attached values to it ... + * @param keyValues key/value pairs to attach to source ... + */ +export function assign( + source: object, + ...keyValues: { key: string; value: any }[] +) { + // + keyValues.forEach((kv) => { + source[kv.key] = kv.value; + }); + + // + return source; +} + +/** + * an extension on objects which returns its keys array ... + * + * @param obj source object + */ +export function keys(obj: any) { + // + if (isNullOrUndefined(obj)) { + return []; + } + + // + return Object.keys(obj); +} + +/** + * return entries of objects ... + * + * @param obj object + */ +export function entries(obj: any) { + return Object.entries(obj); +} + +/** + * an extension on objects which returns its values array ... + * + * @param obj source object + */ +export function values(obj: any) { + return Object.values(obj); +} + +/** + * iterate over object keys ... + * + * @param obj source object + */ +export function iterate(obj: any) { + // + if (isObject(obj)) { + // + for (const property in obj) { + // + if (obj.hasOwnProperty(property)) { + // + if (isObject(obj[property])) { + iterate(obj[property]); + } + } + } + } +} + +/** + * retrieve prototype of an object ... + * + * @param object source object ... + */ +export function getPrototypeOf(object: any) { + if (Object.getPrototypeOf) { + return Object.getPrototypeOf(object); + } else { + return object.constructor.prototype; + } +} + +/** + * retrieve Object Key name ... + * + * @param propName Object Prop Name + */ +export function nameof(propName: keyof T) { + return propName; +} + +/** + * retrieve Object Key name ... + * + * @param propName Object Prop Name + */ +export function nameofType(type: Type, propName: keyof T) { + return propName.toString(); +} + +/** + * retrieve Object Key name ... + * + * @param propName Object Prop Name + */ +export function nameofInstance(obj: T, propName: keyof T) { + return propName.toString(); +} + +/** + * Retrieve Property Type ... + * + * @param obj + * @param key + * @returns + */ +export function getPropertyType(obj: T, key: K): T[K] { + return obj[key]; +} + +/** + * Merge two Object ... + * + * @param argA Object A + * @param argB Object B + */ +export function merge(argA: Partial, argB: Partial): Partial | any { + // + const result = {} as Partial; + + // + // Handle ARG A ... + if (argA) { + // + const aKeys = keys(argA); + if (hasChild(aKeys)) { + // + aKeys.forEach((k) => { + // + const aValue = argA[k]; + if (aValue) { + result[k] = aValue; + } + }); + } + } + + // + // Handle ARG B ... + if (argB) { + // + const bKeys = keys(argB); + if (hasChild(bKeys)) { + // + bKeys.forEach((k) => { + // + const bValue = argB[k]; + if (bValue) { + result[k] = bValue; + } + }); + } + } + + // + return result; +} + +export function mergeAll( + source: Partial, + ...args: Partial[] +): Partial | any { + // + let result = { + ...source, + } as Partial; + + // + args.forEach((arg) => { + result = merge(result, arg); + }); + + // + return result; +} + +/** + * extract partial content of an object ... + * + * @param source source object ... + * @param modelKeys the object keys for extracting ... + */ +export function toPartialObject( + source: T, + modelKeys: Array> +) { + // + if (!source || !hasChild(modelKeys)) { + return null; + } + + // + const result: Partial = {}; + modelKeys.forEach((k) => { + if (source[k]) { + assign(result, { key: k.toString(), value: source[k] }); + } + }); + + // + return result; +} + +/** + * convert a literal object to map ... + * + * @param obj an object to convert ... + */ +export function toMap(obj: any): Map { + return new Map(Object.entries(obj)); +} + +/** + * retrieve property value ... + * + * @param object the context object ... + * @param prop the property name ... + */ +export function getValue(object: T, prop: keyof T) { + // + if (object && object[prop]) { + return object[prop]; + } else { + return ''; + } +} + +/** + * retrieve and return an object Filed Names ... + * + * @param object the input object of a type ... + */ +export function getFieldNames(object: T): XModelFieldTypeIndex { + return mergeAll>( + {}, + ...(Object.keys(object).map((k) => { + return assign({}, { key: k, value: k }); + }) as XModelFieldTypeIndex[]) + ); +} + +/** + * retrieve a property value from an object by providing path + * + * @param object the model which going to parse + * @param propertyPath the . seperated property path + * @returns the object value for specified property + */ +export function getPropertyValue(object: T, propertyPath?: string) { + // + if (!object || isNullOrEmptyString(propertyPath)) { + return object; + } + + // + var prop, + props = propertyPath.split('.'); + + // + for (var i = 0, iLen = props.length - 1; i < iLen; i++) { + // + prop = props[i]; + + // + var candidate = object[prop]; + + // + if (candidate !== undefined) { + object = candidate; + } else { + break; + } + } + + // + return object[props[i]]; +} + +/** + * set value to specified nested object model ... + * + * @param object the model which going to parse + * @param propertyPath the . seperated property path + * @param value the value which needs to assign + * @param forceCreate if object is undefined create it + */ +export function setPropertyValue( + object: T, + propertyPath: string, + value?: any, + forceCreate?: boolean +) { + // + if (!object || isNullOrEmptyString(propertyPath)) { + return; + } + + // + var prop, + props = propertyPath.split('.'); + + // + for (var i = 0, iLen = props.length - 1; i < iLen; i++) { + // + prop = props[i]; + + // + var candidate = object[prop]; + + // + if (candidate !== undefined) { + object = candidate; + } else { + break; + } + } + + // + object[props[i]] = value; +} + +/** + * retrieve all available property paths of an object + * + * @param object the model which going to parse + * @param prefix the prefix path if reeds to assign + * @param extractSchema if true extracted arrays instead of objects + * @param containsChildsObjectHeaders if true the result will contains nested objects headers + * @returns an array of string which contains all property paths which mach the specified options + */ +export function getPropertyPaths( + object: T, + prefix?: string, + extractSchema: boolean = false, + containsChildsObjectHeaders: boolean = false +) { + // + // Create empty result Array ... + const result: string[] = []; + + // + // Check object exists ... + if (!object) { + return result; + } + + // + // get Object keys and check object contains key ... + const objectKeys = keys(object); + if (!hasChild(objectKeys)) { + return result; + } + + // + // loop through keys ... + for (let i = 0; i < objectKeys.length; i++) { + // + //#region Extarct required info ... + // + // Current key for process ... + const key = objectKeys[i]; + + // + // the master object value based on current key ... + const value = object[key]; + + // + // prepare prefix for current key ... + const keyPath = isNullOrEmptyString(prefix) ? key : `${prefix}.${key}`; + //#endregion + + // + // determine the value related to current key is an iterable object + // and required to handle it as nested object or not ... + const canResurse = isObject(value) || isArray(value); + if (canResurse) { + // + // if value must recursivly checked (Nested Objects) ... + + // + // if containsChildsObjectHeaders set to true add keyPath to result ... + if (!!containsChildsObjectHeaders) { + result.push(keyPath); + } + + // + // define current value empty paths array ... + let partPaths: string[] = []; + if (!!extractSchema) { + // + // if extractSchema set to true ... + + // + // For array nested values add .0 path az first argumentt ... + if (isArray(value)) { + result.push(`${keyPath}.0`); + } + + // + // get value path parts recursively based on type of value ... + partPaths = isArray(value) + ? // + // for Array Values retrieve only first childs type paths ... + getPropertyPaths( + (value as any[])[0], + `${keyPath}.0`, + extractSchema, + containsChildsObjectHeaders + ) + : // + // for Object Values retrieve all including keys ... + getPropertyPaths( + value, + keyPath, + extractSchema, + containsChildsObjectHeaders + ); + } else { + // + // if extractSchema set to false ... + // retrieve all available paths recursivly as an Object ... + partPaths = getPropertyPaths( + value, + keyPath, + extractSchema, + containsChildsObjectHeaders + ); + } + + // + // Check part Paths hasChild or not and attach proper values to result ... + if (hasChild(partPaths)) { + result.push(...partPaths); + } else { + result.push(keyPath); + } + } else { + // + // if value isprimitive type and no Nested Object ... + result.push(keyPath); + } + } + + // + // return result ... + return result; +} + +/** + * retrieve an specified object schema property paths ... + * + * @param object the model which going to parse + * @returns an array of string which contains all object's schema property paths + */ +export function getSchemaPaths(object: T) { + return getPropertyPaths(object, undefined, true, true); +} + +/** + * generate an object based on given property paths ... + * + * @param paths an string array which represent all property paths of an Object ... + * @param defaultValue determines default value which set to generated object's primitive properties ... + * @returns an specified type object + */ +export function generateObjectFromPaths( + paths: string[], + assginPropertyName: boolean = false, + defaultValue: any = null +): XDeep { + // + // Check paths contains childs ... + if (!hasChild(paths)) { + return null; + } + + // + // create empty result ... + let result = {} as XDeep; + + // + // loop through all paths ... + for (let i = 0; i < paths.length; i++) { + // + // current property path ... + const path = paths[i]; + + // + // filter all paths and exclude current path from them ... + const filteredPaths = paths.filter((p) => p.includes(path) && p !== path); + + // + // if filtered paths contains any childs this means current property is a nested object or array ... + const isNode = countChild(filteredPaths) > 0; + + // + // if filtered paths contains any childs including current path +'.0' this means current property is a nested array ... + const isArray = isNode && filteredPaths.includes(`${path}.0`); + + // + if (!!assginPropertyName) { + // + const pathParts = path.includes('.') ? path.split('.') : []; + defaultValue = hasChild(pathParts) ? lastItem(pathParts) : path; + } + + // + // set proper value to result object's current property as following: + // - if is a node set {}; + // - if is an array set []; + // - if primitive or else set defaultValue; + setPropertyValue(result, path, isNode ? (isArray ? [] : {}) : defaultValue); + } + + // + // return result object ... + return result; +} + +/** + * generate an object based on given property paths ... + * + * @param paths an string array which represent all property paths of an Object ... + * @param defaultValue determines default value which set to generated object's primitive properties ... + * @returns an specified type object + */ +export function generatePartialObjectFromPaths( + paths: string[], + assginPropertyName: boolean = false, + defaultValue: any = null +): XDeepPartial { + // + // Check paths contains childs ... + if (!hasChild(paths)) { + return null; + } + + // + // create empty result ... + let result = {} as XDeepPartial; + + // + // loop through all paths ... + for (let i = 0; i < paths.length; i++) { + // + // current property path ... + const path = paths[i]; + + // + // filter all paths and exclude current path from them ... + const filteredPaths = paths.filter((p) => p.includes(path) && p !== path); + + // + // if filtered paths contains any childs this means current property is a nested object or array ... + const isNode = countChild(filteredPaths) > 0; + + // + // if filtered paths contains any childs including current path +'.0' this means current property is a nested array ... + const isArray = isNode && filteredPaths.includes(`${path}.0`); + + // + if (!!assginPropertyName) { + // + const pathParts = path.includes('.') ? path.split('.') : []; + defaultValue = hasChild(pathParts) ? lastItem(pathParts) : path; + } + + // + // set proper value to result object's current property as following: + // - if is a node set {}; + // - if is an array set []; + // - if primitive or else set defaultValue; + setPropertyValue(result, path, isNode ? (isArray ? [] : {}) : defaultValue); + } + + // + // return result object ... + return result; +} + +/** + * generate an Schema object which represent given object ... + * + * @param object the model which going to parse + * @param defaultValue determines default value which set to Schema object's primitive properties ... + * @returns an Schema object + */ +export function getSchema( + object: T, + defaultValue: any = null +): XDeep { + // + const paths = getSchemaPaths(object); + const result = generateObjectFromPaths(paths, true, defaultValue); + + // + return result; +} + +/** + * generate an Schema object which represent given object ... + * + * @param object the model which going to parse + * @param defaultValue determines default value which set to Schema object's primitive properties ... + * @returns an Schema object + */ +export function getPartialSchema( + object: T, + defaultValue: any = null +): XDeepPartial { + // + const paths = getSchemaPaths(object); + const result = generatePartialObjectFromPaths(paths, true, defaultValue); + + // + return result; +} + +/** + * retrieve all exists path required to model actions ... + * + * @param object input object + * @returns an array of required paths for model actions + */ +export function extractModelInfoPaths(object: T) { + // + const schemaPaths = getSchemaPaths(object); + const result = onlyUniques(schemaPaths.map((part) => part.replace('.0', ''))); + + // + return result; +} + +/** + * retrieve an object of all available paths to model actions ... + * + * @param object input object + * @returns an object which filled by required paths + */ +export function extractModelInfoObject(object: T) { + // + const modelInfoPaths = extractModelInfoPaths(object); + const modelInfoKeyValuePairs = modelInfoPaths.map((path) => { + // + const result: XBaseKeyValue = { + key: path, + value: path, + }; + + // + return assign({}, result); + }); + const result = mergeAll({}, ...modelInfoKeyValuePairs) as any; + + // + return result; +} +//#endregion + +// +//#region Same Checkers ... +/** + * Check two object of Type is Same or not ... + * + * @param source the source object which required to type check + * @param dest the dest object which checked type with source + * @param forceMemberTypes determines all property types in each object must be same as type in other one. default is false + */ +export function isSameType(source: any, dest: any, forceMemberTypes = false) { + // + if (!source || !dest) { + return !source && !dest; + } + + // + const sType = getType(source); + const dType = getType(dest); + + // + if (sType !== dType) { + return false; + } + + // + if (sType === DataTypes.Object) { + // + // get Keys ... + const sourceKeys = keys(source); + const destKeys = keys(dest); + + // + if (countChild(sourceKeys) !== countChild(destKeys)) { + return false; + } + + // + const isAllSourceKeyExistsInDestKeys = sourceKeys.every((k) => + destKeys.includes(k) + ); + const isAllDestKeyExistsInSourceKeys = destKeys.every((k) => + sourceKeys.includes(k) + ); + + // + if (!isAllSourceKeyExistsInDestKeys || !isAllDestKeyExistsInSourceKeys) { + return false; + } + + // + if (forceMemberTypes) { + // + let memberTypesResult = true; + sourceKeys.forEach((k) => { + // + const kInSourceType = getType(source[k]); + const kInDestType = getType(dest[k]); + + // + if (kInDestType !== kInSourceType) { + memberTypesResult = false; + } + }); + + // + if (!memberTypesResult) { + return false; + } + } + } + + // + return true; +} + +/** + * Check Source and Destination array is Same ... + * + * @param source source array + * @param dest destination array + */ +export function isSameArray( + source: any[], + dest: any[], + forceCheckType?: boolean +): boolean { + // + if (!source || !dest) { + return (!source && !dest) as boolean; + } + + // + if (source.length !== dest.length) { + return false; + } + + // + if (!isSameType(source, dest)) { + return false; + } + + // + const result = source.sort().every((v, i) => { + // + const s = v; + const d = dest.sort()[i]; + + // + if (!!forceCheckType) { + if (!isSameType(s, d)) { + return false; + } + } + + // + if (isArray(s)) { + return isSameArray(s, d, forceCheckType); + } + + // + if (isObject(s)) { + return isSameObject(s, d, forceCheckType); + } + + // + return v === dest.sort()[i]; + }); + + // + return result; +} + +/** + * Check and Compare source and dest objects ... + * + * @param source source object + * @param dest destination object + * @param forceSameType check type of each member + */ +export function isSameObject( + source: object, + dest: object, + forceCheckType?: boolean +): boolean { + // + if (!source || !dest) { + return (!source && !dest) as boolean; + } + + // + const sKeysArr = Object.keys(source); + const dKeysArr = Object.keys(dest); + + // + const isSameKeys = isSameArray(sKeysArr, dKeysArr, forceCheckType); + if (!isSameKeys) { + return false; + } + + // + const result = sKeysArr.every((v, i) => { + // + const s = source[v]; + const d = dest[v]; + + // + if (!!forceCheckType) { + if (!isSameType(s, d)) { + return false; + } + } + + // + if (isArray(s)) { + return isSameArray(s, d, forceCheckType); + } + + // + if (isDate(s)) { + return isSameDate(s, d); + } + + // + if (isObject(s)) { + return isSameObject(s, d, forceCheckType); + } + + // + return s === d; + }); + + // + return result; +} + +/** + * Check Interface Types ... + */ +export function isOfType(value: any, keys: (keyof T)[]): value is T { + return ( + typeof value === "object" && + value !== null && + keys.every(k => k in value) + ); +} + +/** + * Create a Type Guard Function ... + * + * @param keys + * @returns + */ +export function typeGuard(keys: (keyof T)[]) { + return (value: any): value is T => + typeof value === "object" && + value !== null && + keys.every(k => k in value); +} +//#endregion + +// +//#region Enum Utils ... +/** + * return Keys of Enum ... + */ +// tslint:disable-next-line: ban-types +export function getKeys(T: Object) { + // + const result: string[] = []; + Object.keys(T).forEach((key) => { + // + const valProperty = parseInt(key, 10); + if (!isNaN(valProperty) && valProperty >= 0) { + result.push(T[valProperty]); + } + }); + + // + return result; +} + +/** + * return a cross value of key ... + */ +// tslint:disable-next-line:ban-types +export function getVal(T: Object, value: number | string) { + // + if (!T || isNullOrUndefined(value)) { + return null; + } + + // + if (!isNaN(parseInt(value.toString(), 10))) { + return T[value]; + } + + // + return null; +} + +/** + * convert an enumerable object to object .. + * + * @param T an enum object ... + */ +// tslint:disable-next-line:ban-types +export function toObject(T: Object) { + return Object.assign({}, T); +} +//#endregion + +// +//#region Component Tools ... +/** + * Access Specified Component Selector ... + * + * @param component + * @returns + */ +export function getComponentSelector(component: Type): string | null { + return (component as any).ɵcmp?.selectors?.[0]?.[0] ?? null; +} + +/** + * Access Specified Component's Name ... + * + * @param component + * @returns + */ +export function getComponentName(component: Type): string | null { + return component.toString(); +} + +/** + * Check Specified Value is inside Keys of Specified Type or not ... + * + * @param obj + * @param key + * @returns + */ +export function isXKeyOf( + obj: T, + key: PropertyKey +): key is keyof T { + return key in obj; +} +//#endregion + +// +//#region Wait For Element ... +/** + * Wait for Element Exists ... + * + * @param selector + * @returns + */ +export function waitForXElement(selector: string): Promise { + // + // Validate Selector ... + if (isNullOrEmptyString(selector)) { + return Promise.resolve(undefined); + } + + // + return new Promise((resolve) => { + // + // Try immediately + const el = document.querySelector(selector); + if (el) { + resolve(el); + return; + } + + // Otherwise, observe DOM changes + const observer = new MutationObserver(() => { + const el = document.querySelector(selector); + if (el) { + resolve(el); + observer.disconnect(); + } + }); + + observer.observe(document.body, { + childList: true, + subtree: true, + }); + }); +} + +/** + * Wait for Elements Exists ... + * + * @param selector + * @returns + */ +export function waitForXElements(selector: string): Promise { + // + // Validate Selector ... + if (isNullOrEmptyString(selector)) { + return Promise.resolve([]); + } + + // + return new Promise((resolve) => { + // + // Try immediately + const els = document.querySelectorAll(selector); + if (els) { + resolve(Array.from(els)); + return; + } + + // Otherwise, observe DOM changes + const observer = new MutationObserver(() => { + const els = document.querySelectorAll(selector); + if (els) { + resolve(Array.from(els)); + observer.disconnect(); + } + }); + + observer.observe(document.body, { + childList: true, + subtree: true, + }); + }); +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\tools\x-observable.tools.ts + +import { + of, + from, + Subject, + forkJoin, + Observable, + isObservable, + combineLatest, +} from 'rxjs'; +import { + map, + first, + finalize, + switchMap, + distinctUntilChanged, +} from 'rxjs/operators'; +import { + isObject, + isPromise, + isNullOrUndefined, + isNullOrEmptyString, +} from './x-type-detect.tools'; +import { keys } from './x-object.tools'; +import { EventEmitter, Type } from '@angular/core'; +import { hasChild, toArray } from './x-array.tools'; +import { + XHasAction, + XOneOrManyType, + XPropertyType, + XStandardType, +} from '../typings/x.typings'; + +// +//#region Handlers ... +export function applyFinally(obs: Observable, action: () => void) { + return obs.pipe(finalize(action)); +} + +export function hasValue(obs: Observable) { + return obs.pipe(map((res) => !isNullOrUndefined(res))); +} + +export function hasStringValue(obs: Observable) { + return obs.pipe(map((res) => !isNullOrEmptyString(res))); +} + +export function notValue(obs: Observable) { + return obs.pipe(map((res) => (isNullOrUndefined(res) ? true : !res))); +} + +export function toPromise(value: XStandardType) { + return toObservable(value).pipe(first()).toPromise(); +} + +export function toObservable(value: XStandardType) { + // + let result: Observable; + + // + if (isObservable(value)) { + result = value as Observable; + } else if (isPromise(value)) { + result = from(value as Promise); + } else { + result = of(value as T); + } + + // + return result.pipe(first()); +} + +/** + * filter an array by providing an observable condition ... + * @param list an instance of XStandardType> + * @param condition a condition + * @returns array result ... + */ +export function filterList( + list: XStandardType>, + condition: (item: T) => XStandardType +) { + return toObservable(list) + .pipe(map((valueType) => toArray(valueType))) + .pipe( + switchMap((array) => + combineLatest( + array.map((item) => + toObservable(condition(item)).pipe( + distinctUntilChanged(), + map((result) => (result ? item : null)) + ) + ) + ) + ), + map((resultArray) => resultArray.filter((action) => action)) + ); +} + +export function flatObject(input: T): Observable { + // + let result = {}; + let afterJoin = {}; + + // + const inputKeys = keys(input); + if (!hasChild(inputKeys)) { + return of(result); + } + + // + for (const key of inputKeys) { + // + const item = input[key]; + + // + const isItemObject = isObject(item); + if (isItemObject) { + // + if (item instanceof EventEmitter || item instanceof Subject) { + afterJoin[key] = item; + } else { + result[key] = flatObject(item); + } + } else { + result[key] = toObservable(item); + } + } + + // + return forkJoin(result).pipe( + map((res) => { + return { + // + ...(res as any), + + // + ...afterJoin, + }; + }) + ); +} + +export function nextSubjectWhenElementExists( + action: XPropertyType, 'action'>, + className: string, + actionProvider: Subject>, + delay: number = 200, + payload?: any +) { + // + // Validate ... + if ( + isNullOrUndefined(action) || + isNullOrEmptyString(className) || + isNullOrUndefined(actionProvider) + ) { + return; + } + + // + // Query to Select Target Elements ... + const elements: NodeListOf = document.querySelectorAll( + `.${className}` + ); + + // + if (isNullOrUndefined(elements) || !hasChild(elements)) { + // + setTimeout( + () => + nextSubjectWhenElementExists( + action, + className, + actionProvider, + delay, + payload + ), + delay + ); + return; + } + + // + actionProvider.next({ action, payload }); +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\tools\x-page.tools.ts + +import { toPromise } from './x-observable.tools'; +import { XPage } from '../typings/x-page.typings'; +import { hasChild, toArray } from './x-array.tools'; +import { XOneOrManyType } from '../typings/x.typings'; +import { mergeRoutes, toNormalString } from './x-string.tools'; +import { isNullOrEmptyString, isNullOrUndefined } from './x-type-detect.tools'; + +export async function toFlatPages(pages: XOneOrManyType) { + // + const result: XPage[] = []; + + // + if (!pages || !hasChild(pages)) { + return result; + } + + // + const pagesArray = toArray(pages); + for (const page of pagesArray) { + // + const childsArray = await toPromise(page.childs); + const isParent = hasChild(childsArray); + + // + result.push(page); + + // + if (isParent) { + // + const childsRes = await toFlatPages(page.childs); + result.push(...childsRes); + } + } + + // + return result; +} + +export async function findPageByRoute( + pages: XOneOrManyType, + route: XOneOrManyType +) { + // + let result: XPage = null; + + // + if (!hasChild(pages) || !hasChild(route)) { + return result; + } + + // + const pRoute = mergeRoutes(route); + const pPages = await toFlatPages(pages); + if (!hasChild(pPages)) { + return result; + } + + // + for (const page of pPages) { + // + if (hasChild(page.route)) { + // + const pageRoute = mergeRoutes(page.route); + if ( + !isNullOrEmptyString(pageRoute) && + toNormalString(pageRoute) === toNormalString(pRoute) + ) { + result = page; + } + } + } + + // + return result; +} + +export async function containsPagesByRoute( + pages: XOneOrManyType, + route: XOneOrManyType +) { + // + const result: XPage[] = []; + + // + if (!hasChild(pages) || !hasChild(route)) { + return result; + } + + // + const pRoute = mergeRoutes(route); + if (isNullOrEmptyString(pRoute)) { + return result; + } + + // + const pPages = await toFlatPages(pages); + if (!hasChild(pPages)) { + return result; + } + + // + for (const page of pPages) { + // + if (hasChild(page.route)) { + // + const pageRoute = mergeRoutes(page.route); + if ( + !isNullOrEmptyString(pageRoute) && + toNormalString(pRoute).includes(toNormalString(pageRoute)) + ) { + result.push(page); + } + } + } + + // + return result; +} + +export async function parentPagesByRoute( + pages: XOneOrManyType, + route: XOneOrManyType +) { + // + let result: XPage[] = []; + + // + if (!hasChild(pages) || !hasChild(route)) { + return result; + } + + // + const parentPages = await containsPagesByRoute(pages, route); + const foundedPage = await findPageByRoute(pages, route); + if (!hasChild(parentPages) || isNullOrUndefined(foundedPage)) { + return result; + } + + // + result = parentPages.filter( + (i) => toNormalString(i.id) !== toNormalString(foundedPage.id) + ); + + // + return result; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\tools\x-string.tools.ts + +import { Md5 } from 'md5-typescript'; +import { hasChild, toArray } from './x-array.tools'; +import { XOneOrManyType } from '../typings/x.typings'; +import { isNullOrEmptyString, isString } from './x-type-detect.tools'; + +// +export const XStringPlaceHolder = '(%$s)'; + +export const XEnDigits: string[] = [ + '0', + '1', + '2', + '3', + '4', + '5', + '6', + '7', + '8', + '9', +]; +export const XFaDigits: string[] = [ + '۰', + '۱', + '۲', + '۳', + '۴', + '۵', + '۶', + '۷', + '۸', + '۹', +]; + +// +//#region String Utils ... +/** + * determines given string is empty or not ... + */ +export function isEmpty(value: string) { + // + if (isString(value) && value.length === 0) { + return true; + } + + // + return false; +} + +/** + * return digits of given string ... + */ +export function getDigits(value: string) { + // + if (!isString(value)) { + throw new Error('Invalid Parameter ...'); + } + + // + let result = ''; + for (let i = 0; i < value.length; i++) { + // + const chr = value.substr(i, 1); + if (chr in XEnDigits) { + // + result += chr; + } + } + + // + return result; +} + +/** + * return non digits of given string ... + */ +export function getNonDigits(value: string) { + // + if (!isString(value)) { + throw new Error('Invalid Parameter ...'); + } + + // + value = toEnNumbers(value); + + // + let result = ''; + for (let i = 0; i < value.length; i++) { + // + const chr = value.substr(i, 1); + if (!(chr in XEnDigits)) { + // + result += chr; + } + } + + // + return result; +} + +/** + * determine an expression contains only digits + */ +export function isOnlyDigits(value: string) { + // + if (isEmpty(value)) { + return false; + } + + // + const pattern = /^\d+$/; + return pattern.test(value); +} + +/** + * merge several parts of a url ... + * + * @param route an instance of url tree array ... + */ +export function mergeRoutes(route: XOneOrManyType) { + // + if (!hasChild(route)) { + return '/'; + } + + // + try { + return toArray(route).join('/').replace('//', '/'); + } catch {} + + // + return '/'; +} + +/** + * format string formatter ... + * + * @param value an string to format ... + * @param args the args for string formatter ... + */ +export function stringFormatter(value: string, ...args: any) { + // + if (!isString(value)) { + throw new Error('first arg must be an string ...'); + } + + // + if (args.length < 1) { + throw new Error('there mest be values ...'); + } + + // + const holder = XStringPlaceHolder; + let numberOfHolders = 0; + + // + if (value.length < holder.length) { + throw new Error('the sting doesnt contain any holder ...'); + } + + // + // tslint:disable-next-line:no-shadowed-variable + for (let i = 0; i < value.length; i++) { + // + const indexStr = value.substr(i, holder.length); + + // + if (indexStr === holder) { + numberOfHolders++; + } + } + + // + if (numberOfHolders !== args.length) { + throw new Error('theholders and parameters counts must be same ...'); + } + + // + // tslint:disable-next-line:prefer-for-of + for (let i = 0; i < args.length; i++) { + // + const a = value.indexOf(holder); + + // + value = + value.substr(0, a) + + args[i].toString() + + value.substr(a + holder.length, value.length - (a + holder.length)); + } + + // + return value; +} + +/** + * change all persian and arabic numers to english digits + */ +export function toEnNumbers(value: string): string { + // + let result = value ? value.toString() : ''; + + // + const persianNumbers = [ + /۰/g, + /۱/g, + /۲/g, + /۳/g, + /۴/g, + /۵/g, + /۶/g, + /۷/g, + /۸/g, + /۹/g, + ]; + const arabicNumbers = [ + /٠/g, + /١/g, + /٢/g, + /٣/g, + /٤/g, + /٥/g, + /٦/g, + /٧/g, + /٨/g, + /٩/g, + ]; + + // + for (let i = 0; i < 10; i++) { + if (!isNullOrEmptyString(result)) { + result = result + .replace(persianNumbers[i], i.toString()) + .replace(arabicNumbers[i], i.toString()); + } + } + + // + return result; +} + +/** + * change all english digits to persian digits + */ +export function toPersianNumbers(value: string): string { + // + let result = value; + + // + for (let i = 0; i < 10; i++) { + result = result.replace(new RegExp(i.toString(), 'g'), XFaDigits[i]); + } + + // + return result; +} + +/** + * remove all spaces on string value ... + * + * @param value an string value ... + */ +export function removeSpaces(value: string): string { + return value.replace(/\s/g, ''); +} + +/** + * remove margin spaces and make text lowercase + */ +export function toNormalString(value: string): string { + return isNullOrEmptyString(value) ? '' : value.trim().toLowerCase(); +} + +/** + * Capitalize an string ... + * + * @param value input string .. + * @returns string + */ +export function capital(value: string): string { + // + if (isNullOrEmptyString(value) || isNullOrEmptyString(value.trim())) { + return ''; + } + + // + value = value.trim(); + const firstChar = value.charAt(0).toLocaleUpperCase(); + const otherChars = value.slice(1); + + // + const result = `${firstChar}${otherChars}`; + return result; +} + +/** + * convert an string to Md5 ... + * + * @param value an string which going to convert + */ +export function toMd5(value: string) { + return isNullOrEmptyString(value) ? '' : Md5.init(value); +} + +/** + * convert given string to array buffer ... + * + * @param value the string which need to convert + */ +export function toArrayBuffer(value: string) { + // + if (!value || value.length === 0) { + return null; + } + + // + const stringLength = value.length; + const buffer = new ArrayBuffer(stringLength * 2); + const bufferView = new Uint16Array(buffer); + + // + for (let i = 0; i < stringLength; i++) { + bufferView[i] = value.charCodeAt(i); + } + + // + return buffer; +} + +/** + * convert byte array to string ... + * + * @param buffer the bytes array of an string + */ +export function fromArryBuffer(buffer: ArrayBuffer) { + return String.fromCharCode.apply(null, Array.from(new Uint16Array(buffer))); +} + +/** + * escape reg exp chars from an string ... + * + * @param source given string value to escape + * @returns escaped source value + */ +export function escapeRegExp(source: string) { + return source.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string +} + +/** + * find all indexes of an string inside another and return them as an array ... + */ +export function findAllIndex(source: string, query: string) { + // + const result: number[] = []; + + // + const regExp = new RegExp(escapeRegExp(query), 'gi'); + let match: any; + // tslint:disable-next-line:no-conditional-assignment + while ((match = regExp.exec(source)) !== null) { + result.push(match.index); + } + + // + return result; +} + +/** + * insert a text to specific position on another text ... + */ +export function insertText(source: string, insertedText: string, pos: number) { + // + if (!source && !insertText) { + return ''; + } + + // + if (!source && insertText) { + return insertedText; + } + + // + if (pos > source.length) { + return source + insertedText; + } + + // + return [source.slice(0, pos), insertedText, source.slice(pos)].join(''); +} + +/** + * remove all occurence of an string inside of another string + */ +export function removeAll( + source: string, + removedString: string, + caseSensitive = false +) { + // + if (isNullOrEmptyString(removedString)) { + removedString = ''; + } + + // + const regExp = new RegExp( + removedString.toString(), + caseSensitive ? 'gi' : 'g' + ); + + // + if (isNullOrEmptyString(source)) { + source = ''; + } + + // + const result = source.toString().replace(regExp, ''); + + // + return result; +} + +/** + * extract all unique characters of an string ... + * + * @param value source string + */ +export function extractUniqueChars(value: string) { + // + if (isNullOrEmptyString(value)) { + return ''; + } + + // + let result = ''; + + // + // tslint:disable-next-line:prefer-for-of + for (let i = 0; i < value.length; i++) { + if (result.indexOf(value.charAt(i)) === -1) { + result += value[i]; + } + } + + // + return result; +} + +/** + * check an string contains unique chars or not ... + * + * @param value source string + */ +export function hasUniqueChar(value: string) { + // + if (isNullOrEmptyString(value)) { + return false; + } + + // + const uniqueChars = extractUniqueChars(value); + if (isNullOrEmptyString(uniqueChars)) { + return false; + } + + // + // const valueCharsArray = Array.from(value); + const uniqueCharsArray = Array.from(uniqueChars); + const findUniquArray = uniqueCharsArray.map((v) => { + // + const resultRes = findAllIndex(value, v); + + // + return resultRes && resultRes.length === 1; + }); + + // + const result = findUniquArray.includes(true); + + // + return result; +} + +/** + * convert an array object to list string ... + * + * @param values an array object to convert it to list string + * @param seperator list seperator ... + */ +export function toListString( + values: XOneOrManyType, + seperator: string = ',' +) { + // + const valueArr = toArray(values); + if (!hasChild(valueArr)) { + return ''; + } + + // + return valueArr.join(seperator); +} + +/** + * convert a list string to string array .. + * + * @param listString a list string + * @param seperator list seperator ... + */ +export function parseListString(listString: string, seperator: string = ',') { + // + if (!isNullOrEmptyString(listString)) { + return []; + } + + // + return listString.split(seperator); +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\tools\x-task.tools.ts + +export const taskIdStartKey = '_['; +export const taskIdEndKey = ']'; + +/** + * check if task name contains id ... + * + * @param key the task name ... + */ +export function hasTaskId(key: string | any) { + // + if (!key) { + return false; + } + + // + const mKey = key.toString() as string; + return mKey.includes(taskIdStartKey) && mKey.includes(taskIdEndKey); +} + +/** + * retrieve and extract the raw task name from a task name ... + * + * @param key the task name ... + */ +export function getTaskName(key: string | any) { + // + if (!key) { + return ''; + } + + // + const mKey = key.toString() as string; + + // + if (!hasTaskId(mKey)) { + return mKey; + } + + // + const taskId = getTaskId(mKey); + const taskIdString = `${taskIdStartKey}${taskId}${taskIdEndKey}`; + const result = mKey.replace(taskIdString, ''); + + // + return result; +} + +/** + * retrieve and extract the id inside a task name ... + * + * @param key the task name ... + */ +export function getTaskId(key: string | any) { + // + // Validate Args ... + if (!key || !hasTaskId(key)) { + return ''; + } + + // + // Task Id ... + let result = key.toString() as string; + + // + // Validating Info ... + let index = result.indexOf(taskIdStartKey); + + // + result = result.substr(index, result.length); + result = result.replace(taskIdStartKey, ''); + + // + index = result.indexOf(taskIdEndKey); + result = result.substring(0, index); + + // + // Return Result ... + return result; +} + +/** + * prepare a Task With ID and return ... + * + * @param key the Task Name ... + * @param id th ID ... + */ +export function setTaskId(key: string | any, id: string | any) { + return `${key.toString()}${taskIdStartKey}${id.toString()}${taskIdEndKey}`; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\tools\x-to-form-data.ts + +import { + isDate, + isNull, + isFile, + isBlob, + isArray, + isObject, + isBoolean, + isUndefined, + isNullOrUndefined, + isNullOrEmptyString, +} from './x-type-detect.tools'; +import { blobToFile, generateFileName } from './x-file.tools'; + +export interface XToFormDataOptions { + /** + * include array indices in FormData keys + * defaults to false + */ + indices?: boolean; + + /** + * treat null values like undefined values and ignore them + * defaults to false + */ + nullsAsUndefineds?: boolean; + + /** + * convert true or false to 1 or 0 respectively + * defaults to false + */ + booleansAsIntegers?: boolean; +} + +/** + * Convert an Object to FormData ... + * + * @param source the object which going to convert to FormData ... + * @param options Converter Options ... + * @param formData an exists FormData instance which apply object to it ... + * @param keysPrefix key prefix for FormData Values ... + */ +export function toFormData( + source: any, + options?: XToFormDataOptions, + formData?: FormData, + keysPrefix?: string +) { + // + //#region Prepare Options ... + options = options || {}; + + options.indices = isNullOrUndefined(options.indices) ? true : options.indices; + + options.nullsAsUndefineds = isNullOrUndefined(options.nullsAsUndefineds) + ? false + : options.nullsAsUndefineds; + + options.booleansAsIntegers = isNullOrUndefined(options.booleansAsIntegers) + ? false + : options.booleansAsIntegers; + //#endregion + + // + formData = formData || new FormData(); + + // + //#region Do Convertion ... + if (isUndefined(source)) { + return formData; + } else if (isNull(source)) { + // + if (!options.nullsAsUndefineds) { + formData.append(keysPrefix, ''); + } + } else if (isBoolean(source)) { + // + if (options.booleansAsIntegers) { + formData.append(keysPrefix, source ? '1' : '0'); + } else { + formData.append(keysPrefix, source); + } + } else if (isArray(source)) { + // + if (source.length) { + source.forEach((value: any, index: number) => { + const key = keysPrefix + '[' + (options.indices ? index : '') + ']'; + formData = toFormData(value, options, formData, key); + }); + } + } else if (isDate(source)) { + formData.append(keysPrefix, source.toISOString()); + } else if (isObject(source) && !isFile(source) && !isBlob(source)) { + // + Object.keys(source).forEach((prop) => { + // + const value = source[prop]; + + // + if (isArray(value)) { + while (prop.length > 2 && prop.lastIndexOf('[]') === prop.length - 2) { + prop = prop.substring(0, prop.length - 2); + } + } + + // + const key = keysPrefix ? keysPrefix + '[' + prop + ']' : prop; + + // + formData = toFormData(value, options, formData, key); + }); + } else if (isFile(source) || isBlob(source)) { + // + let file: File = null; + if (isFile(source)) { + file = source as File; + } else if (isBlob(source)) { + // + const blob = source as Blob; + const fileName = generateFileName(blob); + file = blobToFile(blob, fileName); + } + + // + formData.append(keysPrefix, file, file.name); + } else { + formData.append(keysPrefix, source.toString()); + } + //#endregion + + return formData; +} + +/** + * log an instance of FormData object's Content in Console ... + */ +export async function getFormDataContent(source: FormData) { + return await new Response(source).text(); +} + +/** + * log an instance of FormData object's Content in Console ... + */ +export async function logFormData(source: FormData) { + // + const result = await getFormDataContent(source); + console.log(result); +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\tools\x-type-convert.tools.ts + +// +//#region Type Converter ... +/** + * convert given object to string ... + */ +export function toXString(object: any) { + // + try { + // + return String(object); + } catch (error) { + // + throw error; + } +} + +/** + * convert given object to number ... + */ +export function toNumber(object: any) { + // + try { + // + return Number(object); + } catch (error) { + // + throw error; + } +} + +/** + * convert given object to date ... + */ +export function toDate(object: any) { + // + try { + // + return new Date(object); + } catch (error) { + // + throw error; + } +} + +/** + * convert given object to boolean ... + */ +export function toBoolean(object: any) { + // + try { + // + // tslint:disable-next-line:no-eval + return Boolean(eval(object)); + } catch (error) { + // + throw error; + } +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\tools\x-type-defaults.tools.ts + +import { + isArray, + isObject, + isNumber, + isString, + isBoolean, +} from './x-type-detect.tools'; +import { XStandardType } from '../typings/x.typings'; +import { toObservable, toPromise } from './x-observable.tools'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + +export async function getDefaultValueAsync(t: XStandardType): Promise { + // + let result: any; + + // + const tVal = await toPromise(toObservable(t)); + + // + if (isBoolean(tVal)) { + result = false; + } else if (isNumber(tVal)) { + result = 0; + } else if (isString(tVal)) { + result = ''; + } else if (isObject(tVal) && !isArray(tVal)) { + result = null; + } else if (isArray(tVal)) { + result = []; + } else { + result = undefined; + } + + // + return result as T; +} + +export function getDefaultValue(t: XStandardType): Observable { + // + return toObservable(t).pipe( + map((tVal) => { + // + let result: any; + + // + if (isBoolean(tVal)) { + result = false; + } else if (isNumber(tVal)) { + result = 0; + } else if (isString(tVal)) { + result = ''; + } else if (isObject(tVal) && !isArray(tVal)) { + result = null; + } else if (isArray(tVal)) { + result = []; + } else { + result = undefined; + } + + // + return result as T; + }) + ); +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\tools\x-type-detect.tools.ts + +/** + * Custom Data Types ... + */ +export const DataTypes = { + Null: 'null', + Undefined: 'undefined', + String: 'string', + Number: 'number', + Boolean: 'boolean', + Object: 'object', + Function: 'function', +}; + +/** + * determines type of an object + * + * @param variable specified object + */ +export function getType(variable: any) { + return typeof variable; +} + +// +//#region Type Checker ... +/** + * determine a var is null or not ... + */ +export function isNull(variable: any) { + // + if ( + !variable && + (variable === null || getType(variable) === DataTypes.Null) + ) { + return true; + } + + // + return false; +} + +/** + * determine a var is undefined ... + */ +export function isUndefined(variable: any) { + // + if (getType(variable) === DataTypes.Undefined) { + return true; + } + + // + return false; +} + +/** + * setermine a var is null or undefined ... + */ +export function isNullOrUndefined(variable: any) { + // + if (isNull(variable) || isUndefined(variable)) { + return true; + } + + // + return false; +} + +export function isNullOrEmptyString(variable: string) { + // + if (isNull(variable) || isUndefined(variable) || variable.length === 0) { + return true; + } + + // + return false; +} + +export function isNullOrZeroLength(variable: any[]) { + // + if (isNullOrUndefined(variable)) { + return true; + } + + // + return variable.length === 0; +} + +/** + * determine a var is string or not ... + */ +export function isString(variable: any) { + // + if (getType(variable) === DataTypes.String) { + return true; + } + + // + return false; +} + +/** + * determine a var is Number or not ... + */ +export function isNumber(variable: any) { + // + if (getType(variable) === DataTypes.Number) { + return true; + } + + // + var numValue = parseInt(variable, 10); + if (isNaN(numValue)) { + return false; + } + + // + return true; +} + +/** + * determine a var is boolean or not ... + */ +export function isBoolean(variable: any) { + // + if (getType(variable) === DataTypes.Boolean) { + return true; + } + + // + return false; +} + +/** + * determine a object is Array or not ... + * + * IMPORTANT: an array must be an object ... + */ +export function isArray(value: any) { + return value && Array.isArray(value); +} + +/** + * determine a var is object or not ... + */ +export function isObject(variable: any) { + // + return ( + variable && + getType(variable) === DataTypes.Object && + variable === Object(variable) + ); +} + +/** + * determine a var is function or not ... + */ +export function isFunction(variable: any) { + // + if (getType(variable) === DataTypes.Function) { + return true; + } + + // + return false; +} + +/** + * determine a var is Blob ... + */ +export function isBlob(value: any) { + return ( + value && + typeof value.size === 'number' && + typeof value.type === 'string' && + typeof value.slice === 'function' + ); +} + +/** + * check an input arg is a date object or not ... + * + * @param source any arg which required to check + */ +export function isDate(source: any) { + return ( + source && + isObject(source) && + (source instanceof Date || + isFunction(source.getMonth) || + Object.prototype.toString.call(source) === '[object Date]') + ); +} + +/** + * determine a var is File ... + */ +export function isFile(value: any) { + return ( + isBlob(value) && + typeof value.name === 'string' && + (typeof value.lastModifiedDate === 'object' || + typeof value.lastModified === 'number') + ); +} + +/** + * check wheather an object is promise or not ... + */ +export function isPromise(variable: any) { + if (!isNullOrUndefined(variable) && isFunction(variable.then)) { + return true; + } + + return false; +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\tools\x-uuid-tools.ts + +import { v4 as uuidv4 } from 'uuid'; +import { v5 as uuidv5 } from 'uuid'; + +export const DEFAULT_NAMESPACE = 'SaherElm IT Center'; +export function generateUuid( + prefix?: string, + content?: string, + nameSpace: string = DEFAULT_NAMESPACE +) { + // + const uId = content ? uuidv5(content, nameSpace) : uuidv4(); + const result = prefix ? `${prefix}_${uId}` : uId; + + // + return result; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\typings\x-base-component.typings.ts + +export const DEFAULT_EXPORT_TYPING_BASE = 'default'; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\typings\x-color.typings.ts + +// +import { XBaseKeyValue } from './x.typings'; + +//#region Color Tools ... +export interface XColorDescribtor { + name: XColor; + brightnessLevel: XColorBrightnessLevel; + isContrast?: boolean; +} + +export enum XColor { + Primary = 'primary', + Secondary = 'secondary', + Tertiary = 'tertiary', + Success = 'success', + Warning = 'warning', + Danger = 'danger', + Dark = 'dark', + Medium = 'medium', + Light = 'light', + // + Transparent = 'cb-inherit', +} + +export enum XColorWithBrightness { + // + PrimaryTint = 'primary-tint', + Primary = 'primary', + PrimaryShade = 'primary-shade', + // + SecondaryTint = 'secondary-tint', + Secondary = 'secondary', + SecondaryShade = 'secondary-shade', + // + TertiaryTint = 'tertiary-tint', + Tertiary = 'tertiary', + TertiaryShade = 'tertiary-shade', + // + SuccessTint = 'success-tint', + Success = 'success', + SuccessShade = 'success-shade', + // + WarningTint = 'warning-tint', + Warning = 'warning', + WarningShade = 'warning-shade', + // + DangerTint = 'danger-tint', + Danger = 'danger', + DangerShade = 'danger-shade', + // + DarkTint = 'dark-tint', + Dark = 'dark', + DarkShade = 'dark-shade', + // + MediumTint = 'medium-tint', + Medium = 'medium', + MediumShade = 'medium-shade', + // + LightTint = 'light-tint', + Light = 'light', + LightShade = 'light-shade', + // + Transparent = 'cb-inherit', +} + +export type XColorIdentifier = XColor | XColorWithBrightness | string; + +export enum XColorBrightnessLevel { + Tint = 'tint', + Regular = 'regular', + Shade = 'shade', +} + +export interface XRGBAColor { + r?: number; + g?: number; + b?: number; + a?: number; +} + +export interface XHSVAColor { + h?: number; + s?: number; + v?: number; + a?: number; +} + +export interface XHSLAColor { + h?: number; + s?: number; + l?: number; + a?: number; +} + +export enum XColorFormat { + XHEX = 'hex', + XRGBA = 'rgba', + XHSLA = 'hsla', + XHSVA = 'hsva', +} + +export type XColorFormatIdentifier = XColorFormat | string; + +export interface XColorKeyValue extends XBaseKeyValue {} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\typings\x-date-time.typings.ts + +export enum XUnitOfTime { + Year, + Month, + Day +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\typings\x-exception.typings.ts + +import { XExceptionIDs, Exceptions } from '../constants/x-exception.resources'; + +export interface XException { + id: number; + message: string; + payload?: any[]; +} + +export type XExceptionIndex = { + [key in keyof typeof XExceptionIDs]: XException; +}; + +export type XExceptions = keyof typeof Exceptions; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\typings\x-manager.typings.ts + +export enum XUiEvent { + // + StartLoading, + StopLoading, + + // + Disabled, + Enabled, + + // + Locked, + UnLocked, + + // + BackPrevented, + BackReleased, + + // + Ready +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\typings\x-notification.typings.ts + +import { + AlertInput, + ToastOptions, + ModalOptions, + AlertOptions, + ComponentRef, + PickerOptions, + PopoverOptions, + LoadingOptions, + ActionSheetOptions, +} from '@ionic/core'; +import { XResourceIdentifier } from './x-translation.typings'; +import { XExceptions, XException } from './x-exception.typings'; +import { XSpinnerIdentifier, XOneOrManyType } from './x.typings'; +import { XExceptionIDs } from '../constants/x-exception.resources'; +import { XResourceIDs } from '../constants/x-translation.resources'; + +// +//#region Typings for Dialogs and Notifications ... +/** + * Action Type for Notifications in Audio Service + */ +export enum XActionType { + User, + System, + Capture, +} + +/** + * represent a single notification sound for specific actions + */ +export interface XNotificationSound { + type: XActionType; + resourcePath: string; +} + +/** + * represent a group of notification sounds and coresponding actions + */ +export interface NotificationSoundIndex { + [name: string]: XNotificationSound; +} + +export interface XNotificationParams { + type?: XActionType; + opt?: ToastOptions; + playSound?: boolean; + dissmissable: boolean; + onDidDismiss?: () => void; + onWillDismiss?: () => void; +} + +export interface XTypedNotificationParams extends XNotificationParams { + message: string | XResourceIDs; +} + +export interface XErrorNotificationParams extends XNotificationParams { + exception: XExceptionIDs | XExceptions | XException | any; +} + +type XXActionModalOptions = Pick< + Partial, + | 'id' + | 'animated' + | 'backdropDismiss' + | 'cssClass' + | 'keyboardClose' + | 'translucent' +>; +export interface XActionModalOptions extends XXActionModalOptions { + // + type?: XActionType; + playSound?: boolean; + header?: XResourceIdentifier; + subHeader?: XResourceIdentifier; + buttons?: XModalButton[]; + + // + hasYes?: boolean; + yesRole?: XModalButtonRoleIdentifier; + yesHandler?: () => boolean | void | Promise; + + hasNo?: boolean; + noRole?: XModalButtonRoleIdentifier; + noHandler?: () => boolean | void | Promise; + + hasOk?: boolean; + okRole?: XModalButtonRoleIdentifier; + okHandler?: () => boolean | void | Promise; + + hasRetry?: boolean; + retryRole?: XModalButtonRoleIdentifier; + retryHandler?: () => boolean | void | Promise; + + hasAccept?: boolean; + acceptRole?: XModalButtonRoleIdentifier; + acceptHandler?: () => boolean | void | Promise; + + hasCancel?: boolean; + cancelRole?: XModalButtonRoleIdentifier; + cancelHandler?: () => boolean | void | Promise; + + hasDismiss?: boolean; + dismissRole?: XModalButtonRoleIdentifier; + dismissHandler?: () => boolean | void | Promise; +} + +type XXModalOptions = Pick< + Partial, + | 'componentProps' + | 'showBackdrop' + | 'backdropDismiss' + | 'cssClass' + | 'animated' + | 'component' + | 'keyboardClose' + | 'id' +>; +export interface XModalOptions extends XXModalOptions { + component: ComponentRef; + autoHeight?: boolean; + fullScreen?: boolean; +} + +type XXPopOverOptions = Pick< + Partial, + | 'animated' + | 'backdropDismiss' + | 'componentProps' + | 'cssClass' + | 'component' + | 'id' + | 'showBackdrop' + | 'translucent' + | 'keyboardClose' + | 'event' +>; +export interface XPopoverOptions extends XXPopOverOptions { + component: ComponentRef; +} + +type XXLoadingOptions = Pick< + Partial, + | 'animated' + | 'backdropDismiss' + | 'cssClass' + | 'duration' + | 'id' + | 'keyboardClose' + | 'showBackdrop' + | 'translucent' + | 'message' +>; + +// tslint:disable-next-line:no-empty-interface +export interface XLoadingOptions extends XXLoadingOptions { + spinner?: XSpinnerIdentifier; +} + +type XXAlertOptions = Pick< + Partial, + | 'animated' + | 'translucent' + | 'backdropDismiss' + | 'id' + | 'keyboardClose' + | 'cssClass' +>; + +export enum XModalButtonRole { + Cancel = 'cancel', + Selected = 'selected', + Destructive = 'destructive', +} + +export type XModalButtonRoleIdentifier = XModalButtonRole | string; + +export interface XModalButton { + text: XResourceIdentifier; + role?: XModalButtonRoleIdentifier; + cssClass?: XOneOrManyType; + handler?: ( + value?: any + ) => + | boolean + | void + | { + [key: string]: any; + } + | any; +} + +export type XAlertInputType = + | 'date' + | 'email' + | 'number' + | 'password' + | 'search' + | 'tel' + | 'text' + | 'url' + | 'time' + | 'checkbox' + | 'radio'; + +export interface XModalInput { + id?: string; + value?: any; + name?: string; + checked?: boolean; + disabled?: boolean; + min?: string | number; + max?: string | number; + type?: XAlertInputType; + label?: XResourceIdentifier; + placeholder?: XResourceIdentifier; + handler?: (input: AlertInput) => void; +} + +export interface XAlertOptions extends XXAlertOptions { + header?: XResourceIdentifier; + subHeader?: XResourceIdentifier; + message: XResourceIdentifier; + inputs?: XModalInput[]; + buttons?: XModalButton[]; +} + +type XXPickerOptions = Pick< + Partial, + 'animated' | 'backdropDismiss' | 'id' | 'cssClass' | 'keyboardClose' +>; + +export interface XPickerColumn { + name: string; + align?: string; + selectedIndex?: number; + prevSelected?: number; + prefix?: string; + suffix?: string; + options: XPickerColumnOption[]; + cssClass?: string | string[]; + columnWidth?: string; + prefixWidth?: string; + suffixWidth?: string; + optionsWidth?: string; + refresh?: () => void; +} + +export interface XPickerColumnOption { + text?: XResourceIdentifier; + value: any; + disabled?: boolean; + duration?: number; + transform?: string; + selected?: boolean; +} + +export interface XPickerOptions extends XXPickerOptions { + buttons?: XModalButton[]; + columns: XPickerColumn[]; +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\typings\x-page.typings.ts + +import { XOneOrManyType } from './x.typings'; +import { XResourceIdentifier } from './x-translation.typings'; + +export interface XPage { + id: string; + name: string; + icon?: string; + baseRoute: string; + title: XResourceIdentifier; + route: XOneOrManyType; + childs?: XOneOrManyType; + description?: XResourceIdentifier; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\typings\x-query.typings.ts + +export interface XBaseDataPagerQuery { + filter?: string; + pageNumber: number; + rowsInPage: number; +} + +export interface XBaseDataPagerQueryResult { + count: number; + items: T[]; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\typings\x-setting.typings.ts + +export type XOrientation = 'landscape' | 'portrait'; + +export enum XDeviceTypes { + Unknown, + Mobile, + Tablet, + Desktop +} + +export interface XDeviceInfo { + os: string; + osVersion: string; + browser: string; + userAgent: string; + deviceType: XDeviceTypes; + userId?: number; + + identifier?: string; + token?: string; +} + +export type XCssProperties = + | 'x-app-bar-color' + | 'ui-direction' + | 'toolbar-height' + | string; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\typings\x-storage.typings.ts + +/** + * represent a single item which can be stored ... + */ +export interface XStorageItem { + key: string; + value: string; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\typings\x-task-manager.typings.ts + +import { Observable } from 'rxjs'; +import { XException } from './x-exception.typings'; + +export enum XTaskPriority { + High, + Medium, + Regular, + Weak +} + +export interface XTask { + affectLoading?: boolean; + name: string; + priority: XTaskPriority; + task: Observable; + result?: T; + error?: XException; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\typings\x-theme-manager.typings.ts + +export enum XThemeType { + Light = 'light', + Dark = 'dark', +} + +export type XThemeTypeIdentifier = XThemeType | string; + +export enum XThemeCssVars { + // + //#region Primary ... + XColorPrimary = 'x-color-primary', + XColorPrimaryContrast = 'x-color-primary-contrast', + + XColorPrimaryShade = 'x-color-primary-shade', + XColorPrimaryShadeContrast = 'x-color-primary-shade-contrast', + + XColorPrimaryTint = 'x-color-primary-tint', + XColorPrimaryTintContrast = 'x-color-primary-tint-contrast', + //#endregion + + // + //#region Secondary ... + XColorSecondary = 'x-color-secondary', + XColorSecondaryContrast = 'x-color-secondary-contrast', + + XColorSecondaryShade = 'x-color-secondary-shade', + XColorSecondaryShadeContrast = 'x-color-secondary-shade-contrast', + + XColorSecondaryTint = 'x-color-secondary-tint', + XColorSecondaryTintContrast = 'x-color-secondary-tint-contrast', + //#endregion + + // + //#region Tertiary ... + XColorTertiary = 'x-color-tertiary', + XColorTertiaryContrast = 'x-color-tertiary-contrast', + + XColorTertiaryShade = 'x-color-tertiary-shade', + XColorTertiaryShadeContrast = 'x-color-tertiary-shade-contrast', + + XColorTertiaryTint = 'x-color-tertiary-tint', + XColorTertiaryTintContrast = 'x-color-tertiary-tint-contrast', + //#endregion + + // + //#region Success ... + XColorSuccess = 'x-color-success', + XColorSuccessContrast = 'x-color-success-contrast', + + XColorSuccessShade = 'x-color-success-shade', + XColorSuccessShadeContrast = 'x-color-success-shade-contrast', + + XColorSuccessTint = 'x-color-success-tint', + XColorSuccessTintContrast = 'x-color-success-tint-contrast', + //#endregion + + // + //#region Warning ... + XColorWarning = 'x-color-warning', + XColorWarningContrast = 'x-color-warning-contrast', + + XColorWarningShade = 'x-color-warning-shade', + XColorWarningShadeContrast = 'x-color-warning-shade-contrast', + + XColorWarningTint = 'x-color-warning-tint', + XColorWarningTintContrast = 'x-color-warning-tint-contrast', + //#endregion + + // + //#region Danger ... + XColorDanger = 'x-color-danger', + XColorDangerContrast = 'x-color-danger-contrast', + + XColorDangerShade = 'x-color-danger-shade', + XColorDangerShadeContrast = 'x-color-danger-shade-contrast', + + XColorDangerTint = 'x-color-danger-tint', + XColorDangerTintContrast = 'x-color-danger-tint-contrast', + //#endregion + + // + //#region Dark ... + XColorDark = 'x-color-dark', + XColorDarkContrast = 'x-color-dark-contrast', + + XColorDarkShade = 'x-color-dark-shade', + XColorDarkShadeContrast = 'x-color-dark-shade-contrast', + + XColorDarkTint = 'x-color-dark-tint', + XColorDarkTintContrast = 'x-color-dark-tint-contrast', + //#endregion + + // + //#region Medium ... + XColorMedium = 'x-color-medium', + XColorMediumContrast = 'x-color-medium-contrast', + + XColorMediumShade = 'x-color-medium-shade', + XColorMediumShadeContrast = 'x-color-medium-shade-contrast', + + XColorMediumTint = 'x-color-medium-tint', + XColorMediumTintContrast = 'x-color-medium-tint-contrast', + //#endregion + + // + //#region Light ... + XColorLight = 'x-color-light', + XColorLightContrast = 'x-color-light-contrast', + + XColorLightShade = 'x-color-light-shade', + XColorLightShadeContrast = 'x-color-light-shade-contrast', + + XColorLightTint = 'x-color-light-tint', + XColorLightTintContrast = 'x-color-light-tint-contrast', + //#endregion +} + +export type XThemeCssVarsIdentifier = XThemeCssVars | string; + +export type XThemeDescriptorColors = keyof typeof XThemeCssVars; + +export type XThemeColors = { + [key in XThemeDescriptorColors]: string; +}; + +export interface XThemeDescriptor { + type: XThemeTypeIdentifier; + colors: XThemeColors; +} + +export interface XThemePack { + name: string; + light: XThemeDescriptor; + dark: XThemeDescriptor; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\typings\x-translation.typings.ts + +import { XResourceIDs } from '../constants/x-translation.resources'; +import { toEnNumbers, toPersianNumbers } from '../tools/x-string.tools'; + +export declare type XLanguageDirection = 'ltr' | 'rtl'; +export declare type XLocale = 'en-US' | 'fa-IR'; + +export interface XLanguageDescriptor { + name: string; + locale: XLocale; + language: string; + direction: XLanguageDirection; + resources?: XTranslationItem[]; +} + +export interface XLanguageDescriptorIndex { + [name: string]: XLanguageDescriptor; +} + +export type XTranslationId = keyof typeof XResourceIDs; + +export interface XTranslationItem { + id: XTranslationId | string; + value: string; +} + +export interface XTranslationIndex { + [name: string]: XTranslationItem; +} + +export interface XLocalesIndex { + [key: string]: XTranslationIndex; +} + +export interface GetDateProps { + date?: Date; + locale?: XLocale; + dateFormat?: string; + toUtc?: boolean; +} + +export function applyLocale(content: string, locale: XLocale = 'en-US') { + switch (locale) { + case 'en-US': + return toEnNumbers(content); + + case 'fa-IR': + return toPersianNumbers(content); + } +} + +export type XResourceIdentifier = XResourceIDs | XTranslationId | string; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\typings\x.typings.ts + +import { Observable } from 'rxjs'; +import { XFileBlob } from '../tools/x-file.tools'; +import { XLocale } from './x-translation.typings'; +import { CountryCode, PhoneNumber } from 'libphonenumber-js'; + +// +//#region General ... +export type XStandardType = Observable | Promise | T; + +export type XOneOrManyType = T | T[]; + +export type XFormControlState = 'VALID' | 'INVALID' | 'PENDING' | 'DISABLED'; + +export type XRouterDirection = 'root' | 'forward' | 'back'; + +export type XFileReaderInputType = File | Blob | XFileBlob | string; + +export enum XRouterTarget { + TOP = '_top', + SELF = '_self', + BLANK = '_blank', + PARENT = '_parent', +} + +export type XRouterTargetIdentifier = XRouterTarget | string; + +export enum XMimeType { + // + //#region Applications ... + ApplicationOctetStream = 'application/octet-stream', + ApplicationPDF = 'application/pdf', + ApplicationZIP = 'application/zip', + ApplicationAtomXML = 'application/atom+xml', + ApplicationGZIP = 'application/gzip', + ApplicationJSON = 'application/json', + ApplicationJWT = 'application/jwt', + ApplicationMP4 = 'application/mp4', + ApplicationRTF = 'application/rtf', + ApplicationSQL = 'application/sql', + ApplicationWSDLXML = 'application/wsdl+xml', + ApplicationXWWWFormURLEncoded = 'application/x-www-form-urlencoded', + ApplicationXX509CaCert = 'application/x-x509-ca-cert', + ApplicationXML = 'application/xml', + ApplicationJavascript = 'application/javascript', + ApplicationECMAScript = 'application/ecmascript', + //#endregion + + // + //#region Audios ... + Audio3GPP = 'audio/3gpp', + AudioAAC = 'audio/aac', + AudioAMR = 'audio/AMR', + AudioMPA = 'audio/MPA', + AudioMP4 = 'audio/mp4', + AudioMPEG = 'audio/mpeg', + AudioOGG = 'audio/ogg', + //#endregion + + // + //#region Fonts ... + FontCollection = 'font/collection', + FontOTF = 'font/otf', + FontSFNT = 'font/sfnt', + FontTTF = 'font/ttf', + FontWOFF = 'font/woff', + FontWOFF2 = 'font/woff2', + //#endregion + + // + //#region Image ... + ImageBMP = 'image/bmp', + ImageGIF = 'image/gif', + ImageJPEG = 'image/jpg', + ImagePNG = 'image/png', + ImageSVG = 'image/svg+xml', + ImageTIFF = 'image/tiff', + ImageWMF = 'image/wmf', + //#endregion + + // + //#region Messages ... + MessageSIP = 'message/sip', + MessageSIPFrag = 'message/sipfrag', + Message = 'message/', + //#endregion + + // + //#region Multipart ... + MultipartEncrypted = 'multipart/encrypted', + MultipartFormData = 'multipart/form-data', + MultipartHeaderSET = 'multipart/header-set', + MultipartRelated = 'multipart/related', + MultipartReport = 'multipart/report', + MultipartSigned = 'multipart/signed', + MultipartVoiceMessage = 'multipart/voice-message', + //#endregion + + // + //#region Text ... + TextCalendar = 'text/calendar', + TextCSS = 'text/css', + TextCSV = 'text/csv', + TextCSVSchema = 'text/csv-schema', + TextDirectory = 'text/directory', + TextDNS = 'text/dns', + TextHTML = 'text/html', + TextECMAScript = 'text/ecmascript', + TextJavascript = 'text/javascript', + TextMarkdown = 'text/markdown', + TextPlain = 'text/plain', + TextRTF = 'text/rtf', + TextStrings = 'text/strings', + TextVCard = 'text/vcard', + TextXML = 'text/xml', + //#endregion + + // + //#region Video ... + Video3GPP = 'video/3gpp', + Video3GPP2 = 'video/3gpp2', + VideoH265 = 'video/H265', + VideoJPEG = 'video/JPEG', + VideoMP4 = 'video/mp4', + VideoMPV = 'video/MPV', + VideoMPEG = 'video/mpeg', + VideoOGG = 'video/ogg', + VideoQuicktime = 'video/quicktime', + //#endregion +} + +export type XMimeTypeIdentifier = XMimeType | string; + +export enum XSpinner { + Lines = 'lines', + LinesSmall = 'lines-small', + Dots = 'dots', + Bubbles = 'bubbles', + Circles = 'circles', + Circular = 'circular', + Crescent = 'crescent', +} + +export type XSpinnerIdentifier = XSpinner | string | any; + +export type XCountryCode = CountryCode; + +export type XPhoneNumber = PhoneNumber; + +export enum XFormatterType { + ApplyLocale = 'apply_locale', + FormatCurrency = 'format_currency', + StringFormatter = 'string_formatter', + FormatCurrencyAndApplyLocale = 'format_currency_apply_locale', + StringFormatterAndApplyLocale = 'string_formatter_apply_locale', +} + +export interface XFormatterOption { + type: XFormatterType; + locale?: XLocale; + seperator?: string; + currencyLable?: string; + pattern?: number[]; +} + +export enum XDialogResult { + Cancel, + Ok, + Retry, + Opened, + Restart, +} + +export interface XBaseKeyValue { + key: string; + value: T; +} + +export interface XKeyValue { + key: TKey; + value: TValue; +} + +export interface XHasAction { + action: T; + payload?: any +} + +export type XKey = keyof T; +export type XKeysOfXType = { [K in keyof T]: T[K] extends U ? K : never }[keyof T]; +export type XStringKeys = Extract, string>; +export type XValueOf = T[XKey]; +export type XRef = { value: T }; +export type XPropertyType = T[K]; +export type XModelFieldTypeIndex = { + [key in XKey]: string; +}; +export type XPartialModelFieldNames = XOneOrManyType< + Partial>> +>; +export type XDeep = keyof T & T extends object + ? { [P in keyof T]: XDeep } + : T; +export type XDeepPartial = T extends Function + ? T + : T extends object + ? { [P in keyof T]?: XDeepPartial } + : T; + +export type XModelKeyType = keyof Partial | string; + +export type XModelFieldsType = XOneOrManyType>; + +export type XModelKeyProvider = ( + key: XModelKeyType +) => XStandardType; + +export type XModelValueProvider = ( + key: XModelKeyType, + value: any +) => XStandardType; + +export interface XModelProperty + extends XKeyValue, any> { + prefix?: string; +} + +export type XGConstructor = new (...args: any[]) => T; +export type XConstructor = new (...args: any[]) => {}; +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\validators\x-general-validators.ts + +import { + XPhoneNumber, + XCountryCode, + XOneOrManyType +} from '../typings/x.typings'; +import { + isNullOrUndefined, + isNullOrEmptyString +} from '../tools/x-type-detect.tools'; +import { hasChild, toArray } from '../tools/x-array.tools'; +import { throwException } from '../tools/x-exception.tools'; +import { parsePhoneNumberFromString } from 'libphonenumber-js'; +import { XExceptionIDs } from '../constants/x-exception.resources'; + +// +//#region Custom Type Checker and Converters ... +/** + * check specified value is Valid URL Address or not ... + * + * @param value specified string which required to check + */ +export function isValidURL(value: string) { + // + const pattern = new RegExp( + '^(https?:\\/\\/)?' + // protocol + '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.?)+[a-z]{2,}|' + // domain name + '((\\d{1,3}\\.){3}\\d{1,3}))' + // ip (v4) address + '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port + '(\\?[;&a-z\\d%_.~+=-]*)?' + // query string + '(\\#[-a-z\\d_]*)?$', + 'i' + ); + + // + return pattern.test(value); +} + +/** + * check specified value is Valid URL Address or not ... + * + * @param value specified string which required to check + */ +export function isValidEmail(value: string) { + // + const pattern = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}/gim; + + // + return pattern.test(value); +} + +/** + * check specified value is Valid Phone Number or not ... + * + * @param value specified string which required to check + * @param defaultCountryCode an optional value which used for country code if not exists in value + */ +export function isValidPhoneNumber( + value: string, + defaultCountryCode?: XCountryCode +) { + // + const mParsedPhoneNumber = parsePhoneNumber(value, defaultCountryCode); + if (!mParsedPhoneNumber) { + return false; + } + + // + return mParsedPhoneNumber.isValid(); +} + +/** + * parse an string as phone number and extract several information of it ... + * + * @param value specified string is represent phone number ... + * @param defaultCountryCode an optional value which used for country code if not exists in value + */ +export function parsePhoneNumber( + value: string, + defaultCountryCode?: XCountryCode +): XPhoneNumber { + // + try { + return parsePhoneNumberFromString(value, defaultCountryCode); + } catch { + return null; + } +} + +/** + * normalize specified phone number and format it ... + * + * @param value specified string which required to normalized ... + * @param defaultCountryCode an optional value which used for country code if not exists in value + */ +export function normalizePhoneNumber( + value: string, + defaultCountryCode?: XCountryCode +) { + // + try { + // + const parsedNumber = parsePhoneNumber(value, defaultCountryCode); + if (!parsedNumber) { + throw new Error('can not Parse Phone Number ...'); + } + + // + return parsedNumber.number.toString(); + } catch { + return null; + } +} +//#endregion + +// +//#region Data Validators from Several Types ... +export class XValidators { + // + private static throwInvalidArgsException() { + throwException(XExceptionIDs.InvalidArgs); + } + + /** + * check args not Null or Undefined ... + * + * @param args any args which required to check + */ + static validateNotNull(...args: any[]) { + // + const isValid = args.every(arg => !isNullOrUndefined(arg)); + if (!isValid) { + XValidators.throwInvalidArgsException(); + } + } + + /** + * check args not empty string or null or undefined ... + * + * @param args any args which required to check + */ + static validateNotEmpty(...args: string[]) { + // + const isValid = args.every(arg => !isNullOrEmptyString(arg)); + if (!isValid) { + XValidators.throwInvalidArgsException(); + } + } + + /** + * check args must have childs ... + * + * @param args any args which required to check + */ + static validateHasChilds(...args: any[][]) { + // + const isValid = args.every(arg => hasChild(arg)); + if (!isValid) { + XValidators.throwInvalidArgsException(); + } + } + + /** + * check all args not equal to specified values ... + * + * @param values a single or collection value which going to compare + * @param args any args which required to check + */ + static validateNotEquals(values: XOneOrManyType, ...args: T[]) { + // + const mValues = toArray(values); + + // + const isValid = args.every(arg => !mValues.includes(arg)); + if (!isValid) { + XValidators.throwInvalidArgsException(); + } + } + + /** + * check all args equal to specified values ... + * + * @param values a single or collection value which going to compare + * @param args any args which required to check + */ + static validateEquals(values: XOneOrManyType, ...args: T[]) { + // + const mValues = toArray(values); + + // + const isValid = args.every(arg => mValues.includes(arg)); + if (!isValid) { + XValidators.throwInvalidArgsException(); + } + } + + /** + * check all args bigger than specified values ... + * + * @param values a single or collection value which going to compare + * @param args any args which required to check + */ + static validateBiggerThan(values: number | number[], ...args: number[]) { + // + const mValues = toArray(values); + + // + const isValid = args.every(arg => mValues.every(n => arg > n)); + if (!isValid) { + XValidators.throwInvalidArgsException(); + } + } + + /** + * check all args less than specified values ... + * + * @param values a single or collection value which going to compare + * @param args any args which required to check + */ + static validateLessThan(values: number | number[], ...args: number[]) { + // + const mValues = toArray(values); + + // + const isValid = args.every(arg => mValues.every(n => arg < n)); + if (!isValid) { + XValidators.throwInvalidArgsException(); + } + } + + /** + * check all args bigger than or equals to specified values ... + * + * @param values a single or collection value which going to compare + * @param args any args which required to check + */ + static validateBiggerThanOrEquals( + values: number | number[], + ...args: number[] + ) { + // + const mValues = toArray(values); + + // + const isValid = args.every(arg => mValues.every(n => arg >= n)); + if (!isValid) { + XValidators.throwInvalidArgsException(); + } + } + + /** + * check all args less than or equals to specified values ... + * + * @param values a single or collection value which going to compare + * @param args any args which required to check + */ + static validateLessThanOrEquals( + values: number | number[], + ...args: number[] + ) { + // + const mValues = toArray(values); + + // + const isValid = args.every(arg => mValues.every(n => arg <= n)); + if (!isValid) { + XValidators.throwInvalidArgsException(); + } + } + + /** + * check specified value is Valid URL Address or not ... + * + * @param value specified string which required to check + */ + static validateURL(value: string) { + // + XValidators.validateNotEmpty(value); + + // + if (!isValidURL(value)) { + throwException(XExceptionIDs.InvalidEmailAddress); + } + } + + /** + * check specified value is Valid Email Address or not ... + * + * @param value specified string which required to check + */ + static validateEmailAddress(value: string) { + // + XValidators.validateNotEmpty(value); + + // + if (!isValidEmail(value)) { + throwException(XExceptionIDs.InvalidEmailAddress); + } + } + + /** + * check specified value is Valid Phone Number or not ... + * + * @param value specified string which required to check + * @param defaultCountryCode an optional value which used for country code if not exists in value + */ + static validatePhoneNumber(value: string, defaultCountryCode?: XCountryCode) { + // + XValidators.validateNotEmpty(value); + + // + if (!isValidPhoneNumber(value, defaultCountryCode)) { + throwException(XExceptionIDs.InvalidMobileNumber); + } + } +} +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\validators\x-valid-email.directive.ts + +import { Directive } from '@angular/core'; +import { isValidEmail } from './x-general-validators'; +import { Validator, AbstractControl, ValidationErrors } from '@angular/forms'; + +@Directive({ + // tslint:disable-next-line:directive-selector + selector: '[xValidEmail]' +}) +export class XValidEmailDirective implements Validator { + // + private errorObject = { + xValidEmail: true + }; + + validate(control: AbstractControl): ValidationErrors { + // + const sourceValue = + control && control.value ? (control.value as string) : null; + + // + let isValid = false; + try { + isValid = isValidEmail(sourceValue); + } catch {} + + // + return !isValid ? this.errorObject : null; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\validators\x-valid-phone-number.directive.ts + +import { XCountryCode } from '../typings/x.typings'; +import { Directive, Input, Inject } from '@angular/core'; +import { isValidPhoneNumber } from './x-general-validators'; +import { X_FRAMEWORK_CORE_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkCoreConfig } from '../config/x-framework-core.config'; +import { Validator, AbstractControl, ValidationErrors } from '@angular/forms'; + +@Directive({ + // tslint:disable-next-line:directive-selector + selector: '[xValidPhoneNumber]' +}) +export class XValidPhoneNumberDirective implements Validator { + // + private errorObject = { + xValidMobile: true + }; + + // + @Input() + defaultCountryCode: XCountryCode = this.config.defaultCountryCode; + + constructor( + @Inject(X_FRAMEWORK_CORE_CONFIG) private config: XFrameworkCoreConfig + ) {} + + validate(control: AbstractControl): ValidationErrors { + // + const value = control && control.value ? (control.value as string) : null; + + // + const isValid = isValidPhoneNumber(value, this.defaultCountryCode); + + // + return !isValid ? this.errorObject : null; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\validators\x-valid-url.directive.ts + +import { Directive, Input } from '@angular/core'; +import { isValidURL } from './x-general-validators'; +import { Validator, AbstractControl, ValidationErrors } from '@angular/forms'; + +@Directive({ + // tslint:disable-next-line:directive-selector + selector: '[xValidUrl]' +}) +export class XValidUrlDirective implements Validator { + // + private errorObject = { + xValidUrl: true + }; + + @Input() + isOptional: boolean; + + constructor() {} + + validate(control: AbstractControl): ValidationErrors { + // + const sourceValue = + control && control.value ? (control.value as string) : null; + + // + let isValid = false; + if (!sourceValue && this.isOptional) { + isValid = true; + } else { + isValid = sourceValue && isValidURL(sourceValue); + } + + // + return !isValid ? this.errorObject : null; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkCoreHolder\projects\x-framework-core\src\lib\validators\x-value-match.directive.ts + +import { + NgModel, + Validator, + AbstractControl, + ValidationErrors +} from '@angular/forms'; +import { Directive, Input } from '@angular/core'; +import { isNullOrEmptyString } from '../tools/x-type-detect.tools'; + +@Directive({ + // tslint:disable-next-line:directive-selector + selector: '[xValueMatch]' +}) +export class XValueMatchDirective implements Validator { + private errorObject = { + xValueMatch: true + }; + + // + @Input('xValueMatch') + matchTo: NgModel; + + // + validate(control: AbstractControl): ValidationErrors { + // + if (!control) { + return this.errorObject; + } + const value = control.value as string; + + // + if (!this.matchTo) { + return this.errorObject; + } + const destControl = this.matchTo.control; + if (!destControl) { + return this.errorObject; + } + + // + const destValue = destControl.value; + + // + const isValid = + !isNullOrEmptyString(destValue) && + !isNullOrEmptyString(value) && + value === destValue; + + // + if (isValid && destControl.invalid) { + destControl.updateValueAndValidity(); + } + + // + return !isValid ? this.errorObject : null; + } +} diff --git a/Documents/GeneratedContext/xDashboard-xFrameworkIdentitySDKHolder-20260809_235714.txt b/Documents/GeneratedContext/xDashboard-xFrameworkIdentitySDKHolder-20260809_235714.txt new file mode 100644 index 0000000..34cc4bc --- /dev/null +++ b/Documents/GeneratedContext/xDashboard-xFrameworkIdentitySDKHolder-20260809_235714.txt @@ -0,0 +1,8160 @@ + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkIdentitySDKHolder\projects\x-framework-identity-sdk\src\public-api.ts + +/* + * Public API Surface of x-framework-identity-sdk + */ +// +// Config ... +export * from './lib/config/x-api-service.config'; +export * from './lib/config/x-framework-identity-sdk.config'; + +// +// Base ... +export * from './lib/base/x-base-api.service'; + +// +// Models ... +export * from './lib/models/x-user.dto'; +export * from './lib/models/x-page.dto'; +export * from './lib/models/x-login.dto'; +export * from './lib/models/x-query.dto'; +export * from './lib/models/x-string.dto'; +export * from './lib/models/x-friend-dto'; +export * from './lib/models/x-discovery.dto'; +export * from './lib/models/x-friendship.dto'; +export * from './lib/models/x-device-info.dto'; +export * from './lib/models/x-registration-dto'; + +// +// Guards ... +export * from './lib/guards/auth.guard'; +export * from './lib/guards/not-auth.guard'; + +// +// Services ... +export * from './lib/x-account.service'; +export * from './lib/services/api-http-interceptor.service'; + +// +// Constants ... +export * from './lib/constants/x-account.keys'; +export * from './lib/constants/x-header.enum'; +export * from './lib/constants/x-api-scope.enum'; + +// +// Validators ... +export * from './lib/validators/api-validators'; + +// +// Typings ... +export * from './lib/typings/x-account.typings'; +export * from './lib/typings/x-endpoint.typings'; +export * from './lib/typings/x-registration.typings'; + +// +// Providers ... +export * from './lib/providers/x-url.encoder'; + +// +// Tools ... +export * from './lib/tools/x-endpoint.tools'; + +// +// Helpers ... +export * from './lib/helpers/x-account.helper'; + +// +// Token ... +export * from './lib/tokens/x-injectable-tokens'; + +// +// Module ... +export * from './lib/x-framework-identity-sdk.module'; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkIdentitySDKHolder\projects\x-framework-identity-sdk\src\test.ts + +// This file is required by karma.conf.js and loads recursively all the .spec and framework files + +import 'zone.js'; +import 'zone.js/testing'; +import { getTestBed } from '@angular/core/testing'; +import { + BrowserDynamicTestingModule, + platformBrowserDynamicTesting +} from '@angular/platform-browser-dynamic/testing'; + +declare const require: { + context(path: string, deep?: boolean, filter?: RegExp): { + keys(): string[]; + (id: string): T; + }; +}; + +// First, initialize the Angular testing environment. +getTestBed().initTestEnvironment( + BrowserDynamicTestingModule, + platformBrowserDynamicTesting() +); +// Then we find all the tests. +const context = require.context('./', true, /\.spec\.ts$/); +// And load the modules. +context.keys().map(context); + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkIdentitySDKHolder\projects\x-framework-identity-sdk\src\lib\x-account.service.ts + +import { + X_API_CONFIG, + X_FRAMEWORK_IDENTITY_SDK_CONFIG, +} from './tokens/x-injectable-tokens'; +import { Router } from '@angular/router'; +import { HttpClient } from '@angular/common/http'; +import { Inject, Injectable } from '@angular/core'; +import { XManagerService } from 'x-framework-services'; +import { XApiConfiguration } from './config/x-api-service.config'; +import { XAccountFinalService } from './partial/x-account.final.service'; +import { XFrameworkIdentitySDKConfig } from './config/x-framework-identity-sdk.config'; + +@Injectable({ + providedIn: 'root', +}) +export class XAccountService extends XAccountFinalService { + // + //#region Constructor ... + constructor( + protected router: Router, + protected httpClient: HttpClient, + @Inject(X_FRAMEWORK_IDENTITY_SDK_CONFIG) + config: XFrameworkIdentitySDKConfig, + @Inject(X_API_CONFIG) apiConfig: XApiConfiguration, + protected managerService: XManagerService + ) { + super(router, httpClient, config, apiConfig, managerService); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkIdentitySDKHolder\projects\x-framework-identity-sdk\src\lib\x-framework-identity-sdk.module.ts + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { RouterModule } from '@angular/router'; +import { XFrameworkCoreModule } from 'x-framework-core'; +import { HttpClientModule } from '@angular/common/http'; +import { XFrameworkServicesModule } from 'x-framework-services'; + +@NgModule({ + declarations: [], + imports: [ + RouterModule, + CommonModule, + HttpClientModule, + XFrameworkCoreModule, + XFrameworkServicesModule, + ], + exports: [ + CommonModule, + HttpClientModule, + XFrameworkCoreModule, + XFrameworkServicesModule, + ], + providers: [], +}) +export class XFrameworkIdentitySdkModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkIdentitySDKHolder\projects\x-framework-identity-sdk\src\lib\base\x-base-api.service.ts + +import { + XParam, + XLocale, + XHeader, + hasChild, + XKeyValue, + toXString, + XBaseService, + XContentType, + XExceptionIDs, + XOneOrManyType, + throwException, + isNullOrUndefined, + isNullOrEmptyString, +} from 'x-framework-core'; +import { Inject } from '@angular/core'; +import { + X_API_CONFIG, + X_FRAMEWORK_IDENTITY_SDK_CONFIG, +} from '../tokens/x-injectable-tokens'; +import { XQueryDto } from '../models/x-query.dto'; +import { setParams } from '../tools/x-endpoint.tools'; +import { XPageRequestDto } from '../models/x-page.dto'; +import { XEndPoints } from '../typings/x-endpoint.typings'; +import { XHttpUrlEncodingCodec } from '../providers/x-url.encoder'; +import { XApiConfiguration } from '../config/x-api-service.config'; +import { getAuthorizationHeaderValue } from '../constants/x-header.enum'; +import { HttpHeaders, HttpClient, HttpParams } from '@angular/common/http'; +import { XLoginResponseDto, XTokenResponseDto } from '../models/x-login.dto'; +import { XFrameworkIdentitySDKConfig } from '../config/x-framework-identity-sdk.config'; + +export abstract class XBaseApiService extends XBaseService { + // + public readonly basePath: string; + public readonly version: string; + public readonly baseEndPointRoute: string; + public readonly defaultHeaders = new HttpHeaders(); + public apiConfig = new XApiConfiguration(); + + constructor( + public readonly endPoint: XEndPoints, + public readonly applyApiIdentifier: boolean = false, + public readonly applyApiVersion: boolean = false, + protected httpClient: HttpClient, + @Inject(X_FRAMEWORK_IDENTITY_SDK_CONFIG) + config: XFrameworkIdentitySDKConfig, + @Inject(X_API_CONFIG) apiConfig: XApiConfiguration + ) { + // + super(config); + + // + if (apiConfig) { + // + this.version = ''; + if (applyApiVersion) { + this.version = isNullOrEmptyString(apiConfig.apiVersion) + ? null + : apiConfig.apiVersion.replace('/', ''); + } + + // + let bApiPath = apiConfig.baseApiPath || config.baseUrl; + if (isNullOrEmptyString(bApiPath)) { + throwException(XExceptionIDs.InvalidConfiguration); + } + + // + bApiPath = bApiPath.endsWith('/') + ? bApiPath.substring(0, bApiPath.length - 1) + : bApiPath; + + // + // Check Api Identifier ... + if (applyApiIdentifier) { + const apiIdentifier = config.apiIdentifier || apiConfig.apiIdentifier; + if (!isNullOrEmptyString(apiIdentifier)) { + bApiPath = `${bApiPath}/${apiIdentifier}`; + } + } + + // + this.basePath = bApiPath.endsWith('/') + ? bApiPath.substring(0, bApiPath.length - 1) + : bApiPath; + + // + this.apiConfig = apiConfig; + } + + // + this.baseEndPointRoute = isNullOrEmptyString(this.version) + ? `${this.basePath}/${this.endPoint}` + : `${this.basePath}/${this.version}/${this.endPoint}`; + + // + if ( + isNullOrUndefined(this.basePath) || + isNullOrUndefined(this.baseEndPointRoute) + ) { + throwException(XExceptionIDs.InvalidConfiguration); + } + + // + // Setting XPowered in Default Headers ... + this.defaultHeaders = this.defaultHeaders.set( + XHeader.XPoweredBy, + apiConfig.poweredByValue + ); + + // + // Setting Certificate Check ... + if ( + isNullOrUndefined(config.rejectUnauthorized) && + config.rejectUnauthorized === true + ) { + // + this.defaultHeaders = this.defaultHeaders.set( + XHeader.RejectUnauthorized, + toXString(config.rejectUnauthorized) + ); + } + } + + // + //#region Actions ... + + /** + * @param consumes string[] mime-types + * @return true: consumes contains 'multipart/form-data', false: otherwise + */ + public canConsumeForm(consumes: string[]): boolean { + // + const form = 'multipart/form-data'; + for (const consume of consumes) { + if (form === consume) { + return true; + } + } + + // + return false; + } + + /** + * adding Authentication & Authorization headers + */ + public addAuthentication( + headers: HttpHeaders, + tokens?: XTokenResponseDto + ): HttpHeaders; + public addAuthentication( + headers: HttpHeaders, + tokens?: XLoginResponseDto + ): HttpHeaders { + // + // if doesn't Provide Tokens, read and apply from Configurations ... + if (!tokens) { + // + // Handle Authentication (access_token) required + if ( + !isNullOrUndefined(this.apiConfig.apiKeys) && + !isNullOrEmptyString(this.apiConfig.apiKeys.Authorization) + ) { + headers = headers.set( + XHeader.Authorization, + this.apiConfig.apiKeys.Authorization + ); + } + } + + // + // if Provide Tokens read and apply from it ... + if (tokens) { + // + // Access Token ... + if (!isNullOrEmptyString(tokens.accessToken)) { + headers = headers.set( + XHeader.Authorization, + getAuthorizationHeaderValue(tokens.accessToken) + ); + } + + // + // Refresh Token ... + if (!isNullOrEmptyString(tokens.refreshToken)) { + headers = headers.set(XHeader.RefreshToken, tokens.refreshToken); + } + + // + // Expired At ... + if (!isNullOrUndefined(tokens.expiresAt)) { + headers = headers.set(XHeader.ExpiresAt, tokens.expiresAt.toString()); + } + } + + // + return headers; + } + + /** + * add custom header to request + * + * @param headers headers object + * @param key custom header key + * @param value custom header value + */ + public addCustomHeader( + headers?: HttpHeaders, + item?: XKeyValue, + forceAddDefaultHeaders = false + ): HttpHeaders { + // + // Instance Headers if not Exists ... + if (!headers) { + headers = new HttpHeaders(); + } + + // + // Add Default Headers if required ... + if ( + forceAddDefaultHeaders && + !isNullOrUndefined(this.defaultHeaders) && + hasChild(this.defaultHeaders.keys()) + ) { + // + this.defaultHeaders.keys().forEach((dKey) => { + // + const dValue = this.defaultHeaders.get(dKey); + headers = headers.set(dKey, dValue); + }); + } + + // + // Add Item ... + if ( + item && + !isNullOrUndefined(item) && + !isNullOrUndefined(item.key) && + !isNullOrUndefined(item.value) + ) { + // + headers = headers.set(item.key, toXString(item.value)); + } + + // + return headers; + } + + /** + * add default accept selected to headers + */ + public addAcceptJson(headers: HttpHeaders): HttpHeaders { + // + // to determine the Accept header + const httpHeaderAccepts: string[] = [XContentType.AppJson]; + const httpHeaderAcceptSelected: string | undefined = + this.apiConfig.selectHeaderAccept(httpHeaderAccepts); + if (httpHeaderAcceptSelected !== undefined) { + headers = headers.set(XHeader.Accept, httpHeaderAcceptSelected); + } + + // + return headers; + } + + /** + * adding application/octet-stream Accept in header ... + * + * @param headers the exists contents of header + */ + public addAcceptOctetStream(headers: HttpHeaders): HttpHeaders { + // + // to determine the Accept header + const httpHeaderAccepts: string[] = [XContentType.AppOctetStream]; + const httpHeaderAcceptSelected: string | undefined = + this.apiConfig.selectHeaderAccept(httpHeaderAccepts); + if (httpHeaderAcceptSelected !== undefined) { + headers = headers.set(XHeader.Accept, httpHeaderAcceptSelected); + } + + // + return headers; + } + + /** + * add language to headers ... + * + * @param headers the source headers ... + * @param language the specific language name ... + */ + public addLanguageHeader( + headers: HttpHeaders, + language: XLocale | string + ): HttpHeaders { + // + if (isNullOrEmptyString(language)) { + return headers; + } + + // + return this.addCustomHeader(headers, { + key: XParam.Language, + value: language, + }); + } + + /** + * add ContentType to headers + */ + public addContentType( + headers: HttpHeaders, + consumes?: string[] + ): HttpHeaders { + // + // to determine the Content-Type header + if (!consumes) { + consumes = []; + } + consumes = [...consumes, XContentType.AppJson]; + const httpContentTypeSelected: string | undefined = + this.apiConfig.selectHeaderContentType(consumes); + if (httpContentTypeSelected !== undefined) { + headers = headers.set(XHeader.ContentType, httpContentTypeSelected); + } + + // + return headers; + } + + /** + * generate proper http params based on XQuery ... + * + * @param query the Qury Options ... + */ + public generateXQueryHttpParams(query?: XQueryDto): HttpParams { + // + let result = new HttpParams({ + encoder: new XHttpUrlEncodingCodec(), + }); + + // + if (query) { + // + // Filter ... + if (!isNullOrEmptyString(query.filter)) { + result = result.set(XParam.Filter, query.filter as string); + } + + // + // ContainsDetail ... + if (!isNullOrUndefined(query.containsDetail)) { + result = result.set( + XParam.ContainsDetail, + toXString(query.containsDetail) + ); + } + + // + // EnableTracking ... + if (!isNullOrUndefined(query.enableTracking)) { + result = result.set( + XParam.EnableTracking, + toXString(query.enableTracking) + ); + } + + // + // SortBy ... + if (!isNullOrEmptyString(query.sortBy)) { + result = result.set(XParam.SortBy, toXString(query.sortBy)); + } + + // + // IsAscending ... + if (!isNullOrUndefined(query.isAscending)) { + result = result.set(XParam.IsAscending, toXString(query.isAscending)); + } + + // + // Page ... + if (!isNullOrUndefined(query.page)) { + result = result.set(XParam.Page, toXString(query.page)); + } + + // + // PageSize ... + if (!isNullOrUndefined(query.pageSize)) { + result = result.set(XParam.PageSize, toXString(query.pageSize)); + } + } + + // + return result; + } + + /** + * generate proper http params based on RequestPage ... + * + * @param request the RequestPage Options ... + */ + public generateXRequestPageHttpParams(request?: XPageRequestDto): HttpParams { + // + let queryParameters = new HttpParams({ + encoder: new XHttpUrlEncodingCodec(), + }); + + // + if (request) { + // + // First ... + if (!isNullOrUndefined(request.first)) { + queryParameters = queryParameters.set( + XParam.First, + toXString(request.first) + ); + } + + // + // Last ... + if (!isNullOrUndefined(request.last)) { + queryParameters = queryParameters.set( + XParam.Last, + toXString(request.last) + ); + } + + // + // After ... + if (!isNullOrEmptyString(request.after)) { + queryParameters = queryParameters.set( + XParam.After, + toXString(request.after) + ); + } + + // + // Before ... + if (!isNullOrEmptyString(request.before)) { + queryParameters = queryParameters.set( + XParam.Before, + toXString(request.before) + ); + } + + // + // SortBy ... + if (!isNullOrEmptyString(request.sortBy)) { + queryParameters = queryParameters.set( + XParam.SortBy, + toXString(request.sortBy) + ); + } + + // + // DescendingSort ... + if (!!request.descendingSort) { + queryParameters = queryParameters.set( + XParam.DescendingSort, + toXString(request.descendingSort) + ); + } + } + + // + return queryParameters; + } + + /** + * add specific param to an existings params collection ... + * + * @param params the exists params ... + * @param item the specific key value ... + */ + public addCustomParam( + params?: HttpParams, + item?: XKeyValue + ): HttpParams { + // + if (!params) { + // + params = new HttpParams({ + encoder: new XHttpUrlEncodingCodec(), + }); + } + + // + if ( + item && + !isNullOrUndefined(item) && + !isNullOrUndefined(item.key) && + !isNullOrUndefined(item.value) + ) { + // + params = params.set(item.key, toXString(item.value)); + } + + // + return params; + } + + /** + * add specific language param to an existings params collection ... + * + * @param params the exists params ... + * @param language the specific language value ... + */ + public addLanguageQueryParam( + params?: HttpParams, + language?: XLocale | string + ): HttpParams { + // + if (!params) { + // + params = new HttpParams({ + encoder: new XHttpUrlEncodingCodec(), + }); + } + + // + if (isNullOrEmptyString(language)) { + return params; + } + + // + params = params.set(XParam.Language, language); + + // + return params; + } + + /** + * attach containsDetail to Query String ... + * + * @param params exists param .. + * @param containsDetail the value ... + * @returns an instance of HttpParams ... + */ + public addContainsDetailQueryParam( + params?: HttpParams, + containsDetail?: boolean + ): HttpParams { + // + if (!params) { + // + params = new HttpParams({ + encoder: new XHttpUrlEncodingCodec(), + }); + } + + // + if (!!containsDetail) { + params = params.set(XParam.ContainsDetail, toXString(containsDetail)); + } + + // + return params; + } + + /** + * generate containsDetail Http Param ... + * + * @param containsDetail the param value ... + */ + public generateContainsDetailHttpParam(containsDetail: boolean): HttpParams { + // + let queryParameters = new HttpParams({ + encoder: new XHttpUrlEncodingCodec(), + }); + if (containsDetail) { + queryParameters = queryParameters.set( + XParam.ContainsDetail, + containsDetail.toString() + ); + } + + // + return queryParameters; + } + + /** + * generate and retrieve language http param ... + * + * @param language the specific language + */ + public generateLanguageHttpParam(language: XLocale | string): HttpParams { + // + let result = new HttpParams({ + encoder: new XHttpUrlEncodingCodec(), + }); + + // + if (!isNullOrEmptyString(language)) { + result = result.set(XParam.Language, language); + } + + // + return result; + } + + /** + * retrieve specific Action Route + * @param action the specified Action endpoint + * @returns complete action route contains route params + */ + public getActionRoute( + action: T, + params?: XOneOrManyType> + ) { + // + if (isNullOrEmptyString(String(action))) { + return this.baseEndPointRoute; + } + + // + let result = `${this.baseEndPointRoute}/${String(action)}`; + if (!hasChild(params)) { + return result; + } + + // + result = setParams(result, params); + return result; + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkIdentitySDKHolder\projects\x-framework-identity-sdk\src\lib\config\x-api-service.config.ts + +import { + XApiHeader, + getAuthorizationHeaderValue +} from '../constants/x-header.enum'; +import { isNullOrUndefined } from 'x-framework-core'; +import { XUserAccountInfo } from '../typings/x-account.typings'; + +export interface XApiConfigurationParameters { + apiKeys?: { [key: string]: string }; + username?: string; + password?: string; + accessToken?: string | (() => string); + baseApiPath?: string; + apiVersion?: string; + apiIdentifier?: string; + withCredentials?: boolean; + poweredByValue?: string; + revisionSecretKey?: string; +} + +export class XApiConfiguration { + apiKeys?: { [key: string]: string }; + username?: string; + password?: string; + accessToken?: string | (() => string); + baseApiPath?: string; + apiVersion?: string; + apiIdentifier?: string; + withCredentials?: boolean; + poweredByValue?: string; + revisionSecretKey?: string; + + constructor(configurationParameters: XApiConfigurationParameters = {}) { + this.apiKeys = configurationParameters.apiKeys; + this.username = configurationParameters.username; + this.password = configurationParameters.password; + this.accessToken = configurationParameters.accessToken; + this.baseApiPath = configurationParameters.baseApiPath; + this.apiVersion = configurationParameters.apiVersion; + this.apiIdentifier = configurationParameters.apiIdentifier; + this.withCredentials = configurationParameters.withCredentials; + this.poweredByValue = configurationParameters.poweredByValue; + this.revisionSecretKey = configurationParameters.revisionSecretKey; + } + + /** + * Select the correct content-type to use for a request. + * Uses {@link Configuration#isJsonMime} to determine the correct content-type. + * If no content type is found return the first found type if the contentTypes is not empty + * @param contentTypes - the array of content types that are available for selection + * @returns the selected content-type or undefined if no selection could be made. + */ + public selectHeaderContentType(contentTypes: string[]): string | undefined { + if (contentTypes.length === 0) { + return undefined; + } + + const type = contentTypes.find(x => this.isJsonMime(x)); + if (type === undefined) { + return contentTypes[0]; + } + return type; + } + + /** + * Select the correct accept content-type to use for a request. + * Uses {@link Configuration#isJsonMime} to determine the correct accept content-type. + * If no content type is found return the first found type if the contentTypes is not empty + * @param accepts - the array of content types that are available for selection. + * @returns the selected content-type or undefined if no selection could be made. + */ + public selectHeaderAccept(accepts: string[]): string | undefined { + if (accepts.length === 0) { + return undefined; + } + + const type = accepts.find(x => this.isJsonMime(x)); + if (type === undefined) { + return accepts[0]; + } + return type; + } + + /** + * Check if the given MIME is a JSON MIME. + * JSON MIME examples: + * application/json + * application/json; charset=UTF8 + * APPLICATION/JSON + * application/vnd.company+json + * @param mime - MIME (Multipurpose Internet Mail Extensions) + * @return True if the given MIME is JSON, false otherwise. + */ + public isJsonMime(mime: string): boolean { + const jsonMime: RegExp = new RegExp( + '^(application/json|[^;/ \t]+/[^;/ \t]+[+]json)[ \t]*(;.*)?$', + 'i' + ); + return ( + mime != null && + (jsonMime.test(mime) || + mime.toLowerCase() === 'application/json-patch+json') + ); + } + + /** + * when a User LoggedIn, the User Info must passed to this object + * for using to add authentication in services ... + * + * @param userAccount loggedIn user info + */ + public async setUserAccount(userAccount: XUserAccountInfo): Promise { + // + // Fill Empty Values to user Account if it' null ... + // this is used for clearing stored data on logout event ... + if (isNullOrUndefined(userAccount)) { + userAccount = { + userSelectBy: '', + accessToken: '', + refreshToken: '', + expiresAt: -1, + profile: null + }; + } + + // + this.accessToken = userAccount.accessToken; + this.username = userAccount.userSelectBy; + + // + if (!this.apiKeys) { + this.apiKeys = {}; + } + + // + this.apiKeys[XApiHeader.ExpiresAt] = userAccount.expiresAt + ? userAccount.expiresAt.toString() + : '-1'; + this.apiKeys[XApiHeader.RefreshToken] = userAccount.refreshToken; + this.apiKeys[XApiHeader.Authorization] = getAuthorizationHeaderValue( + userAccount.accessToken + ); + } +} + +export const COLLECTION_FORMATS = { + csv: ',', + tsv: ' ', + ssv: ' ', + pipes: '|' +}; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkIdentitySDKHolder\projects\x-framework-identity-sdk\src\lib\config\x-framework-identity-sdk.config.ts + +import { XFrameworkCoreConfig } from 'x-framework-core'; +import { XFrameworkServicesConfig } from 'x-framework-services'; + +export type XSharedConfig = XFrameworkCoreConfig & + XFrameworkServicesConfig & { [key: string]: any }; + +export interface XFrameworkIdentitySDKConfig extends XSharedConfig { + // + // Api Configuration ... + baseUrl: string; + apiVersion: string; + poweredValue: string; + apiIdentifier: string; + + // + // Server SSL Check ... + rejectUnauthorized?: boolean; + + // + // Registration Types ... + registrationType: string[]; + + // + // Registration Available ... + minAvailableDateConst: number; + maxAvailableDateConst: number; + + // + // Debouncing async actions Time ... + debounceTime: number; + + // + nameMinLength: number; + nameMaxLength: number; + + // + registrationOnlyWithInvitation: boolean; + + // + userNameMinLength: number; + userNameMaxLength: number; + + // + passwordPolicies?: { + requireDigit?: boolean; + requiredLength?: number; + requiredUniqueChars?: number; + requireLowercase?: boolean; + requireNonAlphanumeric?: boolean; + requireUppercase?: boolean; + }; + + // + passwordMinLength: number; + passwordMaxLength: number; + + // + verificationCodeLength: number; + delayBetweenTwoVerificationCode: number; + + // + // Attach Pages Index to Handle in Guard Rejects ... + Pages: any; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkIdentitySDKHolder\projects\x-framework-identity-sdk\src\lib\constants\x-account.keys.ts + +export enum XAccountStorageKeys { + UserSelectBy = '[ACCOUNT] USER', + DefaultUser = '[ACCOUNT] DEFAULT USER', + UserInfos = '[ACCOUNT] USER INFO' +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkIdentitySDKHolder\projects\x-framework-identity-sdk\src\lib\constants\x-api-scope.enum.ts + +export enum XApiScope { + Read, + Write, + Admin, + Manage, +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkIdentitySDKHolder\projects\x-framework-identity-sdk\src\lib\constants\x-header.enum.ts + +export enum XApiHeader { + ExpiresAt = 'expires_at', + RefreshToken = 'refresh_token', + Authorization = 'Authorization', + AuthorizationPrefix = 'Bearer ', +} + +export function getAuthorizationHeaderValue(token: string): string { + return `${XApiHeader.AuthorizationPrefix}${token}`; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkIdentitySDKHolder\projects\x-framework-identity-sdk\src\lib\guards\auth.guard.ts + +import { + Route, + CanLoad, + UrlTree, + UrlSegment, + CanActivate, + CanActivateChild, + RouterStateSnapshot, + ActivatedRouteSnapshot, +} from '@angular/router'; +import { Observable } from 'rxjs'; +import { map, tap, filter } from 'rxjs/operators'; +import { Injectable, Inject } from '@angular/core'; +import { XManagerService } from 'x-framework-services'; +import { XAccountService } from './../x-account.service'; +import { XLoggable, XExceptionIDs, isNullOrUndefined } from 'x-framework-core'; +import { X_FRAMEWORK_IDENTITY_SDK_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkIdentitySDKConfig } from '../config/x-framework-identity-sdk.config'; + +@Injectable({ + providedIn: 'root', +}) +export class AuthGuard + extends XLoggable + implements CanActivate, CanActivateChild, CanLoad +{ + // + constructor( + private authService: XAccountService, + private managerService: XManagerService, + @Inject(X_FRAMEWORK_IDENTITY_SDK_CONFIG) + public config: XFrameworkIdentitySDKConfig + ) { + super(config); + } + + // + //#region Actions ... + // + canActivate( + next: ActivatedRouteSnapshot, + state: RouterStateSnapshot + ): + | Observable + | Promise + | boolean + | UrlTree { + return this.handleGuard(); + } + + // + canActivateChild( + next: ActivatedRouteSnapshot, + state: RouterStateSnapshot + ): + | Observable + | Promise + | boolean + | UrlTree { + return this.handleGuard(); + } + + // + canLoad( + route: Route, + segments: UrlSegment[] + ): Observable | Promise | boolean { + return this.handleGuard(); + } + //#endregion + + // + //#region Private Action ... + private handleGuard(): Observable { + return this.authService.state$.pipe( + filter((state) => !isNullOrUndefined(state)), + map((state) => state.isLoggedIn), + tap((isPassed) => { + // + if (!isPassed) { + this.handleGuardReject(); + } + }) + ); + } + + // + private handleGuardReject(): void { + // + this.managerService.notificationService.presentErrorNotification({ + exception: XExceptionIDs.NotAuthorized, + dissmissable: true, + }); + + // + this.managerService.navigateByPageReplace(this.config.Pages.Login); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkIdentitySDKHolder\projects\x-framework-identity-sdk\src\lib\guards\not-auth.guard.ts + +import { + Route, + UrlTree, + CanLoad, + UrlSegment, + CanActivate, + CanActivateChild, + RouterStateSnapshot, + ActivatedRouteSnapshot, +} from '@angular/router'; +import { Observable } from 'rxjs'; +import { map, tap, filter } from 'rxjs/operators'; +import { Injectable, Inject } from '@angular/core'; +import { XManagerService } from 'x-framework-services'; +import { XAccountService } from './../x-account.service'; +import { + XLoggable, + XExceptionIDs, + isNullOrUndefined, + toObservable, +} from 'x-framework-core'; +import { X_FRAMEWORK_IDENTITY_SDK_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkIdentitySDKConfig } from '../config/x-framework-identity-sdk.config'; + +@Injectable({ + providedIn: 'root', +}) +export class NotAuthGuard + extends XLoggable + implements CanActivate, CanActivateChild, CanLoad +{ + // + constructor( + private authService: XAccountService, + private managerService: XManagerService, + @Inject(X_FRAMEWORK_IDENTITY_SDK_CONFIG) + public config: XFrameworkIdentitySDKConfig + ) { + super(config); + } + + // + //#region Actions ... + // + canActivate( + next: ActivatedRouteSnapshot, + state: RouterStateSnapshot + ): + | Observable + | Promise + | boolean + | UrlTree { + return this.handleGuard(); + } + + // + canActivateChild( + next: ActivatedRouteSnapshot, + state: RouterStateSnapshot + ): + | Observable + | Promise + | boolean + | UrlTree { + // + return this.handleGuard(); + } + + // + canLoad( + route: Route, + segments: UrlSegment[] + ): Observable | Promise | boolean { + return this.handleGuard(); + } + //#endregion + + // + //#region Private Action ... + private handleGuard(): Observable { + return toObservable(this.authService.isLoggedIn()).pipe( + map((isLoggedIn) => !isLoggedIn), + tap((isPassed) => { + // + if (!isPassed) { + this.handleGuardReject(); + } + }) + ); + } + + // + private handleGuardReject(): void { + // + this.managerService.notificationService.presentErrorNotification({ + exception: XExceptionIDs.MustLogout, + dissmissable: true, + }); + + // + this.managerService.navigateByPageReplace(this.config.Pages.Startup); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkIdentitySDKHolder\projects\x-framework-identity-sdk\src\lib\helpers\x-account.helper.ts + +import { XValidators } from 'x-framework-core'; +import { XUserAccountInfo } from '../typings/x-account.typings'; + +export function validateUserAccountInfo(model: XUserAccountInfo): void { + // + XValidators.validateNotNull(model); + XValidators.validateNotEmpty( + model.userSelectBy, + model.accessToken, + model.refreshToken + ); + XValidators.validateBiggerThan(0, model.expiresAt); +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkIdentitySDKHolder\projects\x-framework-identity-sdk\src\lib\models\x-device-info.dto.ts + +export interface XDeviceInfo { + os: string; + osVersion: string; + browser: string; + userAgent: string; + deviceType: XDeviceTypes; + userId?: number; + + identifier?: string; + token?: string; +} + +export enum XDeviceTypes { + Unknown, + Mobile, + Tablet, + Desktop +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkIdentitySDKHolder\projects\x-framework-identity-sdk\src\lib\models\x-discovery.dto.ts + +import { XBaseDto } from 'x-framework-core'; + +/** + * Models the response from an OpenID Connect discovery endpoint + */ +export interface XDiscoveryDto extends XBaseDto { + /** + * Gets the raw response. + */ + raw?: string; + /** + * Gets the response as a JObject. + */ + json?: any; + /** + * Gets a value indicating whether an error occurred. + */ + isError?: boolean; + /** + * Gets the status code. + */ + statusCode?: any; + /** + * Gets the error. + */ + error?: string; + /** + * Gets or sets the type of the error. + */ + errorType?: any; + /** + * Gets the exception. + */ + exception?: any; + /** + * Gets or sets the JSON web key set. + */ + keySet?: any; + issuer?: string; + authorizeEndpoint?: string; + tokenEndpoint?: string; + userInfoEndpoint?: string; + introspectionEndpoint?: string; + revocationEndpoint?: string; + deviceAuthorizationEndpoint?: string; + jwksUri?: string; + endSessionEndpoint?: string; + checkSessionIframe?: string; + registrationEndpoint?: string; + frontChannelLogoutSupported?: boolean; + frontChannelLogoutSessionSupported?: boolean; + grantTypesSupported?: Array; + codeChallengeMethodsSupported?: Array; + scopesSupported?: Array; + subjectTypesSupported?: Array; + responseModesSupported?: Array; + responseTypesSupported?: Array; + claimsSupported?: Array; + tokenEndpointAuthenticationMethodsSupported?: Array; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkIdentitySDKHolder\projects\x-framework-identity-sdk\src\lib\models\x-friend-dto.ts + +import { + XBaseDto, + isSameObject, + isNullOrUndefined, + isNullOrEmptyString, +} from 'x-framework-core'; +import { XFriendshipState } from './x-friendship.dto'; + +/** + * All Available Friendship Types ... + */ +export enum XFriendshipType { + Follower = 0, + Following = 1, +} + +/** + * a Dto interface Which Represent a User Friend ... + */ +export interface XFriendDto extends XBaseDto { + avatar: string; + userId: string; + username: string; + lastname: string; + firstname: string; + type?: XFriendshipType; + state?: XFriendshipState; +} + +export const defaultFriendDto: XFriendDto = { + avatar: '', + userId: '', + username: '', + lastname: '', + firstname: '', + type: undefined, + state: undefined, +}; + +export function prepareFriendDtoFields(model?: XFriendDto) { + // + if (isNullOrUndefined(model)) { + model = { + ...defaultFriendDto, + }; + } + + // + model = { + ...defaultFriendDto, + + // + avatar: !isNullOrEmptyString(model.avatar) + ? model.avatar + : defaultFriendDto.avatar, + userId: !isNullOrEmptyString(model.userId) + ? model.userId + : defaultFriendDto.userId, + username: !isNullOrEmptyString(model.username) + ? model.username + : defaultFriendDto.username, + lastname: !isNullOrEmptyString(model.lastname) + ? model.lastname + : defaultFriendDto.lastname, + firstname: !isNullOrEmptyString(model.firstname) + ? model.firstname + : defaultFriendDto.firstname, + + // + type: !isNullOrUndefined(model.type) ? model.type : defaultFriendDto.type, + state: !isNullOrUndefined(model.state) + ? model.state + : defaultFriendDto.state, + }; + + // + return model; +} + +export function isDefaultFriendDto(model: XFriendDto) { + return isSameObject(prepareFriendDtoFields(model), defaultFriendDto); +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkIdentitySDKHolder\projects\x-framework-identity-sdk\src\lib\models\x-friendship.dto.ts + +import { XBaseDto } from 'x-framework-core'; + +export enum XFriendshipState { + None, + Pending, + Accepted, + Rejected, + Blocked +} + +export type XFriendshipStates = keyof typeof XFriendshipState; + +export interface XFriendshipFollowerDto extends XBaseDto { + state: XFriendshipState; + userId: string; + destId: string; +} + +export interface XFriendshipFollowingDto extends XBaseDto { + state: XFriendshipState; + userId: string; + destId: string; +} + +export interface XFriendshipInfoDto { + userId: string; + isFollower: boolean; + isFollowing: boolean; + followerState: XFriendshipState; + followingState: XFriendshipState; + followers: number; + followings: number; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkIdentitySDKHolder\projects\x-framework-identity-sdk\src\lib\models\x-login.dto.ts + +import { XLocale } from 'x-framework-core'; +import { XUserProfileDto } from './x-user.dto'; +import { XDeviceInfo } from './x-device-info.dto'; +import { XBaseResponseDto, XBaseRequestDto } from 'x-framework-core'; + +export interface XLoginRequestDto extends XBaseRequestDto { + userSelectBy: string; + password: string; + device?: XDeviceInfo; + language?: XLocale; +} + +export interface XTokenResponseDto extends XBaseResponseDto { + accessToken: string; + refreshToken: string; + expiresAt: number; +} + +export interface XLoginResponseDto extends XTokenResponseDto { + profile: XUserProfileDto; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkIdentitySDKHolder\projects\x-framework-identity-sdk\src\lib\models\x-page.dto.ts + +export interface XPageRequestDto { + first?: number; + last?: number; + after: string; + before: string; + sortBy: string; + descendingSort: boolean; +} + +export interface XPageResponseDto { + nodes: Array; + totalCount: number; + hasNextPage: boolean; + hasPreviousPage: boolean; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkIdentitySDKHolder\projects\x-framework-identity-sdk\src\lib\models\x-query.dto.ts + +export interface XQueryDto { + page?: number; + filter?: string; + sortBy?: string; + pageSize?: number; + isAscending?: boolean; + containsDetail?: boolean; + enableTracking?: boolean; +} + +export interface XQueryResultDto { + page?: number; + items?: Array; + pageSize?: number; + totalPages?: number; + totalItems?: number; + totalFilteredItems?: number; + totalFilteredPages?: number; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkIdentitySDKHolder\projects\x-framework-identity-sdk\src\lib\models\x-registration-dto.ts + +import { XDeviceInfo } from './x-device-info.dto'; +import { XBaseRequestDto, XBaseResponseDto } from 'x-framework-core'; + +export interface XActionRequestDto extends XBaseRequestDto { + acceptTerms?: boolean; + termsAndConditions?: string; + lang?: string; + actionToken?: string; + device?: XDeviceInfo; + firstName?: string; + lastName?: string; + dateOfBirth?: Date; + userId?: string; + userName?: string; + email?: string; + mobileNumber?: string; + password?: string; + newPassword?: string; + emailVerificationCode?: string; + mobileVerificationCode?: string; + thumbnail?: string; + returnUrl?: string; + avatar?: File; +} + +export interface XActionResponseDto extends XBaseResponseDto { + token?: string; + expiration?: Date; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkIdentitySDKHolder\projects\x-framework-identity-sdk\src\lib\models\x-string.dto.ts + +import { + XLocale, + isSameObject, + isNullOrUndefined, + isNullOrEmptyString, +} from 'x-framework-core'; +import { XBaseNumberIDDto, XBaseRequestDto } from 'x-framework-core'; + +export interface XStringDto extends XBaseNumberIDDto { + language: XLocale | string; + resourceTitle: string; + translatedValue: string; +} + +export interface XUpdateStringsRequestDto extends XBaseRequestDto { + resources: Array; +} + +export const defaultStringDto: XStringDto = { + id: 0, + language: '', + resourceTitle: '', + translatedValue: '', +}; + +export function prepareStringDtoFields(model: XStringDto) { + // + if (!model) { + model = { + ...defaultStringDto, + }; + } + + // + if (isNullOrUndefined(model.id)) { + model.id = defaultStringDto.id; + } + + // + if (isNullOrEmptyString(model.language)) { + model.language = defaultStringDto.language; + } + + // + if (isNullOrEmptyString(model.resourceTitle)) { + model.resourceTitle = defaultStringDto.resourceTitle; + } + + // + if (isNullOrEmptyString(model.translatedValue)) { + model.translatedValue = defaultStringDto.translatedValue; + } + + // + return model; +} + +export function isDefaultStringDto(model: XStringDto) { + return isSameObject(prepareStringDtoFields(model), defaultStringDto); +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkIdentitySDKHolder\projects\x-framework-identity-sdk\src\lib\models\x-user.dto.ts + +import { + fromUtcDate, + XBaseRequestDto, + XBaseResponseDto, + XBaseNumberIDDto, +} from 'x-framework-core'; +import { isSameObject } from 'x-framework-core'; +import { XFriendshipInfoDto } from './x-friendship.dto'; + +export enum XGender { + Male, + Female, +} + +export interface XUserProfileDto extends XBaseResponseDto { + // + userId: string; + userName: string; + + email?: string; + emailConfirmed?: boolean; + phoneNumber?: string; + phoneNumberConfirmed?: boolean; + dateOfBirth?: Date; + + creationDate?: Date; + + firstName?: string; + lastName?: string; + lastLogin?: Date; + avatar?: string; + gender?: XGender; + friendshipInfo?: XFriendshipInfoDto; + roles?: Array; + avatars?: Array; + + isEnable?: boolean; + isBanned?: boolean; +} + +export const defaultUserProfileDto: XUserProfileDto = { + // + userId: '', + userName: '', + + // + email: '', + emailConfirmed: false, + + // + phoneNumber: '', + phoneNumberConfirmed: false, + + // + dateOfBirth: undefined, + creationDate: undefined, + + // + firstName: '', + lastName: '', + + // + lastLogin: undefined, + avatar: '', + + // + gender: XGender.Male, + + // + roles: [], + avatars: [], + + // + friendshipInfo: undefined, + + // + isEnable: false, + isBanned: false, +}; + +export function prepareUserProfileDtoFields( + model: XUserProfileDto +): XUserProfileDto { + // + if (!model) { + model = { + ...defaultUserProfileDto, + }; + } + + // + model = { + ...defaultUserProfileDto, + + // + userId: model.userId || defaultUserProfileDto.userId, + userName: model.userName || defaultUserProfileDto.userName, + + // + firstName: model.firstName || defaultUserProfileDto.firstName, + lastName: model.lastName || defaultUserProfileDto.lastName, + + // + gender: model.gender || defaultUserProfileDto.gender, + + // + email: model.email || defaultUserProfileDto.email, + emailConfirmed: + model.emailConfirmed || defaultUserProfileDto.emailConfirmed, + + // + phoneNumber: model.phoneNumber || defaultUserProfileDto.phoneNumber, + phoneNumberConfirmed: + model.phoneNumberConfirmed || defaultUserProfileDto.phoneNumberConfirmed, + + // + dateOfBirth: + fromUtcDate(model.dateOfBirth) || defaultUserProfileDto.dateOfBirth, + creationDate: + fromUtcDate(model.creationDate) || defaultUserProfileDto.creationDate, + lastLogin: fromUtcDate(model.lastLogin) || defaultUserProfileDto.lastLogin, + + // + avatar: model.avatar || defaultUserProfileDto.avatar, + friendshipInfo: + model.friendshipInfo || defaultUserProfileDto.friendshipInfo, + roles: model.roles || defaultUserProfileDto.roles, + avatars: model.avatars || defaultUserProfileDto.avatars, + + // + isEnable: model.isEnable || defaultUserProfileDto.isEnable, + isBanned: model.isBanned || defaultUserProfileDto.isBanned, + }; + + // + return model; +} + +export function isDefaultUserProfileDto(model: XUserProfileDto): boolean { + return isSameObject( + prepareUserProfileDtoFields(model), + defaultUserProfileDto + ); +} + +export interface XavatarDto extends XBaseResponseDto { + id: number; + name: string; + path: string; + thumb: string; + thumbPath: string; + creationDate: Date; +} + +export interface XUserNameIdResponseDto extends XBaseResponseDto { + id: string; + userName: string; +} + +export interface XUserNameIdRequestDto extends XBaseRequestDto { + ids: string[]; +} + +export interface XProfileUpdateRequestDto extends XBaseRequestDto { + email: string; + emailConfirmed?: boolean; + phoneNumber: string; + phoneNumberConfirmed?: boolean; + firstName: string; + lastName: string; + avatar: string; + dateOfBirth?: Date; + creationDate?: Date; + lastLogin?: Date; + gender: XGender; + roles?: Array; + isEnable?: boolean; + isBanned?: boolean; +} + +export interface XChangeRoleRequest extends XBaseRequestDto { + userId: string; + newRole: string; + bussinessRoleInfo: XBusinessOwner; +} + +export interface XBusinessOwner extends XBaseNumberIDDto { + userId: string; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkIdentitySDKHolder\projects\x-framework-identity-sdk\src\lib\partial\x-account.admin.service.ts + +import { Observable } from 'rxjs'; +import { + XAccountEndPoint, + XAccountEndPointParam, +} from '../typings/x-endpoint.typings'; +import { XUserNameIdRequestDto } from '../models/x-user.dto'; +import { HttpEvent, HttpResponse } from '@angular/common/http'; +import { XAccountRegistrationService } from './x-account.registration.service'; + +export class XAccountAdminService extends XAccountRegistrationService { + // + //#region Ban ... + /** + * ban users access to resources + * + * @param body the collection of user id's + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public ban( + body: XUserNameIdRequestDto, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public ban( + body: XUserNameIdRequestDto, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public ban( + body: XUserNameIdRequestDto, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public ban( + body: XUserNameIdRequestDto, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.Ban); + + // + // Prepare Result ... + return this.httpClient.post(endPointPath, body, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region UnBan ... + /** + * unban users access to resources + * + * @param body the collection of user id's + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public unBan( + body: XUserNameIdRequestDto, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public unBan( + body: XUserNameIdRequestDto, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public unBan( + body: XUserNameIdRequestDto, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public unBan( + body: XUserNameIdRequestDto, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.UnBan); + + // + // Prepare Result ... + return this.httpClient.post(endPointPath, body, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region IsBanned ... + /** + * check a User is Banned or not + * + * @param destUser the user id's which going to check Banned or not + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public isBanned( + destUser: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public isBanned( + destUser: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public isBanned( + destUser: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public isBanned( + destUser: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.IsBanned, { + key: XAccountEndPointParam.XUserSelectByParam, + value: destUser, + }); + + // + // Prepare Result ... + return this.httpClient.get(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkIdentitySDKHolder\projects\x-framework-identity-sdk\src\lib\partial\x-account.authentication.service.ts + +import { + XHeader, + fromJson, + toPromise, + XValidators, + XExceptionIDs, + throwException, + isNullOrEmptyString, +} from 'x-framework-core'; +import { + XTokenResponseDto, + XLoginRequestDto, + XLoginResponseDto, +} from '../models/x-login.dto'; +import { Observable, of } from 'rxjs'; +import { + XAccountEndPoint, + XAccountEndPointParam, +} from '../typings/x-endpoint.typings'; +import { map, concatMap } from 'rxjs/operators'; +import { XDiscoveryDto } from '../models/x-discovery.dto'; +import { XApiScope } from '../constants/x-api-scope.enum'; +import { HttpResponse, HttpEvent } from '@angular/common/http'; +import { XAccountBaseService } from './x-account.base.service'; +import { XUserAccountInfo } from '../typings/x-account.typings'; + +export abstract class XAccountAuthenticationService extends XAccountBaseService { + // + //#region DiscoveryDocument ... + /** + * Retrieve Discovery Document + * + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public requestDiscoveryDocument( + observe?: 'body', + reportProgress?: boolean + ): Observable; + public requestDiscoveryDocument( + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public requestDiscoveryDocument( + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public requestDiscoveryDocument( + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAcceptJson(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.DiscoveryDocument); + + // + // return result ... + return this.httpClient.get(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region ScopeAccessToken ... + /** + * Request Specific Scope Access Token for API Actions + * + * @param scope the specific api scope + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public requestScopeAccessToken( + scope: XApiScope, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public requestScopeAccessToken( + scope: XApiScope, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public requestScopeAccessToken( + scope: XApiScope, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public requestScopeAccessToken( + scope: XApiScope, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAcceptJson(headers); + headers = this.addCustomHeader(headers, { + key: XHeader.Scope, + value: scope, + }); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.RequestScopeAccessToken); + + // + // return result ... + return this.httpClient.post(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region Authenticate ... + /** + * Authenticate a User + * + * @param body an instance of XLoginRequest class which holds UserName and Password + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public authenticate( + body: XLoginRequestDto, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public authenticate( + body: XLoginRequestDto, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public authenticate( + body: XLoginRequestDto, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public authenticate( + body: XLoginRequestDto, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotNull(body); + XValidators.validateNotEmpty(body.password, body.userSelectBy); + + // + if (this.stateSnapshot && this.stateSnapshot.isLoggedIn) { + throwException(XExceptionIDs.MustLogout); + } + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.Authenticate); + + // + // return result ... + return this.httpClient.post(endPointPath, body, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region Login ... + /** + * Login a User + * + * @param body an instance of XLoginRequest class which holds UserName and Password + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public login( + body: XLoginRequestDto, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public login( + body: XLoginRequestDto, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public login( + body: XLoginRequestDto, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public login( + body: XLoginRequestDto, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotNull(body, body.device); + XValidators.validateNotEmpty( + body.password, + body.userSelectBy, + body.language + ); + + // + if (this.stateSnapshot && this.stateSnapshot.isLoggedIn) { + throwException(XExceptionIDs.MustLogout); + } + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.Login); + + // + // return result ... + return this.httpClient + .post(endPointPath, body, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }) + .pipe( + concatMap((res) => { + // + // Convert Login Response ... + const mResponse = fromJson(res); + + // + // Prepare XUserAccountInfo instance ... + const mUserInfo: XUserAccountInfo = { + ...mResponse, + userSelectBy: body.userSelectBy, + }; + + // + // Update User Account Info ... + return of( + this.addOrUpdateUserAccount(mUserInfo.userSelectBy, mUserInfo, true) + ).pipe(map((_) => mUserInfo)); + }), + // + // set User Account in API Config ... + concatMap((res) => { + return of(this.apiConfig.setUserAccount(res)).pipe(map((_) => res)); + }) + ); + } + //#endregion + + // + //#region RefreshTokens ... + /** + * Refresh Expired Tokens + * + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public refreshTokens( + observe?: 'body', + reportProgress?: boolean + ): Observable; + public refreshTokens( + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public refreshTokens( + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public refreshTokens( + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + const tokens = this.retrieveTokens(); + headers = this.addAuthentication(headers, tokens); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.RefreshTokens); + + // + // return result ... + return this.httpClient + .post(endPointPath, null, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }) + .pipe( + concatMap((res) => { + // + // Convert Login Response ... + const mResponse = fromJson(res); + + // + // Prepare XUserAccountInfo instance ... + const mUserInfo: XUserAccountInfo = { + ...mResponse, + profile: this.stateSnapshot.profile, + userSelectBy: this.stateSnapshot.userSelectBy, + }; + + // + // Update User Account Info ... + return of( + this.addOrUpdateUserAccount(mUserInfo.userSelectBy, mUserInfo, true) + ).pipe( + map((_) => { + return { + userInfo: mUserInfo, + response: mResponse, + }; + }) + ); + }), + // + // set User Account in API Config ... + concatMap((res) => { + return of(this.apiConfig.setUserAccount(res.userInfo)).pipe( + map((_) => res.response) + ); + }) + ); + } + + /** + * Refresh Expired Tokens + * + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public refreshTokensOnly( + observe?: 'body', + reportProgress?: boolean + ): Observable; + public refreshTokensOnly( + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public refreshTokensOnly( + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public refreshTokensOnly( + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + const tokens = this.retrieveTokens(); + headers = this.addAuthentication(headers, tokens); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.RefreshTokens); + + // + // return result ... + return this.httpClient + .post(endPointPath, null, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region Validate ... + /** + * Validate Revision Checksum + * + * @param revision token renew Revision + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public validateRevision( + revision: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public validateRevision( + revision: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public validateRevision( + revision: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public validateRevision( + revision: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + XValidators.validateNotEmpty(revision); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.Validate); + + // + // return result ... + return this.httpClient.post( + endPointPath, + { revision }, + { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + } + ); + } + //#endregion + + // + //#region Logout ... + /** + * Logout User ... + */ + public async logout(returnUrl?: string) { + // + const isLoggedIn = await this.isLoggedIn(); + if (!isLoggedIn) { + throwException(XExceptionIDs.NotAllowed); + } + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.Logout); + + // + // return result ... + try { + // + // Try Call Logout ... + await toPromise( + this.httpClient.post(endPointPath, null, { + withCredentials: this.apiConfig.withCredentials, + headers, + }) + ); + } catch {} + + // + const defUserId = await this.getDefaultUserIdentifier(); + XValidators.validateNotEmpty(defUserId); + + // + const defUser = await this.getDefaultUser(); + XValidators.validateNotNull(defUser); + + // + await this.removeDefaultUser(true); + + // + if (!isNullOrEmptyString(returnUrl)) { + this.router.navigateByUrl(returnUrl); + } + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkIdentitySDKHolder\projects\x-framework-identity-sdk\src\lib\partial\x-account.base.service.ts + +import { + keys, + getKeys, + hasChild, + fromJson, + getArrayOf, + XValidators, + toObservable, + XExceptionIDs, + throwException, + toNormalString, + getErrorException, + isNullOrUndefined, + isNullOrEmptyString, +} from 'x-framework-core'; +import { + XAccountState, + XUserAccountInfo, + XUserAccountInfoIndex, +} from '../typings/x-account.typings'; +import { Inject } from '@angular/core'; +import { + X_API_CONFIG, + X_FRAMEWORK_IDENTITY_SDK_CONFIG, +} from '../tokens/x-injectable-tokens'; +import { + XAccountEndPoint, + XAccountEndPointParam, +} from './../typings/x-endpoint.typings'; +import { Router } from '@angular/router'; +import { HttpClient } from '@angular/common/http'; +import { BehaviorSubject, Observable, of } from 'rxjs'; +import { XManagerService } from 'x-framework-services'; +import { XUserProfileDto } from '../models/x-user.dto'; +import { map, concatMap, filter } from 'rxjs/operators'; +import { XBaseApiService } from '../base/x-base-api.service'; +import { XAccountStorageKeys } from '../constants/x-account.keys'; +import { XApiConfiguration } from '../config/x-api-service.config'; +import { validateUserAccountInfo } from '../helpers/x-account.helper'; +import { XLoginResponseDto, XTokenResponseDto } from '../models/x-login.dto'; +import { XFrameworkIdentitySDKConfig } from '../config/x-framework-identity-sdk.config'; + +export abstract class XAccountBaseService extends XBaseApiService { + // + stateSnapshot: XAccountState; + state$ = new BehaviorSubject(null); + + // + //#region Constructor ... + constructor( + protected router: Router, + protected httpClient: HttpClient, + @Inject(X_FRAMEWORK_IDENTITY_SDK_CONFIG) + config: XFrameworkIdentitySDKConfig, + @Inject(X_API_CONFIG) configuration: XApiConfiguration, + protected managerService: XManagerService + ) { + // + super('Account', false, false, httpClient, config, configuration); + + // + // handle initial state + toObservable(this.currentState()) + .pipe( + filter( + (state) => + !isNullOrUndefined(state) && + !isNullOrUndefined(state.profile) && + !isNullOrEmptyString(state.userSelectBy) && + !isNullOrEmptyString(state.accessToken) && + !isNullOrEmptyString(state.refreshToken) + ), + concatMap((state) => { + return toObservable(this.isExpired()).pipe( + map((isExpired) => { + return { + state, + isExpired, + }; + }) + ); + }), + concatMap((info) => { + if (!!info.isExpired) { + return toObservable(this.refreshCurrentToken()).pipe( + concatMap(() => { + return toObservable(this.currentState()).pipe( + map((newState) => { + return { + ...info, + state: { + ...newState, + }, + }; + }) + ); + }) + ); + } else { + return of(info); + } + }) + ) + .subscribe((info) => { + this.state$.next(info.state); + }); + } + //#endregion + + // + //#region Service Actions ... + /** + * + * @returns string message ... + */ + public hi(): Observable { + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAcceptJson(headers); + + // + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.Hi); + + // + // return result ... + return this.httpClient + .get(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe: 'body', + reportProgress: false, + }) + .pipe(map((res) => fromJson(res))); + } + + /** + * + * @returns string message ... + */ + public passRequireXPowered(): Observable { + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAcceptJson(headers); + + // + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.PassRequireXPowered); + + // + // return result ... + return this.httpClient + .get(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe: 'body', + reportProgress: false, + }) + .pipe(map((res) => fromJson(res))); + } + + /** + * API Read Scope + * @returns string message ... + */ + public passReadAccess(): Observable { + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAcceptJson(headers); + headers = this.addAuthentication(headers); + + // + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.PassReadAccess); + + // + // return result ... + return this.httpClient + .get(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe: 'body', + reportProgress: false, + }) + .pipe(map((res) => fromJson(res))); + } + + /** + * API Write Scope + * @returns string message ... + */ + public passWriteAccess(): Observable { + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAcceptJson(headers); + headers = this.addAuthentication(headers); + + // + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.PassWriteAccess); + + // + // return result ... + return this.httpClient + .get(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe: 'body', + reportProgress: false, + }) + .pipe(map((res) => fromJson(res))); + } + + /** + * API Admin Scope + * @returns string message ... + */ + public passAdminAccess(): Observable { + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAcceptJson(headers); + headers = this.addAuthentication(headers); + + // + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.PassAdminAccess); + + // + // return result ... + return this.httpClient + .get(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe: 'body', + reportProgress: false, + }) + .pipe(map((res) => fromJson(res))); + } + + /** + * API Manage Scop + * @returns string message ... + */ + public passManageAccess(): Observable { + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAcceptJson(headers); + headers = this.addAuthentication(headers); + + // + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.PassManageAccess); + + // + // return result ... + return this.httpClient + .get(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe: 'body', + reportProgress: false, + }) + .pipe(map((res) => fromJson(res))); + } + + /** + * a simple Action which returns a List of Authenticated User Claims + * @returns string message which represent current user's claims ... + */ + public hiClaims(): Observable { + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAcceptJson(headers); + headers = this.addAuthentication(headers); + + // + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.HiClaims); + + // + // return result ... + return this.httpClient + .get(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe: 'body', + reportProgress: false, + }) + .pipe(map((res) => fromJson(res))); + } + + /** + * a simple Hello User for Checking Authentication and Policy + * @returns string message which contains authenticated user name ... + */ + public hiUser(): Observable { + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAcceptJson(headers); + headers = this.addAuthentication(headers); + + // + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.HiUser); + + // + // return result ... + return this.httpClient + .get(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe: 'body', + reportProgress: false, + }) + .pipe(map((res) => fromJson(res))); + } + + /** + * a simple Hello User for Checking Authentication and Policy + * @returns string message which contains authenticated user name ... + */ + public hiEnabledUser(): Observable { + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAcceptJson(headers); + headers = this.addAuthentication(headers); + + // + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.HiEnabledUser); + + // + // return result ... + return this.httpClient + .get(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe: 'body', + reportProgress: false, + }) + .pipe(map((res) => fromJson(res))); + } + + /** + * a simple Hello User for Checking Authentication and Policy + * @returns string message which contains authenticated user name ... + */ + public hiAgent(): Observable { + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAcceptJson(headers); + headers = this.addAuthentication(headers); + + // + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.HiAgent); + + // + // return result ... + return this.httpClient + .get(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe: 'body', + reportProgress: false, + }) + .pipe(map((res) => fromJson(res))); + } + + /** + * a simple Hello User for Checking Authentication and Policy + * @returns string message which contains authenticated user name ... + */ + public hiEnabledAgent(): Observable { + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAcceptJson(headers); + headers = this.addAuthentication(headers); + + // + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.HiEnabledAgent); + + // + // return result ... + return this.httpClient + .get(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe: 'body', + reportProgress: false, + }) + .pipe(map((res) => fromJson(res))); + } + + /** + * a simple Hello User for Checking Authentication and Policy + * @returns string message which contains authenticated user name ... + */ + public hiAdmin(): Observable { + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAcceptJson(headers); + headers = this.addAuthentication(headers); + + // + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.HiAdmin); + + // + // return result ... + return this.httpClient + .get(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe: 'body', + reportProgress: false, + }) + .pipe(map((res) => fromJson(res))); + } + + /** + * a simple Hello User for Checking Authentication and Policy + * @returns string message which contains authenticated user name ... + */ + public hiEnabledAdmin(): Observable { + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAcceptJson(headers); + headers = this.addAuthentication(headers); + + // + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.HiEnabledAdmin); + + // + // return result ... + return this.httpClient + .get(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe: 'body', + reportProgress: false, + }) + .pipe(map((res) => fromJson(res))); + } + //#endregion + + // + //#region Account Handler ... + /** + * retrieve current user tokens ... + * + * @returns an instance of XTokenResponse + */ + public retrieveTokens(): XTokenResponseDto { + // + if (isNullOrUndefined(this.stateSnapshot)) { + return null; + } + + // + const result: XTokenResponseDto = { + accessToken: this.stateSnapshot.accessToken, + refreshToken: this.stateSnapshot.refreshToken, + expiresAt: this.stateSnapshot.expiresAt, + }; + + // + return result; + } + + /** + * reNew Account State ... + */ + public async reNewState(state?: XAccountState): Promise { + // + const currState = await this.currentState(); + this.state$.next({ + ...currState, + ...state, + }); + + // + this.apiConfig.setUserAccount(currState); + } + + /** + * retrieve current Account State ... + */ + public async currentState(): Promise { + // + let isLoggedIn = false; + let defaultUser = await this.getDefaultUser(); + if (!defaultUser) { + // + // Making Default State + defaultUser = { + userSelectBy: '', + accessToken: '', + refreshToken: '', + expiresAt: -1, + profile: null, + }; + } else { + isLoggedIn = true; + } + + // + const mCurrentState = { + isLoggedIn, + ...defaultUser, + } as XAccountState; + + // + this.stateSnapshot = mCurrentState; + this.apiConfig.setUserAccount(mCurrentState); + + // + return mCurrentState; + } + + /** + * Validate Token Responses with recieved revision ... + * + * @param model the model which going to validate + * @param revision the recieved revision + */ + public validateLoginResponseRevision( + model: XLoginResponseDto, + revision: string + ): boolean { + // + const source = + model.accessToken + + this.apiConfig.revisionSecretKey + + model.refreshToken + + this.apiConfig.revisionSecretKey + + model.expiresAt.toString(); + + // + return !isNullOrUndefined(model) && + !isNullOrEmptyString(revision) && + !isNullOrEmptyString(model.accessToken) && + !isNullOrEmptyString(model.refreshToken) && + model.expiresAt > -1 + ? this.managerService.securityService.toMd5(source) === revision + : false; + } + + // + //#region IsLoggedIn ... + public async isLoggedIn(): Promise { + // + const currState = await this.currentState(); + + // + return currState.isLoggedIn; + } + + public async isExpired(): Promise { + // + const defaultUser = await this.getDefaultUser(); + if (!defaultUser) { + throwException(XExceptionIDs.NotFound); + } + + // + let expiredAt = defaultUser.expiresAt; + const currentDate = new Date().getTime(); + + // + // Fix different length ... + const expiredAtStr = expiredAt.toString(); + const currentDateStr = currentDate.toString(); + const lengthDiff = currentDateStr.length - expiredAtStr.length; + if (lengthDiff > 0) { + expiredAt = expiredAt * Math.pow(10, lengthDiff); + } + + // + return currentDate >= expiredAt; + } + //#endregion + + // + //#region Refresh ... + /** + * refresh expired token ... + */ + public refreshCurrentToken() { + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + const tokens = this.retrieveTokens(); + headers = this.addAuthentication(headers, tokens); + + // + const observe: any = 'body'; + const reportProgress: boolean = false; + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.RefreshTokens); + + // + // return result ... + return this.httpClient + .post(endPointPath, null, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }) + .pipe( + concatMap((res) => { + // + // Convert Login Response ... + const mResponse = fromJson(res); + + // + // Prepare XUserAccountInfo instance ... + const mUserInfo: XUserAccountInfo = { + ...mResponse, + profile: this.stateSnapshot.profile, + userSelectBy: this.stateSnapshot.userSelectBy, + }; + + // + // Update User Account Info ... + return of( + this.addOrUpdateUserAccount(mUserInfo.userSelectBy, mUserInfo, true) + ).pipe( + map((_) => { + return { + userInfo: mUserInfo, + response: mResponse, + }; + }) + ); + }), + // + // set User Account in API Config ... + concatMap((res) => { + return of(this.apiConfig.setUserAccount(res.userInfo)).pipe( + map((_) => res.response) + ); + }) + ); + } + //#endregion + + // + //#region Account Info Handlers ... + /** + * check a User Accounts Exists or not ... + * + * @param userSelectBy user identifier + */ + public async hasUserAccount(userSelectBy: string): Promise { + // + XValidators.validateNotEmpty(userSelectBy); + + // + userSelectBy = toNormalString(userSelectBy); + + // + const accounts = await this.getAccounts(); + if (!accounts) { + return false; + } + + // + const userIdentifiers = keys(accounts); + if (!hasChild(userIdentifiers)) { + return false; + } + + // + const result = + userIdentifiers.findIndex((i) => toNormalString(i) === userSelectBy) > -1; + + // + return result; + } + + /** + * retrieve specific user account + * + * @param userSelectBy user identifier + */ + public async getUserAccount(userSelectBy: string): Promise { + // + XValidators.validateNotEmpty(userSelectBy); + + // + userSelectBy = toNormalString(userSelectBy); + + // + const isExists = await this.hasUserAccount(userSelectBy); + if (!isExists) { + throwException(XExceptionIDs.NotFound); + } + + // + const accounts = await this.getAccountsAsArray(); + if (!hasChild(accounts)) { + throwException(XExceptionIDs.NotFound); + } + + // + const result = accounts.find( + (a) => toNormalString(a.userSelectBy) === userSelectBy + ); + + // + return result; + } + + /** + * Retrieve user info by it's token's ... + * + * @param info the user tokens info + */ + public async getUserByToken( + info: XLoginResponseDto + ): Promise { + // + const accounts = await this.getAccountsAsArray(); + if (!hasChild(accounts)) { + throwException(XExceptionIDs.NotFound); + } + + // + const account = accounts.find( + (a) => + a.accessToken === info.accessToken && + a.refreshToken === info.refreshToken && + a.expiresAt === info.expiresAt + ); + if (!accounts) { + throwException(XExceptionIDs.NotFound); + } + + // + return account; + } + + /** + * add new user account ... + * + * @param userSelectBy user identifier + * @param accountInfo user logged in account info + */ + public async addUserAccount( + userSelectBy: string, + accountInfo: XUserAccountInfo, + setAsDefault?: boolean + ): Promise { + // + XValidators.validateNotEmpty(userSelectBy); + validateUserAccountInfo(accountInfo); + + // + const isExists = await this.hasUserAccount(userSelectBy); + if (isExists) { + throwException(XExceptionIDs.Duplicate); + } + + // + const numOfAccounts = await this.countAccounts(); + if (numOfAccounts === 0) { + setAsDefault = true; + } + + // + const accounts = await this.getAccounts(); + accounts[userSelectBy] = accountInfo; + await this.updateAccounts(accounts); + + // + if (setAsDefault) { + await this.setDefaultUser(userSelectBy); + } + + // + await this.reNewState(); + } + + /** + * update exists user account info ... + * + * @param userSelectBy user identifier + * @param accountInfo user logged in account info + */ + public async updateUserAccount( + userSelectBy: string, + accountInfo: XUserAccountInfo, + setAsDefault?: boolean + ): Promise { + // + XValidators.validateNotEmpty(userSelectBy); + validateUserAccountInfo(accountInfo); + + // + const isExists = await this.hasUserAccount(userSelectBy); + if (!isExists) { + throwException(XExceptionIDs.NotFound); + } + + // + const accounts = await this.getAccounts(); + accounts[userSelectBy] = accountInfo; + await this.updateAccounts(accounts); + + // + if (setAsDefault) { + await this.setDefaultUser(userSelectBy); + } + + // + await this.reNewState(); + } + + /** + * Update an Existing Profile to User's Storage ... + * + * @param profile Updated Profile to Save ... + * @returns + */ + public async updateUserProfile(profile: XUserProfileDto): Promise { + // + XValidators.validateNotNull(profile); + + // + const defaultUser = await this.getDefaultUser(); + if ( + !defaultUser || + (defaultUser && !defaultUser.profile) || + (defaultUser && + defaultUser.profile && + defaultUser.profile.userId !== profile.userId) + ) { + return; + } + + // + defaultUser.profile = profile; + await this.addOrUpdateUserAccount(defaultUser.userSelectBy, defaultUser); + } + + /** + * add new/ update exists user account ... + * + * @param userSelectBy user identifier + * @param accountInfo user logged in account info + */ + public async addOrUpdateUserAccount( + userSelectBy: string, + accountInfo: XUserAccountInfo, + setAsDefault?: boolean + ): Promise { + // + XValidators.validateNotEmpty(userSelectBy); + validateUserAccountInfo(accountInfo); + + // + const isExists = await this.hasUserAccount(userSelectBy); + if (!isExists) { + await this.addUserAccount(userSelectBy, accountInfo, setAsDefault); + } else { + await this.updateUserAccount(userSelectBy, accountInfo, setAsDefault); + } + + // + await this.reNewState(); + } + + /** + * Update User's Refreshed Token ... + * + * @param oldInfo user's old token's info ... + * @param newInfo new user's token info which returned after token refreshing ... + */ + public async updateUserTokens( + oldInfo: XLoginResponseDto, + newInfo: XTokenResponseDto + ): Promise; + public async updateUserTokens( + oldInfo: XLoginResponseDto, + newInfo: XLoginResponseDto + ): Promise { + // + const count = await this.countAccounts(); + if (count === 0) { + throwException(XExceptionIDs.NotFound); + } + + // + const accounts = await this.getAccountsAsArray(); + + // + const account = accounts.find( + (a) => + a.accessToken === oldInfo.accessToken && + a.refreshToken === oldInfo.refreshToken && + a.expiresAt === oldInfo.expiresAt + ); + if (!account) { + throwException(XExceptionIDs.NotFound); + } + + // + account.accessToken = newInfo.accessToken; + account.refreshToken = newInfo.refreshToken; + account.expiresAt = newInfo.expiresAt; + + // + await this.updateUserAccount(account.userSelectBy, account); + await this.reNewState(); + } + + /** + * remove exists user account ... + * + * @param userSelectBy user identifier + */ + public async removeUserAccount(userSelectBy: string): Promise { + // + XValidators.validateNotEmpty(userSelectBy); + + // + userSelectBy = toNormalString(userSelectBy); + + // + const isExists = await this.hasUserAccount(userSelectBy); + if (!isExists) { + throwException(XExceptionIDs.NotFound); + } + + // + const accounts = await this.getAccountsAsArray(); + const updatedAccounts = accounts.filter( + (a) => toNormalString(a.userSelectBy) !== userSelectBy + ); + + // + const isDefaultUser = await this.isDefaultUser(userSelectBy); + if (isDefaultUser) { + await this.removeDefaultUser(); + await this.reNewState(); + } + + // + const updateAcountIndex: XUserAccountInfoIndex = {}; + updatedAccounts.forEach((a) => { + updateAcountIndex[a.userSelectBy] = a; + }); + + // + await this.updateAccounts(updateAcountIndex); + } + + /** + * Check a User Identifier is Defualt User or not + */ + public async isDefaultUser(userSelectBy: string): Promise { + // + XValidators.validateNotEmpty(userSelectBy); + + // + userSelectBy = toNormalString(userSelectBy); + + // + const defId = await this.getDefaultUserIdentifier(); + if (isNullOrEmptyString(defId)) { + return false; + } + + // + const result = toNormalString(defId) === userSelectBy; + + // + return result; + } + + /** + * read default user account identifier ... + */ + public async getDefaultUserIdentifier(): Promise { + // + const result = this.managerService.secureStorageService.readOfType( + XAccountStorageKeys.DefaultUser, + false + ); + + // + return result; + } + + /** + * retrieve default user account info + */ + public async getDefaultUser(): Promise { + // + const defaultUserIdentifier = await this.getDefaultUserIdentifier(); + if (isNullOrEmptyString(defaultUserIdentifier)) { + return null; + } + + // + const result = await this.getUserAccount(defaultUserIdentifier); + + // + return result; + } + + public async getDefaultUserTokens(): Promise { + // + const defaultUser = await this.getDefaultUser(); + if (!defaultUser) { + throw getErrorException(XExceptionIDs.NotFound); + } else { + // + const result: XLoginResponseDto = { + accessToken: defaultUser.accessToken, + refreshToken: defaultUser.refreshToken, + expiresAt: defaultUser.expiresAt, + profile: defaultUser.profile, + }; + + // + return result; + } + } + + /** + * set a user as default user ... + * + * @param userSelectBy user identifier + */ + public async setDefaultUser(userSelectBy: string): Promise { + // + XValidators.validateNotEmpty(userSelectBy); + + // + const isExists = await this.hasUserAccount(userSelectBy); + if (!isExists) { + throwException(XExceptionIDs.NotFound); + } + + // + this.managerService.secureStorageService.write( + XAccountStorageKeys.DefaultUser, + userSelectBy, + true + ); + + // + await this.reNewState(); + } + + /** + * Remove default user account + */ + public async removeDefaultUser(forceRemoveUser?: boolean): Promise { + // + const defId = await this.getDefaultUserIdentifier(); + if (isNullOrEmptyString(defId)) { + return; + } + + // + this.managerService.secureStorageService.remove( + XAccountStorageKeys.DefaultUser, + false + ); + + // + if (forceRemoveUser) { + const isExists = await this.hasUserAccount(defId); + if (isExists) { + await this.removeUserAccount(defId); + } + } + + // + const accounts = await this.getAccountsAsArray(); + if (hasChild(accounts)) { + await this.setDefaultUser(accounts[0].userSelectBy); + } + + // + await this.reNewState(); + } + + /** + * count all exists accounts + */ + public async countAccounts(): Promise { + // + const accounts = await this.getAccountsAsArray(); + + // + let result = 0; + if (!hasChild(accounts)) { + return result; + } + + // + result = accounts.length; + return result; + } + + /** + * retrieve all exists accounts + */ + public async getAccounts(): Promise { + // + const result = + this.managerService.secureStorageService.readOfType( + XAccountStorageKeys.UserInfos, + false + ) || {}; + + // + return result; + } + + /** + * update user Accounts ... + * + * @param model updated Accounts + */ + public async updateAccounts(model: XUserAccountInfoIndex): Promise { + // + XValidators.validateNotNull(model); + + // + await this.removeAccounts(); + + // + this.managerService.secureStorageService.write( + XAccountStorageKeys.UserInfos, + model, + true + ); + } + + /** + * get user accounts info as Array + */ + public async getAccountsAsArray(): Promise { + // + const accounts = await this.getAccounts(); + const result = getArrayOf(accounts) || []; + + // + return result; + } + + /** + * Remove All user Accounts + */ + public async removeAccounts(forceReNewState?: boolean): Promise { + // + this.managerService.secureStorageService.remove( + XAccountStorageKeys.UserInfos, + false + ); + + // + if (forceReNewState) { + await this.reNewState(); + } + } + + /** + * reset All Account Infos + */ + public async resetAccounts(): Promise { + // + const storageKeys = getKeys(XAccountStorageKeys); + + // + storageKeys.forEach((k) => { + this.managerService.secureStorageService.remove(k, false); + }); + + // + await this.reNewState(); + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkIdentitySDKHolder\projects\x-framework-identity-sdk\src\lib\partial\x-account.final.service.ts + +import { Observable, of } from 'rxjs'; +import { filter, map } from 'rxjs/operators'; +import { isNullOrUndefined, notValue } from 'x-framework-core'; +import { XAccountSearchService } from './x-account.search.service'; + +export abstract class XAccountFinalService extends XAccountSearchService { + // + //#region Provider Actions ... + /** + * check a user id is same as current user id or not ... + * @param userId dest user id's + */ + isMe(userId?: string): Observable { + // + if (!userId) { + return of(false); + } + + // + return this.state$.pipe( + filter((res) => !isNullOrUndefined(res)), + map( + (state) => + state.profile.email === userId || + state.profile.userId === userId || + state.profile.userName === userId || + state.profile.phoneNumber === userId + ) + ); + } + + /** + * check a user id is not same as current user id or not ... + * @param userId dest user id's + */ + isNotMe(userId: string): Observable { + return notValue(this.isMe(userId)); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkIdentitySDKHolder\projects\x-framework-identity-sdk\src\lib\partial\x-account.friendship.service.ts + +import { Observable } from 'rxjs'; +import { + XFriendshipState, + XFriendshipInfoDto, + XFriendshipFollowerDto, + XFriendshipFollowingDto, +} from '../models/x-friendship.dto'; +import { + XAccountEndPoint, + XAccountEndPointParam, +} from '../typings/x-endpoint.typings'; +import { XFriendDto } from '../models/x-friend-dto'; +import { HttpEvent, HttpResponse } from '@angular/common/http'; +import { XQueryDto, XQueryResultDto } from '../models/x-query.dto'; +import { isNullOrEmptyString, XValidators } from 'x-framework-core'; +import { XAccountProfileService } from './x-account.profile.service'; + +export abstract class XAccountFriendshipService extends XAccountProfileService { + // + //#region Follow ... + /** + * Follow a User + * + * @param destUser selected dest User + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public follow( + destUser: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public follow( + destUser: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public follow( + destUser: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public follow( + destUser: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(destUser); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.Follow, { + key: XAccountEndPointParam.XUserSelectByParam, + value: destUser, + }); + + // + // return response ... + return this.httpClient.post(endPointPath, null, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region Cancel ... + /** + * Cancel Following + * + * @param destUser selected dest User + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public cancel( + destUser: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public cancel( + destUser: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public cancel( + destUser: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public cancel( + destUser: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(destUser); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.Cancel, { + key: XAccountEndPointParam.XUserSelectByParam, + value: destUser, + }); + + // + // return response ... + return this.httpClient.post(endPointPath, null, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region UnFollowFollower ... + /** + * Unfollow a Follower + * + * @param destUser selected dest User + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public unFollowFollower( + destUser: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public unFollowFollower( + destUser: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public unFollowFollower( + destUser: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public unFollowFollower( + destUser: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(destUser); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.UnFollowFollower, { + key: XAccountEndPointParam.XUserSelectByParam, + value: destUser, + }); + + // + // Result ... + return this.httpClient.post(endPointPath, null, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region UnFollowFollowing ... + /** + * Unfollow Following + * + * @param destUser selected dest User + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public unFollowFollowing( + destUser: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public unFollowFollowing( + destUser: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public unFollowFollowing( + destUser: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public unFollowFollowing( + destUser: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(destUser); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.UnFollowFollowing, { + key: XAccountEndPointParam.XUserSelectByParam, + value: destUser, + }); + + // + // return result ... + return this.httpClient.post(endPointPath, null, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region Block ... + /** + * Block a Follower + * + * @param destUser selected dest User + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public block( + destUser: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public block( + destUser: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public block( + destUser: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public block( + destUser: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(destUser); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.Block, { + key: XAccountEndPointParam.XUserSelectByParam, + value: destUser, + }); + + // + // return response ... + return this.httpClient.post(endPointPath, null, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region UnBlock ... + /** + * Unblock a Blocked User + * + * @param destUser selected dest User + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public unBlock( + destUser: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public unBlock( + destUser: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public unBlock( + destUser: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public unBlock( + destUser: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(destUser); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.UnBlock, { + key: XAccountEndPointParam.XUserSelectByParam, + value: destUser, + }); + + // + // Resut ... + return this.httpClient.post(endPointPath, null, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region AcceptRequest ... + /** + * Accept a Following Request + * + * @param destUser selected dest User + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public accept( + destUser: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public accept( + destUser: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public accept( + destUser: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public accept( + destUser: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(destUser); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.AcceptRequest, { + key: XAccountEndPointParam.XUserSelectByParam, + value: destUser, + }); + + // + // return response ... + return this.httpClient.post(endPointPath, null, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region RejectRequest ... + /** + * Reject a Following Request + * + * @param destUser selected dest User + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public reject( + destUser: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public reject( + destUser: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public reject( + destUser: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public reject( + destUser: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(destUser); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.RejectRequest, { + key: XAccountEndPointParam.XUserSelectByParam, + value: destUser, + }); + + // + // Result ... + return this.httpClient.post(endPointPath, null, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region FriendshipInfo ... + /** + * Get Friendship Info Model between Specific User and Current User + * + * @param destUser selected dest User + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public friendshipInfo( + destUser: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public friendshipInfo( + destUser: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public friendshipInfo( + destUser: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public friendshipInfo( + destUser: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(destUser); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.FriendshipInfo, { + key: XAccountEndPointParam.XUserSelectByParam, + value: destUser, + }); + + // + // Prepare Result ... + return this.httpClient.get(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region IsFollower ... + /** + * Check a User IsFollower of Requested User + * + * @param destUser selected dest User + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public isFollower( + destUser: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public isFollower( + destUser: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public isFollower( + destUser: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public isFollower( + destUser: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(destUser); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.IsFollower, { + key: XAccountEndPointParam.XUserSelectByParam, + value: destUser, + }); + + // + // Return result ... + return this.httpClient.get(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region FollowerState ... + /** + * Get Follower State of a User + * + * @param destUser selected dest User + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public followerState( + destUser: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public followerState( + destUser: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public followerState( + destUser: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public followerState( + destUser: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(destUser); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.FollowerState, { + key: XAccountEndPointParam.XUserSelectByParam, + value: destUser, + }); + + // + // return response ... + return this.httpClient.get(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region Follower ... + /** + * Get Specific Follower + * + * @param destUser selected dest User + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public follower( + destUser: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public follower( + destUser: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public follower( + destUser: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public follower( + destUser: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(destUser); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.Follower, { + key: XAccountEndPointParam.XUserSelectByParam, + value: destUser, + }); + + // + // return response ... + return this.httpClient.get(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region Followers ... + /** + * Get Followers List Of Current User + * + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public followers( + observe?: 'body', + reportProgress?: boolean + ): Observable>; + public followers( + observe?: 'response', + reportProgress?: boolean + ): Observable>>; + public followers( + observe?: 'events', + reportProgress?: boolean + ): Observable>>; + public followers( + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.Followers); + + // + // return result ... + return this.httpClient.get>(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region AllFollowers ... + /** + * Get All Followers List Includes Blocked, Requested and etc of Current User + * + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public allFollowers( + observe?: 'body', + reportProgress?: boolean + ): Observable>; + public allFollowers( + observe?: 'response', + reportProgress?: boolean + ): Observable>>; + public allFollowers( + observe?: 'events', + reportProgress?: boolean + ): Observable>>; + public allFollowers( + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.AllFollowers); + + // + // return response ... + return this.httpClient.get>(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region FollowersList ... + /** + * Return Followers UserName's List of Current User + * + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public followersList( + observe?: 'body', + reportProgress?: boolean + ): Observable>; + public followersList( + observe?: 'response', + reportProgress?: boolean + ): Observable>>; + public followersList( + observe?: 'events', + reportProgress?: boolean + ): Observable>>; + public followersList( + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.FollowersList); + + // + // return result ... + return this.httpClient.get>(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region QueryFollowers ... + /** + * query Followers + * + * @param query an instance of XQuery interface which provide projectio data + * @param destUser a User for retrieve it's Friend Info ... + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public queryFollowers( + query?: XQueryDto, + destUser?: string, + observe?: 'body', + reportProgress?: boolean + ): Observable>; + public queryFollowers( + query?: XQueryDto, + destUser?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>>; + public queryFollowers( + query?: XQueryDto, + destUser?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>>; + public queryFollowers( + query?: XQueryDto, + destUser?: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + let params = this.generateXQueryHttpParams(query); + + // + // Attach DestUser to Query Params if Provided ... + if (!isNullOrEmptyString(destUser)) { + params = this.addCustomParam(params, { + key: 'destUser', + value: destUser, + }); + } + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.QueryFollowers); + + // + // Prepare Result ... + return this.httpClient.get>(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + params, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region IsFollowing ... + /** + * Check a User is in Followings of Current User + * + * @param destUser selected dest User + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public isFollowing( + destUser: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public isFollowing( + destUser: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public isFollowing( + destUser: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public isFollowing( + destUser: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(destUser); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.IsFollowing, { + key: XAccountEndPointParam.XUserSelectByParam, + value: destUser, + }); + + // + // Result ... + return this.httpClient.get(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region FollowingState ... + /** + * Get Following State Relation between Specific User and Current User + * + * @param destUser selected dest User + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public followingState( + destUser: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public followingState( + destUser: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public followingState( + destUser: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public followingState( + destUser: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(destUser); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.FollowingState, { + key: XAccountEndPointParam.XUserSelectByParam, + value: destUser, + }); + + // + // return result ... + return this.httpClient.get(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region Following ... + /** + * Get Specific Following Model + * + * @param destUser selected dest User + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public following( + destUser: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public following( + destUser: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public following( + destUser: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public following( + destUser: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotEmpty(destUser); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.Following, { + key: XAccountEndPointParam.XUserSelectByParam, + value: destUser, + }); + + // + // return result ... + return this.httpClient.get(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region Followings ... + /** + * Get Followings of Current User + * + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public followings( + observe?: 'body', + reportProgress?: boolean + ): Observable>; + public followings( + observe?: 'response', + reportProgress?: boolean + ): Observable>>; + public followings( + observe?: 'events', + reportProgress?: boolean + ): Observable>>; + public followings( + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.Followings); + + // + // return result ... + return this.httpClient.get>(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region AllFollowings ... + /** + * Get All Following List Includes Blocked, Requested and etc of Current User + * + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public allFollowings( + observe?: 'body', + reportProgress?: boolean + ): Observable>; + public allFollowings( + observe?: 'response', + reportProgress?: boolean + ): Observable>>; + public allFollowings( + observe?: 'events', + reportProgress?: boolean + ): Observable>>; + public allFollowings( + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.AllFollowings); + + // + // return response ... + return this.httpClient.get>(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region FollowingsList ... + /** + * Return Following UserName's List of Current User + * + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public followingsList( + observe?: 'body', + reportProgress?: boolean + ): Observable>; + public followingsList( + observe?: 'response', + reportProgress?: boolean + ): Observable>>; + public followingsList( + observe?: 'events', + reportProgress?: boolean + ): Observable>>; + public followingsList( + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.FollowingsList); + + // + // return result ... + return this.httpClient.get>(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region QueryFollowings ... + /** + * query Followings + * + * @param query an instance of XQuery interface which provide projectio data + * @param destUser a User for retrieve it's Friend Info ... + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public queryFollowings( + query?: XQueryDto, + destUser?: string, + observe?: 'body', + reportProgress?: boolean + ): Observable>; + public queryFollowings( + query?: XQueryDto, + destUser?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>>; + public queryFollowings( + query?: XQueryDto, + destUser?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>>; + public queryFollowings( + query?: XQueryDto, + destUser?: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + let params = this.generateXQueryHttpParams(query); + + // + // Attach DestUser to Query Params if Provided ... + if (!isNullOrEmptyString(destUser)) { + params = this.addCustomParam(params, { + key: 'destUser', + value: destUser, + }); + } + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.QueryFollowings); + + // + // Prepare Result ... + return this.httpClient.get>(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + params, + headers, + observe, + reportProgress, + }); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkIdentitySDKHolder\projects\x-framework-identity-sdk\src\lib\partial\x-account.profile.service.ts + +import { + XavatarDto, + XUserProfileDto, + XUserNameIdRequestDto, + XUserNameIdResponseDto, + XProfileUpdateRequestDto, +} from '../models/x-user.dto'; +import { Observable } from 'rxjs'; +import { + XAccountEndPoint, + XAccountEndPointParam, +} from '../typings/x-endpoint.typings'; +import { XHeader, XValidators } from 'x-framework-core'; +import { HttpResponse, HttpEvent } from '@angular/common/http'; +import { XQueryDto, XQueryResultDto } from '../models/x-query.dto'; +import { XAccountAuthenticationService } from './x-account.authentication.service'; + +export abstract class XAccountProfileService extends XAccountAuthenticationService { + // + //#region GetNames ... + /** + * Get UserName's Related to a Collection of user identifiers + * + * @param ids selected user identifiers + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public getNames( + ids: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public getNames( + ids: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public getNames( + ids: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public getNames( + ids: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate ARgs ... + XValidators.validateNotEmpty(ids); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.GetNames, { + key: XAccountEndPointParam.XIds, + value: ids, + }); + + // + // Prepare Result ... + return this.httpClient.get(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region GetNameIds ... + /** + * Get UserName's Related to a Collection of user id's + * + * @param model selected user identifiers + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public getNameIds( + model: XUserNameIdRequestDto, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public getNameIds( + model: XUserNameIdRequestDto, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public getNameIds( + model: XUserNameIdRequestDto, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public getNameIds( + model: XUserNameIdRequestDto, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate ARgs ... + XValidators.validateNotNull(model); + XValidators.validateHasChilds(model.ids); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.GetNameIds); + + // + // Prepare Result ... + return this.httpClient.post(endPointPath, model, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region Profile ... + /** + * Get Profile Object of Specific User + * + * @param userSelectBy selected user identifier + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public getProfile( + userSelectBy: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public getProfile( + userSelectByParam: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public getProfile( + userSelectByParam: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public getProfile( + userSelectByParam: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate ARgs ... + XValidators.validateNotEmpty(userSelectByParam); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.Profile, { + key: XAccountEndPointParam.XUserSelectByParam, + value: userSelectByParam, + }); + + // + // Prepare Result ... + return this.httpClient.get(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region QueryProfiles ... + /** + * query user profiles + * + * @param query an instance of XQuery interface which provide projectio data + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public queryProfiles( + query?: XQueryDto, + observe?: 'body', + reportProgress?: boolean + ): Observable>; + public queryProfiles( + query?: XQueryDto, + observe?: 'response', + reportProgress?: boolean + ): Observable>>; + public queryProfiles( + query?: XQueryDto, + observe?: 'events', + reportProgress?: boolean + ): Observable>>; + public queryProfiles( + query?: XQueryDto, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + const queryParameters = this.generateXQueryHttpParams(query); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.QueryProfiles); + + // + // Prepare Result ... + return this.httpClient.get>(endPointPath, { + params: queryParameters, + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region QueryAvatars ... + /** + * query specific users avatars ... + * + * @param userSelectByParam user identifier + * @param query an instance of XQuery interface which provide projectio data + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public queryAvatars( + userSelectByParam?: string, + query?: XQueryDto, + observe?: 'body', + reportProgress?: boolean + ): Observable>; + public queryAvatars( + userSelectByParam?: string, + query?: XQueryDto, + observe?: 'response', + reportProgress?: boolean + ): Observable>>; + public queryAvatars( + userSelectByParam?: string, + query?: XQueryDto, + observe?: 'events', + reportProgress?: boolean + ): Observable>>; + public queryAvatars( + userSelectByParam?: string, + query?: XQueryDto, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + const queryParameters = this.generateXQueryHttpParams(query); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.QueryAvatars, { + key: XAccountEndPointParam.XUserSelectByParam, + value: userSelectByParam, + }); + + // + // Prepare Result ... + return this.httpClient.get>(endPointPath, { + params: queryParameters, + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region ProfileUpdate ... + /** + * Update Specific User's Profile + * + * @param userSelectByParam selected user identifier + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public updateProfile( + userSelectByParam: string, + body: XProfileUpdateRequestDto, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public updateProfile( + userSelectByParam: string, + body: XProfileUpdateRequestDto, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public updateProfile( + userSelectByParam: string, + body: XProfileUpdateRequestDto, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public updateProfile( + userSelectByParam: string, + body: XProfileUpdateRequestDto, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate ARgs ... + XValidators.validateNotEmpty(userSelectByParam); + XValidators.validateNotNull(body); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.ProfileUpdate, { + key: XAccountEndPointParam.XUserSelectByParam, + value: userSelectByParam, + }); + + // + // Prepare Result ... + return this.httpClient.post(endPointPath, body, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region FullProfileUpdate ... + /** + * Update Specific User's Profile + * + * @param userSelectByParam selected user identifier + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public fullUpdateProfile( + userSelectByParam: string, + body: XProfileUpdateRequestDto, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public fullUpdateProfile( + userSelectByParam: string, + body: XProfileUpdateRequestDto, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public fullUpdateProfile( + userSelectByParam: string, + body: XProfileUpdateRequestDto, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public fullUpdateProfile( + userSelectByParam: string, + body: XProfileUpdateRequestDto, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate ARgs ... + XValidators.validateNotEmpty(userSelectByParam); + XValidators.validateNotNull(body); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.FullProfileUpdate, { + key: XAccountEndPointParam.XUserSelectByParam, + value: userSelectByParam, + }); + + // + // Prepare Result ... + return this.httpClient.post(endPointPath, body, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region AddAvatar ... + /** + * Add a New Profile Image + * + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public addAvatar( + body: File, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public addAvatar( + body: File, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public addAvatar( + body: File, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public addAvatar( + body: File, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate ARgs ... + XValidators.validateNotNull(body); + + // + const formData: FormData = new FormData(); + formData.append(XHeader.File, body, body.name); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.AddAvatar); + + // + // Prepare Result ... + return this.httpClient.post(endPointPath, formData, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + + //#endregion + + // + //#region AddAvatars ... + /** + * Add a Collection of Profile Images + * + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public addAvatars( + body: File[], + observe?: 'body', + reportProgress?: boolean + ): Observable; + public addAvatars( + body: File[], + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public addAvatars( + body: File[], + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public addAvatars( + body: File[], + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate ARgs ... + XValidators.validateHasChilds(body); + + // + const formData: FormData = new FormData(); + body.forEach((f) => { + formData.append(XHeader.Files, f, f.name); + }); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.AddAvatars); + + // + // Prepare Result ... + return this.httpClient.post(endPointPath, formData, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region SetAvatar ... + /** + * set Specified Profile Image as Avatar + * + * @param id profile image id + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public setAvatar( + id: number, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public setAvatar( + id: number, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public setAvatar( + id: number, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public setAvatar( + id: number, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotNull(id); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.SetAvatar, { + key: XAccountEndPointParam.XId, + value: id.toString(), + }); + + // + // Prepare Result ... + return this.httpClient.post(endPointPath, null, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region RemoveAvatars ... + /** + * Remove Profile Images + * + * @param ids profile image id's which need's to remove + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public removeAvatars( + ids: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public removeAvatars( + ids: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public removeAvatars( + ids: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public removeAvatars( + ids: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate ARgs ... + XValidators.validateNotEmpty(ids); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.RemoveAvatars, { + key: XAccountEndPointParam.XIds, + value: ids, + }); + + // + // Prepare Result ... + return this.httpClient.delete(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkIdentitySDKHolder\projects\x-framework-identity-sdk\src\lib\partial\x-account.registration.service.ts + +import { of } from 'rxjs'; +import { Observable } from 'rxjs'; +import { + XActionRequestDto, + XActionResponseDto, +} from '../models/x-registration-dto'; +import { + XAccountEndPoint, + XAccountEndPointParam, +} from '../typings/x-endpoint.typings'; +import { HttpEvent, HttpResponse } from '@angular/common/http'; +import { XHeader, XCountryCode, XValidators } from 'x-framework-core'; +import { XAccountFriendshipService } from './x-account.friendship.service'; + +export abstract class XAccountRegistrationService extends XAccountFriendshipService { + // + //#region IsConfirmedEmail ... + /** + * Check User is Confirmed Email or not + * + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public isConfirmedEmail( + observe?: 'body', + reportProgress?: boolean + ): Observable; + public isConfirmedEmail( + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public isConfirmedEmail( + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public isConfirmedEmail( + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.IsConfirmedEmail); + + // + // return result ... + return this.httpClient.get(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region IsConfirmedMobile ... + /** + * Check User is Confirmed Mobile or not + * + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public isConfirmedMobile( + observe?: 'body', + reportProgress?: boolean + ): Observable; + public isConfirmedMobile( + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public isConfirmedMobile( + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public isConfirmedMobile( + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.IsConfirmedMobile); + + // + // return result ... + return this.httpClient.get(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region CanRegisterUserName ... + /** + * Check a UserName is Available For Registration or not + * + * @param userName an string value which represent desired username + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public canRegisterUserName( + userName?: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public canRegisterUserName( + userName?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public canRegisterUserName( + userName?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public canRegisterUserName( + userName?: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + try { + XValidators.validateNotEmpty(userName); + } catch { + return of(false); + } + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAcceptJson(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.CanRegisterUserName, { + key: XAccountEndPointParam.XUserName, + value: userName, + }); + + // + // return result ... + return this.httpClient.get(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region CanRegisterMobileNumber ... + /** + * Check a Mobile Number is Available For Registration or not + * + * @param mobileNumber an string value which represent desired mobile number + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public canRegisterMobileNumber( + mobileNumber?: string, + defaultCountryCode?: XCountryCode, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public canRegisterMobileNumber( + mobileNumber?: string, + defaultCountryCode?: XCountryCode, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public canRegisterMobileNumber( + mobileNumber?: string, + defaultCountryCode?: XCountryCode, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public canRegisterMobileNumber( + mobileNumber?: string, + defaultCountryCode?: XCountryCode, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + try { + XValidators.validateNotEmpty(mobileNumber); + XValidators.validatePhoneNumber( + mobileNumber, + defaultCountryCode || this.config.defaultCountryCode + ); + } catch { + return of(false); + } + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAcceptJson(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.CanRegisterMobileNumber, { + key: XAccountEndPointParam.XMobileNumber, + value: mobileNumber, + }); + + // + // return result ... + return this.httpClient.get(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region CanRegisterEmail ... + /** + * Check a Email Address is Available For Registration or not + * + * @param email an string value which represent desired email address + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public canRegisterEmail( + email?: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public canRegisterEmail( + email?: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public canRegisterEmail( + email?: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public canRegisterEmail( + email?: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + try { + XValidators.validateNotEmpty(email); + XValidators.validateEmailAddress(email); + } catch { + return of(false); + } + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAcceptJson(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.CanRegisterEmail, { + key: XAccountEndPointParam.XEmail, + value: email, + }); + + // + // return result ... + return this.httpClient.get(endPointPath, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region RequestConfirmEmail ... + /** + * Request For Email Address Change/ Confirm + * + * @param body request for action + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public requestConfirmEmail( + body: XActionRequestDto, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public requestConfirmEmail( + body: XActionRequestDto, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public requestConfirmEmail( + body: XActionRequestDto, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public requestConfirmEmail( + body: XActionRequestDto, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate ARgs ... + XValidators.validateNotNull(body); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.RequestConfirmEmail); + + // + // Prepare Result ... + return this.httpClient.post(endPointPath, body, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region RequestConfirmMobile ... + /** + * Request For Mobile Number Change/ Confirm + * + * @param body request for action + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public requestConfirmMobile( + body: XActionRequestDto, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public requestConfirmMobile( + body: XActionRequestDto, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public requestConfirmMobile( + body: XActionRequestDto, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public requestConfirmMobile( + body: XActionRequestDto, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate ARgs ... + XValidators.validateNotNull(body); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.RequestConfirmMobile); + + // + // Prepare Result ... + return this.httpClient.post(endPointPath, body, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region RequestResetPassword ... + /** + * Request Reset Password Action + * + * @param body request for action + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public requestResetPassword( + body: XActionRequestDto, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public requestResetPassword( + body: XActionRequestDto, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public requestResetPassword( + body: XActionRequestDto, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public requestResetPassword( + body: XActionRequestDto, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate ARgs ... + XValidators.validateNotNull(body); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.RequestResetPassword); + + // + // Prepare Result ... + return this.httpClient.post(endPointPath, body, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region RequestConfirmRegistration ... + /** + * Request Registration Confirm + * + * @param body request for action + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public requestConfirmRegistration( + body: XActionRequestDto, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public requestConfirmRegistration( + body: XActionRequestDto, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public requestConfirmRegistration( + body: XActionRequestDto, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public requestConfirmRegistration( + body: XActionRequestDto, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate ARgs ... + XValidators.validateNotNull(body); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.RequestConfirmRegistration); + + // + // Prepare Result ... + return this.httpClient.post(endPointPath, body, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region ConfirmRegistration ... + /** + * Confirm Registration ... + * + * @param body request for action + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public confirmRegistration( + body: XActionRequestDto, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public confirmRegistration( + body: XActionRequestDto, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public confirmRegistration( + body: XActionRequestDto, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public confirmRegistration( + body: XActionRequestDto, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate ARgs ... + XValidators.validateNotNull(body); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.ConfirmRegistration); + + // + // Prepare Result ... + return this.httpClient.post(endPointPath, body, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region ConfirmEmailAddress ... + /** + * Confirm Email Address + * + * @param body request for action + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public confirmEmail( + body: XActionRequestDto, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public confirmEmail( + body: XActionRequestDto, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public confirmEmail( + body: XActionRequestDto, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public confirmEmail( + body: XActionRequestDto, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate ARgs ... + XValidators.validateNotNull(body); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.ConfirmEmailAddress); + + // + // Prepare Result ... + return this.httpClient.post(endPointPath, body, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region ConfirmMobileNumber ... + /** + * Confirm Mobile Number + * + * @param body request for action + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public confirmMobile( + body: XActionRequestDto, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public confirmMobile( + body: XActionRequestDto, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public confirmMobile( + body: XActionRequestDto, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public confirmMobile( + body: XActionRequestDto, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate ARgs ... + XValidators.validateNotNull(body); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.ConfirmMobileNumber); + + // + // Prepare Result ... + return this.httpClient.post(endPointPath, body, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region InviteUser ... + /** + * Invite a User to Register on Dashboard + * + * @param body action request object + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public invite( + body: XActionRequestDto, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public invite( + body: XActionRequestDto, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public invite( + body: XActionRequestDto, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public invite( + body: XActionRequestDto, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + XValidators.validateNotNull(body); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.InviteUser); + + // + // return result ... + return this.httpClient.post(endPointPath, body, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region RequestRegistration ... + /** + * Recieve some Basic Informations and Start Registration Proccess if they Valid Registration Proccess Starts with Invoking this Action + * + * @param body action request object + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public requestRegistration( + body: XActionRequestDto, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public requestRegistration( + body: XActionRequestDto, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public requestRegistration( + body: XActionRequestDto, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public requestRegistration( + body: XActionRequestDto, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate ARgs ... + XValidators.validateNotNull(body); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.RequestRegistration); + + // + // Prepare Result ... + return this.httpClient.post(endPointPath, body, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region AddAccountInfo ... + /** + * Add User Account Informations + * + * @param body action request object + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public addAccountInfo( + body: XActionRequestDto, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public addAccountInfo( + body: XActionRequestDto, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public addAccountInfo( + body: XActionRequestDto, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public addAccountInfo( + body: XActionRequestDto, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate ARgs ... + XValidators.validateNotNull(body); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.AddAccountInfo); + + // + // Prepare Result ... + return this.httpClient.post(endPointPath, body, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region AttachProfileImage ... + /** + * Upload and Attach a Profile Image for User + * + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public attachProfileImage( + body: File, + actionToken: string, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public attachProfileImage( + body: File, + actionToken: string, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public attachProfileImage( + body: File, + actionToken: string, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public attachProfileImage( + body: File, + actionToken: string, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate ARgs ... + XValidators.validateNotNull(body); + XValidators.validateNotEmpty(actionToken); + + // + const formData: FormData = new FormData(); + formData.append(XHeader.File, body, body.name); + formData.append(XHeader.ActionToken, actionToken); + + // + // Instantiiate Headers from Default Headers ... + const headers = this.defaultHeaders; + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.AttachProfileImage); + + // + // Prepare Result ... + return this.httpClient.post(endPointPath, formData, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region FinishRegistration ... + /** + * Finish Registrationn Proccess + * + * @param body action request object + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public finishRegistration( + body: XActionRequestDto, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public finishRegistration( + body: XActionRequestDto, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public finishRegistration( + body: XActionRequestDto, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public finishRegistration( + body: XActionRequestDto, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate ARgs ... + XValidators.validateNotNull(body); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.FinishRegistration); + + // + // Prepare Result ... + return this.httpClient.post(endPointPath, body, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region ChangePassword ... + /** + * Change a User's Password + * + * @param body an instance of XActionRequestDto class which provider requirement for action + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public changePassword( + body: XActionRequestDto, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public changePassword( + body: XActionRequestDto, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public changePassword( + body: XActionRequestDto, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public changePassword( + body: XActionRequestDto, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate Args ... + XValidators.validateNotNull(body, body.device); + XValidators.validateNotEmpty( + body.lang, + body.password, + body.newPassword, + body.returnUrl + ); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + headers = this.addAuthentication(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.ChangePassword); + + // + // return result ... + return this.httpClient.post(endPointPath, body, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion + + // + //#region ResetPassword ... + /** + * Reset Password + * + * @param body request for action + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public resetPassword( + body: XActionRequestDto, + observe?: 'body', + reportProgress?: boolean + ): Observable; + public resetPassword( + body: XActionRequestDto, + observe?: 'response', + reportProgress?: boolean + ): Observable>; + public resetPassword( + body: XActionRequestDto, + observe?: 'events', + reportProgress?: boolean + ): Observable>; + public resetPassword( + body: XActionRequestDto, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + // Validate ARgs ... + XValidators.validateNotNull(body); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.ResetPassword); + + // + // Prepare Result ... + return this.httpClient.post(endPointPath, body, { + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkIdentitySDKHolder\projects\x-framework-identity-sdk\src\lib\partial\x-account.search.service.ts + +import { Observable } from 'rxjs'; +import { + XAccountEndPoint, + XAccountEndPointParam, +} from '../typings/x-endpoint.typings'; +import { XUserProfileDto } from '../models/x-user.dto'; +import { HttpEvent, HttpResponse } from '@angular/common/http'; +import { XAccountAdminService } from './x-account.admin.service'; +import { XQueryDto, XQueryResultDto } from '../models/x-query.dto'; + +export abstract class XAccountSearchService extends XAccountAdminService { + // + //#region QueryOpenToSearchProfiles ... + /** + * query user profiles + * + * @param query an instance of XQuery interface which provide projectio data + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public queryOpenToSearchProfiles( + query?: XQueryDto, + observe?: 'body', + reportProgress?: boolean + ): Observable>; + public queryOpenToSearchProfiles( + query?: XQueryDto, + observe?: 'response', + reportProgress?: boolean + ): Observable>>; + public queryOpenToSearchProfiles( + query?: XQueryDto, + observe?: 'events', + reportProgress?: boolean + ): Observable>>; + public queryOpenToSearchProfiles( + query?: XQueryDto, + observe: any = 'body', + reportProgress: boolean = false + ): Observable { + // + const queryParameters = this.generateXQueryHttpParams(query); + + // + // Instantiiate Headers from Default Headers ... + let headers = this.defaultHeaders; + headers = this.addAuthentication(headers); + headers = this.addAcceptJson(headers); + headers = this.addContentType(headers); + + // + // Prepare Endpoint + const endPointPath = this.getActionRoute< + XAccountEndPoint, + XAccountEndPointParam + >(XAccountEndPoint.QueryOpenToSearchProfiles); + + // + // Prepare Result ... + return this.httpClient.get>(endPointPath, { + params: queryParameters, + withCredentials: this.apiConfig.withCredentials, + headers, + observe, + reportProgress, + }); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkIdentitySDKHolder\projects\x-framework-identity-sdk\src\lib\providers\x-url.encoder.ts + +import { HttpUrlEncodingCodec } from '@angular/common/http'; + +export class XHttpUrlEncodingCodec extends HttpUrlEncodingCodec { + encodeKey(k: string): string { + k = super.encodeKey(k); + return k.replace(/\+/gi, '%2B'); + } + encodeValue(v: string): string { + v = super.encodeValue(v); + return v.replace(/\+/gi, '%2B'); + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkIdentitySDKHolder\projects\x-framework-identity-sdk\src\lib\services\api-http-interceptor.service.ts + +import { + XHeader, + isObject, + countChild, + findAllIndex, + toObservable, + XBaseService, + XExceptionIDs, + getErrorException, + XStringPlaceHolder, + isNullOrEmptyString, +} from 'x-framework-core'; +import { + HttpEvent, + HttpRequest, + HttpHandler, + HttpEventType, + HttpInterceptor, + HttpErrorResponse, +} from '@angular/common/http'; +import { Observable, throwError } from 'rxjs'; +import { Injectable, Inject } from '@angular/core'; +import { XManagerService } from 'x-framework-services'; +import { XAccountService } from './../x-account.service'; +import { XLoginResponseDto } from '../models/x-login.dto'; +import { finalize, catchError, concatMap, map } from 'rxjs/operators'; +import { X_FRAMEWORK_IDENTITY_SDK_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkIdentitySDKConfig } from '../config/x-framework-identity-sdk.config'; + +@Injectable({ + providedIn: 'root', +}) +export class ApiHttpInterceptorService + extends XBaseService + implements HttpInterceptor +{ + // + private isRefreshing = false; + + // + constructor( + private authService: XAccountService, + @Inject(X_FRAMEWORK_IDENTITY_SDK_CONFIG) + config: XFrameworkIdentitySDKConfig, + private managerService: XManagerService + ) { + super(config); + } + + intercept( + req: HttpRequest, + next: HttpHandler + ): Observable> { + return this.handleInterceptRequest(req, next); + } + + handleInterceptRequest( + req: HttpRequest, + next: HttpHandler + ): Observable> { + // + // Adding Identifier ... + req = this.applyHeadersToRequest(req); + + // + // Apply RefreshToken Handler ... + const handledRefreshToken = !this.isRefreshing + ? this.applyRefreshTokenHandler(next.handle(req), req, next) + : next.handle(req); + // const handledRefreshToken = next.handle(req); + + // + // Apply Response Headers Handler ... + const handledResponseHeaders = + this.applyResponseHandler(handledRefreshToken); + + // + // Apply Error Handler ... + const handledErrors = this.applyErrorHandler(handledResponseHeaders); + + // + // Apply Finally Handler ... + const handledFinally = this.applyFinally(handledErrors); + + // + // Final Level ... + const result = handledFinally; + + // + return result; + } + + // + //#region Actions / Handlers ... + applyHeadersToRequest(request: HttpRequest): HttpRequest { + return request.clone({ + setHeaders: { + XRegisteredTo: `${this.config.poweredValue}`, + }, + }); + } + + applyFinally( + current: Observable> + ): Observable> { + // + return current.pipe( + finalize(() => { + // + // Collection of instructions after finish or error ... + this.isRefreshing = false; + this.managerService.loading = false; + }) + ); + } + + applyRefreshTokenHandler( + current: Observable>, + req: HttpRequest, + next: HttpHandler + ): Observable> { + // + return current.pipe( + catchError((error) => { + // + if (error instanceof HttpErrorResponse && error.status === 401) { + this.logError('401 => ', error); + + // + const isExpired$ = toObservable(this.authService.isExpired()); + + // + return isExpired$.pipe( + concatMap((isExpired) => { + if (isExpired) { + return this.handleRefreshToken(req, next); + } else { + return throwError(error); + } + }) + ); + } else { + return throwError(error); + } + }) + ); + } + + applyResponseHandler( + current: Observable> + ): Observable> { + // + return current.pipe( + map((event: HttpEvent) => { + // this.handleResponseHeaders(event); + return event; + }) + ); + } + + applyErrorHandler( + current: Observable> + ): Observable> { + return current.pipe( + catchError((error: HttpErrorResponse) => { + // + this.managerService.loading = false; + const mException = getErrorException(error.error); + + // + // Check mException contains string formatter place holder or not ... + if (mException.message.includes(XStringPlaceHolder)) { + // + // try to retrieve place holder contents ... + const placeHolderIndexes = findAllIndex( + mException.message, + XStringPlaceHolder + ); + const countOfPlaceHolders = countChild(placeHolderIndexes); + + // + // retrieve place holder content from message ... + const placeHolderContents: string[] = []; + let placeHolderContent = + error.error && isObject(error.error) + ? (error.error.message as string) + : ''; + if ( + !isNullOrEmptyString(placeHolderContent) && + placeHolderContent.includes(':') + ) { + // + placeHolderContent = placeHolderContent + .substring(placeHolderContent.indexOf(':') + 1) + .trim(); + + // + if (countOfPlaceHolders > 1 && placeHolderContent.includes(',')) { + placeHolderContents.push(...placeHolderContent.split(',')); + } else { + placeHolderContents.push(placeHolderContent); + } + } + + // + // now we can fix and add place holder params to message of mException using stringFormatter function ... + mException.payload = [...placeHolderContents]; + } + + // + this.managerService.error = mException; + + // + this.logError('HttpInterceptor: ', error, mException); + + // + return throwError(mException); + }) + ); + } + + handleRefreshToken( + req: HttpRequest, + next: HttpHandler + ): Observable> { + // + if (!this.isRefreshing) { + this.isRefreshing = true; + + // + return this.authService.state$.pipe( + concatMap((accountState) => { + return this.authService.refreshTokens().pipe( + map((loginResponse) => { + return { + accountState, + loginResponse, + }; + }) + ); + }), + concatMap((accountInfo) => { + // + this.logWarn('Login Refresh Response: ', accountInfo.loginResponse); + + // + let headers = this.authService.defaultHeaders; + headers = this.authService.addAuthentication( + headers, + accountInfo.loginResponse + ); + headers = Object.assign(req.headers, headers); + + // + req = req.clone({ + headers, + }); + + // + // Update User Account ... + const updateAccount$ = toObservable( + this.authService.updateUserTokens( + accountInfo.accountState, + accountInfo.loginResponse + ) + ); + + // + return updateAccount$.pipe( + concatMap(() => { + return next + .handle(req) + .pipe(finalize(() => (this.isRefreshing = false))); + }) + ); + }) + ); + } else { + return this.handleRefreshToken(req, next); + } + } + + async handleResponseHeaders(event: HttpEvent): Promise { + // + if (event.type === HttpEventType.Response) { + // + const headers = event.headers; + + // + const isContainsNewAuthentication = + headers.has(XHeader.Authorization) && + headers.has(XHeader.RefreshToken) && + headers.has(XHeader.ExpiresAt) && + headers.has(XHeader.RevisionChecksum); + + // + if (isContainsNewAuthentication) { + // + const oldTokens = await this.authService.getDefaultUserTokens(); + + // + const accessToken = headers + .get(XHeader.Authorization) + .replace(XHeader.AuthorizationPrefix, ''); + const refreshToken = headers.get(XHeader.RefreshToken); + const expiresAt = +headers.get(XHeader.ExpiresAt); + const revisionChecksum = headers.get(XHeader.RevisionChecksum); + + // + const profile = oldTokens.profile; + + // + const newTokens: XLoginResponseDto = { + accessToken, + refreshToken, + expiresAt, + profile, + }; + + // + const isValid = this.authService.validateLoginResponseRevision( + newTokens, + revisionChecksum + ); + + // + if (isValid) { + await this.authService.updateUserTokens(oldTokens, newTokens); + } else { + // + this.managerService.notificationService.presentErrorNotification({ + exception: XExceptionIDs.InvalidToken, + dissmissable: true, + }); + + // + await this.authService.logout(); + } + } + } + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkIdentitySDKHolder\projects\x-framework-identity-sdk\src\lib\tokens\x-injectable-tokens.ts + +import { InjectionToken } from '@angular/core'; +import { XApiConfiguration } from '../config/x-api-service.config'; +import { XFrameworkIdentitySDKConfig } from '../config/x-framework-identity-sdk.config'; + +export const X_FRAMEWORK_IDENTITY_SDK_CONFIG = new InjectionToken( + 'x_config' +); + +export const X_API_CONFIG = new InjectionToken( + 'apiConfiguration' +); + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkIdentitySDKHolder\projects\x-framework-identity-sdk\src\lib\tools\x-endpoint.tools.ts + +import { + values, + toArray, + hasChild, + XKeyValue, + XOneOrManyType, + isNullOrEmptyString, +} from 'x-framework-core'; +import { XAccountEndPointParam } from '../typings/x-endpoint.typings'; + +export function isContainsParams( + route: string, + params?: XOneOrManyType> +) { + // + // Validate Args ... + if (isNullOrEmptyString(route)) { + return false; + } + + // + const paramValues = values(XAccountEndPointParam) as Array; + if (!hasChild(paramValues)) { + return false; + } + + // + let result = paramValues.some((param) => route.includes(param)); + if (!result) { + // + const paramsArr = toArray(params).map((p) => p.key) as Array; + if (!hasChild(paramsArr)) { + return false; + } + + // + result = paramsArr.some((param) => route.includes(param)); + } + + // + return result; +} + +export function setParams( + route: string, + params?: XOneOrManyType> +) { + // + // Validate Args ... + if ( + !hasChild(params) || + !isContainsParams(route, params) || + isNullOrEmptyString(route) + ) { + return route; + } + + // + // Do Actions ... + toArray(params).forEach((paramKeyValue) => { + // + route = route.replace( + String(paramKeyValue.key), + encodeURIComponent(String(paramKeyValue.value)) + ); + }); + + // + return route; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkIdentitySDKHolder\projects\x-framework-identity-sdk\src\lib\typings\x-account.typings.ts + +import { XLoginResponseDto } from '../models/x-login.dto'; + +export interface XUserAccountInfo extends XLoginResponseDto { + userSelectBy: string; +} + +export interface XUserAccountInfoIndex { + [userSelectBy: string]: XUserAccountInfo; +} + +export interface XAccountState extends XUserAccountInfo { + isLoggedIn: boolean; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkIdentitySDKHolder\projects\x-framework-identity-sdk\src\lib\typings\x-endpoint.typings.ts + +export enum XEndPoint { + Account, +} + +export type XEndPoints = keyof typeof XEndPoint | string; + +/** + * prepare all Route Params which used in Account Services Endpoints ... + */ +export enum XAccountEndPointParam { + XId = '{xId}', + XIds = '{xIds}', + XEmail = '{xEmail}', + XUserName = '{xUserName}', + XMobileNumber = '{xMobileNumber}', + XUserSelectByParam = '{xUserSelectByParam}', +} + +export type XAccountEndPointParams = keyof typeof XAccountEndPointParam; + +export enum XAccountEndPoint { + // + //#region Test ... + Hi = 'Test/Hi', + PassReadAccess = 'Test/PassReadAccess', + PassWriteAccess = 'Test/PassWriteAccess', + PassAdminAccess = 'Test/PassAdminAccess', + PassManageAccess = 'Test/PassManageAccess', + PassRequireXPowered = 'Test/PassRequireXPowered', + HiClaims = 'Test/HiClaims', + HiUser = 'Test/HiUser', + HiEnabledUser = 'Test/HiEnabledUser', + HiColleague = 'Test/HiColleague', + HiEnabledColleague = 'Test/HiEnabledColleague', + HiAgent = 'Test/HiAgent', + HiEnabledAgent = 'Test/HiEnabledAdmingent', + HiAdmin = 'Test/HiAdmin', + HiEnabledAdmin = 'Test/HiEnabledAdmin', + //#endregion + + // + //#region Admin ... + Ban = 'Ban', + UnBan = 'UnBan', + IsBanned = 'IsBanned/{xUserSelectByParam}', + //#endregion + + // + //#region Authentication ... + DiscoveryDocument = 'DiscoveryDocument', + RequestScopeAccessToken = 'RequestScopeAccessToken', + Login = 'Login', + Logout = 'Logout', + Authenticate = 'Authenticate', + RefreshTokens = 'RefreshTokens', + Validate = 'Validate', + //#endregion + + // + //#region Registration ... + IsConfirmedEmail = 'IsConfirmedEmail', + IsConfirmedMobile = 'IsConfirmedMobile', + CanRegisterUserName = 'CanRegisterUserName/{xUserName}', + CanRegisterMobileNumber = 'CanRegisterMobileNumber/{xMobileNumber}', + CanRegisterEmail = 'CanRegisterEmail/{xEmail}', + RequestConfirmEmail = 'RequestConfirmEmail', + RequestConfirmMobile = 'RequestConfirmMobile', + RequestResetPassword = 'RequestResetPassword', + RequestConfirmRegistration = 'RequestConfirmRegistration', + ChangePassword = 'ChangePassword', + ResetPassword = 'ResetPassword', + ConfirmRegistration = 'ConfirmRegistration', + ConfirmEmailAddress = 'ConfirmEmailAddress', + ConfirmMobileNumber = 'ConfirmMobileNumber', + InviteUser = 'InviteUser', + RequestRegistration = 'RequestRegistration', + AddAccountInfo = 'AddAccountInfo', + AttachProfileImage = 'AttachProfileImage', + FinishRegistration = 'FinishRegistration', + //#endregion + + // + //#region Friendship ... + Follow = 'Friendship/{xUserSelectByParam}/Follow', + Cancel = 'Friendship/{xUserSelectByParam}/Cancel', + UnFollowFollower = 'Friendship/{xUserSelectByParam}/UnFollowFollower', + UnFollowFollowing = 'Friendship/{xUserSelectByParam}/UnFollowFollowing', + Block = 'Friendship/{xUserSelectByParam}/Block', + UnBlock = 'Friendship/{xUserSelectByParam}/UnBlock', + AcceptRequest = 'Friendship/{xUserSelectByParam}/AcceptRequest', + RejectRequest = 'Friendship/{xUserSelectByParam}/RejectRequest', + FriendshipInfo = 'Friendship/{xUserSelectByParam}/FriendshipInfo', + IsFollower = 'Friendship/{xUserSelectByParam}/IsFollower', + Follower = 'Friendship/{xUserSelectByParam}/Follower', + FollowerState = 'Friendship/{xUserSelectByParam}/FollowerState', + Followers = 'Friendship/Followers', + AllFollowers = 'Friendship/AllFollowers', + FollowersList = 'Friendship/FollowersList', + QueryFollowers = 'Friendship/QueryFollowers', + IsFollowing = 'Friendship/{xUserSelectByParam}/IsFollowing', + Following = 'Friendship/{xUserSelectByParam}/Following', + FollowingState = 'Friendship/{xUserSelectByParam}/FollowingState', + Followings = 'Friendship/Followings', + AllFollowings = 'Friendship/AllFollowings', + FollowingsList = 'Friendship/FollowingsList', + QueryFollowings = 'Friendship/QueryFollowings', + //#endregion + + // + //#region Profile ... + GetNames = 'Profile/{xIds}/GetNames', + GetNameIds = 'Profile/GetNameIds', + Profile = 'Profile/{xUserSelectByParam}', + QueryProfiles = 'Profile/Query', + QueryAvatars = 'Profile/Avatars/Query/{xUserSelectByParam}', + ProfileUpdate = 'Profile/Update/{xUserSelectByParam}', + FullProfileUpdate = 'Profile/{xUserSelectByParam}', + AddAvatar = 'Profile/Avatar', + AddAvatars = 'Profile/Avatars', + SetAvatar = 'Profile/Avatars/{xId}/Set', + RemoveAvatars = 'Profile/Avatars/{xIds}', + //#endregion + + // + //#region Search ... + QueryOpenToSearchProfiles = 'OpenToSearch/Query', + //#endregion +} + +export type XAccountEndPoints = keyof typeof XAccountEndPoint; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkIdentitySDKHolder\projects\x-framework-identity-sdk\src\lib\typings\x-registration.typings.ts + +export enum RegistrationStep { + Terms = 'terms', + Info = 'info', + MobileConfirmation = 'mobile_confirmation', + EmailConfirmation = 'email_confirmation', + Avatar = 'avatar', + Finish = 'finish' +} + +export type RegistrationStepIdentifier = RegistrationStep | string; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkIdentitySDKHolder\projects\x-framework-identity-sdk\src\lib\validators\api-validators.ts + +import { + tap, + concatMap, + debounceTime, + distinctUntilChanged, +} from 'rxjs/operators'; +import { + ValidatorFn, + AbstractControl, + AsyncValidatorFn, + ValidationErrors, +} from '@angular/forms'; +import { + applyLocale, + XResourceIDs, + XCountryCode, + hasUniqueChar, + stringFormatter, + isNullOrEmptyString, + normalizePhoneNumber, +} from 'x-framework-core'; +import { Subject, of, Observable } from 'rxjs'; +import { Injectable, Inject } from '@angular/core'; +import { XAccountService } from '../x-account.service'; +import { XLocalizationService } from 'x-framework-services'; +import { X_FRAMEWORK_IDENTITY_SDK_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkIdentitySDKConfig } from '../config/x-framework-identity-sdk.config'; + +export interface XPasswordValidationErrorObject { + // + requireDigit?: boolean; + requireDigitErr?: boolean; + + // + requiredLength?: number; + requiredLengthErr?: boolean; + + // + requiredUniqueChars?: number; + requiredUniqueCharsErr?: boolean; + + // + requireLowercase?: boolean; + requireLowercaseErr?: boolean; + + // + requireNonAlphanumeric?: boolean; + requireNonAlphanumericErr?: boolean; + + // + requireUppercase?: boolean; + requireUppercaseErr?: boolean; +} + +export interface XPasswordValidatorError { + xPasswordValidator: XPasswordValidationErrorObject | null; +} + +@Injectable({ + providedIn: 'root', +}) +export class ApiValidators { + // + //#region Props ... + // + private userNameErrorObject = { + userNameValidator: true, + }; + + // + private emailErrorObject = { + emailValidator: true, + }; + + // + private mobileErrorObject = { + mobileValidator: true, + }; + + // + private passwordErrorObject: XPasswordValidatorError = { + xPasswordValidator: { + // + requireDigit: true, + requireDigitErr: false, + + // + requiredLength: 0, + requiredLengthErr: false, + + // + requiredUniqueChars: 0, + requiredUniqueCharsErr: false, + + // + requireLowercase: true, + requireLowercaseErr: false, + + // + requireNonAlphanumeric: true, + requireNonAlphanumericErr: false, + + // + requireUppercase: true, + requireUppercaseErr: false, + }, + }; + + // + public loading$ = new Subject(); + //#endregion + + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_IDENTITY_SDK_CONFIG) + private config: XFrameworkIdentitySDKConfig, + private accountService: XAccountService, + private localizationService: XLocalizationService + ) {} + //#endregion + + // + //#region Validators ... + passwordValidator(): ValidatorFn { + // + const validatorFn = (control: AbstractControl): ValidationErrors | null => { + // + // Check Password Configs ... + if (!this.config || !this.config.passwordPolicies) { + return null; + } + + // + const pswPolicies = this.config.passwordPolicies; + + // + const errObj: XPasswordValidatorError = { + ...this.passwordErrorObject, + xPasswordValidator: { + ...this.passwordErrorObject.xPasswordValidator, + ...pswPolicies, + }, + }; + + // + const value = control && control.value ? (control.value as string) : null; + + // + // Required Length ... + if (pswPolicies.requiredLength) { + // + const isLengthPassed = + value && value.length >= pswPolicies.requiredLength; + + // + if (!isLengthPassed) { + // + errObj.xPasswordValidator.requiredLengthErr = true; + + // + return errObj; + } + } + + // + // Required Digit ... + if (pswPolicies.requireDigit) { + // + const containsDigitRegExp = new RegExp('(?=.*[0-9])'); + const isRequiredDigitPassed = containsDigitRegExp.test(value); + + // + if (!isRequiredDigitPassed) { + // + errObj.xPasswordValidator.requireDigitErr = true; + + // + return errObj; + } + } + + // + // Required LowerCase ... + if (pswPolicies.requireLowercase) { + // + const containsLowerCaseRegExp = new RegExp('(?=.*[a-z])'); + const isRequiredLowercasePassed = containsLowerCaseRegExp.test(value); + + // + if (!isRequiredLowercasePassed) { + // + errObj.xPasswordValidator.requireLowercaseErr = true; + + // + return errObj; + } + } + + // + // Required UpperCase ... + if (pswPolicies.requireUppercase) { + // + const containsUpperCaseRegExp = new RegExp('(?=.*[A-Z])'); + const isRequiredUppercasePassed = containsUpperCaseRegExp.test(value); + + // + if (!isRequiredUppercasePassed) { + // + errObj.xPasswordValidator.requireUppercaseErr = true; + + // + return errObj; + } + } + + // + // Required NonAlphabet/Special Char ... + if (pswPolicies.requireNonAlphanumeric) { + // + const containsSpecialCharsRegExp = new RegExp('(?=.*[!@#$%^&*])'); + const isRequiredSpecialCharsPassed = + containsSpecialCharsRegExp.test(value); + + // + if (!isRequiredSpecialCharsPassed) { + // + errObj.xPasswordValidator.requireNonAlphanumericErr = true; + + // + return errObj; + } + } + + // + // Required Unique Char ... + if (pswPolicies.requiredUniqueChars) { + // + const isRequiredUniqueCharPassed = hasUniqueChar(value); + + // + if (!isRequiredUniqueCharPassed) { + // + errObj.xPasswordValidator.requiredUniqueCharsErr = true; + + // + return errObj; + } + } + + // + return null; + }; + + // + return validatorFn; + } + + emailValidator(): AsyncValidatorFn { + // + const validatorFn = ( + control: AbstractControl + ): Observable => { + // + const value = control && control.value ? (control.value as string) : null; + + // + return of(null).pipe( + tap((_) => this.loading$.next(true)), + debounceTime(this.config.debounceTime), + distinctUntilChanged(), + concatMap((_) => this.accountService.canRegisterEmail(value)), + concatMap((canRegister) => + canRegister ? of(null) : of(this.emailErrorObject) + ), + tap((_) => this.loading$.next(false)) + ); + }; + + // + return validatorFn; + } + + mobileNumberValidator(defaultCountryCode?: XCountryCode): AsyncValidatorFn { + // + const validatorFn = ( + control: AbstractControl + ): Observable => { + // + const value = control && control.value ? (control.value as string) : null; + const normalValue = normalizePhoneNumber(value, defaultCountryCode); + + // + return of(null).pipe( + tap((_) => this.loading$.next(true)), + debounceTime(this.config.debounceTime), + distinctUntilChanged(), + concatMap((_) => + this.accountService.canRegisterMobileNumber(normalValue.toString()) + ), + concatMap((canRegister) => + canRegister ? of(null) : of(this.mobileErrorObject) + ), + tap((_) => this.loading$.next(false)) + ); + }; + + // + return validatorFn; + } + + userNameValidator(): AsyncValidatorFn { + // + const validatorFn = ( + control: AbstractControl + ): Observable => { + // + const value = control && control.value ? (control.value as string) : null; + + // + return of(null).pipe( + tap((_) => this.loading$.next(true)), + debounceTime(this.config.debounceTime), + distinctUntilChanged(), + concatMap((_) => this.accountService.canRegisterUserName(value)), + concatMap((canRegister) => + canRegister ? of(null) : of(this.userNameErrorObject) + ), + tap((_) => this.loading$.next(false)) + ); + }; + + // + return validatorFn; + } + //#endregion + + // + //#region Data Providers ... + /** + * generate error message for password validation ... + * + * @param error the XPasswordValidationError instance ... + */ + generatePasswordValidationError(error: XPasswordValidationErrorObject) { + // + if (!error) { + return ''; + } + + // + let errorMessage = ''; + + // + // Handle Required Length ... + if (error.requiredLength > 0 && error.requiredLengthErr) { + // + errorMessage += !isNullOrEmptyString(errorMessage) ? '\n' : ''; + errorMessage += `${this.getRequiredLengthError(error.requiredLength)}`; + } + + // + // Handle Required UniqueChars ... + else if (error.requiredUniqueChars > 0 && error.requiredUniqueCharsErr) { + // + errorMessage += !isNullOrEmptyString(errorMessage) ? '\n' : ''; + errorMessage += `${this.getRequiredUniqueChanrsError( + error.requiredUniqueChars + )}`; + } + + // + // Handle Required Digits ... + else if (error.requireDigit && error.requireDigitErr) { + // + errorMessage += !isNullOrEmptyString(errorMessage) ? '\n' : ''; + errorMessage += `${this.getRequiredDigitsError()}`; + } + + // + // Handle Required LowerCase ... + else if (error.requireLowercase && error.requireLowercaseErr) { + // + errorMessage += !isNullOrEmptyString(errorMessage) ? '\n' : ''; + errorMessage += `${this.getRequiredLowercaseError()}`; + } + + // + // Handle Required UpperCase ... + else if (error.requireUppercase && error.requireUppercaseErr) { + // + errorMessage += !isNullOrEmptyString(errorMessage) ? '\n' : ''; + errorMessage += `${this.getRequiredUppercaseError()}`; + } + + // + // Handle Required NonAlphanumeric ... + else if (error.requireNonAlphanumeric && error.requireNonAlphanumericErr) { + // + errorMessage += !isNullOrEmptyString(errorMessage) ? '\n' : ''; + errorMessage += `${this.getRequiredNonAlphanumericError()}`; + } + + // + return errorMessage; + } + //#endregion + + // + //#region Private ... + private getRequiredLengthError(length: number) { + // + const error = this.localizationService.getValue( + XResourceIDs.required_length + ); + + // + if (!error) { + return ''; + } + + // + return applyLocale( + stringFormatter(error, length), + this.localizationService.locale + ); + } + + private getRequiredUniqueChanrsError(length: number) { + // + const error = this.localizationService.getValue( + XResourceIDs.required_unique_chars + ); + + // + if (!error) { + return ''; + } + + // + return applyLocale( + stringFormatter(error, length), + this.localizationService.locale + ); + } + + private getRequiredDigitsError() { + return this.localizationService.getValue(XResourceIDs.require_digit); + } + + private getRequiredLowercaseError() { + return this.localizationService.getValue(XResourceIDs.require_lowercase); + } + + private getRequiredUppercaseError() { + return this.localizationService.getValue(XResourceIDs.require_uppercase); + } + + private getRequiredNonAlphanumericError() { + return this.localizationService.getValue( + XResourceIDs.require_non_alphanumeric + ); + } + //#endregion +} diff --git a/Documents/GeneratedContext/xDashboard-xFrameworkPushServiceHolder-20260809_235659.txt b/Documents/GeneratedContext/xDashboard-xFrameworkPushServiceHolder-20260809_235659.txt new file mode 100644 index 0000000..b00e099 --- /dev/null +++ b/Documents/GeneratedContext/xDashboard-xFrameworkPushServiceHolder-20260809_235659.txt @@ -0,0 +1,1074 @@ + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkPushServiceHolder\projects\x-framework-push-service\src\public-api.ts + +/* + * Public API Surface of x-framework-push-service + */ + +// +export * from './lib/tokens/x-injectable-tokens'; +export * from './lib/models/x-hub-connection.dto'; +export * from './lib/typings/x-push-notification.typings'; +export * from './lib/policies/x-push-service-token-fetcher'; +export * from './lib/config/x-framework-push-service.config'; +export * from './lib/policies/x-push-service-connection-retry-policy'; + +// +// Base ... +export * from './lib/base/x-rtc-peer.service'; +export * from './lib/base/x-base-push.service'; +export * from './lib/base/x-base-web-rtc.service'; +export * from './lib/base/x-base-entity.push.service'; + +// +// Interfaces ... + +// +// Models ... + +// +// Tools ... + +// +// Module ... +export * from './lib/x-framework-push-service.module'; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkPushServiceHolder\projects\x-framework-push-service\src\lib\x-framework-push-service.module.ts + +import { NgModule } from '@angular/core'; + +@NgModule({ + declarations: [ + ], + imports: [ + ], + exports: [ + ] +}) +export class XFrameworkPushServiceModule { } + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkPushServiceHolder\projects\x-framework-push-service\src\lib\base\x-base-entity.push.service.ts + +import { Subject } from 'rxjs'; +import { Injectable } from '@angular/core'; +import { XBasePushService } from './x-base-push.service'; +import { hasChild, parseJson, isNullOrUndefined } from 'x-framework-core'; +import { XBaseEntityPushAction } from '../typings/x-push-notification.typings'; + +@Injectable() +export abstract class XBaseEntityPushService extends XBasePushService { + // + //#region Props ... + onAdded$ = new Subject<{ model: TEntity; actorId: string }>(); + onUpdated$ = new Subject<{ model: TEntity; actorId: string }>(); + onDeleted$ = new Subject<{ model: TEntity; actorId: string }>(); + onAddOrUpdated$ = new Subject<{ model: TEntity; actorId: string }>(); + onManyAdded$ = new Subject<{ model: Array; actorId: string }>(); + onManyUpdated$ = new Subject<{ model: Array; actorId: string }>(); + onManyDeleted$ = new Subject<{ model: Array; actorId: string }>(); + //#endregion + + // + //#region Actions ... + /** + * Fire Add Action ... + * + * @param model + * @returns + */ + async add(model: TEntity) { + // + // Validate ... + let isValid = !isNullOrUndefined(model); + if (!isValid) { + return; + } + + // + await this.invoke(XBaseEntityPushAction.Add, JSON.stringify(model)); + } + + /** + * Fire Update Action ... + * + * @param model + * @returns + */ + async update(model: TEntity) { + // + // Validate ... + let isValid = !isNullOrUndefined(model); + if (!isValid) { + return; + } + + // + await this.invoke(XBaseEntityPushAction.Update, JSON.stringify(model)); + } + + /** + * Fire Delete Action ... + * + * @param model + * @returns + */ + async delete(model: TEntity) { + // + // Validate ... + let isValid = !isNullOrUndefined(model); + if (!isValid) { + return; + } + + // + await this.invoke(XBaseEntityPushAction.Delete, JSON.stringify(model)); + } + + /** + * Fire Add Or Update Action ... + * + * @param model + * @returns + */ + async addOrUpdate(model: TEntity) { + // + // Validate ... + let isValid = !isNullOrUndefined(model); + if (!isValid) { + return; + } + + // + await this.invoke(XBaseEntityPushAction.AddOrUpdate, JSON.stringify(model)); + } + + /** + * Fire Add Many Action ... + * + * @param model + * @returns + */ + async addMany(models: Array) { + // + // Validate ... + let isValid = !isNullOrUndefined(models) && hasChild(models); + if (!isValid) { + return; + } + + // + await this.invoke(XBaseEntityPushAction.AddMany, JSON.stringify(models)); + } + + /** + * Fire Update Many Action ... + * + * @param model + * @returns + */ + async updateMany(models: Array) { + // + // Validate ... + let isValid = !isNullOrUndefined(models) && hasChild(models); + if (!isValid) { + return; + } + + // + await this.invoke(XBaseEntityPushAction.UpdateMany, JSON.stringify(models)); + } + + /** + * Fire Delete Many Action ... + * + * @param model + * @returns + */ + async deleteMany(models: Array) { + // + // Validate ... + let isValid = !isNullOrUndefined(models) && hasChild(models); + if (!isValid) { + return; + } + + // + await this.invoke(XBaseEntityPushAction.DeleteMany, JSON.stringify(models)); + } + //#endregion + + // + registerSubjects(): void { + super.registerSubjects(); + + // + this.onAdded((model: TEntity, actorId: string) => { + this.onAdded$.next({ model, actorId }); + }); + + // + this.onUpdated((model: TEntity, actorId: string) => { + this.onUpdated$.next({ model, actorId }); + }); + + // + this.onDeleted((model: TEntity, actorId: string) => { + this.onDeleted$.next({ model, actorId }); + }); + + // + this.onAddOrUpdated((model: TEntity, actorId: string) => { + this.onAddOrUpdated$.next({ model, actorId }); + }); + + // + this.onManyAdded((model: Array, actorId: string) => { + this.onManyAdded$.next({ model, actorId }); + }); + + // + this.onManyUpdated((model: Array, actorId: string) => { + this.onManyUpdated$.next({ model, actorId }); + }); + + // + this.onManyDeleted((model: Array, actorId: string) => { + this.onManyDeleted$.next({ model, actorId }); + }); + } + + // + //#region Event Oners ... + /** + * Register Add Event Listener ... + * + * @param callback + */ + public onAdded(callback: (model: TEntity, actorId: string) => any) { + // + this.on(XBaseEntityPushAction.Add, (modelJson: string, actorId: string) => { + // + const model = parseJson(modelJson); + callback(model, actorId); + }); + } + + /** + * Register Update Event Listener ... + * + * @param callback + */ + public onUpdated(callback: (model: TEntity, actorId: string) => any) { + // + this.on( + XBaseEntityPushAction.Update, + (modelJson: string, actorId: string) => { + // + const model = parseJson(modelJson); + callback(model, actorId); + } + ); + } + + /** + * Register Delete Event Listener ... + * + * @param callback + */ + public onDeleted(callback: (model: TEntity, actorId: string) => any) { + // + this.on( + XBaseEntityPushAction.Delete, + (modelJson: string, actorId: string) => { + // + const model = parseJson(modelJson); + callback(model, actorId); + } + ); + } + + /** + * Register Add Or Update Event Listener ... + * + * @param callback + */ + public onAddOrUpdated(callback: (model: TEntity, actorId: string) => any) { + // + this.on( + XBaseEntityPushAction.AddOrUpdate, + (modelJson: string, actorId: string) => { + // + const model = parseJson(modelJson); + callback(model, actorId); + } + ); + } + + /** + * Register Many Added Event Listener ... + * + * @param callback + */ + public onManyAdded( + callback: (model: Array, actorId: string) => any + ) { + // + this.on( + XBaseEntityPushAction.AddMany, + (modelJson: string, actorId: string) => { + // + const model = parseJson>(modelJson); + callback(model, actorId); + } + ); + } + + /** + * Register Many Updated Event Listener ... + * + * @param callback + */ + public onManyUpdated( + callback: (model: Array, actorId: string) => any + ) { + // + this.on( + XBaseEntityPushAction.UpdateMany, + (modelJson: string, actorId: string) => { + // + const model = parseJson>(modelJson); + callback(model, actorId); + } + ); + } + + /** + * Register Many Deleted Event Listener ... + * + * @param callback + */ + public onManyDeleted( + callback: (model: Array, actorId: string) => any + ) { + // + this.on( + XBaseEntityPushAction.DeleteMany, + (modelJson: string, actorId: string) => { + // + const model = parseJson>(modelJson); + callback(model, actorId); + } + ); + } + //#endregion + + // + //#region Event Offers ... + /** + * UnRegister Add Event Listener ... + * + * @param callback + */ + public offAdded(callback?: (model: TEntity, actorId: string) => any) { + this.off(XBaseEntityPushAction.Add, callback); + } + + /** + * UnRegister Update Event Listener ... + * + * @param callback + */ + public offUpdated(callback?: (model: TEntity, actorId: string) => any) { + this.off(XBaseEntityPushAction.Update, callback); + } + + /** + * UnRegister Delete Event Listener ... + * + * @param callback + */ + public offDeleted(callback?: (model: TEntity, actorId: string) => any) { + this.off(XBaseEntityPushAction.Delete, callback); + } + + /** + * UnRegister Add Or Update Event Listener ... + * + * @param callback + */ + public offAddOrUpdated(callback?: (model: TEntity, actorId: string) => any) { + this.off(XBaseEntityPushAction.AddOrUpdate, callback); + } + + /** + * UnRegister Many Added Event Listener ... + * + * @param callback + */ + public offManyAdded( + callback?: (model: Array, actorId: string) => any + ) { + this.off(XBaseEntityPushAction.AddMany, callback); + } + + /** + * UnRegister Many Updated Event Listener ... + * + * @param callback + */ + public offManyUpdated( + callback?: (model: Array, actorId: string) => any + ) { + this.off(XBaseEntityPushAction.UpdateMany, callback); + } + + /** + * UnRegister Many Deleted Event Listener ... + * + * @param callback + */ + public offManyDeleted( + callback?: (model: Array, actorId: string) => any + ) { + this.off(XBaseEntityPushAction.DeleteMany, callback); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkPushServiceHolder\projects\x-framework-push-service\src\lib\base\x-base-push.service.ts + +import { + isFunction, + XBaseService, + XExceptionIDs, + throwException, + isNullOrUndefined, + isNullOrEmptyString, +} from 'x-framework-core'; +import { + ITransport, + HubConnection, + MessageHeaders, + HttpTransportType, + HubConnectionState, + HubConnectionBuilder, + IHttpConnectionOptions, +} from '@microsoft/signalr'; +import { Subject } from 'rxjs'; +import { + X_FRAMEWORK_PUSH_SERVICE_CONFIG, + X_FRAMEWORK_PUSH_SERVICE_TOKEN_FETCHER, +} from '../tokens/x-injectable-tokens'; +import { Inject, Injectable, Optional } from '@angular/core'; +import { XBasePushAction } from '../typings/x-push-notification.typings'; +import { MessagePackHubProtocol } from '@microsoft/signalr-protocol-msgpack'; +import { IXPushServiceTokenFetcher } from '../policies/x-push-service-token-fetcher'; +import { XFrameworkPushServiceConfig } from '../config/x-framework-push-service.config'; +import { XPushServiceConnectionRetryPolicy } from '../policies/x-push-service-connection-retry-policy'; + +@Injectable() +export abstract class XBasePushService extends XBaseService { + // + //#region Props ... + // + private CONNECTION_ID: string; + private HUB_CONNECTION: HubConnection; + private HUB_CONNECTION_POLICY: XPushServiceConnectionRetryPolicy; + + // + public readonly connect$ = new Subject(); + public readonly error$ = new Subject(); + public readonly newConnection$ = new Subject(); + public readonly connectionClosed$ = new Subject(); + + /** + * readonly connection id ... + */ + get connectionId() { + // + let result = ''; + + // + if (!isNullOrUndefined(this.HUB_CONNECTION)) { + result = this.HUB_CONNECTION.connectionId; + } + + // + return result; + } + //#endregion + + // + //#region Abstract ... + /** + * retrieve specific hub's name and route ... + */ + abstract hubName(): string; + + /** + * specify the service required access token validator or not ... + */ + abstract requiredAccessToken(): boolean; + + /** + * specify which protocols can used as transport protocol ... + */ + abstract transportType(): HttpTransportType | ITransport; + + /** + * Specified Skip Negotiation ... + */ + public skipNegotiation: boolean = false; + //#endregion + + // + //#region Constructor ... + constructor( + // + @Inject(X_FRAMEWORK_PUSH_SERVICE_CONFIG) + public config: XFrameworkPushServiceConfig, + + // + @Optional() + @Inject(X_FRAMEWORK_PUSH_SERVICE_TOKEN_FETCHER) + public tokenFetcher?: IXPushServiceTokenFetcher + ) { + super(config); + } + //#endregion + + // + //#region Actions ... + /** + * determines connection is stablished ot not ... + * @returns boolean value ... + */ + public isConnected() { + // + const result = + !isNullOrUndefined(this.HUB_CONNECTION) && + this.HUB_CONNECTION.state === HubConnectionState.Connected; + return result; + } + + /** + * retrieve hub route ... + * @returns string represent hub url ... + */ + public getHubRoute() { + // + let result = ''; + + // + // check config exists and + // check server route ... + if ( + isNullOrUndefined(this.config) || + isNullOrEmptyString(this.hubName()) || + isNullOrEmptyString(this.config.hubServerRoute) + ) { + return result; + } + + // + // Configure route ... + result = this.config.hubServerRoute.endsWith('/') + ? `${this.config.hubServerRoute}${ + this.config.hubsBaseRoute + }/${this.hubName()}` + : `${this.config.hubServerRoute}/${ + this.config.hubsBaseRoute + }/${this.hubName()}`; + + // + return result; + } + + /** + * connect to server ... + */ + public async connect() { + // + // Instantiate Hub Connection ... + if (!this.HUB_CONNECTION) { + // + // Prepare Reconnection Policy ... + this.HUB_CONNECTION_POLICY = new XPushServiceConnectionRetryPolicy( + this.config + ); + + // + // retrieve and validate ... + let hubRoute = this.getHubRoute(); + if (isNullOrEmptyString(hubRoute)) { + throwException(XExceptionIDs.InvalidConfiguration); + } + + // + // Normalize Transport Type ... + let transport: HttpTransportType | ITransport = + HttpTransportType.LongPolling; + if (!isNullOrUndefined(this.transportType())) { + transport = this.transportType(); + } + + // + // Add Custom Headers if Required ... + let headers: MessageHeaders = {}; + + // + // Create Connection Options ... + const connectionOptions: IHttpConnectionOptions = { + headers, + transport, + skipNegotiation: this.skipNegotiation, + }; + + // + // Attach Token Fetcher ... + if ( + !!this.requiredAccessToken() && + !isNullOrUndefined(this.tokenFetcher) + ) { + connectionOptions.accessTokenFactory = () => + this.tokenFetcher.getToken(); + } + // + // Create Connection builder ... + const connectionBuilder = new HubConnectionBuilder() + .withUrl(hubRoute, connectionOptions) + .configureLogging(this.config.connectionLogLevel) + .withAutomaticReconnect(this.HUB_CONNECTION_POLICY); + + // + // Handle Message Protocol ... + if (!!this.config.addSupportMessageProtocol) { + connectionBuilder.withHubProtocol(new MessagePackHubProtocol()); + } + + // + // Add LogLevel ... + if (!isNullOrUndefined(this.config.connectionLogLevel)) { + connectionBuilder.configureLogging(this.config.connectionLogLevel); + } + + // + // Create Connection ... + this.HUB_CONNECTION = connectionBuilder.build(); + + // + // Add Default Base Actions Event Handlers ... + this.on(XBasePushAction.NewConnection, (connectionId) => + this.newConnection$.next(connectionId) + ); + this.on(XBasePushAction.ConnectionClosed, (connectionId) => + this.connectionClosed$.next(connectionId) + ); + } + + // + await this.HUB_CONNECTION.start(); + if (!isNullOrEmptyString(this.HUB_CONNECTION.connectionId)) { + // + this.CONNECTION_ID = this.HUB_CONNECTION.connectionId; + this.connect$.next(); + this.registerSubjects(); + } else { + this.CONNECTION_ID = ''; + } + } + + /** + * Disconnect a Connection if Exists ... + */ + public async disconnect() { + // + if ( + this.HUB_CONNECTION && + this.HUB_CONNECTION.state !== HubConnectionState.Disconnected + ) { + // + await this.HUB_CONNECTION.stop(); + this.HUB_CONNECTION = undefined; + } + } + + /** + * Attach an Event Listener to Hub ... + * + * @param event + * @param callback + */ + on(event: string, callback: (...args: any[]) => any) { + // + // Validate ... + if ( + !isFunction(callback) || + isNullOrEmptyString(event) || + isNullOrUndefined(callback) || + isNullOrUndefined(this.HUB_CONNECTION) + ) { + return; + } + + // + this.HUB_CONNECTION.on(event, callback); + } + + /** + * Detach an Event Listener to Hub ... + * + * @param event + * @param callback + */ + off(event: string, callback?: (...args: any[]) => any): void { + // + // Validate ... + if (isNullOrEmptyString(event) || isNullOrUndefined(this.HUB_CONNECTION)) { + return; + } + + // + if (isNullOrUndefined(callback)) { + this.HUB_CONNECTION.off(event); + } else if (!isFunction(callback) || isNullOrUndefined(callback)) { + this.HUB_CONNECTION.off(event, callback); + } + } + + /** + * Invoke Specified Hub Actions ... + * + * @param method + * @param args + * @returns + */ + async invoke(method: string, ...args: any[]) { + // + // Validate Hub Connected ... + if (!this.isConnected() || isNullOrUndefined(this.HUB_CONNECTION)) { + return await Promise.resolve(undefined); + } + + // + return await this.HUB_CONNECTION.invoke(method, ...args); + } + //#endregion + + // + //#region Abstractions ... + /** + * here we have to register all events subjects ... + */ + registerSubjects() {} + //#endregion + + // + //#region Private ... + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkPushServiceHolder\projects\x-framework-push-service\src\lib\base\x-base-web-rtc.service.ts + +import { Subject } from 'rxjs'; +import { + X_FRAMEWORK_PUSH_SERVICE_CONFIG, + X_FRAMEWORK_PUSH_SERVICE_TOKEN_FETCHER, +} from '../tokens/x-injectable-tokens'; +import { isString } from 'x-framework-core'; +import { XBasePushService } from './x-base-push.service'; +import { Inject, Injectable, Optional } from '@angular/core'; +import { XWebRTCAction } from '../typings/x-push-notification.typings'; +import { IXPushServiceTokenFetcher } from '../policies/x-push-service-token-fetcher'; +import { XFrameworkPushServiceConfig } from '../config/x-framework-push-service.config'; + +@Injectable() +export abstract class XBaseWebRTCService extends XBasePushService { + // + //#region Props ... + public onOfferRecieved$ = new Subject(); + public onAnswerRecieved$ = new Subject<{ + answer: any; + requesterId: string; + }>(); + public onCandidateRecieved$ = new Subject<{ + candidate: any; + senderId: string; + }>(); + //#endregion + + // + //#region Constructor ... + constructor( + // + @Inject(X_FRAMEWORK_PUSH_SERVICE_CONFIG) + public config: XFrameworkPushServiceConfig, + + // + @Optional() + @Inject(X_FRAMEWORK_PUSH_SERVICE_TOKEN_FETCHER) + public tokenFetcher?: IXPushServiceTokenFetcher + ) { + super(config, tokenFetcher); + } + //#endregion + + // + //#region Actions ... + public async offer(offer: any, to: string) { + // + if (!isString(offer)) { + offer = JSON.stringify(offer); + } + + // + await this.invoke(XWebRTCAction.Offer, offer, to); + } + + public async answer(answer: any) { + // + if (!isString(answer)) { + answer = JSON.stringify(answer); + } + + // + await this.invoke(XWebRTCAction.Answer, answer); + } + + public async candidate(candidate: any, to: string) { + // + if (!isString(candidate)) { + candidate = JSON.stringify(candidate); + } + + // + await this.invoke(XWebRTCAction.Candidate, candidate, to); + } + //#endregion + + /** + * Register All Available + */ + registerSubjects(): void { + super.registerSubjects(); + + // + this.onOfferRecieved((offer: any) => { + this.onOfferRecieved$.next(offer); + }); + + // + this.onAnswerRecieved((answer: any, requesterId: string) => { + this.onAnswerRecieved$.next({ answer, requesterId }); + }); + + // + this.onCandidateRecieved((candidate: any, senderId: string) => { + this.onCandidateRecieved$.next({ candidate, senderId }); + }); + } + + // + //#region Event Handler Attaches ... + public onOfferRecieved(callback: (offer: any) => any) { + this.on(XWebRTCAction.Offer, callback); + } + + public onAnswerRecieved(callback: (answer: any, requesterId: string) => any) { + this.on(XWebRTCAction.Answer, callback); + } + + public onCandidateRecieved( + callback: (candidate: any, senderId: string) => any + ) { + this.on(XWebRTCAction.Candidate, callback); + } + //#endregion + + // + //#region Event Handler Detachers ... + public offOfferRecieved(callback?: (offer: any) => any) { + this.off(XWebRTCAction.Offer, callback); + } + + public offAnswerRecieved( + callback?: (answer: any, requesterId: string) => any + ) { + this.off(XWebRTCAction.Answer, callback); + } + + public offCandidateRecieved( + callback?: (candidate: any, senderId: string) => any + ) { + this.off(XWebRTCAction.Candidate, callback); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkPushServiceHolder\projects\x-framework-push-service\src\lib\base\x-rtc-peer.service.ts + + +import { Inject, Injectable } from '@angular/core'; +import { X_FRAMEWORK_PUSH_SERVICE_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkPushServiceConfig } from '../config/x-framework-push-service.config'; + +@Injectable({ + providedIn: 'root' +}) +export class XRTCPeerService { + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_PUSH_SERVICE_CONFIG) + public config: XFrameworkPushServiceConfig + ) {} + //#endregion + + // + //#region Actions ... + public createConnection() { + // + const result = new RTCPeerConnection({ + iceServers: [...this.config.iceServers], + }); + + // + return result; + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkPushServiceHolder\projects\x-framework-push-service\src\lib\config\x-framework-push-service.config.ts + +import { LogLevel } from '@microsoft/signalr'; +import { XFrameworkCoreConfig } from 'x-framework-core'; +import { XFrameworkServicesConfig } from 'x-framework-services'; + +export type XSharedConfig = XFrameworkCoreConfig & XFrameworkServicesConfig; + +export interface XFrameworkPushServiceConfig extends XSharedConfig { + // + // Push Service Configuration ... + hubsBaseRoute: string; + hubServerRoute: string; + connectionLogLevel?: LogLevel; + pushConnectionMaxRetry: number; + addSupportMessageProtocol?: boolean; + pushConnectionReconnectDelay: number; + + // + iceServers: Array; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkPushServiceHolder\projects\x-framework-push-service\src\lib\models\x-hub-connection.dto.ts + +import { XBaseDto } from 'x-framework-core'; + +export interface XHubConnectionDto extends XBaseDto { + userId: string; + username?: string; + connectionId: string; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkPushServiceHolder\projects\x-framework-push-service\src\lib\policies\x-push-service-connection-retry-policy.ts + +import { Inject } from '@angular/core'; +import { IRetryPolicy, RetryContext } from '@microsoft/signalr'; +import { XFrameworkPushServiceConfig } from '../config/x-framework-push-service.config'; +import { X_FRAMEWORK_PUSH_SERVICE_CONFIG } from '../tokens/x-injectable-tokens'; + +export class XPushServiceConnectionRetryPolicy implements IRetryPolicy { + // + //#region Props ... + retryReason: Error = null; + previousRetryCount: number = 0; + elapsedMilliseconds: number = 0; + //#endregion + + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_PUSH_SERVICE_CONFIG) + public config: XFrameworkPushServiceConfig + ) {} + //#endregion + + // + nextRetryDelayInMilliseconds(retryContext: RetryContext): number { + // + this.retryReason = retryContext.retryReason; + this.previousRetryCount = retryContext.previousRetryCount; + this.elapsedMilliseconds = retryContext.elapsedMilliseconds; + + // + if (retryContext.previousRetryCount < this.config.pushConnectionMaxRetry) { + return this.config.pushConnectionReconnectDelay; + } else { + return null; + } + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkPushServiceHolder\projects\x-framework-push-service\src\lib\policies\x-push-service-token-fetcher.ts + +import { Observable } from 'rxjs'; + +/** + * an interface for accessing tokens and user related infos ... + */ +export interface IXPushServiceTokenFetcher { + token$: Observable; + getToken(): string; +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkPushServiceHolder\projects\x-framework-push-service\src\lib\tokens\x-injectable-tokens.ts + +import { InjectionToken } from '@angular/core'; +import { IXPushServiceTokenFetcher } from '../policies/x-push-service-token-fetcher'; +import { XFrameworkPushServiceConfig } from '../config/x-framework-push-service.config'; + +export const X_FRAMEWORK_PUSH_SERVICE_CONFIG = new InjectionToken( + 'x_config' +); + +export const X_FRAMEWORK_PUSH_SERVICE_TOKEN_FETCHER = new InjectionToken( + 'x_push_token_fetcher' +); + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkPushServiceHolder\projects\x-framework-push-service\src\lib\typings\x-push-notification.typings.ts + +/** + * Base Hub Service Actions ... + */ +export enum XBasePushAction { + /// + /// new Connection Join ... + /// + NewConnection = 'NewConnection', + /// + /// an Exists Connection Closed ... + /// + ConnectionClosed = 'ConnectionClosed', +} + +/** + * Base Web RTC Actions ... + */ +export enum XWebRTCAction { + Offer = 'Offer', + Answer = 'Answer', + Candidate = 'Candidate', +} + +/** + * Base Entity Push Service Actions ... + */ +export enum XBaseEntityPushAction { + Add = 'Add', + Update = 'Update', + Delete = 'Delete', + AddMany = 'AddMany', + UpdateMany = 'UpdateMany', + DeleteMany = 'DeleteMany', + AddOrUpdate = 'AddOrUpdate', +} diff --git a/Documents/GeneratedContext/xDashboard-xFrameworkServicesHolder-20260809_235622.txt b/Documents/GeneratedContext/xDashboard-xFrameworkServicesHolder-20260809_235622.txt new file mode 100644 index 0000000..7bab28c --- /dev/null +++ b/Documents/GeneratedContext/xDashboard-xFrameworkServicesHolder-20260809_235622.txt @@ -0,0 +1,8256 @@ + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkServicesHolder\crypto-js.d.ts + +// Type definitions for crypto-js v4.0.0 +// Project: https://github.com/brix/crypto-js +// Definitions by: Michael Zabka +// Max Lysenko +// Brendan Early +// Doma +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped + +type WordArray = CryptoJS.lib.WordArray; +type CipherParams = CryptoJS.lib.CipherParams; +type X64Word = CryptoJS.x64.Word; + +/** + * Encoding strategy. + */ +interface Encoder { + /** + * Converts a word array to a hex string. + * + * @param wordArray The word array. + * + * @return The hex string. + * + * @example + * + * var hexString = CryptoJS.enc.Hex.stringify(wordArray); + */ + stringify(wordArray: WordArray): string; + /** + * Converts a hex string to a word array. + * + * @param hexStr The hex string. + * + * @return The word array. + * + * @example + * + * var wordArray = CryptoJS.enc.Hex.parse(hexString); + */ + parse(str: string): WordArray; +} + +/** + * Abstract buffered block algorithm template. + * + * The property blockSize must be implemented in a concrete subtype. + */ +interface BufferedBlockAlgorithm { + /** + * The number of blocks that should be kept unprocessed in the buffer. Default: 0 + */ + _minBufferSize: number; + /** + * Resets this block algorithm's data buffer to its initial state. + * + * @example + * + * bufferedBlockAlgorithm.reset(); + */ + reset(): void; + /** + * Adds new data to this block algorithm's buffer. + * + * @param data The data to append. Strings are converted to a WordArray using UTF-8. + * + * @example + * + * bufferedBlockAlgorithm._append('data'); + * bufferedBlockAlgorithm._append(wordArray); + */ + _append(data: WordArray | string): void; + /** + * Processes available data blocks. + * + * This method invokes _doProcessBlock(offset), which must be implemented by a concrete subtype. + * + * @param doFlush Whether all blocks and partial blocks should be processed. + * + * @return The processed data. + * + * @example + * + * var processedData = bufferedBlockAlgorithm._process(); + * var processedData = bufferedBlockAlgorithm._process(!!'flush'); + */ + _process(doFlush?: boolean): WordArray; + /** + * Creates a copy of this object. + * + * @return The clone. + * + * @example + * + * var clone = bufferedBlockAlgorithm.clone(); + */ + clone(): BufferedBlockAlgorithm; +} + +/** + * Abstract hasher template. + */ +interface Hasher { + /** + * The number of 32-bit words this hasher operates on. Default: 16 (512 bits) + */ + blockSize: number; + /** + * Resets this hasher to its initial state. + * + * @example + * + * hasher.reset(); + */ + reset(): void; + /** + * Updates this hasher with a message. + * + * @param messageUpdate The message to append. + * + * @return This hasher. + * + * @example + * + * hasher.update('message'); + * hasher.update(wordArray); + */ + update(messageUpdate: WordArray | string): this; + /** + * Finalizes the hash computation. + * Note that the finalize operation is effectively a destructive, read-once operation. + * + * @param messageUpdate (Optional) A final message update. + * + * @return The hash. + * + * @example + * + * var hash = hasher.finalize(); + * var hash = hasher.finalize('message'); + * var hash = hasher.finalize(wordArray); + */ + finalize(messaegUpdate?: WordArray | string): WordArray; +} + +interface HasherStatic { + /** + * Initializes a newly created hasher. + * + * @param cfg (Optional) The configuration options to use for this hash computation. + * + * @example + * + * var hasher = CryptoJS.algo.SHA256.create(); + */ + create(cfg?: object): Hasher; +} + +interface HasherHelper { + (message: WordArray | string, cfg?: object): WordArray; +} + +interface HmacHasherHelper { + (message: WordArray | string, key: WordArray | string): WordArray; +} + +/** + * Abstract base cipher template. + */ +interface Cipher { + /** + * This cipher's key size. Default: 4 (128 bits) + */ + keySize: number; + /** + * This cipher's IV size. Default: 4 (128 bits) + */ + ivSize: number; + /** + * A constant representing encryption mode. + */ + readonly _ENC_XFORM_MODE: number; + /** + * A constant representing decryption mode. + */ + readonly _DEV_XFORM_MODE: number; + + /** + * Resets this cipher to its initial state. + * + * @example + * + * cipher.reset(); + */ + reset(): void; + + /** + * Adds data to be encrypted or decrypted. + * + * @param dataUpdate The data to encrypt or decrypt. + * + * @return The data after processing. + * + * @example + * + * var encrypted = cipher.process('data'); + * var encrypted = cipher.process(wordArray); + */ + process(dataUpdate: WordArray | string): WordArray; + + /** + * Finalizes the encryption or decryption process. + * Note that the finalize operation is effectively a destructive, read-once operation. + * + * @param dataUpdate The final data to encrypt or decrypt. + * + * @return The data after final processing. + * + * @example + * + * var encrypted = cipher.finalize(); + * var encrypted = cipher.finalize('data'); + * var encrypted = cipher.finalize(wordArray); + */ + finalize(dataUpdate?: WordArray | string): WordArray; +} + +interface CipherStatic { + /** + * Creates this cipher in encryption mode. + * + * @param key The key. + * @param cfg (Optional) The configuration options to use for this operation. + * + * @return A cipher instance. + * + * @example + * + * var cipher = CryptoJS.algo.AES.createEncryptor(keyWordArray, { iv: ivWordArray }); + */ + createEncryptor(key: WordArray, cfg?: CipherOption): Cipher; + + /** + * Creates this cipher in decryption mode. + * + * @param key The key. + * @param cfg (Optional) The configuration options to use for this operation. + * + * @return A cipher instance. + * + * @example + * + * var cipher = CryptoJS.algo.AES.createDecryptor(keyWordArray, { iv: ivWordArray }); + */ + createDecryptor(key: WordArray, cfg?: CipherOption): Cipher; + + /** + * Initializes a newly created cipher. + * + * @param xformMode Either the encryption or decryption transormation mode constant. + * @param key The key. + * @param cfg (Optional) The configuration options to use for this operation. + * + * @example + * + * var cipher = CryptoJS.algo.AES.create(CryptoJS.algo.AES._ENC_XFORM_MODE, keyWordArray, { iv: ivWordArray }); + */ + create(xformMode: number, key: WordArray, cfg?: CipherOption): Cipher; +} + +interface CipherHelper { + encrypt( + message: WordArray | string, + key: WordArray | string, + cfg?: CipherOption + ): CipherParams; + decrypt( + ciphertext: CipherParams | string, + key: WordArray | string, + cfg?: CipherOption + ): WordArray; +} + +/** + * Configuration options. + */ +interface CipherOption { + /** + * The IV to use for this operation. + */ + iv?: WordArray | undefined; + format?: Format | undefined; + [key: string]: any; +} + +interface Mode { + /** + * Processes the data block at offset. + * + * @param words The data words to operate on. + * @param offset The offset where the block starts. + * + * @example + * + * mode.processBlock(data.words, offset); + */ + processBlock(words: number[], offset: number): void; +} + +interface ModeStatic { + /** + * Initializes a newly created mode. + * + * @param cipher A block cipher instance. + * @param iv The IV words. + * + * @example + * + * var mode = CryptoJS.mode.CBC.Encryptor.create(cipher, iv.words); + */ + create(cipher: Cipher, iv: number[]): Mode; +} + +/** + * Abstract base block cipher mode template. + */ +interface BlockCipherMode { + Encryptor: ModeStatic; + Decryptor: ModeStatic; + /** + * Creates this mode for encryption. + * + * @param cipher A block cipher instance. + * @param iv The IV words. + * + * @example + * + * var mode = CryptoJS.mode.CBC.createEncryptor(cipher, iv.words); + */ + createEncryptor(cipher: Cipher, iv: number[]): Mode; + + /** + * Creates this mode for decryption. + * + * @param cipher A block cipher instance. + * @param iv The IV words. + * + * @example + * + * var mode = CryptoJS.mode.CBC.createDecryptor(cipher, iv.words); + */ + createDecryptor(cipher: Cipher, iv: number[]): Mode; +} + +/** + * Abstract base block cipher mode template. + */ +interface BlockCipherMode { + /** + * Creates this mode for encryption. + * + * @param cipher A block cipher instance. + * @param iv The IV words. + * + * @example + * + * var mode = CryptoJS.mode.CBC.createEncryptor(cipher, iv.words); + */ + createEncryptor(cipher: Cipher): Mode; +} + +/** + * Padding strategy. + */ +interface Padding { + /** + * Pads data using the algorithm defined in PKCS #5/7. + * + * @param data The data to pad. + * @param blockSize The multiple that the data should be padded to. + * + * @example + * + * CryptoJS.pad.Pkcs7.pad(wordArray, 4); + */ + pad(data: WordArray, blockSize: number): void; + + /** + * Unpads data that had been padded using the algorithm defined in PKCS #5/7. + * + * @param data The data to unpad. + * + * @example + * + * CryptoJS.pad.Pkcs7.unpad(wordArray); + */ + unpad(data: WordArray): void; +} + +/** + * Abstract base block cipher template. + */ +interface BlockCipher { + /** + * The number of 32-bit words this cipher operates on. Default: 4 (128 bits) + */ + blockSize: number; +} + +/** + * Configuration options. + */ +interface BlockCipherOption { + /** + * The block mode to use. Default: CBC + */ + mode: Mode; + /** + * The padding strategy to use. Default: Pkcs7 + */ + padding: Padding; +} +/** + * Formatting strategy. + */ +interface Format { + /** + * Converts a cipher params object to an OpenSSL-compatible string. + * + * @param cipherParams The cipher params object. + * + * @return The OpenSSL-compatible string. + * + * @example + * + * var openSSLString = CryptoJS.format.OpenSSL.stringify(cipherParams); + */ + stringify(cipherParams: CipherParams): string; + + /** + * Converts an OpenSSL-compatible string to a cipher params object. + * + * @param openSSLStr The OpenSSL-compatible string. + * + * @return The cipher params object. + * + * @example + * + * var cipherParams = CryptoJS.format.OpenSSL.parse(openSSLString); + */ + parse(str: string): CipherParams; +} + +/** + * An array of 64-bit words. + */ +interface X64WordArray { + /** + * The array of CryptoJS.x64.Word objects. + */ + words: number[]; + /** + * The number of significant bytes in this word array. + */ + sigBytes: number; + + /** + * Converts this 64-bit word array to a 32-bit word array. + * + * @return This word array's data as a 32-bit word array. + * + * @example + * + * var x32WordArray = x64WordArray.toX32(); + */ + toX32(): WordArray; + + /** + * Creates a copy of this word array. + * + * @return The clone. + * + * @example + * + * var clone = x64WordArray.clone(); + */ + clone(): X64WordArray; +} + +/** + * Base object for prototypal inheritance. + */ +interface Base { + /** + * Creates a copy of this object. + * + * @return The clone. + * + * @example + * + * var clone = instance.clone(); + */ + clone(): this; +} + +/** + * Configuration options. + */ +interface KDFOption { + /** + * The key size in words to generate. + */ + keySize?: number | undefined; + /** + * The hasher to use. + */ + hasher?: HasherStatic | undefined; + /** + * The number of iterations to perform. + */ + iterations?: number | undefined; +} + +declare namespace CryptoJS { + /** + * Library namespace. + */ + export namespace lib { + /** + * Base object for prototypal inheritance. + */ + const Base: { + /** + * Creates a new object that inherits from this object. + * + * @param overrides Properties to copy into the new object. + * + * @return The new object. + * + * @example + * + * var MyType = CryptoJS.lib.Base.extend({ + * field: 'value', + * + * method: function () { + * } + * }); + */ + extend(overrides: object): any; + + /** + * Extends this object and runs the init method. + * Arguments to create() will be passed to init(). + * + * @return The new object. + * + * @example + * + * var instance = MyType.create(); + */ + create(...args: any[]): any; + + /** + * Copies properties into this object. + * + * @param properties The properties to mix in. + * + * @example + * + * MyType.mixIn({ + * field: 'value' + * }); + */ + mixIn(properties: object): any; + }; + + /** + * An array of 32-bit words. + */ + interface WordArray { + /** + * The array of 32-bit words. + */ + words: number[]; + /** + * The number of significant bytes in this word array. + */ + sigBytes: number; + /** + * Converts this word array to a string. + * + * @param encoder (Optional) The encoding strategy to use. Default: CryptoJS.enc.Hex + * + * @return The stringified word array. + * + * @example + * + * var string = wordArray + ''; + * var string = wordArray.toString(); + * var string = wordArray.toString(CryptoJS.enc.Utf8); + */ + toString(encoder?: Encoder): string; + + /** + * Concatenates a word array to this word array. + * + * @param wordArray The word array to append. + * + * @return This word array. + * + * @example + * + * wordArray1.concat(wordArray2); + */ + concat(wordArray: WordArray): this; + + /** + * Removes insignificant bits. + * + * @example + * + * wordArray.clamp(); + */ + clamp(): void; + + /** + * Creates a copy of this word array. + * + * @return The clone. + * + * @example + * + * var clone = wordArray.clone(); + */ + clone(): WordArray; + } + const WordArray: { + /** + * Initializes a newly created word array. + * + * @param words (Optional) An array of 32-bit words. + * @param sigBytes (Optional) The number of significant bytes in the words. + * + * @example + * + * var wordArray = CryptoJS.lib.WordArray.create(); + * var wordArray = CryptoJS.lib.WordArray.create([0x00010203, 0x04050607]); + * var wordArray = CryptoJS.lib.WordArray.create([0x00010203, 0x04050607], 6); + */ + create(words?: number[], sigBytes?: number): WordArray; + /** + * Creates a word array filled with random bytes. + * + * @param nBytes The number of random bytes to generate. + * + * @return The random word array. + * + * @example + * + * var wordArray = CryptoJS.lib.WordArray.random(16); + */ + random(nBytes: number): WordArray; + }; + + const BufferedBlockAlgorithm: any; + + const Hasher: { + /** + * Creates a shortcut function to a hasher's object interface. + * + * @param hasher The hasher to create a helper for. + * + * @return The shortcut function. + * + * @example + * + * var SHA256 = CryptoJS.lib.Hasher._createHelper(CryptoJS.algo.SHA256); + */ + _createHelper(hasher: HasherStatic): HasherHelper; + /** + * Creates a shortcut function to the HMAC's object interface. + * + * @param hasher The hasher to use in this HMAC helper. + * + * @return The shortcut function. + * + * @example + * + * var HmacSHA256 = CryptoJS.lib.Hasher._createHmacHelper(CryptoJS.algo.SHA256); + */ + _createHmacHelper(hasher: HasherStatic): HmacHasherHelper; + }; + + const Cipher: { + /** + * Creates shortcut functions to a cipher's object interface. + * + * @param cipher The cipher to create a helper for. + * + * @return An object with encrypt and decrypt shortcut functions. + * + * @example + * + * var AES = CryptoJS.lib.Cipher._createHelper(CryptoJS.algo.AES); + */ + _createHelper(cipher: Cipher): CipherHelper; + }; + + interface CipherOption { + /** + * The IV to use for this operation. + */ + iv?: WordArray | undefined; + format?: Format | undefined; + [key: string]: any; + } + + /** + * A collection of cipher parameters. + */ + interface CipherParams { + /** + * The raw ciphertext. + */ + ciphertext: WordArray; + /** + * The key to this ciphertext. + */ + key: WordArray; + /** + * The IV used in the ciphering operation. + */ + iv: WordArray; + /** + * The salt used with a key derivation function. + */ + salt: WordArray; + /** + * The cipher algorithm. + */ + algorithm: CipherStatic; + /** + * The block mode used in the ciphering operation. + */ + mode: Mode; + /** + * The padding scheme used in the ciphering operation. + */ + padding: Padding; + /** + * The block size of the cipher. + */ + blockSize: number; + /** + * The default formatting strategy to convert this cipher params object to a string. + */ + formatter: Format; + /** + * Converts this cipher params object to a string. + * + * @param formatter (Optional) The formatting strategy to use. + * + * @return The stringified cipher params. + * + * @throws Error If neither the formatter nor the default formatter is set. + * + * @example + * + * var string = cipherParams + ''; + * var string = cipherParams.toString(); + * var string = cipherParams.toString(CryptoJS.format.OpenSSL); + */ + toString(formatter?: Format): string; + } + const CipherParams: { + /** + * Initializes a newly created cipher params object. + * + * @param cipherParams An object with any of the possible cipher parameters. + * + * @example + * + * var cipherParams = CryptoJS.lib.CipherParams.create({ + * ciphertext: ciphertextWordArray, + * key: keyWordArray, + * iv: ivWordArray, + * salt: saltWordArray, + * algorithm: CryptoJS.algo.AES, + * mode: CryptoJS.mode.CBC, + * padding: CryptoJS.pad.PKCS7, + * blockSize: 4, + * formatter: CryptoJS.format.OpenSSL + * }); + */ + create(cipherParams: Partial): CipherParams; + }; + + /** + * Abstract base stream cipher template. + */ + interface StreamCipher extends Cipher { + /** + * The number of 32-bit words this cipher operates on. Default: 1 (32 bits) + */ + blockSize: number; + } + + /** + * Abstract base block cipher mode template. + */ + const BlockCipherMode: any; + + /** + * A cipher wrapper that returns ciphertext as a serializable cipher params object. + */ + const SerializableCipher: { + /** + * Encrypts a message. + * + * @param cipher The cipher algorithm to use. + * @param message The message to encrypt. + * @param key The key. + * @param cfg (Optional) The configuration options to use for this operation. + * + * @return A cipher params object. + * + * @example + * + * var ciphertextParams = CryptoJS.lib.SerializableCipher.encrypt(CryptoJS.algo.AES, message, key); + * var ciphertextParams = CryptoJS.lib.SerializableCipher.encrypt(CryptoJS.algo.AES, message, key, { iv: iv }); + * var ciphertextParams = CryptoJS.lib.SerializableCipher.encrypt(CryptoJS.algo.AES, message, key, { iv: iv, format: CryptoJS.format.OpenSSL }); + */ + encrypt( + cipher: CipherStatic, + message: WordArray | string, + key: WordArray, + cfg?: CipherOption + ): CipherParams; + + /** + * Decrypts serialized ciphertext. + * + * @param cipher The cipher algorithm to use. + * @param ciphertext The ciphertext to decrypt. + * @param key The key. + * @param cfg (Optional) The configuration options to use for this operation. + * + * @return The plaintext. + * + * @example + * + * var plaintext = CryptoJS.lib.SerializableCipher.decrypt(CryptoJS.algo.AES, formattedCiphertext, key, { iv: iv, format: CryptoJS.format.OpenSSL }); + * var plaintext = CryptoJS.lib.SerializableCipher.decrypt(CryptoJS.algo.AES, ciphertextParams, key, { iv: iv, format: CryptoJS.format.OpenSSL }); + */ + decrypt( + cipher: CipherStatic, + ciphertext: WordArray | string, + key: WordArray, + cfg?: CipherOption + ): CipherParams; + + /** + * Converts serialized ciphertext to CipherParams, + * else assumed CipherParams already and returns ciphertext unchanged. + * + * @param ciphertext The ciphertext. + * @param format The formatting strategy to use to parse serialized ciphertext. + * + * @return The unserialized ciphertext. + * + * @example + * + * var ciphertextParams = CryptoJS.lib.SerializableCipher._parse(ciphertextStringOrParams, format); + */ + _parse(ciphertext: CipherParams | string, format: Format): CipherParams; + }; + + /** + * A serializable cipher wrapper that derives the key from a password, + * and returns ciphertext as a serializable cipher params object. + */ + const PasswordBasedCipher: { + /** + * Encrypts a message using a password. + * + * @param cipher The cipher algorithm to use. + * @param message The message to encrypt. + * @param password The password. + * @param cfg (Optional) The configuration options to use for this operation. + * + * @return A cipher params object. + * + * @example + * + * var ciphertextParams = CryptoJS.lib.PasswordBasedCipher.encrypt(CryptoJS.algo.AES, message, 'password'); + * var ciphertextParams = CryptoJS.lib.PasswordBasedCipher.encrypt(CryptoJS.algo.AES, message, 'password', { format: CryptoJS.format.OpenSSL }); + */ + encrypt( + cipher: CipherStatic, + message: WordArray | string, + password: string, + cfg?: CipherOption + ): CipherParams; + + /** + * Decrypts serialized ciphertext using a password. + * + * @param cipher The cipher algorithm to use. + * @param ciphertext The ciphertext to decrypt. + * @param password The password. + * @param cfg (Optional) The configuration options to use for this operation. + * + * @return The plaintext. + * + * @example + * + * var plaintext = CryptoJS.lib.PasswordBasedCipher.decrypt(CryptoJS.algo.AES, formattedCiphertext, 'password', { format: CryptoJS.format.OpenSSL }); + * var plaintext = CryptoJS.lib.PasswordBasedCipher.decrypt(CryptoJS.algo.AES, ciphertextParams, 'password', { format: CryptoJS.format.OpenSSL }); + */ + decrypt( + cipher: CipherStatic, + ciphertext: CipherParams | string, + password: string, + cfg?: CipherOption + ): WordArray; + }; + } + /** + * Padding namespace. + */ + export namespace pad { + /** + * PKCS #5/7 padding strategy. + */ + const Pkcs7: Padding; + + /** + * ANSI X.923 padding strategy. + */ + const AnsiX923: Padding; + + /** + * ISO 10126 padding strategy. + */ + const Iso10126: Padding; + + /** + * ISO/IEC 9797-1 Padding Method 2. + */ + const Iso97971: Padding; + + /** + * Zero padding strategy. + */ + const ZeroPadding: Padding; + + /** + * A noop padding strategy. + */ + const NoPadding: Padding; + } + + /** + * Key derivation function namespace. + */ + export namespace kdf { + /** + * OpenSSL key derivation function. + */ + const OpenSSL: { + /** + * Derives a key and IV from a password. + * + * @param password The password to derive from. + * @param keySize The size in words of the key to generate. + * @param ivSize The size in words of the IV to generate. + * @param salt (Optional) A 64-bit salt to use. If omitted, a salt will be generated randomly. + * + * @return A cipher params object with the key, IV, and salt. + * + * @example + * + * var derivedParams = CryptoJS.kdf.OpenSSL.execute('Password', 256/32, 128/32); + * var derivedParams = CryptoJS.kdf.OpenSSL.execute('Password', 256/32, 128/32, 'saltsalt'); + */ + execute( + password: string, + keySize: number, + ivSize: number, + salt?: WordArray | string + ): CipherParams; + }; + } + + /** + * Mode namespace. + */ + export namespace mode { + /** + * Cipher Block Chaining mode. + */ + const CBC: BlockCipherMode; + + /** + * Cipher Feedback block mode. + */ + const CFB: BlockCipherMode; + /** + * Counter block mode. + */ + const CTR: BlockCipherMode; + /** + * @preserve + * Counter block mode compatible with Dr Brian Gladman fileenc.c + * derived from CryptoJS.mode.CTR + * Jan Hruby jhruby.web@gmail.com + */ + const CTRGladman: BlockCipherMode; + /** + * Output Feedback block mode. + */ + const OFB: BlockCipherMode; + + /** + * Electronic Codebook block mode. + */ + const ECB: BlockCipherMode; + } + + /** + * Format namespace. + */ + export namespace format { + /** + * OpenSSL formatting strategy. + */ + const OpenSSL: Format; + const Hex: Format; + } + + /** + * Encoder namespace. + */ + export namespace enc { + /** + * Hex encoding strategy. + */ + const Hex: Encoder; + /** + * Latin1 encoding strategy. + */ + const Latin1: Encoder; + /** + * UTF-8 encoding strategy. + */ + const Utf8: Encoder; + /** + * UTF-16 BE encoding strategy. + */ + const Utf16: Encoder; + const Utf16BE: Encoder; + + /** + * UTF-16 LE encoding strategy. + */ + const Utf16LE: Encoder; + /** + * Base64 encoding strategy. + */ + const Base64: Encoder; + } + + /** + * Algorithm namespace. + */ + export namespace algo { + /** + * MD5 hash algorithm. + */ + const MD5: HasherStatic; + + /** + * SHA-1 hash algorithm. + */ + const SHA1: HasherStatic; + + /** + * SHA-256 hash algorithm. + */ + const SHA256: HasherStatic; + /** + * SHA-224 hash algorithm. + */ + const SHA224: HasherStatic; + /** + * SHA-512 hash algorithm. + */ + const SHA512: HasherStatic; + + /** + * SHA-384 hash algorithm. + */ + const SHA384: HasherStatic; + /** + * SHA-3 hash algorithm. + */ + const SHA3: HasherStatic; + /** + * RIPEMD160 hash algorithm. + */ + const RIPEMD160: HasherStatic; + /** + * HMAC algorithm. + */ + abstract class HMAC { + /** + * Initializes a newly created HMAC. + * + * @param hasher The hash algorithm to use. + * @param key The secret key. + * + * @example + * + * var hmacHasher = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, key); + */ + static create(hasher: HasherStatic, key: WordArray | string): HMAC; + /** + * Resets this HMAC to its initial state. + * + * @example + * + * hmacHasher.reset(); + */ + reset(): void; + + /** + * Updates this HMAC with a message. + * + * @param messageUpdate The message to append. + * + * @return This HMAC instance. + * + * @example + * + * hmacHasher.update('message'); + * hmacHasher.update(wordArray); + */ + update(messageUpdate: WordArray | string): this; + + /** + * Finalizes the HMAC computation. + * Note that the finalize operation is effectively a destructive, read-once operation. + * + * @param messageUpdate (Optional) A final message update. + * + * @return The HMAC. + * + * @example + * + * var hmac = hmacHasher.finalize(); + * var hmac = hmacHasher.finalize('message'); + * var hmac = hmacHasher.finalize(wordArray); + */ + finalize(messageUpdate?: WordArray | string): WordArray; + } + /** + * Password-Based Key Derivation Function 2 algorithm. + */ + abstract class PBKDF2 { + /** + * Initializes a newly created key derivation function. + * + * @param cfg (Optional) The configuration options to use for the derivation. + * + * @example + * + * var kdf = CryptoJS.algo.PBKDF2.create(); + * var kdf = CryptoJS.algo.PBKDF2.create({ keySize: 8 }); + * var kdf = CryptoJS.algo.PBKDF2.create({ keySize: 8, iterations: 1000 }); + */ + static create(cfg?: KDFOption): PBKDF2; + + /** + * Computes the Password-Based Key Derivation Function 2. + * + * @param password The password. + * @param salt A salt. + * + * @return The derived key. + * + * @example + * + * var key = kdf.compute(password, salt); + */ + compute(password: WordArray | string, salt: WordArray): WordArray; + } + /** + * This key derivation function is meant to conform with EVP_BytesToKey. + * www.openssl.org/docs/crypto/EVP_BytesToKey.html + */ + abstract class EvpKDF { + /** + * Initializes a newly created key derivation function. + * + * @param cfg (Optional) The configuration options to use for the derivation. + * + * @example + * + * var kdf = CryptoJS.algo.EvpKDF.create(); + * var kdf = CryptoJS.algo.EvpKDF.create({ keySize: 8 }); + * var kdf = CryptoJS.algo.EvpKDF.create({ keySize: 8, iterations: 1000 }); + */ + static create(cfg?: { + keySize: number; + hasher?: HasherStatic | undefined; + iterations: number; + }): EvpKDF; + + /** + * Derives a key from a password. + * + * @param password The password. + * @param salt A salt. + * + * @return The derived key. + * + * @example + * + * var key = kdf.compute(password, salt); + */ + compute(password: WordArray | string, salt: WordArray): WordArray; + } + + /** + * AES block cipher algorithm. + */ + const AES: CipherStatic; + + /** + * DES block cipher algorithm. + */ + const DES: CipherStatic; + + /** + * Triple-DES block cipher algorithm. + */ + const TripleDES: CipherStatic; + + /** + * RC4 stream cipher algorithm. + */ + const RC4: CipherStatic; + + /** + * Modified RC4 stream cipher algorithm. + */ + const RC4Drop: CipherStatic; + + /** + * Rabbit stream cipher algorithm + */ + const Rabbit: CipherStatic; + + /** + * Rabbit stream cipher algorithm. + * + * This is a legacy version that neglected to convert the key to little-endian. + * This error doesn't affect the cipher's security, + * but it does affect its compatibility with other implementations. + */ + const RabbitLegacy: CipherStatic; + } + + /** + * x64 namespace. + */ + export namespace x64 { + /** + * A 64-bit word. + */ + interface Word { + /** + * Bitwise NOTs this word. + * + * @return A new x64-Word object after negating. + * + * @example + * + * var negated = x64Word.not(); + */ + not(): X64Word; + /** + * Bitwise ANDs this word with the passed word. + * + * @param word The x64-Word to AND with this word. + * + * @return A new x64-Word object after ANDing. + * + * @example + * + * var anded = x64Word.and(anotherX64Word); + */ + and(word: X64Word): X64Word; + + /** + * Bitwise ORs this word with the passed word. + * + * @param word The x64-Word to OR with this word. + * + * @return A new x64-Word object after ORing. + * + * @example + * + * var ored = x64Word.or(anotherX64Word); + */ + or(word: X64Word): X64Word; + + /** + * Bitwise XORs this word with the passed word. + * + * @param word The x64-Word to XOR with this word. + * + * @return A new x64-Word object after XORing. + * + * @example + * + * var xored = x64Word.xor(anotherX64Word); + */ + xor(word: X64Word): X64Word; + /** + * Shifts this word n bits to the left. + * + * @param n The number of bits to shift. + * + * @return A new x64-Word object after shifting. + * + * @example + * + * var shifted = x64Word.shiftL(25); + */ + shiftL(n: number): X64Word; + /** + * Shifts this word n bits to the right. + * + * @param n The number of bits to shift. + * + * @return A new x64-Word object after shifting. + * + * @example + * + * var shifted = x64Word.shiftR(7); + */ + shiftR(n: number): X64Word; + /** + * Rotates this word n bits to the left. + * + * @param n The number of bits to rotate. + * + * @return A new x64-Word object after rotating. + * + * @example + * + * var rotated = x64Word.rotL(25); + */ + rotL(n: number): X64Word; + + /** + * Rotates this word n bits to the right. + * + * @param n The number of bits to rotate. + * + * @return A new x64-Word object after rotating. + * + * @example + * + * var rotated = x64Word.rotR(7); + */ + rotR(n: number): X64Word; + /** + * Adds this word with the passed word. + * + * @param word The x64-Word to add with this word. + * + * @return A new x64-Word object after adding. + * + * @example + * + * var added = x64Word.add(anotherX64Word); + */ + add(word: X64Word): X64Word; + } + + const Word: { + /** + * Initializes a newly created 64-bit word. + * + * @param high The high 32 bits. + * @param low The low 32 bits. + * + * @example + * + * var x64Word = CryptoJS.x64.Word.create(0x00010203, 0x04050607); + */ + create(high: number, low: number): X64Word; + }; + + /** + * Initializes a newly created word array. + * + * @param words (Optional) An array of CryptoJS.x64.Word objects. + * @param sigBytes (Optional) The number of significant bytes in the words. + * + * @example + * + * var wordArray = CryptoJS.x64.WordArray.create(); + * + * var wordArray = CryptoJS.x64.WordArray.create([ + * CryptoJS.x64.Word.create(0x00010203, 0x04050607), + * CryptoJS.x64.Word.create(0x18191a1b, 0x1c1d1e1f) + * ]); + * + * var wordArray = CryptoJS.x64.WordArray.create([ + * CryptoJS.x64.Word.create(0x00010203, 0x04050607), + * CryptoJS.x64.Word.create(0x18191a1b, 0x1c1d1e1f) + * ], 10); + */ + const WordArray: { + create(words?: X64WordArray[], sigBytes?: number): X64WordArray; + }; + } + + /** + * Shortcut function to the hasher's object interface. + * + * @param message The message to hash. + * + * @return The hash. + * + * @example + * + * var hash = CryptoJS.MD5('message'); + * var hash = CryptoJS.MD5(wordArray); + */ + export const MD5: HasherHelper; + /** + * Shortcut function to the HMAC's object interface. + * + * @param message The message to hash. + * @param key The secret key. + * + * @return The HMA'C. + * + * @example + * + * var hmac = CryptoJS.HmacMD5(message, key); + */ + export const HmacMD5: HmacHasherHelper; + /** + * Shortcut function to the hasher's object interface. + * + * @param message The message to hash. + * + * @return The hash. + * + * @example + * + * var hash = CryptoJS.SHA1('message'); + * var hash = CryptoJS.SHA1(wordArray); + */ + export const SHA1: HasherHelper; + /** + * Shortcut function to the HMAC's object interface. + * + * @param message The message to hash. + * @param key The secret key. + * + * @return The HMAC. + * + * @example + * + * var hmac = CryptoJS.HmacSHA1(message, key); + */ + export const HmacSHA1: HmacHasherHelper; + + /** + * Shortcut function to the hasher's object interface. + * + * @param message The message to hash. + * + * @return The hash. + * + * @example + * + * var hash = CryptoJS.SHA256('message'); + * var hash = CryptoJS.SHA256(wordArray); + */ + export const SHA256: HasherHelper; + /** + * Shortcut function to the HMAC's object interface. + * + * @param message The message to hash. + * @param key The secret key. + * + * @return The HMAC. + * + * @example + * + * var hmac = CryptoJS.HmacSHA256(message, key); + */ + export const HmacSHA256: HmacHasherHelper; + /** + * Shortcut function to the hasher's object interface. + * + * @param message The message to hash. + * + * @return The hash. + * + * @example + * + * var hash = CryptoJS.SHA224('message'); + * var hash = CryptoJS.SHA224(wordArray); + */ + export const SHA224: HasherHelper; + /** + * Shortcut function to the HMAC's object interface. + * + * @param message The message to hash. + * @param key The secret key. + * + * @return The HMAC. + * + * @example + * + * var hmac = CryptoJS.HmacSHA224(message, key); + */ + export const HmacSHA224: HmacHasherHelper; + /** + * Shortcut function to the hasher's object interface. + * + * @param message The message to hash. + * + * @return The hash. + * + * @example + * + * var hash = CryptoJS.SHA512('message'); + * var hash = CryptoJS.SHA512(wordArray); + */ + export const SHA512: HasherHelper; + /** + * Shortcut function to the HMAC's object interface. + * + * @param message The message to hash. + * @param key The secret key. + * + * @return The HMAC. + * + * @example + * + * var hmac = CryptoJS.HmacSHA512(message, key); + */ + export const HmacSHA512: HmacHasherHelper; + /** + * Shortcut function to the hasher's object interface. + * + * @param message The message to hash. + * + * @return The hash. + * + * @example + * + * var hash = CryptoJS.SHA384('message'); + * var hash = CryptoJS.SHA384(wordArray); + */ + export const SHA384: HasherHelper; + /** + * Shortcut function to the HMAC's object interface. + * + * @param message The message to hash. + * @param key The secret key. + * + * @return The HMAC. + * + * @example + * + * var hmac = CryptoJS.HmacSHA384(message, key); + */ + export const HmacSHA384: HmacHasherHelper; + + /** + * Shortcut function to the hasher's object interface. + * + * @param message The message to hash. + * + * @return The hash. + * + * @example + * + * var hash = CryptoJS.SHA3('message'); + * var hash = CryptoJS.SHA3(wordArray); + */ + export const SHA3: HasherHelper; + /** + * Shortcut function to the HMAC's object interface. + * + * @param message The message to hash. + * @param key The secret key. + * + * @return The HMAC. + * + * @example + * + * var hmac = CryptoJS.HmacSHA3(message, key); + */ + export const HmacSHA3: HmacHasherHelper; + + /** + * Shortcut function to the hasher's object interface. + * + * @param message The message to hash. + * + * @return The hash. + * + * @example + * + * var hash = CryptoJS.RIPEMD160('message'); + * var hash = CryptoJS.RIPEMD160(wordArray); + */ + export const RIPEMD160: HasherHelper; + /** + * Shortcut function to the HMAC's object interface. + * + * @param message The message to hash. + * @param key The secret key. + * + * @return The HMAC. + * + * @example + * + * var hmac = CryptoJS.HmacRIPEMD160(message, key); + */ + export const HmacRIPEMD160: HmacHasherHelper; + /** + * Computes the Password-Based Key Derivation Function 2. + * + * @param password The password. + * @param salt A salt. + * @param cfg (Optional) The configuration options to use for this computation. + * + * @return The derived key. + * + * @example + * + * var key = CryptoJS.PBKDF2(password, salt); + * var key = CryptoJS.PBKDF2(password, salt, { keySize: 8 }); + * var key = CryptoJS.PBKDF2(password, salt, { keySize: 8, iterations: 1000 }); + */ + export function PBKDF2( + password: WordArray | string, + salt: WordArray | string, + cfg?: KDFOption + ): WordArray; + + /** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.AES.encrypt(message, key, cfg); + * var plaintext = CryptoJS.AES.decrypt(ciphertext, key, cfg); + */ + export const AES: CipherHelper; + + /** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.DES.encrypt(message, key, cfg); + * var plaintext = CryptoJS.DES.decrypt(ciphertext, key, cfg); + */ + export const DES: CipherHelper; + + /** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.TripleDES.encrypt(message, key, cfg); + * var plaintext = CryptoJS.TripleDES.decrypt(ciphertext, key, cfg); + */ + export const TripleDES: CipherHelper; + + /** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.RC4.encrypt(message, key, cfg); + * var plaintext = CryptoJS.RC4.decrypt(ciphertext, key, cfg); + */ + export const RC4: CipherHelper; + + /** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.RC4Drop.encrypt(message, key, cfg); + * var plaintext = CryptoJS.RC4Drop.decrypt(ciphertext, key, cfg); + */ + export const RC4Drop: CipherHelper; + + /** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.Rabbit.encrypt(message, key, cfg); + * var plaintext = CryptoJS.Rabbit.decrypt(ciphertext, key, cfg); + */ + export const Rabbit: CipherHelper; + + /** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.RabbitLegacy.encrypt(message, key, cfg); + * var plaintext = CryptoJS.RabbitLegacy.decrypt(ciphertext, key, cfg); + */ + export const RabbitLegacy: CipherHelper; + + /** + * Derives a key from a password. + * + * @param password The password. + * @param salt A salt. + * @param cfg (Optional) The configuration options to use for this computation. + * + * @return The derived key. + * + * @example + * + * var key = CryptoJS.EvpKDF(password, salt); + * var key = CryptoJS.EvpKDF(password, salt, { keySize: 8 }); + * var key = CryptoJS.EvpKDF(password, salt, { keySize: 8, iterations: 1000 }); + */ + export function EvpKDF( + password: WordArray | string, + salt: WordArray | string, + cfg?: { + keySize: number; + hasher?: HasherStatic | undefined; + iterations: number; + } + ): WordArray; +} + +export = CryptoJS; + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkServicesHolder\projects\x-framework-services\src\public-api.ts + +/* + * Public API Surface of x-framework-services + */ + +// +//#region Export Services ... +export * from './lib/x-dialog.service'; +export * from './lib/x-manager.service'; +export * from './lib/x-storage.service'; +export * from './lib/x-security.service'; +export * from './lib/x-settings.service'; +export * from './lib/x-identifier.service'; +export * from './lib/x-geolocation.service'; +export * from './lib/x-localization.service'; +export * from './lib/x-notification.service'; +export * from './lib/x-task-manager.service'; +export * from './lib/x-theme-manager.service'; +export * from './lib/x-secure-storage.service'; +export * from './lib/x-http-interceptor.service'; +//#endregion + +// +//#region Export Directives ... +export * from './lib/directives/x-swipable.directive'; +export * from './lib/directives/x-date-presenter.directive'; +export * from './lib/directives/x-currency-formatter.directive'; +export * from './lib/directives/x-from-input-formatter.directive'; +export * from './lib/directives/x-locale-input-formatter.directive'; +//#endregion + +// +//#region Export Pipes ... +export * from './lib/pipes/x-translate.pipe'; +//#endregion + +// +//#region Tokens ... +export * from './lib/tokens/x-injectable-tokens'; +//#endregion + +// +//#region Export Config ... +export * from './lib/config/x-framework-services.config'; +//#endregion + +// +//#region Export Modules ... +export * from './lib/modules/x-directives.module'; +export * from './lib/x-framework-services.module'; +//#endregion + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkServicesHolder\projects\x-framework-services\src\test.ts + +// This file is required by karma.conf.js and loads recursively all the .spec and framework files + +import 'zone.js'; +import 'zone.js/testing'; +import { getTestBed } from '@angular/core/testing'; +import { + BrowserDynamicTestingModule, + platformBrowserDynamicTesting +} from '@angular/platform-browser-dynamic/testing'; + +declare const require: { + context(path: string, deep?: boolean, filter?: RegExp): { + keys(): string[]; + (id: string): T; + }; +}; + +// First, initialize the Angular testing environment. +getTestBed().initTestEnvironment( + BrowserDynamicTestingModule, + platformBrowserDynamicTesting() +); +// Then we find all the tests. +const context = require.context('./', true, /\.spec\.ts$/); +// And load the modules. +context.keys().map(context); + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkServicesHolder\projects\x-framework-services\src\lib\x-dialog.service.ts + +import { + NavController, + AlertController, + ModalController, + PickerController, + PopoverController, + LoadingController, + ActionSheetController, +} from '@ionic/angular'; +import { + toArray, + hasChild, + XActionType, + XModalInput, + XBaseService, + XResourceIDs, + XModalButton, + XPickerColumn, + XAlertOptions, + XModalOptions, + XPickerOptions, + XPopoverOptions, + XLoadingOptions, + XModalButtonRole, + XActionModalOptions, + XPickerColumnOption, + XModalButtonRoleIdentifier, +} from 'x-framework-core'; +import { Observable, of } from 'rxjs'; +import { Injectable, Inject } from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; +import { XLocalizationService } from './x-localization.service'; +import { XNotificationService } from './x-notification.service'; +import { X_FRAMEWORK_SERVICES_CONFIG } from './tokens/x-injectable-tokens'; +import { XFrameworkServicesConfig } from './config/x-framework-services.config'; + +@Injectable({ + providedIn: 'root', +}) +export class XDialogService extends XBaseService { + // + constructor( + @Inject(X_FRAMEWORK_SERVICES_CONFIG) config: XFrameworkServicesConfig, + public matDialogService: MatDialog, + public modalController: ModalController, + public alertController: AlertController, + public pickerController: PickerController, + public navigationController: NavController, + public popoverController: PopoverController, + public loadingController: LoadingController, + private notificationService: XNotificationService, + private localizationService: XLocalizationService, + public actionSheetController: ActionSheetController + ) { + super(config); + } + + // + //#region Service Actions ... + nativeAlert(message?: string | XResourceIDs): Observable { + // + message = !message ? XResourceIDs.are_you_sure : message; + + // + const mMessage = this.localizationService.getValue(message); + const confirmation = window.alert(mMessage); + + // + return of(confirmation); + } + + nativeConfirm(message?: string | XResourceIDs): Observable { + // + message = !message ? XResourceIDs.are_you_sure : message; + + // + const mMessage = this.localizationService.getValue(message); + const confirmation = window.confirm(mMessage); + + // + return of(confirmation); + } + + async presentActionModal(props: XActionModalOptions) { + // + const options = await this.toActionSheetOptions(props); + const actionModal = await this.actionSheetController.create(options); + + // + if (props.playSound) { + this.notificationService.playSound(props.type || XActionType.System); + } + + // + await actionModal.present(); + return actionModal; + } + + async presentModal(props: XModalOptions) { + // + if (props.fullScreen) { + // + const mFullscreen = 'fullscreen'; + if (hasChild(props.cssClass)) { + props.cssClass = [...toArray(props.cssClass), mFullscreen]; + } else { + props.cssClass = [mFullscreen]; + } + } else if (props.autoHeight) { + // + const mAutoHeight = 'autoHeight'; + if (hasChild(props.cssClass)) { + props.cssClass = [...toArray(props.cssClass), mAutoHeight]; + } else { + props.cssClass = [mAutoHeight]; + } + } + + // + const modal = await this.modalController.create(props); + await modal.present(); + return modal; + } + + async presentPopover(props: XPopoverOptions) { + // + const popover = await this.popoverController.create(props); + await popover.present(); + return popover; + } + + async presentLoading(props: XLoadingOptions) { + // + const loading = await this.loadingController.create(props); + await loading.present(); + return loading; + } + + async presentAlert(props: XAlertOptions) { + // + const options = await this.prepareAlertOptions(props); + const alert = await this.alertController.create(options); + + // + await alert.present(); + return alert; + } + + async presentPicker(props: XPickerOptions) { + // + const options = await this.preparePickerOptions(props); + const picker = await this.pickerController.create(options); + + // + await picker.present(); + return picker; + } + //#endregion + + // + //#region Commonly Used dialogs ... + async presentAreYouSureOkCancleDialog( + okHandler: () => void, + okRole?: XModalButtonRoleIdentifier, + cancelHandler?: () => void, + cancelRole?: XModalButtonRoleIdentifier + ) { + await this.presentActionModal({ + header: XResourceIDs.are_you_sure, + buttons: [], + keyboardClose: true, + backdropDismiss: true, + hasOk: true, + okRole, + okHandler, + hasCancel: true, + cancelRole, + cancelHandler, + }); + } + + async presentAreYouSureYesNoDialog( + yesHandler: () => void, + yesRole?: XModalButtonRoleIdentifier, + noHandler?: () => void, + noRole?: XModalButtonRoleIdentifier + ) { + await this.presentActionModal({ + header: XResourceIDs.are_you_sure, + buttons: [], + keyboardClose: true, + backdropDismiss: true, + hasNo: true, + noRole, + noHandler, + hasYes: true, + yesRole, + yesHandler, + }); + } + //#endregion + + // + //#region Private ... + // + //#region Button Preparer ... + getButton( + text?: XResourceIDs | string, + icon?: string, + role?: XModalButtonRoleIdentifier, + handler?: () => boolean | void | Promise + ) { + // + if (!role) { + role = XModalButtonRole.Selected; + } + + // + const mText = this.getPreparedMessage(text); + return { + text: mText, + icon, + role, + handler, + }; + } + + getYesButton( + role?: XModalButtonRoleIdentifier, + handler?: () => boolean | void | Promise + ) { + // + if (!role) { + role = XModalButtonRole.Selected; + } + + // + const mText = this.getPreparedMessage(XResourceIDs.yes); + const mIcon = 'checkmark'; + + return { + text: mText, + icon: mIcon, + role, + handler, + }; + } + + getNoButton( + role?: XModalButtonRoleIdentifier, + handler?: () => boolean | void | Promise + ) { + // + if (!role) { + role = XModalButtonRole.Selected; + } + + // + const mText = this.getPreparedMessage(XResourceIDs.no); + const mIcon = 'close'; + + return { + text: mText, + icon: mIcon, + role, + handler, + }; + } + + getOkButton( + role?: XModalButtonRoleIdentifier, + handler?: () => boolean | void | Promise + ) { + // + if (!role) { + role = XModalButtonRole.Selected; + } + + // + const mText = this.getPreparedMessage(XResourceIDs.ok); + const mIcon = 'checkmark'; + + return { + text: mText, + icon: mIcon, + role, + handler, + }; + } + + getCancelButton( + role?: XModalButtonRoleIdentifier, + handler?: () => boolean | void | Promise + ) { + // + if (!role) { + role = XModalButtonRole.Selected; + } + + // + const mText = this.getPreparedMessage(XResourceIDs.cancel); + const mIcon = 'close'; + + return { + text: mText, + icon: mIcon, + role, + handler, + }; + } + + getAcceptButton( + role?: XModalButtonRoleIdentifier, + handler?: () => boolean | void | Promise + ) { + // + if (!role) { + role = XModalButtonRole.Selected; + } + + // + const mText = this.getPreparedMessage(XResourceIDs.accept); + const mIcon = 'checkmark'; + + return { + text: mText, + icon: mIcon, + role, + handler, + }; + } + + getRetryButton( + role?: XModalButtonRoleIdentifier, + handler?: () => boolean | void | Promise + ) { + // + if (!role) { + role = XModalButtonRole.Selected; + } + + // + const mText = this.getPreparedMessage(XResourceIDs.retry); + const mIcon = 'refresh'; + + return { + text: mText, + icon: mIcon, + role, + handler, + }; + } + + getDismissButton( + role?: XModalButtonRoleIdentifier, + handler?: () => boolean | void | Promise + ) { + // + if (!role) { + role = XModalButtonRole.Destructive; + } + + // + const mText = this.getPreparedMessage(XResourceIDs.dismiss); + const mIcon = 'remove-circle-outline'; + + return { + text: mText, + icon: mIcon, + role, + handler, + }; + } + //#endregion + + private prepareModalButton(button?: XModalButton) { + // + const alertButton = { + ...button, + text: button.text ? this.getPreparedMessage(button.text) : '', + }; + + // + return alertButton; + } + + private async prepareModalButtons(buttons?: XModalButton[]) { + // + if (!hasChild(buttons)) { + return []; + } + + // + return buttons.map((b) => this.prepareModalButton(b)); + } + + private prepareModalInput(input?: XModalInput) { + // + const alertInput = { + ...input, + label: input.label ? this.getPreparedMessage(input.label) : '', + placeholder: input.placeholder + ? this.getPreparedMessage(input.placeholder) + : '', + }; + + // + return alertInput; + } + + private async prepareModalInputs(inputs?: XModalInput[]) { + // + if (!hasChild(inputs)) { + return []; + } + + // + return inputs.map((i) => this.prepareModalInput(i)); + } + + private async prepareAlertOptions(options?: XAlertOptions) { + // + const alertOptions = { + ...options, + header: options.header ? this.getPreparedMessage(options.header) : '', + subHeader: options.subHeader + ? this.getPreparedMessage(options.subHeader) + : '', + message: options.message ? this.getPreparedMessage(options.message) : '', + inputs: hasChild(options.inputs) + ? await this.prepareModalInputs(options.inputs) + : [], + buttons: hasChild(options.buttons) + ? await this.prepareModalButtons(options.buttons) + : [], + }; + + // + return alertOptions; + } + + private preparePickerColumnOption(columnOption?: XPickerColumnOption) { + // + const pickerColumnOption = { + ...columnOption, + text: this.getPreparedMessage(columnOption.text), + }; + + // + return pickerColumnOption; + } + + private async preparePickerColumnOptions( + columnOptions?: XPickerColumnOption[] + ) { + // + if (!hasChild(columnOptions)) { + return []; + } + + // + return columnOptions.map((co) => this.preparePickerColumnOption(co)); + } + + private async preparePickerColumn(column?: XPickerColumn) { + // + const pickerColumn = { + ...column, + options: await this.preparePickerColumnOptions(column.options), + }; + + // + return pickerColumn; + } + + private async preparePickerColumns(columns: XPickerColumn[]) { + // + if (!hasChild(columns)) { + return []; + } + + // + return columns.map((c) => this.preparePickerColumn(c)); + } + + private async preparePickerOptions(options?: XPickerOptions) { + // + const pickerOptions = { + ...options, + buttons: (await this.prepareModalButtons(options.buttons)) || [], + columns: + (await Promise.all(await this.preparePickerColumns(options.columns))) || + [], + }; + + // + return pickerOptions; + } + + private async toActionSheetOptions(options?: XActionModalOptions) { + // + let buttons: any[] = []; + + // + const optButtons = (await this.prepareModalButtons(options.buttons)) || []; + buttons = [...buttons, ...optButtons]; + + // + //#region Handle Preserved Buttons ... + if (options.hasRetry) { + buttons.push( + this.getRetryButton(options.retryRole, options.retryHandler) + ); + } + + // + if (options.hasAccept) { + buttons.push( + this.getAcceptButton(options.acceptRole, options.acceptHandler) + ); + } + + // + if (options.hasOk) { + buttons.push(this.getOkButton(options.okRole, options.okHandler)); + } + + // + if (options.hasCancel) { + buttons.push( + this.getCancelButton(options.cancelRole, options.cancelHandler) + ); + } + + // + if (options.hasYes) { + buttons.push(this.getYesButton(options.yesRole, options.yesHandler)); + } + + // + if (options.hasNo) { + buttons.push(this.getNoButton(options.noRole, options.noHandler)); + } + + // + if (options.hasDismiss) { + buttons.push( + this.getDismissButton(options.dismissRole, options.dismissHandler) + ); + } + //#endregion + + // + const actionModalOptions = { + ...options, + header: options.header ? this.getPreparedMessage(options.header) : '', + subHeader: options.subHeader + ? this.getPreparedMessage(options.subHeader) + : '', + backdropDismiss: options.backdropDismiss || true, + buttons, + }; + + // + return actionModalOptions; + } + + private getPreparedMessage(key: string | number) { + return this.localizationService.isExistsId(key) + ? this.localizationService.getValue(key) + : key + ? key.toString() + : ''; + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkServicesHolder\projects\x-framework-services\src\lib\x-framework-services.module.ts + +import { NgModule } from '@angular/core'; +import { IonicModule } from '@ionic/angular'; +import { CommonModule } from '@angular/common'; +import { RouterModule } from '@angular/router'; +import { XDialogService } from './x-dialog.service'; +import { XHistoryService } from './x-history.service'; +import { XManagerService } from './x-manager.service'; +import { XStorageService } from './x-storage.service'; +import { HttpClientModule } from '@angular/common/http'; +import { XSecurityService } from './x-security.service'; +import { XSettingsService } from './x-settings.service'; +import { XGeolocationService } from './x-geolocation.service'; +import { XTaskManagerService } from './x-task-manager.service'; +import { XLocalizationService } from './x-localization.service'; +import { XNotificationService } from './x-notification.service'; +import { XThemeManagerService } from './x-theme-manager.service'; +import { XDirectivesModule } from './modules/x-directives.module'; +import { XSecureStorageService } from './x-secure-storage.service'; + +@NgModule({ + declarations: [], + imports: [ + IonicModule, + CommonModule, + RouterModule, + HttpClientModule, + XDirectivesModule, + ], + exports: [ + CommonModule, + HttpClientModule, + XDirectivesModule, + ], + providers: [ + // + XDialogService, + XStorageService, + XManagerService, + XHistoryService, + XSettingsService, + XSecurityService, + XGeolocationService, + XTaskManagerService, + XLocalizationService, + XNotificationService, + XThemeManagerService, + XSecureStorageService, + ] +}) +export class XFrameworkServicesModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkServicesHolder\projects\x-framework-services\src\lib\x-geolocation.service.ts + +import { + getError, + Exceptions, + XException, + XBaseService, +} from 'x-framework-core'; +import { Observable } from 'rxjs'; +import { Injectable, Output, EventEmitter, Inject } from '@angular/core'; +import { X_FRAMEWORK_SERVICES_CONFIG } from './tokens/x-injectable-tokens'; +import { XFrameworkServicesConfig } from './config/x-framework-services.config'; + +@Injectable({ + providedIn: 'root', +}) +export class XGeolocationService extends XBaseService { + // + private IS_SUPPORT = false; + private NAVIGATION_WATCHER: any; + + // + options: PositionOptions; + + // + @Output() + changeLoading: EventEmitter = new EventEmitter(); + + // + isLoading = false; + + // + constructor( + @Inject(X_FRAMEWORK_SERVICES_CONFIG) config: XFrameworkServicesConfig + ) { + super(config); + + // + // Check Service is Supported or not ... + if (navigator.geolocation) { + this.IS_SUPPORT = true; + } + + // + this.options = { + ...config.geoLocationConfig, + }; + } + + /** + * determines service is Supported or not + */ + get isSupport() { + return this.IS_SUPPORT; + } + + /** + * get given position link for open on Google Maps ... + */ + getGoogleMapLink(position: GeolocationPosition) { + return `https://maps.google.com/?q=${position.coords.latitude},${position.coords.longitude}`; + } + + /** + * get given position Lat,Long string ... + */ + getLatLong(position: GeolocationPosition) { + return `${position.coords.latitude},${position.coords.longitude}`; + } + + /** + * get current location ... + */ + getCurrentLocation(): Observable { + // + this.startLoading(); + + return new Observable((observer) => { + // + if (!this.IS_SUPPORT) { + // + this.stopLoading(); + observer.error(getError(Exceptions.UnsupportedBrowser)); + } + + // + navigator.geolocation.getCurrentPosition( + (position) => { + // + this.stopLoading(); + + // + observer.next(position); + observer.complete(); + }, + (error) => { + // + this.stopLoading(); + + // + let exception: XException; + switch (error.code) { + case 1: + exception = Exceptions.PermissionDenied; + break; + + case 2: + exception = Exceptions.PositionUnavailable; + break; + + case 3: + default: + exception = Exceptions.Timeout; + break; + } + + // + observer.error(getError(exception)); + }, + { + ...this.options, + } + ); + }); + } + + /** + * start Watching Location Changes ... + */ + watchLocation(): Observable { + // + this.startLoading(); + + // + return new Observable((observer) => { + this.NAVIGATION_WATCHER = navigator.geolocation.watchPosition( + (position) => { + // + if (!this.IS_SUPPORT) { + // + this.stopLoading(); + observer.error(getError(Exceptions.UnsupportedBrowser)); + observer.complete(); + } + + // + observer.next(position); + }, + (error) => { + // + this.stopLoading(); + let exception: XException; + switch (error.code) { + case 1: + exception = Exceptions.PermissionDenied; + break; + + case 2: + exception = Exceptions.PositionUnavailable; + break; + + case 3: + default: + exception = Exceptions.Timeout; + break; + } + + // + observer.error(getError(exception)); + } + ); + }); + } + + /** + * Stop Watching Location Changes ... + */ + stopWatchingLocation() { + // + this.stopLoading(); + navigator.geolocation.clearWatch(this.NAVIGATION_WATCHER); + } + + private startLoading() { + // + this.isLoading = true; + this.changeLoading.emit(true); + } + + private stopLoading() { + // + this.isLoading = false; + this.changeLoading.emit(false); + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkServicesHolder\projects\x-framework-services\src\lib\x-history.service.ts + +import { Subject } from 'rxjs'; +import { Injectable } from '@angular/core'; +import { Location, PlatformLocation } from '@angular/common'; + +@Injectable({ + providedIn: 'root', +}) +export class XHistoryService { + // + //#region Props ... + // + public readonly backHandler = new Subject(); + + // + //#region Getters ... + // + get href() { + return this.platformLocation.href; + } + + get protocol() { + return this.platformLocation.protocol; + } + + get hostname() { + return this.platformLocation.hostname; + } + + get port() { + return this.platformLocation.port; + } + + get pathname() { + return this.platformLocation.pathname; + } + + get search() { + return this.platformLocation.search; + } + + get hash() { + return this.platformLocation.hash; + } + //#endregion + //#endregion + + // + //#region Constructor ... + constructor( + public location: Location, + public platformLocation: PlatformLocation + ) { + // + // Prevent from Back Button ... + this.platformLocation.onPopState(() => { + this.backHandler.next(); + }); + } + //#endregion + + // + //#region Actions ... + pushEmptyState() { + this.pushState(null, null, this.platformLocation.href); + } + + replaceEmptyState() { + this.replaceState(null, null, this.platformLocation.href); + } + + getBaseHrefFromDOM() { + return this.platformLocation.getBaseHrefFromDOM(); + } + + getState() { + return this.platformLocation.getState(); + } + + replaceState(state: any, title: string, url: string) { + this.platformLocation.replaceState(state, title, url); + } + + pushState(state: any, title: string, url: string) { + this.platformLocation.pushState(state, title, url); + } + + forward() { + this.location.forward(); + } + + back() { + this.location.back(); + } + //#endregion + + // + //#region Private ... + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkServicesHolder\projects\x-framework-services\src\lib\x-http-interceptor.service.ts + +import { + HttpEvent, + HttpRequest, + HttpHandler, + HttpInterceptor, + HttpErrorResponse, +} from '@angular/common/http'; +import { Observable, throwError } from 'rxjs'; +import { Injectable, Inject } from '@angular/core'; +import { finalize, catchError } from 'rxjs/operators'; +import { XManagerService } from './x-manager.service'; +import { XBaseService, getErrorException } from 'x-framework-core'; +import { X_FRAMEWORK_SERVICES_CONFIG } from './tokens/x-injectable-tokens'; +import { XFrameworkServicesConfig } from './config/x-framework-services.config'; + +@Injectable({ + providedIn: 'root', +}) +export class XHttpInterceptorService + extends XBaseService + implements HttpInterceptor { + // + constructor( + @Inject(X_FRAMEWORK_SERVICES_CONFIG) config: XFrameworkServicesConfig, + private managerService: XManagerService + ) { + super(config); + } + + intercept( + req: HttpRequest, + next: HttpHandler + ): Observable> { + return this.handleInterceptRequest(req, next); + } + + handleInterceptRequest( + req: HttpRequest, + next: HttpHandler + ): Observable> { + // + // Apply Error Handler ... + const handledErrors = this.applyErrorHandler(next.handle(req)); + + // + // Apply Finally Handler ... + const handledFinally = this.applyFinally(handledErrors); + + // + // Final Level ... + const result = handledFinally; + + // + return result; + } + + // + //#region Actions / Handlers ... + applyFinally(current: Observable>) { + // + return current.pipe( + finalize(() => { + // + // Collection of instructions after finish or error ... + this.managerService.loading = false; + }) + ); + } + + applyErrorHandler(current: Observable>) { + return current.pipe( + catchError((error: HttpErrorResponse) => { + // + this.managerService.loading = true; + const mException = getErrorException(error.error); + + // + this.managerService.error = mException; + + // + return throwError(error); + }) + ); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkServicesHolder\projects\x-framework-services\src\lib\x-identifier.service.ts + +import { Injectable } from '@angular/core'; +import { XBaseService } from 'x-framework-core'; + +@Injectable({ + providedIn: 'root', +}) +export class XIdentifierService extends XBaseService { + // + //#region Props ... + private readonly idStartKey = '_['; + private readonly idEndKey = ']'; + //#endregion + + /** + * check if task name contains id ... + * + * @param key the task name ... + */ + hasIdentifier(key: string | any, forceAppName = false) { + // + if (!key) { + return false; + } + + // + const mKey = key.toString() as string; + const result = + mKey.includes(this.idStartKey) && mKey.includes(this.idEndKey); + + // + return forceAppName + ? result && mKey.includes(this.config.appInstanceName) + : result; + } + + /** + * retrieve and extract the raw task name from a task name ... + * + * @param key the task name ... + */ + getName(key: string | any) { + // + if (!key) { + return ''; + } + + // + const mKey = key.toString() as string; + + // + if (!this.hasIdentifier(mKey)) { + return mKey; + } + + // + const startIndex = mKey.indexOf(this.idStartKey); + let result = mKey.substr(0, startIndex); + result = result.replace(this.idStartKey, ''); + + // + return result; + } + + /** + * retrieve and extract the id inside a task name ... + * + * @param key the task name ... + */ + getIdentifier(key: string | any, cleanAppName = true) { + // + // Validate Args ... + if (!key || !this.hasIdentifier(key)) { + return ''; + } + + // + // Task Id ... + let result = key.toString() as string; + + // + // Validating Info ... + let index = result.indexOf(this.idStartKey); + + // + result = result.substr(index, result.length); + result = result.replace(this.idStartKey, ''); + + // + index = result.indexOf(this.idEndKey); + result = result.substring(0, index); + + // + if (cleanAppName) { + result = result.replace(this.config.appInstanceName, ''); + } + + // + // Return Result ... + return result; + } + + /** + * prepare a Task With ID and return ... + * + * @param key the Task Name ... + * @param id th ID ... + */ + setIdentifier(key: string | any, id: string | any, containsAppName = false) { + return `${key.toString()}${this.idStartKey}${ + containsAppName ? this.config.appInstanceName : '' + }${id.toString()}${this.idEndKey}`; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkServicesHolder\projects\x-framework-services\src\lib\x-localization.service.ts + +import { + XParam, + getVal, + XLocale, + isNumber, + formatDate, + XException, + Exceptions, + fromUtcDate, + XBaseService, + XResourceIDs, + GetDateProps, + XExceptionIDs, + throwException, + isValidException, + XTranslationItem, + isNullOrUndefined, + getErrorException, + XLanguageDirection, + XResourceIdentifier, + XLanguageDescriptor, + getErrorDateFormatString, + getDateOfBirthDateFormatString, +} from 'x-framework-core'; +import { Injectable, Inject } from '@angular/core'; +import { X_FRAMEWORK_SERVICES_CONFIG } from './tokens/x-injectable-tokens'; +import { XFrameworkServicesConfig } from './config/x-framework-services.config'; + +@Injectable({ + providedIn: 'root', +}) +export class XLocalizationService extends XBaseService { + // + private LANGUAGEDESCRIPTOR: XLanguageDescriptor; + private AVAILABLELANGUAGES: XLanguageDescriptor[]; + + // + constructor( + @Inject(X_FRAMEWORK_SERVICES_CONFIG) config: XFrameworkServicesConfig + ) { + super(config); + + // + // Inject Loacle and set Application Locale and Direction based on it ... + this.AVAILABLELANGUAGES = config.availableLanguages; + this.LANGUAGEDESCRIPTOR = config.availableLanguages.find( + (al) => al.locale === config.defaultLanguage + ); + window[XParam.Language] = this.LANGUAGEDESCRIPTOR.locale; + } + + // + //#region Properties ... + /** + * Current System Locale ... + */ + get locale() { + return this.languageDescriptor.locale; + } + + /** + * Current Locale UI Direction ... + */ + get direction(): XLanguageDirection { + return this.languageDescriptor.direction; + } + + /** + * current available language + */ + get language() { + return this.languageDescriptor.language; + } + + /** + * get Current Locale Name ... + */ + get name() { + return this.languageDescriptor.name; + } + + /** + * get Current Locale Language Descriptor Object ... + */ + get languageDescriptor() { + return this.LANGUAGEDESCRIPTOR; + } + + /** + * get Available Languages ... + */ + get availableLanguages() { + return this.AVAILABLELANGUAGES; + } + //#endregion + + /** + * change current language ... + * + * @param locale name of available language + */ + changeLocale(locale: XLocale) { + // + const languageDescriptor = this.languageDescriptorOfLocale(locale); + if (!languageDescriptor) { + throwException(XExceptionIDs.InvalidConfiguration); + } + + // + window[XParam.Language] = locale; + this.LANGUAGEDESCRIPTOR = languageDescriptor; + } + + /** + * Find specific Language Descriptor ... + */ + languageDescriptorOfLocale(locale: XLocale) { + // + return this.availableLanguages.find((al) => al.locale === locale); + } + + /** + * check an id exists in resources or not + */ + isExistsId(id: XResourceIdentifier) { + // + const key = getVal(XResourceIDs, id) || id; + + // + return ( + this.languageDescriptor.resources.findIndex((tr) => tr.id === key) > -1 + ); + } + + /** + * check an id exists in resources of specific locale or not + */ + isExistsIdOfLocale(locale: XLocale, id: XResourceIdentifier) { + // + const key = getVal(XResourceIDs, id) || id; + + // + return ( + this.languageDescriptorOfLocale(locale).resources.findIndex( + (tr) => tr.id === key + ) > -1 + ); + } + + /** + * get translation item of current language + */ + getTranslationItem(id: XResourceIdentifier): XTranslationItem { + // + const key = getVal(XResourceIDs, id) || id; + + // + return this.languageDescriptor.resources.find((tr) => tr.id === key); + } + + /** + * get translation item of specific language + */ + getTranslationItemOfLocale(locale: XLocale, id: XResourceIdentifier) { + // + const key = getVal(XResourceIDs, id) || id; + + // + const selectedLocale = this.languageDescriptorOfLocale(locale); + + // + if (!selectedLocale || !this.isExistsIdOfLocale(locale, key)) { + throwException(XExceptionIDs.NotFound); + } + + // + return selectedLocale.resources.find((tr) => tr.id === key); + } + + /** + * get Value of specific id in Current Locale + */ + getValue(id: XResourceIdentifier) { + // + if (!this.isExistsId(id)) { + return null; + } + + // + return this.getTranslationItem(id).value; + } + + /** + * get Value of specific id in specific Locale + */ + getValueOfLocale(locale: XLocale, id: XResourceIdentifier) { + // + if (!this.isExistsIdOfLocale(locale, id)) { + return null; + } + + // + return this.getTranslationItemOfLocale(locale, id).value; + } + + /** + * get coresponding translated value of errors + */ + getErrorMessage(err: any, locale?: XLocale) { + // + if (!locale) { + locale = this.locale; + } + + // + let exception: XException; + + // + if (isValidException(err)) { + exception = err as XException; + } else { + exception = getErrorException(err); + } + + // + if (exception.id && isNumber(exception.id)) { + // + const exceptionID = `ex_${exception.id}`; + return this.getValueOfLocale(locale, exceptionID); + } + + // + return this.getValueOfLocale(locale, 'general_error'); + } + + /** + * get and format date + */ + getDate(props?: GetDateProps): string { + // + if (!props) { + props = {}; + } + + // + if (!props.locale) { + props.locale = this.locale; + } + // + if (!props.date) { + props.date = new Date(); + } + // + if (!props.dateFormat) { + props.dateFormat = getDateOfBirthDateFormatString(props.locale); + } + // + if (!props.toUtc) { + props.toUtc = false; + } + // + return formatDate(props.date, props.locale, props.dateFormat, props.toUtc); + } + + /** + * combine some resources and represent single joined string ... + * + * @param locale translated dest locale ... + * @param joinString the string which used to join translated parts ... + * @param resources an array of translation resources ... + */ + combine( + locale?: XLocale, + joinString: string = ' ', + ...resources: XResourceIdentifier[] + ) { + // + if (isNullOrUndefined(locale)) { + locale = this.locale; + } + + // + const result = resources + .map((r) => this.getValueOfLocale(locale, r)) + .join(joinString); + + // + return result; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkServicesHolder\projects\x-framework-services\src\lib\x-manager.service.ts + +import { Location } from '@angular/common'; +import { Platform } from '@angular/cdk/platform'; +import { HttpClient } from '@angular/common/http'; +import { Inject, Injectable } from '@angular/core'; +import { XDialogService } from './x-dialog.service'; +import { XStorageService } from './x-storage.service'; +import { XHistoryService } from './x-history.service'; +import { XSecurityService } from './x-security.service'; +import { XSettingsService } from './x-settings.service'; +import { Router, ActivatedRoute } from '@angular/router'; +import { XIdentifierService } from './x-identifier.service'; +import { XGeolocationService } from './x-geolocation.service'; +import { XTaskManagerService } from './x-task-manager.service'; +import { MenuController, NavController } from '@ionic/angular'; +import { XNotificationService } from './x-notification.service'; +import { XLocalizationService } from './x-localization.service'; +import { XThemeManagerService } from './x-theme-manager.service'; +import { XSecureStorageService } from './x-secure-storage.service'; +import { X_FRAMEWORK_SERVICES_CONFIG } from './tokens/x-injectable-tokens'; +import { XFrameworkServicesConfig } from './config/x-framework-services.config'; +import { XManagerFinalService } from './x-manager-service-partial/x-manager-final.service'; + +@Injectable({ + providedIn: 'root', +}) +export class XManagerService extends XManagerFinalService { + constructor( + @Inject(X_FRAMEWORK_SERVICES_CONFIG) + public config: XFrameworkServicesConfig, + public router: Router, + public platform: Platform, + public location: Location, + public httpClient: HttpClient, + public dialogService: XDialogService, + public menuController: MenuController, + public activatedRoute: ActivatedRoute, + public historyService: XHistoryService, + public storageService: XStorageService, + public securityService: XSecurityService, + public settingsService: XSettingsService, + public navigationController: NavController, + public identifierService: XIdentifierService, + public geoLocationService: XGeolocationService, + public taskManagerService: XTaskManagerService, + public themeManagerService: XThemeManagerService, + public notificationService: XNotificationService, + public localizationService: XLocalizationService, + public secureStorageService: XSecureStorageService + ) { + super( + config, + router, + platform, + location, + httpClient, + dialogService, + menuController, + activatedRoute, + historyService, + storageService, + securityService, + settingsService, + navigationController, + geoLocationService, + taskManagerService, + themeManagerService, + notificationService, + localizationService, + secureStorageService + ); + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkServicesHolder\projects\x-framework-services\src\lib\x-notification.service.ts + +import { + XColor, + isNumber, + XActionType, + XValidators, + XResourceIDs, + XBaseService, + XNotificationParams, + XTypedNotificationParams, + XErrorNotificationParams, +} from 'x-framework-core'; +import { Howl } from 'howler'; +import { ToastOptions } from '@ionic/core'; +import { ToastController } from '@ionic/angular'; +import { Injectable, Inject } from '@angular/core'; +import { XLocalizationService } from './x-localization.service'; +import { X_FRAMEWORK_SERVICES_CONFIG } from './tokens/x-injectable-tokens'; +import { XFrameworkServicesConfig } from './config/x-framework-services.config'; + +@Injectable({ + providedIn: 'root', +}) +export class XNotificationService extends XBaseService { + // + private NOTIFICATION_IDS: string[] = []; + + // + private userNotifiCationSound: Howl; + private captureNotifiCationSound: Howl; + private systemNotifiCationSound: Howl; + + // + public notificationDelay: number; + + constructor( + @Inject(X_FRAMEWORK_SERVICES_CONFIG) config: XFrameworkServicesConfig, + private localizationService: XLocalizationService, + private toastController: ToastController + ) { + // + super(config); + + // + const userActionConfig = config.notificationAudioSources.find( + (as) => as.type === XActionType.User + ); + const systemActionConfig = config.notificationAudioSources.find( + (as) => as.type === XActionType.System + ); + const captureActionConfig = config.notificationAudioSources.find( + (as) => as.type === XActionType.Capture + ); + + // + this.notificationDelay = config.defaultNotificationDelay; + + // + this.userNotifiCationSound = new Howl({ + src: [userActionConfig.resourcePath], + }); + this.captureNotifiCationSound = new Howl({ + src: [captureActionConfig.resourcePath], + }); + this.systemNotifiCationSound = new Howl({ + src: [systemActionConfig.resourcePath], + }); + } + + /** + * play sound based on type + */ + playSound(type: XActionType = XActionType.System) { + // + switch (type) { + // + case XActionType.Capture: + this.playCaptureNotification(); + break; + + // + case XActionType.User: + this.playUserNotification(); + break; + + // + default: + case XActionType.System: + this.playSystemNotification(); + break; + } + } + + /** + * play system sound + */ + playSystemNotification() { + // + if (this.systemNotifiCationSound.playing()) { + return; + } + + // + this.systemNotifiCationSound.play(); + } + + /** + * play user sound + */ + playUserNotification() { + // + if (this.userNotifiCationSound.playing()) { + return; + } + + // + this.userNotifiCationSound.play(); + } + + /** + * play Capture sound + */ + playCaptureNotification() { + // + if (this.captureNotifiCationSound.playing()) { + return; + } + + // + this.captureNotifiCationSound.play(); + } + + // + //#region Toas Related ... + /** + * create a notification object ... + * + * @param opt notification options + */ + createNotification(props?: XNotificationParams) { + const toastOpt = this.prepareToastOptions(props); + return this.toastController.create(toastOpt); + } + + /** + * create and present notification ... + * + * @param opt notification options + */ + async presentNotification(props?: XNotificationParams) { + // + const mProps = props ? props : this.getDefaultNotificationOptions(); + + // + const notification = await this.createNotification(mProps); + this.NOTIFICATION_IDS.push(notification.id); + + // + notification.onDidDismiss().then(() => { + if (mProps.onDidDismiss) { + mProps.onDidDismiss(); + this.NOTIFICATION_IDS = this.NOTIFICATION_IDS.filter( + (id) => id === notification.id + ); + } + }); + + // + notification.onWillDismiss().then(() => { + if (mProps.onWillDismiss) { + mProps.onWillDismiss(); + } + }); + + // + notification.present(); + + // + if (props && props.playSound) { + this.playSound(props.type); + } + } + + async presentXNotification(props: XTypedNotificationParams) { + // + // Validate Args ... + XValidators.validateNotNull(props); + + // + const isResourceID = isNumber(props.message); + const mMessage = isResourceID + ? this.localizationService.getValue(props.message as XResourceIDs) + : (props.message as string); + const defOpts = this.getDefaultNotificationOptions(); + const mProps = { + ...defOpts, + ...props, + opt: { + ...defOpts.opt, + ...props.opt, + message: mMessage, + }, + }; + + // + await this.presentNotification(mProps); + } + + /** + * present Info notification + * + * @param props Notification Properties + */ + async presentInfoNotification( + props: XTypedNotificationParams + ): Promise { + await this.presentXNotification({ + ...props, + opt: { + ...props.opt, + color: XColor.Tertiary, + }, + }); + } + + /** + * present Success Notification + * + * @param props Notification Properties + */ + async presentSuccessNotification( + props: XTypedNotificationParams + ): Promise { + await this.presentXNotification({ + ...props, + opt: { + ...props.opt, + color: XColor.Success, + }, + }); + } + + /** + * present Default Success Notification + */ + async presentDefaultSuccessNotification( + dissmissHandler?: () => void + ): Promise { + // + const mProps: XTypedNotificationParams = { + message: XResourceIDs.success_msg, + dissmissable: true, + onWillDismiss: dissmissHandler, + }; + + await this.presentSuccessNotification(mProps); + } + + /** + * present warning notifications + * + * @param props Notification Properties + */ + async presentWarningNotification( + props: XTypedNotificationParams + ): Promise { + await this.presentXNotification({ + ...props, + opt: { + ...props.opt, + color: XColor.Warning, + }, + }); + } + + /** + * present error notification + * + * @param props Notification Properties + */ + async presentDangerNotification( + props: XTypedNotificationParams + ): Promise { + await this.presentXNotification({ + ...props, + opt: { + ...props.opt, + color: XColor.Danger, + }, + }); + } + + /** + * present an Exception or Error Message ... + * + * @param props Notification Properties + */ + async presentErrorNotification( + props: XErrorNotificationParams + ): Promise { + // + const mMessage = this.localizationService.getErrorMessage(props.exception); + await this.presentXNotification({ + ...props, + message: mMessage, + dissmissable: props.dissmissable, + opt: { + ...props.opt, + color: XColor.Danger, + }, + }); + } + + async closeAllNotifications() { + this.NOTIFICATION_IDS.forEach(async (id) => { + try { + await this.toastController.dismiss(null, null, id); + } catch {} + }); + } + + /** + * combines default notification options by + * given notification options ... + * + * @param opt notification options + */ + private prepareToastOptions(props?: XNotificationParams): ToastOptions { + // + if (!props) { + props = this.getDefaultNotificationOptions(); + } + + // + // Handle Dissmissable + const mDuration = props.dissmissable ? props.opt.duration : 0; + + // + const result = { + ...props.opt, + duration: mDuration, + }; + + // + return result; + } + + /** + * generate default notification options ... + */ + private getDefaultNotificationOptions(): XNotificationParams { + return { + playSound: false, + type: XActionType.System, + dissmissable: true, + opt: { + color: XColor.Tertiary, + position: 'bottom', + duration: this.notificationDelay, + // , + // showCloseButton: true, + // closeButtonText: this.localizationService.getValue(XResourceIDs.dismiss) + }, + }; + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkServicesHolder\projects\x-framework-services\src\lib\x-secure-storage.service.ts + +import { + fromJson, + XStorageItem, + XExceptionIDs, + throwException, + isNullOrUndefined +} from 'x-framework-core'; +import { Injectable, Inject } from '@angular/core'; +import { XStorageService } from './x-storage.service'; +import { XSecurityService } from './x-security.service'; +import { X_FRAMEWORK_SERVICES_CONFIG } from './tokens/x-injectable-tokens'; +import { XFrameworkServicesConfig } from './config/x-framework-services.config'; + +@Injectable({ + providedIn: 'root' +}) +export class XSecureStorageService extends XStorageService { + // + constructor( + @Inject(X_FRAMEWORK_SERVICES_CONFIG) config: XFrameworkServicesConfig, + private securitySerive: XSecurityService + ) { + super(config); + } + + /** + * writing a new item in Storage ... + * + * @param key the item key + * @param data the data object which needs to save + * @param forceRewrite overwrite item if exists, the default is false, + * means if key exists throws an error ... + */ + write(key: string, data: any, forceRewrite: boolean = false) { + // + if (!forceRewrite && this.isExists(key)) { + throwException(XExceptionIDs.Duplicate); + } + + // + const theKey = this.getPreparedKey(key); + const encryptedValue = this.securitySerive.encryptWithKey( + JSON.stringify(data) + ); + + // + localStorage.setItem(theKey, encryptedValue); + } + + /** + * read an item from Storage ... + * @param key the item key + * @param forceExists determine the given key must be exists or not, true is default, + * means if key not exists throws an error ... + */ + read(key: string, forceExists: boolean = true): any | null { + // + if (forceExists && !this.isExists(key)) { + throwException(XExceptionIDs.NotFound); + } + + // + const theKey = this.getPreparedKey(key); + const item = localStorage.getItem(theKey); + if (!isNullOrUndefined(item)) { + try { + const decryptedValue = this.securitySerive.decryptWithKey(item); + return JSON.parse(decryptedValue); + } catch {} + } + + // + return null; + } + readOfType(key: string, forceExists: boolean = true): T | null { + // + if (!this.isExists(key)) { + // + if (forceExists) { + throwException(XExceptionIDs.NotFound); + } + + // + return null; + } + + // + const valStr = this.read(key, forceExists); + return fromJson(valStr); + } + + /** + * get a List of Stored items as an Array of StorageItem ... + * @param force true is defualt, means only return list of items + * from Storage which Stored by Service ... + */ + list(force: boolean = true, decrypt: boolean = false): XStorageItem[] | null { + // + if (localStorage.length === 0) { + return null; + } + + // + const items: XStorageItem[] = []; + for (let i = 0; i < localStorage.length; i++) { + // + const mKey = localStorage.key(i); + + // + const mValue = localStorage.getItem(mKey); + const mValueRes = decrypt + ? this.securitySerive.decryptWithKey(mValue) + : mValue; + + // + if (force) { + const theRawKey = this.getRawKey(mKey); + if (!isNullOrUndefined(theRawKey)) { + // + items.push({ + key: theRawKey, + value: mValueRes + }); + } + } else { + // + items.push({ + key: mKey, + value: mValueRes + }); + } + } + + // + return items; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkServicesHolder\projects\x-framework-services\src\lib\x-security.service.ts + +import * as CryptoJS from 'crypto-js'; +import { DomSanitizer } from '@angular/platform-browser'; +import { X_FRAMEWORK_SERVICES_CONFIG } from './tokens/x-injectable-tokens'; +import { toMd5, XBaseService, isNullOrEmptyString } from 'x-framework-core'; +import { Injectable, Inject, Sanitizer, SecurityContext } from '@angular/core'; +import { XFrameworkServicesConfig } from './config/x-framework-services.config'; + +@Injectable({ + providedIn: 'root', +}) +export class XSecurityService extends XBaseService { + // + private readonly SECRET_KEY: string; + private readonly CIPHER_KEY: string; + private readonly CIPHER_IV: string; + private readonly CIPHER_OPTION: CryptoJS.lib.CipherOption; + + // + constructor( + @Inject(X_FRAMEWORK_SERVICES_CONFIG) config: XFrameworkServicesConfig, + private sanitizer: Sanitizer, + private domSanitizer: DomSanitizer + ) { + super(config); + + // + this.SECRET_KEY = toMd5(config.secretKey); + + // + const forIv = this.SECRET_KEY.substring(0, 16); + this.CIPHER_IV = CryptoJS.enc.Utf8.parse(forIv).toString(); + + // + const forKey = this.SECRET_KEY.substring(0, 32); + this.CIPHER_KEY = CryptoJS.enc.Utf8.parse(forKey).toString(); + + // + this.CIPHER_OPTION = { + iv: CryptoJS.enc.Utf8.parse(this.CIPHER_IV), + mode: CryptoJS.mode.CBC, + padding: CryptoJS.pad.Pkcs7, + }; + } + + encryptWithKey( + value: string, + key?: string, + options?: CryptoJS.lib.CipherOption + ) { + // + if (isNullOrEmptyString(value)) { + return null; + } + + // + if (!key) { + key = this.CIPHER_KEY; + } + + // + if (!options) { + options = this.CIPHER_OPTION; + } + + // + return CryptoJS.AES.encrypt(value, key, options).toString(); + } + + encryptWithSecret( + value: string, + secret?: string, + options?: CryptoJS.lib.CipherOption + ) { + // + if (isNullOrEmptyString(value)) { + return null; + } + + // + if (!secret) { + secret = this.SECRET_KEY; + } + + // + if (!options) { + options = this.CIPHER_OPTION; + } + + // + return CryptoJS.AES.encrypt(value, secret, options); + } + + decryptWithKey( + cipherValue: string, + key?: string, + options?: CryptoJS.lib.CipherOption + ) { + // + if (isNullOrEmptyString(cipherValue)) { + return null; + } + + // + if (!key) { + key = this.CIPHER_KEY; + } + + // + if (!options) { + options = this.CIPHER_OPTION; + } + + // + const result = CryptoJS.AES.decrypt(cipherValue, key, options).toString( + CryptoJS.enc.Utf8 + ); + + // + return result; + } + + decryptWithSecret( + cipherValue: string, + secret?: string, + options?: CryptoJS.lib.CipherOption + ) { + // + if (isNullOrEmptyString(cipherValue)) { + return null; + } + + // + if (!secret) { + secret = this.SECRET_KEY; + } + + // + if (!options) { + options = this.CIPHER_OPTION; + } + + // + return CryptoJS.AES.decrypt(cipherValue, secret, options).toString(); + } + + toMd5(value: string) { + return toMd5(value); + } + + toSafeString(value: string, context: SecurityContext = SecurityContext.NONE) { + return this.sanitizer.sanitize(context, value); + } + + toSafeHTML(value: string) { + return this.domSanitizer.bypassSecurityTrustHtml(value); + } + + toSafeResourceURL(value: string) { + return this.domSanitizer.bypassSecurityTrustResourceUrl(value); + } + + toSafeURL(value: string) { + return this.domSanitizer.bypassSecurityTrustUrl(value); + } + + toSafeScript(value: string) { + return this.domSanitizer.bypassSecurityTrustScript(value); + } + + toSafeStyle(value: string) { + return this.domSanitizer.bypassSecurityTrustStyle(value); + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkServicesHolder\projects\x-framework-services\src\lib\x-settings.service.ts + +import { + isArray, + XLocale, + XDeviceInfo, + XValidators, + XOrientation, + XBaseService, + XDeviceTypes, + XOneOrManyType, + XCssProperties, + XLanguageDirection, + XSettingsStorageKey, + isNullOrEmptyString, +} from 'x-framework-core'; +import { + Breakpoints, + BreakpointState, + BreakpointObserver, +} from '@angular/cdk/layout'; +import { Observable } from 'rxjs'; +import { Md5 } from 'md5-typescript'; +import { map } from 'rxjs/operators'; +import { Platform } from '@ionic/angular'; +import { Injectable, Inject } from '@angular/core'; +import { XStorageService } from './x-storage.service'; +import { DeviceDetectorService } from 'ngx-device-detector'; +import { XLocalizationService } from './x-localization.service'; +import { Meta, Title, MetaDefinition } from '@angular/platform-browser'; +import { X_FRAMEWORK_SERVICES_CONFIG } from './tokens/x-injectable-tokens'; +import { XFrameworkServicesConfig } from './config/x-framework-services.config'; + +@Injectable({ + providedIn: 'root', +}) +export class XSettingsService extends XBaseService { + // + private PREFIX = '--'; + private DEVICE_INFO: XDeviceInfo; + private SIDE_MENU_OPENED: boolean; + + // + //#region Breakpoint Observers ... + isMobile$: Observable; + isXSmall$: Observable; + isSmall$: Observable; + isMedium$: Observable; + isLarge$: Observable; + isXLarge$: Observable; + isTablet$: Observable; + isWeb$: Observable; + isPortrait$: Observable; + isLandscape$: Observable; + orientation$: Observable; + //#endregion + + // + //#region Properties ... + // + get uiDirection(): XLanguageDirection { + return this.localizationService.direction; + } + //#endregion + + constructor( + public platform: Platform, + private meta: Meta, + private titleService: Title, + @Inject(X_FRAMEWORK_SERVICES_CONFIG) config: XFrameworkServicesConfig, + private storageService: XStorageService, + private localizationService: XLocalizationService, + private deviceService: DeviceDetectorService, + private breakPointObserver: BreakpointObserver + ) { + // + super(config); + + // + // Handle Ui Directions based on Current Locale ... + this.applyCurrentLocale(); + + // + // Generate Device Info ... + this.prepareDeviceInfo(); + + // + // define objservable ... + this.defineUiBreakPointObservers(); + + // + // Handle Metas for Theme ... + this.handleThemeMetaTags(); + + // + // Prepare Toolbar Height ... + } + + /** + * restart application ... + */ + restart() { + // + if (!window || !window.location) { + return; + } + + // + const href = window.location.href; + setTimeout(() => { + window.location.href = href; + window.location.reload(); + }, 100) + } + + /** + * change system locale ... + * + * @param locale available locale + */ + async changeLocale(locale: XLocale, refresh: boolean = false) { + // + XValidators.validateNotNull(locale); + if (locale === this.localizationService.locale) { + return; + } + + // + this.storageService.write(XSettingsStorageKey.Locale, locale, true); + this.localizationService.changeLocale(locale); + this.applyCurrentLocale(); + + // + if (refresh) { + this.restart(); + } + } + + // + //#region Met Seo Service ... + addTag(tag: MetaDefinition, forceCreation?: boolean) { + this.meta.addTag(tag, forceCreation); + } + + addTags(tags: MetaDefinition[], forceCreation?: boolean) { + this.meta.addTags(tags, forceCreation); + } + + getTag(attrSelector: string) { + this.meta.getTag(attrSelector); + } + + getTags(attrSelector: string) { + this.meta.getTags(attrSelector); + } + + updateTag(tag: MetaDefinition, attrSelector?: string) { + this.meta.updateTag(tag, attrSelector); + } + + removeTag(attrSelector: string) { + this.meta.removeTag(attrSelector); + } + + removeTagElement(meta: HTMLMetaElement) { + this.meta.removeTagElement(meta); + } + //#endregion + + // + //#region Title Service ... + /** + * set Page ( Current Document ) Title + * in Browser ... + */ + setTitle(title?: string) { + // + const appNameKey = this.config.appNameResource; + const appName = this.localizationService.getValue(appNameKey); + + // + const pageTitle = !isNullOrEmptyString(title) + ? `${appName} > ${title}` + : appName; + + // + this.titleService.setTitle(pageTitle); + } + + /** + * get the Current Document Title ... + */ + getTitle() { + return this.titleService.getTitle(); + } + //#endregion + + // + //#region Device Service ... + /** + * get Current Device Model Info + */ + get device() { + return this.DEVICE_INFO; + } + + /** + * get Current Device token + */ + get deviceToken() { + return this.DEVICE_INFO.token; + } + //#endregion + + // + //#region Css Property Handler ... + /** + * reading the CSS Property ... + * + * @param key the key of property + */ + readCssProperty(key: XCssProperties): string { + // + const bodyStyles = window.getComputedStyle(document.body); + return bodyStyles.getPropertyValue(this.prepareCssKey(key)); + } + + getCssVar(name: XCssProperties, element?: HTMLElement) { + // + const docElement = element + ? getComputedStyle(element) + : getComputedStyle(document.documentElement); + return !name || !docElement || isNullOrEmptyString(name) + ? '' + : docElement.getPropertyValue(this.prepareCssKey(name)); + } + + setCssVar( + name: XCssProperties, + value: string, + elements?: XOneOrManyType + ) { + // + if (isNullOrEmptyString(name)) { + return; + } + + // + if (!elements || !isArray(elements)) { + // + const docElement = elements + ? (elements as HTMLElement).style + : document.documentElement.style; + + // + docElement.setProperty(this.prepareCssKey(name), value); + } else { + (elements as HTMLElement[]).forEach((e) => { + e.style.setProperty(this.prepareCssKey(name), value); + }); + } + } + //#endregion + + // + //#region Theme Manager ... + + //#endregion + + // + //#region Private + private applyCurrentLocale() { + // + let storedLocale = this.localizationService.locale; + const isExistsLocale = this.storageService.isExists( + XSettingsStorageKey.Locale + ); + if (isExistsLocale) { + storedLocale = this.storageService.readOfType( + XSettingsStorageKey.Locale + ); + this.localizationService.changeLocale(storedLocale); + } + + // + this.storageService.write(XSettingsStorageKey.Locale, storedLocale, true); + + // + //#region Handle Ui Directions based on Current Locale ... + document.documentElement.lang = this.localizationService.language; + document.documentElement.dir = this.localizationService.direction; + + // + this.setTitle(); + + // + // Document CSS ... + document.body.style.setProperty( + this.prepareCssKey('ui-direction'), + this.localizationService.direction + ); + + // + const isRtl = this.localizationService.direction === 'rtl'; + document.body.style.setProperty( + this.prepareCssKey('is-rtl'), + isRtl.toString() + ); + //#endregion + } + + private prepareDeviceInfo() { + // + //#region Generate Device Info ... + // + // Reading Device Specifications ... + const deviceOs = this.deviceService.os; + const deviceBrowser = this.deviceService.browser; + const deviceOsVersion = this.deviceService.os_version; + const deviceUserAgent = this.deviceService.userAgent; + + // + // Generating Device Type ... + let mDeviceType: XDeviceTypes; + if (this.deviceService.isDesktop()) { + mDeviceType = XDeviceTypes.Desktop; + } else if (this.deviceService.isTablet()) { + mDeviceType = XDeviceTypes.Tablet; + } else if (this.deviceService.isMobile()) { + mDeviceType = XDeviceTypes.Mobile; + } else { + mDeviceType = XDeviceTypes.Unknown; + } + + // + // Generating Device Identifier and it's token ... + const deviceIdentifier = `[${deviceOs}]-[${deviceOsVersion}]-[${mDeviceType}]-[${deviceBrowser}]-[${deviceUserAgent}]`; + const deviceToken = Md5.init(deviceIdentifier); + + // + this.DEVICE_INFO = { + os: deviceOs, + browser: deviceBrowser, + osVersion: deviceOsVersion, + userAgent: deviceUserAgent, + deviceType: mDeviceType, + identifier: deviceIdentifier, + token: deviceToken, + }; + //#endregion + } + + private defineUiBreakPointObservers() { + // + //#region define objservable ... + this.isPortrait$ = this.breakPointObserver + .observe([ + Breakpoints.WebPortrait, + Breakpoints.TabletPortrait, + Breakpoints.HandsetPortrait, + ]) + .pipe(map((breakPointState: BreakpointState) => breakPointState.matches)); + + // + this.isLandscape$ = this.breakPointObserver + .observe([ + Breakpoints.WebLandscape, + Breakpoints.TabletLandscape, + Breakpoints.HandsetLandscape, + ]) + .pipe(map((breakPointState: BreakpointState) => breakPointState.matches)); + + // + this.orientation$ = this.breakPointObserver + .observe([ + Breakpoints.WebPortrait, + Breakpoints.TabletPortrait, + Breakpoints.HandsetPortrait, + Breakpoints.WebLandscape, + Breakpoints.TabletLandscape, + Breakpoints.HandsetLandscape, + ]) + .pipe( + map(() => { + if ( + this.breakPointObserver.isMatched([ + Breakpoints.WebPortrait, + Breakpoints.TabletPortrait, + Breakpoints.HandsetPortrait, + ]) + ) { + return 'portrait'; + } else { + return 'landscape'; + } + }) + ); + + // + this.isXSmall$ = this.breakPointObserver + .observe(Breakpoints.XSmall) + .pipe(map((breakPointState: BreakpointState) => breakPointState.matches)); + + // + this.isSmall$ = this.breakPointObserver + .observe(Breakpoints.Small) + .pipe(map((breakPointState: BreakpointState) => breakPointState.matches)); + + // + this.isMedium$ = this.breakPointObserver + .observe(Breakpoints.Medium) + .pipe(map((breakPointState: BreakpointState) => breakPointState.matches)); + + // + this.isLarge$ = this.breakPointObserver + .observe(Breakpoints.Large) + .pipe(map((breakPointState: BreakpointState) => breakPointState.matches)); + + // + this.isXLarge$ = this.breakPointObserver + .observe(Breakpoints.XLarge) + .pipe(map((breakPointState: BreakpointState) => breakPointState.matches)); + + // + this.isTablet$ = this.breakPointObserver + .observe(Breakpoints.Tablet) + .pipe(map((breakPointState: BreakpointState) => breakPointState.matches)); + + // + this.isWeb$ = this.breakPointObserver + .observe(Breakpoints.Web) + .pipe(map((breakPointState: BreakpointState) => breakPointState.matches)); + + // + this.isMobile$ = this.breakPointObserver + .observe('(max-width: 768px)') + .pipe(map((breakPointState: BreakpointState) => breakPointState.matches)); + //#endregion + } + + private handleThemeMetaTags() { + // + //#region Handle Metas for Theme ... + const darkPrimaryColor = this.readCssProperty('x-app-bar-color'); + this.addTag( + { + name: 'theme-color', + content: darkPrimaryColor, + }, + true + ); + this.addTag( + { + name: 'msapplication-navbutton-color', + content: darkPrimaryColor, + }, + true + ); + this.addTag( + { + name: 'apple-mobile-web-app-capable', + content: 'yes', + }, + true + ); + this.addTag( + { + name: 'apple-mobile-web-app-status-bar-style', + content: darkPrimaryColor, + }, + true + ); + //#endregion + } + + private prepareCssKey(key: XCssProperties) { + return `${this.PREFIX}${key}`; + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkServicesHolder\projects\x-framework-services\src\lib\x-storage.service.ts + +import { + fromJson, + XStorageItem, + XBaseService, + XExceptionIDs, + throwException, + isNullOrUndefined +} from 'x-framework-core'; +import { Injectable, Inject } from '@angular/core'; +import { X_FRAMEWORK_SERVICES_CONFIG } from './tokens/x-injectable-tokens'; +import { XFrameworkServicesConfig } from './config/x-framework-services.config'; + +@Injectable({ + providedIn: 'root' +}) +export class XStorageService extends XBaseService { + // + private keyPrefix: string; + + // + constructor( + @Inject(X_FRAMEWORK_SERVICES_CONFIG) config: XFrameworkServicesConfig + ) { + super(config); + + // + if (isNullOrUndefined(typeof Storage)) { + throwException(XExceptionIDs.StorageNotSupported); + } + + // + this.keyPrefix = `${config.appInstanceName}`; + } + + /** + * determine a key exists in storage or not ... + * @param key the item key + */ + isExists(key: string) { + // + const theKey = this.getPreparedKey(key); + const result = localStorage.getItem(theKey) !== null; + + // + return result; + } + + /** + * writing a new item in Storage ... + * + * @param key the item key + * @param data the data object which needs to save + * @param forceRewrite overwrite item if exists, the default is false, + * means if key exists throws an error ... + */ + write(key: string, data: any, forceRewrite: boolean = false) { + // + if (!forceRewrite && this.isExists(key)) { + throwException(XExceptionIDs.Duplicate); + } + + // + const theKey = this.getPreparedKey(key); + localStorage.setItem(theKey, JSON.stringify(data)); + } + + /** + * read an item from Storage ... + * @param key the item key + * @param forceExists determine the given key must be exists or not, true is default, + * means if key not exists throws an error ... + */ + read(key: string, forceExists: boolean = true): any | null { + // + if (forceExists && !this.isExists(key)) { + throwException(XExceptionIDs.NotFound); + } + + // + const theKey = this.getPreparedKey(key); + const item = localStorage.getItem(theKey); + if (!isNullOrUndefined(item)) { + try { + return JSON.parse(item); + } catch {} + } + + // + return null; + } + readOfType(key: string, forceExists: boolean = true): T | null { + // + if (!this.isExists(key)) { + // + if (forceExists) { + throwException(XExceptionIDs.NotFound); + } + + // + return null; + } + + // + const valStr = this.read(key, forceExists); + return fromJson(valStr); + } + + /** + * remove an stored item from Storage ... + * @param key the item key + * @param forceExists determine the given key must be exists or not, true is default, + * means if key not exists throws an error ... + */ + remove(key: string, forceExists: boolean = true) { + // + if (forceExists && !this.isExists(key)) { + throwException(XExceptionIDs.NotFound); + } + + // + const theKey = this.getPreparedKey(key); + localStorage.removeItem(theKey); + } + + /** + * get a List of Stored items as an Array of StorageItem ... + * @param force true is defualt, means only return list of items + * from Storage which Stored by Service ... + */ + list(force: boolean = true): XStorageItem[] | null { + // + if (localStorage.length === 0) { + return null; + } + + // + const items: XStorageItem[] = []; + for (let i = 0; i < localStorage.length; i++) { + // + const mKey = localStorage.key(i); + + // + if (force) { + const theRawKey = this.getRawKey(mKey); + if (!isNullOrUndefined(theRawKey)) { + // + items.push({ + key: theRawKey, + value: localStorage.getItem(mKey) + }); + } + } else { + // + items.push({ + key: mKey, + value: localStorage.getItem(mKey) + }); + } + } + + // + return items; + } + + /** + * get the number of items which stored in Storage ... + * @param force true is defualt, means only return number of items + * from Storage which Stored by Service ... + */ + length(force: boolean = true) { + // + if (force) { + return this.list().length; + } + + // + return localStorage.length; + } + + /** + * clear all items from Storage ... + * @param force true is defualt, means only clear items + * from Storage which Stored by Service ... + */ + clear(force: boolean = true) { + localStorage.clear(); + } + + protected getPreparedKey(key: string) { + return `${this.keyPrefix}_${key}`; + } + + protected getRawKey(key: string): string | null { + // + const baseKeySubstr = `${this.keyPrefix}_`; + if (key.indexOf(baseKeySubstr) === -1) { + return null; + } + + // + return key.replace(baseKeySubstr, ''); + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkServicesHolder\projects\x-framework-services\src\lib\x-task-manager.service.ts + +import { + XTask, + XException, + XBaseService, + XTaskPriority, + isValidException, + getErrorException, +} from 'x-framework-core'; +import { Injectable, Inject } from '@angular/core'; +import { concatMap, catchError, tap } from 'rxjs/operators'; +import { X_FRAMEWORK_SERVICES_CONFIG } from './tokens/x-injectable-tokens'; +import { Subscription, Subject, BehaviorSubject, of, throwError } from 'rxjs'; +import { XFrameworkServicesConfig } from './config/x-framework-services.config'; + +@Injectable({ + providedIn: 'root', +}) +export class XTaskManagerService extends XBaseService { + // + //#region Props ... + // + subscription: Subscription; + + // + // Notify loading ... + public allTasksFinished = new Subject(); + public loading = new BehaviorSubject(false); + + // + // notify Specific Task Start or Finished ... + public taskStart = new Subject>(); + public taskError = new Subject>(); + public taskFinished = new Subject>(); + + // + private isPaused = false; + private isStarted = false; + private tasks = new Map>(); + private taskQueue = new Subject>(); + + /** + * retrieve a Collection of Tasks which Pending to run ... + */ + get taskList() { + return this.getSortedTasksLists(); + } + + /** + * retrieve Running State of Tasks ... + */ + get paused() { + return this.isPaused; + } + //#endregion + + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_SERVICES_CONFIG) config: XFrameworkServicesConfig + ) { + super(config); + + // + // Register Task Queue Runner ... + this.taskQueue.asObservable().subscribe((t) => { + this.handleTaskRun(t); + }); + + // + this.allTasksFinished.asObservable().subscribe(() => { + this.loading.next(false); + }); + } + //#endregion + + // + //#region Actions ... + /** + * determines a Tasks is in Pending List or not ... + */ + has(taskName: string) { + return this.tasks.has(taskName); + } + + /** + * delete a task from Pending List ... + */ + delete(taskName: string) { + return this.tasks.delete(taskName); + } + + /** + * add a Task to Pending List ... + */ + add(task: XTask) { + // + // Prevent Duplicate ... + if (this.has(task.name)) { + return false; + } + + // + // add Task to Pending List ... + this.tasks.set(task.name, task); + + // + // Start Running if it's not Started Before ... + if (!this.isStarted) { + this.requestNext(); + } + + // + return true; + } + + /** + * Change Specific Tasks Priority ... + */ + changePriority(taskName: string, priority: XTaskPriority) { + // + if (!this.has(taskName)) { + return; + } + + // + this.tasks.get(taskName).priority = priority; + } + + /** + * Count Pending List Tasks ... + */ + count() { + return this.tasks.size; + } + + /** + * Remove All Pending List Task ... + */ + clear() { + this.isStarted = false; + this.tasks.clear(); + } + + /** + * Pause Running of Tasks ... + */ + pause() { + this.isPaused = true; + } + + /** + * Continue Running Pending Tasks ... + */ + continue() { + // + if (!this.isPaused) { + return; + } + + // + this.isPaused = false; + this.requestNext(); + } + + /** + * Toggle Pause/Continue Tasks Running ... + */ + toggle() { + // + if (this.isPaused) { + this.continue(); + } else { + this.pause(); + } + } + //#endregion + + // + //#region Private ... + private requestNext() { + // + if (this.isPaused) { + return; + } + + // + const task = this.popTask(); + + // + if (task) { + // + this.isStarted = true; + this.taskQueue.next(task); + } else { + // + this.isStarted = false; + if (this.tasks.size === 0) { + // + this.loading.next(false); + this.allTasksFinished.next(); + } + } + } + + private popTask() { + // + const result = this.getSortedTasksLists().pop() || null; + + // + if (result) { + this.tasks.delete(result.name); + } + return result; + } + + private getSortedTasksLists() { + // + return Array.from(this.tasks.values()) + .sort((a, b) => a.priority - b.priority) + .reverse(); + } + + private applyWrapperToTask(task: XTask) { + // + return of(task).pipe( + concatMap((res) => { + // + if (res.affectLoading) { + this.loading.next(true); + } + + // + this.taskStart.next(res); + + // + return res.task.pipe( + catchError((err) => { + // + this.isStarted = false; + this.loading.next(false); + + // + res.error = getErrorException(err); + + // + if (isValidException(err.error)) { + res.error = err.error as XException; + } + + // + this.taskError.next(res); + + // + this.handleUnSubscribe(); + return throwError(res.error); + }), + tap((tRes) => { + // + this.isStarted = false; + this.loading.next(false); + + // + res.result = tRes; + this.taskFinished.next(res); + + // + this.handleUnSubscribe(); + this.requestNext(); + }) + ); + }) + ); + } + + private handleTaskRun(task: XTask) { + // + const mXTObs = this.applyWrapperToTask(task); + + // + this.subscription = mXTObs.subscribe( + (_) => {}, + (_) => {} + ); + } + + private handleUnSubscribe() { + // + if (this.subscription) { + // + this.subscription.unsubscribe(); + this.subscription = null; + } + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkServicesHolder\projects\x-framework-services\src\lib\x-theme-manager.service.ts + +import { + keys, + hasChild, + XThemeType, + XThemePack, + XThemeColors, + downloadFile, + XBaseService, + XExceptionIDs, + XThemeCssVars, + toNormalString, + isNullOrUndefined, + isNullOrEmptyString, + XExceptionIdentifier, + XThemeTypeIdentifier, + XThemeCssVarsIdentifier, +} from 'x-framework-core'; +import { XStorageService } from './x-storage.service'; +import { XSettingsService } from './x-settings.service'; +import { XNotificationService } from './x-notification.service'; +import { X_FRAMEWORK_SERVICES_CONFIG } from './tokens/x-injectable-tokens'; +import { Injectable, Inject, EventEmitter, ElementRef } from '@angular/core'; +import { XFrameworkServicesConfig } from './config/x-framework-services.config'; + +const XThemeManagerStorageKeys = { + CurrentTheme: 'current_theme', + CurrentThemeType: 'current_theme_type', + Themes: 'themes', +}; + +@Injectable({ + providedIn: 'root', +}) +export class XThemeManagerService extends XBaseService { + // + //#region Props ... + // + private PREFIX = '--'; + readonly ColorCssVars = Object.assign({}, XThemeCssVars); + + // + notifyChanges = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + constructor( + @Inject(X_FRAMEWORK_SERVICES_CONFIG) + public config: XFrameworkServicesConfig, + private storageService: XStorageService, + private settingsService: XSettingsService, + private notificationService: XNotificationService + ) { + // + super(config); + + // + this.initializeService(); + } + //#endregion + + // + //#region Actions ... + async applyCurrentTheme(element?: ElementRef) { + // + const currentTheme = await this.getCurrentTheme(); + const isDefaultThemeExists = !isNullOrUndefined(currentTheme); + if (isDefaultThemeExists) { + // + const type = await this.getCurrentThemeType(); + await this.setTheme(currentTheme, type, false, false, element); + + // + this.notifyChanges.emit(); + } + } + + async getCurrentThemeName() { + // + const mKey = XThemeManagerStorageKeys.CurrentTheme; + + // + const isExists = this.storageService.isExists(mKey); + if (!isExists) { + return ''; + } + + // + const result = this.storageService.readOfType(mKey); + + // + return result; + } + + async setCurrentThemeName(themeName: string, notifyChanges: boolean = true) { + // + const isExists = await this.isThemeExists(themeName); + if (!isExists) { + // + if (notifyChanges) { + await this.issueFailedNotification(XExceptionIDs.NotFound); + } + + // + return false; + } + + // + const mKey = XThemeManagerStorageKeys.CurrentTheme; + + // + this.storageService.write(mKey, themeName, true); + + // + if (notifyChanges) { + // + this.notifyChanges.emit(); + await this.issueSuccessNotification(); + } + + // + return true; + } + + async getCurrentThemeType() { + // + const mKey = XThemeManagerStorageKeys.CurrentThemeType; + + // + const isExists = this.storageService.isExists(mKey); + if (!isExists) { + return XThemeType.Light; + } + + // + const result = this.storageService.readOfType(mKey); + + // + return result; + } + + async setCurrentThemeType( + themeType: XThemeTypeIdentifier, + notifyChanges: boolean = true + ) { + // + if (isNullOrEmptyString(themeType)) { + // + if (notifyChanges) { + await this.issueFailedNotification(XExceptionIDs.InvalidArgs); + } + + // + return false; + } + + // + const mKey = XThemeManagerStorageKeys.CurrentThemeType; + + // + this.storageService.write(mKey, themeType, true); + + // + if (notifyChanges) { + // + this.notifyChanges.emit(); + await this.issueSuccessNotification(); + } + + // + return true; + } + + async setTheme( + theme: XThemePack, + whichType: XThemeTypeIdentifier = XThemeType.Light, + notifyChanges: boolean = true, + restart: boolean = true, + element?: ElementRef + ) { + // + if (isNullOrUndefined(theme)) { + // + if (notifyChanges) { + await this.issueFailedNotification(XExceptionIDs.InvalidArgs); + } + + // + return false; + } + + // + const isExists = await this.isThemeExists(theme.name); + if (!isExists) { + // + if (notifyChanges) { + await this.issueFailedNotification(XExceptionIDs.NotFound); + } + + // + return false; + } + + // + let result = false; + switch (whichType) { + // + case XThemeType.Light: + result = await this.setThemeColors(theme.light.colors, element); + break; + + // + case XThemeType.Dark: + result = await this.setThemeColors(theme.dark.colors, element); + break; + } + + // + if (result) { + // + await this.setCurrentThemeType(whichType, false); + await this.setCurrentThemeName(theme.name, false); + } + + // + if (result && notifyChanges) { + // + this.notifyChanges.emit(); + await this.issueSuccessNotification(); + } + + // + if (result && restart) { + this.settingsService.restart(); + } + + // + return result; + } + + async cloneTheme( + themeName: string, + clonedThemeName: string, + notifyChanges: boolean = true + ) { + // + const isExists = await this.isThemeExists(themeName); + if (!isExists) { + // + if (notifyChanges) { + await this.issueFailedNotification(XExceptionIDs.NotFound); + } + + // + return false; + } + + // + const isClonedThemeNameReserved = await this.isThemeExists(clonedThemeName); + if (isClonedThemeNameReserved) { + // + if (notifyChanges) { + await this.issueFailedNotification(XExceptionIDs.Duplicate); + } + + // + return false; + } + + // + const theme = await this.getTheme(themeName); + if (!theme) { + // + if (notifyChanges) { + await this.issueFailedNotification(XExceptionIDs.NotFound); + } + + // + return false; + } + + // + theme.name = clonedThemeName; + const result = await this.saveTheme(theme, false); + + // + if (result && notifyChanges) { + this.notifyChanges.emit(); + } + + // + return result; + } + + async exportTheme(themeName: string) { + // + if (isNullOrEmptyString(themeName)) { + return; + } + + // + const theme = await this.getTheme(themeName); + if (isNullOrUndefined(theme)) { + return; + } + + // + const theJSON = JSON.stringify(theme); + + // + downloadFile(theJSON, 'text/json', `x-framework-${theme.name}-theme.json`); + } + + async removeTheme(themeName: string, notifyChanges: boolean = true) { + // + const isExists = await this.isThemeExists(themeName); + if (!isExists) { + // + if (notifyChanges) { + await this.issueFailedNotification(XExceptionIDs.NotFound); + } + + // + return false; + } + + // + const currentThemeName = await this.getCurrentThemeName(); + if (!currentThemeName) { + // + if (notifyChanges) { + await this.issueFailedNotification(XExceptionIDs.NotFound); + } + + // + return false; + } + + // + const themes = await this.getThemes(); + if (!hasChild(themes) || themes.length === 1) { + // + if (notifyChanges) { + await this.issueFailedNotification(XExceptionIDs.NotFound); + } + + // + return false; + } + + // + const filteredThemes = themes.filter( + (th) => toNormalString(th.name) !== toNormalString(themeName) + ); + + // + let result = false; + try { + // + result = true; + this.writeThemes(filteredThemes); + } catch { + result = false; + } + + // + const isCurrentRemoved = + toNormalString(currentThemeName) === toNormalString(themeName); + if (isCurrentRemoved) { + // + const newCurrentTheme = filteredThemes[0]; + const themeType = await this.getCurrentThemeType(); + result = await this.setTheme(newCurrentTheme, themeType, false, false); + } + + // + if (result && notifyChanges) { + // + this.notifyChanges.emit(); + await this.issueSuccessNotification(); + } + + // + if (result && isCurrentRemoved) { + this.settingsService.restart(); + } + + // + return result; + } + + async saveTheme(theme: XThemePack, notifyChanges: boolean = true) { + // + if (isNullOrUndefined(theme)) { + // + if (notifyChanges) { + await this.issueFailedNotification(XExceptionIDs.InvalidArgs); + } + + // + return false; + } + + // + const isExists = await this.isThemeExists(theme.name); + if (isExists) { + // + if (notifyChanges) { + await this.issueFailedNotification(XExceptionIDs.Duplicate); + } + + // + return false; + } + + // + const themeType = await this.getCurrentThemeType(); + + // + const themes = await this.getThemes(); + if (!hasChild(themes)) { + // + const newThemes = [theme]; + + // + this.writeThemes(newThemes); + + // + const result = await this.setTheme(theme, themeType, true); + return result; + } else { + // + const themeIndex = themes.findIndex((th) => th.name === theme.name); + if (themeIndex === -1) { + themes.push(theme); + } else { + themes[themeIndex] = theme; + } + + // + this.writeThemes(themes); + + // + if (notifyChanges) { + // + this.notifyChanges.emit(); + await this.issueSuccessNotification(); + } + + // + return true; + } + } + + async updateTheme( + themeName: string, + updatedTheme: XThemePack, + notifiChanges: boolean = true, + restartIfCurrent: boolean = true + ) { + // + if (isNullOrEmptyString(themeName) || isNullOrUndefined(updatedTheme)) { + // + if (notifiChanges) { + await this.issueFailedNotification(XExceptionIDs.InvalidArgs); + } + + // + return false; + } + + // + const isExists = await this.isThemeExists(themeName); + if (!isExists) { + // + if (notifiChanges) { + await this.issueFailedNotification(XExceptionIDs.NotFound); + } + + // + return false; + } + + // + const currentThemeName = await this.getCurrentThemeName(); + const isCurrent = + toNormalString(themeName) === toNormalString(currentThemeName); + + // + let result = false; + const themes = this.readThemes(); + const themeIndex = themes.findIndex( + (th) => toNormalString(th.name) === toNormalString(themeName) + ); + if (themeIndex < 0) { + // + if (notifiChanges) { + await this.issueFailedNotification(XExceptionIDs.NotFound); + } + + // + return result; + } + + // + themes[themeIndex] = updatedTheme; + try { + result = true; + this.writeThemes(themes); + } catch { + // + if (notifiChanges) { + await this.issueFailedNotification(XExceptionIDs.ActionFailed); + } + + // + return false; + } + + // + if (result && isCurrent) { + // + const mode = await this.getCurrentThemeType(); + const isSetted = await this.setTheme(updatedTheme, mode, false, false); + if (!isSetted) { + // + if (notifiChanges) { + await this.issueFailedNotification(XExceptionIDs.ActionFailed); + } + + // + return false; + } + } + + // + if (result && notifiChanges) { + await this.issueSuccessNotification(); + } + + // + if (isCurrent && restartIfCurrent) { + this.settingsService.restart(); + } + + // + return result; + } + + async getCurrentTheme() { + // + const currentThemeName = await this.getCurrentThemeName(); + const isExists = await this.isThemeExists(currentThemeName); + if (!isExists) { + return null; + } + + // + const result = await this.getTheme(currentThemeName); + return result; + } + + async getThemeNames() { + // + const themes = await this.getThemes(); + if (!hasChild(themes)) { + return []; + } + + // + return themes.map((th) => th.name); + } + + async getThemes() { + // + const result = this.readThemes(); + + // + return result; + } + + async getTheme(themeName: string) { + // + const isExists = await this.isThemeExists(themeName); + if (!isExists) { + return null; + } + + // + const themes = await this.getThemes(); + return themes.find( + (th) => toNormalString(th.name) === toNormalString(themeName) + ); + } + + async isThemeExists(themeName: string) { + // + if (isNullOrEmptyString(themeName)) { + return false; + } + + // + const themes = await this.getThemeNames(); + const result = + themes.findIndex( + (th) => toNormalString(th) === toNormalString(themeName) + ) > -1; + + // + return result; + } + + async setLightMode() { + // + const currentTheme = await this.getCurrentTheme(); + if (isNullOrUndefined(currentTheme)) { + return false; + } + + // + return await this.setTheme(currentTheme, XThemeType.Light, false, true); + } + + async setDarkMode() { + // + const currentTheme = await this.getCurrentTheme(); + if (isNullOrUndefined(currentTheme)) { + return false; + } + + // + return await this.setTheme(currentTheme, XThemeType.Dark, false, true); + } + + async toggleMode() { + // + const currentTheme = await this.getCurrentTheme(); + const currentThemeMode = await this.getCurrentThemeType(); + if (isNullOrUndefined(currentTheme)) { + return false; + } + + // + const mode: XThemeType = + currentThemeMode === XThemeType.Light + ? XThemeType.Dark + : XThemeType.Light; + + // + return await this.setTheme(currentTheme, mode, false, true); + } + //#endregion + + // + //#region Private ... + private async initializeService() { + // + const currentTheme = await this.getCurrentTheme(); + const isDefaultThemeExists = !isNullOrUndefined(currentTheme); + if (!isDefaultThemeExists) { + // + const defaultTheme: XThemePack = { + name: 'default', + light: { + type: XThemeType.Light, + colors: { + // + //#region Primary ... + // + XColorPrimary: this.getCssVar(XThemeCssVars.XColorPrimary), + XColorPrimaryContrast: this.getCssVar( + XThemeCssVars.XColorPrimaryContrast + ), + + // + XColorPrimaryShade: this.getCssVar( + XThemeCssVars.XColorPrimaryShade + ), + XColorPrimaryShadeContrast: this.getCssVar( + XThemeCssVars.XColorPrimaryShadeContrast + ), + + // + XColorPrimaryTint: this.getCssVar(XThemeCssVars.XColorPrimaryTint), + XColorPrimaryTintContrast: this.getCssVar( + XThemeCssVars.XColorPrimaryTintContrast + ), + //#endregion + + // + //#region Secondary ... + // + XColorSecondary: this.getCssVar(XThemeCssVars.XColorSecondary), + XColorSecondaryContrast: this.getCssVar( + XThemeCssVars.XColorSecondaryContrast + ), + + // + XColorSecondaryShade: this.getCssVar( + XThemeCssVars.XColorSecondaryShade + ), + XColorSecondaryShadeContrast: this.getCssVar( + XThemeCssVars.XColorSecondaryShadeContrast + ), + + // + XColorSecondaryTint: this.getCssVar( + XThemeCssVars.XColorSecondaryTint + ), + XColorSecondaryTintContrast: this.getCssVar( + XThemeCssVars.XColorSecondaryTintContrast + ), + //#endregion + + // + //#region Tertiary ... + // + XColorTertiary: this.getCssVar(XThemeCssVars.XColorTertiary), + XColorTertiaryContrast: this.getCssVar( + XThemeCssVars.XColorTertiaryContrast + ), + + // + XColorTertiaryShade: this.getCssVar( + XThemeCssVars.XColorTertiaryShade + ), + XColorTertiaryShadeContrast: this.getCssVar( + XThemeCssVars.XColorTertiaryShadeContrast + ), + + // + XColorTertiaryTint: this.getCssVar( + XThemeCssVars.XColorTertiaryTint + ), + XColorTertiaryTintContrast: this.getCssVar( + XThemeCssVars.XColorTertiaryTintContrast + ), + //#endregion + + // + //#region Success ... + // + XColorSuccess: this.getCssVar(XThemeCssVars.XColorSuccess), + XColorSuccessContrast: this.getCssVar( + XThemeCssVars.XColorSuccessContrast + ), + + // + XColorSuccessShade: this.getCssVar( + XThemeCssVars.XColorSuccessShade + ), + XColorSuccessShadeContrast: this.getCssVar( + XThemeCssVars.XColorSuccessShadeContrast + ), + + // + XColorSuccessTint: this.getCssVar(XThemeCssVars.XColorSuccessTint), + XColorSuccessTintContrast: this.getCssVar( + XThemeCssVars.XColorSuccessTintContrast + ), + //#endregion + + // + //#region Warning ... + // + XColorWarning: this.getCssVar(XThemeCssVars.XColorWarning), + XColorWarningContrast: this.getCssVar( + XThemeCssVars.XColorWarningContrast + ), + + // + XColorWarningShade: this.getCssVar( + XThemeCssVars.XColorWarningShade + ), + XColorWarningShadeContrast: this.getCssVar( + XThemeCssVars.XColorWarningShadeContrast + ), + + // + XColorWarningTint: this.getCssVar(XThemeCssVars.XColorWarningTint), + XColorWarningTintContrast: this.getCssVar( + XThemeCssVars.XColorWarningTintContrast + ), + //#endregion + + // + //#region Danger ... + // + XColorDanger: this.getCssVar(XThemeCssVars.XColorDanger), + XColorDangerContrast: this.getCssVar( + XThemeCssVars.XColorDangerContrast + ), + + // + XColorDangerShade: this.getCssVar(XThemeCssVars.XColorDangerShade), + XColorDangerShadeContrast: this.getCssVar( + XThemeCssVars.XColorDangerShadeContrast + ), + + // + XColorDangerTint: this.getCssVar(XThemeCssVars.XColorDangerTint), + XColorDangerTintContrast: this.getCssVar( + XThemeCssVars.XColorDangerTintContrast + ), + //#endregion + + // + //#region Dark ... + // + XColorDark: this.getCssVar(XThemeCssVars.XColorDark), + XColorDarkContrast: this.getCssVar( + XThemeCssVars.XColorDarkContrast + ), + + // + XColorDarkShade: this.getCssVar(XThemeCssVars.XColorDarkShade), + XColorDarkShadeContrast: this.getCssVar( + XThemeCssVars.XColorDarkShadeContrast + ), + + // + XColorDarkTint: this.getCssVar(XThemeCssVars.XColorDarkTint), + XColorDarkTintContrast: this.getCssVar( + XThemeCssVars.XColorDarkTintContrast + ), + //#endregion + + // + //#region Medium ... + // + XColorMedium: this.getCssVar(XThemeCssVars.XColorMedium), + XColorMediumContrast: this.getCssVar( + XThemeCssVars.XColorMediumContrast + ), + + // + XColorMediumShade: this.getCssVar(XThemeCssVars.XColorMediumShade), + XColorMediumShadeContrast: this.getCssVar( + XThemeCssVars.XColorMediumShadeContrast + ), + + // + XColorMediumTint: this.getCssVar(XThemeCssVars.XColorMediumTint), + XColorMediumTintContrast: this.getCssVar( + XThemeCssVars.XColorMediumTintContrast + ), + //#endregion + + // + //#region Light ... + // + XColorLight: this.getCssVar(XThemeCssVars.XColorLight), + XColorLightContrast: this.getCssVar( + XThemeCssVars.XColorLightContrast + ), + + // + XColorLightShade: this.getCssVar(XThemeCssVars.XColorLightShade), + XColorLightShadeContrast: this.getCssVar( + XThemeCssVars.XColorLightShadeContrast + ), + + // + XColorLightTint: this.getCssVar(XThemeCssVars.XColorLightTint), + XColorLightTintContrast: this.getCssVar( + XThemeCssVars.XColorLightTintContrast + ), + //#endregion + }, + }, + dark: { + type: XThemeType.Dark, + colors: { + // + //#region Primary ... + // + XColorPrimary: this.getCssVar(XThemeCssVars.XColorDark), + XColorPrimaryContrast: this.getCssVar( + XThemeCssVars.XColorDarkContrast + ), + + // + XColorPrimaryShade: this.getCssVar(XThemeCssVars.XColorDarkShade), + XColorPrimaryShadeContrast: this.getCssVar( + XThemeCssVars.XColorDarkShadeContrast + ), + + // + XColorPrimaryTint: this.getCssVar(XThemeCssVars.XColorDarkTint), + XColorPrimaryTintContrast: this.getCssVar( + XThemeCssVars.XColorDarkTintContrast + ), + //#endregion + + // + //#region Secondary ... + // + XColorSecondary: this.getCssVar(XThemeCssVars.XColorDarkTint), + XColorSecondaryContrast: this.getCssVar( + XThemeCssVars.XColorDarkTintContrast + ), + + // + XColorSecondaryShade: this.getCssVar(XThemeCssVars.XColorDarkTint), + XColorSecondaryShadeContrast: this.getCssVar( + XThemeCssVars.XColorDarkTintContrast + ), + + // + XColorSecondaryTint: this.getCssVar(XThemeCssVars.XColorDarkTint), + XColorSecondaryTintContrast: this.getCssVar( + XThemeCssVars.XColorDarkTintContrast + ), + //#endregion + + // + //#region Tertiary ... + // + XColorTertiary: this.getCssVar(XThemeCssVars.XColorTertiary), + XColorTertiaryContrast: this.getCssVar( + XThemeCssVars.XColorTertiaryContrast + ), + + // + XColorTertiaryShade: this.getCssVar( + XThemeCssVars.XColorTertiaryShade + ), + XColorTertiaryShadeContrast: this.getCssVar( + XThemeCssVars.XColorTertiaryShadeContrast + ), + + // + XColorTertiaryTint: this.getCssVar( + XThemeCssVars.XColorTertiaryTint + ), + XColorTertiaryTintContrast: this.getCssVar( + XThemeCssVars.XColorTertiaryTintContrast + ), + //#endregion + + // + //#region Success ... + // + XColorSuccess: this.getCssVar(XThemeCssVars.XColorSuccess), + XColorSuccessContrast: this.getCssVar( + XThemeCssVars.XColorSuccessContrast + ), + + // + XColorSuccessShade: this.getCssVar( + XThemeCssVars.XColorSuccessShade + ), + XColorSuccessShadeContrast: this.getCssVar( + XThemeCssVars.XColorSuccessShadeContrast + ), + + // + XColorSuccessTint: this.getCssVar(XThemeCssVars.XColorSuccessTint), + XColorSuccessTintContrast: this.getCssVar( + XThemeCssVars.XColorSuccessTintContrast + ), + //#endregion + + // + //#region Warning ... + // + XColorWarning: this.getCssVar(XThemeCssVars.XColorWarning), + XColorWarningContrast: this.getCssVar( + XThemeCssVars.XColorWarningContrast + ), + + // + XColorWarningShade: this.getCssVar( + XThemeCssVars.XColorWarningShade + ), + XColorWarningShadeContrast: this.getCssVar( + XThemeCssVars.XColorWarningShadeContrast + ), + + // + XColorWarningTint: this.getCssVar(XThemeCssVars.XColorWarningTint), + XColorWarningTintContrast: this.getCssVar( + XThemeCssVars.XColorWarningTintContrast + ), + //#endregion + + // + //#region Danger ... + // + XColorDanger: this.getCssVar(XThemeCssVars.XColorDanger), + XColorDangerContrast: this.getCssVar( + XThemeCssVars.XColorDangerContrast + ), + + // + XColorDangerShade: this.getCssVar(XThemeCssVars.XColorDangerShade), + XColorDangerShadeContrast: this.getCssVar( + XThemeCssVars.XColorDangerShadeContrast + ), + + // + XColorDangerTint: this.getCssVar(XThemeCssVars.XColorDangerTint), + XColorDangerTintContrast: this.getCssVar( + XThemeCssVars.XColorDangerTintContrast + ), + //#endregion + + // + //#region Dark ... + // + XColorDark: this.getCssVar(XThemeCssVars.XColorDark), + XColorDarkContrast: this.getCssVar( + XThemeCssVars.XColorDarkContrast + ), + + // + XColorDarkShade: this.getCssVar(XThemeCssVars.XColorDarkShade), + XColorDarkShadeContrast: this.getCssVar( + XThemeCssVars.XColorDarkShadeContrast + ), + + // + XColorDarkTint: this.getCssVar(XThemeCssVars.XColorDarkTint), + XColorDarkTintContrast: this.getCssVar( + XThemeCssVars.XColorDarkTintContrast + ), + //#endregion + + // + //#region Medium ... + // + XColorMedium: this.getCssVar(XThemeCssVars.XColorMedium), + XColorMediumContrast: this.getCssVar( + XThemeCssVars.XColorMediumContrast + ), + + // + XColorMediumShade: this.getCssVar(XThemeCssVars.XColorMediumShade), + XColorMediumShadeContrast: this.getCssVar( + XThemeCssVars.XColorMediumShadeContrast + ), + + // + XColorMediumTint: this.getCssVar(XThemeCssVars.XColorMediumTint), + XColorMediumTintContrast: this.getCssVar( + XThemeCssVars.XColorMediumTintContrast + ), + //#endregion + + // + //#region Light ... + // + XColorLight: this.getCssVar(XThemeCssVars.XColorLight), + XColorLightContrast: this.getCssVar( + XThemeCssVars.XColorLightContrast + ), + + // + XColorLightShade: this.getCssVar(XThemeCssVars.XColorLightShade), + XColorLightShadeContrast: this.getCssVar( + XThemeCssVars.XColorLightShadeContrast + ), + + // + XColorLightTint: this.getCssVar(XThemeCssVars.XColorLightTint), + XColorLightTintContrast: this.getCssVar( + XThemeCssVars.XColorLightTintContrast + ), + //#endregion + }, + }, + }; + + // + await this.saveTheme(defaultTheme, false); + } else { + // + const type = await this.getCurrentThemeType(); + await this.setTheme(currentTheme, type, false, false); + } + + // + this.notifyChanges.emit(); + } + + private async issueFailedNotification(exception?: XExceptionIdentifier) { + // + if (isNullOrUndefined(exception)) { + exception = XExceptionIDs.ActionFailed; + } + + // + await this.notificationService.presentErrorNotification({ + exception, + dissmissable: false, + }); + } + + private async issueSuccessNotification() { + // + await this.notificationService.presentDefaultSuccessNotification(); + } + + // + //#region Read and Write Storage ... + private writeThemes(themes: XThemePack[]) { + // + const mKey = XThemeManagerStorageKeys.Themes; + + // + this.storageService.write(mKey, themes, true); + } + + private readThemes() { + // + const mKey = XThemeManagerStorageKeys.Themes; + + // + const isExists = this.storageService.isExists(mKey); + if (!isExists) { + return []; + } + + // + return this.storageService.readOfType(mKey, false); + } + //#endregion + + // + //#region CssVar Manipulators ... + /** + * prepare a css var name + * + * @param key css variable name + */ + private prepareCssKey(key: XThemeCssVarsIdentifier) { + return `${this.PREFIX}${key}`; + } + + /** + * remove css key prefix ... + * + * @param key a prepared css var ... + */ + private removeCssKeyPrefix(key: XThemeCssVarsIdentifier) { + return key.replace(this.PREFIX, ''); + } + + /** + * read specific css var value of an element + * @param name css variable name + * @param element destination element + */ + private getCssVar(name: XThemeCssVarsIdentifier) { + // + return getComputedStyle(document.body).getPropertyValue( + this.prepareCssKey(name) + ); + } + + /** + * set css var into element ... + * + * @param name css var name + * @param value css var value + * @param elements destination element of array of elements + */ + private setCssVar( + name: XThemeCssVarsIdentifier, + value: string, + element?: ElementRef + ) { + // + if (isNullOrEmptyString(name)) { + return; + } + + // + // Setting Variable on Element if exists ... + if (element && element.nativeElement) { + (element.nativeElement as HTMLElement).style.setProperty( + this.prepareCssKey(name), + value + ); + } + + // + // Setting Variable on root ... + document.documentElement.style.setProperty(this.prepareCssKey(name), value); + } + + private async setThemeColors(theme: XThemeColors, element?: ElementRef) { + // + if (isNullOrUndefined(theme)) { + return false; + } + + // + const colorKeys = keys(theme); + colorKeys.forEach((cKey) => { + // + const cssKey = this.ColorCssVars[cKey] as string; + const ionCssKey = cssKey.replace('x-', 'ion-'); + const cssValue = toNormalString(theme[cKey]); + + // + this.setCssVar(cssKey, cssValue, element); + this.setCssVar(ionCssKey, cssValue, element); + }); + + // + return true; + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkServicesHolder\projects\x-framework-services\src\lib\config\x-framework-services.config.ts + +import { XFrameworkCoreConfig } from 'x-framework-core'; + +// tslint:disable-next-line:no-empty-interface +export interface XFrameworkServicesConfig extends XFrameworkCoreConfig { + geoLocationConfig?: PositionOptions +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkServicesHolder\projects\x-framework-services\src\lib\directives\x-currency-formatter.directive.ts + +import { + Input, + Inject, + Renderer2, + Directive, + ElementRef, + forwardRef, + HostListener +} from '@angular/core'; +import { + XLocale, + removeAll, + applyLocale, + isNullOrEmptyString +} from 'x-framework-core'; +import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms'; +import { X_FRAMEWORK_SERVICES_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkServicesConfig } from '../config/x-framework-services.config'; + +export const X_CURRENCY_FORMATTER: any = { + useExisting: forwardRef(() => XCurrencyFormatterDirective), + provide: NG_VALUE_ACCESSOR, + multi: true +}; + +@Directive({ + // tslint:disable-next-line:directive-selector + selector: '[xCurrencyFormatter]', + providers: [X_CURRENCY_FORMATTER] +}) +export class XCurrencyFormatterDirective implements ControlValueAccessor { + // + //#region Props ... + @Input() + locale: XLocale; + + @Input() + applyLocaleToCurrency: boolean; + + @Input() + currencySeperator: string; + + @Input() + enabledCurrencyFormatter: boolean; + + onChange = (_: any) => {}; + + onTouched = () => {}; + //#endregion + + // + //#region Constructor ... + constructor( + private renderer: Renderer2, + private elementRef: ElementRef, + @Inject(X_FRAMEWORK_SERVICES_CONFIG) + private config: XFrameworkServicesConfig + ) {} + //#endregion + + // + //#region Host Bindings ... + @HostListener('change', ['$event.target.value']) + change(value: string) { + // + const res = this.getUnformattedValue(value); + this.onChange(res); + } + + @HostListener('input', ['$event.target.value']) + input(value: string) { + // + const res = this.getUnformattedValue(value); + this.onChange(res); + } + + @HostListener('blur') + onBlur() { + this.onTouched(); + } + + @HostListener('keydown', ['$event']) + onKeyDown(event: any) { + if (this.enabledCurrencyFormatter) { + this.setOnlyNumberType(event); + } + } + //#endregion + + // + //#region Implemenntation of Interface ... + writeValue(value: string): void { + this.setValue(value); + } + + registerOnChange(fn: (_: string | any | null) => void): void { + this.onChange = value => { + // + fn(this.enabledCurrencyFormatter ? +value : value); + this.setValue(value); + }; + } + + registerOnTouched(fn: () => void): void { + this.onTouched = fn; + } + + setDisabledState(isDisabled: boolean): void { + this.renderer.setProperty( + this.elementRef.nativeElement, + 'disabled', + isDisabled + ); + } + //#endregion + + // + //#region Private ... + private isElementValid() { + return this.elementRef && this.elementRef.nativeElement; + } + + private getFormattedValue(value: string) { + // + if (!this.enabledCurrencyFormatter || !value) { + return value || ''; + } + + // + if (this.applyLocaleToCurrency) { + value = applyLocale(value, this.locale || this.config.defaultLanguage); + } + + // + const result = value + .toString() + .replace(/\B(?=(\d{3})+(?!\d))/g, this.currencySeperator); + + // + return result; + } + + private getUnformattedValue(value: string) { + // + const result = removeAll(value, this.currencySeperator, true); + return result; + } + + private getNormalizedValue(value: string) { + // + if (!this.isElementValid()) { + return value; + } + + // + value = isNullOrEmptyString(value) ? '' : this.getUnformattedValue(value); + + // + if (this.enabledCurrencyFormatter) { + value = this.getFormattedValue(value); + } else { + value = this.getUnformattedValue(value); + } + + // + return value; + } + + private setValue(value: string) { + // + const normalizedValue = this.getNormalizedValue(value); + this.renderer.setProperty( + this.elementRef.nativeElement, + 'value', + normalizedValue + ); + } + + private setOnlyNumberType(e: any) { + // + const keyCode = e.code as string; + const isValidKey = + keyCode.includes('Digit') || keyCode.includes('Backspace'); + + // + if (!isValidKey) { + e.preventDefault(); + } + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkServicesHolder\projects\x-framework-services\src\lib\directives\x-date-presenter.directive.ts + +import { + XLocale, + formatDate, + getDateOfBirthDateFormatString, +} from 'x-framework-core'; +import { XLocalizationService } from '../x-localization.service'; +import { + Directive, + ElementRef, + OnInit, + Input, + OnChanges, + SimpleChanges, +} from '@angular/core'; + +@Directive({ + // tslint:disable-next-line:directive-selector + selector: '[xDatePresenter]', +}) +export class XDatePresenterDirective implements OnInit, OnChanges { + // + @Input() + date?: Date | string; + + @Input() + locale?: XLocale; + + @Input() + format?: string; + + @Input() + isUtc?: boolean; + + @Input() + toUtc?: boolean; + + constructor( + private element: ElementRef, + private localizationService: XLocalizationService + ) {} + + ngOnInit() { + this.presentDate(); + } + + ngOnChanges(changes: SimpleChanges): void { + this.presentDate(); + } + + private presentDate() { + // + if (!this.locale) { + this.locale = this.localizationService.locale; + } + + // + if (!this.date) { + this.date = new Date(); + } + + // + if (!this.format) { + this.format = getDateOfBirthDateFormatString(this.locale); + } + + // + if (!this.toUtc) { + this.toUtc = false; + } + + // + let dateStr: string; + try { + dateStr = formatDate( + this.date, + this.locale, + this.format, + this.isUtc, + this.toUtc + ); + } catch { + dateStr = '---'; + } + + // + this.element.nativeElement.innerHTML = dateStr; + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkServicesHolder\projects\x-framework-services\src\lib\directives\x-from-input-formatter.directive.ts + +import { + Input, + Inject, + Directive, + Renderer2, + ElementRef, + forwardRef, + HostListener, +} from '@angular/core'; +import { + XLocale, + hasChild, + removeAll, + getDigits, + toXString, + applyLocale, + getNonDigits, + removeSpaces, + isNullOrUndefined, + extractUniqueChars, + isNullOrEmptyString, +} from 'x-framework-core'; +import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms'; +import { X_FRAMEWORK_SERVICES_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkServicesConfig } from '../config/x-framework-services.config'; + +export const X_FORM_INPUT_FORMATTER: any = { + useExisting: forwardRef(() => XFromInputFormatterDirective), + provide: NG_VALUE_ACCESSOR, + multi: true, +}; + +export enum XFormInputFormatter { + ApplyLocale = 'apply_locale', + FormatCurrency = 'format_currency', + StringFormatter = 'string_formatter', + StringInputFormatter = 'string_input_formatter', + FormatCurrencyAndApplyLocale = 'format_currency_apply_locale', + StringFormatterAndApplyLocale = 'string_formatter_apply_locale', + StringInputFormatterAndApplyLocale = 'string_input_formatter_apply_locale', +} + +export type XFormInputFormatterIdentifier = XFormInputFormatter | string; + +export enum XStringInputFormatType { + None = 'none', + LowerCase = 'lowercase', + UpperCase = 'uppercase', + NonSpace = 'non_space', + NonDigits = 'none_digits', + OnlyDigits = 'only_digits', + UniqueChars = 'unique_chars', +} + +export type XStringInputFormatTypeIdentifier = XStringInputFormatType | string; + +@Directive({ + // tslint:disable-next-line:directive-selector + selector: '[xFromInputFormatter]', + providers: [X_FORM_INPUT_FORMATTER], +}) +export class XFromInputFormatterDirective implements ControlValueAccessor { + // + //#region Props ... + @Input() + locale: XLocale; + + @Input() + inputFormatterType: XFormInputFormatterIdentifier; + + @Input() + stringInputFormatterType: XStringInputFormatTypeIdentifier; + + @Input() + seperator: string; + + @Input() + currencyLable: string; + + @Input() + stringFormatter: number[]; + + @Input() + enabledInputFormatter: boolean; + + // + onChange = (_: any) => {}; + + onTouched = () => {}; + + onFocused = () => {}; + //#endregion + + // + //#region Constructor ... + constructor( + private renderer: Renderer2, + private elementRef: ElementRef, + @Inject(X_FRAMEWORK_SERVICES_CONFIG) + private config: XFrameworkServicesConfig + ) {} + //#endregion + + // + //#region Type Detectors ... + isCurrencyType() { + return ( + this.inputFormatterType === XFormInputFormatter.FormatCurrency || + this.inputFormatterType === + XFormInputFormatter.FormatCurrencyAndApplyLocale + ); + } + + isStringFormatterType() { + return ( + this.inputFormatterType === XFormInputFormatter.StringFormatter || + this.inputFormatterType === + XFormInputFormatter.StringFormatterAndApplyLocale || + this.inputFormatterType === XFormInputFormatter.StringInputFormatter || + this.inputFormatterType === + XFormInputFormatter.StringInputFormatterAndApplyLocale + ); + } + + isLocaleType() { + return ( + this.inputFormatterType === XFormInputFormatter.ApplyLocale || + this.inputFormatterType === + XFormInputFormatter.FormatCurrencyAndApplyLocale || + this.inputFormatterType === + XFormInputFormatter.StringFormatterAndApplyLocale || + this.inputFormatterType === + XFormInputFormatter.StringInputFormatterAndApplyLocale + ); + } + //#endregion + + // + //#region Host Bindings ... + @HostListener('change', ['$event.target.value']) + change(value: string) { + // + const res = this.getUnformattedValue(value); + this.onChange(res); + } + + @HostListener('input', ['$event.target.value']) + input(value: string) { + // + const res = this.getUnformattedValue(value); + this.onChange(res); + } + + @HostListener('focus') + onFocus() { + this.onFocused(); + if (this.isCurrencyType()) { + this.clearCurrencyLabel(); + } + } + + @HostListener('blur') + onBlur() { + this.onTouched(); + if (this.isCurrencyType()) { + this.applyCurrencyLabel(); + } + } + + @HostListener('keydown', ['$event']) + onKeyDown(event: any) { + // if (this.isCurrencyType()) { + // this.setOnlyNumberType(event); + // } + } + //#endregion + + // + //#region Implemenntation of Interface ... + writeValue(value: string): void { + this.setValue(value); + } + + registerOnChange(fn: (_: string | any | null) => void): void { + this.onChange = (value) => { + // + fn(this.isCurrencyType() ? +value : value); + + // + this.setValue(value); + }; + } + + registerOnTouched(fn: () => void): void { + this.onTouched = fn; + } + + setDisabledState(isDisabled: boolean): void { + // + if (isDisabled && this.isCurrencyType()) { + this.applyCurrencyLabel(); + } + + // + this.renderer.setProperty( + this.elementRef.nativeElement, + 'disabled', + isDisabled + ); + } + //#endregion + + // + //#region Private ... + private isElementValid() { + return this.elementRef && this.elementRef.nativeElement; + } + + private getFormattedValue(value: string) { + // + // Normallize Value ... + if (isNullOrEmptyString(value) || !this.enabledInputFormatter) { + return ''; + } + + // + // Prepared Formatted Value ... + let result = this.isCurrencyType() + ? this.getCurrencyFormattedValue(value) + : this.isStringFormatterType() + ? this.getStringFormattedValue(value) + : value || ''; + + // + // Apply Locale if Localeable Input Type ... + if (this.isLocaleType()) { + result = this.getLocaleFormattedValue(result); + } + + // + return result; + } + + private getUnformattedValue(value: string) { + // + let result = value.toString() || ''; + + // + const seperator = isNullOrEmptyString(this.seperator) ? '' : this.seperator; + + // + // Remove All occurence of seperator ... + if (this.enabledInputFormatter) { + result = removeAll(value, seperator, true); + } + + // + // Currency Lable ... + if (this.isCurrencyType() && !isNullOrEmptyString(this.currencyLable)) { + result = result.replace(this.currencyLable, '').trim(); + } + + // + // Change Locale to Default ... + if (this.isLocaleType()) { + result = !isNullOrEmptyString(result) ? applyLocale(result, 'en-US') : ''; + } + + // + return result; + } + + private getNormalizedValue(value: string) { + // + if (!this.isElementValid()) { + return value; + } + + // + // Normallize Value and prevent from null ... + let result = value ? value.toString() || '' : ''; + result = isNullOrEmptyString(value) ? '' : this.getUnformattedValue(value); + if (!this.enabledInputFormatter) { + return result; + } + + // + // Handle Apply Locale ... + if (this.inputFormatterType === XFormInputFormatter.ApplyLocale) { + // + result = applyLocale(value, this.locale || this.config.defaultLanguage); + return result; + } + + // + // Handle Formatter ... + if (this.isCurrencyType() || this.isStringFormatterType()) { + return this.getFormattedValue(result); + } + + // + return value; + } + + private setValue(value: string, normalizeValue = true) { + // + const normalizedValue = normalizeValue + ? this.getNormalizedValue(value) + : value || ''; + this.renderer.setProperty( + this.elementRef.nativeElement, + 'value', + normalizedValue + ); + } + + private getValue() { + return this.elementRef.nativeElement.value + ? (this.elementRef.nativeElement.value as string) + : ''; + } + + private setOnlyNumberType(e: any) { + // + const keyCode = e.code as string; + const numPadKeys = [...new Array(10).keys()].map((c, i) => `Numpad${i}`); + const isValidKey = + keyCode.includes('Tab') || + keyCode.includes('Digit') || + keyCode.includes('Backspace') || + numPadKeys.includes(keyCode); + + // + if (!isValidKey) { + e.preventDefault(); + } + } + + // + //#region Formatter Actions ... + private getLocaleFormattedValue(value: any) { + // + const mValue = value ? (value.toString() as string) : ''; + return applyLocale(mValue, this.locale || this.config.defaultLanguage); + } + + private getStringFormattedValue(value: any) { + // + const mValue = !isNullOrUndefined(value) ? toXString(value) : ''; + + // + let mResultValue = ''; + + // + if ( + this.inputFormatterType === XFormInputFormatter.StringFormatter || + this.inputFormatterType === + XFormInputFormatter.StringFormatterAndApplyLocale + ) { + // + if (!hasChild(this.stringFormatter)) { + return mValue; + } + + // + let mSource = mValue; + this.stringFormatter.forEach((l, i) => { + if (mSource.length >= l) { + // + const selected = mSource.substring(0, l); + + // + mResultValue += + i === this.stringFormatter.length + ? selected + : `${selected}${this.seperator}`; + + // + mSource = mSource.substr(l, mValue.length); + } + }); + + // + if (mSource.length > 0) { + mResultValue += `${mSource}`; + } + } else if ( + this.inputFormatterType === XFormInputFormatter.StringInputFormatter || + this.inputFormatterType === + XFormInputFormatter.StringInputFormatterAndApplyLocale + ) { + // + switch (this.stringInputFormatterType) { + // + case XStringInputFormatType.LowerCase: + mResultValue = mValue.toLowerCase(); + break; + + // + case XStringInputFormatType.UpperCase: + mResultValue = mValue.toUpperCase(); + break; + + // + case XStringInputFormatType.NonDigits: + // + mResultValue = getNonDigits(mValue); + break; + + // + case XStringInputFormatType.UniqueChars: + // + mResultValue = extractUniqueChars(mValue); + break; + + // + case XStringInputFormatType.NonSpace: + mResultValue = removeSpaces(mValue.trim()); + break; + + // + case XStringInputFormatType.OnlyDigits: + mResultValue = getDigits(mValue); + break; + + // + default: + mResultValue = mValue; + break; + } + } + + // + return mResultValue; + } + + private getCurrencyFormattedValue(value: any) { + // + const mValue = value ? getDigits(value.toString() as string) : ''; + const seperator = isNullOrEmptyString(this.seperator) ? '' : this.seperator; + const formattedValue = mValue.replace(/\B(?=(\d{3})+(?!\d))/g, seperator); + + // + const result = !isNullOrEmptyString(formattedValue) ? formattedValue : ''; + + // + return result; + } + + private applyCurrencyLabel() { + // + if (!this.isCurrencyType()) { + return; + } + + // + let value = this.getValue(); + + // + if ( + !isNullOrEmptyString(value) && + !isNullOrEmptyString(this.currencyLable) + ) { + value = `${value} ${this.currencyLable}`; + } + + // + this.setValue(value, false); + } + + private clearCurrencyLabel() { + // + if (!this.isCurrencyType()) { + return; + } + + // + let value = this.getValue(); + + // + if ( + !isNullOrEmptyString(value) && + !isNullOrEmptyString(this.currencyLable) + ) { + value = value.replace(this.currencyLable, ''); + } + + // + this.setValue(value); + } + //#endregion + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkServicesHolder\projects\x-framework-services\src\lib\directives\x-locale-input-formatter.directive.ts + +import { + Input, + Inject, + Directive, + Renderer2, + ElementRef, + forwardRef, + HostListener, +} from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { X_FRAMEWORK_SERVICES_CONFIG } from '../tokens/x-injectable-tokens'; +import { XLocale, applyLocale, isNullOrEmptyString } from 'x-framework-core'; +import { XFrameworkServicesConfig } from '../config/x-framework-services.config'; + +export const X_LOCALE_FORMATTER: any = { + useExisting: forwardRef(() => XLocaleInputFormatterDirective), + provide: NG_VALUE_ACCESSOR, + multi: true, +}; + +@Directive({ + // tslint:disable-next-line:directive-selector + selector: '[xLocaleInputFormatter]', + providers: [X_LOCALE_FORMATTER], +}) +export class XLocaleInputFormatterDirective implements ControlValueAccessor { + // + //#region Props ... + @Input() + locale: XLocale; + + @Input() + enabledLocaleFormatter: boolean; + + onChange = (_: any) => {}; + + onTouched = () => {}; + //#endregion + + // + //#region Constructor ... + constructor( + private renderer: Renderer2, + private elementRef: ElementRef, + @Inject(X_FRAMEWORK_SERVICES_CONFIG) + private config: XFrameworkServicesConfig + ) {} + //#endregion + + // + //#region Host Bindings ... + @HostListener('change', ['$event.target.value']) + change(value: string) { + this.onChange(value); + } + + @HostListener('input', ['$event.target.value']) + input(value: string) { + this.onChange(value); + } + + @HostListener('blur') + onBlur() { + this.onTouched(); + } + //#endregion + + // + //#region Implemenntation of Interface ... + writeValue(value: string): void { + this.setValue(value); + } + + registerOnChange(fn: (_: string | any | null) => void): void { + this.onChange = (value) => { + // + fn(value); + this.setValue(value); + }; + } + + registerOnTouched(fn: () => void): void { + this.onTouched = fn; + } + + setDisabledState(isDisabled: boolean): void { + this.renderer.setProperty( + this.elementRef.nativeElement, + 'disabled', + isDisabled + ); + } + //#endregion + + // + //#region Private ... + private isElementValid() { + return this.elementRef && this.elementRef.nativeElement; + } + + private getFormattedValue(value: string) { + // + if (!this.enabledLocaleFormatter || !value) { + return value || ''; + } + + // + value = applyLocale(value, this.locale || this.config.defaultLanguage); + + // + return value; + } + + private getNormalizedValue(value: string) { + // + if (!this.isElementValid()) { + return value; + } + + // + value = isNullOrEmptyString(value) ? '' : this.getFormattedValue(value); + + // + return value; + } + + private setValue(value: string) { + // + const normalizedValue = this.getNormalizedValue(value); + this.renderer.setProperty( + this.elementRef.nativeElement, + 'value', + normalizedValue + ); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkServicesHolder\projects\x-framework-services\src\lib\directives\x-swipable.directive.ts + +import { + Input, + OnInit, + Output, + OnChanges, + Directive, + OnDestroy, + ElementRef, + EventEmitter, + SimpleChanges, + AfterViewInit, +} from '@angular/core'; +import { Subject, Observable } from 'rxjs'; +import { keys, nameof } from 'x-framework-core'; +import { GestureController } from '@ionic/angular'; +import { Gesture, GestureDetail } from '@ionic/core'; +import { takeUntil, debounceTime } from 'rxjs/operators'; +import { XSettingsService } from '../x-settings.service'; + +export enum XSwipeDirection { + UP = 'up', + DOWN = 'down', + LEFT = 'left', + START = 'start', + RIGHT = 'right', + END = 'end', +} + +export type XSwipeDirectionIdentifier = XSwipeDirection | string; + +export enum XSwipeSupportedDirections { + VERTICALL, + HORIZONTAL_LEFT_RIGHT, + HORIZONTAL_START_END, + ALL, +} + +export interface XSwipeEvent { + deltaX: number; + deltaY: number; + direction: XSwipeDirectionIdentifier; +} + +@Directive({ + // tslint:disable-next-line:directive-selector + selector: '[xSwipable]', +}) +export class XSwipableDirective + implements OnInit, AfterViewInit, OnDestroy, OnChanges { + // + //#region Props ... + private gesture: Gesture; + private unsubscriber = new Subject(); + private issueSwipeEvents$ = new Subject(); + private uiDirection = this.settingsService.uiDirection; + //#endregion + + // + //#region Inputs ... + @Input() + threshold = 60; + + @Input() + swipeDisabled = false; + + @Input() + debounceTime = 300; + + @Input() + supportedDirections: XSwipeSupportedDirections = + XSwipeSupportedDirections.HORIZONTAL_START_END; + //#endregion + + // + //#region Event Emitters ... + @Output() + swipe = new EventEmitter(); + + @Output() + swipeUp = new EventEmitter(); + + @Output() + swipeDown = new EventEmitter(); + + @Output() + swipeLeft = new EventEmitter(); + + @Output() + swipeStart = new EventEmitter(); + + @Output() + swipeRight = new EventEmitter(); + + @Output() + swipeEnd = new EventEmitter(); + //#endregion + + // + //#region Constructor ... + constructor( + private elementRef: ElementRef, + private gestureCtrl: GestureController, + private settingsService: XSettingsService + ) {} + //#endregion + + // + //#region LifeCycles ... + ngOnInit() { + this.prepareGesture(); + } + + ngAfterViewInit() { + this.registerIssueSwipeHandler(); + } + + ngOnDestroy() { + this.destroyGesture(); + this.issueSwipeEvents$.complete(); + this.releaseUnsubscriber(); + } + + ngOnChanges(changes: SimpleChanges) { + // + const changeKesy = keys(changes); + + // + const isThresholdChanged = changeKesy.includes( + nameof('threshold') + ); + if (isThresholdChanged) { + this.prepareGesture(); + } + + // + const isDebounceTimeChanged = changeKesy.includes( + nameof('debounceTime') + ); + if (isDebounceTimeChanged) { + this.registerIssueSwipeHandler(); + } + } + //#endregion + + // + //#region Private ... + private prepareGesture() { + // + this.destroyGesture(); + + // + this.gesture = this.gestureCtrl.create({ + el: this.elementRef.nativeElement, + threshold: this.threshold, + gestureName: 'x-gesture', + onMove: (detail) => { + this.onMoveHandler(detail); + }, + }); + + // + this.gesture.enable(); + } + + private destroyGesture() { + // + if (this.gesture) { + // + this.gesture.destroy(); + this.gesture = null; + } + } + + private releaseUnsubscriber() { + // + if (!this.unsubscriber.closed) { + this.unsubscriber.next(); + this.unsubscriber.complete(); + } + } + + private applyTakeUntilWrapper(task: Observable): Observable { + return task.pipe(takeUntil(this.unsubscriber)); + } + + private registerIssueSwipeHandler() { + // + this.applyTakeUntilWrapper( + this.issueSwipeEvents$ + .asObservable() + .pipe(debounceTime(this.debounceTime)) + ).subscribe((gDetail) => { + // + if (this.swipeDisabled) { + return; + } + + // + const mSwipeEvent: Partial = { + deltaX: gDetail.deltaX, + deltaY: gDetail.deltaY, + }; + + // + // Handle Vertical ... + if ( + this.supportedDirections === XSwipeSupportedDirections.ALL || + this.supportedDirections === XSwipeSupportedDirections.VERTICALL + ) { + // + let swipeDir: XSwipeDirection; + + // + if (gDetail.deltaY >= 0) { + // + swipeDir = XSwipeDirection.DOWN; + + // + this.swipeDown.emit(); + } else { + // + swipeDir = XSwipeDirection.UP; + + // + this.swipeUp.emit(); + } + + // + // Handle Global Event ... + mSwipeEvent.direction = swipeDir; + this.swipe.emit(mSwipeEvent as XSwipeEvent); + } + + // + // Handle Horizontal Left Right ... + if ( + this.supportedDirections === XSwipeSupportedDirections.ALL || + this.supportedDirections === + XSwipeSupportedDirections.HORIZONTAL_LEFT_RIGHT + ) { + // + let swipeDir: XSwipeDirection; + + // + if (gDetail.deltaX >= 0) { + // + swipeDir = XSwipeDirection.RIGHT; + + // + this.swipeRight.emit(); + } else { + // + swipeDir = XSwipeDirection.LEFT; + + // + this.swipeLeft.emit(); + } + + // + // Handle Global Event ... + mSwipeEvent.direction = swipeDir; + this.swipe.emit(mSwipeEvent as XSwipeEvent); + } + + // + // Handle Horizontal Start End ... + if ( + this.supportedDirections === XSwipeSupportedDirections.ALL || + this.supportedDirections === + XSwipeSupportedDirections.HORIZONTAL_START_END + ) { + // + let swipeDir: XSwipeDirection; + + // + if (gDetail.deltaX >= 0) { + // + if (this.uiDirection === 'ltr') { + // + swipeDir = XSwipeDirection.END; + + // + this.swipeEnd.emit(); + } else { + // + swipeDir = XSwipeDirection.START; + + // + this.swipeStart.emit(); + } + } else { + // + if (this.uiDirection === 'ltr') { + // + swipeDir = XSwipeDirection.START; + + // + this.swipeStart.emit(); + } else { + // + swipeDir = XSwipeDirection.END; + + // + this.swipeEnd.emit(); + } + } + + // + // Handle Global Event ... + mSwipeEvent.direction = swipeDir; + this.swipe.emit(mSwipeEvent as XSwipeEvent); + } + }); + } + + private onMoveHandler(ev: GestureDetail) { + // + if (this.swipeDisabled) { + return; + } + + // + this.issueSwipeEvents$.next(ev); + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkServicesHolder\projects\x-framework-services\src\lib\modules\x-directives.module.ts + +import { NgModule } from '@angular/core'; +import { IonicModule } from '@ionic/angular'; +import { CommonModule } from '@angular/common'; +import { XTranslatePipe } from '../pipes/x-translate.pipe'; +import { XSwipableDirective } from '../directives/x-swipable.directive'; +import { XDatePresenterDirective } from '../directives/x-date-presenter.directive'; +import { XCurrencyFormatterDirective } from '../directives/x-currency-formatter.directive'; +import { XFromInputFormatterDirective } from '../directives/x-from-input-formatter.directive'; +import { XLocaleInputFormatterDirective } from '../directives/x-locale-input-formatter.directive'; + +@NgModule({ + declarations: [ + // + // Pipes ... + XTranslatePipe, + + // + // Directives + XSwipableDirective, + XDatePresenterDirective, + XCurrencyFormatterDirective, + XFromInputFormatterDirective, + XLocaleInputFormatterDirective + ], + imports: [CommonModule, IonicModule], + exports: [ + // + CommonModule, + IonicModule, + + // + // Pipes ... + XTranslatePipe, + + // + // Directives + XSwipableDirective, + XDatePresenterDirective, + XCurrencyFormatterDirective, + XFromInputFormatterDirective, + XLocaleInputFormatterDirective + ] +}) +export class XDirectivesModule {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkServicesHolder\projects\x-framework-services\src\lib\pipes\x-translate.pipe.ts + +import { + XLocale, + toNumber, + isString, + XResourceIDs, + XTranslationItem +} from 'x-framework-core'; +import { Pipe, PipeTransform } from '@angular/core'; +import { XLocalizationService } from '../x-localization.service'; + +@Pipe({ + name: 'xTranslate' +}) +export class XTranslatePipe implements PipeTransform { + // + constructor(private localizationService: XLocalizationService) {} + + /** + * translate a resource to specific language in a pipe + * + * @param key the resource identifier + * @param locale the selected or default locale + */ + transform( + key: XResourceIDs | XTranslationItem | string, + locale?: XLocale + ): string { + // + if (!locale) { + locale = this.localizationService.locale; + } + + // + const isStringKey = isString(key); + if (!isStringKey) { + key = toNumber(key) as XResourceIDs; + } + + // + const isExist = isStringKey + ? this.localizationService.isExistsIdOfLocale(locale, key as string) + : this.localizationService.isExistsIdOfLocale( + locale, + key as XResourceIDs + ); + if (!isExist) { + return ''; + } + + // + return isStringKey + ? this.localizationService.getValueOfLocale(locale, key as string) + : this.localizationService.getValueOfLocale(locale, key as XResourceIDs); + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkServicesHolder\projects\x-framework-services\src\lib\tokens\x-injectable-tokens.ts + +import { InjectionToken } from '@angular/core'; +import { XFrameworkServicesConfig } from '../config/x-framework-services.config'; + +export const X_FRAMEWORK_SERVICES_CONFIG = new InjectionToken< + XFrameworkServicesConfig +>('x_config'); + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkServicesHolder\projects\x-framework-services\src\lib\x-manager-service-partial\x-manager-base.service.ts + +import { Location } from '@angular/common'; +import { XBaseService } from 'x-framework-core'; +import { Platform } from '@angular/cdk/platform'; +import { HttpClient } from '@angular/common/http'; +import { Inject, Injectable } from '@angular/core'; +import { XDialogService } from '../x-dialog.service'; +import { XHistoryService } from '../x-history.service'; +import { XStorageService } from '../x-storage.service'; +import { XSecurityService } from '../x-security.service'; +import { Router, ActivatedRoute } from '@angular/router'; +import { XSettingsService } from '../x-settings.service'; +import { MenuController, NavController } from '@ionic/angular'; +import { XGeolocationService } from '../x-geolocation.service'; +import { XTaskManagerService } from '../x-task-manager.service'; +import { XNotificationService } from '../x-notification.service'; +import { XLocalizationService } from '../x-localization.service'; +import { XThemeManagerService } from '../x-theme-manager.service'; +import { XSecureStorageService } from '../x-secure-storage.service'; +import { X_FRAMEWORK_SERVICES_CONFIG } from '../tokens/x-injectable-tokens'; +import { XFrameworkServicesConfig } from '../config/x-framework-services.config'; + +@Injectable() +export abstract class XManagerBaseService extends XBaseService { + constructor( + @Inject(X_FRAMEWORK_SERVICES_CONFIG) + public config: XFrameworkServicesConfig, + public router: Router, + public platform: Platform, + public location: Location, + public httpClient: HttpClient, + public dialogService: XDialogService, + public menuController: MenuController, + public activatedRoute: ActivatedRoute, + public historyService: XHistoryService, + public storageService: XStorageService, + public securityService: XSecurityService, + public settingsService: XSettingsService, + public navigationController: NavController, + public geoLocationService: XGeolocationService, + public taskManagerService: XTaskManagerService, + public themeManagerService: XThemeManagerService, + public notificationService: XNotificationService, + public localizationService: XLocalizationService, + public secureStorageService: XSecureStorageService + ) { + super(config); + } +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkServicesHolder\projects\x-framework-services\src\lib\x-manager-service-partial\x-manager-final.service.ts + +import { XManagerRouteService } from './x-manager-route.service'; + +export abstract class XManagerFinalService extends XManagerRouteService {} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkServicesHolder\projects\x-framework-services\src\lib\x-manager-service-partial\x-manager-route.service.ts + +import { + keys, + XPage, + toArray, + fromJson, + hasChild, + XRouterTarget, + XOneOrManyType, + XParamIdentifier, + isNullOrEmptyString, +} from 'x-framework-core'; +import { XManagerUIStateService } from './x-manager-ui-state.service'; +import { Params, UrlTree, ParamMap, NavigationExtras } from '@angular/router'; + +export abstract class XManagerRouteService extends XManagerUIStateService { + // + //#region Props ... + // + public routeDataChanged$ = this.router.routerState.root.data; + public routeParmChanged$ = this.router.routerState.root.params; + public routeQueryParmChanged$ = this.router.routerState.root.queryParams; + //#endregion + + // + //#region Route Related Actions ... + /** + * some times we had to force change location ... + * + * @param route the specified route + */ + changeLocation(route?: string) { + window.location.href = this.getFullUrl(route); + } + + /** + * retrieve base url (Origin) address + */ + getBaseUrl() { + return window.location.origin; + } + + /** + * retrieve current route + */ + getRoute() { + return this.router && this.router.url ? this.router.url : '/'; + } + + /** + * merge several parts of a url ... + * + * @param route an instance of url tree array ... + */ + mergeRoutes(route: XOneOrManyType) { + // + if (!hasChild(route)) { + return '/'; + } + + // + try { + return toArray(route).join('/').replace('//', '/'); + } catch {} + + // + return '/'; + } + + /** + * retrieve page route ... + * + * @param page an instance of XPage ... + */ + getPageRoute(page: XPage) { + // + if (!page || !hasChild(page.route)) { + return null; + } + + // + return this.mergeRoutes(page.route); + } + + /** + * generate and retrieve the fully qualified URL address of + * specified route + * + * @param route the selected app route + */ + getFullUrl(route?: string) { + // + const mBase = this.getBaseUrl().endsWith('/') + ? this.getBaseUrl() + : `${this.getBaseUrl()}/`; + const mRoute = !route + ? '' + : !route.startsWith('/') + ? route + : route.substring(1); + + // + return `${mBase}${mRoute}`; + } + + /** + * navigate to specific location ... + * + * @param commands url or url tree parts which required to navigate + * @param extras the navigation params or options + */ + navigate(commands: any[], extras?: NavigationExtras) { + return this.router.navigate(commands, extras); + } + + /** + * navigate to specific location ... + * + * @param commands url or url tree parts which required to navigate + * @param extras the navigation params or options + * @param replaceUrl determines the url replaced or not + */ + navigateReplace( + commands: any[], + extras?: NavigationExtras, + replaceUrl: boolean = true + ) { + return this.router.navigate(commands, { ...extras, replaceUrl }); + } + + /** + * navigate to specific location by it's url ... + * + * @param url specific url address for navigating + * @param extras the navigation params or options + */ + navigateByUrl(url: string | UrlTree, extras?: NavigationExtras) { + return this.router.navigate([url], extras); + } + + /** + * navigate to specific location by it's url ... + * + * @param url specific url address for navigating + * @param extras the navigation params or options + * @param replaceUrl determines the url replaced or not + */ + navigateByUrlReplace( + url: string | UrlTree, + extras?: NavigationExtras, + replaceUrl: boolean = true + ) { + return this.router.navigate([url], { ...extras, replaceUrl }); + } + + /** + * navigate to Specific Page ... + * + * @param page an instance of XPage + * @param extras the navigation params or options + */ + navigateByPage(page: XPage, extras?: NavigationExtras) { + // + const route = this.getPageRoute(page); + if (isNullOrEmptyString(route)) { + return false; + } + + // + return this.navigateByUrl(route, extras); + } + + /** + * navigate to Specific Page ... + * + * @param page an instance of XPage + * @param extras the navigation params or options + * @param replaceUrl determines the url replaced or not + */ + navigateByPageReplace( + page: XPage, + extras?: NavigationExtras, + replaceUrl: boolean = true + ) { + // + const route = this.getPageRoute(page); + if (isNullOrEmptyString(route)) { + return false; + } + + // + return this.navigateByUrl(route, { ...extras, replaceUrl }); + } + + /** + * Applies an array of commands to the current URL tree and creates a new URL tree. + * + * When given an activated route, applies the given commands starting from the route. + * Otherwise, applies the given command starting from the root. + * + * @param commands An array of commands to apply. + * @param navigationExtras Options that control the navigation strategy. + * @returns The new URL tree. + */ + createUrlTree(commands: any[], navigationExtras?: NavigationExtras): UrlTree { + return this.router.createUrlTree(commands, navigationExtras); + } + + /** Serializes a `UrlTree` into a string */ + serializeUrl(url: UrlTree): string { + return this.router.serializeUrl(url); + } + + /** Parses a string into a `UrlTree` */ + parseUrl(url: string): UrlTree { + return this.router.parseUrl(url); + } + + /** Returns whether the url is activated */ + isActive(url: string | UrlTree, exact: boolean): boolean { + return this.router.isActive(url, exact); + } + + /** + * open specifc url ... + * + * @param url URL address + * @param target link taget + */ + openUrl(url: string, target: XRouterTarget = XRouterTarget.BLANK) { + // + if (isNullOrEmptyString(url)) { + return; + } + + // + const link = document.createElement('a'); + link.target = target; + link.href = url; + + // + link.click(); + link.remove(); + } + //#endregion + + // + //#region Handling Query Param actions ... + /** + * change page Query Params ... + * @param params Query Parameters + */ + changeQueryParams(params: Params) { + // + return this.router.navigate([], { + relativeTo: this.activatedRoute, + queryParams: params, + queryParamsHandling: 'merge', + }); + } + + clearQueryParams() { + // + return this.router.navigate([], { + relativeTo: this.activatedRoute, + queryParams: {}, + }); + } + + /** + * Get CurrentPages Query Params ... + */ + getQueryParams(): Params { + return this.router.routerState.snapshot.root.queryParams; + } + + getQueryParamMap(): ParamMap { + return this.router.routerState.snapshot.root.queryParamMap; + } + + isContainQueryParam(param: XParamIdentifier) { + return this.getQueryParamMap().has(param); + } + + getQueryParamVal(param: XParamIdentifier) { + // + if (!this.isContainQueryParam(param)) { + return null; + } + + // + return this.getQueryParamMap().get(param); + } + + getQueryParamValOfType(param: XParamIdentifier) { + // + if (!this.isContainQueryParam(param)) { + return null; + } + + // + return fromJson(this.getQueryParamMap().get(param)); + } + //#endregion + + // + //#region Handling Param actions ... + /** + * Get CurrentPages Query Params ... + */ + getParams(): Params { + return this.router.routerState.snapshot.root.params; + } + + getParamMap(): Params { + return this.router.routerState.snapshot.root.paramMap; + } + + isContainParam(param: XParamIdentifier) { + return this.getParamMap().has(param); + } + + changeParam(params: Params) { + // + return this.router.navigate(['', params], { + relativeTo: this.activatedRoute, + replaceUrl: true, + }); + } + + getParamVal(param: XParamIdentifier) { + // + if (!this.isContainParam(param)) { + return null; + } + + // + return this.getParamMap().get(param); + } + + getParamValOfType(param: XParamIdentifier) { + // + if (!this.isContainParam(param)) { + return null; + } + + // + return fromJson(this.getParamMap().get(param)); + } + //#endregion + + // + //#region State Handling ... + /** + * change page state ... + * @param params State Parameters + */ + changeState(params: Params) { + // + return this.router.navigate([], { + relativeTo: this.activatedRoute, + replaceUrl: true, + state: params, + }); + } + + getStates(): Params { + return this.router.getCurrentNavigation().extras.state || {}; + } + + isContainState(param: XParamIdentifier) { + // + const paramKeys = keys(this.getStates()); + const result = paramKeys.includes(param); + + // + return result; + } + + getStateVal(param: XParamIdentifier) { + // + if (!this.isContainState(param)) { + return null; + } + + // + return this.getStates()[param] || null; + } + + getStateValOfType(param: XParamIdentifier) { + // + if (!this.isContainState(param)) { + return null; + } + + // + return fromJson(this.getStateVal(param)) || null; + } + //#endregion +} + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkServicesHolder\projects\x-framework-services\src\lib\x-manager-service-partial\x-manager-ui-state.service.ts + +import { + XLocale, + XUiEvent, + notValue, + XException, + XLanguageDirection, + isNullOrEmptyString, +} from 'x-framework-core'; +import { Subject, of } from 'rxjs'; +import { filter, concatMap } from 'rxjs/operators'; +import { XManagerBaseService } from './x-manager-base.service'; + +export abstract class XManagerUIStateService extends XManagerBaseService { + // + //#region props ... + private IS_LOADING: boolean; + private UI_DISABLED: boolean; + private ERROR: XException = null; + + // + public uiStateChanged = new Subject(); + + // + public isMobileUi$ = this.settingsService.isMobile$; + public isNotMobileUi$ = notValue(this.settingsService.isMobile$); + + // + public isLoading$ = this.uiStateChanged.asObservable().pipe( + filter((uiState) => + [XUiEvent.StartLoading, XUiEvent.StopLoading].includes(uiState) + ), + concatMap((state) => { + return of(state === XUiEvent.StartLoading ? true : false); + }) + ); + //#endregion + + // + //#region Getter(s) ... + set uiDisabled(v: boolean) { + // + if (v === this.UI_DISABLED) { + return; + } + + this.UI_DISABLED = v; + this.uiStateChanged.next(v ? XUiEvent.Disabled : XUiEvent.Enabled); + } + + // + get uiDisabled() { + return this.UI_DISABLED; + } + + // + set loading(v: boolean) { + // + if (v === this.IS_LOADING) { + return; + } + + // + this.IS_LOADING = v; + this.uiStateChanged.next(v ? XUiEvent.StartLoading : XUiEvent.StopLoading); + } + + // + get isLoading() { + return this.IS_LOADING; + } + + // + set error(exception: XException | null) { + // + if (exception === this.ERROR) { + return; + } + + // + this.ERROR = exception; + + // + if (exception) { + // + if (this.loading) { + this.loading = false; + } + + // + this.uiDisabled = true; + } else { + // + this.uiDisabled = false; + } + } + + // + get error() { + return this.ERROR; + } + + // + get hasError() { + return !!this.ERROR; + } + + // + get uiDirection(): XLanguageDirection { + return this.settingsService.uiDirection; + } + + // + get currentLanguage() { + return this.localizationService.language; + } + + // + get currentLocale(): XLocale { + return this.localizationService.locale; + } + + // + get isRtl() { + return this.settingsService.uiDirection === 'rtl'; + } + + // + get isLtr() { + return this.settingsService.uiDirection === 'ltr'; + } + //#endregion + + // + //#region UI Actions ... + // + public dispatchResizeEvent() { + // + window.dispatchEvent(new Event('resize')); + setTimeout(() => window.dispatchEvent(new Event('resize')), 500); + } + + /** + * save a text value to clipboard ... + * + * @param value the content which reuired to save to clipboard ... + */ + public saveToClipboard(value: string) { + // + if (isNullOrEmptyString(value)) { + return false; + } + + // + if ( + document.queryCommandSupported && + document.queryCommandSupported('copy') + ) { + // + const textarea = document.createElement('textarea'); + textarea.textContent = value; + // Prevent scrolling to bottom of page in Microsoft Edge. + textarea.style.position = 'fixed'; + document.body.appendChild(textarea); + textarea.select(); + + // + try { + // Security exception may be thrown by some browsers. + return document.execCommand('copy'); + } catch (ex) { + return false; + } finally { + document.body.removeChild(textarea); + } + } + + // + return false; + } + //#endregion + + // + //#region Private ... + //#endregion +} diff --git a/Documents/Scripts/nugetModuleHandler.js b/Documents/Scripts/nugetModuleHandler.js new file mode 100644 index 0000000..7c40e5e --- /dev/null +++ b/Documents/Scripts/nugetModuleHandler.js @@ -0,0 +1,1048 @@ +const path = require("path"); +const execSync = require("child_process").execSync; + +// +// Check Os Type is Windows or not ... +var isWin = process.platform === "win32"; + +// +// Prepare Exec Path ... +const currentPath = __dirname; +const execPathSource = path.join(currentPath, "..", ".."); + +// +let execPath = execPathSource; +if (isWin) { + execPath = execPathSource; +} else { + execPath = replaceAll(" ", execPathSource, "\\ "); +} + +// +const cmdSeparator = isWin ? "&" : "&&"; +const argPrefix = "**"; + +// +// Default Module Versions ... +const defaultModuleVersiom = "1.0.0"; + +// +// Define Modules Descriptors by order ... +const supportedModules = [ + // + // xExceptions ... + { + name: "xExceptions", + cmdPrefix: "Exceptions", + version: "", + }, + // + // xCommons ... + { + name: "xCommons", + cmdPrefix: "Commons", + version: "", + }, + // + // xModels ... + { + name: "xModels", + cmdPrefix: "Models", + version: "", + }, + // + // xMessageService ... + { + name: "xMessageService", + cmdPrefix: "MessageService", + version: "", + }, + // + // xStorageService ... + { + name: "xStorageService", + cmdPrefix: "StorageService", + version: "", + }, + // + // xDataService ... + { + name: "xDataService", + cmdPrefix: "DataService", + version: "", + }, + // + // xIdentityModels ... + { + name: "xIdentityModels", + cmdPrefix: "IdentityModels", + version: "", + }, + // + // xHttpService ... + { + name: "xHttpService", + cmdPrefix: "HttpService", + version: "", + }, + // + // xIdentityService ... + { + name: "xIdentityService", + cmdPrefix: "IdentityService", + version: "", + }, + // + // xPushService ... + { + name: "xPushService", + cmdPrefix: "PushService", + version: "", + }, + // + // xEventService ... + { + name: "xEventService", + cmdPrefix: "EventService", + version: "", + }, + // + // xStringService ... + { + name: "xStringService", + cmdPrefix: "StringService", + version: "", + }, + // + // xTermsAndConditionsService ... + { + name: "xTermsAndConditionsService", + cmdPrefix: "TermsAndConditionsService", + version: "", + }, + // + // xCategoryService ... + { + name: "xCategoryService", + cmdPrefix: "CategoryService", + version: "", + }, + // + // xTagService ... + { + name: "xTagService", + cmdPrefix: "TagService", + version: "", + }, + // + // xFileService ... + { + name: "xFileService", + cmdPrefix: "FileService", + version: "", + }, + // + // xWebinarService ... + { + name: "xWebinarService", + cmdPrefix: "WebinarService", + version: "", + }, + // + // xAiModels ... + { + name: "xAiModels", + cmdPrefix: "AiModels", + version: "", + }, + // + // xAiService ... + { + name: "xAiService", + cmdPrefix: "AiService", + version: "", + }, +]; + +// +// Define Per Module Actions ... +const supportedActions = [ + { + name: "clean", + description: "Clean Project ...", + placeHolder: "dotnet clean ./Modules/MODULE_NAME/MODULE_NAME.csproj", + }, + { + name: "restore", + description: "Restore Project Dependencies ...", + placeHolder: "dotnet restore ./Modules/MODULE_NAME/MODULE_NAME.csproj", + }, + { + name: "build", + description: "Build Project ...", + placeHolder: "dotnet build ./Modules/MODULE_NAME/MODULE_NAME.csproj", + }, + { + name: "rmBin", + description: "Remove bin folder of Project ...", + placeHolder: "shx --silent rm -rf ./Modules/MODULE_NAME/bin", + }, + { + name: "clearPacked", + description: "Removes Module Packed file from pack folder ...", + placeHolder: "shx --silent rm ./Documents/pack/xDashboard.MODULE_NAME.*", + }, + { + name: "pack", + description: "Pack Project as a Nuget Package ...", + placeHolder: + "dotnet pack -c Release ./Modules/MODULE_NAME/MODULE_NAME.csproj", + }, + { + name: "cp", + description: "Copy Project Packed file to pack folder ...", + placeHolder: + "shx --silent cp ./Modules/MODULE_NAME/bin/Release/*.nupkg ./Documents/pack", + }, + { + name: "nugetPublish", + description: "Push Project Nuget Package's to Server ...", + placeHolder: + "npm run nugetHandler push ./Documents/pack/xDashboard.MODULE_NAME.MODULE_VERSION.nupkg", + }, + { + name: "nugetDelete", + description: "Remove Nuget Package's from Server ...", + placeHolder: + "npm run nugetHandler delete xDashboard.MODULE_NAME MODULE_VERSION", + }, +]; + +// +// Compound Actions ... +// these type do multiple actions on a single module ... +const supportedCompoundActions = [ + { + name: "repack", + description: "repack a module as nuget package on pack folder ...", + actions: ["rmBin", "build", "pack", "cp"], + }, + { + name: "publish", + description: "publish a module as nuget package on nuget server ...", + actions: [ + "rmBin", + "pack", + "cp", + "nugetPublish", + "npm run clearNugetTempHttpCache", + "npm run rmDashboardNugets", + ], + }, + { + name: "rePublish", + description: "republish a module as nuget package on nuget server ...", + actions: [ + "nugetDelete", + "rmBin", + "pack", + "cp", + "nugetPublish", + "npm run clearNugetTempHttpCache", + "npm run rmDashboardNugets", + ], + }, +]; + +// +// Supported Cli Commands ... +const supportedCommands = [ + // + // 1 + { + arg: "help", + description: "show command helps ...", + }, + // + // 2 + { + arg: "ls-commands", + description: "show a list of supported commands ...", + }, + // + // 3 + { + arg: "list-commands", + description: "show a list of supported commands ...", + }, + // + // 4 + { + arg: "ls-modules", + description: "show a list of supported modules ...", + }, + // + // 5 + { + arg: "list-modules", + description: "show a list of supported modules ...", + }, + // + // 6 + { + arg: "get", + description: "retrieve an specific command text for specific module ...", + }, + // + // 7 + { + arg: "get-script", + description: + "retrieve an specific script node of specific command for specific module ...", + }, + // + // 8 + { + arg: "do", + description: "run an specific command text for specific module ...", + }, + // + // TODO: Remove this ... + // 9 + // { + // arg: "do-on", + // description: + // "do an specific action on all exists or specified comma seperated modules ...", + // }, + // + // 10 + // { + // arg: "get-do-on", + // description: + // "get specific action command on all exists or specified comma seperated modules ...", + // }, + // + // 11 + { + arg: "module-scripts", + description: "generate an scripts list for specific module ...", + }, + // + // 12 + { + arg: "m-scripts", + description: "generate an scripts list for specific module ...", + }, + // + // 13 + { + arg: "scripts", + description: "generate an scripts list for all modules ...", + }, +]; + +// +//#region Startup ... +// +// Start App ... +const cmdCommands = extractCMDCommands(); +if (cmdCommands == null || cmdCommands.length == 0) { + doCMD({ arg: "help", index: 0 }); +} else { + for (var cmd of cmdCommands) { + doCMD(cmd); + } +} +//#endregion + +// +//#region Providers ... +/** + * each supported cmd args must start by argPrefix to be processed, this is a method to clear it from specific value ... + * @param {string} arg the arg text ... + * @returns normallize arg string ... + */ +function normallizedArg(arg) { + return `${arg}`.replace(argPrefix, ""); +} + +/** + * search all occurance of specific value on an string and replace it with specified string ... + * @param {string} searchString the value which need to changed ... + * @param {string} source the placeholder value for searching ... + * @param {string} replaceWith a value which going to replaced with searchValue ... + * @returns string + */ +function replaceAll(searchString, source, replaceWith) { + // + let result = `${source}` + .split(searchString) + .map((p) => + p.indexOf(searchString) > -1 ? p.replace(searchString, replaceWith) : p + ) + .map((a, i, arr) => (i < arr.length - 1 ? a + replaceWith : a)) + .join(""); + + // + return result; +} + +/** + * retrieve all available cmd args ... + * @returns an string array ... + */ +function getCMDs() { + return supportedCommands.map((sc) => sc.arg); +} + +/** + * check an specific arg is in supported commands list or not ... + * @param {string} cmd the command arg + * @returns boolean + */ +function isCMDExists(cmd) { + // + cmd = normallizedArg(cmd); + return getCMDs().includes(cmd); +} + +/** + * retrieve all inputted args object models ... + * @returns an array of cmd arg object models ... + */ +function getReceivedArgs() { + return process.argv.map((arg, index) => { + return { + arg: + arg.endsWith("/") || arg.endsWith("\\") + ? arg.substr(0, arg.length - 1) + : arg, + index: index, + }; + }); +} + +/** + * retrieve all exists and supported cmd args from received args ... + * @returns an array of cmd arg object models ... + */ +function extractCMDCommands() { + return getReceivedArgs() + .filter( + (a) => + a.arg.length > 2 && a.arg.startsWith(argPrefix) && isCMDExists(a.arg) + ) + .map((a) => { + return { arg: normallizedArg(a.arg), index: a.index }; + }); +} + +/** + * check a module exists on system supported modules or not ... + * @param {string} module system supported module name ... + * @returns boolean + */ +function isModuleExists(module) { + return supportedModules.findIndex((a) => a.name == module) >= 0; +} + +/** + * get supported module model object, if exists ... + * + * @param {string} module system supported module name ... + * @param {string} version module version ... + * @returns an object which represent moduleModel ... + */ +function getModule(module, version) { + // + let result = supportedModules.find((a) => a.name == module); + if (result) { + result = { + ...result, + version: version || defaultModuleVersiom, + }; + } + + // + return result; +} + +/** + * retrieve just module name ... + * + * @param {string} moduleName desired module name ... + * @returns cleared version module name ... + */ +function getModuleName(moduleName) { + // + if (!moduleName || moduleName === "") { + return moduleName; + } + + // + if (!moduleName.includes("@")) { + return moduleName; + } + + // + const parts = moduleName.split("@"); + if (!parts || parts.length < 2) { + return moduleName; + } + + // + return parts[0]; +} + +/** + * retrieve module versionm ... + * + * @param {string} moduleName desired module name ... + * @returns module version or default module version ... + */ +function getModuleVersion(moduleName) { + // + if (!moduleName || moduleName === "") { + return defaultModuleVersiom; + } + + // + if (!moduleName.includes("@")) { + return defaultModuleVersiom; + } + + // + const parts = moduleName.split("@"); + if (!parts || parts.length < 2) { + return defaultModuleVersiom; + } + + // + return parts[1]; +} + +/** + * get npm script object model for specific single action on specific module ... + * @param {string} action system supported single action name ... + * @param {string} module system supported module name ... + * @param {string} version module version ... + * @returns script descriptor object model, null ... + */ +function getScript(action, module, version) { + // + const command = getCommand(action, module); + + // + const actionModel = getAction(action); + if (!actionModel) { + throw "Invalid Action: " + action; + } + + // + const moduleModel = getModule(module, version); + + // + return { + name: actionModel.name + moduleModel.cmdPrefix, + cmd: command, + }; +} + +/** + * get npm script object model for specific compound action on specific module ... + * @param {string} action system supported compound action name ... + * @param {string} module system supported module name ... + * @param {string} version module version ... + * @param {bool} byScriptName determines extracted command based on script name or raw command ... + * @returns script descriptor object model, null ... + */ +function getCompoundScript(action, module, version, byScriptNames) { + // + const command = getCompoundCommand(action, module, version, byScriptNames); + + // + const actionModel = getCompoundAction(action); + if (!actionModel) { + throw "Invalid Action: " + action; + } + + // + const moduleModel = getModule(module, version); + + // + return { + name: actionModel.name + moduleModel.cmdPrefix, + cmd: command, + }; +} + +/** + * get supported single action model object, if exists ... + * @param {string} action system supported action name ... + * @returns an object which represent actionModel ... + */ +function getAction(action) { + return supportedActions.find((a) => a.name == action); +} + +/** + * get supported compound action model object, if exists ... + * @param {string} action system supported coumpound action name ... + * @returns an object which represent actionModel ... + */ +function getCompoundAction(action) { + return supportedCompoundActions.find((a) => a.name == action); +} + +/** + * generate command for specific single action on specific module ... + * @param {string} action system supported action name ... + * @param {string} module system supported module name ... + * @param {string} version module version ... + * @param {bool} byScriptName determines extracted command based on script name or raw command ... + * @returns command string ... + */ +function getCommand(action, module, version, byScriptName = false) { + // + const actionExists = isActionExists(action); + if (!actionExists) { + return action; + } + + // + const actionModel = getAction(action); + if (module) { + // + const moduleExists = isModuleExists(module); + if (!moduleExists) { + throw "Module not found: " + module; + } + } + + // + const moduleModel = getModule(module, version); + + // + let result = ""; + + // + result = byScriptName + ? `npm run ${getScript(action, module).name}` + : replaceAll("MODULE_NAME", actionModel.placeHolder, moduleModel.name); + if (actionModel.name == "nugetPublish" || actionModel.name == "nugetDelete") { + result = result.replace("MODULE_VERSION", moduleModel.version); + } + + // + return result; +} + +/** + * generate command for specific compound action on specific module ... + * @param {string} action system supported coumpound action name ... + * @param {string} module system supported module name ... + * @param {string} version module version ... + * @param {bool} byScriptName determines extracted command based on script name or raw command ... + * @returns command string ... + */ +function getCompoundCommand(action, module, version, byScriptNames = false) { + // + const actionExists = isCompoundActionExists(action); + if (!actionExists) { + throw "Compund Action Not Found: " + action; + } + + // + const moduleExists = isModuleExists(module); + if (!moduleExists) { + throw "Module not found: " + module; + } + + // + const actionModel = getCompoundAction(action); + + // + let result = ""; + actionModel.actions.forEach((a) => { + // + const cmd = getCommand(a, module, version); + const script = isActionExists(a) ? getScript(a, module, version) : null; + + // + if (result.length > 0) { + result = result + ` ${byScriptNames ? "&&" : cmdSeparator} `; + } + + // + result += byScriptNames + ? script == null + ? cmd + : "npm run " + script.name + : cmd; + }); + + // + return result; +} + +/** + * check an action exists in supported single actions or not ... + * @param {string} action system supported action name ... + * @returns boolean + */ +function isActionExists(action) { + return supportedActions.findIndex((a) => a.name == action) >= 0; +} + +/** + * check an action exists in supported compound actions or not ... + * @param {string} action system supported compound action name ... + * @returns boolean + */ +function isCompoundActionExists(action) { + return supportedCompoundActions.findIndex((a) => a.name == action) >= 0; +} + +/** + * do specific cmd arg command ... + * @param {object} param0 cmd arg object model ... + */ +function doCMD({ arg, index }) { + // + var isExists = isCMDExists(arg); + if (!isExists) { + throw "Command not found: " + arg; + } + + // + const args = getReceivedArgs(); + + // + const nextArg = args[index + 1]; + const actionName = + nextArg == null ? null : isCMDExists(nextArg.arg) ? null : nextArg.arg; + const isSingle = isActionExists(actionName); + const isCompound = isCompoundActionExists(actionName); + const actionModel = + actionName == null + ? null + : isSingle + ? getAction(actionName) + : isCompound + ? getCompoundAction(actionName) + : actionName; + + // + const nextOfNextArg = args[index + 2]; + const moduleNameExpressionm = + nextOfNextArg == null + ? null + : isCMDExists(nextOfNextArg.arg) + ? null + : nextOfNextArg.arg; + + // + const moduleName = getModuleName(moduleNameExpressionm); + const moduleVersion = getModuleVersion(moduleNameExpressionm); + const isItExistsModule = isModuleExists(moduleName); + const moduleModel = + !isItExistsModule || moduleName == null + ? null + : getModule(moduleName, moduleVersion); + + // + switch (arg) { + // + case "help": + // + console.log("Supported Commands: "); + console.log("========================="); + supportedCommands.forEach((c) => { + // + console.log(" "); + console.warn(c.arg); + console.log(c.description); + }); + console.log(" "); + break; + + // + case "ls-commands": + case "list-commands": + // + console.log("Supported Actions: "); + console.log("========================="); + + // + console.log(" "); + console.log("1/2 => Single Actions:"); + console.log("==========================="); + supportedActions.forEach((a) => { + // + console.log(" "); + console.warn(a.name); + console.log(a.description); + }); + console.log(" "); + + // + console.log(" "); + console.log("2/2 => Compound Actions:"); + console.log("==========================="); + supportedCompoundActions.forEach((a) => { + // + console.log(" "); + console.warn(a.name); + console.log(a.description); + }); + console.log(" "); + break; + + // + case "ls-modules": + case "list-modules": + // + console.log("Available Modules: "); + console.log("========================="); + supportedModules.forEach((m, i) => { + console.warn(i + 1, " => ", m.name); + }); + console.log(" "); + break; + + // + case "do": + case "get": + doOrGetCommandHandler(actionModel, moduleModel, arg !== "do"); + break; + + // + case "get-script": + // + if (actionModel == null) { + throw "invalid action ..."; + } + + // + if (moduleModel == null) { + throw "invalid module ..."; + } + + // + const script = isSingle + ? getScript(actionName, moduleName) + : isCompound + ? getCompoundScript(actionName, moduleName) + : null; + + // + if (script == null) { + console.log("there is no script for action: ", actionModel); + } else { + logScript(script); + } + break; + + // + // TODO: Remove this ... + // case "do-on": + // case "get-do-on": + // doOrGetOnCommandHandler(index, args, actionModel, arg !== "do-on"); + // break; + + // + case "m-scripts": + case "module-scripts": + // + const moduleArg = args[index + 1]; + const scriptModuleName = + moduleArg == null + ? null + : isCMDExists(moduleArg.arg) + ? null + : !isModuleExists(moduleArg.arg) + ? null + : moduleArg.arg; + const destModuleModel = + scriptModuleName == null ? null : getModule(scriptModuleName); + + // + if (destModuleModel == null) { + throw "invalid module ..."; + } + + // + console.log( + "Export all available scripts for module: ", + scriptModuleName + ); + console.log( + "====================================================================" + ); + console.log(" "); + + // + // Single ... + for (const a of supportedActions) { + // + const script = getScript(a.name, scriptModuleName); + logScript(script); + } + + // + // Compound ... + for (const a of supportedCompoundActions) { + // + const script = getCompoundScript(a.name, scriptModuleName, true); + logScript(script); + } + break; + + // + case "scripts": + // + supportedModules.forEach((m) => { + // + // Single ... + supportedActions.forEach((a) => { + // + const script = getScript(a.name, m.name); + logScript(script); + }); + + // + // Compound ... + supportedCompoundActions.forEach((a) => { + // + const script = getCompoundScript(a.name, m.name, true); + logScript(script); + }); + }); + + // + supportedActions.forEach((a) => { + // + const scriptName = `${a.name}AllModules`; + const scriptCmd = getAllCommandsForAction(a.name, true); + + // + logScript({ name: scriptName, cmd: scriptCmd }); + }); + + // + supportedCompoundActions.forEach((a) => { + // + const scriptName = `${a.name}AllModules`; + const scriptCmd = getAllCommandsForAction(a.name, true); + + // + logScript({ name: scriptName, cmd: scriptCmd }); + }); + break; + } +} + +/** + * handle execution, or log specific single or compound action model object on specific module model object ... + * @param {object} actionModel action descriptor module ... + * @param {object} moduleModel module descriptor model ... + * @param {bool} justLog determines just log the command or just execute it ... + */ +function doOrGetCommandHandler(actionModel, moduleModel, justLog = true) { + // + if (!actionModel || actionModel == null) { + throw "invalid action ..."; + } + + // + const isSingle = isActionExists( + actionModel.name ? actionModel.name : actionModel + ); + const isCompound = isCompoundActionExists( + actionModel.name ? actionModel.name : actionModel + ); + + // + if ((isSingle || isCompound) && moduleModel == null) { + throw "invalid module ..."; + } + + // + if (!isSingle && !isCompound) { + if (justLog) { + console.log(actionModel); + } else { + executeInPath(actionModel); + } + } else { + // + const cmdText = isSingle + ? getCommand(actionModel.name, moduleModel.name, moduleModel.version) + : isCompound + ? getCompoundCommand( + actionModel.name, + moduleModel.name, + moduleModel.version + ) + : ""; + + // + if (justLog) { + console.log(cmdText); + } else { + executeInPath(cmdText); + } + } +} + +/** + * log specific script object model to console ... + * @param {object} script script descriptor object model ... + */ +function logScript(script) { + console.log(`"${script.name}": "${script.cmd}"`); +} + +/** + * execute an specific command on a path, if path is null get default execution path ... + * @param {string} cmd command string ... + * @param {string?} path the path for execute command on it ... + */ +function executeInPath(cmd, path) { + // + if (!path) { + path = execPath; + } + + // + if (!cmd) { + // + console.error("command not found ..."); + throw "command not found ..."; + } + + // + console.log(" "); + console.log("executing: ", cmd); + + // + try { + // + const cmdInPath = + path === "" || path === null || path === undefined + ? "" + : "cd " + path + " " + cmdSeparator + " " + cmd; + + // + execSync(cmdInPath); + } catch (ex) { + // + const errMsg = `Execution of ${cmd} failed with reason: ${ex} ...`; + console.error(errMsg); + throw "execution failed ..."; + } + + // + console.log("done"); +} +//#endregion Providers ... \ No newline at end of file diff --git a/Documents/Scripts/x-color.tools.js b/Documents/Scripts/x-color.tools.js new file mode 100644 index 0000000..c3ac5f9 --- /dev/null +++ b/Documents/Scripts/x-color.tools.js @@ -0,0 +1,506 @@ +/** + * XColor Tools Module ... + * a module for handle colorify text contents in node js ... + * + * Maintainer + * + * Hadi Khazaee Asl (hadi_khazaee_asl@yahoo.com) + */ + +// +//#region Module Imports ... +const XValueTools = require("./x-value.tools"); +//#endregion + +// +//#region Constants ... +/** + * these are available style which can applied to an string ... + */ +const AVAILABLE_STYLES = { + // + Bold: "\x1b[1m", + Dim: "\x1b[2m", + Underlined: "\x1b[4m", + Blink: "\x1b[5m", + ReverseFandB: "\x1b[7m", + Hidden: "\x1b[8m", + // + // Commonly used for reset all Styles ... + Reset: "\x1b[0m" +}; + +/** + * these are available foreground colors which can applied to an string ... + */ +const AVAILABLE_FOREGROUND_COLORS = { + Default: "\x1b[39m", + Black: "\x1b[30m", + Red: "\x1b[31m", + Green: "\x1b[32m", + Yellow: "\x1b[33m", + Blue: "\x1b[34m", + Magenta: "\x1b[35m", + Cyan: "\x1b[36m", + LightGray: "\x1b[37m", + DarkGray: "\x1b[90m", + LightRed: "\x1b[91m", + LightGreen: "\x1b[92m", + LightYellow: "\x1b[93m", + LightBlue: "\x1b[94m", + LightMagenta: "\x1b[95m", + LightCyan: "\x1b[96m", + White: "\x1b[97m", +}; + +/** + * these are available background colors which can applied to an string ... + */ +const AVAILABLE_BACKGROUND_COLORS = { + Default: "\x1b[49m", + Black: "\x1b[40m", + Red: "\x1b[41m", + Green: "\x1b[42m", + Yellow: "\x1b[43m", + Blue: "\x1b[44m", + Magenta: "\x1b[45m", + Cyan: "\x1b[46m", + LightGray: "\x1b[47m", + DarkGray: "\x1b[100m", + LightRed: "\x1b[101m", + LightGreen: "\x1b[102m", + LightYellow: "\x1b[103m", + LightBlue: "\x1b[104m", + LightMagenta: "\x1b[105m", + LightCyan: "\x1b[106m", + White: "\x1b[107m", +}; + +/** + * these are available style names, which exports from module and + * users can use them ... + */ +const STYLE_NAMES = { + Bold: "Bold", + Dim: "Dim", + Underlined: "Underlined", + Blink: "Blink", + ReverseFandB: "ReverseFandB", + Hidden: "Hidden", + Reset: "Reset", +}; + +/** + * these are available color names, which exports from module and + * users can use them ... + */ +const COLOR_NAMES = { + Default: "Default", + Black: "Black", + Red: "Red", + Green: "Green", + Yellow: "Yellow", + Blue: "Blue", + Magenta: "Magenta", + Cyan: "Cyan", + LightGray: "LightGray", + DarkGray: "DarkGray", + LightRed: "LightRed", + LightGreen: "LightGreen", + LightYellow: "LightYellow", + LightBlue: "LightBlue", + LightMagenta: "LightMagenta", + LightCyan: "LightCyan", + White: "White", +}; +//#endregion + +// +//#region Actions ... +/** + * apply specified style and color on a content ... + * + * @param {string} content specified content for styling ... + * @param {string} color specific color name for using to styling ... + * @param {string} style soecufic style name to use ... + * @param {boolean} toForeground apply specified color as foreground ... + * @param {boolean} toBackground apply specified color as background ... + * @returns {string} styled content ... + */ +function apply( + content, + color, + style, + toForeground = true, + toBackground = false +) { + // + let result = content; + + // + // Validate Arg ... + if (!XValueTools.isValidArg(content)) { + return result; + } + + // + // Detect and Validate Style and Apply it ... + let eStyle = AVAILABLE_STYLES[style]; + if (XValueTools.isValidArg(eStyle)) { + result = `${eStyle}${result}${AVAILABLE_STYLES.Reset}`; + } + + // + // Detect and Validate Foreground Color and Apply it ... + let eFColor = AVAILABLE_FOREGROUND_COLORS[color]; + if ( + !!toForeground + && XValueTools.isValidArg(eFColor) + ) { + result = `${eFColor}${result}${AVAILABLE_STYLES.Reset}`; + } + + // + // Detect and Validate Background Color and Apply it ... + let eBColor = AVAILABLE_BACKGROUND_COLORS[color]; + if ( + !!toBackground + && XValueTools.isValidArg(eBColor) + ) { + result = `${eBColor}${result}${AVAILABLE_STYLES.Reset}`; + } + +} + +/** + * apply specific style on a content ... + * + * @param {string} content specific content which going to styled ... + * @param {string} style a member of STYLE_NAMES which specified that which style going to applied to content ... + * @returns {string} styled content ... + */ +function applyStyle(content, style) { + // + // Validate Arg ... + if (!XValueTools.isValidArg(content)) { + return content; + } + + // + let eStyle = AVAILABLE_STYLES[style]; + if (eStyle === undefined) { + return content; + } + + // + return `${eStyle}${content}${AVAILABLE_STYLES.Reset}`; +} + +/** + * apply specific foreground color on a content ... + * + * @param {string} content specific content which going to colorified ... + * @param {string} color a member of COLOR_NAMES which specified that which color going to applied to content ... + * @returns {string} colorified content ... + */ +function applyForegroundColor(content, color) { + // + // Validate Arg ... + if (!XValueTools.isValidArg(content)) { + return content; + } + + // + let eColor = AVAILABLE_FOREGROUND_COLORS[color]; + if (eColor === undefined) { + return content; + } + + // + return `${eColor}${content}${AVAILABLE_STYLES.Reset}`; +} + +/** + * apply specific background color on a content ... + * + * @param {string} content specific content which going to colorified ... + * @param {string} color a member of COLOR_NAMES which specified that which color going to applied to content ... + * @returns {string} colorified content ... + */ +function applyBackgroundColor(content, color) { + // + // Validate Arg ... + if (!XValueTools.isValidArg(content)) { + return content; + } + + // + let eColor = AVAILABLE_BACKGROUND_COLORS[color]; + if (eColor === undefined) { + return content; + } + + // + return `${eColor}${content}${AVAILABLE_STYLES.Reset}`; +} + +/** + * generate style and color applier expression ... + * + * @param {string} color specific color name for using to styling ... + * @param {string} style soecufic style name to use ... + * @param {boolean} reset close applier string by reset styles ... + * @param {boolean} toForeground apply specified color as foreground ... + * @param {boolean} toBackground apply specified color as background ... + * @returns {string} style and color applier string ... + */ +function getApplier( + style = "", + color = "", + reset = false, + toForeground = true, + toBackground = false +) { + // + let result = ""; + + // + // Detect and Validate Style and Apply it ... + let eStyle = AVAILABLE_STYLES[style]; + if (XValueTools.isValidArg(eStyle)) { + result = `${eStyle}`; + } + + // + // Detect and Validate Foreground Color and Apply it ... + let eFColor = AVAILABLE_FOREGROUND_COLORS[color]; + if ( + !!toForeground + && XValueTools.isValidArg(eFColor) + ) { + result = `${eFColor}`; + } + + // + // Detect and Validate Background Color and Apply it ... + let eBColor = AVAILABLE_BACKGROUND_COLORS[color]; + if ( + !!toBackground + && XValueTools.isValidArg(eBColor) + ) { + result = `${eBColor}`; + } + + // + if ( + !!reset && + result.length > 0 + ) { + result = `${result}${AVAILABLE_STYLES.Reset}`; + } + + // + return result; +} + +/** + * generate style applier expression ... + * + * @param {string} style soecufic style name to use ... + * @param {boolean} reset close applier string by reset styles ... + * @returns {string} style applier string ... + */ +function getStyleApplier( + style = "", + reset = false +) { + // + let result = ""; + + // + // Detect and Validate Style and Apply it ... + let eStyle = AVAILABLE_STYLES[style]; + if (XValueTools.isValidArg(eStyle)) { + result = `${eStyle}`; + } + + // + if ( + !!reset && + result.length > 0 + ) { + result = `${result}${AVAILABLE_STYLES.Reset}`; + } + + // + return result; +} + +/** + * generate color applier expression ... + * + * @param {string} color specific color name for using to styling ... + * @param {boolean} reset close applier string by reset styles ... + * @param {boolean} toForeground apply specified color as foreground ... + * @param {boolean} toBackground apply specified color as background ... + * @returns {string} color applier string ... + */ +function getColorApplier( + color = "", + reset = false, + toForeground = true, + toBackground = false +) { + // + let result = ""; + + // + // Detect and Validate Foreground Color and Apply it ... + let eFColor = AVAILABLE_FOREGROUND_COLORS[color]; + if ( + !!toForeground + && XValueTools.isValidArg(eFColor) + ) { + result = `${eFColor}`; + } + + // + // Detect and Validate Background Color and Apply it ... + let eBColor = AVAILABLE_BACKGROUND_COLORS[color]; + if ( + !!toBackground + && XValueTools.isValidArg(eBColor) + ) { + result = `${eBColor}`; + } + + // + if ( + !!reset && + result.length > 0 + ) { + result = `${result}${AVAILABLE_STYLES.Reset}`; + } + + // + return result; +} + +/** + * colorified specific content ... + * + * @param {string} content specified content for styling ... + * @param {string} color specific color name for using to styling ... + * @param {boolean} toForeground apply specified color as foreground ... + * @param {boolean} toBackground apply specified color as background ... + * @returns {string} + */ +function colorifyContent( + content = "", + color = "", + toForeground = true, + toBackground = false +) { + // + let result = content; + + // + if (!XValueTools.isValidArg(content)) { + return result; + } + + // + // Finde Colors ... + + // + // Detect and Validate Foreground Color and Apply it ... + let eFColor = AVAILABLE_FOREGROUND_COLORS[color]; + if ( + !!toForeground + && XValueTools.isValidArg(eFColor) + ) { + result = `${eFColor}${result}`; + } + + // + // Detect and Validate Background Color and Apply it ... + let eBColor = AVAILABLE_BACKGROUND_COLORS[color]; + if ( + !!toBackground + && XValueTools.isValidArg(eBColor) + ) { + result = `${eBColor}${result}`; + } + + // + if ( + result.length > 0 + && ( + XValueTools.isValidArg(eFColor) || + XValueTools.isValidArg(eBColor) + ) + ) { + result = `${result}${AVAILABLE_STYLES.Reset}`; + } + + // + return result; +} + +/** + * apply style on specific content ... + * + * @param {string} content specified content for styling ... + * @param {string} style soecufic style name to use ... + * @returns {string} + */ +function stylifiyContent( + content = "", + style = "", +) { + // + let result = content; + + // + if (!XValueTools.isValidArg(content)) { + return result; + } + + // + // Detect and Validate Style and Apply it ... + let eStyle = AVAILABLE_STYLES[style]; + if (XValueTools.isValidArg(eStyle)) { + result = `${eStyle}${result}`; + } + + // + if ( + result.length > 0 + && XValueTools.isValidArg(eStyle) + ) { + result = `${result}${AVAILABLE_STYLES.Reset}`; + } + + // + return result; +} +//#endregion + +// +//#region Module Exports ... +module.exports = { + // + STYLE_NAMES, + COLOR_NAMES, + // + apply, + applyStyle, + getApplier, + getColorApplier, + getStyleApplier, + colorifyContent, + stylifiyContent, + applyForegroundColor, + applyBackgroundColor, +} +//#endregion \ No newline at end of file diff --git a/Documents/Scripts/x-file.tools.js b/Documents/Scripts/x-file.tools.js new file mode 100644 index 0000000..2a3535d --- /dev/null +++ b/Documents/Scripts/x-file.tools.js @@ -0,0 +1,887 @@ +/** + * XFile Tools Module ... + * a module for handle all file/folder manipulating task in node js ... + * + * Maintainer + * + * Hadi Khazaee Asl (hadi_khazaee_asl@yahoo.com) + */ + +// +//#region Module Imports ... +const fs = require('fs'); +const os = require('os'); +const Path = require('path'); +const http = require('http'); +const https = require('https'); +const XValueTools = require('./x-value.tools'); +//#endregion + +// +//#region Constants ... +/** + * current os path separators ... + */ +const PathSeparator = Path.sep; + +/** + * current directory ... + */ +const CurrentDir = __dirname; +//#endregion + +// +//#region Actions ... +// +//#region Global ... +/** + * retrieve a path status ... + * + * @param {string} path a path value to check ... + * @returns an stat object ... + */ +function getStatus(path = '') { + // + if (!XValueTools.isValidArg(path)) { + return undefined; + } + + // + return fs.statSync(path); +} + +/** + * retrieve user's Home path ... + * + * @returns {string} a path ... + */ +function getHomePath() { + return os.homedir(); +} +//#endregion + +// +//#region Path ... +/** + * retrieve the base name of specific address path ... + * + * @param {string} path address of file or folder ... + * @returns string ... + */ +function basename(path = '') { + // + if (!XValueTools.isValidArg(path)) { + return ''; + } + + // + const result = Path.basename(path); + return result; +} + +/** + * join several path segments together ... + * + * @param {...string} path path params ... + * @returns a joined paths ... + */ +function joinPath(...path) { + return Path.join(...path); +} + +/** + * resolve a relative path to absolute ... + * + * @param {string[]} path a path value to check ... + * @returns {string} + */ +function resolvePath(...path) { + return Path.resolve(...path); +} +//#endregion + +// +//#region File ... +/** + * determines a path destination is a file or not ... + * + * @param {string} path a path value to check ... + * @returns a boolean value ... + */ +function isFileExists(path = '') { + // + if (!XValueTools.isValidArg(path)) { + return false; + } + + // + try { + const stat = getStatus(path); + if (!stat) { + return false; + } + + // + return stat.isFile(); + } catch { + return false; + } +} + +/** + * remove a file ... + * + * @param {string} path a file path ... + * @returns {Promise} action done or not ... + */ +function removeFile(path = '') { + return new Promise((resolve) => { + // + if (!isFileExists(path)) { + resolve(false); + } + + // + fs.unlink(path, (err) => { + // + if (err) { + resolve(false); + return; + } + + // + resolve(true); + }); + }); +} + +/** + * copy a file to destination path ... + * + * @param {string} source source file path ... + * @param {string} dest dest folder path ... + * @returns {Promise} action done or not ... + */ +function copyFile( + source = '', + dest = '' +) { + return new Promise((resolve) => { + // + if ( + !isFileExists(source) || + !isDirectoryExists(dest) + ) { + resolve(false); + return; + } + + // + const destFilePath = Path.join(dest, Path.basename(source)); + fs.copyFile(source, destFilePath, (err) => { + // + if (err) { + resolve(false); + return; + } + + // + resolve(true); + }); + }); +} + +/** + * reading specified file content ... + * + * @param {string} path a file path ... + * @returns {Promise} file content ... + */ +function readFile(path = '') { + return new Promise((resolve) => { + // + if (!isFileExists(path)) { + resolve(''); + return; + } + + // + fs.readFile(path, 'utf8', (err, content) => { + // + if (err) { + resolve(undefined); + return; + } + + // + resolve(content); + }); + }); +} + +/** + * write content to a file ... + * + * @param {string} path a file path ... + * @param {string} content the content which going to write to the file ... + * @param {bool} overwrite determines file overwrite if exists ... + * @returns {Promise} action done or not ... + */ +function writeFile( + path = '', + content = '', + overwrite = true +) { + return new Promise((resolve) => { + // + if (isFileExists(path) && !overwrite) { + // + resolve(false); + return; + } + + // + // Normalize Content ... + content = XValueTools.isValidArg(content) ? + content : + ''; + + // + fs.writeFile(path, content, (err) => { + // + if (err) { + // + resolve(false); + return; + } + + // + resolve(true); + }); + }); +} + +/** + * create a file ... + * + * @param {string} path file path ... + * @param {string} fileName file name ... + * @returns {Promise} action done or not ... + */ +function createFile( + path = '', + fileName = '' +) { + return new Promise((resolve) => { + // + const filePath = Path.join(path, fileName); + if ( + isFileExists(filePath) || + !XValueTools.isValidArg(path) || + !XValueTools.isValidArg(fileName) + ) { + // + resolve(false); + return; + } + + // + fs.writeFile(filePath, '', (err) => { + // + if (err) { + // + resolve(false); + return; + } + + // + resolve(true); + }); + }); +} + +/** + * remove a file name extension ... + * + * @param {string} name + * @returns {string} name without extension ... + */ +function removeFileExtension(name = '') { + return name.substring(0, name.lastIndexOf('.')) || name; +} + +/** + * retrieve a file extension ... + * + * @param {string} path a path which locate a file ... + * @returns {string} + */ +function getFileExtension(path = '') { + // + let result = ""; + + // + // Validate Arg ... + if ( + !XValueTools.isValidArg(path) + || !isFileExists(path) + ) { + return result; + } + + // + // Retrieve just file name ... + const fileName = basename(path); + result = fileName.replace( + removeFileExtension(fileName), + "" + ); + + // + return result; +} + +/** + * retrieve all files list recursively from specific path ... + * + * @param {string} path a source folder path ... + * @param {string[]} extensions which file extensions need to be listed, live empty for all files ... + * @returns {Promise} + */ +async function getRecursiveFilesList( + path = "", + extensions = [] +) { + // + let result = []; + + // + // Validate Args ... + if ( + !XValueTools.isValidArg(path) + || !isDirectoryExists(path) + ) { + return result; + } + + // + const contents = await getDirectoryContents(path); + for (const item of contents) { + // + const itemPath = joinPath( + path, + item + ); + + // + const isItemFile = isFileExists(itemPath); + if (isItemFile) { + // + const itemFileExtension = getFileExtension(itemPath); + const isFileInSupportedExtensions = + extensions === undefined || extensions.length === 0 + ? true + : extensions.includes(itemFileExtension) + ; + if (isFileInSupportedExtensions) { + result.push(itemPath); + } + } else if (isDirectoryExists(itemPath)) { + // + const itemPathFiles = await getRecursiveFilesList(itemPath, extensions); + result.push(...itemPathFiles); + } + } + + // + return result; +} + +/** + * Extract Specific files from a folder and it's content ... + * + * @param {string} path Folder Path ... + * @returns + */ +async function extractFiles(path = "", ext = [], excludeWorkspaceFolders = []) { + // + const result = []; + + // + if (!ext || ext.length == 0) { + return result; + } + + // + const pathContent = await getDirectoryContents(path); + if (!pathContent || pathContent.length == 0) { + return result; + } + + // + // Loop through Content ... + for (const c of pathContent) { + // + // Prepare full path ... + const cPath = joinPath(path, c); + + // + // check content path is file or not ... + const isCFile = isFileExists(cPath); + const isCDirectory = isDirectoryExists(cPath); + if (isCFile) { + // + // Check it is MQL file or not ... + const cExt = getFileExtension(cPath); + if (ext.includes(cExt)) { + result.push(cPath); + } + } else if (isCDirectory) { + // + const destFolderName = basename(cPath); + const isExcluded = excludeWorkspaceFolders.includes(destFolderName); + if (isExcluded) { + continue; + } + + // + const cFiles = await extractFiles(cPath, ext); + if (cFiles && cFiles.length > 0) { + result.push(...cFiles); + } + } + } + + // + return result; +} +//#endregion + +// +//#region Directory ... +/** + * create a directory ... + * + * @param {string} path destination path including dir name ... + * @param {boolean} recursive create directories recursively ... + * @returns action done or not ... + */ +function createDirectory( + path = '', + recursive = true +) { + // + let result = false; + + // + if ( + isDirectoryExists(path) || + !XValueTools.isValidArg(path) + ) { + return false; + } + + // + try { + // + fs.mkdirSync(path, { recursive: recursive }); + result = true; + return result; + } catch { + return false; + } +} + +/** + * remove a directory ... + * + * @param {string} path destination path including dir name ... + * @param {boolean} recursive removes directories recursively ... + * @returns {Promise} action done or not ... + */ +function removeDirectory( + path = '', + recursive = false +) { + return new Promise((resolve) => { + // + if (!isDirectoryExists(path)) { + resolve(false); + return; + } + + // + fs.rm(path, { + recursive + }, (err) => { + // + if (err) { + resolve(false); + return; + } + + // + resolve(true); + }); + }); +} + +/** + * determines a path destination is a directory or not ... + * + * @param {string} path a folder path ... + * @returns represent destnation path is Directory or not ... + */ +function isDirectoryExists(path = '') { + // + if (!XValueTools.isValidArg(path)) { + return false; + } + + // + try { + // + const isExists = fs.existsSync(path); + if (!isExists) { + return false; + } + + // + const stat = getStatus(path); + if (!stat) { + return false; + } + + // + const result = stat.isDirectory(); + return result; + } catch { + return false; + } +} + +/** + * retrieve a directory content ... + * + * @param {string} path a folder path ... + * @returns {Promise} a collection of folder files ... + */ +function getDirectoryContents(path = '') { + return new Promise((resolve) => { + // + if (!isDirectoryExists(path)) { + resolve([]); + return; + } + + // + fs.readdir(path, (err, content) => { + // + if (err) { + resolve([]); + return; + } + + // + resolve(content); + }); + }); +} + +/** + * retrieve a directory files ... + * + * @param {string} path a folder path ... + * @returns {Promise} a collection of folder files ... + */ +function getDirectoryFiles( + path = '', + containsHiddenFiles = false +) { + return new Promise((resolve) => { + // + if (!isDirectoryExists(path)) { + resolve([]); + return; + } + + // + fs.readdir(path, (err, content) => { + // + if (err) { + resolve([]); + return; + } + + // + if (!containsHiddenFiles) { + content = content.filter(c => !c.startsWith('.')); + } + + // + const result = []; + content + .forEach(c => { + // + const cPath = Path.join(path, c); + if (isFileExists(cPath)) { + result.push(c); + } + }); + + // + resolve(result); + }); + }); +} + +/** + * retrieve a directory folders ... + * + * @param {string} path a folder path ... + * @returns {Promise} a collection of folder names ... + */ +function getDirectoryFolders(path = '') { + return new Promise((resolve) => { + // + if (!isDirectoryExists(path)) { + resolve([]); + return; + } + + // + fs.readdir(path, (err, content) => { + // + if (err) { + resolve([]); + return; + } + + // + const result = []; + content.forEach(c => { + // + const cPath = Path.join(path, c); + if (isDirectoryExists(cPath)) { + result.push(c); + } + }); + + // + resolve(result); + }); + }); +} + +/** + * copy a folder with all of it's content to dest ... + * + * @param {string} source source folder path ... + * @param {string} dest dest folder path ... + * @returns {Promise} action done or not ... + */ +async function copyFolder( + source = '', + dest = '' +) { + // + if ( + !isDirectoryExists(source) || + !XValueTools.isValidArg(dest) || + !XValueTools.isValidArg(source) + ) { + return false; + } + + // + const folderName = Path.basename(source); + const destPath = Path.join(dest, folderName); + + // + // Create Dest Path folder if not exists ... + if (!isDirectoryExists(destPath)) { + // + let result = createDirectory(destPath, true); + if (!result) { + return false; + } + } + + // + // Files ... + const files = await getDirectoryFiles(source); + if (files && files.constructor === Array && files.length > 0) { + // + const filesPromises = files.map(file => copyFile(Path.join(source, file), destPath)); + const filesResult = (await Promise.all(filesPromises)).every(r => !!r); + if (!filesResult) { + return false; + } + } + + // + // Folders ... + const folders = await getDirectoryFolders(source); + if (folders && folders.constructor === Array && folders.length > 0) { + // + const folderPromises = folders.map(folder => copyFolder(Path.join(source, folder), destPath)); + const filesResult = (await Promise.all(folderPromises)).every(r => !!r); + if (!filesResult) { + return false; + } + } + + // + return true; +} +//#endregion + +// +//#region Downloader ... +/** + * download a file from specific url and store it ... + * + * @param {string} filepath the file name and path which required to put download file on it ... + * @param {string} url the web url for downloading ... + */ +function download(filepath, url) { + // + // Validate Args ... + if ( + !XValueTools.isValidArg(url) + || !XValueTools.isValidURL(url) + || !XValueTools.isValidArg(filepath) + ) { + return; + } + + // + var file = fs.createWriteStream(filepath); + + // + if (url.startsWith('https:')) { + https.get(url, function (response) { + response.pipe(file); + }); + } else if (url.startsWith('http:')) { + http.get(url, function (response) { + response.pipe(file); + }); + } +} +//#endregion + +// +//#region JSON ... +/** + * read and parse a JSON content from a file ... + * + * @param {string} path source file path ... + * @returns {any} + */ +function readJSON(path = "") { + // + // Validate Args ... + if (!isFileExists(path)) { + return undefined; + } + + // + let result = undefined; + try { + result = require(path); + } catch { + result = undefined; + } + + // + return result; +} + +/** + * write a JSON Object content into a file ... + * + * @param {string} path dest file path ... + * @param {any} content an object which required to write to file ... + * @returns {Promise} + */ +async function writeJSON( + path = "", + content = undefined +) { + // + let result = false; + + // + // Validate Args ... + if (!XValueTools.isValidArg(path)) { + return result; + } + + // + // Normallize Content ... + if (content === undefined) { + content = {}; + } + + // + const contentString = XValueTools.beautifyJSON(content); + + // + result = await writeFile( + path, + contentString, + true + ); + + // + return result; +} +//#endregion +//#endregion + +// +//#region Module Exports ... +module.exports = { + // + CurrentDir, + PathSeparator, + + // + getStatus, + getHomePath, + + // + basename, + joinPath, + resolvePath, + + // + download, + + // + readJSON, + writeJSON, + + // + copyFile, + readFile, + writeFile, + createFile, + removeFile, + isFileExists, + getFileExtension, + removeFileExtension, + getRecursiveFilesList, + extractFiles, + + // + copyFolder, + createDirectory, + removeDirectory, + isDirectoryExists, + getDirectoryFiles, + getDirectoryFolders, + getDirectoryContents, +}; +//#endregion \ No newline at end of file diff --git a/Documents/Scripts/x-shell.tools.js b/Documents/Scripts/x-shell.tools.js new file mode 100644 index 0000000..0ee13de --- /dev/null +++ b/Documents/Scripts/x-shell.tools.js @@ -0,0 +1,219 @@ +/** + * XShell Tools Module ... + * a module for handling shell actions and retrieve OS Info in node js ... + * + * Maintainer + * + * Hadi Khazaee Asl (hadi_khazaee_asl@yahoo.com) + */ + +// +//#region Imports ... +const os = require('os'); +const process = require('process'); +const { exec } = require("child_process"); +const XFileTools = require('./x-file.tools'); +//#endregion + +// +//#region Constants ... +// +const OS = { + Aix: 'aix', + Darwin: 'darwin', + FreeBSD: 'freebsd', + Linux: 'linux', + OpenBSD: 'openbsd', + SnOS: 'sunos', + Windows: 'win32' +}; + +// +const isWindows = process.platform === OS.Windows; +//#endregion + +// +//#region Actions ... +// +//#region Pure shell commands ... +/** + * execute a command using NodeJS on shell ... + * + * @param {string} cmd command to execute ... + * @param {string} cwd working directory ... + * + * @returns Promise instance ... + */ +function execute(cmd, cwd) { + return new Promise((resolve, reject) => { + // + if (!cmd || cmd.toString().length === 0 || (cwd && !XFileTools.isDirectoryExists(cwd))) { + reject('invalid args ...'); + return; + } + + // + exec(cmd, { cwd }, (err, result, stdError) => { + // + if (err) { + reject(err); + return; + } + + // + if (stdError) { + // + // reject(stdError); + // return; + } + + // + resolve(result); + }); + }); +}; + +/** + * determines a command exists on host or not ... + * + * @param {string} name specific command name ... + * + * @returns boolean Promise ... + */ +function checkCommandExists(name) { + return new Promise(resolve => { + // + if (!name) { + resolve(false); + return; + } + + // + const cmd = isWindows ? `${name} >nul 2>&1` : `type ${name} >/dev/null 2>&1`; + execute(cmd).then(result => { + resolve(true); + }) + .catch(err => { + resolve(false); + }); + }); +} +//#endregion + +// +//#region required commands state ... +/** + * check al required commands exists or not ... + * + * @returns + */ +async function isRequiredCommandsExists() { + // + let result = false; + + // + // const isTarExists = await isTarCommandExists(); + // const isCatExists = await isCatCommandExists(); + // const isGrepExists = await isGrepCommandExists(); + // const isSedExists = await isSedCommandExists(); + const isNpmExists = await isNpmCommandExists(); + const isNgExists = await isNgCommandExists(); + const isIonicExists = await isIonicCommandExists(); + const isCordovaExists = await isCordovaCommandExists(); + + // + result = isNpmExists + && isNgExists + && isIonicExists + && isCordovaExists + ; + + // + return result; +} + +/** + * retrieve required commands state object ... + * + * @returns + */ +async function getRequiredCommandsStates() { + // + const result = {}; + + // + // const isTarExists = await isTarCommandExists(); + // const isCatExists = await isCatCommandExists(); + // const isGrepExists = await isGrepCommandExists(); + // const isSedExists = await isSedCommandExists(); + const isNpmExists = await isNpmCommandExists(); + const isNgExists = await isNgCommandExists(); + const isIonicExists = await isIonicCommandExists(); + const isCordovaExists = await isCordovaCommandExists(); + + // + result['npm'] = isNpmExists; + result['ng'] = isNgExists; + result['ionic'] = isIonicExists; + result['cordova'] = isCordovaExists; + + // + return result; +} +//#endregion + +// +//#region Commonly used Command Checkers ... +/** + * determines npm command exists or not ... + * + * @returns + */ +function isNpmCommandExists() { + return checkCommandExists('npm'); +} + +/** + * determines ng command exists or not ... + * + * @returns + */ +function isNgCommandExists() { + return checkCommandExists('ng'); +} + +/** + * determines ionic command exists or not ... + * + * @returns + */ +function isIonicCommandExists() { + return checkCommandExists('ionic'); +} + +/** + * determines cordova command exists or not ... + * + * @returns + */ +function isCordovaCommandExists() { + return checkCommandExists('cordova'); +} +//#endregion +//#endregion + +// +//#region Module Exports ... +module.exports = { + // + execute, + checkCommandExists, + isRequiredCommandsExists, + getRequiredCommandsStates, + + // + isNgCommandExists, + isIonicCommandExists, + isCordovaCommandExists, +} +//#endregion \ No newline at end of file diff --git a/Documents/Scripts/x-type-detector.tools.js b/Documents/Scripts/x-type-detector.tools.js new file mode 100644 index 0000000..86ed3d8 --- /dev/null +++ b/Documents/Scripts/x-type-detector.tools.js @@ -0,0 +1,268 @@ +/** + * DataType Tools Module ... + * a module for manipulate and detect supported data types in node js ... + * + * Maintainer + * + * Hadi Khazaee Asl (hadi_khazaee_asl@yahoo.com) + */ + +// +//#region Constants ... +/** + * all supported data types ... + */ +const DataTypes = { + Null: 'null', + Date: 'date', + Array: 'array', + Object: 'object', + String: 'string', + Number: 'number', + Unknown: 'unknown', + Boolean: 'boolean', + Function: 'function', + Undefined: 'undefined', +}; +//#endregion + +// +//#region Actions ... +/** + * detect type of a content ... + * + * @param {any} value the content which going to check ... + * @returns + */ +function getType(value) { + // + const type = typeof value; + + // + let result = DataTypes.Unknown; + let dateIdentifier = '[object Date]'; + let constructor = value && value.constructor ? + value.constructor.toString() : + ''; + + // + switch (type) { + // + case 'undefined': + result = DataTypes.Undefined; + break; + + // + case 'boolean': + result = DataTypes.Boolean; + break; + + // + case 'string': + result = DataTypes.String; + break; + + // + case 'number': + result = DataTypes.Number; + break; + + // + case 'function': + result = DataTypes.Function; + break; + + // + case 'object': + // + // Null ... + if (value === null) { + result = DataTypes.Null; + } else + // + // Array ... + if (Array.isArray(value)) { + result = DataTypes.Array; + } else + // + // Data ... + if ( + value instanceof Date || + isFunction(value.getMonth) || + constructor.includes(dateIdentifier) || + Object.prototype.toString.call(value) === dateIdentifier + ) { + result = DataTypes.Date; + } else + // + // Object ... + { + result = DataTypes.Object; + } + break; + + // + default: + result = DataTypes.Unknown; + break; + } + + // + return result; +} + +/** + * retrieve an object constructor ... + * + * @param {any} value the content which going to check ... + * @returns + */ +function getConstructor(value) { + // + const result = value && value.constructor ? + value.constructor.toString() : + ''; + + // + return result; +} + +/** + * retrieve an object prototype ... + * + * @param {any} value the content which going to check ... + * @returns + */ +function getPrototype(value) { + // + const result = value ? + Object.prototype.toString.call(value) : + ''; + + // + return result; +} + +/** + * check an object is null or not ... + * + * @param {any} value the content which going to check ... + * @returns + */ +function isNull(value) { + return getType(value) === DataTypes.Null; +} + +/** + * check an object is undefined or not ... + * + * @param {any} value the content which going to check ... + * @returns + */ +function isUndefined(value) { + return getType(value) === DataTypes.Undefined; +} + +/** + * check an object is null or undefined or not ... + * + * @param {any} value the content which going to check ... + * @returns + */ +function isNullOrUndefined(value) { + return isNull(value) || isUndefined(value); +} + +/** + * check an object is a date or not ... + * + * @param {any} value the content which going to check ... + * @returns + */ +function isDate(value) { + return getType(value) === DataTypes.Date; +} + +/** + * check an object is number or not ... + * + * @param {any} value the content which going to check ... + * @returns + */ +function isNumber(value) { + return getType(value) === DataTypes.Number; +} + +/** + * check an object is string or not ... + * + * @param {any} value the content which going to check ... + * @returns + */ +function isString(value) { + return getType(value) === DataTypes.String; +} + +/** + * check an object is boolean or not ... + * + * @param {any} value the content which going to check ... + * @returns + */ +function isBoolean(value) { + return getType(value) === DataTypes.Boolean; +} + +/** + * check an object is an Array or not ... + * + * @param {any} value the content which going to check ... + * @returns + */ +function isArray(value) { + return getType(value) === DataTypes.Array; +} + +/** + * check an object is and Object or not ... + * + * @param {any} value the content which going to check ... + * @returns + */ +function isObject(value) { + return getType(value) === DataTypes.Object; +} + +/** + * check an object is a Function or not ... + * + * @param {any} value the content which going to check ... + * @returns + */ +function isFunction(value) { + return getType(value) === DataTypes.Function; +} +//#endregion + +// +//#region Module Exports ... +module.exports = { + // + DataTypes, + + // + getType, + getConstructor, + getPrototype, + isNull, + isUndefined, + isNullOrUndefined, + isDate, + isNumber, + isString, + isBoolean, + isArray, + isObject, + isFunction, +} +//#endregion \ No newline at end of file diff --git a/Documents/Scripts/x-value.tools.js b/Documents/Scripts/x-value.tools.js new file mode 100644 index 0000000..81cfcc4 --- /dev/null +++ b/Documents/Scripts/x-value.tools.js @@ -0,0 +1,1528 @@ +/** + * Value Tools Module ... + * a module for Validate args and values ... + * + * Maintainer + * + * Hadi Khazaee Asl (hadi_khazaee_asl@yahoo.com) + */ + +// +//#region Imports ... +const XTypeTools = require('./x-type-detector.tools'); +//#endregion + +// +//#region Actions ... +/** +* validate a value as an argument ... +* +* @param {string} value the value which going to validate ... +* @returns +*/ +function isValidArg(value) { + // + let result = false; + + // + result = value && + value.length > 0; + + // + return result; +} + +/** + * validate a collection of valuea as an arguments ... + * + * @param {string|string[]} values the value collection which going to checked ... + * @returns + */ +function isValidArgs(values) { + // + // Normalize Values ... + const normalValues = toNormalArray(values); + if (!hasChildArray(normalValues)) { + return false; + } + + // + let result = normalValues.every(nv => isValidArg(nv)); + return result; +} + +/** + * converts content to an array object ... + * + * @param {string|string[]} content the content which required to normalized ... + * @returns {string[]} array object ... + */ +function toNormalArray(content = '') { + // + // Validate Args ... + if (!isValidArg(content)) { + return []; + } + + // + const result = Array.isArray(content) ? + [...content] : + content.includes(',') && + content !== ',' ? + [...content.split(',')] : + [content]; + + // + return result; +} + +/** + * determines value is array and has atleast one child ... + * + * @param {any} value the value which going to checked ... + * @returns {boolean} result of checking ... + */ +function hasChildArray(value) { + // + let result = false; + result = value && + Array.isArray(value) && + value.length > 0; + + // + return result; +} + +/** + * check an string is ends with specified symbol ... + * + * @param {string} symbol specified search symbol ... + * @param {string} value specified search content ... + * @param {boolean} ignoreCase specified search case sensitive or not ... + * @returns + */ +function endsWidth( + symbol = '', + value = '', + ignoreCase = true +) { + // + if (!isValidArg(value)) { + return false; + } + + // + if (!isValidArg(symbol)) { + return true; + } + + // + // TODO: uncomment it if necessary ... + // symbol = symbol.trim(); + + // + let result = false; + const subtractedContent = value.substring(value.length - symbol.length); + result = !!ignoreCase ? + subtractedContent + .toLowerCase() === symbol + .toLowerCase() : + subtractedContent === symbol; + + // + return result; +} + +/** + * surround specified content by provided symbol ... + * + * @param {string} symbol surround string ... + * @param {string} content used content to surrounded by symbol ... + * @returns surrounded content ... + */ +function surroundBy( + symbol = '', + content = '' +) { + // + // Validate Args ... + if (!isValidArg(content)) { + content = ''; + } + + // + if (!isValidArg(symbol)) { + symbol = ''; + } + + // + if (isSurrounded(symbol, content)) { + return content; + } + + // + const result = `${symbol}${content}${symbol}`; + return result; +} + +/** + * convert a parsed sign to clear string ... + * + * @param {string} value a parsed content ... + * @returns clear parsed signs ... + */ +function toStringExpression(value) { + // + let result = ''; + result = value; + + // + if ( + !value || + !isValidArg(value) || + !isSurroundedString(value) + ) { + return result; + } + + // + const startSymbol = value.charAt(0); + const endSymbol = value.charAt(value.length - 1); + + // + if (startSymbol !== endSymbol) { + return result; + } + + // + if ( + startSymbol === "\"" || + startSymbol === "\'" + ) { + result = clearSurround(startSymbol, value); + } + + // + return result; +} + + + +/** + * find and cleare proposed candidates from specified content ... + * + * @param {string|string[]} candidates the proposed string(s) whic going to cleared from content ... + * @param {string} content the destination content which used to find and replace candidates on it ... + * @returns cleared content ... + */ +function clearContent( + candidates, + content +) { + // + // Validate Args ... + if ( + !candidates || + !isValidArg(content) + ) { + return ''; + } + + // + // Normalize candidates ... + const normalCandidates = toNormalArray(candidates) + .filter(c => isValidArg(c)); + if (!hasChildArray(normalCandidates)) { + return content; + } + + // + // define regexp for content ... + const regExpExpression = normalCandidates.join('|'); + const regExp = new RegExp( + regExpExpression, + 'gi' + ); + + // + let result = ''; + result = content.replace(regExp, ''); + + // + return result; +} + +/** + * clear surrounded symbol from content ... + * + * @param {string} symbol specified search symbol ... + * @param {string} content specified content to clear ... + * @returns + */ +function clearSurround( + symbol = '', + content = '' +) { + // + // Validate Args ... + if ( + !isValidArg(content) || + (isValidArg(symbol) && + symbol.length >= content.length - 1) + ) { + return ''; + } + + // + // Validate Surround ... + if (!isSurrounded(symbol, content)) { + return content; + } + + // + if (!isValidArg(symbol)) { + return content; + } + + // + let result = content.substring(symbol.length, content.length - symbol.length); + return result; +} + +/** + * clear object sign ... + * + * @param {string} content + * @returns cleared content ... + */ +function clearObjectSurround(content = '') { + // + // Validate Args ... + if ( + !isValidArg(content) || + !isSurroundedObject(content) + ) { + return content; + } + + // + const endIndex = content.length - ( + endsWidth('}', content) ? + 1 : + endsWidth('},', content) || + endsWidth('};', content) || + endsWidth('}\n', content) ? + 2 : + 0 + ); + + // + const result = content.substring(1, endIndex); + return result; +} + +/** + * clear array sign ... + * + * @param {string} content + * @returns cleared content ... + */ +function clearArraySurround(content = '') { + // + // Validate Args ... + if ( + !isValidArg(content) || + !isSurroundedArray(content) + ) { + return content; + } + + // + const endIndex = content.length - ( + endsWidth(']', content) ? + 1 : + endsWidth('],', content) || + endsWidth('];', content) || + endsWidth(']\n', content) ? + 2 : + 0 + ); + + // + const result = content.substring(1, endIndex); + return result; +} + +/** + * slice a content to individual parts ... + * + * @param {number|number[]} indexes which indexes used to slice ... + * @param {strng} content the content which going to sliced ... + * @returns sliced parts of content ... + */ +function sliceContent(indexes, content) { + // + // Validate Args ... + if ( + !isValidArg(indexes) || + !isValidArg(content) + ) { + return []; + } + + // + // Normalize indexes ... + const normalIndexes = toNormalArray(indexes) + .filter(index => index > -1 && index < content.length); + if (!hasChildArray(normalIndexes)) { + return []; + } + + // + let result = ['']; + result.pop(); + + // + let lastIndex = 0; + for (const index of normalIndexes) { + // + const part = content.substring(lastIndex, index); + if (isValidArg(part)) { + result.push(part.trim()); + } + + // + lastIndex = index + 1; + } + + // + // check remained parts ... + if (lastIndex < content.length) { + // + const part = content.substring(lastIndex); + if (isValidArg(part)) { + result.push(part.trim()); + } + } + + // + return result; +} + + + +/** + * check an string is starts and ends with specified symbol ... + * + * @param {string} symbol specified search symbol ... + * @param {string} content specified search content ... + * @param {boolean} ignoreCase specified search case sensitive or not ... + * @returns + */ +function isSurrounded( + symbol = '', + content = '', + ignoreCase = true +) { + // + // Validate Args ... + if (!isValidArg(content)) { + return false; + } + + // + if (!isValidArg(symbol)) { + return true; + } + + // + symbol = symbol.trim(); + + // + let result = false; + result = (!!ignoreCase ? + content + .toLowerCase() + .startsWith(symbol.toLowerCase()) : + content.startsWith(symbol) + ) && + endsWidth(symbol, content, ignoreCase); + + // + return result; +} + +/** + * determines a content is an string surrounded value or not ... + * + * @param {string} content a content which going to check ... + * @returns + */ +function isSurroundedString(content = '') { + // + // Validate Args ... + if (!isValidArg(content)) { + return false; + } + + // + let result = false; + result = ( + content.startsWith('\'') && + ( + endsWidth('\'', content) || + endsWidth('\',', content) || + endsWidth('\';', content) || + endsWidth('\'\n', content) + ) || + content.startsWith('\"') && + ( + endsWidth('\"', content) || + endsWidth('\",', content) || + endsWidth('\";', content) || + endsWidth('\"\n', content) + ) || + content.startsWith('`') && + ( + endsWidth('\`', content) || + endsWidth('\`,', content) || + endsWidth('\`;', content) || + endsWidth('\`\n', content) + ) + ); + return result; +} + +/** + * check a content is an array content or not ... + * + * @param {string} content + * @returns + */ +function isSurroundedArray(content = '') { + // + // Validate Args ... + if (!isValidArg(content)) { + return false; + } + + // + let result = false; + result = content.startsWith('[') && + ( + endsWidth(']', content) || + endsWidth('],', content) || + endsWidth('];', content) || + endsWidth(']\n', content) + ); + return result; +} + +/** + * check a content is an object content or not ... + * + * @param {string} content + * @returns + */ +function isSurroundedObject(content = '') { + // + // Validate Args ... + if (!isValidArg(content)) { + return false; + } + + // + let result = false; + result = content.startsWith('{') && + ( + endsWidth('}', content) || + endsWidth('},', content) || + endsWidth('};', content) || + endsWidth('}\n', content) + ); + return result; +} + +/** + * check an index model is contains inside anothers or not ... + * + * @param {{ start: number, end: number}} source the source index model to check ... + * @param {...{ start: number, end: number}} dest the collection of index models which going to check ... + * @returns + */ +function isIndexInside(source, ...dest) { + // + let result = false; + + // + // Validate Args ... + if (!source || !dest) { + return result; + } + + // + // Check idx is standard ... + if ( + !source || + !source.end || + !source.start || + source.start > source.end + ) { + return false; + } + + // + // Check types of destinations ... + for (let index of dest) { + // + // Check idx is standard ... + if ( + !index || + !index.end || + !index.start || + index.start > index.end + ) { + return false; + } + } + + // + if (dest.includes(source)) { + dest = dest.filter(d => d !== source); + } + + // + const insideContentIndex = dest.find(dIndex => { + // + const result = source.start >= dIndex.start && + source.start <= dIndex.end && + source.end >= dIndex.start && + source.end <= dIndex.end; + return result; + }); + + // + result = insideContentIndex; + return result; +} + +/** + * check a number exists in + * @param {number} idx the number which going to check ... + * @param {{ start: number, end: number}} index the index model + * @returns + */ +function isInsideIndex(idx = -1, index) { + // + let result = false; + + // + // Validate Args ... + if ( + !index || + !index.hasOwnProperty('end') || + !index.hasOwnProperty('start') + ) { + return result; + } + + // + result = idx > index.start && idx < index.end; + return result; +} + +/** + * determines an object is key/value and type ... + * + * @param {any} content the object which we are going to check ... + * @returns + */ +function isKeyValueType(content) { + // + const result = content && + content.hasOwnProperty('key') && + content.hasOwnProperty('type') && + content.hasOwnProperty('value'); + + // + return result; +} + + + +/** + * find all indexes of token(s) in content ... + * + * @param {string|string[]} tokens the tokens which require to search ... + * @param {string} content the content which using to search ... + * @returns {number[]} all occured indexes ... + */ +function findAllIndexes( + tokens, + content +) { + // + // Validate Args ... + if ( + !isValidArg(tokens) || + !isValidArg(content) + ) { + return []; + } + + // + // normalize tokens ... + const normalTokens = toNormalArray(tokens); + if (!hasChildArray(normalTokens)) { + return []; + } + + // + // Parse contents ... + let match; + const result = []; + const regExp = new RegExp( + normalTokens.join('|'), + 'g' + ); + while ((match = regExp.exec(content)) !== null) { + result.push(match.index); + } + if (!hasChildArray(result)) { + return []; + } + + // + return result; +} + +/** + * find nearest symbol in a content from specified index ... + * + * @param {string|string[]} candidates specifies which symbols to find ... + * @param {string} content te content for seasrch ... + * @param {number} startFromIndex the index of start position ... + * @returns + */ +function findNearest( + candidates, + content = '', + startFromIndex = 0 +) { + // + // Validate Args ... + if ( + !isValidArg(content) || + !isValidArg(candidates) || + !isValidArg(content.trim()) + ) { + return ''; + } + + // + // Normalize Content ... + content = content.trim(); + + // + // Normalize Starts From ... + startFromIndex = startFromIndex < 0 || startFromIndex > content.length - 1 ? 0 : startFromIndex; + + // + // Normalize Symbols ... + const normalCandidates = toNormalArray(candidates); + if (!hasChildArray(normalCandidates)) { + return ''; + } + + // + let nearestIndex = findNearestIndex( + normalCandidates, + content, + startFromIndex + ); + if (nearestIndex < 0) { + return ''; + } + + // + let result = content.substring( + nearestIndex, + nearestIndex + Math.max(...normalCandidates.map(nc => nc.length)) + ).trim(); + return result; +} + +/** + * search candidates in content and find nearest one and return it ... + * + * @param {string|string[]} candidates which candidates required to check ... + * @param {string} content the content which required to search candidates ... + * @param {number} startFromIndex the proposed index to start searching content from on ... + * @returns + */ +function findNearestIndex( + candidates = [''], + content = '', + startFromIndex = 0, + ignoreClosedItems = false +) { + // + // Validate Args ... + if ( + !isValidArg(content) || + !isValidArg(candidates) + ) { + return -1; + } + + // + // Mormalize startFromIndex value ... + startFromIndex = startFromIndex < 0 ? + 0 : + startFromIndex > content.length - 1 ? + content.length - 1 : + startFromIndex; + + // + // Normalize candidates ... + let normalCandidates = toNormalArray(candidates); + + // + if (!!ignoreClosedItems) { + normalCandidates = normalCandidates + .map(c => content.indexOf( + c, + startFromIndex + ) + ).map(c => +c) + .filter(c => +c > -1); + } else { + normalCandidates = findAllIndexesOutOfCloseds(normalCandidates, content) + .filter(c => c >= startFromIndex); + } + if (!hasChildArray(normalCandidates)) { + return -1; + } + + // + let result = -1; + result = Math.min(...normalCandidates); + + // + return result; +} + +/** + * find close index of specific sign in destination content ... + * + * @param {string} openSymbol open of area sign ... + * @param {string} closeSymbol close of area sign ... + * @param {string} content the destination content for parsing ... + * @param {number} startsFrom an start index of content for start parsing ... + * @returns + */ +function findCloseIndex( + openSymbol = '', + closeSymbol = '', + content = '', + startsFrom = -1 +) { + // + // Validate Args ... + if ( + startsFrom < 0 || + !isValidArg(content) || + !isValidArg(openSymbol) || + !isValidArg(closeSymbol) || + openSymbol === closeSymbol || + !content.includes(openSymbol) || + !content.includes(closeSymbol) || + startsFrom > content.length - 1 + ) { + return -1; + } + + // + let result = -1; + + // + let index = startsFrom; + const openSignStack = []; + while (index < content.length) { + // + const openCandidate = content.substring(index, index + openSymbol.length); + const closeCandidate = content.substring(index, index + closeSymbol.length); + + // + if (openCandidate === openSymbol) { + openSignStack.push(index); + } else if (closeCandidate === closeSymbol) { + // + if (openSignStack.length > 0) { + openSignStack.pop(); + } + + // + if (openSignStack.length === 0) { + result = index; + break; + } + } + + // + index++; + } + + // + return result; +} + +/** + * find closed contents of specific sign in destination content ... + * + * @param {string} openSymbol open of area sign ... + * @param {string} closeSymbol close of area sign ... + * @param {string} content the destination content for parsing ... + * @param {number} startsFrom an start index of content for start parsing ... + * @returns + */ +function findClosedContent( + openSymbol = '', + closeSymbol = '', + content = '', + startsFrom = 0, +) { + // + let result = [{ + start: -1, + end: -1, + content: '' + }]; + result.pop(); + + // + // Validate Args ... + if ( + !isValidArg(content) || + !isValidArg(openSymbol) || + !isValidArg(closeSymbol) || + !content.includes(openSymbol) || + !content.includes(closeSymbol) || + openSymbol.length >= content.length || + closeSymbol.length >= content.length + ) { + return result; + } + + // + // Normalize Start From ... + startsFrom = startsFrom < 0 || startsFrom >= content.length ? 0 : startsFrom; + + // + let canContinue = true; + while (!!canContinue) { + // + let openSymbolIndex = content.indexOf(openSymbol, startsFrom); + if (openSymbolIndex < 0) { + // + canContinue = false; + continue; + } + + // + let closeSymbolIndex = findCloseIndex( + openSymbol, + closeSymbol, + content, + openSymbolIndex + ); + if (closeSymbolIndex < 0) { + // + canContinue = false; + continue; + } + if (content.charAt(closeSymbolIndex) !== closeSymbol) { + closeSymbolIndex++; + } + + // + // Prevent ${} string interpolations ... + if ( + openSymbol === '{' && + openSymbolIndex - 1 >= 0 + ) { + // + const prevChar = content.charAt(openSymbolIndex - 1); + if (prevChar === '$') { + continue; + } + } + + // + const item = content + .substring(openSymbolIndex, closeSymbolIndex + 1) + .trim(); + startsFrom = closeSymbolIndex + 1; + result.push({ + start: openSymbolIndex, + end: closeSymbolIndex, + content: item + }); + } + + // + if (!hasChildArray(result)) { + return result; + } + + // + return result; +} + +/** + * extract a collection of closed contents exists in a content ... + * + * @param symbols a collection of open and close contents ... + * @param {string} content a content for searching inside it ... + * @returns a collection of closed content ... + */ +function findClosedContents( + symbols = [ + { + openSymbol: '', + closeSymbol: '' + } + ], + content = '' +) { + // + let result = [{ + start: -1, + end: -1, + content: '' + }]; + result.pop(); + + // + if ( + !isValidArg(content) || + !hasChildArray(symbols) + ) { + return []; + } + + // + symbols.forEach(symbol => { + // + const closedContent = findClosedContent( + symbol.openSymbol, + symbol.closeSymbol, + content + ); + + // + if (hasChildArray(closedContent)) { + result.push( + ...closedContent + ); + } + }); + + // + return result; +} + +/** + * find all string content closed items ... + * + * @param {string} content a content which going to search ... + * @returns a collection of index descriptors ... + */ +function findClosedStrings(content = '') { + // + // Validate Args ... + if (!isValidArg(content)) { + return []; + } + + // + // there are 3 types of strings ... + // ' " and ` + + // + const token1 = '\''; + const token1Stack = [0]; + token1Stack.pop(); + + // + const token2 = '"'; + const token2Stack = [0]; + token2Stack.pop(); + + // + const token3 = '`'; + const token3Stack = [0]; + token3Stack.pop(); + + // + const result = [ + { + start: -1, + end: -1, + content: '' + } + ]; + result.pop(); + + // + for (let i = 0; i < content.length; i++) { + // + const currentChart = content.charAt(i); + + // + //#region Token 1 ... + if (currentChart === token1) { + // + if (!hasChildArray(token1Stack)) { + token1Stack.push(i); + } else { + // + const start = token1Stack.pop(); + const end = i + 1; + const pContent = content.substring(start, end); + result.push({ + start, + end, + content: pContent + }); + } + } + //#endregion + + // + //#region Token 2 ... + if (currentChart === token2) { + // + if (!hasChildArray(token2Stack)) { + token2Stack.push(i); + } else { + // + const start = token2Stack.pop(); + const end = i + 1; + const pContent = content.substring(start, end); + result.push({ + start, + end, + content: pContent + }); + } + } + //#endregion + + // + //#region Token 3 ... + if (currentChart === token3) { + // + if (!hasChildArray(token3Stack)) { + token3Stack.push(i); + } else { + // + const start = token3Stack.pop(); + const end = i + 1; + const pContent = content.substring(start, end); + result.push({ + start, + end, + content: pContent + }); + } + } + //#endregion + } + + // + return result; +} + +/** + * find container indexes from within indexes ... + * + * @param {...{start: number, end: number}} index the collection of indexes to find beiggers inside ... + * @returns + */ +function findContainerIndexes(...index) { + // + let result = [ + { + start: -1, + end: -1, + content: '' + } + ]; + result.pop(); + + // + let concatedIndexes = []; + for (let idx of index) { + // + // Index Childs ... + for (let iix of idx) { + // + // Check idx is standard ... + if ( + !iix || + !iix.end || + !iix.start || + iix.start > iix.end + ) { + continue; + } + + // + // Check index inside another index ... + concatedIndexes.push(iix); + } + } + + // + if (!hasChildArray(concatedIndexes)) { + return result; + } + + // + for (let ccIdx of concatedIndexes) { + // + const isInside = isIndexInside(ccIdx, ...concatedIndexes); + if (isInside) { + continue; + } + + // + if (hasChildArray(result)) { + // + const isInsideResult = isIndexInside(ccIdx, ...result); + if (isInsideResult) { + continue; + } + } + + // + result.push(ccIdx); + } + + // + return result; +} + +/** + * find all tokens indexes inside a content which they are not inside closed items ... + * + * @param {string|string[]} tokens the tokens which require to search ... + * @param {string} content the content which using to search ... + * @returns + */ +function findAllIndexesOutOfCloseds1( + tokens, + content, +) { + // + // Validate Args ... + if (!isValidArg(content)) { + return []; + } + + // + // Extract token Indexes ... + const tokensIndexes = findAllIndexes(tokens, content); + if (!hasChildArray(tokensIndexes)) { + return []; + } + + // + // Extract Closed Objects Indexes ... + const closedArrays = findClosedContent('[', ']', content); + const closedObjects = findClosedContent('{', '}', content); + const closedStrings = findClosedStrings(content); + if ( + !hasChildArray(closedObjects) && + !hasChildArray(closedArrays) && + !hasChildArray(closedStrings) + ) { + return tokensIndexes; + } + + // + // find bigger sloded ... + let containerClosedItems = findContainerIndexes( + closedArrays, + closedObjects, + closedStrings + ).filter(i => !isSurroundedString(i.content)); + + // + let result = [-1]; + result.pop(); + + // + for (let tIdx of tokensIndexes) { + // + let canAdd = true; + for (let ccIndex of containerClosedItems) { + // + canAdd = isInsideIndex(tIdx, ccIndex); + if (canAdd) { + break; + } + } + + // + if (!canAdd) { + result.push(tIdx); + } + } + + // + return result; +} + +/** + * find all tokens indexes inside a content which they are not inside closed items ... + * + * @param {string|string[]} tokens the tokens which require to search ... + * @param {string} content the content which using to search ... + * @returns + */ +function findAllIndexesOutOfCloseds( + tokens, + content, +) { + // + // Define Result ... + let result = [-1]; + result.pop(); + + // + // Validate Args ... + if (!isValidArg(content)) { + return []; + } + + // + // Normalize Tokens ... + const normalizeTokens = toNormalArray(tokens); + if (!hasChildArray(normalizeTokens)) { + return result; + } + + // + // Extract Closed Objects Indexes ... + let openSymbols = [ + '{', + '[', + '\'', + '"', + '`' + ]; + let closeSymbols = [ + '}', + ']', + '\'', + '"', + '`' + ]; + let openStack = []; + let lastOpenSymbol = ''; + let lastClosedIndex = -1; + for (let i = 0; i < content.length; i++) { + // + const prevChar = content.charAt(i - 1) || ''; + const char = content.charAt(i); + const nextChar = content.charAt(i + 1) || ''; + + // + const isToken = normalizeTokens.includes(char); + const isOpenSymbol = openSymbols.includes(char); + const isCloseSymbol = closeSymbols.includes(char); + + // + if (isOpenSymbol && lastOpenSymbol !== char) { + openStack.push(i); + lastOpenSymbol = char === '\'' || char === '"' || char === '`' ? char : lastOpenSymbol; + } else if (isCloseSymbol) { + // + lastOpenSymbol = char === '\'' || char === '"' || char === '`' ? '' : lastOpenSymbol; + let lastPop = openStack.pop(); + if (openStack.length === 0) { + lastClosedIndex = lastPop; + } + } else if (isToken) { + if ( + i > lastClosedIndex && + openStack.length === 0 + ) { + result.push(i); + } + } + } + + // + return result; +} +//#endregion + +// +//#region Validator Actions ... +/** + * check specified value is Valid URL Address or not ... + * + * @param {string} value specified string which required to check + * @returns {boolean} + */ +function isValidURL(value = "") { + // + if (!isValidArg(value)) { + return false; + } + + const pattern = new RegExp( + '^([a-zA-Z]+:\\/\\/)?' + // protocol + '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name + '((\\d{1,3}\\.){3}\\d{1,3}))' + // OR IP (v4) address + '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path + '(\\?[;&a-z\\d%_.~+=-]*)?' + // query string + '(\\#[-a-z\\d_]*)?$', // fragment locator + 'i' + ); + + // + const result = pattern.test(value); + return result; +} + +/** + * check specified value is Valid URL Address or not ... + * + * @param {string} value specified string which required to check + */ +function isValidEmail(value = "") { + // + if (!isValidArg(value)) { + return false; + } + + // + const pattern = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}/gim; + + // + const result = pattern.test(value); + return result; +} +//#endregion + +// +//#region Normallizers ... +/** + * normallize an string ... + * + * @param {string} value specified value ... + * @returns {string} + */ +function toNormalString(value = "") { + // + if (!XTypeTools.isString(value)) { + value = ""; + } + + // + const result = value + .trim() + .toLowerCase(); + + // + return result; +} +//#endregion + +// +//#region Beautifiers ... +/** + * converts an object to string based on json and beautify result ... + * + * @param {any} object + * @returns {string} + */ +function beautifyJSON(object = undefined) { + // + // TODO: Complete this ... + let result = ""; + + // + // validate object ... + if (object === undefined) { + return result; + } + + // + // Convert result ... + result = JSON.stringify( + object, + null, + 4 + ); + + // + return result; +} +//#endregion + +// +//#region Module Exports ... +module.exports = { + // + endsWidth, + surroundBy, + isValidArg, + isValidArgs, + toNormalArray, + hasChildArray, + toStringExpression, + + // + sliceContent, + clearContent, + clearSurround, + clearArraySurround, + clearObjectSurround, + + // + isSurrounded, + isIndexInside, + isInsideIndex, + isKeyValueType, + isSurroundedArray, + isSurroundedString, + isSurroundedObject, + + // + findNearest, + findAllIndexes, + findCloseIndex, + findNearestIndex, + findClosedContent, + findClosedStrings, + findClosedContents, + findAllIndexesOutOfCloseds, + + // + // Validator Actions ... + isValidURL, + isValidEmail, + + // + // Normallizers ... + toNormalString, + + // + beautifyJSON, +} +//#endregion \ No newline at end of file diff --git a/Modules/xApiSDKHolder b/Modules/xApiSDKHolder index 2f271b2..60b4f6d 160000 --- a/Modules/xApiSDKHolder +++ b/Modules/xApiSDKHolder @@ -1 +1 @@ -Subproject commit 2f271b21a216f773713329d562ad9cc90868af9a +Subproject commit 60b4f6df69574b6765422e88ddc729ca1642ff0d diff --git a/Modules/xCategoryService b/Modules/xCategoryService index 335fd95..6c7fd31 160000 --- a/Modules/xCategoryService +++ b/Modules/xCategoryService @@ -1 +1 @@ -Subproject commit 335fd95d234b7b40796cf71cec85e159f788496d +Subproject commit 6c7fd31a9390ed6314f26968bb6d4c23eda78b51 diff --git a/Modules/xCommons b/Modules/xCommons index f9c6671..870e959 160000 --- a/Modules/xCommons +++ b/Modules/xCommons @@ -1 +1 @@ -Subproject commit f9c6671dd7f683b2f59d5d9d76c864ebc2db86a2 +Subproject commit 870e9596afa49e25ce6c0efe097849680abee9ee diff --git a/Modules/xDataService b/Modules/xDataService index 2b17be0..9e8fc9a 160000 --- a/Modules/xDataService +++ b/Modules/xDataService @@ -1 +1 @@ -Subproject commit 2b17be057516678dea1187891c310d5563b1a189 +Subproject commit 9e8fc9a510bfcc6845cbd170c1aba9041f121205 diff --git a/Modules/xEventService b/Modules/xEventService index 4765d25..564c80f 160000 --- a/Modules/xEventService +++ b/Modules/xEventService @@ -1 +1 @@ -Subproject commit 4765d255af357232cb4897239b4ca8b9ac9c5831 +Subproject commit 564c80fd912d23e08beaafafa69f8f7e48093bce diff --git a/Modules/xExceptions b/Modules/xExceptions index 158968e..de7d2c1 160000 --- a/Modules/xExceptions +++ b/Modules/xExceptions @@ -1 +1 @@ -Subproject commit 158968ec8852d8b6c2405ddb501b7780728a7032 +Subproject commit de7d2c18cc7ec28245a4c8a018e5f28db294f00d diff --git a/Modules/xFileService b/Modules/xFileService index 19ef6aa..3b2a869 160000 --- a/Modules/xFileService +++ b/Modules/xFileService @@ -1 +1 @@ -Subproject commit 19ef6aa887e701115c97ea0ce52d254b0fbd6a7c +Subproject commit 3b2a869fb2b0f9d4ed64c83b07f2c095a7927fdd diff --git a/Modules/xFrameworkComponentsHolder b/Modules/xFrameworkComponentsHolder new file mode 160000 index 0000000..8ab1322 --- /dev/null +++ b/Modules/xFrameworkComponentsHolder @@ -0,0 +1 @@ +Subproject commit 8ab13222229f1220bf1a2950ed367c627bd6e16c diff --git a/Modules/xFrameworkCoreHolder b/Modules/xFrameworkCoreHolder new file mode 160000 index 0000000..fb123af --- /dev/null +++ b/Modules/xFrameworkCoreHolder @@ -0,0 +1 @@ +Subproject commit fb123afb951e78816f2820398229746a7f95f3a9 diff --git a/Modules/xFrameworkIdentitySDKHolder b/Modules/xFrameworkIdentitySDKHolder new file mode 160000 index 0000000..d0b8116 --- /dev/null +++ b/Modules/xFrameworkIdentitySDKHolder @@ -0,0 +1 @@ +Subproject commit d0b81164480c270e3dee8ea96310a18a6e94bc4d diff --git a/Modules/xFrameworkPushServiceHolder b/Modules/xFrameworkPushServiceHolder new file mode 160000 index 0000000..bbdabb0 --- /dev/null +++ b/Modules/xFrameworkPushServiceHolder @@ -0,0 +1 @@ +Subproject commit bbdabb010846251cdedf2153315cd2ef3ba3c0e1 diff --git a/Modules/xFrameworkServicesHolder b/Modules/xFrameworkServicesHolder new file mode 160000 index 0000000..d34473a --- /dev/null +++ b/Modules/xFrameworkServicesHolder @@ -0,0 +1 @@ +Subproject commit d34473ad525e2c5a788008efaf6372dfecbaf3df diff --git a/Modules/xHttpService b/Modules/xHttpService index b122b51..7c07bfc 160000 --- a/Modules/xHttpService +++ b/Modules/xHttpService @@ -1 +1 @@ -Subproject commit b122b5183358fee307d93589b6b44a9d1124af98 +Subproject commit 7c07bfc063e36671774ec79337db1d701db479f2 diff --git a/Modules/xIdentityModels b/Modules/xIdentityModels index ccae613..ca6fbc2 160000 --- a/Modules/xIdentityModels +++ b/Modules/xIdentityModels @@ -1 +1 @@ -Subproject commit ccae6137fbe4cf3286ecb9689d23938a68c5cdb4 +Subproject commit ca6fbc2201d520e2cadc874cb906e83aeb19daae diff --git a/Modules/xIdentityService b/Modules/xIdentityService index 7e6f547..4be04e5 160000 --- a/Modules/xIdentityService +++ b/Modules/xIdentityService @@ -1 +1 @@ -Subproject commit 7e6f547081f9e43c999bd860a9b28cf775e667ec +Subproject commit 4be04e5ddf15a07acb8f7a44e12344162d14570f diff --git a/Modules/xMessageService b/Modules/xMessageService index 53e2e74..2817e62 160000 --- a/Modules/xMessageService +++ b/Modules/xMessageService @@ -1 +1 @@ -Subproject commit 53e2e7473dd0a9821bf6827dcf40a52681119a82 +Subproject commit 2817e62d9334cec9b9b0e5c51e5ef37f3fbf636f diff --git a/Modules/xModels b/Modules/xModels index fa57be5..4c8a06a 160000 --- a/Modules/xModels +++ b/Modules/xModels @@ -1 +1 @@ -Subproject commit fa57be5fa5cb59640bbca75a166f2ec4cdc9bf53 +Subproject commit 4c8a06aa3e1ef33b3adb7284ca880014a7b6508b diff --git a/Modules/xPushService b/Modules/xPushService index ba3adfe..844ea07 160000 --- a/Modules/xPushService +++ b/Modules/xPushService @@ -1 +1 @@ -Subproject commit ba3adfeadbe288b090f553b20cbd2b024f6efc3b +Subproject commit 844ea07a0266eab75642f051ebe5ccbe0e4b2ef1 diff --git a/Modules/xStorageService b/Modules/xStorageService index 6e70137..f323c85 160000 --- a/Modules/xStorageService +++ b/Modules/xStorageService @@ -1 +1 @@ -Subproject commit 6e701376dffe3925b1aa3f9b0a8a2b4dab6a83dd +Subproject commit f323c85bb60d996fc919e9ddbe7662febf402417 diff --git a/Modules/xStringService b/Modules/xStringService index 85fbc08..e6920fd 160000 --- a/Modules/xStringService +++ b/Modules/xStringService @@ -1 +1 @@ -Subproject commit 85fbc0899dab91131657f49fa9ca26e9fbf15c3e +Subproject commit e6920fd447af9d5b2dedc3e8dbc53ebb51316e48 diff --git a/Modules/xTagService b/Modules/xTagService index 003c313..2a5009f 160000 --- a/Modules/xTagService +++ b/Modules/xTagService @@ -1 +1 @@ -Subproject commit 003c313d7cae5b180eb43e3b0b4a964421249510 +Subproject commit 2a5009febc0aded2f6913a50fcba532391a982c2 diff --git a/Modules/xTermsAndConditionsService b/Modules/xTermsAndConditionsService index 313897e..8434172 160000 --- a/Modules/xTermsAndConditionsService +++ b/Modules/xTermsAndConditionsService @@ -1 +1 @@ -Subproject commit 313897e91152e547c07f38e7ece7d8c2cc1ab2cc +Subproject commit 84341720185c1e9c4b1fc594f565f94d5c70839a diff --git a/Modules/xWebinarService b/Modules/xWebinarService index 00b2819..f559811 160000 --- a/Modules/xWebinarService +++ b/Modules/xWebinarService @@ -1 +1 @@ -Subproject commit 00b28191307712b0219226f4e57683b33a75fa7d +Subproject commit f559811f8bfa94607110c77532031794867dd4aa diff --git a/package.json b/package.json index fdc7d07..bd7684a 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "z01": "|---------------|", "z02": "| Common |", "z03": "|---------------|", + "true": "", "loginRegistry": "npm login --registry https://npm.saherelmhub.ir/", "addUserRegistry": "npm adduser --registry https://npm.saherelmhub.ir/", "z04": "|---------------|", @@ -22,10 +23,25 @@ "z4": "|---------------|", "z5": "| /Git |", "z6": "|---------------|", + "q1": "|---------------|", + "q2": "| Scripts |", + "q3": "|---------------|", + "moduleHandler": "node ./Documents/Scripts/nugetModuleHandler.js", + "do": "npm run moduleHandler **do", + "doClean": "npm run do clean", + "doRestore": "npm run do restore", + "doBuild": "npm run do build", + "doRmBin": "npm run do rmBin", + "doClearPacked": "npm run do clearPacked", + "doPack": "npm run do pack", + "doCp": "npm run do cp", + "doRePack": "npm run do repack", + "q4": "|---------------|", + "q5": "| /Scripts |", + "q6": "|---------------|", "a1": "|---------------|", "a2": "| DotNet |", "a3": "|---------------|", - "true": "", "dotClean": "dotnet clean", "dotBuild": "dotnet build", "dotRestore": "dotnet restore", @@ -64,6 +80,286 @@ "g4": "|---------------|", "g5": "| /Nuget |", "g6": "|---------------|", + "zz1": "|---------------|", + "zz2": "| NugetModules |", + "zz3": "|---------------|", + "zza1": "|---------------|", + "zza2": "| xExceptions |", + "zza3": "|---------------|", + "cleanExceptions": "npm run doClean xExceptions", + "restoreExceptions": "npm run doRestore xExceptions", + "buildExceptions": "npm run doBuild xExceptions", + "packExceptions": "npm run doPack xExceptions", + "cpExceptions": "npm run doCp xExceptions", + "repackExceptions": "npm run doRePack xExceptions", + "zza4": "|---------------|", + "zza5": "| /xExceptions |", + "zza6": "|---------------|", + "zzb1": "|---------------|", + "zzb2": "| xCommons |", + "zzb3": "|---------------|", + "cleanCommons": "npm run doClean xCommons", + "restoreCommons": "npm run doRestore xCommons", + "buildCommons": "npm run doBuild xCommons", + "packCommons": "npm run doPack xCommons", + "cpCommons": "npm run doCp xCommons", + "repackCommons": "npm run doRePack xCommons", + "zzb4": "|---------------|", + "zzb5": "| /xCommons |", + "zzb6": "|---------------|", + "zzc1": "|---------------|", + "zzc2": "| xModels |", + "zzc3": "|---------------|", + "cleanModels": "npm run doClean xModels", + "restoreModels": "npm run doRestore xModels", + "buildModels": "npm run doBuild xModels", + "packModels": "npm run doPack xModels", + "cpModels": "npm run doCp xModels", + "repackModels": "npm run doRePack xModels", + "zzc4": "|---------------|", + "zzc5": "| /xModels |", + "zzc6": "|---------------|", + "zzd1": "|------------------|", + "zzd2": "| xMessageService |", + "zzd3": "|------------------|", + "cleanMessageService": "npm run doClean xMessageService", + "restoreMessageService": "npm run doRestore xMessageService", + "buildMessageService": "npm run doBuild xMessageService", + "packMessageService": "npm run doPack xMessageService", + "cpMessageService": "npm run doCp xMessageService", + "repackMessageService": "npm run doRePack xMessageService", + "zzd4": "|------------------|", + "zzd5": "| /xMessageService |", + "zzd6": "|------------------|", + "zze1": "|------------------|", + "zze2": "| xStorageService |", + "zze3": "|------------------|", + "cleanStorageService": "npm run doClean xStorageService", + "restoreStorageService": "npm run doRestore xStorageService", + "buildStorageService": "npm run doBuild xStorageService", + "packStorageService": "npm run doPack xStorageService", + "cpStorageService": "npm run doCp xStorageService", + "repackStorageService": "npm run doRePack xStorageService", + "zze4": "|------------------|", + "zze5": "| /xStorageService |", + "zze6": "|------------------|", + "zzf1": "|---------------|", + "zzf2": "| xDataService |", + "zzf3": "|---------------|", + "cleanDataService": "npm run doClean xDataService", + "restoreDataService": "npm run doRestore xDataService", + "buildDataService": "npm run doBuild xDataService", + "packDataService": "npm run doPack xDataService", + "cpDataService": "npm run doCp xDataService", + "repackDataService": "npm run doRePack xDataService", + "zzf4": "|---------------|", + "zzf5": "| /xDataService |", + "zzf6": "|---------------|", + "zzg1": "|------------------|", + "zzg2": "| xIdentityModels |", + "zzg3": "|------------------|", + "cleanIdentityModels": "npm run doClean xIdentityModels", + "restoreIdentityModels": "npm run doRestore xIdentityModels", + "buildIdentityModels": "npm run doBuild xIdentityModels", + "packIdentityModels": "npm run doPack xIdentityModels", + "cpIdentityModels": "npm run doCp xIdentityModels", + "repackIdentityModels": "npm run doRePack xIdentityModels", + "zzg4": "|------------------|", + "zzg5": "| /xIdentityModels |", + "zzg6": "|------------------|", + "zzh1": "|---------------|", + "zzh2": "| xHttpService |", + "zzh3": "|---------------|", + "cleanHttpService": "npm run doClean xHttpService", + "restoreHttpService": "npm run doRestore xHttpService", + "buildHttpService": "npm run doBuild xHttpService", + "packHttpService": "npm run doPack xHttpService", + "cpHttpService": "npm run doCp xHttpService", + "repackHttpService": "npm run doRePack xHttpService", + "zzh4": "|---------------|", + "zzh5": "| /xHttpService |", + "zzh6": "|---------------|", + "zzi1": "|-------------------|", + "zzi2": "| xIdentityService |", + "zzi3": "|-------------------|", + "cleanIdentityService": "npm run doClean xIdentityService", + "restoreIdentityService": "npm run doRestore xIdentityService", + "buildIdentityService": "npm run doBuild xIdentityService", + "packIdentityService": "npm run doPack xIdentityService", + "cpIdentityService": "npm run doCp xIdentityService", + "repackIdentityService": "npm run doRePack xIdentityService", + "zzi4": "|-------------------|", + "zzi5": "| /xIdentityService |", + "zzi6": "|-------------------|", + "zzj1": "|---------------|", + "zzj2": "| xPushService |", + "zzj3": "|---------------|", + "cleanPushService": "npm run doClean xPushService", + "restorePushService": "npm run doRestore xPushService", + "buildPushService": "npm run doBuild xPushService", + "packPushService": "npm run doPack xPushService", + "cpPushService": "npm run doCp xPushService", + "repackPushService": "npm run doRePack xPushService", + "zzj4": "|---------------|", + "zzj5": "| /xPushService |", + "zzj6": "|---------------|", + "zzk1": "|----------------|", + "zzk2": "| xEventService |", + "zzk3": "|----------------|", + "cleanEventService": "npm run doClean xEventService", + "restoreEventService": "npm run doRestore xEventService", + "buildEventService": "npm run doBuild xEventService", + "packEventService": "npm run doPack xEventService", + "cpEventService": "npm run doCp xEventService", + "repackEventService": "npm run doRePack xEventService", + "zzk4": "|----------------|", + "zzk5": "| /xEventService |", + "zzk6": "|----------------|", + "zzl1": "|-----------------|", + "zzl2": "| xStringService |", + "zzl3": "|-----------------|", + "cleanStringService": "npm run doClean xStringService", + "restoreStringService": "npm run doRestore xStringService", + "buildStringService": "npm run doBuild xStringService", + "packStringService": "npm run doPack xStringService", + "cpStringService": "npm run doCp xStringService", + "repackStringService": "npm run doRePack xStringService", + "zzl4": "|-----------------|", + "zzl5": "| /xStringService |", + "zzl6": "|-----------------|", + "zzm1": "|-----------------------------|", + "zzm2": "| xTermsAndConditionsService |", + "zzm3": "|-----------------------------|", + "cleanTermsAndConditionsService": "npm run doClean xTermsAndConditionsService", + "restoreTermsAndConditionsService": "npm run doRestore xTermsAndConditionsService", + "buildTermsAndConditionsService": "npm run doBuild xTermsAndConditionsService", + "packTermsAndConditionsService": "npm run doPack xTermsAndConditionsService", + "cpTermsAndConditionsService": "npm run doCp xTermsAndConditionsService", + "repackTermsAndConditionsService": "npm run doRePack xTermsAndConditionsService", + "zzm4": "|-----------------------------|", + "zzm5": "| /xTermsAndConditionsService |", + "zzm6": "|-----------------------------|", + "zzn1": "|-------------------|", + "zzn2": "| xCategoryService |", + "zzn3": "|-------------------|", + "cleanCategoryService": "npm run doClean xCategoryService", + "restoreCategoryService": "npm run doRestore xCategoryService", + "buildCategoryService": "npm run doBuild xCategoryService", + "packCategoryService": "npm run doPack xCategoryService", + "cpCategoryService": "npm run doCp xCategoryService", + "repackCategoryService": "npm run doRePack xCategoryService", + "zzn4": "|-------------------|", + "zzn5": "| /xCategoryService |", + "zzn6": "|-------------------|", + "zzo1": "|--------------|", + "zzo2": "| xTagService |", + "zzo3": "|--------------|", + "cleanTagService": "npm run doClean xTagService", + "restoreTagService": "npm run doRestore xTagService", + "buildTagService": "npm run doBuild xTagService", + "packTagService": "npm run doPack xTagService", + "cpTagService": "npm run doCp xTagService", + "repackTagService": "npm run doRePack xTagService", + "zzo4": "|--------------|", + "zzo5": "| /xTagService |", + "zzo6": "|--------------|", + "zzp1": "|---------------|", + "zzp2": "| xFileService |", + "zzp3": "|---------------|", + "cleanFileService": "npm run doClean xFileService", + "restoreFileService": "npm run doRestore xFileService", + "buildFileService": "npm run doBuild xFileService", + "packFileService": "npm run doPack xFileService", + "cpFileService": "npm run doCp xFileService", + "repackFileService": "npm run doRePack xFileService", + "zzp4": "|---------------|", + "zzp5": "| /xFileService |", + "zzp6": "|---------------|", + "zzq1": "|------------------|", + "zzq2": "| xWebinarService |", + "zzq3": "|------------------|", + "cleanWebinarService": "npm run doClean xWebinarService", + "restoreWebinarService": "npm run doRestore xWebinarService", + "buildWebinarService": "npm run doBuild xWebinarService", + "packWebinarService": "npm run doPack xWebinarService", + "cpWebinarService": "npm run doCp xWebinarService", + "repackWebinarService": "npm run doRePack xWebinarService", + "zzq4": "|------------------|", + "zzq5": "| /xWebinarService |", + "zzq6": "|------------------|", + "zzr1": "|-------------|", + "zzr2": "| xAiModels |", + "zzr3": "|-------------|", + "cleanAiModels": "npm run doClean xAiModels", + "restoreAiModels": "npm run doRestore xAiModels", + "buildAiModels": "npm run doBuild xAiModels", + "packAiModels": "npm run doPack xAiModels", + "cpAiModels": "npm run doCp xAiModels", + "repackAiModels": "npm run doRePack xAiModels", + "zzr4": "|-------------|", + "zzr5": "| /xAiModels |", + "zzr6": "|-------------|", + "zzs1": "|-------------|", + "zzs2": "| xAiService |", + "zzs3": "|-------------|", + "cleanAiService": "npm run doClean xAiService", + "restoreAiService": "npm run doRestore xAiService", + "buildAiService": "npm run doBuild xAiService", + "packAiService": "npm run doPack xAiService", + "cpAiService": "npm run doCp xAiService", + "repackAiService": "npm run doRePack xAiService", + "zzs4": "|-------------|", + "zzs5": "| /xAiService |", + "zzs6": "|-------------|", + "zz4": "|---------------|", + "zz5": "| /NugetModules |", + "zz6": "|---------------|", + "xx1": "|--------------|", + "xx2": "| NpmModules |", + "xx3": "|--------------|", + "xxa1": "|-----------------|", + "xxa2": "| xFrameworkCore |", + "xxa3": "|-----------------|", + "buildFrameworkCore":"cd ./Modules/xFrameworkCoreHolder && npm run buildModule", + "repackFrameworkCore":"cd ./Modules/xFrameworkCoreHolder && npm run publishModule", + "xxa4": "|-----------------|", + "xxa5": "| /xFrameworkCore |", + "xxa6": "|-----------------|", + "xxb1": "|---------------------|", + "xxb2": "| xFrameworkServices |", + "xxb3": "|---------------------|", + "buildFrameworkServices":"cd ./Modules/xFrameworkServicesHolder && npm run buildModule", + "repackFrameworkServices":"cd ./Modules/xFrameworkServicesHolder && npm run publishModule", + "xxb4": "|---------------------|", + "xxb5": "| /xFrameworkServices |", + "xxb6": "|---------------------|", + "xxc1": "|-----------------------|", + "xxc2": "| xFrameworkComponents |", + "xxc3": "|-----------------------|", + "buildFrameworkComponents":"cd ./Modules/xFrameworkComponentsHolder && npm run buildModule", + "repackFrameworkComponents":"cd ./Modules/xFrameworkComponentsHolder && npm run publishModule", + "xxc4": "|-----------------------|", + "xxc5": "| /xFrameworkComponents |", + "xxc6": "|-----------------------|", + "xxd1": "|------------------------|", + "xxd2": "| xFrameworkPushService |", + "xxd3": "|------------------------|", + "buildFrameworkPushService":"cd ./Modules/xFrameworkPushServiceHolder && npm run buildModule", + "repackFrameworkPushService":"cd ./Modules/xFrameworkPushServiceHolder && npm run publishModule", + "xxd4": "|------------------------|", + "xxd5": "| /xFrameworkPushService |", + "xxd6": "|------------------------|", + "xxe1": "|----------------------------|", + "xxe2": "| xFrameworkIdentityService |", + "xxe3": "|----------------------------|", + "buildFrameworkIdentityService":"cd ./Modules/xFrameworkIdentityServiceHolder && npm run buildModule", + "repackFrameworkIdentityService":"cd ./Modules/xFrameworkIdentityServiceHolder && npm run publishModule", + "xxe4": "|----------------------------|", + "xxe5": "| /xFrameworkIdentityService |", + "xxe6": "|----------------------------|", + "xx4": "|--------------|", + "xx5": "| /NpmModules |", + "xx6": "|--------------|", "b1": "|---------------|", "b2": "| Api |", "b3": "|---------------|", diff --git a/xClient b/xClient index 594d233..16696d0 160000 --- a/xClient +++ b/xClient @@ -1 +1 @@ -Subproject commit 594d23304875b8ba90c637ba48c3d4bd16aa7bf4 +Subproject commit 16696d08d0635c3844a731da1ce3d791df70b1ba