Initial Commit on git.saherelm.ir ...

This commit is contained in:
2026-08-09 20:36:44 +03:30
commit 4a551d54d1
45 changed files with 8547 additions and 0 deletions
+16
View File
@@ -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
View File
@@ -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
+15
View File
@@ -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)
+45
View File
@@ -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"
}
+1759
View File
File diff suppressed because it is too large Load Diff
+76
View File
@@ -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"
}
}
+15
View File
@@ -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
}
@@ -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;
}
}
@@ -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);
}
}
@@ -0,0 +1,3 @@
import { XManagerRouteService } from './x-manager-route.service';
export abstract class XManagerFinalService extends XManagerRouteService {}
@@ -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
}
@@ -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
+26
View File
@@ -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"
]
}
+39
View File
@@ -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
}
}