Browse Source

clone theme-shared package to nx workspace

pull/9837/head
mehmet-erim 5 years ago
parent
commit
c30a3300d3
  1. 89
      npm/ng-packs/nx/ng-packs/angular.json
  2. 2
      npm/ng-packs/nx/ng-packs/libs/core/locale/ng-package.json
  3. 2
      npm/ng-packs/nx/ng-packs/libs/core/ng-package.json
  4. 0
      npm/ng-packs/nx/ng-packs/libs/core/src/public-api.ts
  5. 2
      npm/ng-packs/nx/ng-packs/libs/core/testing/ng-package.json
  6. 9
      npm/ng-packs/nx/ng-packs/libs/core/tsconfig.json
  7. 4
      npm/ng-packs/nx/ng-packs/libs/core/tsconfig.lib.prod.json
  8. 36
      npm/ng-packs/nx/ng-packs/libs/theme-shared/.eslintrc.json
  9. 7
      npm/ng-packs/nx/ng-packs/libs/theme-shared/README.md
  10. 7
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/ng-package.json
  11. 56
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/adapters/date-time.adapter.ts
  12. 40
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/adapters/date.adapter.ts
  13. 35
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/adapters/time.adapter.ts
  14. 32
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/components/abstract-actions/abstract-actions.component.ts
  15. 76
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/components/date-time-picker/date-time-picker.component.ts
  16. 138
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/components/extensible-form/extensible-form-prop.component.html
  17. 193
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/components/extensible-form/extensible-form-prop.component.ts
  18. 23
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/components/extensible-form/extensible-form.component.html
  19. 64
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/components/extensible-form/extensible-form.component.ts
  20. 40
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/components/extensible-table/extensible-table.component.html
  21. 145
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/components/extensible-table/extensible-table.component.ts
  22. 43
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/components/grid-actions/grid-actions.component.html
  23. 36
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/components/grid-actions/grid-actions.component.ts
  24. 20
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/components/page-toolbar/page-toolbar.component.html
  25. 49
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/components/page-toolbar/page-toolbar.component.ts
  26. 1
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/constants/extra-properties.ts
  27. 19
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/directives/disabled.directive.ts
  28. 54
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/directives/prop-data.directive.ts
  29. 15
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/enums/props.enum.ts
  30. 79
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/models/actions.ts
  31. 53
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/models/entity-actions.ts
  32. 71
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/models/entity-props.ts
  33. 109
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/models/form-props.ts
  34. 108
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/models/internal/object-extensions.ts
  35. 3
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/models/object-extensions.ts
  36. 85
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/models/props.ts
  37. 78
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/models/toolbar-actions.ts
  38. 16
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/services/extensions.service.ts
  39. 13
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/tokens/extensions.token.ts
  40. 56
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/ui-extensions.module.ts
  41. 49
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/utils/actions.util.ts
  42. 73
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/utils/enum.util.ts
  43. 3
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/utils/factory.util.ts
  44. 61
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/utils/form-props.util.ts
  45. 33
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/utils/localization.util.ts
  46. 62
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/utils/props.util.ts
  47. 200
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/utils/state.util.ts
  48. 72
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/utils/typeahead.util.ts
  49. 16
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/utils/validation.util.ts
  50. 70
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/public-api.ts
  51. 22
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/tests/actions.spec.ts
  52. 53
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/tests/actions.util.spec.ts
  53. 35
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/tests/date-time.adapter.spec.ts
  54. 31
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/tests/date.adapter.spec.ts
  55. 151
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/tests/entity-actions.spec.ts
  56. 166
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/tests/entity-props.spec.ts
  57. 114
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/tests/enum.util.spec.ts
  58. 50
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/tests/extensions.service.spec.ts
  59. 28
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/tests/factory.util.spec.ts
  60. 193
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/tests/form-props.spec.ts
  61. 142
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/tests/form-props.util.spec.ts
  62. 34
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/tests/localization.util.spec.ts
  63. 22
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/tests/props.spec.ts
  64. 72
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/tests/props.util.spec.ts
  65. 369
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/tests/state.util.spec.ts
  66. 36
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/tests/time.adapter.spec.ts
  67. 216
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/tests/toolbar-actions.spec.ts
  68. 72
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/tests/typeahead.util.spec.ts
  69. 20
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/tests/validation.util.spec.ts
  70. 8
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/testing/src/lib/ui-extensions-testing.module.ts
  71. 1
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/testing/src/public-api.ts
  72. 7
      npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/testing/ssng-package.json
  73. 20
      npm/ng-packs/nx/ng-packs/libs/theme-shared/jest.config.js
  74. 16
      npm/ng-packs/nx/ng-packs/libs/theme-shared/ng-package.json
  75. 22
      npm/ng-packs/nx/ng-packs/libs/theme-shared/package.json
  76. 23
      npm/ng-packs/nx/ng-packs/libs/theme-shared/src/lib/animations/bounce.animations.ts
  77. 90
      npm/ng-packs/nx/ng-packs/libs/theme-shared/src/lib/animations/collapse.animations.ts
  78. 74
      npm/ng-packs/nx/ng-packs/libs/theme-shared/src/lib/animations/fade.animations.ts
  79. 6
      npm/ng-packs/nx/ng-packs/libs/theme-shared/src/lib/animations/index.ts
  80. 12
      npm/ng-packs/nx/ng-packs/libs/theme-shared/src/lib/animations/modal.animations.ts
  81. 7
      npm/ng-packs/nx/ng-packs/libs/theme-shared/src/lib/animations/slide.animations.ts
  82. 17
      npm/ng-packs/nx/ng-packs/libs/theme-shared/src/lib/animations/toast.animations.ts
  83. 13
      npm/ng-packs/nx/ng-packs/libs/theme-shared/src/lib/components/breadcrumb/breadcrumb.component.html
  84. 58
      npm/ng-packs/nx/ng-packs/libs/theme-shared/src/lib/components/breadcrumb/breadcrumb.component.ts
  85. 82
      npm/ng-packs/nx/ng-packs/libs/theme-shared/src/lib/components/button/button.component.ts
  86. 11
      npm/ng-packs/nx/ng-packs/libs/theme-shared/src/lib/components/chart/chart.component.html
  87. 142
      npm/ng-packs/nx/ng-packs/libs/theme-shared/src/lib/components/chart/chart.component.ts
  88. 39
      npm/ng-packs/nx/ng-packs/libs/theme-shared/src/lib/components/confirmation/confirmation.component.html
  89. 82
      npm/ng-packs/nx/ng-packs/libs/theme-shared/src/lib/components/confirmation/confirmation.component.scss
  90. 37
      npm/ng-packs/nx/ng-packs/libs/theme-shared/src/lib/components/confirmation/confirmation.component.ts
  91. 37
      npm/ng-packs/nx/ng-packs/libs/theme-shared/src/lib/components/http-error-wrapper/http-error-wrapper.component.html
  92. 14
      npm/ng-packs/nx/ng-packs/libs/theme-shared/src/lib/components/http-error-wrapper/http-error-wrapper.component.scss
  93. 90
      npm/ng-packs/nx/ng-packs/libs/theme-shared/src/lib/components/http-error-wrapper/http-error-wrapper.component.ts
  94. 15
      npm/ng-packs/nx/ng-packs/libs/theme-shared/src/lib/components/index.ts
  95. 24
      npm/ng-packs/nx/ng-packs/libs/theme-shared/src/lib/components/loader-bar/loader-bar.component.scss
  96. 120
      npm/ng-packs/nx/ng-packs/libs/theme-shared/src/lib/components/loader-bar/loader-bar.component.ts
  97. 40
      npm/ng-packs/nx/ng-packs/libs/theme-shared/src/lib/components/loading/loading.component.ts
  98. 16
      npm/ng-packs/nx/ng-packs/libs/theme-shared/src/lib/components/modal/modal-close.directive.ts
  99. 13
      npm/ng-packs/nx/ng-packs/libs/theme-shared/src/lib/components/modal/modal-container.component.ts
  100. 26
      npm/ng-packs/nx/ng-packs/libs/theme-shared/src/lib/components/modal/modal-ref.service.ts

89
npm/ng-packs/nx/ng-packs/angular.json

@ -8,9 +8,9 @@
"prefix": "abp",
"architect": {
"build": {
"builder": "@nrwl/angular:ng-packagr-lite",
"outputs": ["dist/libs/core"],
"builder": "@angular-devkit/build-ng-packagr:build",
"options": {
"tsConfig": "libs/core/tsconfig.lib.json",
"project": "libs/core/ng-package.json"
},
"configurations": {
@ -160,6 +160,88 @@
}
}
}
},
"theme-basic": {
"projectType": "library",
"root": "libs/theme-basic",
"sourceRoot": "libs/theme-basic/src",
"prefix": "abp",
"architect": {
"build": {
"builder": "@angular-devkit/build-ng-packagr:build",
"options": {
"tsConfig": "libs/theme-basic/tsconfig.lib.json",
"project": "libs/theme-basic/ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "libs/theme-basic/tsconfig.lib.prod.json"
},
"development": {
"tsConfig": "libs/theme-basic/tsconfig.lib.json"
}
},
"defaultConfiguration": "production"
},
"test": {
"builder": "@nrwl/jest:jest",
"outputs": ["coverage/libs/theme-basic"],
"options": {
"jestConfig": "libs/theme-basic/jest.config.js",
"passWithNoTests": true
}
},
"lint": {
"builder": "@nrwl/linter:eslint",
"options": {
"lintFilePatterns": [
"libs/theme-basic/src/**/*.ts",
"libs/theme-basic/src/**/*.html"
]
}
}
}
},
"theme-shared": {
"projectType": "library",
"root": "libs/theme-shared",
"sourceRoot": "libs/theme-shared/src",
"prefix": "abp",
"architect": {
"build": {
"builder": "@angular-devkit/build-ng-packagr:build",
"options": {
"tsConfig": "libs/theme-shared/tsconfig.lib.json",
"project": "libs/theme-shared/ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "libs/theme-shared/tsconfig.lib.prod.json"
},
"development": {
"tsConfig": "libs/theme-shared/tsconfig.lib.json"
}
},
"defaultConfiguration": "production"
},
"test": {
"builder": "@nrwl/jest:jest",
"outputs": ["coverage/libs/theme-shared"],
"options": {
"jestConfig": "libs/theme-shared/jest.config.js",
"passWithNoTests": true
}
},
"lint": {
"builder": "@nrwl/linter:eslint",
"options": {
"lintFilePatterns": [
"libs/theme-shared/src/**/*.ts",
"libs/theme-shared/src/**/*.html"
]
}
}
}
}
},
"cli": {
@ -186,7 +268,8 @@
"@nrwl/angular:library": {
"style": "scss",
"linter": "eslint",
"unitTestRunner": "jest"
"unitTestRunner": "jest",
"strict": false
},
"@nrwl/angular:component": {
"style": "scss"

2
npm/ng-packs/nx/ng-packs/libs/core/locale/ng-package.json

@ -1,6 +1,6 @@
{
"$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../dist/core/locale",
"dest": "../../../dist/libs/core/locale",
"lib": {
"entryFile": "src/public-api.ts"
}

2
npm/ng-packs/nx/ng-packs/libs/core/ng-package.json

@ -2,7 +2,7 @@
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../dist/libs/core",
"lib": {
"entryFile": "src/index.ts"
"entryFile": "src/public-api.ts"
},
"allowedNonPeerDependencies": [
"@abp/utils",

0
npm/ng-packs/nx/ng-packs/libs/core/src/index.ts → npm/ng-packs/nx/ng-packs/libs/core/src/public-api.ts

2
npm/ng-packs/nx/ng-packs/libs/core/testing/ng-package.json

@ -1,6 +1,6 @@
{
"$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../dist/core/testing",
"dest": "../../../dist/libs/core/testing",
"lib": {
"entryFile": "src/public-api.ts"
}

9
npm/ng-packs/nx/ng-packs/libs/core/tsconfig.json

@ -11,15 +11,6 @@
}
],
"compilerOptions": {
"forceConsistentCasingInFileNames": true,
"strict": false,
"noImplicitReturns": false,
"noFallthroughCasesInSwitch": true,
"allowSyntheticDefaultImports": true,
},
"angularCompilerOptions": {
"strictInjectionParameters": true,
"strictInputAccessModifiers": false,
"strictTemplates": false
}
}

4
npm/ng-packs/nx/ng-packs/libs/core/tsconfig.lib.prod.json

@ -5,7 +5,7 @@
"declarationMap": false,
},
"angularCompilerOptions": {
"compilationMode": "partial"
// "enableIvy": false
"compilationMode": "partial",
"enableIvy": false
}
}

36
npm/ng-packs/nx/ng-packs/libs/theme-shared/.eslintrc.json

@ -0,0 +1,36 @@
{
"extends": ["../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts"],
"extends": [
"plugin:@nrwl/nx/angular",
"plugin:@angular-eslint/template/process-inline-templates"
],
"rules": {
"@angular-eslint/directive-selector": [
"error",
{
"type": "attribute",
"prefix": "abp",
"style": "camelCase"
}
],
"@angular-eslint/component-selector": [
"error",
{
"type": "element",
"prefix": "abp",
"style": "kebab-case"
}
]
}
},
{
"files": ["*.html"],
"extends": ["plugin:@nrwl/nx/angular-template"],
"rules": {}
}
]
}

7
npm/ng-packs/nx/ng-packs/libs/theme-shared/README.md

@ -0,0 +1,7 @@
# theme-shared
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test theme-shared` to execute the unit tests.

7
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/ng-package.json

@ -0,0 +1,7 @@
{
"$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../dist/libs/theme-shared/extensions",
"lib": {
"entryFile": "src/public-api.ts"
}
}

56
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/adapters/date-time.adapter.ts

@ -0,0 +1,56 @@
import { Injectable } from '@angular/core';
import { NgbDateStruct, NgbTimeStruct } from '@ng-bootstrap/ng-bootstrap';
@Injectable()
export class DateTimeAdapter {
value: NgbDateTimeStruct;
fromModel(value: string | Date): NgbDateTimeStruct | null {
if (!value) return null;
const date = new Date(value);
if (isNaN((date as unknown) as number)) return null;
this.value = {
year: date.getFullYear(),
month: date.getMonth() + 1,
day: date.getDate(),
hour: date.getHours(),
minute: date.getMinutes(),
second: date.getSeconds(),
};
return this.value;
}
toModel(value: NgbDateTimeStruct | null): string {
if (!value) return '';
const now = new Date();
value = {
year: now.getUTCFullYear(),
month: now.getMonth() + 1,
day: now.getDate(),
hour: 0,
minute: 0,
second: 0,
...this.value,
...value,
};
const date = new Date(
value.year,
value.month - 1,
value.day,
value.hour,
value.minute,
value.second,
);
return new Date(date).toISOString();
}
}
type NgbDateTimeStruct = NgbDateStruct & NgbTimeStruct;

40
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/adapters/date.adapter.ts

@ -0,0 +1,40 @@
import { formatDate } from '@angular/common';
import { Injectable } from '@angular/core';
import { NgbDateAdapter, NgbDateStruct } from '@ng-bootstrap/ng-bootstrap';
@Injectable()
export class DateAdapter extends NgbDateAdapter<string> {
fromModel(value: string | Date): NgbDateStruct | null {
if (!value) return null;
let date: Date;
if (typeof value === 'string') {
date = this.dateOf(value);
} else {
date = new Date(value);
}
if (isNaN(date as unknown as number)) return null;
return {
day: date.getDate(),
month: date.getMonth() + 1,
year: date.getFullYear(),
};
}
toModel(value: NgbDateStruct | null): string {
if (!value) return '';
const date = new Date(value.year, value.month - 1, value.day);
const formattedDate = formatDate(date, 'yyyy-MM-dd', 'en');
return formattedDate;
}
protected dateOf(value: string): Date {
const dateUtc = new Date(Date.parse(value));
return new Date(dateUtc.getTime() + Math.abs(dateUtc.getTimezoneOffset() * 60000));
}
}

35
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/adapters/time.adapter.ts

@ -0,0 +1,35 @@
import { formatDate } from '@angular/common';
import { Injectable } from '@angular/core';
import { NgbTimeAdapter, NgbTimeStruct } from '@ng-bootstrap/ng-bootstrap';
@Injectable()
export class TimeAdapter extends NgbTimeAdapter<string> {
fromModel(value: string | Date): NgbTimeStruct | null {
if (!value) return null;
const date = isTimeStr(value)
? new Date(0, 0, 1, ...value.split(':').map(Number))
: new Date(value);
if (isNaN((date as unknown) as number)) return null;
return {
hour: date.getHours(),
minute: date.getMinutes(),
second: date.getSeconds(),
};
}
toModel(value: NgbTimeStruct | null): string {
if (!value) return '';
const date = new Date(0, 0, 1, value.hour, value.minute, value.second);
const formattedDate = formatDate(date, 'HH:mm', 'en');
return formattedDate;
}
}
function isTimeStr(value: string | Date): value is string {
return /^((2[123])|[01][0-9])(\:[0-5][0-9]){1,2}$/.test(String(value));
}

32
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/components/abstract-actions/abstract-actions.component.ts

@ -0,0 +1,32 @@
import { Directive, Injector, Input } from '@angular/core';
import { ActionData, ActionList } from '../../models/actions';
import { ExtensionsService } from '../../services/extensions.service';
import { EXTENSIONS_ACTION_TYPE, EXTENSIONS_IDENTIFIER } from '../../tokens/extensions.token';
// tslint:disable: directive-class-suffix
// Fix for https://github.com/angular/angular/issues/23904
// @dynamic
@Directive()
export abstract class AbstractActionsComponent<L extends ActionList<any>> extends ActionData<
InferredRecord<L>
> {
readonly actionList: L;
readonly getInjected: InferredData<L>['getInjected'];
@Input() readonly record: InferredData<L>['record'];
constructor(injector: Injector) {
super();
// tslint:disable-next-line
this.getInjected = injector.get.bind(injector);
const extensions = injector.get(ExtensionsService);
const name = injector.get(EXTENSIONS_IDENTIFIER);
const type = injector.get(EXTENSIONS_ACTION_TYPE);
this.actionList = (extensions[type].get(name).actions as unknown) as L;
}
}
type InferredData<L> = ActionData<InferredRecord<L>>;
type InferredRecord<L> = L extends ActionList<infer R> ? R : never;

76
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/components/date-time-picker/date-time-picker.component.ts

@ -0,0 +1,76 @@
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
Input,
Optional,
SkipSelf,
ViewChild,
} from '@angular/core';
import { ControlContainer } from '@angular/forms';
import {
NgbDateAdapter,
NgbInputDatepicker,
NgbTimeAdapter,
NgbTimepicker,
} from '@ng-bootstrap/ng-bootstrap';
import { DateTimeAdapter } from '../../adapters/date-time.adapter';
import { FormProp } from '../../models/form-props';
import { selfFactory } from '../../utils/factory.util';
@Component({
exportAs: 'abpDateTimePicker',
selector: 'abp-date-time-picker',
template: `
<input
[id]="prop.id"
[formControlName]="prop.name"
(ngModelChange)="setTime($event)"
(click)="datepicker.open()"
(keyup.space)="datepicker.open()"
ngbDatepicker
#datepicker="ngbDatepicker"
type="text"
class="form-control"
/>
<ngb-timepicker
#timepicker
[formControlName]="prop.name"
(ngModelChange)="setDate($event)"
[meridian]="meridian"
></ngb-timepicker>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
viewProviders: [
{
provide: ControlContainer,
useFactory: selfFactory,
deps: [[new Optional(), new SkipSelf(), ControlContainer]],
},
{
provide: NgbDateAdapter,
useClass: DateTimeAdapter,
},
{
provide: NgbTimeAdapter,
useClass: DateTimeAdapter,
},
],
})
export class DateTimePickerComponent {
@Input() prop: FormProp;
@Input() meridian = false;
@ViewChild(NgbInputDatepicker) date: NgbInputDatepicker;
@ViewChild(NgbTimepicker) time: NgbTimepicker;
constructor(public readonly cdRef: ChangeDetectorRef) {}
setDate(datestr: string) {
this.date.writeValue(datestr);
}
setTime(datestr: string) {
this.time.writeValue(datestr);
}
}

138
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/components/extensible-form/extensible-form-prop.component.html

