Initial Commit on git.saherelm.ir ...
This commit is contained in:
@@ -0,0 +1,16 @@
|
|||||||
|
# Editor configuration, see https://editorconfig.org
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.ts]
|
||||||
|
quote_type = single
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
max_line_length = off
|
||||||
|
trim_trailing_whitespace = false
|
||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# compiled output
|
||||||
|
/dist
|
||||||
|
/tmp
|
||||||
|
/out-tsc
|
||||||
|
# Only exists if Bazel was run
|
||||||
|
/bazel-out
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
|
||||||
|
# profiling files
|
||||||
|
chrome-profiler-events*.json
|
||||||
|
|
||||||
|
# IDEs and editors
|
||||||
|
/.idea
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
|
.c9/
|
||||||
|
*.launch
|
||||||
|
.settings/
|
||||||
|
*.sublime-workspace
|
||||||
|
|
||||||
|
# IDE - VSCode
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.history/*
|
||||||
|
|
||||||
|
# misc
|
||||||
|
/.sass-cache
|
||||||
|
/connect.lock
|
||||||
|
/coverage
|
||||||
|
/libpeerconnection.log
|
||||||
|
npm-debug.log
|
||||||
|
yarn-error.log
|
||||||
|
testem.log
|
||||||
|
/typings
|
||||||
|
|
||||||
|
# System Files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
#
|
||||||
|
documentation
|
||||||
|
package-lock.json
|
||||||
|
|
||||||
|
#
|
||||||
|
.angular
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# XFrameworkServices
|
||||||
|
|
||||||
|
contains some sort of usefull services, framework controller services, directives based on services and etc.
|
||||||
|
|
||||||
|
most important service in this module is XManagerService which responsible for providing all features of this module in a singe easy to use interface.
|
||||||
|
|
||||||
|
depends on x-framework-core.
|
||||||
|
|
||||||
|
## Maintainer
|
||||||
|
|
||||||
|
Hadi Khazaee asl
|
||||||
|
|
||||||
|
[https://www.saherelm.ir](https://www.saherelm.ir)
|
||||||
|
|
||||||
|
[hadi_khazaee_asl@yahoo.com](mailto:hadi_khazaee_asl@yahoo.com)
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
{
|
||||||
|
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||||
|
"cli": {
|
||||||
|
"analytics": false
|
||||||
|
},
|
||||||
|
"version": 1,
|
||||||
|
"newProjectRoot": "projects",
|
||||||
|
"projects": {
|
||||||
|
"x-framework-services": {
|
||||||
|
"projectType": "library",
|
||||||
|
"root": "projects/x-framework-services",
|
||||||
|
"sourceRoot": "projects/x-framework-services/src",
|
||||||
|
"prefix": "x",
|
||||||
|
"architect": {
|
||||||
|
"build": {
|
||||||
|
"builder": "@angular-devkit/build-angular:ng-packagr",
|
||||||
|
"options": {
|
||||||
|
"project": "projects/x-framework-services/ng-package.json"
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"tsConfig": "projects/x-framework-services/tsconfig.lib.prod.json"
|
||||||
|
},
|
||||||
|
"development": {
|
||||||
|
"tsConfig": "projects/x-framework-services/tsconfig.lib.json"
|
||||||
|
},
|
||||||
|
"ivy": {
|
||||||
|
"tsConfig": "projects/x-framework-services/tsconfig.lib.ivy.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultConfiguration": "production"
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"builder": "@angular-devkit/build-angular:karma",
|
||||||
|
"options": {
|
||||||
|
"main": "projects/x-framework-services/src/test.ts",
|
||||||
|
"tsConfig": "projects/x-framework-services/tsconfig.spec.json",
|
||||||
|
"karmaConfig": "projects/x-framework-services/karma.conf.js"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultProject": "x-framework-services"
|
||||||
|
}
|
||||||
Vendored
+1759
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,76 @@
|
|||||||
|
{
|
||||||
|
"name": "x-framework-services-holder",
|
||||||
|
"version": "0.0.2",
|
||||||
|
"description": "an isolation layoer of xFrameworkServices module",
|
||||||
|
"keywords": [
|
||||||
|
"x-framework",
|
||||||
|
"x-framework-services"
|
||||||
|
],
|
||||||
|
"homepage": "https://saherelm.ir",
|
||||||
|
"author": {
|
||||||
|
"name": "Hadi Khazaee Asl",
|
||||||
|
"email": "hadi_khazaee_asl@yahoo.com",
|
||||||
|
"url": "https://saherelm.ir"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"ng": "ng",
|
||||||
|
"publishModule": "npm run buildModule && npm run packModule",
|
||||||
|
"build:ivy": "ng build x-framework-services --configuration=ivy",
|
||||||
|
"serveDocs": "compodoc -p projects/x-framework-services/tsconfig.lib.json --theme material -s",
|
||||||
|
"buildModule": "npm run preBuildModule && ng build x-framework-services --configuration production",
|
||||||
|
"packModule": "cd dist/x-framework-services && npm pack && shx mv *.tgz ../../../../pack && cd ../..",
|
||||||
|
"prepareDependenciesByLink": "npm link x-framework-core",
|
||||||
|
"prepareDependencies": "npm i --save-dev ../../pack/x-framework-core-0.0.1.tgz",
|
||||||
|
"prepareDependenciesFromRegistry": "npm i -D --registry https://npm.saherelmhub.ir x-framework-core",
|
||||||
|
"preBuildModule": "shx true --silent mkdir ../../pack && shx true --silent rm -rf dist && shx true --silent rm ../../pack/x-framework-services*.tgz && npm run prepareDependencies",
|
||||||
|
"generateDocs": "compodoc -p projects/x-framework-services/tsconfig.lib.json --theme material --hideGenerator --disableSourceCode --disableDomTree --disableTemplateTab --disableStyleTab --disablePrivate --disableLifeCycleHooks --disableRoutesGraph --disableCoverage --customFavicon ../xFrameworkDocuments/Resources/favicon.png --customLogo ../xFrameworkDocuments/Resources/favicon.png"
|
||||||
|
},
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@angular/animations": "~12.0.4",
|
||||||
|
"@angular/common": "~12.0.4",
|
||||||
|
"@angular/compiler": "~12.0.4",
|
||||||
|
"@angular/core": "~12.0.4",
|
||||||
|
"@angular/forms": "~12.0.4",
|
||||||
|
"@angular/platform-browser": "~12.0.4",
|
||||||
|
"@angular/platform-browser-dynamic": "~12.0.4",
|
||||||
|
"@angular/router": "~12.0.4",
|
||||||
|
"rxjs": "~6.6.0",
|
||||||
|
"tslib": "^2.1.0",
|
||||||
|
"zone.js": "~0.11.4"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@angular-devkit/build-angular": "~12.0.4",
|
||||||
|
"@angular/cdk": "^12.1.2",
|
||||||
|
"@angular/cli": "~12.0.4",
|
||||||
|
"@angular/compiler-cli": "~12.0.4",
|
||||||
|
"@angular/material": "^12.1.2",
|
||||||
|
"@angular/material-moment-adapter": "^12.1.2",
|
||||||
|
"@compodoc/compodoc": "^1.1.14",
|
||||||
|
"@ionic/angular": "^5.6.11",
|
||||||
|
"@types/crypto-js": "^4.0.2",
|
||||||
|
"@types/howler": "^2.2.3",
|
||||||
|
"@types/jasmine": "~3.6.0",
|
||||||
|
"@types/node": "^12.11.1",
|
||||||
|
"@types/uuid": "^8.3.1",
|
||||||
|
"crypto-js": "^4.0.0",
|
||||||
|
"howler": "^2.2.3",
|
||||||
|
"ionicons": "^5.5.2",
|
||||||
|
"jalali-moment": "^3.3.10",
|
||||||
|
"jasmine-core": "~3.7.0",
|
||||||
|
"karma": "~6.3.0",
|
||||||
|
"karma-chrome-launcher": "~3.1.0",
|
||||||
|
"karma-coverage": "~2.0.3",
|
||||||
|
"karma-jasmine": "~4.0.0",
|
||||||
|
"karma-jasmine-html-reporter": "^1.5.0",
|
||||||
|
"libphonenumber-js": "^1.9.22",
|
||||||
|
"md5-typescript": "^1.0.5",
|
||||||
|
"moment": "^2.29.1",
|
||||||
|
"ng-packagr": "^12.0.0",
|
||||||
|
"ngx-device-detector": "^2.1.1",
|
||||||
|
"shx": "^0.3.3",
|
||||||
|
"typescript": "~4.2.3",
|
||||||
|
"uuid": "^8.3.2",
|
||||||
|
"x-framework-core": "file:../../pack/x-framework-core-0.0.1.tgz"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# XFrameworkServices
|
||||||
|
|
||||||
|
contains some sort of usefull services, framework controller services, directives based on services and etc.
|
||||||
|
|
||||||
|
most important service in this module is XManagerService which responsible for providing all features of this module in a singe easy to use interface.
|
||||||
|
|
||||||
|
depends on x-framework-core.
|
||||||
|
|
||||||
|
## Maintainer
|
||||||
|
|
||||||
|
Hadi Khazaee asl
|
||||||
|
|
||||||
|
<https://www.saherelm.ir>
|
||||||
|
|
||||||
|
hadi_khazaee_asl@yahoo.com
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
// Karma configuration file, see link for more information
|
||||||
|
// https://karma-runner.github.io/1.0/config/configuration-file.html
|
||||||
|
|
||||||
|
module.exports = function (config) {
|
||||||
|
config.set({
|
||||||
|
basePath: '',
|
||||||
|
frameworks: ['jasmine', '@angular-devkit/build-angular'],
|
||||||
|
plugins: [
|
||||||
|
require('karma-jasmine'),
|
||||||
|
require('karma-chrome-launcher'),
|
||||||
|
require('karma-jasmine-html-reporter'),
|
||||||
|
require('karma-coverage'),
|
||||||
|
require('@angular-devkit/build-angular/plugins/karma')
|
||||||
|
],
|
||||||
|
client: {
|
||||||
|
jasmine: {
|
||||||
|
// you can add configuration options for Jasmine here
|
||||||
|
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
|
||||||
|
// for example, you can disable the random execution with `random: false`
|
||||||
|
// or set a specific seed with `seed: 4321`
|
||||||
|
},
|
||||||
|
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
||||||
|
},
|
||||||
|
jasmineHtmlReporter: {
|
||||||
|
suppressAll: true // removes the duplicated traces
|
||||||
|
},
|
||||||
|
coverageReporter: {
|
||||||
|
dir: require('path').join(__dirname, '../../coverage/x-framework-services'),
|
||||||
|
subdir: '.',
|
||||||
|
reporters: [
|
||||||
|
{ type: 'html' },
|
||||||
|
{ type: 'text-summary' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
reporters: ['progress', 'kjhtml'],
|
||||||
|
port: 9876,
|
||||||
|
colors: true,
|
||||||
|
logLevel: config.LOG_INFO,
|
||||||
|
autoWatch: true,
|
||||||
|
browsers: ['Chrome'],
|
||||||
|
singleRun: false,
|
||||||
|
restartOnFileChange: true
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
|
||||||
|
"dest": "../../dist/x-framework-services",
|
||||||
|
"lib": {
|
||||||
|
"entryFile": "src/public-api.ts"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"name": "x-framework-services",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "provide services of xFramework",
|
||||||
|
"keywords": [
|
||||||
|
"x-framework",
|
||||||
|
"x-framework-services"
|
||||||
|
],
|
||||||
|
"homepage": "https://saherelm.ir",
|
||||||
|
"author": {
|
||||||
|
"name": "Hadi Khazaee Asl",
|
||||||
|
"email": "hadi_khazaee_asl@yahoo.com",
|
||||||
|
"url": "https://saherelm.ir"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@angular/common": "^12.0.4",
|
||||||
|
"@angular/core": "^12.0.4"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { XFrameworkCoreConfig } from 'x-framework-core';
|
||||||
|
|
||||||
|
// tslint:disable-next-line:no-empty-interface
|
||||||
|
export interface XFrameworkServicesConfig extends XFrameworkCoreConfig {
|
||||||
|
geoLocationConfig?: PositionOptions
|
||||||
|
}
|
||||||
@@ -0,0 +1,191 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,493 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
+137
@@ -0,0 +1,137 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,326 @@
|
|||||||
|
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<GestureDetail>();
|
||||||
|
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<XSwipeEvent>();
|
||||||
|
|
||||||
|
@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<XSwipableDirective>('threshold')
|
||||||
|
);
|
||||||
|
if (isThresholdChanged) {
|
||||||
|
this.prepareGesture();
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
const isDebounceTimeChanged = changeKesy.includes(
|
||||||
|
nameof<XSwipableDirective>('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<T>(task: Observable<T>): Observable<T> {
|
||||||
|
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<XSwipeEvent> = {
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
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 {}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { InjectionToken } from '@angular/core';
|
||||||
|
import { XFrameworkServicesConfig } from '../config/x-framework-services.config';
|
||||||
|
|
||||||
|
export const X_FRAMEWORK_SERVICES_CONFIG = new InjectionToken<
|
||||||
|
XFrameworkServicesConfig
|
||||||
|
>('x_config');
|
||||||
@@ -0,0 +1,576 @@
|
|||||||
|
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<void> {
|
||||||
|
//
|
||||||
|
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<boolean> {
|
||||||
|
//
|
||||||
|
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<boolean>
|
||||||
|
) {
|
||||||
|
//
|
||||||
|
if (!role) {
|
||||||
|
role = XModalButtonRole.Selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
const mText = this.getPreparedMessage(text);
|
||||||
|
return {
|
||||||
|
text: mText,
|
||||||
|
icon,
|
||||||
|
role,
|
||||||
|
handler,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getYesButton(
|
||||||
|
role?: XModalButtonRoleIdentifier,
|
||||||
|
handler?: () => boolean | void | Promise<boolean>
|
||||||
|
) {
|
||||||
|
//
|
||||||
|
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<boolean>
|
||||||
|
) {
|
||||||
|
//
|
||||||
|
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<boolean>
|
||||||
|
) {
|
||||||
|
//
|
||||||
|
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<boolean>
|
||||||
|
) {
|
||||||
|
//
|
||||||
|
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<boolean>
|
||||||
|
) {
|
||||||
|
//
|
||||||
|
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<boolean>
|
||||||
|
) {
|
||||||
|
//
|
||||||
|
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<boolean>
|
||||||
|
) {
|
||||||
|
//
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
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 {}
|
||||||
@@ -0,0 +1,193 @@
|
|||||||
|
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<boolean> = new EventEmitter<boolean>();
|
||||||
|
|
||||||
|
//
|
||||||
|
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<GeolocationPosition> {
|
||||||
|
//
|
||||||
|
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<GeolocationPosition> {
|
||||||
|
//
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
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<any>,
|
||||||
|
next: HttpHandler
|
||||||
|
): Observable<HttpEvent<any>> {
|
||||||
|
return this.handleInterceptRequest(req, next);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleInterceptRequest(
|
||||||
|
req: HttpRequest<any>,
|
||||||
|
next: HttpHandler
|
||||||
|
): Observable<HttpEvent<any>> {
|
||||||
|
//
|
||||||
|
// 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<HttpEvent<any>>) {
|
||||||
|
//
|
||||||
|
return current.pipe(
|
||||||
|
finalize(() => {
|
||||||
|
//
|
||||||
|
// Collection of instructions after finish or error ...
|
||||||
|
this.managerService.loading = false;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
applyErrorHandler(current: Observable<HttpEvent<any>>) {
|
||||||
|
return current.pipe(
|
||||||
|
catchError((error: HttpErrorResponse) => {
|
||||||
|
//
|
||||||
|
this.managerService.loading = true;
|
||||||
|
const mException = getErrorException(error.error);
|
||||||
|
|
||||||
|
//
|
||||||
|
this.managerService.error = mException;
|
||||||
|
|
||||||
|
//
|
||||||
|
return throwError(error);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
//#endregion
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
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}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,290 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
import { XManagerRouteService } from './x-manager-route.service';
|
||||||
|
|
||||||
|
export abstract class XManagerFinalService extends XManagerRouteService {}
|
||||||
+399
@@ -0,0 +1,399 @@
|
|||||||
|
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<string>) {
|
||||||
|
//
|
||||||
|
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<T>(param: XParamIdentifier) {
|
||||||
|
//
|
||||||
|
if (!this.isContainQueryParam(param)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
return fromJson<T>(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<T>(param: XParamIdentifier) {
|
||||||
|
//
|
||||||
|
if (!this.isContainParam(param)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
return fromJson<T>(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<T>(param: XParamIdentifier) {
|
||||||
|
//
|
||||||
|
if (!this.isContainState(param)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
return fromJson<T>(this.getStateVal(param)) || null;
|
||||||
|
}
|
||||||
|
//#endregion
|
||||||
|
}
|
||||||
+185
@@ -0,0 +1,185 @@
|
|||||||
|
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<XUiEvent>();
|
||||||
|
|
||||||
|
//
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,366 @@
|
|||||||
|
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<void> {
|
||||||
|
await this.presentXNotification({
|
||||||
|
...props,
|
||||||
|
opt: {
|
||||||
|
...props.opt,
|
||||||
|
color: XColor.Tertiary,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* present Success Notification
|
||||||
|
*
|
||||||
|
* @param props Notification Properties
|
||||||
|
*/
|
||||||
|
async presentSuccessNotification(
|
||||||
|
props: XTypedNotificationParams
|
||||||
|
): Promise<void> {
|
||||||
|
await this.presentXNotification({
|
||||||
|
...props,
|
||||||
|
opt: {
|
||||||
|
...props.opt,
|
||||||
|
color: XColor.Success,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* present Default Success Notification
|
||||||
|
*/
|
||||||
|
async presentDefaultSuccessNotification(
|
||||||
|
dissmissHandler?: () => void
|
||||||
|
): Promise<void> {
|
||||||
|
//
|
||||||
|
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<void> {
|
||||||
|
await this.presentXNotification({
|
||||||
|
...props,
|
||||||
|
opt: {
|
||||||
|
...props.opt,
|
||||||
|
color: XColor.Warning,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* present error notification
|
||||||
|
*
|
||||||
|
* @param props Notification Properties
|
||||||
|
*/
|
||||||
|
async presentDangerNotification(
|
||||||
|
props: XTypedNotificationParams
|
||||||
|
): Promise<void> {
|
||||||
|
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<void> {
|
||||||
|
//
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
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<T>(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<T>(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,478 @@
|
|||||||
|
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<boolean>;
|
||||||
|
isXSmall$: Observable<boolean>;
|
||||||
|
isSmall$: Observable<boolean>;
|
||||||
|
isMedium$: Observable<boolean>;
|
||||||
|
isLarge$: Observable<boolean>;
|
||||||
|
isXLarge$: Observable<boolean>;
|
||||||
|
isTablet$: Observable<boolean>;
|
||||||
|
isWeb$: Observable<boolean>;
|
||||||
|
isPortrait$: Observable<boolean>;
|
||||||
|
isLandscape$: Observable<boolean>;
|
||||||
|
orientation$: Observable<XOrientation>;
|
||||||
|
//#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<HTMLElement>
|
||||||
|
) {
|
||||||
|
//
|
||||||
|
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<XLocale>(
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,203 @@
|
|||||||
|
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<T>(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<T>(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, '');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,295 @@
|
|||||||
|
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<boolean>(false);
|
||||||
|
|
||||||
|
//
|
||||||
|
// notify Specific Task Start or Finished ...
|
||||||
|
public taskStart = new Subject<XTask<any>>();
|
||||||
|
public taskError = new Subject<XTask<any>>();
|
||||||
|
public taskFinished = new Subject<XTask<any>>();
|
||||||
|
|
||||||
|
//
|
||||||
|
private isPaused = false;
|
||||||
|
private isStarted = false;
|
||||||
|
private tasks = new Map<string, XTask<any>>();
|
||||||
|
private taskQueue = new Subject<XTask<any>>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<T>(task: XTask<T>) {
|
||||||
|
//
|
||||||
|
// 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<any>) {
|
||||||
|
//
|
||||||
|
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<any>) {
|
||||||
|
//
|
||||||
|
const mXTObs = this.applyWrapperToTask(task);
|
||||||
|
|
||||||
|
//
|
||||||
|
this.subscription = mXTObs.subscribe(
|
||||||
|
(_) => {},
|
||||||
|
(_) => {}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleUnSubscribe() {
|
||||||
|
//
|
||||||
|
if (this.subscription) {
|
||||||
|
//
|
||||||
|
this.subscription.unsubscribe();
|
||||||
|
this.subscription = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//#endregion
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
// 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[];
|
||||||
|
<T>(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);
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.lib.json",
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"enableIvy": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "../../out-tsc/lib",
|
||||||
|
"target": "es2015",
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"inlineSources": true,
|
||||||
|
"types": [],
|
||||||
|
"lib": [
|
||||||
|
"dom",
|
||||||
|
"es2018"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"src/test.ts",
|
||||||
|
"**/*.spec.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||||
|
{
|
||||||
|
"extends": "./tsconfig.lib.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"declarationMap": false
|
||||||
|
},
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"compilationMode": "partial"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "../../out-tsc/spec",
|
||||||
|
"types": [
|
||||||
|
"jasmine"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"src/test.ts"
|
||||||
|
],
|
||||||
|
"include": [
|
||||||
|
"**/*.spec.ts",
|
||||||
|
"**/*.d.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||||
|
{
|
||||||
|
"compileOnSave": false,
|
||||||
|
"compilerOptions": {
|
||||||
|
"strict": true,
|
||||||
|
"baseUrl": "./",
|
||||||
|
"sourceMap": true,
|
||||||
|
"target": "es2017",
|
||||||
|
"module": "es2020",
|
||||||
|
"declaration": false,
|
||||||
|
"importHelpers": true,
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"strictNullChecks": false,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"outDir": "./dist/out-tsc",
|
||||||
|
"downlevelIteration": true,
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"suppressImplicitAnyIndexErrors": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"lib": [
|
||||||
|
"es2018",
|
||||||
|
"dom"
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"x-framework-services": [
|
||||||
|
"dist/x-framework-services/x-framework-services",
|
||||||
|
"dist/x-framework-services"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"strictTemplates": true,
|
||||||
|
"strictInjectionParameters": true,
|
||||||
|
"strictInputAccessModifiers": true,
|
||||||
|
"enableI18nLegacyMessageIdFormat": false
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user