Layout template has applied.
This commit is contained in:
commit
dc34ae0d0e
73
.angular-cli.json
Normal file
73
.angular-cli.json
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
{
|
||||||
|
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||||
|
"project": {
|
||||||
|
"name": "overflow-member-webapp"
|
||||||
|
},
|
||||||
|
"apps": [
|
||||||
|
{
|
||||||
|
"root": "src",
|
||||||
|
"outDir": "dist",
|
||||||
|
"assets": [
|
||||||
|
"assets",
|
||||||
|
"upload.php"
|
||||||
|
],
|
||||||
|
"index": "index.html",
|
||||||
|
"main": "main.ts",
|
||||||
|
"polyfills": "polyfills.ts",
|
||||||
|
"test": "test.ts",
|
||||||
|
"tsconfig": "tsconfig.app.json",
|
||||||
|
"testTsconfig": "tsconfig.spec.json",
|
||||||
|
"prefix": "of",
|
||||||
|
"styles": [
|
||||||
|
"../node_modules/primeng/resources/primeng.min.css",
|
||||||
|
"../node_modules/fullcalendar/dist/fullcalendar.min.css",
|
||||||
|
"../node_modules/quill/dist/quill.snow.css",
|
||||||
|
"styles.scss"
|
||||||
|
],
|
||||||
|
"scripts": [
|
||||||
|
"../node_modules/jquery/dist/jquery.js",
|
||||||
|
"../node_modules/moment/moment.js",
|
||||||
|
"../node_modules/chart.js/dist/Chart.js",
|
||||||
|
"../node_modules/fullcalendar/dist/fullcalendar.js",
|
||||||
|
"../node_modules/quill/dist/quill.js"
|
||||||
|
],
|
||||||
|
"environmentSource": "environments/environment.ts",
|
||||||
|
"environments": {
|
||||||
|
"dev": "environments/environment.ts",
|
||||||
|
"prod": "environments/environment.prod.ts"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"e2e": {
|
||||||
|
"protractor": {
|
||||||
|
"config": "./protractor.conf.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lint": [
|
||||||
|
{
|
||||||
|
"project": "src/tsconfig.app.json",
|
||||||
|
"exclude": "**/node_modules/**"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"project": "src/tsconfig.spec.json",
|
||||||
|
"exclude": "**/node_modules/**"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"project": "e2e/tsconfig.e2e.json",
|
||||||
|
"exclude": "**/node_modules/**"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"test": {
|
||||||
|
"karma": {
|
||||||
|
"config": "./karma.conf.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaults": {
|
||||||
|
"styleExt": "css",
|
||||||
|
"component": {},
|
||||||
|
"serve": {
|
||||||
|
"host": "0.0.0.0",
|
||||||
|
"port": 4200
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
.editorconfig
Normal file
13
.editorconfig
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
# Editor configuration, see http://editorconfig.org
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
max_line_length = off
|
||||||
|
trim_trailing_whitespace = false
|
45
.gitignore
vendored
Normal file
45
.gitignore
vendored
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# compiled output
|
||||||
|
/dist
|
||||||
|
/dist-server
|
||||||
|
/tmp
|
||||||
|
/out-tsc
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# misc
|
||||||
|
/.sass-cache
|
||||||
|
/connect.lock
|
||||||
|
/coverage
|
||||||
|
/libpeerconnection.log
|
||||||
|
npm-debug.log
|
||||||
|
testem.log
|
||||||
|
/typings
|
||||||
|
|
||||||
|
# e2e
|
||||||
|
/e2e/*.js
|
||||||
|
/e2e/*.map
|
||||||
|
|
||||||
|
# System Files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
yarn.lock
|
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"git.ignoreLimitWarning": true
|
||||||
|
}
|
28
README.md
Normal file
28
README.md
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
# PrimeCli
|
||||||
|
|
||||||
|
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.3.0.
|
||||||
|
|
||||||
|
## Development server
|
||||||
|
|
||||||
|
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
|
||||||
|
|
||||||
|
## Code scaffolding
|
||||||
|
|
||||||
|
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build.
|
||||||
|
|
||||||
|
## Running unit tests
|
||||||
|
|
||||||
|
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
|
||||||
|
|
||||||
|
## Running end-to-end tests
|
||||||
|
|
||||||
|
Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
|
||||||
|
Before running the tests make sure you are serving the app via `ng serve`.
|
||||||
|
|
||||||
|
## Further help
|
||||||
|
|
||||||
|
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
|
10
e2e/app.e2e-spec.ts
Normal file
10
e2e/app.e2e-spec.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import { UltimaPage } from './app.po';
|
||||||
|
|
||||||
|
describe('ultima App', function() {
|
||||||
|
let page: UltimaPage;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
page = new UltimaPage();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
7
e2e/app.po.ts
Normal file
7
e2e/app.po.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import { browser, element, by } from 'protractor';
|
||||||
|
|
||||||
|
export class UltimaPage {
|
||||||
|
navigateTo() {
|
||||||
|
return browser.get('/');
|
||||||
|
}
|
||||||
|
}
|
14
e2e/tsconfig.e2e.json
Normal file
14
e2e/tsconfig.e2e.json
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "../out-tsc/e2e",
|
||||||
|
"baseUrl": "./",
|
||||||
|
"module": "commonjs",
|
||||||
|
"target": "es5",
|
||||||
|
"types": [
|
||||||
|
"jasmine",
|
||||||
|
"jasminewd2",
|
||||||
|
"node"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
33
karma.conf.js
Normal file
33
karma.conf.js
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
// 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/cli'],
|
||||||
|
plugins: [
|
||||||
|
require('karma-jasmine'),
|
||||||
|
require('karma-chrome-launcher'),
|
||||||
|
require('karma-jasmine-html-reporter'),
|
||||||
|
require('karma-coverage-istanbul-reporter'),
|
||||||
|
require('@angular/cli/plugins/karma')
|
||||||
|
],
|
||||||
|
client:{
|
||||||
|
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
||||||
|
},
|
||||||
|
coverageIstanbulReporter: {
|
||||||
|
reports: [ 'html', 'lcovonly' ],
|
||||||
|
fixWebpackSourcePaths: true
|
||||||
|
},
|
||||||
|
angularCli: {
|
||||||
|
environment: 'dev'
|
||||||
|
},
|
||||||
|
reporters: ['progress', 'kjhtml'],
|
||||||
|
port: 9876,
|
||||||
|
colors: true,
|
||||||
|
logLevel: config.LOG_INFO,
|
||||||
|
autoWatch: true,
|
||||||
|
browsers: ['Chrome'],
|
||||||
|
singleRun: false
|
||||||
|
});
|
||||||
|
};
|
13344
package-lock.json
generated
Normal file
13344
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
69
package.json
Normal file
69
package.json
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
{
|
||||||
|
"name": "overflow-member-wepapp",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"license": "COMMERCIAL",
|
||||||
|
"angular-cli": {},
|
||||||
|
"scripts": {
|
||||||
|
"ng": "ng",
|
||||||
|
"start": "ng serve",
|
||||||
|
"build": "ng build",
|
||||||
|
"test": "ng test",
|
||||||
|
"lint": "ng lint",
|
||||||
|
"e2e": "ng e2e"
|
||||||
|
},
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@angular/animations": "^5.2.9",
|
||||||
|
"@angular/common": "^5.2.9",
|
||||||
|
"@angular/compiler": "^5.2.9",
|
||||||
|
"@angular/core": "^5.2.9",
|
||||||
|
"@angular/forms": "^5.2.9",
|
||||||
|
"@angular/http": "^5.2.9",
|
||||||
|
"@angular/platform-browser": "^5.2.9",
|
||||||
|
"@angular/platform-browser-dynamic": "^5.2.9",
|
||||||
|
"@angular/router": "^5.2.0",
|
||||||
|
"@ngrx/core": "^1.2.0",
|
||||||
|
"@ngrx/effects": "^5.2.0",
|
||||||
|
"@ngrx/entity": "^5.2.0",
|
||||||
|
"@ngrx/router-store": "^5.2.0",
|
||||||
|
"@ngrx/store": "^5.2.0",
|
||||||
|
"@ngrx/store-devtools": "^5.2.0",
|
||||||
|
"angular-l10n": "^4.1.5",
|
||||||
|
"angular-tree-component": "^7.1.0",
|
||||||
|
"angularx-qrcode": "^1.1.0",
|
||||||
|
"ip-cidr": "^1.1.2",
|
||||||
|
"reflect-metadata": "^0.1.12",
|
||||||
|
"chart.js": "^2.7.2",
|
||||||
|
"core-js": "^2.5.4",
|
||||||
|
"fullcalendar": "^3.9.0",
|
||||||
|
"intl": "^1.2.5",
|
||||||
|
"jquery": "^3.3.1",
|
||||||
|
"moment": "^2.22.0",
|
||||||
|
"quill": "^1.3.6",
|
||||||
|
"rxjs": "^5.5.8",
|
||||||
|
"web-animations-js": "^2.3.1",
|
||||||
|
"zone.js": "^0.8.25",
|
||||||
|
"primeng": "^5.2.4",
|
||||||
|
"ngx-cookie-service": "^1.0.10"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@angular/cli": "1.6.5",
|
||||||
|
"@angular/compiler-cli": "^5.2.9",
|
||||||
|
"@types/jasmine": "~2.8.5",
|
||||||
|
"@types/jasminewd2": "~2.0.3",
|
||||||
|
"@types/node": "~8.0.0",
|
||||||
|
"codelyzer": "^4.2.1",
|
||||||
|
"jasmine-core": "~2.8.0",
|
||||||
|
"jasmine-spec-reporter": "~4.2.1",
|
||||||
|
"karma": "^2.0.0",
|
||||||
|
"karma-chrome-launcher": "^2.2.0",
|
||||||
|
"karma-cli": "^1.0.1",
|
||||||
|
"karma-jasmine": "^1.1.1",
|
||||||
|
"karma-jasmine-html-reporter": "^0.2.2",
|
||||||
|
"karma-coverage-istanbul-reporter": "^1.4.2",
|
||||||
|
"protractor": "~5.3.1",
|
||||||
|
"ts-node": "~4.0.1",
|
||||||
|
"tslint": "~5.9.1",
|
||||||
|
"typescript": "~2.5.3"
|
||||||
|
}
|
||||||
|
}
|
28
protractor.conf.js
Normal file
28
protractor.conf.js
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
// Protractor configuration file, see link for more information
|
||||||
|
// https://github.com/angular/protractor/blob/master/lib/config.ts
|
||||||
|
|
||||||
|
const { SpecReporter } = require('jasmine-spec-reporter');
|
||||||
|
|
||||||
|
exports.config = {
|
||||||
|
allScriptsTimeout: 11000,
|
||||||
|
specs: [
|
||||||
|
'./e2e/**/*.e2e-spec.ts'
|
||||||
|
],
|
||||||
|
capabilities: {
|
||||||
|
'browserName': 'chrome'
|
||||||
|
},
|
||||||
|
directConnect: true,
|
||||||
|
baseUrl: 'http://localhost:4200/',
|
||||||
|
framework: 'jasmine',
|
||||||
|
jasmineNodeOpts: {
|
||||||
|
showColors: true,
|
||||||
|
defaultTimeoutInterval: 30000,
|
||||||
|
print: function() {}
|
||||||
|
},
|
||||||
|
onPrepare() {
|
||||||
|
require('ts-node').register({
|
||||||
|
project: 'e2e/tsconfig.e2e.json'
|
||||||
|
});
|
||||||
|
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
|
||||||
|
}
|
||||||
|
};
|
6
src/@loafer/core/error.ts
Normal file
6
src/@loafer/core/error.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
export class IllegalArgumentError extends Error {
|
||||||
|
public constructor(message?: string) {
|
||||||
|
super(message);
|
||||||
|
Object.setPrototypeOf(this, new.target.prototype);
|
||||||
|
}
|
||||||
|
}
|
1
src/@loafer/core/index.ts
Normal file
1
src/@loafer/core/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export * from './type';
|
64
src/@loafer/core/reflect/AccessibleObject.ts
Normal file
64
src/@loafer/core/reflect/AccessibleObject.ts
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
import {
|
||||||
|
Type,
|
||||||
|
} from '../type';
|
||||||
|
|
||||||
|
import {
|
||||||
|
TypeUtil,
|
||||||
|
} from '../util/TypeUtil';
|
||||||
|
|
||||||
|
import { AnnotatedElement } from './AnnotatedElement';
|
||||||
|
import { Annotation } from './Annotation';
|
||||||
|
|
||||||
|
export abstract class AccessibleObject implements AnnotatedElement {
|
||||||
|
private _annotations: Map<Type<any>, Annotation>;
|
||||||
|
|
||||||
|
protected constructor() {
|
||||||
|
this._annotations = new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
public _addAnnotation<AnnotationType extends Annotation>(annotation: AnnotationType): void {
|
||||||
|
this._annotations.set(TypeUtil.getType(annotation), annotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public isAnnotationPresent<AnnotationType extends Annotation>(annotationClass: Type<AnnotationType>): boolean {
|
||||||
|
return null !== this.getAnnotation(annotationClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getOwnAnnotation<AnnotationType extends Annotation>(annotationClass: Type<AnnotationType>): AnnotationType | undefined {
|
||||||
|
return <AnnotationType>this._annotations.get(annotationClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getOwnAnnotations(): Map<Type<any>, Annotation> {
|
||||||
|
return this._annotations;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getOwnAnnotationsByType<AnnotationType extends Annotation>(annotationClass: Type<AnnotationType>)
|
||||||
|
: AnnotationType[] | undefined {
|
||||||
|
if (0 === this._annotations.size) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const results: AnnotationType[] = [];
|
||||||
|
for (const classType of Array.from(this._annotations.keys())) {
|
||||||
|
if (classType === annotationClass) {
|
||||||
|
results.push(<AnnotationType>this._annotations.get(classType));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (0 === results.length) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getAnnotation<AnnotationType extends Annotation>(annotationClass: Type<AnnotationType>): AnnotationType | undefined {
|
||||||
|
return this.getOwnAnnotation(annotationClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getAnnotations(): Map<Type<any>, Annotation> {
|
||||||
|
return this.getOwnAnnotations();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getAnnotationsByType<AnnotationType extends Annotation>(annotationClass: Type<AnnotationType>)
|
||||||
|
: AnnotationType[] | undefined {
|
||||||
|
return this.getOwnAnnotationsByType(annotationClass);
|
||||||
|
}
|
||||||
|
}
|
17
src/@loafer/core/reflect/AnnotatedElement.ts
Normal file
17
src/@loafer/core/reflect/AnnotatedElement.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import {
|
||||||
|
Type,
|
||||||
|
} from '../type';
|
||||||
|
|
||||||
|
import { Annotation } from './Annotation';
|
||||||
|
|
||||||
|
export interface AnnotatedElement {
|
||||||
|
_addAnnotation<AnnotationType extends Annotation>(annotation: AnnotationType): void;
|
||||||
|
|
||||||
|
isAnnotationPresent<AnnotationType extends Annotation>(annotationClass: Type<AnnotationType>): boolean;
|
||||||
|
getOwnAnnotation<AnnotationType extends Annotation>(annotationClass: Type<AnnotationType>): AnnotationType | undefined;
|
||||||
|
getOwnAnnotations(): Map<Type<any>, Annotation>;
|
||||||
|
getOwnAnnotationsByType<AnnotationType extends Annotation>(annotationClass: Type<AnnotationType>): AnnotationType[] | undefined;
|
||||||
|
getAnnotation<AnnotationType extends Annotation>(annotationClass: Type<AnnotationType>): AnnotationType | undefined;
|
||||||
|
getAnnotations(): Map<Type<any>, Annotation>;
|
||||||
|
getAnnotationsByType<AnnotationType extends Annotation>(annotationClass: Type<AnnotationType>): AnnotationType[] | undefined;
|
||||||
|
}
|
7
src/@loafer/core/reflect/Annotation.ts
Normal file
7
src/@loafer/core/reflect/Annotation.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export abstract class Annotation<Attribute = {}> {
|
||||||
|
public readonly attribute: Attribute;
|
||||||
|
|
||||||
|
public constructor(attribute?: Attribute) {
|
||||||
|
this.attribute = attribute;
|
||||||
|
}
|
||||||
|
}
|
228
src/@loafer/core/reflect/Class.ts
Normal file
228
src/@loafer/core/reflect/Class.ts
Normal file
|
@ -0,0 +1,228 @@
|
||||||
|
import {
|
||||||
|
Type,
|
||||||
|
PropertyKeyType,
|
||||||
|
} from '../type';
|
||||||
|
|
||||||
|
import {
|
||||||
|
TypeUtil,
|
||||||
|
} from '../util/TypeUtil';
|
||||||
|
|
||||||
|
import { AccessibleObject } from './AccessibleObject';
|
||||||
|
import { Annotation } from './Annotation';
|
||||||
|
import { SystemClassRegistry } from './ClassRegistry';
|
||||||
|
import { Constructor } from './Constructor';
|
||||||
|
import { Field } from './Field';
|
||||||
|
import { Method } from './Method';
|
||||||
|
import { Metadata } from './Metadata';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export class Class extends AccessibleObject {
|
||||||
|
private _type: Type<any>;
|
||||||
|
private _constructor: Constructor;
|
||||||
|
private _fields: Map<PropertyKeyType, Field>;
|
||||||
|
private _methods: Map<PropertyKeyType, Method>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* forClass
|
||||||
|
*/
|
||||||
|
public static forType(type: Type<any>): Class | undefined {
|
||||||
|
return SystemClassRegistry.get(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* _defineClass
|
||||||
|
*/
|
||||||
|
public static _defineClass(type: Type<any>): Class {
|
||||||
|
let clazz: Class = Class.forType(type);
|
||||||
|
if (undefined === clazz) {
|
||||||
|
clazz = new Class(type);
|
||||||
|
SystemClassRegistry.set(type, clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null === clazz._constructor) {
|
||||||
|
const parameterTypes = Metadata.getOwnParamTypes(type);
|
||||||
|
if (undefined !== parameterTypes) {
|
||||||
|
clazz._constructor = new Constructor(clazz, parameterTypes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return clazz;
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor(type: Type<any>) {
|
||||||
|
super();
|
||||||
|
this._type = type;
|
||||||
|
this._fields = new Map();
|
||||||
|
this._methods = new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* _defineField
|
||||||
|
*/
|
||||||
|
public _defineConstructor(parameterTypes: any[]): Constructor {
|
||||||
|
let cons: Constructor = this._constructor;
|
||||||
|
if (undefined === cons) {
|
||||||
|
cons = new Constructor(this, parameterTypes);
|
||||||
|
this._constructor = cons;
|
||||||
|
}
|
||||||
|
return cons;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* _defineField
|
||||||
|
*/
|
||||||
|
public _defineField(propertyKey: PropertyKeyType, propertyType: any): Field {
|
||||||
|
let field: Field = this._fields.get(propertyKey);
|
||||||
|
if (undefined === field) {
|
||||||
|
field = new Field(this, propertyKey, propertyType);
|
||||||
|
this._fields.set(propertyKey, field);
|
||||||
|
}
|
||||||
|
return field;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* _defineMethod
|
||||||
|
*/
|
||||||
|
public _defineMethod(propertyKey: PropertyKeyType, parameterTypes: any[], returnType: any): Method {
|
||||||
|
let method: Method = this._methods.get(propertyKey);
|
||||||
|
if (undefined === method) {
|
||||||
|
method = new Method(this, propertyKey, parameterTypes, returnType);
|
||||||
|
this._methods.set(propertyKey, method);
|
||||||
|
}
|
||||||
|
return method;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getType(): Type<any> {
|
||||||
|
return this._type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getConstructor(): Constructor {
|
||||||
|
if (undefined === this._constructor) {
|
||||||
|
this._constructor = new Constructor(this, undefined);
|
||||||
|
}
|
||||||
|
return this._constructor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getOwnField(propertyKey: PropertyKeyType): Field | undefined {
|
||||||
|
return this._fields.get(propertyKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getOwnFields(): Map<PropertyKeyType, Field> {
|
||||||
|
return this._fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getField(propertyKey: PropertyKeyType): Field | undefined {
|
||||||
|
const fields = this.getFields();
|
||||||
|
|
||||||
|
return fields.get(propertyKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getFields(): Map<PropertyKeyType, Field> {
|
||||||
|
const fields: Map<PropertyKeyType, Field> = new Map();
|
||||||
|
|
||||||
|
const types = TypeUtil.ancestorsOf(this._type);
|
||||||
|
if (null === types || 0 === types.length) {
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < types.length; i++) {
|
||||||
|
const tType = types[i];
|
||||||
|
const tClazz = Class.forType(tType);
|
||||||
|
if (undefined === tClazz) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
tClazz.getOwnFields().forEach((value: Field, key: PropertyKeyType, map: Map<PropertyKeyType, Field>): void => {
|
||||||
|
fields.set(key, value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getOwnMethod(propertyKey: PropertyKeyType): Method | undefined {
|
||||||
|
return this._methods.get(propertyKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getOwnMethods(): Map<PropertyKeyType, Method> {
|
||||||
|
return this._methods;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getMethod(propertyKey: PropertyKeyType): Method | undefined {
|
||||||
|
const methods = this.getMethods();
|
||||||
|
|
||||||
|
return methods.get(propertyKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getMethods(): Map<PropertyKeyType, Method> {
|
||||||
|
const methods: Map<PropertyKeyType, Method> = new Map();
|
||||||
|
|
||||||
|
const types = TypeUtil.ancestorsOf(this._type);
|
||||||
|
if (null === types || 0 === types.length) {
|
||||||
|
return methods;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < types.length; i++) {
|
||||||
|
const tClazzType = types[i];
|
||||||
|
const tClazz = Class.forType(tClazzType);
|
||||||
|
if (undefined === tClazz) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
tClazz.getOwnMethods().forEach((value: Method, key: PropertyKeyType, map: Map<PropertyKeyType, Method>): void => {
|
||||||
|
methods.set(key, value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return methods;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getAnnotation<AnnotationType extends Annotation>(annotationClass: Type<AnnotationType>): AnnotationType | undefined {
|
||||||
|
const annotations = this.getAnnotations();
|
||||||
|
|
||||||
|
return <AnnotationType>annotations.get(annotationClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getAnnotations(): Map<Type<any>, Annotation> {
|
||||||
|
const annotations: Map<Type<any>, Annotation> = new Map();
|
||||||
|
|
||||||
|
const types = TypeUtil.ancestorsOf(this._type);
|
||||||
|
if (null === types || 0 === types.length) {
|
||||||
|
return annotations;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < types.length; i++) {
|
||||||
|
const tClazzType = types[i];
|
||||||
|
const tClazz = Class.forType(tClazzType);
|
||||||
|
if (undefined === tClazz) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
tClazz.getOwnAnnotations().forEach((value: Annotation, key: Type<any>, map: Map<Type<any>, Annotation>): void => {
|
||||||
|
annotations.set(key, value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return annotations;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getAnnotationsByType<AnnotationType extends Annotation>(annotationClass: Type<AnnotationType>)
|
||||||
|
: AnnotationType[] | undefined {
|
||||||
|
const annotations = this.getAnnotations();
|
||||||
|
if (0 === annotations.size) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const results: AnnotationType[] = [];
|
||||||
|
for (const classType of Array.from(annotations.keys())) {
|
||||||
|
if (classType === annotationClass) {
|
||||||
|
results.push(<AnnotationType>annotations.get(classType));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (0 === results.length) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getName(): string {
|
||||||
|
return this._type.name;
|
||||||
|
}
|
||||||
|
}
|
12
src/@loafer/core/reflect/ClassRegistry.ts
Normal file
12
src/@loafer/core/reflect/ClassRegistry.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import { Type } from '../type';
|
||||||
|
import { Registry } from '../util/Registry';
|
||||||
|
|
||||||
|
import { Class } from './Class';
|
||||||
|
|
||||||
|
export class ClassRegistry extends Registry<Type<any>, Class> {
|
||||||
|
public constructor(parent?: ClassRegistry) {
|
||||||
|
super(parent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SystemClassRegistry = new ClassRegistry();
|
26
src/@loafer/core/reflect/Constructor.ts
Normal file
26
src/@loafer/core/reflect/Constructor.ts
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import {
|
||||||
|
PropertyKeyType,
|
||||||
|
} from '../type';
|
||||||
|
|
||||||
|
import {
|
||||||
|
TypeUtil,
|
||||||
|
} from '../util/TypeUtil';
|
||||||
|
|
||||||
|
import { Class } from './Class';
|
||||||
|
import { Executable } from './Executable';
|
||||||
|
|
||||||
|
export class Constructor extends Executable {
|
||||||
|
private _rawConstructor: Function;
|
||||||
|
|
||||||
|
public constructor(declaringClazz: Class, parameterTypes?: any[]) {
|
||||||
|
super(declaringClazz, CONSTRUCTOR_NAME, parameterTypes);
|
||||||
|
this._rawConstructor = TypeUtil.getPrototype(declaringClazz.getType())[CONSTRUCTOR_NAME];
|
||||||
|
}
|
||||||
|
|
||||||
|
public newInstance(...args: any[]): any {
|
||||||
|
const ctor = this.getDeclaringClass().getType();
|
||||||
|
return new (ctor.bind.apply(ctor, [null].concat(args)))();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const CONSTRUCTOR_NAME = 'constructor';
|
75
src/@loafer/core/reflect/Executable.ts
Normal file
75
src/@loafer/core/reflect/Executable.ts
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
import {
|
||||||
|
PropertyKeyType,
|
||||||
|
} from '../type';
|
||||||
|
|
||||||
|
import {
|
||||||
|
TypeUtil,
|
||||||
|
} from '../util/TypeUtil';
|
||||||
|
|
||||||
|
import { AccessibleObject } from './AccessibleObject';
|
||||||
|
import { Class } from './Class';
|
||||||
|
import { Member } from './Member';
|
||||||
|
import { Parameter } from './Parameter';
|
||||||
|
|
||||||
|
export abstract class Executable extends AccessibleObject implements Member {
|
||||||
|
private _clazz: Class;
|
||||||
|
private _name: PropertyKeyType;
|
||||||
|
private _parameters: Parameter[];
|
||||||
|
|
||||||
|
protected constructor(declaringClazz: Class, name: PropertyKeyType, parameterTypes?: any[]) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this._clazz = declaringClazz;
|
||||||
|
this._name = name;
|
||||||
|
|
||||||
|
if (undefined === parameterTypes) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parameterNames = TypeUtil.getParameterNames(declaringClazz.getType(), name);
|
||||||
|
this._parameters = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < parameterTypes.length; i++) {
|
||||||
|
const parameterType = parameterTypes[i];
|
||||||
|
const parameterName = parameterNames[i];
|
||||||
|
const parameter: Parameter = new Parameter(this, parameterType, parameterName, i);
|
||||||
|
this._parameters.push(parameter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public getDeclaringClass(): Class {
|
||||||
|
return this._clazz;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getName(): PropertyKeyType {
|
||||||
|
return this._name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* getParameterCount
|
||||||
|
*/
|
||||||
|
public getParameterCount(): number {
|
||||||
|
if (null === this._parameters) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return this._parameters.length;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* getParameters
|
||||||
|
*/
|
||||||
|
public getParameters(): Parameter[] | undefined {
|
||||||
|
return this._parameters;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* getParameter
|
||||||
|
*/
|
||||||
|
public getParameter(index: number): Parameter | undefined {
|
||||||
|
if (null === this._parameters) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (0 > index || this._parameters.length <= index) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return this._parameters[index];
|
||||||
|
}
|
||||||
|
}
|
33
src/@loafer/core/reflect/Field.ts
Normal file
33
src/@loafer/core/reflect/Field.ts
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import {
|
||||||
|
PropertyKeyType,
|
||||||
|
} from '../type';
|
||||||
|
|
||||||
|
import { AccessibleObject } from './AccessibleObject';
|
||||||
|
import { Class } from './Class';
|
||||||
|
import { Member } from './Member';
|
||||||
|
|
||||||
|
export class Field extends AccessibleObject implements Member {
|
||||||
|
private _clazz: Class;
|
||||||
|
private _name: PropertyKeyType;
|
||||||
|
private _type: any;
|
||||||
|
|
||||||
|
public constructor(declaringClazz: Class, name: PropertyKeyType, fieldType: any) {
|
||||||
|
super();
|
||||||
|
this._clazz = declaringClazz;
|
||||||
|
this._name = name;
|
||||||
|
this._type = fieldType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getDeclaringClass(): Class {
|
||||||
|
return this._clazz;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getName(): PropertyKeyType {
|
||||||
|
return this._name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getType(): any {
|
||||||
|
return this._type;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
10
src/@loafer/core/reflect/Member.ts
Normal file
10
src/@loafer/core/reflect/Member.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import {
|
||||||
|
PropertyKeyType,
|
||||||
|
} from '../type';
|
||||||
|
|
||||||
|
import { Class } from './Class';
|
||||||
|
|
||||||
|
export interface Member {
|
||||||
|
getDeclaringClass(): Class;
|
||||||
|
getName(): PropertyKeyType;
|
||||||
|
}
|
521
src/@loafer/core/reflect/Metadata.ts
Normal file
521
src/@loafer/core/reflect/Metadata.ts
Normal file
|
@ -0,0 +1,521 @@
|
||||||
|
import {
|
||||||
|
MetadataKeyType,
|
||||||
|
PropertyKeyType,
|
||||||
|
} from '../type';
|
||||||
|
|
||||||
|
import { TypeUtil } from '../util/TypeUtil';
|
||||||
|
|
||||||
|
|
||||||
|
export class Metadata {
|
||||||
|
/**
|
||||||
|
* Gets the metadata value for the provided metadata key on the target object or its prototype chain.
|
||||||
|
* @param key A key used to store and retrieve metadata.
|
||||||
|
* @param target The target object on which the metadata is defined.
|
||||||
|
* @param propertyKey The property key for the target.
|
||||||
|
* @returns The metadata value for the metadata key if found; otherwise, `undefined`.
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* ```typescript
|
||||||
|
* class Example {
|
||||||
|
* // property declarations are not part of ES6, though they are valid in TypeScript:
|
||||||
|
* // static staticProperty;
|
||||||
|
* // property;
|
||||||
|
*
|
||||||
|
* static staticMethod(p) { }
|
||||||
|
* method(p) { }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // constructor
|
||||||
|
* result = Metadata.get("custom:annotation", Example);
|
||||||
|
*
|
||||||
|
* // property (on constructor)
|
||||||
|
* result = Metadata.get("custom:annotation", Example, "staticProperty");
|
||||||
|
*
|
||||||
|
* // property (on prototype)
|
||||||
|
* result = Metadata.get("custom:annotation", Example.prototype, "property");
|
||||||
|
*
|
||||||
|
* // method (on constructor)
|
||||||
|
* result = Metadata.get("custom:annotation", Example, "staticMethod");
|
||||||
|
*
|
||||||
|
* // method (on prototype)
|
||||||
|
* result = Metadata.get("custom:annotation", Example.prototype, "method");
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public static get(key: MetadataKeyType, target: any, propertyKey?: PropertyKeyType): any {
|
||||||
|
return Reflect.getMetadata(key, TypeUtil.getType(target), propertyKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the metadata value for the provided metadata key on the target object or its prototype chain.
|
||||||
|
* @param key A key used to store and retrieve metadata.
|
||||||
|
* @param target The target object on which the metadata is defined.
|
||||||
|
* @param propertyKey The property key for the target.
|
||||||
|
* @returns The metadata value for the metadata key if found; otherwise, `undefined`.
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* ```typescript
|
||||||
|
* class Example {
|
||||||
|
* // property declarations are not part of ES6, though they are valid in TypeScript:
|
||||||
|
* // static staticProperty;
|
||||||
|
* // property;
|
||||||
|
*
|
||||||
|
* static staticMethod(p) { }
|
||||||
|
* method(p) { }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // constructor
|
||||||
|
* result = Metadata.getOwn("custom:annotation", Example);
|
||||||
|
*
|
||||||
|
* // property (on constructor)
|
||||||
|
* result = Metadata.getOwn("custom:annotation", Example, "staticProperty");
|
||||||
|
*
|
||||||
|
* // property (on prototype)
|
||||||
|
* result = Metadata.getOwn("custom:annotation", Example.prototype, "property");
|
||||||
|
*
|
||||||
|
* // method (on constructor)
|
||||||
|
* result = Metadata.getOwn("custom:annotation", Example, "staticMethod");
|
||||||
|
*
|
||||||
|
* // method (on prototype)
|
||||||
|
* result = Metadata.getOwn("custom:annotation", Example.prototype, "method");
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public static getOwn(key: MetadataKeyType, target: any, propertyKey?: PropertyKeyType): any {
|
||||||
|
return Reflect.getOwnMetadata(key, TypeUtil.getType(target), propertyKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the metadata value for the provided metadata DESIGN_TYPE on the target object or its prototype chain.
|
||||||
|
* @param target The target object on which the metadata is defined.
|
||||||
|
* @param propertyKey The property key for the target.
|
||||||
|
* @returns The metadata value for the metadata key if found; otherwise, `undefined`.
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* ```typescript
|
||||||
|
* class Example {
|
||||||
|
* // property declarations are not part of ES6, though they are valid in TypeScript:
|
||||||
|
* // static staticProperty;
|
||||||
|
* // property;
|
||||||
|
*
|
||||||
|
* static staticMethod(p) { }
|
||||||
|
* method(p) { }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // on contructor
|
||||||
|
* result = Metadata.getType(Example);
|
||||||
|
*
|
||||||
|
* // property (on constructor)
|
||||||
|
* result = Metadata.getType(Example, "staticProperty");
|
||||||
|
*
|
||||||
|
* // method (on constructor)
|
||||||
|
* result = Metadata.getType(Example, "staticMethod");
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public static getType(target: any, propertyKey?: PropertyKeyType): any {
|
||||||
|
return Reflect.getMetadata(DESIGN_TYPE, target, propertyKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the metadata value for the provided metadata DESIGN_TYPE on the target object or its prototype chain.
|
||||||
|
* @param target The target object on which the metadata is defined.
|
||||||
|
* @param propertyKey The property key for the target.
|
||||||
|
* @returns The metadata value for the metadata key if found; otherwise, `undefined`.
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* ```typescript
|
||||||
|
* class Example {
|
||||||
|
* // property declarations are not part of ES6, though they are valid in TypeScript:
|
||||||
|
* // static staticProperty;
|
||||||
|
* // property;
|
||||||
|
*
|
||||||
|
* static staticMethod(p) { }
|
||||||
|
* method(p) { }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // on contructor
|
||||||
|
* result = Metadata.getOwnType(Example);
|
||||||
|
*
|
||||||
|
* // property (on constructor)
|
||||||
|
* result = Metadata.getOwnType(Example, "staticProperty");
|
||||||
|
*
|
||||||
|
* // method (on constructor)
|
||||||
|
* result = Metadata.getOwnType(Example, "staticMethod");
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public static getOwnType(target: any, propertyKey?: PropertyKeyType): any {
|
||||||
|
return Reflect.getMetadata(DESIGN_TYPE, target, propertyKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the metadata value for the provided metadata DESIGN_RETURN_TYPE on the target object or its prototype chain.
|
||||||
|
* @param target The target object on which the metadata is defined.
|
||||||
|
* @param propertyKey The property key for the target.
|
||||||
|
* @returns The metadata value for the metadata key if found; otherwise, `undefined`.
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* ```typescript
|
||||||
|
* class Example {
|
||||||
|
* // property declarations are not part of ES6, though they are valid in TypeScript:
|
||||||
|
* // static staticProperty;
|
||||||
|
* // property;
|
||||||
|
*
|
||||||
|
* static staticMethod(p) { }
|
||||||
|
* method(p) { }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // on contructor
|
||||||
|
* result = Metadata.getReturnType(Example);
|
||||||
|
*
|
||||||
|
* // property (on constructor)
|
||||||
|
* result = Metadata.getReturnType(Example, "staticProperty");
|
||||||
|
*
|
||||||
|
* // method (on constructor)
|
||||||
|
* result = Metadata.getReturnType(Example, "staticMethod");
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public static getReturnType(target: any, propertyKey?: PropertyKeyType): any {
|
||||||
|
return Reflect.getMetadata(DESIGN_RETURN_TYPE, target, propertyKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the metadata value for the provided metadata DESIGN_RETURN_TYPE on the target object or its prototype chain.
|
||||||
|
* @param target The target object on which the metadata is defined.
|
||||||
|
* @param propertyKey The property key for the target.
|
||||||
|
* @returns The metadata value for the metadata key if found; otherwise, `undefined`.
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* ```typescript
|
||||||
|
* class Example {
|
||||||
|
* // property declarations are not part of ES6, though they are valid in TypeScript:
|
||||||
|
* // static staticProperty;
|
||||||
|
* // property;
|
||||||
|
*
|
||||||
|
* static staticMethod(p) { }
|
||||||
|
* method(p) { }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // on contructor
|
||||||
|
* result = Metadata.getOwnReturnType(Example);
|
||||||
|
*
|
||||||
|
* // property (on constructor)
|
||||||
|
* result = Metadata.getOwnReturnType(Example, "staticProperty");
|
||||||
|
*
|
||||||
|
* // method (on constructor)
|
||||||
|
* result = Metadata.getOwnReturnType(Example, "staticMethod");
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public static getOwnReturnType(target: any, propertyKey?: PropertyKeyType): any {
|
||||||
|
return Reflect.getOwnMetadata(DESIGN_RETURN_TYPE, target, propertyKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a value indicating whether the target object or its prototype chain has the provided metadata key defined.
|
||||||
|
* @param key A key used to store and retrieve metadata.
|
||||||
|
* @param target The target object on which the metadata is defined.
|
||||||
|
* @param propertyKey The property key for the target.
|
||||||
|
* @returns `true` if the metadata key was defined on the target object or its prototype chain; otherwise, `false`.
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* ```typescript
|
||||||
|
* class Example {
|
||||||
|
* // property declarations are not part of ES6, though they are valid in TypeScript:
|
||||||
|
* // static staticProperty;
|
||||||
|
* // property;
|
||||||
|
*
|
||||||
|
* static staticMethod(p) { }
|
||||||
|
* method(p) { }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // constructor
|
||||||
|
* result = Metadata.has("custom:annotation", Example);
|
||||||
|
*
|
||||||
|
* // property (on constructor)
|
||||||
|
* result = Metadata.has("custom:annotation", Example, "staticProperty");
|
||||||
|
*
|
||||||
|
* // property (on prototype)
|
||||||
|
* result = Metadata.has("custom:annotation", Example.prototype, "property");
|
||||||
|
*
|
||||||
|
* // method (on constructor)
|
||||||
|
* result = Metadata.has("custom:annotation", Example, "staticMethod");
|
||||||
|
*
|
||||||
|
* // method (on prototype)
|
||||||
|
* result = Metadata.has("custom:annotation", Example.prototype, "method");
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public static has(key: MetadataKeyType, target: any, propertyKey?: PropertyKeyType): boolean {
|
||||||
|
return Reflect.hasMetadata(key, TypeUtil.getType(target), propertyKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a value indicating whether the target object or its prototype chain has the provided metadata key defined.
|
||||||
|
* @param key A key used to store and retrieve metadata.
|
||||||
|
* @param target The target object on which the metadata is defined.
|
||||||
|
* @param propertyKey The property key for the target.
|
||||||
|
* @returns `true` if the metadata key was defined on the target object or its prototype chain; otherwise, `false`.
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* ```typescript
|
||||||
|
* class Example {
|
||||||
|
* // property declarations are not part of ES6, though they are valid in TypeScript:
|
||||||
|
* // static staticProperty;
|
||||||
|
* // property;
|
||||||
|
*
|
||||||
|
* static staticMethod(p) { }
|
||||||
|
* method(p) { }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // constructor
|
||||||
|
* result = Metadata.has("custom:annotation", Example);
|
||||||
|
*
|
||||||
|
* // property (on constructor)
|
||||||
|
* result = Metadata.hasOwn("custom:annotation", Example, "staticProperty");
|
||||||
|
*
|
||||||
|
* // property (on prototype)
|
||||||
|
* result = Metadata.hasOwn("custom:annotation", Example.prototype, "property");
|
||||||
|
*
|
||||||
|
* // method (on constructor)
|
||||||
|
* result = Metadata.hasOwn("custom:annotation", Example, "staticMethod");
|
||||||
|
*
|
||||||
|
* // method (on prototype)
|
||||||
|
* result = Metadata.hasOwn("custom:annotation", Example.prototype, "method");
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public static hasOwn(key: MetadataKeyType, target: any, propertyKey?: PropertyKeyType): boolean {
|
||||||
|
return Reflect.hasOwnMetadata(key, TypeUtil.getType(target), propertyKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the metadata entry from the target object with the provided key.
|
||||||
|
* @param key A key used to store and retrieve metadata.
|
||||||
|
* @param target The target object on which the metadata is defined.
|
||||||
|
* @param propertyKey The property key for the target.
|
||||||
|
* @returns `true` if the metadata entry was found and deleted; otherwise, false.
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* ```typescript
|
||||||
|
* class Example {
|
||||||
|
* // property declarations are not part of ES6, though they are valid in TypeScript:
|
||||||
|
* // static staticProperty;
|
||||||
|
* // property;
|
||||||
|
*
|
||||||
|
* static staticMethod(p) { }
|
||||||
|
* method(p) { }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // constructor
|
||||||
|
* result = Metadata.delete("custom:annotation", Example);
|
||||||
|
*
|
||||||
|
* // property (on constructor)
|
||||||
|
* result = Metadata.delete("custom:annotation", Example, "staticProperty");
|
||||||
|
*
|
||||||
|
* // property (on prototype)
|
||||||
|
* result = Metadata.delete("custom:annotation", Example.prototype, "property");
|
||||||
|
*
|
||||||
|
* // method (on constructor)
|
||||||
|
* result = Metadata.delete("custom:annotation", Example, "staticMethod");
|
||||||
|
*
|
||||||
|
* // method (on prototype)
|
||||||
|
* result = Metadata.delete("custom:annotation", Example.prototype, "method");
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public static delete(key: MetadataKeyType, target: any, propertyKey?: PropertyKeyType): boolean {
|
||||||
|
return Reflect.deleteMetadata(key, TypeUtil.getType(target), propertyKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the metadata value for the provided metadata DESIGN_PARAM_TYPES on the target object or its prototype chain.
|
||||||
|
* @param target The target object on which the metadata is defined.
|
||||||
|
* @param propertyKey The property key for the target.
|
||||||
|
* @param value A value that contains attached metadata.
|
||||||
|
* @returns The metadata value for the metadata key if found; otherwise, `undefined`.
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* ```typescript
|
||||||
|
* class Example {
|
||||||
|
* // property declarations are not part of ES6, though they are valid in TypeScript:
|
||||||
|
* // static staticProperty;
|
||||||
|
* // property;
|
||||||
|
*
|
||||||
|
* static staticMethod(p) { }
|
||||||
|
* method(p) { }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // on contructor
|
||||||
|
* result = Metadata.setParamTypes(Example, undefined, [Object]);
|
||||||
|
*
|
||||||
|
* // property (on constructor)
|
||||||
|
* result = Metadata.setParamTypes(Example, "staticProperty", [Object]);
|
||||||
|
*
|
||||||
|
* // property (on prototype)
|
||||||
|
* result = Metadata.setParamTypes(Example.prototype, "property", [Object]);
|
||||||
|
*
|
||||||
|
* // method (on constructor)
|
||||||
|
* result = Metadata.setParamTypes(Example, "staticMethod", [Object]);
|
||||||
|
*
|
||||||
|
* // method (on prototype)
|
||||||
|
* result = Metadata.setParamTypes(Example.prototype, "method", [Object]);
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public static setParamTypes(target: any, propertyKey: PropertyKeyType, value: any): void {
|
||||||
|
return this.set(DESIGN_PARAM_TYPES, value, target.prototype, propertyKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all metadata for a metadataKey.
|
||||||
|
* @param metadataKey
|
||||||
|
*/
|
||||||
|
public static getTargetsFromPropertyKey = (metadataKey: MetadataKeyType): any[] =>
|
||||||
|
PROPERTIES.has(metadataKey) ? PROPERTIES.get(metadataKey) || [] : []
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define a unique metadata entry on the target.
|
||||||
|
* @param key A key used to store and retrieve metadata.
|
||||||
|
* @param value A value that contains attached metadata.
|
||||||
|
* @param target The target object on which to define metadata.
|
||||||
|
* @param propertyKey The property key for the target.
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* ```typescript
|
||||||
|
* class Example {
|
||||||
|
* // property declarations are not part of ES6, though they are valid in TypeScript:
|
||||||
|
* // static staticProperty;
|
||||||
|
* // property;
|
||||||
|
*
|
||||||
|
* static staticMethod(p) { }
|
||||||
|
* method(p) { }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // constructor
|
||||||
|
* Reflect.defineMetadata("custom:annotation", options, Example);
|
||||||
|
*
|
||||||
|
* // property (on constructor)
|
||||||
|
* Reflect.defineMetadata("custom:annotation", Number, Example, "staticProperty");
|
||||||
|
*
|
||||||
|
* // property (on prototype)
|
||||||
|
* Reflect.defineMetadata("custom:annotation", Number, Example.prototype, "property");
|
||||||
|
*
|
||||||
|
* // method (on constructor)
|
||||||
|
* Reflect.defineMetadata("custom:annotation", Number, Example, "staticMethod");
|
||||||
|
*
|
||||||
|
* // method (on prototype)
|
||||||
|
* Reflect.defineMetadata("custom:annotation", Number, Example.prototype, "method");
|
||||||
|
*
|
||||||
|
* // decorator factory as metadata-producing annotation.
|
||||||
|
* function MyAnnotation(options): PropertyDecorator {
|
||||||
|
* return (target, key) => Reflect.defineMetadata("custom:annotation", options, target, key);
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public static set(key: MetadataKeyType, value: any, target: any, propertyKey?: PropertyKeyType): void {
|
||||||
|
|
||||||
|
const targets: any[] = PROPERTIES.has(key) ? PROPERTIES.get(key) || [] : [];
|
||||||
|
const classConstructor = TypeUtil.getType(target);
|
||||||
|
|
||||||
|
if (targets.indexOf(classConstructor) === -1) {
|
||||||
|
targets.push(classConstructor);
|
||||||
|
PROPERTIES.set(key, targets);
|
||||||
|
}
|
||||||
|
|
||||||
|
Reflect.defineMetadata(key, value, TypeUtil.getType(target), propertyKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the metadata value for the provided metadata DESIGN_PARAM_TYPES on the target object or its prototype chain.
|
||||||
|
* @param target The target object on which the metadata is defined.
|
||||||
|
* @param propertyKey The property key for the target.
|
||||||
|
* @returns The metadata value for the metadata key if found; otherwise, `undefined`.
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* ```typescript
|
||||||
|
* class Example {
|
||||||
|
* // property declarations are not part of ES6, though they are valid in TypeScript:
|
||||||
|
* // static staticProperty;
|
||||||
|
* // property;
|
||||||
|
*
|
||||||
|
* static staticMethod(p) { }
|
||||||
|
* method(p) { }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // on contructor
|
||||||
|
* result = Metadata.getParamTypes(Example);
|
||||||
|
*
|
||||||
|
* // property (on constructor)
|
||||||
|
* result = Metadata.getParamTypes(Example, "staticProperty");
|
||||||
|
*
|
||||||
|
* // method (on constructor)
|
||||||
|
* result = Metadata.getParamTypes(Example, "staticMethod");
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public static getParamTypes(target: any, propertyKey?: PropertyKeyType): any[] {
|
||||||
|
return Reflect.getMetadata(DESIGN_PARAM_TYPES, target, propertyKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the metadata value for the provided metadata DESIGN_PARAM_TYPES on the target object or its prototype chain.
|
||||||
|
* @param target The target object on which the metadata is defined.
|
||||||
|
* @param propertyKey The property key for the target.
|
||||||
|
* @returns The metadata value for the metadata key if found; otherwise, `undefined`.
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* ```typescript
|
||||||
|
* class Example {
|
||||||
|
* // property declarations are not part of ES6, though they are valid in TypeScript:
|
||||||
|
* // static staticProperty;
|
||||||
|
* // property;
|
||||||
|
*
|
||||||
|
* static staticMethod(p) { }
|
||||||
|
* method(p) { }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // on contructor
|
||||||
|
* result = Metadata.getParamTypes(Example);
|
||||||
|
*
|
||||||
|
* // property (on constructor)
|
||||||
|
* result = Metadata.getParamTypes(Example, "staticProperty");
|
||||||
|
*
|
||||||
|
* // method (on constructor)
|
||||||
|
* result = Metadata.getParamTypes(Example, "staticMethod");
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public static getOwnParamTypes(target: any, propertyKey?: PropertyKeyType): any[] {
|
||||||
|
return Reflect.getOwnMetadata(DESIGN_PARAM_TYPES, target, propertyKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Metadata key
|
||||||
|
* @private
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
const DESIGN_PARAM_TYPES = 'design:paramtypes';
|
||||||
|
/**
|
||||||
|
* Metadata key
|
||||||
|
* @private
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
const DESIGN_TYPE = 'design:type';
|
||||||
|
/**
|
||||||
|
* Metadata key
|
||||||
|
* @private
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
const DESIGN_RETURN_TYPE = 'design:returntype';
|
||||||
|
/**
|
||||||
|
* Properties collections
|
||||||
|
* @private
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
const PROPERTIES: Map<MetadataKeyType, any[]> = new Map<MetadataKeyType, any[]>();
|
29
src/@loafer/core/reflect/Method.ts
Normal file
29
src/@loafer/core/reflect/Method.ts
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import {
|
||||||
|
PropertyKeyType,
|
||||||
|
} from '../type';
|
||||||
|
|
||||||
|
import {
|
||||||
|
TypeUtil,
|
||||||
|
} from '../util/TypeUtil';
|
||||||
|
|
||||||
|
import { Class } from './Class';
|
||||||
|
import { Executable } from './Executable';
|
||||||
|
|
||||||
|
export class Method extends Executable {
|
||||||
|
private _returnType: any;
|
||||||
|
private _rawMethod: Function;
|
||||||
|
|
||||||
|
public constructor(declaringClazz: Class, name: PropertyKeyType, parameterTypes: any[], returnType: any) {
|
||||||
|
super(declaringClazz, name, parameterTypes);
|
||||||
|
this._returnType = returnType;
|
||||||
|
this._rawMethod = TypeUtil.getPrototype(declaringClazz.getType())[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
public getReturnType(): any {
|
||||||
|
return this._returnType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public invoke(instance: Object, ...args: any[]): any {
|
||||||
|
return this._rawMethod.apply(instance, args);
|
||||||
|
}
|
||||||
|
}
|
33
src/@loafer/core/reflect/Parameter.ts
Normal file
33
src/@loafer/core/reflect/Parameter.ts
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import { AccessibleObject } from './AccessibleObject';
|
||||||
|
import { Executable } from './Executable';
|
||||||
|
|
||||||
|
export class Parameter extends AccessibleObject {
|
||||||
|
private _executable: Executable;
|
||||||
|
private _type: any;
|
||||||
|
private _index: number;
|
||||||
|
private _name: string;
|
||||||
|
|
||||||
|
public constructor(executable: Executable, parameterType: any, name: string, index: number) {
|
||||||
|
super();
|
||||||
|
this._executable = executable;
|
||||||
|
this._type = parameterType;
|
||||||
|
this._name = name;
|
||||||
|
this._index = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getDeclaringExecutable(): Executable {
|
||||||
|
return this._executable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getType(): any {
|
||||||
|
return this._type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getName(): string {
|
||||||
|
return this._name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getIndex(): number {
|
||||||
|
return this._index;
|
||||||
|
}
|
||||||
|
}
|
12
src/@loafer/core/reflect/index.ts
Normal file
12
src/@loafer/core/reflect/index.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
export * from './AccessibleObject';
|
||||||
|
export * from './AnnotatedElement';
|
||||||
|
export * from './Annotation';
|
||||||
|
export * from './Class';
|
||||||
|
export * from './Constructor';
|
||||||
|
|
||||||
|
export * from './Executable';
|
||||||
|
export * from './Field';
|
||||||
|
export * from './Member';
|
||||||
|
export * from './Metadata';
|
||||||
|
export * from './Method';
|
||||||
|
export * from './Parameter';
|
16
src/@loafer/core/type.ts
Normal file
16
src/@loafer/core/type.ts
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
export declare const Type: FunctionConstructor;
|
||||||
|
export declare function isType(v: any): v is Type<any>;
|
||||||
|
export interface Type<T> extends Function {
|
||||||
|
new (...args: any[]): T;
|
||||||
|
}
|
||||||
|
|
||||||
|
export declare type IdentityType<T> = T | symbol;
|
||||||
|
export declare type PropertyKeyType = IdentityType<string>;
|
||||||
|
export declare type MetadataKeyType = IdentityType<string>;
|
||||||
|
|
||||||
|
export enum PrimitiveType {
|
||||||
|
ANY = 'any',
|
||||||
|
STRING = 'string',
|
||||||
|
NUMBER = 'number',
|
||||||
|
BOOLEAN = 'boolean',
|
||||||
|
}
|
39
src/@loafer/core/util/AnnotationUtil.ts
Normal file
39
src/@loafer/core/util/AnnotationUtil.ts
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import { Class, Annotation } from '../reflect';
|
||||||
|
import { Type } from '../type';
|
||||||
|
|
||||||
|
export abstract class AnnotationUtils {
|
||||||
|
public static hasAnnotation<T extends Annotation>(type: Type<any>, annotationClass: Type<T>): boolean {
|
||||||
|
const annotation = AnnotationUtils.getAnnotation(type, annotationClass);
|
||||||
|
if (undefined !== annotation) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getAnnotation<T extends Annotation>(type: Type<any>, annotationClass: Type<T>): T | undefined {
|
||||||
|
const clazz = Class.forType(type);
|
||||||
|
if (undefined === clazz) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const annotations = clazz.getAnnotations();
|
||||||
|
if (0 === annotations.size) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const annonClassType of Array.from(annotations.keys())) {
|
||||||
|
if (annonClassType === annotationClass) {
|
||||||
|
return <T>annotations.get(annonClassType);
|
||||||
|
}
|
||||||
|
const annotation = AnnotationUtils.getAnnotation(annonClassType, annotationClass);
|
||||||
|
if (undefined !== annotation) {
|
||||||
|
return annotation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
66
src/@loafer/core/util/Registry.ts
Normal file
66
src/@loafer/core/util/Registry.ts
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
export abstract class Registry<K, V> {
|
||||||
|
private _parent: Registry<K, V>;
|
||||||
|
private _map: Map<K, V>;
|
||||||
|
|
||||||
|
protected constructor(parent?: Registry<K, V>) {
|
||||||
|
this._parent = parent;
|
||||||
|
this._map = new Map<K, V>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public get parent(): Registry<K, V> | undefined {
|
||||||
|
return this._parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get size(): number {
|
||||||
|
return this._map.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get(key: K): V | undefined {
|
||||||
|
let v = this._map.get(key);
|
||||||
|
if (undefined === v && undefined !== this._parent) {
|
||||||
|
v = this._parent.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
public has(key: K): boolean {
|
||||||
|
let exist = this._map.has(key);
|
||||||
|
if (!exist && undefined !== this._parent) {
|
||||||
|
exist = this._parent.has(key);
|
||||||
|
}
|
||||||
|
return exist;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set(key: K, value: V): void {
|
||||||
|
this._map.set(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public entries(): IterableIterator<[K, V]> {
|
||||||
|
return this._map.entries();
|
||||||
|
}
|
||||||
|
|
||||||
|
public keys(): IterableIterator<K> {
|
||||||
|
return this._map.keys();
|
||||||
|
}
|
||||||
|
|
||||||
|
public values(): IterableIterator<V> {
|
||||||
|
return this._map.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
public clear(): void {
|
||||||
|
this._map.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public delete(key: K): boolean {
|
||||||
|
let result = this._map.delete(key);
|
||||||
|
if (!result && undefined !== this._parent) {
|
||||||
|
result = this._parent.delete(key);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public forEach(callback: (vlaue: V, key: K, map: Map<K, V>) => void, thisArg?: any): void {
|
||||||
|
this._map.forEach(callback, thisArg);
|
||||||
|
}
|
||||||
|
}
|
407
src/@loafer/core/util/TypeUtil.ts
Normal file
407
src/@loafer/core/util/TypeUtil.ts
Normal file
|
@ -0,0 +1,407 @@
|
||||||
|
import {
|
||||||
|
Type,
|
||||||
|
PrimitiveType,
|
||||||
|
PropertyKeyType,
|
||||||
|
} from '../type';
|
||||||
|
|
||||||
|
|
||||||
|
export class TypeUtil {
|
||||||
|
/**
|
||||||
|
* Get the provide constructor.
|
||||||
|
* @param target
|
||||||
|
*/
|
||||||
|
public static getContructor<T>(target: any): Type<T> {
|
||||||
|
return typeof target === 'function' ? target : target.constructor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the provide constructor if target is an instance.
|
||||||
|
* @param target
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
public static getType<T>(target: any): Type<T> {
|
||||||
|
return target.prototype ? target : target.constructor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the provide prototype if target is an instance.
|
||||||
|
* @param target
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
public static getPrototype(target: any): Object {
|
||||||
|
return typeof target === 'function' ? target.prototype : target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param target
|
||||||
|
* @returns {symbol}
|
||||||
|
*/
|
||||||
|
public static getTypeOrSymbol(target: any): any {
|
||||||
|
return typeof target === 'symbol' ? target : TypeUtil.getType(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if the given obj is a primitive.
|
||||||
|
* @param target
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
public static isPrimitiveOrPrimitiveType(target: any): boolean {
|
||||||
|
return TypeUtil.isString(target)
|
||||||
|
|| TypeUtil.isNumber(target)
|
||||||
|
|| TypeUtil.isBoolean(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param target
|
||||||
|
* @returns {PrimitiveType}
|
||||||
|
*/
|
||||||
|
public static primitiveOf(target: any): PrimitiveType {
|
||||||
|
if (TypeUtil.isString(target)) {
|
||||||
|
return PrimitiveType.STRING;
|
||||||
|
}
|
||||||
|
if (TypeUtil.isNumber(target)) {
|
||||||
|
return PrimitiveType.NUMBER;
|
||||||
|
}
|
||||||
|
if (TypeUtil.isBoolean(target)) {
|
||||||
|
return PrimitiveType.BOOLEAN;
|
||||||
|
}
|
||||||
|
return PrimitiveType.ANY;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param target
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
public static isString(target: any): boolean {
|
||||||
|
return typeof target === 'string' || target instanceof String || target === String;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param target
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
public static isNumber(target: any): boolean {
|
||||||
|
return typeof target === 'number' || target instanceof Number || target === Number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param target
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
public static isBoolean(target: any): boolean {
|
||||||
|
return typeof target === 'boolean' || target instanceof Boolean || target === Boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param target
|
||||||
|
* @returns {Boolean}
|
||||||
|
*/
|
||||||
|
public static isArray(target: any): boolean {
|
||||||
|
return Array.isArray(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if the clazz is an array.
|
||||||
|
* @param target
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
public static isArrayOrArrayType(target: any): boolean {
|
||||||
|
if (target === Array) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return TypeUtil.isArray(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if the target.
|
||||||
|
* @param target
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
public static isCollection(target: any): boolean {
|
||||||
|
return TypeUtil.isArrayOrArrayType(target)
|
||||||
|
|| target === Map
|
||||||
|
|| target instanceof Map
|
||||||
|
|| target === Set
|
||||||
|
|| target instanceof Set
|
||||||
|
|| target === WeakMap
|
||||||
|
|| target instanceof WeakMap
|
||||||
|
|| target === WeakSet
|
||||||
|
|| target instanceof WeakSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param target
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
public static isDate(target: any): boolean {
|
||||||
|
return target === Date || target instanceof Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param target
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
public static isMethod(target: any, propertyKey: PropertyKeyType): boolean {
|
||||||
|
if (typeof(target[propertyKey]) === undefined) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return typeof target[propertyKey] === 'function';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param target
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
public static isObject(target: any): boolean {
|
||||||
|
return target === Object;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param target
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
public static isType(target: any): boolean {
|
||||||
|
return !TypeUtil.isPrimitiveOrPrimitiveType(target)
|
||||||
|
&& !TypeUtil.isObject(target)
|
||||||
|
&& !TypeUtil.isDate(target)
|
||||||
|
&& target !== undefined
|
||||||
|
&& !TypeUtil.isPromise(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if the value is an empty string, null or undefined.
|
||||||
|
* @param value
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
public static isEmpty(value: any): boolean {
|
||||||
|
return value === '' || value === null || value === undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get object name
|
||||||
|
*/
|
||||||
|
public static nameOf(obj: any): string {
|
||||||
|
switch (typeof obj) {
|
||||||
|
default:
|
||||||
|
return '' + obj;
|
||||||
|
case 'symbol':
|
||||||
|
return TypeUtil.nameOfSymbol(obj);
|
||||||
|
case 'function':
|
||||||
|
return TypeUtil.nameOfType(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the provide name.
|
||||||
|
* @param target
|
||||||
|
*/
|
||||||
|
public static nameOfType(target: any): string {
|
||||||
|
return typeof target === 'function'
|
||||||
|
? target.name
|
||||||
|
: target.constructor.name;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Get symbol name.
|
||||||
|
* @param sym
|
||||||
|
*/
|
||||||
|
public static nameOfSymbol(sym: symbol): string {
|
||||||
|
return sym.toString().replace('Symbol(', '').replace(')', '');
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param out
|
||||||
|
* @param obj
|
||||||
|
* @param {{[p: string]: (collection: any[], value: any) => any}} reducers
|
||||||
|
* @returns {any}
|
||||||
|
*/
|
||||||
|
public static deepExtends(out: any, obj: any, reducers: { [key: string]: (collection: any[], value: any) => any } = {}): any {
|
||||||
|
|
||||||
|
if (obj === undefined || obj === null) {
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TypeUtil.isPrimitiveOrPrimitiveType(obj) || typeof obj === 'symbol' || typeof obj === 'function') {
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TypeUtil.isArrayOrArrayType(obj)) {
|
||||||
|
out = out || [];
|
||||||
|
} else {
|
||||||
|
out = out || {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultReducer = reducers.default ? reducers.default : (collection: any[], value: any) => {
|
||||||
|
collection.push(value);
|
||||||
|
return collection;
|
||||||
|
};
|
||||||
|
const set = (key: string | number, value: any) => {
|
||||||
|
if (TypeUtil.isArrayOrArrayType(obj)) {
|
||||||
|
out.push(value);
|
||||||
|
} else {
|
||||||
|
out[key] = value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.keys(obj).forEach(key => {
|
||||||
|
let value = obj[key];
|
||||||
|
|
||||||
|
if (value === undefined || value === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value === '' && out[key] !== '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TypeUtil.isPrimitiveOrPrimitiveType(value) || typeof value === 'function') {
|
||||||
|
set(key, value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TypeUtil.isArrayOrArrayType(value)) {
|
||||||
|
|
||||||
|
value = value.map((v: any) => TypeUtil.deepExtends(undefined, v));
|
||||||
|
|
||||||
|
set(key, []
|
||||||
|
.concat(out[key] || [], value)
|
||||||
|
.reduce((collection: any[], v: any) =>
|
||||||
|
reducers[key] ? reducers[key](collection, v) : defaultReducer(collection, v),
|
||||||
|
[]));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Object
|
||||||
|
if (TypeUtil.isArrayOrArrayType(obj)) {
|
||||||
|
set(key, TypeUtil.deepExtends(undefined, value, reducers));
|
||||||
|
} else {
|
||||||
|
set(key, TypeUtil.deepExtends(out[key], value, reducers));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (TypeUtil.isArrayOrArrayType(out)) {
|
||||||
|
out.reduce((collection: any[], value: any) => defaultReducer(collection, value), []);
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param target
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
public static isPromise(target: any): boolean {
|
||||||
|
return target === Promise || target instanceof Promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param target
|
||||||
|
* @returns {any}
|
||||||
|
*/
|
||||||
|
public static getInheritedType(target: Type<any>): Type<any> {
|
||||||
|
return Object.getPrototypeOf(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param target
|
||||||
|
* @param {PropertyKeyType} propertyKey
|
||||||
|
* @returns {PropertyDescriptor}
|
||||||
|
*/
|
||||||
|
public static descriptorOf(target: any, propertyKey: PropertyKeyType): PropertyDescriptor {
|
||||||
|
return Object.getOwnPropertyDescriptor(target && target.prototype || target, propertyKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param target
|
||||||
|
* @param {PropertyKeyType} propertyKey
|
||||||
|
* @returns {string[]}
|
||||||
|
*/
|
||||||
|
public static getParameterNames(target: any, propertyKey: PropertyKeyType): string[] {
|
||||||
|
const rawType = TypeUtil.getPrototype(target);
|
||||||
|
const fn: Function = rawType[propertyKey];
|
||||||
|
|
||||||
|
const code = fn.toString()
|
||||||
|
.replace(COMMENTS, '')
|
||||||
|
.replace(FAT_ARROWS, '')
|
||||||
|
.replace(DEFAULT_PARAMS, '');
|
||||||
|
|
||||||
|
const result = code.slice(code.indexOf('(') + 1, code.indexOf(')')).match(/([^\s,]+)/g);
|
||||||
|
|
||||||
|
return result === null
|
||||||
|
? []
|
||||||
|
: result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param target
|
||||||
|
* @returns {Array}
|
||||||
|
*/
|
||||||
|
public static ancestorsOf(target: Type<any>): Type<any>[] {
|
||||||
|
const classes: Type<any>[] = [];
|
||||||
|
|
||||||
|
let currentTarget = TypeUtil.getType(target);
|
||||||
|
|
||||||
|
while (TypeUtil.nameOf(currentTarget) !== '') {
|
||||||
|
classes.unshift(currentTarget);
|
||||||
|
currentTarget = TypeUtil.getInheritedType(currentTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
return classes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param target
|
||||||
|
* @param {string} name
|
||||||
|
* @param {Function} callback
|
||||||
|
*/
|
||||||
|
public static applyBefore(target: any, name: string, callback: Function): void {
|
||||||
|
const original = target[name];
|
||||||
|
target[name] = function (...args: any[]): any {
|
||||||
|
callback(...args);
|
||||||
|
return original.apply(this, args);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Promise<any>} promise
|
||||||
|
* @param {number} time
|
||||||
|
* @returns {Promise<any>}
|
||||||
|
*/
|
||||||
|
public static promiseTimeout(promise: Promise<any>, time: number = 1000): Promise<{ ok: boolean, response: any }> {
|
||||||
|
const timeout = (p: Promise<any>, t: number) => new Promise((resolve) => {
|
||||||
|
p.then((response) => {
|
||||||
|
resolve();
|
||||||
|
return response;
|
||||||
|
});
|
||||||
|
setTimeout(() => resolve({ok: false}), t);
|
||||||
|
});
|
||||||
|
|
||||||
|
promise = promise.then((response) => ({ok: true, response}));
|
||||||
|
|
||||||
|
return Promise.race([
|
||||||
|
promise,
|
||||||
|
timeout(promise, time),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
|
||||||
|
const DEFAULT_PARAMS = /=[^,]+/mg;
|
||||||
|
const FAT_ARROWS = /=>.*$/mg;
|
22
src/@loafer/decorator/Decorator.ts
Normal file
22
src/@loafer/decorator/Decorator.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import {
|
||||||
|
Type,
|
||||||
|
PropertyKeyType,
|
||||||
|
} from '@loafer/core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Annotation,
|
||||||
|
} from '@loafer/core/reflect';
|
||||||
|
|
||||||
|
export interface Decorator<Attribute = {}> {
|
||||||
|
classDecorator?: <TFunction extends Function>(target: TFunction) => TFunction | void;
|
||||||
|
propertyDecorator?: (target: Object, propertyKey: PropertyKeyType) => void;
|
||||||
|
methodDecorator?: <T>(target: Object, propertyKey: PropertyKeyType,
|
||||||
|
descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void;
|
||||||
|
parameterDecorator?: (target: Object, propertyKey: PropertyKeyType, parameterIndex: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class Decorator<Attribute = {}> extends Annotation<Attribute> {
|
||||||
|
public constructor(attribute?: Attribute) {
|
||||||
|
super(attribute);
|
||||||
|
}
|
||||||
|
}
|
107
src/@loafer/decorator/DecoratorFactory.ts
Normal file
107
src/@loafer/decorator/DecoratorFactory.ts
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
import {
|
||||||
|
Type,
|
||||||
|
MetadataKeyType,
|
||||||
|
} from '@loafer/core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Annotation,
|
||||||
|
Class,
|
||||||
|
Constructor,
|
||||||
|
Field,
|
||||||
|
Method,
|
||||||
|
Parameter,
|
||||||
|
Metadata,
|
||||||
|
} from '@loafer/core/reflect';
|
||||||
|
|
||||||
|
import {
|
||||||
|
TypeUtil,
|
||||||
|
} from '@loafer/core/util/TypeUtil';
|
||||||
|
|
||||||
|
import { Decorator } from './Decorator';
|
||||||
|
import { DecoratorUtil } from './util';
|
||||||
|
import { DecoratorType } from './type';
|
||||||
|
import { NotSupportedDecoratorError } from './error';
|
||||||
|
|
||||||
|
|
||||||
|
export class DecoratorFactory {
|
||||||
|
public static create = <Attribute = {}>(DecoratorClass: Type<Decorator<Attribute>>) => {
|
||||||
|
return (attribute: Attribute) => {
|
||||||
|
const annotation: Decorator<Attribute> = new DecoratorClass(attribute);
|
||||||
|
const name: string = DecoratorClass.name;
|
||||||
|
|
||||||
|
return (...decoratorArgs: any[]) => {
|
||||||
|
const decoratorType: DecoratorType = DecoratorUtil.getDecoratorType(decoratorArgs);
|
||||||
|
|
||||||
|
const [target, propertyKey, descriptorOrParameterIndex] = decoratorArgs;
|
||||||
|
|
||||||
|
const clazz: Class = Class._defineClass(TypeUtil.getType(target));
|
||||||
|
let field: Field = null;
|
||||||
|
let method: Method = null;
|
||||||
|
let parameter: Parameter = null;
|
||||||
|
let cons: Constructor = null;
|
||||||
|
|
||||||
|
switch (decoratorType) {
|
||||||
|
case DecoratorType.CLASS:
|
||||||
|
try {
|
||||||
|
cons = clazz._defineConstructor(Metadata.getOwnParamTypes(target));
|
||||||
|
clazz._addAnnotation(annotation);
|
||||||
|
return annotation.classDecorator.call(annotation, target);
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof NotSupportedDecoratorError) {
|
||||||
|
throw new NotSupportedDecoratorError(`Cannot apply @${name} decorator on Class.`);
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
case DecoratorType.PROPERTY:
|
||||||
|
try {
|
||||||
|
field = clazz._defineField(propertyKey, Metadata.getOwnType(target, propertyKey));
|
||||||
|
field._addAnnotation(annotation);
|
||||||
|
return annotation.propertyDecorator.call(annotation, target, propertyKey);
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof NotSupportedDecoratorError) {
|
||||||
|
throw new NotSupportedDecoratorError(`Cannot apply @${name} decorator on Property.`);
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
case DecoratorType.METHOD:
|
||||||
|
try {
|
||||||
|
method = clazz._defineMethod(propertyKey,
|
||||||
|
Metadata.getOwnParamTypes(target, propertyKey),
|
||||||
|
Metadata.getOwnReturnType(target, propertyKey));
|
||||||
|
method._addAnnotation(annotation);
|
||||||
|
|
||||||
|
return annotation.methodDecorator.call(annotation, target, propertyKey, descriptorOrParameterIndex);
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof NotSupportedDecoratorError) {
|
||||||
|
throw new NotSupportedDecoratorError(`Cannot apply @${name} decorator on Method.`);
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
case DecoratorType.PARAMETER:
|
||||||
|
try {
|
||||||
|
if (undefined === propertyKey) {
|
||||||
|
cons = clazz.getConstructor();
|
||||||
|
parameter = cons.getParameter(descriptorOrParameterIndex);
|
||||||
|
} else {
|
||||||
|
method = clazz._defineMethod(propertyKey,
|
||||||
|
Metadata.getOwnParamTypes(target, propertyKey),
|
||||||
|
Metadata.getOwnReturnType(target, propertyKey));
|
||||||
|
parameter = method.getParameter(descriptorOrParameterIndex);
|
||||||
|
}
|
||||||
|
parameter._addAnnotation(annotation);
|
||||||
|
|
||||||
|
return annotation.parameterDecorator.call(annotation, target, propertyKey, descriptorOrParameterIndex);
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof NotSupportedDecoratorError) {
|
||||||
|
throw new NotSupportedDecoratorError(`Cannot apply @${name} decorator on Parameter.`);
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new NotSupportedDecoratorError(`Cannot determine decorator[@${name}] type.`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
13
src/@loafer/decorator/error.ts
Normal file
13
src/@loafer/decorator/error.ts
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
export class NotSupportedDecoratorError extends Error {
|
||||||
|
public constructor(message?: string) {
|
||||||
|
super(message);
|
||||||
|
Object.setPrototypeOf(this, new.target.prototype);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NotDecoratedClassError extends Error {
|
||||||
|
public constructor(message?: string) {
|
||||||
|
super(message);
|
||||||
|
Object.setPrototypeOf(this, new.target.prototype);
|
||||||
|
}
|
||||||
|
}
|
5
src/@loafer/decorator/index.ts
Normal file
5
src/@loafer/decorator/index.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
export * from './Decorator';
|
||||||
|
export * from './DecoratorFactory';
|
||||||
|
export * from './error';
|
||||||
|
export * from './type';
|
||||||
|
export * from './util';
|
8
src/@loafer/decorator/type.ts
Normal file
8
src/@loafer/decorator/type.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
export enum DecoratorType {
|
||||||
|
CLASS = 'Clazz',
|
||||||
|
PROPERTY = 'Property',
|
||||||
|
METHOD = 'Method',
|
||||||
|
PARAMETER = 'Parameter',
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DecoratorParametersType = [any, string | symbol, number | PropertyDescriptor];
|
48
src/@loafer/decorator/util.ts
Normal file
48
src/@loafer/decorator/util.ts
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
import {
|
||||||
|
PropertyKeyType,
|
||||||
|
} from '@loafer/core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
TypeUtil,
|
||||||
|
} from '@loafer/core/util/TypeUtil';
|
||||||
|
|
||||||
|
|
||||||
|
import {
|
||||||
|
DecoratorType,
|
||||||
|
DecoratorParametersType,
|
||||||
|
} from './type';
|
||||||
|
|
||||||
|
export class DecoratorUtil {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {any[]} args
|
||||||
|
* @returns {DecoratorType}
|
||||||
|
*/
|
||||||
|
public static getDecoratorType(args: any[]): DecoratorType {
|
||||||
|
const [, propertyKey, descriptor] = args;
|
||||||
|
|
||||||
|
if (typeof descriptor === 'number') {
|
||||||
|
return DecoratorType.PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (propertyKey && descriptor === undefined || descriptor && (descriptor.get || descriptor.set)) {
|
||||||
|
return DecoratorType.PROPERTY;
|
||||||
|
}
|
||||||
|
return (descriptor && descriptor.value) ? DecoratorType.METHOD : DecoratorType.CLASS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param target
|
||||||
|
* @param {string} propertyKey
|
||||||
|
* @returns {DecoratorParametersType}
|
||||||
|
*/
|
||||||
|
public static decoratorArgs(target: any, propertyKey: PropertyKeyType): DecoratorParametersType {
|
||||||
|
return [
|
||||||
|
target,
|
||||||
|
propertyKey,
|
||||||
|
TypeUtil.descriptorOf(target, propertyKey),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
6
src/@loafer/ng-logger/core/config.ts
Normal file
6
src/@loafer/ng-logger/core/config.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import { LoggerLevel } from './type';
|
||||||
|
|
||||||
|
export interface LoggerConfig {
|
||||||
|
level: LoggerLevel;
|
||||||
|
serverLogLevel?: LoggerLevel;
|
||||||
|
}
|
3
src/@loafer/ng-logger/core/index.ts
Normal file
3
src/@loafer/ng-logger/core/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export * from './config';
|
||||||
|
export * from './token';
|
||||||
|
export * from './type';
|
3
src/@loafer/ng-logger/core/token.ts
Normal file
3
src/@loafer/ng-logger/core/token.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
import { InjectionToken } from '@angular/core';
|
||||||
|
|
||||||
|
export const _LOGGER_CONFIG = new InjectionToken('@loafer/ng-logger Internal Logger config');
|
26
src/@loafer/ng-logger/core/type.ts
Normal file
26
src/@loafer/ng-logger/core/type.ts
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
export enum LoggerLevel {
|
||||||
|
TRACE = 0,
|
||||||
|
DEBUG,
|
||||||
|
INFO,
|
||||||
|
LOG,
|
||||||
|
WARN,
|
||||||
|
ERROR,
|
||||||
|
OFF,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LoggerLevelName = [
|
||||||
|
'TRACE',
|
||||||
|
'DEBUG',
|
||||||
|
'INFO',
|
||||||
|
'LOG',
|
||||||
|
'WARN',
|
||||||
|
'ERROR',
|
||||||
|
'OFF'
|
||||||
|
];
|
||||||
|
|
||||||
|
export interface ServerLoggingParameter {
|
||||||
|
level: string;
|
||||||
|
message: string;
|
||||||
|
addtional?: string;
|
||||||
|
timestamp: Date;
|
||||||
|
}
|
1
src/@loafer/ng-logger/index.ts
Normal file
1
src/@loafer/ng-logger/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export * from './ng-logger.module';
|
65
src/@loafer/ng-logger/ng-logger.module.ts
Normal file
65
src/@loafer/ng-logger/ng-logger.module.ts
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
import {
|
||||||
|
NgModule,
|
||||||
|
ModuleWithProviders,
|
||||||
|
Type,
|
||||||
|
Inject,
|
||||||
|
InjectionToken,
|
||||||
|
} from '@angular/core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
_LOGGER_CONFIG,
|
||||||
|
LoggerConfig,
|
||||||
|
} from './core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
SERVICES, LoggerService,
|
||||||
|
} from './service';
|
||||||
|
|
||||||
|
export interface LoggerFeatureModuleConfig {
|
||||||
|
url?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LoggerRootModuleConfig {
|
||||||
|
config: LoggerConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({})
|
||||||
|
export class LoggerRootModule {
|
||||||
|
constructor(
|
||||||
|
private loggerService: LoggerService,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({})
|
||||||
|
export class LoggerFeatureModule {
|
||||||
|
constructor(
|
||||||
|
private loggerService: LoggerService,
|
||||||
|
private root: LoggerRootModule,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({})
|
||||||
|
export class LoggerModule {
|
||||||
|
static forRoot(config: LoggerRootModuleConfig): ModuleWithProviders {
|
||||||
|
return {
|
||||||
|
ngModule: LoggerRootModule,
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: _LOGGER_CONFIG,
|
||||||
|
useValue: config.config,
|
||||||
|
},
|
||||||
|
SERVICES,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static forFeature(config: LoggerFeatureModuleConfig): ModuleWithProviders {
|
||||||
|
return {
|
||||||
|
ngModule: LoggerFeatureModule,
|
||||||
|
providers: [
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
7
src/@loafer/ng-logger/service/index.ts
Normal file
7
src/@loafer/ng-logger/service/index.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export * from './logger.service';
|
||||||
|
|
||||||
|
import { LoggerService } from './logger.service';
|
||||||
|
|
||||||
|
export const SERVICES = [
|
||||||
|
LoggerService,
|
||||||
|
];
|
158
src/@loafer/ng-logger/service/logger.service.ts
Normal file
158
src/@loafer/ng-logger/service/logger.service.ts
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
import { Injectable, Inject, PLATFORM_ID } from '@angular/core';
|
||||||
|
import { isPlatformBrowser } from '@angular/common';
|
||||||
|
|
||||||
|
import {
|
||||||
|
LoggerConfig,
|
||||||
|
LoggerLevel,
|
||||||
|
LoggerLevelName,
|
||||||
|
_LOGGER_CONFIG,
|
||||||
|
} from '../core';
|
||||||
|
|
||||||
|
export type ConsoleFunc = (message?: any, ...optionalParams: any[]) => void;
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LoggerService {
|
||||||
|
private _isIE: boolean;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
@Inject(_LOGGER_CONFIG) private readonly config: LoggerConfig,
|
||||||
|
@Inject(PLATFORM_ID) private readonly platformId,
|
||||||
|
) {
|
||||||
|
this._isIE = isPlatformBrowser(platformId) &&
|
||||||
|
!!(navigator.userAgent.indexOf('MSIE') !== -1 || navigator.userAgent.match(/Trident\//) || navigator.userAgent.match(/Edge\//));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public get trace(): ConsoleFunc {
|
||||||
|
return this.getConsoleMethod(LoggerLevel.TRACE);
|
||||||
|
}
|
||||||
|
public get debug(): ConsoleFunc {
|
||||||
|
return this.getConsoleMethod(LoggerLevel.DEBUG);
|
||||||
|
}
|
||||||
|
public get info(): ConsoleFunc {
|
||||||
|
return this.getConsoleMethod(LoggerLevel.INFO);
|
||||||
|
}
|
||||||
|
public get log(): ConsoleFunc {
|
||||||
|
return this.getConsoleMethod(LoggerLevel.LOG);
|
||||||
|
}
|
||||||
|
public get warn(): ConsoleFunc {
|
||||||
|
return this.getConsoleMethod(LoggerLevel.WARN);
|
||||||
|
}
|
||||||
|
public get error(): ConsoleFunc {
|
||||||
|
return this.getConsoleMethod(LoggerLevel.ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _console_log: ConsoleFunc;
|
||||||
|
|
||||||
|
private getConsoleMethod(level: LoggerLevel): ConsoleFunc {
|
||||||
|
if (level < this.config.level) {
|
||||||
|
return (message, ...additional: any[]) => {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._isIE) {
|
||||||
|
switch (level) {
|
||||||
|
case LoggerLevel.WARN:
|
||||||
|
return console.warn.bind(console, ...this.getLogHeader(level));
|
||||||
|
case LoggerLevel.ERROR:
|
||||||
|
return console.error.bind(console, ...this.getLogHeader(level));
|
||||||
|
case LoggerLevel.INFO:
|
||||||
|
return console.info.bind(console, ...this.getLogHeader(level));
|
||||||
|
default:
|
||||||
|
return console.log.bind(console, ...this.getLogHeader(level));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return console.log.bind(console, ...this.getLogHeader(level));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getLogHeader(level: LoggerLevel): any[] {
|
||||||
|
const params: any[] = [];
|
||||||
|
params.push(`%c${this._timestamp()} [${LoggerLevelName[level]}]`);
|
||||||
|
if (!this._isIE) {
|
||||||
|
const color = this._getColor(level);
|
||||||
|
params.push(`color:${color}`);
|
||||||
|
}
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _timestamp(): string {
|
||||||
|
return new Date().toISOString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _log(level: LoggerLevel, message, additional: any[] = []): void {
|
||||||
|
if (!message) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow logging on server even if client log level is off
|
||||||
|
// if (logOnServer) {
|
||||||
|
// this._logOnServer(level, message, additional);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if no message or the log level is less than the environ
|
||||||
|
if (level < this.config.level) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
message = typeof message === 'string' ? message : JSON.stringify(message, null, 2);
|
||||||
|
} catch (e) {
|
||||||
|
additional = [message, ...additional];
|
||||||
|
message = 'The provided "message" value could not be parsed with JSON.stringify().';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Coloring doesn't work in IE
|
||||||
|
if (this._isIE) {
|
||||||
|
return this._logIE(level, message, additional);
|
||||||
|
}
|
||||||
|
|
||||||
|
const color = this._getColor(level);
|
||||||
|
|
||||||
|
const params: any[] = [];
|
||||||
|
params.push(`%c${this._timestamp()} [${LoggerLevelName[level]}]`);
|
||||||
|
params.push(`color:${color}`);
|
||||||
|
params.push(message);
|
||||||
|
params.push(...additional);
|
||||||
|
|
||||||
|
console.log.apply(console, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _logIE(level: LoggerLevel, message: string, additional: any[] = []): void {
|
||||||
|
const params: any[] = [];
|
||||||
|
params.push(`${this._timestamp()} [${LoggerLevelName[level]}] `);
|
||||||
|
params.push(message);
|
||||||
|
params.push(...additional);
|
||||||
|
|
||||||
|
switch (level) {
|
||||||
|
case LoggerLevel.WARN:
|
||||||
|
console.warn.apply(console, params);
|
||||||
|
break;
|
||||||
|
case LoggerLevel.ERROR:
|
||||||
|
console.error.apply(console, params);
|
||||||
|
break;
|
||||||
|
case LoggerLevel.INFO:
|
||||||
|
console.info.apply(console, params);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.log.apply(console, params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getColor(level: LoggerLevel): 'blue' | 'teal' | 'gray' | 'red' | undefined {
|
||||||
|
switch (level) {
|
||||||
|
case LoggerLevel.TRACE:
|
||||||
|
return 'blue';
|
||||||
|
case LoggerLevel.DEBUG:
|
||||||
|
return 'teal';
|
||||||
|
case LoggerLevel.INFO:
|
||||||
|
case LoggerLevel.LOG:
|
||||||
|
return 'gray';
|
||||||
|
case LoggerLevel.WARN:
|
||||||
|
case LoggerLevel.ERROR:
|
||||||
|
return 'red';
|
||||||
|
case LoggerLevel.OFF:
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
64
src/@loafer/ng-rest/client/RESTClient.ts
Normal file
64
src/@loafer/ng-rest/client/RESTClient.ts
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
import { Injectable, Inject } from '@angular/core';
|
||||||
|
import { Location } from '@angular/common';
|
||||||
|
import { HttpClient, HttpHeaders, HttpParams, HttpErrorResponse } from '@angular/common/http';
|
||||||
|
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
import 'rxjs/add/operator/do';
|
||||||
|
import 'rxjs/add/operator/map';
|
||||||
|
import 'rxjs/add/operator/catch';
|
||||||
|
import 'rxjs/add/operator/timeout';
|
||||||
|
import 'rxjs/add/observable/throw';
|
||||||
|
|
||||||
|
import {
|
||||||
|
RESTError,
|
||||||
|
RESTClientError,
|
||||||
|
} from '../protocol';
|
||||||
|
|
||||||
|
export class RESTClient {
|
||||||
|
constructor(
|
||||||
|
private _baseURL: string,
|
||||||
|
private _httpClient: HttpClient,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public get httpClient(): HttpClient {
|
||||||
|
return this._httpClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
public request<T>(method: string, entry: string, options?: {
|
||||||
|
body?: any;
|
||||||
|
headers?: HttpHeaders | {
|
||||||
|
[header: string]: string | string[];
|
||||||
|
};
|
||||||
|
observe?: 'body';
|
||||||
|
params?: HttpParams | {
|
||||||
|
[param: string]: string | string[];
|
||||||
|
};
|
||||||
|
responseType?: 'json';
|
||||||
|
reportProgress?: boolean;
|
||||||
|
withCredentials?: boolean;
|
||||||
|
}): Observable<T> {
|
||||||
|
return this._httpClient
|
||||||
|
.request<T>(method, Location.joinWithSlash(this._baseURL, entry), options)
|
||||||
|
.map(response => response)
|
||||||
|
.catch((error: HttpErrorResponse) => {
|
||||||
|
const restClientError: RESTClientError = {
|
||||||
|
request: {
|
||||||
|
method: method,
|
||||||
|
entry: entry,
|
||||||
|
options: options,
|
||||||
|
},
|
||||||
|
response: error.error,
|
||||||
|
};
|
||||||
|
|
||||||
|
console.error(restClientError);
|
||||||
|
// const aryMsg = error.error.message.split('|');
|
||||||
|
// const resError: RESTError = {
|
||||||
|
// code: error.error.code,
|
||||||
|
// message: aryMsg[0],
|
||||||
|
// data: aryMsg[1] === 'null' ? '' : aryMsg[1],
|
||||||
|
// };
|
||||||
|
return Observable.throw(restClientError);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
1
src/@loafer/ng-rest/client/index.ts
Normal file
1
src/@loafer/ng-rest/client/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export * from './RESTClient';
|
1
src/@loafer/ng-rest/core/index.ts
Normal file
1
src/@loafer/ng-rest/core/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export * from './token';
|
3
src/@loafer/ng-rest/core/token.ts
Normal file
3
src/@loafer/ng-rest/core/token.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
import { InjectionToken } from '@angular/core';
|
||||||
|
|
||||||
|
export const _REST_BASE_URL = new InjectionToken('@loafer/ng-rest Internal Base URL');
|
1
src/@loafer/ng-rest/index.ts
Normal file
1
src/@loafer/ng-rest/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export * from './ng-rest.module';
|
64
src/@loafer/ng-rest/ng-rest.module.ts
Normal file
64
src/@loafer/ng-rest/ng-rest.module.ts
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
import {
|
||||||
|
NgModule,
|
||||||
|
ModuleWithProviders,
|
||||||
|
Type,
|
||||||
|
Inject,
|
||||||
|
InjectionToken,
|
||||||
|
} from '@angular/core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
_REST_BASE_URL,
|
||||||
|
} from './core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
SERVICES, RESTService,
|
||||||
|
} from './service';
|
||||||
|
|
||||||
|
export interface RESTFeatureModuleConfig {
|
||||||
|
url?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RESTRootModuleConfig {
|
||||||
|
baseURL: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({})
|
||||||
|
export class RESTRootModule {
|
||||||
|
constructor(
|
||||||
|
private restService: RESTService,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({})
|
||||||
|
export class RESTFeatureModule {
|
||||||
|
constructor(
|
||||||
|
private restService: RESTService,
|
||||||
|
private root: RESTRootModule,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({})
|
||||||
|
export class RESTModule {
|
||||||
|
static forRoot(config: RESTRootModuleConfig): ModuleWithProviders {
|
||||||
|
return {
|
||||||
|
ngModule: RESTRootModule,
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: _REST_BASE_URL,
|
||||||
|
useValue: config.baseURL,
|
||||||
|
},
|
||||||
|
SERVICES,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static forFeature(config: RESTFeatureModuleConfig): ModuleWithProviders {
|
||||||
|
return {
|
||||||
|
ngModule: RESTFeatureModule,
|
||||||
|
providers: [
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
14
src/@loafer/ng-rest/protocol/RESTError.ts
Normal file
14
src/@loafer/ng-rest/protocol/RESTError.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
export interface RESTClientError {
|
||||||
|
request: {
|
||||||
|
method: string;
|
||||||
|
entry: string;
|
||||||
|
options?: any;
|
||||||
|
};
|
||||||
|
response: RESTError;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RESTError {
|
||||||
|
code: number;
|
||||||
|
message: string;
|
||||||
|
data?: any;
|
||||||
|
}
|
1
src/@loafer/ng-rest/protocol/index.ts
Normal file
1
src/@loafer/ng-rest/protocol/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export * from './RESTError';
|
7
src/@loafer/ng-rest/service/index.ts
Normal file
7
src/@loafer/ng-rest/service/index.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export * from './rest.service';
|
||||||
|
|
||||||
|
import { RESTService } from './rest.service';
|
||||||
|
|
||||||
|
export const SERVICES = [
|
||||||
|
RESTService,
|
||||||
|
];
|
15
src/@loafer/ng-rest/service/rest.service.spec.ts
Normal file
15
src/@loafer/ng-rest/service/rest.service.spec.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import { TestBed, inject } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { RESTService } from './rest.service';
|
||||||
|
|
||||||
|
describe('RESTService', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
providers: [RESTService]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', inject([RESTService], (service: RESTService) => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
}));
|
||||||
|
});
|
26
src/@loafer/ng-rest/service/rest.service.ts
Normal file
26
src/@loafer/ng-rest/service/rest.service.ts
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import { Injectable, Inject } from '@angular/core';
|
||||||
|
import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
|
||||||
|
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
|
||||||
|
import { Location } from '@angular/common';
|
||||||
|
|
||||||
|
import 'rxjs/add/operator/do';
|
||||||
|
import 'rxjs/add/operator/map';
|
||||||
|
import 'rxjs/add/operator/catch';
|
||||||
|
import 'rxjs/add/operator/timeout';
|
||||||
|
import 'rxjs/add/observable/throw';
|
||||||
|
|
||||||
|
import { _REST_BASE_URL } from '../core';
|
||||||
|
import { RESTClient } from '../client';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class RESTService extends RESTClient {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(_REST_BASE_URL) _baseURL: string,
|
||||||
|
@Inject(HttpClient) _httpClient: HttpClient,
|
||||||
|
) {
|
||||||
|
super(_baseURL, _httpClient);
|
||||||
|
}
|
||||||
|
}
|
145
src/@loafer/ng-rpc/client/RPCClient.ts
Normal file
145
src/@loafer/ng-rpc/client/RPCClient.ts
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
import { Subject } from 'rxjs/Subject';
|
||||||
|
|
||||||
|
import { RPCClientError } from '../protocol/RPCError';
|
||||||
|
import { RPCClientRWC } from './RPCClientRWC';
|
||||||
|
|
||||||
|
import {
|
||||||
|
RPCClientCodec,
|
||||||
|
RPCClientResponseCodec,
|
||||||
|
RPCClientNotificationCodec,
|
||||||
|
} from '../protocol/RPCClientCodec';
|
||||||
|
|
||||||
|
export interface RPCRequestState {
|
||||||
|
subject: Subject<any>;
|
||||||
|
request: {
|
||||||
|
method: string;
|
||||||
|
params: any[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class RPCClient {
|
||||||
|
private _requestID: number;
|
||||||
|
|
||||||
|
private _pendingRequestsCount: number;
|
||||||
|
private _pendingRequests: Map<number, RPCRequestState>;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
private _codec: RPCClientCodec,
|
||||||
|
private _rwc: RPCClientRWC,
|
||||||
|
) {
|
||||||
|
this._requestID = 0;
|
||||||
|
this._pendingRequestsCount = 0;
|
||||||
|
this._pendingRequests = new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
private getRequestID(): number {
|
||||||
|
return ++this._requestID;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* connect
|
||||||
|
*/
|
||||||
|
public connect(queryString?: string): void {
|
||||||
|
this._rwc.connect(queryString);
|
||||||
|
this._rwc.readResponse().subscribe(
|
||||||
|
(value: Object) => {
|
||||||
|
this.onMessage(value);
|
||||||
|
},
|
||||||
|
(error: any) => {
|
||||||
|
console.error(error);
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* close
|
||||||
|
*/
|
||||||
|
public disconnect() {
|
||||||
|
this._rwc.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* notify
|
||||||
|
*/
|
||||||
|
public send(method: string, ...args: any[]): void {
|
||||||
|
this.sendInternal(false, method, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* call
|
||||||
|
*/
|
||||||
|
public call<T>(method: string, ...args: any[]): Observable<T> {
|
||||||
|
return this.sendInternal<T>(true, method, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* callTimeout
|
||||||
|
*/
|
||||||
|
public callTimeout<T>(ms: number, method: string, ...args: any[]): Observable<T> {
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
private sendInternal<T>(hasResponse: boolean, method: string, args?: any[]): Observable<T> | undefined {
|
||||||
|
let id: number;
|
||||||
|
let resSubject: Subject<T>;
|
||||||
|
if (hasResponse) {
|
||||||
|
id = this.getRequestID();
|
||||||
|
resSubject = new Subject<T>();
|
||||||
|
const reqState: RPCRequestState = {
|
||||||
|
subject: resSubject,
|
||||||
|
request: {
|
||||||
|
method: method,
|
||||||
|
params: args,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this._pendingRequests.set(id, reqState);
|
||||||
|
this._pendingRequestsCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
const req = this._codec.request(method, args, id);
|
||||||
|
this._rwc.writeRequest(req);
|
||||||
|
|
||||||
|
if (undefined !== resSubject) {
|
||||||
|
return resSubject.asObservable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private onMessage(message: Object): void {
|
||||||
|
const resCodec = this._codec.response(message);
|
||||||
|
|
||||||
|
if (resCodec.isNotification()) {
|
||||||
|
this.onNotification(resCodec.notification());
|
||||||
|
} else {
|
||||||
|
this.onResponse(resCodec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onResponse(resCodec: RPCClientResponseCodec): void {
|
||||||
|
const id = resCodec.id();
|
||||||
|
const result = resCodec.result();
|
||||||
|
const error = resCodec.error();
|
||||||
|
|
||||||
|
const reqState: RPCRequestState = this._pendingRequests.get(id);
|
||||||
|
|
||||||
|
this._pendingRequests.delete(id);
|
||||||
|
this._pendingRequestsCount--;
|
||||||
|
|
||||||
|
if (undefined !== result) {
|
||||||
|
reqState.subject.next(result);
|
||||||
|
} else if (undefined !== error) {
|
||||||
|
const rpcClientError: RPCClientError = {
|
||||||
|
request: reqState.request,
|
||||||
|
response: error,
|
||||||
|
};
|
||||||
|
console.error(rpcClientError);
|
||||||
|
reqState.subject.error(rpcClientError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract onNotification(notiCodec: RPCClientNotificationCodec): void;
|
||||||
|
}
|
11
src/@loafer/ng-rpc/client/RPCClientRWC.ts
Normal file
11
src/@loafer/ng-rpc/client/RPCClientRWC.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import { InjectionToken } from '@angular/core';
|
||||||
|
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
|
||||||
|
export interface RPCClientRWC {
|
||||||
|
connect(queryString?: string): void;
|
||||||
|
readResponse(): Observable<Object>;
|
||||||
|
writeRequest(data: any): void;
|
||||||
|
disconnect(): void;
|
||||||
|
connectionStatus(): Observable<boolean>;
|
||||||
|
}
|
2
src/@loafer/ng-rpc/client/index.ts
Normal file
2
src/@loafer/ng-rpc/client/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './RPCClient';
|
||||||
|
export * from './RPCClientRWC';
|
|
@ -0,0 +1,62 @@
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
import { Subject } from 'rxjs/Subject';
|
||||||
|
import { map } from 'rxjs/operator/map';
|
||||||
|
|
||||||
|
import {
|
||||||
|
RxWebsocketSubject,
|
||||||
|
RxWebsocketSubjectConfig,
|
||||||
|
} from './RxWebsocketSubject';
|
||||||
|
|
||||||
|
import { RPCClientRWC } from '../../RPCClientRWC';
|
||||||
|
|
||||||
|
export class RPCClientWebsocketRWC implements RPCClientRWC {
|
||||||
|
private _wsSocketSubject: RxWebsocketSubject<Object>;
|
||||||
|
private _responseSubject: Subject<Object>;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
private _config: RxWebsocketSubjectConfig,
|
||||||
|
) {
|
||||||
|
this._wsSocketSubject = new RxWebsocketSubject<Object>(this._config);
|
||||||
|
}
|
||||||
|
|
||||||
|
public connect(queryString?: string): void {
|
||||||
|
if (undefined !== queryString) {
|
||||||
|
this._wsSocketSubject.queryString = queryString;
|
||||||
|
}
|
||||||
|
this._wsSocketSubject.connect();
|
||||||
|
this._wsSocketSubject.subscribe(
|
||||||
|
(value: Object) => {
|
||||||
|
if (undefined !== this._responseSubject) {
|
||||||
|
this._responseSubject.next(value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(error: any) => {
|
||||||
|
if (undefined !== this._responseSubject) {
|
||||||
|
this._responseSubject.error(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
console.log('sss');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public disconnect(): void {
|
||||||
|
this._wsSocketSubject.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
public connectionStatus(): Observable<boolean> {
|
||||||
|
return this._wsSocketSubject.connectionStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public readResponse(): Observable<Object> {
|
||||||
|
if (undefined === this._responseSubject) {
|
||||||
|
this._responseSubject = new Subject<Object>();
|
||||||
|
}
|
||||||
|
return this._responseSubject.asObservable();
|
||||||
|
}
|
||||||
|
|
||||||
|
public writeRequest(data: any): void {
|
||||||
|
this._wsSocketSubject.write(data);
|
||||||
|
}
|
||||||
|
}
|
117
src/@loafer/ng-rpc/client/rwc/websocket/RxWebsocketSubject.ts
Normal file
117
src/@loafer/ng-rpc/client/rwc/websocket/RxWebsocketSubject.ts
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
import { Observer } from 'rxjs/Observer';
|
||||||
|
import { Subject } from 'rxjs/Subject';
|
||||||
|
import {
|
||||||
|
WebSocketSubject,
|
||||||
|
WebSocketSubjectConfig
|
||||||
|
} from 'rxjs/observable/dom/WebSocketSubject';
|
||||||
|
|
||||||
|
import 'rxjs/add/operator/distinctUntilChanged';
|
||||||
|
import 'rxjs/add/operator/share';
|
||||||
|
import 'rxjs/add/operator/takeWhile';
|
||||||
|
import 'rxjs/add/observable/interval';
|
||||||
|
|
||||||
|
export interface RxWebsocketSubjectConfig {
|
||||||
|
url: string;
|
||||||
|
protocol?: string | Array<string>;
|
||||||
|
reconnectInterval?: 5000;
|
||||||
|
reconnectRetry?: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RxWebsocketSubject<T> extends Subject<T> {
|
||||||
|
private _reconnectionObservable: Observable<number>;
|
||||||
|
private _wsSubjectConfig: WebSocketSubjectConfig;
|
||||||
|
private _socket: WebSocketSubject<any>;
|
||||||
|
private _connectionObserver: Observer<boolean>;
|
||||||
|
private _connectionStatus: Observable<boolean>;
|
||||||
|
private _queryString: string;
|
||||||
|
|
||||||
|
public constructor(private _config: RxWebsocketSubjectConfig) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this._connectionStatus = new Observable<boolean>((observer) => {
|
||||||
|
this._connectionObserver = observer;
|
||||||
|
}).share().distinctUntilChanged();
|
||||||
|
|
||||||
|
this._wsSubjectConfig = {
|
||||||
|
url: _config.url,
|
||||||
|
protocol: _config.protocol,
|
||||||
|
closeObserver: {
|
||||||
|
next: (e: CloseEvent) => {
|
||||||
|
this._socket = null;
|
||||||
|
this._connectionObserver.next(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
openObserver: {
|
||||||
|
next: (e: Event) => {
|
||||||
|
this._connectionObserver.next(true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
this._connectionStatus.subscribe((isConnected: boolean) => {
|
||||||
|
if (!this._reconnectionObservable && typeof(isConnected) === 'boolean' && !isConnected) {
|
||||||
|
this.reconnect();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public set queryString(query: string) {
|
||||||
|
this._queryString = query;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get queryString(): string | undefined {
|
||||||
|
return this._queryString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get connectionStatus(): Observable<boolean> {
|
||||||
|
return this._connectionStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public connect(): void {
|
||||||
|
const wsSubjectConfig = Object.assign({}, this._wsSubjectConfig);
|
||||||
|
if (undefined !== this._queryString) {
|
||||||
|
wsSubjectConfig.url = wsSubjectConfig.url + '?' + this._queryString;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._socket = new WebSocketSubject(wsSubjectConfig);
|
||||||
|
this._socket.subscribe(
|
||||||
|
(m) => {
|
||||||
|
this.next(m);
|
||||||
|
},
|
||||||
|
(error: Event) => {
|
||||||
|
if (!this._socket) {
|
||||||
|
this.reconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public disconnect(): void {
|
||||||
|
this._socket.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
private reconnect(): void {
|
||||||
|
this._reconnectionObservable = Observable.interval(this._config.reconnectInterval)
|
||||||
|
.takeWhile((v, index) => {
|
||||||
|
return index < this._config.reconnectRetry && !this._socket;
|
||||||
|
});
|
||||||
|
this._reconnectionObservable.subscribe(
|
||||||
|
() => {
|
||||||
|
this.connect();
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
() => {
|
||||||
|
this._reconnectionObservable = null;
|
||||||
|
if (!this._socket) {
|
||||||
|
this.complete();
|
||||||
|
this._connectionObserver.complete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public write(data: any): void {
|
||||||
|
this._socket.next(data);
|
||||||
|
}
|
||||||
|
}
|
2
src/@loafer/ng-rpc/client/rwc/websocket/index.ts
Normal file
2
src/@loafer/ng-rpc/client/rwc/websocket/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './RPCClientWebsocketRWC';
|
||||||
|
export * from './RxWebsocketSubject';
|
6
src/@loafer/ng-rpc/core/error.ts
Normal file
6
src/@loafer/ng-rpc/core/error.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
export class SubscriberParameterError extends Error {
|
||||||
|
public constructor(message?: string) {
|
||||||
|
super(message);
|
||||||
|
Object.setPrototypeOf(this, new.target.prototype);
|
||||||
|
}
|
||||||
|
}
|
2
src/@loafer/ng-rpc/core/index.ts
Normal file
2
src/@loafer/ng-rpc/core/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './error';
|
||||||
|
export * from './token';
|
8
src/@loafer/ng-rpc/core/token.ts
Normal file
8
src/@loafer/ng-rpc/core/token.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import { InjectionToken } from '@angular/core';
|
||||||
|
|
||||||
|
export const RPC_CODEC = new InjectionToken('@loafer/ng-rpc RPC codec');
|
||||||
|
export const RPC_RWC = new InjectionToken('@loafer/ng-rpc RPC rwc');
|
||||||
|
|
||||||
|
export const _ROOT_SUBSCRIBERS = new InjectionToken('@loafer/ng-rpc RPC root subscribers');
|
||||||
|
export const _FEATURE_SUBSCRIBERS = new InjectionToken('@loafer/ng-rpc RPC feature subscribers');
|
||||||
|
|
1
src/@loafer/ng-rpc/decorator/index.ts
Normal file
1
src/@loafer/ng-rpc/decorator/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export * from './rpc-subscriber.decorator';
|
26
src/@loafer/ng-rpc/decorator/rpc-subscriber.decorator.ts
Normal file
26
src/@loafer/ng-rpc/decorator/rpc-subscriber.decorator.ts
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import {
|
||||||
|
Type,
|
||||||
|
PropertyKeyType,
|
||||||
|
} from '@loafer/core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Decorator,
|
||||||
|
DecoratorFactory,
|
||||||
|
} from '@loafer/decorator';
|
||||||
|
|
||||||
|
export interface RPCSubscriberDecoratorAttribute {
|
||||||
|
method: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RPCSubscriberDecorator extends Decorator<RPCSubscriberDecoratorAttribute> {
|
||||||
|
public constructor(config: RPCSubscriberDecoratorAttribute) {
|
||||||
|
super(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
public methodDecorator = <T>(target: Object, propertyKey: PropertyKeyType,
|
||||||
|
descriptor: TypedPropertyDescriptor<T>): TypedPropertyDescriptor<T> | void => {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RPCSubscriber = DecoratorFactory.create<RPCSubscriberDecoratorAttribute>(RPCSubscriberDecorator);
|
1
src/@loafer/ng-rpc/index.ts
Normal file
1
src/@loafer/ng-rpc/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export * from './ng-rpc.module';
|
109
src/@loafer/ng-rpc/ng-rpc.module.ts
Normal file
109
src/@loafer/ng-rpc/ng-rpc.module.ts
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
import {
|
||||||
|
NgModule,
|
||||||
|
ModuleWithProviders,
|
||||||
|
Type,
|
||||||
|
Inject,
|
||||||
|
InjectionToken,
|
||||||
|
} from '@angular/core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
RPC_CODEC,
|
||||||
|
RPC_RWC,
|
||||||
|
|
||||||
|
_ROOT_SUBSCRIBERS,
|
||||||
|
_FEATURE_SUBSCRIBERS,
|
||||||
|
} from './core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
RPCClientRWC,
|
||||||
|
} from './client/RPCClientRWC';
|
||||||
|
|
||||||
|
import {
|
||||||
|
RPCClientCodec,
|
||||||
|
} from './protocol';
|
||||||
|
|
||||||
|
import {
|
||||||
|
SERVICES, RPCService,
|
||||||
|
} from './service';
|
||||||
|
|
||||||
|
import {
|
||||||
|
SUBSCRIBERS, RPCSubscribeService,
|
||||||
|
} from './subscribe';
|
||||||
|
|
||||||
|
export interface RPCFeatureModuleConfig {
|
||||||
|
subscribers?: Type<any>[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RPCRootModuleConfig {
|
||||||
|
subscribers?: Type<any>[];
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({})
|
||||||
|
export class RPCRootModule {
|
||||||
|
constructor(
|
||||||
|
private rpcService: RPCService,
|
||||||
|
private rpcSubscribeService: RPCSubscribeService,
|
||||||
|
@Inject(_ROOT_SUBSCRIBERS) rootSubscribers: any[],
|
||||||
|
) {
|
||||||
|
rootSubscribers.forEach((subscriber) => {
|
||||||
|
rpcSubscribeService.addSubscriber(subscriber);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({})
|
||||||
|
export class RPCFeatureModule {
|
||||||
|
constructor(
|
||||||
|
private rpcService: RPCService,
|
||||||
|
private rpcSubscribeService: RPCSubscribeService,
|
||||||
|
@Inject(_FEATURE_SUBSCRIBERS) featureSubscribersGroups: any[][],
|
||||||
|
private root: RPCRootModule,
|
||||||
|
) {
|
||||||
|
featureSubscribersGroups.forEach((featureSubscribers) => {
|
||||||
|
featureSubscribers.forEach((subscriber) => {
|
||||||
|
rpcSubscribeService.addSubscriber(subscriber);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({})
|
||||||
|
export class RPCModule {
|
||||||
|
static forRoot(config: RPCRootModuleConfig): ModuleWithProviders {
|
||||||
|
const subscribers = undefined === config.subscribers ? [] : config.subscribers;
|
||||||
|
return {
|
||||||
|
ngModule: RPCRootModule,
|
||||||
|
providers: [
|
||||||
|
subscribers,
|
||||||
|
{
|
||||||
|
provide: _ROOT_SUBSCRIBERS,
|
||||||
|
deps: subscribers,
|
||||||
|
useFactory: createSourceInstances,
|
||||||
|
},
|
||||||
|
SERVICES,
|
||||||
|
SUBSCRIBERS,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static forFeature(config: RPCFeatureModuleConfig): ModuleWithProviders {
|
||||||
|
const subscribers = undefined === config.subscribers ? [] : config.subscribers;
|
||||||
|
|
||||||
|
return {
|
||||||
|
ngModule: RPCFeatureModule,
|
||||||
|
providers: [
|
||||||
|
subscribers,
|
||||||
|
{
|
||||||
|
provide: _FEATURE_SUBSCRIBERS,
|
||||||
|
multi: true,
|
||||||
|
deps: subscribers,
|
||||||
|
useFactory: createSourceInstances,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createSourceInstances(...instances: any[]) {
|
||||||
|
return instances;
|
||||||
|
}
|
20
src/@loafer/ng-rpc/protocol/RPCClientCodec.ts
Normal file
20
src/@loafer/ng-rpc/protocol/RPCClientCodec.ts
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import { RPCError } from './RPCError';
|
||||||
|
|
||||||
|
export interface RPCClientCodec {
|
||||||
|
request(method: string, args: any[], id: number): any;
|
||||||
|
response(res: any): RPCClientResponseCodec;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RPCClientResponseCodec {
|
||||||
|
id(): number | undefined;
|
||||||
|
error(): RPCError | undefined;
|
||||||
|
result(): any | undefined;
|
||||||
|
|
||||||
|
isNotification(): boolean;
|
||||||
|
notification(): RPCClientNotificationCodec | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RPCClientNotificationCodec {
|
||||||
|
method(): string;
|
||||||
|
params(): any | undefined;
|
||||||
|
}
|
41
src/@loafer/ng-rpc/protocol/RPCError.ts
Normal file
41
src/@loafer/ng-rpc/protocol/RPCError.ts
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
export interface RPCClientError {
|
||||||
|
request: {
|
||||||
|
method: string,
|
||||||
|
params: any[],
|
||||||
|
};
|
||||||
|
response: RPCError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error object representation when a method invocation fails.
|
||||||
|
*/
|
||||||
|
export interface RPCError {
|
||||||
|
/** Indicates the error type that occurred. */
|
||||||
|
code: RPCErrorCode;
|
||||||
|
|
||||||
|
/** A short description of the error. */
|
||||||
|
message: string;
|
||||||
|
|
||||||
|
/** Additional information about the error */
|
||||||
|
data?: any;
|
||||||
|
}/*
|
||||||
|
|
||||||
|
/** Error codes are same as xml-rpc codes. See http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php */
|
||||||
|
export const enum RPCErrorCode {
|
||||||
|
/** Parse error Invalid JSON was received by the Server. */
|
||||||
|
ParseError = -32700,
|
||||||
|
|
||||||
|
/** Invalid Request The JSON sent is not a valid Request object. */
|
||||||
|
InvalidRequest = -32600,
|
||||||
|
|
||||||
|
/** The method does not exist / is not available. */
|
||||||
|
MethodNotFound = -32601,
|
||||||
|
|
||||||
|
/** Invalid method parameter(s). */
|
||||||
|
InvalidParams = - -32602,
|
||||||
|
|
||||||
|
/** Internal JSON-RPC error. */
|
||||||
|
InternalError = -32603
|
||||||
|
|
||||||
|
/** -32000 to -32099: Reserved for implementation-defined Server errors. */
|
||||||
|
}
|
2
src/@loafer/ng-rpc/protocol/index.ts
Normal file
2
src/@loafer/ng-rpc/protocol/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './RPCClientCodec';
|
||||||
|
export * from './RPCError';
|
125
src/@loafer/ng-rpc/protocol/json/JSONRPCClientCodec.ts
Normal file
125
src/@loafer/ng-rpc/protocol/json/JSONRPCClientCodec.ts
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
import {
|
||||||
|
RPCClientCodec,
|
||||||
|
RPCClientResponseCodec,
|
||||||
|
RPCClientNotificationCodec,
|
||||||
|
} from '../RPCClientCodec';
|
||||||
|
|
||||||
|
import {
|
||||||
|
RPCError,
|
||||||
|
} from '../RPCError';
|
||||||
|
|
||||||
|
export interface ClientNotification {
|
||||||
|
method: string;
|
||||||
|
params?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ClientRequest {
|
||||||
|
jsonrpc: string;
|
||||||
|
method: string;
|
||||||
|
params?: string[];
|
||||||
|
id?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ClientResponse {
|
||||||
|
jsonrpc: string;
|
||||||
|
result?: any;
|
||||||
|
error?: RPCError;
|
||||||
|
id?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class JSONRPCClientCodec implements RPCClientCodec {
|
||||||
|
public request(method: string, args: any[], id?: number): any {
|
||||||
|
const params = convertParamsToStringArray(args);
|
||||||
|
|
||||||
|
const req: ClientRequest = {
|
||||||
|
jsonrpc: '2.0',
|
||||||
|
method: method,
|
||||||
|
params: 0 === params.length ? null : params,
|
||||||
|
id: id,
|
||||||
|
};
|
||||||
|
return JSON.stringify(req);
|
||||||
|
}
|
||||||
|
public response(res: any): RPCClientResponseCodec {
|
||||||
|
const _res: ClientResponse = {
|
||||||
|
id: res.id,
|
||||||
|
jsonrpc: res.jsonrpc,
|
||||||
|
result: res.result,
|
||||||
|
error: res.error,
|
||||||
|
};
|
||||||
|
return new JSONRPCClientResponseCodec(_res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertParamsToStringArray(args: any[]): string[] | undefined {
|
||||||
|
const values: string[] = [];
|
||||||
|
|
||||||
|
if (undefined === args || null === args || 0 === args.length) {
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let indexI = 0; indexI < args.length; indexI++) {
|
||||||
|
const arg = args[indexI];
|
||||||
|
|
||||||
|
switch (typeof arg) {
|
||||||
|
case 'boolean':
|
||||||
|
case 'number': // enum
|
||||||
|
values.push(String(arg));
|
||||||
|
break;
|
||||||
|
case 'string':
|
||||||
|
values.push(arg);
|
||||||
|
break;
|
||||||
|
case 'object': // array, map
|
||||||
|
values.push(JSON.stringify(arg));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`Not supported type[${typeof arg}]`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class JSONRPCClientResponseCodec implements RPCClientResponseCodec {
|
||||||
|
public constructor(private _res: ClientResponse) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public id(): number | undefined {
|
||||||
|
return this._res.id;
|
||||||
|
}
|
||||||
|
public error(): RPCError | undefined {
|
||||||
|
return this._res.error;
|
||||||
|
}
|
||||||
|
public result(): any | undefined {
|
||||||
|
return this._res.result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public isNotification(): boolean {
|
||||||
|
if (undefined !== this.id() || undefined === this.result()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public notification(): RPCClientNotificationCodec | undefined {
|
||||||
|
if (undefined !== this.id() || undefined === this.result()) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const _noti: ClientNotification = {
|
||||||
|
method: this._res.result.method,
|
||||||
|
params: this._res.result.params,
|
||||||
|
};
|
||||||
|
return new JSONRPCClientNotificationCodec(_noti);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class JSONRPCClientNotificationCodec implements RPCClientNotificationCodec {
|
||||||
|
public constructor(private _noti: ClientNotification) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public method(): string {
|
||||||
|
return this._noti.method;
|
||||||
|
}
|
||||||
|
public params(): any[] | undefined {
|
||||||
|
return this._noti.params;
|
||||||
|
}
|
||||||
|
}
|
1
src/@loafer/ng-rpc/protocol/json/index.ts
Normal file
1
src/@loafer/ng-rpc/protocol/json/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export * from './JSONRPCClientCodec';
|
7
src/@loafer/ng-rpc/service/index.ts
Normal file
7
src/@loafer/ng-rpc/service/index.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export * from './rpc.service';
|
||||||
|
|
||||||
|
import { RPCService } from './rpc.service';
|
||||||
|
|
||||||
|
export const SERVICES = [
|
||||||
|
RPCService,
|
||||||
|
];
|
39
src/@loafer/ng-rpc/service/rpc.service.ts
Normal file
39
src/@loafer/ng-rpc/service/rpc.service.ts
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import { Injectable, Inject } from '@angular/core';
|
||||||
|
import { Store, select } from '@ngrx/store';
|
||||||
|
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
import { Subject } from 'rxjs/Subject';
|
||||||
|
|
||||||
|
import {
|
||||||
|
RPC_CODEC,
|
||||||
|
RPC_RWC,
|
||||||
|
} from '../core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
RPCClient,
|
||||||
|
RPCClientRWC,
|
||||||
|
} from '../client';
|
||||||
|
|
||||||
|
import {
|
||||||
|
RPCClientCodec,
|
||||||
|
RPCClientNotificationCodec,
|
||||||
|
} from '../protocol';
|
||||||
|
|
||||||
|
import {
|
||||||
|
RPCSubscribeService,
|
||||||
|
} from '../subscribe';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class RPCService extends RPCClient {
|
||||||
|
constructor(
|
||||||
|
@Inject(RPC_CODEC) rpcClientCodec: RPCClientCodec,
|
||||||
|
@Inject(RPC_RWC) rpcClientRWC: RPCClientRWC,
|
||||||
|
private rpcSubscribeService: RPCSubscribeService,
|
||||||
|
) {
|
||||||
|
super(rpcClientCodec, rpcClientRWC);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onNotification(notiCodec: RPCClientNotificationCodec): void {
|
||||||
|
this.rpcSubscribeService.notify(notiCodec);
|
||||||
|
}
|
||||||
|
}
|
7
src/@loafer/ng-rpc/subscribe/index.ts
Normal file
7
src/@loafer/ng-rpc/subscribe/index.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export * from './subscribe.service';
|
||||||
|
|
||||||
|
import { RPCSubscribeService } from './subscribe.service';
|
||||||
|
|
||||||
|
export const SUBSCRIBERS = [
|
||||||
|
RPCSubscribeService,
|
||||||
|
];
|
161
src/@loafer/ng-rpc/subscribe/subscribe.service.ts
Normal file
161
src/@loafer/ng-rpc/subscribe/subscribe.service.ts
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
import { Injectable, Inject } from '@angular/core';
|
||||||
|
import { Store, select } from '@ngrx/store';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Type,
|
||||||
|
PropertyKeyType,
|
||||||
|
} from '@loafer/core';
|
||||||
|
|
||||||
|
import { TypeUtil } from '@loafer/core/util/TypeUtil';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Class,
|
||||||
|
Method,
|
||||||
|
Metadata,
|
||||||
|
} from '@loafer/core/reflect';
|
||||||
|
|
||||||
|
import { RPCSubscriberDecorator } from '../decorator';
|
||||||
|
|
||||||
|
import {
|
||||||
|
RPCClientNotificationCodec,
|
||||||
|
} from '../protocol';
|
||||||
|
import { SubscriberParameterError } from '../core';
|
||||||
|
|
||||||
|
export interface SubscriberMethod {
|
||||||
|
className: PropertyKeyType;
|
||||||
|
methodName: PropertyKeyType;
|
||||||
|
parameterTypes: string[];
|
||||||
|
|
||||||
|
method: Method;
|
||||||
|
instance: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class RPCSubscribeService {
|
||||||
|
private subscriberTypes: Set<Type<any>>;
|
||||||
|
private subscribers: Set<any>;
|
||||||
|
private subscriberMethodMap: Map<string, SubscriberMethod[]>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
) {
|
||||||
|
this.subscriberTypes = new Set();
|
||||||
|
this.subscribers = new Set();
|
||||||
|
this.subscriberMethodMap = new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
public addSubscriber(subscriber: Type<any>): void {
|
||||||
|
const type = TypeUtil.getType(subscriber);
|
||||||
|
|
||||||
|
if (this.subscriberTypes.has(type)) {
|
||||||
|
console.log(`Subscriber[${type.name}] has been added`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.subscriberTypes.add(type);
|
||||||
|
|
||||||
|
const clazz = Class.forType(type);
|
||||||
|
if (undefined === clazz) {
|
||||||
|
console.log(`Type[${subscriber.name}] is not decorated type`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const methods = clazz.getMethods();
|
||||||
|
methods.forEach((method, propertyKey) => {
|
||||||
|
const annon = method.getAnnotation(RPCSubscriberDecorator);
|
||||||
|
if (undefined === annon) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const subscriberMethodName = annon.attribute.method;
|
||||||
|
let subscriberMethods: SubscriberMethod[] = this.subscriberMethodMap.get(subscriberMethodName);
|
||||||
|
if (undefined === subscriberMethods) {
|
||||||
|
subscriberMethods = [];
|
||||||
|
this.subscriberMethodMap.set(subscriberMethodName, subscriberMethods);
|
||||||
|
}
|
||||||
|
|
||||||
|
const paramTypes = this.getParamTypes(method);
|
||||||
|
|
||||||
|
const subscriberMethod: SubscriberMethod = {
|
||||||
|
className: clazz.getName(),
|
||||||
|
methodName: method.getName(),
|
||||||
|
parameterTypes: paramTypes,
|
||||||
|
method: method,
|
||||||
|
instance: subscriber,
|
||||||
|
};
|
||||||
|
|
||||||
|
subscriberMethods.push(subscriberMethod);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public notify(codec: RPCClientNotificationCodec): void {
|
||||||
|
const method = codec.method();
|
||||||
|
const params = codec.params();
|
||||||
|
|
||||||
|
const subscriberMethods: SubscriberMethod[] = this.subscriberMethodMap.get(method);
|
||||||
|
if (undefined === subscriberMethods) {
|
||||||
|
console.warn(`Subscriber for method[${method}] is not exist`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
subscriberMethods.forEach((subscriberMethod) => {
|
||||||
|
try {
|
||||||
|
const args = this.converParams(params, subscriberMethod.parameterTypes);
|
||||||
|
subscriberMethod.method.invoke(subscriberMethod.instance, ...args);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private getParamTypes(method: Method): string[] {
|
||||||
|
if (undefined === method || null === method || 0 === method.getParameterCount()) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const parameters = method.getParameters();
|
||||||
|
const results: string[] = [];
|
||||||
|
for (let indexI = 0; indexI < parameters.length; indexI++) {
|
||||||
|
const paramType = parameters[indexI].getType();
|
||||||
|
results.push(paramType.name);
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
private converParams(params: string[], paramTypes: string[]): any[] {
|
||||||
|
const results: any[] = [];
|
||||||
|
|
||||||
|
if (undefined === params || null === params || 0 === params.length) {
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
if (undefined === paramTypes || null === paramTypes || 0 === paramTypes.length) {
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
if (params.length !== paramTypes.length) {
|
||||||
|
throw new SubscriberParameterError(`Count is not same from server[${params.length}] and method[${paramTypes.length}]`);
|
||||||
|
}
|
||||||
|
for (let indexI = 0; indexI < params.length; indexI++) {
|
||||||
|
const param = params[indexI];
|
||||||
|
const type = paramTypes[indexI];
|
||||||
|
switch (type) {
|
||||||
|
case 'Object':
|
||||||
|
case 'Array':
|
||||||
|
case 'Map':
|
||||||
|
results.push(JSON.parse(param));
|
||||||
|
break;
|
||||||
|
case 'String':
|
||||||
|
results.push(param);
|
||||||
|
break;
|
||||||
|
case 'Number':
|
||||||
|
results.push(Number(param));
|
||||||
|
break;
|
||||||
|
case 'Boolean':
|
||||||
|
results.push(Boolean(param));
|
||||||
|
break;
|
||||||
|
case 'Function':
|
||||||
|
throw new SubscriberParameterError(`Function type [${indexI}] is not allowed`);
|
||||||
|
default:
|
||||||
|
throw new SubscriberParameterError(`${type} type parameter[${indexI}] is not allowed`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
}
|
57
src/app/app-l10n.module.ts
Normal file
57
src/app/app-l10n.module.ts
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
import { NgModule, APP_INITIALIZER } from '@angular/core';
|
||||||
|
import { BrowserModule, Title } from '@angular/platform-browser';
|
||||||
|
|
||||||
|
import {
|
||||||
|
L10nConfig,
|
||||||
|
L10nLoader,
|
||||||
|
LocalizationModule,
|
||||||
|
LocaleValidationModule,
|
||||||
|
StorageStrategy,
|
||||||
|
ProviderType
|
||||||
|
} from 'angular-l10n';
|
||||||
|
|
||||||
|
const l10nConfig: L10nConfig = {
|
||||||
|
locale: {
|
||||||
|
languages: [
|
||||||
|
{ code: 'en', dir: 'ltr' },
|
||||||
|
{ code: 'kr', dir: 'ltr' },
|
||||||
|
],
|
||||||
|
defaultLocale: { languageCode: 'en', countryCode: 'US' },
|
||||||
|
storage: StorageStrategy.Cookie,
|
||||||
|
cookieExpiration: 30
|
||||||
|
},
|
||||||
|
translation: {
|
||||||
|
providers: [
|
||||||
|
{ type: ProviderType.Static, prefix: './assets/translations/of-' }
|
||||||
|
],
|
||||||
|
caching: true,
|
||||||
|
composedKeySeparator: '.',
|
||||||
|
missingValue: 'No key',
|
||||||
|
i18nPlural: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export function initL10n(l10nLoader: L10nLoader): Function {
|
||||||
|
return () => l10nLoader.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
LocalizationModule.forRoot(l10nConfig),
|
||||||
|
LocaleValidationModule.forRoot()
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
LocalizationModule,
|
||||||
|
LocaleValidationModule,
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
Title,
|
||||||
|
{
|
||||||
|
provide: APP_INITIALIZER,
|
||||||
|
useFactory: initL10n,
|
||||||
|
deps: [L10nLoader],
|
||||||
|
multi: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class AppL10NModule { }
|
22
src/app/app-logger.module.ts
Normal file
22
src/app/app-logger.module.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { LoggerModule } from '@loafer/ng-logger';
|
||||||
|
import { LoggerLevel } from '@loafer/ng-logger/core';
|
||||||
|
|
||||||
|
import { environment } from '../environments/environment';
|
||||||
|
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
exports: [
|
||||||
|
LoggerModule,
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
LoggerModule.forRoot({
|
||||||
|
config: {
|
||||||
|
level: LoggerLevel.DEBUG,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class AppLoggerModule { }
|
17
src/app/app-rest.module.ts
Normal file
17
src/app/app-rest.module.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { RESTModule } from '@loafer/ng-rest';
|
||||||
|
|
||||||
|
import { environment } from '../environments/environment';
|
||||||
|
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
exports: [
|
||||||
|
RESTModule,
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
RESTModule.forRoot({baseURL: environment.restBaseURL}),
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class AppRESTModule { }
|
25
src/app/app-routing.module.ts
Normal file
25
src/app/app-routing.module.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
// import { NgModule } from '@angular/core';
|
||||||
|
// import { Routes, RouterModule, PreloadAllModules } from '@angular/router';
|
||||||
|
// import { AuthGuard } from './commons/guard/auth.guard';
|
||||||
|
|
||||||
|
// const routes: Routes = [
|
||||||
|
// { path: '', loadChildren: './pages/pages.module#PagesModule' },
|
||||||
|
// { path: 'auth', loadChildren: './pages/auth/auth-page.module#AuthPageModule' },
|
||||||
|
// { path: 'errors', loadChildren: './pages/errors/errors-page.module#ErrorsPageModule' },
|
||||||
|
// ];
|
||||||
|
|
||||||
|
// @NgModule({
|
||||||
|
// imports: [RouterModule.forRoot(routes, {useHash: true, preloadingStrategy: PreloadAllModules})],
|
||||||
|
// exports: [RouterModule],
|
||||||
|
// })
|
||||||
|
// export class AppRoutingModule { }
|
||||||
|
|
||||||
|
import { Routes, RouterModule } from '@angular/router';
|
||||||
|
import { ModuleWithProviders } from '@angular/core';
|
||||||
|
|
||||||
|
export const routes: Routes = [
|
||||||
|
{ path: '', loadChildren: './pages/pages.module#PagesModule' },
|
||||||
|
{ path: 'auth', loadChildren: './pages/auth/auth-page.module#AuthPageModule' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const AppRoutingModule: ModuleWithProviders = RouterModule.forRoot(routes);
|
36
src/app/app-rpc.module.ts
Normal file
36
src/app/app-rpc.module.ts
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { RPCModule } from '@loafer/ng-rpc';
|
||||||
|
|
||||||
|
import {
|
||||||
|
RPC_CODEC,
|
||||||
|
RPC_RWC,
|
||||||
|
} from '@loafer/ng-rpc/core';
|
||||||
|
import { JSONRPCClientCodec } from '@loafer/ng-rpc/protocol/json';
|
||||||
|
import {
|
||||||
|
RPCClientWebsocketRWC,
|
||||||
|
RxWebsocketSubjectConfig,
|
||||||
|
} from '@loafer/ng-rpc/client/rwc/websocket';
|
||||||
|
|
||||||
|
import { environment } from '../environments/environment';
|
||||||
|
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
exports: [
|
||||||
|
RPCModule,
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
RPCModule.forRoot({}),
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{provide: 'WEBAPP_RPC_CONFIG', useValue: environment.webappRPCConfig},
|
||||||
|
|
||||||
|
{provide: RPC_CODEC, useFactory: () => new JSONRPCClientCodec()},
|
||||||
|
{
|
||||||
|
provide: RPC_RWC,
|
||||||
|
useFactory: (config: RxWebsocketSubjectConfig) => new RPCClientWebsocketRWC(config),
|
||||||
|
deps: ['WEBAPP_RPC_CONFIG']
|
||||||
|
},
|
||||||
|
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class AppRPCModule { }
|
61
src/app/app-store.module.ts
Normal file
61
src/app/app-store.module.ts
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { StoreModule } from '@ngrx/store';
|
||||||
|
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
|
||||||
|
import {
|
||||||
|
StoreRouterConnectingModule,
|
||||||
|
RouterStateSerializer,
|
||||||
|
RouterReducerState,
|
||||||
|
} from '@ngrx/router-store';
|
||||||
|
import { EffectsModule } from '@ngrx/effects';
|
||||||
|
import { combineReducers, ActionReducer, ActionReducerMap, MetaReducer } from '@ngrx/store';
|
||||||
|
|
||||||
|
import { SimpleRouterStateSerializer } from './commons/util/ngrx/router-store/serializer/simple-router-state-serializer';
|
||||||
|
|
||||||
|
import { environment } from '../environments/environment';
|
||||||
|
import * as AppStore from './commons/store';
|
||||||
|
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
exports: [
|
||||||
|
StoreModule,
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
StoreModule.forRoot(AppStore.REDUCERS),
|
||||||
|
/**
|
||||||
|
* @ngrx/router-store keeps router state up-to-date in the store.
|
||||||
|
*/
|
||||||
|
StoreRouterConnectingModule.forRoot({
|
||||||
|
/*
|
||||||
|
They stateKey defines the name of the state used by the router-store reducer.
|
||||||
|
This matches the key defined in the map of reducers
|
||||||
|
*/
|
||||||
|
stateKey: 'router',
|
||||||
|
}),
|
||||||
|
/**
|
||||||
|
* Store devtools instrument the store retaining past versions of state
|
||||||
|
* and recalculating new states. This enables powerful time-travel
|
||||||
|
* debugging.
|
||||||
|
*
|
||||||
|
* To use the debugger, install the Redux Devtools extension for either
|
||||||
|
* Chrome or Firefox
|
||||||
|
*
|
||||||
|
* See: https://github.com/zalmoxisus/redux-devtools-extension
|
||||||
|
*/
|
||||||
|
StoreDevtoolsModule.instrument({
|
||||||
|
name: 'overFlow WebApp DevTools',
|
||||||
|
maxAge: 50,
|
||||||
|
logOnly: environment.production,
|
||||||
|
}),
|
||||||
|
EffectsModule.forRoot(AppStore.EFFECTS),
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
/**
|
||||||
|
* The `RouterStateSnapshot` provided by the `Router` is a large complex structure.
|
||||||
|
* A custom RouterStateSerializer is used to parse the `RouterStateSnapshot` provided
|
||||||
|
* by `@ngrx/router-store` to include only the desired pieces of the snapshot.
|
||||||
|
*/
|
||||||
|
{ provide: RouterStateSerializer, useClass: SimpleRouterStateSerializer },
|
||||||
|
],
|
||||||
|
|
||||||
|
})
|
||||||
|
export class AppStoreModule { }
|
36
src/app/app.component.html
Normal file
36
src/app/app.component.html
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
<div class="layout-wrapper" [ngClass]="{'layout-compact':layoutCompact}" (click)="onLayoutClick()">
|
||||||
|
|
||||||
|
<div #layoutContainer class="layout-container"
|
||||||
|
[ngClass]="{'menu-layout-static': !isOverlay(),
|
||||||
|
'menu-layout-overlay': isOverlay(),
|
||||||
|
'layout-menu-overlay-active': overlayMenuActive,
|
||||||
|
'menu-layout-horizontal': isHorizontal(),
|
||||||
|
'menu-layout-slim': isSlim(),
|
||||||
|
'layout-menu-static-inactive': staticMenuDesktopInactive,
|
||||||
|
'layout-menu-static-active': staticMenuMobileActive}">
|
||||||
|
|
||||||
|
<of-topbar></of-topbar>
|
||||||
|
|
||||||
|
<div class="layout-menu" [ngClass]="{'layout-menu-dark':darkMenu}" (click)="onMenuClick($event)">
|
||||||
|
<p-scrollPanel #scrollPanel [style]="{height: '100%'}">
|
||||||
|
<of-inline-profile *ngIf="profileMode=='inline'&&!isHorizontal()"></of-inline-profile>
|
||||||
|
<of-menu [reset]="resetMenu"></of-menu>
|
||||||
|
</p-scrollPanel>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="layout-main">
|
||||||
|
<of-breadcrumb></of-breadcrumb>
|
||||||
|
|
||||||
|
<div class="layout-content">
|
||||||
|
<router-outlet></router-outlet>
|
||||||
|
|
||||||
|
<of-footer></of-footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<of-rightpanel></of-rightpanel>
|
||||||
|
|
||||||
|
<div class="layout-mask"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
0
src/app/app.component.scss
Normal file
0
src/app/app.component.scss
Normal file
39
src/app/app.component.spec.ts
Normal file
39
src/app/app.component.spec.ts
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
/* tslint:disable:no-unused-variable */
|
||||||
|
|
||||||
|
import { TestBed, async } from '@angular/core/testing';
|
||||||
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
|
import { AppComponent } from './app.component';
|
||||||
|
import { AppTopbarComponent } from './app.topbar.component';
|
||||||
|
import { AppRightpanelComponent} from './app.rightpanel.component';
|
||||||
|
import { AppInlineProfileComponent } from './app.profile.component';
|
||||||
|
import { AppFooterComponent } from './app.footer.component';
|
||||||
|
import { AppBreadcrumbComponent } from './app.breadcrumb.component';
|
||||||
|
import { AppMenuComponent, AppSubMenuComponent } from './app.menu.component';
|
||||||
|
import { BreadcrumbService } from './breadcrumb.service';
|
||||||
|
import {ScrollPanel} from 'primeng/primeng';
|
||||||
|
|
||||||
|
describe('AppComponent', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [ RouterTestingModule ],
|
||||||
|
declarations: [ AppComponent,
|
||||||
|
AppTopbarComponent,
|
||||||
|
AppMenuComponent,
|
||||||
|
AppSubMenuComponent,
|
||||||
|
AppFooterComponent,
|
||||||
|
AppBreadcrumbComponent,
|
||||||
|
AppInlineProfileComponent,
|
||||||
|
AppRightpanelComponent,
|
||||||
|
ScrollPanel
|
||||||
|
],
|
||||||
|
providers: [BreadcrumbService]
|
||||||
|
});
|
||||||
|
TestBed.compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create the app', async(() => {
|
||||||
|
const fixture = TestBed.createComponent(AppComponent);
|
||||||
|
const app = fixture.debugElement.componentInstance;
|
||||||
|
expect(app).toBeTruthy();
|
||||||
|
}));
|
||||||
|
});
|
313
src/app/app.component.ts
Normal file
313
src/app/app.component.ts
Normal file
|
@ -0,0 +1,313 @@
|
||||||
|
import { Component, AfterViewInit, ElementRef, Renderer, ViewChild, OnDestroy, OnInit, NgZone } from '@angular/core';
|
||||||
|
import { ScrollPanel } from 'primeng/primeng';
|
||||||
|
|
||||||
|
enum MenuOrientation {
|
||||||
|
STATIC,
|
||||||
|
OVERLAY,
|
||||||
|
SLIM,
|
||||||
|
HORIZONTAL
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'of-root',
|
||||||
|
templateUrl: './app.component.html',
|
||||||
|
styleUrls: ['./app.component.scss']
|
||||||
|
})
|
||||||
|
export class AppComponent implements AfterViewInit, OnDestroy, OnInit {
|
||||||
|
|
||||||
|
layoutCompact = true;
|
||||||
|
|
||||||
|
layoutMode: MenuOrientation = MenuOrientation.STATIC;
|
||||||
|
|
||||||
|
darkMenu = false;
|
||||||
|
|
||||||
|
profileMode = 'inline';
|
||||||
|
|
||||||
|
rotateMenuButton: boolean;
|
||||||
|
|
||||||
|
topbarMenuActive: boolean;
|
||||||
|
|
||||||
|
overlayMenuActive: boolean;
|
||||||
|
|
||||||
|
staticMenuDesktopInactive: boolean;
|
||||||
|
|
||||||
|
staticMenuMobileActive: boolean;
|
||||||
|
|
||||||
|
rightPanelActive: boolean;
|
||||||
|
|
||||||
|
rightPanelClick: boolean;
|
||||||
|
|
||||||
|
layoutContainer: HTMLDivElement;
|
||||||
|
|
||||||
|
layoutMenuScroller: HTMLDivElement;
|
||||||
|
|
||||||
|
menuClick: boolean;
|
||||||
|
|
||||||
|
topbarItemClick: boolean;
|
||||||
|
|
||||||
|
activeTopbarItem: any;
|
||||||
|
|
||||||
|
resetMenu: boolean;
|
||||||
|
|
||||||
|
menuHoverActive: boolean;
|
||||||
|
|
||||||
|
@ViewChild('layoutContainer') layourContainerViewChild: ElementRef;
|
||||||
|
|
||||||
|
@ViewChild('scrollPanel') layoutMenuScrollerViewChild: ScrollPanel;
|
||||||
|
|
||||||
|
rippleInitListener: any;
|
||||||
|
|
||||||
|
rippleMouseDownListener: any;
|
||||||
|
|
||||||
|
constructor(public renderer: Renderer, public zone: NgZone) { }
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.zone.runOutsideAngular(() => { this.bindRipple(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
bindRipple() {
|
||||||
|
this.rippleInitListener = this.init.bind(this);
|
||||||
|
document.addEventListener('DOMContentLoaded', this.rippleInitListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.rippleMouseDownListener = this.rippleMouseDown.bind(this);
|
||||||
|
document.addEventListener('mousedown', this.rippleMouseDownListener, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
rippleMouseDown(e) {
|
||||||
|
for (let target = e.target; target && target !== this; target = target['parentNode']) {
|
||||||
|
if (!this.isVisible(target)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Element.matches() -> https://developer.mozilla.org/en-US/docs/Web/API/Element/matches
|
||||||
|
if (this.selectorMatches(target, '.ripplelink, .ui-button')) {
|
||||||
|
const element = target;
|
||||||
|
this.rippleEffect(element, e);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
selectorMatches(el, selector) {
|
||||||
|
const p = Element.prototype;
|
||||||
|
const f = p['matches'] || p['webkitMatchesSelector'] || p['mozMatchesSelector'] || p['msMatchesSelector'] || function (s) {
|
||||||
|
return [].indexOf.call(document.querySelectorAll(s), this) !== -1;
|
||||||
|
};
|
||||||
|
return f.call(el, selector);
|
||||||
|
}
|
||||||
|
|
||||||
|
isVisible(el) {
|
||||||
|
return !!(el.offsetWidth || el.offsetHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
rippleEffect(element, e) {
|
||||||
|
if (element.querySelector('.ink') === null) {
|
||||||
|
const inkEl = document.createElement('span');
|
||||||
|
this.addClass(inkEl, 'ink');
|
||||||
|
|
||||||
|
if (this.hasClass(element, 'ripplelink')) {
|
||||||
|
element.querySelector('span').insertAdjacentHTML('afterend', '<span class=\'ink\'></span>');
|
||||||
|
} else {
|
||||||
|
element.appendChild(inkEl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ink = element.querySelector('.ink');
|
||||||
|
this.removeClass(ink, 'ripple-animate');
|
||||||
|
|
||||||
|
if (!ink.offsetHeight && !ink.offsetWidth) {
|
||||||
|
const d = Math.max(element.offsetWidth, element.offsetHeight);
|
||||||
|
ink.style.height = d + 'px';
|
||||||
|
ink.style.width = d + 'px';
|
||||||
|
}
|
||||||
|
|
||||||
|
const x = e.pageX - this.getOffset(element).left - (ink.offsetWidth / 2);
|
||||||
|
const y = e.pageY - this.getOffset(element).top - (ink.offsetHeight / 2);
|
||||||
|
|
||||||
|
ink.style.top = y + 'px';
|
||||||
|
ink.style.left = x + 'px';
|
||||||
|
ink.style.pointerEvents = 'none';
|
||||||
|
this.addClass(ink, 'ripple-animate');
|
||||||
|
}
|
||||||
|
hasClass(element, className) {
|
||||||
|
if (element.classList) {
|
||||||
|
return element.classList.contains(className);
|
||||||
|
} else {
|
||||||
|
return new RegExp('(^| )' + className + '( |$)', 'gi').test(element.className);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addClass(element, className) {
|
||||||
|
if (element.classList) {
|
||||||
|
element.classList.add(className);
|
||||||
|
} else {
|
||||||
|
element.className += ' ' + className;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
removeClass(element, className) {
|
||||||
|
if (element.classList) {
|
||||||
|
element.classList.remove(className);
|
||||||
|
} else {
|
||||||
|
element.className = element.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getOffset(el) {
|
||||||
|
const rect = el.getBoundingClientRect();
|
||||||
|
|
||||||
|
return {
|
||||||
|
top: rect.top + (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0),
|
||||||
|
left: rect.left + (window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
unbindRipple() {
|
||||||
|
if (this.rippleInitListener) {
|
||||||
|
document.removeEventListener('DOMContentLoaded', this.rippleInitListener);
|
||||||
|
}
|
||||||
|
if (this.rippleMouseDownListener) {
|
||||||
|
document.removeEventListener('mousedown', this.rippleMouseDownListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngAfterViewInit() {
|
||||||
|
this.layoutContainer = <HTMLDivElement>this.layourContainerViewChild.nativeElement;
|
||||||
|
setTimeout(() => { this.layoutMenuScrollerViewChild.moveBar(); }, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
onLayoutClick() {
|
||||||
|
if (!this.topbarItemClick) {
|
||||||
|
this.activeTopbarItem = null;
|
||||||
|
this.topbarMenuActive = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.menuClick) {
|
||||||
|
if (this.isHorizontal() || this.isSlim()) {
|
||||||
|
this.resetMenu = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.overlayMenuActive || this.staticMenuMobileActive) {
|
||||||
|
this.hideOverlayMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.menuHoverActive = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.rightPanelClick) {
|
||||||
|
this.rightPanelActive = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.topbarItemClick = false;
|
||||||
|
this.menuClick = false;
|
||||||
|
this.rightPanelClick = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
onMenuButtonClick(event) {
|
||||||
|
this.menuClick = true;
|
||||||
|
this.rotateMenuButton = !this.rotateMenuButton;
|
||||||
|
this.topbarMenuActive = false;
|
||||||
|
|
||||||
|
if (this.layoutMode === MenuOrientation.OVERLAY) {
|
||||||
|
this.overlayMenuActive = !this.overlayMenuActive;
|
||||||
|
} else {
|
||||||
|
if (this.isDesktop()) {
|
||||||
|
this.staticMenuDesktopInactive = !this.staticMenuDesktopInactive;
|
||||||
|
} else {
|
||||||
|
this.staticMenuMobileActive = !this.staticMenuMobileActive;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
onMenuClick($event) {
|
||||||
|
this.menuClick = true;
|
||||||
|
this.resetMenu = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
onTopbarMenuButtonClick(event) {
|
||||||
|
this.topbarItemClick = true;
|
||||||
|
this.topbarMenuActive = !this.topbarMenuActive;
|
||||||
|
|
||||||
|
this.hideOverlayMenu();
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
onTopbarItemClick(event, item) {
|
||||||
|
this.topbarItemClick = true;
|
||||||
|
|
||||||
|
if (this.activeTopbarItem === item) {
|
||||||
|
this.activeTopbarItem = null;
|
||||||
|
} else {
|
||||||
|
this.activeTopbarItem = item;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
onRightPanelButtonClick(event) {
|
||||||
|
this.rightPanelClick = true;
|
||||||
|
this.rightPanelActive = !this.rightPanelActive;
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
onRightPanelClick() {
|
||||||
|
this.rightPanelClick = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
hideOverlayMenu() {
|
||||||
|
this.rotateMenuButton = false;
|
||||||
|
this.overlayMenuActive = false;
|
||||||
|
this.staticMenuMobileActive = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
isTablet() {
|
||||||
|
const width = window.innerWidth;
|
||||||
|
return width <= 1024 && width > 640;
|
||||||
|
}
|
||||||
|
|
||||||
|
isDesktop() {
|
||||||
|
return window.innerWidth > 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
isMobile() {
|
||||||
|
return window.innerWidth <= 640;
|
||||||
|
}
|
||||||
|
|
||||||
|
isOverlay() {
|
||||||
|
return this.layoutMode === MenuOrientation.OVERLAY;
|
||||||
|
}
|
||||||
|
|
||||||
|
isHorizontal() {
|
||||||
|
return this.layoutMode === MenuOrientation.HORIZONTAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
isSlim() {
|
||||||
|
return this.layoutMode === MenuOrientation.SLIM;
|
||||||
|
}
|
||||||
|
|
||||||
|
changeToStaticMenu() {
|
||||||
|
this.layoutMode = MenuOrientation.STATIC;
|
||||||
|
}
|
||||||
|
|
||||||
|
changeToOverlayMenu() {
|
||||||
|
this.layoutMode = MenuOrientation.OVERLAY;
|
||||||
|
}
|
||||||
|
|
||||||
|
changeToHorizontalMenu() {
|
||||||
|
this.layoutMode = MenuOrientation.HORIZONTAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
changeToSlimMenu() {
|
||||||
|
this.layoutMode = MenuOrientation.SLIM;
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.unbindRipple();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
56
src/app/app.module.ts
Normal file
56
src/app/app.module.ts
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
|
||||||
|
import { PrimeNGModules } from '../packages/commons/prime-ng/prime-ng.module';
|
||||||
|
import { HttpClientModule } from '@angular/common/http';
|
||||||
|
import { AppRoutingModule } from './app-routing.module';
|
||||||
|
import { AppStoreModule } from './app-store.module';
|
||||||
|
import { AppL10NModule } from './app-l10n.module';
|
||||||
|
import { AppRPCModule } from './app-rpc.module';
|
||||||
|
import { AppRESTModule } from './app-rest.module';
|
||||||
|
import { AppLoggerModule } from './app-logger.module';
|
||||||
|
|
||||||
|
import { AppComponent } from './app.component';
|
||||||
|
import { LocationStrategy, HashLocationStrategy } from '@angular/common';
|
||||||
|
|
||||||
|
import { CookieService } from 'ngx-cookie-service';
|
||||||
|
import { AuthGuard } from './commons/guard/auth.guard';
|
||||||
|
|
||||||
|
import { AppMenuComponent } from 'app/commons/component/layout/menu/app.menu.component';
|
||||||
|
import { AppSubMenuComponent } from 'app/commons/component/layout/menu/app.submenu.component';
|
||||||
|
import { AppTopbarComponent } from 'app/commons/component/layout/topbar/app.topbar.component';
|
||||||
|
import { AppFooterComponent } from 'app/commons/component/layout/footer/app.footer.component';
|
||||||
|
import { AppBreadcrumbComponent } from 'app/commons/component/layout/breadcrumb/app.breadcrumb.component';
|
||||||
|
import { AppRightpanelComponent } from 'app/commons/component/layout/right-panel/app.rightpanel.component';
|
||||||
|
import { AppInlineProfileComponent } from 'app/commons/component/layout/profile/app.profile.component';
|
||||||
|
import { BreadcrumbService } from 'app/commons/component/layout/breadcrumb/breadcrumb.service';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
PrimeNGModules,
|
||||||
|
HttpClientModule,
|
||||||
|
AppRoutingModule,
|
||||||
|
AppStoreModule,
|
||||||
|
AppL10NModule,
|
||||||
|
AppRPCModule,
|
||||||
|
AppRESTModule,
|
||||||
|
AppLoggerModule,
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
AppComponent,
|
||||||
|
AppMenuComponent,
|
||||||
|
AppSubMenuComponent,
|
||||||
|
AppTopbarComponent,
|
||||||
|
AppFooterComponent,
|
||||||
|
AppBreadcrumbComponent,
|
||||||
|
AppRightpanelComponent,
|
||||||
|
AppInlineProfileComponent,
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{ provide: LocationStrategy, useClass: HashLocationStrategy },
|
||||||
|
BreadcrumbService,
|
||||||
|
CookieService,
|
||||||
|
AuthGuard,
|
||||||
|
],
|
||||||
|
bootstrap: [AppComponent]
|
||||||
|
})
|
||||||
|
export class AppModule { }
|
|
@ -0,0 +1,29 @@
|
||||||
|
<div class="layout-breadcrumb">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a routerLink="">
|
||||||
|
<i class="material-icons">home</i>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>/</li>
|
||||||
|
<ng-template ngFor let-item let-last="last" [ngForOf]="items">
|
||||||
|
<li>
|
||||||
|
<a [routerLink]="item.routerLink" *ngIf="item.routerLink">{{item.label}}</a>
|
||||||
|
<ng-container *ngIf="!item.routerLink">{{item.label}}</ng-container>
|
||||||
|
</li>
|
||||||
|
<li *ngIf="!last">/</li>
|
||||||
|
</ng-template>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="layout-breadcrumb-options">
|
||||||
|
<a routerLink="/" title="Backup">
|
||||||
|
<i class="material-icons">backup</i>
|
||||||
|
</a>
|
||||||
|
<a routerLink="/" title="Bookmark">
|
||||||
|
<i class="material-icons">bookmark</i>
|
||||||
|
</a>
|
||||||
|
<a routerLink="/" title="Logout">
|
||||||
|
<i class="material-icons">power_settings_new</i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { Component, OnDestroy } from '@angular/core';
|
||||||
|
import { AppComponent } from 'app/app.component';
|
||||||
|
import { BreadcrumbService } from './breadcrumb.service';
|
||||||
|
import { Subscription } from 'rxjs/Subscription';
|
||||||
|
import { MenuItem } from 'primeng/primeng';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'of-breadcrumb',
|
||||||
|
templateUrl: './app.breadcrumb.component.html'
|
||||||
|
})
|
||||||
|
export class AppBreadcrumbComponent implements OnDestroy {
|
||||||
|
|
||||||
|
subscription: Subscription;
|
||||||
|
|
||||||
|
items: MenuItem[];
|
||||||
|
|
||||||
|
constructor(public breadcrumbService: BreadcrumbService) {
|
||||||
|
this.subscription = breadcrumbService.itemsHandler.subscribe(response => {
|
||||||
|
this.items = response;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
if (this.subscription) {
|
||||||
|
this.subscription.unsubscribe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Subject } from 'rxjs/Subject';
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
import { MenuItem } from 'primeng/primeng';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class BreadcrumbService {
|
||||||
|
|
||||||
|
private itemsSource = new Subject<MenuItem[]>();
|
||||||
|
|
||||||
|
itemsHandler = this.itemsSource.asObservable();
|
||||||
|
|
||||||
|
setItems(items: MenuItem[]) {
|
||||||
|
this.itemsSource.next(items);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
<div class="footer">
|
||||||
|
<div class="card clearfix">
|
||||||
|
<span class="footer-text-left">Loafle</span>
|
||||||
|
<span class="footer-text-right">
|
||||||
|
<span class="material-icons ui-icon-copyright"></span>
|
||||||
|
<span>All Rights Reserved</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,9 @@
|
||||||
|
import {Component} from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'of-footer',
|
||||||
|
templateUrl: './app.footer.component.html'
|
||||||
|
})
|
||||||
|
export class AppFooterComponent {
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
<ul of-submenu [item]="model" root="true" class="ultima-menu ultima-main-menu clearfix" [reset]="reset" visible="true"></ul>
|
48
src/app/commons/component/layout/menu/app.menu.component.ts
Normal file
48
src/app/commons/component/layout/menu/app.menu.component.ts
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
|
import { trigger, state, style, transition, animate } from '@angular/animations';
|
||||||
|
import { MenuItem } from 'primeng/primeng';
|
||||||
|
import { AppComponent } from 'app/app.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'of-menu',
|
||||||
|
templateUrl: './app.menu.component.html'
|
||||||
|
})
|
||||||
|
|
||||||
|
export class AppMenuComponent implements OnInit {
|
||||||
|
|
||||||
|
@Input() reset: boolean;
|
||||||
|
|
||||||
|
model: any[];
|
||||||
|
|
||||||
|
constructor(public app: AppComponent) { }
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.model = [
|
||||||
|
{ label: 'Home', icon: 'home', routerLink: ['/'] },
|
||||||
|
{
|
||||||
|
label: 'Infra', icon: 'all_inclusive', items: [
|
||||||
|
{ label: 'Map', icon: 'map', routerLink: ['/map'] },
|
||||||
|
{ label: 'Sensors', icon: 'compare_arrows', routerLink: ['/sensors'] },
|
||||||
|
{ label: 'Probes', icon: 'dock', routerLink: ['/probes'] },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Monitor', icon: 'remove_red_eye', items: [
|
||||||
|
{ label: 'Overview', icon: 'rate_review', routerLink: ['/overview'] },
|
||||||
|
{ label: 'Dashboards', icon: 'dashboard', routerLink: ['/dashboards'] },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ label: 'Alert', icon: 'warning', routerLink: ['/alert'] },
|
||||||
|
{ label: 'Report', icon: 'print', routerLink: ['/report'] },
|
||||||
|
{ label: 'Log', icon: 'history', routerLink: ['/log'] },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
changeTheme(theme) {
|
||||||
|
const themeLink: HTMLLinkElement = <HTMLLinkElement>document.getElementById('theme-css');
|
||||||
|
const layoutLink: HTMLLinkElement = <HTMLLinkElement>document.getElementById('layout-css');
|
||||||
|
|
||||||
|
themeLink.href = 'assets/theme/theme-' + theme + '.css';
|
||||||
|
layoutLink.href = 'assets/layout/css/layout-' + theme + '.css';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
<ng-template ngFor let-child let-i="index" [ngForOf]="(root ? item : item.items)">
|
||||||
|
<li [ngClass]="{'active-menuitem': isActive(i)}" [class]="child.badgeStyleClass" *ngIf="child.visible === false ? false : true">
|
||||||
|
<a [href]="child.url||'#'" (click)="itemClick($event,child,i)" (mouseenter)="onMouseEnter(i)"
|
||||||
|
class="ripplelink" *ngIf="!child.routerLink"
|
||||||
|
[attr.tabindex]="!visible ? '-1' : null" [attr.target]="child.target">
|
||||||
|
<i *ngIf="child.icon" class="material-icons">{{child.icon}}</i>
|
||||||
|
<span>{{child.label}}</span>
|
||||||
|
<span class="menuitem-badge" *ngIf="child.badge">{{child.badge}}</span>
|
||||||
|
<i class="material-icons submenu-icon" *ngIf="child.items">keyboard_arrow_down</i>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a (click)="itemClick($event,child,i)" (mouseenter)="onMouseEnter(i)" class="ripplelink" *ngIf="child.routerLink"
|
||||||
|
[routerLink]="child.routerLink" routerLinkActive="active-menuitem-routerlink"
|
||||||
|
[routerLinkActiveOptions]="{exact: true}" [attr.tabindex]="!visible ? '-1' : null" [attr.target]="child.target">
|
||||||
|
<i *ngIf="child.icon" class="material-icons">{{child.icon}}</i>
|
||||||
|
<span>{{child.label}}</span>
|
||||||
|
<span class="menuitem-badge" *ngIf="child.badge">{{child.badge}}</span>
|
||||||
|
<i class="material-icons submenu-icon" *ngIf="child.items">keyboard_arrow_down</i>
|
||||||
|
</a>
|
||||||
|
<div class="layout-menu-tooltip">
|
||||||
|
<div class="layout-menu-tooltip-arrow"></div>
|
||||||
|
<div class="layout-menu-tooltip-text">{{child.label}}</div>
|
||||||
|
</div>
|
||||||
|
<ul of-submenu [item]="child" *ngIf="child.items" [visible]="isActive(i)" [reset]="reset"
|
||||||
|
[@children]="(app.isSlim()||app.isHorizontal())&&root ? isActive(i) ?
|
||||||
|
'visible' : 'hidden' : isActive(i) ? 'visibleAnimated' : 'hiddenAnimated'"></ul>
|
||||||
|
</li>
|
||||||
|
</ng-template>
|
110
src/app/commons/component/layout/menu/app.submenu.component.ts
Normal file
110
src/app/commons/component/layout/menu/app.submenu.component.ts
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
|
import { trigger, state, style, transition, animate } from '@angular/animations';
|
||||||
|
import { MenuItem } from 'primeng/primeng';
|
||||||
|
import { AppComponent } from 'app/app.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
/* tslint:disable:component-selector */
|
||||||
|
selector: '[of-submenu]',
|
||||||
|
/* tslint:enable:component-selector */
|
||||||
|
templateUrl: './app.submenu.component.html',
|
||||||
|
animations: [
|
||||||
|
trigger('children', [
|
||||||
|
state('hiddenAnimated', style({
|
||||||
|
height: '0px'
|
||||||
|
})),
|
||||||
|
state('visibleAnimated', style({
|
||||||
|
height: '*'
|
||||||
|
})),
|
||||||
|
state('visible', style({
|
||||||
|
height: '*',
|
||||||
|
'z-index': 100
|
||||||
|
})),
|
||||||
|
state('hidden', style({
|
||||||
|
height: '0px',
|
||||||
|
'z-index': '*'
|
||||||
|
})),
|
||||||
|
transition('visibleAnimated => hiddenAnimated', animate('400ms cubic-bezier(0.86, 0, 0.07, 1)')),
|
||||||
|
transition('hiddenAnimated => visibleAnimated', animate('400ms cubic-bezier(0.86, 0, 0.07, 1)'))
|
||||||
|
])
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class AppSubMenuComponent {
|
||||||
|
|
||||||
|
@Input() item: MenuItem;
|
||||||
|
|
||||||
|
@Input() root: boolean;
|
||||||
|
|
||||||
|
@Input() visible: boolean;
|
||||||
|
|
||||||
|
_reset: boolean;
|
||||||
|
|
||||||
|
activeIndex: number;
|
||||||
|
|
||||||
|
constructor(public app: AppComponent) { }
|
||||||
|
|
||||||
|
itemClick(event: Event, item: MenuItem, index: number) {
|
||||||
|
if (this.root) {
|
||||||
|
this.app.menuHoverActive = !this.app.menuHoverActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
// avoid processing disabled items
|
||||||
|
if (item.disabled) {
|
||||||
|
event.preventDefault();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// activate current item and deactivate active sibling if any
|
||||||
|
this.activeIndex = (this.activeIndex === index) ? null : index;
|
||||||
|
|
||||||
|
// execute command
|
||||||
|
if (item.command) {
|
||||||
|
item.command({ originalEvent: event, item: item });
|
||||||
|
}
|
||||||
|
|
||||||
|
// prevent hash change
|
||||||
|
if (item.items || (!item.url && !item.routerLink)) {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.app.layoutMenuScrollerViewChild.moveBar();
|
||||||
|
}, 450);
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
// hide menu
|
||||||
|
if (!item.items) {
|
||||||
|
if (this.app.isHorizontal() || this.app.isSlim()) {
|
||||||
|
this.app.resetMenu = true;
|
||||||
|
} else {
|
||||||
|
this.app.resetMenu = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.app.overlayMenuActive = false;
|
||||||
|
this.app.staticMenuMobileActive = false;
|
||||||
|
this.app.menuHoverActive = !this.app.menuHoverActive;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMouseEnter(index: number) {
|
||||||
|
if (this.root && this.app.menuHoverActive && (this.app.isHorizontal() || this.app.isSlim())
|
||||||
|
&& !this.app.isMobile() && !this.app.isTablet()) {
|
||||||
|
this.activeIndex = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isActive(index: number): boolean {
|
||||||
|
return this.activeIndex === index;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Input() get reset(): boolean {
|
||||||
|
return this._reset;
|
||||||
|
}
|
||||||
|
|
||||||
|
set reset(val: boolean) {
|
||||||
|
this._reset = val;
|
||||||
|
|
||||||
|
if (this._reset && (this.app.isHorizontal() || this.app.isSlim())) {
|
||||||
|
this.activeIndex = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
<div class="profile" [ngClass]="{'profile-expanded':active}">
|
||||||
|
<a href="#" (click)="onClick($event)">
|
||||||
|
<img class="profile-image" src="assets/layout/images/avatar.png" />
|
||||||
|
<span class="profile-name">Jane Williams</span>
|
||||||
|
<i class="material-icons">keyboard_arrow_down</i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul class="ultima-menu profile-menu" [@menu]="active ? 'visible' : 'hidden'">
|
||||||
|
<li role="menuitem">
|
||||||
|
<a href="#" class="ripplelink" [attr.tabindex]="!active ? '-1' : null">
|
||||||
|
<i class="material-icons">person</i>
|
||||||
|
<span>Profile</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li role="menuitem">
|
||||||
|
<a href="#" class="ripplelink" [attr.tabindex]="!active ? '-1' : null">
|
||||||
|
<i class="material-icons">security</i>
|
||||||
|
<span>Privacy</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li role="menuitem">
|
||||||
|
<a href="#" class="ripplelink" [attr.tabindex]="!active ? '-1' : null">
|
||||||
|
<i class="material-icons">settings_application</i>
|
||||||
|
<span>Settings</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li role="menuitem">
|
||||||
|
<a href="#" class="ripplelink" [attr.tabindex]="!active ? '-1' : null">
|
||||||
|
<i class="material-icons">power_settings_new</i>
|
||||||
|
<span>Logout</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
|
@ -0,0 +1,33 @@
|
||||||
|
import {Component, trigger, state, transition, style, animate} from '@angular/core';
|
||||||
|
import {AppComponent} from 'app/app.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'of-inline-profile',
|
||||||
|
templateUrl: './app.profile.component.html',
|
||||||
|
animations: [
|
||||||
|
trigger('menu', [
|
||||||
|
state('hidden', style({
|
||||||
|
height: '0px'
|
||||||
|
})),
|
||||||
|
state('visible', style({
|
||||||
|
height: '*'
|
||||||
|
})),
|
||||||
|
transition('visible => hidden', animate('400ms cubic-bezier(0.86, 0, 0.07, 1)')),
|
||||||
|
transition('hidden => visible', animate('400ms cubic-bezier(0.86, 0, 0.07, 1)'))
|
||||||
|
])
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class AppInlineProfileComponent {
|
||||||
|
|
||||||
|
active: boolean;
|
||||||
|
|
||||||
|
constructor(public app: AppComponent) {}
|
||||||
|
|
||||||
|
onClick(event) {
|
||||||
|
this.active = !this.active;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.app.layoutMenuScrollerViewChild.moveBar();
|
||||||
|
}, 450);
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user