@ -0,0 +1,138 @@
<div class="form-group" *abpPermission="prop.permission" [ngSwitch]="getComponent(prop)">
<ng-template ngSwitchCase="input">
<ng-template [ngTemplateOutlet]="label"></ng-template>
<input
#field
[id]="prop.id"
[formControlName]="prop.name"
[autocomplete]="prop.autocomplete"
[type]="getType(prop)"
[abpDisabled]="disabled"
[readonly]="readonly"
class="form-control"
/>
</ng-template>
<ng-template ngSwitchCase="hidden">
<input [formControlName]="prop.name" type="hidden" />
</ng-template>
<ng-template ngSwitchCase="checkbox">
<div class="custom-checkbox custom-control" validationTarget>
<input
#field
[id]="prop.id"
[formControlName]="prop.name"
[abpDisabled]="disabled"
type="checkbox"
class="custom-control-input"
/>
<ng-template
[ngTemplateOutlet]="label"
[ngTemplateOutletContext]="{ $implicit: 'custom-control-label' }"
></ng-template>
</div>
</ng-template>
<ng-template ngSwitchCase="select">
<ng-template [ngTemplateOutlet]="label"></ng-template>
<select
#field
[id]="prop.id"
[formControlName]="prop.name"
[abpDisabled]="disabled"
class="custom-select form-control"
>
<option
*ngFor="let option of options$ | async; trackBy: track.by('value')"
[ngValue]="option.value"
>
{{ option.key }}
</option>
</select>
</ng-template>
<ng-template ngSwitchCase="multiselect">
<ng-template [ngTemplateOutlet]="label"></ng-template>
<select
#field
[id]="prop.id"
[formControlName]="prop.name"
[abpDisabled]="disabled"
multiple="multiple"
class="custom-select form-control"
>
<option
*ngFor="let option of options$ | async; trackBy: track.by('value')"
[ngValue]="option.value"
>
{{ option.key }}
</option>
</select>
</ng-template>
<ng-template ngSwitchCase="typeahead">
<ng-template [ngTemplateOutlet]="label"></ng-template>
<div #typeahead class="position-relative" validationStyle validationTarget>
<input
#field
[id]="prop.id"
[autocomplete]="prop.autocomplete"
[abpDisabled]="disabled"
[ngbTypeahead]="search"
[editable]="false"
[inputFormatter]="typeaheadFormatter"
[resultFormatter]="typeaheadFormatter"
[ngModelOptions]="{ standalone: true }"
[(ngModel)]="typeaheadModel"
(selectItem)="setTypeaheadValue($event.item)"
(blur)="setTypeaheadValue(typeaheadModel)"
[class.is-invalid]="typeahead.classList.contains('is-invalid')"
class="form-control"
/>
<input [formControlName]="prop.name" type="hidden" />
</div>
</ng-template>
<ng-template ngSwitchCase="date">
<ng-template [ngTemplateOutlet]="label"></ng-template>
<input
[id]="prop.id"
[formControlName]="prop.name"
(click)="datepicker.open()"
(keyup.space)="datepicker.open()"
ngbDatepicker
#datepicker="ngbDatepicker"
type="text"
class="form-control"
/>
</ng-template>
<ng-template ngSwitchCase="time">
<ng-template [ngTemplateOutlet]="label"></ng-template>
<ngb-timepicker [formControlName]="prop.name"></ngb-timepicker>
</ng-template>
<ng-template ngSwitchCase="dateTime">
<ng-template [ngTemplateOutlet]="label"></ng-template>
<abp-date-time-picker [prop]="prop" [meridian]="meridian"></abp-date-time-picker>
</ng-template>
<ng-template ngSwitchCase="textarea">
<ng-template [ngTemplateOutlet]="label"></ng-template>
<textarea
#field
[id]="prop.id"
[formControlName]="prop.name"
[abpDisabled]="disabled"
[readonly]="readonly"
class="form-control"
></textarea>
</ng-template>
</div>
<ng-template #label let-classes>
<label [htmlFor]="prop.id" [ngClass]="classes"
>{{ prop.displayName | abpLocalization }} {{ asterisk }}</label
>
</ng-template>

193
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/components/extensible-form/extensible-form-prop.component.ts

@ -0,0 +1,193 @@
import { ABP, AbpValidators, ConfigStateService, TrackByService } from '@abp/ng.core';
import {
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ElementRef,
Input,
OnChanges,
Optional,
SimpleChanges,
SkipSelf,
ViewChild,
} from '@angular/core';
import {
ControlContainer,
FormGroup,
FormGroupDirective,
ValidatorFn,
Validators,
} from '@angular/forms';
import { NgbDateAdapter, NgbTimeAdapter } from '@ng-bootstrap/ng-bootstrap';
import { Observable, of } from 'rxjs';
import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators';
import snq from 'snq';
import { DateAdapter } from '../../adapters/date.adapter';
import { TimeAdapter } from '../../adapters/time.adapter';
import { EXTRA_PROPERTIES_KEY } from '../../constants/extra-properties';
import { ePropType } from '../../enums/props.enum';
import { FormProp } from '../../models/form-props';
import { PropData } from '../../models/props';
import { selfFactory } from '../../utils/factory.util';
import { addTypeaheadTextSuffix } from '../../utils/typeahead.util';
@Component({
selector: 'abp-extensible-form-prop',
templateUrl: './extensible-form-prop.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
viewProviders: [
{
provide: ControlContainer,
useFactory: selfFactory,
deps: [[new Optional(), new SkipSelf(), ControlContainer]],
},
{ provide: NgbDateAdapter, useClass: DateAdapter },
{ provide: NgbTimeAdapter, useClass: TimeAdapter },
],
})
export class ExtensibleFormPropComponent implements OnChanges, AfterViewInit {
@Input() data: PropData;
@Input() prop: FormProp;
@Input() first: boolean;
@ViewChild('field') private fieldRef: ElementRef<HTMLElement>;
asterisk = '';
options$: Observable<ABP.Option<any>[]> = of([]);
validators: ValidatorFn[] = [];
readonly: boolean;
disabled: boolean;
private readonly form: FormGroup;
typeaheadModel: any;
setTypeaheadValue(selectedOption: ABP.Option<string>) {
this.typeaheadModel = selectedOption || { key: null, value: null };
const { key, value } = this.typeaheadModel;
const [keyControl, valueControl] = this.getTypeaheadControls();
if (valueControl.value && !value) valueControl.markAsDirty();
keyControl.setValue(key);
valueControl.setValue(value);
}
search = (text$: Observable<string>) =>
text$
? text$.pipe(
debounceTime(300),
distinctUntilChanged(),
switchMap(text => this.prop.options(this.data, text)),
)
: of([]);
typeaheadFormatter = (option: ABP.Option<any>) => option.key;
get meridian() {
return (
this.configState.getDeep('localization.currentCulture.dateTimeFormat.shortTimePattern') || ''
).includes('tt');
}
get isInvalid() {
const control = this.form.get(this.prop.name);
return control.touched && control.invalid;
}
constructor(
public readonly cdRef: ChangeDetectorRef,
public readonly track: TrackByService,
protected configState: ConfigStateService,
groupDirective: FormGroupDirective,
) {
this.form = groupDirective.form;
}
private getTypeaheadControls() {
const { name } = this.prop;
const extraPropName = `${EXTRA_PROPERTIES_KEY}.${name}`;
const keyControl =
this.form.get(addTypeaheadTextSuffix(extraPropName)) ||
this.form.get(addTypeaheadTextSuffix(name));
const valueControl = this.form.get(extraPropName) || this.form.get(name);
return [keyControl, valueControl];
}
private setAsterisk() {
this.asterisk = this.validators.some(isRequired) ? '*' : '';
}
ngAfterViewInit() {
if (this.first && this.fieldRef) {
this.fieldRef.nativeElement.focus();
}
}
getComponent(prop: FormProp): string {
switch (prop.type) {
case ePropType.Boolean:
return 'checkbox';
case ePropType.Date:
return 'date';
case ePropType.DateTime:
return 'dateTime';
case ePropType.Hidden:
return 'hidden';
case ePropType.MultiSelect:
return 'multiselect';
case ePropType.Text:
return 'textarea';
case ePropType.Time:
return 'time';
case ePropType.Typeahead:
return 'typeahead';
default:
return prop.options ? 'select' : 'input';
}
}
getType(prop: FormProp): string {
switch (prop.type) {
case ePropType.Date:
case ePropType.String:
return 'text';
case ePropType.Boolean:
return 'checkbox';
case ePropType.Number:
return 'number';
case ePropType.Email:
return 'email';
case ePropType.Password:
return 'password';
default:
return 'hidden';
}
}
ngOnChanges({ prop }: SimpleChanges) {
const currentProp = snq<FormProp>(() => prop.currentValue);
const { options, readonly, disabled, validators } = currentProp || {};
if (options) this.options$ = options(this.data);
if (readonly) this.readonly = readonly(this.data);
if (disabled) this.disabled = disabled(this.data);
if (validators) {
this.validators = validators(this.data);
this.setAsterisk();
}
const [keyControl, valueControl] = this.getTypeaheadControls();
if (keyControl && valueControl)
this.typeaheadModel = { key: keyControl.value, value: valueControl.value };
}
}
function isRequired(validator: ValidatorFn) {
return validator === Validators.required || validator === AbpValidators.required;
}

23
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/components/extensible-form/extensible-form.component.html

@ -0,0 +1,23 @@
<ng-container *ngIf="form">
<ng-container *abpPropData="let data; fromList: propList; withRecord: record">
<ng-container *ngFor="let prop of propList; let first = first; trackBy: track.by('name')">
<ng-container *ngIf="prop.visible(data)">
<ng-container
[formGroupName]="extraPropertiesKey"
*ngIf="extraProperties.controls[prop.name]; else tempDefault"
>
<abp-extensible-form-prop [prop]="prop" [data]="data"></abp-extensible-form-prop>
</ng-container>
<ng-template #tempDefault>
<abp-extensible-form-prop
*ngIf="form.get(prop.name)"
[prop]="prop"
[data]="data"
[first]="first"
></abp-extensible-form-prop>
</ng-template>
</ng-container>
</ng-container>
</ng-container>
</ng-container>

64
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/components/extensible-form/extensible-form.component.ts

@ -0,0 +1,64 @@
import { TrackByService } from '@abp/ng.core';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
Inject,
Input,
Optional,
QueryList,
SkipSelf,
ViewChildren,
} from '@angular/core';
import { ControlContainer, FormGroup } from '@angular/forms';
import { EXTRA_PROPERTIES_KEY } from '../../constants/extra-properties';
import { FormPropList } from '../../models/form-props';
import { ExtensionsService } from '../../services/extensions.service';
import { EXTENSIONS_IDENTIFIER } from '../../tokens/extensions.token';
import { selfFactory } from '../../utils/factory.util';
import { ExtensibleFormPropComponent } from './extensible-form-prop.component';
@Component({
exportAs: 'abpExtensibleForm',
selector: 'abp-extensible-form',
templateUrl: './extensible-form.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
viewProviders: [
{
provide: ControlContainer,
useFactory: selfFactory,
deps: [[new Optional(), new SkipSelf(), ControlContainer]],
},
],
})
export class ExtensibleFormComponent<R = any> {
@ViewChildren(ExtensibleFormPropComponent)
formProps: QueryList<ExtensibleFormPropComponent>;
@Input()
set selectedRecord(record: R) {
const type = !record || JSON.stringify(record) === '{}' ? 'create' : 'edit';
this.propList = this.extensions[`${type}FormProps`].get(this.identifier).props;
this.record = record;
}
extraPropertiesKey = EXTRA_PROPERTIES_KEY;
propList: FormPropList<R>;
record: R;
get form(): FormGroup {
return (this.container ? this.container.control : { controls: {} }) as FormGroup;
}
get extraProperties(): FormGroup {
return (this.form.controls.extraProperties || { controls: {} }) as FormGroup;
}
constructor(
public readonly cdRef: ChangeDetectorRef,
public readonly track: TrackByService,
private container: ControlContainer,
private extensions: ExtensionsService,
@Inject(EXTENSIONS_IDENTIFIER) private identifier: string,
) {}
}

40
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/components/extensible-table/extensible-table.component.html

@ -0,0 +1,40 @@
<ngx-datatable default [rows]="data" [count]="recordsTotal" [list]="list">
<ngx-datatable-column
*ngIf="actionsTemplate || (actionList.length && hasAtLeastOnePermittedAction)"
[name]="actionsText | abpLocalization"
[maxWidth]="columnWidths[0]"
[width]="columnWidths[0]"
[sortable]="false"
>
<ng-template let-row="row" let-i="rowIndex" ngx-datatable-cell-template>
<ng-container
*ngTemplateOutlet="actionsTemplate || gridActions; context: { $implicit: row, index: i }"
></ng-container>
<ng-template #gridActions>
<abp-grid-actions [index]="i" [record]="row" text="AbpUi::Actions"></abp-grid-actions>
</ng-template>
</ng-template>
</ngx-datatable-column>
<ng-container *ngFor="let prop of propList; let i = index; trackBy: trackByFn">
<ngx-datatable-column
[width]="columnWidths[i + 1] || 200"
[name]="prop.displayName | abpLocalization"
[prop]="prop.name"
[sortable]="prop.sortable"
>
<ng-template let-row="row" let-i="index" ngx-datatable-cell-template>
<ng-container *abpPermission="prop.permission">
<div
*ngIf="row['_' + prop.name].visible"
[innerHTML]="row['_' + prop.name].value | async"
(click)="
prop.action && prop.action({ getInjected: getInjected, record: row, index: i })
"
[class.pointer]="prop.action"
></div>
</ng-container>
</ng-template>
</ngx-datatable-column>
</ng-container>
</ngx-datatable>

145
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/components/extensible-table/extensible-table.component.ts

