@acorex/platform 0.0.0-ACOREX
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/auth/README.md +3 -0
- package/common/README.md +3 -0
- package/core/README.md +4 -0
- package/fesm2022/acorex-platform-auth.mjs +1362 -0
- package/fesm2022/acorex-platform-auth.mjs.map +1 -0
- package/fesm2022/acorex-platform-common-common-settings.provider-G9XcXXOG.mjs +127 -0
- package/fesm2022/acorex-platform-common-common-settings.provider-G9XcXXOG.mjs.map +1 -0
- package/fesm2022/acorex-platform-common.mjs +4601 -0
- package/fesm2022/acorex-platform-common.mjs.map +1 -0
- package/fesm2022/acorex-platform-core.mjs +4374 -0
- package/fesm2022/acorex-platform-core.mjs.map +1 -0
- package/fesm2022/acorex-platform-domain.mjs +3234 -0
- package/fesm2022/acorex-platform-domain.mjs.map +1 -0
- package/fesm2022/acorex-platform-layout-builder.mjs +2847 -0
- package/fesm2022/acorex-platform-layout-builder.mjs.map +1 -0
- package/fesm2022/acorex-platform-layout-components-binding-expression-editor-popup.component-CXEdvDTf.mjs +121 -0
- package/fesm2022/acorex-platform-layout-components-binding-expression-editor-popup.component-CXEdvDTf.mjs.map +1 -0
- package/fesm2022/acorex-platform-layout-components.mjs +8583 -0
- package/fesm2022/acorex-platform-layout-components.mjs.map +1 -0
- package/fesm2022/acorex-platform-layout-designer.mjs +2474 -0
- package/fesm2022/acorex-platform-layout-designer.mjs.map +1 -0
- package/fesm2022/acorex-platform-layout-entity.mjs +19150 -0
- package/fesm2022/acorex-platform-layout-entity.mjs.map +1 -0
- package/fesm2022/acorex-platform-layout-views.mjs +1468 -0
- package/fesm2022/acorex-platform-layout-views.mjs.map +1 -0
- package/fesm2022/acorex-platform-layout-widget-core.mjs +2950 -0
- package/fesm2022/acorex-platform-layout-widget-core.mjs.map +1 -0
- package/fesm2022/acorex-platform-layout-widgets-button-widget-designer.component-Dy7jF-oD.mjs +72 -0
- package/fesm2022/acorex-platform-layout-widgets-button-widget-designer.component-Dy7jF-oD.mjs.map +1 -0
- package/fesm2022/acorex-platform-layout-widgets-file-list-popup.component-9uCkMxcc.mjs +158 -0
- package/fesm2022/acorex-platform-layout-widgets-file-list-popup.component-9uCkMxcc.mjs.map +1 -0
- package/fesm2022/acorex-platform-layout-widgets-image-preview.popup-C_EPAvCU.mjs +29 -0
- package/fesm2022/acorex-platform-layout-widgets-image-preview.popup-C_EPAvCU.mjs.map +1 -0
- package/fesm2022/acorex-platform-layout-widgets-page-widget-designer.component-D10yO28c.mjs +172 -0
- package/fesm2022/acorex-platform-layout-widgets-page-widget-designer.component-D10yO28c.mjs.map +1 -0
- package/fesm2022/acorex-platform-layout-widgets-repeater-widget-column.component-BGQqY5Mw.mjs +111 -0
- package/fesm2022/acorex-platform-layout-widgets-repeater-widget-column.component-BGQqY5Mw.mjs.map +1 -0
- package/fesm2022/acorex-platform-layout-widgets-tabular-data-edit-popup.component-DmzNTYiS.mjs +274 -0
- package/fesm2022/acorex-platform-layout-widgets-tabular-data-edit-popup.component-DmzNTYiS.mjs.map +1 -0
- package/fesm2022/acorex-platform-layout-widgets-tabular-data-view-popup.component-BNG_588B.mjs +64 -0
- package/fesm2022/acorex-platform-layout-widgets-tabular-data-view-popup.component-BNG_588B.mjs.map +1 -0
- package/fesm2022/acorex-platform-layout-widgets-text-block-widget-designer.component-Vo4fWHtX.mjs +34 -0
- package/fesm2022/acorex-platform-layout-widgets-text-block-widget-designer.component-Vo4fWHtX.mjs.map +1 -0
- package/fesm2022/acorex-platform-layout-widgets.mjs +29791 -0
- package/fesm2022/acorex-platform-layout-widgets.mjs.map +1 -0
- package/fesm2022/acorex-platform-native.mjs +155 -0
- package/fesm2022/acorex-platform-native.mjs.map +1 -0
- package/fesm2022/acorex-platform-runtime-catalog-command-definition.mjs +20 -0
- package/fesm2022/acorex-platform-runtime-catalog-command-definition.mjs.map +1 -0
- package/fesm2022/acorex-platform-runtime-catalog-query-definition.mjs +20 -0
- package/fesm2022/acorex-platform-runtime-catalog-query-definition.mjs.map +1 -0
- package/fesm2022/acorex-platform-runtime.mjs +899 -0
- package/fesm2022/acorex-platform-runtime.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-default-entity-master-create-view.component-Cvvr4HnL.mjs +160 -0
- package/fesm2022/acorex-platform-themes-default-entity-master-create-view.component-Cvvr4HnL.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-default-entity-master-modify-view.component-TYoLN1Jq.mjs +120 -0
- package/fesm2022/acorex-platform-themes-default-entity-master-modify-view.component-TYoLN1Jq.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-default-entity-master-single-view.component-C2z5Lq9y.mjs +237 -0
- package/fesm2022/acorex-platform-themes-default-entity-master-single-view.component-C2z5Lq9y.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-default-error-401.component-C7EYJzSr.mjs +31 -0
- package/fesm2022/acorex-platform-themes-default-error-401.component-C7EYJzSr.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-default-error-404.component-7MVLMwIa.mjs +25 -0
- package/fesm2022/acorex-platform-themes-default-error-404.component-7MVLMwIa.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-default-error-offline.component-DR6G8gPC.mjs +19 -0
- package/fesm2022/acorex-platform-themes-default-error-offline.component-DR6G8gPC.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-default.mjs +2589 -0
- package/fesm2022/acorex-platform-themes-default.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-shared-icon-chooser-column.component-CqkWJYdv.mjs +55 -0
- package/fesm2022/acorex-platform-themes-shared-icon-chooser-column.component-CqkWJYdv.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-shared-icon-chooser-view.component-BOTuLdWN.mjs +57 -0
- package/fesm2022/acorex-platform-themes-shared-icon-chooser-view.component-BOTuLdWN.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-shared-settings.provider-DSs1o1M6.mjs +168 -0
- package/fesm2022/acorex-platform-themes-shared-settings.provider-DSs1o1M6.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-column.component-CHfrTtol.mjs +65 -0
- package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-column.component-CHfrTtol.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-view.component-BSmvnUVq.mjs +64 -0
- package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-view.component-BSmvnUVq.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-shared.mjs +2125 -0
- package/fesm2022/acorex-platform-themes-shared.mjs.map +1 -0
- package/fesm2022/acorex-platform-workflow.mjs +2501 -0
- package/fesm2022/acorex-platform-workflow.mjs.map +1 -0
- package/fesm2022/acorex-platform.mjs +6 -0
- package/fesm2022/acorex-platform.mjs.map +1 -0
- package/layout/builder/README.md +1578 -0
- package/layout/components/README.md +3 -0
- package/layout/designer/README.md +4 -0
- package/layout/entity/README.md +4 -0
- package/layout/views/README.md +3 -0
- package/layout/widget-core/README.md +4 -0
- package/layout/widgets/README.md +3 -0
- package/native/README.md +4 -0
- package/package.json +103 -0
- package/runtime/README.md +3 -0
- package/themes/default/README.md +3 -0
- package/themes/shared/README.md +3 -0
- package/types/acorex-platform-auth.d.ts +680 -0
- package/types/acorex-platform-common.d.ts +2926 -0
- package/types/acorex-platform-core.d.ts +2896 -0
- package/types/acorex-platform-domain.d.ts +2353 -0
- package/types/acorex-platform-layout-builder.d.ts +926 -0
- package/types/acorex-platform-layout-components.d.ts +2903 -0
- package/types/acorex-platform-layout-designer.d.ts +422 -0
- package/types/acorex-platform-layout-entity.d.ts +3189 -0
- package/types/acorex-platform-layout-views.d.ts +667 -0
- package/types/acorex-platform-layout-widget-core.d.ts +1086 -0
- package/types/acorex-platform-layout-widgets.d.ts +5478 -0
- package/types/acorex-platform-native.d.ts +28 -0
- package/types/acorex-platform-runtime-catalog-command-definition.d.ts +137 -0
- package/types/acorex-platform-runtime-catalog-query-definition.d.ts +125 -0
- package/types/acorex-platform-runtime.d.ts +470 -0
- package/types/acorex-platform-themes-default.d.ts +573 -0
- package/types/acorex-platform-themes-shared.d.ts +170 -0
- package/types/acorex-platform-workflow.d.ts +1806 -0
- package/types/acorex-platform.d.ts +2 -0
- package/workflow/README.md +4 -0
|
@@ -0,0 +1,4601 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { InjectionToken, inject, Injectable, Injector, signal, makeEnvironmentProviders, NgModule, ErrorHandler, EventEmitter, Input, Output, Directive, ChangeDetectionStrategy, Component, ViewEncapsulation, Inject, runInInjectionContext, model, linkedSignal, afterNextRender } from '@angular/core';
|
|
3
|
+
import { kebabCase, merge, sortBy, cloneDeep, get, omit } from 'lodash-es';
|
|
4
|
+
import { Router, ROUTES, RouterModule } from '@angular/router';
|
|
5
|
+
import { AXPSessionService, AXPSessionStatus } from '@acorex/platform/auth';
|
|
6
|
+
import { Subject, distinctUntilChanged, merge as merge$1 } from 'rxjs';
|
|
7
|
+
import { AXPPlatformScope, AXPBroadcastEventService, objectKeyValueTransforms, AXPSystemActionType, AXPModuleManifestModule, AXPAppStartUpProvider, AXP_EXPRESSION_EVALUATOR_SCOPE_PROVIDER, AXPHookService, AXPDataGenerator, AXPModuleManifestRegistry } from '@acorex/platform/core';
|
|
8
|
+
import { AXTranslationService } from '@acorex/core/translation';
|
|
9
|
+
import { AXPWidgetsCatalog } from '@acorex/platform/layout/widget-core';
|
|
10
|
+
import { AXPopupModule, AXPopupService } from '@acorex/components/popup';
|
|
11
|
+
import { AXToastService, AXToastModule } from '@acorex/components/toast';
|
|
12
|
+
import { AXDateTimeModule } from '@acorex/core/date-time';
|
|
13
|
+
import * as i1 from '@acorex/platform/workflow';
|
|
14
|
+
import { AXPWorkflowService, createWorkFlowEvent, AXPWorkflowAction, AXPWorkflowError, AXPWorkflowModule } from '@acorex/platform/workflow';
|
|
15
|
+
import { AXDataSource } from '@acorex/cdk/common';
|
|
16
|
+
import { AXPCommandExecutor, AXPCommandService } from '@acorex/platform/runtime';
|
|
17
|
+
import { signalStore, withState, withMethods, patchState, withHooks } from '@ngrx/signals';
|
|
18
|
+
import { AXFormatService } from '@acorex/core/format';
|
|
19
|
+
import { AXDialogService } from '@acorex/components/dialog';
|
|
20
|
+
import { AXFileService } from '@acorex/core/file';
|
|
21
|
+
import * as i5 from '@acorex/components/button';
|
|
22
|
+
import { AXButtonModule } from '@acorex/components/button';
|
|
23
|
+
import { AXCheckBoxModule } from '@acorex/components/check-box';
|
|
24
|
+
import * as i6 from '@acorex/components/color-box';
|
|
25
|
+
import { AXColorBoxModule } from '@acorex/components/color-box';
|
|
26
|
+
import * as i4 from '@acorex/components/decorators';
|
|
27
|
+
import { AXDecoratorModule } from '@acorex/components/decorators';
|
|
28
|
+
import * as i3 from '@acorex/components/label';
|
|
29
|
+
import { AXLabelModule } from '@acorex/components/label';
|
|
30
|
+
import * as i2 from '@acorex/components/number-box';
|
|
31
|
+
import { AXNumberBoxModule } from '@acorex/components/number-box';
|
|
32
|
+
import { AXBasePageComponent } from '@acorex/components/page';
|
|
33
|
+
import { AXTextBoxModule } from '@acorex/components/text-box';
|
|
34
|
+
import * as i1$1 from '@angular/forms';
|
|
35
|
+
import { FormsModule } from '@angular/forms';
|
|
36
|
+
|
|
37
|
+
const AXP_APP_VERSION_PROVIDER = new InjectionToken('AXP_APP_VERSION_PROVIDER', {
|
|
38
|
+
providedIn: 'root',
|
|
39
|
+
factory: () => {
|
|
40
|
+
return new AXPAppVersionDefaultProvider();
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
class AXPAppVersionDefaultProvider {
|
|
44
|
+
async provider() {
|
|
45
|
+
return {
|
|
46
|
+
version: '1.0.0',
|
|
47
|
+
build: 1,
|
|
48
|
+
date: new Date(),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
class AXPAppVersionService {
|
|
53
|
+
constructor() {
|
|
54
|
+
this.provider = inject(AXP_APP_VERSION_PROVIDER);
|
|
55
|
+
}
|
|
56
|
+
async getAppVersion() {
|
|
57
|
+
return this.provider.provider();
|
|
58
|
+
}
|
|
59
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPAppVersionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
60
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPAppVersionService, providedIn: 'root' }); }
|
|
61
|
+
}
|
|
62
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPAppVersionService, decorators: [{
|
|
63
|
+
type: Injectable,
|
|
64
|
+
args: [{
|
|
65
|
+
providedIn: 'root',
|
|
66
|
+
}]
|
|
67
|
+
}] });
|
|
68
|
+
|
|
69
|
+
var AXPRelationshipKind;
|
|
70
|
+
(function (AXPRelationshipKind) {
|
|
71
|
+
AXPRelationshipKind[AXPRelationshipKind["Association"] = 0] = "Association";
|
|
72
|
+
AXPRelationshipKind[AXPRelationshipKind["Composition"] = 1] = "Composition";
|
|
73
|
+
AXPRelationshipKind[AXPRelationshipKind["Aggregation"] = 2] = "Aggregation";
|
|
74
|
+
})(AXPRelationshipKind || (AXPRelationshipKind = {}));
|
|
75
|
+
var AXPRelationshipCardinality;
|
|
76
|
+
(function (AXPRelationshipCardinality) {
|
|
77
|
+
AXPRelationshipCardinality[AXPRelationshipCardinality["OneToOne"] = 0] = "OneToOne";
|
|
78
|
+
AXPRelationshipCardinality[AXPRelationshipCardinality["OneToMany"] = 1] = "OneToMany";
|
|
79
|
+
AXPRelationshipCardinality[AXPRelationshipCardinality["ManyToMany"] = 2] = "ManyToMany";
|
|
80
|
+
})(AXPRelationshipCardinality || (AXPRelationshipCardinality = {}));
|
|
81
|
+
var AXPEntityCommandScope;
|
|
82
|
+
(function (AXPEntityCommandScope) {
|
|
83
|
+
AXPEntityCommandScope["TypeLevel"] = "typeLevel";
|
|
84
|
+
AXPEntityCommandScope["Selected"] = "selected";
|
|
85
|
+
AXPEntityCommandScope["Individual"] = "individual";
|
|
86
|
+
AXPEntityCommandScope["Section"] = "section";
|
|
87
|
+
})(AXPEntityCommandScope || (AXPEntityCommandScope = {}));
|
|
88
|
+
var AXPEntityQueryType;
|
|
89
|
+
(function (AXPEntityQueryType) {
|
|
90
|
+
AXPEntityQueryType["Single"] = "single";
|
|
91
|
+
AXPEntityQueryType["List"] = "list";
|
|
92
|
+
})(AXPEntityQueryType || (AXPEntityQueryType = {}));
|
|
93
|
+
function getEntityInfo(entity) {
|
|
94
|
+
return {
|
|
95
|
+
source: `${entity.module}.${entity.name}`,
|
|
96
|
+
module: {
|
|
97
|
+
route: kebabCase(entity.module),
|
|
98
|
+
},
|
|
99
|
+
entity: {
|
|
100
|
+
route: kebabCase(entity.name),
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function createQueryView(name, title, fixed, params = {
|
|
105
|
+
columns: [],
|
|
106
|
+
conditions: [],
|
|
107
|
+
sorts: [],
|
|
108
|
+
}) {
|
|
109
|
+
params = Object.assign({ columns: [], sorts: [], conditions: [] }, params);
|
|
110
|
+
return {
|
|
111
|
+
name,
|
|
112
|
+
title,
|
|
113
|
+
fixed,
|
|
114
|
+
columns: params.columns ?? [],
|
|
115
|
+
conditions: params.conditions ?? [],
|
|
116
|
+
sorts: params.sorts ?? [],
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function createAllQueryView(params = {
|
|
120
|
+
columns: [],
|
|
121
|
+
sorts: [],
|
|
122
|
+
conditions: [],
|
|
123
|
+
}) {
|
|
124
|
+
params = Object.assign({ columns: [], sorts: [], conditions: [] }, params);
|
|
125
|
+
return createQueryView('all', '@general:terms.interface.selection.all-items', true, {
|
|
126
|
+
columns: params.columns ?? [],
|
|
127
|
+
conditions: params.conditions ?? [],
|
|
128
|
+
sorts: params.sorts ?? [],
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
// new version
|
|
132
|
+
var AXPEntityType;
|
|
133
|
+
(function (AXPEntityType) {
|
|
134
|
+
AXPEntityType[AXPEntityType["Entity"] = 0] = "Entity";
|
|
135
|
+
AXPEntityType[AXPEntityType["AggregateRoot"] = 1] = "AggregateRoot";
|
|
136
|
+
AXPEntityType[AXPEntityType["ValueObject"] = 2] = "ValueObject";
|
|
137
|
+
})(AXPEntityType || (AXPEntityType = {}));
|
|
138
|
+
|
|
139
|
+
const EQ_OPER = { name: 'equal', title: '@general:terms.operators.equal', hasValue: true };
|
|
140
|
+
const NOT_EQ_OPER = { name: 'notEqual', title: '@general:terms.operators.not-equal', hasValue: true };
|
|
141
|
+
const GT_OPER = { name: 'greaterThan', title: '@general:terms.operators.greater-than', hasValue: true };
|
|
142
|
+
const LT_OPER = { name: 'lessThan', title: '@general:terms.operators.less-than', hasValue: true };
|
|
143
|
+
const GTE_OPER = {
|
|
144
|
+
name: 'greaterThanOrEqual',
|
|
145
|
+
title: '@general:terms.operators.greater-than-or-equal',
|
|
146
|
+
hasValue: true,
|
|
147
|
+
};
|
|
148
|
+
const LTE_OPER = { name: 'lessThanOrEqual', title: '@general:terms.operators.less-than-or-equal', hasValue: true };
|
|
149
|
+
const CONTAINS_OPER = { name: 'contains', title: '@general:terms.operators.contains', hasValue: true };
|
|
150
|
+
const NOT_CONTAINS_OPER = { name: 'notContains', title: '@general:terms.operators.not-contains', hasValue: true };
|
|
151
|
+
const IN_OPER = { name: 'in', title: '@general:terms.operators.in', hasValue: true };
|
|
152
|
+
const STARTS_WITH_OPER = { name: 'startsWith', title: '@general:terms.operators.starts-with', hasValue: true };
|
|
153
|
+
const ENDS_WITH_OPER = { name: 'endsWith', title: '@general:terms.operators.ends-with', hasValue: true };
|
|
154
|
+
const IS_EMPTY_OPER = { name: 'isEmpty', title: '@general:terms.operators.is-empty', hasValue: false };
|
|
155
|
+
const IS_NOT_EMPTY_OPER = { name: 'isNotEmpty', title: '@general:terms.operators.is-not-empty', hasValue: false };
|
|
156
|
+
const BETWEEN_OPER = { name: 'between', title: '@general:terms.operators.between', hasValue: true };
|
|
157
|
+
const STRING_OPERATORS = [
|
|
158
|
+
EQ_OPER,
|
|
159
|
+
NOT_EQ_OPER,
|
|
160
|
+
CONTAINS_OPER,
|
|
161
|
+
NOT_CONTAINS_OPER,
|
|
162
|
+
STARTS_WITH_OPER,
|
|
163
|
+
ENDS_WITH_OPER,
|
|
164
|
+
IS_EMPTY_OPER,
|
|
165
|
+
IS_NOT_EMPTY_OPER,
|
|
166
|
+
IN_OPER
|
|
167
|
+
];
|
|
168
|
+
const NUMBER_OPERATORS = [
|
|
169
|
+
EQ_OPER,
|
|
170
|
+
NOT_EQ_OPER,
|
|
171
|
+
BETWEEN_OPER,
|
|
172
|
+
GT_OPER,
|
|
173
|
+
GTE_OPER,
|
|
174
|
+
LT_OPER,
|
|
175
|
+
LTE_OPER,
|
|
176
|
+
IS_EMPTY_OPER,
|
|
177
|
+
IS_NOT_EMPTY_OPER,
|
|
178
|
+
];
|
|
179
|
+
const BOOLEAN_OPERATORS = [EQ_OPER];
|
|
180
|
+
const DATE_OPERATORS = [
|
|
181
|
+
EQ_OPER,
|
|
182
|
+
NOT_EQ_OPER,
|
|
183
|
+
GT_OPER,
|
|
184
|
+
GTE_OPER,
|
|
185
|
+
LT_OPER,
|
|
186
|
+
LTE_OPER,
|
|
187
|
+
IS_EMPTY_OPER,
|
|
188
|
+
IS_NOT_EMPTY_OPER,
|
|
189
|
+
];
|
|
190
|
+
const ALL_DEFAULT_OPERATORS = [
|
|
191
|
+
EQ_OPER,
|
|
192
|
+
NOT_EQ_OPER,
|
|
193
|
+
GT_OPER,
|
|
194
|
+
LT_OPER,
|
|
195
|
+
GTE_OPER,
|
|
196
|
+
LTE_OPER,
|
|
197
|
+
CONTAINS_OPER,
|
|
198
|
+
IN_OPER,
|
|
199
|
+
NOT_CONTAINS_OPER,
|
|
200
|
+
STARTS_WITH_OPER,
|
|
201
|
+
ENDS_WITH_OPER,
|
|
202
|
+
IS_EMPTY_OPER,
|
|
203
|
+
IS_NOT_EMPTY_OPER,
|
|
204
|
+
BETWEEN_OPER,
|
|
205
|
+
];
|
|
206
|
+
|
|
207
|
+
const AXP_HOME_PAGES = new InjectionToken('AXP_HOME_PAGES');
|
|
208
|
+
const AXP_HOME_PAGE_DEFAULT_KEY = new InjectionToken('AXP_HOME_PAGE_DEFAULT_KEY', {
|
|
209
|
+
factory: () => 'home'
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Regional and locale-related setting keys used by platform widgets and components.
|
|
214
|
+
* Most values use the `LocaleManagement:Setting:Regional.*` prefix; definitions live in the locale-management module where applicable.
|
|
215
|
+
*/
|
|
216
|
+
var AXPRegionalSetting;
|
|
217
|
+
(function (AXPRegionalSetting) {
|
|
218
|
+
AXPRegionalSetting["LocaleProfile"] = "LocaleManagement:Setting:Regional.LocaleProfile";
|
|
219
|
+
AXPRegionalSetting["TimeZone"] = "LocaleManagement:Setting:Regional.TimeZone";
|
|
220
|
+
AXPRegionalSetting["Language"] = "LocaleManagement:Setting:Regional.Language";
|
|
221
|
+
AXPRegionalSetting["Country"] = "LocaleManagement:Setting:Regional.Country";
|
|
222
|
+
AXPRegionalSetting["Calendar"] = "LocaleManagement:Setting:Regional.Calendar";
|
|
223
|
+
AXPRegionalSetting["FirstDayOfWeek"] = "LocaleManagement:Setting:Regional.FirstDayOfWeek";
|
|
224
|
+
AXPRegionalSetting["WeekendDays"] = "LocaleManagement:Setting:Regional.WeekendDays";
|
|
225
|
+
AXPRegionalSetting["ShortDate"] = "LocaleManagement:Setting:Regional.ShortDate";
|
|
226
|
+
AXPRegionalSetting["MediumDate"] = "LocaleManagement:Setting:Regional.MediumDate";
|
|
227
|
+
AXPRegionalSetting["LongDate"] = "LocaleManagement:Setting:Regional.LongDate";
|
|
228
|
+
AXPRegionalSetting["ShortTime"] = "LocaleManagement:Setting:Regional.ShortTime";
|
|
229
|
+
AXPRegionalSetting["MediumTime"] = "LocaleManagement:Setting:Regional.MediumTime";
|
|
230
|
+
AXPRegionalSetting["LongTime"] = "LocaleManagement:Setting:Regional.LongTime";
|
|
231
|
+
AXPRegionalSetting["MeasurementUnits"] = "LocaleManagement:Setting:Regional.MeasurementUnits";
|
|
232
|
+
AXPRegionalSetting["TemperatureUnits"] = "LocaleManagement:Setting:Regional.TemperatureUnits";
|
|
233
|
+
AXPRegionalSetting["DistanceUnits"] = "LocaleManagement:Setting:Regional.DistanceUnits";
|
|
234
|
+
AXPRegionalSetting["WeightUnits"] = "LocaleManagement:Setting:Regional.WeightUnits";
|
|
235
|
+
AXPRegionalSetting["VolumeUnits"] = "LocaleManagement:Setting:Regional.VolumeUnits";
|
|
236
|
+
AXPRegionalSetting["SpeedUnits"] = "LocaleManagement:Setting:Regional.SpeedUnits";
|
|
237
|
+
AXPRegionalSetting["AreaUnits"] = "LocaleManagement:Setting:Regional.AreaUnits";
|
|
238
|
+
/** Default multi-language behavior for standard text field names; defined in locale-management `AXMRegionalSettingProvider`. */
|
|
239
|
+
AXPRegionalSetting["MultiLanguageSupport"] = "LocaleManagement:Setting:Regional.MultiLanguageSupport";
|
|
240
|
+
})(AXPRegionalSetting || (AXPRegionalSetting = {}));
|
|
241
|
+
|
|
242
|
+
const AXP_SETTING_VALUE_PROVIDER = new InjectionToken('AXP_SETTING_VALUE_PROVIDER', {
|
|
243
|
+
providedIn: 'root',
|
|
244
|
+
factory: () => {
|
|
245
|
+
const injector = inject(Injector);
|
|
246
|
+
return [
|
|
247
|
+
new AXPSettingValueProviderDefault(AXPPlatformScope.Platform, injector),
|
|
248
|
+
new AXPSettingValueProviderDefault(AXPPlatformScope.Tenant, injector),
|
|
249
|
+
new AXPSettingValueProviderDefault(AXPPlatformScope.User, injector)
|
|
250
|
+
];
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
class AXPSettingValueProviderDefault {
|
|
254
|
+
constructor(_scope, injector) {
|
|
255
|
+
this._scope = _scope;
|
|
256
|
+
this.injector = injector;
|
|
257
|
+
this.cache = new Map();
|
|
258
|
+
this.localStorageKey = null;
|
|
259
|
+
this.sessionService = this.injector.get(AXPSessionService);
|
|
260
|
+
// Platform scope doesn't depend on session, initialize immediately
|
|
261
|
+
if (this.scope === AXPPlatformScope.Platform) {
|
|
262
|
+
this.localStorageKey = `AXP_SETTINGS_SCOPE(P)`;
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
this.sessionService.status$.subscribe((status) => {
|
|
266
|
+
if (status === AXPSessionStatus.Authorized || status === AXPSessionStatus.Authenticated) {
|
|
267
|
+
if (this.scope === AXPPlatformScope.User) {
|
|
268
|
+
this.localStorageKey = `AXP_SETTINGS_SCOPE(${this.sessionService.user?.id ?? 'U'})`;
|
|
269
|
+
}
|
|
270
|
+
else if (this.scope === AXPPlatformScope.Tenant) {
|
|
271
|
+
this.localStorageKey = `AXP_SETTINGS_SCOPE(${this.sessionService.tenant?.id ?? 'T'})`;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
get scope() {
|
|
278
|
+
return this._scope;
|
|
279
|
+
}
|
|
280
|
+
async load() {
|
|
281
|
+
if (!this.localStorageKey) {
|
|
282
|
+
return Promise.resolve([]);
|
|
283
|
+
}
|
|
284
|
+
// Load settings from localStorage as a single key
|
|
285
|
+
const storedSettings = localStorage.getItem(this.localStorageKey);
|
|
286
|
+
if (storedSettings) {
|
|
287
|
+
const parsedSettings = JSON.parse(storedSettings);
|
|
288
|
+
Object.entries(parsedSettings).forEach(([key, value]) => {
|
|
289
|
+
this.cache.set(key, value);
|
|
290
|
+
});
|
|
291
|
+
return Promise.resolve(Array.from(this.cache.entries()).map(c => ({ key: c[0], value: c[1] })));
|
|
292
|
+
}
|
|
293
|
+
return Promise.resolve([]);
|
|
294
|
+
}
|
|
295
|
+
async set(keyOrValues, value) {
|
|
296
|
+
if (typeof keyOrValues === 'string') {
|
|
297
|
+
// Single value update
|
|
298
|
+
this.cache.set(keyOrValues, value);
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
// Bulk update
|
|
302
|
+
for (const [key, val] of Object.entries(keyOrValues)) {
|
|
303
|
+
this.cache.set(key, val);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
await this.saveToLocalStorage();
|
|
307
|
+
}
|
|
308
|
+
async saveToLocalStorage() {
|
|
309
|
+
if (!this.localStorageKey) {
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
const settingsObject = {};
|
|
313
|
+
this.cache.forEach((value, key) => {
|
|
314
|
+
settingsObject[key] = value;
|
|
315
|
+
});
|
|
316
|
+
localStorage.setItem(this.localStorageKey, JSON.stringify(settingsObject));
|
|
317
|
+
}
|
|
318
|
+
async clear() {
|
|
319
|
+
if (!this.localStorageKey) {
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
localStorage.removeItem(this.localStorageKey);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
class AXPSettingDefinitionProviderContext {
|
|
327
|
+
constructor() {
|
|
328
|
+
this.rootGroups = [];
|
|
329
|
+
this.groupMap = new Map();
|
|
330
|
+
}
|
|
331
|
+
addGroup(name, title, description, icon) {
|
|
332
|
+
const newGroup = {
|
|
333
|
+
name,
|
|
334
|
+
title,
|
|
335
|
+
description,
|
|
336
|
+
icon,
|
|
337
|
+
groups: [],
|
|
338
|
+
sections: [],
|
|
339
|
+
};
|
|
340
|
+
this.rootGroups.push(newGroup);
|
|
341
|
+
this.groupMap.set(name, newGroup); // Index by name
|
|
342
|
+
return new AXPSettingDefinitionGroupBuilder(this, newGroup);
|
|
343
|
+
}
|
|
344
|
+
group(name) {
|
|
345
|
+
const foundGroup = this.groupMap.get(name);
|
|
346
|
+
if (!foundGroup) {
|
|
347
|
+
console.error(`Group with name "${name}" not found.`);
|
|
348
|
+
return null;
|
|
349
|
+
}
|
|
350
|
+
return new AXPSettingDefinitionGroupBuilder(this, foundGroup);
|
|
351
|
+
}
|
|
352
|
+
getGroups() {
|
|
353
|
+
return this.rootGroups;
|
|
354
|
+
}
|
|
355
|
+
// Expose groupMap for controlled access
|
|
356
|
+
hasGroup(name) {
|
|
357
|
+
return this.groupMap.has(name);
|
|
358
|
+
}
|
|
359
|
+
getGroup(name) {
|
|
360
|
+
return this.groupMap.get(name);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
class AXPSettingDefinitionGroupBuilder {
|
|
364
|
+
constructor(context, group) {
|
|
365
|
+
this.context = context;
|
|
366
|
+
this.group = group;
|
|
367
|
+
}
|
|
368
|
+
addSection(name, title, description) {
|
|
369
|
+
const newSection = {
|
|
370
|
+
name,
|
|
371
|
+
title,
|
|
372
|
+
description: description,
|
|
373
|
+
settings: [],
|
|
374
|
+
};
|
|
375
|
+
this.group.sections.push(newSection);
|
|
376
|
+
return new AXPSettingDefinitionSectionBuilder(this, newSection);
|
|
377
|
+
}
|
|
378
|
+
section(name) {
|
|
379
|
+
const foundSection = this.group.sections.find((section) => section.name === name);
|
|
380
|
+
if (!foundSection) {
|
|
381
|
+
throw new Error(`Section with name "${name}" not found in group "${this.group.name}".`);
|
|
382
|
+
}
|
|
383
|
+
return new AXPSettingDefinitionSectionBuilder(this, foundSection);
|
|
384
|
+
}
|
|
385
|
+
addGroup(name, title, description, icon) {
|
|
386
|
+
const newGroup = {
|
|
387
|
+
name,
|
|
388
|
+
title,
|
|
389
|
+
description: description,
|
|
390
|
+
icon,
|
|
391
|
+
groups: [],
|
|
392
|
+
sections: [],
|
|
393
|
+
};
|
|
394
|
+
this.group.groups.push(newGroup);
|
|
395
|
+
if (this.context.hasGroup(name)) {
|
|
396
|
+
throw new Error(`Group with name "${name}" already exists.`);
|
|
397
|
+
}
|
|
398
|
+
this.context['groupMap'].set(name, newGroup);
|
|
399
|
+
return new AXPSettingDefinitionGroupBuilder(this.context, newGroup);
|
|
400
|
+
}
|
|
401
|
+
endGroup() {
|
|
402
|
+
return this.context;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
class AXPSettingDefinitionSectionBuilder {
|
|
406
|
+
constructor(groupBuilder, section) {
|
|
407
|
+
this.groupBuilder = groupBuilder;
|
|
408
|
+
this.section = section;
|
|
409
|
+
}
|
|
410
|
+
addSetting(setting) {
|
|
411
|
+
const newSetting = {
|
|
412
|
+
name: setting.key,
|
|
413
|
+
title: setting.title,
|
|
414
|
+
description: setting.description,
|
|
415
|
+
isRequired: setting.isRequired ?? false,
|
|
416
|
+
isInherited: setting.isInherited ?? false,
|
|
417
|
+
isEncrypted: setting.isEncrypted ?? false,
|
|
418
|
+
defaultValue: setting.defaultValue,
|
|
419
|
+
scope: setting.scope ?? 'P',
|
|
420
|
+
layout: setting.widget.layout,
|
|
421
|
+
widget: {
|
|
422
|
+
type: setting.widget.type,
|
|
423
|
+
name: setting.key,
|
|
424
|
+
path: setting.key,
|
|
425
|
+
defaultValue: setting.defaultValue,
|
|
426
|
+
options: merge(setting.widget.options ?? {}, {
|
|
427
|
+
label: Object.keys(setting.widget.options ?? {}).includes('label') ? setting.widget.options?.['label'] : setting.title,
|
|
428
|
+
validationRules: setting.validationRules ?? [],
|
|
429
|
+
}),
|
|
430
|
+
valueTransforms: setting.valueTransforms,
|
|
431
|
+
triggers: setting.widget.triggers,
|
|
432
|
+
},
|
|
433
|
+
};
|
|
434
|
+
this.section.settings.push(newSetting);
|
|
435
|
+
return this;
|
|
436
|
+
}
|
|
437
|
+
endSection() {
|
|
438
|
+
return this.groupBuilder;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Injection token for setting providers
|
|
443
|
+
const AXP_SETTING_DEFINITION_PROVIDER = new InjectionToken('AXP_SETTING_DEFINITION_PROVIDER');
|
|
444
|
+
class AXPSettingDefinitionProviderService {
|
|
445
|
+
constructor() {
|
|
446
|
+
this.providers = inject(AXP_SETTING_DEFINITION_PROVIDER, { optional: true });
|
|
447
|
+
this.cache = null;
|
|
448
|
+
}
|
|
449
|
+
async load() {
|
|
450
|
+
if (this.cache) {
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
const context = new AXPSettingDefinitionProviderContext();
|
|
454
|
+
// Load providers from DI tokens
|
|
455
|
+
if (Array.isArray(this.providers)) {
|
|
456
|
+
for (const provider of this.providers) {
|
|
457
|
+
if (provider instanceof Promise) {
|
|
458
|
+
// If provider is a promise, resolve it
|
|
459
|
+
const resolvedProvider = await provider;
|
|
460
|
+
await resolvedProvider.provide(context);
|
|
461
|
+
}
|
|
462
|
+
else {
|
|
463
|
+
// If provider is a direct instance, use it directly
|
|
464
|
+
await provider.provide(context);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
this.cache = context.getGroups();
|
|
469
|
+
}
|
|
470
|
+
async reload() {
|
|
471
|
+
this.cache = null;
|
|
472
|
+
await this.load();
|
|
473
|
+
}
|
|
474
|
+
async getListAsync(scope) {
|
|
475
|
+
await this.load();
|
|
476
|
+
return this.getList(scope);
|
|
477
|
+
}
|
|
478
|
+
getList(scope) {
|
|
479
|
+
if (!this.cache) {
|
|
480
|
+
return [];
|
|
481
|
+
}
|
|
482
|
+
const scopeOrder = ['P', 'T', 'U']; // Scopes hierarchy in ascending order
|
|
483
|
+
const filterByScope = (groups, currentScope) => {
|
|
484
|
+
const currentScopeIndex = scopeOrder.indexOf(currentScope);
|
|
485
|
+
return groups
|
|
486
|
+
.map((group) => ({
|
|
487
|
+
...group,
|
|
488
|
+
sections: group.sections.map((section) => ({
|
|
489
|
+
...section,
|
|
490
|
+
settings: section.settings.filter((setting) => {
|
|
491
|
+
const settingScopeIndex = scopeOrder.indexOf(setting.scope);
|
|
492
|
+
// Include settings where:
|
|
493
|
+
// 1. The scope matches the requested scope.
|
|
494
|
+
if (setting.scope === currentScope)
|
|
495
|
+
return true;
|
|
496
|
+
// 2. The setting scope is higher (closer to 'U') and is inherited.
|
|
497
|
+
if (setting.isInherited &&
|
|
498
|
+
settingScopeIndex > currentScopeIndex // Higher scope
|
|
499
|
+
) {
|
|
500
|
+
return true;
|
|
501
|
+
}
|
|
502
|
+
// 3. Exclude settings with a lower or irrelevant scope.
|
|
503
|
+
return false;
|
|
504
|
+
}),
|
|
505
|
+
})).filter((section) => section.settings.length > 0), // Keep only sections with settings
|
|
506
|
+
groups: filterByScope(group.groups, currentScope), // Recursively filter nested groups
|
|
507
|
+
}))
|
|
508
|
+
.filter((group) => group.sections.length > 0 || group.groups.length > 0); // Keep groups with valid sections or nested groups
|
|
509
|
+
};
|
|
510
|
+
return sortBy(filterByScope(this.cache, scope), 'title');
|
|
511
|
+
}
|
|
512
|
+
async defaultValues() {
|
|
513
|
+
const defaults = {};
|
|
514
|
+
const collectDefaults = (groups) => {
|
|
515
|
+
groups.forEach((group) => {
|
|
516
|
+
group.sections.forEach((section) => {
|
|
517
|
+
section.settings.forEach((setting) => {
|
|
518
|
+
if (setting.defaultValue !== undefined) {
|
|
519
|
+
defaults[setting.name] = setting.defaultValue;
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
});
|
|
523
|
+
collectDefaults(group.groups);
|
|
524
|
+
});
|
|
525
|
+
};
|
|
526
|
+
if (!this.cache) {
|
|
527
|
+
await this.load();
|
|
528
|
+
}
|
|
529
|
+
collectDefaults(this.cache);
|
|
530
|
+
return defaults;
|
|
531
|
+
}
|
|
532
|
+
findGroup(scope, groupName) {
|
|
533
|
+
return this.searchRecursive(this.getList(scope), groupName, []); // Initialize with an empty breadcrumb
|
|
534
|
+
}
|
|
535
|
+
searchRecursive(groups, groupName, breadcrumb) {
|
|
536
|
+
for (const group of groups) {
|
|
537
|
+
const currentBreadcrumb = [...breadcrumb, { name: group.name, title: group.title, description: group.description }];
|
|
538
|
+
// If the group name matches, return its details
|
|
539
|
+
if (group.name === groupName) {
|
|
540
|
+
return {
|
|
541
|
+
breadcrumb: currentBreadcrumb,
|
|
542
|
+
sections: group.sections.length > 0 ? group.sections : null,
|
|
543
|
+
groups: group.groups.length > 0 ? group.groups : null,
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
// Recursively search in nested groups
|
|
547
|
+
const nestedResult = this.searchRecursive(group.groups, groupName, currentBreadcrumb);
|
|
548
|
+
if (nestedResult.breadcrumb.length > 0) {
|
|
549
|
+
return nestedResult;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
// If no matching group is found
|
|
553
|
+
return {
|
|
554
|
+
breadcrumb: [],
|
|
555
|
+
groups: [],
|
|
556
|
+
sections: []
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPSettingDefinitionProviderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
560
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPSettingDefinitionProviderService, providedIn: 'root' }); }
|
|
561
|
+
}
|
|
562
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPSettingDefinitionProviderService, decorators: [{
|
|
563
|
+
type: Injectable,
|
|
564
|
+
args: [{ providedIn: 'root' }]
|
|
565
|
+
}] });
|
|
566
|
+
// Multi-provider token for higher-layer default values
|
|
567
|
+
const AXP_SETTING_DEFAULT_VALUES_PROVIDERS = new InjectionToken('AXP_SETTING_DEFAULT_VALUES_PROVIDERS', {
|
|
568
|
+
providedIn: 'root',
|
|
569
|
+
factory: () => [],
|
|
570
|
+
});
|
|
571
|
+
// Aggregator service that merges definition defaults with higher-layer providers (by priority)
|
|
572
|
+
class AXPSettingDefaultValuesAggregatorService {
|
|
573
|
+
constructor() {
|
|
574
|
+
this.definitionService = inject(AXPSettingDefinitionProviderService);
|
|
575
|
+
this.providers = inject(AXP_SETTING_DEFAULT_VALUES_PROVIDERS, { optional: true }) ?? [];
|
|
576
|
+
}
|
|
577
|
+
async getDefaults() {
|
|
578
|
+
const baseDefaults = await this.definitionService.defaultValues();
|
|
579
|
+
const ordered = [...this.providers].sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0));
|
|
580
|
+
const merged = { ...baseDefaults };
|
|
581
|
+
for (const provider of ordered) {
|
|
582
|
+
try {
|
|
583
|
+
const overrideMap = await provider.provide();
|
|
584
|
+
Object.assign(merged, overrideMap);
|
|
585
|
+
}
|
|
586
|
+
catch (err) {
|
|
587
|
+
console.error('AXPSettingDefaultValuesProvider failed:', err);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
return merged;
|
|
591
|
+
}
|
|
592
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPSettingDefaultValuesAggregatorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
593
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPSettingDefaultValuesAggregatorService, providedIn: 'root' }); }
|
|
594
|
+
}
|
|
595
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPSettingDefaultValuesAggregatorService, decorators: [{
|
|
596
|
+
type: Injectable,
|
|
597
|
+
args: [{ providedIn: 'root' }]
|
|
598
|
+
}] });
|
|
599
|
+
|
|
600
|
+
class AXPSettingsService {
|
|
601
|
+
constructor() {
|
|
602
|
+
this.providers = inject(AXP_SETTING_VALUE_PROVIDER);
|
|
603
|
+
this.injector = inject(Injector);
|
|
604
|
+
this.eventService = inject(AXPBroadcastEventService);
|
|
605
|
+
this.sessionService = inject(AXPSessionService);
|
|
606
|
+
this.scopedSettingsCache = new Map();
|
|
607
|
+
this.onChanged = new Subject();
|
|
608
|
+
this.onLoaded = new Subject();
|
|
609
|
+
// Initialize scoped caches for dynamic scopes
|
|
610
|
+
const staticScopes = [
|
|
611
|
+
AXPPlatformScope.Platform,
|
|
612
|
+
AXPPlatformScope.Tenant,
|
|
613
|
+
AXPPlatformScope.User,
|
|
614
|
+
];
|
|
615
|
+
staticScopes.forEach((scope) => {
|
|
616
|
+
if (!this.scopedSettingsCache.has(scope)) {
|
|
617
|
+
this.scopedSettingsCache.set(scope, new Map());
|
|
618
|
+
}
|
|
619
|
+
});
|
|
620
|
+
// Listen for setting changes from other tabs
|
|
621
|
+
this.eventService.listen('AXPSettingChangedEvent').subscribe((event) => {
|
|
622
|
+
const data = event.data;
|
|
623
|
+
data.keys.forEach((key) => {
|
|
624
|
+
this.scopedSettingsCache.get(data.scope)?.set(key, data.values[key]);
|
|
625
|
+
});
|
|
626
|
+
this.onChanged.next(data); // Notify subscribers in the current tab
|
|
627
|
+
});
|
|
628
|
+
// reload settings when user is authenticated
|
|
629
|
+
this.sessionService.isAuthenticated$.pipe(distinctUntilChanged()).subscribe(async (isAuthenticated) => {
|
|
630
|
+
if (isAuthenticated) {
|
|
631
|
+
await this.reload();
|
|
632
|
+
}
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
async reload() {
|
|
636
|
+
await this.load();
|
|
637
|
+
}
|
|
638
|
+
async load() {
|
|
639
|
+
try {
|
|
640
|
+
const settingsList = [];
|
|
641
|
+
for (const provider of this.providers) {
|
|
642
|
+
const scopeCache = this.scopedSettingsCache.get(provider.scope) ?? new Map();
|
|
643
|
+
const providerSettings = await provider.load();
|
|
644
|
+
providerSettings.forEach((setting) => {
|
|
645
|
+
scopeCache.set(setting.key, setting.value);
|
|
646
|
+
});
|
|
647
|
+
settingsList.push(...providerSettings);
|
|
648
|
+
}
|
|
649
|
+
this.onLoaded.next();
|
|
650
|
+
return settingsList;
|
|
651
|
+
}
|
|
652
|
+
catch (error) {
|
|
653
|
+
console.error('Error loading settings', error);
|
|
654
|
+
throw error;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
async get(key) {
|
|
658
|
+
if (this.scopedSettingsCache.size === 0) {
|
|
659
|
+
await this.load();
|
|
660
|
+
}
|
|
661
|
+
const scopeOrder = [
|
|
662
|
+
AXPPlatformScope.User,
|
|
663
|
+
AXPPlatformScope.Tenant,
|
|
664
|
+
AXPPlatformScope.Platform,
|
|
665
|
+
];
|
|
666
|
+
for (const scope of scopeOrder) {
|
|
667
|
+
const scopeCache = this.scopedSettingsCache.get(scope);
|
|
668
|
+
if (scopeCache && scopeCache.has(key)) {
|
|
669
|
+
const value = scopeCache.get(key);
|
|
670
|
+
if (value !== undefined && value !== null) {
|
|
671
|
+
return cloneDeep(value);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
const defaults = await this.injector.get(AXPSettingDefaultValuesAggregatorService).getDefaults();
|
|
676
|
+
return get(defaults, key); // Fallback if no value is found
|
|
677
|
+
}
|
|
678
|
+
async defaultValues(scope) {
|
|
679
|
+
let scopeOrder = [
|
|
680
|
+
AXPPlatformScope.Platform,
|
|
681
|
+
AXPPlatformScope.Tenant,
|
|
682
|
+
AXPPlatformScope.User,
|
|
683
|
+
].reverse();
|
|
684
|
+
const scopeIndex = scopeOrder.indexOf(scope);
|
|
685
|
+
if (scopeIndex === -1) {
|
|
686
|
+
throw new Error(`Invalid scope: ${scope}`);
|
|
687
|
+
}
|
|
688
|
+
scopeOrder = scopeOrder.slice(scopeIndex + 1);
|
|
689
|
+
// Accumulate defaults from the current scope and higher scopes
|
|
690
|
+
const accumulatedDefaults = {};
|
|
691
|
+
for (let i = scopeIndex; i < scopeOrder.length; i++) {
|
|
692
|
+
const currentScope = scopeOrder[i];
|
|
693
|
+
const scopeCache = this.scopedSettingsCache.get(currentScope);
|
|
694
|
+
if (scopeCache) {
|
|
695
|
+
scopeCache.forEach((value, key) => {
|
|
696
|
+
if (!(key in accumulatedDefaults)) {
|
|
697
|
+
accumulatedDefaults[key] = value;
|
|
698
|
+
}
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
// Merge with aggregated default values (definition + higher-layer providers)
|
|
703
|
+
const globalDefaults = await this.injector.get(AXPSettingDefaultValuesAggregatorService).getDefaults();
|
|
704
|
+
return { ...globalDefaults, ...accumulatedDefaults };
|
|
705
|
+
}
|
|
706
|
+
scope(scope) {
|
|
707
|
+
const provider = this.providers.find((p) => p.scope === scope);
|
|
708
|
+
if (!provider) {
|
|
709
|
+
throw new Error(`No provider found for scope: ${scope}`);
|
|
710
|
+
}
|
|
711
|
+
return new ScopedSettingService(this, provider, this.scopedSettingsCache.get(scope));
|
|
712
|
+
}
|
|
713
|
+
invokeChangeEvent(event) {
|
|
714
|
+
this.onChanged.next(event);
|
|
715
|
+
this.eventService.publish('AXPSettingChangedEvent', event);
|
|
716
|
+
}
|
|
717
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPSettingsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
718
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPSettingsService, providedIn: 'root' }); }
|
|
719
|
+
}
|
|
720
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPSettingsService, decorators: [{
|
|
721
|
+
type: Injectable,
|
|
722
|
+
args: [{
|
|
723
|
+
providedIn: 'root',
|
|
724
|
+
}]
|
|
725
|
+
}], ctorParameters: () => [] });
|
|
726
|
+
class ScopedSettingService {
|
|
727
|
+
constructor(parent, provider, scopeCache) {
|
|
728
|
+
this.parent = parent;
|
|
729
|
+
this.provider = provider;
|
|
730
|
+
this.scopeCache = scopeCache;
|
|
731
|
+
}
|
|
732
|
+
async get(key) {
|
|
733
|
+
const settings = await this.provider.load();
|
|
734
|
+
const setting = settings.find((s) => s.key === key);
|
|
735
|
+
return setting ? cloneDeep(setting.value) : undefined;
|
|
736
|
+
}
|
|
737
|
+
async all() {
|
|
738
|
+
const settings = await this.provider.load();
|
|
739
|
+
return Object.fromEntries(settings.map((s) => [s.key, cloneDeep(s.value)]));
|
|
740
|
+
}
|
|
741
|
+
async defaultValues() {
|
|
742
|
+
return this.parent.defaultValues(this.provider.scope);
|
|
743
|
+
}
|
|
744
|
+
async set(keyOrValues, value) {
|
|
745
|
+
if (typeof keyOrValues === 'string') {
|
|
746
|
+
// Single key-value pair
|
|
747
|
+
await this.provider.set(keyOrValues, value);
|
|
748
|
+
this.scopeCache.set(keyOrValues, value); // Sync the cache
|
|
749
|
+
this.parent.invokeChangeEvent({
|
|
750
|
+
scope: this.provider.scope,
|
|
751
|
+
keys: [keyOrValues],
|
|
752
|
+
values: { [keyOrValues]: value },
|
|
753
|
+
entries: [{ key: keyOrValues, value }],
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
else {
|
|
757
|
+
// Multiple key-value pairs
|
|
758
|
+
await this.provider.set(keyOrValues);
|
|
759
|
+
Object.entries(keyOrValues).forEach(([key, val]) => this.scopeCache.set(key, val)); // Sync the cache
|
|
760
|
+
const entries = Object.entries(keyOrValues).map(([key, val]) => ({ key, value: val }));
|
|
761
|
+
this.parent.invokeChangeEvent({
|
|
762
|
+
scope: this.provider.scope,
|
|
763
|
+
keys: Object.keys(keyOrValues),
|
|
764
|
+
values: keyOrValues,
|
|
765
|
+
entries,
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
async update(key, updateFn) {
|
|
770
|
+
const currentValue = await this.get(key);
|
|
771
|
+
const newValue = updateFn(currentValue);
|
|
772
|
+
await this.set(key, newValue);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
const DEFAULT_APPLY_DEFAULT_MULTILANGUAGE = true;
|
|
777
|
+
/**
|
|
778
|
+
* Synchronous access to default multi-language behavior for named entity fields and widget
|
|
779
|
+
* property bindings. Mirrors {@link AXPLayoutOrderingConfigService}: avoids async in entity
|
|
780
|
+
* modifiers and startup deadlocks.
|
|
781
|
+
*/
|
|
782
|
+
class AXPDefaultMultiLanguageConfigService {
|
|
783
|
+
constructor() {
|
|
784
|
+
this.settingsService = inject(AXPSettingsService);
|
|
785
|
+
this._applyDefault = signal(DEFAULT_APPLY_DEFAULT_MULTILANGUAGE, ...(ngDevMode ? [{ debugName: "_applyDefault" }] : /* istanbul ignore next */ []));
|
|
786
|
+
this.syncScheduled = false;
|
|
787
|
+
this.settingsService.onLoaded.subscribe(() => this.syncFromSettings());
|
|
788
|
+
this.settingsService.onChanged.subscribe(() => this.syncFromSettings());
|
|
789
|
+
}
|
|
790
|
+
getApplyDefaultMultiLanguage() {
|
|
791
|
+
if (!this.syncScheduled) {
|
|
792
|
+
this.syncScheduled = true;
|
|
793
|
+
this.settingsService
|
|
794
|
+
.get(AXPRegionalSetting.MultiLanguageSupport)
|
|
795
|
+
.then((value) => this._applyDefault.set(value ?? DEFAULT_APPLY_DEFAULT_MULTILANGUAGE))
|
|
796
|
+
.catch(() => this._applyDefault.set(DEFAULT_APPLY_DEFAULT_MULTILANGUAGE));
|
|
797
|
+
}
|
|
798
|
+
return this._applyDefault();
|
|
799
|
+
}
|
|
800
|
+
async syncFromSettings() {
|
|
801
|
+
try {
|
|
802
|
+
const value = await this.settingsService.get(AXPRegionalSetting.MultiLanguageSupport);
|
|
803
|
+
this._applyDefault.set(value ?? DEFAULT_APPLY_DEFAULT_MULTILANGUAGE);
|
|
804
|
+
}
|
|
805
|
+
catch {
|
|
806
|
+
this._applyDefault.set(DEFAULT_APPLY_DEFAULT_MULTILANGUAGE);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPDefaultMultiLanguageConfigService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
810
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPDefaultMultiLanguageConfigService, providedIn: 'root' }); }
|
|
811
|
+
}
|
|
812
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPDefaultMultiLanguageConfigService, decorators: [{
|
|
813
|
+
type: Injectable,
|
|
814
|
+
args: [{ providedIn: 'root' }]
|
|
815
|
+
}], ctorParameters: () => [] });
|
|
816
|
+
|
|
817
|
+
//#region ---- Constants ----
|
|
818
|
+
/**
|
|
819
|
+
* Widget types that support the multiLanguage option (text / rich text editors).
|
|
820
|
+
*/
|
|
821
|
+
const MULTILANGUAGE_CAPABLE_WIDGET_TYPES = new Set([
|
|
822
|
+
'text-editor',
|
|
823
|
+
'large-text-editor',
|
|
824
|
+
'rich-text-editor',
|
|
825
|
+
'template-content-editor',
|
|
826
|
+
]);
|
|
827
|
+
/**
|
|
828
|
+
* Common property names / binding path segments that are usually translatable when the feature is
|
|
829
|
+
* enabled in settings. Explicit `multiLanguage` on the property still wins.
|
|
830
|
+
*/
|
|
831
|
+
const DEFAULT_MULTILANGUAGE_FIELD_NAMES = new Set([
|
|
832
|
+
'title',
|
|
833
|
+
'description',
|
|
834
|
+
'note',
|
|
835
|
+
'content',
|
|
836
|
+
/** e.g. `post.summaryContent` — last path segment */
|
|
837
|
+
'summaryContent',
|
|
838
|
+
'question',
|
|
839
|
+
'summary',
|
|
840
|
+
'body',
|
|
841
|
+
'caption',
|
|
842
|
+
'remarks',
|
|
843
|
+
'subtitle',
|
|
844
|
+
'label',
|
|
845
|
+
'placeholder',
|
|
846
|
+
'message',
|
|
847
|
+
'subject',
|
|
848
|
+
]);
|
|
849
|
+
//#endregion
|
|
850
|
+
//#region ---- Helpers ----
|
|
851
|
+
function interfacePathLastSegment(path) {
|
|
852
|
+
if (path == null || typeof path !== 'string' || !path.length) {
|
|
853
|
+
return null;
|
|
854
|
+
}
|
|
855
|
+
const seg = path.includes('.') ? path.split('.').pop() : path;
|
|
856
|
+
return seg && seg.length ? seg : null;
|
|
857
|
+
}
|
|
858
|
+
function shouldApplyDefaultMultiLanguageToEntityProperty(prop) {
|
|
859
|
+
if (prop.schema.dataType !== 'string') {
|
|
860
|
+
return false;
|
|
861
|
+
}
|
|
862
|
+
if (!DEFAULT_MULTILANGUAGE_FIELD_NAMES.has(prop.name)) {
|
|
863
|
+
return false;
|
|
864
|
+
}
|
|
865
|
+
const widgetType = prop.schema.interface?.type;
|
|
866
|
+
if (typeof widgetType !== 'string' || !MULTILANGUAGE_CAPABLE_WIDGET_TYPES.has(widgetType)) {
|
|
867
|
+
return false;
|
|
868
|
+
}
|
|
869
|
+
const opts = prop.schema.interface?.options;
|
|
870
|
+
if (opts && 'multiLanguage' in opts) {
|
|
871
|
+
return false;
|
|
872
|
+
}
|
|
873
|
+
return true;
|
|
874
|
+
}
|
|
875
|
+
function shouldApplyDefaultMultiLanguageToWidgetProperty(prop) {
|
|
876
|
+
if (prop.schema.dataType !== 'string') {
|
|
877
|
+
return false;
|
|
878
|
+
}
|
|
879
|
+
const key = interfacePathLastSegment(prop.schema.interface.path);
|
|
880
|
+
if (!key || !DEFAULT_MULTILANGUAGE_FIELD_NAMES.has(key)) {
|
|
881
|
+
return false;
|
|
882
|
+
}
|
|
883
|
+
const widgetType = prop.schema.interface.type;
|
|
884
|
+
if (typeof widgetType !== 'string' || !MULTILANGUAGE_CAPABLE_WIDGET_TYPES.has(widgetType)) {
|
|
885
|
+
return false;
|
|
886
|
+
}
|
|
887
|
+
const opts = prop.schema.interface.options;
|
|
888
|
+
if (opts && 'multiLanguage' in opts) {
|
|
889
|
+
return false;
|
|
890
|
+
}
|
|
891
|
+
return true;
|
|
892
|
+
}
|
|
893
|
+
/**
|
|
894
|
+
* Leaf widget nodes in entity `schema.interface` trees (e.g. GridLayout → RichText under `pre` /
|
|
895
|
+
* `post`) — same path/type rules as {@link shouldApplyDefaultMultiLanguageToWidgetProperty}, without
|
|
896
|
+
* `schema.dataType` (nodes do not carry entity schema).
|
|
897
|
+
*/
|
|
898
|
+
function shouldApplyDefaultMultiLanguageToWidgetNode(node) {
|
|
899
|
+
const widgetType = node.type;
|
|
900
|
+
if (typeof widgetType !== 'string' || !MULTILANGUAGE_CAPABLE_WIDGET_TYPES.has(widgetType)) {
|
|
901
|
+
return false;
|
|
902
|
+
}
|
|
903
|
+
const key = interfacePathLastSegment(node.path);
|
|
904
|
+
if (!key || !DEFAULT_MULTILANGUAGE_FIELD_NAMES.has(key)) {
|
|
905
|
+
return false;
|
|
906
|
+
}
|
|
907
|
+
const opts = node.options;
|
|
908
|
+
if (opts && 'multiLanguage' in opts) {
|
|
909
|
+
return false;
|
|
910
|
+
}
|
|
911
|
+
return true;
|
|
912
|
+
}
|
|
913
|
+
/**
|
|
914
|
+
* When {@link AXPDefaultMultiLanguageConfigService#getApplyDefaultMultiLanguage} is true, sets
|
|
915
|
+
* `options.multiLanguage` on eligible editor leaves inside an entity property's widget tree
|
|
916
|
+
* (including nested layouts). Used by entity middleware so object fields (e.g. `pre`, `post`) are
|
|
917
|
+
* covered, not only top-level string properties.
|
|
918
|
+
*/
|
|
919
|
+
function withDefaultMultiLanguageOnWidgetNodeTree(root, applyDefault) {
|
|
920
|
+
if (root == null || !applyDefault) {
|
|
921
|
+
return root ?? undefined;
|
|
922
|
+
}
|
|
923
|
+
return visitWidgetNodeForDefaultMultiLanguage(root);
|
|
924
|
+
}
|
|
925
|
+
function visitWidgetNodeForDefaultMultiLanguage(node) {
|
|
926
|
+
let next = node;
|
|
927
|
+
if (node.children?.length) {
|
|
928
|
+
const newChildren = node.children.map((child) => visitWidgetNodeForDefaultMultiLanguage(child));
|
|
929
|
+
if (newChildren.some((c, i) => c !== node.children[i])) {
|
|
930
|
+
next = { ...node, children: newChildren };
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
if (shouldApplyDefaultMultiLanguageToWidgetNode(next)) {
|
|
934
|
+
return {
|
|
935
|
+
...next,
|
|
936
|
+
options: {
|
|
937
|
+
...(next.options ?? {}),
|
|
938
|
+
multiLanguage: true,
|
|
939
|
+
},
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
return next;
|
|
943
|
+
}
|
|
944
|
+
/**
|
|
945
|
+
* When {@link AXPDefaultMultiLanguageConfigService#getApplyDefaultMultiLanguage} is true, sets
|
|
946
|
+
* `schema.interface.options.multiLanguage` for eligible string widget properties (same rules as
|
|
947
|
+
* entity middleware), unless the property already specifies `multiLanguage`.
|
|
948
|
+
*/
|
|
949
|
+
function withDefaultMultiLanguageOnWidgetProperty(prop, applyDefault) {
|
|
950
|
+
if (!applyDefault || !shouldApplyDefaultMultiLanguageToWidgetProperty(prop)) {
|
|
951
|
+
return prop;
|
|
952
|
+
}
|
|
953
|
+
const iface = prop.schema.interface;
|
|
954
|
+
const existingOpts = (iface.options ?? {});
|
|
955
|
+
return {
|
|
956
|
+
...prop,
|
|
957
|
+
schema: {
|
|
958
|
+
...prop.schema,
|
|
959
|
+
interface: {
|
|
960
|
+
...iface,
|
|
961
|
+
options: {
|
|
962
|
+
...existingOpts,
|
|
963
|
+
multiLanguage: true,
|
|
964
|
+
},
|
|
965
|
+
},
|
|
966
|
+
},
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
//#endregion
|
|
970
|
+
|
|
971
|
+
//#region ---- Settings expression scope ----
|
|
972
|
+
/**
|
|
973
|
+
* Exposes platform settings in expression strings, e.g.
|
|
974
|
+
* `{{ settings.get('Common:Setting:Entity.ShowPageBadge') }}`.
|
|
975
|
+
*/
|
|
976
|
+
class AXPSettingsEvaluatorScopeProvider {
|
|
977
|
+
constructor() {
|
|
978
|
+
this.settingsService = inject(AXPSettingsService);
|
|
979
|
+
}
|
|
980
|
+
async provide(context) {
|
|
981
|
+
context.addScope('settings', {
|
|
982
|
+
get: async (key) => {
|
|
983
|
+
return await this.settingsService.get(key);
|
|
984
|
+
},
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
//#endregion
|
|
989
|
+
|
|
990
|
+
var AXPHomePageSettings;
|
|
991
|
+
(function (AXPHomePageSettings) {
|
|
992
|
+
AXPHomePageSettings["UserHomePath"] = "Common:Setting:Startup.HomePage";
|
|
993
|
+
})(AXPHomePageSettings || (AXPHomePageSettings = {}));
|
|
994
|
+
|
|
995
|
+
class AXPHomePageService {
|
|
996
|
+
get flatCandidates() {
|
|
997
|
+
return this.candidates.flatMap((c) => c);
|
|
998
|
+
}
|
|
999
|
+
constructor() {
|
|
1000
|
+
this.defaultHomePageKey = inject(AXP_HOME_PAGE_DEFAULT_KEY, { optional: true }) ?? 'home';
|
|
1001
|
+
this.userHomePageKey = null;
|
|
1002
|
+
this.candidates = inject(AXP_HOME_PAGES, { optional: true }) ?? [];
|
|
1003
|
+
this.settingsService = inject(AXPSettingsService);
|
|
1004
|
+
this.sessionService = inject(AXPSessionService);
|
|
1005
|
+
this.injector = inject(Injector);
|
|
1006
|
+
this.originalConfig = null;
|
|
1007
|
+
this.loginPath = 'auth/login';
|
|
1008
|
+
// remove circular dependency
|
|
1009
|
+
setTimeout(async () => {
|
|
1010
|
+
await this.init();
|
|
1011
|
+
});
|
|
1012
|
+
}
|
|
1013
|
+
async init() {
|
|
1014
|
+
await this.load();
|
|
1015
|
+
//
|
|
1016
|
+
const keys = [AXPHomePageSettings.UserHomePath];
|
|
1017
|
+
this.settingsService.onChanged.subscribe(async (c) => {
|
|
1018
|
+
if (keys.some((k) => c.keys.includes(k))) {
|
|
1019
|
+
await this.load();
|
|
1020
|
+
this.refreshRoutes();
|
|
1021
|
+
}
|
|
1022
|
+
});
|
|
1023
|
+
//
|
|
1024
|
+
merge$1(this.sessionService.status$, this.sessionService.application$, this.sessionService.tenant$).subscribe(async (data) => {
|
|
1025
|
+
await this.load();
|
|
1026
|
+
this.refreshRoutes();
|
|
1027
|
+
});
|
|
1028
|
+
}
|
|
1029
|
+
async load() {
|
|
1030
|
+
this.userHomePageKey = await this.settingsService.get(AXPHomePageSettings.UserHomePath);
|
|
1031
|
+
}
|
|
1032
|
+
getRegisteredList() {
|
|
1033
|
+
return this.flatCandidates.filter((c) => c.route);
|
|
1034
|
+
}
|
|
1035
|
+
getRegisteredRoutes() {
|
|
1036
|
+
const defaultHomePage = this.flatCandidates.find((c) => c.key === this.defaultHomePageKey);
|
|
1037
|
+
const home = this.flatCandidates.find((c) => this.userHomePageKey && c.key === this.userHomePageKey) ?? defaultHomePage;
|
|
1038
|
+
if (home && home.route && this.sessionService.application?.name) {
|
|
1039
|
+
const fullPath = this.findPath(home.route);
|
|
1040
|
+
return [
|
|
1041
|
+
{ path: '', redirectTo: fullPath, pathMatch: 'full' },
|
|
1042
|
+
{ path: ':app', redirectTo: fullPath, pathMatch: 'full' },
|
|
1043
|
+
{ ...home?.route, ...{ data: { key: home?.key, title: home?.title, path: fullPath } } },
|
|
1044
|
+
];
|
|
1045
|
+
}
|
|
1046
|
+
return [{ path: '', redirectTo: this.loginPath, pathMatch: 'full' }];
|
|
1047
|
+
}
|
|
1048
|
+
getCurrent() {
|
|
1049
|
+
const key = this.userHomePageKey ?? this.defaultHomePageKey;
|
|
1050
|
+
const homePage = this.getRegisteredRoutes().find((c) => c.data?.['key'] === key);
|
|
1051
|
+
if (homePage) {
|
|
1052
|
+
return {
|
|
1053
|
+
path: homePage.data?.['path'],
|
|
1054
|
+
title: homePage.data?.['title'],
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
1057
|
+
return {
|
|
1058
|
+
path: this.loginPath,
|
|
1059
|
+
title: '',
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
1062
|
+
findPath(target) {
|
|
1063
|
+
const rawPath = this.buildFullPaths(target).join('/');
|
|
1064
|
+
const appName = this.sessionService.application?.name;
|
|
1065
|
+
if (appName) {
|
|
1066
|
+
return rawPath.replace(':app', appName);
|
|
1067
|
+
}
|
|
1068
|
+
return rawPath;
|
|
1069
|
+
}
|
|
1070
|
+
buildFullPaths(route, parentPath = []) {
|
|
1071
|
+
const segments = [...parentPath];
|
|
1072
|
+
if (route.path) {
|
|
1073
|
+
segments.push(route.path);
|
|
1074
|
+
}
|
|
1075
|
+
// Recursively search the first matched child (deepest first match)
|
|
1076
|
+
if (route.children?.length) {
|
|
1077
|
+
return this.buildFullPaths(route.children[0], segments);
|
|
1078
|
+
}
|
|
1079
|
+
return segments;
|
|
1080
|
+
}
|
|
1081
|
+
refreshRoutes() {
|
|
1082
|
+
const router = this.injector.get(Router);
|
|
1083
|
+
// 👇 On first call, remember what was there before:
|
|
1084
|
+
if (!this.originalConfig) {
|
|
1085
|
+
this.originalConfig = router.config;
|
|
1086
|
+
}
|
|
1087
|
+
this.applyMergedConfig(router);
|
|
1088
|
+
}
|
|
1089
|
+
applyMergedConfig(router) {
|
|
1090
|
+
const newHomeRoutes = this.getRegisteredRoutes(); // your 3 home entries
|
|
1091
|
+
// Filter the original routes to drop the old home placeholders.
|
|
1092
|
+
// We know your dynamic routes always have path === '' or ':app'
|
|
1093
|
+
// or carry a `data.key` matching one of your home-page keys.
|
|
1094
|
+
const otherRoutes = (this.originalConfig ?? []).filter((r) => r.path !== '' && r.path !== ':app' && !(r.data && typeof r.data['key'] === 'string'));
|
|
1095
|
+
// Build the new merged tree
|
|
1096
|
+
const merged = [...newHomeRoutes, ...otherRoutes];
|
|
1097
|
+
// TODO: check this for custom menu like :app/terms
|
|
1098
|
+
router.resetConfig(merged);
|
|
1099
|
+
}
|
|
1100
|
+
async navigateTo() {
|
|
1101
|
+
// wait for the router to be initialized
|
|
1102
|
+
setTimeout(() => {
|
|
1103
|
+
const router = this.injector.get(Router);
|
|
1104
|
+
const current = this.getCurrent();
|
|
1105
|
+
router.navigate([current.path], { onSameUrlNavigation: 'reload' });
|
|
1106
|
+
}, 100);
|
|
1107
|
+
}
|
|
1108
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPHomePageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1109
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPHomePageService, providedIn: 'root' }); }
|
|
1110
|
+
}
|
|
1111
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPHomePageService, decorators: [{
|
|
1112
|
+
type: Injectable,
|
|
1113
|
+
args: [{
|
|
1114
|
+
providedIn: 'root',
|
|
1115
|
+
}]
|
|
1116
|
+
}], ctorParameters: () => [] });
|
|
1117
|
+
|
|
1118
|
+
function dynamicRoutesFactory() {
|
|
1119
|
+
const service = inject(AXPHomePageService);
|
|
1120
|
+
return service.getRegisteredRoutes();
|
|
1121
|
+
}
|
|
1122
|
+
function provideDynamicHomePage() {
|
|
1123
|
+
return makeEnvironmentProviders([
|
|
1124
|
+
{
|
|
1125
|
+
provide: ROUTES,
|
|
1126
|
+
useFactory: dynamicRoutesFactory,
|
|
1127
|
+
multi: true
|
|
1128
|
+
}
|
|
1129
|
+
]);
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
class AXPHomePageSettingProvider {
|
|
1133
|
+
constructor(injector) {
|
|
1134
|
+
this.injector = injector;
|
|
1135
|
+
this.translateService = this.injector.get(AXTranslationService);
|
|
1136
|
+
this.homePageService = this.injector.get(AXPHomePageService);
|
|
1137
|
+
this.defaultHomePageKey = inject(AXP_HOME_PAGE_DEFAULT_KEY, { optional: true }) ?? 'home';
|
|
1138
|
+
}
|
|
1139
|
+
async provide(context) {
|
|
1140
|
+
const trans = async (key) => await this.translateService.translateAsync(`@general:settings.${key}`);
|
|
1141
|
+
const list = await this.homePageService.getRegisteredList().map((c) => ({ id: c.key, title: c.title }));
|
|
1142
|
+
if (list.length === 0) {
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
1145
|
+
// Define the 'General Settings' group
|
|
1146
|
+
context
|
|
1147
|
+
.addGroup('general', await trans('general.title'), await trans('general.description'), 'fa-light fa-palette')
|
|
1148
|
+
// Add the 'Startup' section
|
|
1149
|
+
.addSection('startup', await trans('general.startup.title'), await trans('general.startup.description'))
|
|
1150
|
+
// Add the 'Home Page' setting
|
|
1151
|
+
.addSetting({
|
|
1152
|
+
key: AXPHomePageSettings.UserHomePath,
|
|
1153
|
+
title: await trans('general.startup.home-page.title'),
|
|
1154
|
+
scope: AXPPlatformScope.User,
|
|
1155
|
+
isInherited: true,
|
|
1156
|
+
defaultValue: this.defaultHomePageKey || list[0].id,
|
|
1157
|
+
valueTransforms: objectKeyValueTransforms('id'),
|
|
1158
|
+
widget: {
|
|
1159
|
+
type: AXPWidgetsCatalog.select,
|
|
1160
|
+
options: {
|
|
1161
|
+
valueField: 'id',
|
|
1162
|
+
textField: 'title',
|
|
1163
|
+
dataSource: list,
|
|
1164
|
+
},
|
|
1165
|
+
},
|
|
1166
|
+
description: await trans('general.startup.home-page.description'),
|
|
1167
|
+
})
|
|
1168
|
+
// End the 'Startup' section
|
|
1169
|
+
.endSection()
|
|
1170
|
+
// End the 'General Settings' group
|
|
1171
|
+
.endGroup();
|
|
1172
|
+
}
|
|
1173
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPHomePageSettingProvider, deps: [{ token: i0.Injector }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1174
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPHomePageSettingProvider }); }
|
|
1175
|
+
}
|
|
1176
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPHomePageSettingProvider, decorators: [{
|
|
1177
|
+
type: Injectable
|
|
1178
|
+
}], ctorParameters: () => [{ type: i0.Injector }] });
|
|
1179
|
+
|
|
1180
|
+
class AXPHomePageModule {
|
|
1181
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPHomePageModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
1182
|
+
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.9", ngImport: i0, type: AXPHomePageModule }); }
|
|
1183
|
+
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPHomePageModule, providers: [
|
|
1184
|
+
{
|
|
1185
|
+
provide: AXP_SETTING_DEFINITION_PROVIDER,
|
|
1186
|
+
useClass: AXPHomePageSettingProvider,
|
|
1187
|
+
multi: true,
|
|
1188
|
+
},
|
|
1189
|
+
] }); }
|
|
1190
|
+
}
|
|
1191
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPHomePageModule, decorators: [{
|
|
1192
|
+
type: NgModule,
|
|
1193
|
+
args: [{
|
|
1194
|
+
imports: [],
|
|
1195
|
+
providers: [
|
|
1196
|
+
{
|
|
1197
|
+
provide: AXP_SETTING_DEFINITION_PROVIDER,
|
|
1198
|
+
useClass: AXPHomePageSettingProvider,
|
|
1199
|
+
multi: true,
|
|
1200
|
+
},
|
|
1201
|
+
],
|
|
1202
|
+
}]
|
|
1203
|
+
}] });
|
|
1204
|
+
|
|
1205
|
+
class AXPDataProvider {
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
class AXPCustomOperatorService {
|
|
1209
|
+
}
|
|
1210
|
+
class AXPCustomOperatorServiceImpl extends AXPCustomOperatorService {
|
|
1211
|
+
getCustomOperators() {
|
|
1212
|
+
return {};
|
|
1213
|
+
}
|
|
1214
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPCustomOperatorServiceImpl, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1215
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPCustomOperatorServiceImpl }); }
|
|
1216
|
+
}
|
|
1217
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPCustomOperatorServiceImpl, decorators: [{
|
|
1218
|
+
type: Injectable
|
|
1219
|
+
}] });
|
|
1220
|
+
|
|
1221
|
+
class AXPFilterOperatorMiddlewareService {
|
|
1222
|
+
}
|
|
1223
|
+
class AXPFilterOperatorMiddlewareServiceImpl extends AXPFilterOperatorMiddlewareService {
|
|
1224
|
+
constructor() {
|
|
1225
|
+
super(...arguments);
|
|
1226
|
+
this.customOperatorService = inject(AXPCustomOperatorService);
|
|
1227
|
+
this.operators = {
|
|
1228
|
+
...this.getDefaultOperators(),
|
|
1229
|
+
...this.customOperatorService.getCustomOperators(),
|
|
1230
|
+
};
|
|
1231
|
+
}
|
|
1232
|
+
transformFilters(filters) {
|
|
1233
|
+
return filters.map((filter) => this.transformFilter(filter));
|
|
1234
|
+
}
|
|
1235
|
+
transformFilter(filter) {
|
|
1236
|
+
const { operator, value, filters, field } = filter;
|
|
1237
|
+
// Find the operator (either default or custom)
|
|
1238
|
+
const filterOperator = this.operators[operator?.type ?? ''] || null;
|
|
1239
|
+
// Start by transforming the filter itself
|
|
1240
|
+
const transformedFilter = {
|
|
1241
|
+
...filter, // Keep the original filter structure
|
|
1242
|
+
operator: filterOperator ? filterOperator : operator, // Add operator name
|
|
1243
|
+
};
|
|
1244
|
+
// If the filter contains nested filters, recursively transform them
|
|
1245
|
+
if (filters && filters.length > 0) {
|
|
1246
|
+
transformedFilter.filters = this.transformFilters(filters); // Recursively transform nested filters
|
|
1247
|
+
}
|
|
1248
|
+
return transformedFilter;
|
|
1249
|
+
}
|
|
1250
|
+
getOperator(key) {
|
|
1251
|
+
return this.operators[key];
|
|
1252
|
+
}
|
|
1253
|
+
// Helper function to return the default operators
|
|
1254
|
+
getDefaultOperators() {
|
|
1255
|
+
return ALL_DEFAULT_OPERATORS.reduce((acc, operator) => {
|
|
1256
|
+
acc[operator.name] = {
|
|
1257
|
+
type: operator.name,
|
|
1258
|
+
};
|
|
1259
|
+
return acc;
|
|
1260
|
+
}, {});
|
|
1261
|
+
}
|
|
1262
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFilterOperatorMiddlewareServiceImpl, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1263
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFilterOperatorMiddlewareServiceImpl }); }
|
|
1264
|
+
}
|
|
1265
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFilterOperatorMiddlewareServiceImpl, decorators: [{
|
|
1266
|
+
type: Injectable
|
|
1267
|
+
}] });
|
|
1268
|
+
|
|
1269
|
+
class AXPErrorHandlerRegistryService {
|
|
1270
|
+
constructor(injector) {
|
|
1271
|
+
this.injector = injector;
|
|
1272
|
+
this.errorHandlers = [];
|
|
1273
|
+
}
|
|
1274
|
+
register(...plugins) {
|
|
1275
|
+
plugins.forEach(t => {
|
|
1276
|
+
const childInjector = Injector.create({ providers: [{ provide: t, useClass: t, deps: [] }], parent: this.injector });
|
|
1277
|
+
const handler = childInjector.get(t);
|
|
1278
|
+
if (handler) {
|
|
1279
|
+
this.errorHandlers.push(handler);
|
|
1280
|
+
}
|
|
1281
|
+
});
|
|
1282
|
+
}
|
|
1283
|
+
get handlers() {
|
|
1284
|
+
return this.errorHandlers;
|
|
1285
|
+
}
|
|
1286
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPErrorHandlerRegistryService, deps: [{ token: i0.Injector }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1287
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPErrorHandlerRegistryService, providedIn: 'root' }); }
|
|
1288
|
+
}
|
|
1289
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPErrorHandlerRegistryService, decorators: [{
|
|
1290
|
+
type: Injectable,
|
|
1291
|
+
args: [{
|
|
1292
|
+
providedIn: 'root'
|
|
1293
|
+
}]
|
|
1294
|
+
}], ctorParameters: () => [{ type: i0.Injector }] });
|
|
1295
|
+
|
|
1296
|
+
class AXPGlobalErrorHandler extends ErrorHandler {
|
|
1297
|
+
constructor(injector, registry) {
|
|
1298
|
+
super();
|
|
1299
|
+
this.injector = injector;
|
|
1300
|
+
this.registry = registry;
|
|
1301
|
+
}
|
|
1302
|
+
handleError(error) {
|
|
1303
|
+
const handleErrorRecursively = (index, error) => {
|
|
1304
|
+
const errorHandlers = this.registry.handlers;
|
|
1305
|
+
if (index < errorHandlers.length) {
|
|
1306
|
+
errorHandlers[index].handleError(error, (err) => {
|
|
1307
|
+
handleErrorRecursively(index + 1, err);
|
|
1308
|
+
});
|
|
1309
|
+
}
|
|
1310
|
+
else {
|
|
1311
|
+
super.handleError(error); // Fallback to default handler
|
|
1312
|
+
}
|
|
1313
|
+
};
|
|
1314
|
+
if (error.message?.startsWith('NG0100')) {
|
|
1315
|
+
//ignore it
|
|
1316
|
+
return;
|
|
1317
|
+
}
|
|
1318
|
+
handleErrorRecursively(0, error);
|
|
1319
|
+
}
|
|
1320
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPGlobalErrorHandler, deps: [{ token: i0.Injector }, { token: AXPErrorHandlerRegistryService }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1321
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPGlobalErrorHandler, providedIn: 'root' }); }
|
|
1322
|
+
}
|
|
1323
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPGlobalErrorHandler, decorators: [{
|
|
1324
|
+
type: Injectable,
|
|
1325
|
+
args: [{ providedIn: 'root' }]
|
|
1326
|
+
}], ctorParameters: () => [{ type: i0.Injector }, { type: AXPErrorHandlerRegistryService }] });
|
|
1327
|
+
|
|
1328
|
+
const AXP_TOKEN_DEFINITION_PROVIDER = new InjectionToken('AXP_TOKEN_DEFINITION_PROVIDER', {
|
|
1329
|
+
factory: () => [],
|
|
1330
|
+
});
|
|
1331
|
+
|
|
1332
|
+
//#region ---- Imports ----
|
|
1333
|
+
//#endregion
|
|
1334
|
+
class AXPTokenDefinitionService {
|
|
1335
|
+
constructor() {
|
|
1336
|
+
//#region ---- Providers & Caches ----
|
|
1337
|
+
this.definitionProviders = inject(AXP_TOKEN_DEFINITION_PROVIDER, { optional: true }) || [];
|
|
1338
|
+
/** Cache for definitions by name. */
|
|
1339
|
+
this.definitionsByName = new Map();
|
|
1340
|
+
}
|
|
1341
|
+
//#endregion
|
|
1342
|
+
//#region ---- Public API ----
|
|
1343
|
+
/**
|
|
1344
|
+
* Returns a token definition by its name.
|
|
1345
|
+
* First checks the cache, then queries all providers if not found.
|
|
1346
|
+
* @param name The token name.
|
|
1347
|
+
* @returns The token definition if found, undefined otherwise.
|
|
1348
|
+
*/
|
|
1349
|
+
async getDefinition(name) {
|
|
1350
|
+
// Check cache first
|
|
1351
|
+
if (this.definitionsByName.has(name)) {
|
|
1352
|
+
return this.definitionsByName.get(name);
|
|
1353
|
+
}
|
|
1354
|
+
// Not in cache, query providers
|
|
1355
|
+
// Resolve all providers (some may be promises, some may be arrays)
|
|
1356
|
+
const resolvedProviders = await Promise.all(this.definitionProviders);
|
|
1357
|
+
// Flatten the array - some providers may be arrays of providers (e.g., dynamic token providers)
|
|
1358
|
+
const flattenedProviders = [];
|
|
1359
|
+
for (const provider of resolvedProviders) {
|
|
1360
|
+
if (Array.isArray(provider)) {
|
|
1361
|
+
// If provider is an array, add all items
|
|
1362
|
+
flattenedProviders.push(...provider);
|
|
1363
|
+
}
|
|
1364
|
+
else if (provider) {
|
|
1365
|
+
// If provider is a single provider, add it
|
|
1366
|
+
flattenedProviders.push(provider);
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
const match = flattenedProviders.find(def => def?.name === name);
|
|
1370
|
+
if (match) {
|
|
1371
|
+
this.definitionsByName.set(name, match);
|
|
1372
|
+
return match;
|
|
1373
|
+
}
|
|
1374
|
+
return undefined;
|
|
1375
|
+
}
|
|
1376
|
+
/**
|
|
1377
|
+
* Gets the resolved value of a token by its name.
|
|
1378
|
+
* @param name The token name.
|
|
1379
|
+
* @returns The resolved token value, or undefined if the token is not found.
|
|
1380
|
+
*/
|
|
1381
|
+
async getValue(name) {
|
|
1382
|
+
const definition = await this.getDefinition(name);
|
|
1383
|
+
if (definition) {
|
|
1384
|
+
return await definition.execute();
|
|
1385
|
+
}
|
|
1386
|
+
return undefined;
|
|
1387
|
+
}
|
|
1388
|
+
//#endregion
|
|
1389
|
+
//#region ---- Cache Management ----
|
|
1390
|
+
/** Clears the definitions by name cache. */
|
|
1391
|
+
clearDefinitionsCache() {
|
|
1392
|
+
this.definitionsByName.clear();
|
|
1393
|
+
}
|
|
1394
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTokenDefinitionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1395
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTokenDefinitionService, providedIn: 'root' }); }
|
|
1396
|
+
}
|
|
1397
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTokenDefinitionService, decorators: [{
|
|
1398
|
+
type: Injectable,
|
|
1399
|
+
args: [{
|
|
1400
|
+
providedIn: 'root',
|
|
1401
|
+
}]
|
|
1402
|
+
}] });
|
|
1403
|
+
|
|
1404
|
+
class AXPTokenEvaluatorScopeProvider {
|
|
1405
|
+
constructor() {
|
|
1406
|
+
this.tokenService = inject(AXPTokenDefinitionService);
|
|
1407
|
+
}
|
|
1408
|
+
async provide(context) {
|
|
1409
|
+
context.addScope('tokens', {
|
|
1410
|
+
get: async (name) => {
|
|
1411
|
+
return await this.tokenService.getValue(name);
|
|
1412
|
+
}
|
|
1413
|
+
});
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
class AXPStickyDirective {
|
|
1418
|
+
get isSticky() {
|
|
1419
|
+
return this._isSticky;
|
|
1420
|
+
}
|
|
1421
|
+
set isSticky(value) {
|
|
1422
|
+
if (this._isSticky !== value) {
|
|
1423
|
+
this._isSticky = value;
|
|
1424
|
+
this.isStickyChange.emit(value);
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
constructor(element, renderer, zone, cdr) {
|
|
1428
|
+
this.element = element;
|
|
1429
|
+
this.renderer = renderer;
|
|
1430
|
+
this.zone = zone;
|
|
1431
|
+
this.cdr = cdr;
|
|
1432
|
+
this.stickyOffset = 100;
|
|
1433
|
+
this.isStickyChange = new EventEmitter();
|
|
1434
|
+
this._isSticky = false;
|
|
1435
|
+
this.onWindowResize = () => {
|
|
1436
|
+
this.applyStickyStateFromScrollPosition();
|
|
1437
|
+
};
|
|
1438
|
+
this.onParentScroll = () => {
|
|
1439
|
+
this.applyStickyStateFromScrollPosition();
|
|
1440
|
+
};
|
|
1441
|
+
}
|
|
1442
|
+
ngAfterViewInit() {
|
|
1443
|
+
this.initSticky();
|
|
1444
|
+
// Add mutation observer for dynamic content
|
|
1445
|
+
this.mutationObserver = new MutationObserver(() => {
|
|
1446
|
+
this.applyStickyStateFromScrollPosition();
|
|
1447
|
+
});
|
|
1448
|
+
this.mutationObserver.observe(this.element.nativeElement, {
|
|
1449
|
+
childList: true,
|
|
1450
|
+
subtree: true,
|
|
1451
|
+
});
|
|
1452
|
+
window.addEventListener('resize', this.onWindowResize);
|
|
1453
|
+
}
|
|
1454
|
+
initSticky() {
|
|
1455
|
+
if (this.stickyParent instanceof HTMLElement) {
|
|
1456
|
+
this.parentElement = this.stickyParent;
|
|
1457
|
+
}
|
|
1458
|
+
else {
|
|
1459
|
+
this.parentElement = document.querySelector(this.stickyParent) || window;
|
|
1460
|
+
}
|
|
1461
|
+
this.targetElement = document.querySelector(this.stickyTarget) || this.element.nativeElement;
|
|
1462
|
+
this.parentElement.addEventListener('scroll', this.onParentScroll);
|
|
1463
|
+
this.applyStickyStateFromScrollPosition();
|
|
1464
|
+
}
|
|
1465
|
+
getScrollTop() {
|
|
1466
|
+
return this.parentElement === window ? window.scrollY : this.parentElement.scrollTop;
|
|
1467
|
+
}
|
|
1468
|
+
/**
|
|
1469
|
+
* Toggle stuck styling only while this element is visually pinned (matches `position: sticky; top: 0`).
|
|
1470
|
+
* Scroll-offset thresholds would stay true for every section already passed, so multiple stacked stickies
|
|
1471
|
+
* would all keep `--stuck`; geometry fixes that.
|
|
1472
|
+
*/
|
|
1473
|
+
computeShouldStick() {
|
|
1474
|
+
if (this.getScrollTop() <= 0) {
|
|
1475
|
+
return false;
|
|
1476
|
+
}
|
|
1477
|
+
const tRect = this.targetElement.getBoundingClientRect();
|
|
1478
|
+
const epsilon = 3;
|
|
1479
|
+
let limitY;
|
|
1480
|
+
if (this.parentElement === window) {
|
|
1481
|
+
limitY = this.stickyOffset;
|
|
1482
|
+
}
|
|
1483
|
+
else {
|
|
1484
|
+
limitY = this.parentElement.getBoundingClientRect().top;
|
|
1485
|
+
}
|
|
1486
|
+
return tRect.top <= limitY + epsilon && tRect.top >= limitY - epsilon;
|
|
1487
|
+
}
|
|
1488
|
+
applyStickyStateFromScrollPosition() {
|
|
1489
|
+
this.zone.runOutsideAngular(() => {
|
|
1490
|
+
const shouldStick = this.computeShouldStick();
|
|
1491
|
+
if (shouldStick !== this.isSticky) {
|
|
1492
|
+
this.zone.run(() => {
|
|
1493
|
+
this.isSticky = shouldStick;
|
|
1494
|
+
this.toggleStickyClasses(shouldStick);
|
|
1495
|
+
this.cdr.markForCheck();
|
|
1496
|
+
});
|
|
1497
|
+
}
|
|
1498
|
+
});
|
|
1499
|
+
}
|
|
1500
|
+
toggleStickyClasses(isSticky) {
|
|
1501
|
+
const classes = this.stickyClass.split(' ').filter(Boolean);
|
|
1502
|
+
classes.forEach((className) => {
|
|
1503
|
+
if (isSticky) {
|
|
1504
|
+
this.renderer.addClass(this.targetElement, className);
|
|
1505
|
+
}
|
|
1506
|
+
else {
|
|
1507
|
+
this.renderer.removeClass(this.targetElement, className);
|
|
1508
|
+
}
|
|
1509
|
+
});
|
|
1510
|
+
}
|
|
1511
|
+
ngOnDestroy() {
|
|
1512
|
+
this.parentElement.removeEventListener('scroll', this.onParentScroll);
|
|
1513
|
+
window.removeEventListener('resize', this.onWindowResize);
|
|
1514
|
+
if (this.mutationObserver) {
|
|
1515
|
+
this.mutationObserver.disconnect();
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPStickyDirective, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }, { token: i0.NgZone }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1519
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: AXPStickyDirective, isStandalone: true, selector: "[axpSticky]", inputs: { stickyClass: ["axpSticky", "stickyClass"], stickyOffset: "stickyOffset", stickyParent: "stickyParent", stickyTarget: "stickyTarget" }, outputs: { isStickyChange: "isStickyChange" }, exportAs: ["axpSticky"], ngImport: i0 }); }
|
|
1520
|
+
}
|
|
1521
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPStickyDirective, decorators: [{
|
|
1522
|
+
type: Directive,
|
|
1523
|
+
args: [{
|
|
1524
|
+
selector: '[axpSticky]',
|
|
1525
|
+
exportAs: 'axpSticky',
|
|
1526
|
+
standalone: true,
|
|
1527
|
+
}]
|
|
1528
|
+
}], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.Renderer2 }, { type: i0.NgZone }, { type: i0.ChangeDetectorRef }], propDecorators: { stickyClass: [{
|
|
1529
|
+
type: Input,
|
|
1530
|
+
args: ['axpSticky']
|
|
1531
|
+
}], stickyOffset: [{
|
|
1532
|
+
type: Input,
|
|
1533
|
+
args: ['stickyOffset']
|
|
1534
|
+
}], isStickyChange: [{
|
|
1535
|
+
type: Output
|
|
1536
|
+
}], stickyParent: [{
|
|
1537
|
+
type: Input
|
|
1538
|
+
}], stickyTarget: [{
|
|
1539
|
+
type: Input
|
|
1540
|
+
}] } });
|
|
1541
|
+
|
|
1542
|
+
//#region ---- Menu Context Builder ----
|
|
1543
|
+
/**
|
|
1544
|
+
* Creates a rich context for menu manipulation
|
|
1545
|
+
* Similar to entity modifier context pattern
|
|
1546
|
+
*/
|
|
1547
|
+
function createMenuContext(items) {
|
|
1548
|
+
const context = {
|
|
1549
|
+
get items() {
|
|
1550
|
+
return items;
|
|
1551
|
+
},
|
|
1552
|
+
find(nameOrPattern) {
|
|
1553
|
+
if (typeof nameOrPattern === 'string') {
|
|
1554
|
+
return context.findByName(nameOrPattern);
|
|
1555
|
+
}
|
|
1556
|
+
const found = context.findByPattern(nameOrPattern);
|
|
1557
|
+
return found.length > 0 ? found[0] : createEmptyFinder();
|
|
1558
|
+
},
|
|
1559
|
+
findByName(name) {
|
|
1560
|
+
return createMenuItemFinder(items, name);
|
|
1561
|
+
},
|
|
1562
|
+
findByPattern(pattern) {
|
|
1563
|
+
const regex = typeof pattern === 'string' ? wildcardToRegex(pattern) : pattern;
|
|
1564
|
+
const results = [];
|
|
1565
|
+
const searchRecursive = (itemList) => {
|
|
1566
|
+
for (const item of itemList) {
|
|
1567
|
+
if (item.name && regex.test(item.name)) {
|
|
1568
|
+
results.push(createMenuItemFinder(items, item.name));
|
|
1569
|
+
}
|
|
1570
|
+
if (item.children) {
|
|
1571
|
+
searchRecursive(item.children);
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
};
|
|
1575
|
+
searchRecursive(items);
|
|
1576
|
+
return results;
|
|
1577
|
+
},
|
|
1578
|
+
add(...newItems) {
|
|
1579
|
+
items.push(...newItems);
|
|
1580
|
+
return context;
|
|
1581
|
+
},
|
|
1582
|
+
remove(predicate) {
|
|
1583
|
+
removeRecursive(items, predicate);
|
|
1584
|
+
return context;
|
|
1585
|
+
},
|
|
1586
|
+
sort() {
|
|
1587
|
+
sortItemsRecursively(items);
|
|
1588
|
+
return context;
|
|
1589
|
+
},
|
|
1590
|
+
toArray() {
|
|
1591
|
+
return items;
|
|
1592
|
+
},
|
|
1593
|
+
};
|
|
1594
|
+
return context;
|
|
1595
|
+
}
|
|
1596
|
+
//#endregion
|
|
1597
|
+
//#region ---- Menu Item Finder Implementation ----
|
|
1598
|
+
/**
|
|
1599
|
+
* Creates a finder for a specific menu item
|
|
1600
|
+
*/
|
|
1601
|
+
function createMenuItemFinder(rootItems, targetName) {
|
|
1602
|
+
const findItemWithParent = (items, name, parent = { items: rootItems }) => {
|
|
1603
|
+
for (const item of items) {
|
|
1604
|
+
if (item.name === name) {
|
|
1605
|
+
return { item, parent };
|
|
1606
|
+
}
|
|
1607
|
+
if (item.children) {
|
|
1608
|
+
const result = findItemWithParent(item.children, name, { items: item.children });
|
|
1609
|
+
if (result.item) {
|
|
1610
|
+
return result;
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
return { item: undefined, parent };
|
|
1615
|
+
};
|
|
1616
|
+
const finder = {
|
|
1617
|
+
get() {
|
|
1618
|
+
return findItemWithParent(rootItems, targetName).item;
|
|
1619
|
+
},
|
|
1620
|
+
exists() {
|
|
1621
|
+
return finder.get() !== undefined;
|
|
1622
|
+
},
|
|
1623
|
+
update(updater) {
|
|
1624
|
+
const { item } = findItemWithParent(rootItems, targetName);
|
|
1625
|
+
if (item) {
|
|
1626
|
+
const updates = typeof updater === 'function' ? updater(item) : updater;
|
|
1627
|
+
Object.assign(item, updates);
|
|
1628
|
+
}
|
|
1629
|
+
return finder;
|
|
1630
|
+
},
|
|
1631
|
+
remove() {
|
|
1632
|
+
const { item, parent } = findItemWithParent(rootItems, targetName);
|
|
1633
|
+
if (item) {
|
|
1634
|
+
const index = parent.items.indexOf(item);
|
|
1635
|
+
if (index !== -1) {
|
|
1636
|
+
parent.items.splice(index, 1);
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
return finder;
|
|
1640
|
+
},
|
|
1641
|
+
hide() {
|
|
1642
|
+
// Hide by removing the item from menu
|
|
1643
|
+
return finder.remove();
|
|
1644
|
+
},
|
|
1645
|
+
show() {
|
|
1646
|
+
// Show is a no-op since items are visible by default
|
|
1647
|
+
// To truly implement show, we'd need to track removed items and re-add them
|
|
1648
|
+
console.warn('show() is not fully implemented - items are visible by default');
|
|
1649
|
+
return finder;
|
|
1650
|
+
},
|
|
1651
|
+
setPriority(priority) {
|
|
1652
|
+
return finder.update({ priority });
|
|
1653
|
+
},
|
|
1654
|
+
addChildren(...children) {
|
|
1655
|
+
const { item } = findItemWithParent(rootItems, targetName);
|
|
1656
|
+
if (item) {
|
|
1657
|
+
item.children = item.children || [];
|
|
1658
|
+
item.children.push(...children);
|
|
1659
|
+
}
|
|
1660
|
+
return finder;
|
|
1661
|
+
},
|
|
1662
|
+
removeChildren(predicate) {
|
|
1663
|
+
const { item } = findItemWithParent(rootItems, targetName);
|
|
1664
|
+
if (item && item.children) {
|
|
1665
|
+
item.children = item.children.filter((c) => !predicate(c));
|
|
1666
|
+
}
|
|
1667
|
+
return finder;
|
|
1668
|
+
},
|
|
1669
|
+
moveTo(parentName) {
|
|
1670
|
+
const { item, parent: oldParent } = findItemWithParent(rootItems, targetName);
|
|
1671
|
+
if (!item) {
|
|
1672
|
+
return finder;
|
|
1673
|
+
}
|
|
1674
|
+
// Remove from old parent
|
|
1675
|
+
const oldIndex = oldParent.items.indexOf(item);
|
|
1676
|
+
if (oldIndex !== -1) {
|
|
1677
|
+
oldParent.items.splice(oldIndex, 1);
|
|
1678
|
+
}
|
|
1679
|
+
// Add to new parent
|
|
1680
|
+
if (!parentName || parentName === '') {
|
|
1681
|
+
// Move to root (handles null, undefined, and empty string)
|
|
1682
|
+
rootItems.push(item);
|
|
1683
|
+
}
|
|
1684
|
+
else {
|
|
1685
|
+
const { item: newParentItem } = findItemWithParent(rootItems, parentName);
|
|
1686
|
+
if (newParentItem) {
|
|
1687
|
+
newParentItem.children = newParentItem.children || [];
|
|
1688
|
+
newParentItem.children.push(item);
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
return finder;
|
|
1692
|
+
},
|
|
1693
|
+
insertBefore(...items) {
|
|
1694
|
+
const { item, parent } = findItemWithParent(rootItems, targetName);
|
|
1695
|
+
if (item) {
|
|
1696
|
+
const index = parent.items.indexOf(item);
|
|
1697
|
+
if (index !== -1) {
|
|
1698
|
+
parent.items.splice(index, 0, ...items);
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
return finder;
|
|
1702
|
+
},
|
|
1703
|
+
insertAfter(...items) {
|
|
1704
|
+
const { item, parent } = findItemWithParent(rootItems, targetName);
|
|
1705
|
+
if (item) {
|
|
1706
|
+
const index = parent.items.indexOf(item);
|
|
1707
|
+
if (index !== -1) {
|
|
1708
|
+
parent.items.splice(index + 1, 0, ...items);
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
return finder;
|
|
1712
|
+
},
|
|
1713
|
+
};
|
|
1714
|
+
return finder;
|
|
1715
|
+
}
|
|
1716
|
+
/**
|
|
1717
|
+
* Creates an empty finder for items not found
|
|
1718
|
+
*/
|
|
1719
|
+
function createEmptyFinder() {
|
|
1720
|
+
return {
|
|
1721
|
+
get: () => undefined,
|
|
1722
|
+
exists: () => false,
|
|
1723
|
+
update: () => createEmptyFinder(),
|
|
1724
|
+
remove: () => createEmptyFinder(),
|
|
1725
|
+
hide: () => createEmptyFinder(),
|
|
1726
|
+
show: () => createEmptyFinder(),
|
|
1727
|
+
setPriority: () => createEmptyFinder(),
|
|
1728
|
+
addChildren: () => createEmptyFinder(),
|
|
1729
|
+
removeChildren: () => createEmptyFinder(),
|
|
1730
|
+
moveTo: () => createEmptyFinder(),
|
|
1731
|
+
insertBefore: () => createEmptyFinder(),
|
|
1732
|
+
insertAfter: () => createEmptyFinder(),
|
|
1733
|
+
};
|
|
1734
|
+
}
|
|
1735
|
+
//#endregion
|
|
1736
|
+
//#region ---- Helper Functions ----
|
|
1737
|
+
/**
|
|
1738
|
+
* Converts wildcard pattern to regex
|
|
1739
|
+
*/
|
|
1740
|
+
function wildcardToRegex(pattern) {
|
|
1741
|
+
const escaped = pattern.replace(/[.+?^${}()|\[\]\\]/g, '\\$&');
|
|
1742
|
+
const regexStr = `^${escaped.replace(/\*/g, '.*')}$`;
|
|
1743
|
+
return new RegExp(regexStr);
|
|
1744
|
+
}
|
|
1745
|
+
/**
|
|
1746
|
+
* Recursively removes items matching predicate
|
|
1747
|
+
*/
|
|
1748
|
+
function removeRecursive(items, predicate) {
|
|
1749
|
+
for (let i = items.length - 1; i >= 0; i--) {
|
|
1750
|
+
const item = items[i];
|
|
1751
|
+
if (predicate(item)) {
|
|
1752
|
+
items.splice(i, 1);
|
|
1753
|
+
}
|
|
1754
|
+
else if (item.children) {
|
|
1755
|
+
removeRecursive(item.children, predicate);
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
/**
|
|
1760
|
+
* Recursively sorts items by priority
|
|
1761
|
+
*/
|
|
1762
|
+
function sortItemsRecursively(items) {
|
|
1763
|
+
items.sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0));
|
|
1764
|
+
items.forEach((item) => {
|
|
1765
|
+
if (item.children && item.children.length > 0) {
|
|
1766
|
+
sortItemsRecursively(item.children);
|
|
1767
|
+
}
|
|
1768
|
+
});
|
|
1769
|
+
}
|
|
1770
|
+
//#endregion
|
|
1771
|
+
|
|
1772
|
+
/**
|
|
1773
|
+
* Injection token for menu middlewares (multi-provider)
|
|
1774
|
+
*/
|
|
1775
|
+
const AXP_MENU_MIDDLEWARE = new InjectionToken('AXP_MENU_MIDDLEWARE');
|
|
1776
|
+
//#endregion
|
|
1777
|
+
|
|
1778
|
+
//#region ---- Menu Middleware Registry ----
|
|
1779
|
+
/**
|
|
1780
|
+
* Central service for managing menu middlewares with priority-based ordering
|
|
1781
|
+
* Similar to token registry pattern used in identifier-management
|
|
1782
|
+
*/
|
|
1783
|
+
class AXPMenuMiddlewareRegistry {
|
|
1784
|
+
constructor() {
|
|
1785
|
+
//#region ---- Fields ----
|
|
1786
|
+
/**
|
|
1787
|
+
* Manually registered middlewares
|
|
1788
|
+
*/
|
|
1789
|
+
this.manualMiddlewares = new Map();
|
|
1790
|
+
/**
|
|
1791
|
+
* Provider-based middlewares (multi-provider injection)
|
|
1792
|
+
*/
|
|
1793
|
+
this.providerMiddlewares = inject(AXP_MENU_MIDDLEWARE, { optional: true }) ?? [];
|
|
1794
|
+
}
|
|
1795
|
+
//#endregion
|
|
1796
|
+
//#region ---- Registration Methods ----
|
|
1797
|
+
/**
|
|
1798
|
+
* Manually register a middleware
|
|
1799
|
+
* @param middleware - The middleware to register
|
|
1800
|
+
*/
|
|
1801
|
+
register(middleware) {
|
|
1802
|
+
const name = middleware.name || `middleware_${Date.now()}_${Math.random()}`;
|
|
1803
|
+
const existing = this.manualMiddlewares.get(name);
|
|
1804
|
+
if (!existing || (middleware.priority ?? 0) > (existing.priority ?? 0)) {
|
|
1805
|
+
this.manualMiddlewares.set(name, middleware);
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
/**
|
|
1809
|
+
* Unregister a middleware by name
|
|
1810
|
+
* @param name - The name of the middleware to remove
|
|
1811
|
+
*/
|
|
1812
|
+
unregister(name) {
|
|
1813
|
+
this.manualMiddlewares.delete(name);
|
|
1814
|
+
}
|
|
1815
|
+
/**
|
|
1816
|
+
* Get a specific middleware by name
|
|
1817
|
+
* @param name - The middleware name
|
|
1818
|
+
*/
|
|
1819
|
+
get(name) {
|
|
1820
|
+
// Provider-provided middlewares take precedence
|
|
1821
|
+
const fromProvider = this.providerMiddlewares
|
|
1822
|
+
.filter((m) => m.name === name)
|
|
1823
|
+
.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0))[0];
|
|
1824
|
+
if (fromProvider) {
|
|
1825
|
+
return fromProvider;
|
|
1826
|
+
}
|
|
1827
|
+
return this.manualMiddlewares.get(name);
|
|
1828
|
+
}
|
|
1829
|
+
/**
|
|
1830
|
+
* Get all middlewares sorted by priority (highest first)
|
|
1831
|
+
* Combines provider-based and manually registered middlewares
|
|
1832
|
+
*/
|
|
1833
|
+
list() {
|
|
1834
|
+
const map = new Map();
|
|
1835
|
+
// Helper to add middleware, respecting priority
|
|
1836
|
+
const addMiddleware = (m) => {
|
|
1837
|
+
const name = m.name || `anonymous_${Date.now()}`;
|
|
1838
|
+
const existing = map.get(name);
|
|
1839
|
+
if (!existing || (m.priority ?? 0) > (existing.priority ?? 0)) {
|
|
1840
|
+
map.set(name, m);
|
|
1841
|
+
}
|
|
1842
|
+
};
|
|
1843
|
+
// Add provider middlewares first (higher precedence)
|
|
1844
|
+
this.providerMiddlewares.forEach(addMiddleware);
|
|
1845
|
+
// Add manual middlewares
|
|
1846
|
+
Array.from(this.manualMiddlewares.values()).forEach(addMiddleware);
|
|
1847
|
+
// Return sorted by priority (highest first)
|
|
1848
|
+
return Array.from(map.values()).sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
|
1849
|
+
}
|
|
1850
|
+
/**
|
|
1851
|
+
* Check if a middleware exists
|
|
1852
|
+
* @param name - The middleware name
|
|
1853
|
+
*/
|
|
1854
|
+
exists(name) {
|
|
1855
|
+
return this.get(name) !== undefined;
|
|
1856
|
+
}
|
|
1857
|
+
/**
|
|
1858
|
+
* Clear all manually registered middlewares
|
|
1859
|
+
* Provider-based middlewares are not affected
|
|
1860
|
+
*/
|
|
1861
|
+
clear() {
|
|
1862
|
+
this.manualMiddlewares.clear();
|
|
1863
|
+
}
|
|
1864
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPMenuMiddlewareRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1865
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPMenuMiddlewareRegistry, providedIn: 'root' }); }
|
|
1866
|
+
}
|
|
1867
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPMenuMiddlewareRegistry, decorators: [{
|
|
1868
|
+
type: Injectable,
|
|
1869
|
+
args: [{ providedIn: 'root' }]
|
|
1870
|
+
}] });
|
|
1871
|
+
|
|
1872
|
+
const AXP_MENU_PROVIDER = new InjectionToken('AXP_MENU_PROVIDER');
|
|
1873
|
+
//TODO: check performance of this service
|
|
1874
|
+
class AXPMenuProviderService {
|
|
1875
|
+
constructor() {
|
|
1876
|
+
//#region ---- Dependencies ----
|
|
1877
|
+
this.providers = inject(AXP_MENU_PROVIDER, { optional: true });
|
|
1878
|
+
this.middlewareRegistry = inject(AXPMenuMiddlewareRegistry);
|
|
1879
|
+
//#endregion
|
|
1880
|
+
//#region ---- State ----
|
|
1881
|
+
this.cache = null;
|
|
1882
|
+
this.pendingInserts = [];
|
|
1883
|
+
this.pendingRemovals = [];
|
|
1884
|
+
this.pendingUpdates = [];
|
|
1885
|
+
this.pendingAdditions = [];
|
|
1886
|
+
this.pendingMoves = [];
|
|
1887
|
+
/**
|
|
1888
|
+
* Observable for menu reload events
|
|
1889
|
+
* Emits when menu cache is cleared and consumers should reload
|
|
1890
|
+
*/
|
|
1891
|
+
this.menuReloadSubject = new Subject();
|
|
1892
|
+
this.menuReload$ = this.menuReloadSubject.asObservable();
|
|
1893
|
+
}
|
|
1894
|
+
//#endregion
|
|
1895
|
+
//#region ---- Public API ----
|
|
1896
|
+
/**
|
|
1897
|
+
* Get menu items with middlewares applied (for end-user display)
|
|
1898
|
+
*/
|
|
1899
|
+
async items() {
|
|
1900
|
+
// Return cached items if available
|
|
1901
|
+
if (this.cache) {
|
|
1902
|
+
return this.cache;
|
|
1903
|
+
}
|
|
1904
|
+
const items = [];
|
|
1905
|
+
const context = this.createMenuProviderContext(items);
|
|
1906
|
+
// Load providers from DI tokens
|
|
1907
|
+
if (Array.isArray(this.providers)) {
|
|
1908
|
+
for (const provider of this.providers) {
|
|
1909
|
+
if (provider instanceof Promise) {
|
|
1910
|
+
// If provider is a promise, resolve it
|
|
1911
|
+
const resolvedProvider = await provider;
|
|
1912
|
+
await resolvedProvider.provide(context);
|
|
1913
|
+
}
|
|
1914
|
+
else {
|
|
1915
|
+
// If provider is a direct instance, use it directly
|
|
1916
|
+
await provider.provide(context);
|
|
1917
|
+
}
|
|
1918
|
+
// Apply pending operations after each provider so subsequent providers
|
|
1919
|
+
// can find items added by earlier providers (e.g. Tokens under Data Definitions)
|
|
1920
|
+
this.applyPendingOperations(items);
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
// Sort items by priority
|
|
1924
|
+
items.sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0));
|
|
1925
|
+
// Apply middlewares using registry (sorted by priority)
|
|
1926
|
+
await this.applyMiddlewares(items);
|
|
1927
|
+
// ADD level property to items
|
|
1928
|
+
setMenuItemMeta(items);
|
|
1929
|
+
// Cache the computed items
|
|
1930
|
+
this.cache = items;
|
|
1931
|
+
return items;
|
|
1932
|
+
}
|
|
1933
|
+
/**
|
|
1934
|
+
* Get raw menu items WITHOUT middleware applied (for management/editing)
|
|
1935
|
+
* Used by menu management pages to show original items before customization
|
|
1936
|
+
*/
|
|
1937
|
+
async rawItems() {
|
|
1938
|
+
const items = [];
|
|
1939
|
+
const context = this.createMenuProviderContext(items);
|
|
1940
|
+
// Load providers from DI tokens
|
|
1941
|
+
if (Array.isArray(this.providers)) {
|
|
1942
|
+
for (const provider of this.providers) {
|
|
1943
|
+
if (provider instanceof Promise) {
|
|
1944
|
+
// If provider is a promise, resolve it
|
|
1945
|
+
const resolvedProvider = await provider;
|
|
1946
|
+
await resolvedProvider.provide(context);
|
|
1947
|
+
}
|
|
1948
|
+
else {
|
|
1949
|
+
// If provider is a direct instance, use it directly
|
|
1950
|
+
await provider.provide(context);
|
|
1951
|
+
}
|
|
1952
|
+
// Apply pending operations after each provider
|
|
1953
|
+
this.applyPendingOperations(items);
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1956
|
+
// Sort items by priority
|
|
1957
|
+
items.sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0));
|
|
1958
|
+
// ADD level property to items (but NO middleware)
|
|
1959
|
+
setMenuItemMeta(items);
|
|
1960
|
+
return items;
|
|
1961
|
+
}
|
|
1962
|
+
/**
|
|
1963
|
+
* Clear the cache to force reload of menu items
|
|
1964
|
+
* Notifies all subscribers to reload their menu data
|
|
1965
|
+
*/
|
|
1966
|
+
clearCache() {
|
|
1967
|
+
this.cache = null;
|
|
1968
|
+
this.pendingInserts = [];
|
|
1969
|
+
this.pendingRemovals = [];
|
|
1970
|
+
this.pendingUpdates = [];
|
|
1971
|
+
this.pendingAdditions = [];
|
|
1972
|
+
this.pendingMoves = [];
|
|
1973
|
+
// Notify subscribers that menu should be reloaded
|
|
1974
|
+
this.menuReloadSubject.next();
|
|
1975
|
+
}
|
|
1976
|
+
//#endregion
|
|
1977
|
+
//#region ---- Private Methods ----
|
|
1978
|
+
/**
|
|
1979
|
+
* Apply middlewares in priority order using the enhanced context API
|
|
1980
|
+
*/
|
|
1981
|
+
async applyMiddlewares(items) {
|
|
1982
|
+
const middlewares = this.middlewareRegistry.list();
|
|
1983
|
+
for (const middleware of middlewares) {
|
|
1984
|
+
try {
|
|
1985
|
+
const context = createMenuContext(items);
|
|
1986
|
+
await middleware.process(context);
|
|
1987
|
+
// The context mutates the items array, so no need to reassign
|
|
1988
|
+
}
|
|
1989
|
+
catch (error) {
|
|
1990
|
+
console.error(`Error in menu middleware ${middleware.name || 'anonymous'}:`, error);
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
/**
|
|
1995
|
+
* Create provider context for menu providers
|
|
1996
|
+
*/
|
|
1997
|
+
createMenuProviderContext(items) {
|
|
1998
|
+
return {
|
|
1999
|
+
addItems: (newItems) => {
|
|
2000
|
+
for (const newItem of newItems) {
|
|
2001
|
+
if (newItem.name) {
|
|
2002
|
+
const existingItem = this.findItemByName(items, newItem.name);
|
|
2003
|
+
if (existingItem) {
|
|
2004
|
+
// Update existing item, merging properties
|
|
2005
|
+
this.mergeMenuItem(existingItem, newItem);
|
|
2006
|
+
}
|
|
2007
|
+
else {
|
|
2008
|
+
// Add new item if it doesn't exist
|
|
2009
|
+
items.push(newItem);
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
else {
|
|
2013
|
+
// Add item without name (no way to check for duplicates)
|
|
2014
|
+
items.push(newItem);
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
},
|
|
2018
|
+
find: (target) => {
|
|
2019
|
+
const foundItemInfo = this.findItemWithParent(items, target);
|
|
2020
|
+
const exists = foundItemInfo.foundItem !== null;
|
|
2021
|
+
return {
|
|
2022
|
+
exists,
|
|
2023
|
+
insert: (newItems, position) => {
|
|
2024
|
+
this.pendingInserts.push({ target, newItems, position });
|
|
2025
|
+
},
|
|
2026
|
+
remove: () => {
|
|
2027
|
+
this.pendingRemovals.push(target);
|
|
2028
|
+
},
|
|
2029
|
+
update: (updatedItem) => {
|
|
2030
|
+
this.pendingUpdates.push({ target, updatedItem });
|
|
2031
|
+
},
|
|
2032
|
+
addItems: (newItems) => {
|
|
2033
|
+
this.pendingAdditions.push({ target, newItems });
|
|
2034
|
+
},
|
|
2035
|
+
moveTo: (destination, position = 'inside') => {
|
|
2036
|
+
this.pendingMoves.push({
|
|
2037
|
+
source: target,
|
|
2038
|
+
destination,
|
|
2039
|
+
position: position ?? 'inside',
|
|
2040
|
+
});
|
|
2041
|
+
},
|
|
2042
|
+
};
|
|
2043
|
+
}
|
|
2044
|
+
};
|
|
2045
|
+
}
|
|
2046
|
+
applyPendingOperations(items) {
|
|
2047
|
+
for (const { target, newItems, position } of this.pendingInserts) {
|
|
2048
|
+
const foundItemInfo = this.findItemWithParent(items, target);
|
|
2049
|
+
const { foundItem, parentItems } = foundItemInfo;
|
|
2050
|
+
if (!foundItem) {
|
|
2051
|
+
console.warn(`Target "${target}" not found, appending items.`);
|
|
2052
|
+
items.push(...newItems);
|
|
2053
|
+
}
|
|
2054
|
+
else {
|
|
2055
|
+
if (position === 'inside') {
|
|
2056
|
+
if (!foundItem.children) {
|
|
2057
|
+
foundItem.children = [];
|
|
2058
|
+
}
|
|
2059
|
+
// Check for duplicates by name and merge or add
|
|
2060
|
+
for (const newItem of newItems) {
|
|
2061
|
+
if (newItem.name) {
|
|
2062
|
+
const existingChild = this.findItemByName(foundItem.children, newItem.name);
|
|
2063
|
+
if (existingChild) {
|
|
2064
|
+
this.mergeMenuItem(existingChild, newItem);
|
|
2065
|
+
}
|
|
2066
|
+
else {
|
|
2067
|
+
foundItem.children.push(newItem);
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
else {
|
|
2071
|
+
foundItem.children.push(newItem);
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
else {
|
|
2076
|
+
// For 'before' and 'after', check for duplicates in parent items
|
|
2077
|
+
const itemsToInsert = [];
|
|
2078
|
+
for (const newItem of newItems) {
|
|
2079
|
+
if (newItem.name) {
|
|
2080
|
+
const existingItem = this.findItemByName(parentItems, newItem.name);
|
|
2081
|
+
if (existingItem) {
|
|
2082
|
+
this.mergeMenuItem(existingItem, newItem);
|
|
2083
|
+
}
|
|
2084
|
+
else {
|
|
2085
|
+
itemsToInsert.push(newItem);
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
else {
|
|
2089
|
+
itemsToInsert.push(newItem);
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
if (itemsToInsert.length > 0) {
|
|
2093
|
+
const index = parentItems.indexOf(foundItem);
|
|
2094
|
+
const insertPosition = position === 'before' ? index : index + 1;
|
|
2095
|
+
parentItems.splice(insertPosition, 0, ...itemsToInsert);
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
for (const target of this.pendingRemovals) {
|
|
2101
|
+
const foundItemInfo = this.findItemWithParent(items, target);
|
|
2102
|
+
const { foundItem, parentItems } = foundItemInfo;
|
|
2103
|
+
if (!foundItem) {
|
|
2104
|
+
console.warn(`Target "${target}" not found, nothing to remove.`);
|
|
2105
|
+
}
|
|
2106
|
+
else {
|
|
2107
|
+
const index = parentItems.indexOf(foundItem);
|
|
2108
|
+
parentItems.splice(index, 1);
|
|
2109
|
+
}
|
|
2110
|
+
}
|
|
2111
|
+
for (const { target, updatedItem } of this.pendingUpdates) {
|
|
2112
|
+
const foundItemInfo = this.findItemWithParent(items, target);
|
|
2113
|
+
const { foundItem } = foundItemInfo;
|
|
2114
|
+
if (!foundItem) {
|
|
2115
|
+
console.warn(`Target "${target}" not found, nothing to update.`);
|
|
2116
|
+
}
|
|
2117
|
+
else {
|
|
2118
|
+
Object.assign(foundItem, updatedItem);
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2121
|
+
for (const { target, newItems } of this.pendingAdditions) {
|
|
2122
|
+
const foundItemInfo = this.findItemWithParent(items, target);
|
|
2123
|
+
const { foundItem } = foundItemInfo;
|
|
2124
|
+
if (!foundItem) {
|
|
2125
|
+
console.warn(`Target "${target}" not found, nothing to add items to.`);
|
|
2126
|
+
}
|
|
2127
|
+
else {
|
|
2128
|
+
if (!foundItem.children) {
|
|
2129
|
+
foundItem.children = [];
|
|
2130
|
+
}
|
|
2131
|
+
// Check for duplicates by name and merge or add
|
|
2132
|
+
for (const newItem of newItems) {
|
|
2133
|
+
if (newItem.name) {
|
|
2134
|
+
const existingChild = this.findItemByName(foundItem.children, newItem.name);
|
|
2135
|
+
if (existingChild) {
|
|
2136
|
+
this.mergeMenuItem(existingChild, newItem);
|
|
2137
|
+
}
|
|
2138
|
+
else {
|
|
2139
|
+
foundItem.children.push(newItem);
|
|
2140
|
+
}
|
|
2141
|
+
}
|
|
2142
|
+
else {
|
|
2143
|
+
foundItem.children.push(newItem);
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
for (const move of this.pendingMoves) {
|
|
2149
|
+
this.applyPendingMove(items, move.source, move.destination, move.position);
|
|
2150
|
+
}
|
|
2151
|
+
// Clear pending operations after applying them
|
|
2152
|
+
this.pendingInserts = [];
|
|
2153
|
+
this.pendingRemovals = [];
|
|
2154
|
+
this.pendingUpdates = [];
|
|
2155
|
+
this.pendingAdditions = [];
|
|
2156
|
+
this.pendingMoves = [];
|
|
2157
|
+
}
|
|
2158
|
+
findItemWithParent(items, target, parentItems = items) {
|
|
2159
|
+
for (const item of items) {
|
|
2160
|
+
if (item.name === target) {
|
|
2161
|
+
return { foundItem: item, parentItems };
|
|
2162
|
+
}
|
|
2163
|
+
if (item.children) {
|
|
2164
|
+
const result = this.findItemWithParent(item.children, target, item.children);
|
|
2165
|
+
if (result.foundItem) {
|
|
2166
|
+
return result;
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
return { foundItem: null, parentItems: items };
|
|
2171
|
+
}
|
|
2172
|
+
/**
|
|
2173
|
+
* Find menu item by name in the items tree (recursive search)
|
|
2174
|
+
*/
|
|
2175
|
+
findItemByName(items, name) {
|
|
2176
|
+
for (const item of items) {
|
|
2177
|
+
if (item.name === name) {
|
|
2178
|
+
return item;
|
|
2179
|
+
}
|
|
2180
|
+
if (item.children) {
|
|
2181
|
+
const found = this.findItemByName(item.children, name);
|
|
2182
|
+
if (found) {
|
|
2183
|
+
return found;
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
return null;
|
|
2188
|
+
}
|
|
2189
|
+
applyPendingMove(items, sourceName, destName, position) {
|
|
2190
|
+
if (sourceName === destName) {
|
|
2191
|
+
console.warn(`moveTo: source and destination are the same ("${sourceName}"), ignored.`);
|
|
2192
|
+
return;
|
|
2193
|
+
}
|
|
2194
|
+
const sourceInfo = this.findItemWithParent(items, sourceName);
|
|
2195
|
+
const { foundItem: sourceItem, parentItems: sourceParent } = sourceInfo;
|
|
2196
|
+
if (!sourceItem) {
|
|
2197
|
+
console.warn(`moveTo: source "${sourceName}" not found.`);
|
|
2198
|
+
return;
|
|
2199
|
+
}
|
|
2200
|
+
const destInfo = this.findItemWithParent(items, destName);
|
|
2201
|
+
const { foundItem: destItem, parentItems: destParent } = destInfo;
|
|
2202
|
+
if (!destItem) {
|
|
2203
|
+
console.warn(`moveTo: destination "${destName}" not found.`);
|
|
2204
|
+
return;
|
|
2205
|
+
}
|
|
2206
|
+
if (this.itemIsInSubtreeOf(sourceItem, destItem)) {
|
|
2207
|
+
console.warn(`moveTo: cannot move "${sourceName}" because "${destName}" is inside the source subtree.`);
|
|
2208
|
+
return;
|
|
2209
|
+
}
|
|
2210
|
+
const sourceIndex = sourceParent.indexOf(sourceItem);
|
|
2211
|
+
if (sourceIndex === -1) {
|
|
2212
|
+
console.warn(`moveTo: source "${sourceName}" not in parent list (inconsistent).`);
|
|
2213
|
+
return;
|
|
2214
|
+
}
|
|
2215
|
+
sourceParent.splice(sourceIndex, 1);
|
|
2216
|
+
if (position === 'inside') {
|
|
2217
|
+
if (!destItem.children) {
|
|
2218
|
+
destItem.children = [];
|
|
2219
|
+
}
|
|
2220
|
+
destItem.children.push(sourceItem);
|
|
2221
|
+
return;
|
|
2222
|
+
}
|
|
2223
|
+
const destIndexInParent = destParent.indexOf(destItem);
|
|
2224
|
+
if (destIndexInParent === -1) {
|
|
2225
|
+
console.warn(`moveTo: destination "${destName}" not in parent list, reverting source removal.`);
|
|
2226
|
+
sourceParent.splice(sourceIndex, 0, sourceItem);
|
|
2227
|
+
return;
|
|
2228
|
+
}
|
|
2229
|
+
const insertIndex = position === 'before' ? destIndexInParent : destIndexInParent + 1;
|
|
2230
|
+
destParent.splice(insertIndex, 0, sourceItem);
|
|
2231
|
+
}
|
|
2232
|
+
itemIsInSubtreeOf(root, candidate) {
|
|
2233
|
+
if (root === candidate) {
|
|
2234
|
+
return true;
|
|
2235
|
+
}
|
|
2236
|
+
if (!root.children?.length) {
|
|
2237
|
+
return false;
|
|
2238
|
+
}
|
|
2239
|
+
for (const child of root.children) {
|
|
2240
|
+
if (child === candidate) {
|
|
2241
|
+
return true;
|
|
2242
|
+
}
|
|
2243
|
+
if (this.itemIsInSubtreeOf(child, candidate)) {
|
|
2244
|
+
return true;
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
return false;
|
|
2248
|
+
}
|
|
2249
|
+
/**
|
|
2250
|
+
* Merge new item properties into existing item, handling children merge
|
|
2251
|
+
*/
|
|
2252
|
+
mergeMenuItem(existingItem, newItem) {
|
|
2253
|
+
// Merge top-level properties (newItem properties override existing only if defined)
|
|
2254
|
+
if (newItem.text !== undefined)
|
|
2255
|
+
existingItem.text = newItem.text;
|
|
2256
|
+
if (newItem.description !== undefined)
|
|
2257
|
+
existingItem.description = newItem.description;
|
|
2258
|
+
if (newItem.icon !== undefined)
|
|
2259
|
+
existingItem.icon = newItem.icon;
|
|
2260
|
+
if (newItem.path !== undefined)
|
|
2261
|
+
existingItem.path = newItem.path;
|
|
2262
|
+
if (newItem.priority !== undefined)
|
|
2263
|
+
existingItem.priority = newItem.priority;
|
|
2264
|
+
if (newItem.type !== undefined)
|
|
2265
|
+
existingItem.type = newItem.type;
|
|
2266
|
+
if (newItem.command !== undefined)
|
|
2267
|
+
existingItem.command = newItem.command;
|
|
2268
|
+
if (newItem.badgeKey !== undefined)
|
|
2269
|
+
existingItem.badgeKey = newItem.badgeKey;
|
|
2270
|
+
if (newItem.data !== undefined)
|
|
2271
|
+
existingItem.data = newItem.data;
|
|
2272
|
+
if (newItem.policy !== undefined)
|
|
2273
|
+
existingItem.policy = newItem.policy;
|
|
2274
|
+
// Merge children if both have children
|
|
2275
|
+
if (newItem.children && newItem.children.length > 0) {
|
|
2276
|
+
if (!existingItem.children) {
|
|
2277
|
+
existingItem.children = [];
|
|
2278
|
+
}
|
|
2279
|
+
// For each child in newItem, check if it exists by name and merge or add
|
|
2280
|
+
for (const newChild of newItem.children) {
|
|
2281
|
+
if (newChild.name) {
|
|
2282
|
+
const existingChild = this.findItemByName(existingItem.children, newChild.name);
|
|
2283
|
+
if (existingChild) {
|
|
2284
|
+
this.mergeMenuItem(existingChild, newChild);
|
|
2285
|
+
}
|
|
2286
|
+
else {
|
|
2287
|
+
existingItem.children.push(newChild);
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
else {
|
|
2291
|
+
existingItem.children.push(newChild);
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPMenuProviderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
2297
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPMenuProviderService, providedIn: 'root' }); }
|
|
2298
|
+
}
|
|
2299
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPMenuProviderService, decorators: [{
|
|
2300
|
+
type: Injectable,
|
|
2301
|
+
args: [{ providedIn: 'root' }]
|
|
2302
|
+
}] });
|
|
2303
|
+
function setMenuItemMeta(items, parentIndex = '', depth = 0) {
|
|
2304
|
+
items.forEach((item, index) => {
|
|
2305
|
+
const currentIndex = parentIndex ? `${parentIndex}-${index}` : `${index}`;
|
|
2306
|
+
const hasChildren = !!(item.children && Array.isArray(item.children) && item.children.length > 0);
|
|
2307
|
+
item.meta = {
|
|
2308
|
+
index: currentIndex,
|
|
2309
|
+
isRoot: depth === 0,
|
|
2310
|
+
hasChildren,
|
|
2311
|
+
depth
|
|
2312
|
+
};
|
|
2313
|
+
if (hasChildren) {
|
|
2314
|
+
setMenuItemMeta(item.children, currentIndex, depth + 1);
|
|
2315
|
+
}
|
|
2316
|
+
});
|
|
2317
|
+
}
|
|
2318
|
+
|
|
2319
|
+
//#region ---- Imports ----
|
|
2320
|
+
//#endregion
|
|
2321
|
+
//#region ---- Helpers ----
|
|
2322
|
+
function flattenMenuItems(items, depth) {
|
|
2323
|
+
const rows = [];
|
|
2324
|
+
let index = 0;
|
|
2325
|
+
for (const it of items) {
|
|
2326
|
+
const fallback = `menu-${depth}-${index}`;
|
|
2327
|
+
index += 1;
|
|
2328
|
+
const id = it.name ?? it.path ?? fallback;
|
|
2329
|
+
rows.push({ id, title: it.text });
|
|
2330
|
+
if (it.children?.length) {
|
|
2331
|
+
rows.push(...flattenMenuItems(it.children, depth + 1));
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
return rows;
|
|
2335
|
+
}
|
|
2336
|
+
//#endregion
|
|
2337
|
+
//#region ---- Menu items data source ----
|
|
2338
|
+
/**
|
|
2339
|
+
* Registered menu items (raw tree flattened) for select widgets via dataSource name {@link MENU_ITEMS_DATASOURCE_NAME}.
|
|
2340
|
+
*/
|
|
2341
|
+
const MENU_ITEMS_DATASOURCE_NAME = 'platform-menu-items';
|
|
2342
|
+
/**
|
|
2343
|
+
* Data source definition for menu items from {@link AXPMenuProviderService#rawItems}.
|
|
2344
|
+
*/
|
|
2345
|
+
class AXPMenuItemsDataSourceDefinition {
|
|
2346
|
+
constructor() {
|
|
2347
|
+
//#region ---- Services & Dependencies ----
|
|
2348
|
+
this.menuProviderService = inject(AXPMenuProviderService);
|
|
2349
|
+
//#endregion
|
|
2350
|
+
}
|
|
2351
|
+
//#endregion
|
|
2352
|
+
//#region ---- Public API ----
|
|
2353
|
+
async items() {
|
|
2354
|
+
return [
|
|
2355
|
+
{
|
|
2356
|
+
name: MENU_ITEMS_DATASOURCE_NAME,
|
|
2357
|
+
title: 'Menu items',
|
|
2358
|
+
source: () => new AXDataSource({
|
|
2359
|
+
key: 'id',
|
|
2360
|
+
load: async () => {
|
|
2361
|
+
const raw = await this.menuProviderService.rawItems();
|
|
2362
|
+
const list = flattenMenuItems(raw, 0);
|
|
2363
|
+
return { items: list, total: list.length };
|
|
2364
|
+
},
|
|
2365
|
+
byKey: async (key) => {
|
|
2366
|
+
const raw = await this.menuProviderService.rawItems();
|
|
2367
|
+
const list = flattenMenuItems(raw, 0);
|
|
2368
|
+
return list.find((item) => item.id === key);
|
|
2369
|
+
},
|
|
2370
|
+
pageSize: 1000,
|
|
2371
|
+
}),
|
|
2372
|
+
columns: [
|
|
2373
|
+
{
|
|
2374
|
+
name: 'id',
|
|
2375
|
+
title: 'ID',
|
|
2376
|
+
datatype: 'string',
|
|
2377
|
+
type: AXPWidgetsCatalog.text,
|
|
2378
|
+
},
|
|
2379
|
+
{
|
|
2380
|
+
name: 'title',
|
|
2381
|
+
title: 'Title',
|
|
2382
|
+
datatype: 'string',
|
|
2383
|
+
type: AXPWidgetsCatalog.text,
|
|
2384
|
+
},
|
|
2385
|
+
],
|
|
2386
|
+
filters: [
|
|
2387
|
+
{
|
|
2388
|
+
field: 'title',
|
|
2389
|
+
title: 'Title',
|
|
2390
|
+
operator: { type: 'equal' },
|
|
2391
|
+
widget: { type: AXPWidgetsCatalog.text },
|
|
2392
|
+
filterType: { advance: true, inline: true },
|
|
2393
|
+
},
|
|
2394
|
+
],
|
|
2395
|
+
textField: { name: 'title', title: 'Title' },
|
|
2396
|
+
valueField: { name: 'id', title: 'ID' },
|
|
2397
|
+
},
|
|
2398
|
+
];
|
|
2399
|
+
}
|
|
2400
|
+
}
|
|
2401
|
+
//#endregion
|
|
2402
|
+
|
|
2403
|
+
//#region ---- Menu Middleware Provider Helper ----
|
|
2404
|
+
/**
|
|
2405
|
+
* Helper function to provide menu middlewares with proper DI setup
|
|
2406
|
+
* Similar to provideCommandMiddleware pattern
|
|
2407
|
+
*
|
|
2408
|
+
* @example
|
|
2409
|
+
* ```typescript
|
|
2410
|
+
* // In app.config.ts or module providers
|
|
2411
|
+
* export const appConfig: ApplicationConfig = {
|
|
2412
|
+
* providers: [
|
|
2413
|
+
* provideMenuMiddleware([
|
|
2414
|
+
* customizationMiddleware,
|
|
2415
|
+
* securityMiddleware,
|
|
2416
|
+
* loggingMiddleware
|
|
2417
|
+
* ])
|
|
2418
|
+
* ]
|
|
2419
|
+
* };
|
|
2420
|
+
* ```
|
|
2421
|
+
*/
|
|
2422
|
+
function provideMenuMiddleware(middlewares) {
|
|
2423
|
+
return makeEnvironmentProviders([
|
|
2424
|
+
...middlewares.map((middleware) => ({
|
|
2425
|
+
provide: AXP_MENU_MIDDLEWARE,
|
|
2426
|
+
useValue: middleware,
|
|
2427
|
+
multi: true,
|
|
2428
|
+
})),
|
|
2429
|
+
]);
|
|
2430
|
+
}
|
|
2431
|
+
/**
|
|
2432
|
+
* Helper to create a class-based middleware with priority
|
|
2433
|
+
*
|
|
2434
|
+
* @example
|
|
2435
|
+
* ```typescript
|
|
2436
|
+
* const myMiddleware = createMenuMiddleware({
|
|
2437
|
+
* name: 'my-middleware',
|
|
2438
|
+
* priority: 100,
|
|
2439
|
+
* process: async (context) => {
|
|
2440
|
+
* context.find('dashboard').setPriority(1);
|
|
2441
|
+
* }
|
|
2442
|
+
* });
|
|
2443
|
+
* ```
|
|
2444
|
+
*/
|
|
2445
|
+
function createMenuMiddleware(config) {
|
|
2446
|
+
return {
|
|
2447
|
+
name: config.name,
|
|
2448
|
+
priority: config.priority ?? 0,
|
|
2449
|
+
process: config.process,
|
|
2450
|
+
};
|
|
2451
|
+
}
|
|
2452
|
+
//#endregion
|
|
2453
|
+
|
|
2454
|
+
const AXPMenuService = signalStore({ providedIn: 'root' },
|
|
2455
|
+
// Initial State
|
|
2456
|
+
withState((router = inject(Router)) => {
|
|
2457
|
+
return {
|
|
2458
|
+
items: [],
|
|
2459
|
+
selectedMenuItem: {
|
|
2460
|
+
item: null,
|
|
2461
|
+
isFullMatch: false,
|
|
2462
|
+
},
|
|
2463
|
+
};
|
|
2464
|
+
}),
|
|
2465
|
+
// Methods for State Management
|
|
2466
|
+
withMethods((store, router = inject(Router), workflow = inject(AXPWorkflowService), commandExecutor = inject(AXPCommandExecutor)) => {
|
|
2467
|
+
return {
|
|
2468
|
+
setMenuItems(items) {
|
|
2469
|
+
patchState(store, { items: items });
|
|
2470
|
+
},
|
|
2471
|
+
selectMenuItemByRoute(path) {
|
|
2472
|
+
const currentRawPath = path;
|
|
2473
|
+
const findItem = (items) => {
|
|
2474
|
+
for (const item of items) {
|
|
2475
|
+
// Recursively search children first
|
|
2476
|
+
if (item.children) {
|
|
2477
|
+
const foundChild = findItem(item.children);
|
|
2478
|
+
if (foundChild.item) {
|
|
2479
|
+
return foundChild;
|
|
2480
|
+
}
|
|
2481
|
+
}
|
|
2482
|
+
if (item.path) {
|
|
2483
|
+
// Check if the item path matches the current path (including query param subset if provided on item)
|
|
2484
|
+
const matchResult = pathsMatch(item.path, currentRawPath, ['list', 'view', 'edit', 'create']);
|
|
2485
|
+
if (matchResult.isMatch) {
|
|
2486
|
+
return { item, isPartialMatch: matchResult.isPartial };
|
|
2487
|
+
}
|
|
2488
|
+
}
|
|
2489
|
+
}
|
|
2490
|
+
return { item: null, isPartialMatch: false };
|
|
2491
|
+
};
|
|
2492
|
+
const pathsMatch = (itemPath, currentPath, trailingSegmentsToIgnore = []) => {
|
|
2493
|
+
// Split base and query/hash
|
|
2494
|
+
const itemNoHash = itemPath.split('#')[0];
|
|
2495
|
+
const currentNoHash = currentPath.split('#')[0];
|
|
2496
|
+
const [itemBase, itemQueryString = ''] = itemNoHash.split('?');
|
|
2497
|
+
const [currentBase, currentQueryString = ''] = currentNoHash.split('?');
|
|
2498
|
+
const itemSegments = itemBase.split('/').filter((segment) => segment.length > 0);
|
|
2499
|
+
const pathSegments = currentBase.split('/').filter((segment) => segment.length > 0);
|
|
2500
|
+
const menuLength = itemSegments.length;
|
|
2501
|
+
const pathLength = pathSegments.length;
|
|
2502
|
+
// Identify the effective base length for the menuPath
|
|
2503
|
+
let effectiveMenuLength = menuLength;
|
|
2504
|
+
if (menuLength > 0 && trailingSegmentsToIgnore.includes(itemSegments[menuLength - 1])) {
|
|
2505
|
+
effectiveMenuLength--; // Exclude trailing non-hierarchical segments
|
|
2506
|
+
}
|
|
2507
|
+
// Check if the menuPath (base segments) is a prefix of browserPath
|
|
2508
|
+
let isPrefix = true;
|
|
2509
|
+
if (pathLength < effectiveMenuLength) {
|
|
2510
|
+
return { isMatch: false, isPartial: false };
|
|
2511
|
+
}
|
|
2512
|
+
for (let i = 0; i < effectiveMenuLength; i++) {
|
|
2513
|
+
if (itemSegments[i] !== pathSegments[i]) {
|
|
2514
|
+
isPrefix = false;
|
|
2515
|
+
break;
|
|
2516
|
+
}
|
|
2517
|
+
}
|
|
2518
|
+
if (!isPrefix) {
|
|
2519
|
+
return { isMatch: false, isPartial: false };
|
|
2520
|
+
}
|
|
2521
|
+
// Partial match: menuPath base is a strict prefix of browserPath
|
|
2522
|
+
const isPartialMatch = effectiveMenuLength < pathLength;
|
|
2523
|
+
// Exact match: all segments match completely
|
|
2524
|
+
const isExactMatch = effectiveMenuLength === pathLength;
|
|
2525
|
+
// Query params subset match: if item defines any query params, require them to match
|
|
2526
|
+
const itemParams = new URLSearchParams(itemQueryString);
|
|
2527
|
+
const currentParams = new URLSearchParams(currentQueryString);
|
|
2528
|
+
let queryMatches = true;
|
|
2529
|
+
const itemParamPairs = [];
|
|
2530
|
+
itemParams.forEach((value, key) => {
|
|
2531
|
+
itemParamPairs.push([key, value]);
|
|
2532
|
+
});
|
|
2533
|
+
if (itemParamPairs.length > 0) {
|
|
2534
|
+
for (const [k, v] of itemParamPairs) {
|
|
2535
|
+
if (currentParams.get(k) !== v) {
|
|
2536
|
+
queryMatches = false;
|
|
2537
|
+
break;
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
return {
|
|
2542
|
+
isMatch: (isExactMatch || isPartialMatch) && queryMatches,
|
|
2543
|
+
isPartial: isPartialMatch && !isExactMatch,
|
|
2544
|
+
};
|
|
2545
|
+
};
|
|
2546
|
+
const items = store.items();
|
|
2547
|
+
const { item, isPartialMatch } = findItem(items);
|
|
2548
|
+
patchState(store, { selectedMenuItem: { item, isFullMatch: !isPartialMatch } });
|
|
2549
|
+
},
|
|
2550
|
+
executeCommand(item) {
|
|
2551
|
+
if (item.path) {
|
|
2552
|
+
workflow.execute('navigate', {
|
|
2553
|
+
type: 'router',
|
|
2554
|
+
options: { path: item.path },
|
|
2555
|
+
});
|
|
2556
|
+
patchState(store, { selectedMenuItem: { item, isFullMatch: true } });
|
|
2557
|
+
}
|
|
2558
|
+
if (item.command) {
|
|
2559
|
+
const command = item.command;
|
|
2560
|
+
if (workflow.exists(command?.name)) {
|
|
2561
|
+
workflow.execute(command.name, command.options);
|
|
2562
|
+
}
|
|
2563
|
+
else {
|
|
2564
|
+
commandExecutor.execute(command.name, command.options);
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2567
|
+
},
|
|
2568
|
+
isItemOpen(item) {
|
|
2569
|
+
const selectedItem = store.selectedMenuItem();
|
|
2570
|
+
if (!selectedItem.item) {
|
|
2571
|
+
return false;
|
|
2572
|
+
}
|
|
2573
|
+
const findParent = (currentItem, targetItem) => {
|
|
2574
|
+
if (currentItem.children?.includes(targetItem)) {
|
|
2575
|
+
return true;
|
|
2576
|
+
}
|
|
2577
|
+
return currentItem.children?.some((child) => findParent(child, targetItem)) ?? false;
|
|
2578
|
+
};
|
|
2579
|
+
return item === selectedItem.item || findParent(item, selectedItem.item);
|
|
2580
|
+
},
|
|
2581
|
+
};
|
|
2582
|
+
}), withHooks((store, menuProviderService = inject(AXPMenuProviderService)) => {
|
|
2583
|
+
let reloadSubscription;
|
|
2584
|
+
return {
|
|
2585
|
+
onInit() {
|
|
2586
|
+
// Load initial menu items
|
|
2587
|
+
(async () => {
|
|
2588
|
+
const items = await menuProviderService.items();
|
|
2589
|
+
patchState(store, { items: items });
|
|
2590
|
+
})();
|
|
2591
|
+
// Subscribe to menu reload events
|
|
2592
|
+
reloadSubscription = menuProviderService.menuReload$.subscribe(async () => {
|
|
2593
|
+
const items = await menuProviderService.items();
|
|
2594
|
+
patchState(store, { items: items });
|
|
2595
|
+
});
|
|
2596
|
+
},
|
|
2597
|
+
onDestroy() {
|
|
2598
|
+
// Clean up subscription
|
|
2599
|
+
if (reloadSubscription) {
|
|
2600
|
+
reloadSubscription.unsubscribe();
|
|
2601
|
+
}
|
|
2602
|
+
},
|
|
2603
|
+
};
|
|
2604
|
+
}));
|
|
2605
|
+
|
|
2606
|
+
/**
|
|
2607
|
+
* Service for checking menu item visibility based on permissions, features, and children.
|
|
2608
|
+
* This service helps filter out empty menu items that have no visible children.
|
|
2609
|
+
*/
|
|
2610
|
+
class AXPMenuVisibilityService {
|
|
2611
|
+
constructor() {
|
|
2612
|
+
this.sessionService = inject(AXPSessionService);
|
|
2613
|
+
}
|
|
2614
|
+
/**
|
|
2615
|
+
* Checks if a menu item should be visible based on permissions and features.
|
|
2616
|
+
*/
|
|
2617
|
+
isItemVisible(item) {
|
|
2618
|
+
const policy = item.policy;
|
|
2619
|
+
if (!policy) {
|
|
2620
|
+
return true; // No policy means visible
|
|
2621
|
+
}
|
|
2622
|
+
// Check features
|
|
2623
|
+
if (policy.features && policy.features.length > 0) {
|
|
2624
|
+
const featureKeys = Array.isArray(policy.features) ? policy.features : [policy.features];
|
|
2625
|
+
if (!this.sessionService.isFeatureEnabled(...featureKeys)) {
|
|
2626
|
+
return false;
|
|
2627
|
+
}
|
|
2628
|
+
}
|
|
2629
|
+
// Check permissions
|
|
2630
|
+
if (policy.permissions && policy.permissions.length > 0) {
|
|
2631
|
+
const permissionKeys = Array.isArray(policy.permissions) ? policy.permissions : [policy.permissions];
|
|
2632
|
+
if (!this.sessionService.authorize(...permissionKeys)) {
|
|
2633
|
+
return false;
|
|
2634
|
+
}
|
|
2635
|
+
}
|
|
2636
|
+
return true;
|
|
2637
|
+
}
|
|
2638
|
+
/**
|
|
2639
|
+
* Recursively checks if a menu item has any visible children.
|
|
2640
|
+
* @param item The menu item to check
|
|
2641
|
+
* @param getRouterLink Optional function to check if an item has a router link
|
|
2642
|
+
*/
|
|
2643
|
+
hasVisibleChildren(item, getRouterLink) {
|
|
2644
|
+
if (!item.children || item.children.length === 0) {
|
|
2645
|
+
return false;
|
|
2646
|
+
}
|
|
2647
|
+
return item.children.some((child) => {
|
|
2648
|
+
// Check if child itself is visible
|
|
2649
|
+
if (!this.isItemVisible(child)) {
|
|
2650
|
+
return false;
|
|
2651
|
+
}
|
|
2652
|
+
// If child has a routerLink, it's visible
|
|
2653
|
+
if (getRouterLink && getRouterLink(child)) {
|
|
2654
|
+
return true;
|
|
2655
|
+
}
|
|
2656
|
+
// If child has visible children, it's visible
|
|
2657
|
+
if (this.hasVisibleChildren(child, getRouterLink)) {
|
|
2658
|
+
return true;
|
|
2659
|
+
}
|
|
2660
|
+
// For items without routerLink, check if they have a command (non-empty menu items)
|
|
2661
|
+
if (child.command) {
|
|
2662
|
+
return true;
|
|
2663
|
+
}
|
|
2664
|
+
return false;
|
|
2665
|
+
});
|
|
2666
|
+
}
|
|
2667
|
+
/**
|
|
2668
|
+
* Determines if a menu item should be rendered.
|
|
2669
|
+
* A menu item should be rendered if:
|
|
2670
|
+
* 1. It has a routerLink, OR
|
|
2671
|
+
* 2. It has visible children, OR
|
|
2672
|
+
* 3. It has a command (for non-router navigation)
|
|
2673
|
+
*
|
|
2674
|
+
* @param item The menu item to check
|
|
2675
|
+
* @param getRouterLink Optional function to check if an item has a router link
|
|
2676
|
+
*/
|
|
2677
|
+
shouldRenderMenuItem(item, getRouterLink) {
|
|
2678
|
+
// Check if item itself is visible (permissions/features)
|
|
2679
|
+
if (!this.isItemVisible(item)) {
|
|
2680
|
+
return false;
|
|
2681
|
+
}
|
|
2682
|
+
// If it has a routerLink, render it
|
|
2683
|
+
if (getRouterLink && getRouterLink(item)) {
|
|
2684
|
+
return true;
|
|
2685
|
+
}
|
|
2686
|
+
// If it has visible children, render it
|
|
2687
|
+
if (this.hasVisibleChildren(item, getRouterLink)) {
|
|
2688
|
+
return true;
|
|
2689
|
+
}
|
|
2690
|
+
// If it has a command (for non-router navigation), render it
|
|
2691
|
+
if (item.command) {
|
|
2692
|
+
return true;
|
|
2693
|
+
}
|
|
2694
|
+
// Otherwise, don't render empty menu items
|
|
2695
|
+
return false;
|
|
2696
|
+
}
|
|
2697
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPMenuVisibilityService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
2698
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPMenuVisibilityService, providedIn: 'root' }); }
|
|
2699
|
+
}
|
|
2700
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPMenuVisibilityService, decorators: [{
|
|
2701
|
+
type: Injectable,
|
|
2702
|
+
args: [{ providedIn: 'root' }]
|
|
2703
|
+
}] });
|
|
2704
|
+
|
|
2705
|
+
class AXPMenuSearchDefinitionProvider {
|
|
2706
|
+
async provide(context) {
|
|
2707
|
+
context.addDefinition('menu', 'Menu', 'menu', 'fa-thin fa-bars', 2, {
|
|
2708
|
+
format: {
|
|
2709
|
+
id: '{{data.name}}',
|
|
2710
|
+
},
|
|
2711
|
+
actions: [
|
|
2712
|
+
{
|
|
2713
|
+
name: 'navigate',
|
|
2714
|
+
type: AXPSystemActionType.View,
|
|
2715
|
+
priority: 'primary',
|
|
2716
|
+
},
|
|
2717
|
+
],
|
|
2718
|
+
});
|
|
2719
|
+
}
|
|
2720
|
+
}
|
|
2721
|
+
|
|
2722
|
+
class AXPMenuSearchProvider {
|
|
2723
|
+
constructor() {
|
|
2724
|
+
this.menuService = inject(AXPMenuProviderService);
|
|
2725
|
+
this.translateService = inject(AXTranslationService);
|
|
2726
|
+
}
|
|
2727
|
+
async search(text) {
|
|
2728
|
+
const menuItems = await this.searchMenuItems(await this.menuService.items(), text);
|
|
2729
|
+
return sortBy(menuItems.map((item) => ({
|
|
2730
|
+
group: 'menu', // Use `name` or empty string if undefined
|
|
2731
|
+
title: item.text, // Use `text` for the `title`
|
|
2732
|
+
icon: item.icon, // Include the `icon` if present
|
|
2733
|
+
data: omit(item, ['parent', 'children']), // Include all data except `parent` and `children`
|
|
2734
|
+
command: item.path ? {
|
|
2735
|
+
name: 'navigate',
|
|
2736
|
+
options: {
|
|
2737
|
+
type: 'router',
|
|
2738
|
+
options: {
|
|
2739
|
+
path: item.path,
|
|
2740
|
+
},
|
|
2741
|
+
}
|
|
2742
|
+
} : item.command,
|
|
2743
|
+
parent: item.parent
|
|
2744
|
+
? {
|
|
2745
|
+
title: item.parent.text,
|
|
2746
|
+
}
|
|
2747
|
+
: undefined,
|
|
2748
|
+
})), [(o) => this.translateService.translateSync(o.title)]);
|
|
2749
|
+
}
|
|
2750
|
+
/**
|
|
2751
|
+
* Recursively searches AXPMenuItem and its children for a matching text.
|
|
2752
|
+
*
|
|
2753
|
+
* @param menuItems - The array of AXPMenuItem to search in.
|
|
2754
|
+
* @param searchText - The text to search for (case-insensitive).
|
|
2755
|
+
* @returns An array of AXPMenuItem that match the search text.
|
|
2756
|
+
*/
|
|
2757
|
+
async searchMenuItems(menuItems, searchText) {
|
|
2758
|
+
const result = [];
|
|
2759
|
+
for (const item of menuItems) {
|
|
2760
|
+
// Check if the current item's text matches the search text
|
|
2761
|
+
if (item.type != 'group' &&
|
|
2762
|
+
(item.children?.length ?? 0) == 0 &&
|
|
2763
|
+
(await this.translateService.translateAsync(item.text)).toLowerCase().includes(searchText.toLowerCase())) {
|
|
2764
|
+
result.push(item);
|
|
2765
|
+
}
|
|
2766
|
+
// Recursively search in children if they exist
|
|
2767
|
+
if (item.children && item.children.length > 0) {
|
|
2768
|
+
const childResults = await this.searchMenuItems(item.children, searchText);
|
|
2769
|
+
result.push(...childResults.map((x) => ({ ...x, parent: item })));
|
|
2770
|
+
}
|
|
2771
|
+
}
|
|
2772
|
+
return result;
|
|
2773
|
+
}
|
|
2774
|
+
}
|
|
2775
|
+
|
|
2776
|
+
class AXPFooterTextSlotComponent {
|
|
2777
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFooterTextSlotComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2778
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.9", type: AXPFooterTextSlotComponent, isStandalone: true, selector: "ng-component", inputs: { text: "text" }, ngImport: i0, template: `
|
|
2779
|
+
<small class="ax-text-sm" [innerHTML]="text"></small>
|
|
2780
|
+
`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
2781
|
+
}
|
|
2782
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFooterTextSlotComponent, decorators: [{
|
|
2783
|
+
type: Component,
|
|
2784
|
+
args: [{
|
|
2785
|
+
template: `
|
|
2786
|
+
<small class="ax-text-sm" [innerHTML]="text"></small>
|
|
2787
|
+
`,
|
|
2788
|
+
standalone: true,
|
|
2789
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
2790
|
+
}]
|
|
2791
|
+
}], propDecorators: { text: [{
|
|
2792
|
+
type: Input
|
|
2793
|
+
}] } });
|
|
2794
|
+
|
|
2795
|
+
class AXPNavBarSlotComponent {
|
|
2796
|
+
handleCommand(action) {
|
|
2797
|
+
// if (action)
|
|
2798
|
+
// this.store.dispatch(action)
|
|
2799
|
+
}
|
|
2800
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPNavBarSlotComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2801
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AXPNavBarSlotComponent, isStandalone: true, selector: "ng-component", inputs: { items: "items" }, ngImport: i0, template: `
|
|
2802
|
+
<div class="ax-flex ax-items-center ax-justify-between ax-gap-5">
|
|
2803
|
+
@for (link of items; track link) {
|
|
2804
|
+
<a (click)="handleCommand(link.command)" >{{ link.text }}</a>
|
|
2805
|
+
}
|
|
2806
|
+
</div>
|
|
2807
|
+
`, isInline: true, encapsulation: i0.ViewEncapsulation.None }); }
|
|
2808
|
+
}
|
|
2809
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPNavBarSlotComponent, decorators: [{
|
|
2810
|
+
type: Component,
|
|
2811
|
+
args: [{
|
|
2812
|
+
template: `
|
|
2813
|
+
<div class="ax-flex ax-items-center ax-justify-between ax-gap-5">
|
|
2814
|
+
@for (link of items; track link) {
|
|
2815
|
+
<a (click)="handleCommand(link.command)" >{{ link.text }}</a>
|
|
2816
|
+
}
|
|
2817
|
+
</div>
|
|
2818
|
+
`,
|
|
2819
|
+
imports: [],
|
|
2820
|
+
encapsulation: ViewEncapsulation.None
|
|
2821
|
+
}]
|
|
2822
|
+
}], propDecorators: { items: [{
|
|
2823
|
+
type: Input
|
|
2824
|
+
}] } });
|
|
2825
|
+
|
|
2826
|
+
class AXPSearchDefinitionProviderContext {
|
|
2827
|
+
constructor() {
|
|
2828
|
+
this.definitions = [];
|
|
2829
|
+
this.definitionMap = new Map();
|
|
2830
|
+
}
|
|
2831
|
+
addDefinition(name, title, group, icon, order, options) {
|
|
2832
|
+
const definitionSearch = {
|
|
2833
|
+
name,
|
|
2834
|
+
title,
|
|
2835
|
+
group,
|
|
2836
|
+
icon,
|
|
2837
|
+
order,
|
|
2838
|
+
format: {
|
|
2839
|
+
title: options?.format?.title ?? '{{ title }}',
|
|
2840
|
+
description: options?.format?.description ?? undefined,
|
|
2841
|
+
icon: options?.format?.icon ?? undefined,
|
|
2842
|
+
id: options?.format?.id ?? '{{ id }}',
|
|
2843
|
+
},
|
|
2844
|
+
actions: options?.actions ?? [],
|
|
2845
|
+
};
|
|
2846
|
+
this.definitions.push(definitionSearch);
|
|
2847
|
+
this.definitionMap.set(name, definitionSearch); // Index by name
|
|
2848
|
+
return new AXPSearchDefinitionBuilder(this, definitionSearch);
|
|
2849
|
+
}
|
|
2850
|
+
getDefinitions() {
|
|
2851
|
+
return this.definitions;
|
|
2852
|
+
}
|
|
2853
|
+
// Expose groupMap for controlled access
|
|
2854
|
+
hasEntity(name) {
|
|
2855
|
+
return this.definitionMap.has(name);
|
|
2856
|
+
}
|
|
2857
|
+
getDefinition(name) {
|
|
2858
|
+
return this.definitionMap.get(name);
|
|
2859
|
+
}
|
|
2860
|
+
}
|
|
2861
|
+
class AXPSearchDefinitionBuilder {
|
|
2862
|
+
constructor(context, definition) {
|
|
2863
|
+
this.context = context;
|
|
2864
|
+
this.definition = definition;
|
|
2865
|
+
}
|
|
2866
|
+
addAction(name) {
|
|
2867
|
+
const newAction = {
|
|
2868
|
+
name,
|
|
2869
|
+
type: AXPSystemActionType.View,
|
|
2870
|
+
priority: 'primary',
|
|
2871
|
+
};
|
|
2872
|
+
this.definition.actions.push(newAction);
|
|
2873
|
+
return new AXPSearchDefinitionActionBuilder(this);
|
|
2874
|
+
}
|
|
2875
|
+
action(name) {
|
|
2876
|
+
const foundAction = this.definition.actions.find((action) => action.name === name);
|
|
2877
|
+
if (!foundAction) {
|
|
2878
|
+
throw new Error(`action with name "${name}" not found in entity "${this.definition.name}".`);
|
|
2879
|
+
}
|
|
2880
|
+
return new AXPSearchDefinitionActionBuilder(this);
|
|
2881
|
+
}
|
|
2882
|
+
endEntity() {
|
|
2883
|
+
return this.context;
|
|
2884
|
+
}
|
|
2885
|
+
}
|
|
2886
|
+
class AXPSearchDefinitionActionBuilder {
|
|
2887
|
+
constructor(entityBuilder) {
|
|
2888
|
+
this.entityBuilder = entityBuilder;
|
|
2889
|
+
}
|
|
2890
|
+
endAction() {
|
|
2891
|
+
return this.entityBuilder;
|
|
2892
|
+
}
|
|
2893
|
+
}
|
|
2894
|
+
|
|
2895
|
+
// Injection token for setting providers
|
|
2896
|
+
const AXP_SEARCH_DEFINITION_PROVIDER = new InjectionToken('AXP_SEARCH_DEFINITION_PROVIDER');
|
|
2897
|
+
class AXPSearchDefinitionProviderService {
|
|
2898
|
+
constructor() {
|
|
2899
|
+
this.providers = inject(AXP_SEARCH_DEFINITION_PROVIDER, { optional: true });
|
|
2900
|
+
this.cache = null;
|
|
2901
|
+
}
|
|
2902
|
+
async load() {
|
|
2903
|
+
if (this.cache) {
|
|
2904
|
+
return;
|
|
2905
|
+
}
|
|
2906
|
+
const context = new AXPSearchDefinitionProviderContext();
|
|
2907
|
+
// Load providers from DI tokens (backward compatibility)
|
|
2908
|
+
if (Array.isArray(this.providers)) {
|
|
2909
|
+
for (const provider of this.providers) {
|
|
2910
|
+
await provider.provide(context);
|
|
2911
|
+
}
|
|
2912
|
+
}
|
|
2913
|
+
// Note: Search definition providers are different from search command providers
|
|
2914
|
+
// Search command providers are handled separately in search.service.ts
|
|
2915
|
+
this.cache = context.getDefinitions();
|
|
2916
|
+
}
|
|
2917
|
+
async getListAsync() {
|
|
2918
|
+
await this.load();
|
|
2919
|
+
return this.getList();
|
|
2920
|
+
}
|
|
2921
|
+
getList() {
|
|
2922
|
+
if (!this.cache) {
|
|
2923
|
+
return [];
|
|
2924
|
+
}
|
|
2925
|
+
else {
|
|
2926
|
+
return this.cache;
|
|
2927
|
+
}
|
|
2928
|
+
}
|
|
2929
|
+
findDefinition(definitionName) {
|
|
2930
|
+
const definition = this.getList().find((definition) => definition.name === definitionName);
|
|
2931
|
+
if (!definition) {
|
|
2932
|
+
throw new Error(`Definition with name ${definitionName} not found`);
|
|
2933
|
+
}
|
|
2934
|
+
return definition;
|
|
2935
|
+
}
|
|
2936
|
+
findDefinitionByGroup(definitionGroup) {
|
|
2937
|
+
const definition = this.getList().find((definition) => definition.group === definitionGroup);
|
|
2938
|
+
if (!definition) {
|
|
2939
|
+
throw new Error(`Definition with group ${definitionGroup} not found`);
|
|
2940
|
+
}
|
|
2941
|
+
return definition;
|
|
2942
|
+
}
|
|
2943
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPSearchDefinitionProviderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
2944
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPSearchDefinitionProviderService, providedIn: 'root' }); }
|
|
2945
|
+
}
|
|
2946
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPSearchDefinitionProviderService, decorators: [{
|
|
2947
|
+
type: Injectable,
|
|
2948
|
+
args: [{ providedIn: 'root' }]
|
|
2949
|
+
}] });
|
|
2950
|
+
|
|
2951
|
+
class AXPSearchCommandProvider {
|
|
2952
|
+
async search(text) {
|
|
2953
|
+
return this.commands.filter((command) => command.title.toLowerCase().includes(text.toLowerCase()));
|
|
2954
|
+
}
|
|
2955
|
+
}
|
|
2956
|
+
|
|
2957
|
+
// Injection token for setting providers
|
|
2958
|
+
const AXP_SEARCH_PROVIDER = new InjectionToken('AXP_SEARCH_PROVIDER');
|
|
2959
|
+
class AXPSearchService {
|
|
2960
|
+
constructor() {
|
|
2961
|
+
this.providers = inject(AXP_SEARCH_PROVIDER, { optional: true });
|
|
2962
|
+
this.definitionService = inject(AXPSearchDefinitionProviderService);
|
|
2963
|
+
this.formatService = inject(AXFormatService);
|
|
2964
|
+
this.translationService = inject(AXTranslationService);
|
|
2965
|
+
}
|
|
2966
|
+
async search(text) {
|
|
2967
|
+
//TODO better handle this
|
|
2968
|
+
if (this.definitionService.getList().length == 0) {
|
|
2969
|
+
await this.definitionService.getListAsync();
|
|
2970
|
+
}
|
|
2971
|
+
// Collect providers from DI tokens
|
|
2972
|
+
const allProviders = [];
|
|
2973
|
+
// Load from DI tokens
|
|
2974
|
+
if (Array.isArray(this.providers)) {
|
|
2975
|
+
allProviders.push(...this.providers);
|
|
2976
|
+
}
|
|
2977
|
+
else if (this.providers) {
|
|
2978
|
+
allProviders.push(this.providers);
|
|
2979
|
+
}
|
|
2980
|
+
if (allProviders.length === 0) {
|
|
2981
|
+
throw new Error('No search providers available');
|
|
2982
|
+
}
|
|
2983
|
+
const providersWithSearch = allProviders.filter((provider) => provider != null && typeof provider.search === 'function');
|
|
2984
|
+
if (providersWithSearch.length === 0) {
|
|
2985
|
+
throw new Error('No search providers with a valid search method available');
|
|
2986
|
+
}
|
|
2987
|
+
const mergeData = [];
|
|
2988
|
+
const promises = providersWithSearch.map((provider) => provider.search(text));
|
|
2989
|
+
const results = await Promise.all(promises);
|
|
2990
|
+
for (const resultArray of results) {
|
|
2991
|
+
for (const result of resultArray) {
|
|
2992
|
+
try {
|
|
2993
|
+
const definition = this.definitionService.findDefinition(result.group);
|
|
2994
|
+
mergeData.push({
|
|
2995
|
+
definitionName: definition.group,
|
|
2996
|
+
definitionTitle: definition.title,
|
|
2997
|
+
name: result.group,
|
|
2998
|
+
id: `${definition.name}:${this.formatService.format(definition.format.id, 'string', result)}`,
|
|
2999
|
+
title: result.title ??
|
|
3000
|
+
(definition.format.title
|
|
3001
|
+
? this.formatService.format(definition.format.title, 'string', result.data)
|
|
3002
|
+
: 'Unknown'),
|
|
3003
|
+
description: result.description ??
|
|
3004
|
+
(definition.format.description
|
|
3005
|
+
? this.formatService.format(definition.format.description, 'string', result.data)
|
|
3006
|
+
: undefined),
|
|
3007
|
+
icon: result.icon ?? definition.icon,
|
|
3008
|
+
data: result.data,
|
|
3009
|
+
command: result.command,
|
|
3010
|
+
actions: definition.actions,
|
|
3011
|
+
parent: result.parent,
|
|
3012
|
+
order: definition.order,
|
|
3013
|
+
});
|
|
3014
|
+
}
|
|
3015
|
+
catch (e) {
|
|
3016
|
+
console.error(e);
|
|
3017
|
+
}
|
|
3018
|
+
}
|
|
3019
|
+
}
|
|
3020
|
+
// Group data
|
|
3021
|
+
const groupedData = [];
|
|
3022
|
+
const groupMap = {};
|
|
3023
|
+
// Create groups
|
|
3024
|
+
for (const item of mergeData) {
|
|
3025
|
+
const groupName = item.definitionName; // Change this to the property you want to group by
|
|
3026
|
+
if (!groupMap[groupName]) {
|
|
3027
|
+
groupMap[groupName] = {
|
|
3028
|
+
name: groupName,
|
|
3029
|
+
title: item.definitionTitle, // You can adjust the title logic if needed
|
|
3030
|
+
order: item.order, // Add order to the group
|
|
3031
|
+
children: [],
|
|
3032
|
+
};
|
|
3033
|
+
groupedData.push(groupMap[groupName]);
|
|
3034
|
+
}
|
|
3035
|
+
groupMap[groupName].children.push(omit(item, ['definitionName', 'definitionTitle', 'order']));
|
|
3036
|
+
}
|
|
3037
|
+
// Sort groups by order
|
|
3038
|
+
groupedData.sort((a, b) => a.order - b.order);
|
|
3039
|
+
return groupedData;
|
|
3040
|
+
}
|
|
3041
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPSearchService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
3042
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPSearchService, providedIn: 'root' }); }
|
|
3043
|
+
}
|
|
3044
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPSearchService, decorators: [{
|
|
3045
|
+
type: Injectable,
|
|
3046
|
+
args: [{ providedIn: 'root' }]
|
|
3047
|
+
}] });
|
|
3048
|
+
|
|
3049
|
+
/**
|
|
3050
|
+
* Shared utility for creating Entity:Create command options used in search command providers.
|
|
3051
|
+
* Use with command: { name: 'Entity:Create', options: createEntityCommandOptions(...) }
|
|
3052
|
+
*/
|
|
3053
|
+
function createEntityCommandOptions(entityKey, entityInfo) {
|
|
3054
|
+
return {
|
|
3055
|
+
__context__: {
|
|
3056
|
+
entity: entityKey,
|
|
3057
|
+
entityInfo: {
|
|
3058
|
+
name: entityInfo.name,
|
|
3059
|
+
module: entityInfo.module,
|
|
3060
|
+
title: entityInfo.title,
|
|
3061
|
+
parentKey: undefined,
|
|
3062
|
+
source: entityKey,
|
|
3063
|
+
},
|
|
3064
|
+
data: {},
|
|
3065
|
+
options: {},
|
|
3066
|
+
},
|
|
3067
|
+
};
|
|
3068
|
+
}
|
|
3069
|
+
/**
|
|
3070
|
+
* Creates a search command for "Create {Entity}" with consistent i18n pattern.
|
|
3071
|
+
* Use titleKey/descriptionKey from module (e.g. @module:entity.permissions.create.title).
|
|
3072
|
+
* Standard: "Create {Entity}" for title, "Create new {entities}" for description.
|
|
3073
|
+
*/
|
|
3074
|
+
function createEntitySearchCommand(config) {
|
|
3075
|
+
return {
|
|
3076
|
+
id: config.id,
|
|
3077
|
+
group: 'command',
|
|
3078
|
+
title: config.titleKey,
|
|
3079
|
+
description: config.descriptionKey ?? config.titleKey,
|
|
3080
|
+
icon: config.icon,
|
|
3081
|
+
command: {
|
|
3082
|
+
name: 'Entity:Create',
|
|
3083
|
+
options: createEntityCommandOptions(config.entityKey, config.entityInfo),
|
|
3084
|
+
},
|
|
3085
|
+
};
|
|
3086
|
+
}
|
|
3087
|
+
|
|
3088
|
+
var AXPCommonSettings;
|
|
3089
|
+
(function (AXPCommonSettings) {
|
|
3090
|
+
AXPCommonSettings["EnableOperationToasts"] = "Common:Setting:Notifications.EnableOperationToasts";
|
|
3091
|
+
AXPCommonSettings["EntityFilterApplyMode"] = "Common:Setting:Entity.FilterApplyMode";
|
|
3092
|
+
AXPCommonSettings["ShowCategoryColumnsByDefault"] = "Common:Setting:Entity.ShowCategoryColumnsByDefault";
|
|
3093
|
+
AXPCommonSettings["ApplyLayoutOrdering"] = "Common:Setting:Entity.ApplyLayoutOrdering";
|
|
3094
|
+
AXPCommonSettings["RedirectToDetailsAfterCreate"] = "Common:Setting:Entity.RedirectToDetailsAfterCreate";
|
|
3095
|
+
AXPCommonSettings["ShowPageBadge"] = "Common:Setting:Entity.ShowPageBadge";
|
|
3096
|
+
AXPCommonSettings["DebugMode"] = "PlatformDevTools:Setting:Developer.DebugMode";
|
|
3097
|
+
})(AXPCommonSettings || (AXPCommonSettings = {}));
|
|
3098
|
+
|
|
3099
|
+
//TODO Loading, Redirect, Drawer, Show toast
|
|
3100
|
+
const AXPRedirectEvent = createWorkFlowEvent('Redirect Event Fired');
|
|
3101
|
+
const AXPRefreshEvent = createWorkFlowEvent('Refresh Event Fired');
|
|
3102
|
+
const AXPReloadEvent = createWorkFlowEvent('Reload Event Fired');
|
|
3103
|
+
class AXPWorkflowNavigateAction extends AXPWorkflowAction {
|
|
3104
|
+
constructor() {
|
|
3105
|
+
super(...arguments);
|
|
3106
|
+
this.router = inject(Router);
|
|
3107
|
+
}
|
|
3108
|
+
async execute(context) {
|
|
3109
|
+
const payload = context.getVariable('payload');
|
|
3110
|
+
if (Array.isArray(payload.commands)) {
|
|
3111
|
+
this.router.navigate(payload.commands, payload.extras);
|
|
3112
|
+
}
|
|
3113
|
+
else {
|
|
3114
|
+
if (payload.commands.toLowerCase().startsWith('http') || payload.target == 'blank') {
|
|
3115
|
+
window.open(payload.commands, '_blank');
|
|
3116
|
+
}
|
|
3117
|
+
else
|
|
3118
|
+
this.router.navigate([payload.commands], payload.extras);
|
|
3119
|
+
}
|
|
3120
|
+
}
|
|
3121
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowNavigateAction, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
3122
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowNavigateAction }); }
|
|
3123
|
+
}
|
|
3124
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowNavigateAction, decorators: [{
|
|
3125
|
+
type: Injectable
|
|
3126
|
+
}] });
|
|
3127
|
+
class AXPDialogConfirmAction extends AXPWorkflowAction {
|
|
3128
|
+
constructor() {
|
|
3129
|
+
super(...arguments);
|
|
3130
|
+
this.dialogService = inject(AXDialogService);
|
|
3131
|
+
this.translationService = inject(AXTranslationService);
|
|
3132
|
+
this.defaultAction = 'cancel';
|
|
3133
|
+
}
|
|
3134
|
+
async execute(context) {
|
|
3135
|
+
console.log('AXPDialogConfirmAction', this.title, this.message, this.type);
|
|
3136
|
+
context.setOutput('result', false);
|
|
3137
|
+
const dialogResult = await this.dialogService.confirm(await this.translationService.translateAsync(this.title), await this.translationService.translateAsync(this.message), (await this.translationService.translateAsync(this.type)), 'horizontal', false, 'cancel');
|
|
3138
|
+
context.setOutput('result', dialogResult.result);
|
|
3139
|
+
}
|
|
3140
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPDialogConfirmAction, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
3141
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPDialogConfirmAction }); }
|
|
3142
|
+
}
|
|
3143
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPDialogConfirmAction, decorators: [{
|
|
3144
|
+
type: Injectable
|
|
3145
|
+
}] });
|
|
3146
|
+
class AXPToastAction extends AXPWorkflowAction {
|
|
3147
|
+
constructor() {
|
|
3148
|
+
super(...arguments);
|
|
3149
|
+
this.toastService = inject(AXToastService);
|
|
3150
|
+
this.translationService = inject(AXTranslationService);
|
|
3151
|
+
this.settingsService = inject(AXPSettingsService);
|
|
3152
|
+
}
|
|
3153
|
+
async execute(context) {
|
|
3154
|
+
const showResult = await this.settingsService.get(AXPCommonSettings.EnableOperationToasts);
|
|
3155
|
+
if (showResult) {
|
|
3156
|
+
this.toastService.show({
|
|
3157
|
+
color: this.color,
|
|
3158
|
+
title: await this.translationService.translateAsync(this.title),
|
|
3159
|
+
content: await this.translationService.translateAsync(this.content),
|
|
3160
|
+
closeButton: true,
|
|
3161
|
+
timeOut: 3000,
|
|
3162
|
+
timeOutProgress: true,
|
|
3163
|
+
});
|
|
3164
|
+
}
|
|
3165
|
+
}
|
|
3166
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPToastAction, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
3167
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPToastAction }); }
|
|
3168
|
+
}
|
|
3169
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPToastAction, decorators: [{
|
|
3170
|
+
type: Injectable
|
|
3171
|
+
}] });
|
|
3172
|
+
class AXPReloadAction extends AXPWorkflowAction {
|
|
3173
|
+
async execute(context) {
|
|
3174
|
+
this.dispatch(AXPReloadEvent({ entity: context.getVariable('entity'), meta: context.getVariable('meta') }));
|
|
3175
|
+
}
|
|
3176
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPReloadAction, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
3177
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPReloadAction }); }
|
|
3178
|
+
}
|
|
3179
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPReloadAction, decorators: [{
|
|
3180
|
+
type: Injectable
|
|
3181
|
+
}] });
|
|
3182
|
+
|
|
3183
|
+
class AXPWorkflowRouterNavigateAction extends AXPWorkflowAction {
|
|
3184
|
+
constructor() {
|
|
3185
|
+
super(...arguments);
|
|
3186
|
+
this.router = inject(Router);
|
|
3187
|
+
}
|
|
3188
|
+
async execute(context) {
|
|
3189
|
+
const command = context.getVariable();
|
|
3190
|
+
const path = command.options.path;
|
|
3191
|
+
if (path?.toLowerCase()?.startsWith('http') || command.options?.extras?.target === 'blank') {
|
|
3192
|
+
window.open(path, '_blank');
|
|
3193
|
+
}
|
|
3194
|
+
else {
|
|
3195
|
+
const [basePathAndFragment, queryString] = path.split('?');
|
|
3196
|
+
const [basePath, fragment] = basePathAndFragment.split('#'); // Extract fragment if present
|
|
3197
|
+
const queryParams = this.parseQueryParams(queryString);
|
|
3198
|
+
this.router.navigate([basePath], {
|
|
3199
|
+
...command.options.extras,
|
|
3200
|
+
queryParams, // Pass query parameters separately
|
|
3201
|
+
fragment, // Pass fragment explicitly
|
|
3202
|
+
});
|
|
3203
|
+
}
|
|
3204
|
+
}
|
|
3205
|
+
/**
|
|
3206
|
+
* Parses query parameters from a query string into an object.
|
|
3207
|
+
*/
|
|
3208
|
+
parseQueryParams(queryString) {
|
|
3209
|
+
if (!queryString)
|
|
3210
|
+
return undefined;
|
|
3211
|
+
return queryString
|
|
3212
|
+
.split('&')
|
|
3213
|
+
.map((pair) => pair.split('='))
|
|
3214
|
+
.reduce((params, [key, value]) => {
|
|
3215
|
+
params[decodeURIComponent(key)] = decodeURIComponent(value);
|
|
3216
|
+
return params;
|
|
3217
|
+
}, {});
|
|
3218
|
+
}
|
|
3219
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowRouterNavigateAction, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
3220
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowRouterNavigateAction }); }
|
|
3221
|
+
}
|
|
3222
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPWorkflowRouterNavigateAction, decorators: [{
|
|
3223
|
+
type: Injectable
|
|
3224
|
+
}] });
|
|
3225
|
+
const AXPNavigateWorkflow = {
|
|
3226
|
+
startStepId: 'start',
|
|
3227
|
+
steps: {
|
|
3228
|
+
start: {
|
|
3229
|
+
action: 'navigate-router',
|
|
3230
|
+
},
|
|
3231
|
+
},
|
|
3232
|
+
};
|
|
3233
|
+
|
|
3234
|
+
class AXMWorkflowErrorHandler {
|
|
3235
|
+
constructor() {
|
|
3236
|
+
this.dialog = inject(AXDialogService);
|
|
3237
|
+
}
|
|
3238
|
+
handleError(error, next) {
|
|
3239
|
+
if (error instanceof AXPWorkflowError) {
|
|
3240
|
+
this.dialog.alert("Somthing is wrong!", error.inner ? error.inner.message : error.message, 'danger');
|
|
3241
|
+
next(error);
|
|
3242
|
+
}
|
|
3243
|
+
else {
|
|
3244
|
+
next(error);
|
|
3245
|
+
}
|
|
3246
|
+
}
|
|
3247
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXMWorkflowErrorHandler, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
3248
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXMWorkflowErrorHandler }); }
|
|
3249
|
+
}
|
|
3250
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXMWorkflowErrorHandler, decorators: [{
|
|
3251
|
+
type: Injectable
|
|
3252
|
+
}] });
|
|
3253
|
+
|
|
3254
|
+
class AXPCommonModule {
|
|
3255
|
+
static forRoot(configs) {
|
|
3256
|
+
return {
|
|
3257
|
+
ngModule: AXPCommonModule,
|
|
3258
|
+
providers: [
|
|
3259
|
+
...(configs?.errorHandlers || []),
|
|
3260
|
+
{
|
|
3261
|
+
provide: 'AXPCommonModuleFactory',
|
|
3262
|
+
useFactory: (registry) => () => {
|
|
3263
|
+
registry.register(...(configs?.errorHandlers || []));
|
|
3264
|
+
},
|
|
3265
|
+
deps: [AXPErrorHandlerRegistryService],
|
|
3266
|
+
multi: true,
|
|
3267
|
+
},
|
|
3268
|
+
],
|
|
3269
|
+
};
|
|
3270
|
+
}
|
|
3271
|
+
static forChild(configs) {
|
|
3272
|
+
return {
|
|
3273
|
+
ngModule: AXPCommonModule,
|
|
3274
|
+
providers: [
|
|
3275
|
+
...(configs?.errorHandlers || []),
|
|
3276
|
+
{
|
|
3277
|
+
provide: 'AXPCommonModuleFactory',
|
|
3278
|
+
useFactory: (registry) => () => {
|
|
3279
|
+
registry.register(...(configs?.errorHandlers || []));
|
|
3280
|
+
},
|
|
3281
|
+
deps: [AXPErrorHandlerRegistryService],
|
|
3282
|
+
multi: true,
|
|
3283
|
+
},
|
|
3284
|
+
],
|
|
3285
|
+
};
|
|
3286
|
+
}
|
|
3287
|
+
/**
|
|
3288
|
+
* @ignore
|
|
3289
|
+
*/
|
|
3290
|
+
constructor(instances) {
|
|
3291
|
+
instances.forEach((f) => {
|
|
3292
|
+
f();
|
|
3293
|
+
});
|
|
3294
|
+
}
|
|
3295
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPCommonModule, deps: [{ token: 'AXPCommonModuleFactory' }], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
3296
|
+
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.9", ngImport: i0, type: AXPCommonModule, imports: [i1.AXPWorkflowModule, AXPopupModule,
|
|
3297
|
+
AXDateTimeModule,
|
|
3298
|
+
AXToastModule,
|
|
3299
|
+
AXPModuleManifestModule], exports: [RouterModule] }); }
|
|
3300
|
+
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPCommonModule, providers: [
|
|
3301
|
+
AXPAppStartUpProvider,
|
|
3302
|
+
{
|
|
3303
|
+
provide: ErrorHandler,
|
|
3304
|
+
useClass: AXPGlobalErrorHandler,
|
|
3305
|
+
},
|
|
3306
|
+
{
|
|
3307
|
+
provide: AXPCustomOperatorService,
|
|
3308
|
+
useClass: AXPCustomOperatorServiceImpl,
|
|
3309
|
+
},
|
|
3310
|
+
{
|
|
3311
|
+
provide: AXPFilterOperatorMiddlewareService,
|
|
3312
|
+
useClass: AXPFilterOperatorMiddlewareServiceImpl,
|
|
3313
|
+
},
|
|
3314
|
+
{
|
|
3315
|
+
provide: AXP_SEARCH_PROVIDER,
|
|
3316
|
+
useClass: AXPMenuSearchProvider,
|
|
3317
|
+
multi: true,
|
|
3318
|
+
},
|
|
3319
|
+
{
|
|
3320
|
+
provide: AXP_SEARCH_DEFINITION_PROVIDER,
|
|
3321
|
+
useClass: AXPMenuSearchDefinitionProvider,
|
|
3322
|
+
multi: true,
|
|
3323
|
+
},
|
|
3324
|
+
{
|
|
3325
|
+
provide: AXP_EXPRESSION_EVALUATOR_SCOPE_PROVIDER,
|
|
3326
|
+
useClass: AXPTokenEvaluatorScopeProvider,
|
|
3327
|
+
multi: true,
|
|
3328
|
+
},
|
|
3329
|
+
{
|
|
3330
|
+
provide: AXP_EXPRESSION_EVALUATOR_SCOPE_PROVIDER,
|
|
3331
|
+
useClass: AXPSettingsEvaluatorScopeProvider,
|
|
3332
|
+
multi: true,
|
|
3333
|
+
},
|
|
3334
|
+
{
|
|
3335
|
+
provide: AXP_SETTING_DEFINITION_PROVIDER,
|
|
3336
|
+
useFactory: async () => {
|
|
3337
|
+
const injector = inject(Injector);
|
|
3338
|
+
const provider = (await import('./acorex-platform-common-common-settings.provider-G9XcXXOG.mjs')).AXPCommonSettingProvider;
|
|
3339
|
+
return new provider(injector);
|
|
3340
|
+
},
|
|
3341
|
+
multi: true,
|
|
3342
|
+
},
|
|
3343
|
+
], imports: [AXPWorkflowModule.forChild({
|
|
3344
|
+
actions: {
|
|
3345
|
+
'navigate-router': AXPWorkflowRouterNavigateAction,
|
|
3346
|
+
'show-prompt-dialog': AXPDialogConfirmAction,
|
|
3347
|
+
'show-toast': AXPToastAction,
|
|
3348
|
+
reload: AXPReloadAction,
|
|
3349
|
+
},
|
|
3350
|
+
workflows: {
|
|
3351
|
+
navigate: AXPNavigateWorkflow,
|
|
3352
|
+
},
|
|
3353
|
+
}),
|
|
3354
|
+
AXPopupModule,
|
|
3355
|
+
AXDateTimeModule,
|
|
3356
|
+
AXToastModule,
|
|
3357
|
+
AXPModuleManifestModule, RouterModule] }); }
|
|
3358
|
+
}
|
|
3359
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPCommonModule, decorators: [{
|
|
3360
|
+
type: NgModule,
|
|
3361
|
+
args: [{
|
|
3362
|
+
imports: [
|
|
3363
|
+
AXPWorkflowModule.forChild({
|
|
3364
|
+
actions: {
|
|
3365
|
+
'navigate-router': AXPWorkflowRouterNavigateAction,
|
|
3366
|
+
'show-prompt-dialog': AXPDialogConfirmAction,
|
|
3367
|
+
'show-toast': AXPToastAction,
|
|
3368
|
+
reload: AXPReloadAction,
|
|
3369
|
+
},
|
|
3370
|
+
workflows: {
|
|
3371
|
+
navigate: AXPNavigateWorkflow,
|
|
3372
|
+
},
|
|
3373
|
+
}),
|
|
3374
|
+
AXPopupModule,
|
|
3375
|
+
AXDateTimeModule,
|
|
3376
|
+
AXToastModule,
|
|
3377
|
+
AXPModuleManifestModule,
|
|
3378
|
+
],
|
|
3379
|
+
exports: [RouterModule],
|
|
3380
|
+
providers: [
|
|
3381
|
+
AXPAppStartUpProvider,
|
|
3382
|
+
{
|
|
3383
|
+
provide: ErrorHandler,
|
|
3384
|
+
useClass: AXPGlobalErrorHandler,
|
|
3385
|
+
},
|
|
3386
|
+
{
|
|
3387
|
+
provide: AXPCustomOperatorService,
|
|
3388
|
+
useClass: AXPCustomOperatorServiceImpl,
|
|
3389
|
+
},
|
|
3390
|
+
{
|
|
3391
|
+
provide: AXPFilterOperatorMiddlewareService,
|
|
3392
|
+
useClass: AXPFilterOperatorMiddlewareServiceImpl,
|
|
3393
|
+
},
|
|
3394
|
+
{
|
|
3395
|
+
provide: AXP_SEARCH_PROVIDER,
|
|
3396
|
+
useClass: AXPMenuSearchProvider,
|
|
3397
|
+
multi: true,
|
|
3398
|
+
},
|
|
3399
|
+
{
|
|
3400
|
+
provide: AXP_SEARCH_DEFINITION_PROVIDER,
|
|
3401
|
+
useClass: AXPMenuSearchDefinitionProvider,
|
|
3402
|
+
multi: true,
|
|
3403
|
+
},
|
|
3404
|
+
{
|
|
3405
|
+
provide: AXP_EXPRESSION_EVALUATOR_SCOPE_PROVIDER,
|
|
3406
|
+
useClass: AXPTokenEvaluatorScopeProvider,
|
|
3407
|
+
multi: true,
|
|
3408
|
+
},
|
|
3409
|
+
{
|
|
3410
|
+
provide: AXP_EXPRESSION_EVALUATOR_SCOPE_PROVIDER,
|
|
3411
|
+
useClass: AXPSettingsEvaluatorScopeProvider,
|
|
3412
|
+
multi: true,
|
|
3413
|
+
},
|
|
3414
|
+
{
|
|
3415
|
+
provide: AXP_SETTING_DEFINITION_PROVIDER,
|
|
3416
|
+
useFactory: async () => {
|
|
3417
|
+
const injector = inject(Injector);
|
|
3418
|
+
const provider = (await import('./acorex-platform-common-common-settings.provider-G9XcXXOG.mjs')).AXPCommonSettingProvider;
|
|
3419
|
+
return new provider(injector);
|
|
3420
|
+
},
|
|
3421
|
+
multi: true,
|
|
3422
|
+
},
|
|
3423
|
+
],
|
|
3424
|
+
}]
|
|
3425
|
+
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
3426
|
+
type: Inject,
|
|
3427
|
+
args: ['AXPCommonModuleFactory']
|
|
3428
|
+
}] }] });
|
|
3429
|
+
|
|
3430
|
+
const ENVIRONMENT = new InjectionToken('ENVIRONMENT');
|
|
3431
|
+
const AXP_PLATFORM_CONFIG_TOKEN = new InjectionToken('AXP_PLATFORM_CONFIG_TOKEN', {
|
|
3432
|
+
providedIn: 'root',
|
|
3433
|
+
factory: () => {
|
|
3434
|
+
return AXPPlatformDefaultConfigs;
|
|
3435
|
+
},
|
|
3436
|
+
});
|
|
3437
|
+
const AXPPlatformDefaultConfigs = {
|
|
3438
|
+
copyright: 'ACoreX @ 2024',
|
|
3439
|
+
title: 'ACoreX Platform',
|
|
3440
|
+
network: {
|
|
3441
|
+
timeOut: 5000,
|
|
3442
|
+
},
|
|
3443
|
+
};
|
|
3444
|
+
function configPlatform(config = AXPPlatformDefaultConfigs) {
|
|
3445
|
+
return merge(AXPPlatformDefaultConfigs, config);
|
|
3446
|
+
}
|
|
3447
|
+
|
|
3448
|
+
const AXP_ROOT_CONFIG_TOKEN = new InjectionToken('AXP_ROOT_CONFIG_TOKEN');
|
|
3449
|
+
|
|
3450
|
+
class AXPFileStorageService {
|
|
3451
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFileStorageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
3452
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFileStorageService }); }
|
|
3453
|
+
}
|
|
3454
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFileStorageService, decorators: [{
|
|
3455
|
+
type: Injectable
|
|
3456
|
+
}] });
|
|
3457
|
+
|
|
3458
|
+
var AXPFileStorageStatus;
|
|
3459
|
+
(function (AXPFileStorageStatus) {
|
|
3460
|
+
AXPFileStorageStatus["Temporary"] = "temporary";
|
|
3461
|
+
AXPFileStorageStatus["Committed"] = "committed";
|
|
3462
|
+
AXPFileStorageStatus["PendingDeletion"] = "pendingDeletion";
|
|
3463
|
+
AXPFileStorageStatus["Archived"] = "archived";
|
|
3464
|
+
AXPFileStorageStatus["Error"] = "error";
|
|
3465
|
+
})(AXPFileStorageStatus || (AXPFileStorageStatus = {}));
|
|
3466
|
+
|
|
3467
|
+
//#endregion
|
|
3468
|
+
//#region ---- Injection Token ----
|
|
3469
|
+
/**
|
|
3470
|
+
* Multi-provider injection token for file action providers.
|
|
3471
|
+
* Modules can provide multiple implementations using this token.
|
|
3472
|
+
*/
|
|
3473
|
+
const AXP_FILE_ACTION_PROVIDER = new InjectionToken('AXP_FILE_ACTION_PROVIDER');
|
|
3474
|
+
//#endregion
|
|
3475
|
+
|
|
3476
|
+
//#region ---- Service ----
|
|
3477
|
+
/**
|
|
3478
|
+
* Service for managing file uploader actions.
|
|
3479
|
+
* Aggregates actions from multiple providers and filters them based on plugin configuration.
|
|
3480
|
+
* Wraps the hook system for backward compatibility.
|
|
3481
|
+
*/
|
|
3482
|
+
class AXPFileActionsService {
|
|
3483
|
+
constructor() {
|
|
3484
|
+
//#region ---- Dependencies ----
|
|
3485
|
+
this.actionProviders = inject(AXP_FILE_ACTION_PROVIDER, { optional: true }) || [];
|
|
3486
|
+
this.hookService = inject(AXPHookService);
|
|
3487
|
+
this.injector = inject(Injector);
|
|
3488
|
+
}
|
|
3489
|
+
//#endregion
|
|
3490
|
+
//#region ---- Public API ----
|
|
3491
|
+
/**
|
|
3492
|
+
* Get all actions from providers, filtered based on plugin configuration.
|
|
3493
|
+
* Wraps the hook system for backward compatibility.
|
|
3494
|
+
* @param payload Initial payload with capabilities and plugin configuration.
|
|
3495
|
+
* @returns Filtered list of actions.
|
|
3496
|
+
*/
|
|
3497
|
+
async getActions(payload) {
|
|
3498
|
+
// First, run hook system for backward compatibility
|
|
3499
|
+
// Note: Hook system may still use old format with 'host', but we pass capabilities
|
|
3500
|
+
// Hook providers that need host will need to be migrated to use capabilities
|
|
3501
|
+
let hookPayload;
|
|
3502
|
+
try {
|
|
3503
|
+
hookPayload = await this.hookService.runAsync('file-uploader.actions', {
|
|
3504
|
+
...payload,
|
|
3505
|
+
// For backward compatibility, hooks might expect 'host' but we're using capabilities
|
|
3506
|
+
// Old hook providers will need to be migrated
|
|
3507
|
+
});
|
|
3508
|
+
}
|
|
3509
|
+
catch (err) {
|
|
3510
|
+
// If hook fails, continue with new system
|
|
3511
|
+
console.warn('[AXPFileActionsService] Hook system failed, continuing with providers only', err);
|
|
3512
|
+
}
|
|
3513
|
+
// Merge hook actions into payload (if any)
|
|
3514
|
+
const mergedPayload = {
|
|
3515
|
+
...payload,
|
|
3516
|
+
actions: hookPayload?.actions ?? payload.actions ?? [],
|
|
3517
|
+
};
|
|
3518
|
+
// Resolve all providers (handle both direct and Promise<provider>)
|
|
3519
|
+
const providers = await this.resolveProviders();
|
|
3520
|
+
// Filter providers by key
|
|
3521
|
+
const matchingProviders = providers
|
|
3522
|
+
.filter((p) => p.key === 'file-uploader.actions')
|
|
3523
|
+
.sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0));
|
|
3524
|
+
// Execute providers sequentially (waterfall pattern)
|
|
3525
|
+
let currentPayload = { ...mergedPayload, actions: [...mergedPayload.actions] };
|
|
3526
|
+
for (const provider of matchingProviders) {
|
|
3527
|
+
try {
|
|
3528
|
+
currentPayload = await Promise.resolve(runInInjectionContext(this.injector, () => provider.execute(currentPayload)));
|
|
3529
|
+
}
|
|
3530
|
+
catch (err) {
|
|
3531
|
+
console.error(`[AXPFileActionsService] Provider '${provider.key}' failed`, err);
|
|
3532
|
+
}
|
|
3533
|
+
}
|
|
3534
|
+
// Filter actions based on plugin configuration
|
|
3535
|
+
return this.filterActions(currentPayload.actions, payload.plugins, payload.excludePlugins);
|
|
3536
|
+
}
|
|
3537
|
+
//#endregion
|
|
3538
|
+
//#region ---- Private Methods ----
|
|
3539
|
+
/**
|
|
3540
|
+
* Resolve all providers (handle both direct providers and Promise<provider>).
|
|
3541
|
+
*/
|
|
3542
|
+
async resolveProviders() {
|
|
3543
|
+
return Promise.all(this.actionProviders.map((p) => (p instanceof Promise ? p : Promise.resolve(p))));
|
|
3544
|
+
}
|
|
3545
|
+
/**
|
|
3546
|
+
* Filter actions based on enabled plugins and exclude list.
|
|
3547
|
+
*/
|
|
3548
|
+
filterActions(actions, enabledPlugins, excludePlugins) {
|
|
3549
|
+
const enabledPluginNames = enabledPlugins?.map((p) => p.name) ?? [];
|
|
3550
|
+
const excludedPluginNames = excludePlugins ?? [];
|
|
3551
|
+
return actions.filter((action) => {
|
|
3552
|
+
// Exclude if in exclude list
|
|
3553
|
+
if (excludedPluginNames.includes(action.plugin)) {
|
|
3554
|
+
return false;
|
|
3555
|
+
}
|
|
3556
|
+
// Include if global
|
|
3557
|
+
if (action.global === true) {
|
|
3558
|
+
return true;
|
|
3559
|
+
}
|
|
3560
|
+
// Include if plugin is explicitly enabled
|
|
3561
|
+
if (enabledPluginNames.length > 0 && enabledPluginNames.includes(action.plugin)) {
|
|
3562
|
+
return true;
|
|
3563
|
+
}
|
|
3564
|
+
// If no plugins are enabled, only show global actions
|
|
3565
|
+
if (enabledPluginNames.length === 0) {
|
|
3566
|
+
return !!action.global;
|
|
3567
|
+
}
|
|
3568
|
+
return false;
|
|
3569
|
+
});
|
|
3570
|
+
}
|
|
3571
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFileActionsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
3572
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFileActionsService, providedIn: 'root' }); }
|
|
3573
|
+
}
|
|
3574
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFileActionsService, decorators: [{
|
|
3575
|
+
type: Injectable,
|
|
3576
|
+
args: [{
|
|
3577
|
+
providedIn: 'root',
|
|
3578
|
+
}]
|
|
3579
|
+
}] });
|
|
3580
|
+
|
|
3581
|
+
//#endregion
|
|
3582
|
+
|
|
3583
|
+
const UploadFromComputerActionProvider = {
|
|
3584
|
+
key: 'file-uploader.actions',
|
|
3585
|
+
priority: 1, // Higher priority than upload-from-drive to show first
|
|
3586
|
+
execute: (payload) => {
|
|
3587
|
+
const fileService = inject(AXFileService);
|
|
3588
|
+
// Capture widget options for use in the action
|
|
3589
|
+
const widgetOptions = payload.options;
|
|
3590
|
+
payload.actions = [
|
|
3591
|
+
...payload.actions,
|
|
3592
|
+
{
|
|
3593
|
+
plugin: 'upload-from-computer',
|
|
3594
|
+
global: true,
|
|
3595
|
+
textKey: '@document-management:actions.upload-from-device',
|
|
3596
|
+
icon: 'fa-light fa-file-arrow-up',
|
|
3597
|
+
run: async (capabilities) => {
|
|
3598
|
+
// Open file picker with widget options
|
|
3599
|
+
const files = await fileService.choose({
|
|
3600
|
+
multiple: widgetOptions?.multiple ?? true,
|
|
3601
|
+
accept: widgetOptions?.accept,
|
|
3602
|
+
});
|
|
3603
|
+
// If no files are selected, return
|
|
3604
|
+
if (files.length === 0) {
|
|
3605
|
+
return;
|
|
3606
|
+
}
|
|
3607
|
+
// Create file list items from selected files
|
|
3608
|
+
const fileItems = files.map((file) => ({
|
|
3609
|
+
id: AXPDataGenerator.uuid(),
|
|
3610
|
+
name: file.name,
|
|
3611
|
+
size: file.size,
|
|
3612
|
+
status: 'attached',
|
|
3613
|
+
source: {
|
|
3614
|
+
kind: 'blob',
|
|
3615
|
+
value: file,
|
|
3616
|
+
},
|
|
3617
|
+
}));
|
|
3618
|
+
// Add files using capabilities
|
|
3619
|
+
capabilities.addFiles(fileItems);
|
|
3620
|
+
},
|
|
3621
|
+
},
|
|
3622
|
+
];
|
|
3623
|
+
return payload;
|
|
3624
|
+
},
|
|
3625
|
+
};
|
|
3626
|
+
|
|
3627
|
+
const AXP_FILE_TYPE_INFO_PROVIDER = new InjectionToken('AXP_FILE_TYPE_INFO_PROVIDER');
|
|
3628
|
+
class AXPFileTypeProviderService {
|
|
3629
|
+
constructor() {
|
|
3630
|
+
this.providers = inject(AXP_FILE_TYPE_INFO_PROVIDER, { optional: true });
|
|
3631
|
+
}
|
|
3632
|
+
async items() {
|
|
3633
|
+
const items = [];
|
|
3634
|
+
if (Array.isArray(this.providers)) {
|
|
3635
|
+
for (const provider of this.providers) {
|
|
3636
|
+
items.push(...(await provider.items()));
|
|
3637
|
+
}
|
|
3638
|
+
}
|
|
3639
|
+
return items;
|
|
3640
|
+
}
|
|
3641
|
+
async get(name) {
|
|
3642
|
+
return (await this.items()).find((c) => c.name == name);
|
|
3643
|
+
}
|
|
3644
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFileTypeProviderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
3645
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFileTypeProviderService, providedIn: 'root' }); }
|
|
3646
|
+
}
|
|
3647
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPFileTypeProviderService, decorators: [{
|
|
3648
|
+
type: Injectable,
|
|
3649
|
+
args: [{ providedIn: 'root' }]
|
|
3650
|
+
}] });
|
|
3651
|
+
|
|
3652
|
+
class AXPLockService {
|
|
3653
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLockService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
3654
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLockService }); }
|
|
3655
|
+
}
|
|
3656
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLockService, decorators: [{
|
|
3657
|
+
type: Injectable
|
|
3658
|
+
}] });
|
|
3659
|
+
|
|
3660
|
+
/**
|
|
3661
|
+
* Status Definition Types
|
|
3662
|
+
* Defines the structure for status definitions and their transitions
|
|
3663
|
+
*/
|
|
3664
|
+
/**
|
|
3665
|
+
* Abstract class for status providers
|
|
3666
|
+
* Similar to AXPTaskBadgeProvider pattern
|
|
3667
|
+
*/
|
|
3668
|
+
class AXPStatusProvider {
|
|
3669
|
+
}
|
|
3670
|
+
//#endregion
|
|
3671
|
+
|
|
3672
|
+
//#region ---- Injection Token ----
|
|
3673
|
+
/**
|
|
3674
|
+
* Injection token for status providers
|
|
3675
|
+
* Use with multi: true to register multiple providers
|
|
3676
|
+
*/
|
|
3677
|
+
const AXP_STATUS_PROVIDERS = new InjectionToken('AXP_STATUS_PROVIDERS');
|
|
3678
|
+
//#endregion
|
|
3679
|
+
//#region ---- Provider Service ----
|
|
3680
|
+
/**
|
|
3681
|
+
* Service for managing status definitions from multiple providers
|
|
3682
|
+
* Similar to AXPTaskBadgeService pattern - no cache, direct access
|
|
3683
|
+
*
|
|
3684
|
+
* @example
|
|
3685
|
+
* ```typescript
|
|
3686
|
+
* // In a module providers array:
|
|
3687
|
+
* {
|
|
3688
|
+
* provide: AXP_STATUS_PROVIDERS,
|
|
3689
|
+
* useClass: TaskStatusProvider,
|
|
3690
|
+
* multi: true,
|
|
3691
|
+
* }
|
|
3692
|
+
*
|
|
3693
|
+
* // Example provider implementation:
|
|
3694
|
+
* export class TaskStatusProvider extends AXPStatusProvider {
|
|
3695
|
+
* readonly key = 'task-management.status';
|
|
3696
|
+
* readonly statuses = [
|
|
3697
|
+
* { name: 'todo', title: 'To Do', color: '#6b7280', icon: 'fa-light fa-circle', isInitial: true },
|
|
3698
|
+
* { name: 'in-progress', title: 'In Progress', color: '#3b82f6', icon: 'fa-light fa-play' },
|
|
3699
|
+
* { name: 'done', title: 'Done', color: '#10b981', icon: 'fa-light fa-check-circle', isFinal: true },
|
|
3700
|
+
* ];
|
|
3701
|
+
* readonly transitions = [
|
|
3702
|
+
* { from: 'todo', to: 'in-progress', title: 'Start', condition: '{{user.canEdit}}' },
|
|
3703
|
+
* { from: 'in-progress', to: 'done', title: 'Complete' },
|
|
3704
|
+
* ];
|
|
3705
|
+
* readonly defaultStatus = 'todo';
|
|
3706
|
+
* }
|
|
3707
|
+
*
|
|
3708
|
+
* // Later, get the status:
|
|
3709
|
+
* const status = await statusService.getStatus('task-management.status');
|
|
3710
|
+
* if (status) {
|
|
3711
|
+
* console.log('Statuses:', status.statuses);
|
|
3712
|
+
* console.log('Transitions:', status.transitions);
|
|
3713
|
+
* }
|
|
3714
|
+
* ```
|
|
3715
|
+
*/
|
|
3716
|
+
class AXPStatusDefinitionProviderService {
|
|
3717
|
+
constructor() {
|
|
3718
|
+
//#region ---- Dependencies ----
|
|
3719
|
+
this.providers = inject(AXP_STATUS_PROVIDERS, { optional: true }) ?? [];
|
|
3720
|
+
this.commandService = inject(AXPCommandService);
|
|
3721
|
+
//#endregion
|
|
3722
|
+
//#region ---- State ----
|
|
3723
|
+
this.statusMap = new Map();
|
|
3724
|
+
this.initializationPromise = null;
|
|
3725
|
+
this.isInitialized = false;
|
|
3726
|
+
}
|
|
3727
|
+
//#endregion
|
|
3728
|
+
//#region ---- Initialization ----
|
|
3729
|
+
/**
|
|
3730
|
+
* Initialize providers (resolve promises if needed)
|
|
3731
|
+
* Called lazily on first access
|
|
3732
|
+
*/
|
|
3733
|
+
async initialize() {
|
|
3734
|
+
if (this.isInitialized) {
|
|
3735
|
+
return;
|
|
3736
|
+
}
|
|
3737
|
+
if (this.initializationPromise) {
|
|
3738
|
+
return this.initializationPromise;
|
|
3739
|
+
}
|
|
3740
|
+
this.initializationPromise = this._doInitialize();
|
|
3741
|
+
await this.initializationPromise;
|
|
3742
|
+
}
|
|
3743
|
+
async _doInitialize() {
|
|
3744
|
+
// Register all providers, resolving promises if needed
|
|
3745
|
+
for (const provider of this.providers) {
|
|
3746
|
+
let resolvedProvider;
|
|
3747
|
+
if (provider instanceof Promise) {
|
|
3748
|
+
// If provider is a promise, resolve it
|
|
3749
|
+
resolvedProvider = await provider;
|
|
3750
|
+
}
|
|
3751
|
+
else {
|
|
3752
|
+
// If provider is a direct instance, use it directly
|
|
3753
|
+
resolvedProvider = provider;
|
|
3754
|
+
}
|
|
3755
|
+
if (this.statusMap.has(resolvedProvider.key)) {
|
|
3756
|
+
console.warn(`Status with key "${resolvedProvider.key}" already exists. Overwriting with new status.`);
|
|
3757
|
+
}
|
|
3758
|
+
this.statusMap.set(resolvedProvider.key, resolvedProvider);
|
|
3759
|
+
}
|
|
3760
|
+
this.isInitialized = true;
|
|
3761
|
+
}
|
|
3762
|
+
//#endregion
|
|
3763
|
+
//#region ---- Public API ----
|
|
3764
|
+
/**
|
|
3765
|
+
* Get a status provider by key
|
|
3766
|
+
* Ensures initialization is complete before accessing the status map.
|
|
3767
|
+
*
|
|
3768
|
+
* @param key - Status key (e.g., 'task-management.status')
|
|
3769
|
+
* @returns Promise that resolves to status provider with all statuses and transitions, or undefined if not found
|
|
3770
|
+
*
|
|
3771
|
+
* @example
|
|
3772
|
+
* ```typescript
|
|
3773
|
+
* const status = await statusService.getStatus('task-management.status');
|
|
3774
|
+
* if (status) {
|
|
3775
|
+
* console.log('Statuses:', status.statuses);
|
|
3776
|
+
* console.log('Transitions:', status.transitions);
|
|
3777
|
+
* }
|
|
3778
|
+
* ```
|
|
3779
|
+
*/
|
|
3780
|
+
async getStatus(key) {
|
|
3781
|
+
await this.initialize();
|
|
3782
|
+
return this.statusMap.get(key);
|
|
3783
|
+
}
|
|
3784
|
+
/**
|
|
3785
|
+
* Execute a status transition workflow action
|
|
3786
|
+
* Uses transition.command if provided, otherwise defaults to Entity:UpdateStatus
|
|
3787
|
+
* Validates the transition is allowed before executing
|
|
3788
|
+
*
|
|
3789
|
+
* @param definitionKey - Status definition key
|
|
3790
|
+
* @param transition - The transition to execute
|
|
3791
|
+
* @param context - Context data for the command (must include entity name and id/ids)
|
|
3792
|
+
* @returns Promise that resolves when the command execution completes
|
|
3793
|
+
*
|
|
3794
|
+
* @example
|
|
3795
|
+
* ```typescript
|
|
3796
|
+
* await statusService.executeTransition(
|
|
3797
|
+
* 'task-management.status',
|
|
3798
|
+
* { from: 'todo', to: 'in-progress' },
|
|
3799
|
+
* { entity: 'TaskManagement.Task', id: '123', ...entityData }
|
|
3800
|
+
* );
|
|
3801
|
+
* ```
|
|
3802
|
+
*/
|
|
3803
|
+
async executeTransition(definitionKey, transition, context) {
|
|
3804
|
+
await this.initialize();
|
|
3805
|
+
const provider = this.statusMap.get(definitionKey);
|
|
3806
|
+
if (!provider) {
|
|
3807
|
+
throw new Error(`Status provider with key "${definitionKey}" not found.`);
|
|
3808
|
+
}
|
|
3809
|
+
// Validate transition is allowed
|
|
3810
|
+
if (!this.isValidTransition(provider, transition.from, transition.to)) {
|
|
3811
|
+
throw new Error(`Invalid transition from "${transition.from}" to "${transition.to}"`);
|
|
3812
|
+
}
|
|
3813
|
+
// Determine which command to use
|
|
3814
|
+
let commandName;
|
|
3815
|
+
let commandOptions;
|
|
3816
|
+
if (transition.command) {
|
|
3817
|
+
// Use transition's explicit command
|
|
3818
|
+
commandName = transition.command.name;
|
|
3819
|
+
commandOptions = {
|
|
3820
|
+
...(transition.command.options ?? {}),
|
|
3821
|
+
...context,
|
|
3822
|
+
};
|
|
3823
|
+
}
|
|
3824
|
+
else {
|
|
3825
|
+
// Use default Entity:UpdateStatus command
|
|
3826
|
+
const entity = context['entityName'] ?? context['__meta__']?.config?.name;
|
|
3827
|
+
if (!entity) {
|
|
3828
|
+
throw new Error('Entity name is required in context when using default status update command');
|
|
3829
|
+
}
|
|
3830
|
+
const id = context['id'];
|
|
3831
|
+
const ids = context['ids'];
|
|
3832
|
+
if (!id && !ids) {
|
|
3833
|
+
throw new Error('Either id or ids must be provided in context');
|
|
3834
|
+
}
|
|
3835
|
+
commandName = 'Entity:UpdateStatus';
|
|
3836
|
+
commandOptions = {
|
|
3837
|
+
entity,
|
|
3838
|
+
status: transition.to,
|
|
3839
|
+
statusField: context['statusField'] ?? 'statusId',
|
|
3840
|
+
...(id ? { id } : {}),
|
|
3841
|
+
...(ids ? { ids } : {}),
|
|
3842
|
+
};
|
|
3843
|
+
}
|
|
3844
|
+
// Execute the command
|
|
3845
|
+
const result = await this.commandService.execute(commandName, commandOptions);
|
|
3846
|
+
if (!result?.success) {
|
|
3847
|
+
throw new Error(result?.message?.text || 'Failed to execute status transition command');
|
|
3848
|
+
}
|
|
3849
|
+
}
|
|
3850
|
+
/**
|
|
3851
|
+
* Validate if a transition is allowed
|
|
3852
|
+
* Checks if the transition exists in the provider's transitions list
|
|
3853
|
+
*/
|
|
3854
|
+
isValidTransition(provider, from, to) {
|
|
3855
|
+
return provider.transitions.some((transition) => transition.from === from && transition.to === to);
|
|
3856
|
+
}
|
|
3857
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPStatusDefinitionProviderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
3858
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPStatusDefinitionProviderService, providedIn: 'root' }); }
|
|
3859
|
+
}
|
|
3860
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPStatusDefinitionProviderService, decorators: [{
|
|
3861
|
+
type: Injectable,
|
|
3862
|
+
args: [{ providedIn: 'root' }]
|
|
3863
|
+
}] });
|
|
3864
|
+
|
|
3865
|
+
/**
|
|
3866
|
+
* Standard system status types
|
|
3867
|
+
* These are the predefined status values used across the platform
|
|
3868
|
+
*/
|
|
3869
|
+
var AXPSystemStatusType;
|
|
3870
|
+
(function (AXPSystemStatusType) {
|
|
3871
|
+
AXPSystemStatusType["Todo"] = "todo";
|
|
3872
|
+
AXPSystemStatusType["InProgress"] = "in-progress";
|
|
3873
|
+
AXPSystemStatusType["InReview"] = "in-review";
|
|
3874
|
+
AXPSystemStatusType["Open"] = "open";
|
|
3875
|
+
AXPSystemStatusType["Closed"] = "closed";
|
|
3876
|
+
AXPSystemStatusType["Blocked"] = "blocked";
|
|
3877
|
+
AXPSystemStatusType["Done"] = "done";
|
|
3878
|
+
AXPSystemStatusType["Cancelled"] = "cancelled";
|
|
3879
|
+
AXPSystemStatusType["Draft"] = "draft";
|
|
3880
|
+
AXPSystemStatusType["PendingReview"] = "pending-review";
|
|
3881
|
+
AXPSystemStatusType["Approved"] = "approved";
|
|
3882
|
+
AXPSystemStatusType["Published"] = "published";
|
|
3883
|
+
AXPSystemStatusType["Archived"] = "archived";
|
|
3884
|
+
AXPSystemStatusType["Rejected"] = "rejected";
|
|
3885
|
+
AXPSystemStatusType["Active"] = "active";
|
|
3886
|
+
AXPSystemStatusType["Inactive"] = "inactive";
|
|
3887
|
+
AXPSystemStatusType["Pending"] = "pending";
|
|
3888
|
+
AXPSystemStatusType["Completed"] = "completed";
|
|
3889
|
+
AXPSystemStatusType["Suspended"] = "suspended";
|
|
3890
|
+
AXPSystemStatusType["Failed"] = "failed";
|
|
3891
|
+
AXPSystemStatusType["Review"] = "review";
|
|
3892
|
+
AXPSystemStatusType["Expired"] = "expired";
|
|
3893
|
+
})(AXPSystemStatusType || (AXPSystemStatusType = {}));
|
|
3894
|
+
const i18n = (key) => `@general:statuses.${key}`;
|
|
3895
|
+
/**
|
|
3896
|
+
* Standard system status definitions
|
|
3897
|
+
* Provides consistent status definitions across the platform
|
|
3898
|
+
* Uses AXPStatusDefinition interface from status-definition.types.ts
|
|
3899
|
+
*/
|
|
3900
|
+
const AXPSystemStatuses = Object.freeze({
|
|
3901
|
+
Open: {
|
|
3902
|
+
name: AXPSystemStatusType.Open,
|
|
3903
|
+
title: i18n('open.title'),
|
|
3904
|
+
icon: 'fa-light fa-circle',
|
|
3905
|
+
color: 'secondary',
|
|
3906
|
+
description: i18n('open.description'),
|
|
3907
|
+
order: 1,
|
|
3908
|
+
isInitial: true,
|
|
3909
|
+
isFinal: false,
|
|
3910
|
+
},
|
|
3911
|
+
Closed: {
|
|
3912
|
+
name: AXPSystemStatusType.Closed,
|
|
3913
|
+
title: i18n('closed.title'),
|
|
3914
|
+
icon: 'fa-light fa-circle',
|
|
3915
|
+
color: 'danger',
|
|
3916
|
+
description: i18n('closed.description'),
|
|
3917
|
+
order: 2,
|
|
3918
|
+
isInitial: false,
|
|
3919
|
+
isFinal: true,
|
|
3920
|
+
},
|
|
3921
|
+
Todo: {
|
|
3922
|
+
name: AXPSystemStatusType.Todo,
|
|
3923
|
+
title: i18n('todo.title'),
|
|
3924
|
+
icon: 'fa-light fa-circle',
|
|
3925
|
+
color: 'secondary',
|
|
3926
|
+
description: i18n('todo.description'),
|
|
3927
|
+
order: 1,
|
|
3928
|
+
isInitial: true,
|
|
3929
|
+
isFinal: false,
|
|
3930
|
+
},
|
|
3931
|
+
InProgress: {
|
|
3932
|
+
name: AXPSystemStatusType.InProgress,
|
|
3933
|
+
title: i18n('in-progress.title'),
|
|
3934
|
+
icon: 'fa-light fa-play',
|
|
3935
|
+
color: 'info',
|
|
3936
|
+
description: i18n('in-progress.description'),
|
|
3937
|
+
order: 2,
|
|
3938
|
+
isInitial: false,
|
|
3939
|
+
isFinal: false,
|
|
3940
|
+
},
|
|
3941
|
+
InReview: {
|
|
3942
|
+
name: AXPSystemStatusType.InReview,
|
|
3943
|
+
title: i18n('in-review.title'),
|
|
3944
|
+
icon: 'fa-light fa-eye',
|
|
3945
|
+
color: 'info',
|
|
3946
|
+
description: i18n('in-review.description'),
|
|
3947
|
+
order: 3,
|
|
3948
|
+
isInitial: false,
|
|
3949
|
+
isFinal: false,
|
|
3950
|
+
},
|
|
3951
|
+
Blocked: {
|
|
3952
|
+
name: AXPSystemStatusType.Blocked,
|
|
3953
|
+
title: i18n('blocked.title'),
|
|
3954
|
+
icon: 'fa-light fa-ban',
|
|
3955
|
+
color: 'danger',
|
|
3956
|
+
description: i18n('blocked.description'),
|
|
3957
|
+
order: 4,
|
|
3958
|
+
isInitial: false,
|
|
3959
|
+
isFinal: false,
|
|
3960
|
+
},
|
|
3961
|
+
Done: {
|
|
3962
|
+
name: AXPSystemStatusType.Done,
|
|
3963
|
+
title: i18n('done.title'),
|
|
3964
|
+
icon: 'fa-light fa-check-circle',
|
|
3965
|
+
color: 'success',
|
|
3966
|
+
description: i18n('done.description'),
|
|
3967
|
+
order: 5,
|
|
3968
|
+
isInitial: false,
|
|
3969
|
+
isFinal: true,
|
|
3970
|
+
},
|
|
3971
|
+
Cancelled: {
|
|
3972
|
+
name: AXPSystemStatusType.Cancelled,
|
|
3973
|
+
title: i18n('cancelled.title'),
|
|
3974
|
+
icon: 'fa-light fa-times-circle',
|
|
3975
|
+
color: 'neutral',
|
|
3976
|
+
description: i18n('cancelled.description'),
|
|
3977
|
+
order: 6,
|
|
3978
|
+
isInitial: false,
|
|
3979
|
+
isFinal: true,
|
|
3980
|
+
},
|
|
3981
|
+
Draft: {
|
|
3982
|
+
name: AXPSystemStatusType.Draft,
|
|
3983
|
+
title: i18n('draft.title'),
|
|
3984
|
+
icon: 'fa-light fa-file-pen',
|
|
3985
|
+
color: 'neutral',
|
|
3986
|
+
description: i18n('draft.description'),
|
|
3987
|
+
order: 1,
|
|
3988
|
+
isInitial: true,
|
|
3989
|
+
isFinal: false,
|
|
3990
|
+
},
|
|
3991
|
+
PendingReview: {
|
|
3992
|
+
name: AXPSystemStatusType.PendingReview,
|
|
3993
|
+
title: i18n('pending-review.title'),
|
|
3994
|
+
icon: 'fa-light fa-clock',
|
|
3995
|
+
color: 'warning',
|
|
3996
|
+
description: i18n('pending-review.description'),
|
|
3997
|
+
order: 2,
|
|
3998
|
+
isInitial: false,
|
|
3999
|
+
isFinal: false,
|
|
4000
|
+
},
|
|
4001
|
+
Approved: {
|
|
4002
|
+
name: AXPSystemStatusType.Approved,
|
|
4003
|
+
title: i18n('approved.title'),
|
|
4004
|
+
icon: 'fa-light fa-check-circle',
|
|
4005
|
+
color: 'success',
|
|
4006
|
+
description: i18n('approved.description'),
|
|
4007
|
+
order: 3,
|
|
4008
|
+
isInitial: false,
|
|
4009
|
+
isFinal: false,
|
|
4010
|
+
},
|
|
4011
|
+
Published: {
|
|
4012
|
+
name: AXPSystemStatusType.Published,
|
|
4013
|
+
title: i18n('published.title'),
|
|
4014
|
+
icon: 'fa-light fa-globe',
|
|
4015
|
+
color: 'primary',
|
|
4016
|
+
description: i18n('published.description'),
|
|
4017
|
+
order: 4,
|
|
4018
|
+
isInitial: false,
|
|
4019
|
+
isFinal: false,
|
|
4020
|
+
},
|
|
4021
|
+
Archived: {
|
|
4022
|
+
name: AXPSystemStatusType.Archived,
|
|
4023
|
+
title: i18n('archived.title'),
|
|
4024
|
+
icon: 'fa-light fa-archive',
|
|
4025
|
+
color: 'danger',
|
|
4026
|
+
description: i18n('archived.description'),
|
|
4027
|
+
order: 5,
|
|
4028
|
+
isInitial: false,
|
|
4029
|
+
isFinal: true,
|
|
4030
|
+
},
|
|
4031
|
+
Rejected: {
|
|
4032
|
+
name: AXPSystemStatusType.Rejected,
|
|
4033
|
+
title: i18n('rejected.title'),
|
|
4034
|
+
icon: 'fa-light fa-times-circle',
|
|
4035
|
+
color: 'danger',
|
|
4036
|
+
description: i18n('rejected.description'),
|
|
4037
|
+
order: 6,
|
|
4038
|
+
isInitial: false,
|
|
4039
|
+
isFinal: true,
|
|
4040
|
+
},
|
|
4041
|
+
Active: {
|
|
4042
|
+
name: AXPSystemStatusType.Active,
|
|
4043
|
+
title: i18n('active.title'),
|
|
4044
|
+
icon: 'fa-light fa-check-circle',
|
|
4045
|
+
color: 'success',
|
|
4046
|
+
description: i18n('active.description'),
|
|
4047
|
+
order: 1,
|
|
4048
|
+
isInitial: false,
|
|
4049
|
+
isFinal: false,
|
|
4050
|
+
},
|
|
4051
|
+
Inactive: {
|
|
4052
|
+
name: AXPSystemStatusType.Inactive,
|
|
4053
|
+
title: i18n('inactive.title'),
|
|
4054
|
+
icon: 'fa-light fa-circle',
|
|
4055
|
+
color: 'secondary',
|
|
4056
|
+
description: i18n('inactive.description'),
|
|
4057
|
+
order: 2,
|
|
4058
|
+
isInitial: false,
|
|
4059
|
+
isFinal: false,
|
|
4060
|
+
},
|
|
4061
|
+
Pending: {
|
|
4062
|
+
name: AXPSystemStatusType.Pending,
|
|
4063
|
+
title: i18n('pending.title'),
|
|
4064
|
+
icon: 'fa-light fa-clock',
|
|
4065
|
+
color: 'warning',
|
|
4066
|
+
description: i18n('pending.description'),
|
|
4067
|
+
order: 1,
|
|
4068
|
+
isInitial: true,
|
|
4069
|
+
isFinal: false,
|
|
4070
|
+
},
|
|
4071
|
+
Completed: {
|
|
4072
|
+
name: AXPSystemStatusType.Completed,
|
|
4073
|
+
title: i18n('completed.title'),
|
|
4074
|
+
icon: 'fa-light fa-check-circle',
|
|
4075
|
+
color: 'success',
|
|
4076
|
+
description: i18n('completed.description'),
|
|
4077
|
+
order: 10,
|
|
4078
|
+
isInitial: false,
|
|
4079
|
+
isFinal: true,
|
|
4080
|
+
},
|
|
4081
|
+
Suspended: {
|
|
4082
|
+
name: AXPSystemStatusType.Suspended,
|
|
4083
|
+
title: i18n('suspended.title'),
|
|
4084
|
+
icon: 'fa-light fa-pause-circle',
|
|
4085
|
+
color: 'neutral',
|
|
4086
|
+
description: i18n('suspended.description'),
|
|
4087
|
+
order: 5,
|
|
4088
|
+
isInitial: false,
|
|
4089
|
+
isFinal: false,
|
|
4090
|
+
},
|
|
4091
|
+
Failed: {
|
|
4092
|
+
name: AXPSystemStatusType.Failed,
|
|
4093
|
+
title: i18n('failed.title'),
|
|
4094
|
+
icon: 'fa-light fa-xmark-circle',
|
|
4095
|
+
color: 'danger',
|
|
4096
|
+
description: i18n('failed.description'),
|
|
4097
|
+
order: 10,
|
|
4098
|
+
isInitial: false,
|
|
4099
|
+
isFinal: true,
|
|
4100
|
+
},
|
|
4101
|
+
Review: {
|
|
4102
|
+
name: AXPSystemStatusType.Review,
|
|
4103
|
+
title: i18n('review.title'),
|
|
4104
|
+
icon: 'fa-light fa-eye',
|
|
4105
|
+
color: 'info',
|
|
4106
|
+
description: i18n('review.description'),
|
|
4107
|
+
order: 3,
|
|
4108
|
+
},
|
|
4109
|
+
Expired: {
|
|
4110
|
+
name: AXPSystemStatusType.Expired,
|
|
4111
|
+
title: i18n('expired.title'),
|
|
4112
|
+
icon: 'fa-light fa-clock',
|
|
4113
|
+
color: 'danger',
|
|
4114
|
+
description: i18n('expired.description'),
|
|
4115
|
+
order: 10,
|
|
4116
|
+
isInitial: false,
|
|
4117
|
+
isFinal: true,
|
|
4118
|
+
},
|
|
4119
|
+
});
|
|
4120
|
+
/**
|
|
4121
|
+
* Get system status definition by type
|
|
4122
|
+
* @param type - Status type
|
|
4123
|
+
* @returns System status definition or undefined
|
|
4124
|
+
*/
|
|
4125
|
+
function getSystemStatus(type) {
|
|
4126
|
+
return Object.values(AXPSystemStatuses).find(status => status.name === type);
|
|
4127
|
+
}
|
|
4128
|
+
/**
|
|
4129
|
+
* Resolves the visual appearance (color and icon) for status
|
|
4130
|
+
* using the system statuses from the core module.
|
|
4131
|
+
* @param statusType - Status type string
|
|
4132
|
+
* @returns Color and icon for the status
|
|
4133
|
+
*/
|
|
4134
|
+
function resolveStatusLook(statusType) {
|
|
4135
|
+
// Try to get system status first
|
|
4136
|
+
const systemStatusType = statusType;
|
|
4137
|
+
if (systemStatusType) {
|
|
4138
|
+
const systemStatus = getSystemStatus(systemStatusType);
|
|
4139
|
+
if (systemStatus) {
|
|
4140
|
+
// Convert hex color to semantic color type if needed
|
|
4141
|
+
// For now, return as-is and let the UI handle it
|
|
4142
|
+
return {
|
|
4143
|
+
color: systemStatus.color,
|
|
4144
|
+
icon: systemStatus.icon || ''
|
|
4145
|
+
};
|
|
4146
|
+
}
|
|
4147
|
+
}
|
|
4148
|
+
// Fallback for unknown statuses
|
|
4149
|
+
return {
|
|
4150
|
+
color: 'secondary',
|
|
4151
|
+
icon: ''
|
|
4152
|
+
};
|
|
4153
|
+
}
|
|
4154
|
+
/**
|
|
4155
|
+
* Resolves the complete status information (title, description, icon, color) for a status
|
|
4156
|
+
* using the system statuses from the core module.
|
|
4157
|
+
* @param statusType - Status type string
|
|
4158
|
+
* @returns Complete status information as AXPStatusDefinition
|
|
4159
|
+
*/
|
|
4160
|
+
function getStatusInfo(statusType) {
|
|
4161
|
+
const systemStatusType = statusType;
|
|
4162
|
+
if (systemStatusType) {
|
|
4163
|
+
const systemStatus = getSystemStatus(systemStatusType);
|
|
4164
|
+
if (systemStatus) {
|
|
4165
|
+
return systemStatus;
|
|
4166
|
+
}
|
|
4167
|
+
}
|
|
4168
|
+
// Fallback for unknown statuses
|
|
4169
|
+
return {
|
|
4170
|
+
name: statusType,
|
|
4171
|
+
title: statusType,
|
|
4172
|
+
description: `Status: ${statusType}`,
|
|
4173
|
+
icon: '',
|
|
4174
|
+
color: 'secondary'
|
|
4175
|
+
};
|
|
4176
|
+
}
|
|
4177
|
+
/**
|
|
4178
|
+
* Get system status definition directly (no conversion needed since it's already AXPStatusDefinition)
|
|
4179
|
+
* @param statusType - Status type
|
|
4180
|
+
* @param overrides - Optional overrides for the status definition
|
|
4181
|
+
* @returns AXPStatusDefinition
|
|
4182
|
+
*/
|
|
4183
|
+
function systemStatusToDefinition(statusType, overrides) {
|
|
4184
|
+
const systemStatus = getSystemStatus(statusType);
|
|
4185
|
+
if (!systemStatus) {
|
|
4186
|
+
throw new Error(`System status ${statusType} not found`);
|
|
4187
|
+
}
|
|
4188
|
+
return {
|
|
4189
|
+
...systemStatus,
|
|
4190
|
+
...overrides,
|
|
4191
|
+
};
|
|
4192
|
+
}
|
|
4193
|
+
|
|
4194
|
+
class AXPClipBoardService {
|
|
4195
|
+
constructor() {
|
|
4196
|
+
this.toast = inject(AXToastService);
|
|
4197
|
+
}
|
|
4198
|
+
copy(title, value) {
|
|
4199
|
+
const copyText = document.createElement('input');
|
|
4200
|
+
copyText.type = 'text';
|
|
4201
|
+
copyText.select();
|
|
4202
|
+
copyText.setSelectionRange(0, 99999); // For mobile devices
|
|
4203
|
+
copyText.remove();
|
|
4204
|
+
navigator.clipboard.writeText(value);
|
|
4205
|
+
this.toast.success(`${title} copied!`);
|
|
4206
|
+
}
|
|
4207
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPClipBoardService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
4208
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPClipBoardService, providedIn: 'root' }); }
|
|
4209
|
+
}
|
|
4210
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPClipBoardService, decorators: [{
|
|
4211
|
+
type: Injectable,
|
|
4212
|
+
args: [{
|
|
4213
|
+
providedIn: 'root',
|
|
4214
|
+
}]
|
|
4215
|
+
}] });
|
|
4216
|
+
|
|
4217
|
+
//#region ---- Imports ----
|
|
4218
|
+
//#endregion
|
|
4219
|
+
//#region ---- Debug Service ----
|
|
4220
|
+
/**
|
|
4221
|
+
* Service for debugging user access information.
|
|
4222
|
+
* Displays loaded modules, features, and permissions for the current user.
|
|
4223
|
+
*/
|
|
4224
|
+
class AXPDebugService {
|
|
4225
|
+
constructor() {
|
|
4226
|
+
//#region ---- Services & Dependencies ----
|
|
4227
|
+
this.sessionService = inject(AXPSessionService);
|
|
4228
|
+
this.manifestRegistry = inject(AXPModuleManifestRegistry);
|
|
4229
|
+
}
|
|
4230
|
+
//#endregion
|
|
4231
|
+
//#region ---- Public Methods ----
|
|
4232
|
+
/**
|
|
4233
|
+
* Display all loaded modules, features, and permissions for the current user.
|
|
4234
|
+
*/
|
|
4235
|
+
async displayUserAccessInfo() {
|
|
4236
|
+
try {
|
|
4237
|
+
// Ensure registry is initialized
|
|
4238
|
+
await this.manifestRegistry.initialize();
|
|
4239
|
+
// Get all registered modules
|
|
4240
|
+
const allManifests = this.manifestRegistry.getAll();
|
|
4241
|
+
// Get all feature definitions
|
|
4242
|
+
const allFeatures = this.manifestRegistry.getAllFeatureDefinitions();
|
|
4243
|
+
// Get current user permissions
|
|
4244
|
+
const userPermissions = this.sessionService.permissions;
|
|
4245
|
+
const user = this.sessionService.user;
|
|
4246
|
+
console.group('🔐 User Access Information');
|
|
4247
|
+
console.log(`User: ${user?.name || user?.title || 'Unknown'} (${user?.id || 'N/A'})`);
|
|
4248
|
+
console.log('');
|
|
4249
|
+
if (allManifests.length === 0) {
|
|
4250
|
+
console.log('No modules loaded');
|
|
4251
|
+
console.groupEnd();
|
|
4252
|
+
return;
|
|
4253
|
+
}
|
|
4254
|
+
// Group features by module
|
|
4255
|
+
const featuresByModule = new Map();
|
|
4256
|
+
allFeatures.forEach((feature) => {
|
|
4257
|
+
const moduleName = feature.module;
|
|
4258
|
+
if (!featuresByModule.has(moduleName)) {
|
|
4259
|
+
featuresByModule.set(moduleName, []);
|
|
4260
|
+
}
|
|
4261
|
+
featuresByModule.get(moduleName).push(feature);
|
|
4262
|
+
});
|
|
4263
|
+
// Group permissions by module (if they follow ModuleName:PermissionKey pattern)
|
|
4264
|
+
const permissionsByModule = new Map();
|
|
4265
|
+
const ungroupedPermissions = [];
|
|
4266
|
+
userPermissions.forEach((permission) => {
|
|
4267
|
+
const parts = permission.split(':');
|
|
4268
|
+
if (parts.length >= 2) {
|
|
4269
|
+
const moduleName = parts[0];
|
|
4270
|
+
if (!permissionsByModule.has(moduleName)) {
|
|
4271
|
+
permissionsByModule.set(moduleName, []);
|
|
4272
|
+
}
|
|
4273
|
+
permissionsByModule.get(moduleName).push(permission);
|
|
4274
|
+
}
|
|
4275
|
+
else {
|
|
4276
|
+
ungroupedPermissions.push(permission);
|
|
4277
|
+
}
|
|
4278
|
+
});
|
|
4279
|
+
// Display modules in tree format
|
|
4280
|
+
allManifests.forEach((manifest) => {
|
|
4281
|
+
const moduleName = manifest.name;
|
|
4282
|
+
const moduleFeatures = featuresByModule.get(moduleName) || [];
|
|
4283
|
+
const modulePermissions = permissionsByModule.get(moduleName) || [];
|
|
4284
|
+
console.group(`📦 ${moduleName}`);
|
|
4285
|
+
// Display Features
|
|
4286
|
+
if (moduleFeatures.length > 0) {
|
|
4287
|
+
console.log(' --- Features:');
|
|
4288
|
+
moduleFeatures.forEach((feature) => {
|
|
4289
|
+
console.log(` • ${feature.name}`);
|
|
4290
|
+
});
|
|
4291
|
+
}
|
|
4292
|
+
else {
|
|
4293
|
+
console.log(' --- Features: (none)');
|
|
4294
|
+
}
|
|
4295
|
+
// Display Permissions
|
|
4296
|
+
if (modulePermissions.length > 0) {
|
|
4297
|
+
console.log(' --- Permissions:');
|
|
4298
|
+
modulePermissions.forEach((permission) => {
|
|
4299
|
+
console.log(` • ${permission}`);
|
|
4300
|
+
});
|
|
4301
|
+
}
|
|
4302
|
+
else {
|
|
4303
|
+
console.log(' --- Permissions: (none)');
|
|
4304
|
+
}
|
|
4305
|
+
console.groupEnd();
|
|
4306
|
+
});
|
|
4307
|
+
// Display ungrouped permissions if any
|
|
4308
|
+
if (ungroupedPermissions.length > 0) {
|
|
4309
|
+
console.group('📁 Other Permissions');
|
|
4310
|
+
ungroupedPermissions.forEach((permission) => {
|
|
4311
|
+
console.log(` • ${permission}`);
|
|
4312
|
+
});
|
|
4313
|
+
console.groupEnd();
|
|
4314
|
+
}
|
|
4315
|
+
// Summary
|
|
4316
|
+
console.log('');
|
|
4317
|
+
console.log(`Summary: ${allManifests.length} modules, ${allFeatures.length} features, ${userPermissions.length} permissions`);
|
|
4318
|
+
console.groupEnd();
|
|
4319
|
+
}
|
|
4320
|
+
catch (error) {
|
|
4321
|
+
console.error('[AXPDebugService] Failed to display user access info:', error);
|
|
4322
|
+
}
|
|
4323
|
+
}
|
|
4324
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPDebugService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
4325
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPDebugService, providedIn: 'root' }); }
|
|
4326
|
+
}
|
|
4327
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPDebugService, decorators: [{
|
|
4328
|
+
type: Injectable,
|
|
4329
|
+
args: [{
|
|
4330
|
+
providedIn: 'root',
|
|
4331
|
+
}]
|
|
4332
|
+
}] });
|
|
4333
|
+
|
|
4334
|
+
class AXMOrgChartPrintPage extends AXBasePageComponent {
|
|
4335
|
+
constructor() {
|
|
4336
|
+
super(...arguments);
|
|
4337
|
+
this.ratio = signal(false, ...(ngDevMode ? [{ debugName: "ratio" }] : /* istanbul ignore next */ []));
|
|
4338
|
+
this.baseWidth = signal(undefined, ...(ngDevMode ? [{ debugName: "baseWidth" }] : /* istanbul ignore next */ []));
|
|
4339
|
+
this.baseHeight = signal(undefined, ...(ngDevMode ? [{ debugName: "baseHeight" }] : /* istanbul ignore next */ []));
|
|
4340
|
+
this.bgcolor = model(...(ngDevMode ? [undefined, { debugName: "bgcolor" }] : /* istanbul ignore next */ []));
|
|
4341
|
+
this.width = model(...(ngDevMode ? [undefined, { debugName: "width" }] : /* istanbul ignore next */ []));
|
|
4342
|
+
this.height = model(...(ngDevMode ? [undefined, { debugName: "height" }] : /* istanbul ignore next */ []));
|
|
4343
|
+
this.quality = model(...(ngDevMode ? [undefined, { debugName: "quality" }] : /* istanbul ignore next */ []));
|
|
4344
|
+
this.qualityComputed = linkedSignal(() => (this.data.quality ?? 1) * 100, ...(ngDevMode ? [{ debugName: "qualityComputed" }] : /* istanbul ignore next */ []));
|
|
4345
|
+
this.scale = model(...(ngDevMode ? [undefined, { debugName: "scale" }] : /* istanbul ignore next */ []));
|
|
4346
|
+
this.#init = afterNextRender(() => {
|
|
4347
|
+
this.bgcolor.set(this.data.bgcolor);
|
|
4348
|
+
this.width.set(this.data.width);
|
|
4349
|
+
this.baseWidth.set(this.data.width);
|
|
4350
|
+
this.height.set(this.data.height);
|
|
4351
|
+
this.baseHeight.set(this.data.height);
|
|
4352
|
+
this.quality.set(this.data.quality);
|
|
4353
|
+
this.scale.set(this.data.scale);
|
|
4354
|
+
});
|
|
4355
|
+
}
|
|
4356
|
+
#init;
|
|
4357
|
+
calculateRatio(event, type) {
|
|
4358
|
+
if (!this.ratio() || !event.isUserInteraction)
|
|
4359
|
+
return;
|
|
4360
|
+
const value = event.value;
|
|
4361
|
+
if (type === 'width') {
|
|
4362
|
+
const newHeight = Math.round((value / this.baseWidth()) * this.baseHeight() * 100) / 100;
|
|
4363
|
+
this.height.set(newHeight);
|
|
4364
|
+
this.width.set(value);
|
|
4365
|
+
}
|
|
4366
|
+
else {
|
|
4367
|
+
const newWidth = Math.round((value / this.baseHeight()) * this.baseWidth() * 100) / 100;
|
|
4368
|
+
this.width.set(newWidth);
|
|
4369
|
+
this.height.set(value);
|
|
4370
|
+
}
|
|
4371
|
+
}
|
|
4372
|
+
async handleClose(isCanceled = false) {
|
|
4373
|
+
this.close({
|
|
4374
|
+
bgcolor: this.bgcolor(),
|
|
4375
|
+
width: this.width(),
|
|
4376
|
+
height: this.height(),
|
|
4377
|
+
quality: this.qualityComputed() / 100,
|
|
4378
|
+
scale: this.scale(),
|
|
4379
|
+
isCanceled,
|
|
4380
|
+
});
|
|
4381
|
+
}
|
|
4382
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXMOrgChartPrintPage, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
4383
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.9", type: AXMOrgChartPrintPage, isStandalone: true, selector: "ng-component", inputs: { bgcolor: { classPropertyName: "bgcolor", publicName: "bgcolor", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, quality: { classPropertyName: "quality", publicName: "quality", isSignal: true, isRequired: false, transformFunction: null }, scale: { classPropertyName: "scale", publicName: "scale", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { bgcolor: "bgcolorChange", width: "widthChange", height: "heightChange", quality: "qualityChange", scale: "scaleChange" }, usesInheritance: true, ngImport: i0, template: ` <div class="ax-grid ax-grid-cols-[auto_18rem] ax-gap-4 ax-justify-center ax-p-4 ax-items-center">
|
|
4384
|
+
<ax-label>Resolution (px)</ax-label>
|
|
4385
|
+
<div class="ax-flex ax-gap-2 ax-items-center">
|
|
4386
|
+
<ax-number-box
|
|
4387
|
+
[changeOnScroll]="true"
|
|
4388
|
+
[decimals]="4"
|
|
4389
|
+
[value]="width()"
|
|
4390
|
+
(onValueChanged)="calculateRatio($event, 'width')"
|
|
4391
|
+
name="Width"
|
|
4392
|
+
>
|
|
4393
|
+
</ax-number-box>
|
|
4394
|
+
<ax-button [toggleable]="true" [(selected)]="ratio"
|
|
4395
|
+
><ax-icon><i class="fa-solid fa-vector-square"></i></ax-icon
|
|
4396
|
+
></ax-button>
|
|
4397
|
+
<ax-number-box
|
|
4398
|
+
[changeOnScroll]="true"
|
|
4399
|
+
[decimals]="4"
|
|
4400
|
+
[value]="height()"
|
|
4401
|
+
(onValueChanged)="calculateRatio($event, 'height')"
|
|
4402
|
+
name="Height"
|
|
4403
|
+
>
|
|
4404
|
+
</ax-number-box>
|
|
4405
|
+
</div>
|
|
4406
|
+
|
|
4407
|
+
<ax-label>Background color</ax-label>
|
|
4408
|
+
<ax-color-box [(ngModel)]="bgcolor"></ax-color-box>
|
|
4409
|
+
|
|
4410
|
+
<ax-label>Quality of image (50-100%)</ax-label>
|
|
4411
|
+
<ax-number-box
|
|
4412
|
+
[changeOnScroll]="true"
|
|
4413
|
+
[minValue]="50"
|
|
4414
|
+
[maxValue]="100"
|
|
4415
|
+
[(ngModel)]="qualityComputed"
|
|
4416
|
+
name="Quality"
|
|
4417
|
+
>
|
|
4418
|
+
</ax-number-box>
|
|
4419
|
+
|
|
4420
|
+
<ax-label>Scale</ax-label>
|
|
4421
|
+
<ax-number-box [changeOnScroll]="true" [minValue]="1" [(ngModel)]="scale" name="scale"> </ax-number-box>
|
|
4422
|
+
</div>
|
|
4423
|
+
|
|
4424
|
+
<ax-footer>
|
|
4425
|
+
<ax-prefix> </ax-prefix>
|
|
4426
|
+
<ax-suffix>
|
|
4427
|
+
<ax-button text="Cancel" look="solid" (onClick)="handleClose(true)"> </ax-button>
|
|
4428
|
+
<ax-button text="Print" look="solid" color="primary" (onClick)="handleClose()">
|
|
4429
|
+
<ax-icon class="fa-solid fa-print"> </ax-icon>
|
|
4430
|
+
</ax-button>
|
|
4431
|
+
</ax-suffix>
|
|
4432
|
+
</ax-footer>`, isInline: true, dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: AXCheckBoxModule }, { kind: "ngmodule", type: AXNumberBoxModule }, { kind: "component", type: i2.AXNumberBoxComponent, selector: "ax-number-box", inputs: ["disabled", "readonly", "tabIndex", "placeholder", "value", "state", "name", "id", "look", "minValue", "maxValue", "showSpinButtons", "thousandsSeparator", "decimals", "changeOnScroll", "step"], outputs: ["valueChange", "stateChange", "onValueChanged", "onBlur", "onFocus", "readonlyChange", "disabledChange", "onKeyDown", "onKeyUp", "onKeyPress", "thousandsSeparatorChange"] }, { kind: "ngmodule", type: AXTextBoxModule }, { kind: "ngmodule", type: AXLabelModule }, { kind: "component", type: i3.AXLabelComponent, selector: "ax-label", inputs: ["required", "for"], outputs: ["requiredChange"] }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i4.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "component", type: i4.AXDecoratorGenericComponent, selector: "ax-footer, ax-header, ax-content, ax-divider, ax-form-hint, ax-prefix, ax-suffix, ax-text, ax-title, ax-subtitle, ax-placeholder, ax-overlay" }, { kind: "ngmodule", type: AXButtonModule }, { kind: "component", type: i5.AXButtonComponent, selector: "ax-button", inputs: ["disabled", "size", "tabIndex", "color", "look", "text", "toggleable", "selected", "iconOnly", "type", "loadingText"], outputs: ["onBlur", "onFocus", "onClick", "selectedChange", "toggleableChange", "lookChange", "colorChange", "disabledChange", "loadingTextChange"] }, { kind: "ngmodule", type: AXColorBoxModule }, { kind: "component", type: i6.AXColorBoxComponent, selector: "ax-color-box", inputs: ["disabled", "readonly", "tabIndex", "placeholder", "value", "state", "name", "id", "look", "showBadge", "showValue", "showClearButton", "showIcon"], outputs: ["valueChange", "stateChange", "onValueChanged", "onBlur", "onFocus", "readonlyChange", "disabledChange", "onOpened", "onClosed"] }] }); }
|
|
4433
|
+
}
|
|
4434
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXMOrgChartPrintPage, decorators: [{
|
|
4435
|
+
type: Component,
|
|
4436
|
+
args: [{
|
|
4437
|
+
template: ` <div class="ax-grid ax-grid-cols-[auto_18rem] ax-gap-4 ax-justify-center ax-p-4 ax-items-center">
|
|
4438
|
+
<ax-label>Resolution (px)</ax-label>
|
|
4439
|
+
<div class="ax-flex ax-gap-2 ax-items-center">
|
|
4440
|
+
<ax-number-box
|
|
4441
|
+
[changeOnScroll]="true"
|
|
4442
|
+
[decimals]="4"
|
|
4443
|
+
[value]="width()"
|
|
4444
|
+
(onValueChanged)="calculateRatio($event, 'width')"
|
|
4445
|
+
name="Width"
|
|
4446
|
+
>
|
|
4447
|
+
</ax-number-box>
|
|
4448
|
+
<ax-button [toggleable]="true" [(selected)]="ratio"
|
|
4449
|
+
><ax-icon><i class="fa-solid fa-vector-square"></i></ax-icon
|
|
4450
|
+
></ax-button>
|
|
4451
|
+
<ax-number-box
|
|
4452
|
+
[changeOnScroll]="true"
|
|
4453
|
+
[decimals]="4"
|
|
4454
|
+
[value]="height()"
|
|
4455
|
+
(onValueChanged)="calculateRatio($event, 'height')"
|
|
4456
|
+
name="Height"
|
|
4457
|
+
>
|
|
4458
|
+
</ax-number-box>
|
|
4459
|
+
</div>
|
|
4460
|
+
|
|
4461
|
+
<ax-label>Background color</ax-label>
|
|
4462
|
+
<ax-color-box [(ngModel)]="bgcolor"></ax-color-box>
|
|
4463
|
+
|
|
4464
|
+
<ax-label>Quality of image (50-100%)</ax-label>
|
|
4465
|
+
<ax-number-box
|
|
4466
|
+
[changeOnScroll]="true"
|
|
4467
|
+
[minValue]="50"
|
|
4468
|
+
[maxValue]="100"
|
|
4469
|
+
[(ngModel)]="qualityComputed"
|
|
4470
|
+
name="Quality"
|
|
4471
|
+
>
|
|
4472
|
+
</ax-number-box>
|
|
4473
|
+
|
|
4474
|
+
<ax-label>Scale</ax-label>
|
|
4475
|
+
<ax-number-box [changeOnScroll]="true" [minValue]="1" [(ngModel)]="scale" name="scale"> </ax-number-box>
|
|
4476
|
+
</div>
|
|
4477
|
+
|
|
4478
|
+
<ax-footer>
|
|
4479
|
+
<ax-prefix> </ax-prefix>
|
|
4480
|
+
<ax-suffix>
|
|
4481
|
+
<ax-button text="Cancel" look="solid" (onClick)="handleClose(true)"> </ax-button>
|
|
4482
|
+
<ax-button text="Print" look="solid" color="primary" (onClick)="handleClose()">
|
|
4483
|
+
<ax-icon class="fa-solid fa-print"> </ax-icon>
|
|
4484
|
+
</ax-button>
|
|
4485
|
+
</ax-suffix>
|
|
4486
|
+
</ax-footer>`,
|
|
4487
|
+
imports: [
|
|
4488
|
+
FormsModule,
|
|
4489
|
+
AXCheckBoxModule,
|
|
4490
|
+
AXNumberBoxModule,
|
|
4491
|
+
AXTextBoxModule,
|
|
4492
|
+
AXLabelModule,
|
|
4493
|
+
AXDecoratorModule,
|
|
4494
|
+
AXButtonModule,
|
|
4495
|
+
AXColorBoxModule,
|
|
4496
|
+
],
|
|
4497
|
+
}]
|
|
4498
|
+
}], propDecorators: { bgcolor: [{ type: i0.Input, args: [{ isSignal: true, alias: "bgcolor", required: false }] }, { type: i0.Output, args: ["bgcolorChange"] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }, { type: i0.Output, args: ["widthChange"] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }, { type: i0.Output, args: ["heightChange"] }], quality: [{ type: i0.Input, args: [{ isSignal: true, alias: "quality", required: false }] }, { type: i0.Output, args: ["qualityChange"] }], scale: [{ type: i0.Input, args: [{ isSignal: true, alias: "scale", required: false }] }, { type: i0.Output, args: ["scaleChange"] }] } });
|
|
4499
|
+
|
|
4500
|
+
// AXPPdfService
|
|
4501
|
+
class AXPExportService {
|
|
4502
|
+
constructor() {
|
|
4503
|
+
this.popupService = inject(AXPopupService);
|
|
4504
|
+
}
|
|
4505
|
+
async generateBlobFromElement(element, blobOptions) {
|
|
4506
|
+
const domtoimage = (await import('dom-to-image')).default;
|
|
4507
|
+
const originalWidth = element.scrollWidth;
|
|
4508
|
+
const originalHeight = element.scrollHeight;
|
|
4509
|
+
let popup, options = {}, newOptions = {};
|
|
4510
|
+
if (blobOptions) {
|
|
4511
|
+
({ popup, ...options } = blobOptions);
|
|
4512
|
+
}
|
|
4513
|
+
if (popup) {
|
|
4514
|
+
const result = await this.popupService.open(AXMOrgChartPrintPage, {
|
|
4515
|
+
header: true,
|
|
4516
|
+
size: 'fit',
|
|
4517
|
+
draggable: true,
|
|
4518
|
+
hasBackdrop: true,
|
|
4519
|
+
title: 'Export Options',
|
|
4520
|
+
data: { data: options },
|
|
4521
|
+
});
|
|
4522
|
+
if (result.data.isCanceled) {
|
|
4523
|
+
throw new Error('Process is Canceled by User!');
|
|
4524
|
+
}
|
|
4525
|
+
else {
|
|
4526
|
+
newOptions = { ...options, ...result.data };
|
|
4527
|
+
}
|
|
4528
|
+
}
|
|
4529
|
+
return domtoimage.toBlob(element, {
|
|
4530
|
+
...newOptions,
|
|
4531
|
+
width: (newOptions.width ?? originalWidth) * (newOptions?.scale ?? 1),
|
|
4532
|
+
height: (newOptions.height ?? originalHeight) * (newOptions?.scale ?? 1),
|
|
4533
|
+
style: {
|
|
4534
|
+
transform: `scale(${newOptions?.scale ?? 1})`,
|
|
4535
|
+
transformOrigin: 'top left',
|
|
4536
|
+
},
|
|
4537
|
+
});
|
|
4538
|
+
}
|
|
4539
|
+
download(blob, filename) {
|
|
4540
|
+
const link = document.createElement('a');
|
|
4541
|
+
link.href = URL.createObjectURL(blob);
|
|
4542
|
+
link.download = filename;
|
|
4543
|
+
document.body.appendChild(link);
|
|
4544
|
+
link.click();
|
|
4545
|
+
document.body.removeChild(link);
|
|
4546
|
+
URL.revokeObjectURL(link.href);
|
|
4547
|
+
}
|
|
4548
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPExportService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
4549
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPExportService, providedIn: 'root' }); }
|
|
4550
|
+
}
|
|
4551
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPExportService, decorators: [{
|
|
4552
|
+
type: Injectable,
|
|
4553
|
+
args: [{
|
|
4554
|
+
providedIn: 'root',
|
|
4555
|
+
}]
|
|
4556
|
+
}] });
|
|
4557
|
+
|
|
4558
|
+
function AXPCleanNestedFilters(filters) {
|
|
4559
|
+
return filters
|
|
4560
|
+
.map((filter) => {
|
|
4561
|
+
if (!filter)
|
|
4562
|
+
return undefined;
|
|
4563
|
+
if (filter.filters?.length) {
|
|
4564
|
+
const cleanedNestedFilters = AXPCleanNestedFilters(filter.filters);
|
|
4565
|
+
if (!filter.field && !filter.operator && cleanedNestedFilters.length === 0) {
|
|
4566
|
+
return undefined;
|
|
4567
|
+
}
|
|
4568
|
+
return {
|
|
4569
|
+
...filter,
|
|
4570
|
+
filters: cleanedNestedFilters.filter(Boolean),
|
|
4571
|
+
};
|
|
4572
|
+
}
|
|
4573
|
+
if ((!filter.value && filter.value != false) || filter.value === 'unknown') {
|
|
4574
|
+
return undefined;
|
|
4575
|
+
}
|
|
4576
|
+
return filter;
|
|
4577
|
+
})
|
|
4578
|
+
.filter(Boolean);
|
|
4579
|
+
}
|
|
4580
|
+
|
|
4581
|
+
const AXP_LOCALE_MANAGEMENT_PORT = new InjectionToken('AXP_LOCALE_MANAGEMENT_PORT');
|
|
4582
|
+
|
|
4583
|
+
//#region ---- Versioning Types ----
|
|
4584
|
+
var AXVChangeType;
|
|
4585
|
+
(function (AXVChangeType) {
|
|
4586
|
+
AXVChangeType["Create"] = "Create";
|
|
4587
|
+
AXVChangeType["Update"] = "Update";
|
|
4588
|
+
AXVChangeType["Delete"] = "Delete";
|
|
4589
|
+
})(AXVChangeType || (AXVChangeType = {}));
|
|
4590
|
+
//#endregion
|
|
4591
|
+
//#region ---- Service Contract ----
|
|
4592
|
+
class AXPVersioningService {
|
|
4593
|
+
}
|
|
4594
|
+
//#endregion
|
|
4595
|
+
|
|
4596
|
+
/**
|
|
4597
|
+
* Generated bundle index. Do not edit.
|
|
4598
|
+
*/
|
|
4599
|
+
|
|
4600
|
+
export { ALL_DEFAULT_OPERATORS, AXMWorkflowErrorHandler, AXPAppVersionService, AXPCleanNestedFilters, AXPClipBoardService, AXPCommonModule, AXPCommonSettings, AXPCustomOperatorService, AXPCustomOperatorServiceImpl, AXPDataProvider, AXPDebugService, AXPDefaultMultiLanguageConfigService, AXPDialogConfirmAction, AXPEntityCommandScope, AXPEntityQueryType, AXPEntityType, AXPErrorHandlerRegistryService, AXPExportService, AXPFileActionsService, AXPFileStorageService, AXPFileStorageStatus, AXPFileTypeProviderService, AXPFilterOperatorMiddlewareService, AXPFilterOperatorMiddlewareServiceImpl, AXPFooterTextSlotComponent, AXPGlobalErrorHandler, AXPHomePageModule, AXPHomePageService, AXPLockService, AXPMenuItemsDataSourceDefinition, AXPMenuMiddlewareRegistry, AXPMenuProviderService, AXPMenuSearchDefinitionProvider, AXPMenuSearchProvider, AXPMenuService, AXPMenuVisibilityService, AXPNavBarSlotComponent, AXPNavigateWorkflow, AXPPlatformDefaultConfigs, AXPRedirectEvent, AXPRefreshEvent, AXPRegionalSetting, AXPRelationshipCardinality, AXPRelationshipKind, AXPReloadAction, AXPReloadEvent, AXPSearchCommandProvider, AXPSearchDefinitionActionBuilder, AXPSearchDefinitionBuilder, AXPSearchDefinitionProviderContext, AXPSearchDefinitionProviderService, AXPSearchService, AXPSettingDefaultValuesAggregatorService, AXPSettingDefinitionGroupBuilder, AXPSettingDefinitionProviderContext, AXPSettingDefinitionProviderService, AXPSettingDefinitionSectionBuilder, AXPSettingsEvaluatorScopeProvider, AXPSettingsService, AXPStatusDefinitionProviderService, AXPStatusProvider, AXPStickyDirective, AXPSystemStatusType, AXPSystemStatuses, AXPToastAction, AXPTokenDefinitionService, AXPTokenEvaluatorScopeProvider, AXPVersioningService, AXPWorkflowNavigateAction, AXPWorkflowRouterNavigateAction, AXP_APP_VERSION_PROVIDER, AXP_FILE_ACTION_PROVIDER, AXP_FILE_TYPE_INFO_PROVIDER, AXP_HOME_PAGES, AXP_HOME_PAGE_DEFAULT_KEY, AXP_LOCALE_MANAGEMENT_PORT, AXP_MENU_MIDDLEWARE, AXP_MENU_PROVIDER, AXP_PLATFORM_CONFIG_TOKEN, AXP_ROOT_CONFIG_TOKEN, AXP_SEARCH_DEFINITION_PROVIDER, AXP_SEARCH_PROVIDER, AXP_SETTING_DEFAULT_VALUES_PROVIDERS, AXP_SETTING_DEFINITION_PROVIDER, AXP_SETTING_VALUE_PROVIDER, AXP_STATUS_PROVIDERS, AXP_TOKEN_DEFINITION_PROVIDER, AXVChangeType, BETWEEN_OPER, BOOLEAN_OPERATORS, CONTAINS_OPER, DATE_OPERATORS, DEFAULT_MULTILANGUAGE_FIELD_NAMES, ENDS_WITH_OPER, ENVIRONMENT, EQ_OPER, GTE_OPER, GT_OPER, IN_OPER, IS_EMPTY_OPER, IS_NOT_EMPTY_OPER, LTE_OPER, LT_OPER, MENU_ITEMS_DATASOURCE_NAME, MULTILANGUAGE_CAPABLE_WIDGET_TYPES, NOT_CONTAINS_OPER, NOT_EQ_OPER, NUMBER_OPERATORS, STARTS_WITH_OPER, STRING_OPERATORS, UploadFromComputerActionProvider, configPlatform, createAllQueryView, createEntityCommandOptions, createEntitySearchCommand, createMenuContext, createMenuMiddleware, createQueryView, getEntityInfo, getStatusInfo, getSystemStatus, provideDynamicHomePage, provideMenuMiddleware, resolveStatusLook, shouldApplyDefaultMultiLanguageToEntityProperty, shouldApplyDefaultMultiLanguageToWidgetNode, shouldApplyDefaultMultiLanguageToWidgetProperty, systemStatusToDefinition, withDefaultMultiLanguageOnWidgetNodeTree, withDefaultMultiLanguageOnWidgetProperty };
|
|
4601
|
+
//# sourceMappingURL=acorex-platform-common.mjs.map
|