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