@ -0,0 +1,145 @@
import {
ListService,
ConfigStateService,
getShortDateFormat,
getShortDateShortTimeFormat,
getShortTimeFormat,
PermissionService,
} from '@abp/ng.core';
import { formatDate } from '@angular/common';
import {
ChangeDetectionStrategy,
Component,
Inject,
Injector,
Input,
LOCALE_ID,
TemplateRef,
TrackByFunction,
Type,
InjectionToken,
InjectFlags,
SimpleChanges,
OnChanges,
} from '@angular/core';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { ePropType } from '../../enums/props.enum';
import { EntityProp, EntityPropList } from '../../models/entity-props';
import { PropData } from '../../models/props';
import { ExtensionsService } from '../../services/extensions.service';
import { EXTENSIONS_IDENTIFIER } from '../../tokens/extensions.token';
import { EntityActionList } from '../../models/entity-actions';
const DEFAULT_ACTIONS_COLUMN_WIDTH = 150;
@Component({
exportAs: 'abpExtensibleTable',
selector: 'abp-extensible-table',
templateUrl: './extensible-table.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ExtensibleTableComponent<R = any> implements OnChanges {
protected _actionsText: string;
@Input()
set actionsText(value: string) {
this._actionsText = value;
}
get actionsText(): string {
return this._actionsText ?? (this.actionList.length > 1 ? 'AbpUi::Actions' : '');
}
@Input() data: R[];
@Input() list: ListService;
@Input() recordsTotal: number;
@Input() set actionsColumnWidth(width: number) {
this.setColumnWidths(width ? Number(width) : undefined);
}
@Input() actionsTemplate: TemplateRef<any>;
getInjected: <T>(token: Type<T> | InjectionToken<T>, notFoundValue?: T, flags?: InjectFlags) => T;
readonly columnWidths: number[];
readonly propList: EntityPropList<R>;
readonly actionList: EntityActionList<R>;
readonly trackByFn: TrackByFunction<EntityProp<R>> = (_, item) => item.name;
hasAtLeastOnePermittedAction: boolean;
constructor(
@Inject(LOCALE_ID) private locale: string,
private config: ConfigStateService,
injector: Injector,
) {
// tslint:disable-next-line
this.getInjected = injector.get.bind(injector);
const extensions = injector.get(ExtensionsService);
const name = injector.get(EXTENSIONS_IDENTIFIER);
this.propList = extensions.entityProps.get(name).props;
this.actionList = extensions['entityActions'].get(name)
.actions as unknown as EntityActionList<R>;
const permissionService = injector.get(PermissionService);
this.hasAtLeastOnePermittedAction =
permissionService.filterItemsByPolicy(
this.actionList.toArray().map(action => ({ requiredPolicy: action.permission })),
).length > 0;
this.setColumnWidths(DEFAULT_ACTIONS_COLUMN_WIDTH);
}
private setColumnWidths(actionsColumn: number) {
const widths = [actionsColumn];
this.propList.forEach(({ value: prop }) => {
widths.push(prop.columnWidth);
});
(this.columnWidths as any) = widths;
}
private getDate(value: Date, format: string) {
return value ? formatDate(value, format, this.locale) : '';
}
private getIcon(value: boolean) {
return value
? '<div class="text-center text-success"><i class="fa fa-check"></i></div>'
: '<div class="text-center text-danger"><i class="fa fa-times"></i></div>';
}
getContent(prop: EntityProp<R>, data: PropData): Observable<string> {
return prop.valueResolver(data).pipe(
map(value => {
switch (prop.type) {
case ePropType.Boolean:
return this.getIcon(value);
case ePropType.Date:
return this.getDate(value, getShortDateFormat(this.config));
case ePropType.Time:
return this.getDate(value, getShortTimeFormat(this.config));
case ePropType.DateTime:
return this.getDate(value, getShortDateShortTimeFormat(this.config));
default:
return value;
// More types can be handled in the future
}
}),
);
}
ngOnChanges({ data }: SimpleChanges) {
if (!data?.currentValue) return;
this.data = data.currentValue.map((record, index) => {
this.propList.forEach(prop => {
const propData = { getInjected: this.getInjected, record, index } as any;
record[`_${prop.value.name}`] = {
visible: prop.value.visible(propData),
value: this.getContent(prop.value, propData),
};
});
return record;
});
}
}

43
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/components/grid-actions/grid-actions.component.html

@ -0,0 +1,43 @@
<div *ngIf="actionList.length > 1" ngbDropdown container="body" class="d-inline-block">
<button
class="btn btn-primary btn-sm dropdown-toggle"
data-toggle="dropdown"
aria-haspopup="true"
ngbDropdownToggle
>
<i [ngClass]="icon" [class.mr-1]="icon"></i>{{ text | abpLocalization }}
</button>
<div ngbDropdownMenu>
<ng-container
*ngFor="let action of actionList; trackBy: trackByFn"
[ngTemplateOutlet]="btnItem"
[ngTemplateOutletContext]="{ $implicit: action }"
>
</ng-container>
</div>
</div>
<ng-container
*ngIf="actionList.length === 1"
[ngTemplateOutlet]="btnItem"
[ngTemplateOutletContext]="{ $implicit: actionList.get(0).value }"
></ng-container>
<ng-template #btnItem let-action>
<ng-container *ngIf="action.visible(data)">
<button
ngbDropdownItem
*abpPermission="action.permission"
(click)="action.action(data)"
type="button"
class="{{ actionList.length === 1 ? 'btn btn-primary' : '' }}"
[class.text-center]="actionList.length === 1"
>
<i [ngClass]="action.icon" [class.mr-1]="action.icon"></i>
<span *ngIf="action.icon; else ellipsis">{{ action.text | abpLocalization }}</span>
<ng-template #ellipsis>
<div abpEllipsis>{{ action.text | abpLocalization }}</div>
</ng-template>
</button>
</ng-container>
</ng-template>

36
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/components/grid-actions/grid-actions.component.ts

@ -0,0 +1,36 @@
import {
ChangeDetectionStrategy,
Component,
Injector,
Input,
TrackByFunction,
} from '@angular/core';
import { EntityAction, EntityActionList } from '../../models/entity-actions';
import { EXTENSIONS_ACTION_TYPE } from '../../tokens/extensions.token';
import { AbstractActionsComponent } from '../abstract-actions/abstract-actions.component';
@Component({
exportAs: 'abpGridActions',
selector: 'abp-grid-actions',
templateUrl: './grid-actions.component.html',
providers: [
{
provide: EXTENSIONS_ACTION_TYPE,
useValue: 'entityActions',
},
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class GridActionsComponent<R = any> extends AbstractActionsComponent<EntityActionList<R>> {
@Input() icon = 'fa fa-cog';
@Input() readonly index: number;
@Input() text = '';
readonly trackByFn: TrackByFunction<EntityAction<R>> = (_, item) => item.text;
constructor(injector: Injector) {
super(injector);
}
}

20
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/components/page-toolbar/page-toolbar.component.html

@ -0,0 +1,20 @@
<div class="row justify-content-end mx-n1" id="AbpContentToolbar">
<div class="col-auto px-1 pt-0 pt-md-2" *ngFor="let action of actionList; trackBy: trackByFn">
<ng-container *ngIf="action.visible(data)">
<ng-container *abpPermission="action.permission">
<ng-container *ngIf="action.component as component; else button">
<ng-container
*ngComponentOutlet="component; injector: createInjector(action)"
></ng-container>
</ng-container>
<ng-template #button>
<button (click)="action.action(data)" type="button" class="btn btn-primary btn-sm">
<i [ngClass]="action.icon" [class.mr-1]="action.icon"></i>
{{ action.text | abpLocalization }}
</button>
</ng-template>
</ng-container>
</ng-container>
</div>
</div>

49
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/components/page-toolbar/page-toolbar.component.ts

@ -0,0 +1,49 @@
import {
ChangeDetectionStrategy,
Component,
InjectFlags,
InjectionToken,
Injector,
TrackByFunction,
Type,
} from '@angular/core';
import { ToolbarActionList, ToolbarComponent } from '../../models/toolbar-actions';
import {
EXTENSIONS_ACTION_CALLBACK,
EXTENSIONS_ACTION_DATA,
EXTENSIONS_ACTION_TYPE,
} from '../../tokens/extensions.token';
import { AbstractActionsComponent } from '../abstract-actions/abstract-actions.component';
@Component({
exportAs: 'abpPageToolbar',
selector: 'abp-page-toolbar',
templateUrl: './page-toolbar.component.html',
providers: [
{
provide: EXTENSIONS_ACTION_TYPE,
useValue: 'toolbarActions',
},
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PageToolbarComponent<R = any> extends AbstractActionsComponent<ToolbarActionList<R>> {
readonly trackByFn: TrackByFunction<ToolbarComponent<R>> = (_, item) =>
item.action || item.component;
constructor(private readonly injector: Injector) {
super(injector);
}
createInjector(action: ToolbarComponent<R>): Injector {
const get = <T>(token: Type<T> | InjectionToken<T>, notFoundValue?: T, flags?: InjectFlags) => {
return token === EXTENSIONS_ACTION_DATA
? this.data
: token === EXTENSIONS_ACTION_CALLBACK
? (data = this.data) => action.action(data)
: this.getInjected.call(this.injector, token, notFoundValue, flags);
};
return { get };
}
}

1
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/constants/extra-properties.ts

@ -0,0 +1 @@
export const EXTRA_PROPERTIES_KEY = 'extraProperties';

19
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/directives/disabled.directive.ts

@ -0,0 +1,19 @@
import { Directive, Host, Input, OnChanges, SimpleChanges } from '@angular/core';
import { NgControl } from '@angular/forms';
@Directive({
selector: '[abpDisabled]',
})
export class DisabledDirective implements OnChanges {
@Input()
abpDisabled: boolean;
constructor(@Host() private ngControl: NgControl) {}
// Related issue: https://github.com/angular/angular/issues/35330
ngOnChanges({ abpDisabled }: SimpleChanges) {
if (this.ngControl.control && abpDisabled) {
this.ngControl.control[abpDisabled.currentValue ? 'disable' : 'enable']();
}
}
}

54
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/directives/prop-data.directive.ts

@ -0,0 +1,54 @@
import {
Directive,
Injector,
Input,
OnChanges,
OnDestroy,
TemplateRef,
ViewContainerRef,
} from '@angular/core';
import { PropData, PropList } from '../models/props';
@Directive({
exportAs: 'abpPropData',
selector: '[abpPropData]',
})
export class PropDataDirective<L extends PropList<any>> extends PropData<InferredData<L>>
implements OnChanges, OnDestroy {
/* tslint:disable:no-input-rename */
@Input('abpPropDataFromList') readonly propList: L;
@Input('abpPropDataWithRecord') readonly record: InferredData<L>['record'];
@Input('abpPropDataAtIndex') readonly index: number;
/* tslint:enable:no-input-rename */
readonly getInjected: InferredData<L>['getInjected'];
constructor(
private tempRef: TemplateRef<any>,
private vcRef: ViewContainerRef,
injector: Injector,
) {
super();
// tslint:disable-next-line
this.getInjected = injector.get.bind(injector);
}
ngOnChanges() {
this.vcRef.clear();
this.vcRef.createEmbeddedView(this.tempRef, {
$implicit: this.data,
index: 0,
});
}
ngOnDestroy() {
this.vcRef.clear();
}
}
type InferredData<L> = PropData<InferredRecord<L>>;
type InferredRecord<L> = L extends PropList<infer R> ? R : never;

15
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/enums/props.enum.ts

@ -0,0 +1,15 @@
export const enum ePropType {
Boolean = 'boolean',
Date = 'date',
DateTime = 'dateTime',
Email = 'email',
Enum = 'enum',
Hidden = 'hidden',
MultiSelect = 'multiselect',
Number = 'number',
Password = 'password',
String = 'string',
Text = 'text',
Time = 'time',
Typeahead = 'typeahead',
}

79
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/models/actions.ts

@ -0,0 +1,79 @@
/* tslint:disable:variable-name */
import { LinkedList } from '@abp/utils';
import { InjectFlags, InjectionToken, Type } from '@angular/core';
import { O } from 'ts-toolbelt';
export abstract class ActionList<R = any, A = Action<R>> extends LinkedList<A> {}
export abstract class ActionData<R = any> {
abstract getInjected: <T>(
token: Type<T> | InjectionToken<T>,
notFoundValue?: T,
flags?: InjectFlags,
) => T;
index?: number;
abstract record: R;
get data(): ReadonlyActionData<R> {
return {
getInjected: this.getInjected,
index: this.index,
record: this.record,
};
}
}
export type ReadonlyActionData<R = any> = O.Readonly<Omit<ActionData<R>, 'data'>>;
export abstract class Action<R = any> {
constructor(
public readonly permission: string,
public readonly visible: ActionPredicate<R> = _ => true,
public readonly action: ActionCallback<R> = _ => {},
) {}
}
export type ActionCallback<T, R = any> = (data?: Omit<ActionData<T>, 'data'>) => R;
export type ActionPredicate<T> = (data?: Omit<ActionData<T>, 'data'>) => boolean;
export abstract class ActionsFactory<C extends Actions<any>> {
protected abstract _ctor: Type<C>;
private contributorCallbacks: ActionContributorCallbacks<InferredActionList<C>> = {};
get(name: string): C {
this.contributorCallbacks[name] = this.contributorCallbacks[name] || [];
return new this._ctor(this.contributorCallbacks[name]);
}
}
export abstract class Actions<L extends ActionList> {
protected abstract _ctor: Type<L>;
get actions(): L {
const actionList = new this._ctor();
this.callbackList.forEach(callback => callback(actionList));
return actionList;
}
constructor(private readonly callbackList: ActionContributorCallback<L>[]) {}
addContributor(contributeCallback: ActionContributorCallback<L>) {
this.callbackList.push(contributeCallback);
}
clearContributors() {
while (this.callbackList.length) this.callbackList.pop();
}
}
export type ActionContributorCallbacks<L extends ActionList<any>> = Record<
string,
ActionContributorCallback<L>[]
>;
export type ActionContributorCallback<L extends ActionList<any>> = (actionList: L) => any;
type InferredActionList<C> = C extends Actions<infer L> ? L : never;

53
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/models/entity-actions.ts

@ -0,0 +1,53 @@
/* tslint:disable:variable-name */
import { Type } from '@angular/core';
import { O } from 'ts-toolbelt';
import {
Action,
ActionContributorCallback,
ActionContributorCallbacks,
ActionList,
Actions,
ActionsFactory,
} from './actions';
export class EntityActionList<R = any> extends ActionList<R, EntityAction<R>> {}
export class EntityActions<R = any> extends Actions<EntityActionList<R>> {
protected _ctor: Type<EntityActionList<R>> = EntityActionList;
}
export class EntityActionsFactory<R = any> extends ActionsFactory<EntityActions<R>> {
protected _ctor: Type<EntityActions<R>> = EntityActions;
}
export class EntityAction<R = any> extends Action<R> {
readonly text: string;
readonly icon: string;
constructor(options: EntityActionOptions<R>) {
super(options.permission, options.visible, options.action);
this.text = options.text;
this.icon = options.icon || '';
}
static create<R = any>(options: EntityActionOptions<R>) {
return new EntityAction<R>(options);
}
static createMany<R = any>(arrayOfOptions: EntityActionOptions<R>[]) {
return arrayOfOptions.map(EntityAction.create);
}
}
export type EntityActionOptions<R = any> = O.Optional<
O.Writable<EntityAction<R>>,
'permission' | 'visible' | 'icon'
>;
export type EntityActionDefaults<R = any> = Record<string, EntityAction<R>[]>;
export type EntityActionContributorCallback<R = any> = ActionContributorCallback<
EntityActionList<R>
>;
export type EntityActionContributorCallbacks<R = any> = ActionContributorCallbacks<
EntityActionList<R>
>;

71
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/models/entity-props.ts

@ -0,0 +1,71 @@
/* tslint:disable:variable-name */
import { Type } from '@angular/core';
import { Observable, of } from 'rxjs';
import { O } from 'ts-toolbelt';
import {
Prop,
PropCallback,
PropContributorCallback,
PropContributorCallbacks,
PropList,
Props,
PropsFactory,
} from './props';
import { ActionCallback } from './actions';
export class EntityPropList<R = any> extends PropList<R, EntityProp<R>> {}
export class EntityProps<R = any> extends Props<EntityPropList<R>> {
protected _ctor: Type<EntityPropList<R>> = EntityPropList;
}
export class EntityPropsFactory<R = any> extends PropsFactory<EntityProps<R>> {
protected _ctor: Type<EntityProps<R>> = EntityProps;
}
export class EntityProp<R = any> extends Prop<R> {
readonly columnWidth: number | undefined;
readonly sortable: boolean;
readonly valueResolver: PropCallback<R, Observable<any>>;
readonly action: ActionCallback<R>;
constructor(options: EntityPropOptions<R>) {
super(
options.type,
options.name,
options.displayName,
options.permission,
options.visible,
options.isExtra,
);
this.columnWidth = options.columnWidth;
this.sortable = options.sortable || false;
this.valueResolver = options.valueResolver || (data => of(data.record[this.name]));
this.action = options.action;
}
static create<R = any>(options: EntityPropOptions<R>) {
return new EntityProp<R>(options);
}
static createMany<R = any>(arrayOfOptions: EntityPropOptions<R>[]) {
return arrayOfOptions.map(EntityProp.create);
}
}
export type EntityPropOptions<R = any> = O.Optional<
O.Writable<EntityProp<R>>,
| 'permission'
| 'visible'
| 'displayName'
| 'isExtra'
| 'columnWidth'
| 'sortable'
| 'valueResolver'
| 'action'
>;
export type EntityPropDefaults<R = any> = Record<string, EntityProp<R>[]>;
export type EntityPropContributorCallback<R = any> = PropContributorCallback<EntityPropList<R>>;
export type EntityPropContributorCallbacks<R = any> = PropContributorCallbacks<EntityPropList<R>>;

109
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/models/form-props.ts

@ -0,0 +1,109 @@
/* tslint:disable:variable-name */
import { ABP } from '@abp/ng.core';
import { Injector, Type } from '@angular/core';
import { AsyncValidatorFn, ValidatorFn } from '@angular/forms';
import { Observable } from 'rxjs';
import { O } from 'ts-toolbelt';
import {
Prop,
PropCallback,
PropContributorCallback,
PropContributorCallbacks,
PropData,
PropList,
PropPredicate,
Props,
PropsFactory,
} from './props';
export class FormPropList<R = any> extends PropList<R, FormProp<R>> {}
export class FormProps<R = any> extends Props<FormPropList<R>> {
protected _ctor: Type<FormPropList<R>> = FormPropList;
}
export class CreateFormPropsFactory<R = any> extends PropsFactory<FormProps<R>> {
protected _ctor: Type<FormProps<R>> = FormProps;
}
export class EditFormPropsFactory<R = any> extends PropsFactory<FormProps<R>> {
protected _ctor: Type<FormProps<R>> = FormProps;
}
export class FormProp<R = any> extends Prop<R> {
readonly validators: PropCallback<R, ValidatorFn[]>;
readonly asyncValidators: PropCallback<R, AsyncValidatorFn[]>;
readonly disabled: PropPredicate<R>;
readonly readonly: PropPredicate<R>;
readonly autocomplete: string;
readonly defaultValue: boolean | number | string | Date;
readonly options: PropCallback<R, Observable<ABP.Option<any>[]>> | undefined;
readonly id: string | undefined;
constructor(options: FormPropOptions<R>) {
super(
options.type,
options.name,
options.displayName,
options.permission,
options.visible,
options.isExtra,
);
this.asyncValidators = options.asyncValidators || (_ => []);
this.validators = options.validators || (_ => []);
this.disabled = options.disabled || (_ => false);
this.readonly = options.readonly || (_ => false);
this.autocomplete = options.autocomplete || 'off';
this.options = options.options;
this.id = options.id || options.name;
const defaultValue = options.defaultValue;
this.defaultValue = isFalsyValue(defaultValue) ? defaultValue : defaultValue || null;
}
static create<R = any>(options: FormPropOptions<R>) {
return new FormProp<R>(options);
}
static createMany<R = any>(arrayOfOptions: FormPropOptions<R>[]) {
return arrayOfOptions.map(FormProp.create);
}
}
export class FormPropData<R = any> extends PropData<R> {
getInjected: PropData<R>['getInjected'];
constructor(injector: Injector, public readonly record: R) {
super();
// tslint:disable-next-line
this.getInjected = injector.get.bind(injector);
}
}
export type FormPropOptions<R = any> = O.Optional<
O.Writable<FormProp<R>>,
| 'permission'
| 'visible'
| 'displayName'
| 'isExtra'
| 'validators'
| 'asyncValidators'
| 'disabled'
| 'readonly'
| 'autocomplete'
| 'defaultValue'
| 'options'
| 'id'
>;
export type CreateFormPropDefaults<R = any> = Record<string, FormProp<R>[]>;
export type CreateFormPropContributorCallback<R = any> = PropContributorCallback<FormPropList<R>>;
export type CreateFormPropContributorCallbacks<R = any> = PropContributorCallbacks<FormPropList<R>>;
export type EditFormPropDefaults<R = any> = Record<string, FormProp<R>[]>;
export type EditFormPropContributorCallback<R = any> = PropContributorCallback<FormPropList<R>>;
export type EditFormPropContributorCallbacks<R = any> = PropContributorCallbacks<FormPropList<R>>;
function isFalsyValue(defaultValue: FormProp['defaultValue']): boolean {
return [0, '', false].indexOf(defaultValue as any) > -1;
}

108
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/models/internal/object-extensions.ts

@ -0,0 +1,108 @@
import { ePropType } from '../../enums/props.enum';
import { EntityPropList } from '../entity-props';
import { FormPropList } from '../form-props';
import { PropContributorCallbacks } from '../props';
export type DisplayNameGeneratorFn = (
displayName: LocalizableStringDto,
fallback: LocalizableStringDto,
) => string;
export type EntityExtensions = Record<string, EntityExtensionDto>;
export interface EntityExtensionDto {
properties: EntityExtensionProperties;
configuration: Record<string, object>;
}
export type EntityExtensionProperties = Record<string, ExtensionPropertyDto>;
export interface ExtensionEnumDto {
fields: ExtensionEnumFieldDto[];
localizationResource?: string;
transformed?: any;
}
export interface ExtensionEnumFieldDto {
name?: string;
value: any;
}
export interface ExtensionPropertyApiCreateDto {
isAvailable: boolean;
}
export interface ExtensionPropertyApiDto {
onGet: ExtensionPropertyApiGetDto;
onCreate: ExtensionPropertyApiCreateDto;
onUpdate: ExtensionPropertyApiUpdateDto;
}
export interface ExtensionPropertyApiGetDto {
isAvailable: boolean;
}
export interface ExtensionPropertyApiUpdateDto {
isAvailable: boolean;
}
export interface ExtensionPropertyAttributeDto {
typeSimple?: string;
config: Record<string, any>;
}
export interface ExtensionPropertyDto {
type?: string;
typeSimple?: ePropType;
displayName: LocalizableStringDto;
api: ExtensionPropertyApiDto;
ui: ExtensionPropertyUiDto;
attributes: ExtensionPropertyAttributeDto[];
configuration: Record<string, any>;
defaultValue: any;
}
export interface ExtensionPropertyUiDto {
onTable: ExtensionPropertyUiTableDto;
onCreateForm: ExtensionPropertyUiFormDto;
onEditForm: ExtensionPropertyUiFormDto;
lookup?: ExtensionPropertyUiLookupDto;
}
export interface ExtensionPropertyUiFormDto {
isVisible: boolean;
}
export interface ExtensionPropertyUiLookupDto {
url?: string;
resultListPropertyName?: string;
displayPropertyName?: string;
valuePropertyName?: string;
filterParamName?: string;
}
export interface ExtensionPropertyUiTableDto {
isSortable?: boolean;
isVisible: boolean;
}
export interface LocalizableStringDto {
name?: string;
resource?: string;
}
export interface ModuleExtensionDto {
entities: Record<string, EntityExtensionDto>;
configuration: Record<string, object>;
}
export interface ObjectExtensionsDto {
modules: Record<string, ModuleExtensionDto>;
enums: Record<string, ExtensionEnumDto>;
}
export interface PropContributors<T = any> {
prop: PropContributorCallbacks<EntityPropList<T>>;
createForm: PropContributorCallbacks<FormPropList<T>>;
editForm: PropContributorCallbacks<FormPropList<T>>;
}

3
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/models/object-extensions.ts

@ -0,0 +1,3 @@
import * as ObjectExtensions from './internal/object-extensions';
export { ObjectExtensions };

85
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/models/props.ts

@ -0,0 +1,85 @@
/* tslint:disable:variable-name */
import { LinkedList } from '@abp/utils';
import { InjectFlags, InjectionToken, Type } from '@angular/core';
import { O } from 'ts-toolbelt';
import { ePropType } from '../enums/props.enum';
export abstract class PropList<R = any, A = Prop<R>> extends LinkedList<A> {}
export abstract class PropData<R = any> {
abstract getInjected: <T>(
token: Type<T> | InjectionToken<T>,
notFoundValue?: T,
flags?: InjectFlags,
) => T;
index?: number;
abstract record: R;
get data(): ReadonlyPropData<R> {
return {
getInjected: this.getInjected,
index: this.index,
record: this.record,
};
}
}
export type ReadonlyPropData<R = any> = O.Readonly<Omit<PropData<R>, 'data'>>;
export abstract class Prop<R = any> {
constructor(
public readonly type: ePropType,
public readonly name: string,
public readonly displayName: string,
public readonly permission: string,
public readonly visible: PropPredicate<R> = _ => true,
public readonly isExtra = false,
) {
this.displayName = this.displayName || this.name;
}
}
export type PropCallback<T, R = any> = (data?: Omit<PropData<T>, 'data'>, auxData?: any) => R;
export type PropPredicate<T> = (data?: Omit<PropData<T>, 'data'>, auxData?: any) => boolean;
export abstract class PropsFactory<C extends Props<any>> {
protected abstract _ctor: Type<C>;
private contributorCallbacks: PropContributorCallbacks<InferredPropList<C>> = {};
get(name: string): C {
this.contributorCallbacks[name] = this.contributorCallbacks[name] || [];
return new this._ctor(this.contributorCallbacks[name]);
}
}
export abstract class Props<L extends PropList> {
protected abstract _ctor: Type<L>;
get props(): L {
const propList = new this._ctor();
this.callbackList.forEach(callback => callback(propList));
return propList;
}
constructor(private readonly callbackList: PropContributorCallback<L>[]) {}
addContributor(contributeCallback: PropContributorCallback<L>) {
this.callbackList.push(contributeCallback);
}
clearContributors() {
while (this.callbackList.length) this.callbackList.pop();
}
}
export type PropContributorCallbacks<L extends PropList<any>> = Record<
string,
PropContributorCallback<L>[]
>;
export type PropContributorCallback<L extends PropList<any>> = (propList: L) => any;
type InferredPropList<C> = C extends Props<infer L> ? L : never;

78
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/models/toolbar-actions.ts

@ -0,0 +1,78 @@
/* tslint:disable:variable-name */
import { Type } from '@angular/core';
import { O } from 'ts-toolbelt';
import {
Action,
ActionContributorCallback,
ActionContributorCallbacks,
ActionList,
Actions,
ActionsFactory,
} from './actions';
export class ToolbarActionList<R = any> extends ActionList<
R,
ToolbarAction<R> | ToolbarComponent<R>
> {}
export class ToolbarActions<R = any> extends Actions<ToolbarActionList<R>> {
protected _ctor: Type<ToolbarActionList<R>> = ToolbarActionList;
}
export class ToolbarActionsFactory<R = any> extends ActionsFactory<ToolbarActions<R>> {
protected _ctor: Type<ToolbarActions<R>> = ToolbarActions;
}
export class ToolbarAction<R = any> extends Action<R> {
readonly text: string;
readonly icon: string;
constructor(options: ToolbarActionOptions<R>) {
super(options.permission, options.visible, options.action);
this.text = options.text;
this.icon = options.icon || '';
}
static create<R = any>(options: ToolbarActionOptions<R>) {
return new ToolbarAction<R>(options);
}
static createMany<R = any>(arrayOfOptions: ToolbarActionOptions<R>[]) {
return arrayOfOptions.map(ToolbarAction.create);
}
}
export class ToolbarComponent<R = any> extends Action<R> {
readonly component: Type<any>;
constructor(options: ToolbarComponentOptions<R>) {
super(options.permission, options.visible, options.action);
this.component = options.component;
}
static create<R = any>(options: ToolbarComponentOptions<R>) {
return new ToolbarComponent<R>(options);
}
static createMany<R = any>(arrayOfOptions: ToolbarComponentOptions<R>[]) {
return arrayOfOptions.map(ToolbarComponent.create);
}
}
export type ToolbarActionOptions<R = any> = O.Optional<
O.Writable<ToolbarAction<R>>,
'permission' | 'visible' | 'icon'
>;
export type ToolbarComponentOptions<R = any> = O.Optional<
O.Writable<ToolbarComponent<R>>,
'permission' | 'visible' | 'action'
>;
export type ToolbarActionDefaults<R = any> = Record<string, ToolbarAction<R>[]>;
export type ToolbarActionContributorCallback<R = any> = ActionContributorCallback<
ToolbarActionList<R>
>;
export type ToolbarActionContributorCallbacks<R = any> = ActionContributorCallbacks<
ToolbarActionList<R>
>;

16
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/services/extensions.service.ts

@ -0,0 +1,16 @@
import { Injectable } from '@angular/core';
import { EntityActionsFactory } from '../models/entity-actions';
import { EntityPropsFactory } from '../models/entity-props';
import { CreateFormPropsFactory, EditFormPropsFactory } from '../models/form-props';
import { ToolbarActionsFactory } from '../models/toolbar-actions';
@Injectable({
providedIn: 'root',
})
export class ExtensionsService<R = any> {
readonly entityActions = new EntityActionsFactory<R>();
readonly toolbarActions = new ToolbarActionsFactory<R[]>();
readonly entityProps = new EntityPropsFactory<R>();
readonly createFormProps = new CreateFormPropsFactory<R>();
readonly editFormProps = new EditFormPropsFactory<R>();
}

13
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/tokens/extensions.token.ts

@ -0,0 +1,13 @@
import { InjectionToken } from '@angular/core';
import { ActionCallback, ReadonlyActionData as ActionData } from '../models/actions';
import { ExtensionsService } from '../services/extensions.service';
export const EXTENSIONS_IDENTIFIER = new InjectionToken<string>('EXTENSIONS_IDENTIFIER');
export type ActionKeys = Extract<'entityActions' | 'toolbarActions', keyof ExtensionsService>;
export const EXTENSIONS_ACTION_TYPE = new InjectionToken<ActionKeys>('EXTENSIONS_ACTION_TYPE');
export const EXTENSIONS_ACTION_DATA = new InjectionToken<ActionData>('EXTENSIONS_ACTION_DATA');
export const EXTENSIONS_ACTION_CALLBACK = new InjectionToken<ActionCallback<unknown>>(
'EXTENSIONS_ACTION_DATA',
);

56
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/ui-extensions.module.ts

@ -0,0 +1,56 @@
import { CoreModule } from '@abp/ng.core';
import { ThemeSharedModule } from '@abp/ng.theme.shared';
import { NgModule } from '@angular/core';
import {
NgbDatepickerModule,
NgbDropdownModule,
NgbTimepickerModule,
NgbTypeaheadModule,
} from '@ng-bootstrap/ng-bootstrap';
import { NgxValidateCoreModule } from '@ngx-validate/core';
import { DateTimePickerComponent } from './components/date-time-picker/date-time-picker.component';
import { ExtensibleFormPropComponent } from './components/extensible-form/extensible-form-prop.component';
import { ExtensibleFormComponent } from './components/extensible-form/extensible-form.component';
import { ExtensibleTableComponent } from './components/extensible-table/extensible-table.component';
import { GridActionsComponent } from './components/grid-actions/grid-actions.component';
import { PageToolbarComponent } from './components/page-toolbar/page-toolbar.component';
import { DisabledDirective } from './directives/disabled.directive';
import { PropDataDirective } from './directives/prop-data.directive';
@NgModule({
exports: [
DateTimePickerComponent,
PageToolbarComponent,
GridActionsComponent,
ExtensibleFormComponent,
ExtensibleTableComponent,
PropDataDirective,
DisabledDirective,
],
declarations: [
DateTimePickerComponent,
PageToolbarComponent,
GridActionsComponent,
ExtensibleFormPropComponent,
ExtensibleFormComponent,
ExtensibleTableComponent,
PropDataDirective,
DisabledDirective,
],
imports: [
CoreModule,
ThemeSharedModule,
NgxValidateCoreModule,
NgbDatepickerModule,
NgbDropdownModule,
NgbTimepickerModule,
NgbTypeaheadModule,
],
})
export class BaseUiExtensionsModule {}
@NgModule({
exports: [BaseUiExtensionsModule],
imports: [BaseUiExtensionsModule],
})
export class UiExtensionsModule {}

49
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/utils/actions.util.ts

@ -0,0 +1,49 @@
import { ActionContributorCallback, ActionList, ActionsFactory } from '../models/actions';
import {
EntityActionContributorCallbacks,
EntityActionDefaults,
EntityActions,
EntityActionsFactory,
} from '../models/entity-actions';
import {
ToolbarActionContributorCallbacks,
ToolbarActionDefaults,
ToolbarActions,
ToolbarActionsFactory,
} from '../models/toolbar-actions';
export function mergeWithDefaultActions<F extends ActionsFactory<any>>(
extension: F,
defaultActions: InferredActionDefaults<F>,
...contributors: InferredActionContributorCallbacks<F>[]
) {
Object.keys(defaultActions).forEach((name: string) => {
const actions: InferredActions<F> = extension.get(name);
actions.clearContributors();
actions.addContributor((actionList: ActionList) =>
actionList.addManyTail(defaultActions[name]),
);
contributors.forEach(contributor =>
(contributor[name] || []).forEach((callback: ActionContributorCallback<any>) =>
actions.addContributor(callback),
),
);
});
}
type InferredActionDefaults<F> = F extends EntityActionsFactory<infer RE>
? EntityActionDefaults<RE>
: F extends ToolbarActionsFactory<infer RT>
? ToolbarActionDefaults<RT>
: never;
type InferredActionContributorCallbacks<F> = F extends EntityActionsFactory<infer RE>
? EntityActionContributorCallbacks<RE>
: F extends ToolbarActionsFactory<infer RT>
? ToolbarActionContributorCallbacks<RT>
: never;
type InferredActions<F> = F extends EntityActionsFactory<infer RE>
? EntityActions<RE>
: F extends ToolbarActionsFactory<infer RT>
? ToolbarActions<RT>
: never;

73
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/utils/enum.util.ts

@ -0,0 +1,73 @@
import { ABP, LocalizationService } from '@abp/ng.core';
import { merge, Observable, of } from 'rxjs';
import { map } from 'rxjs/operators';
import { EXTRA_PROPERTIES_KEY } from '../constants/extra-properties';
import { ObjectExtensions } from '../models/object-extensions';
import { PropCallback } from '../models/props';
export function createEnum(members: ObjectExtensions.ExtensionEnumFieldDto[]) {
const enumObject: any = {};
members.forEach(({ name, value }) => {
enumObject[(enumObject[name] = value as any)] = name;
});
return enumObject;
}
export function createEnumValueResolver<T = any>(
enumType: string,
lookupEnum: ObjectExtensions.ExtensionEnumDto,
propName: string,
): PropCallback<T, Observable<string>> {
return data => {
const value = data.record[EXTRA_PROPERTIES_KEY][propName];
const key = lookupEnum.transformed[value];
const l10n = data.getInjected(LocalizationService);
const localizeEnum = createEnumLocalizer(l10n, enumType, lookupEnum);
return createLocalizationStream(l10n, localizeEnum(key));
};
}
export function createEnumOptions<T = any>(
enumType: string,
lookupEnum: ObjectExtensions.ExtensionEnumDto,
): PropCallback<T, Observable<ABP.Option<any>[]>> {
return data => {
const l10n = data.getInjected(LocalizationService);
const localizeEnum = createEnumLocalizer(l10n, enumType, lookupEnum);
return createLocalizationStream(
l10n,
lookupEnum.fields.map(({ name, value }) => ({
key: localizeEnum(name),
value,
})),
);
};
}
function createLocalizationStream(l10n: LocalizationService, mapTarget: any) {
return merge(of(null), l10n.languageChange$).pipe(map(() => mapTarget));
}
function createEnumLocalizer(
l10n: LocalizationService,
enumType: string,
lookupEnum: ObjectExtensions.ExtensionEnumDto,
): (key: string) => string {
const resource = lookupEnum.localizationResource;
const shortType = getShortEnumType(enumType);
return key =>
l10n.localizeWithFallbackSync(
[resource],
['Enum:' + shortType + '.' + key, shortType + '.' + key, key],
key,
);
}
function getShortEnumType(enumType: string): string {
return enumType.split('.').pop();
}

3
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/utils/factory.util.ts

@ -0,0 +1,3 @@
export function selfFactory(dependency?: any) {
return dependency;
}

61
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/utils/form-props.util.ts

@ -0,0 +1,61 @@
import { FormControl, FormGroup } from '@angular/forms';
import { DateTimeAdapter } from '../adapters/date-time.adapter';
import { DateAdapter } from '../adapters/date.adapter';
import { TimeAdapter } from '../adapters/time.adapter';
import { EXTRA_PROPERTIES_KEY } from '../constants/extra-properties';
import { ePropType } from '../enums/props.enum';
import { FormPropList } from '../models/form-props';
import { PropData } from '../models/props';
import { ExtensionsService } from '../services/extensions.service';
import { EXTENSIONS_IDENTIFIER } from '../tokens/extensions.token';
export function generateFormFromProps<R extends any>(data: PropData<R>) {
const extensions = data.getInjected(ExtensionsService);
const identifier = data.getInjected(EXTENSIONS_IDENTIFIER);
const form = new FormGroup({});
const extraForm = new FormGroup({});
form.addControl(EXTRA_PROPERTIES_KEY, extraForm);
const record = data.record || {};
const type = JSON.stringify(record) === '{}' ? 'create' : 'edit';
const props: FormPropList<R> = extensions[`${type}FormProps`].get(identifier).props;
const extraProperties = record[EXTRA_PROPERTIES_KEY] || {};
props.forEach(({ value: prop }) => {
const name = prop.name;
const isExtraProperty = prop.isExtra || name in extraProperties;
let value = isExtraProperty ? extraProperties[name] : name in record ? record[name] : undefined;
if (typeof value === 'undefined') value = prop.defaultValue;
if (value) {
let adapter: DateAdapter | TimeAdapter | DateTimeAdapter;
switch (prop.type) {
case ePropType.Date:
adapter = new DateAdapter();
value = adapter.toModel(adapter.fromModel(value));
break;
case ePropType.Time:
adapter = new TimeAdapter();
value = adapter.toModel(adapter.fromModel(value));
break;
case ePropType.DateTime:
adapter = new DateTimeAdapter();
value = adapter.toModel(adapter.fromModel(value) as any);
break;
default:
break;
}
}
const formControl = new FormControl(value, {
asyncValidators: prop.asyncValidators(data),
validators: prop.validators(data),
});
(isExtraProperty ? extraForm : form).addControl(name, formControl);
});
return form;
}

33
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/utils/localization.util.ts

@ -0,0 +1,33 @@
import {
ApplicationLocalizationConfigurationDto,
createLocalizationPipeKeyGenerator,
} from '@abp/ng.core';
import { ObjectExtensions } from '../models/object-extensions';
export function createDisplayNameLocalizationPipeKeyGenerator(
localization: ApplicationLocalizationConfigurationDto,
) {
const generateLocalizationPipeKey = createLocalizationPipeKeyGenerator(localization);
return (
displayName: ObjectExtensions.LocalizableStringDto,
fallback: ObjectExtensions.LocalizableStringDto,
) => {
if (displayName && displayName.name)
return generateLocalizationPipeKey(
[displayName.resource],
[displayName.name],
displayName.name,
);
const key = generateLocalizationPipeKey(
[fallback.resource],
['DisplayName:' + fallback.name],
undefined,
);
if (key) return key;
return generateLocalizationPipeKey([fallback.resource], [fallback.name], fallback.name);
};
}

62
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/utils/props.util.ts

@ -0,0 +1,62 @@
import { of } from 'rxjs';
import { EXTRA_PROPERTIES_KEY } from '../constants/extra-properties';
import {
EntityPropContributorCallbacks,
EntityPropDefaults,
EntityProps,
EntityPropsFactory,
} from '../models/entity-props';
import {
CreateFormPropContributorCallbacks,
CreateFormPropDefaults,
CreateFormPropsFactory,
EditFormPropContributorCallbacks,
EditFormPropDefaults,
EditFormPropsFactory,
FormProps,
} from '../models/form-props';
import { PropContributorCallback, PropData, PropList, PropsFactory } from '../models/props';
export function createExtraPropertyValueResolver<T>(name: string) {
return (data?: PropData<T>) => of(data.record[EXTRA_PROPERTIES_KEY][name]);
}
export function mergeWithDefaultProps<F extends PropsFactory<any>>(
extension: F,
defaultProps: InferredPropDefaults<F>,
...contributors: InferredPropContributorCallbacks<F>[]
) {
Object.keys(defaultProps).forEach((name: string) => {
const props: InferredProps<F> = extension.get(name);
props.clearContributors();
props.addContributor((propList: PropList) => propList.addManyTail(defaultProps[name]));
contributors.forEach(contributor =>
(contributor[name] || []).forEach((callback: PropContributorCallback<any>) =>
props.addContributor(callback),
),
);
});
}
type InferredPropDefaults<F> = F extends EntityPropsFactory<infer RE>
? EntityPropDefaults<RE>
: F extends CreateFormPropsFactory<infer RCF>
? CreateFormPropDefaults<RCF>
: F extends EditFormPropsFactory<infer REF>
? EditFormPropDefaults<REF>
: never;
type InferredPropContributorCallbacks<F> = F extends EntityPropsFactory<infer RE>
? EntityPropContributorCallbacks<RE>
: F extends CreateFormPropsFactory<infer RCF>
? CreateFormPropContributorCallbacks<RCF>
: F extends EditFormPropsFactory<infer REF>
? EditFormPropContributorCallbacks<REF>
: never;
type InferredProps<F> = F extends EntityPropsFactory<infer RE>
? EntityProps<RE>
: F extends CreateFormPropsFactory<infer RCF>
? FormProps<RCF>
: F extends EditFormPropsFactory<infer REF>
? FormProps<REF>
: never;

200
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/utils/state.util.ts

@ -0,0 +1,200 @@
import {
ABP,
ApplicationLocalizationConfigurationDto,
ConfigStateService,
ExtensionPropertyUiLookupDto,
} from '@abp/ng.core';
import { Observable, pipe, zip } from 'rxjs';
import { filter, map, switchMap, take } from 'rxjs/operators';
import { ePropType } from '../enums/props.enum';
import { EntityProp, EntityPropList } from '../models/entity-props';
import { FormProp, FormPropList } from '../models/form-props';
import { ObjectExtensions } from '../models/object-extensions';
import { PropCallback } from '../models/props';
import { createEnum, createEnumOptions, createEnumValueResolver } from './enum.util';
import { createDisplayNameLocalizationPipeKeyGenerator } from './localization.util';
import { createExtraPropertyValueResolver } from './props.util';
import {
createTypeaheadDisplayNameGenerator,
createTypeaheadOptions,
getTypeaheadType,
hasTypeaheadTextSuffix,
} from './typeahead.util';
import { getValidatorsFromProperty } from './validation.util';
function selectObjectExtensions(
configState: ConfigStateService,
): Observable<ObjectExtensions.ObjectExtensionsDto> {
return configState.getOne$('objectExtensions');
}
function selectLocalization(
configState: ConfigStateService,
): Observable<ApplicationLocalizationConfigurationDto> {
return configState.getOne$('localization');
}
function selectEnums(
configState: ConfigStateService,
): Observable<Record<string, ObjectExtensions.ExtensionEnumDto>> {
return selectObjectExtensions(configState).pipe(
map((extensions: ObjectExtensions.ObjectExtensionsDto) =>
Object.keys(extensions.enums).reduce((acc, key) => {
const { fields, localizationResource } = extensions.enums[key];
acc[key] = {
fields,
localizationResource,
transformed: createEnum(fields),
};
return acc;
}, {} as Record<string, ObjectExtensions.ExtensionEnumDto>),
),
);
}
export function getObjectExtensionEntitiesFromStore(
configState: ConfigStateService,
moduleKey: string,
) {
return selectObjectExtensions(configState).pipe(
map(extensions => {
if (!extensions) return null;
return (extensions.modules[moduleKey] || ({} as ObjectExtensions.ModuleExtensionDto))
.entities;
}),
map(entities => (isUndefined(entities) ? {} : entities)),
filter<ObjectExtensions.EntityExtensions>(Boolean),
take(1),
);
}
export function mapEntitiesToContributors<T = any>(
configState: ConfigStateService,
resource: string,
) {
return pipe(
switchMap(entities =>
zip(selectLocalization(configState), selectEnums(configState)).pipe(
map(([localization, enums]) => {
const generateDisplayName = createDisplayNameLocalizationPipeKeyGenerator(localization);
return Object.keys(entities).reduce(
(acc, key: keyof ObjectExtensions.EntityExtensions) => {
acc.prop[key] = [];
acc.createForm[key] = [];
acc.editForm[key] = [];
const entity: ObjectExtensions.EntityExtensionDto = entities[key];
if (!entity) return acc;
const properties = entity.properties;
if (!properties) return acc;
const mapPropertiesToContributors = createPropertiesToContributorsMapper<T>(
generateDisplayName,
resource,
enums,
);
return mapPropertiesToContributors(properties, acc, key);
},
{
prop: {},
createForm: {},
editForm: {},
} as ObjectExtensions.PropContributors,
);
}),
),
),
take(1),
);
}
function createPropertiesToContributorsMapper<T = any>(
generateDisplayName: ObjectExtensions.DisplayNameGeneratorFn,
resource: string,
enums: Record<string, ObjectExtensions.ExtensionEnumDto>,
) {
return (
properties: ObjectExtensions.EntityExtensionProperties,
contributors: ObjectExtensions.PropContributors<T>,
key: string,
) => {
const isExtra = true;
const generateTypeaheadDisplayName = createTypeaheadDisplayNameGenerator(
generateDisplayName,
properties,
);
Object.keys(properties).forEach((name: string) => {
const property = properties[name];
const propName = name;
const lookup = property.ui.lookup || ({} as ExtensionPropertyUiLookupDto);
const type = getTypeaheadType(lookup, name) || getTypeFromProperty(property);
const generateDN = hasTypeaheadTextSuffix(name)
? generateTypeaheadDisplayName
: generateDisplayName;
const displayName = generateDN(property.displayName, { name, resource });
if (property.ui.onTable.isVisible) {
const sortable = Boolean(property.ui.onTable.isSortable);
const columnWidth = type === ePropType.Boolean ? 150 : 250;
const valueResolver =
type === ePropType.Enum
? createEnumValueResolver(property.type, enums[property.type], propName)
: createExtraPropertyValueResolver<T>(propName);
const entityProp = new EntityProp<T>({
type,
name: propName,
displayName,
sortable,
columnWidth,
valueResolver,
isExtra,
});
const contributor = (propList: EntityPropList<T>) => propList.addTail(entityProp);
contributors.prop[key].push(contributor);
}
const isOnCreateForm = property.ui.onCreateForm.isVisible;
const isOnEditForm = property.ui.onEditForm.isVisible;
if (isOnCreateForm || isOnEditForm) {
const defaultValue = property.defaultValue;
const validators = () => getValidatorsFromProperty(property);
let options: PropCallback<any, Observable<ABP.Option<any>[]>>;
if (type === ePropType.Enum) options = createEnumOptions(propName, enums[property.type]);
else if (type === ePropType.Typeahead) options = createTypeaheadOptions(lookup);
const formProp = new FormProp({
type,
name: propName,
displayName,
options,
defaultValue,
validators,
isExtra,
});
const formContributor = (propList: FormPropList<T>) => propList.addTail(formProp);
if (isOnCreateForm) contributors.createForm[key].push(formContributor);
if (isOnEditForm) contributors.editForm[key].push(formContributor);
}
});
return contributors;
};
}
function getTypeFromProperty(property: ObjectExtensions.ExtensionPropertyDto): ePropType {
return (property.typeSimple.replace(/\?$/, '') as string) as ePropType;
}
function isUndefined(obj: any): obj is undefined {
return typeof obj === 'undefined';
}

72
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/utils/typeahead.util.ts

@ -0,0 +1,72 @@
import { ABP, ExtensionPropertyUiLookupDto, RestService } from '@abp/ng.core';
import { Observable, of } from 'rxjs';
import { map } from 'rxjs/operators';
import { ePropType } from '../enums/props.enum';
import { ObjectExtensions } from '../models/object-extensions';
import { PropCallback } from '../models/props';
const TYPEAHEAD_TEXT_SUFFIX = '_Text';
const TYPEAHEAD_TEXT_SUFFIX_REGEX = /_Text$/;
export function createTypeaheadOptions(
lookup: ExtensionPropertyUiLookupDto,
): PropCallback<any, Observable<ABP.Option<any>[]>> {
return (data, searchText) =>
searchText
? data
.getInjected(RestService)
.request(
{
method: 'GET',
url: lookup.url,
params: {
[lookup.filterParamName]: searchText,
},
},
{ apiName: 'Default' },
)
.pipe(
map(response => {
const list = response[lookup.resultListPropertyName];
const mapToOption = (item: any) => ({
key: item[lookup.displayPropertyName],
value: item[lookup.valuePropertyName],
});
return list.map(mapToOption);
}),
)
: of([]);
}
export function getTypeaheadType(lookup: ExtensionPropertyUiLookupDto, name: string) {
return Boolean(lookup.url)
? ePropType.Typeahead
: name.endsWith(TYPEAHEAD_TEXT_SUFFIX)
? ePropType.Hidden
: undefined;
}
export function createTypeaheadDisplayNameGenerator(
displayNameGeneratorFn: ObjectExtensions.DisplayNameGeneratorFn,
properties: ObjectExtensions.EntityExtensionProperties,
): ObjectExtensions.DisplayNameGeneratorFn {
return (displayName, fallback) => {
const name = removeTypeaheadTextSuffix(fallback.name);
return displayNameGeneratorFn(displayName || properties[name].displayName, {
name,
resource: fallback.resource,
});
};
}
export function addTypeaheadTextSuffix(name: string) {
return name + TYPEAHEAD_TEXT_SUFFIX;
}
export function hasTypeaheadTextSuffix(name: string) {
return TYPEAHEAD_TEXT_SUFFIX_REGEX.test(name);
}
export function removeTypeaheadTextSuffix(name: string) {
return name.replace(TYPEAHEAD_TEXT_SUFFIX_REGEX, '');
}

16
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/lib/utils/validation.util.ts

@ -0,0 +1,16 @@
import { AbpValidators } from '@abp/ng.core';
import { ValidatorFn } from '@angular/forms';
import { ObjectExtensions } from '../models/object-extensions';
export function getValidatorsFromProperty(
property: ObjectExtensions.ExtensionPropertyDto,
): ValidatorFn[] {
const validators: ValidatorFn[] = [];
property.attributes.forEach(attr => {
if (attr.typeSimple in AbpValidators)
validators.push(AbpValidators[attr.typeSimple](attr.config));
});
return validators;
}

70
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/public-api.ts

@ -0,0 +1,70 @@
export * from './lib/adapters/date-time.adapter';
export * from './lib/adapters/date.adapter';
export * from './lib/adapters/time.adapter';
export * from './lib/components/date-time-picker/date-time-picker.component';
export * from './lib/components/extensible-form/extensible-form-prop.component';
export * from './lib/components/extensible-form/extensible-form.component';
export * from './lib/components/extensible-table/extensible-table.component';
export * from './lib/components/grid-actions/grid-actions.component';
export * from './lib/components/page-toolbar/page-toolbar.component';
export * from './lib/constants/extra-properties';
export * from './lib/directives/disabled.directive';
export * from './lib/directives/prop-data.directive';
export * from './lib/enums/props.enum';
export {
ActionCallback,
ActionList,
ActionPredicate,
ReadonlyActionData as ActionData,
} from './lib/models/actions';
export {
EntityAction,
EntityActionContributorCallback,
EntityActionList,
EntityActionOptions,
EntityActions,
EntityActionsFactory,
} from './lib/models/entity-actions';
export {
EntityProp,
EntityPropContributorCallback,
EntityPropList,
EntityPropOptions,
EntityProps,
EntityPropsFactory,
} from './lib/models/entity-props';
export {
CreateFormPropContributorCallback,
CreateFormPropsFactory,
EditFormPropContributorCallback,
EditFormPropsFactory,
FormProp,
FormPropData,
FormPropList,
FormPropOptions,
FormProps,
} from './lib/models/form-props';
export * from './lib/models/object-extensions';
export {
PropCallback,
PropList,
PropPredicate,
ReadonlyPropData as PropData,
} from './lib/models/props';
export {
ToolbarAction,
ToolbarActionContributorCallback,
ToolbarActionList,
ToolbarActionOptions,
ToolbarActions,
ToolbarActionsFactory,
ToolbarComponent,
ToolbarComponentOptions,
} from './lib/models/toolbar-actions';
export * from './lib/services/extensions.service';
export * from './lib/tokens/extensions.token';
export * from './lib/ui-extensions.module';
export * from './lib/utils/actions.util';
export * from './lib/utils/form-props.util';
export * from './lib/utils/props.util';
export * from './lib/utils/state.util';

22
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/tests/actions.spec.ts

@ -0,0 +1,22 @@
import { ActionData } from '../lib/models/actions';
describe('ActionData', () => {
describe('#data', () => {
it('should return record and getInjected', () => {
const spy = jest.fn();
class Data extends ActionData<string> {
index = 0;
record = 'X';
getInjected = spy;
}
const data = new Data();
data.data.getInjected(null);
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith(null);
expect(data.data.index).toBe(0);
expect(data.data.record).toBe('X');
});
});
});

53
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/tests/actions.util.spec.ts

@ -0,0 +1,53 @@
import {
EntityAction,
EntityActionContributorCallbacks,
EntityActionDefaults,
EntityActionsFactory,
} from '../lib/models/entity-actions';
import { mergeWithDefaultActions } from '../lib/utils/actions.util';
describe('Entity Action Utils', () => {
describe('#mergeEntityActions', () => {
let entityActions: EntityActionsFactory;
beforeEach(() => {
entityActions = new EntityActionsFactory();
});
it('should merge default actions with action contributors', () => {
const defaults: EntityActionDefaults = {
x: [(1 as any) as EntityAction, (2 as any) as EntityAction, (3 as any) as EntityAction],
y: [(1 as any) as EntityAction, (2 as any) as EntityAction, (3 as any) as EntityAction],
};
const contributors1: EntityActionContributorCallbacks = {
x: [
actionList => {
const x2 = actionList.dropByIndex(1); // 1 <-> 3
actionList.addHead(x2.value); // 2 <-> 1 <-> 3
},
actionList => {
actionList.dropTail(); // 2 <-> 1
},
],
};
const contributors2: EntityActionContributorCallbacks = {
y: [
actionList => {
const y2 = actionList.dropByIndex(1); // 1 <-> 3
actionList.addTail(y2.value); // 1 <-> 3 <-> 2
},
actionList => {
actionList.dropHead(); // 3 <-> 2
},
],
};
mergeWithDefaultActions(entityActions, defaults, contributors1, contributors2);
expect(entityActions.get('x').actions.toString()).toBe('2 <-> 1');
expect(entityActions.get('y').actions.toString()).toBe('3 <-> 2');
});
});
});

35
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/tests/date-time.adapter.spec.ts

@ -0,0 +1,35 @@
import { DateTimeAdapter } from '../lib/adapters/date-time.adapter';
describe('DateTime Adapter', () => {
const adapter = new DateTimeAdapter();
const date = new Date(2002, 2, 30, 13, 30, 45, 0);
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
const hour = date.getHours();
const minute = date.getMinutes();
const second = date.getSeconds();
describe('#fromModel', () => {
test.each`
param | expected
${undefined} | ${null}
${null} | ${null}
${'x'} | ${null}
${date} | ${{ year, month, day, hour, minute, second }}
`('should return $expected when $param is given', ({ param, expected }) => {
expect(adapter.fromModel(param)).toEqual(expected);
});
});
describe('#toModel', () => {
test.each`
param | expected
${undefined} | ${''}
${null} | ${''}
${{ year, month, day, hour, minute, second }} | ${date.toISOString()}
`('should return $expected when $param is given', ({ param, expected }) => {
expect(adapter.toModel(param)).toEqual(expected);
});
});
});

31
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/tests/date.adapter.spec.ts

@ -0,0 +1,31 @@
import { DateAdapter } from '../lib/adapters/date.adapter';
describe('Date Adapter', () => {
const adapter = new DateAdapter();
describe('#fromModel', () => {
test.each`
param | expected
${undefined} | ${null}
${null} | ${null}
${'x'} | ${null}
${'2002-03-30'} | ${{ day: 30, month: 3, year: 2002 }}
${'03/30/2002'} | ${{ day: 30, month: 3, year: 2002 }}
${new Date(0)} | ${{ day: 1, month: 1, year: 1970 }}
`('should return $expected when $param is given', ({ param, expected }) => {
expect(adapter.fromModel(param)).toEqual(expected);
});
});
describe('#toModel', () => {
test.each`
param | expected
${undefined} | ${''}
${null} | ${''}
${{ day: 30, month: 3, year: 2002 }} | ${'2002-03-30'}
${{ day: 1, month: 1, year: 1970 }} | ${'1970-01-01'}
`('should return $expected when $param is given', ({ param, expected }) => {
expect(adapter.toModel(param)).toEqual(expected);
});
});
});

151
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/tests/entity-actions.spec.ts

@ -0,0 +1,151 @@
import { LinkedList } from '@abp/utils';
import {
EntityAction,
EntityActionContributorCallback,
EntityActionList,
EntityActions,
EntityActionsFactory,
} from '../lib/models/entity-actions';
describe('EntityActionList', () => {
it('should inherit from LinkedList', () => {
expect(new EntityActionList() instanceof LinkedList).toBe(true);
});
});
describe('EntityActions', () => {
const add1toTail: EntityActionContributorCallback = actionList => actionList.addTail(1 as any);
const add2toTail: EntityActionContributorCallback = actionList => actionList.addTail(2 as any);
const add3toTail: EntityActionContributorCallback = actionList => actionList.addTail(3 as any);
const dropIndex1: EntityActionContributorCallback = actionList => actionList.dropByIndex(1);
describe('#actions', () => {
test.each`
callbackList | expected
${[]} | ${''}
${[add1toTail, add2toTail, add3toTail]} | ${'1 <-> 2 <-> 3'}
${[add3toTail, add2toTail, add1toTail]} | ${'3 <-> 2 <-> 1'}
${[add1toTail, add2toTail, add3toTail, dropIndex1]} | ${'1 <-> 3'}
`(
'should return $expected when given callbackList is $callbackList',
({ callbackList, expected }) => {
const creator = new EntityActions(callbackList);
expect(creator.actions.toString()).toBe(expected);
},
);
});
describe('#addContributor', () => {
const creator = new EntityActions([]);
test.each`
callbackList | callback | expected
${[]} | ${add1toTail} | ${'1'}
${[add1toTail]} | ${add2toTail} | ${'1 <-> 2'}
${[add1toTail, add2toTail]} | ${add3toTail} | ${'1 <-> 2 <-> 3'}
${[add1toTail, add2toTail, add3toTail]} | ${dropIndex1} | ${'1 <-> 3'}
`(
'should set actions to $expected when callbackList is $callbackList and given callback is $callback',
({ callback, expected }) => {
creator.addContributor(callback);
expect(creator.actions.toString()).toBe(expected);
},
);
});
});
describe('EntityActionsFactory', () => {
describe('#get', () => {
it('should create and return an EntityActions instance', () => {
const entityActions = new EntityActionsFactory();
const creator = entityActions.get('');
expect(creator).toBeInstanceOf(EntityActions);
});
it('should store and pass contributorCallbacks to EntityActionsCreator instance it returns', () => {
const entityActions = new EntityActionsFactory();
const creatorX1 = entityActions.get('X');
expect(creatorX1).toBeInstanceOf(EntityActions);
expect(creatorX1.actions.toArray()).toHaveLength(0);
creatorX1.addContributor(actionList => actionList.addTail(1 as any));
const creatorX2 = entityActions.get('X');
expect(creatorX2.actions.toArray()).toHaveLength(1);
});
});
});
describe('EntityAction', () => {
it('should be created when options object is passed as argument', () => {
const options = {
text: 'TEXT',
action: () => 'ACTION',
permission: 'PERMISSION',
visible: () => false,
icon: 'ICON',
};
const action = new EntityAction(options);
expect(action.text).toBe(options.text);
expect(action.action(null)).toBe(options.action());
expect(action.permission).toBe(options.permission);
expect(action.visible(null)).toBe(options.visible());
expect(action.icon).toBe(options.icon);
});
it('should be created when only required options are passed', () => {
const options = {
text: 'TEXT',
action: () => 'ACTION',
};
const action = new EntityAction(options);
expect(action.text).toBe(options.text);
expect(action.action).toBe(options.action);
expect(action.permission).toBeUndefined();
expect(action.visible(null)).toBe(true);
expect(action.icon).toBe('');
});
describe('#create', () => {
it('should return a new instance from given options', () => {
const options = {
text: 'TEXT',
action: () => 'ACTION',
};
const action = EntityAction.create(options);
expect(action).toBeInstanceOf(EntityAction);
expect(action.text).toBe(options.text);
expect(action.action).toBe(options.action);
});
});
describe('#createMany', () => {
it('should return multiple instances from given options array', () => {
const options1 = {
text: 'TEXT 1',
action: () => 'ACTION 1',
};
const options2 = {
text: 'TEXT 2',
action: () => 'ACTION 2',
};
const [action1, action2] = EntityAction.createMany([options1, options2]);
expect(action1).toBeInstanceOf(EntityAction);
expect(action1.text).toBe(options1.text);
expect(action1.action).toBe(options1.action);
expect(action2).toBeInstanceOf(EntityAction);
expect(action2.text).toBe(options2.text);
expect(action2.action).toBe(options2.action);
});
});
});

166
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/tests/entity-props.spec.ts

@ -0,0 +1,166 @@
import { LinkedList } from '@abp/utils';
import { NEVER } from 'rxjs';
import { ePropType } from '../lib/enums/props.enum';
import {
EntityProp,
EntityPropContributorCallback,
EntityPropList,
EntityProps,
EntityPropsFactory,
} from '../lib/models/entity-props';
import { PropData } from '../lib/models/props';
describe('EntityPropList', () => {
it('should inherit from LinkedList', () => {
expect(new EntityPropList() instanceof LinkedList).toBe(true);
});
});
describe('EntityProps', () => {
const add1toTail: EntityPropContributorCallback = propList => propList.addTail(1 as any);
const add2toTail: EntityPropContributorCallback = propList => propList.addTail(2 as any);
const add3toTail: EntityPropContributorCallback = propList => propList.addTail(3 as any);
const dropIndex1: EntityPropContributorCallback = propList => propList.dropByIndex(1);
describe('#props', () => {
test.each`
callbackList | expected
${[]} | ${''}
${[add1toTail, add2toTail, add3toTail]} | ${'1 <-> 2 <-> 3'}
${[add3toTail, add2toTail, add1toTail]} | ${'3 <-> 2 <-> 1'}
${[add1toTail, add2toTail, add3toTail, dropIndex1]} | ${'1 <-> 3'}
`(
'should return $expected when given callbackList is $callbackList',
({ callbackList, expected }) => {
const creator = new EntityProps(callbackList);
expect(creator.props.toString()).toBe(expected);
},
);
});
describe('#addContributor', () => {
const creator = new EntityProps([]);
test.each`
callbackList | callback | expected
${[]} | ${add1toTail} | ${'1'}
${[add1toTail]} | ${add2toTail} | ${'1 <-> 2'}
${[add1toTail, add2toTail]} | ${add3toTail} | ${'1 <-> 2 <-> 3'}
${[add1toTail, add2toTail, add3toTail]} | ${dropIndex1} | ${'1 <-> 3'}
`(
'should set props to $expected when callbackList is $callbackList and given callback is $callback',
({ callback, expected }) => {
creator.addContributor(callback);
expect(creator.props.toString()).toBe(expected);
},
);
});
});
describe('EntityPropsFactory', () => {
describe('#get', () => {
it('should create and return an EntityProps instance', () => {
const entityProps = new EntityPropsFactory();
const creator = entityProps.get('');
expect(creator).toBeInstanceOf(EntityProps);
});
it('should store and pass contributorCallbacks to EntityPropsCreator instance it returns', () => {
const entityProps = new EntityPropsFactory();
const creatorX1 = entityProps.get('X');
expect(creatorX1).toBeInstanceOf(EntityProps);
expect(creatorX1.props.toArray()).toHaveLength(0);
creatorX1.addContributor(propList => propList.addTail(1 as any));
const creatorX2 = entityProps.get('X');
expect(creatorX2.props.toArray()).toHaveLength(1);
});
});
});
describe('EntityProp', () => {
it('should be created when options object is passed as argument', () => {
const options = {
type: ePropType.String,
name: 'NAME',
displayName: 'DISPLAY NAME',
permission: 'PERMISSION',
visible: () => false,
valueResolver: () => NEVER,
sortable: true,
columnWidth: 999,
};
const prop = new EntityProp(options);
expect(prop.type).toBe(options.type);
expect(prop.name).toBe(options.name);
expect(prop.displayName).toBe(options.displayName);
expect(prop.permission).toBe(options.permission);
expect(prop.visible()).toBe(options.visible());
expect(prop.valueResolver()).toBe(options.valueResolver());
expect(prop.sortable).toBe(options.sortable);
expect(prop.columnWidth).toBe(options.columnWidth);
});
it('should be created when only required options are passed', done => {
const options = {
type: ePropType.String,
name: 'NAME',
};
const prop = new EntityProp(options);
expect(prop.type).toBe(options.type);
expect(prop.name).toBe(options.name);
expect(prop.displayName).toBe(options.name);
expect(prop.permission).toBeUndefined();
expect(prop.visible()).toBe(true);
expect(prop.sortable).toBe(false);
expect(prop.columnWidth).toBeUndefined();
prop.valueResolver({ record: { NAME: 'X' } } as PropData).subscribe(value => {
expect(value).toBe('X');
done();
});
});
describe('#create', () => {
it('should return a new instance from given options', () => {
const options = {
type: ePropType.String,
name: 'NAME',
};
const prop = EntityProp.create(options);
expect(prop).toBeInstanceOf(EntityProp);
expect(prop.type).toBe(options.type);
expect(prop.name).toBe(options.name);
});
});
describe('#createMany', () => {
it('should return multiple instances from given options array', () => {
const options1 = {
type: ePropType.String,
name: 'NAME 1',
};
const options2 = {
type: ePropType.Boolean,
name: 'NAME 2',
};
const [prop1, prop2] = EntityProp.createMany([options1, options2]);
expect(prop1).toBeInstanceOf(EntityProp);
expect(prop1.type).toBe(options1.type);
expect(prop1.name).toBe(options1.name);
expect(prop2).toBeInstanceOf(EntityProp);
expect(prop2.type).toBe(options2.type);
expect(prop2.name).toBe(options2.name);
});
});
});

114
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/tests/enum.util.spec.ts

@ -0,0 +1,114 @@
import { ConfigStateService, LocalizationService } from '@abp/ng.core';
import { BehaviorSubject } from 'rxjs';
import { take } from 'rxjs/operators';
import { PropData } from '../lib/models/props';
import { createEnum, createEnumOptions, createEnumValueResolver } from '../lib/utils/enum.util';
const mockSessionState = {
languageChange$: new BehaviorSubject('tr'),
getLanguage: () => 'tr',
onLanguageChange$: () => new BehaviorSubject('tr'),
} as any;
const fields = [
{ name: 'foo', value: 1 },
{ name: 'bar', value: 2 },
{ name: 'baz', value: 3 },
];
class MockPropData<R = any> extends PropData<R> {
getInjected: PropData<R>['getInjected'];
constructor(public readonly record: R) {
super();
}
}
const mockL10n = {
values: {
Default: {
'Enum:MyEnum.foo': 'Foo',
'MyEnum.bar': 'Bar',
baz: 'Baz',
},
},
defaultResourceName: 'Default',
currentCulture: null,
languages: [],
};
describe('Enum Utils', () => {
describe('#createEnum', () => {
const enumFromFields = createEnum(fields);
test.each`
key | expected
${'foo'} | ${1}
${'bar'} | ${2}
${'baz'} | ${3}
${1} | ${'foo'}
${2} | ${'bar'}
${3} | ${'baz'}
`('should create an enum that returns $expected when $key is accessed', ({ key, expected }) => {
expect(enumFromFields[key]).toBe(expected);
});
});
describe('#createEnumValueResolver', () => {
test.each`
value | expected
${1} | ${'Foo'}
${2} | ${'Bar'}
${3} | ${'Baz'}
`(
'should create a resolver that returns observable $expected when enum value is $value',
async ({ value, expected }) => {
const service = createMockLocalizationService();
const valueResolver = createEnumValueResolver(
'MyCompanyName.MyProjectName.MyEnum',
{
fields,
localizationResource: null,
transformed: createEnum(fields),
},
'EnumProp',
);
const propData = new MockPropData({ extraProperties: { EnumProp: value } });
propData.getInjected = () => service as any;
const resolved = await valueResolver(propData).pipe(take(1)).toPromise();
expect(resolved).toBe(expected);
},
);
});
describe('#createEnumOptions', () => {
it('should create a generator that returns observable options from enums', async () => {
const service = createMockLocalizationService();
const options = createEnumOptions('MyCompanyName.MyProjectName.MyEnum', {
fields,
localizationResource: null,
transformed: createEnum(fields),
});
const propData = new MockPropData({});
propData.getInjected = () => service as any;
const resolved = await options(propData).pipe(take(1)).toPromise();
expect(resolved).toEqual([
{ key: 'Foo', value: 1 },
{ key: 'Bar', value: 2 },
{ key: 'Baz', value: 3 },
]);
});
});
});
function createMockLocalizationService() {
const configState = new ConfigStateService();
configState.setState({ localization: mockL10n } as any);
return new LocalizationService(mockSessionState, null, null, configState, null);
}

50
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/tests/extensions.service.spec.ts

@ -0,0 +1,50 @@
import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest';
import { EntityActionsFactory } from '../lib/models/entity-actions';
import { EntityPropsFactory } from '../lib/models/entity-props';
import { CreateFormPropsFactory, EditFormPropsFactory } from '../lib/models/form-props';
import { ToolbarActionsFactory } from '../lib/models/toolbar-actions';
import { ExtensionsService } from '../lib/services/extensions.service';
describe('ExtensionsService', () => {
let service: ExtensionsService;
let spectator: SpectatorService<ExtensionsService>;
const createService = createServiceFactory({
service: ExtensionsService,
});
beforeEach(() => {
spectator = createService();
service = spectator.service;
});
describe('#entityActions', () => {
it('should be an instance of EntityActionsFactory class', () => {
expect(service.entityActions).toBeInstanceOf(EntityActionsFactory);
});
});
describe('#toolbarActions', () => {
it('should be an instance of ToolbarActionsFactory class', () => {
expect(service.toolbarActions).toBeInstanceOf(ToolbarActionsFactory);
});
});
describe('#entityProps', () => {
it('should be an instance of EntityPropsFactory class', () => {
expect(service.entityProps).toBeInstanceOf(EntityPropsFactory);
});
});
describe('#createFormProps', () => {
it('should be an instance of CreateFormPropsFactory class', () => {
expect(service.createFormProps).toBeInstanceOf(CreateFormPropsFactory);
});
});
describe('#editFormProps', () => {
it('should be an instance of EditFormPropsFactory class', () => {
expect(service.editFormProps).toBeInstanceOf(EditFormPropsFactory);
});
});
});

28
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/tests/factory.util.spec.ts

@ -0,0 +1,28 @@
import { selfFactory } from '../lib/utils/factory.util';
describe('Factory Utils', () => {
describe('#selfFactory', () => {
const arr = [];
const obj = {};
const date = new Date();
const promise = Promise.resolve(null);
test.each`
parameter
${'x'}
${''}
${1}
${0}
${true}
${false}
${arr}
${obj}
${date}
${promise}
${null}
${undefined}
`('should return given $parameter back', ({ parameter }) => {
expect(selfFactory(parameter)).toBe(parameter);
});
});
});

193
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/tests/form-props.spec.ts

@ -0,0 +1,193 @@
import { LinkedList } from '@abp/utils';
import { NEVER } from 'rxjs';
import { ePropType } from '../lib/enums/props.enum';
import {
CreateFormPropContributorCallback,
CreateFormPropsFactory,
EditFormPropsFactory,
FormProp,
FormPropList,
FormProps,
} from '../lib/models/form-props';
describe('FormPropList', () => {
it('should inherit from LinkedList', () => {
expect(new FormPropList() instanceof LinkedList).toBe(true);
});
});
describe('FormProps', () => {
const add1toTail: CreateFormPropContributorCallback = propList => propList.addTail(1 as any);
const add2toTail: CreateFormPropContributorCallback = propList => propList.addTail(2 as any);
const add3toTail: CreateFormPropContributorCallback = propList => propList.addTail(3 as any);
const dropIndex1: CreateFormPropContributorCallback = propList => propList.dropByIndex(1);
describe('#props', () => {
test.each`
callbackList | expected
${[]} | ${''}
${[add1toTail, add2toTail, add3toTail]} | ${'1 <-> 2 <-> 3'}
${[add3toTail, add2toTail, add1toTail]} | ${'3 <-> 2 <-> 1'}
${[add1toTail, add2toTail, add3toTail, dropIndex1]} | ${'1 <-> 3'}
`(
'should return $expected when given callbackList is $callbackList',
({ callbackList, expected }) => {
const creator = new FormProps(callbackList);
expect(creator.props.toString()).toBe(expected);
},
);
});
describe('#addContributor', () => {
const creator = new FormProps([]);
test.each`
callbackList | callback | expected
${[]} | ${add1toTail} | ${'1'}
${[add1toTail]} | ${add2toTail} | ${'1 <-> 2'}
${[add1toTail, add2toTail]} | ${add3toTail} | ${'1 <-> 2 <-> 3'}
${[add1toTail, add2toTail, add3toTail]} | ${dropIndex1} | ${'1 <-> 3'}
`(
'should set props to $expected when callbackList is $callbackList and given callback is $callback',
({ callback, expected }) => {
creator.addContributor(callback);
expect(creator.props.toString()).toBe(expected);
},
);
});
});
describe('FormPropsFactory', () => {
describe('#get', () => {
it('should create and return an FormProps instance', () => {
const formProps = new CreateFormPropsFactory();
const creator = formProps.get('');
expect(creator).toBeInstanceOf(FormProps);
});
it('should store and pass contributorCallbacks to FormPropsCreator instance it returns', () => {
const formProps = new EditFormPropsFactory();
const creatorX1 = formProps.get('X');
expect(creatorX1).toBeInstanceOf(FormProps);
expect(creatorX1.props.toArray()).toHaveLength(0);
creatorX1.addContributor(propList => propList.addTail(1 as any));
const creatorX2 = formProps.get('X');
expect(creatorX2.props.toArray()).toHaveLength(1);
});
});
});
describe('FormProp', () => {
it('should be created when options object is passed as argument', () => {
const options = {
type: ePropType.String,
name: 'NAME',
displayName: 'DISPLAY NAME',
permission: 'PERMISSION',
visible: () => false,
asyncValidators: () => [null],
validators: () => [null],
disabled: () => true,
readonly: () => true,
autocomplete: 'AUTOCOMPLETE',
defaultValue: 'DEFAULT VALUE',
options: () => NEVER,
id: 'ID',
};
const prop = new FormProp(options);
expect(prop.type).toBe(options.type);
expect(prop.name).toBe(options.name);
expect(prop.displayName).toBe(options.displayName);
expect(prop.permission).toBe(options.permission);
expect(prop.visible()).toBe(options.visible());
expect(prop.asyncValidators()).toEqual(options.asyncValidators());
expect(prop.validators()).toEqual(options.validators());
expect(prop.disabled()).toBe(options.disabled());
expect(prop.readonly()).toBe(options.readonly());
expect(prop.autocomplete).toBe(options.autocomplete);
expect(prop.defaultValue).toBe(options.defaultValue);
expect(prop.options()).toBe(options.options());
expect(prop.id).toBe(options.id);
});
it('should be created when only required options are passed', () => {
const options = {
type: ePropType.String,
name: 'NAME',
};
const prop = new FormProp(options);
expect(prop.type).toBe(options.type);
expect(prop.name).toBe(options.name);
expect(prop.displayName).toBe(options.name);
expect(prop.permission).toBeUndefined();
expect(prop.visible()).toBe(true);
expect(prop.asyncValidators()).toEqual([]);
expect(prop.validators()).toEqual([]);
expect(prop.disabled()).toBe(false);
expect(prop.readonly()).toBe(false);
expect(prop.autocomplete).toBe('off');
expect(prop.defaultValue).toBeNull();
expect(prop.options).toBeUndefined();
expect(prop.id).toBe(options.name);
});
test.each`
defaultValue | expected
${0} | ${0}
${''} | ${''}
${false} | ${false}
${undefined} | ${null}
`(
'should set defaultValue as $expected when $defaultValue is given',
({ defaultValue, expected }) => {
const options = { type: null, name: null, defaultValue };
const prop = new FormProp(options);
expect(prop.defaultValue).toBe(expected);
},
);
describe('#create', () => {
it('should return a new instance from given options', () => {
const options = {
type: ePropType.String,
name: 'NAME',
};
const prop = FormProp.create(options);
expect(prop).toBeInstanceOf(FormProp);
expect(prop.type).toBe(options.type);
expect(prop.name).toBe(options.name);
});
});
describe('#createMany', () => {
it('should return multiple instances from given options array', () => {
const options1 = {
type: ePropType.String,
name: 'NAME 1',
};
const options2 = {
type: ePropType.Boolean,
name: 'NAME 2',
};
const [prop1, prop2] = FormProp.createMany([options1, options2]);
expect(prop1).toBeInstanceOf(FormProp);
expect(prop1.type).toBe(options1.type);
expect(prop1.name).toBe(options1.name);
expect(prop2).toBeInstanceOf(FormProp);
expect(prop2.type).toBe(options2.type);
expect(prop2.name).toBe(options2.name);
});
});
});

142
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/tests/form-props.util.spec.ts

@ -0,0 +1,142 @@
import { LocalizationService } from '@abp/ng.core';
import { Injector } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest';
import { ePropType } from '../lib/enums/props.enum';
import { FormProp, FormPropData } from '../lib/models/form-props';
import { ExtensionsService } from '../lib/services/extensions.service';
import { EXTENSIONS_IDENTIFIER } from '../lib/tokens/extensions.token';
import { generateFormFromProps } from '../lib/utils/form-props.util';
describe('Form Prop Utils', () => {
describe('#generateFormFromProps', () => {
let spectator: SpectatorService<ExtensionsService<Foo>>;
let injector: Injector;
const identifier = 'X';
const createService = createServiceFactory({
service: ExtensionsService,
providers: [
{
provide: EXTENSIONS_IDENTIFIER,
useValue: identifier,
},
{
provide: LocalizationService,
useValue: { currentLang: 'en' },
},
],
});
beforeEach(() => {
spectator = createService();
const props = FormProp.createMany<Foo>([
{
type: ePropType.String,
name: 'foo',
validators: () => [Validators.required],
defaultValue: 'bar',
},
{
type: ePropType.Boolean,
name: 'bool',
},
{
type: ePropType.Date,
name: 'date',
},
{
type: ePropType.DateTime,
name: 'dateTime',
},
{
type: ePropType.Time,
name: 'time',
},
]);
spectator.service.createFormProps
.get(identifier)
.addContributor(propList => propList.addManyTail(props));
spectator.service.editFormProps
.get(identifier)
.addContributor(propList => propList.addManyTail(props));
const generator = getInjected(spectator);
injector = {
get: () => generator.next().value as any,
};
});
it('should return a blank FormGroup instance', () => {
const data = new FormPropData<Foo>(injector, null);
const formGroup = generateFormFromProps(data);
expect(formGroup).toBeInstanceOf(FormGroup);
expect(formGroup.value.foo).toBe('bar');
const formControl = formGroup.get('foo');
expect(formControl).toBeInstanceOf(FormControl);
expect(formControl.valid).toBe(true);
});
it('should return a prefilled FormGroup instance', () => {
const data = new FormPropData<Foo>(injector, { id: 1, foo: null });
const formGroup = generateFormFromProps(data);
expect(formGroup).toBeInstanceOf(FormGroup);
expect(formGroup.value.foo).toBe(null);
const formControl = formGroup.get('foo');
expect(formControl).toBeInstanceOf(FormControl);
expect(formControl.invalid).toBe(true);
});
it('should add a FormGroup named extraProperties', () => {
const data = new FormPropData<Foo>(injector, null);
const formGroup = generateFormFromProps(data);
const extraPropertiesGroup = formGroup.get('extraProperties');
expect(extraPropertiesGroup).toBeInstanceOf(FormGroup);
});
it('should add extraProperties to extraProperties FormGroup', () => {
const data = new FormPropData<Foo>(injector, {
id: 1,
foo: undefined,
extraProperties: {
bool: true,
date: '03/30/2002',
dateTime: '2002-03-30 13:30:59Z',
time: '13:30:59',
},
});
const formGroup = generateFormFromProps(data);
const extraPropertiesGroup = formGroup.get('extraProperties');
expect(extraPropertiesGroup.value).toEqual({
bool: true,
date: '2002-03-30',
dateTime: '2002-03-30T13:30:59.000Z',
time: '13:30',
});
});
});
});
function* getInjected(spectator: SpectatorService<ExtensionsService>) {
yield spectator.service;
yield spectator.inject(EXTENSIONS_IDENTIFIER);
yield spectator.inject(LocalizationService);
}
interface Foo {
id: number;
foo: string;
extraProperties?: {
bool: boolean;
date: string;
dateTime: string;
time: string;
};
}

34
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/tests/localization.util.spec.ts

@ -0,0 +1,34 @@
import { ApplicationLocalizationConfigurationDto } from '@abp/ng.core';
import { createDisplayNameLocalizationPipeKeyGenerator } from '../lib/utils/localization.util';
describe('Localization Utils', () => {
describe('#createDisplayNameLocalizationPipeKeyGenerator', () => {
const generateDisplayName = createDisplayNameLocalizationPipeKeyGenerator({
values: {
Foo: { Bar: 'Bar', 'DisplayName:Bar': 'Bar' },
Default: { Bar: 'Bar', 'DisplayName:Bar': 'Bar' },
},
defaultResourceName: 'Default',
currentCulture: null,
languages: [],
languageFilesMap: null,
languagesMap: null,
} as ApplicationLocalizationConfigurationDto);
test.each`
displayName | fallback | expected
${{ name: 'Bar', resource: 'Foo' }} | ${null} | ${'Foo::Bar'}
${{ name: 'Baz', resource: 'Foo' }} | ${null} | ${'Baz'}
${null} | ${{ name: 'Bar', resource: 'Foo' }} | ${'Foo::DisplayName:Bar'}
${null} | ${{ name: 'Bar', resource: 'Default' }} | ${'Default::DisplayName:Bar'}
${null} | ${{ name: 'Baz', resource: 'Default' }} | ${'Baz'}
`(
'should return $expected when diplay name is $displayName and fallback is $fallback',
({ displayName, fallback, expected }) => {
const result = generateDisplayName(displayName, fallback);
expect(result).toBe(expected);
},
);
});
});

22
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/tests/props.spec.ts

@ -0,0 +1,22 @@
import { PropData } from '../lib/models/props';
describe('PropData', () => {
describe('#data', () => {
it('should return record and getInjected', () => {
const spy = jest.fn();
class Data extends PropData<string> {
index = 0;
record = 'X';
getInjected = spy;
}
const data = new Data();
data.data.getInjected(null);
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith(null);
expect(data.data.index).toBe(0);
expect(data.data.record).toBe('X');
});
});
});

72
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/tests/props.util.spec.ts

@ -0,0 +1,72 @@
import {
EntityProp,
EntityPropContributorCallbacks,
EntityPropDefaults,
EntityPropsFactory,
} from '../lib/models/entity-props';
import { PropData } from '../lib/models/props';
import { createExtraPropertyValueResolver, mergeWithDefaultProps } from '../lib/utils/props.util';
class MockPropData<R = any> extends PropData<R> {
getInjected: PropData<R>['getInjected'];
constructor(public readonly record: R) {
super();
}
}
describe('Entity Prop Utils', () => {
describe('#createExtraPropertyValueResolver', () => {
it('should return a resolver that resolves an observable value from extraProperties', async () => {
const valueResolver = createExtraPropertyValueResolver('foo');
const propData = new MockPropData({ extraProperties: { foo: 'bar' } });
const bar = await valueResolver(propData).toPromise();
expect(bar).toBe('bar');
});
});
describe('#mergeEntityProps', () => {
let entityProps: EntityPropsFactory;
beforeEach(() => {
entityProps = new EntityPropsFactory();
});
it('should merge default props with prop contributors', () => {
const defaults: EntityPropDefaults = {
x: [(1 as any) as EntityProp, (2 as any) as EntityProp, (3 as any) as EntityProp],
y: [(1 as any) as EntityProp, (2 as any) as EntityProp, (3 as any) as EntityProp],
};
const contributors1: EntityPropContributorCallbacks = {
x: [
propList => {
const x2 = propList.dropByIndex(1); // 1 <-> 3
propList.addHead(x2.value); // 2 <-> 1 <-> 3
},
propList => {
propList.dropTail(); // 2 <-> 1
},
],
};
const contributors2: EntityPropContributorCallbacks = {
y: [
propList => {
const y2 = propList.dropByIndex(1); // 1 <-> 3
propList.addTail(y2.value); // 1 <-> 3 <-> 2
},
propList => {
propList.dropHead(); // 3 <-> 2
},
],
};
mergeWithDefaultProps(entityProps, defaults, contributors1, contributors2);
expect(entityProps.get('x').props.toString()).toBe('2 <-> 1');
expect(entityProps.get('y').props.toString()).toBe('3 <-> 2');
});
});
});

369
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/tests/state.util.spec.ts

@ -0,0 +1,369 @@
import { ConfigStateService } from '@abp/ng.core';
import { of } from 'rxjs';
import { take } from 'rxjs/operators';
import { ePropType } from '../lib/enums/props.enum';
import { EntityPropList } from '../lib/models/entity-props';
import { FormPropList } from '../lib/models/form-props';
import { ObjectExtensions } from '../lib/models/object-extensions';
import {
getObjectExtensionEntitiesFromStore,
mapEntitiesToContributors,
} from '../lib/utils/state.util';
const configState = new ConfigStateService();
configState.setState(createMockState() as any);
describe('State Utils', () => {
describe('#getObjectExtensionEntitiesFromStore', () => {
it('should return observable entities of an existing module', async () => {
const entities = await getObjectExtensionEntitiesFromStore(
configState,
'Identity',
).toPromise();
expect('Role' in entities).toBe(true);
});
it('should return observable empty object if module does not exist', async () => {
const entities = await getObjectExtensionEntitiesFromStore(configState, 'Saas').toPromise();
expect(entities).toEqual({});
});
it('should not emit when object extensions do not exist', done => {
const emptyConfigState = new ConfigStateService();
const emit = jest.fn();
getObjectExtensionEntitiesFromStore(emptyConfigState, 'Identity').subscribe(emit);
setTimeout(() => {
expect(emit).not.toHaveBeenCalled();
done();
}, 1000);
});
});
describe('#mapEntitiesToContributors', () => {
it('should return contributors from given entities', async () => {
const contributors = await of(createMockEntities())
.pipe(mapEntitiesToContributors(configState, 'AbpIdentity'), take(1))
.toPromise();
const propList = new EntityPropList();
contributors.prop.Role.forEach(callback => callback(propList));
expect(propList.length).toBe(4);
expect(propList.head.value.name).toBe('Title');
expect(propList.head.next.value.name).toBe('IsHero');
expect(propList.head.next.next.value.name).toBe('MyEnum');
expect(propList.head.next.next.next.value.name).toBe('Foo_Text');
const createFormList = new FormPropList();
contributors.createForm.Role.forEach(callback => callback(createFormList));
expect(createFormList.length).toBe(4);
expect(createFormList.head.value.name).toBe('Title');
expect(createFormList.head.next.value.name).toBe('MyEnum');
expect(createFormList.head.next.next.value.name).toBe('Foo');
expect(createFormList.head.next.next.next.value.name).toBe('Foo_Text');
const editFormList = new FormPropList();
contributors.editForm.Role.forEach(callback => callback(editFormList));
expect(editFormList.length).toBe(4);
expect(editFormList.head.value.name).toBe('Title');
expect(editFormList.head.next.value.name).toBe('IsHero');
expect(editFormList.head.next.next.value.name).toBe('Foo');
expect(editFormList.head.next.next.next.value.name).toBe('Foo_Text');
});
});
});
function createMockState() {
return {
objectExtensions: {
modules: {
Identity: {
entities: createMockEntities(),
configuration: null,
},
},
enums: {
'MyCompanyName.MyProjectName.MyEnum': {
fields: [
{
name: 'MyEnumValue0',
value: 0,
},
{
name: 'MyEnumValue1',
value: 1,
},
{
name: 'MyEnumValue2',
value: 2,
},
],
localizationResource: null,
},
},
},
localization: {
values: {
Default: {},
AbpIdentity: {},
},
defaultResourceName: 'Default',
currentCulture: null,
languages: [],
},
};
}
function createMockEntities(): Record<string, ObjectExtensions.EntityExtensionDto> {
return {
Role: {
properties: {
Title: {
type: 'System.String',
typeSimple: ePropType.String,
displayName: null,
api: {
onGet: {
isAvailable: true,
},
onCreate: {
isAvailable: true,
},
onUpdate: {
isAvailable: true,
},
},
ui: {
onTable: {
isSortable: true,
isVisible: true,
},
onCreateForm: {
isVisible: true,
},
onEditForm: {
isVisible: true,
},
lookup: null,
},
attributes: [
{
typeSimple: 'required',
config: {},
},
{
typeSimple: 'stringLength',
config: {
maximumLength: 20,
minimumLength: 2,
},
},
],
configuration: {},
defaultValue: null,
},
IsHero: {
type: 'System.Boolean',
typeSimple: ePropType.Boolean,
displayName: null,
api: {
onGet: {
isAvailable: true,
},
onCreate: {
isAvailable: true,
},
onUpdate: {
isAvailable: true,
},
},
ui: {
onTable: {
isSortable: false,
isVisible: true,
},
onCreateForm: {
isVisible: false,
},
onEditForm: {
isVisible: true,
},
lookup: null,
},
attributes: [],
configuration: {},
defaultValue: null,
},
AsOf: {
type: 'System.Date',
typeSimple: ePropType.Date,
displayName: {
name: 'Active as of',
resource: 'AbpIdentity',
},
api: {
onGet: {
isAvailable: true,
},
onCreate: {
isAvailable: true,
},
onUpdate: {
isAvailable: true,
},
},
ui: {
onTable: {
isSortable: false,
isVisible: false,
},
onCreateForm: {
isVisible: false,
},
onEditForm: {
isVisible: false,
},
lookup: null,
},
attributes: [],
configuration: {},
defaultValue: null,
},
MyEnum: {
type: 'MyCompanyName.MyProjectName.MyEnum',
typeSimple: ePropType.Enum,
displayName: null,
api: {
onGet: {
isAvailable: true,
},
onCreate: {
isAvailable: true,
},
onUpdate: {
isAvailable: true,
},
},
ui: {
onTable: {
isSortable: false,
isVisible: true,
},
onCreateForm: {
isVisible: true,
},
onEditForm: {
isVisible: false,
},
lookup: null,
},
attributes: [
{
typeSimple: 'required',
config: {
allowEmptyStrings: false,
},
},
{
typeSimple: 'enumDataType',
config: {
enumType: 'MyCompanyName.MyProjectName.MyEnum',
dataType: 'Custom',
customDataType: 'Enumeration',
},
},
],
configuration: {},
defaultValue: 2,
},
Foo: {
type: 'System.String',
typeSimple: ePropType.String,
displayName: null,
api: {
onGet: {
isAvailable: false,
},
onCreate: {
isAvailable: true,
},
onUpdate: {
isAvailable: true,
},
},
ui: {
onTable: {
isVisible: false,
},
onCreateForm: {
isVisible: true,
},
onEditForm: {
isVisible: true,
},
lookup: {
url: '/api/identity/roles',
resultListPropertyName: 'items',
displayPropertyName: 'text',
valuePropertyName: 'id',
filterParamName: 'filter',
},
},
attributes: [],
configuration: {},
defaultValue: null,
},
Foo_Text: {
type: 'System.String',
typeSimple: ePropType.String,
displayName: {
name: 'Foo',
resource: '_',
},
api: {
onGet: {
isAvailable: true,
},
onCreate: {
isAvailable: true,
},
onUpdate: {
isAvailable: true,
},
},
ui: {
onTable: {
isVisible: true,
},
onCreateForm: {
isVisible: true,
},
onEditForm: {
isVisible: true,
},
lookup: {
url: null,
resultListPropertyName: 'items',
displayPropertyName: 'text',
valuePropertyName: 'id',
filterParamName: 'filter',
},
},
attributes: [],
configuration: {},
defaultValue: null,
},
},
configuration: {},
},
User: {
properties: null,
configuration: {},
},
ClaimType: null,
};
}

36
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/tests/time.adapter.spec.ts

@ -0,0 +1,36 @@
import { TimeAdapter } from '../lib/adapters/time.adapter';
describe('Time Adapter', () => {
const adapter = new TimeAdapter();
describe('#fromModel', () => {
const date = new Date();
const hour = date.getHours();
const minute = date.getMinutes();
const second = date.getSeconds();
test.each`
param | expected
${undefined} | ${null}
${null} | ${null}
${'x'} | ${null}
${'13:30:45'} | ${{ hour: 13, minute: 30, second: 45 }}
${'13:30'} | ${{ hour: 13, minute: 30, second: 0 }}
${date} | ${{ hour, minute, second }}
`('should return $expected when $param is given', ({ param, expected }) => {
expect(adapter.fromModel(param)).toEqual(expected);
});
});
describe('#toModel', () => {
test.each`
param | expected
${undefined} | ${''}
${null} | ${''}
${{ hour: 13, minute: 30, second: 0 }} | ${'13:30'}
${{ hour: 13, minute: 30, second: 45 }} | ${'13:30'}
`('should return $expected when $param is given', ({ param, expected }) => {
expect(adapter.toModel(param)).toEqual(expected);
});
});
});

216
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/tests/toolbar-actions.spec.ts

@ -0,0 +1,216 @@
import { LinkedList } from '@abp/utils';
import {
ToolbarAction,
ToolbarActionContributorCallback,
ToolbarActionList,
ToolbarActions,
ToolbarActionsFactory,
ToolbarComponent,
} from '../lib/models/toolbar-actions';
describe('ToolbarActionList', () => {
it('should inherit from LinkedList', () => {
expect(new ToolbarActionList() instanceof LinkedList).toBe(true);
});
});
describe('ToolbarActions', () => {
const add1toTail: ToolbarActionContributorCallback = actionList => actionList.addTail(1 as any);
const add2toTail: ToolbarActionContributorCallback = actionList => actionList.addTail(2 as any);
const add3toTail: ToolbarActionContributorCallback = actionList => actionList.addTail(3 as any);
const dropIndex1: ToolbarActionContributorCallback = actionList => actionList.dropByIndex(1);
describe('#actions', () => {
test.each`
callbackList | expected
${[]} | ${''}
${[add1toTail, add2toTail, add3toTail]} | ${'1 <-> 2 <-> 3'}
${[add3toTail, add2toTail, add1toTail]} | ${'3 <-> 2 <-> 1'}
${[add1toTail, add2toTail, add3toTail, dropIndex1]} | ${'1 <-> 3'}
`(
'should return $expected when given callbackList is $callbackList',
({ callbackList, expected }) => {
const creator = new ToolbarActions(callbackList);
expect(creator.actions.toString()).toBe(expected);
},
);
});
describe('#addContributor', () => {
const creator = new ToolbarActions([]);
test.each`
callbackList | callback | expected
${[]} | ${add1toTail} | ${'1'}
${[add1toTail]} | ${add2toTail} | ${'1 <-> 2'}
${[add1toTail, add2toTail]} | ${add3toTail} | ${'1 <-> 2 <-> 3'}
${[add1toTail, add2toTail, add3toTail]} | ${dropIndex1} | ${'1 <-> 3'}
`(
'should set actions to $expected when callbackList is $callbackList and given callback is $callback',
({ callback, expected }) => {
creator.addContributor(callback);
expect(creator.actions.toString()).toBe(expected);
},
);
});
});
describe('ToolbarActionsFactory', () => {
describe('#get', () => {
it('should create and return an ToolbarActions instance', () => {
const entityActions = new ToolbarActionsFactory();
const creator = entityActions.get('');
expect(creator).toBeInstanceOf(ToolbarActions);
});
it('should store and pass contributorCallbacks to ToolbarActionsCreator instance it returns', () => {
const entityActions = new ToolbarActionsFactory();
const creatorX1 = entityActions.get('X');
expect(creatorX1).toBeInstanceOf(ToolbarActions);
expect(creatorX1.actions.toArray()).toHaveLength(0);
creatorX1.addContributor(actionList => actionList.addTail(1 as any));
const creatorX2 = entityActions.get('X');
expect(creatorX2.actions.toArray()).toHaveLength(1);
});
});
});
describe('ToolbarAction', () => {
it('should be created when options object is passed as argument', () => {
const options = {
text: 'TEXT',
action: () => 'ACTION',
permission: 'PERMISSION',
visible: () => false,
icon: 'ICON',
};
const action = new ToolbarAction(options);
expect(action.text).toBe(options.text);
expect(action.action(null)).toBe(options.action());
expect(action.permission).toBe(options.permission);
expect(action.visible(null)).toBe(options.visible());
expect(action.icon).toBe(options.icon);
});
it('should be created when only required options are passed', () => {
const options = {
text: 'TEXT',
action: () => 'ACTION',
};
const action = new ToolbarAction(options);
expect(action.text).toBe(options.text);
expect(action.action).toBe(options.action);
expect(action.permission).toBeUndefined();
expect(action.visible(null)).toBe(true);
expect(action.icon).toBe('');
});
describe('#create', () => {
it('should return a new instance from given options', () => {
const options = {
text: 'TEXT',
action: () => 'ACTION',
};
const action = ToolbarAction.create(options);
expect(action).toBeInstanceOf(ToolbarAction);
expect(action.text).toBe(options.text);
expect(action.action).toBe(options.action);
});
});
describe('#createMany', () => {
it('should return multiple instances from given options array', () => {
const options1 = {
text: 'TEXT 1',
action: () => 'ACTION 1',
};
const options2 = {
text: 'TEXT 2',
action: () => 'ACTION 2',
};
const [action1, action2] = ToolbarAction.createMany([options1, options2]);
expect(action1).toBeInstanceOf(ToolbarAction);
expect(action1.text).toBe(options1.text);
expect(action1.action).toBe(options1.action);
expect(action2).toBeInstanceOf(ToolbarAction);
expect(action2.text).toBe(options2.text);
expect(action2.action).toBe(options2.action);
});
});
});
class MockComponent1 {}
class MockComponent2 {}
describe('ToolbarComponent', () => {
it('should be created when options object is passed as argument', () => {
const options = {
component: MockComponent1,
action: () => 'ACTION',
permission: 'PERMISSION',
visible: () => false,
};
const action = new ToolbarComponent(options);
expect(action.component).toBe(options.component);
expect(action.action).toBe(options.action);
expect(action.permission).toBe(options.permission);
expect(action.visible).toBe(options.visible);
});
it('should be created when only required options are passed', () => {
const options = {
component: MockComponent1,
};
const action = new ToolbarComponent(options);
expect(action.component).toBe(options.component);
expect(action.action()).toBeUndefined();
expect(action.permission).toBeUndefined();
expect(action.visible()).toBe(true);
});
describe('#create', () => {
it('should return a new instance from given options', () => {
const options = {
component: MockComponent1,
};
const action = ToolbarComponent.create(options);
expect(action).toBeInstanceOf(ToolbarComponent);
expect(action.component).toBe(options.component);
});
});
describe('#createMany', () => {
it('should return multiple instances from given options array', () => {
const options1 = {
component: MockComponent1,
};
const options2 = {
component: MockComponent2,
};
const [action1, action2] = ToolbarComponent.createMany([options1, options2]);
expect(action1).toBeInstanceOf(ToolbarComponent);
expect(action1.component).toBe(options1.component);
expect(action2).toBeInstanceOf(ToolbarComponent);
expect(action2.component).toBe(options2.component);
});
});
});

72
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/tests/typeahead.util.spec.ts

@ -0,0 +1,72 @@
import { ExtensionPropertyUiLookupDto } from '@abp/ng.core';
import { of } from 'rxjs';
import { createTypeaheadOptions } from '../lib/utils/typeahead.util';
const lookup: ExtensionPropertyUiLookupDto = {
url: 'url',
resultListPropertyName: 'list',
displayPropertyName: 'text',
valuePropertyName: 'id',
filterParamName: 'filter',
};
describe('Typeahead Utils', () => {
describe('#createTypeaheadOptions', () => {
it('should return observable empty array when search text does not exist', async () => {
const list = await createTypeaheadOptions(null)(null, null).toPromise();
expect(list).toEqual([]);
});
it('should call request method of RestService with lookup url, filter param and search text', async () => {
const data = createData([]);
const service = data.getInjected();
await createTypeaheadOptions(lookup)(data, 'x').toPromise();
expect(service.request).toHaveBeenCalledTimes(1);
expect(service.request).toHaveBeenCalledWith(
{
method: 'GET',
url: 'url',
params: {
filter: 'x',
},
},
{ apiName: 'Default' },
);
});
it('should return options based on given lookup data', async () => {
const data = createData([
{
text: 'foo',
id: 'bar',
},
{
text: 'baz',
id: 'qux',
},
]);
const options = await createTypeaheadOptions(lookup)(data, 'x').toPromise();
expect(options).toEqual([
{
key: 'foo',
value: 'bar',
},
{
key: 'baz',
value: 'qux',
},
]);
});
});
});
function createData(list: { text: string; id: string }[]): any {
const service = { request: jest.fn(() => of({ list })) };
return {
getInjected: () => service,
index: 0,
record: null,
};
}

20
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/src/tests/validation.util.spec.ts

@ -0,0 +1,20 @@
import { Validators } from '@angular/forms';
import { ObjectExtensions } from '../lib/models/object-extensions';
import { getValidatorsFromProperty } from '../lib/utils/validation.util';
describe('Validation Utils', () => {
describe('#getValidatorsFromProperty', () => {
it('should return a list of validators derived from property attributes', () => {
const property = {
attributes: [
{
typeSimple: 'emailAddress',
config: {},
},
],
} as ObjectExtensions.ExtensionPropertyDto;
expect(getValidatorsFromProperty(property)[0]).toBe(Validators.email);
});
});
});

8
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/testing/src/lib/ui-extensions-testing.module.ts

@ -0,0 +1,8 @@
import { BaseUiExtensionsModule } from '@abp/ng.theme.shared/extensions';
import { NgModule } from '@angular/core';
@NgModule({
exports: [BaseUiExtensionsModule],
imports: [BaseUiExtensionsModule],
})
export class UiExtensionsTestingModule {}

1
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/testing/src/public-api.ts

@ -0,0 +1 @@
export * from './lib/ui-extensions-testing.module';

7
npm/ng-packs/nx/ng-packs/libs/theme-shared/extensions/testing/ssng-package.json

@ -0,0 +1,7 @@
{
"$schema": "../../../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../dist/theme-shared/extensions/testing",
"lib": {
"entryFile": "src/public-api.ts"
}
}

20
npm/ng-packs/nx/ng-packs/libs/theme-shared/jest.config.js

@ -0,0 +1,20 @@
module.exports = {
displayName: 'theme-shared',
preset: '../../jest.preset.js',
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
globals: {
'ts-jest': {
tsconfig: '<rootDir>/tsconfig.spec.json',
stringifyContentPathRegex: '\\.(html|svg)$',
},
},
coverageDirectory: '../../coverage/libs/theme-shared',
transform: {
'^.+\\.(ts|js|html)$': 'jest-preset-angular',
},
snapshotSerializers: [
'jest-preset-angular/build/serializers/no-ng-attributes',
'jest-preset-angular/build/serializers/ng-snapshot',
'jest-preset-angular/build/serializers/html-comment',
],
};

16
npm/ng-packs/nx/ng-packs/libs/theme-shared/ng-package.json

@ -0,0 +1,16 @@
{
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../dist/libs/theme-shared",
"lib": {
"entryFile": "src/public-api.ts"
},
"allowedNonPeerDependencies": [
"@abp/ng.core",
"@fortawesome/fontawesome-free",
"@ng-bootstrap/ng-bootstrap",
"@ngx-validate/core",
"@swimlane/ngx-datatable",
"bootstrap",
"chart.js"
]
}

22
npm/ng-packs/nx/ng-packs/libs/theme-shared/package.json

@ -0,0 +1,22 @@
{
"name": "@abp/ng.theme.shared",
"version": "4.4.0",
"homepage": "https://abp.io",
"repository": {
"type": "git",
"url": "https://github.com/abpframework/abp.git"
},
"dependencies": {
"@abp/ng.core": "~4.4.0",
"@fortawesome/fontawesome-free": "^5.14.0",
"@ng-bootstrap/ng-bootstrap": "^7.0.0",
"@ngx-validate/core": "^0.0.13",
"@swimlane/ngx-datatable": "^17.1.0",
"bootstrap": "~4.6.0",
"chart.js": "^2.9.3",
"tslib": "^2.0.0"
},
"publishConfig": {
"access": "public"
}
}

23
npm/ng-packs/nx/ng-packs/libs/theme-shared/src/lib/animations/bounce.animations.ts

@ -0,0 +1,23 @@
import { animate, animation, keyframes, style } from '@angular/animations';
export const bounceIn = animation(
[
style({ opacity: '0', display: '{{ display }}' }),
animate(
'{{ time}} {{ easing }}',
keyframes([
style({ opacity: '0', transform: '{{ transform }} scale(0.0)', offset: 0 }),
style({ opacity: '0', transform: '{{ transform }} scale(0.8)', offset: 0.5 }),
style({ opacity: '1', transform: '{{ transform }} scale(1.0)', offset: 1 })
])
)
],
{
params: {
time: '350ms',
easing: 'cubic-bezier(.7,.31,.72,1.47)',
display: 'block',
transform: 'translate(-50%, -50%)'
}
}
);

90
npm/ng-packs/nx/ng-packs/libs/theme-shared/src/lib/animations/collapse.animations.ts

@ -0,0 +1,90 @@
import {
animate,
animation,
trigger,
state,
style,
transition,
useAnimation,
} from '@angular/animations';
export const collapseY = animation(
[
style({ height: '*', overflow: 'hidden', 'box-sizing': 'border-box' }),
animate('{{ time }} {{ easing }}', style({ height: '0', padding: '0px' })),
],
{ params: { time: '350ms', easing: 'ease' } },
);
export const collapseYWithMargin = animation(
[
style({ 'margin-top': '0' }),
animate('{{ time }} {{ easing }}', style({ 'margin-left': '-100%' })),
],
{
params: { time: '500ms', easing: 'ease' },
},
);
export const collapseX = animation(
[
style({ width: '*', overflow: 'hidden', 'box-sizing': 'border-box' }),
animate('{{ time }} {{ easing }}', style({ width: '0', padding: '0px' })),
],
{ params: { time: '350ms', easing: 'ease' } },
);
export const expandY = animation(
[
style({ height: '0', overflow: 'hidden', 'box-sizing': 'border-box' }),
animate('{{ time }} {{ easing }}', style({ height: '*', padding: '*' })),
],
{ params: { time: '350ms', easing: 'ease' } },
);
export const expandYWithMargin = animation(
[
style({ 'margin-top': '-100%' }),
animate('{{ time }} {{ easing }}', style({ 'margin-top': '0' })),
],
{
params: { time: '500ms', easing: 'ease' },
},
);
export const expandX = animation(
[
style({ width: '0', overflow: 'hidden', 'box-sizing': 'border-box' }),
animate('{{ time }} {{ easing }}', style({ width: '*', padding: '*' })),
],
{ params: { time: '350ms', easing: 'ease' } },
);
export const collapse = trigger('collapse', [
state('collapsed', style({ height: '0', overflow: 'hidden' })),
state('expanded', style({ height: '*', overflow: 'hidden' })),
transition('expanded => collapsed', useAnimation(collapseY)),
transition('collapsed => expanded', useAnimation(expandY)),
]);
export const collapseWithMargin = trigger('collapseWithMargin', [
state('collapsed', style({ 'margin-top': '-100%' })),
state('expanded', style({ 'margin-top': '0' })),
transition('expanded => collapsed', useAnimation(collapseYWithMargin), {
params: { time: '400ms', easing: 'linear' },
}),
transition('collapsed => expanded', useAnimation(expandYWithMargin)),
]);
export const collapseLinearWithMargin = trigger('collapseLinearWithMargin', [
state('collapsed', style({ 'margin-top': '-100vh' })),
state('expanded', style({ 'margin-top': '0' })),
transition(
'expanded => collapsed',
useAnimation(collapseYWithMargin, { params: { time: '200ms', easing: 'linear' } }),
),
transition(
'collapsed => expanded',
useAnimation(expandYWithMargin, { params: { time: '250ms', easing: 'linear' } }),
),
]);

74
npm/ng-packs/nx/ng-packs/libs/theme-shared/src/lib/animations/fade.animations.ts

@ -0,0 +1,74 @@
import { animate, animation, style } from '@angular/animations';
export const fadeIn = animation([style({ opacity: '0' }), animate('{{ time}} {{ easing }}', style({ opacity: '1' }))], {
params: { time: '350ms', easing: 'ease' },
});
export const fadeOut = animation(
[style({ opacity: '1' }), animate('{{ time}} {{ easing }}', style({ opacity: '0' }))],
{ params: { time: '350ms', easing: 'ease' } },
);
export const fadeInDown = animation(
[
style({ opacity: '0', transform: '{{ transform }} translateY(-20px)' }),
animate('{{ time }} {{ easing }}', style({ opacity: '1', transform: '{{ transform }} translateY(0)' })),
],
{ params: { time: '350ms', easing: 'ease', transform: '' } },
);
export const fadeInUp = animation(
[
style({ opacity: '0', transform: '{{ transform }} translateY(20px)' }),
animate('{{ time }} {{ easing }}', style({ opacity: '1', transform: '{{ transform }} translateY(0)' })),
],
{ params: { time: '350ms', easing: 'ease', transform: '' } },
);
export const fadeInLeft = animation(
[
style({ opacity: '0', transform: '{{ transform }} translateX(20px)' }),
animate('{{ time }} {{ easing }}', style({ opacity: '1', transform: '{{ transform }} translateX(0)' })),
],
{ params: { time: '350ms', easing: 'ease', transform: '' } },
);
export const fadeInRight = animation(
[
style({ opacity: '0', transform: '{{ transform }} translateX(-20px)' }),
animate('{{ time }} {{ easing }}', style({ opacity: '1', transform: '{{ transform }} translateX(0)' })),
],
{ params: { time: '350ms', easing: 'ease', transform: '' } },
);
export const fadeOutDown = animation(
[
style({ opacity: '1', transform: '{{ transform }} translateY(0)' }),
animate('{{ time }} {{ easing }}', style({ opacity: '0', transform: '{{ transform }} translateY(20px)' })),
],
{ params: { time: '350ms', easing: 'ease', transform: '' } },
);
export const fadeOutUp = animation(
[
style({ opacity: '1', transform: '{{ transform }} translateY(0)' }),
animate('{{ time }} {{ easing }}', style({ opacity: '0', transform: '{{ transform }} translateY(-20px)' })),
],
{ params: { time: '350ms', easing: 'ease', transform: '' } },
);
export const fadeOutLeft = animation(
[
style({ opacity: '1', transform: '{{ transform }} translateX(0)' }),
animate('{{ time }} {{ easing }}', style({ opacity: '0', transform: '{{ transform }} translateX(20px)' })),
],
{ params: { time: '350ms', easing: 'ease', transform: '' } },
);
export const fadeOutRight = animation(
[
style({ opacity: '1', transform: '{{ transform }} translateX(0)' }),
animate('{{ time }} {{ easing }}', style({ opacity: '0', transform: '{{ transform }} translateX(-20px)' })),
],
{ params: { time: '350ms', easing: 'ease', transform: '' } },
);

6
npm/ng-packs/nx/ng-packs/libs/theme-shared/src/lib/animations/index.ts

@ -0,0 +1,6 @@
export * from './bounce.animations';
export * from './collapse.animations';
export * from './fade.animations';
export * from './modal.animations';
export * from './slide.animations';
export * from './toast.animations';

12
npm/ng-packs/nx/ng-packs/libs/theme-shared/src/lib/animations/modal.animations.ts

@ -0,0 +1,12 @@
import { transition, trigger, useAnimation } from '@angular/animations';
import { fadeIn, fadeInDown, fadeOut } from './fade.animations';
export const fadeAnimation = trigger('fade', [
transition(':enter', useAnimation(fadeIn)),
transition(':leave', useAnimation(fadeOut)),
]);
export const dialogAnimation = trigger('dialog', [
transition(':enter', useAnimation(fadeInDown)),
transition(':leave', useAnimation(fadeOut)),
]);

7
npm/ng-packs/nx/ng-packs/libs/theme-shared/src/lib/animations/slide.animations.ts

@ -0,0 +1,7 @@
import { animate, state, style, transition, trigger, query } from '@angular/animations';
export const slideFromBottom = trigger('slideFromBottom', [
transition('* <=> *', [
style({ 'margin-top': '20px', opacity: '0' }),
animate('0.2s ease-out', style({ opacity: '1', 'margin-top': '0px' })),
]),
]);

17
npm/ng-packs/nx/ng-packs/libs/theme-shared/src/lib/animations/toast.animations.ts

@ -0,0 +1,17 @@
import { animate, query, style, transition, trigger } from '@angular/animations';
export const toastInOut = trigger('toastInOut', [
transition('* <=> *', [
query(
':enter',
[
style({ opacity: 0, transform: 'translateY(20px)' }),
animate('350ms ease', style({ opacity: 1, transform: 'translateY(0)' })),
],
{ optional: true },
),
query(':leave', animate('450ms ease', style({ opacity: 0 })), {
optional: true,
}),
]),
]);

13
npm/ng-packs/nx/ng-packs/libs/theme-shared/src/lib/components/breadcrumb/breadcrumb.component.html

@ -0,0 +1,13 @@
<ol class="breadcrumb" *ngIf="segments.length">
<li class="breadcrumb-item">
<a routerLink="/"><i class="fa fa-home"></i> </a>
</li>
<li
*ngFor="let segment of segments; let last = last"
class="breadcrumb-item"
[class.active]="last"
aria-current="page"
>
{{ segment.name | abpLocalization }}
</li>
</ol>

58
npm/ng-packs/nx/ng-packs/libs/theme-shared/src/lib/components/breadcrumb/breadcrumb.component.ts

@ -0,0 +1,58 @@
import {
ABP,
getRoutePath,
RouterEvents,
RoutesService,
SubscriptionService,
TreeNode,
} from '@abp/ng.core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { map, startWith } from 'rxjs/operators';
import { eThemeSharedRouteNames } from '../../enums';
@Component({
selector: 'abp-breadcrumb',
templateUrl: './breadcrumb.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [SubscriptionService],
})
export class BreadcrumbComponent implements OnInit {
segments: Partial<ABP.Route>[] = [];
constructor(
public readonly cdRef: ChangeDetectorRef,
private router: Router,
private routes: RoutesService,
private subscription: SubscriptionService,
private routerEvents: RouterEvents,
) {}
ngOnInit(): void {
this.subscription.addOne(
this.routerEvents.getNavigationEvents('End').pipe(
// tslint:disable-next-line:deprecation
startWith(null),
map(() => this.routes.search({ path: getRoutePath(this.router) })),
),
route => {
this.segments = [];
if (route) {
let node = { parent: route } as TreeNode<ABP.Route>;
while (node.parent) {
node = node.parent;
const { parent, children, isLeaf, ...segment } = node;
if (!isAdministration(segment)) this.segments.unshift(segment);
}
this.cdRef.detectChanges();
}
},
);
}
}
function isAdministration(route: ABP.Route) {
return route.name === eThemeSharedRouteNames.Administration;
}

82
npm/ng-packs/nx/ng-packs/libs/theme-shared/src/lib/components/button/button.component.ts

@ -0,0 +1,82 @@
import {
Component,
EventEmitter,
Input,
Output,
ViewChild,
ElementRef,
Renderer2,
OnInit,
} from '@angular/core';
import { ABP } from '@abp/ng.core';
@Component({
selector: 'abp-button',
template: `
<button
#button
[id]="buttonId"
[attr.type]="buttonType"
[ngClass]="buttonClass"
[disabled]="loading || disabled"
(click.stop)="click.next($event); abpClick.next($event)"
(focus)="focus.next($event); abpFocus.next($event)"
(blur)="blur.next($event); abpBlur.next($event)"
>
<i [ngClass]="icon" class="mr-1"></i><ng-content></ng-content>
</button>
`,
})
export class ButtonComponent implements OnInit {
@Input()
buttonId = '';
@Input()
buttonClass = 'btn btn-primary';
@Input()
buttonType = 'button';
@Input()
iconClass: string;
@Input()
loading = false;
@Input()
disabled = false;
@Input()
attributes: ABP.Dictionary<string>;
// tslint:disable
@Output() readonly click = new EventEmitter<MouseEvent>();
@Output() readonly focus = new EventEmitter<FocusEvent>();
@Output() readonly blur = new EventEmitter<FocusEvent>();
// tslint:enable
@Output() readonly abpClick = new EventEmitter<MouseEvent>();
@Output() readonly abpFocus = new EventEmitter<FocusEvent>();
@Output() readonly abpBlur = new EventEmitter<FocusEvent>();
@ViewChild('button', { static: true })
buttonRef: ElementRef<HTMLButtonElement>;
get icon(): string {
return `${this.loading ? 'fa fa-spinner fa-spin' : this.iconClass || 'd-none'}`;
}
constructor(private renderer: Renderer2) {}
ngOnInit() {
if (this.attributes) {
Object.keys(this.attributes).forEach(key => {
this.renderer.setAttribute(this.buttonRef.nativeElement, key, this.attributes[key]);
});
}
}
}

11
npm/ng-packs/nx/ng-packs/libs/theme-shared/src/lib/components/chart/chart.component.html

@ -0,0 +1,11 @@
<div
style="position:relative"
[style.width]="responsive && !width ? null : width"
[style.height]="responsive && !height ? null : height"
>
<canvas
[attr.width]="responsive && !width ? null : width"
[attr.height]="responsive && !height ? null : height"
(click)="onCanvasClick($event)"
></canvas>
</div>

142
npm/ng-packs/nx/ng-packs/libs/theme-shared/src/lib/components/chart/chart.component.ts

@ -0,0 +1,142 @@
import {
AfterViewInit,
Component,
ElementRef,
EventEmitter,
Input,
OnDestroy,
Output,
ChangeDetectorRef,
} from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { chartJsLoaded$ } from '../../utils/widget-utils';
declare const Chart: any;
@Component({
selector: 'abp-chart',
templateUrl: './chart.component.html',
})
export class ChartComponent implements AfterViewInit, OnDestroy {
@Input() type: string;
@Input() options: any = {};
@Input() plugins: any[] = [];
@Input() width: string;
@Input() height: string;
@Input() responsive = true;
// tslint:disable-next-line: no-output-on-prefix
@Output() readonly onDataSelect: EventEmitter<any> = new EventEmitter();
@Output() readonly initialized = new BehaviorSubject(this);
private _initialized: boolean;
_data: any;
chart: any;
constructor(public el: ElementRef, private cdRef: ChangeDetectorRef) {}
@Input() get data(): any {
return this._data;
}
set data(val: any) {
this._data = val;
this.reinit();
}
get canvas() {
return this.el.nativeElement.children[0].children[0];
}
get base64Image() {
return this.chart.toBase64Image();
}
ngAfterViewInit() {
chartJsLoaded$.subscribe(() => {
this.testChartJs();
this.initChart();
this._initialized = true;
});
}
testChartJs() {
try {
// tslint:disable-next-line: no-unused-expression
Chart;
} catch (error) {
throw new Error(`Chart is not found. Import the Chart from app.module like shown below:
import('chart.js');
`);
}
}
onCanvasClick = event => {
if (this.chart) {
const element = this.chart.getElementAtEvent(event);
const dataset = this.chart.getDatasetAtEvent(event);
if (element && element.length && dataset) {
this.onDataSelect.emit({
originalEvent: event,
element: element[0],
dataset,
});
}
}
};
initChart = () => {
const opts = this.options || {};
opts.responsive = this.responsive;
// allows chart to resize in responsive mode
if (opts.responsive && (this.height || this.width)) {
opts.maintainAspectRatio = false;
}
this.chart = new Chart(this.canvas, {
type: this.type,
data: this.data,
options: this.options,
plugins: this.plugins,
});
this.cdRef.detectChanges();
};
generateLegend = () => {
if (this.chart) {
return this.chart.generateLegend();
}
};
refresh = () => {
if (this.chart) {
this.chart.update();
this.cdRef.detectChanges();
}
};
reinit = () => {
if (this.chart) {
this.chart.destroy();
this.initChart();
}
};
ngOnDestroy() {
if (this.chart) {
this.chart.destroy();
this._initialized = false;
this.chart = null;
}
}
}

39
npm/ng-packs/nx/ng-packs/libs/theme-shared/src/lib/components/confirmation/confirmation.component.html

@ -0,0 +1,39 @@
<div class="confirmation" *ngIf="confirmation$ | async as data">
<div
class="confirmation-backdrop"
(click)="data.options?.dismissible ? close(dismiss) : null"
></div>
<div class="confirmation-dialog">
<div class="icon-container" [ngClass]="data.severity" *ngIf="data.severity">
<i class="fa icon" [ngClass]="getIconClass(data)"></i>
</div>
<div class="content">
<h1
class="title"
*ngIf="data.title"
[innerHTML]="data.title | abpLocalization: data.options?.titleLocalizationParams"
></h1>
<p
class="message"
*ngIf="data.message"
[innerHTML]="data.message | abpLocalization: data.options?.messageLocalizationParams"
></p>
</div>
<div class="footer">
<button
id="cancel"
class="confirmation-button confirmation-button--reject"
[innerHTML]="data.options?.cancelText || 'AbpUi::Cancel' | abpLocalization"
*ngIf="!data?.options?.hideCancelBtn"
(click)="close(reject)"
></button>
<button
id="confirm"
class="confirmation-button confirmation-button--approve"
[innerHTML]="data.options?.yesText || 'AbpUi::Yes' | abpLocalization"
*ngIf="!data?.options?.hideYesBtn"
(click)="close(confirm)"
></button>
</div>
</div>
</div>

82
npm/ng-packs/nx/ng-packs/libs/theme-shared/src/lib/components/confirmation/confirmation.component.scss

@ -0,0 +1,82 @@
.confirmation {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: flex;
align-items: center;
justify-content: center;
z-index: 1060;
.confirmation-backdrop {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: 1061 !important;
}
.confirmation-dialog {
display: flex;
flex-direction: column;
margin: 20px auto;
padding: 0;
width: 450px;
min-height: 300px;
z-index: 1062 !important;
@media screen and (max-width: 500px) {
width: 90vw;
}
.icon-container {
display: flex;
align-items: center;
justify-content: center;
margin: 0 0 10px 0;
padding: 20px;
.icon {
width: 100px;
height: 100px;
stroke-width: 1;
font-size: 80px;
text-align: center;
}
}
.content {
flex-grow: 1;
display: block;
.title {
display: block;
margin: 0;
padding: 0;
font-size: 27px;
font-weight: 600;
text-align: center;
}
.message {
display: block;
margin: 10px auto;
padding: 20px;
font-size: 16px;
font-weight: 400;
text-align: center;
}
}
.footer {
display: flex;
align-items: center;
justify-content: flex-end;
margin: 10px 0 0 0;
padding: 20px;
width: 100%;
.confirmation-button {
display: inline-block;
margin: 0px 5px;
padding: 10px 20px;
border: none;
border-radius: 6px;
font-size: 14px;
font-weight: 600;
}
}
}
}

37
npm/ng-packs/nx/ng-packs/libs/theme-shared/src/lib/components/confirmation/confirmation.component.ts

@ -0,0 +1,37 @@
import { Component } from '@angular/core';
import { ReplaySubject } from 'rxjs';
import { Confirmation } from '../../models/confirmation';
@Component({
selector: 'abp-confirmation',
templateUrl: './confirmation.component.html',
styleUrls: ['./confirmation.component.scss'],
})
export class ConfirmationComponent {
confirm = Confirmation.Status.confirm;
reject = Confirmation.Status.reject;
dismiss = Confirmation.Status.dismiss;
confirmation$: ReplaySubject<Confirmation.DialogData>;
clear: (status: Confirmation.Status) => void;
close(status: Confirmation.Status) {
this.clear(status);
}
getIconClass({ severity }: Confirmation.DialogData): string {
switch (severity) {
case 'info':
return 'fa-info-circle';
case 'success':
return 'fa-check-circle';
case 'warning':
return 'fa-exclamation-triangle';
case 'error':
return 'fa-times-circle';
default:
return 'fa-question-circle';
}
}
}

37
npm/ng-packs/nx/ng-packs/libs/theme-shared/src/lib/components/http-error-wrapper/http-error-wrapper.component.html

@ -0,0 +1,37 @@
<div
#container
id="abp-http-error-container"
class="error"
[style.backgroundColor]="backgroundColor"
>
<button
*ngIf="!hideCloseIcon"
id="abp-close-button"
type="button"
class="close mr-2"
(click)="destroy()"
>
<span aria-hidden="true">&times;</span>
</button>
<div *ngIf="!customComponent" class="row centered">
<div class="col-md-12">
<div class="error-template">
<h1>{{ statusText }} {{ title | abpLocalization }}</h1>
<div class="error-details">
{{ details | abpLocalization }}
</div>
<div class="error-actions">
<a
*ngIf="isHomeShow"
(click)="destroy()"
routerLink="/"
class="btn btn-primary btn-md mt-2"
><span class="glyphicon glyphicon-home"></span>
{{ { key: '::Menu:Home', defaultValue: 'Home' } | abpLocalization }}
</a>
</div>
</div>
</div>
</div>
</div>

14
npm/ng-packs/nx/ng-packs/libs/theme-shared/src/lib/components/http-error-wrapper/http-error-wrapper.component.scss

@ -0,0 +1,14 @@
.error {
position: fixed;
top: 0;
width: 100vw;
height: 100vh;
z-index: 999999;
}
.centered {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}

90
npm/ng-packs/nx/ng-packs/libs/theme-shared/src/lib/components/http-error-wrapper/http-error-wrapper.component.ts

@ -0,0 +1,90 @@
import { Config, SubscriptionService } from '@abp/ng.core';
import {
AfterViewInit,
ApplicationRef,
Component,
ComponentFactoryResolver,
ElementRef,
EmbeddedViewRef,
Injector,
OnDestroy,
OnInit,
Type,
ViewChild,
} from '@angular/core';
import { fromEvent, Subject } from 'rxjs';
import { debounceTime, filter } from 'rxjs/operators';
import snq from 'snq';
@Component({
selector: 'abp-http-error-wrapper',
templateUrl: './http-error-wrapper.component.html',
styleUrls: ['http-error-wrapper.component.scss'],
providers: [SubscriptionService],
})
export class HttpErrorWrapperComponent implements AfterViewInit, OnDestroy, OnInit {
appRef: ApplicationRef;
cfRes: ComponentFactoryResolver;
injector: Injector;
status = 0;
title: Config.LocalizationParam = 'Oops!';
details: Config.LocalizationParam = 'Sorry, an error has occured.';
customComponent: Type<any> = null;
destroy$: Subject<void>;
hideCloseIcon = false;
backgroundColor: string;
isHomeShow = true;
@ViewChild('container', { static: false })
containerRef: ElementRef<HTMLDivElement>;
get statusText(): string {
return this.status ? `[${this.status}]` : '';
}
constructor(private subscription: SubscriptionService) {}
ngOnInit() {
this.backgroundColor =
snq(() => window.getComputedStyle(document.body).getPropertyValue('background-color')) ||
'#fff';
}
ngAfterViewInit() {
if (this.customComponent) {
const customComponentRef = this.cfRes
.resolveComponentFactory(this.customComponent)
.create(this.injector);
customComponentRef.instance.errorStatus = this.status;
customComponentRef.instance.destroy$ = this.destroy$;
this.appRef.attachView(customComponentRef.hostView);
this.containerRef.nativeElement.appendChild(
(customComponentRef.hostView as EmbeddedViewRef<any>).rootNodes[0],
);
customComponentRef.changeDetectorRef.detectChanges();
}
const keyup$ = fromEvent(document, 'keyup').pipe(
debounceTime(150),
filter((key: KeyboardEvent) => key && key.key === 'Escape'),
);
this.subscription.addOne(keyup$, () => this.destroy());
}
ngOnDestroy() {}
destroy() {
this.destroy$.next();
this.destroy$.complete();
}
}

15
npm/ng-packs/nx/ng-packs/libs/theme-shared/src/lib/components/index.ts

@ -0,0 +1,15 @@
export * from './breadcrumb/breadcrumb.component';
export * from './button/button.component';
export * from './chart/chart.component';
export * from './confirmation/confirmation.component';
export * from './http-error-wrapper/http-error-wrapper.component';
export * from './loader-bar/loader-bar.component';
export * from './loading/loading.component';
export * from './modal/modal.component';
export * from './modal/modal-close.directive';
export * from './modal/modal-ref.service';
export * from './sort-order-icon/sort-order-icon.component';
export * from './table-empty-message/table-empty-message.component';
export * from './table/table.component';
export * from './toast-container/toast-container.component';
export * from './toast/toast.component';

24
npm/ng-packs/nx/ng-packs/libs/theme-shared/src/lib/components/loader-bar/loader-bar.component.scss

@ -0,0 +1,24 @@
.abp-loader-bar {
left: 0;
opacity: 0;
position: fixed;
top: 0;
transition: opacity 0.4s linear 0.4s;
z-index: 99999;
&.is-loading {
opacity: 1;
transition: none;
}
.abp-progress {
height: 3px;
left: 0;
position: fixed;
top: 0;
&.progressing {
transition: width 0.4s ease;
}
}
}

120
npm/ng-packs/nx/ng-packs/libs/theme-shared/src/lib/components/loader-bar/loader-bar.component.ts

@ -0,0 +1,120 @@
import { HttpWaitService, RouterWaitService, SubscriptionService } from '@abp/ng.core';
import { ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { combineLatest, Subscription, timer } from 'rxjs';
@Component({
selector: 'abp-loader-bar',
template: `
<div id="abp-loader-bar" [ngClass]="containerClass" [class.is-loading]="isLoading">
<div
class="abp-progress"
[class.progressing]="progressLevel"
[style.width.vw]="progressLevel"
[ngStyle]="{
'background-color': color,
'box-shadow': boxShadow
}"
></div>
</div>
`,
styleUrls: ['./loader-bar.component.scss'],
providers: [SubscriptionService],
})
export class LoaderBarComponent implements OnDestroy, OnInit {
protected _isLoading: boolean;
@Input()
set isLoading(value: boolean) {
this._isLoading = value;
this.cdRef.detectChanges();
}
get isLoading(): boolean {
return this._isLoading;
}
@Input()
containerClass = 'abp-loader-bar';
@Input()
color = '#77b6ff';
progressLevel = 0;
interval = new Subscription();
timer = new Subscription();
intervalPeriod = 350;
stopDelay = 800;
private readonly clearProgress = () => {
this.progressLevel = 0;
this.cdRef.detectChanges();
};
private readonly reportProgress = () => {
if (this.progressLevel < 75) {
this.progressLevel += 1 + Math.random() * 9;
} else if (this.progressLevel < 90) {
this.progressLevel += 0.4;
} else if (this.progressLevel < 100) {
this.progressLevel += 0.1;
} else {
this.interval.unsubscribe();
}
this.cdRef.detectChanges();
};
get boxShadow(): string {
return `0 0 10px rgba(${this.color}, 0.5)`;
}
constructor(
private router: Router,
private cdRef: ChangeDetectorRef,
private subscription: SubscriptionService,
private httpWaitService: HttpWaitService,
private routerWaitService: RouterWaitService,
) {}
ngOnInit() {
this.subscribeLoading();
}
subscribeLoading() {
this.subscription.addOne(
combineLatest([this.httpWaitService.getLoading$(), this.routerWaitService.getLoading$()]),
([httpLoading, routerLoading]) => {
if (httpLoading || routerLoading) this.startLoading();
else this.stopLoading();
},
);
}
ngOnDestroy() {
this.interval.unsubscribe();
}
startLoading() {
if (this.isLoading || !this.interval.closed) return;
this.isLoading = true;
this.progressLevel = 0;
this.cdRef.detectChanges();
this.interval = timer(0, this.intervalPeriod).subscribe(this.reportProgress);
this.timer.unsubscribe();
}
stopLoading() {
this.interval.unsubscribe();
this.progressLevel = 100;
this.isLoading = false;
if (!this.timer.closed) return;
this.timer = timer(this.stopDelay).subscribe(this.clearProgress);
}
}

40
npm/ng-packs/nx/ng-packs/libs/theme-shared/src/lib/components/loading/loading.component.ts

@ -0,0 +1,40 @@
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
@Component({
selector: 'abp-loading',
template: `
<div class="abp-loading">
<i class="fa fa-spinner fa-pulse abp-spinner"></i>
</div>
`,
encapsulation: ViewEncapsulation.None,
styles: [
`
.abp-loading {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: 1040;
}
.abp-loading .abp-spinner {
position: absolute;
top: 50%;
left: 50%;
font-size: 14px;
-moz-transform: translateX(-50%) translateY(-50%);
-o-transform: translateX(-50%) translateY(-50%);
-ms-transform: translateX(-50%) translateY(-50%);
-webkit-transform: translateX(-50%) translateY(-50%);
transform: translateX(-50%) translateY(-50%);
}
`,
],
})
export class LoadingComponent implements OnInit {
constructor() {}
ngOnInit() {}
}

16
npm/ng-packs/nx/ng-packs/libs/theme-shared/src/lib/components/modal/modal-close.directive.ts

@ -0,0 +1,16 @@
import { Directive, HostListener, Optional } from '@angular/core';
import { ModalComponent } from './modal.component';
@Directive({ selector: '[abpClose]' })
export class ModalCloseDirective {
constructor(@Optional() private modal: ModalComponent) {
if (!modal) {
console.error('Please use abpClose within an abp-modal');
}
}
@HostListener('click')
onClick() {
this.modal?.close();
}
}

13
npm/ng-packs/nx/ng-packs/libs/theme-shared/src/lib/components/modal/modal-container.component.ts

@ -0,0 +1,13 @@
import { Component, ViewChild, ViewContainerRef } from '@angular/core';
/**
* @deprecated To be removed in v5.0
*/
@Component({
selector: 'abp-modal-container',
template: '<ng-container #container></ng-container>',
})
export class ModalContainerComponent {
@ViewChild('container', { static: true, read: ViewContainerRef })
container: ViewContainerRef;
}

26
npm/ng-packs/nx/ng-packs/libs/theme-shared/src/lib/components/modal/modal-ref.service.ts

@ -0,0 +1,26 @@
import { Injectable } from '@angular/core';
export type ModalDismissMode = 'hard' | 'soft';
export interface DismissableModal {
dismiss(mode: ModalDismissMode);
}
@Injectable({ providedIn: 'root' })
export class ModalRefService {
modalRefs: DismissableModal[] = [];
register(modal: DismissableModal) {
this.modalRefs.push(modal);
}
unregister(modal: DismissableModal) {
const index = this.modalRefs.indexOf(modal);
if (index > -1) {
this.modalRefs.splice(index, 1);
}
}
dismissAll(mode: ModalDismissMode) {
this.modalRefs.forEach(modal => modal.dismiss(mode));
}
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save